Compare commits

..

2249 Commits

Author SHA1 Message Date
NewSoupVi
f3d7f247d1 Codeowners: Actually link the correct person for Yu Gi Oh 2024-05-24 00:18:31 +02:00
Doug Hoskisson
d09b214309 Core: Utils.py typing (#3064)
* Core: Utils.py typing

`get_fuzzy_results` typing

There are places that this is called with a `word_list` that is not a `Sequence`, and it is valid (e.g., `set` or `dict`).

To decide the right type, we look at how `word_list` is used:
- the parameter to `len` - requires `__len__`
- the 2nd parameter to `map` - requires `__iter__`

Then we look at https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes and ask what is the simplest type that includes both `__len__` and `__iter__`: `Collection`

(Python 3.8 requires using the alias in `typing`, instead of `collections.abc`)

* a bit more typing and cleaning

* fine, take away my fun for something that no one is ever going to see anyway...

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-05-23 19:55:45 +02:00
Fabian Dill
860ab10b0b Generate: remove tag "-" (#3036)
* Generate: introduce Remove, similar to Merge

* make + dict behave as + for each value


---------

Co-authored-by: Zach Parks <zach@alliware.com>
2024-05-23 15:03:21 +02:00
CookieCat
3f8c348a49 AHIT: Fix Your Contract has Expired being placed on the first level when it shouldn't (#3379) 2024-05-23 09:49:17 +02:00
Zach Parks
e1ff5073b5 WebHost, Core: Move item and location descriptions to WebWorld responsibilities. (#2508)
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2024-05-23 02:08:08 -05:00
Star Rauchenberger
8b6eae0a14 Lingo: Add option groups (#3352)
* Lingo: Add option groups

* Touched up option docstrings
2024-05-23 02:22:39 +02:00
agilbert1412
89d0dae299 Stardew valley: Create Option Groups (#3376)
* - Fix link in Stardew Setup Guide

* - Create option groups for Stardew Valley

* - Cleaned up the imports

* - Fixed double quotes and trailing comma

* - Improve order in the multipliers category
2024-05-23 02:22:28 +02:00
LiquidCat64
56d01f3913 CV64: Add option groups (#3360)
* Add the option groups.

* Get rid of all mid-sentence line breaks.
2024-05-23 02:16:13 +02:00
Scipio Wright
a43e294786 TUNIC: Add option presets (#3377)
* Add option presets

* why the hell is there an s here

* entrance rando yes
2024-05-23 02:12:59 +02:00
Justus Lind
92392c0e65 Update Song List to Muse Dash 4.3.0 (#3216) 2024-05-23 02:11:27 +02:00
Star Rauchenberger
893a157b23 Lingo: Minor logic fixes (part 2) (#3250)
* Lingo: Minor logic fixes (part 2)

* Update the datafile

* Renamed Fearless Mastery

* Move Rhyme Room LEAP into upper room

* Rename Artistic achievement location

* Fix broken wondrous painting

* Added a test for the Wondrous painting thing
2024-05-23 02:09:52 +02:00
Scipio Wright
02d3fdf2a6 Update options to look better on webhost after update, also give death link a description (#3329) 2024-05-23 02:05:21 +02:00
Bryce Wilson
cd160842ba BizHawkClient: Linting/style (#3335) 2024-05-23 02:03:42 +02:00
Bryce Wilson
93f63a3e31 Pokemon Emerald: Fix broken Markdown in spanish setup guide (#3320)
* Pokemon Emerald: Fix broken Markdown in spanish setup guide

* Pokemon Emerald: Minor formatting in spanish setup guide

* oops
2024-05-23 02:01:27 +02:00
Scipio Wright
b4fec93c82 Update guide with some linux instructions (#3330) 2024-05-23 02:00:06 +02:00
Exempt-Medic
1ae0a9b76f WebHost: Fixing default values for LocationSets (#3374)
* Update macros.html

* Update macros.html
2024-05-23 01:58:06 +02:00
Fabian Dill
0ea20f3929 Core: add panic_method setting (#3261) 2024-05-22 14:02:18 +02:00
PoryGone
20134d3b1e Celeste 64: Option Groups (#3321)
* Celeste 64: Option Groups

* Retarget OptionGroup import
2024-05-21 18:22:39 -04:00
PoryGone
a1c2e8715e DKC3: Option Groups (#3322)
* DKC3: Option Groups

* Retarget OptionGroup import
2024-05-21 18:19:37 -04:00
NewSoupVi
61be79b7ea The Witness: Option Groups & Tooltip formatting (#3342)
* Add option groups

* Option tooltip formatting

* eof

* reindent, apparently I'm stupid

* lint

* oops indent
2024-05-21 18:17:12 -04:00
Scipio Wright
e7544d835c TUNIC: Add option groups, fix option descriptions (#3344)
* Add option groups, fix up option descriptions

* Change sword progression description back

* Add missed newline change, missed space after asterisk
2024-05-21 18:12:52 -04:00
PoryGone
62e68ba1cc SMW: Option Groups and Presets (#3345)
* SMW: Add Option Groups and Presets

* Fix Boss Shuffle Preset

* Tooltip formatting
2024-05-21 18:09:05 -04:00
Trevor L
9441cc31b7 Hylics 2: Update option docstrings (#3359)
* Update Options.py

* "pool" -> "item pool"
2024-05-21 17:53:00 -04:00
PoryGone
5c66681233 SA2B: Option Groups and Dataclass (#3357)
* Merge Conflicts

* SA2B: Option Groups

* Re-add erroneously-removed import
2024-05-21 17:48:23 -04:00
Trevor L
92b1f3cd19 Bomb Rush Cyberfunk: Update option docstrings (#3358)
* Update Options.py

* Update Options.py
2024-05-21 17:31:01 -04:00
jamesbrq
514ad69f44 Remove logging from validate_rom (#3362) 2024-05-21 20:57:59 +02:00
Fabian Dill
461f5db35a Customserver: only save on exit if it's in a good state. (#3351) 2024-05-21 14:08:59 +02:00
CookieCat
fe7bc8784d A Hat in Time: Implement New Game (#2640)
Adds A Hat in Time as a supported game in Archipelago.
2024-05-20 09:04:06 +02:00
Louis M
c792ae76ca Aquaria: Adding Aquaria to README and some other minors changes (#3313) 2024-05-20 08:58:44 +02:00
Chris Wilson
bfe215d5a7 Use world.web.options_presets directly instead of creating an empty dict first (#3348) 2024-05-20 01:57:07 -04:00
Chris Wilson
5910b94deb Update options pages macros to respect valid_keys for item and location options (#3347) 2024-05-20 00:26:42 -04:00
Fabian Dill
14ffd1c70c Subnautica: fix use of _valid_keys were valid_keys should be used. (#3346)
* Subnautica: fix use of _valid_keys were valid_keys should be used.

* Update Options.py
2024-05-20 00:20:01 -04:00
Scipio Wright
754fc11c1b TUNIC: ER Refactor for better plando connections, fewer shops improvement (#3075)
* Fixed shop changes

* Update option description

* Apply suggestions from Vi's review (thank you)

* Fix for plando connections on a full scene

* Plando connections should work better now for complicated paths

* Even more good plando connections yes

* Starting to move the info over

* Fixing up formatting a bit

* Remove unneeded item info

* Put in updated_reachable_regions, to replace add_dependent_regions

* Updated to match ladder shuffle

* More stuff I guess

* It functions!

* It mostly works with plando now, some slight issues still

* Fixed minor logic bug

* Fixed world leakage

* Change exception message

* Make exception message better for troubleshooting failed connections

* Merged with main

* technically a logic fix but it would never matter cause no start shuffle

* Add a couple more alias item groups cause yeah

* Rename beneath the vault front -> beneath the vault main

* Flip lantern access rule to the region

* Add missing connection to traversal reqs

* Move start_inventory_from_pool to the top so that it's next to start_inventory

* Reword the fixed shop description slightly

* Refactor per ixrec's comments

* Greatly reduced an overcomplicated block because Vi is cool and smart and also cool

* Rewrite traversal reqs thing per Vi's comments
2024-05-20 01:01:24 +02:00
Star Rauchenberger
12cde88f95 Lingo: Fixed edge case sunwarp shuffle accessibility issue (#3228)
* Lingo: Fixed edge case sunwarp shuffle accessibility issue

* Minor readability update

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-20 00:56:24 +02:00
Alchav
e0b6889634 ALTTP: Second attempt to fix Swamp Palace boss logic (#3315) 2024-05-19 22:18:41 +02:00
black-sliver
14321d6ba2 Factorio: update factorio-rcon (#3198)
2.1.1 didn't work with py3.8, 2.1.2 fixes that
2024-05-19 20:41:18 +02:00
black-sliver
e978109410 WebHost: properly stop worker threads (#3340)
* WebHost: properly stop worker threads

* Less jank

* Forgot the try-catch around the while true
2024-05-19 20:40:36 +02:00
black-sliver
019dfb8242 CustomServer: re-add missing Archipelago to data package (#3341) 2024-05-19 20:40:08 +02:00
Doug Hoskisson
8e9a050889 Zillion: "item counts" OptionGroup (#3338) 2024-05-19 14:36:47 -04:00
Fabian Dill
2801e21296 WebHost: fixup WebHostLib/options.py (#3332)
* WebHost: fixup WebHostLib/options.py

* Update WebHostLib/options.py

* Update WebHostLib/options.py

* fix visibility flag handling
2024-05-19 14:21:46 -04:00
Fabian Dill
e97eddcdaf WebHost: move atexit saving to end of room hosting function (#3339) 2024-05-19 18:25:56 +02:00
Fabian Dill
d3f4ee4994 WebHost: re-introduce per-Room Locker (#3337) 2024-05-19 16:31:35 +02:00
black-sliver
cf34f125d6 CustomServer: don't mutate static server data (#3334)
when switching to multiple rooms per process, we ended up modifying the static server data
because that's how _load works and the data is now shared between multiple rooms.
2024-05-19 15:32:11 +02:00
Fabian Dill
663b50b33e WebHost: fix AutoLauncher restarting rooms due to race condition (#3333) 2024-05-19 15:17:55 +02:00
Doug Hoskisson
230a9e620b Core: move OptionGroup definition to Options.py (#3325) 2024-05-19 04:40:41 +02:00
Silvris
1b6fb7b090 Tests: test that no worlds fail to load (#3318)
* test that no worlds fail to load

* pep8

* Update test_implemented.py

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-19 00:42:58 +02:00
jamesbrq
0e893889c7 MLSS: General bugfixes + Add patch extension to inno_setup.iss (#3286)
* Remove outdated header change for ROM verification

* Update Connections to be compatible with python ver. 3.8

* Update inno_setup.iss

* Update inno_setup.iss
2024-05-18 22:26:50 +02:00
Rensen3
2bc345504e YGO06: make sure it runs on 3.8 support (#3324)
* YGO06: make sure it runs on python 3.8

* YGO06: change merge of dict, so it runs on python 3.8
2024-05-18 13:53:17 +02:00
Chris Wilson
5e3c5dedf3 WebHost: Massive overhaul of options pages (#2614)
* Implement support for option groups. WebHost options pages still need to be updated.

* Remove debug output

* In-progress conversion of player-options to Jinja rendering

* Support "Randomize" button without JS, transpile SCSS to CSS, include map file for later editors

* Un-alphabetize options, add default group name for item/location Option classes, implement more option types

* Re-flow UI generation to avoid printing rows with unsupported or invalid option types, add support for TextChoice options

* Support all remaining option types

* Rendering improvements and CSS fixes for prettiness

* Wrap options in a form, update button styles, fix labels, disable inputs where the default is random, nuke the JS

* Minor CSS tweaks, as recommended by the designer

* Hide JS-required elements in noscript tag. Add JS reactivity to range, named-range, and randomize buttons.

* Fix labels, add JS handling for TextChoice

* Make option groups collapsable

* PEP8 current option_groups progress (#2604)

* Make the python more PEP8 and remove unneeded imports

* remove LocationSet from `Item & Location Options` group

* It's ugly, but YAML generation is working

* Stop generating JSON files for player-options pages

* Do not include ItemDict entries whose values are zero

* Properly format yaml output

* Save options when form is submitted, load options on page load

* Fix options being omitted from the page if a group has an even number of options

* Implement generate-game, escape option descriptions

* Fix "randomize" checkboxes not properly setting YAML options to "random"

* Add a separator between item/location groups and items/locations in their respective lists

* Implement option presets

* Fix docs to detail what actually ended up happening

* implement option groups on webworld to allow dev sorting (#2616)

* Force extremely long item/location/option names with no spaces to text-wrap

* Fix "randomize" button being too wide in single-column display, change page header to include game name

* Update preset select to read "custom" when updating form inputs. Show error message if the user doesn't input a name

* Un-break weighted-options, add option group names to weighted options

* Nuke weighted-options. Set up framework to rebuild it in Jinja.

* Generate styles with scss, remove styles which will be replaced, add placeholders for worlds

* Support Toggle, DefaultOnToggle, and Choice options in weighted-options

* Implement expand/collapse without JS for worlds and option groups

* Properly style set options

* Implement Range and NamedRange. Also, CSS is hard.

* Add support for remaining option types. JS and backend still forthcoming.

* Add JS functionality for collapsing game divs, populating span values on range updates. Add <noscript> tag to warn users with JS disabled.

* Support showing/hiding game divs based on range value for game

* Add support for adding/deleting range rows

* Save settings to localStorage on form submission

* Save deleted options on form submission

* Break weighted-options into a per-game page.

- Break weighted-options into a per-game page
- Add "advanced options" links to supported games page
- Use details/summary tags on supported games, player-options, and weighted-options
- Fix bug preventing previously deleted rows from being removed on page load if JS is enabled
- Move route handling for options pages to options.py
- Remove world handling from weighted-options

* Implement loading previous settings from localStorage on page load if JS is enabled

* Weighted options can now generate YAML files and single-player games

* options pages now respect option visibility settings for simple and complex pages

* Remove `/weighted-settings` redirect, fix weighted-options link on player-options page

* Fix instance of AutoWorld not having access to proper `random`

* Catch instances of frozenset along with set

* Restore word-wrap in tooltips

* Fix word wrap in player-options labels

* Add `dedent` filter to help with formatting tooltips in player-options

* Do not change the ordering of keys when printing yaml files

* Move necessary import out of conditional statement

* Expand only the first option group by default on both options pages

* Respect option visibility when generating yaml template files

* Swap to double quotes

* Replace instances of `/weighted-settings` with `/weighted-options`, swap out incomplete links

* Strip newlines and spaces after applying dedent filter

* Fix documentation for option groups

* Update site map

* Update various docs

* Sort OptionSet lists alphabetically

* Minor style tweak

* Fix extremely long text overflowing tooltips

* Convert player-options to use CSS grid instead of tables

* Do not display link to weighted-options page on supported games if the options page is an external link

* Update worlds/AutoWorld.py

Bugfix by @alwaysintreble

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>

* Fix NamedRange options not being properly set if a preset it loaded

* Move option-presets route into options.py

* Include preset name in YAML if not "default" and not "custom"

* Removed macros for PlandoBosses and DefaultOnToggle, as they were handled by their parent classes

* Fix not disabling custom inputs when the randomize button is clicked

* Only sort OptionList and OptionSet valid_keys if they are unordered

* Quick style fixes for player-settings to give `select` elements `text-overflow: ellipsis` and increase base size of left-column

* Prevent showing a horizontal scroll bar on player-options if the browser width was beneath a certain threshold

* Fix a bug in weighted-options which prevented inputting a negative value for new range inputs

---------

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
2024-05-18 00:11:57 -04:00
NewSoupVi
5fb0126754 Core: Player name property on world class (#3042)
* player property on world class

* Remove dat shi from overcooked

* Update worlds/AutoWorld.py

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2024-05-18 00:18:57 +02:00
Rensen3
b4c263fc9d YGO06: add new game yugioh06 to CODEOWNERS inno_setup and readme (#3316) 2024-05-18 00:09:03 +02:00
Bryce Wilson
013862b068 Pokemon Emerald: Update changelog (#3317)
* Pokemon Emerald: Update changelog

* Pokemon Emerald: Fix spelling error in changelog

Co-authored-by: Remy Jette <remy@remyjette.com>

---------

Co-authored-by: Remy Jette <remy@remyjette.com>
2024-05-18 00:06:30 +02:00
Doug Hoskisson
280b67f996 some worlds: some typing in LocalRom (#3090)
* some worlds: some typing in `LocalRom`

### `read_bytes`

It's not safe to return `bytearray` when we think it's `bytes`
```python
a = rom.read_bytes(8, 3)
hash(a)  # This won't crash, right?
```

### `write_bytes`

`Iterable[SupportsIndex]` is what's required for `bytearray.__setitem__(slice, values)`
We need to add `__len__` for the `len(values)` in this function.

* remove `object` inheritance
2024-05-17 21:41:57 +02:00
NewSoupVi
9ae7083bfc Fix Monastery Entry RIght righqeuotghqeougtfgas (#3213) 2024-05-17 19:29:55 +02:00
NewSoupVi
bd18018852 The Witness: Fix Mountain Floor 2 Near Row 5 Symbol Requirement (#3212) 2024-05-17 19:29:46 +02:00
Exempt-Medic
b4b79bcd78 BRCF: Small Fixes (#3314)
* Plural fix

* Update link
2024-05-17 19:24:32 +02:00
Rensen3
539ee1c5da Yu-Gi-oh! 2006: implement new game (#2795)
* Initial implementation of Yu-Gi-Oh! WC 2006

* Added Opponents and banlists

* Initial implementation of Yu-Gi-Oh! WC 2006

* Added Opponents and banlists

* Added Campaign Logic

* Added Bonuses Logic

* Added challenge logic

* fixed yugioh client

* ygo06 rom cleanup and include lua

* ygo06 patch cleanup

* ygo06 move client to world folder

* lots of small changes

* bug fixes

* implemented filler item for yugioh06

* BizHawkClient: Add client and connector

* BizHawkClient: Add launcher component and inno_setup lines

* BizHawkClient: Misc stability updates and small improvements

Bad commit organization a consequence of working with two different branches and not keeping the commits separated

* BizHawkClient: Add docstrings

* BizHawkClient: Pull in changes from other branch

* BizHawkClient: Fix no handler message not displaying after changed ROMs

* BizHawkClient: Remove extra print statement from lua

* BizHawkClient: Change version command to use raw strings

* BizHawkClient: Change script version to single integer

* YGO06: added logic for "all expect type forbidden" limited duels

* YGO06: Structure Deck choice now affects logic. Fixed a bug with tier 5 campaign opponents. Added logic for TD16 Union.

* BizHawkClient: Add newline to version for lua script

* BizHawkClient: Call send_connect from BizHawkClient's watcher loop

* BizHawkClient: Add handling for failed request getting script version

* BizHawkClient: Have base64.lua check lua version explicitly for bit operations

On 2.9, it would detect LuaJIT and flood the console with deprecation warnings

* BizHawkClient: Update connector script for slightly better errors and address Gambatte frame sync issue

* BizHawkClient: Remove accidentally added print statements

* BizHawkClient: Fix connector server not closing correctly

* BizHawkClient: Move some connector code around, some linting

* BizHawkClient: Small cleanup in lua

* BizHawkClient: Lua linting

* BizHawkClient: Remove outdated sentences in docstrings

* YGO06: Logic additions and bug fixes

* BizHawkClient: Correctly null check patch file arg

* BizHawkClient: Initialize logging

* BizHawkClient: Move code to worlds/_bizhawk

Also splits out BizHawk communication functions to their own file for use outside this client

* BizHawkClient: Add license to connector lua, add types to docs

* BizHawkClient: Add module docstrings

* YGO06: Logic additions

* BizHawkClient: Allow clients to define multiple systems

* BizHawkClient: Better logging and handling of interruptions to connection to script

* YGO06: Logic additions

* YGO06: Added text to options

* YGO06: Ported to bizhawk client

* YGO06: fix goal not being detected

* YGO06: fix access item rule for tier 5 column 1 and 2

* YGO06: docu and bug fixes

* YGO06: change name

* YGO06: some fixes

* YGO06: fix starting opponent and booster not applying

* YGO06: added option to reduce the amount of challenges and remove the no ban list from pool.

* YGO06: added rom being asked for on first use

* YGO06: fix rules for challenges

* YGO06: create proper rules for TD04 Ritual Summon

* YGO06: mark most banlists as usefull instead of progression

* YGO06: reduce the required core boosters across the board

* YGO06: fix client not loading if another game already loaded the bizhawk client

* YGO06: fix client not finding the bizhawk client.

* YGO06: fix TD08 Draw not giving out an item

* YGO06: small text changes

* YGO06: update to version 0.4.4

* YGO06: logic mixin clean-up

* YGO06: added option for campaign opponents as goal

* Pokemon Emerald add encounter table randomization

* Pokemon Emerald: Item ball randomization working

* Pokemon Emerald: Clean up code a little

* Pokemon Emerald: Partial rework of region/location creation

* Pokemon Emerald: Dedupe items and add more readable names

* Refactor region creation to manually defined regions

* Split region json

* Use new data.json with flattened constants and add HM locations

* YGO06: bug fixes

* YGO06: bug fix

* YGO06: changes default options to be more beginner friendly

* YGO06: attempt at universal tracker support. Settings are stored in slot data now.

* YGO06: fix for older python versions

* YGO06: fix slot data

* YGO06: added diiferent opponents to the campaign

* YGO06: fix small bug with opponent icons

* YGO06: fix unwanted changes

* YGO06: repair merge with main

* YGO06: map out all of the opponents

* YGO06: added opponent shuffle

* YGO06: added logic to opponent shuffle

* YGO06: added option to use ocg art

* YGO06: bug_fixes

* YGO06: removed todos, since they are not needed anymore

* YGO06: added draft mode

* YGO06: added logic to draft mode

* YGO06: Added Money multiplier when you lose

* YGO06: Fixed Unit Test errors

* YGO06: Added Random deck option

* YGO06: Bug fix with registering client

* YGO06: client clean-up

* YGO06: fixed card misspellings

* YGO06: removed unused imports and other small changes

* YGO06: small changes

* YGO06: fix generation error when the combination of starting with "No Banlist" and not adding "No Banlist" to the pool is selected

* YGO06: fix ocg art path overwriting Huge Revolution bugfix

* YGO06: added comments and other minor changes

* YGO06: fixed byte length in client for money

* YGO06: fixes for webhost and options

* YGO06: use the proper random function

* YGO06: change settings to options

* YGO06: move to procedure patch

* YGO06: fix imports

* YGO06: fix download link for patch not showing

* YGO06: remove unnecessary Optional

* YGO06: fix universal tracker stuff

* YGO06: add typings

* YGO06: small cleanup

* yugioh06:  small change to setup

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* YGO06: remove logic mixin

* YGO06: fix create item and implement create filler and get filler item name

* YGO06: remove double lambdas

* YGO06: use pkgutil.get_data instaed pf zipFile

* YGO06: fix starting items being duplicated

* YGO06: lots of small changes

* YGO06: moved functions to match execution order

* YGO06: run ruff

* YGO06: run ruff format

* YGO06: fix ruff errors

* YGO06: undo ruff format for rules

* YGO06: move import to prevent circular dependency

* YGO06: remove unused class

* YGO06: optimizing rules

* YGO06: some optimization and small bug fix

---------

Co-authored-by: Zunawe <gyroscope15@gmail.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2024-05-17 19:23:05 +02:00
Exempt-Medic
5fb1d0f98a FF1: Switching Options System (#3302) 2024-05-17 19:19:55 +02:00
Louis M
89a2a3c35b Aquaria: implement new game (#3197)
This is a new world for the Aquaria game (https://www.bit-blot.com/aquaria/).
2024-05-17 12:29:00 +02:00
Fabian Dill
7900e4c9a4 WebHost: use a limited process pool to run Rooms (#3214) 2024-05-17 12:21:01 +02:00
Fabian Dill
3dbdd048cd Core: prevent "Could not find identify Component responsible for None" from being logged. (#3225) 2024-05-17 12:19:41 +02:00
Trevor L
68323b46a9 Bomb Rush Cyberfunk: Implement new game (#2925)
Adds Team Reptile's Bomb Rush Cyberfunk as a new game.
2024-05-17 12:13:40 +02:00
Aaron Wagener
2447be92d8 The Messenger: fix generation failure for no portal shuffle with 3 available portals (#3200) 2024-05-17 10:18:50 +02:00
NewSoupVi
88dd27eb3a The Witness: Use OptionError (#3258)
* Use OptionError

* ruff
2024-05-17 10:07:38 +02:00
Exempt-Medic
6d8ac5d054 Core: Remove deprecated get_current_option_name and SpecialRange (#3296)
* Removing deprecated function

* Removing SpecialRange
2024-05-17 10:02:25 +02:00
Exempt-Medic
5a2d839412 Removing deprecated item_count (#3309) 2024-05-17 09:54:57 +02:00
Doug Hoskisson
4bd4a2c541 Docs: remove obsolete yaml generation info (#3304)
* Docs: remove obsolete yaml generation info

This line was added when we didn't have the "Generate Template Options" button in the launcher.

* add information about `Launcher.py`
2024-05-17 01:26:43 +02:00
FlySniper
705cb2e816 Wargroove: Switched to options API. (#3306)
* Wargroove: Switched to options API.

* Update Options.py

* Update __init__.py

* Options is plural

* Wargroove: Options updates with some small fixes.

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-05-16 18:46:13 +02:00
NewSoupVi
467bbd7754 WebHost: Fix setup guide link not working for games with special characters (#3269)
* WebHost: Fix setup guide link not working for games with special characters

* use url_for with _anchor (#3279)
2024-05-15 21:40:40 -04:00
LiquidCat64
4da9cdd91c CV64: Fix items with weird characters landing on Renon's shop crashing (#3305) 2024-05-15 23:50:04 +02:00
chandler05
6576b069f2 Hylics 2: Remove Random Start option and replace it with Start Location option (#3289)
* Hylics 2: Remove Random Start option and replace it with Start Location option

* remove choice

* Readd random start to slot data

* newlines

* Add random_start as a Removed option
2024-05-14 20:35:32 +02:00
Scipio Wright
b78781ab3e Docs: Update advanced yaml guide wording for priority locations (#3298)
* Update advanced yaml guide wording

* Update options api as well

* Update exclude locations description slightly to use more current verbiage

* Update priority locations in options api.md to note what happens if it runs out

* Remove auto-added bullet points

* Slightly mess with wording to make it more succinct
2024-05-14 20:28:15 +02:00
Aaron Wagener
9a82edc931 World: remove ClassVar typing from topology_present (#3294) 2024-05-14 04:35:33 +02:00
Doug Hoskisson
77cce68c08 Zillion: remove deprecated Logger.warn (#3295) 2024-05-13 20:31:15 +02:00
Exempt-Medic
f38655d6b6 Bumper Stickers and Meritous: Options and world: multiworld fixes (#3281)
* Update Options.py

* Update __init__.py

* Correct case

* Correct case

* Update Meritous and actually use Options

* Oops

* Fixing world: multiworld
2024-05-12 18:52:34 +02:00
Exempt-Medic
701fbab837 Core: World: MultiWorld and another deprecated option getter (#3254)
* world: multiworld and deprecated options getting

* Oops

* Found two more
2024-05-12 18:51:20 +02:00
Aaron Wagener
af83050b75 Core: log warning for unknown options (#1385)
* throw an error for unknown options

* move the error to the end of trigger resolution and make trigger names valid

* add bad hardcoded stuff for LTTP

* use itertools.chain instead of a ChainMap

* remove accidental unused import

* make the check after both trigger resolutions so no valid keys are missed, and only check relevant game.

* log a warning instead of crashing

* delete options from the weights once it gets registered for cleaner erroring

* grammar hard

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-05-10 23:00:13 +02:00
Exempt-Medic
8db3e40094 Removing old option getters (#3285) 2024-05-10 16:29:07 +02:00
Trevor L
d48f2ab1b4 Core: Add list/item group exclusive methods to CollectionState (#3192)
* Group exclusive methods

* Add docstrings

* Apply suggestions from code review

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* Put lines back with no whitespace

* Add list methods

---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-08 18:34:32 +02:00
Bryce Wilson
0f1b16d640 Pokemon Emerald: Change Lilycove access logic (#3277)
* Pokemon Emerald: Change logical access to lilycove from east

* Pokemon Emerald: Add tests
2024-05-08 18:26:13 +02:00
Bryce Wilson
76962b8b3b Pokemon Emerald: Fix incorrect access to slateport water encounters (#3243) 2024-05-07 12:43:35 +02:00
NewSoupVi
e04db57dce Core: Add has_list and count_list and improve (?) count_group (#2934)
* Add has_list and count_list and improve (?) count_group

* MESSENGER STOP

* Add docstrings to has_list and count_list

* Add docstrings for has_group and count_group as well

* oops

* Rename to has_from

* docstrings improvement again

* Docstring
2024-05-07 09:23:25 +02:00
digiholic
12b8fef1aa Adds a canary byte check before sending game completion (#3217) 2024-05-07 09:22:11 +02:00
NewSoupVi
0ac8844f6f Core: Add "has_all_counts" and "has_any_count" functions to CollectionState (#2933)
* Add has_all_counts and has_any_counts

* Messenger gave me a red x and I'm mad about it

* Update BaseClasses.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update BaseClasses.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Mapping instead of Dict

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-05-07 08:15:09 +02:00
black-sliver
23eca7d747 SoE: Docs: rework some styling (#3268)
* Docs: SoE: move header and fix header level

* Docs: SoE: be pedantic for required software
2024-05-06 10:55:25 +02:00
Alchav
1a563a14fc LTTP: Yet more LTTP logic fixes (#3270) 2024-05-06 09:36:08 +02:00
jamesbrq
5935093615 Mario & Luigi: Superstar Saga: Implement New Game (#2754)
* Commit for PR

* Commit for PR

* Update worlds/mlss/Client.py

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/mlss/__init__.py

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/mlss/__init__.py

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/mlss/docs/setup_en.md

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Remove deprecated import. Updated settings and romfile syntax

* Updated Options to new system. Changed all references from MultiWorld to World

* Changed switch statements to if else

* Update en_Mario & Luigi Superstar Saga.md

* Updated client.py

* Update Client.py

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Updated logic, Updated patch implementation, Removed unused imports, Cleaned up Code

* Update __init__.py

* Changed reference from world to mlssworld

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* Fix merge conflict + update prep

* v1.2

* Leftover print commands

* Update basepatch.bsdiff

* Update basepatch.bsdiff

* v1.3

* Update Rom.py

* Change tracker locations to serverside, no longer locations. Various code cleanup and logic changes.

* Event removal continuation.

* Partial Implementation of APPP (Incomplete))

* v1.4 Implemented APPP

* Docs Updated

* Update Rom.py

* Update setup_en.md

* Update Rom.py

* Update Rules.py

* Fix for APPP being broken on webhost

* Update Rom.py

* Update Rom.py

* Location name fixes + pants color fixes

* Update Rules.py

* Fix for ultra hammer cutscene

* Fixed compat. issues with python ver. 3.8

* Updated hidden block yaml option

* pre-v1.5

* Update Client.py

* Update basepatch.bsdiff

* v1.5

* Update XP multiplier to have a minimum of 0

* Update 'Beanfruit' to 'Bean Fruit'

* v1.6

* Update Rom.py

* Update basepatch.bsdiff

* Initial review refactor

* Revert state logic changes. Continuation of refactor.

* Fixed failed generations. Finished refactor.

* Reworked colors. Removed all .txt files

* Actually removed the .txt files this time

* Update Rom.py

* Update README.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Options.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Client.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/docs/en_Mario & Luigi Superstar Saga.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Data.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Review refactor.

* Update README.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update worlds/mlss/Rules.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Add coin blocks to LocationName

* Refactor.

* Update Items.py

* Delete mlss.apworld

* Small asm bugfix

* Update basepatch.bsdiff

* Client sends less messages to server

* Update basepatch.bsdiff

---------

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-05-06 09:15:06 +02:00
Fabian Dill
2aa3ef372d WebHost: use redirect for /room form submission (#3271) 2024-05-05 22:59:51 +02:00
Bryce Wilson
d94cf8dcb2 Pokemon Emerald: Add event ticket locations to client data store flags (#3177)
* Pokemon Emerald: Add event ticket locations to client data store flags

* Pokemon Emerald: Add regi doors event flag

* Pokemon Emerald: Add more tracker flags
2024-05-05 10:46:11 +02:00
PoryGone
5fae1c087e Celeste 64: v1.2 Content Update (#3210)
* Cleanup and new option support

* Handle new locations

* Support higher Strawberry counts

* Don't add start inventory items to the pool

* Support Move Shuffle functionality and items

* Hard and Move Shuffle Logic

* Fix Options

* Update CHANGELOG.md

* Add standard moves logic for signs 3 and 4

* Fix Option Tooltip

* Add tracker link to setup guide

* Fix unit test

* Fix option tooltips

* Missing Space

* Move option checking out of rule function

* Delete just_gen500.bat
2024-05-05 08:58:49 +02:00
Bryce Wilson
7e61211365 Pokemon Emerald: Convert to procedure patch (#2995)
* Pokemon Emerald: Convert to procedure patch

* Pokemon Emerald: Remove assertion for vanilla rom's existence

* Pokemon Emerald: Add APPP implication to changelog

* Pokemon Emerald: Move procedure patch changelog line to new version

* Pokemon Emerald: Modify changelog versions

* Pokemon Emerald: Fix patch file download not appearing

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-05 07:08:24 +02:00
Bryce Wilson
7603b4a88f Pokemon Emerald: Change dexsanity to not create locations for blacklisted wilds (#3056) 2024-05-04 21:44:38 +02:00
Aaron Wagener
005fc4e864 Fill: allow for single player fill restrictive placement and sweeping (#2415)
* Core: allow for single player state sweeping

* Fill: have distribute items use single_player fill when it can

* oop

* pass locations to sweep_for_events instead of the player

* finally found the diff that was breaking swap

* LTTP fills everyone's dungeons at once, not just a single player's

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-04 12:42:36 +02:00
Star Rauchenberger
28262a31b8 Lingo: Started using OptionError (#3251) 2024-05-04 08:40:17 +02:00
Bryce Wilson
660b068f5a Pokemon Emerald: Use OptionError (#3264) 2024-05-04 08:38:24 +02:00
Thorsten Horberth
879c3407d8 Yoshi's World: Fixed minor logic inconsistincy in Rules.py (#3241)
* Fixed Logic in Rules.py

As of easy logic of this goal is
    set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state)))
normal logic shouldn't need any collectable.

* Corrected Logic Rules.py
2024-05-04 04:29:12 +02:00
Scipio Wright
d5683c4326 Core: Make output when hinting something with multiple copies show up in a better order (#3245)
* Make the hint info show up in a better order

* Change how old_hints is modified/done

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-04 04:28:09 +02:00
Fabian Dill
f27d1d635b SNIClient: restore old operands header (#3242) 2024-05-03 22:00:05 +02:00
Nicholas Saylor
298c9fc159 Fixed typo and odd capitalization (#3233) 2024-05-03 12:23:08 +02:00
Scipio Wright
26188230b7 TUNIC: Better seed groups for Entrance Rando (#2998)
* Update entrance rando description to discuss seed groups

* Starting off, setting up some names

* It lives

* Some preliminary plando connection handling, probably has errors

* Add missed comma

* if -> elif

* I think this is working properly to handle plando connections

* Update comments

* Fix up shop -> shop portal stuff

* Add back comma that got removed for no reason in the ladder PR

* Remove unnecessary if else

* add back the actually necessary if but not the else

* okay they were both necessary

* Update entrance rando description

* blasphemy

Co-authored-by: Silent <110704408+silent-destroyer@users.noreply.github.com>

* Rename other instances of tunc -> tunic

* Update per Vi's review (thank you)

* Fix a not that shouldn't have been

* Rearrange, update per Vi's comments (thank you)

* Fix indent

* Add a .value

* Add .values

* Fix bad comparison

* Add a not that was supposed to be there

* Replace another isinstance

* Revise option description

* Fix per Kaito's comment

Co-authored-by: Kaito Sinclaire <ks@rosenthalcastle.org>

---------

Co-authored-by: Silent <110704408+silent-destroyer@users.noreply.github.com>
Co-authored-by: Kaito Sinclaire <ks@rosenthalcastle.org>
2024-05-03 07:21:27 +02:00
t3hf1gm3nt
b68be7360c [TLOZ]: Remove use of per_slot_randoms (#3255)
We only used it in two spots for randomizing the secret rupee cave values. Uses proper world random now.
2024-05-03 02:56:20 +02:00
t3hf1gm3nt
255e52642e TLOZ: Fix rings classification, so they are actually considered for logic (#3253) 2024-05-02 16:49:39 -05:00
qwint
49862dca1f move godhome events to create_regions with the others to not try and make them non-events when unshuffled is on (#3221) 2024-05-02 15:26:17 +02:00
Star Rauchenberger
0d586a4467 Lingo: Fix broken good item in panelsanity (#3249) 2024-05-02 15:14:30 +02:00
Ziktofel
8c8b29ae92 SC2: For non-campaign order pick one of the hardest missions as goal (#3180)
This allows End Game as the goal even if long campaigns are present
2024-05-02 12:20:57 +02:00
Natalie Weizenbaum
9d478ba2bc Rules: Verify the default values of Options. (#2403)
* Verify the default values of `Option`s.

Since `Option.verify()` can handle normalization of option names, this allows options  to define defaults which rely on that normalization. For example, it allows a world to exclude certain locations by default.

This also makes it easier to catch errors if a world author accidentally sets an invalid default.

* Update Generate.py

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-02 12:19:15 +02:00
ken
3cc434cd78 Core: organize files on ingest via alpha, not ascii (#3029)
* organize files on ingest via alpha, not ascii

* Change from lower() to casefold()
2024-05-02 12:14:50 +02:00
Scipio Wright
31a5696526 Noita: Add more location groups, capitalize existing ones (#3141)
* Add location groups for each region

* Capitalize existing location groups

* Capitalize new boss location group names

* Update comment with capitalization

* Capitalize location_type in reigons.py
2024-05-02 12:02:14 +02:00
palex00
7bdf9a643c Updating Poptracker-Pack-Link for Pokémon Emerald as the old one was no longer maintained and did not work with 0.4.5 (#3193)
* Replaced the outdated Tracker Pack with a new one that is also pinned in the Discord channel

* Same change but for Spanish

* Update setup_en.md

* catching the bottom link as well

* See English Setupguide
2024-05-02 11:56:35 +02:00
Scipio Wright
c64c80aac0 TUNIC: Location groups for each area of the game (#3024)
* huzzah, location groups

* scope creep pog

* Apply suggestion to the other spot it is applicable at too

* apply berserker's suggestion

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Remove extra location group for shops

* Fire rod for magic wand

* Capitalize itme name groups

* Update docs to capitalize item name groups, remove the little section on aliases

since the aliases bit is really more for someone misremembering the name than anything else, like "fire rod" is because you played a lot of LttP, or Orb instead of Magic Orb is clear.

* Fix rule with item group name

* Capitalization is cool

* Fix merge mistake

* Add Flask group, remove Potions group

* Update docs to detail how to find item and location groups

* Revise per Vi's comment

* Fix test

* fuzzy matching please stop

* Remove test change that was meant for a different branch

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-05-02 10:02:59 +02:00
Aaron Wagener
07d9d6165e Tests: Clean up some of the fill test helpers a bit (#2935)
* Tests: Clean up some of the fill test helpers a bit

* fix some formatting

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-02 10:01:59 +02:00
Star Rauchenberger
fc571ba356 Lingo: Expand sphere 1 under restrictive conditions (#3190) 2024-05-02 09:52:16 +02:00
Emily
ea6235e0d9 Core: Improve join/leave messages, add "HintGame" tag (#2859)
* Add better "verbs" on joining msg, and improve leaving msgs

* Add 'HintGame' tag, for projects like BKSudoku/APSudoku/HintMachine

* data in one place instead of 3

* Clean up 'ignore_game' loop to use any() instead

---------

Co-authored-by: beauxq <beauxq@yahoo.com>
2024-05-02 09:38:49 +02:00
Aaron Wagener
6f8b8fc9c9 Options: Add an OptionError to specify bad options caused the failure (#2343)
* Options: Add an OptionError to specify bad options caused the failure

* inherit from ValueError instead of RuntimeError since this error should be thrown via bad input

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2024-05-02 09:22:50 +02:00
black-sliver
0ed0de3daa Setup: update cx_freeze to 7.x (#3195) 2024-05-01 01:48:32 +02:00
Star Rauchenberger
487a067d10 Lingo: Fix world load on frozen 3.8 (#3220)
* Lingo: Fix world load on frozen 3.8

* Fixed absolute imports in unit test

* Made unpickling safer
2024-04-29 20:38:29 +02:00
Doug Hoskisson
fc4e6adff5 Core: some CommonContext typing (#3227) 2024-04-29 07:26:30 +02:00
t3hf1gm3nt
9cdc90513b [TLOZ]: Dark Rooms and Level 8 Logic Fixes (#3222)
Properly name the Book to Book of Magic in Rules.py so you can actually possibly be expected to use Magical Rod plus Book of Magic to get through dark rooms. No wonder we tend to see candles so early oops.

Also adding a rule that you need candles for access to Level 8 so you aren't required to time a Rod+Book shot against a moblin to burn the bush. Might make this a logic trick or something later.
2024-04-28 01:49:59 +02:00
Alchav
9afe45166c ALTTP: 0.4.6 fixes (#3215)
* Fix randomizer room logic

* Fix Triforce Hunt HUD always present

* Fix Circle of Pots enemy byte

* treasure_hunt_total for Murahdala text
2024-04-28 01:48:59 +02:00
LiquidCat64
9e20fa48e1 CV64: fix import that shouldn't be relative (#3223) 2024-04-28 01:41:30 +02:00
Zach Parks
e76ba928a8 WebHost: Prevent committing data packages with invalid checksums to database and prevent 500 error from invalid zip files. (#3206) 2024-04-26 22:18:12 -04:00
Aaron Wagener
4f1e696243 The Messenger: fix import that shouldn't be relative (#3219) 2024-04-26 21:29:01 +02:00
Fabian Dill
4756c76541 WebHost: remove JSON_AS_ASCII (#3209) 2024-04-24 06:36:35 +02:00
Fabian Dill
2f78860d8c Core/SNIClient/LttP/Factorio: switch to get_settings (#3208) 2024-04-24 06:24:44 +02:00
Scipio Wright
cca9778871 Delete worlds/sc2wol directory (#3202) 2024-04-23 12:58:38 -05:00
Fabian Dill
bb16fe284a Core: make open_filename log that it's asking (#3199) 2024-04-23 19:05:03 +02:00
Fabian Dill
daccb30e3d Factorio: fix client compatibility with Windows 7/Python 3.8 (#3196) 2024-04-22 14:32:31 +02:00
black-sliver
747b48183c Setup: update cx_freeze to latest 6.x and exclude 7.x (#3194)
7.x has a breaking API change for us, so that needs to be resolved separately.
2024-04-22 13:54:35 +02:00
NewSoupVi
e22ac85e15 The Witness: More door renames (#3131) 2024-04-21 11:45:39 -05:00
Kaito Sinclaire
ee69fa6a8c SMZ3: Use correct font tiles for cross-world items in SM (#3095) 2024-04-21 11:44:17 -05:00
Scipio Wright
ec18254e9e TUNIC: Minor edits to game page (#3182)
* Minor edits to game page

* in -> of
2024-04-21 11:39:37 -05:00
Zach Parks
49c0268a84 MultiServer: Fix /alias <name> not removing aliases. (#3186) 2024-04-21 11:37:24 -05:00
Scipio Wright
6aafa6ff04 TUNIC: Fix minimal Heir access in ladder shuffle (#3189) 2024-04-21 17:00:16 +02:00
Silvris
3e27b93c37 SNIClient: set SNESState to SNES_DISCONNECTED when disconnected (#3188) 2024-04-21 16:59:19 +02:00
Fabian Dill
392c47dcef MultiServer: add datastore list command to MultiServer (#3181) 2024-04-21 03:47:01 +02:00
Scipio Wright
442c7d04db Core: Silently fix invalid yaml option for Toggles (#3179) 2024-04-21 03:37:28 +02:00
Chris Wilson
ad4451276d WebHost: Add robots.txt to WebHost (#3157)
* Add a `robots.txt` file to prevent crawlers from scraping the site

* Added `ASSET_RIGHTS` entry to config.yaml to control whether `/robots.txt` is served or not

* Always import robots.py, determine config in route function

* Finish writing a comment

* Remove unnecessary redundant import and config
2024-04-20 20:58:56 -04:00
Silvris
915ad61ecf Core: fix unfilled items "Per-Player Count" (#2661) 2024-04-20 19:57:55 -05:00
Bryce Wilson
5d8aca1b4e Pokemon Emerald: Temporary fix for missing items (#3162) 2024-04-20 19:56:08 -05:00
Aaron Wagener
d4c8083be5 Docs: Mention /check in the advanced yaml guide (#3092)
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-04-20 19:49:48 -05:00
SunCat
a3702abe38 CODEOWNERS: Update owner for ChecksFinder (#3089) 2024-04-20 19:49:00 -05:00
Danaël V
5fcc1aa83f Launcher: Adding Unrated mention to the launcher (#3097) 2024-04-20 19:48:14 -05:00
Flore
b8e0d4c4ee DS3: Update the setup docs to be more up to date (#2932)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
Co-authored-by: Moonlington <Moonlington@users.noreply.github.com>
2024-04-20 19:45:20 -05:00
Doug Hoskisson
fc18f9caf9 Zillion: Docs: add information to skill option documentation (#3118) 2024-04-20 19:43:13 -05:00
Doug Hoskisson
a50e68acd1 Docs: style: multiline brackets (#3143) 2024-04-20 19:42:27 -05:00
agilbert1412
0624ba5e81 Stardew Valley: Options page documentation improvements (#3155) 2024-04-20 19:41:00 -05:00
Zach Parks
a45fa84382 Factorio: Fix 500 error on Factorio multi-tracker. (#3184)
* Factorio: Fix 500 error on Factorio multi-tracker.

* Hopefully this also fixes the webhost test failures.
2024-04-20 19:39:33 -04:00
Zach Parks
532cff1334 ALTTP: Updates and refactors to multi-tracker and player tracker. (#3183)
* ALTTP: Massive game tracker update.

* Adds dropdowns separated by region for each location and its checked status.
* Adds Bombs for bombless start seeds.
* Adds Triforce Pieces to track.
* Update icon image URLs to match in-game closer.
* Fix issue with grouped progressive items.

* Couple missed points.

* Another edge case with details being refreshed.

* Remove old commented out CSS

* Consolidate a table and move an erroneous location in wrong region.

* ALTTP: Updates and refactors to multi-tracker and player tracker.

* Removed some missed commented out code.

* Add triforce to prepare inventory logic.
2024-04-20 18:29:41 -04:00
Zach Parks
0a1ce5b7d8 ALTTP: Updates to player-specific game tracker. (#3133) 2024-04-20 13:18:06 -05:00
David St-Louis
a4acdb6ddf DOOM II: var world->multiworld fix and minor doc fix (#3140) 2024-04-19 23:11:12 +02:00
Fabian Dill
7a004de9a0 LttP: remove glitch triforce setting (#3174) 2024-04-19 23:10:29 +02:00
Silvris
8021ec744f LttP: fix percentage Triforce Pieces and missed cleanup from #3160 (#3178) 2024-04-19 23:10:10 +02:00
Silvris
a06bca95ad CV64: Include player in APPP constructor (#3175) 2024-04-19 01:24:00 +02:00
Alchav
b372b9da20 LTTP: ToH Crystal Switch Logic (#3172) 2024-04-18 21:33:41 +02:00
black-sliver
6087ec539b Settings: disable automatic yaml line breaks (#3096)
* Settings: disable automatic yaml line breaks

* Tests: add settings formatting checks

* Tests: fix typing in test_host_yaml
2024-04-18 19:13:43 +02:00
digiholic
f89cee4b15 MMBN3: Modernizations and Minor Bugfixes (#2991) 2024-04-18 19:02:01 +02:00
JusticePS
727915040d Adventure: Remove runtime changes to location templates (#3010) 2024-04-18 19:01:12 +02:00
PoryGone
c4c4069022 SA2B: Update Setup guide for new Mod Manager (#3085) 2024-04-18 19:00:01 +02:00
Aaron Wagener
5711d2c309 The Messenger: Hotfix item links filler gen (#3078) 2024-04-18 18:59:30 +02:00
Aaron Wagener
580c9c3943 Core: fix item_name_groups unfolding in item links (#3088) 2024-04-18 18:58:18 +02:00
NewSoupVi
fbfe82f57f The Witness: Make item links work properly with the hint system (#3110) 2024-04-18 18:57:22 +02:00
el-u
233eba6681 lufia2ac: prevent double checks (#3154) 2024-04-18 18:56:32 +02:00
David St-Louis
ffff63e6f3 DOOM 1993: Update to use new Options + logic fixes + Doc fix (#3138) 2024-04-18 18:56:10 +02:00
JaredWeakStrike
2704015eef KH2: Docs updates and Excluded Locations Bugfix (#3150) 2024-04-18 18:55:27 +02:00
lordlou
3c70621f1b SM: getitem cheat fix (#3102) 2024-04-18 18:54:46 +02:00
Scipio Wright
5ec342abf4 Noita: Add Meat Realm (#3119) 2024-04-18 18:54:03 +02:00
David St-Louis
2c80a9b8f1 Heretic: Update to use new Options + logic fixes + Doc fix (#3139) 2024-04-18 18:53:09 +02:00
Silvris
6f7c2fa25f KDL3: Fix invalid animal placements and fill error (#3152) 2024-04-18 18:52:23 +02:00
Bryce Wilson
8b8df9fa33 Pokemon Emerald: Fix missing region for water encounters in Dewford (#3103) 2024-04-18 18:51:49 +02:00
Doug Hoskisson
3343d4e364 SM: clean post_fill function (#2863) 2024-04-18 18:50:17 +02:00
NewSoupVi
1faaa0d941 The Witness: Increase variety of the starting item (#3047) 2024-04-18 18:49:15 +02:00
Nicholas Brochu
fec533b65e Zork Grand Inquisitor: Fix Determinism Issues on Fixed Seeds (#3134) 2024-04-18 18:47:27 +02:00
Zach Parks
21184b59d2 MultiServer: Prevent invalid *_mode option values. (#3149) 2024-04-18 18:46:48 +02:00
qwint
444178171b Docs: adds new commonclient commands to webhost docs (#3151) 2024-04-18 18:46:00 +02:00
Star Rauchenberger
740b76ebd5 Lingo: The Pilgrim Update (#2884)
* An option was added to enable or disable the pilgrimage, and it defaults to disabled. When disabled, the client will prevent you from performing a pilgrimage (i.e. the yellow border will not appear when you enter the 1 sunwarp). The sun painting is added to the item pool when pilgrimage is disabled, as otherwise there is no way into the Pilgrim Antechamber. Inversely, the sun painting is no longer in the item pool when pilgrimage is enabled (even if door shuffle is on), requiring you to perform a pilgrimage to get to that room.
* The canonical pilgrimage has been deprecated. Instead, there is logic for determining whether a pilgrimage is possible.
* Two options were added that allow the player to decide whether paintings and/or Crossroads - Roof Access are permitted during the pilgrimage. Both default to disabled. These options apply both to logical expectations in the generator, and are also enforced by the game client.
* An option was added to control how sunwarps are accessed. The default is for them to always be accessible, like in the base game. It is also possible to disable them entirely (which is not possible when pilgrimage is enabled), or lock them behind items similar to door shuffle. It can either be one item that unlocks all sunwarps at the same time, six progressive items that unlock the sunwarps from 1 to 6, or six individual items that unlock the sunwarps in any order. This option is independent from door shuffle.
* An option was added that shuffles sunwarps. This acts similarly to painting shuffle. The 12 sunwarps are re-ordered and re-paired. Sunwarps that were previously entrances or exits do not need to stay entrances or exits. Performing a pilgrimage requires proceeding through the sunwarps in the new order, rather than the original one.
* Pilgrimage was added as a win condition. It requires you to solve the blue PILGRIM panel in the Pilgrim Antechamber.
2024-04-18 18:45:33 +02:00
Magnemania
6b50c91ce2 SM64ex: Logic and Generation Fixes (#3135) 2024-04-18 18:43:19 +02:00
wildham
a91105c958 FFMQ: Update FFMQ setup_en.md (#3091) 2024-04-18 18:42:28 +02:00
Trevor L
c3060a8b66 Hylics 2: Update to new options API (#3147) 2024-04-18 18:40:53 +02:00
PinkSwitch
5996a8163d Yoshi's Island: Minor Fixes (#3142) 2024-04-18 18:40:00 +02:00
Bryce Wilson
ee1e578201 Pokemon Emerald: Fix client crash if 0.4.6 client connects to 0.4.5 seed (#3146) 2024-04-18 18:39:28 +02:00
Bryce Wilson
2d3f3fcc2d Pokemon Emerald: Fix terra/marine caves bugged internal id (#3161) 2024-04-18 18:38:41 +02:00
LiquidCat64
437843bf53 CV64: Use AP Procedure Patch (#3159) 2024-04-18 18:37:51 +02:00
Scipio Wright
7fd7d5e492 Noita: Add the new bosses to the check pool (#3170) 2024-04-18 18:34:57 +02:00
el-u
52c1b04fc8 lufia2ac: prevent 0 byte reads (#3168) 2024-04-18 18:34:26 +02:00
Fabian Dill
6e56f31398 LttP/Core: more ripping and tearing (#3160) 2024-04-18 18:33:16 +02:00
Alchav
f19a84222e ALTTP: Triforce Pieces and Condense Items fixes (#3166) 2024-04-17 19:29:32 +02:00
Alchav
801d1223ac LTTP: Thieves Town KDS key fix (#3145) 2024-04-17 19:22:03 +02:00
Doug Hoskisson
30cdde8605 CI: pyright in github actions (#3121)
* CI: strict mypy check in github actions

mypy_files.txt is a list of files that will fail the CI if mypy finds errors in them

* don't need these

* `Any` should be a way to silence the type checker

* restrict return Any

* CI: pyright in github actions

* fix mistake in translating from mypy

* missed another change from mypy to pyright

* pin pyright version

* add more paths that should trigger check

* use Python instead of bash

* type error for testing CI

* Revert "type error for testing CI"

This reverts commit 99f65f3dad.

* oops

* don't need to redirect output
2024-04-16 23:03:30 +02:00
Fabian Dill
38c54ba393 WebHost: check: display exception chain one layer deep (#3153)
* WebHost: check: display exception chain one layer deep

* Update WebHostLib/check.py
2024-04-15 21:26:59 -04:00
Ziktofel
5da3a40964 SC2 Documentation: Fix the page titles (#3074) 2024-04-16 02:55:36 +02:00
Trevor L
a1ef25455b Hylics 2: Fix logic for medallions in vault (#3148) 2024-04-16 02:54:35 +02:00
Fabian Dill
feb62b4af2 Core: increment version (#3144) 2024-04-16 02:53:12 +02:00
Scipio Wright
6dbeb6c658 TUNIC: Fix chest in incorrect region, incorrect key requirement (#3132) 2024-04-14 21:14:16 -05:00
Fabian Dill
09abc5beaa Core: add visibility attribute to Option (#3125) 2024-04-14 20:49:43 +02:00
Fabian Dill
fb1cf26118 SNIClient/LttP: modern SNI prevents payload overflow (#2523) 2024-04-14 20:40:09 +02:00
Aaron Wagener
842a15fd3c Core: replace Location.event with advancement property (#2871) 2024-04-14 20:37:48 +02:00
Fabian Dill
f67e8497e0 kvui: use all flags in Item Class tooltip (#3011) 2024-04-14 20:36:55 +02:00
Fabian Dill
19f1b265b1 LttP: deprioritize locked locations for ingame hints (#3127) 2024-04-14 20:36:36 +02:00
Fabian Dill
1c14d1107f Subnautica: filler items distribution (#3104) 2024-04-14 20:36:25 +02:00
Fabian Dill
7b3727e945 CommonClient: set max_size to 16 MB (#3124) 2024-04-14 20:36:08 +02:00
Justus Lind
d1274c12b9 Muse Dash: Add filler items and rework generation balance (#2809) 2024-04-14 20:23:13 +02:00
NewSoupVi
7c44d749d4 MultiServer: Support location name groups in !missing and !checked commands (#2538) 2024-04-14 20:22:12 +02:00
Silvris
f1765899c4 MultiServer: add all worlds goal completion message (#2956) 2024-04-14 20:16:45 +02:00
PoryGone
87b9f4a6fa Spoiler: Display all precollected items in the Spoiler Log (#2928) 2024-04-14 20:05:16 +02:00
Scipio Wright
480c15eea0 TUNIC: Fix entrance rule for unrestricted + ladders - entrance rando (#3076) 2024-04-14 19:59:34 +02:00
Alchav
d4ec4d32f0 ALTTP: Bomb Walls Logic Fixes (#3130) 2024-04-14 17:30:40 +02:00
Bryce Wilson
50fb70d832 BizHawkClient: Add error message if patching fails (#2877) 2024-04-14 03:26:25 +02:00
zig-for
ca5c0d9eb8 LADX: Add "boots controls" option (#2085) 2024-04-14 03:21:55 +02:00
Aaron Wagener
98e2d89a1c Core: Let location name groups work with /hint_location (#2814) 2024-04-14 02:25:27 +02:00
PinkSwitch
5bda265f43 Yoshi's Island: Fix Outdated Connection Setup (#3113) 2024-04-14 02:23:59 +02:00
NewSoupVi
9ef1fa825d The Witness: Rename "Town Windmill Entry" to "Windmill Entry" (#3081) 2024-04-14 02:21:18 +02:00
Seldom
f5ff005360 Terraria: Crate logic (#2841) 2024-04-14 02:18:02 +02:00
Scipio Wright
fb3035a78b TUNIC: Some cleanup (#3115) 2024-04-14 02:06:06 +02:00
Ziktofel
3d5c21cec5 SC2: Remove the deprecated data version attribute in order to avoid cached old item names (#3040) 2024-04-14 01:44:51 +02:00
Scipio Wright
feaae1db12 Noita: Do some cleanup to make mypy happy (#3114) 2024-04-14 01:21:20 +02:00
Phaneros
56ec0e902d sc2: Fixing mission levels not counting towards the level 35 threshold to unlock primal kerrigan (#3109) 2024-04-14 01:09:01 +02:00
agilbert1412
c8fd42f938 Stardew Valley: Remove early shipping bin documentation (#3126) 2024-04-14 00:46:41 +02:00
Star Rauchenberger
e5eb54fb27 The Witness: Migrate joke hints to the client (#3049) 2024-04-14 00:46:11 +02:00
Scipio Wright
fbeba1e470 TUNIC: Error catching for logic bugs in ER (#3082) 2024-04-14 00:20:52 +02:00
Star Rauchenberger
11073dfdac Lingo: Remove unnecessary player_logic parameters (#3054)
A world's player_logic is accessible from the LingoWorld object, so it's not necessary to also pass the LingoPlayerLogic object through every function that uses both.
2024-04-14 00:20:31 +02:00
Alchav
7e660dbd23 Pokémon Red and Blue: 0.4.5 Fixes (#3106) 2024-04-13 17:58:50 +02:00
Exempt-Medic
1fc2c5ed4b Core: Getting rid of forfeit_mode (#3099) 2024-04-12 21:25:33 +02:00
Chris Wilson
242126b4b2 ArchipIDLE 2024 (#3079)
* Update item pool to include 25 jokes and videos as progression items, as well as a progression GeroCities profile

* Fix a bug in Items.py causing item names to be appended inappropriately

* Remove unnecessary import

* Change item pool to have 50 jokes and 20 motivational videos

* Adjust item pool to have 40 of both jokes and videos

* Fix imports to allow compressing for distribution as a .apworld
2024-04-12 00:32:10 -04:00
Ziktofel
30dda013de SC2: Fix Typos in location names (#3108) 2024-04-12 03:01:12 +02:00
Scipio Wright
ea4d0abb7f Webhost: Fix a typo on Start Playing page (#3122)
* add an or

* Changed the wording to account for uploading multiple files
2024-04-11 19:31:42 -04:00
PoryGone
9bbc49204d DKC3: Fix List Out of Range Error on Level Shuffle Hint extension (#3077) 2024-04-12 00:53:52 +02:00
Ziktofel
b97cee4372 SC2: Fix vanilla mission order connection (#3101) 2024-04-12 00:52:27 +02:00
Aaron Wagener
5dcafac861 Core: add Location.is_event property (#2968) 2024-04-12 00:49:22 +02:00
Ziktofel
8d28c34f95 SC2: Fix unused_items refill to respect item dependencies. (#3116) 2024-04-12 00:46:15 +02:00
Justus Lind
0f2bd0fb85 Muse Dash: Update songs to 4.2.0. Add a new trap. (#3053) 2024-04-12 00:44:16 +02:00
Bryce Wilson
cf59cfaad0 Pokemon Emerald: Change Ho-Oh capitalization (#3069) 2024-04-12 00:31:53 +02:00
Scipio Wright
c534cb79b5 TUNIC: Fix link to player options page in setup guide (#3086) 2024-04-12 00:30:31 +02:00
Silvris
8952fbdc03 KDL3: Fix boss access on open world disabled (#3120) 2024-04-12 00:29:40 +02:00
Ziktofel
9012afeb75 SC2: Fix possible non-determinism in goal selection (#3123) 2024-04-12 00:28:59 +02:00
NewSoupVi
401a6d9a42 The Witness: The big dumb refactor (#3007) 2024-04-12 00:27:42 +02:00
Aaron Wagener
5d4ed00452 Webhost: add file downloads to the room api endpoint (#2780) 2024-04-11 05:05:52 +02:00
Exempt-Medic
0ba6d90bb8 Fix typo (#3094) 2024-04-10 00:05:02 -04:00
Rjosephson
b007a42487 Ror2: Add progressive stages option (#2813) 2024-04-09 21:14:18 +02:00
qwint
32c92e03e7 Hollow Knight: Adding Godhome Goal Logic (#2952) 2024-04-09 21:12:50 +02:00
el-u
14437d653f lufia2ac: ability to swap party members mid-run and option to gain EXP while inactive (#2800) 2024-04-09 00:33:34 +02:00
Fabian Dill
1021df8b1b Core: remove now unused stuff in Generate.py (#3035) 2024-04-09 00:24:38 +02:00
Nicholas Saylor
569c37cb8e Core, Webhost, Docs: Replace all usages of player settings (#3067)
* Replace all usages of player settings

* Fixed line break error

* Attempt to fix line break again

* Finally figure out what Pycharm did to this file

* Pycharm search failed me

* Remove duplicate s

* Update ArchipIdle

* Revert random newline changes from Pycharm

* Remove player settings from fstrings and rename --samesettings to --sameoptions

* Finally get PyCharm to not auto-format my commits, randomly inserting the newlines

* Removing player-settings

* Missed one

* Remove final line break error
Co-authored-by: Exempt-Medic <60412657+exempt-medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Exempt-Medic <ExemptMedic@Gmail.com>
2024-04-06 19:25:26 -04:00
Robyn (Reckoner)
b296f64d3c fixing minor typos on options (#3080) 2024-04-06 19:17:48 -04:00
Alchav
8d9bd0135e LTTP: Fix Bug With Custom Resource Spending (#3105) 2024-04-06 19:53:20 +02:00
Fabian Dill
885fb4aabe LttP: fix fix fake world always applying 2024-04-06 17:40:52 +02:00
Fabian Dill
3c564d7b96 WebHost: allow deleting Rooms and Seeds, as well as their associated data (#3071) 2024-04-02 16:45:07 +02:00
Alchav
5e5792009c LttP: delete playerSettings.yaml (#3062) 2024-04-01 19:08:21 +02:00
CaitSith2
9aeeeb077a ALttP: Re-mark light/dark world regions after applying plando connections (#2964) 2024-04-01 15:07:56 +02:00
Bryce Wilson
35458380e6 Pokemon Emerald: Fix wonder trade race condition (#2983) 2024-04-01 15:07:11 +02:00
Alchav
4ac1866689 ALTTP: Skull Woods Inverted fix (#2980) 2024-04-01 15:06:31 +02:00
Fabian Dill
4aa03da66e Factorio: fix attempting to create savegame with not filename safe characters (#2842) 2024-04-01 15:06:02 +02:00
Silvris
24a03bc8b6 KDL3: fix shuffled animals not actually being random (#3060) 2024-04-01 15:02:26 +02:00
Aaron Wagener
f813a7005f The Messenger: update docs formatting and fix outdated info (#3033)
* The Messenger: update docs formatting and fix outdated info

* address review feedback

* 120 chars
2024-03-31 18:11:10 +02:00
LiquidCat64
2a0b7e0def CV64: A couple of very small docs corrections. (#3057) 2024-03-31 17:55:55 +02:00
Ixrec
03d47e460e A Short Hike: Clarify installation instructions (#3058)
* Clarify installation instructions

* don't mention 'config' folder since it isn't created until the game starts
2024-03-31 17:55:08 +02:00
Silvris
e546c0f7ff Yoshi's Island: add patch suffix (#3061) 2024-03-31 17:50:31 +02:00
Bryce Wilson
2ec93ba82a Pokemon Emerald: Fix inconsistent location name (#3065) 2024-03-31 17:48:59 +02:00
Aaron Wagener
4e3d396394 The Messenger: Fix precollected notes not being removed from the itempool (#3066)
* The Messenger: fix precollected notes not being properly removed from pool

* The Messenger: bump required client version
2024-03-31 17:47:11 +02:00
Fabian Dill
72c53513f8 WebHost: fix /check creating broken yaml files if files don't end with a newline (#3063) 2024-03-31 03:57:59 +02:00
Aaron Wagener
b7ac6a4cbd The Messenger: Fix various portal shuffle issues (#2976)
* put constants in a bit more sensical order

* fix accidental incorrect scoping

* fix plando rules not being respected

* add docstrings for the plando functions

* fix the portal output pools being overwritten

* use shuffle and pop instead of removing by content so plando can go to the same area twice

* move portal pool rebuilding outside mapping creation

* remove plando_connection cleansing since it isn't shared with transition shuffle
2024-03-30 02:14:53 +01:00
Zach Parks
5f0112e783 Tracker: Add starting inventory to trackers and received items table. (#3051) 2024-03-29 19:13:51 -05:00
Aaron Wagener
bb481256de Core: Make fill failure error more human parseable (#3023) 2024-03-28 21:48:40 -05:00
Aaron Wagener
301d9de975 Docs: adding games rework (#2892)
* Docs: complete adding games.md rework

* remove all the now unused images

* review changes

* address medic's review

* address more comments
2024-03-29 01:31:59 +01:00
Trevor L
9dc708978b Hylics 2: Fix invalid multiworld data, use self.random instead of self.multiworld.random (#3001)
* Hylics 2: Fixes

* Rewrite loop
2024-03-29 01:26:58 +01:00
Bryce Wilson
4391d1f4c1 Pokemon Emerald: Fix opponents learning non-randomized TMs (#3025) 2024-03-29 01:05:39 +01:00
black-sliver
5d9d4ed9f1 SoE: update to pyevermizer v0.48.0 (#3050) 2024-03-29 01:01:31 +01:00
Scipio Wright
c97215e0e7 TUNIC: Minor refactor of the vanilla_portals function (#3009)
* Remove unused, change an if to an elif

* Remove unused import
2024-03-28 22:23:37 +01:00
Alchav
eb66886a90 SC2: Don't Filter Excluded Victory Locations (#3018) 2024-03-28 22:23:01 +01:00
Fabian Dill
de860623d1 Core: differentiate between unknown worlds and broken worlds in error message (#2903) 2024-03-28 22:21:56 +01:00
Bryce Wilson
74b2bf5161 Pokemon Emerald: Exclude norman trainer location during norman goal (#3038) 2024-03-28 22:20:55 +01:00
BadMagic100
74ac66b032 Hollow Knight: 0.4.5 doc revamp and default options tweaks (#2982)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-28 10:49:19 -05:00
Silvris
80d7ac4164 KDL3: RC1 Fixes and Enhancement (#3022)
* fix cloudy park 4 rule, zero deathlink message

* remove redundant door_shuffle bool

when generic ER gets in, this whole function gets rewritten. So just clean it a little now.

* properly fix deathlink messages, fix fill error

* update docs
2024-03-28 15:41:32 +01:00
Ziktofel
77311719fa SC2: Fix HERC upgrades (#3044) 2024-03-28 15:38:34 +01:00
NewSoupVi
cfc1541be9 Docs: Mention the "last received item index" paradigm in the network protocol docs (#2989)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-28 09:19:32 -05:00
Scipio Wright
4d954afd9b TUNIC: Add link to AP plando guide to connection plando section of game page (#2993) 2024-03-28 09:11:20 -05:00
Nicholas Saylor
17748a4bf1 Launcher, Docs: Update UI and Set-Up Guide to Reference Options (#2950) 2024-03-28 09:00:10 -05:00
Entropynines
9182fe563f README: Remove outdated information about launchers (#2966)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-28 08:56:35 -05:00
t3hf1gm3nt
bcf223081f TLOZ: Fix markdown issue with game info page (#2985) 2024-03-28 08:54:56 -05:00
Exempt-Medic
fa93488f3f Docs: Consistent naming for "connection plando" (#2994) 2024-03-28 08:46:00 -05:00
chandler05
db15dd4bde A Short Hike: Fix incorrect info in docs (#3016) 2024-03-28 08:45:19 -05:00
PoryGone
01cdb0d761 SMW: Update World Doc for v2.0 Features (#3034)
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2024-03-28 08:44:23 -05:00
panicbit
d0ac2b744e LADX: fix local and non-local instrument placement (#2987)
* LADX: fix local and non-local instrument placement

* change confusing variable name
2024-03-28 10:11:26 +01:00
Jérémie Bolduc
14f5f0127e Stardew Valley: Fix potential soft lock with vanilla tools and entrance randomizer + Performance improvement for vanilla tool/skills (#3002)
* fix vanilla tool fishing rod requiring metal bars
fix vanilla skill requiring previous level (it's always the same rule or more restrictive)

* add test to ensure fishing rod need fish shop

* fishing rod should be indexed from 0 like a mentally sane person would do.

* fishing rod 0 isn't real, but it definitely can hurt you.

* reeeeeeeee
2024-03-28 09:42:35 +01:00
Bryce Wilson
cf133dde72 Pokemon Emerald: Fix typo (#3020) 2024-03-28 09:32:27 +01:00
Jérémie Bolduc
ca18121811 Stardew Valley: Fix generation fail with SVE and entrance rando when Wizard Tower is in place of Sprite Spring (#2970) 2024-03-28 09:27:49 +01:00
NewSoupVi
1d4512590e requirements.txt: _ instead of - to make PyCharm happy (#3043) 2024-03-27 21:09:09 +01:00
agilbert1412
f7b415dab0 Stardew valley: Game version documentation (#2990)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-26 11:40:58 -05:00
LiquidCat64
702f006c84 CV64: Change all mentions of "settings" to "options" and fix a broken link (#3015) 2024-03-26 14:31:36 +01:00
Yussur Mustafa Oraji
98ce8f8844 sm64ex: New Options API and WebHost fix (#2979) 2024-03-26 14:29:25 +01:00
Scipio Wright
ea47b90367 TUNIC: You can grapple down here without the ladder, neat (#3019) 2024-03-26 14:25:41 +01:00
agilbert1412
bf3856866c Stardew Valley: presets with some of the new available values for existing settings to make them more accurate (#3014) 2024-03-24 21:53:49 +01:00
Phaneros
c0368ae0d4 SC2: Fixed missing upgrade from custom tracker (#3013) 2024-03-24 21:53:20 +01:00
Salzkorn
36c83073ad SC2 Tracker: Fix grouped items pointing at wrong item IDs (#2992) 2024-03-24 21:52:41 +01:00
Ziktofel
2b24539ea5 SC2 Tracker: Use level tinting to let the player know which level he has of Replenishable Magazine (#2986) 2024-03-24 21:52:16 +01:00
Ziktofel
7e904a1c78 SC2: Fix Kerrigan presence resolving when deciding which races should be used (#2978) 2024-03-24 21:51:46 +01:00
Alchav
bdd498db23 ALTTP: Fix #2290's crashes (#2973) 2024-03-22 21:36:27 +01:00
PinkSwitch
355223b8f0 Yoshi's Island: Implement New Game (#2141)
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-22 21:35:00 +01:00
NewSoupVi
aaa3472d5d The Witness: Fix seed bleed issue (#3008) 2024-03-22 21:30:51 +01:00
chandler05
96d93c1ae3 A Short Hike: Add option to customize filler coin count (#3004)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-22 21:30:23 +01:00
Silvris
ca549df20a CommonClient: fix hint tab overlapping (#2957)
Co-authored-by: Remy Jette <remy@remyjette.com>
2024-03-22 21:29:24 +01:00
Star Rauchenberger
44988d430d Lingo: Add trap weights option (#2837) 2024-03-22 21:28:41 +01:00
Danaël V
11b32f17ab Docs: replacing "setting" to "option" in world docs (#2622)
* Update contributing.md

* Update contributing.md

* Update contributing.md

* Update contributing.md

* Update contributing.md

* Update contributing.md

Added non-AP World specific information

* Update contributing.md

Fixed broken link

* Some minor touchups

* Update Contributing.md

Draft for version with picture

* Update contributing.md

Small word change

* Minor updates for conciseness, mostly

* Changed all instances of settings to options in info and setup guides

I combed through all world docs and swapped "setting" to "option" when this was refering to yaml options.
I also changed a leftover "setting" in option.py

* Update contributing.md

* Update contributing.md

* Update setup_en.md

Woops I forgot one

* Update Options.py

Reverted changes regarding options.py

* Update worlds/noita/docs/en_Noita.md

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md

revert change waiting for that page to be updated

* Update worlds/witness/docs/setup_en.md

* Update worlds/witness/docs/en_The Witness.md

* Update worlds/soe/docs/multiworld_en.md

Fixed Typo

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update worlds/witness/docs/en_The Witness.md

* Update worlds/adventure/docs/en_Adventure.md

* Update worlds/witness/docs/setup_en.md

* Updated Stardew valley to hopefully get rid of the merge conflicts

* Didn't work :dismay:

* Delete worlds/sc2wol/docs/setup_en.md

I think this will fix the merge issue

* Now it should work

* Woops

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-03-22 17:46:14 +01:00
Silvris
218cd45844 APProcedurePatch: fix RLE/COPY incorrect sizing (#3006)
* change class variables to instance variables

* Update worlds/Files.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update worlds/Files.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* move required_extensions to tuple

* fix missing tuple ellipsis

* fix classvar mixup

* rename tokens to _tokens. use hasattr

* type hint cleanup

* Update Files.py

* check using isinstance instead

* Update Files.py

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-03-22 09:02:38 +01:00
Exempt-Medic
4196bde597 Docs: Fixing special_range_names example (#3005) 2024-03-21 21:38:36 +01:00
Star Rauchenberger
40f843f54d Lingo: Minor game data fixes (#3003) 2024-03-21 17:00:53 +01:00
GodlFire
da333fbb0c Shivers: Adds missing logic rule for skull dial door location (#2997) 2024-03-21 16:52:16 +01:00
NewSoupVi
43084da23c The Witness: Fix newlines in Witness option tooltips (#2971) 2024-03-21 16:51:29 +01:00
Scipio Wright
14816743fc TUNIC: Shuffle Ladders option (#2919) 2024-03-21 16:50:07 +01:00
Star Rauchenberger
30a0aa2c85 Lingo: Add item/location groups (#2789) 2024-03-21 16:46:53 +01:00
Silvris
f4b7c28a33 APProcedurePatch: hotfix changing class variables to instance variables (#2996)
* change class variables to instance variables

* Update worlds/Files.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update worlds/Files.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* move required_extensions to tuple

* fix missing tuple ellipsis

* fix classvar mixup

* rename tokens to _tokens. use hasattr

* type hint cleanup

* Update Files.py

* check using isinstance instead

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-03-20 23:45:32 +01:00
chandler05
12864f7b24 A Short Hike: Implement New Game (#2577) 2024-03-20 22:44:09 +01:00
LiquidCat64
db02e9d2aa Castlevania 64: Implement New Game (#2472) 2024-03-20 22:03:25 +01:00
Jérémie Bolduc
32315776ac Stardew Valley: Fix extended family legendary fishes being locations with fishsanity set to exclude legendary (#2967) 2024-03-20 21:57:45 +01:00
Magnemania
e9620bea77 SM64: Goal Logic and Hint Bugfixes (#2886) 2024-03-20 21:56:00 +01:00
qwint
183ca35bba CommonClient: Port Casting Bug (#2975) 2024-03-20 14:39:37 +01:00
TheLX5
fcaaa197a1 SMW: Fixes for Bowser being defeatable on Egg Hunt and CI2 DC room access (#2981) 2024-03-20 13:56:19 +01:00
TheLX5
8f7b63a787 SMW: Blocksanity logic fixes (#2988) 2024-03-20 13:56:04 +01:00
Scipio Wright
6f64bb9869 Noita: Remove newline from option description so it doesn't look bad on webhost (#2969) 2024-03-20 13:46:31 +01:00
Bryce Wilson
d0a9d0e2d1 Pokemon Emerald: Bump required client version (#2963) 2024-03-20 13:43:13 +01:00
Silvris
94650a02de Core: implement APProcedurePatch and APTokenMixin (#2536)
* initial work on procedure patch

* more flexibility

load default procedure for version 5 patches
add args for procedure
add default extension for tokens and bsdiff
allow specifying additional required extensions for generation

* pushing current changes to go fix tloz bug

* move tokens into a separate inheritable class

* forgot the commit to remove token from ProcedurePatch

* further cleaning from bad commit

* start on docstrings

* further work on docstrings and typing

* improve docstrings

* fix incorrect docstring

* cleanup

* clean defaults and docstring

* define interface that has only the bare minimum required
for `Patch.create_rom_file`

* change to dictionary.get

* remove unnecessary if statement

* update to explicitly check for procedure, restore compatible version and manual override

* Update Files.py

* remove struct uses

* ensure returning bytes, add token type checking

* Apply suggestions from code review

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* pep8

---------

Co-authored-by: beauxq <beauxq@yahoo.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2024-03-19 23:08:29 +01:00
PoryGone
8a8263fa61 SMW: Increment Required Client Version (#2962) 2024-03-15 18:02:25 +01:00
NewSoupVi
9efc7bae40 The Witness: Add junk hint for Zork: Grand Inquisitor (#2961) 2024-03-15 17:54:21 +01:00
Scipio Wright
e6198585c8 TUNIC: Implement support for connection plando (#2864) 2024-03-15 17:52:05 +01:00
Nicholas Brochu
2a8784ef72 Zork Grand Inquisitor: Implement New Game (#2539)
Adds Archipelago support for Zork Grand Inquisitor, the 1997 point-and-click PC adventure game.

The client (based on `CommonClient`), on top of its regular Archipelago duties, fully handles the randomization of the game and the monitoring / modification of the game state. No game modding needed at all; the player is ready to play an Archipelago seed if they can play the vanilla game through ScummVM.

The "reverse engineering" (there's likely a better term for this...) of the game is my own original work and I included an MIT license at the root of my world directory.

A PopTracker pack was also created to help people learn the game: https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker
2024-03-15 17:35:37 +01:00
Ziktofel
e0e9fdd86a SC2: Multi-campaign (#2954)
Adds HotS, LotV and NCO campaigns to SC2 game.
The world's name has changed to reflect that (it's not only Wings of Liberty now)
The client was patched in a way that can still join to games generated prior this change
---------

Co-authored-by: Magnemania <magnemight@gmail.com>
Co-authored-by: EnvyDragon <138727357+EnvyDragon@users.noreply.github.com>
Co-authored-by: Matthew <matthew.marinets@gmail.com>
Co-authored-by: hopop201 <benjy.hopop201@gmail.com>
Co-authored-by: Salzkorn <salzkitty@gmail.com>
Co-authored-by: genderdruid <pallyoffail@gmail.com>
Co-authored-by: MadiMadsen <137329235+MadiMadsen@users.noreply.github.com>
Co-authored-by: neocerber <neocerber@gmail.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-03-15 17:33:03 +01:00
agilbert1412
ed9cbfecb9 Stardew Valley: Added a Great Combat requirement to an entrance that could block its own key (#2959) 2024-03-15 17:31:01 +01:00
espeon65536
d1ef198566 OoT: Entrance Spoiler Fixes (#2500) 2024-03-15 08:05:44 -05:00
NewSoupVi
1324bccc89 The Witness: Add newly submitted junk hints (#2949) 2024-03-15 14:00:10 +01:00
PoryGone
cafacfe754 SA2B: Add CHANGELOG.md (#2945) 2024-03-15 07:58:20 -05:00
Fabian Dill
e1038b4431 Core: increment version (#2958) 2024-03-15 13:58:08 +01:00
PoryGone
2e8a84121e DKC3: Add CHANGELOG.md (#2946) 2024-03-15 07:57:43 -05:00
PoryGone
0342c1e09d Celeste 64: Add CHANGELOG.md (#2948) 2024-03-15 07:57:05 -05:00
PoryGone
f330e5835c SMW: Add CHANGELOG.md (#2947) 2024-03-15 07:56:12 -05:00
Silent
329a2f9175 TUNIC: Updated display name for a few options (#2953) 2024-03-15 13:53:41 +01:00
Silvris
31249ec6e2 Launcher: make scrollbar more prominent (#2955) 2024-03-15 13:47:31 +01:00
agilbert1412
52e65e208e Stardew Valley: 5.x.x - The Allsanity Update (#2764)
Major Content update for Stardew Valley, including the following features

- Major performance improvements all across the Stardew Valley apworld, including a significant reduction in the test time
- Randomized Farm Type
- Bundles rework (Remixed Bundles and Missing Bundle!)
- New Settings:
  * Shipsanity - Shipping individual items
  * Monstersanity - Slaying monsters
  * Cooksanity - Cooking individual recipes
  * Chefsanity - Learning individual recipes
  * Craftsanity - Crafting individual items
- New Goals:
  * Protector of the Valley - Complete every monster slayer goal
  * Full Shipment - Ship every item
  * Craftmaster - Craft every item
  * Gourmet Chef - Cook every recipe
  * Legend - Earn 10 000 000g
  * Mystery of the Stardrops - Find every stardrop (Maguffin Hunt)
  * Allsanity - Complete every check in your slot
- Building Shuffle: Cheaper options
- Tool Shuffle: Cheaper options
- Money rework
- New traps
- New isolated checks and items, including the farm cave, the movie theater, etc
- Mod Support: SVE [Albrekka]
- Mod Support: Distant Lands [Albrekka]
- Mod Support: Hat Mouse Lacey [Albrekka]
- Mod Support: Boarding House [Albrekka]

Co-authored-by: Witchybun <elnendil@gmail.com>
Co-authored-by: Witchybun <96719127+Witchybun@users.noreply.github.com>
Co-authored-by: Jouramie <jouramie@hotmail.com>
Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
2024-03-15 13:05:14 +01:00
qwint
f7da833572 CommonClient: Don't retry connection when connection details are invalid (#2831) 2024-03-15 09:27:42 +01:00
Star Rauchenberger
afe7aea536 Lingo: Pre-compile datafile to improve loading time (#2829) 2024-03-15 09:26:00 +01:00
Doug Hoskisson
e534abeab0 Core: add layer for patches that don't use Patch.py (#2889)
* Core: add layer for patches that don't use `Patch.py`

* bump container version

* APAutoPatchInterface name

* mystic quest change

* OoT and Adventure changes

* missed name in docstring

* container version compatibility
2024-03-14 22:29:29 +01:00
Bryce Wilson
fa233b2583 Pokemon Emerald: v2 Update (#2918) 2024-03-14 12:37:10 +01:00
Silvris
3e3965272d KDL3: Ensure all abilities accessible on non-minimal (#2929) 2024-03-13 21:05:38 +01:00
qwint
72e6383cc7 HK: Removes Vanilla Items from ItemPool and Uses Grimmchild1 when relevant (#2898) 2024-03-13 06:45:43 -05:00
black-sliver
67ed0fdca5 CI: update actions (#2943) 2024-03-13 08:25:51 +01:00
Aaron Wagener
c4ec8682d5 Core: fix incorrect ordering on the always_allow static method (#2938) 2024-03-12 23:29:32 +01:00
Alchav
1705213353 FFMQ: Update Map Shuffle Seed description (#2658)
* Update Map Shuffle Seed description

* Update worlds/ffmq/Options.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-12 23:21:58 +01:00
Kory Dondzila
d953927b3a Shivers: Renaming for clarity and consistency (#2869)
* Moves plaque location to front for better tracker referencing.

* Tiki should be Shaman.

* Hanging should be Gallows.

* Merrick spelling.

* Clarity change.
2024-03-12 23:17:18 +01:00
black-sliver
ecd84fd1ca CI: build: create setup (#2936)
* CI: build: create setup

also add /DNO_SIGNTOOL to inno_setup.iss

* CI: build: trigger when changing setup-related files
2024-03-12 22:27:17 +01:00
Silvris
fb9ef19c15 Core: add list/dict merging feature to triggers (#2793)
* proof of concept

* add dict support, block top/game level merge

* prevent key error when option being merged is new

* update triggers guide

* Add documentation about add/remove/replace

* move to trailing name instead of proper tag

* update docs

* confirm types

* Update Utils.py

* Update Generate.py

* pep8

* move to + syntax

* forgot to support sets

* specify received type of type error

* Update Generate.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Apply suggestion from review

* add test for update weights

* move test to new test case

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-03-12 22:08:12 +01:00
Doug Hoskisson
03d403ff51 Core: typing for Option.default and a few other ClassVars (#2899)
* Core: typing for `Option.default` and a few other `Option` class variables

This is a replacement for https://github.com/ArchipelagoMW/Archipelago/pull/2173

You can read discussion there for issues we found for why we can't have more specific typing on `default`

instead of setting a default in `Option` (where we don't know the type), we check in the metaclass to make sure they have a default.

* NumericOption doesn't need the type annotation that brings out the mypy bug

* SoE default ClassVar
2024-03-12 22:03:57 +01:00
PoryGone
f8d5fe0e1e SMW: v2.0 Content Update (#2762)
Changelog:

Features:
- New optional Location Checks
  - 3-Up Moons
  - Hidden 1-Ups
  - Bonus Blocks
  - Blocksanity
    - All blocks that contain coins or items are included, with the exception of:
      - Blocks in Top Secret Area & Front Door/Bowser Castle
      - Blocks that are unreachable without glitches/unreasonable movement
- New Items
  - Special Zone Clear
  - New Filler Items
    - 1 Coin
    - 5 Coins
    - 10 Coins
    - 50 Coins
  - New Trap Items
    - Reverse Trap
    - Thwimp Trap
- SFX Shuffle
- Palette Shuffle Overhaul
  - New Curated Palette can now be used for the Overworld and Level Palette Shuffle options
  - Foreground and Background Shuffle options have been merged into a single setting
- Max possible Yoshi Egg value is 255
  - UI in-game is updated to handle 3-digits
  - New `Display Received Item Popups` option: `progression_minus_yoshi_eggs`

Quality of Life:
- In-Game Indicators are now displayed on the map screen for location checks and received items
- In-level sprites are displayed upon receiving certain items
- The Camera Scroll unlocking is now only enabled on levels where it needs to be
- SMW can now handle receiving more than 255 items
- Significant World Code cleanup
  - New Options API
  - Removal of `world: MultiWorld` across the world
- The PopTracker pack now has tabs for every level/sublevel, and can automatically swap tabs while playing if connected to the server

Bug Fixes:
- Several logic tweaks/fixes

"Major credit to @TheLX5 for being the driving force for almost all of this update. We've been collaborating on design and polish of the features for the last few months, but all of the heavy lifting was all @TheLX5."
2024-03-12 22:00:13 +01:00
Remy Jette
b6b88070be CommonClient: Fix item link group name when member slot name contains brackets (#2794) 2024-03-12 21:13:52 +01:00
Fabian Dill
a6e1ea8c48 CommonClient: use rich text for /received (#2715) 2024-03-12 20:40:58 +01:00
Alchav
2692604c09 Core: String comparison with FreeText class (#2942) 2024-03-12 20:40:16 +01:00
NewSoupVi
4bf676e588 The Witness: Obelisk Keys (#2805) 2024-03-12 20:04:13 +01:00
Fabian Dill
ae6c16bde1 MultiServer: send new read_hints datastore values on change (#2558) 2024-03-12 19:58:02 +01:00
Fabian Dill
c795c72471 kvui: allow sorting hints in the hint tab (#2684) 2024-03-12 14:52:57 +01:00
NewSoupVi
30ad2aa4a8 The Witness: Don't unnecessarily break people's 0.4.4 yamls (#2940) 2024-03-12 14:51:10 +01:00
Aaron Wagener
51243abea1 Docs: improve AutoWorld method docstrings (#2509)
* clarify some autoworld docstrings

* revert accidental change
2024-03-12 09:27:41 +01:00
BadMagic100
cb2c00f644 CI: Don't auto-remove content based labels (#2941) 2024-03-12 09:11:13 +01:00
Aaron Wagener
6d3f7865ff The Messenger: fix items accessibility reachability bug due to new rules (#2937) 2024-03-12 00:55:28 +01:00
JaredWeakStrike
a7384b4b63 KH2: Update all instances of multiworld.option_name to option.option_name (#2634)
* update the multiworld to options

* Update worlds/kh2/Rules.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* does this work

* namine sketches

* wrong branch :)

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-12 00:52:16 +01:00
CubeSoldier
3d56f3c096 Docs: Added snes9x-nwa as recommended emulator to the setup guides for SNES games (#1778)
* Added snes9x-nwa as recommended emulator to the setup guides

* Removed snes9x-nwa from the setup guides of DKC3 and SMW

* Update worlds/alttp/docs/multiworld_en.md

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>

* Removed duplicate text
Minor grammar and spelling fixes

* Unified required software for SM, SMZ3 and SoE with ALTTP

* Added instructions for usage of BSNES-Plus for ALTTP, SM and SMZ3

---------

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
2024-03-12 00:42:56 +01:00
Jérémie Bolduc
a3125cb06e Core: Fix OptionList and OptionSet to allow Iterable of Iterable (#2911)
* fix, maybe

* typegard for iterable of any

* wow I'm so tired I just changed the method name without changing what it actually does...

* also exclude bytes in is_iterable_but_str

* apply pr comments

* Update Utils.py

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* Revert "also exclude bytes in is_iterable_but_str"

This reverts commit cf087d2ee2.

---------

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2024-03-12 00:30:14 +01:00
Silvris
d3019421de KDL3: fix invalid inno_setup components and deathlink messages (#2922)
* remove component checking

* fix missing deathlink messages

* move reads under deathlink check
2024-03-11 23:26:21 +01:00
Aaron Wagener
d20d09e682 The Messenger: content update (#2823)
* map option objects to a `World.options` dict

* convert RoR2 to options dict system for testing

* add temp behavior for lttp with notes

* copy/paste bad

* convert `set_default_common_options` to a namespace property

* reorganize test call order

* have fill_restrictive use the new options system

* update world api

* update soe tests

* fix world api

* core: auto initialize a dataclass on the World class with the option results

* core: auto initialize a dataclass on the World class with the option results: small tying improvement

* add `as_dict` method to the options dataclass

* fix namespace issues with tests

* have current option updates use `.value` instead of changing the option

* update ror2 to use the new options system again

* revert the junk pool dict since it's cased differently

* fix begin_with_loop typo

* write new and old options to spoiler

* change factorio option behavior back

* fix comparisons

* move common and per_game_common options to new system

* core: automatically create missing options_dataclass from legacy option_definitions

* remove spoiler special casing and add back the Factorio option changing but in new system

* give ArchipIDLE the default options_dataclass so its options get generated and spoilered properly

* reimplement `inspect.get_annotations`

* move option info generation for webhost to new system

* need to include Common and PerGame common since __annotations__ doesn't include super

* use get_type_hints for the options dictionary

* typing.get_type_hints returns the bases too.

* forgot to sweep through generate

* sweep through all the tests

* swap to a metaclass property

* move remaining usages from get_type_hints to metaclass property

* move remaining usages from __annotations__ to metaclass property

* move remaining usages from legacy dictionaries to metaclass property

* remove legacy dictionaries

* cache the metaclass property

* clarify inheritance in world api

* move the messenger to new options system

* add an assert for my dumb

* update the doc

* rename o to options

* missed a spot

* update new messenger options

* comment spacing

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* fix tests

* fix missing import

* make the documentation definition more accurate

* use options system for loc creation

* type cast MessengerWorld

* fix typo and use quotes for cast

* LTTP: set random seed in tests

* ArchipIdle: remove change here as it's default on AutoWorld

* Stardew: Need to set state because `set_default_common_options` used to

* The Messenger: update shop rando and helpers to new system; optimize imports

* Add a kwarg to `as_dict` to do the casing for you

* RoR2: use new kwarg for less code

* RoR2: revert some accidental reverts

* The Messenger: remove an unnecessary variable

* remove TypeVar that isn't used

* CommonOptions not abstract

* Docs: fix mistake in options api.md

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* create options for item link worlds

* revert accidental doc removals

* Item Links: set default options on group

* Messenger: Limited Movement option first draft

* The Messenger: add automated setup through the launcher

* drop tomllib

* don't uselessly import launcher

* The Messenger: fix missing goal requirement for power seal hunt

* make hard mode goal harder

* make fire seal a bit more lenient

* have limited movement force minimal accessibility

* add an early meditation option

* clean up precollected notes tests a bit

* add linux support

* add steam deck support

* await monokickstart

* minor styling cleanup

* more minor styling cleanup

* Initial implementation of Generic ER

* Move ERType to Entrance.Type, fix typing imports

* updates based on testing (read: flailing)

* Updates from feedback

* Various bug fixes in ERCollectionState

* Use deque instead of queue.Queue

* Allow partial entrances in collection state earlier, doc improvements

* Prevent early loops in region graph, improve reusability of ER stage code

* Typos, grammar, PEP8, and style "fixes"

* use RuntimeError instead of bare Exceptions

* return tuples from connect since it's slightly faster for our purposes

* move the shuffle to the beginning of find_pairing

* do er_state placements within pairing lookups to remove code duplication

* requested adjustments

* Add some temporary performance logging

* Use CollectionState to track available exits and placed regions

* remove seal shuffle option

* some cleanup stuff

* portal rando progress

* pre-emptive region creation

* seals need to be in the datapackage

* put mega shards in old order

* fix typos and make it actually work

* fix more missed connections and add portal events

* fix all the portal rando code

* finish initial logic implementation

* remove/comment out debug stuff

* does not actually support plando yet

* typos and fix a crash when 3 available portals was selected

* finish initial logic for all connections and remove/rename as necessary

* fix typos and add some more leniency

* move item classification determination to its own method rather than split between two spots

* super complicated solution for handling installing the alpha builds

* fix logic bugs and add a test

* implement logic to shuffle the cutscene portals even though it's probably not possible

* just use the one list

* fix some issues with the mod checking/downloading

* Core: have webhost slot name links go through the launcher so that components can use them

* add uri support to the launcher component function

* generate output file under specific conditions

* cleanup connections.py

* set topology_present to true when portals are shuffled

* add requirement for ghost pit loc since it's pretty hard without movement

* bring hard logic back

* misc cleanup

* fix asset grabbing of latest version

* implement ER

* just use the entrances for the spoiler instead of manipulating the cache

* remove test defaults

* remove excessive comprehension

* cleanup and cater data for the client

* add elemental skylands to the shuffle pools

* initial attempts at hint text

* use network items for offline seeds

* change around the offline seed data again

* move er after portal shuffle and ensure a minimal sphere 1

* Add a method to automatically disconnect entrances in a coupled-compliant way

 Update docs and cleanup todos

* Make find_placeable_exits deterministic by sorting blocked_connections set

* add more ER transitions

* fix spoiler output of portal warps

* add path to hint_data

* rename entrance to tot to be a bit clearer

* cleanup imports and update description for hard logic

* cleanup for PR to main

* missed a spot

* cleanup monokickstart

* add location_name_groups

* update docs for new setup

* client can reconnect on its own now, no need for a button.

* fix mod download link grabbing the wrong assets

* cleanup mod pulling a bit and display version it's trying to update to

* plando support

* comment out broken steam deck support

* supports plando

* satisfy flake for currently unused file

* fix the items accessibility test

* review comments

* add searing crags portal to starting portals when disabled like option says

* address sliver comments

* rip out currently unused transition shuffle

* add aerobatics warrior requirement to fire seal

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@yahoo.com>
Co-authored-by: Sean Dempsey <dempsey.sean@outlook.com>
Co-authored-by: qwint <qwint.42@gmail.com>
2024-03-11 23:23:41 +01:00
Aaron Wagener
078d793073 Tests: add test for 2-player-multiworlds (#2386)
* Tests: add test for all games multiworld and test for two player multiworld per game

* make assertSteps behave like call_all

* review improvements

* fix stage calling and loc copying in accessibility

* add docstrings

* lttp is on the options api now

* skip the all games multiworld for now. likely needs to be modified to test specific worlds

* move skip to the class
2024-03-11 23:22:30 +01:00
Aaron Wagener
5fecb7f043 LTTP: fix some hashed string comparisons (#2927) 2024-03-11 10:00:28 +01:00
chandler05
9c920fbc53 MultiServer: Improve command response to help troubleshooting (#2833)
* Update MultiServer.py

* Improve logging again

* Add to other log as well

* Update MultiServer.py
2024-03-11 09:55:22 +01:00
wildham
fed3d04c8d FF1: Fix resending items on disconnect/connect (#2817) 2024-03-11 09:51:51 +01:00
Bryce Wilson
6badc75237 BizHawkClient: Fix error logging in python 3.8 (#2930) 2024-03-11 07:16:48 +01:00
axe-y
c7e735da15 DLCQuest: progression coin bundle update (#2785) 2024-03-10 22:12:55 +01:00
Aaron Wagener
2e1a5b0e3b Core: create the per world random object in the world constructor (#2083)
* Core: create the per world random object in the world constructor

* remove the check that multiworld exists

* add a deprecation warning to per_slot_randoms

* move random import and fix conflicts

* assert worlds don't exist before setting the multiworld seed

* fix the dlcq and sdv tests

* actually use the seed
2024-03-10 18:47:45 +01:00
Seldom
b8c24def8d Terraria: Logic fix: Witch Doctor sells Bewitching Table (#2880) 2024-03-10 16:03:44 +01:00
Kappatechy
3602ed45a4 Bumper Stickers: logic fixes for "off-by-one" errors (#2855)
* Corrected logic error.

Per discussion here: https://discord.com/channels/731205301247803413/1148330200891932742/1192138309120577646

At the moment, the logic expects Treasure Bumper 2 to require 1 bumper, Treasure Bumper 3 to require 2, etc., and for Treasure Bumper 1 to be in Sphere 1. This is incorrect, each Bumper check should require 1 Bumper item of it's type.

This corrects that. I've verified I was able to generate with it by editing my apworld locally, but I'm also not a programmer and don't know anything about tests. However, I'd think this is a simple change.

* Correct logic in Bumper Sticker unit tests

Off By One errors were rampant in the Bumper Stickers unit test logic. This should correct those errors.

* Correct use of "range" function

The function setting the access rules for Treasure and Booster Bumpers was stopping one short of being applied to all the related locations. This has been corrected.

* Restoring and clarifying designer's original level access intent

The original creator of the AP version of Bumper Stickers intentionally set the Treasure Bumper requirements to logically reach each level 1 higher than the actual game requires, and logic tests were built based on this. This design decision has now been restored.

* Revert "Restoring and clarifying designer's original level access intent"

This reverts commit 5186c5fcc3.

* Correct test logic for level 5

While 33 Treasure Bumpers are generated, only 32 are needed to reach level 5. This push corrects the unit test for the level 5 checks.

* Rename generically-named variables

Change variables from generic names (x, y, n) to more meaningful names, for ease of readability.

---------

Co-authored-by: The T <thet113@gmail.com>
2024-03-10 15:36:42 +01:00
panicbit
37add8ee59 LADX: shuffle instruments (#2804)
* ladx: shuffle instruments

* set correct default for shuffled instruments
2024-03-10 14:48:00 +01:00
qwint
5a4d88d554 Clients: add /item_groups and /location_groups (#2822) 2024-03-10 14:46:44 +01:00
Robyn (Reckoner)
a4f89396d9 Docs: Fix typos in Minecraft info page (#2686)
Fixed typos in game page
2024-03-10 13:50:25 +01:00
Zach Parks
8c11c385f3 ALTTP: Fix NotImplemented error when using non-none values for timer. (#2924) 2024-03-10 01:56:57 -06:00
Aaron Wagener
939a5ec959 LTTP: remove multiworld = None (#2290) 2024-03-10 08:18:25 +01:00
axe-y
4ce58c0240 DLC Quest: AP World Status fix (#2908) 2024-03-10 08:13:52 +01:00
Remy Jette
be802b4723 Core: Remove extra " character in /forbid_release help message (#2923)
Was pointed out in the Discord: https://discord.com/channels/731205301247803413/731205301818359821/1215882443261870190
2024-03-10 08:12:55 +01:00
NewSoupVi
3c4ebb2114 The Witness: Fix... I don't know how to explain this one (#2920)
```            
for hint in generated_hints:
    hint = generated_hints.pop(0)
```

lmao
2024-03-09 00:03:02 +01:00
Seafo
3e3b4c6732 Minecraft: Add Pickaxes to the documentation (#2688) 2024-03-08 13:15:36 -08:00
Silvris
3e0ff3f72d KDL3: Post-PR adjustments (#2917)
* logic adjustments, add patch_suffix

* forgot the other two consumables
2024-03-08 07:46:14 +01:00
Silvris
b4bb88fcf7 SNIClient: dynamically generate patch file identifier (#2870)
Co-authored-by: beauxq <beauxq@yahoo.com>
2024-03-07 10:18:22 +01:00
Exempt-Medic
862d77820d Fill: Improve clarity of remaining_fill messages (#2894)
* Update Fill.py

Make the fill message more descriptive

* Removing kwarg
2024-03-07 08:48:55 +01:00
Aaron Wagener
c6b1039e0f Core: call from_any on the options class when creating item links options (#2783) 2024-03-07 08:40:09 +01:00
BadMagic100
ac791f2999 CI: Avoid race condition in labeler workflow (#2910) 2024-03-07 08:24:08 +01:00
Alchav
e33ea0147b LTTP: Missed per_slot_random change (#2907) 2024-03-06 16:43:34 +01:00
PoryGone
db30a0116e Celeste 64: Implement New Game (#2798)
Co-authored-by: chandler05 <66492208+chandler05@users.noreply.github.com>
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2024-03-05 17:55:56 -06:00
Alchav
a5a1494a96 Pokemon R/B: The Big Door Shuffle Update (#2861)
- Perhaps most critically, adds the ability for the door shuffle code to catch door shuffle exceptions and try again. Will try up to 10 times. Should mean Door Shuffle does not need to be disallowed in the big async🤞
- Door Shuffle code has been made drastically faster by searching for the first dead end instead of sorting the whole list of entrances by whether they are dead ends.
- Renames Full to Interiors, and adds a new Full door shuffle that shuffles interior-to-interior doors separately from exterior-to-interior doors.
- Adds a new Decoupled door shuffle.
- Warp Tile Shuffle now has 3 separate options, Vanilla, Shuffle, and Mixed. Shuffle shuffles the warp tiles among themselves, Mixed mixes them into the Door Shuffle pool.
- Safari Zone connections are now shuffled on Full, Insanity, and Decoupled.
- On Simple Door Shuffle, the Town Map is updated to show the new dungeon locations. The Town Map has been updated to show the locations of dungeons that previously were not shown unless you opened the map within them, and the Sea Cottage has been removed from it.
- Adds Auto Level Scaling that chooses the level scaling mode based on the Door Shuffle choice.
- Fixes issues with Flash and Fly move interventions (where it ensures an available Pokémon that can learn it is reachable depending on settings).
- Fixes a possible generation crash with type chart randomization.
- Should fix an issue where `stage_fill_hook` was able to remove the wrong item from the item pool resulting in a duplicated item reference existing.
- Adds a stage_post_fill function which searches for Pokémon in order of spheres, setting all but the first advancement Pokémon event found to `useful` so that spoiler playthrough calculation skips them. In a solo game gen test, this cut gen time from 15 seconds to 10 seconds with same seed number. Difference is likely to be much more massive in larger multiworlds.
2024-03-05 23:01:45 +01:00
Zach Parks
bfa9e7da00 Generate: Trim slot names again after 16 character limitation slice. (#2906) 2024-03-05 15:59:34 -06:00
t3hf1gm3nt
938beb34df TLOZ: use proper rule for completion condition (#2872)
Was pointed out that using `state.locations.checked` is not the best solution, even if it's for a completion condition and the Ganon event location would always have the Triforce of Power event item. So let's just check for the Triforce of Power instead. Thank you Zunawe for pointing it out and Silvris for providing the proper rule to use.
2024-03-05 22:55:59 +01:00
NewSoupVi
4ddfb7ce8b The Witness: Laser Hints (#2895) 2024-03-05 22:54:02 +01:00
NewSoupVi
b147c5bf8a The Witness: Bump required client version (#2897)
Bump required client version from 0.4.4 to 0.4.5.

The [client](https://github.com/NewSoupVi/The-Witness-Randomizer-for-Archipelago/releases/tag/v5.0.0p14) now connects with version 0.4.5.
2024-03-05 22:53:00 +01:00
NewSoupVi
bf60e905ec The Witness: Fix absolute world import (#2905) 2024-03-05 22:51:29 +01:00
Seldom
6926f38414 Terraria: Broken Hero Sword reqs mech bosses (#2879) 2024-03-05 19:35:41 +01:00
Silvris
644f75978d Kirby's Dream Land 3: Implement New Game (#2119)
Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
Co-authored-by: Doug Hoskisson <beauxq@yahoo.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-03-05 09:59:55 -06:00
black-sliver
af4172f32f Docs: Add review expectations to contributing.md (#2843)
Co-authored-by: BadMagic100 <dempsey.sean@outlook.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-03-05 09:55:12 -06:00
Silent
45a15004a4 TUNIC: Update setup guide and game page docs (#2832) 2024-03-05 09:54:18 -06:00
black-sliver
ce43c52589 Doc: fix typo in commands_en.md (#2765) 2024-03-05 09:49:34 -06:00
Bryce Wilson
7384bbdf23 BizHawkClient: Add README (#2689)
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-03-05 09:48:37 -06:00
Nicholas Brochu
bcbb06d78d Fix usage of __new__ for SpecialRange compatibility fallback (#2513) 2024-03-05 09:46:09 -06:00
Remy Jette
26ee9fe05c Pokemon RB: Fix exceptions raised by /bank (#2836)
* If the user tried to run `/bank` with no arguments to see the current
  value while disconnected, previously it threw an exception `KeyError:
  'EnergyLinkNone'`. Now it informs the user that they must be connected
  and in-game, like `/bank deposit` and `/bank withdraw` do.

  I'm also open to adding another `if` branch to make `/bank` only check
  for `ctx.server` instead of combining it with the other bank commands,
  to allow connecting to check the bank before the game save is loaded.
  If that's preferred let me know.

* If the user tried to run `/bank` or `/bank deposit` when the EnergyLink
  hadn't been used yet, they would get a `TypeError` exception. Trying
  `/bank withdraw` would give no output and would crash the lua
  connector script. Now it treats a `None` EnergyLink as `0` and works
  properly.
2024-03-05 09:36:18 +01:00
Justus Lind
12cc930825 Muse Dash: Add Muse Dash 4.1.0 songs (#2878) 2024-03-05 09:33:15 +01:00
Exempt-Medic
b9d561ae25 Core: Update generic.Rules.py (#2896) 2024-03-05 03:55:46 +01:00
Doug Hoskisson
ecec931e9f Core: fix (typing) mistake in PR #2887 (#2891)
I made this variable for more compatible and safer type narrowing, and then I didn't use if for the type narrowing.
2024-03-04 08:26:52 +01:00
Alchav
a70b94fd62 LTTP: Open Pyramid and Shop Prog Balancing Bug Fixes (#2890) 2024-03-04 01:52:03 +01:00
Jérémie Bolduc
37a871eab1 Core: Allow common collections in OptionSet and OptionList constructors (#2874)
* allow common collection in set and list option constructors

* allow any iterable of strings

* add return None

---------

Co-authored-by: beauxq <beauxq@yahoo.com>
2024-03-03 22:30:51 +01:00
Doug Hoskisson
113c54f9be Zillion: remove rom requirement for generation (#2875)
* in the middle of work towards no rom for generation (not working)

* no rom needed for Zillion generation

* revert core changes
2024-03-03 22:10:14 +01:00
Doug Hoskisson
4e31e51d7a Core: clarify error message when reading an APContainer (#2887) 2024-03-03 20:09:06 +01:00
t3hf1gm3nt
519dffdb73 TLOZ: Fix Logic for Gleeok guarded locations (#2734)
Turns out you can't kill Gleeok with bombs or a candle as I happened to find out in a community async. While I'll be fine, a rare combination of settings could put all 4 possible weapons (the three levels of sword and the Magical Rod) to kill Gleeoks behind killing Gleeoks. This fix should prevent that from happening.

Note: Even though there are technically 5 weapons that can kill Gleeok in the pool because at the moment we have an extra copy of the base Sword, I want to future-proof this incase we make changes to the item pool later.

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-03-03 17:59:31 +01:00
Aaron Wagener
d124df72e4 Core: add specific can_reach helpers to CollectionState (#2867) 2024-03-03 17:25:21 +01:00
black-sliver
57d1fe6d79 Docs: add note for stage_assert_generate to settings api (#2885) 2024-03-03 17:00:32 +01:00
Doug Hoskisson
ef37ee81f9 Zillion: apworld-compatible package data (#2860)
* Zillion: apworld-compatible module data

* fixed `World` import
2024-03-03 16:23:02 +01:00
Aaron Wagener
526eb09089 Options: add a DeathLinkMixin dataclass to easily standardize death_link (#2355)
* Options: add a DeathLinkOption dataclass to easily standardize death_link

* rename to DeathLinkMixin

* Update worlds/messenger/options.py
2024-03-03 14:11:44 +01:00
Star Rauchenberger
b2f30d5fd0 Lingo: Add a third location to Starting Room (#2839)
Despite earlier efforts, there were still rare fill errors when door shuffle and color shuffle were on and early color hallways was off, because sphere 1 was too small. This turns "Starting Room - HI" back into a location, which should give the algorithm more room.

The "forced good item" pool has been reconsidered. The problem with the specific item that caused the recent failure (Welcome Back - Shortcut to Starting Room) is that it only provided one location when color shuffle was on, which is a net of zero considering that the GOOD LUCK check was forced. It will no longer show up as a good item unless color shuffle is off. On an opposite vein, Rhyme Room Doors will now show up even if color shuffle is on, because it gives color hallways access by itself.

A good item will only be forced onto GOOD LUCK now if there is more than one player.
2024-03-03 08:20:37 +01:00
wildham
b8bf67a166 FF1: Update Location Names (#2838) 2024-03-03 06:43:45 +01:00
Fabian Dill
2c5b2e0759 MultiServer: make !hint without further arguments only reply to the instigating player (#2339) 2024-03-03 06:34:48 +01:00
Fabian Dill
b65a3b7464 Subnautica: cleanup (#2828) 2024-03-03 06:33:48 +01:00
Silvris
01cf60f48d Launcher: make launcher scrollable (#2881) 2024-03-03 06:32:58 +01:00
Alchav
ad3ffde785 FFMQ: Remove debug print statements (#2882) 2024-03-03 06:31:22 +01:00
zig-for
f17ff15669 LADX: fix modifying item pool in pre_fill (#2060) 2024-03-03 06:28:26 +01:00
Bryce Wilson
983da12a03 Pokemon Emerald: Add exhaustive list of ROM changes (#2801) 2024-02-29 20:42:13 +01:00
NewSoupVi
564ec8c32e The Witness: Allow specifying custom trap weights (#2835)
* Trap weights

* Slightly change the way the option works

* Wording one more time

* Non optional to bring in line with Ixrec's implementation

* Be clear that it's not an absolute amount, but a weight

* E x c l a m a t i o n   p o i n t

* Update worlds/witness/items.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Wait I can just do this now lol

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-02-29 07:40:08 +01:00
Trevor L
7a85ee7ed1 Blasphemous: Remove poptracker pack from setup guide (#2759) 2024-02-29 02:56:20 +01:00
Jarno
e60a2636cd Docs: Fixed broken ClientStatus hyperlink in network protocol.md (#2844) 2024-02-29 02:40:59 +01:00
Danaël V
184dedfa69 Core: Default YAML header updates (#2723)
* Cleaning up (#4)

Cleanup

* Adressed change about spaces no longer being replaced to underscores.

Added a "that" to remove an ambiguity

* Update data/options.yaml

Combined the two sentences into one, per suggestion

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

---------

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-02-29 02:30:28 +01:00
Doug Hoskisson
5e06a75bf2 Core: typing: return type of fill_slot_data to Mapping (#2876)
* Core: typing: return type of `fill_slot_data` to `Mapping`

type checker be like:

"Wait a minute! If you give this mutable dict to those sussy sketchbags, they might mutate it and invalidate your more specific typing!"

Note that this doesn't mean the return value needs to be immutable. It just means the caller won't mutate it (which matches current `Main.py` implementation).

I've seen some talk of introducing ownership to the type system.

https://discuss.python.org/t/we-may-need-better-specification-for-existing-and-future-refinement-types-in-the-type-system/43955/5

Then maybe I could say: "Do whatever you want with it, because I'm giving up ownership."
But that doesn't exist in the type system currently.

* in docs too

* docs talk less about type and more about json

* keep `dict` to be safe with .net client and json
2024-02-29 02:22:42 +01:00
Hisu
3bc2c44ac3 Docs: Add Spanish Guide for Pokemon Emerald (#2696)
* Docs: Add Spanish Guide for Pokemon Emerald

* Docs: Add Spanish Guide for Pokémon Emerald

* Docs: Add Spanish Guide for Pokemon Emerald

* Docs: Add Spanish Guide for Pokemon Emerald

* Docs: Add Spanish Guide for Pokemon Emerald
2024-02-29 01:54:54 +01:00
Nicholas Saylor
7ebd5d3891 DS3: Modified theme and warning color for accessibility (#2312) 2024-02-29 01:26:52 +01:00
black-sliver
a659036e95 Docs: mention that IDs for items and locations can overlap (#2854)
* Docs: mention that IDs for items and locations can overlap

* Update docs/world api.md

Co-authored-by: Ixrec <ericrhitchcock@gmail.com>

---------

Co-authored-by: Ixrec <ericrhitchcock@gmail.com>
2024-02-28 19:55:55 +01:00
PoryGone
36cee91a2c DKC3: Long-overdue World code cleanup (#2820)
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
2024-02-28 04:53:13 +01:00
NewSoupVi
59a6e4a1b5 The Witness: New hint type ("area hints") (#2494)
This new type of "area hint" will instead give you general information about one of the named geographical areas in your world.
Example:

```
There are 4 progression items in the "Quarry" region.
Of them, 2 are for other players.
Also, one of them is a laser for this world.
```

This also renames some of the locations in the game to better fit into an "area", such as the "River Obelisk" being renamed to the "Mountainside Obelisk".

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2024-02-28 04:44:22 +01:00
black-sliver
c126418f35 Utils: YAML goes brrrt (#2868)
Also tests to validate we dont break the API.
2024-02-27 08:44:34 +01:00
Scipio Wright
738a9ebb7d TUNIC: Misc Logic Changes, Additions (#2856)
* Add nmg boss scav kill

* Add boss quick kills

* Fix name of orb

* Remove getting into zig with ice grapple

* Remove connection from quarry to zig

* Add a few missing dependent regions

* Separate the atoll statue and portal pad so that it doesn't assume you can get from one to the other without prayer
2024-02-26 08:30:20 +01:00
Alchav
5c05ab1527 LTTP: KDS Default on (#2850) 2024-02-25 22:28:33 +01:00
Scipio Wright
46fc8df36e TUNIC: Fix for incorrect Zig 3 ER rule (#2849)
* Fix for incorrect ER rule in zig 3

* Add nmg logic to this same connection
2024-02-25 22:27:19 +01:00
Aaron Wagener
8f7b0ee489 Core: don't allow region, location, or entrance with duplicate names (#2453) 2024-02-25 21:56:27 +01:00
Aaron Wagener
86a7ac466e Core: remove bad hardcoded behavior around plando_connections (#2170) 2024-02-25 04:45:23 +01:00
Ixrec
57fcd57a85 Docs: Clarify which kinds of options actually support "random" (#2845)
* Clarify which kinds of options actually support "random"

The current phrasing of this sentence made me expect "random" to work even on my OptionsDict option. After asking `#archipelago-dev` and checking the `Options.py` code, it's become clear that many option types don't (and can't) support "random". This is my best guess at a more correct wording.

* add a sentence about from_text overrides based on black-silver's suggestion
2024-02-24 17:01:54 +01:00
Scipio Wright
6bf4a94537 TUNIC: Use push_precollected for start_with_sword (#2857) 2024-02-23 19:41:59 +01:00
Fabian Dill
96163c6408 Core: provide convenience getters on World class (#2827) 2024-02-23 10:32:14 +01:00
black-sliver
afa5ce4afe CI: add static analysis for native code / cython (#2852)
* CI: add static analysis for native code / cython

* CI: scan-build: also run for requirements.txt
2024-02-23 10:11:00 +01:00
Alchav
b18641091f LTTP: Thieves' Town Big Chest fix (#2853) 2024-02-22 16:56:53 +01:00
NewSoupVi
f8981a4638 Docs: Better description for LocationScouts (#2674)
* Better description for LocationScouts

* Update network protocol.md

* typo

* Update docs/network protocol.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update docs/network protocol.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update docs/network protocol.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Update docs/network protocol.md

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-02-22 09:49:02 +01:00
Exempt-Medic
9f0d736aed Generate: Fix sphere calculation debug message (#2788) 2024-02-22 09:44:03 +01:00
Silvris
ffdcb91a13 CI: add missing core files to "affects: core" labelling (#2824)
* add missing files

* Change to wildcard
2024-02-21 09:51:22 +01:00
black-sliver
17c73916b7 Speedups: no cinit, no pickling (#2851)
* Speedups: remove unnecessary cinit

This was meant for (memory) safety, but cython docs clearly state that this
is done automatically. The code generated for cinit with args is what
triggers a 'possible null deref' in clang's static analyzer, so by removing
cinit, we can now use static analysis.

* Speedups: disable pickling ...

... of LocationStore and internal classes.
This reduces code size and avoids accidentally pickling them.
2024-02-21 08:53:54 +01:00
BootsinSoots
7fc159c881 Docs: Make all guide titles say Guide, for my sanity (and the webhost) (#2304) 2024-02-20 10:22:32 -06:00
black-sliver
38cc90efd0 TextClient: fix logging not always showing up (#2846) 2024-02-20 08:07:33 +01:00
Alchav
7a86285807 LttP: Bombless Start and Options/Shops overhaul (#2357)
## What is this fixing or adding?
Adds Bombless Start option, along with proper bomb logic. This involves updating `can_kill_most_things` to include checking how many bombs can be held. Many places where the ability to kill enemies was assumed, now have logic. This fixes some possible existing logic issues, for example: Mini Moldorm cave checks currently are always in logic despite the fact that on expert enemy health it would require 12 bombs to kill each mini moldorm.

Overhauls options, pulling them out of core and in particular making large changes to how the shop options work.


Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Bondo <38083232+BadmoonzZ@users.noreply.github.com>
Co-authored-by: espeon65536 <espeon65536@gmail.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-02-20 01:07:49 +01:00
Fabian Dill
933e5bacff Core: update requirements (#2716) 2024-02-20 00:25:51 +01:00
Bryce Wilson
818b0a49e1 Pokemon Emerald: Un-exclude locations that must contain progression (#2840) 2024-02-18 01:52:50 +01:00
Nikola-Em
523c7dbfad Lingo: MASTERY (Room) not require "gray" (#2792) 2024-02-17 06:50:51 +01:00
Silent
e8249d1f72 TUNIC: Rename ability item (#2834) 2024-02-16 23:25:20 +01:00
Scipio Wright
04b02f5a4a TUNIC: Add aliases to LogicRules (#2825) 2024-02-16 23:24:25 +01:00
Bryce Wilson
687af30d14 BizHawkClient: Use callbacks in connector script instead of else/ifs (#2784) 2024-02-16 08:59:57 +01:00
Scipio Wright
539307cf0b TUNIC: Universal Tracker Support Update (#2786)
Adds better support for the Universal Tracker (see its channel in the future game design section).
This does absolutely nothing regarding standard gen, just adds some checks for an attribute that only exists when UT is being used.
2024-02-16 05:03:51 +01:00
Star Rauchenberger
4d9202537c Lingo: Fix non-progressive The Colorful (#2782)
The Colorful did not actually properly split into individual doors when progressive colorful was off. This change refactors the code that handles special cases with progressive items to make things clearer (which is important because I will be introducing another one).
2024-02-16 00:19:54 +01:00
Aaron Wagener
3869a25944 Tests: assign the world to WorldTestBase, and a default player field (#2385)
* Tests: assign the World to WorldTestBase and add a player field (because I like typing self.player far more than random 1's all over the place)

* more accurate docstring for world and multiworld

* use self.player within the class
2024-02-15 23:49:52 +01:00
Fabian Dill
9805bf92e4 Core: fix comment that did more harm than good (#2826) 2024-02-15 23:34:29 +01:00
Bryce Wilson
057e372325 Pokemon Emerald: Shuffle initial TMs for diverse_balanced option (#2758) 2024-02-15 21:04:20 +01:00
Star Rauchenberger
2c38b9fd51 Lingo: Various item/location renames (#2746)
## What is this fixing or adding?
- Roof MASTERY panels are now technically in individual regions with more descriptive names, so they can be displayed better on the tracker.
- Orange Tower Seventh Floor - Mastery has been renamed to simply Mastery.
- The Optimistic is its own region now.
- The misnamed CEILING in Room Room has been fixed.
- The misnamed CHEESE in Challenge Room has been fixed.
- The misnamed SOUND in Outside the Bold has been fixed.
- "The Bearer - Shortcut to The Bold" is now "The Bearer - Entrance".
- HUB ROOM - NEAR, FAR and the Warts Straw and Leaf Feel Areas have now been semantically combined into the "Symmetry Room". They are still logically three separate regions.
- The FACTS chain in Challenge Room has been reindexed, and the full chain panel is now indicated as such.
- The Room Room floors have been reindexed.
- Panels in The Observant are now named by their questions, not answers.
- Added a (1) subscript to several panels in Orange Tower Fourth Floor, Outside The Initiated, and The Seeker.

The validate_config.rb script has also been updated to check that all items and locations have an ID.

This change should not impact generation logic at all. It is just changing item and location names.
2024-02-15 21:03:10 +01:00
Doug Hoskisson
475e803500 Core: APPatch interface (#2808)
define interface that has only the bare minimum required
for `Patch.create_rom_file`
2024-02-15 00:23:05 +01:00
Scipio Wright
f178d438b8 TUNIC: Fix duplicate entrance name in ER (#2818) 2024-02-15 00:05:48 +01:00
black-sliver
e5980ac5f5 Core: remove module level AutoWorld import (#2790)
With BaseClasses running `worlds.__init__.py` and worlds importing
`from BaseClasses`, this is likely to result in some extra code being run
because of partial recursive imports. This now lazily loads `worlds` when
needed, at which point `sys.modules` should be properly populated.
2024-02-14 22:56:21 +01:00
Star Rauchenberger
2167db5a88 Lingo: Split up Color Hunt and Champion's Rest (#2745) 2024-02-14 01:56:24 +01:00
Star Rauchenberger
2165253961 Lingo: Detach Art Gallery Exit from Progressive Art Gallery (#2739)
The final stage of Progressive Art Gallery opens up the four-way intersection between the Art Gallery, Orange Tower Fifth Floor, The Bearer, and Outside The Initiated. This is a very useful door, and it would be cool to be able to open it without having to get five progressive items. The original reason this was included in the progression was because getting into the back of Art Gallery early would cause sequence breaks. At this point, the way the client handles the Art Gallery has changed enough that it does not matter if the player can go through this door before getting all progressive art galleries.
2024-02-14 01:55:19 +01:00
Bryce Wilson
57fcdf4fbe Pokemon Emerald: Add missed locations to postgame locations group (#2654) 2024-02-13 22:47:57 +01:00
NewSoupVi
74e79bff06 The Witness: Event System & Item Classification System revamp (#2652)
Two things have been happening.

**Incorrect Events**
Spoiler logs containing events that just straight up have an incorrect name and shouldn't be there. E.g. "Symmetry Island Yellow 3 solved - Monastery Laser Activation" when playing Laser Shuffle where this event should not exist, because Laser Activations are governed by the Laser items.

Now to be clear - There are no logic issues with it. The event will be in the spoiler log, but it won't actually be used in the way that its name suggests.
Basically, every panel in the game has exactly one event name. If the panel is referenced by another panel, it will reference the event instead. So, the Symmetry Laser Panel location will reference Symmetry Island Yellow 3, and an event is created for Symmetry Island Yellow 3. The only problem is the **name**: The canonical name for the event is related to "Symmetry Island Yellow 3" is "Monastery Laser Activation", because that's another thing that panel does sometimes.

From now on, event names are tied to both the panel referencing and the panel being referenced. Only once the referincing panel actually references the dependent panel (during the dependency reduction process in generate_early), is the event actually created.

This also removes some spoiler log clutter where unused events were just in the location list.

**Item classifications**
When playing shuffle_doors, there are a lot of doors in the game that are logically useless depending on settings. When that happens, they should get downgraded from progression to useful. The previous system for this was jank and terrible. Now there is a better system for it, and many items have been added to it. :)
2024-02-13 22:47:19 +01:00
Ishigh1
3ca3417172 LADX: Added some resilience to non-ASCII player names (#2642)
* Added some resilience to non-ASCII player names or items

* Also the client, not even sure if switching to ascii is useful here

* Split a long line in two
2024-02-13 22:46:18 +01:00
BadMagic100
55455914e6 CI: Add a workflow which automates some labeling (#2812)
* Initial content-based labeling

* Improve labeling rules around docs and /worlds/generic

* Improve labeling rules around docs and webhost

* Formatting

* Update matching for webhost

* back to square 1 on is:docu

* Try a better glob for docs

* Formatting

* Manage PR state labels

* Correct syntax for conditions

* Correct syntax for conditions

* add trigger on reopening

* add trigger on closing

* keep labels in sync as pr updates

* Change edit event to sync

* Restrict only to PRs to main

* address review comments

* apply only to PRs into main
2024-02-13 07:48:33 +01:00
Magnemania
0c8f726393 SM64: Move Randomizer Content Update (#2569)
* Super Mario 64: Move Randomizer Update

Co-authored-by: RBman <139954693+RBmans@users.noreply.github.com>
Signed-off-by: Magnemania <magnemight@gmail.com>

* Fixed logic for Vanish Cap Under the Moat

Signed-off-by: Magnemania <magnemight@gmail.com>
2024-02-13 06:14:21 +01:00
Alchav
6f3bc3a7ad Core: Minimal-Items Accessibility Fix (#1888) 2024-02-13 02:45:39 +01:00
Scipio Wright
151e2c3ac2 TUNIC: Add an ER static connection, modify an nmg rule (#2802)
* Add laurels connection at monastery front

* Removed an entrance rule to prevent people from being expected to softlock themselves
2024-02-11 03:15:46 +01:00
NewSoupVi
a6deffb9f2 The Witness: Change all option name comparisons to strings instead of numeric values (#2503)
* Refactor postgame code to be more readable

* Change all references to options to strings

* oops

* Fix some outdated code related to yaml-disabled EPs

* Small fixes to short/longbox stuff (thanks Medic)

* comment

* fix duplicate

* Removed triplicate lmfao

* Better comment

* added another 'unfun' postgame consideration

* comment

* more option strings

* oops

* Remove an unnecessary comparison

* another string missed

* Another was missed

* This would create a really bad merge error
2024-02-11 02:25:03 +01:00
black-sliver
77c326cb81 FFMQ: fix __version__ import in Output.py (#2791)
Importing from Main is a recursive import during Generate, also it's not
listed in Main.__all__ (and pycharm warns about this).
2024-02-11 01:07:23 +01:00
Alchav
1a675821cf Pokémon R/B: Halve Bank Exchange Rate (#2619) 2024-02-11 00:59:15 +01:00
JaredWeakStrike
03c3ef4e72 KH2: Fix Final Form logic softlock (#2803) 2024-02-11 00:50:38 +01:00
Justus Lind
4a703c5aba Muse Dash: Add support for Muse Dash 4.0.0 Songs (#2810) 2024-02-11 00:49:58 +01:00
Exempt-Medic
59ef010842 Fill: Changing deprecated option getter (#2735) 2024-02-10 22:07:11 +01:00
Fabian Dill
4032cfb9ea WebHost: provide None password to URI so it doesn't get stripped (#2777) 2024-02-05 18:11:02 -05:00
PoryGone
281fe01c25 Core: Purge the evil (world: MultiWorld) (#2749)
* Purge the evil

* Some files didn't save

* Fix a couple of missed string references

* multi_world -> multiworld
2024-02-05 00:38:00 +01:00
Fabian Dill
6c19bc42bb Tests: add world load benchmark (#2768) 2024-02-04 09:09:07 +01:00
Alchav
33237bd5c0 LTTP: Create Hyrule Castle Big Key Rule On Universal Small Keys Option (#2787) 2024-02-03 06:45:37 +01:00
NewSoupVi
57cb971177 The Witness: Junk hints for Shivers, Mystic Quest and Heretic (#2592) 2024-01-31 21:07:07 +01:00
t3hf1gm3nt
140f802564 LTTP: Update playerSettings.yaml to require AP version 0.4.4 (#2737)
Updating yaml to make sure people have the proper required version if they ever use this template to play with KDS
2024-01-31 08:01:55 +01:00
Star Rauchenberger
3a51c035ac Lingo: Enable start_inventory_from_pool (#2781) 2024-01-31 07:56:35 +01:00
Aaron Wagener
016c1e9bb4 Docs: world api general cleanup/overhaul (#2598)
* Docs: world api general cleanup/overhaul

* add pep-0287 to style doc

* some cleanup, reorganization, and grammar improvements

* reorder item and region creation

* address review comments

* fix indent

* linter grammar

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-01-30 21:42:33 +01:00
Alchav
697deb98b4 Pokémon R/B: Fix Thunder Stone item groups #2740 2024-01-30 09:06:10 +01:00
Ixrec
144769a141 Tests: use strict equality in some tests # (#2778)
* Tests: replace .assertLess/GreaterEqual() with .assertEqual() in two tests where strict equality seems more correct
2024-01-30 09:00:47 +01:00
Benny D
dc49d50c2c Docs: fixed typo in Stardew Valley setup guide (#2770)
* fix typo

* fix another typo

* Update worlds/stardew_valley/docs/en_Stardew Valley.md

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

---------

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2024-01-30 08:58:31 +01:00
black-sliver
5663c21f39 Tests: test that item/location name groups are not empty (#2748)
* Tests: test that item/location name groups are not empty

* Tests: better name for test_groups TestCase

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-01-30 08:34:54 +01:00
black-sliver
1b188bab3c Doc: add GM libs to network protocol.md (#2744) 2024-01-30 08:21:23 +01:00
JaredWeakStrike
69c80501c4 KH2: Fix empty location groups (#2757)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
2024-01-29 18:39:45 +01:00
JusticePS
0bc9966d6f Adventure: Fix iterable copy error when freeincarnate_max is tuned low (#2774) 2024-01-29 18:37:55 +01:00
Scipio Wright
b4212d1c3e TUNIC: Fix for nmg logic bug (#2772) 2024-01-28 21:13:03 +01:00
black-sliver
aa72f671bc SoE: fix naming of atlas medallion (#2747)
In pyevermizer, it's called Atlas Medallion, not Amulet, leading to an
empty group and to code not considering them as an alchemy ingredient
when swapping out for a trap or an energy core fragment.

Also adds a test.
2024-01-21 19:34:24 +01:00
Scipio Wright
5f9ce2b7b6 Noita: Update to use new Options API (#2370)
Reworking the options to make it work with the new options API.
Also reworked stuff in several spots to use world: NoitaWorld instead of multiworld: MultiWorld
2024-01-19 21:31:45 +01:00
zig-for
1307754f02 LADX: music shuffle (#2101) 2024-01-19 21:14:26 +01:00
Bicoloursnake
ac7b707e3e OOT: Adjust the Logic Trick Keys to be an ordered object (#2736) 2024-01-18 02:18:03 +01:00
Star Rauchenberger
ec440b7785 Lingo: NORTH requires hint panels (#2732) 2024-01-18 01:58:48 +01:00
Danaël V
4c901dcfc0 TUNIC: Change Tunic to TUNIC (#2720) 2024-01-18 01:56:34 +01:00
Fabian Dill
834b6e35b4 Setup: auto update vc redist (#2502) 2024-01-18 01:52:33 +01:00
Fabian Dill
602c2966fc LttP: move _hint_text to SubClasses (#2532) 2024-01-16 17:23:18 +01:00
black-sliver
49ecd4b9c1 CI: flake8: max-complexity=14 (#2731)
The value of 10 does not really fit some of our world patterns and values
up to 15 may be acceptable. Looking at some worlds, 14 seems to be
achievable without too much work and reduces the noise in test output,
making it more usable.
2024-01-16 17:10:58 +01:00
black-sliver
de8fe21d4a Tests: create sane cov defaults (#2728) 2024-01-16 17:10:19 +01:00
NewSoupVi
4fdeec4f70 The Witness: Cleanup - Options Access, data version, snake_case for file name (#2631) 2024-01-16 15:33:34 +01:00
NewSoupVi
71a3e2230d The Witness: Allow Mountain Lasers to go up to 11 instead of 7. (#2618) 2024-01-16 15:27:09 +01:00
JaredWeakStrike
325a510ba7 KH2: Promise charm logic (#2635) 2024-01-16 15:26:18 +01:00
NewSoupVi
5dcaa6ca20 The Witness: Death Link Amnesty (#2646) 2024-01-16 15:24:10 +01:00
NewSoupVi
e15873e861 The Witness: Bonk trap support (#2645) 2024-01-16 15:23:30 +01:00
NewSoupVi
5c7bae7940 The Witness: Local Laser Shuffle + Option Presets (#2590)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2024-01-16 15:14:06 +01:00
NewSoupVi
e6f7ed5060 The Witness: Progressive Symmetry (#2644) 2024-01-16 15:13:04 +01:00
NewSoupVi
1c2dcb7b01 The Witness: Add Desert Control Panels (#2643) 2024-01-16 15:11:52 +01:00
Star Rauchenberger
5df7a8f686 Lingo: Disable forced good item when early color hallways is on (#2729) 2024-01-16 15:10:59 +01:00
Bryce Wilson
3a588099bd Pokemon Emerald: Automatically exclude locations based on goal (#2655) 2024-01-16 15:09:47 +01:00
Yussur Mustafa Oraji
d000b52ae0 V6: Use new options api (#2668)
* v6: Use new options API

* v6: Add display names for some options
2024-01-16 13:38:19 +01:00
NewSoupVi
fe3bc8d6be The Witness: Add Obelisk Side locations to always and priority hints (#2665) 2024-01-16 13:14:38 +01:00
NewSoupVi
7affb885ba The Witness: Add "Town Desert Laser Redirect Control (Panel)" as an item (#2669) 2024-01-16 13:13:44 +01:00
Star Rauchenberger
d390d2eff8 Lingo: Remove colors from Bearer SIXes (#2677) 2024-01-16 13:13:02 +01:00
JaredWeakStrike
0efc13fc8a KH2: Location Groups and Subclasses (#2700) 2024-01-16 13:12:33 +01:00
Star Rauchenberger
c6896c6af9 Lingo: Make The Colorful optionally progressive (#2711) 2024-01-16 13:11:20 +01:00
Star Rauchenberger
adad7b532d Lingo: Turn The Colorful into a countdown achievement (#2710)
The Colorful currently, in logic, does not expect you to solve the achievement panel until all of the doors are opened. This is not enforced by the client in complex door shuffle. It is also not typical of how achievements in Lingo usually work, and it ended up this way because of the fact that The Colorful is, uniquely, not a countdown panel. This change modifies logic so that solving each panel within The Colorful is required in order to access the achievement, rather than opening all of the doors. This will be accompanied by a change to the client that will turn the achievement panel into a countdown.
2024-01-16 13:09:54 +01:00
Held_der_Zeit
d756960a0b Worlds Docs: Translations German (Clique, BK Sudoku, OoT) (#2581)
* Sudoku German

* German OOT (+ Room Image)

* German Clique

* german translation

* translation flexibility - ff1

* german setup - oot

* Transaltion Flexibilty - SM64

* translation flexibilty - factorio

* translation flexibilty - kh2

* translation flexibility - Super Metroid

* translation flexibility - Stardew Valley

* german translation added - clique

* translation flexibility - terraria

* translation flexibilty - checksfinder

* Sudoku Setup - Grammar Fix

* Sudoku Main - Fix Grammar

* Revert "translation flexibility - ff1"

This reverts commit 6df434c682.

* Revert "Transaltion Flexibilty - SM64"

This reverts commit 754bf95d2f.

* Revert "translation flexibilty - factorio"

This reverts commit db1226a9de.

* Revert "translation flexibility - Super Metroid"

This reverts commit ca5bd9a64a.

* Revert "translation flexibilty - kh2"

This reverts commit 076534ee32.

* Revert "translation flexibility - Stardew Valley"

This reverts commit 4b13701394.

* Revert "translation flexibility - terraria"

This reverts commit a0abfc8a03.

* Revert "translation flexibilty - checksfinder"

This reverts commit a4de49961d.

* Sugesstion - Fixes in Grammar (and Typos)

One or two suggesstions need to be changed a bit further (such as an incomplete sentence)

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update guide_de.md

* Update setup_de.md

* Update de_Sudoku.md

* Update __init__.py

* Update worlds/oot/docs/setup_de.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-01-16 06:54:48 +01:00
Alchav
30ec080449 FFMQ: Reset protection (#2727)
Bizhawk's "hard reset" option fills RAM with 0x55s. This causes game completion to be erroneously flagged, and likely many erroneous location checks with it. This fix checks for 0x55 and will not proceed to process anything if present.
2024-01-16 01:21:02 +01:00
Fabian Dill
79e2f7e357 Tests: test that World.options is not set on the class (#2725)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-01-15 20:50:16 +01:00
t3hf1gm3nt
b4077a0717 TLOZ: properly assign options (#2726)
whoops used a = instead of a :
mad that im doing a literal one character change PR :/
2024-01-15 20:19:18 +01:00
black-sliver
518b04c08e SoE: minor typing and style fixes (#2724)
* SoE: fix typing for tests

* SoE: explicitly export pyevermizer

To support loading the module from source (rather than module) we import
pyevermizer from `__init__.py` in other files. This has been an implicit export
and `mypy --strict` disables implicit exports, so we export it explicitly now.

* SoE: fix style in patch.py

* SoE: remove unused imports

* SoE: fix format mistakes

* SoE: cleaner typing in SoEOptions.flags

as suggested by beauxq
2024-01-15 09:17:46 +01:00
GodlFire
d10f8f66c7 Shivers: Fix rule logic for location 'Final Riddle: Guillotine Dropped' (#2706) 2024-01-15 04:48:44 +01:00
t3hf1gm3nt
6d393fe42b TLOZ: update to new options API (#2714) 2024-01-15 04:47:32 +01:00
agilbert1412
5b93db121f Stardew Valley: Added missing rule on the club card (#2722) 2024-01-15 04:29:30 +01:00
Fabian Dill
ad074490bc Test: add location access rule benchmark (#2433) 2024-01-14 21:30:00 +01:00
Fabian Dill
6ac3d5c651 Core: set consistent server defaults (#2566) 2024-01-14 21:24:34 +01:00
Doug Hoskisson
ed6b7b2670 Zillion: remove old option access from item link validation (#2673)
* Zillion: remove old option access from item link validation
and a little bit a cleaning in other stuff nearby

* one option access missed
2024-01-14 15:48:30 +01:00
Doug Hoskisson
6904bd5885 Typing: improve kivy type stubs (#2681) 2024-01-14 15:31:13 +01:00
black-sliver
962b9b28f0 Setup: don't install webhost dependencies (#2717)
also makes ModuleUpdate detect changed requirements for update()
2024-01-14 03:09:03 +01:00
Aaron Wagener
37b03807fd Core: Log the worlds still using the old options API (#2707) 2024-01-14 03:04:12 +01:00
Aaron Wagener
73e41cb701 Core: migrate start_inventory_from_pool to new options API (#2666)
* Core: migrate start_inventory_from_pool to new options API

* get the other spot too

* skip {}

* oops

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-01-14 02:57:53 +01:00
Aaron Wagener
cfd758168c Tests: add a test for worlds to not modify the itempool after create_items (#1460)
* Tests: add a test for worlds to only modify the itempool in `create_items`

* extend test multiworld setup instead of a new function

* cleanup the test a bit

* put more strict wording in `create_items` docstring

* list of shame

* Don't call `set_rules` before testing

* remove ChecksFinder from the list of shame

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2024-01-14 02:15:35 +01:00
Nicholas Saylor
01fb44c186 Docs: Added Disabled World information to README.md (#2705)
* Add rationale for OriBF being disabled

* Removed periods

* Added warning to README.md

* Apply suggestions from code review

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Added disable date

Meant to provide context for any updates the world may need (For example, this world would need to change to the new options sstem in 0.4.4)

* Moved rationale to local README
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-01-13 21:10:16 +01:00
Aaron Wagener
2725c0258f Docs: specify that deathlink cause should contain the player name (#2557)
* Docs: specify that the cause should contain the player name

* accidental whitespace moment

* fix table formatting
2024-01-13 19:23:14 +01:00
black-sliver
0c0adb0745 Core: update kivy (#2718) 2024-01-13 18:01:36 +01:00
Scipio Wright
4a85f21c25 TUNIC: Update game page for blurb about playing vanilla first (#2712)
* Update en_Tunic.md

* Change emphasis a bit

* Move the "haven't played before" section up

* settings -> options

* Update worlds/tunic/docs/en_Tunic.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update setup as well with settings -> options and some recent changes to the in-game settings

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-01-13 15:12:43 +01:00
Silent
3933fd3929 TUNIC: Implement New Game (#2172) 2024-01-12 20:32:15 +01:00
Ame
b241644e54 Docs: add FR guide for DLCQuest (#2699)
* Docs: add Translate FR guide for DLCQuest

* Add Translate

* fix

* Update worlds/dlcquest/docs/fr_DLCQuest.md

Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com>

* Fix Translate

* Fix translate

* Update __init__.py

* Update worlds/dlcquest/__init__.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update worlds/dlcquest/__init__.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2024-01-12 20:26:50 +01:00
black-sliver
e00b5a7d17 SoE: use new AP API and naming and make APworld (#2701)
* SoE: new file naming

also fixes test base deprecation

* SoE: use options_dataclass

* SoE: moar typing

* SoE: no more multiworld.random

* SoE: replace LogicMixin by SoEPlayerLogic object

* SoE: add test that rocket parts always exist

* SoE: Even moar typing

* SoE: can haz apworld now

* SoE: pep up test naming

* SoE: use self.options for trap chances

* SoE: remove unused import with outdated comment

* SoE: move flag and trap extraction to dataclass

as suggested by beauxq

* SoE: test trap option parsing and item generation
2024-01-12 01:07:40 +01:00
Alchav
47dd36456e Pokémon R/B: Fix move intervention (#2687) 2024-01-12 00:49:54 +01:00
Fabian Dill
4ce8a7ec4d PyCharm: ship a working unittest run config (#2694) 2024-01-12 00:49:14 +01:00
Kory Dondzila
a99c1e15ad Shivers: Fixes issue with office elevator rule logic. (#2690)
Office elevator logic was written as
can reach Underground Tunnels OR can reach Office AND have Key for Office Elevator

Meaning that key for office elevator was not required if Underground Tunnels could be reached when it should be.

Changed to
(can reach Underground Tunnels OR can reach Office) AND have Key for Office Elevator
2024-01-12 00:48:22 +01:00
Fabian Dill
44de140add SC2: run download_data via concurrent.futures (#2704) 2024-01-12 00:40:33 +01:00
Doug Hoskisson
ac2387e17c Tests: remove deprecated option access from WorldTestBase (#2671)
* remove deprecated option access from `WorldTestBase`

* one in test_reachability
2024-01-12 00:22:04 +01:00
Danaël V
2760deb5b6 Docs: Fix broken link in Landstalker setup Guide (#2680)
* Cleaning up (#4)

Cleanup

* Update landstalker_setup_en.md

Fixed Redirect
2024-01-12 00:18:11 +01:00
Remy Jette
f530895c33 WebHost: Fix /api/generate (#2693) 2024-01-11 17:44:12 -05:00
Justus Lind
b6f3ccb8c5 Touhou Mugakudan 3 song update. (#2659)
- Adds all the songs from the Touhou Mugakudan -Ⅲ- update. 
- Increases the upper limit of additional songs to 508 due to there being 512 songs now.
- Finally fixes umpopoff. As it was the only song that had less than 3 difficulties but also didn't have proper difficulty values
2024-01-11 23:13:39 +01:00
Flori
388413fcdd Hollow Knight: Fix fragile/unbreakable charm variants counting as 2 distinct charms in logic (#2683)
Deletes CHARM of the 3 unbreakable charms, adds 0.5 CHARM to Queen_fragment, King_Fragment and Void_heart
2024-01-11 23:10:25 +01:00
JaredWeakStrike
4045c6a9cf KH2: Fix relative import (#2702) 2024-01-11 00:56:43 +01:00
JaredWeakStrike
e082c83dc7 KH2: Fix plando breaking because of keyblades (#2678) 2024-01-10 18:22:54 +01:00
Doug Hoskisson
82410fd554 Zillion: client win location check (#2682) 2024-01-10 17:52:43 +01:00
JaredWeakStrike
570ba28bee KH2: Fix Terra logic (#2676) 2024-01-10 06:22:04 +01:00
Alchav
b0638b993d FFMQ: Fix starting progressive gear (#2685) 2024-01-09 03:57:38 +01:00
lordlou
89f211f31e SMZ3: 0.4.4 backward compat client fix (#2667)
fixed broken client compatibility with any seed generated before 0.4.4 introduced with the recent change to the message queue.
2024-01-07 13:13:52 +01:00
Fabian Dill
70fdd6b90d Core: clean up MultiServer.py/auto_shutdown (#2552) 2024-01-07 01:42:57 +01:00
Fabian Dill
f22daca74e CommonClient: request datapackage per-game (#2563) 2024-01-07 01:42:16 +01:00
Danaël V
064a7bf01b Docs: Update regarding new launcher (#2627)
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2024-01-04 21:03:39 -06:00
JaredWeakStrike
02a9430ad5 KH2: Update docs and logic sheet to reflect 4.4 (#2610) 2024-01-04 08:58:41 -06:00
JaredWeakStrike
c19afa4f4e KH2: setup guide update to reflect main rando (#2633) 2024-01-04 08:57:40 -06:00
Zach Parks
c593a960f6 WebHost: Fix 500 Server errors relating to player/multi trackers. (#2664)
* WebHost: Fix player tracker issue with items missing from data package.

 Reported in https://discord.com/channels/731205301247803413/1192202112172576819

* WebHost: Fix multi-tracker error when item links are present.

 Reported in https://discord.com/channels/731205301247803413/1192104719959724062

* Use Utils.KeyedDefaultDict instead of checking for key

* formatted revert

* import tweak
2024-01-04 08:29:42 -06:00
Zach Parks
7406a1e512 WebHost: Copyright update time. (#2660) 2024-01-03 18:43:41 -06:00
Aaron Wagener
0df0955415 Core: check if a location is an event before excluding it (#2653)
* Core: check if a location is an event before excluding it

* log a warning

* put the warning in the right spot
2024-01-02 15:03:39 +01:00
Bryce Wilson
bf17582c55 BizHawkClient: Add some handling for non-string errors (#2656) 2024-01-02 11:32:03 +01:00
JaredWeakStrike
e5c739ee31 KH2: Ability dupe fix and stat increase fix (#2621)
Makes the client make sure the player has the correct amount of stat increase instead of letting the goa mod (apcompanion) do it

abilities: checks the slot where abilities could dupe unless that slot is being used for an actual abiliity given to the player
2024-01-02 11:19:57 +01:00
GodlFire
88c7484b3a Shivers: Fixes rule logic for location 'puzzle solved three floor elevator' (#2657)
Fixes rule logic for location 'puzzle solved three floor elevator'. Missing a parenthesis caused only the key requirement to be checked for the blue maze region.
2024-01-02 11:16:45 +01:00
Doug Hoskisson
c104e81145 Zillion: move client to worlds/zillion (#2649) 2024-01-01 13:42:41 -06:00
wildham
3d1be0c468 FF1: Fix terminated_event access_rule not getting set (#2648) 2024-01-01 18:13:35 +01:00
lordlou
e674e37e08 SMZ3: optimized message queues (#2611) 2023-12-28 16:43:16 -06:00
Jarno
d1a17a350d Docs: Add missing Get location_name_groups_* to network protocol (#2550) 2023-12-28 14:41:24 +01:00
Fabian Dill
24ac3de125 Factorio: "improve" default start items (#2588)
Makes it less likely that people kill themselves via pollution and gives them some healing items they may not even know about.
2023-12-28 14:30:10 +01:00
Scipio Wright
901201f675 Noita: Don't allow impossible slot names (#2608)
* Noita: Add note about allowable slot names

* Update character list

* Update init to raise an exception if a yaml has bad characters

* Slightly adjust exception message
2023-12-28 14:21:54 +01:00
t3hf1gm3nt
c7617f92dd TLOZ: Try accounting for non_local_items with the pool of starting weapons (#2620)
It was brought up that if you attempt to non_local any of the starting weapons, there is still a chance for it to get chosen as your starting weapon if you are on a StartingPosition value lower than very_dangerous. This fix will attempt to build the starting weapons list accounting for non_local items, but if all possible weapons have been set to non_local, force one of them to be your starting weapon anyway since the player is still expecting a starting weapon in their world if they have chosen one of the lower StartingPosition values.
2023-12-28 14:17:23 +01:00
NewSoupVi
8e708f829d The Witness: Fix an instance of multiworld.random being used (#2630)
o_o
2023-12-28 14:12:37 +01:00
Fabian Dill
7af654e619 WebHost: validate uploaded datapackage and calculate own checksum (#2639) 2023-12-28 13:57:41 +01:00
TheLynk
af1f6e9113 Oot : Update setup fr (#2394)
* add new translation

* Add translation for OOT Setup in french

* Update setup_fr.md

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/minecraft/docs/minecraft_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update setup_fr.md

Fix treu to true

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Marech <marechal-l@gmx.com>

* Update OOT Init and Update Minecraft Init

* Fix formatting errors

* Fix wrong link in stardew valley randomizer setup guide

Fix wrong link in stardew valley randomizer setup guide

* Add new translation for Adventure and Archipidle in french

Add new translation for Adventure and Archipidle in french

* Add more store in setup page subnautica for more fairness

Add more store in setup page subnautica for more fairness

* tweak update merge #1685 for lua file

tweak update merge #1685 for lua file

* fix text

fix text

* fix wrong translation

fix wrong translation

* Yes it's better

Yes it's better

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Update OOT Setup FR

Update OOT Setup FR

* Tweak Text

Tweak Text

---------

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-12-28 13:43:42 +01:00
Bryce Wilson
04d194db74 Pokemon Emerald: Change "settings" to "options" in docs (#2517)
* Pokemon Emerald: Change "settings" to "options" in docs

* Pokemon Emerald: Fix two more usages of "setting" instead of "option"

* Pokemon Emerald: Minor rephrase in docs

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>

---------

Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
2023-12-28 13:33:30 +01:00
Rosalie-A
70eb2b58f5 [TLOZ] Fix bug with item drops in non-expanded item pool (#2623)
There was a bug in non-expanded item pool where due to the base patch changes to accommodate more items in dungeons, some items were transformed into glitch items that removed bombs (this also happened in expanded item pool, but the item placement would overwrite the results of this bug so it didn't appear as frequently). Being a Zelda game, losing bombs is bad. This PR fixes the base patch process to avoid this bug, by properly carrying the value of a variable through a procedure.
2023-12-28 12:16:38 +01:00
Alchav
576c705106 Pokémon R/B: Badge plando fix (#2628)
Only attempt to place badges in badge locations if they are empty. Return unplaced badges to the item pool if fewer than 8 locations are being filled.
This should fix errors that occur when items are placed into badge locations via plando, or whatever other worlds may do.
2023-12-28 12:15:48 +01:00
lordlou
b99c734954 SM: strict rom validation fix (#2632)
added a more robust ROM tag validation to free oher games to use tag starting with "SM" followed by another letter (SMW, SMZ3, SMRPG, SMMR,...)
2023-12-28 12:14:13 +01:00
Yussur Mustafa Oraji
7c70b87f29 sm64ex: Fix randomizing Courses and Secrets separately (#2637)
Backported from #2569
2023-12-28 08:01:48 +01:00
Trevor L
2512eb7501 Hylics 2: Add missing logic (#2638) 2023-12-28 06:25:41 +01:00
CaitSith2
bb0a0f2aca Factorio: Fix unbeatable seeds where a science pack needs chemical plant (#2613) 2023-12-22 20:02:49 -08:00
Fabian Dill
0d929b81e8 Factorio: fix files from mod base directory not being grabbed correctly in non-apworld (#2603) 2023-12-21 04:26:41 +01:00
Fabian Dill
8842f5d5c7 Core: make update_reachable_regions local variables more wordy (#2522) 2023-12-21 04:11:11 +01:00
Star Rauchenberger
817197c14d Lingo: Tests no longer disable forced good item (#2602)
The static class with the "disable forced good item" field is gone. Now, certain tests that want to check for specific access progression can run a method that removes the forced good item and adds it back to the pool. Tests that don't care about this will collect the forced good item like normal. This should prevent the intermittent fill failures on complex doors unit tests, since the forced good item should provide enough locations to fill in.
2023-12-18 09:46:24 -06:00
Alchav
c8adadb08b Pokémon R/B: Fix Flash learnable logic (#2615) 2023-12-18 09:39:04 -06:00
Zach Parks
a549af8304 Hollow Knight: Add additional DeathLink option and add ExtraPlatforms option. (#2545) 2023-12-17 10:11:40 -06:00
Fabian Dill
4979314825 Webhost: open graph support for /room (#2580)
* WebHost: add Open Graph metadata to /room

* WebHost: Open Graph cleanup
2023-12-17 00:08:40 -05:00
Silvris
f958af4067 Adventure: Fix KeyError on Retrieved (#2560) 2023-12-16 22:22:51 +01:00
Aaron Wagener
7dff09dc1a Options: set old options api before the world is created (#2378) 2023-12-16 22:21:05 +01:00
lordlou
c56cbd0474 SM: item link replacement fix (#2597) 2023-12-16 04:28:54 +01:00
PoryGone
6c4fdc985d SA2B: Fix Weapons Bed - Omochao 2 Logic (#2605) 2023-12-16 04:16:36 +01:00
Alchav
b500cf600c FFMQ: Actually fix the spellbook option (#2594) 2023-12-16 04:16:13 +01:00
Alchav
394633558f ALTTP: Restore allow_excluded (#2607)
Restores allow_excluded to the dungeon fill_restrictive call, which was apparently removed by mistake during merge conflict resolution
2023-12-15 20:39:09 +01:00
Alchav
3e3af385fa Pokémon R/B: client locations import (#2596) 2023-12-13 23:57:14 +01:00
Yussur Mustafa Oraji
ff556bf4cc sm64ex: Fix server (#2599) 2023-12-13 23:46:46 +01:00
Alchav
a3b0476b4b LTTP: Boss rule fix (#2600) 2023-12-13 23:34:36 +01:00
Zach Parks
0eefe9e936 WebHost: Some refactors and additional checks when uploading files. (#2549) 2023-12-12 20:12:16 -06:00
Aaron Wagener
db1d195cb0 Hollow Knight: remove unused option check (#2595) 2023-12-12 20:11:10 -06:00
Bryce Wilson
45fa9a8f9e BizHawkClient: Add SGB to systems using explicit vblank callback (#2593) 2023-12-12 05:48:20 +01:00
Alchav
e9317d4031 FFMQR: Fix Empty Kaeli Companion Event Location and Spellbook option (#2591) 2023-12-12 02:39:38 +01:00
Aaron Wagener
d9d282c925 Tests: test that the datapackage after generation is still valid (#2575) 2023-12-12 02:14:44 +01:00
Fabian Dill
13122ab466 Core: remove start_inventory_from_pool from early_items (#2579) 2023-12-10 20:42:41 +01:00
Fabian Dill
e8f96dabe8 Core: faster prog balance (#2586)
* Core: rename world to multiworld in balance_multiworld_progression

* Core: small optimization to progression balance speed
2023-12-10 20:42:07 +01:00
Fabian Dill
1a05bad612 Core: update modules (#2551) 2023-12-10 20:38:49 +01:00
NewSoupVi
8142564156 The Witness: Fix non-deterministic hints (#2514) 2023-12-10 20:36:55 +01:00
NewSoupVi
e2109dba50 The Witness: Fix Logic Error for Keep Pressure Plates 2 EP in puzzle_randomization: none (#2515) 2023-12-10 20:35:46 +01:00
Yussur Mustafa Oraji
3a09677333 sm64ex: Fix generations (#2583) 2023-12-10 20:31:43 +01:00
Star Rauchenberger
d3b09bde12 Lingo: Fix entrance checking being broken on default settings (#2506)
The most serious issue this PR addresses is that entrances that use doors without items (a small subset of doors when door shuffle is on, but *every* door when door shuffle is off, which is the default) underestimate the requirements needed to use that entrance. The logic would calculate the panels needed to open the door, but would neglect to keep track of the rooms those panels were in, meaning that doors would be considered openable if you had the colors needed to solve a panel that's in a room you have no access to.

Another issue is that, previously, logic would always consider the "ANOTHER TRY" panel accessible for the purposes of the LEVEL 2 panel hunt. This could result in seeds where the player is expected to have exactly the correct number of solves to reach LEVEL 2, but in reality is short by one because ANOTHER TRY itself is not revealed until the panel hunt is complete. This change marks ANOTHER TRY as non-counting, because even though it is technically a counting panel in-game, it can never contribute to the LEVEL 2 panel hunt. This issue could also apply to THE MASTER, since it is the only other counting panel with special access rules, although it is much less likely. This change adds special handling for counting THE MASTER. These issues were possible to manifest whenever the LEVEL 2 panel hunt was enabled, which it is by default.

Smaller logic issues also fixed in this PR:

* The Orange Tower Basement MASTERY panel was marked as requiring the mastery doors to be opened, when it was actually possible to get it without them by using a painting to get into the room.
* The Pilgrim Room painting item was incorrectly being marked as a filler item, despite it being progression.
* There has been another update to the game that adds connections between areas that were previously not connected. These changes were additive, which is why they are not critical.
* The panel stacks in the rhyme room now require both colours on each panel.
2023-12-10 19:15:42 +01:00
Rjosephson
01d0c05259 RoR2: Remove begin with loop (#2518) 2023-12-10 19:12:46 +01:00
Fabian Dill
19b8624818 Factorio: remove staging folder for mod assembly (#2519) 2023-12-10 19:11:57 +01:00
Alchav
1312884fa2 Pokémon R/B: Fix Silph Co 6F Hostage (#2524)
Fixes an issue where the Silph Co 6F hostage check becomes unavailable if Giovanni has been defeated on 11F. This is due to the NPC having separate scripts depending on whether Giovanni was defeated. The code for the check has been moved to before the branch.
2023-12-10 19:10:09 +01:00
lordlou
6cd5abdc11 SMZ3: KeyTH check fix (#2574) 2023-12-10 19:07:56 +01:00
JaredWeakStrike
6b0eb7da79 KH2: RC1 Bug Fixes (#2530)
Changes the finished_game to new variable so now it only checks the game's memory and if it has sent the finished flag before
Fixed ag2 not requiring 1 of each black magic
Fix hitlist if you exclude summon level 7 and have summon levels option turned off
2023-12-10 18:58:52 +01:00
Doug Hoskisson
b0a09f67f4 Core: some typing and documentation in BaseClasses.py (#2589) 2023-12-10 06:43:17 +01:00
Fabian Dill
c3184e7b19 Factorio: fix wrong parent class for FactorioStartItems (#2587) 2023-12-10 00:10:01 -05:00
t3hf1gm3nt
3214cef6cf TLOZ: Fix starting weapon possibly getting overwritten by triforce fragments (#2578)
As discovered by this bug report https://discord.com/channels/731205301247803413/1182522267687731220 it's currently possible to accidentally have the starting weapon of a player overwritten by a triforce fragment if TriforceLocations is set to dungeons and StartingPosition is set to dangerous. This fix makes sure to remove the location of a placed starting weapon if said location is in a dungeon from the pool of possible locations that triforce fragments can be placed in this circumstance.
2023-12-10 04:23:40 +01:00
Alchav
f10431779b ALTTP: Ensure all Hyrule Castle keys are local in Standard (#2582) 2023-12-09 19:33:51 +01:00
JaredWeakStrike
a9a6c72d2c KH2: Fix events in datapackage (#2576) 2023-12-08 22:39:24 +01:00
PoryGone
9351fb45ca SA2B: Fix KeyError on Unexpected Characters in Slot Names (#2571)
There were no safeguards on characters being used as keys into a conversion dict. Now there are.
2023-12-08 07:17:12 +01:00
beauxq
abfc2ddfed Zillion: fix retrieved packet processing 2023-12-07 22:27:46 +01:00
NewSoupVi
bf801a1efe The Witness: Fix Symmetry Island Upper Panel logic (2nd try)
I got lazy and didn't properly test the last fix.

Big apologies, I got a bit panicked with all the logic errors that were being found.
2023-12-07 20:16:22 +01:00
Bryce Wilson
5bd022138b Pokemon Emerald: Fix missing rule for 2 items on Route 120 (#2570)
Two items on Route 120 are on the other side of a pond but were considered accessible in logic without Surf.


Creates a new separate region for these two items and adds a rule for being able to Surf to get to this region. Also adds the items to the existing surf test.
2023-12-07 20:15:38 +01:00
Aaron Wagener
69ae12823a The Messenger: bump required client version (#2544)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-12-07 08:23:05 +01:00
Aaron Wagener
57001ced0f The Messenger: remove old links and update relevant ones (#2542) 2023-12-07 08:22:12 +01:00
NewSoupVi
3fa01a41cd The Witness: Fix unreachable locations on certain settings (Keep PP2 EP, Theater Flowers EP) (#2499)
Basically, the function for "checking entrances both ways" only checked one way. This resulted in unreachable locations.

This affects Expert seeds with (non-remote doors and specific types of EP Shuffle), as well as seeds with non-remote doors + specific types of disabled panels + specific types of EP Shuffle.

Also includes two changes that makes spoiler logs nicer (not creating unnecessary events).
2023-12-07 06:36:46 +01:00
Alchav
87252c14aa FFMQ: Update to FFMQR 1.5 (#2568)
FFMQR was just updated to 1.5, adding a number of new options. This brings these updates to AP.
2023-12-06 18:24:59 +01:00
Fabian Dill
56ac6573f1 WebHost: fix room shutdown (#2554)
Currently when a room shuts down while clients are connected it instantly spins back up. This fixes that behaviour categorically.
I still don't know why or when this problem started, but it's certainly wreaking havok on prod.
2023-12-06 18:24:13 +01:00
Doug Hoskisson
d8004f82ef Zillion: some typing fixes (#2534)
`colorama` has type stubs when it didn't before

`ZillionDeltaPatch.hash` annotated type could be `None` but md5s doesn't allow `None`

type of `CollectionState.prog_items` changed

`WorldTestBase` moved

all of the following are related to this issue:
https://github.com/python/typing/discussions/1486

CommonContext for `command_processor` (is invalid without specifying immutable - but I don't need it anyway)

ZillionWorld options and settings (is invalid without specifying immutable - but I do need it)
2023-12-06 18:23:43 +01:00
NewSoupVi
597f94dc22 The Witness: Add all the Challenge panels to Challenge exclusion list (#2564)
Just a small cleanup where right now, the logic still considers the entirety of the challenge "solvable" except for Challenge Vault Box
2023-12-06 18:22:11 +01:00
Aaron Wagener
49e1fd0b79 The Messenger: ease rule on key of strength a bit (#2541)
Makes the logic for accessing key of strength just a tiny bit easier since a few players said it was really difficult.
2023-12-06 18:20:18 +01:00
Yussur Mustafa Oraji
530617c9a7 sm64ex: Refactor Regions (#2546)
Refactors region code to remove references to course index.
There were bugs somewhere, but I dont know where tbh.
This fixes them but leaves logic otherwise intact, and much cleaner to look at as there's one list less to take care of.

Additionally, this fixes stopping the clock from Big Boos Haunt.
2023-12-06 18:19:03 +01:00
NewSoupVi
229a263131 The Witness: Fix logic error with Symmetry Island Upper in doors: panels (broken seed reported) (#2565)
Door entities think they can be solved without any other panels needing to be solved.

Usually, this is true, because they no longer need to be "powered on" by a previous panel.
However, there are some entities that need another entity to be powered/solved for a different reason.
In this case, Symmetry Island Lower Left set opens the latches that block your ability to solve the panel. The panel itself actually starts on. Playing doors: panels does not change this, unlike usually where dependencies like this get removed by playing that mode.

In the long term, I want to somehow be able to "mark" dependencies as "environmental" or "power based" so I can distinguish them properly.
2023-12-06 18:17:27 +01:00
NewSoupVi
a861ede8b3 The Witness: Fix various incorrect symbol requirements in Vanilla Puzzles (#2543)
* Fix Vanilla First Floor Left

* More vanilla logic fixes

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2023-12-04 16:26:00 +01:00
el-u
b7111eeccc lufia2ac: fix disappearing Ancient key (#2537)
Since the coop update, the Ancient key (which is always the reward for defeating the boss) would disappear when leaving the cave, making it impossible to open the locked door behind the Ancient Cave entrance counter. While this is basically cosmetic and has no adverse effects on the multiworld (as the door does not lead to any multiworld locations and is only accessible after defeating the final boss anyway), players may still want to enter this room as part of a ritual to celebrate their victory.

Why does this happen? The game keeps track of two different inventories, one for outside and another one for the cave dive. When entering or leaving the cave, important things such as blue chest items and Iris treasures are automatically copied to the other inventory. However, it turns out that the Ancient key doesn't participate in this mechanism. Instead, the script that runs when exiting the cave checks whether event flag 0xC3 is set, and if it is on, it calls a script action that adds the key item to the outside inventory. (Whether or not the player actually had the key item in their in-cave inventory is not checked at all; only the flag matters.)

In the unmodified game, that flag is set by the cutscene script that awards the key. It actually sets two event flags, 0xC3 and 0xD1. The latter is used by the game when trying to display the boss in the cafe basement and is used by AP as the indicator that the boss goal was completed. With the coop update, the event script method that created the key was intercepted and modified to send out a location check instead. That location always has the Ancient key as a fixed item placement; the benefit of handling it as a remote item is that in this way the key essentially serves as a signal that transmits the information of the boss' defeat to all clients cooping on the slot. When receiving the key, however, the custom ASM did only set flag 0xD1. As part of the bugfix, it is now changed to set flag 0xC3 as well.

But that alone is still not enough to make it work. The subroutine that is called by the game to create the key when exiting the cave with flag 0xC3 is the same subroutine that gets called in the cutscene that originally tried to award the key. But that's the one that has been rewritten to send the location check instead. So instead of creating the key when leaving the cave, it would just send the same location check again, effectively doing nothing. Therefore, the other part of the bugfix is to only intercept this subroutine if the player is currently on the Ancient Cave Final Floor (where the cutscene takes place), thus making it possible to recreate the key item when exiting.
2023-12-04 00:06:52 +01:00
Star Rauchenberger
39a92e98c6 Lingo: Default color shuffle to on (#2548)
* Lingo: Default color shuffle on

* Raise error if no progression in multiworld
2023-12-04 00:06:11 +01:00
zig-for
a83bf2f616 LADX: Fix bug with Webhost usage (#2556)
We were using data created in init when we never called init
2023-12-03 21:24:35 +01:00
Alchav
e8ceb12281 Pokémon RB: Fix connection names + missing connection (#2553) 2023-12-02 18:40:38 +01:00
Aaron Wagener
6e38126add Webhost: fix options page redirects (#2540) 2023-12-01 14:20:24 -06:00
Fabian Dill
5e5018dd64 WebHost: flash each message only once (#2547) 2023-12-01 21:19:41 +01:00
Aaron Wagener
c7d4c2f63c Docs: Add documentation on writing and running tests (#2348)
* Docs: Add documentation on writing and running tests

* review improvements

* sliver requests
2023-12-01 10:26:27 +01:00
agilbert1412
80fed1c6fb Stardew Valley: Fixed potential softlock with walnut purchases if Entrance Randomizer locks access to the field office (#2261)
* - Added logic rules for reaching, then completing, the field office in order to be allowed to spend significant amounts of walnuts

* - Revert moving a method for some reason
2023-11-30 09:32:32 +01:00
Brooty Johnson
b9ce2052c5 DS3: update setup guide to preserve downpatching instructions (#2531)
* update DS3 setup guide to preserve downpatching instructions

we want to preserve this on the AP site as the future of the speedsouls wiki is unknown and may disappear at any time.

* Update worlds/dark_souls_3/docs/setup_en.md

Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com>

* Update setup_en.md

---------

Co-authored-by: Danaël V. <104455676+ReverM@users.noreply.github.com>
2023-11-30 09:29:55 +01:00
Chris Wilson
a83501a2a0 Fix a bug in weighted-settings causing accepted range values to be exclusive of outer range (#2535) 2023-11-29 22:57:40 -05:00
t3hf1gm3nt
6c5f8250fb TLOZ: Use the proper location name lookup (#2529) 2023-11-29 00:19:42 -06:00
el-u
39969abd6a WebHostLib: fix NamedRange in options presets (#2528) 2023-11-28 17:11:17 -06:00
Bryce Wilson
737686a88d BizHawkClient: Use local_path when autolaunching BizHawk with lua script (#2526)
* BizHawkClient: Change autolaunch path to lua script to use local_path

* BizHawkClient: Remove unnecessary call to os.path.join and linting
2023-11-28 22:56:27 +01:00
Bryce Wilson
ce2f9312ca BizHawkClient: Change open_connection to use 127.0.0.1 instead of localhost (#2525)
When using localhost on mac, both ipv4 and ipv6 are tried and raise separate errors
which are combined by asyncio and difficult/inelegant to handle.

Python 3.12 adds the argument all_errors, which would make this easier.
2023-11-28 22:50:12 +01:00
Alchav
f54f8622bb Final Fantasy Mystic Quest: Implement new game (#1909)
FFMQR by @wildham0 
Uses an API created by wildham for Map Shuffle, Crest Shuffle and Battlefield Reward Shuffle, using a similar method of obtaining data from an external website to Super Metroid's Varia Preset option.
Generates a .apmq file which the user must bring to the FFMQR website https://www.ffmqrando.net/Archipelago to patch their rom. It is not an actual patch file but contains item placement and options data for the FFMQR website to generate a patched rom with for AP.
Some of the AP options may seem unusual, using Choice instead of Range where it may seem more appropriate, but these are options that are passed to FFMQR and I can only be as flexible as it is.

@wildham0 deserves the bulk of the credit for not only creating FFMQR in the first place but all the ASM work on the rom needed to make this possible, work on FFMQR to allow patching with the .apmq files, and creating the API that meant I did not have to recreate his map shuffle from scratch.
2023-11-26 17:17:59 +01:00
Justus Lind
65f47be511 Muse Dash: Presets and Song Updates (#2512) 2023-11-25 22:13:59 -06:00
Bryce Wilson
eec35ab1c3 Pokemon Emerald: Fix tracker flags being reset in menus (#2511) 2023-11-25 22:13:08 -06:00
PoryGone
7a46209259 SA2B: Add AP 0.4.4 Game Chao Names (#2510) 2023-11-25 22:12:38 -06:00
Aaron Wagener
cfe357eb71 The Messenger, LADX: use collect and remove as intended (#2093)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-11-25 15:07:02 -06:00
Aaron Wagener
fe6a70a1de Docs: add documentation for options comparison (#2505) 2023-11-25 10:48:13 -06:00
Bryce Wilson
6718fa4e3b Installer: Add _bizhawk.apworld to installer deleted files (#2477) 2023-11-25 09:38:12 -06:00
Bryce Wilson
5475b04b90 Pokemon Emerald: Bump apworld version number (#2504) 2023-11-25 09:27:54 -06:00
Dinopony
d46e68cb5f Landstalker: implement new game (#1808)
Co-authored-by: Anthony Demarcy <anthony.demarcy@lumiplan.com>
Co-authored-by: Phar <zach@alliware.com>
2023-11-25 09:00:15 -06:00
JaredWeakStrike
2ccf11f3d7 KH2: Version 2 (#2009)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
Co-authored-by: Joe Prochaska <prochaska.joseph@gmail.com>
2023-11-25 08:46:00 -06:00
David St-Louis
c138918400 DOOM 1993: Added various new options (#2067) 2023-11-25 08:43:14 -06:00
Fabian Dill
59ed2602bd Pokemon: delete old files (#2501) 2023-11-25 15:42:03 +01:00
Brooty Johnson
dd47790c31 DS3: Added 'Early Banner' Setting (#2199)
Co-authored-by: Zach Parks <zach@alliware.com>
2023-11-25 08:38:18 -06:00
David St-Louis
9afca87045 Heretic: implement new game (#2256) 2023-11-25 15:22:30 +01:00
el-u
ba53278147 core: make option resolution in world tests deterministic (#2471)
Co-authored-by: Zach Parks <zach@alliware.com>
2023-11-25 13:53:02 +01:00
Star Rauchenberger
6dccf36f88 Lingo: Various generation optimizations (#2479)
Almost all of the events have been eradicated, which significantly improves both generation speed and playthrough calculation.

Previously, checking for access to a location involved checking for access to each panel in the location, as well as recursively checking for access to any panels required by those panels. This potentially performed the same check multiple times. The access requirements for locations are now calculated and flattened in generate_early, so that the access function can directly check for the required rooms, doors, and colors.

These flattened access requirements are also used for Entrance checking, and register_indirect_condition is used to make sure that can_reach(Region) is safe to use.

The Mastery and Level 2 rules now just run a bunch of access rules and count the number of them that succeed, instead of relying on event items.

Finally: the Level 2 panel hunt is now enabled even when Level 2 is not the victory condition, as I feel that generation is fast enough now for that to be acceptable.
2023-11-25 13:09:08 +01:00
Alchav
8a852abdc4 Pokémon R/B: Migrate support into Bizhawk Client (#2466)
- Removes the Pokémon Client, adding support for Red and Blue to the Bizhawk Client.
- Adds `/bank` commands that mirror SDV's, allowing transferring money into and out of the EnergyLink storage.
- Adds a fix to the base patch so that the progressive card key counter will not increment beyond 10, which would lead to receiving glitch items. This value is checked against and verified that it is not > 10 as part of crash detection by the client, to prevent erroneous location checks when the game crashes, so this is relevant to the new client (although shouldn't happen unless you're using !getitem, or putting progressive card keys as item link replacement items)
2023-11-25 11:57:02 +01:00
Fabian Dill
edb62004ef LttP: remove extra default = False (#2497)
* LttP: remove extra default = False
2023-11-25 11:12:13 +01:00
GodlFire
8d41430cc8 Shivers: Implement New Game (#1836)
Co-authored-by: Mathx2 <Mathx2@gmail.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-11-24 17:23:45 -06:00
Zach Parks
8173fd54e7 DOOM II: Add to CODEOWNERS (#2492) 2023-11-24 17:16:19 -06:00
Zach Parks
e46420f4a9 MultiServer: Create read-only data storage key for client statuses. (#2412) 2023-11-24 17:14:07 -06:00
el-u
c944ecf628 Core: Introduce new Option class NamedRange (#2330)
Co-authored-by: Chris Wilson <chris@legendserver.info>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-11-24 17:10:52 -06:00
Chris Wilson
e64c7b1cbb Fix player-options and weighted-options failing to validate settings if a payer's name is entirely numeric (#2496) 2023-11-24 16:50:32 -05:00
NewSoupVi
15797175c7 The Witness: New junk hints (#2495) 2023-11-24 13:38:46 -06:00
digiholic
4641456ba2 MMBN3: Small Bug Fixes (#2282)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-11-24 11:14:05 -06:00
Star Rauchenberger
a18fb0a14f Lingo: Move datafiles into a subdirectory (#2459) 2023-11-24 18:11:34 +01:00
David St-Louis
c5b0330223 DOOM II: implement new game (#2255) 2023-11-24 18:08:02 +01:00
Ishigh1
530e792c3c Core: Floor and ceil in datastorage (#2448) 2023-11-24 10:42:22 -06:00
Fabian Dill
d892622ab1 Plando: verify from_pool type (#2200) 2023-11-24 10:41:56 -06:00
Exempt-Medic
a8e03420ec Fill: Fix plando removing Usefuls first (#2445)
Co-authored-by: blastron <blastron@mac.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2023-11-24 10:33:59 -06:00
Star Rauchenberger
1ff8ed396b Lingo: Demote warpless painting items to filler (#2481) 2023-11-24 10:30:15 -06:00
NewSoupVi
e93842a52c The Witness: Big™ new™ content update™ (#2114)
Co-authored-by: blastron <blastron@mac.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2023-11-23 23:27:03 -06:00
el-u
205c6acb49 lufia2ac: fix client behavior at max blue chests combined with party member or capsule monster shuffle (#2478)
When option combinations at (or near) the maximum location count were used, the client could trip over a wrongly coded limit and stop sending checks.
2023-11-24 01:59:41 +01:00
Aaron Wagener
2f6b6838cd The Messenger: more optimizations (#2451)
More speed optimizations for The Messenger. Moves Figurines into their own region, so their complicated access rule only needs to be calculated once when doing a sweep. Removes a redundant loop for shop locations by just directly assigning the access rule in the class instead of retroactively. Reduces slot_data to only information that can't be derived, and removes some additional extraneous data. Removes some unused sets and lists. Removes a redundant event location, and increments the required_client_version to prevent clients that don't expect the new slot_data. Drops data version since it's going away soon anyways, to remove conflicts.
2023-11-24 00:38:57 +01:00
Fabian Dill
844481a002 Core: remove duplicate state.item_count (#2463) 2023-11-24 00:35:37 +01:00
Zach Parks
5d9896773d Generate: Add --skip_output flag to bypass assertion and output stages. (#2416) 2023-11-23 16:03:56 -06:00
JaredWeakStrike
9312ad9bfe KH2: Fix grammar to clarify which locations can have a bounty (#2488) 2023-11-23 16:02:20 -06:00
Fabian Dill
cb6467cfe6 Core: update modules, move orjson to core (#2489) 2023-11-23 21:36:20 +01:00
Fabian Dill
28ed786609 LttP: fix Ganons Tower - Compass Room - Bottom Left being listed twice in Ganons Tower location group and add missing Ganons Tower - Compass Room - Bottom Right (#2490) 2023-11-23 21:36:05 +01:00
Bryce Wilson
7efec64745 BizHawkClient: Restore use of ConnectorErrors (#2480) 2023-11-23 20:51:53 +01:00
Alchav
f840ed3a94 Pokémon R/B: Fix trainer regions (#2474)
* Fix Mt Moon B2F trainer regions

* Fix Trainer Party regions
2023-11-23 19:17:09 +01:00
Yussur Mustafa Oraji
286dfd84c0 sm64ex: Replace old launcher tutorial (#2383) 2023-11-23 12:10:32 -06:00
JaredWeakStrike
a1759ed7e1 KH2: Update Game Docs (#2188)
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2023-11-23 12:06:57 -06:00
Star Rauchenberger
ae8a81c0cb Lingo: Change docs to link to the client in the Steam Workshop (#2486) 2023-11-23 11:56:55 -06:00
Bryce Wilson
a7aed71fbe Pokemon Emerald: Fix opponent trainer moves sometimes being MOVE_NONE (#2487) 2023-11-23 11:55:50 -06:00
Bryce Wilson
0d38b41540 BizHawkClient: Add support for multiple concurrent instances (#2475)
This allows multiple client/connector pairs to run at the same time. It also includes a few other miscellaneous small changes that accumulated as I went. They can be split if desired

- Whatever the `client_socket:send` line (~440) was doing with that missing operator, it's no longer doing. Don't ask me how it was working before. Lua is witchcraft.
- Removed the `settimeout(2)` which causes the infamous emulator freeze (and replaced it with a `settimeout(0)` when the server socket is created). It appears to be unnecessary to set a timeout for discovering a client. Maybe at some point in time it was useful to keep the success rate for connecting high, but it seems to not be a problem if the timeout is 0 instead.
  - Also updated the Emerald setup to remove mention of the freezing.
- Connector script now picks the first port that's not in use in a range of 5 ports.
  - To summarize why I was previously under the impression that multiple running scripts would not detect when a port was in use:
    1. Calling `socket.bind` in the existing script will first create an ipv6 socket.
    2. A second concurrent script trying to bind to the same port would I think fail to create an ipv6 socket but then succeed in creating an ipv4 socket on the same port.
    3. That second socket could never communicate with a client; extra clients would just bounce off the first script.
    4. The third concurrent script will then fail on both and actually give an `address already in use` error.  
  - I'm not _really_ sure what's going on there. But forcing one or the other by calling `socket.tcp4()` or `socket.tcp6()` means that only one script will believe it has the port while any others will give `address already in use` as you'd expect.
  - As a side note, our `socket.lua` is much wonkier than I had previously thought. I understand some parts were added for LADX and when BizHawk 2.9 came out, but as far back as the file's history in this repo, it has provided a strange, modified interface as compared to the file it was originally derived from, to no benefit as far as I can tell.
- The connector script closes `server` once it finds a client and opens a new one if the connection drops. I'm not sure this ultimately has an effect, but it seems more proper.
- If the connector script's main function returns because of some error or refusal to proceed, the script no longer tries to resume the coroutine it was part of, which would flood the log with irrelevant errors.
- Creating `SyncError`s in `guarded_read` and `guarded_write` would raise its own error because the wrong variable was being used in its message.
- A call to `_bizhawk.connect` can take a while as the client tries the possible ports. There's a modification that will wait on either the `connect` or the exit event. And if the exit event fires while still looking for a connector script, this cancels the `connect` so the window can close.
  - Related: It takes 2-3 seconds for a call to `asyncio.open_connection` to come back with any sort of response on my machine, which can be significant now that we're trying multiple ports in sequence. I guess it could fire off 5 tasks at once. Might cause some weirdness if there exist multiple scripts and multiple clients looking for each other at the same time.
  - Also related: The first time a client attempts to connect to a script, they accept each other and start communicating as expected. The second client to try that port seems to believe it connects and will then time out on the first message. And then all subsequent attempts to connect to that port by any client will be refused (as expected) until the script shuts down or restarts. I haven't been able to explain this behavior. It adds more time to a client's search for a script, but doesn't ultimately cause problems.
2023-11-23 15:00:46 +01:00
Bryce Wilson
b2e7ce2c36 Pokemon Emerald: Fix using wrong key for extracted constant (#2484) 2023-11-22 12:21:15 -06:00
Remy Jette
af0d47b444 Core: Provide a better error message if only weights.yaml is provided with players: 0 (#2227) 2023-11-22 11:13:02 -06:00
Zach Parks
ee76cce1a3 Rogue Legacy: Fix a preset including an option that prevents generation. (#2473) 2023-11-22 10:42:21 -06:00
agilbert1412
0f98cf525f Stardew Valley: Generate proper filler for item links (#2069)
Co-authored-by: Zach Parks <zach@alliware.com>
2023-11-22 10:04:33 -06:00
Zach Parks
cfd2e9c47f Core: Increment Archipelago Version (#2483) 2023-11-22 10:04:10 -06:00
digiholic
4a9d075b77 MMBN3: Adds instructions for using the Legacy Collection ROM for setup (#2120)
Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
2023-11-22 09:45:32 -06:00
Rjosephson
79406faf27 RoR2: 1.3.0 content update (#2425) 2023-11-22 09:20:32 -06:00
zig-for
01b566b798 LADX: Text shuffle (#2051) 2023-11-22 08:29:33 -06:00
Jarno
d1b22935b4 Timespinner: New options from TS Rando v1.25 + Logic fix (#2090)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-11-22 08:17:33 -06:00
agilbert1412
3b357315ee Git: Added file type .smc to gitignore (#2476) 2023-11-22 08:15:35 -06:00
BadMagic100
f959819801 Hollow Knight: Don't force mimics local (#2482) 2023-11-22 08:15:09 -06:00
agilbert1412
e916b0d6b0 Stardew Valley: Add Options presets (#2470) 2023-11-18 12:35:57 -06:00
Zach Parks
790f192ded WebHost: Refactor tracker.py, removal of dead code, and tweaks to layouts of some tracker pages. (#2438) 2023-11-18 12:29:35 -06:00
Aaron Wagener
185a519248 Core: fix item links around core changes (#2452) 2023-11-16 04:55:18 -06:00
Zach Parks
79ad54623b WebHost, Core: Developer-defined game option presets. (#2143) 2023-11-16 04:37:06 -06:00
Bryce Wilson
3619abc7ca Pokemon Emerald: Fix scorched slab missing surf requirement (#2465) 2023-11-16 04:36:38 -06:00
FlySniper
cb0412e011 Wargroove: Fixed WargrooveClient retaining victory and location information and minor doc fix (#2464) 2023-11-16 04:35:20 -06:00
Justus Lind
e66ce6c05f Muse Dash: Rename some Trap Items to match the wider community name (#2180) 2023-11-16 04:33:56 -06:00
Star Rauchenberger
a4b625c3e3 Lingo: Sync config with game update (#2447) 2023-11-16 04:12:44 -06:00
PoryGone
85d02b2dc5 SA2B: v2.3 - The Chao Update (#2277)
Changelog:

Features:
- New goal
  - Chaos Chao
    - Raise a Chaos Chao to win!
- New optional Location Checks
  - Chao Animal Parts
    - Each body part from each type of animal is a location
  - Chao Stats
    - 0-99 levels of each of the 7 Chao stats can be locations
    - The frequency of Chao Stat locations can be set (every level, every 2nd level, etc)
  - Kindergartensanity
    - Classroom lessons are locations
      - Either all lessons or any one of each category can be set as locations
  - Shopsanity
    - A specified number of locations can be placed in the Chao Black Market
    - These locations are unlocked by acquiring `Chao Coin`s
    - Ring costs for these items can be adjusted 
  - Chao Karate can now be set to one location per fight, instead of one per tournament
- Items
  - If any Chao locations are active, the following will be in the item pool:
    - Chao Eggs
    - Garden Seeds
    - Garden Fruit
    - Chao Hats
    - Chaos Drives
- The starting eggs in the garden can be a random color
- Chao World entrances can be shuffled
- Chao are given default names
- New Traps
  - Reverse Trap

Quality of Life:
- Chao Save Data is now separate per-slot in addition to per-seed
  - This allows a single player to have multiple slots in the same seed, each having separate Chao progress
- Chao Race/Karate progress is now displayed on Stage Select (when hovering over Chao World)
- All Chao can now enter the Hero and Dark races
- Chao Karate difficulty can be set separately from Chao Race difficulty
- Chao Aging can be sped up at will, up to 15×
- New mod `config` option to fine-tune Chao Stat multiplication
  - Note: This does not mix well with the Mod Manager "`Chao Stat Multiplier`" code
- Pong Traps can now activate in Chao World
- Maximum range for possible number of Emblems is now 1000
- General APWorld cleanup and optimization
  - Option access has moved to the new options system
  - An item group now exists for trap items

Bug Fixes:
- Dry Lagoon now has all 11 Animals
- Eternal Engine - 2 (Standard and Hard Logic) now requires only `Tails - Booster`
- Lost Colony - 2 (Hard Logic) now requires no upgrades
- Lost Colony - Animal 9 (Hard Logic) now requires either `Eggman - Jet Engine` or `Eggman - Large Cannon`
2023-11-16 08:08:38 +01:00
Fabian Dill
829c664304 Core: check that location address is unique per player (#2429) 2023-11-15 20:50:00 +01:00
Danaël V
28a20391ab Docs: Rework of Contributing.md (#2278)
* Update contributing.md

* Update docs/contributing.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update contributing.md

Separated the sentence specifically for web stuff as well as slight rephrasing of the first bullet point

* Update docs/contributing.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update docs/contributing.md

Changed the order of two words

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update docs/contributing.md

Clarified "this document"

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2023-11-15 19:18:57 +01:00
Aaron Wagener
bf8432faa7 Docs: minor updates to recommend modern PEP8 (#2384)
* docs: update world api for modern PEP8 conventions

* docs: update options api for modern PEP8 styling

* missed a spot
2023-11-15 17:07:42 +01:00
el-u
2af5410301 core: fix item/location descriptions test (#2450) 2023-11-15 07:26:10 +01:00
Trevor L
41b6aef23c Hylics 2: Unique entrance names, fix APWorld on 3.8 (#2460)
* Blasphemous: Set rules for events later

* Blasphemous: More misc logic fixes

* Update worlds/blasphemous/Rules.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Update worlds/blasphemous/Rules.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Blasphemous: Some cleanup

* Hylics 2: Unique entrance names, fix APWorld on 3.8

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-11-15 07:03:40 +01:00
Fabian Dill
8ce073e355 Core: relax typing hints on has_all and has_any (#2462) 2023-11-15 06:53:37 +01:00
Fabian Dill
287a186ff6 CommonClient: make IncompatibleVersion more explicit. (#2350) 2023-11-15 06:13:41 +01:00
FaultBat
44a9bb59ec Factorio: Update icons to match vanilla style (#2449) 2023-11-15 03:15:37 +01:00
el-u
6c1ae77db4 lufia2ac: improve performance of access rules (#2456)
Modifies various access rules in the lufia2ac world with the aim of making them evaluate quicker.
Instead of having to determine the reachability of another location, they now only have to count items in state, which is faster.
(Also made it reuse the identical lambda for multiple locations, which might save a smidgen of memory.)
2023-11-15 03:11:02 +01:00
black-sliver
0239578a62 Setup: fix orjson import on frozen py3.8 (#2458)
orjson has a .py entry point that imports `from .orjson` (from the DLL), which does not work on 3.8 if the DLL is not in the same folder as the .py. The changed import system in 3.9+ seems to allow this. Excluding it from libraries.zip will put both files into the same folder.
2023-11-13 22:42:34 +01:00
Fabian Dill
81cc016267 LttP: write fairy bottle fill to spoiler and prevent fart in a bottle (#2424) 2023-11-13 06:50:45 +01:00
Fabian Dill
f63743f9a9 Core: limit perf logger to 4 post-point places (#2404) 2023-11-13 06:49:31 +01:00
Fabian Dill
b3a9d58e02 Core: update modules (#2440) 2023-11-13 06:48:50 +01:00
Fabian Dill
ef7d8a6b4f Core: limit parallel APContainer writing (#2443)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-11-13 06:46:40 +01:00
Star Rauchenberger
cc0ea6a9e9 Lingo: Made entrance names unique (#2454) 2023-11-12 19:22:05 -06:00
Remy Jette
4d711a0aa5 Installer: Fix invalid component error in inno_setup.iss (#2455) 2023-11-12 19:21:17 -06:00
Bryce Wilson
43041f7292 Pokemon Emerald: Implement New Game (#1813) 2023-11-12 15:39:34 -06:00
black-sliver
e670ca513b Fill: fix swap error found in CI (#2397)
* Fill: add test for swap error with item rules

https://discord.com/channels/731205301247803413/731214280439103580/1167195750082560121

* Fill: fix swap error found in CI

Swap now assumes the unplaced items can be placed before the to-be-swapped item.
Unsure if that is safe or unsafe.

* Test: clarify docstring and comments in fill swap test

* Test: clarify comments in fill swap test more
2023-11-11 10:54:51 +01:00
Remy Jette
df1e78c6f2 WebHost: Sort tracker last activity 'None' as maximum instead of -1 (#2446)
When managing an async, it can be useful to sort the tracker by Last
Activity to see who has potentially abandoned their slots. Today, if a
slot hasn't been started (last activity is None) then it is sorted as
if last activity is -1, that it is it has had more recent activity than
any other slot.

This change makes it so slots that haven't started are treated as if
they have last activity MAX_VALUE time ago. This way they get sorted
with slots that haven't been touched in a long time which should make
intuitive sense as the "last activity" is effectively inf time ago.
2023-11-11 01:13:32 -05:00
Natalie Weizenbaum
2dd904e758 Allow worlds to provide item and location descriptions (#2409)
These are displayed in the weighted options page as hoverable tooltips.
2023-11-11 01:06:54 -05:00
Aaron Wagener
64159a6d0f The Messenger: fix logic rule for spike darts and power seal hunt (#2414) 2023-11-11 05:49:55 +01:00
Fabian Dill
ac77666f2f Factorio: skip a bunch of file IO (#2444)
In a lot of cases, Factorio would write data to file first, then attach that file into zip. It now directly attaches the data to the zip and encapsulation was used to allow earlier GC in places (rendered templates especially).
2023-11-10 22:02:34 +01:00
Star Rauchenberger
7af7ef2dc7 Lingo: Removed "Reached" event items (#2442) 2023-11-10 13:19:05 -06:00
Star Rauchenberger
f444d570d3 Lingo: Fix edge case painting shuffle accessibility issues (#2441)
* Lingo: Fix painting shuffle logic issue in The Wise

* Lingo: More generic painting cycle prevention

* Lingo: okay how about now

* Lingo: Consider Owl Hallway blocked painting areas in vanilla doors

* Lingo: so honestly I should've seen this one coming

* Lingo: Refined req_blocked for vanilla doors

* Lingo: Orange Tower Basement is also owl-blocked

* Lingo: Rewrite randomize_paintings to eliminate rerolls

Now, mapping is done in two phases, rather than assigning everything at once and then rerolling if the mapping is non-viable.
2023-11-10 13:07:56 -06:00
NewSoupVi
b5bd95771d Raft: Use world.random instead of global random (#2439) 2023-11-09 08:47:36 +01:00
Star Rauchenberger
ea9c31392d Lingo: New game (#1806)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Phar <zach@alliware.com>
2023-11-08 17:35:12 -06:00
Ziktofel
154e17f4ff SC2: 0.4.3 bugfixes (#2273)
Co-authored-by: Matthew <matthew.marinets@gmail.com>
2023-11-08 12:00:55 -06:00
Mewlif
504d09daf6 Undertale: Logic fixes (#2436) 2023-11-08 11:50:29 -06:00
Aaron Wagener
03e1c45d71 Tests: log the seed fo slot_data failures (#2402) 2023-11-08 09:15:06 +01:00
Silvris
ced35c5b78 CommonClient: Add a hints tab (#2392)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-11-07 14:51:35 -06:00
Nicholas Saylor
779a312650 Docs, Undertale: Added Suggestions Missed in #2285 (#2435)
Co-authored-by: jonloveslegos <68133186+jonloveslegos@users.noreply.github.com>
Co-authored-by: kindasneaki <ryandj67@hotmail.com>
Co-authored-by: ScootyPuffJr1 <77215594+scootypuffjr1@users.noreply.github.com>
2023-11-07 14:41:13 -06:00
Fabian Dill
72cb8b7d60 Factorio: inflate location pool (#2422) 2023-11-07 21:02:28 +01:00
TheLynk
5a7d69c8b4 ChecksFinder: Tweak link in ChecksFinder (#2353)
Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>
Co-authored-by: Marech <marechal-l@gmx.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-11-07 11:31:06 -06:00
NewSoupVi
c984b48149 The Witness: Fix Town Tower 4th Door Logic (#2421) 2023-11-07 07:39:36 +01:00
axe-y
84fb2f58fa DLC Quest Stardew: bug (#2423) 2023-11-06 06:01:49 +01:00
Fabian Dill
e1f1bf83c2 Core: Running item Plando dot (#2405) 2023-11-05 00:15:39 -05:00
black-sliver
d2e9bfb196 AppImage: allow loading apworlds from ~/Archipelago and copy scripts (#2358)
also fixes some mypy and flake8 violations in worlds/__init__.py
2023-11-04 10:26:51 +01:00
black-sliver
880326c9a5 SM: fix missed SMWorld.spheres in #2400 (#2419) 2023-11-02 21:08:36 +01:00
espeon65536
ec70cfc798 OoT: fix incorrect calls to sweep_for_events (#2417) 2023-11-02 20:02:38 +01:00
Aaron Wagener
5669579374 Core: make state.prog_items a Dict[int, Counter[str]] (#2407) 2023-11-02 06:41:20 +01:00
espeon65536
19dc0720ba OoT: fix enhanced_map_compass generation failure (#2411) 2023-11-02 06:39:29 +01:00
dennisw100
f701b81308 Docs: Terraria Setup Guide added information about the Upgraded Research Mod (#2338)
Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
Co-authored-by: Seldom <38388947+Seldom-SE@users.noreply.github.com>
2023-11-01 16:08:04 -05:00
kindasneaki
d7ec722aba RoR2: update options (#2391) 2023-10-31 22:34:24 +01:00
Natalie Weizenbaum
dc80f59165 WebHost: Expose name groups through the weighted-settings UI (#2327)
* Factor out a common function for building lists

* Expose name groups through the weighted-settings UI

* Fix weighted-settings page

The request for the JSON file that provides the setting data was missed during the rename in #2037, so prior to this the weighted settings page wasn't rendering at all.
2023-10-31 17:25:07 -04:00
Natalie Weizenbaum
5726d2f962 Fix weighted-settings page (#2408)
The request for the JSON file that provides the setting data was missed during the rename in #2037, so prior to this the weighted settings page wasn't rendering at all.
2023-10-31 17:22:02 -04:00
Nicholas Saylor
560c57fedd Docs, Various Games: Add Unique Local Commands to Game Page (#2285)
* Add Unique Locals Commands to ChecksFinder

* Add Unique Locals Commands to MMBN3 Game Page

* Add Unique Locals Commands to Ocarina of Time Game Page

* Add Unique Locals Commands to Undertale Game Page

* Add Unique Locals Commands to Wargroove Game Page

* Add Unique Locals Commands to The Legend of Zelda Game Page

* Add Unique Locals Commands to Zillion Game Page

* Amend Unique Locals Commands on Final Fantasy 1 Game Page

* Add Unique Locals Commands to Pokemon R/B Game Page

* Grammar fix for FF1

* Corrected sections names to match

* Added commands to Starcraft 2 Wings of Liberty game page

Co-authored-by: Bicoloursnake <60069210+bicoloursnake@users.noreply.github.com>

---------

Co-authored-by: Bicoloursnake <60069210+bicoloursnake@users.noreply.github.com>
2023-10-31 17:20:24 -04:00
Remy Jette
3bff20a3cf WebHost: Round percentage of checks, fix possible 500 error (#2270)
* WebHost: Round percentage of checks, fix possible 500 error

* Round using str.format in the template

How the percentage of checks done should be displayed is a display
concern, so it makes sense to just always do it in the template. That
way, along with using .format() instead of .round, means we always get
exactly the same presentation regardless of whether it ends in .00
(which would not round to two decimal places), is an int (which
`round(2)` wouldn't touch at all), etc.

* Round percent_total_checks_done in lttp multitracker

* Fix non-LttP games showing as 0% done in LttP MultiTracker
2023-10-31 17:20:07 -04:00
Silvris
d2c541c51c SNIClient, ALttP: expose death_text to SNI client, add message to alttp (#1793) 2023-10-31 11:11:18 +01:00
black-sliver
5f5c48e17b Core: fix some memory leak sources without removing caching (#2400)
* Core: fix some memory leak sources

* Core: run gc before detecting memory leaks

* Core: restore caching in BaseClasses.MultiWorld

* SM: move spheres cache to MultiWorld._sm_spheres to avoid memory leak

* Test: add tests for world memory leaks

* Test: limit WorldTestBase leak-check to py>=3.11

---------

Co-authored-by: Fabian Dill <fabian.dill@web.de>
2023-10-31 02:08:56 +01:00
Aaron Wagener
d4498948f2 Core: return the created entrance when connecting regions (#2406) 2023-10-30 21:14:14 +01:00
Alchav
aa56383310 Pokémon R/B: Fix incompatible option combination (#2356) 2023-10-30 21:13:02 +01:00
Fabian Dill
d743d10b2c Core: log completion time if > 1.0 seconds per step (#2345) 2023-10-30 04:06:40 +01:00
espeon65536
db978aa48a OoT Time Optimization (#2401)
- Entrance randomizer no longer grows with multiworld
- Improved ER success rate again by prioritizing Temple of Time even more
- Prefill is faster, has slightly reduced failure rate when map/compass are in dungeon but previous items in any_dungeon (which consumed all available locations), no longer removes items from the main itempool; itemlinked prefill items removed to accomodate improvements
- Now triggers only one recache after `generate_basic` instead of one per oot world
- Avoids recaches during `create_regions`
- All ER temp entrances have unique names (so the entrance cache does not break)
2023-10-30 04:05:49 +01:00
Fabian Dill
f81e72686a Core: log fill progress (#2382)
* Core: log fill progress

* Add names to common fill steps

* Update Fill.py

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* cleanup default name

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-10-30 01:22:00 +01:00
Justus Lind
d5745d4051 Muse Dash: Adds the new songs in the Happy Otaku Pack Vol.18 update. (#2398) 2023-10-30 01:21:29 +01:00
Scipio Wright
36f95b0683 Noita: Fix rare item fill failure for single-player games (#2387) 2023-10-29 20:02:53 +01:00
Fabian Dill
9c80a7c4ec HK: skip for loop (#2390) 2023-10-29 19:53:57 +01:00
Fabian Dill
3e0d1d4e1c Core: change Region caching to on_change from on-miss-strategy (#2366) 2023-10-29 19:47:37 +01:00
black-sliver
d9b076a687 Stardew Valley: simplify in-place (#2393)
this allows skipping multiple simplifications of the same object, e.g. item_rules
also update the logic simplification tests to be a proper unittest.TestCase
2023-10-29 13:20:28 +01:00
Alchav
ff65de1464 Pokemon R/B: Reenable Rock tunnel location access rules (#2396) 2023-10-28 17:32:03 -05:00
Scipio Wright
b874febb1e Noita: Extra Life change (#2247)
* Item rate update, also removed unnecessary reverse region connections

* Converted sets into lists, removed empties
2023-10-28 22:27:57 +02:00
Bryce Wilson
acfc71b8c9 BizHawkClient: Add support for server passwords (#2306) 2023-10-28 21:48:31 +02:00
Trevor L
e8a7200740 Blasphemous: Include ranged attack in logic for all difficulties (#2271) 2023-10-28 21:47:14 +02:00
Yussur Mustafa Oraji
253f3e61f7 sm64ex: All Bowser Stages Goal (#2112) 2023-10-28 21:44:16 +02:00
el-u
2353346768 minecraft: avoid duplicate prefix in output file name (#2048) 2023-10-28 21:43:09 +02:00
t3hf1gm3nt
4b95065c47 TLOZ: Update setup doc to include what version of TLOZ is required (#2395) 2023-10-28 13:49:07 -05:00
eudaimonistic
f5e9fc9b34 Docs, WebHost: Update faq_en.md (#2313)
* Update faq_en.md

Reorganizing information and adding links to some of the various guides and website pages.  Even just adding the Getting Started, Supported Games, and Server Commands links seems like a hefty upgrade.  We have good resources, we should make them obvious.

I think more can probably be done here, but I already shuffled this around a lot.

* Reorganize information again, elaborate single player

Sneaki's suggestion makes way more sense organizationally.  Added more detail to the single player section to more clearly explain the easiest method.

* Usage of multi-world

Consistency

Co-authored-by: kindasneaki <ryandj67@hotmail.com>

* More multi-world

More consistency

Co-authored-by: kindasneaki <ryandj67@hotmail.com>

* Revert to multiworld

Makes more sense and is colloquially the preferred terminology.

* Rework "leaving early"

Changed the "What if a player needs to leave early" section into, "Does everyone need to be connected at the same time?"

This allows the FAQ to explain briefly what a sync multiworld and an async multiworld is.  This is probably good material for the Glossary, but it comes up so much in the Discord that we probably need to explain it here as briefly as possible.  This paragraph lends itself to the question of what to do if a player must leave early anyway.

* Grammatical, tensing, and voice updates for consistency with other pages I originally authored.

---------

Co-authored-by: kindasneaki <ryandj67@hotmail.com>
Co-authored-by: Chris Wilson <chris@legendserver.info>
2023-10-28 14:18:11 -04:00
black-sliver
bf46e0e60f Core: deprecate Utils.get_options and remove Utils.get_default_options (#2352)
* Core: deprecate Utils.get_options and remove Utils.get_default_options

* L2AC, Adventure: use settings instead of Utils.get_options
2023-10-28 19:32:12 +02:00
kindasneaki
7bddea3ee8 Hollow Knight: update item name groups (#2331)
* add missing groups

* remove set comprehensions

* fix boss essence

* reorganized them

* combine boss essence on creation instead of update

* rename to match option names

* Add missing groups

* add PoP totem
2023-10-28 13:30:18 +02:00
Alchav
bdc15186e7 Pokémon R/B: Fix cave surf bug (#2389) 2023-10-28 06:40:06 +02:00
Fabian Dill
20dd478fb5 OoT: create and copy less useless data state (#2379) 2023-10-28 03:13:08 +02:00
black-sliver
e3112e5d51 Stardew Valley: Cut tests by 3 minutes (#2375)
* Stardew Valley: Test: unify mods

* Stardew Valley: Test: don't use SVTestBase where setUp is unused

* Stardew Valley: Test: remove duplicate backpack test

* Stardew Valley: Test: remove 2,3,4 heart tests

assume the math is correct with just 2 points on the curve

* Stardew Valley: Test: reduce duplicate test/gen runs

* Stardew Valley: Test: Change 'long' tests to not use TestBase

TestBase' setUp is not being used in the changed TestCases

* Stardew Valley: Test: Use subtests and inheritance for backpacks

* Stardew Valley: Test: add flag to skip some of the extensive tests by default
2023-10-28 00:18:33 +02:00
Fabian Dill
c470849cee Core: remove custom_data (#2380) 2023-10-27 19:10:16 +02:00
black-sliver
fc2855ca6d Stardew Valley: speed up rules creation by 4% (#2371)
* Stardew Valley: speed up rules creation by 4%

No class should ever inherit from And, Or, False_ or True_ and isinstance is not free.
Sadly there is no cheap way to forbid inheritance, but it was tested using metaclass.

* Stardew Valley: save calls to type()

Local variable is a bit faster than fetching type again

* Stardew Valley: save calls to True_() and False_(), also use 'in' operator

* Stardew Valley: optimize And and Or simplification

* Stardew Valley: optimize logic constructors
2023-10-27 18:09:12 +02:00
ArashiKurobara
6a2407468a OoT: Update YAML Instructions (#1745)
Existing setup guide hard-coded in a YAML from 0.1.7
2023-10-27 15:43:36 +02:00
Aaron Wagener
9281011315 Tests: Add a unit test for slot_data (#2333)
* Tests: Add a unit test for slot_data

* use NetUtils.encode

* modern PEP8
2023-10-27 12:33:59 +02:00
Aaron Wagener
d595b1a67f Docs: slight adding games.md rework (#1192)
* begin reworking adding games.md

* make it presentable

* some doc cleanup

* style cleanup

* rework the "more on that later" section of SDV

* remove now unused images

* make the doc links consistent

* typo

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-10-27 12:30:32 +02:00
Aaron Wagener
16fe66721f Stardew Valley: Use the pre-existing cache rather than ignoring it (#2368) 2023-10-27 12:12:17 +02:00
Jarno
3b5f9d1758 Timespinner: Fixed generation error caused by new options system (#2374) 2023-10-27 12:01:46 +02:00
Bryce Wilson
0f7ebe389e BizHawkClient: Add better launcher component suffix handling (#2367) 2023-10-27 06:14:25 +02:00
Justus Lind
6061bffbb6 Pokemon R/B: Avoid a case of repeatedly checking of state in ER (#2376) 2023-10-27 06:12:04 +02:00
Bryce Wilson
b16804102d BizHawkClient: Add lock for communicating with lua script (#2369) 2023-10-27 03:55:46 +02:00
Fabian Dill
88d69dba97 DLCQuest: logic speed up (#2323) 2023-10-26 00:51:32 +02:00
Fabian Dill
aa73dbab2d Subnautica: avoid cache recreation in create_regions call and clean up function. (#2365) 2023-10-26 00:03:14 +02:00
Fabian Dill
dab704df55 Core/LttP: remove initialize_regions (#2362) 2023-10-25 21:23:52 +02:00
Felix R
e5ca83b5db Bumper Stickers: add location rules (#2254)
* bumpstik: treasure/booster location rules

* bumpstik: oop missed a bit

* bumpstik: apply access rule to Hazards check

* bumpstik: move completion cond. to set_rules

* bumpstik: tests?
I have literally never written these before so 🤷

* bumpstik: oops

* bumpstik: how about this?

* bumpstik: fix some logic

* bumpstik: this almost works but not quite

* bumpstik: accurate region boundaries for BBs
since we're using rules now

* bumpstik: holy heck it works now
2023-10-25 10:22:09 +02:00
Aaron Wagener
be959c05a6 The Messenger: speed up generation for large multiworlds (#2359) 2023-10-25 09:56:56 +02:00
black-sliver
e5554f8630 SoE: create regions cleanup and speedup (#2361)
* SoE: create regions cleanup and speedup

keep local reference instead of hitting multiworld cache
also technically fixes a bug where all locations are in 'menu', not 'ingame'

* SoE: somplify region connection
2023-10-25 09:34:59 +02:00
black-sliver
e87d5d5ac2 SoE: update to v0.46.1
* install via pypi, pin hashes
* add OoB logic option
* add sequence break logic option
* fix turd ball texts
* add option to fix OoB
* better textbox handling when turning in energy core fragments
2023-10-25 00:52:57 +02:00
black-sliver
58642edc17 Core: allow multi-line and --hash in requirements.txt 2023-10-25 00:52:57 +02:00
Aaron Wagener
90c5f45a1f Options: have as_dict return set values as lists to reduce JSON footprint (#2354)
* Options: return set values as lists to reduce JSON footprint

* sorted()

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-10-24 22:50:53 +02:00
black-sliver
78a4b01db5 pytest: run tests on non-windows with new names (#2349) 2023-10-24 10:59:15 +02:00
Fabian Dill
426e9d3090 LttP: make Triforce Piece progression_skip_balancing (#2351) 2023-10-24 08:16:46 +02:00
Seldom
706a2b36db Terraria Old One's Army tier 2 and 3 missing Hardmode req (#2342) 2023-10-24 07:27:57 +02:00
Aaron Wagener
764128568e WebHost: consistent naming for player options (#2037)
* WebHost: unify references to options

* it was just an extra s the whole time...

* grammar

* redirect from old pages

* redirect stuff correctly

* use url_for

* use " for modified strings

* remove redirect cache

* player_settings

* update site map
2023-10-24 02:20:08 +02:00
Justus Lind
12c73acb20 Muse Dash: Make which .net to download more explicit in setup guides. (#2328) 2023-10-23 15:39:37 -05:00
el-u
8109d4a1af lufia2ac: prevent "door stairs" and "rare stairs" (#2341) 2023-10-23 22:20:27 +02:00
Fabian Dill
e394c316f5 Setup: new setup experience (read: torch almost all of it) (#2268) 2023-10-23 22:07:24 +02:00
Alchav
195cf60e8a Pokémon R/B: Door Shuffle efficiency improvement and crash fix (#2347)
Sweep only current player's locations so that more players does not slow it down.
Fix a slight possibility of Full door shuffle crash by only sorting for outdoor dead ends only when connecting from a non-dead end.
2023-10-23 19:28:16 +02:00
espeon65536
724999fc43 Ocarina of Time: long-awaited bugfixes (#2344)
- Added location name groups, so you can make your entire Water Temple priority to annoy everyone else
- Significant improvement to ER generation success rate (~80% to >99%)
- Changed `adult_trade_start` option to a choice option instead of a list (this shouldn't actually break any YAMLs though, due to the lesser-known property of lists parsing as a uniformly-weighted choice)
- Major improvements to the option tooltips where needed. (Possibly too much text now)
- Changed default hint distribution to `async` to help people's generation times. The tooltip explains that it removes WOTH hints so people hopefully don't get tripped up.
- Makes stick and nut capacity upgrades useful items
- Added shop prices and required trials to spoiler log
- Added Cojiro to adult trade item group, because it had been forgotten previously
- Fixed size-modified chests not being moved properly due to trap appearance changing the size
- Fixed Thieves Hideout keyring not being allowed in start inventory
- Fixed hint generation not accurately flagging barren locations on certain dungeon item shuffle settings
- Fixed bug where you could plando arbitrarily-named items into the world, breaking everything
2023-10-22 18:38:47 +02:00
BootsinSoots
50244342d9 Docs: Added Note Explaining BK and fix typo in advanced settings (#2316)
* Added Note Explaining BK

Added suggested change regarding BK mode from Issue #2295

* Changed to Glossary hyperlink

* Fix minor typo in exclude_locations

* Update worlds/generic/docs/advanced_settings_en.md

Co-authored-by: kindasneaki <ryandj67@hotmail.com>

* Docs: Reformat advanced_settings_en/progression_balancing

---------

Co-authored-by: kindasneaki <ryandj67@hotmail.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-10-22 13:11:19 +02:00
Aaron Wagener
30da81c390 Tests: modern PEP8-ify core test modules and methods (#2298)
* rename modules

* rename methods

* add docstrings to the general tests

* add base import stub

* test_base -> bases

* print deprecation warning

* redo 2346
2023-10-22 13:00:27 +02:00
Aaron Wagener
6e6fa13e44 Tests: add multiworld seed to fill subtest (#2346) 2023-10-22 12:12:26 +02:00
NewSoupVi
9f126ad0d0 The Witness: Fix random events not having the correct probabilities (#2340) 2023-10-22 06:48:06 +02:00
Fabian Dill
ee31051c43 WebHost: offer combined yaml file on /check if successful (#2337) 2023-10-22 02:02:30 +02:00
agilbert1412
a5022ccfc5 - Fix Stardew valley option that was accidentally renamed in 993 (#2336) 2023-10-21 23:28:07 +02:00
el-u
1c4303cce6 lufia2ac: add shops to the cave (#2103)
This PR adds a new, optional aspect to the Ancient Cave experience:
During their run, players can have the opportunity to purchase some additional items or spells to improve their party. If enabled, a shop will appear everytime a certain (configurable) number of floors in the dungeon has been completed. The shop inventories are generated randomly (taking into account player preference as well as a system to ensure that more expensive items can only become available deeper into the run).

For customization, 3 new options are introduced: 
- `shop_interval`: Determines by how many floors the shops are separated (or keeps them turned off entirely)
- `shop_inventory`: Determines what's possible to be for sale. (Players can specify weights for general categories of things such as "weapon" or "spell" or even adjust the probabilities of individual items)
- `gold_modifier`: Determines how much gold is dropped by enemies. This is the player's only source of income and thus controls how much money they will have available to spend in shops
2023-10-21 23:27:30 +02:00
Silvris
7c2cb34b45 Plando: prevent duplicate candidate locations (#2286) 2023-10-21 12:59:53 +02:00
Fabian Dill
1a1d607b10 Core: explicitly limit threadpool (#2334) 2023-10-20 05:14:12 +02:00
black-sliver
56796b7ee8 WebHost: minor css changes to make Supported Games page usable without js (#2266)
* WebHost: minor css changes to make Supported Games page usable without js

* Update JS to use querySelectorAll, remove most id attributes from elements, use relative element selectors

* Hide content when clearing search bar

* Remove `console.log`, remove TODO

---------

Co-authored-by: Chris Wilson <chris@legendserver.info>
2023-10-19 20:58:41 -04:00
espeon65536
b82f48fe4b Core: guard against plandoing items onto event locations (#2284) 2023-10-20 02:23:32 +02:00
Justus Lind
385803eb5c Muse Dash: Add support for specifying specific DLCs (#2329) 2023-10-20 02:13:17 +02:00
Aaron Wagener
fb6b66463d OC2: fix mistakes when moving to new options api (#2332) 2023-10-20 01:36:18 +02:00
Bryce Wilson
b707619aad BizHawkClient: Add autostart setting (#2322) 2023-10-19 07:07:15 +02:00
Natalie Weizenbaum
38c9ee146d WebHost: Refactor weighted-settings.js (#2318)
* Refactor weighted-settings.js

This moves most of the infrastructure into two classes:

* WeightedSettings covers the settings page as a whole. It tracks the
  user's current settings in local storage as well as the game data
  from the server so they don't need to be manually passed around from
  function to function.

* GameSettings covers the settings for a single game, and provides a
  view of the current settings and the game data just for that game.

* Fix item count updating
2023-10-18 18:26:52 -04:00
PsyMarth
1c7c83c69e OoT: Update Utils.py (#2310)
Removed optional maxsize parameter, setting it to the default of 128.
2023-10-18 23:53:54 +02:00
Aaron Wagener
e8a48da315 SM: fix missing option import (#2326) 2023-10-18 16:04:12 -05:00
Zach Parks
45e69f3d26 Docs: Triage role expectations documentation. (#2325)
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2023-10-18 15:11:25 -05:00
Alchav
7aab9d4439 Docs: Recommend Bizhawk Version 2.9.1 for Pokémon R/B (#2320) 2023-10-18 21:55:03 +02:00
agilbert1412
5ca1ababfd DLC Quest: Fix code structure, typos, poor code quality (#2066)
"Added a bunch of tests to make sure I don't break anything during refactoring
Huge cleanup in the Regions file, extract methods, remove code duplicate, fix typos, fix variable naming conventions, etc.
Small cleanup in other places, minor stuff just what was needed for Regions"
2023-10-18 21:53:12 +02:00
Trevor L
11ebc523a9 Hylics 2: Various fixes and APWorld support (#2324)
- Fix generation failing with certain gesture shuffle options
    - Fixed passing ItemDict to multidata instead of item code
    - Don't allow CHARGE UP to be placed at Foglast: TV
- APWorld support by removing LogicMixin from Rules.py
2023-10-18 21:50:57 +02:00
Alchav
13b68ecb15 Pokémon R/B: Door Shuffle fixes (#2314)
* Door shuffle fixes

* Add Rt 23's Victory Road exit door to list of unreachable outdoor entrances
2023-10-17 07:20:34 +02:00
Exempt-Medic
e27aeac2e5 HK: Update Setup Guide to use/mention Lumafly (#2308) 2023-10-16 19:59:07 -05:00
el-u
63c7f1deae lufia2ac: switch to new options system (#2289) 2023-10-15 04:53:28 +02:00
Fabian Dill
fffbe68428 Subnautica: cleanup pass (#2293) 2023-10-15 04:51:52 +02:00
Shiny
8fc304269e Docs: add Spanish guide for Muse Dash (#2297)
* adding setup_es

* Update setup_es.md

* Update setup_es.md

* Update __init__.py

referencing setup_es on init.py

* Update __init__.py

fixing a space
2023-10-12 19:51:10 -04:00
NewSoupVi
19d649f92b The Witness: Update docs (outdated information) (#2294)
* Update Witness Game Page

* Update outdated Witness Setup Guide

* Incorporate suggestions
2023-10-12 19:46:16 -04:00
Fabian Dill
1ef3bc78dc CommonClient: inherit Context tags (#2283) 2023-10-11 19:21:02 +02:00
Aaron Wagener
e1ee08a599 FFR: create items in create_items (#2291) 2023-10-11 01:51:13 +02:00
Remy Jette
88dfbd4087 WebHost: Show error instead of 500 for unexpected files in multidata zip (#2260)
* WebHost: Show error instead of 500 for unexpected files in multidata zip

* Add filename to error message

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-10-10 23:20:08 +02:00
Doug Hoskisson
d7475ddd73 Zillion: remove test detection hack (#2287) 2023-10-10 23:08:19 +02:00
Aaron Wagener
7193182294 Core: move option results to the World class instead of MultiWorld (#993)
🤞 

* map option objects to a `World.options` dict

* convert RoR2 to options dict system for testing

* add temp behavior for lttp with notes

* copy/paste bad

* convert `set_default_common_options` to a namespace property

* reorganize test call order

* have fill_restrictive use the new options system

* update world api

* update soe tests

* fix world api

* core: auto initialize a dataclass on the World class with the option results

* core: auto initialize a dataclass on the World class with the option results: small tying improvement

* add `as_dict` method to the options dataclass

* fix namespace issues with tests

* have current option updates use `.value` instead of changing the option

* update ror2 to use the new options system again

* revert the junk pool dict since it's cased differently

* fix begin_with_loop typo

* write new and old options to spoiler

* change factorio option behavior back

* fix comparisons

* move common and per_game_common options to new system

* core: automatically create missing options_dataclass from legacy option_definitions

* remove spoiler special casing and add back the Factorio option changing but in new system

* give ArchipIDLE the default options_dataclass so its options get generated and spoilered properly

* reimplement `inspect.get_annotations`

* move option info generation for webhost to new system

* need to include Common and PerGame common since __annotations__ doesn't include super

* use get_type_hints for the options dictionary

* typing.get_type_hints returns the bases too.

* forgot to sweep through generate

* sweep through all the tests

* swap to a metaclass property

* move remaining usages from get_type_hints to metaclass property

* move remaining usages from __annotations__ to metaclass property

* move remaining usages from legacy dictionaries to metaclass property

* remove legacy dictionaries

* cache the metaclass property

* clarify inheritance in world api

* move the messenger to new options system

* add an assert for my dumb

* update the doc

* rename o to options

* missed a spot

* update new messenger options

* comment spacing

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* fix tests

* fix missing import

* make the documentation definition more accurate

* use options system for loc creation

* type cast MessengerWorld

* fix typo and use quotes for cast

* LTTP: set random seed in tests

* ArchipIdle: remove change here as it's default on AutoWorld

* Stardew: Need to set state because `set_default_common_options` used to

* The Messenger: update shop rando and helpers to new system; optimize imports

* Add a kwarg to `as_dict` to do the casing for you

* RoR2: use new kwarg for less code

* RoR2: revert some accidental reverts

* The Messenger: remove an unnecessary variable

* remove TypeVar that isn't used

* CommonOptions not abstract

* Docs: fix mistake in options api.md

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* create options for item link worlds

* revert accidental doc removals

* Item Links: set default options on group

* change Zillion to new options dataclass

* remove unused parameter to function

* use TypeGuard for Literal narrowing

* move dlc quest to new api

* move overcooked 2 to new api

* fixed some missed code in oc2

* - Tried to be compliant with 993 (WIP?)

* - I think it all works now

* - Removed last trace of me touching core

* typo

* It now passes all tests!

* Improve options, fix all issues I hope

* - Fixed init options

* dlcquest: fix bad imports

* missed a file

* - Reduce code duplication

* add as_dict documentation

* - Use .items(), get option name more directly, fix slot data content

* - Remove generic options from the slot data

* improve slot data documentation

* remove `CommonOptions.get_value` (#21)

* better slot data description

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@yahoo.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Alex Gilbert <alexgilbert@yahoo.com>
2023-10-10 22:30:20 +02:00
Fabian Dill
a7b4914bb7 WebHost: update flask (#2250)
* WebHost: update flask

* WebHost: update flask-caching

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-10-09 10:18:41 +02:00
Fabian Dill
0d8a868ed9 Utils: support messagebox on windows without dependencies (#2224) 2023-10-08 22:14:28 +02:00
Aaron Wagener
6f9484f375 The Messenger: Make modules and tests PEP8 (#2276)
* The Messenger: PEP8 module and test names

* fix dumb

* whitespace
2023-10-08 14:33:39 +02:00
Fabian Dill
cc2247bfa0 CommonClient: fix json prints not being logged in UI mode (#2253) 2023-10-08 13:26:14 +02:00
Aaron Wagener
5eeaf834cb Tests: Add a test for fill to WorldTestBase (#2049)
* Tests: Add a test for fill to WorldTestBase

* test items and minimal accessibility, only bailing out when no reachable locations exist.

* put egg shard max/goal at sane values
114 locations - 35 always-present progression items - 25 excluded locations from settings <= 74 egg shards
past me can't do arithmetic

* f

* i'm bad at git

* make fill import local to prevent circular imports

---------

Co-authored-by: espeon65536 <espeon65536@gmail.com>
2023-10-08 12:08:47 +02:00
Aaron Wagener
fd93f6e722 Tests: add can_reach_region method to WorldTestBase (#2274) 2023-10-08 11:46:30 +02:00
Fabian Dill
5591879547 WebHost/Factorio: use "better" jinja practices in web tracker (#2257) 2023-10-08 11:30:34 +02:00
Alex Nordstrom
c3c6a7eb86 LADX: Set display names in options (#2229)
* set display_name throughout Options.py

* drop whitespace changes

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-10-07 18:36:22 +02:00
Fabian Dill
b8fe3196e0 Setup: also delete old disabled worlds (#2267) 2023-10-07 16:44:21 +02:00
el-u
6028112e0e checksfinder: create items in create_items (#2056) 2023-10-07 16:44:01 +02:00
Fabian Dill
7df1b6f496 LttP: Adjuster no longer breaks when sprite path doesn't exist. 2023-10-07 15:57:05 +02:00
Shiny
debdd4c571 Docs: Pokemon RB Spanish Setup Guide: fixed a bunch of punctuation/grammar and fixed bold format in Configuring Bizhawk section (#2228)
* added setup_es.md

setup_en 100% translated (with a bit of adaptation to spanish linguistics)

* Update __init__.py

add reference to the spanish tutorial

* Update setup_es.md

removed temporary "wip translation" header

* Update setup_es.md

formatting cleanup

* Update setup_es.md

translated "alias for" on lines 73 and 74, which I just forgot to

* Update setup_es.md

fixed a bunch of punctuation/grammar and fixed bold format in Configuring Bizhawk section

* Update worlds/pokemon_rb/docs/setup_es.md

updated bold format as per nicholassaylor's suggestion

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

---------

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2023-10-07 15:13:17 +02:00
kindasneaki
bb09811433 Docs: add info to turn on github actions (#2264)
* add info to turn on github actions

* add missing image

* add when pushing

* reduce picture size

* mention editing actions on your fork instead
2023-10-05 11:41:49 +02:00
kindasneaki
115a6b666c Webhost: random button presisted after being inactive (#2248)
* update game settings to get the proper attribute

* change to ternary operator
2023-10-04 23:53:59 -04:00
Nicholas Saylor
6c4a3959c3 Docs: Categorize Commands in Guide (#2213)
* Update commands_en.md

Commands re-ordered and put into categories

Some commands were better documented / explained more clearly

Other formatting changes

* Status command moved to General category and elaboration on getitem command

* "Multi-world" -> "Multiworld"

* Moved game-specific local commands to game pages
2023-10-04 16:52:34 -04:00
Aaron Wagener
f6e92a18de The Messenger: Fix items accessibility region rule (#2263) 2023-10-04 18:23:29 +02:00
Aaron Wagener
78057476f3 Docs: python 3.11 works now (#2258)
* Docs: python 3.11 works now

* change to py 3.12 unsupported
2023-10-03 12:19:09 +02:00
black-sliver
cdbb2cf7b7 Core: fix unittest world discovery (#2262) 2023-10-03 09:47:22 +02:00
Fabian Dill
6b48f9aac5 WebHost: link to stats from the use statistics directly on landing (#2242) 2023-10-02 20:52:58 -04:00
Bryce Wilson
bc11c9dfd4 BizHawkClient: Add BizHawkClient (#1978)
Adds a generic client that can communicate with BizHawk. Similar to SNIClient, but for arbitrary systems and doesn't have an intermediary application like SNI.
2023-10-03 02:44:19 +02:00
Bryce Wilson
24403eba1b Launcher: Allow opening patches for clients without an exe (#2176)
* Launcher: Allow opening patches for clients without an exe

* Launcher: Restore behavior for not showing patch suffixes for clients that aren't installed
2023-10-02 20:52:00 +02:00
black-sliver
e377068d1f Core: more gitignore (#2249)
gitignore versioned venvs, prof output, appimagetool and sni downloads
2023-10-02 20:17:34 +02:00
Fabian Dill
c7c94eebeb WebHost: fix indentation (#2240) 2023-10-02 20:07:31 +02:00
Fabian Dill
9d38725688 WebHost: update ponyorm (#2241) 2023-10-02 20:07:15 +02:00
Fabian Dill
18bf7425c4 WebHost: cache static misc pages (#2245) 2023-10-02 20:06:56 +02:00
Fabian Dill
17127a4117 kvui: silently fail to disable DPI awareness on Windows (#2246) 2023-10-02 20:06:29 +02:00
black-sliver
5d9b47355e CI: run tests multi-threaded (#2251) 2023-10-02 08:47:28 +02:00
black-sliver
f9761ad4e5 CI: ignore invalid hostname of some macos runners (#2252) 2023-10-02 08:34:50 +02:00
el-u
485aa23afd core: utility method for visualizing worlds as PlantUML (#1935)
* core: typing for MultiWorld.get_regions

* core: utility method for visualizing worlds as PlantUML

* core: utility method for visualizing worlds as PlantUML: update docs
2023-10-02 01:56:55 +02:00
zig-for
e08deff6f9 LADX: Implement remake style warp selection (#1587) 2023-10-02 01:16:25 +02:00
Doug Hoskisson
d5d630dcf0 Zillion: change test detection for running tests with multiprocessing (#2243) 2023-10-02 01:13:30 +02:00
Fabian Dill
58b696e986 Factorio: use orjson (#1809)
* Factorio: use orjson

* Factorio: update orjson

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-09-30 23:58:58 +02:00
lordlou
a3907e800b SMZ3: 0.4.2 non local items fix (#2212)
fixed generation failure when using non_local_items set to "Everything"
For this, GT prefill now allows non local non progression items to be placed.
2023-09-30 21:05:07 +02:00
Trevor L
5c640c6c52 Blasphemous: Fix rules for platforming room in BotSS (#2231) 2023-09-30 21:03:55 +02:00
Aaron Wagener
fe6096464c The Messenger: fix rules for two glacial peak locations (#2234)
* The Messenger: fix rules for two glacial peak locations
2023-09-30 12:35:07 +02:00
Bryce Wilson
5bf3de45f4 DS3: Add cinders item group (#2226) 2023-09-30 12:32:44 +02:00
Aaron Wagener
f33babc420 Tests: add a name removal method (#2233)
* Tests: add a name removal method, and have assertAccessDependency use and dispose its own state

* Update test/TestBase.py

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-09-30 11:53:11 +02:00
Alchav
1c9199761b LTTP: Key Drop Shuffle fix for dungeon state item removal (#2232) 2023-09-29 20:23:46 +02:00
CaitSith2
368fa64914 LttP: Update credits text for GT Big Key when key drop shuffle is on. (#2235) 2023-09-29 20:18:43 +02:00
Fabian Dill
e114ed5566 Core: offer API hook to modify Group World creation 2023-09-27 11:26:38 +02:00
Fabian Dill
5d47c5b316 WebHost: check that worlds system is not loaded in customserver (#2222) 2023-09-27 11:26:08 +02:00
Alchav
812dc413e5 LTTP: Key Drop Shuffle (#282)
Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Bondo <38083232+BadmoonzZ@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-09-27 05:24:10 +02:00
Justus Lind
4aed2be93b Muse Dash: Add mentions to Muse Plus to Docs and Options. (#2179)
* Add mentions to Muse Plus.

* Grammer fix.

* Apply Exempt-Medics Suggestion

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Make [Just as Planned] casing consistent. Fix grammar on Available Trap Types option.

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2023-09-25 22:21:09 -04:00
Chris Wilson
974bab2b24 [WebHost] Add search filter and collapse button to Supported Games page (#2215)
* Add search filter and collapse button to Supported Games page

* Autofocus search input, fix bug with arrow display when searching

* Add "Expand All" and "Collapse All" buttons. Buttons respect visible games.
2023-09-25 22:15:00 -04:00
Daivuk
98d61b32af DOOM 1993: Logic fixes 2023-09-26 01:08:56 +02:00
Fabian Dill
4141a50d8c Terraria: remove unused data 2023-09-25 23:35:55 +02:00
black-sliver
93c18cd9a7 Core/GUI: Better fileselection error (#2216)
* Core: better error if GUI is unavailable

* Core: enable open_directory kdialog and zenity

The native dialog helpers were disabled because there was odd behavior.
This is now fixed and was tested with latest zenity and kdialog.

* Core: fix open_filename suggestion for zenity
2023-09-24 10:30:33 +02:00
Trevor L
5af47425b0 Hylics 2: Add more missing locations (#2219)
Adds two missing locations in Sage Labyrinth and their items
2023-09-24 08:08:40 +02:00
NewSoupVi
b41a1e69b4 The Witness: Fix Itemlinks 2023-09-24 02:04:27 +02:00
Felix R
124113f3d3 bumpstik: update docs (#2198) 2023-09-23 00:54:21 -04:00
Shiny
2b69820619 Pokémon Red and Blue: Adding Spanish Setup Guide (#2205)
* added setup_es.md

setup_en 100% translated (with a bit of adaptation to spanish linguistics)

* Update __init__.py

add reference to the spanish tutorial

* Update setup_es.md

removed temporary "wip translation" header

* Update setup_es.md

formatting cleanup

* Update setup_es.md

translated "alias for" on lines 73 and 74, which I just forgot to
2023-09-23 00:45:46 -04:00
Bicoloursnake
f147f9e5a0 Docs: DKC3, Lufia2AC, SM, SMW, SMZ3: Updating documentation with the current location of SNI Connector.lua (#2203)
* Update SC2 setup guide

Removed a sentence that made sense when I included sudo in the command in the previous sentence, but does not make sense otherwise.

* Update en_Super Mario 64.md

It turns out castle has a lowercase l in it.

* Docs: SMW: Updated SNIClient Connector Lua Directory

* Docs: DKC3: Updated SNIClient Connector Lua Directory

* Docs: Lufia2AC: Updated SNIClient Connector Lua Directory

* Docs: SM: Updated SNIClient Connector Lua Directory

* Docs: SMZ3: Updated SNIClient Connector Lua Directory
2023-09-23 00:40:47 -04:00
Nicholas Saylor
db7c0c9db9 Docs: Clarify Documentation Information for Undertale, Terraria, DOOM 1993 (#2149)
* Cleaned up Undertale documentation
Standardized file names

* Outlined Terraria installation more clearly
Other minor edits to setup guide

* Minor edits to DOOM 1993 set-up guide

* Update worlds/terraria/docs/setup_en.md

Co-authored-by: kindasneaki <ryandj67@hotmail.com>

* Suggested changes from @Seldom-SE

Co-authored-by: Seldom <38388947+seldom-se@users.noreply.github.com>

* Code block to quotation change from code review

Co-authored-by: Seldom <38388947+seldom-se@users.noreply.github.com>
Co-authored-by: Chris Wilson <chris@legendserver.info>

* Code review from @LegendaryLinux

Co-authored-by: Chris Wilson <chris@legendserver.info>

---------

Co-authored-by: kindasneaki <ryandj67@hotmail.com>
Co-authored-by: Seldom <38388947+seldom-se@users.noreply.github.com>
Co-authored-by: Chris Wilson <chris@legendserver.info>
2023-09-22 20:45:52 -04:00
eudaimonistic
b40fba0840 Subnautica: Update en_Subnautica.md (#2207)
Added reference to the four goal options, and a small grammatical change.
2023-09-22 23:08:27 +02:00
black-sliver
ea799c494e Speedups: fix file date check when frozen (#2211) 2023-09-22 23:05:04 +02:00
Fabian Dill
b4b8426def Core: update jellyfish 2023-09-22 21:39:29 +02:00
black-sliver
39a50da55c Factorio: fix world generation in spoiler (#2209)
This used a set operation previously, resulting in random order of dict items.
2023-09-22 21:32:03 +02:00
Ziktofel
9931605f94 SC2 Client: Fix processing metadata from Github Releases for /download_data 2023-09-22 21:27:46 +02:00
NewSoupVi
8834ba88aa Witness: Expert Doors Logic Fix
Expert Swamp Maze currently thinks it needs Red Underwater 4 instead of the door. This could lead to an unbeatable seed in door shuffle, although it's very unlikely.
2023-09-22 02:55:44 +02:00
lordlou
5e46967b7d SM: 0.4.2 broken quick save and reload fix (#2204) 2023-09-22 02:49:27 +02:00
kindasneaki
638d6807db add invisible to locations div (#2191) 2023-09-20 16:53:00 -04:00
black-sliver
d471dcc067 Core, WebHost: lazy-load worlds in unpickler, WebHost and WebHostLib (#2156)
* Core: lazy-load worlds in unpickler

this should hopefully fix customserver's memory consumption

* WebHost: move imports around to save memory in MP

* MultiServer: prefer loading _speedups without pyximport

This saves ~15MB per MP and speeds up module import if it was built in-place.

* Tests: fix tests for changed WebHost imports

* CustomServer: run GC after setup

* CustomServer: cleanup exception handling
2023-09-20 16:05:56 +02:00
CaitSith2
4a27fae1ab Core: Allow any valid priority location in yaml even when they are not used in a given game. (#2128)
* Allow any valid priority location in yaml.

For some games, the use location group name "Everywhere", results in the generator failing no matter what,  as only a subset of the location names will actually be present.  A good example of that is Zillion.  It has 21 location names per room, of which, only at most 2 is ever used.


Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-09-20 11:22:01 +02:00
Remy Jette
794959e182 WebHost: Fix KeyError in alttp multitracker (#2194) 2023-09-20 10:20:03 +02:00
Mewlif
aff852fb45 Undertale: Various Fixes (#2146)
* Changed the pathing code to use os.path.join, instead of adding strings together, also fixed the savepath command using UndertaleContext instead of self.ctx (Credit to Wackerly for finding the self.ctx issue and fix)

* Undertale: Fixed a debug function in the game not requiring debug to be enabled.

* Undetale: Fixed a logic bug with the location "Letter Quest"
2023-09-20 10:18:53 +02:00
Remy Jette
a0eea3a650 WebHost: Don't count item links in the summary row completed worlds (#2193) 2023-09-20 01:27:49 +02:00
lordlou
0012584e51 SM: 0.4.2 percent goals fix (#2183)
fixed percent items goals that can fail generation (reported here https://discord.com/channels/731205301247803413/1147318124383850516/1147318124383850516 and here https://discord.com/channels/731205301247803413/1138137515505750108/1138137515505750108)
2023-09-20 01:26:42 +02:00
Silvris
6e02a4ca3c Plando: fix overwriting outer scope (#2196) 2023-09-20 00:43:37 +02:00
Fabian Dill
2ef05a1799 kvui: remove custom DPI scaling on windows (#2177) 2023-09-17 22:56:59 +02:00
Fabian Dill
fa2891f785 Factorio: offer error message with some more insight for lock file in use (#2187) 2023-09-17 22:47:06 +02:00
agilbert1412
d5d13a6d4d Stardew Valley: Fix two logic bugs with the wizard on Entrance Randomizer (#2192)
* - Added a rule to vault bundles that require access to the wizard
- Fixed the region required to meet the wizard

* - Updated the location count in a test due to a previous coffee bean bugfix that added a location
2023-09-17 20:20:18 +02:00
Doug Hoskisson
b24037e9d9 Zillion: ensure 1st sphere not empty (#2190) 2023-09-17 17:55:21 +02:00
Ziktofel
6d6de4a98e SC2: Typo fix in option display name 2023-09-16 23:00:35 +02:00
Fabian Dill
0e7c7bd1bf Core: update versions (#2186) 2023-09-16 19:23:22 +02:00
Fabian Dill
9312f14ffb Subnautica: add extra Laser Cutter Fragment to priority filler 2023-09-16 18:08:31 +02:00
Fabian Dill
ce8f07b347 Core: fix start_inventory_from_pool only adding one filler per item name 2023-09-16 18:07:40 +02:00
Bryce Wilson
cff6c7c4da DS3: Fix health locations setting not enabling (#2147)
* DS3: Fix health locations setting not enabling

* DS3: Move health locations to their own table

* DS3: Bump data version
2023-09-16 12:17:40 +02:00
Altiami
f9120c620f The Legend of Zelda Conntector: Make items obtained counter in save data 16 bits. (#2117)
Certain multiworld settings (bug observed with item link settings) can cause the total item count in TLoZ world to exceed 255. This causes an overflow in the loop to receive all pending items. This adds an additional byte to be used as a high byte for the items obtained counter.

This approach was taken due to the surrounding bytes being occupied, preventing a direct 16-bit number from being used without moving to a different location and leaving more empty bytes in the memory block for save data.
2023-09-15 20:18:03 +02:00
Ziktofel
44f1a93d31 Docs: Update SC2 map/mod link 2023-09-15 19:28:16 +02:00
Sunny Bat
6d61eae522 Raft: Fix test_collect_remove (#2109) 2023-09-15 09:30:46 +02:00
black-sliver
f05a9ecd2f settings: add default=None to Group.get (#2178)
This is regular dict behavior/emulation.
2023-09-15 09:18:14 +02:00
Ziktofel
648d682add SC2 WoL - Mod, Item and Location update (#2113)
Migrates SC2 WoL world to the new mod with new items and locations. The new mod has a different architecture making it more future proof (with planned adding of other campaigns). Also gets rid of several old bugs

Adds new short game formats intended for sync games (Tiny Grid, Mini Gauntlet). The final mission isn't decided by campaign length anymore but it's configurable instead. Allow excluding missions for Vanilla Shuffled, corrected some documentation.

NOTE: This is a squashed commit with Salz' HotS excluded (not ready for the release and I plan multi-campaign instead)

---------

Co-authored-by: Matthew <matthew.marinets@gmail.com>
2023-09-15 02:22:10 +02:00
BadMagic100
47cf3e06c0 Hollow Knight: Update outdated setup documentation (#2171)
* Hollow Knight: Update outdated setup documentation

* Update a reference from Scarab to Scarab+

Co-authored-by: kindasneaki <ryandj67@hotmail.com>

* Fix numbering

---------

Co-authored-by: kindasneaki <ryandj67@hotmail.com>
2023-09-14 23:57:36 +02:00
agilbert1412
fdac50523b Stardew Valley: Added missing logic rules for dating and marriage (#2160)
* - Added missing logic rules where, to earn hearts above 8 and 10, you need access to dating and marriage respectively.

* - Slight cleanup based on Black Sliver's suggestion
2023-09-14 23:56:13 +02:00
Alchav
7522a32ad6 Pokémon R/B: More tracker slot data (#2174) 2023-09-14 21:49:57 +02:00
lordlou
8ee743ac8a SM: 0.4.2 fixes (#2175)
## What is this fixing or adding?
- fixed failing generation with disabled layout patch by moving door_indicators_plms.ips to AP instead of the base patch (reported at https://discord.com/channels/731205301247803413/1149509811529072751/1149509811529072751)
(part of the fix is in the Basepatch with this commit 46bbda980c)

- fixed broken map data saving when using fast_save (reported at https://discord.com/channels/731205301247803413/1138163133089849344/1138163133089849344)
(part of the fix is in the Basepatch with this commit 54a82774c9)
2023-09-14 21:49:11 +02:00
Seldom
c3cfbf8e1c Terraria: Add the rest of the settings to slot data (#2116)
* Add the rest of the Terraria settings to slot data

* Update worlds/terraria/__init__.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-09-14 09:46:29 +02:00
Friðberg
1756a30acc WebHost: Clean up the exported yaml in weighted settings (#2167)
* Trim output yaml in weighted options

Remove options that have only one possible outcome as well as empty arrays, when building yaml.

* fix quotes
2023-09-11 17:17:11 -04:00
Remy Jette
57c13ff273 WebHost: Support multi-select during check/generate file upload (#2138)
* Support multi-select during check/generate file upload

This will allow the user to select multiple YAML files via Shift-Click
or Control-Click in their browser when generating a game via the site
instead of having to zip them locally first.

* Update generate.html: File -> File(s)

* Change check.html button text to "Upload File(s)" to match generate.html
2023-09-11 16:57:14 -04:00
Rob B
3d9837678c Factorio: better Technology Tree Information description (#2121)
* Fix typo in Factorio options tooltip

* Fix typo, add details

* Apply code review suggestion

It doesn't let me apply more than one change to the same line in a batch.

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Apply code review suggestion from @nicholassaylor

It doesn't let me apply more than one change to the same line in a batch.

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2023-09-11 00:13:39 +02:00
Trevor L
3e95ccd06c Blasphemous: Fixed Amanecidas not requiring Petrified Bell (#2166) 2023-09-11 00:04:57 +02:00
Alchav
0e21a3e121 Pokémon R/B: Fix broken options (#2162) 2023-09-10 23:38:56 +02:00
NewSoupVi
5eef7a34d3 The Witness: Fix Expert Tutorial Gate Close (#2164) 2023-09-10 23:34:20 +02:00
blastron
6c844750ae Witness: fix items being modified by other slots (#2161) 2023-09-10 23:29:42 +02:00
Trevor L
8649b15787 Blasphemous: Add missing logic (#2165)
* Blasphemous: Set rules for events later

* Blasphemous: More misc logic fixes

* Update worlds/blasphemous/Rules.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Update worlds/blasphemous/Rules.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Blasphemous: Some cleanup

* Blasphemous: Add missing logic

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-09-10 23:24:33 +02:00
Remy Jette
fbd64651e4 Pokemon RB: Fix typo on the game info page (#2142)
Thanks Shiny for pointing it out https://discord.com/channels/731205301247803413/1043592720603693167/1147300361883893790
2023-09-10 23:24:09 +02:00
Doug Hoskisson
e01eb4e00c Zillion: webhost config fix (#2145) 2023-09-10 23:03:22 +02:00
Fabian Dill
72b44be41c SNIClient: fix /snes command if tree (#791) 2023-09-10 07:19:40 +02:00
Bryce Wilson
2bdb1b2029 DS3: Update game page (#2163)
* DS3: Update game page

* DS3: Split long sentence in game page docs

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>

* DS3: Minor word change

---------

Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
2023-09-10 04:41:52 +02:00
Bicoloursnake
bf685dc850 Docs, SM64, SC2: Minor Documentation Updates (#2008)
* Update SC2 setup guide

Removed a sentence that made sense when I included sudo in the command in the previous sentence, but does not make sense otherwise.

* Update en_Super Mario 64.md

It turns out castle has a lowercase l in it.
2023-09-10 03:51:12 +02:00
Brooty Johnson
faf4887616 DS3: add more options to slot_data for autotracking (#2148) 2023-09-10 03:33:57 +02:00
Nicholas Saylor
a1418ccb66 Docs: Small typo and proofreading edits (#2078)
* Slight rewording of DS3 game page

Lists made more concise, space added between "generated weapons" and open parenthesis

* Proofread Final Fantasy pages

Fixed minor typos and reworded sentences for conciseness.

* Edited Kingdom Hearts 2 Game Page

Refined style, capitalization, and sentence structure for clarity

* Fixed nested list in Minecraft game page

Each nest needed an additional 2 spaces

* Edited Risk of Rain 2 Game Page

Made various edits to redundancy within the page as well as omitted/unclear information

* Edited Stardew Valley game page

Small capitalization consistency edits and slight rewording for conciseness

* Update worlds/ff1/docs/multiworld_en.md

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/kh2/docs/en_Kingdom Hearts 2.md

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Update worlds/kh2/docs/en_Kingdom Hearts 2.md

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>

* Add information for EXP multiplier

Include Drive Forms and Summons

* Correction for Newt Altars RoR2

Co-Authored-By: kindasneaki <19377912+kindasneaki@users.noreply.github.com>

---------

Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com>
Co-authored-by: kindasneaki <19377912+kindasneaki@users.noreply.github.com>
2023-09-10 03:30:03 +02:00
Fabian Dill
29f8053d6e Factorio: fix website multitracker (#2126)
Co-authored-by: Remy Jette <remy@remyjette.com>
2023-09-10 00:33:36 +02:00
Fabian Dill
f6dafa2b56 Core: collect errors from generate_output at same step as multidata 2023-09-09 19:55:11 +02:00
Fabian Dill
2b9e8fa273 WebHost: flask caching doesn't do lazy init anymore (#2155) 2023-09-09 05:02:05 +02:00
Mathx2
5368451867 Timespinner: Options.py Typo (#2154)
Line 63, changed the commend from (Reccomended) to (Recommended)
2023-09-07 22:23:42 +02:00
Fabian Dill
77a349c1c6 Core/LttP: remove can_reach_private 2023-08-31 22:10:38 +02:00
agilbert1412
c4a3204af7 Stardew Valley: Add missing special order logic rules (#2136)
* - Added missing special order requirements, mostly for the regions where to place the collected items, or the NPC to talk to when done

* - Added missing requirement on being able to go see the wizard cutscene in order to interact with bundles
2023-08-31 06:45:52 +02:00
Remy Jette
9323f7d892 WebHost: Add a summary row to the Multiworld Tracker (#1965)
* WebHost: Add a summary row to the Multiworld Tracker

Implements suggestions from the generation-suggestions channel:
- https://discord.com/channels/731205301247803413/1124186131911688262
- https://discord.com/channels/731205301247803413/1109513647274856518

* Improve secondsToHours function, and remove jQuery from footerCallback function.

* Don't show the summary row on game-specific multi trackers

---------

Co-authored-by: Chris Wilson <chris@legendserver.info>
2023-08-29 17:58:49 -04:00
Fabian Dill
30e747bb4c Windows: create terminal capable Launcher (#2111) 2023-08-29 20:59:39 +02:00
Justus Lind
9d29c6d301 Muse Dash: Fix bad generations occuring due to changing item ids (#2122) 2023-08-29 20:58:34 +02:00
NewSoupVi
aa19a79d26 Witness: Fix one of the hints not being a Haiku (seriously) (#2123)
I hope this gets a prize for "Most irrelevant PR in AP history"

Explanation:
When changing the hint system on the client side to be able to auto-wrap, decisions were made about which line breaks were still explicitly important, with most of them being removed.

This hint was somewhat devalued in the process.

-. --- - .... .. -. --. translates to "Nothing", which I thought was the entirety of the joke.

However, the line breaks were actually also important, because:

dash dot, dash dash dash,
dash, dot dot dot dot, dot dot,
dash dot, dash dash dot

is a Haiku! And the hint's creator (oddGarrett I believe) said this was specifically part of the creative vision for this joke hint. They said it's fine, I don't need to change it, but I couldn't let that stand.

So, the explicit line breaks for this joke hint are back.
2023-08-29 20:56:40 +02:00
Alchav
5a34471266 Pokémon R/B: Fix fishing logic mistake (#2133) 2023-08-29 20:56:07 +02:00
eudaimonistic
ae96010ff1 [Subnautica] update subnautica/docs/setup_en.md (#2131)
At some point the client-side mod for this world started to include support for the "!" in dev console, rendering this line obsolete.  Updated to reflect current client behavior.
2023-08-29 18:05:12 +02:00
CaitSith2
944fe6cb8c Fix root cause of ALttP hardware crashes on collect. (#2132)
As it turns out, SD2SNES / FXPAK Pro has a limit as to how many bytes can be written in a single command packet.  Exceed this limit, and the hardware will crash.  That limit is 512 bytes.  Even then, I have scaled back to 256 bytes at a time for a margin of safety.
2023-08-29 06:07:31 -07:00
agilbert1412
21baa302d4 [SDV] Added a missing logic rule for talking to Leo (#2129) 2023-08-29 00:55:13 +02:00
Fabian Dill
9ad0032eb4 Windows: fix ArchipelagoServer.exe not being installed. 2023-08-25 22:29:56 +02:00
Seldom
09fd65209c Terraria: Fix Soul of Sight requiring Post-The Twins flag instead of access to The Twins (#2115) 2023-08-25 22:27:28 +02:00
Aaron Wagener
d8d9a49564 The Messenger: Fix a typo preventing a location from being created (#2110)
* The Messenger: Fix a typo preventing a location from being created

* Add a unit test that locations are created
2023-08-25 22:25:56 +02:00
zig-for
41a34b140c LttP: Fixes patching on a fresh AP install (#2118) 2023-08-25 22:25:02 +02:00
kindasneaki
b235ba2c52 RoR2: Move filler item creation to get_filler_item_name (#2075) 2023-08-16 09:21:07 -05:00
agilbert1412
7ce9f20bc7 Stardew Valley: Added rules requiring museum access to make donations (#2107) 2023-08-16 09:18:50 -05:00
Trevor L
6c7a7d2be5 Blasphemous: Set rules for event items later + misc logic fixes (#2084)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-08-16 09:09:49 -05:00
Alchav
8af4fda7b6 Pokemon R/B: locations accessibility fixes, etc (#2104) 2023-08-16 09:04:44 -05:00
blastron
e30f364bbd Witness: Fix for Witness plando crashes. (#2092) 2023-08-16 09:03:41 -05:00
Bryce Wilson
be07634b15 Docs: Update generate_output docstring (#2098) 2023-08-16 09:02:43 -05:00
Mewlif
5cd837256f Undertale: Key placement fix (#2030)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-08-16 09:02:01 -05:00
Doug Hoskisson
26b4ff1df2 Zillion: Python 3.11 compatibility fix (#2105) 2023-08-16 09:00:10 -05:00
Seldom
61ff94259a Terraria: Update Terraria Docs mod recommendations (#2095) 2023-08-16 08:57:27 -05:00
lordlou
4cb4c254dc SM: fix location counters preventing some goal completion (#2108) 2023-08-15 10:11:46 +02:00
Zach Parks
3a4b157363 Adjustments to player-settings.css for better UI on small view-widths. (#2019)
Also removes "portrait" media query as it forces this display method for large monitors.
2023-08-12 23:06:28 -04:00
Alchav
7a494d637b Pokémon RB: Route 3 Guard fix (#2077)
* Pokémon RB: Route 3 Guard fix

* Change dexsanity option names

* Diglett's Cave warp fix
2023-08-11 11:04:21 +02:00
David St-Louis
ca06a4b836 DOOM 1993: Fixed rule for red region in E3M9 (#2079) 2023-08-11 11:03:23 +02:00
Justus Lind
3643b1de2c Muse Dash: Make item_name_to_id and location_name_to_id ordering deterministic (#2086)
* Fix up non-deterministic order of item_name_to_id and location_name_to_id.

* Remove debug line.

* Change to use a Chainmap instead and simplify logic a bit.

* Add the forgotten music sheet item.
2023-08-11 11:02:35 +02:00
digiholic
d0c6eaf239 MMBN3: Fixes hint spam when receiving a hint (#2087)
* Only sends hints whenever the list changes

* Further reduces hint spam by not re-sending the entire list when one new thing is added
2023-08-11 11:01:24 +02:00
N00byKing
64d1722acd sm64ex: Fix possible inaccessible region 2023-08-11 10:59:24 +02:00
agilbert1412
01e8e9576c Stardew Valley: Fixed Help wanted rules, added missing coffee bean to cropsanity (#2089)
* - Added missing coffee bean to cropsanity

* - Fix an issue with the seed having the same name as the crop

* - Fix a recently discovered bug with help wanted rules when using a number not divisible by 7
2023-08-11 10:58:27 +02:00
Seldom
d5514c4635 Terraria: Fix Necromantic Scroll requiring Post-Mourning Wood instead of access to Mourning Wood #2094 2023-08-11 10:57:30 +02:00
zig-for
d5474128e3 LADX: Fix hints generation for longer location names #2099 2023-08-11 10:56:36 +02:00
t3hf1gm3nt
8d6b2dfc9c [TLOZ] Fix filepath error for base patch
using os.path.join was causing duplicate parts of the filepath in certain environments. turns out it's not needed when loading the basepatch in our current world structure. this should hopefully fix genning issues on the RC beta site (and presumably the main site once the RC turns into the release)
2023-08-11 10:54:42 +02:00
t3hf1gm3nt
c9404d75b0 [TLOZ] MD5 validation update (#2080)
* - Use proper MD5 validation

The method TLoZ was trying to validate it's baserom was different from basically every other ROM game. Almost all the other ROM games use the same method as each other (except for the external patchers like FF1 and SoE, and OoT has its own special handling that's vastly different), so updating TloZ to match.

Also got rid of the checksum attribute for the TLoZDeltaPatch as it didn't seem to be used anywhere, so felt it was unnecessary and partially confusing to have it right next to the hash attribute that is actually used.

* change error message to reference MD5
2023-08-07 09:31:43 +02:00
black-sliver
eb50e0781e MultiServer: exit console task when console thread dies (#2068) 2023-08-04 10:01:51 +02:00
Alchav
6864f28f3e Pokémon Red and Blue: Progressive Card Key and auto hint bug fixes (#2076)
* Fix Progressive Card Key bug

* Fix auto hint spam
2023-08-02 19:51:53 +02:00
Aaron Wagener
6befc91773 The Messenger: actually implement get_filler_item_name (#2070) 2023-08-01 19:43:10 +02:00
PoryGone
1d6a2bff4f SA2B: Fix generate_filler_item_name (#2074) 2023-08-01 08:15:28 +02:00
PoryGone
898558b121 SMW & DKC3: Add get_filler_item_name override (#2071) 2023-08-01 07:54:05 +02:00
Silvris
a9fb7e2ace Plando: fix automatic locations only working for the first world (#2063)
* copy location_names for each iteration

* remove copy, just set the list
2023-07-31 23:16:42 +02:00
StripesOO7
f29d5c8cae ALTTP: Add fill_slot_data for external trackers (#1919)
* __init__.py: Add fill_slot_data function

Add fill_slot_data function. 
Used by StripesOO7's pop-tracker pack to auto populate settings as convenience for the user

* LTTP__init__.py added race condition to fill_slot_data

* added missing self to multiworl.is_race

* changed filling of slot_data to fill from static list instead of pulling all alttp_options.
additional options needed to be done separately cause they are not stored the same way as the rest. "mode", "goal", etc. are simple values as the rest are key:value pairs so `.value` is not supported and I didn't want to introduce an if-statement.

* changed filling of slot_data to fill from static list instead of pulling all alttp_options.
additional options needed to be done separately cause they are not stored the same way as the rest. "mode", "goal", etc. are simple values as the rest are key:value pairs so `.value` is not supported and I didn't want to introduce an if-statement.

* added a comment to describe the use for the option added to slot_data

---------

Co-authored-by: StripesOO7 <54711792+StripeesOO7@users.noreply.github.com>
2023-07-31 01:37:12 +02:00
Aaron Wagener
cacfd4ffae Core: Add dict functionality to OptionDict (#2036)
* Options: Add support for `items()` and `__getitem__` to OptionDict

* Options: have OptionDict inherit from Mapping

* add typing to __getitem__

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-07-31 01:01:21 +02:00
t3hf1gm3nt
62315e304a TLoZ: Setup doc update (#2045)
- add section about configuring lua core (shamelessly taken from the OoT setup guide) on bizhawk version 2.8 and below
- fix wrong reference to the ff1 connector lua to correctly reference the tloz connector lua
- remove reference to recommended bizhawk version. it's unnecessary
2023-07-30 20:18:15 +02:00
Aaron Wagener
cc39eec646 Stardew Valley: Import base multiworld setup in tests and use it (#2006) 2023-07-30 20:17:12 +02:00
Daivuk
de1ec4a18f DOOM 1993: Include only regions/rules for selected episodes 2023-07-30 20:14:02 +02:00
axe-y
40c9287eba DLCQuest: Add missing gun rule. (#2058) 2023-07-30 20:12:42 +02:00
el-u
5869f78ea7 core: remove the correct item from the item_pool in fill_restrictive 2023-07-30 20:11:29 +02:00
el-u
6c908de13f core: add a test to verify that fill_restrictive removes the exact same item from the item_pool that it has used to fill 2023-07-30 20:11:29 +02:00
black-sliver
29d67ac456 Speedups: ignore warning C4551 for pyximport+MSVC (#2054)
The cython-generated code triggers C4551 on MSVC,
which does not get silenced by pyximport's build flags.
So we silence it on source code level instead.
2023-07-30 10:33:00 +02:00
black-sliver
6d93a6234e MultiServer: fix wrong missing for empty state w/o speedups and add/fix tests (#2052)
* MultiServer: fix wrong missing for empty state w/o speedups

* Tests: fix some tests not being run

* Tests: add test for set intersection with LocationStore
2023-07-29 19:44:10 +02:00
zig-for
b579dbfdf8 LADX: Add rooster option (#2021) 2023-07-29 19:17:50 +02:00
zig-for
6ad33bb16e LADX: Fix hints crash (#2050) 2023-07-29 19:16:39 +02:00
black-sliver
7b8f8918fc Settings: change/fix tests behavior (#2053)
* Settings: disable saving and gui during tests

* Tests: create a fresh host.yaml for TestHostYAML

Now that host.yaml is .gitignored, testing the local host.yaml makes no sense anymore
2023-07-29 18:50:21 +02:00
Justus Lind
a90825eac3 Muse Dash: Add songs from Cosmic Radio Update (#2047) 2023-07-29 18:35:32 +02:00
agilbert1412
280ebf9c34 Stardew valley: Supported Mods documentation and changes required for legal reasons (#2038)
## What is this fixing or adding?
It was pointed out that distributing an archive with copies of all the supported mods could lead to legal problems down the line. So we are moving away from this approach.
This also means that, in the event that a mod gets updated and the previous version is no longer available, we need the ability to update the mod's supported version at any point in time, and cannot rely on AP's release schedule for such updates that will, in most cases, be only changing the string for the required version.

Changes:
- Scrub all references to the support mods zip file from documentation
- Create dedicated "Supported Mods" documentation page, external to AP so we can keep it updated with mod versions regardless of their release schedule
- Remove mod version validation from the AP backend, and manage that in the mod itself, for the same reason.
2023-07-29 04:08:22 +02:00
Zach Parks
672a97c9ae Core: Set locality rules after set_rules stage. (#2044)
* Core: Set locality rules after `generate_basic`.

* Move locality rules to before `generate_basic`.
2023-07-28 21:06:43 -05:00
Fabian Dill
b684ba4822 CommonClient: fix 0.4.2 EnergyLink datastore key 2023-07-29 02:28:13 +02:00
el-u
0d28eeb3c5 alttp: remove excess Blue Mail from hard item pool 2023-07-28 14:08:41 +02:00
NewSoupVi
cf37a69e53 Witness: Fix 2 generation crashes (#2043)
* Fix for error in get_early_items when removing plandoed items.

* Fix Early Caves

* Remove unnecessary list() call

* Update worlds/witness/items.py

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

---------

Co-authored-by: blastron <blastron@mac.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-07-28 09:39:56 +02:00
Silvris
a99a407c41 MultiServer: fix unclosed parenthesis in connection message (#2035) 2023-07-28 03:31:48 +02:00
NewSoupVi
8f447487fb Witness: Add Exempt-Medic to the in-game credits hint 2023-07-28 03:30:45 +02:00
Alchav
eb8855afb9 Pokemon RB: apworld fixes (#2042) 2023-07-27 21:43:37 +02:00
Zach Parks
09c3a99be8 Docs: Create CODEOWNERS document for tracking world maintainers. (#1901)
* Meta: Create code owners document for tracking and notifying owners of world changes.

* Removing @dewiniaid as maintainer for Hollow Knight.

2023-07-11 - Finalization Date for Vote

https://discord.com/channels/731205301247803413/1123286507390767267/1128482720218099812

@ThePhar - Vote to Remove (2023-06-27)
@black-sliver - Vote to Remove (2023-06-27)
@KonoTyran - Vote to Remove (2023-06-27)
@Berserker66 - Vote to Remove (2023-07-09)

Passed with majority to remove maintainer status.

* Adding @BadMagic100 and @ThePhar as maintainers for Hollow Knight.

@BadMagic100 to primarily handle client-side maintenance/updates.
@ThePhar to primarily handle Archipelago-side maintenance/updates.

https://discord.com/channels/731205301247803413/1131762415021858907

@ThePhar - Approved @BadMagic100 (2023-07-20) and @ThePhar (2023-07-24) as Maintainers
@LegendaryLinux - Approved @BadMagic100 (2023-07-20) as Maintainer
@Berserker66 - Approved @BadMagic100 (2023-07-26) and @ThePhar (2023-07-26) as Maintainers
@black-sliver - Approved @BadMagic100 (2023-07-26) and @ThePhar (2023-07-26) as Maintainers
@KonoTyran - Approved @BadMagic100 (2023-07-27) and @ThePhar (2023-07-27) as Maintainers

Passed with a majority to set maintainer status for Hollow Knight.
2023-07-27 09:12:06 -05:00
zig-for
3bf86cd8f0 LADX: Fix getting old items over and over again in Bizhawk (#2011)
There was a bug that randomly after opening and closing the menu, some players on Bizhawk would get old items again. Tracking this down took multiple hours over the course of several weeks. The root cause turned out to be reading from the System Bus domain while an DMA copy was happening. Doing so is undefined behavior on GBC (though I'm sure some game relies on it). On Gambatte, you end up reading some garbage byte no matter what the read is (unsure what the providence of the byte is - some garbage, some register, the actual DMA data, who knows?). Normally, this isn't an issue, as Bizhawk callbacks only happen during vblank/halt, which is generally a state where we have valid WRAM to read from. However - a setting is being passed around the community for Bizhawk that changes the frame counter to go from "only when Vblank happens" to "whenever some number of audio samples have happened" which causes the bizhawk callbacks to happen....nearly whenever. Including during a DMA. You can tell this is happening if you print the `PC` register when reading memory - if it matches `FFXX` then you are executing in a routine in HRAM and likely doing a DMA.

Additionally, the check items counter specifically is in WRAM Bank 1 which could be swapped out of - will have to keep an eye on this - generally LADX lives in Bank 1, but there are a few things that use the other banks (swap space for some objects??). This could be a problem on any platform - if we get more reports of bad items gets, that's probably why.

Also, fixes some logging that was never getting reenabled.
2023-07-27 16:08:14 +02:00
NewSoupVi
2333ddeaf7 The Witness: Make the path behind Keep Pressure Plates 2 logical in Vanilla and Normal (#2013) 2023-07-25 17:58:28 +02:00
NewSoupVi
0e8ad7b9bc Witness: Fixing a world bleed issue with multiple Witness seeds, preventing generation (#2031)
Co-authored-by: blastron <blastron@mac.com>
2023-07-24 22:54:23 -05:00
BadMagic100
9d1a31004f HK: Fix typo in LEFTSLASH (#2027) 2023-07-25 05:32:57 +02:00
Aaron Wagener
f2d0d1e895 The Messenger: Improve the shopping experience (#2029)
* The Messenger: Don't generate Figurines

* The Messenger: add prerequisite shop cost requirements

* The Messenger: don't double the cost anymore

* The Messenger: remove centered mind prereq instead of checking for it

* The Messenger: use cost as a property to cache it and gain back speed

* The Messenger: hardcode the prereqs for more speed

* make the linter and mypy happier

* use cached_property
2023-07-25 02:41:20 +02:00
Fabian Dill
6a96f33ad2 Core: trace error to player, if possible. (#2023) 2023-07-25 02:18:39 +02:00
agilbert1412
bb069443a4 Stardew valley: backpack fix, enum fix (#2028)
* - Reorganised tests for better backpack coverage
- Added a test for backpack locations being absent on vanilla

* - Fix backpack locations on vanilla

* - Fixed a typo in documentation

* - Added missing parenthesis after enum.auto so that Python 3.11 still works

* - Added Blank lines at the end of the backpack test files

* - cleaned whitespace
2023-07-25 01:52:15 +02:00
Zach Parks
fa3d69cf48 CI: Update workflows to use Python 3.11 (#1949)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-07-23 20:15:13 -05:00
black-sliver
6107749cbe CI: remove cython3 beta testing (#2024)
* CI: remove cython3 beta testing

Cython 3.0.0 was released: https://cython.readthedocs.io/en/latest/src/changes.html

* CI: remove duplicate run
2023-07-24 02:53:53 +02:00
Fabian Dill
60289666dc Core: update modules 2023-07-24 02:31:17 +02:00
Fabian Dill
5b8c3425c8 Setup: package the entire websockets module 2023-07-24 00:54:14 +02:00
Alchav
85b92e2696 Pokémon Red and Blue: Version 4 update (#1963)
## What is this fixing or adding?
Adds a large number of new options, including:

- Door Shuffle
- Sphere-based level scaling
- Key Item and Pokedex requirement options to reach the Elite Four
- Split Card Key option
- Dexsanity option can be set to a percentage of Pokémon that will be checks
- Stonesanity: remove the stones from the Celadon Department Store and shuffle them into the item pool, replacing 4 of the 5 Moon Stone items
- Sleep Trap items option
- Randomize Move Types option
- Town Map Fly Location option, to unlock a flight location when finding/receiving the Town Map

Many enhancements have been made, including:
- Game allows you to continue your save file _from Pallet Town_ as a way to save warp back to the beginning of the game. The one-way drop from Diglett's Cave to north Route 2 that had been added to the randomizer has been removed.
- Client auto-hints some locations when you are able to see the item before you can obtain it (but would only show AP Item if it is for another player), including Bike Shop, Oak's Aides, Celadon Prize Corner, and the unchosen Fossil location.

Various bugs have been fixed, including:
- Route 13 wild Pokémon not correctly logically requiring Cut
- Vanilla tm/hm compatibility options giving compatibility for many TMs/HMs erroneously 
- If an item that exists in multiple quantities in the item pool is chosen for one of the locations that are pre-filled with local items, it will continue placing that same item in the remaining locations as long as more of that item exist
- `start_with` option for `randomize_pokedex` still shuffling a Pokédex into the item pool
- The obedience threshold levels being incorrect with 0-2 badges, with Pokémon up to level 30 obeying with 0-1 badges and up to 10 with 2 badges
- Receiving a DeathLink trigger in the Safari Zone causing issues. Now, you will have your steps remaining set to 0 instead of blacking out when you're in the Safari Zone.

Many location names have been changed, as location names are automatically prepended using the Region name and a large number of areas have been split into new regions as part of the overhaul to add Door Shuffle.
2023-07-24 00:46:54 +02:00
Brooty Johnson
cf8ac49f76 DS3: move an items location from RC -> DH (#2017)
* moves items location from RC -> DH

"Ring of Steel Protection+3" actually belongs in DH instead of RC. this will shift the item ID's for the last 3 items in RC, and should not shift any ids in DH

* updated data_version to 7
2023-07-24 00:20:45 +02:00
Fabian Dill
d9594b049c Setup: allow user to auto launch the launcher, so they can conveniently launch things when the setup is done. (#2020) 2023-07-24 00:15:47 +02:00
black-sliver
caa8d478f5 Factorio: update min_client_version (#2018)
Ensure that people don't use an old client that is known to be incompatible.
2023-07-24 00:09:47 +02:00
Daivuk
7279de0605 DOOM 1993: Added Episode 4. Game is now complete
And some bug fixes, balance and small features.
2023-07-23 22:24:54 +02:00
black-sliver
d49860fbeb Fill: fix cleanup-after-swapping performance (#2016)
#1800 introduced a cleanup pass to "eject" unreachable items to hopefully get better error reporting of what could not be placed. The code to do so has an unnecessary amount of sweeps from pool.
2023-07-23 17:57:33 +02:00
Witchybun
591661ca79 Stardew Valley: Fix typo with woods obelisk item (#2015)
Co-authored-by: Witchybun <elnendil@gmail.com>
2023-07-22 23:23:03 -05:00
David St-Louis
e1374492de DOOM 1993: Fixed bad level exit regions (#2007) 2023-07-22 11:02:02 -05:00
el-u
5843f71447 docs: mention all item classifications (#1961)
* docs: mention all item classifications

* docs: mention all item classifications: reword skip_balancing and progression_skip_balancing
2023-07-22 09:56:00 -05:00
KonoTyran
9b1de8fea8 StS: Update location table and move item creation to create_items from generate_basic. (#1938) 2023-07-22 00:51:13 -05:00
el-u
86a55c7837 lufia2ac: code cleanup (#1971) 2023-07-22 00:49:23 -05:00
Aaron Wagener
8405b35a94 The Messenger: use the new region helpers (#1687)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-07-22 00:45:46 -05:00
Mewlif
889a4f4db9 Undertale: Doc updates and client bug fixes. (#1996) 2023-07-22 00:38:21 -05:00
Scipio Wright
191dcb505c Generate: Change yaml is destroyed to yaml is invalid (#1954) 2023-07-21 20:13:50 -05:00
Zach Parks
ecb1e0b74b Core: Add display name for item_links Option. (#1952) 2023-07-21 19:31:23 -05:00
Seldom
f8e2d7f503 Terraria: Fix Lunatic Cultist goal immediately awarded (#1995) 2023-07-21 19:24:06 -05:00
David St-Louis
8015734fcf DOOM 1993: Better region logics/rules, balancing, level exits (#1973) 2023-07-21 19:22:24 -05:00
Aaron Wagener
21228f9c63 The Messenger: Update Docs for latest release (#2005) 2023-07-21 14:43:23 -05:00
agilbert1412
57c1bc800c Stardew Valley: Turned the Treehouse into an unlockable item (#2004) 2023-07-21 12:56:03 -05:00
Fabian Dill
7f180a6d5a Factorio: fix multitracker ID misalignment 2023-07-21 19:11:05 +02:00
digiholic
9839164817 MMBN3: Fixes crash when checking certain locations (#2003) 2023-07-21 12:00:44 -05:00
Bryce Wilson
3c1950dd40 DS3: Accessibility error fix (#1983) 2023-07-21 11:59:17 -05:00
Aaron Wagener
e8bf471dcd Webhost: Fix failing gen for players > 1 (#1998) 2023-07-20 22:40:31 +02:00
Seldom
210d6f81eb Terraria: Fix Python 3.8 compat (#1994) 2023-07-20 10:49:52 +02:00
NewSoupVi
6797216eb8 Witness: Fix type hints being incompatible with 3.8 (#1991)
* Fixed 3.8 typing in init

* Fixed 3.8 typing in items

* Fixed 3.8 typing in player logic

* Fixed 3.8 typing in static_logic

* Fix 3.8 typing in utils

* Fixed fault import
2023-07-20 02:10:48 +02:00
Fabian Dill
1e72851b28 HK: apworld support on 3.10+ 2023-07-20 02:01:13 +02:00
NewSoupVi
75463193ab Witness: Yeah let's not sort the entire multiworld's itempool lol (#1993)
* Witness: Yeah let's not sort the entire multiworld's itempool lol

* Non-stupid dict sorting

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Even less stupid dict sorting

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-07-20 01:20:59 +02:00
agilbert1412
257774c31b Stardew Valley: Fixed Leo's Treehouse being randomized too aggressively (#1992)
* - Fixed Leo's Treehouse being randomized too aggressively

* - Added an automated test to catch badly tagged Non-progression entrances

* - Fixed a logic issue with Void Mayonnaise not being fishable

* - Removed unused import
2023-07-20 01:20:52 +02:00
Zach Parks
ca46a64abc Clique: Refactors and Additional Features supported by v1.5 (#1989) 2023-07-19 17:16:03 -05:00
NewSoupVi
1a29caffcb Witness: (Fatal) Fix incorrect reference due to a faulty merge conflict (#1990) 2023-07-19 17:08:25 -05:00
Witchybun
8fd805235d Undertale: Change Save Data Folder Location (#1966)
Co-authored-by: Witchybun <elnendil@gmail.com>
2023-07-19 16:39:57 -05:00
agilbert1412
62657df3fb Stardew Valley: 4.x.x - The Ginger Update (#1931)
## What is this fixing or adding?
Major content update for Stardew Valley

## How was this tested?
One large-scale public Beta on the archipelago server, plus several smaller private asyncs and test runs

You can go to https://github.com/agilbert1412/StardewArchipelago/releases to grab the mod (latest 4.x.x version), the supported mods and the apworld, to test this PR

## New Features:
- Festival Checks [Easy mode or Hard Mode]
- Special Orders [Both Board and Qi]
- Willy's Boat
- Ginger Island Parrots
- TV Channels
- Trap Items [Available in various difficulty levels]
- Entrance Randomizer: Buildings and Chaos
- New Fishsanity options: Exclude Legendaries, Exclude Hard fish, Only easy fish
- Resource Pack overhaul [Resource packs are now more enjoyable and varied]
- Goal: Greatest Walnut Hunter [Find every single Golden Walnut]
- Goal: Perfection [Achieve Perfection]
- Option: Profit Margin [Multiplier over all earnings]
- Option: Friendsanity Heart Size [Reduce clutter from friendsanity hearts]
- Option: Exclude Ginger Island - will exclude many locations and items to generate a playthrough that does not go to the island
- Mod Support [Curated list of mods]

## New Contributors:
@Witchybun for the mod support

---------

Co-authored-by: Witchybun <embenham05@gmail.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-07-19 20:26:38 +02:00
blastron
1f6db12797 The Witness: Item loading refactor. (#1953)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-07-18 22:02:57 -05:00
Aaron Wagener
18c9779815 The Messenger: Fix location access for Figurine Shop Locations (#1975) 2023-07-18 22:01:44 -05:00
Justus Lind
09f4b7ec38 Muse Dash: Update code to use some newer API (#1980) 2023-07-18 22:00:52 -05:00
Silvris
d14131c3be MMBN3, Pokemon RB: Fix Incorrect Import (#1988) 2023-07-18 21:59:38 -05:00
Scipio Wright
8360435607 Noita: Implement Extra Orbs, Shop Price Reduction, and some slight region tweaks (#1972)
Co-authored-by: Adam Heinermann <aheinerm@gmail.com>
2023-07-18 21:51:01 -05:00
Seldom
83387da6a4 Terraria: Implement New Game (#1405)
Co-authored-by: Zach Parks <zach@alliware.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-07-18 21:37:26 -05:00
NewSoupVi
f318ca8886 Witness: Add junk hints for new games (#1962) 2023-07-18 21:19:58 -05:00
FlySniper
1630529d58 Wargroove (#1982)
## What is this fixing or adding?

Adjusted some map terrain. Made Ambushed in the Middle's HQ more exposed. Made Deep Thicket's AI spawn extra units. Adjusted some terrain in Rebel Village.
Moved item creation from generate_basic to create_items for (https://github.com/ArchipelagoMW/Archipelago/pull/1460)
2023-07-19 01:59:41 +02:00
Scipio Wright
60b8daa3af Docs: Slight update regarding apworld yamls (#1987) 2023-07-18 21:12:04 +02:00
black-sliver
a77739ba18 Settings: implement saving of dict and sequence, add graceful crashing (#1981)
* settings: don't crash when loading an outdated host.yaml

* settings: use temp file to not destroy host.yaml on error

* settings: implement saving of dicts

* settings: simplify dump of dict

* settings: add support for sequences

also a few more comments

* settings: reformat a bit
2023-07-18 20:59:52 +02:00
Fabian Dill
60586aa284 Tests: ensure unreachable_regions is correctly set 2023-07-18 12:41:26 +02:00
Silvris
f1d09d2282 TLoZ: Fix Incorrect Import (#1986) 2023-07-18 10:22:39 +02:00
NewSoupVi
48746f6c62 Witness: Fix Python 3.11 crash and fix Desert Laser hint (#1970) 2023-07-18 10:18:42 +02:00
PoryGone
8c5688e5e2 Add link to location guide into Game Page (#1974) 2023-07-18 09:58:38 +02:00
NewSoupVi
bad79ee11a Witness: Fix excluded EPs not being precompleted anymore (#1979)
One of the recent PRs accidentally removed all ability for the client to see which EPs are precompleted (due to settings)

This is pretty bad, as the client now thinks these EPs need to be completed for "Obelisk Side" locations, when the generator does not. This would lead to impossible seeds.
2023-07-18 09:56:04 +02:00
NewSoupVi
afed1dc558 Witness: Logic Fix (Incorrect Symbols expected for Quarry Lower Row 1 in Expert) (#1984)
Doesn't currently lead to any broken seeds or anything, it just makes seeds unnecessarily restrictive.
2023-07-18 09:34:31 +02:00
Bryce Wilson
8df08b53d9 WebHost: Fix as_dict attribute error (#1977)
* WebHost: Fix as_dict attribute error

Introduced in 827444f5a4

* WebHost: Add assertion that baked_server_options is a dict

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-07-15 22:52:52 +02:00
Silvris
dfe08298ef Installer: Add desktop and start menu shortcuts for Launcher and LADX Client (#1947) 2023-07-14 03:48:38 +02:00
zig-for
48ffad867a LADX: Add Hints (#1932) 2023-07-14 03:14:04 +02:00
PoryGone
a88e75f3a1 DKC3: Move item creation earlier (#1941) 2023-07-14 03:11:49 +02:00
PoryGone
087cc334f4 SMW: Move item creation earlier (#1942) 2023-07-14 03:11:19 +02:00
Ludwig
11278d0e61 SM64: Verbosify SM64 documentation (#1967) 2023-07-14 03:08:09 +02:00
black-sliver
bff2b80acf MultiServer: fix loading of default hint_cost (#1964) 2023-07-11 20:38:09 +02:00
Exempt-Medic
5b606e53fc HK: Fix bugs and update setup guide for Scarab+ and XBox Game Pass support (#1955)
Fixing three bugs:
1. Made Salubra Charm Shop Slots use the actual options value and not the Iselda Shop Slots value.
2. Made Mask Shards no longer considered Filler but instead Progression so they can actually be used for access requirements that require damage boosts.
3. Fixed goal requirements to account for Focus being required for The Hollow Knight and Sealed Siblings endings and adjusted the Radiance and Any goal requirements accordingly.

Updated Setup Guide:
Changed to mention Scarab+ instead of Scarab (it is better maintained and has several quality of life improvements and fixes a bug for XBox Game Pass).
Added info about how to use Scarab+ with XBox Game Pass.
2023-07-11 11:49:40 +02:00
Zach Parks
9af56ec0dd WebHost: Update copyright year in footer. (#1951) 2023-07-09 13:23:50 -05:00
el-u
ab22b11bac Docs: clean up world api.md a bit (#1958) 2023-07-09 18:04:24 +02:00
Aaron Wagener
07d74ac186 Core: Region connection helpers (#1923)
* Region.create_exit and Region.connect helpers

* reduce code duplication and better naming in Region.connect

* thank you tests

* reorder class definition

* define entrance_type on Region

* document helpers

* drop __class_getitem__ for now

* review changes
2023-07-09 17:52:20 +02:00
zig-for
36474c3ccc LADX: Client Fixes (#1934) 2023-07-09 15:17:24 +02:00
espeon65536
736945658a OoT: Python 3.11 Compatibility fix and Minor Bug fixes (#1948)
* OoT: biggoron's sword and giant's knife now considered progression in non-glitchless

* OoT: fixed seeding the random module with the Random object
2023-07-09 14:30:05 +02:00
NewSoupVi
cfe14aec76 The Witness: Add Quarry Stoneworks Control Room Left to the hint pool (#1957) 2023-07-09 14:21:05 +02:00
Remy Jette
feaa30d808 Docs, StS: Document setup for Slay the Spire GOG/Game Pass installations (#1913)
Co-authored-by: KonoTyran <Kono.Tyran@gmail.com>
2023-07-07 09:47:11 -05:00
Trevor L
1338d7a968 Blasphemous: Randomizer 2.0 (#1883)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-07-05 23:39:26 -05:00
kindasneaki
f2117be7d9 RoR2: fix event exits for dlc stages (#1946) 2023-07-05 22:45:43 -05:00
lordlou
5f2c226b43 SMZ3: update to upstream version 11.3.1 and item link fix (#1950) 2023-07-05 22:44:59 -05:00
ScorelessPine
81b956408e SMZ3: Fixed Ganon sign text on AllDungeonsDefeatMotherBrain goal (#1617) 2023-07-05 20:49:36 -05:00
Remy Jette
354a182859 WebHost: Fix docs generation from a .apworld (#1862)
zfile.filename is the full path within the archive, so by default
zf.extract will maintain that directory structure when extracting.

This causes the docs to be placed in the wrong place, as the Javascript
code expects them to be placed directly in the game folder.
2023-07-05 23:36:46 +02:00
black-sliver
827444f5a4 Core: Add settings API ("auto settings") for host.yaml (#1871)
* Add settings API ("auto settings") for host.yaml

* settings: no BOM when saving

* settings: fix saving / groups resetting themselves

* settings: fix AutoWorldRegister import

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Lufia2: settings: clean up imports

* settings: more consistent class naming

* Docs: update world api for settings api refactor

* settings: fix access from World instance

* settings: update migration timeline

* Docs: Apply suggestions from code review

Co-authored-by: Zach Parks <zach@alliware.com>

* Settings: correctly resolve .exe in UserPath and LocalPath

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-07-05 22:39:35 +02:00
Fabian Dill
d8a8997684 Core: remove "names" from multidata (#1928) 2023-07-05 21:51:38 +02:00
Samuel Thayer
e920692ec3 DS3, Docs: Add downpatching instructions to Dark Souls III setup guide (#1874)
* add links to downpatching instructions

* renumber properly

* Update setup_en.md

* Update setup_en.md

* DS3, Docs: Avoid having to update the guide for steam updates

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-07-05 20:21:32 +02:00
black-sliver
6fd16ecced MultiServer: Allow games with no locations, add checks to pure python implementation. (#1944)
* Server: allow games with no locations again

* Server: validate locations in pure python implementation

and rework tests

* Server: fix tests for py<3.11
2023-07-05 10:35:03 +02:00
black-sliver
50537a9161 Setup: don't build speedups in-place, copy isntead (#1945) 2023-07-05 02:53:34 +02:00
Sunny Bat
cbb7616f03 Raft: Only modify itempool during create_items (#1939) 2023-07-04 14:28:09 -05:00
Ziktofel
85a2193f35 SC2: Move itempool generation logic from generate_basic to create_items. (#1940) 2023-07-04 14:27:04 -05:00
Mewlif
857364fa78 Undertale: Bug fix pr (#1937) 2023-07-04 13:09:17 -05:00
zig-for
153125a5ea LADX: Fix being forced to farm for money, fix set iteration (#1924) 2023-07-04 19:33:33 +02:00
black-sliver
b6e78bd1a3 MultiServer: speed up location commands (#1926)
* MultiServer: speed up location commands

Adds optimized pure python wrapper around locations dict
Adds optimized cython implementation of the wrapper, saving cpu time and 80% memory use

* Speedups: auto-build on import and build during setup

* Speedups: add requirements

* CI: don't break with build_ext

* Speedups: use C++ compiler for pyximport

* Speedups: cleanup and more validation

* Speedups: add tests for LocationStore

* Setup: delete temp in-place build modules

* Speedups: more tests and safer indices

The change has no security implications, but ensures that entries[IndexEntry.start] is always valid.

* Speedups: add cython3 compatibility

* Speedups: remove unused import

* Speedups: reformat

* Speedup: fix empty set in test

* Speedups: use regular dict in Locations.get_for_player

* CI: run unittests with beta cython

now with 2x nicer names
2023-07-04 19:12:43 +02:00
Bryce Wilson
d35d3b629e DS3: Dark Souls 3 Major Update/Refactor (#1864)
* fix estus shard/bone shard numbers

there are only 11 estus shards in the pool, so fixed number of estus shards so everything can be collected, and upped the number of bone shards in the pool to fix datapackage numbers. new counts went from: 15(estus) + 5(bones) = 20(total) TO 11(estus) + 9(bones) = 20(total)

* Update locations_data.py

changed estus shard/bone shard counts to match the counts in items_data.py. same reasoning as the commit for that, only 11 estus in game

* added new options "Late DLC"
revampled "Late Basin of Vows" option
added the Fire Demon location in Undead Sanctuary

* first file dump

added new settings for customizing pool options
sorted all the item pools
code clean up
fixed estus/bone shard counts
still need to figure out location excluding

* bunch of changes

added new locations
put locations into specific lists
made DarkSouls3Locations for each list of items
still need to figure out how to exclude

* excluded locations from generating without options, created gotthard_region, update how the pool fills additional items, update location/item tables, create more tables

* code cleanup, remove extra tables, add grave key/eyes of a firekeeper back to key pool

* fixed some logging

* add more detailed options descriptions

* forgot to update progressive locations updates too whoops

* remove irina's tower key from items/location list. the current ID's dont work to shuffle

* fixed item-to-locations, added new weapons, added new armors, added new rings, added "eyes of a fire keeper" to key locations list to balance, adjusted tables

* added HWL: broken straight sword location, moved Greirat's ashes to  NPC items

* remove hwl: broken short sword location/item from pool (does not exist), fix item/location counts in options, general code clean up

* more code cleanup, fix Havels Ring +3 location/properly renamed item, changed Estus/Bone Shard names to not include a +| added a missing undead bone shard

* fixed npc rule, added a bunch of ring locations, fixed ring tables

* updated options

* cleaned up more code, edited some option names

* start of new items system

* DS3: Major refactor (allows for defining more items than those in vanilla locations)

* DS3: Repair changes overwritten by refactor

* DS3: Re-implement new options for location categories

* DS3: Make replacement item lists for most unique item types

* DS3: Remove accidentally added apworld

* DS3: Make option names more consistent

* DS3: Fix Pyromancer's Parting Flame location category

* DS3: Add new items

* DS3: Fix access rule for DLC/Contraption Key

* DS3: Only replace unrandomized progression items with events

Also fix some location names/categories

* DS3: Change some location names to be in line with their items

* DS3: Add randomized infusion code (only works for Broadsword)

* DS3: Make varied item pool an option

* added remaining weapons, shields, armors, rings, spells, dlc equivalents | added remaining dlc ring locations (2 in dreg heap, 5 in ringed city)

* adjusted 'Progressive Locations' counts and added new table

* added more souls + upgrade gems

* added the rest of consumables

* reverted adding an additional 'progressive location 4' table and added bulk progression locations to prog. location 3 table

* DS3: Add infusion categories and some cleanup of items

* DS3: Fix item ordering

* DS3: Fix infusion/upgrade code extra if

* DS3: Disable some unmarked cut content items

* DS3: Rename blessed red and white shield+1

* DS3: Implement guaranteed_items option

* DS3: Remove print statement

* DS3: Add extra check for trying to remove items from an empty list

* add unused content item id's

* DS3: Move cut content to its own list

* DS3: Classify spells and healing upgrades as useful

* DS3: Implement get_filler_item_name

* DS3: Change lower bounds for upgrades from +1 to +0

* DS3: Move Ancient Dragon Greatshield back to vanilla and recategorize some useful consumables

* DS3: Guaranteed items checks for number of existing items before replacing

* added remaining progressive items, fixed npc rules, adjusted option location counts

* delete extra items, add rule for dancer/late basin

* seperate PW into two parts (can access first half w/o contraption key | SKIP more unused items

* DS3: Minor linting changes

* DS3: Update required_client_version

* DS3: Remove rule for bell tower access

The key can always be purchased from the shop

* DS3: Move location category option checks to generate_early

* added "Boss Soul" option to pool

* DS3: Fix rules for boss souls and update misc location count

* DS3: Address minor review comments

* DS3: Change category enums to IntEnum

* DS3: Make apworld

---------

Co-authored-by: Brooty Johnson <83629348+Br00ty@users.noreply.github.com>
2023-07-04 08:46:18 +02:00
Fabian Dill
532c4c068f Subnautica: revamp filler item pool 2023-07-04 08:29:46 +02:00
lordlou
b077b2aeef SM: save and quit escape restriction and bad EscapeTrigger code fix (#1929)
* prevent using save and reload when escaping Zebes

fixed wrong code when EscapeTrigger is required (X.item.advancement isnt defined for ItemLocation)
2023-07-03 06:40:32 -05:00
David St-Louis
e9e18054cf Docs: Added DOOM 1993 to the list of games in the root README.md (#1930) 2023-07-02 16:15:27 -05:00
Bryce Wilson
d94bee20d0 Core: Import random for type hint on World.random (#1927) 2023-07-02 18:23:47 +02:00
David St-Louis
c321c5d256 DOOM 1993: implement new game (#1759)
* DOOM 1993: implement new game

* DOOM 1993 - Phar's cleanup to __init__.py
2023-07-02 10:34:55 -05:00
Fabian Dill
ee40312384 LttP: free core of checks_in_area (#1798) 2023-07-02 13:00:05 +02:00
Aaron Wagener
a6ba185c55 Core: Attribute per slot random directly to the World and discourage using MultiWorld's random (#1649)
* add a random object to the World

* use it in The Messenger

* the worlds don't exist until the end of set options

* set seed in lttp tests

* use world.random for shop shuffle
2023-07-02 05:50:14 -05:00
Fabian Dill
6a88d5aa79 Core: update modules 2023-07-02 09:51:01 +02:00
Justus Lind
4a60d8a4c1 Muse Dash: Fix Rare Test Failure (#1920) 2023-07-02 02:48:17 -05:00
Aaron Wagener
9b15278de8 Core: Add support for non dictionary iterables for Region.add_exits (#1698)
* Core: Add support for non dictionary iterables for `Region.add_exits`

* some cleanup and duplicate code removal

* add unit test for non dict iterable

* use more consistent naming

* sometimes i just make stuff harder on myself :)
2023-06-30 20:37:44 -05:00
Trevor L
fa3c132304 Hylics 2: Add missing location (#1917)
* Hylics 2: Add missing location

* Hylics 2: Change data_version
2023-06-30 17:46:32 -05:00
digiholic
6226713c4d MMBN3: Press program now has proper color index when received remotely (#1918) 2023-06-30 17:34:53 -05:00
Justus Lind
b56da79890 Muse Dash: Add 2023 Anniversary songs and remove a hidden song (#1916)
* Remove CHAOS Glitch. Add test to check for removed songs.

* Add to game list

* Fix oversight with 0 difficulty songs. Fix naming of test.

* Add new songs and update other data.

* Fix accidental copy paste
2023-06-30 08:10:58 -05:00
PoryGone
1d6345d3a2 SA2: Add troubleshooting note about Skip Intro and Cutscene Traps (#1915) 2023-06-29 22:28:08 -05:00
el-u
51a639ceaf lufia2ac: use an appropriate dungeon sprite and battle theme for each boss (#1914) 2023-06-29 22:21:46 -05:00
digiholic
7ecb1e6d6c Docs: Adds MMBN3 to Readme.md (#1912) 2023-06-29 15:01:37 -05:00
Zach Parks
c9fb443c64 OriBF: Move Ori and the Blind Forest to worlds_disabled. (#1906)
* OriBF: Move Ori and the Blind Forest to `worlds_disabled/`

* Add readme for `worlds_disabled` folder

* fix link

* fix link 2

* Remove useless comment

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-06-29 13:36:48 -05:00
digiholic
325299286b Mega Man Battle Network 3: Implement New Game (#1198)
* Initializes MMBN3 world with empty files

* Adds item names to item dict

* Adds locations and names

* Adds skeleton of MMBN3Client. Mostly copy pasta from OOT

* Fixed some style and formatting

* More incremental Lua tests

* Adds all locations and checking to Lua connector

* Made class definitions for TextPet Parser

* Begun connecting item delivery system through lua and textpet

* Lua Connection can now send test items

* Item Delivery is now parameterized. Test command can send any chip

* Adds the ability to send non-chip items

* Fixes name errors in python client

* Fixes count for zenny, attempts to fix bugfrags

* Fixes an issue where you always received 255 bugfrags

* Converts zenny and bugfrag amounts to little endian bytecode

* Checks game state before sending chips

Adds debug option to display information overlayed on rom
Fixes chip indexing issue for chips with ids over 255
Minor text fixes

* Adds in some animation reset instructions during item get message

* Stores previously collected item index in save, re-sends missing items

* Adds title screen check before sending locations

Loading items from save could not be done via RAM. Had to be added in
assembly

* Adds progressive undernet check

* Added library for lzss decoding bits of rom

* More progress on parsing text events from ROM

* Adds a way to inject messages into ScriptArchive data structure and generate bytecode

* Adds Item definitions, passes to client

* Adds regions and item collection rules

* Touched up a few names and values that have changed in preparation for the final patching

* Modifying messages via item is now successful

* Added generate_output hook to generate ROM data

* Generates ROM successfully

* Fixes navi cust give index

* Whoops forgot to wrap this in brackets

* Injects extra scripts for undernet rankings

* Programs had ammount and color swapped

* Prompts the user for their username when connecting

* Adds flagClear to the list of commands to avoid overwriting

* Fixes message box crashes and several other multiworld issues

* Fixes IDs and names of several items and locations

* Added .gba to gitignore

* Fixes compatibility after recent rebase

* Fixes some locations and items that are otherwise unobtainable

* Attempts to make a working launcher in the installer

* Creates installer and fixes several inaccessible locations

* Many minor changes to items, locations, and requirements made during testing

* Adds an info page for MMBN3

* Fixes failing tests by removing duplicate IDs and properly marking progression items

* Accidentally forgot to un-remove the thing

* Whoops, changed this by accident

* Updates self.world references to self.multiworld

* Fixes imports to use from imports instead of using the namespace

* Removed some leftover merge artifacts from inno setup

* Puts back that darned signtool line again

* Adds Overworld Metro keys as items

* Adds TamaCode and puts shortcuts behind cyber passes

* Fixes Numberman code 16 check

* Fixes metro access logic and adds text to metro

* Reworks Lua to fix crashing when many items are queued

* Items for other BN3 games for different players are no longer given in the main player's ROM as well

* Fixes incorrect Item ID for ACDC Metro

* Fixes multi-box text messages

* Adds timer before sending an item

* Forgot to remove the second box of SubMems

* Updates patch and lua to prevent softlocks and crashes

* Adds options for extra undernet ranks, exclude jobs

* Extra GigFreez now gives 20 bugfrags

* Additional Progressive Undernets can no longer appear on the WWW Base

* Moves item signal byte to empty area of flags instead of end of RAM

* Adds Chocolate Shop locations and navi chips to fill them

* Fixes save crash, and added chocolates to lua

* Fixes chocolate stand selling out text, removes DrillMan cube in Undernet

* Replaces old messaging system with direct memory manipulation for receiving items

* Removes NDSPY requirements from MMBN3 by manually adapting the GBA's lz10 algorithm

* Fixes the names of Hospital-1 Locations

* Adds Canary Bit to avoid sending checks when title screen check fails

* Gaining a cybermetro pass will now open the shortcut immediately

* Randomizes the two accessible areas of Undernet 7, adds Hammer as item

* Adds new locations to connector lua

* Injects the name of the item into trade quests

* Fixes copy-paste error in docs

* Fixes merge artifacts and depracated code

* Nut-wafer stand now faces Lan the right way after buying

* Removes unused Goal Option and updates the readme to include most recent changes

* Touch-ups and formatting changes

* The Great Fillerization update. Dozens of items changed to Filler

* Replaces instances of Mega Man with MegaMan

* Update worlds/mmbn3/docs/en_MegaMan Battle Network 3.md

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Update worlds/mmbn3/__init__.py

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* Changes code ordering to suit base class's

* assert_generate now checks for roms. Minor text fixes

* Makes player specific frequency and excluded location options

* Apply suggestions from code review

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Addresses suggested changes from PR review

* Replaces ndspy lz10 with MIT-compliant nlzss lz10

* apworld compatibility fix for mmbn3_options from utils

* Addressing more comments by el-u

* APworld will now pull patch from zip folder

* Apply suggestions from code review

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Cleaned up comments for progressive undernet ROM function, moved index list to field to avoid re-initializing

* Removes improper player-indexed location/item dicts, replaces with world member variables

* Avoids redefining list in progressive undernet ROM function

* Filler items can no longer be generated beyond their specified amounts

* Fixes list copying issue with item frequencies

* Adds BN3 Client Generation back into Launcher settings

* Fixes typos causing huge problems

* Fixed non-relative import for apworld

* Removes custom enum implementation that broke pickle

* Displays message when attempting to load an incorrect ROM, will not attempt to patch it

* Filler items can now only be placed once

* Changes path in setup doc to match Lua path changes

* Fixes file extension for MMBN3 file

* Replaces magic number with reference to value in NetUtils

* Moves victory rules to set_rules. Removes commented out code

* Rewrites Lua script to send block of memory

* Fixes off-by-one error in sending bytes for locations

* Fixes issue with invalid characters in text parsing, and WWW monitor text box parsing

* Moves trade text injection to init so it has access to options

* Attempts to split the text boxes for hinted items

* Trade checks now provide hints if the option is set for them

* Fixes escape character issue for BizHawk 2.9.1

Something in Bizhawk lua parsing changed to dislike the escaped tilde.
I'm not even entirely sure why it was escaped in the first place, but
this should fix the compatibility of it.

* Re-adds desk check that it turns out actually does exist

* Updates requirements to mention bizhawk 2.7 instead of 2.3.1

* Fixes off-by-one error in command byte counts

* Fixes program color indices

* Fixes newline PEP violations

* Reverts an accidental whitespace change made to launcher.py

* Fixes URL formatting on link to settings from setup guide

Co-authored-by: Zach Parks <zach@alliware.com>

* Splits several lines in the readme to avoid excessive length

* Fixes formatting and (hopefully) reduces cringe of joke in setup doc

* Removes unnecessary constructor

* Changes item frequency generation to avoid reusing the same references

Co-authored-by: Zach Parks <zach@alliware.com>

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-06-29 13:36:01 -05:00
Alchav
776b5fab7c LTTP/SM/SMZ3: Show correct item icon for cross-game items (#1112)
Co-authored-by: lordlou <87331798+lordlou@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-06-29 17:47:21 +02:00
Fabian Dill
18e0d25051 Factorio: fix resync not reconciling divergent history 2023-06-29 15:15:12 +02:00
el-u
dfb3df4a8f lufia2ac: coop support + update AP version number to 0.4.2 (#1868)
* Core: typing for async_start

* CommonClient: add a framework for clients to subscribe to data storage key notifications

* Core: update version to 0.4.2

* lufia2ac: coop support
2023-06-29 08:06:58 -05:00
lordlou
d0db728850 SM: 0.4.1 Fixes and Additional Objective Options (#1859)
* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* Fixed multiworld support patch not working with VariaRandomizer's

Added stage_fill_hook to set morph first in progitempool
Added back VariaRandomizer's standard patches

* + added missing files from variaRandomizer project

* + added missing variaRandomizer files (custom sprites)

+ started integrating VariaRandomizer options (WIP)

* Some fixes for player and server name display

- fixed player name of 16 characters reading too far in SM client
- fixed 12 bytes SM player name limit (now 16)
- fixed server name not being displayed in SM when using server cheat ( now displays RECEIVED FROM ARCHIPELAGO)
- request: temporarly changed default seed names displayed in SM main menu to OWTCH

* Fixed Goal completion not triggering in smClient

* integrated VariaRandomizer's options into AP (WIP)

- startAP is working
- door rando is working
- skillset is working

* - fixed itemsounds.ips crash by always including nofanfare.ips into multiworld.ips (itemsounds is now always applied and "itemsounds" preset must always be "off")

* skillset are now instanced per player instead of being a singleton class

* RomPatches are now instanced per player instead of being a singleton class

* DoorManager is now instanced per player instead of being a singleton class

* - fixed the last bugs that prevented generation of >1 SM world

* fixed crash when no skillset preset is specified in randoPreset (default to "casual")

* maxDifficulty support and itemsounds removal

- added support for maxDifficulty
- removed itemsounds patch as its always applied from multiworld patch for now

* Fixed bad merge

* Post merge adaptation

* fixed player name length fix that got lost with the merge

* fixed generation with other game type than SM

* added default randoPreset json for SM in playerSettings.yaml

* fixed broken SM client following merge

* beautified json skillset presets

* Fixed ArchipelagoSmClient not building

* Fixed conflict between mutliworld patch and beam_doors_plms patch

- doorsColorsRando now working

* SM generation now outputs APBP

- Fixed paths for patches and presets when frozen

* added missing file and fixed multithreading issue

* temporarily set data_version = 0

* more work

- added support for AP starting items
- fixed client crash with gamemode being None
- patch.py "compatible_version" is now 3

* commited missing asm files

fixed start item reserve breaking game (was using bad write offset when patching)

* Nothing item are now handled game-side. the game will now skip displaying a message box for received Nothing item (but the client will still receive it).

fixed crash in SMClient when loosing connection to SNI

* fixed No Energy Item missing its ID

fixed Plando

* merge post fixes

* fixed start item Grapple, XRay and Reserve HUD, as well as graphic beams (except ice palette color)

* fixed freeze in blue brinstar caused by Varia's custom PLM not being filled with proper Multiworld PLM address (altLocsAddresses)

* fixed start item x-ray HUD display

* Fixed start items being sent by the server (is all handled in ROM)

Start items are now not removed from itempool anymore
Nothing Item is now local_items so no player will ever pickup Nothing. Doing so reduces contribution of this world to the Multiworld the more Nothing there is though.
Fixed crash (and possibly passing but broken) at generation where the static list of IPSPatches used by all SM worlds was being modified

* fixed settings that could be applied to any SM players

* fixed auth to server only using player name (now does as ALTTP to authenticate)

* - fixed End Credits broken text

* added non SM item name display

* added all supported SM options in playerSettings.yaml

* fixed locations needing a list of parent regions (now generate a region for each location with one-way exits to each (previously) parent region

did some cleaning (mainly reverts on unnecessary core classes

* minor setting fixes and tweaks

- merged Area and lightArea settings
- made missileQty, superQty and powerBombQty use value from 10 to 90 and divide value by float(10) when generating
- fixed inverted layoutPatch setting

* added option start_inventory_removes_from_pool

fixed option names formatting
fixed lint errors
small code and repo cleanup

* Hopefully fixed ROR2 that could not send any items

* - fixed missing required change to ROR2

* fixed 0 hp when respawning without having ever saved (start items were not updating the save checksum)

* fixed typo with doors_colors_rando

* fixed checksum

* added custom sprites for off-world items (progression or not)

the original AP sprite was made with PierRoulette's SM Item Sprite Utility by ijwu

* - added missing change following upstream merge

- changed patch filename extension from apbp to apm3 so patch can be used with the new client

* added morph placement options: early means local and sphere 1

* fixed failing unit tests

* - fixed broken custom_preset options

* - big cleanup to remove unnecessary or unsupported features

* - more cleanup

* - moved sm_randomizer_rom and all always applied patches into an external project that outputs basepatch.ips

- small cleanup

* - added comment to refer to project for generating basepatch.ips (https://github.com/lordlou/SMBasepatch)

* fixed g4_skip patch that can be not applied if hud is enabled

* - fixed off world sprite that can have broken graphics (restricted to use only first 2 palette)

* - updated basepatch to reflect g4_skip removal

- moved more asm files to SMBasepatch project

* - tourian grey doors at baby metroid are now always flashing (allowing to go back if needed)

* fixed wrong path if using built as exe

* - cleaned exposed maxDifficulty options

- removed always enabled Knows

* Merged LttPClient and SMClient into SNIClient

* added varia_custom Preset Option that fetch a preset (read from a new varia_custom_preset Option) from varia's web service

* small doc precision

* - added death_link support

- fixed broken Goal Completion
- post merge fix

* - removed now useless presets

* - fixed bad internal mapping with maxDiff

- increases maxDiff if only Bosses is preventing beating the game

* - added support for lowercase custom preset sections (knows, settings and controller)

- fixed controller settings not applying to ROM

* - fixed death loop when dying with Door rando, bomb or speed booster as starting items

- varia's backup save should now be usable (automatically enabled when doing door rando)

* -added docstring for generated yaml

* fixed bad merge

* fixed broken infinity max difficulty

* commented debug prints

* adjusted credits to mark progression speed and difficulty as Non Available

* added support for more than 255 players (will print Archipelago for higher player number)

* fixed missing cleanup

* added support for 65535 different player names in ROM

* fixed generations failing when only bosses are unreachable

* - replaced setting maxDiff to infinity with a bool only affecting boss logics if only bosses are left to finish

* fixed failling generations when using 'fun' settings

Accessibility checks are forced to 'items' if restricted locations are used by VARIA following usage of 'fun' settings

* fixed debug logger

* removed unsupported "suits_restriction" option

* fixed generations failing when only bosses are unreachable (using a less intrusive approach for AP)

* - fixed deathlink emptying reserves

- added death_link_survive option that lets player survive when receiving a deathlink if the have non-empty reserves

* - merged death_link and death_link_survive options

* fixed death_link

* added a fallback default starting location instead of failing generation if an invalid one was chosen

* added Nothing and NoEnergy as hint blacklist

added missing NoEnergy as local items and removed it from progression

* fixed broken Item links

* fixed failing generation that could happen with Disabled Tourian

fixed shared Location list that could be modified for each world

* added missing force disable of EscapeRando if an escape solution cant be found

* fixed broken animal surprise patches

* prevent receiving items when in the first room of Ceres (message box in mode7 is broken)

* fixed generating with "activate chozo robots" Objective

* added soft reset that saves to initial starting location

reverted code change applied to fix softlocks from comeback checks
reverted forcing all beam local when using door rando

* replaced "save and reset" with "save and fast reload" (using same Start+Select+L+R)

* added documentation about Save and Reload

removed forgotten docstring about forcing beams as local items when using door rando

* fixed frequent failing generation on WebHost (KeyError: 'Kraid')

* added "objectiveRandom", "nbObjective", objectiveList and adapted Objective selection options to better reflect VARIA's.

fixed "collect 100% items" not being excluded when objectiveRandom is used
added Exception when VARIA initial layout fails

* fixed broken non-AP items

fixed determinism caused by the use of a set

* fixed generation failing on Webhost with string as a OptionSet (replaced default with a list of string)

cleaned doc and naming of Objective related Options
2023-06-29 07:51:09 -05:00
Justus Lind
77b0852dca Muse Dash: Add New Game (#1723)
* Alpha 1 Muse dash stuff.

* Add in an option to limit to only base game songs.

* Make all items progression instead of progression_skip_balancing.

* Add in extra_goal_song_items to help make runs less about completing every song.

* Change ID range to be in a more open area, and add some comments.

* Add in Streamer Mode and difficulty range options. Rearrange data files so its easier to get all data at once.

* Fix generation issues.

* Fix up the maximum and remove old option.

* Remove empty items and the option to make filler songs empty.

* Support emerald hunt mode. Make difficulties an option rather than 2 sliders.

* Fix DLC Song option being inverted.

* Fix item counting being broken if there was more than 1 world.

* Make compatible with .apworld specification.

* Make All item names ASCII compatible.

* Add in the additional_item_percentage option.

* Add a test to ensure the item names are within the normal ascii range.

* Add in death link.

* Remove the album from the item name. Not really needed anymore.

* Add the 2 budget is burning albums under the free songs heading. Adds a couple more songs without dlc.

* Sanitise Album names.

* Added the grade needed choice.

* Update songs to v3.1.0

* Adjust difficulty ranges. Add Expert and Master.

* Fix setup_en.md being out of date.

* Add a manual override.

* Add testing for diff ranges. Fix bugs introduced there. Limit option to 11 to not generate an impossible seed.

* Remove regions from Muse Dash.

* Some Oops...

* Attempt to make tests happy.

* Remove supports weighting false to stop webhost test failing.

* Adjusted settings

* Adjust music sheets to use percentages. Various cleanups.

* Fixes to new code.

* Add Ola Dash Album. Add support for overriding song difficulty. Other stylisation changes.

* Attempt fix tests.

* Ooops missed one.

* flake8 suggestions.

* Remove FM 17314 SUGAR RADIO as that song is a bit weird.

* Update document pages.

* Add trap support

* Lower additional song count by 10.

* Tests broke on my end. Using github to test this.

* Looks like I was accidentally adding ~.

* Fix the one song that crashes OoT hint generation

* Various documentation changes.

* Website documents fixup.

* Doc updates part 2.

* Oops. Doc updates part 3.

* Add Muse Dash to the apworld list.

* Add trailing comma.

* Add a couple plando options.

* Set data_version to 1.

* Add in some handling incase someone decides a song is both starter and included.

* Remove brackets around ifs.

* Oops. Accidentally removed a necessary bracket.

* Fix filtering crash due to me mixing up c# and python .remove().

* Add Happy Otaku Pack Vol.17. Also increment data version.

* Update links to melon loader to be the latest.

* Clean up song selection code by shuffling once then popping.

* Add UID to the Data text file, so the same file can be used client and server.

* Increment Data Version because some names have changed.

* Correct some names.

* Update data to v3.4.0 (Addition of Muse Radio FM104)

* Add support for SFX traps. Adjusted how traps were setup a bit.

* Update the docs to include a troubleshooting section.

* Small fixes.

* Remove unnecessary brackets.

* Add .net downloads to docs.

* Avoid failing generation if strict difficulty settings are applied with no dlc songs and streamer mode.

* Forgot to add the worst starting song count.

* Make minimum song count be Starting Songs + 11 instead of Starting Songs * 2 + 1.

* Fix up several issues where song count could mismatch the requested amount.

* Add a test to ensure world size doesn't grow.

* Fix some oversights.

* Remove unnecessary brackets.

* Fix up passing the tuple out when just the key would suffice.

* Adjust typing based on Phar's suggestions.

* Apply the rest of Phar's suggestions with minor tweaks to other parts to suit suggestions.

* Adjust some more stuff to fit 120 characters.

* Some more pep8 stuff and fix tests.

* Some pep8 in tests.
2023-06-29 07:36:39 -05:00
Aaron Wagener
3fba94f000 The Messenger: strip generated filler items for a sufficiently small pool (#1907)
* The Messenger: strip generated filler items for a sufficiently small remaining item pool

* rewrite the test for the small chance there's no large currency shards
2023-06-29 07:33:37 -05:00
William Quelho Ferreira
85582b9458 TLoZ: fix LauncherComponents entry (#1908) 2023-06-28 19:06:45 -05:00
Aaron Wagener
122d404145 Docs: rework main ap setup guide (#1853)
* rework main ap setup guide

* review updates

* add blurb about re-opening rooms and user-content

* more review suggestions

* remove dead links. Windows blurb
2023-06-28 19:06:18 -05:00
Aaron Wagener
07e3fbe845 Docs, LTTP: clarify not using qusb and remove redundancies (#1373)
* clarify not using qusb and remove redundancies

* SNES mini note

* review suggestions

* remove remaining repetitive text

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-28 00:11:06 -05:00
Kory Dondzila
76cace725b WebHost: Fixes multi-tracker checks sorting. (#1893) 2023-06-27 20:40:29 -05:00
NewSoupVi
99656bf059 The Witness: Utils.cache_argsless -> functools.lru_cache (#1897)
* Changed Utils.cache_argsless to functools.lru_cache

* Revmoed unused variable

* Removed remaining direct reference to a .txt outside utils

* Update worlds/witness/utils.py

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-06-28 02:23:50 +02:00
Aaron Wagener
332eab9569 The Messenger: Add Shop Rando (#1834)
* add shop shuffle options and items

* add logic for the shop slots

* write cost tests

* start on shop item logic

* make strike and second wind early items

* some cleanup

* remove 5 shards

* double cost requirement for really expensive items and raise the rates

* add test for shop shuffle with minimum other locations

* put power seal in front of shards

* rename locations and items

* update rules, regions, and shop

* update tests and misc fixes

* minor cleanup

* implement money wrench and figurines

* clean out now unneeded info from slot_data

* docs update and fix a failure when not shuffling shops

* remove shop shuffle option

* Finish out shop rules

* make seals generation easier to read and fix tests

* rule adjustments

* oop

* adjust the prices to be a bit more generous

* add max price to slot data for tracker

* update the hard rules a bit

* remove unnecessary test

* update data_version

* bump version and remove info for fixed issues

* remove now unneeded assert

* review updates

* minor bug fix

* add a test for minimum locations shop costing

* minor optimizations and cleanup

* remove whitespace
2023-06-28 01:39:52 +02:00
zig-for
8c2584f872 LADX: 16 bits for the check ID (#1903) 2023-06-27 16:39:57 -05:00
PoryGone
1ced726d31 SA2B: v2.2 Content Update (#1904)
* Ice Trap Support

* Support Animalsanity

* Add option for controlling number of emblems in pool

* Support Slow Trap

* Support Cutscene Traps

* Support Voice Shuffle

* Handle Boss Rush goals

* Fix create item reference to self.multiworld

* Support Ringlink

* Reduce beep frequency to 20

* Add Boss Rush Chaos Emerald Hunt Goal

* Fix Eternal Engine - Pipe 1 logic

* Add Chao voice shuffle

* Remove unused option

* Adjust wording of Required Cannon's Core Missions

* Fix incorrect region assignment

* Fix incorrect animal logics

* Fix Chao Race tooltip

* Remove Green Hill Animal Location

* Add Location Count info to tooltips

* Don't allow M4 first if animalsanity is active

* Add Iron Boots to Standard Logic Egg Quarters 5

* Make Vanilla Boss Rush actually Vanilla

* Increment Mod Version

* Increment Data Package Version

---------

Co-authored-by: RaspberrySpaceJam <tyler.summers@gmail.com>
2023-06-27 16:38:58 -05:00
Freya Arbjerg
d51e0ec0ab WebHost: Align multitracker status column to the left (#1645)
* Align multitracker status column to the left

* Move 'Status' column to after 'Game' column
2023-06-27 17:37:01 -04:00
Felix R
36b5b1207c Add Bumper Stickers (#811)
* bumpstik: initial commit

* bumpstik: fix game name in location obj

* bumpstik: specified offset

* bumpstik: forgot to call create_regions

* bumpstik: fix entrance generation

* bumpstik: fix completion definition

* bumpstik: treasure bumper, LttP text

* bumpstik: add more score-based locations

* bumpstik: adjust regions

* bumpstik: fill with Treasure Bumpers

* bumpstik: force Treasure Bumper on last location

* bumpstik: don't require Hazard Bumpers for level 4

* bumpstik: treasure bumper locations

* bumpstik: formatting

* bumpstik: refactor to 0.3.5

* bumpstik: Treasure bumpers are now progression

* bumpstik: complete reimplementation of locations

* bumpstik: implement Nothing as item

* bumpstik: level 3 and 4 locations

* bumpstik: correct a goal value

* bumpstik: region defs need one extra treasure

* bumpstik: add more starting paint cans

* bumpstik: toned down final score goal

* bumpstik: changing items, Hazards no longer traps

* bumpstik: remove item groups

* bumpstik: update self.world to self.multiworld

* bumpstik: clean up item types and classes

* bumpstik: add options
also add traps to item pool

* bumpstik: update docs

* bumpstik: oops

* bumpstik: add to master game list on readme

* bumpstik: renaming Task Skip to Task Advance
because "Task Skip" is surprisingly hard to say

* bumpstik: fill with score on item gen
instead of nothing (nothing is still the default filler)

* bumpstik: add 18 checks

* bumpstik: bump ap ver

* bumpstik: add item groups

* bumpstik: make helper items and traps configurable

* bumpstik: make Hazard Bumper progression

* bumpstik: tone final score goal down to 50K

* bumpstik: 0.4.0 region update

* bumpstik: clean up docs
also final goal is now 50K or your score + 5000, whichever is higher

* bumpstik: take datapackage out of testing mode

* bumpstik: Apply suggestions from code review

code changes for .apworld support

Co-authored-by: Zach Parks <zach@alliware.com>

---------

Co-authored-by: Zach Parks <zach@alliware.com>
2023-06-27 15:37:17 -05:00
Fabian Dill
a4e485e297 Launcher: keep alive (#1894) 2023-06-27 09:30:54 +02:00
kindasneaki
a7bc8846cd RoR2: bug fixes (#1891)
* adding back parens that got deleted by accident.

* Void Locus and The Planetarium ids backwards

* change required client version

* beads of fealty was missing for A Moment, whole victory

* found another logic bug

* Update worlds/ror2/__init__.py

Co-authored-by: Zach Parks <zach@alliware.com>

* Remove unnecessary comment

---------

Co-authored-by: Zach Parks <zach@alliware.com>
2023-06-26 23:47:52 -05:00
Fabian Dill
125ee8b198 WebHost: fix dict lookup exceptions 2023-06-27 04:39:21 +02:00
Mewlif
553fe0be19 Undertale for AP (#439)
Randomizes the items, and adds a new item to the pool, "Plot" which lets you go further and further in the game the more you have.

Developers: WirTheAvali (Preferred name for professional use, mewlif)
2023-06-27 04:35:41 +02:00
Zach Parks
71bfb6babd Generate: Add skip progression balancing argument. (#1876) 2023-06-26 16:14:01 -05:00
James Groom
1698c17caa Docs: Revise all docs mentioning Lua in EmuHawk (which are in English), and other misc. corrections (#1782)
* Fix links to TASVideos.org using HTTP

* Revise all docs mentioning Lua in EmuHawk which are in English

resolves TASEmulators/BizHawk#3650

* Correct capitalisation of "BizHawk"

in strings and camelCase identifiers

* Use the term "EmuHawk" when referring to the app, in English docs

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-26 08:53:44 +02:00
black-sliver
751e5cec63 Ori: fix py3.8 apworld compatibility 2023-06-26 08:16:56 +02:00
NewSoupVi
dc46e96e3f Witness: APworld compatibility, but for real this time (#1896)
* removed relative imports from outside the witness package

* Remove Witness from the apworld shame list
2023-06-26 00:38:39 +02:00
NewSoupVi
0934e5c711 The Witness: Fixed seeds not generating with vanilla logic (#1895)
Yikes, I swear I ran like 15 generations with a random yaml, I got so unlucky
2023-06-26 00:20:28 +02:00
Fabian Dill
aa8ffa247d Setup: flip apworld list (#1882)
* Setup: flip apworld list

* Update setup.py

Co-authored-by: kindasneaki <ryandj67@hotmail.com>

* Update setup.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* setup: make TLoZ an apworld

This reverts commit fd026c5eb2.

---------

Co-authored-by: kindasneaki <ryandj67@hotmail.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-25 03:47:38 +02:00
black-sliver
a45e8730cb Fill: fix fill_restrictive for mixed minimal and non-minimal and test (#1800)
* Tests: add test for mixing minimal and non-minimal

* Tests: minor cleanup in test_minimal_mixed_fill

* fix fill_restrictive for mixed minimal/non-minimal

The reason why this only happens for minimal is because it would not accept the solution it found otherwise.
Tracking and releasing unreachable items would be the better solution, but that's a lot harder to do.

* fix typo in fill_restrictive

* fix pep8 in fill_restrictive

* Fill: cleanup invalid unsafe placements, better comments

* Fill: more cleanup
2023-06-25 02:55:13 +02:00
Fabian Dill
46f2f3d7cd Factorio: Client in folder, TextClient: always available (#1829)
* Factorio: move Client into world folder

* Factorio: declare Client as Client Component

* FactorioClient: use centralized launch_subprocess

* TextClient: make always available
2023-06-25 02:31:25 +02:00
black-sliver
a96ff8de16 Linux: add freeze_support, Launcher: use spawn (#1890) 2023-06-25 02:24:43 +02:00
agilbert1412
f3e2e429b8 DLC Quest: Option Documentation improvements (#1887) 2023-06-25 02:13:33 +02:00
NewSoupVi
46b13e0b53 Witness: apworld support (#1885) 2023-06-25 02:00:56 +02:00
t3hf1gm3nt
7a4e903906 TLOZ: APworld support (#1884)
- Remove a relative import in Rules.py
- Clean up a few unused imports in __init__.py
- Use pkgutil instead of open when applying base patch
- make sure rom_name is initialized correctly in modify_multidata

* use os.path.join() instead of explicit "/"
2023-06-25 01:58:54 +02:00
Aaron Wagener
f1ccf1b663 reenable ping 2023-06-25 01:24:39 +02:00
NewSoupVi
ec0822c5eb Docs: Mention Git in the "Optional" section of "Running from Source" (#1880)
* Docs: Mention Git in the "Optional" section of "Running from Source"

GIt is required to install the Zilliandomizer package.

Also, this is probably just nice to have.

* Remove mention of Zillion so the text doesn't need updating.

* Update docs/running from source.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update docs/running from source.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Mention PyCharm's git integration

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-24 12:59:14 +02:00
Fabian Dill
78b981228a Generate: improve error message for missing game (#1857)
---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-23 10:17:35 +02:00
NewSoupVi
f3c788d0cc Witness: Fix missing location
All Pressure Plates puzzles are now always locations.

This makes a line where a Pressure Plates location gets added and a different one cause incorrect behavior.
2023-06-23 10:16:39 +02:00
Zach Parks
59ad9e97e5 WebHost: Fix special-range value setting to custom when randomization is toggled off (#1856)
* WebHost: Fix custom-range value setting to `custom` when randomization is toggled off

* Remove redundant code

* Add optional parameter default
2023-06-22 22:12:22 -04:00
StripesOO7
abd8eaf36e WebHost: Change default spoiler-option for games generated from WebHost to 3 instead of 0 (#1852)
* Change default spoiler-option in WebHostLib/generate.py to 3 instead of 0

* shifting spoiler-default to the JS calls instead of setting it in generate.py

---------

Co-authored-by: StripesOO7 <54711792+StripeesOO7@users.noreply.github.com>
2023-06-22 21:01:09 -05:00
black-sliver
f36468fc25 Docs: add info about maintaining worlds (#1838)
* Docs: add info about mainting worlds

* Docs: fix typos in world maintainer

* Docs: commit suggestions into world maintainers

Thanks Joethepic and Silvris

* Docs: fix more typos in world maintainer

* Docs: more typos

* Docs: world maintainers link to core maintainers

* Docs: world maintainers voting on discord

* Docs: add 'world maintainer' link to 'adding games'

* Docs: unmaintained worlds in 'disabled'

* Docs: world maintainer update from review

Thanks LegendaryLinux

* Doc: rephrase world maintainer voting
2023-06-22 08:51:02 +02:00
black-sliver
a939f50480 Clients: use certifi (#1879)
* Clients: use certifi for wss

On Windows, the local cert store might be outdated and refuse connection to some servers.

* Clients: lazily create ssl_context
2023-06-22 00:01:41 +02:00
Fabian Dill
b04b105bd8 LADX: use custom collect/remove to keep track of logical rupee counts instead of LogixMixin
May contain some pep8, sorry
2023-06-21 12:42:11 +02:00
NewSoupVi
845502ad39 The Witness: Hint distribution changes, added locations, misc fixes (#1785)
Changes:

* Hints should feel a lot less same-y now ("Priority hints" are no longer always hints in disguise)
* Keep Hedge Mazes 1-3 and Pressure Plates 1-3 are added as locations in all settings
* Desert Final Room Hexagonal & Desert Final Room Bent 3 are added as locations
* Entries in exclude_locations that are referring to panels are now sent through slot data. This means they can be pre-skipped on the client side.

Fixes:

* Logic error in the Stoneworks that led to more restrictive seeds than necessary
* Logic error for Theater Flowers EP that led to more restrictive seeds than necessary
* Fixed crash in plando when "item" is a dict with weights
* Spoiler log locations were in random order per region, now they are consistent
2023-06-21 00:45:26 +02:00
Sunny Bat
afe9e12ef4 Raft: Fix item prefilling (#1878) 2023-06-20 09:14:46 +02:00
Fabian Dill
a75159b57e WebHost: import Markup from markupsafe (#1848) 2023-06-20 01:01:42 +02:00
Fabian Dill
61fc80505e Core: refactor some loading mechanisms (#1753)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-20 01:01:18 +02:00
Fabian Dill
25f285b242 Launcher: deprecate FUNC Component type (#1872)
* Launcher: add hidden component type

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-19 09:57:17 +02:00
Fabian Dill
c4e28a8736 Setup: pin cx-Freeze to latest working version 2023-06-19 00:40:14 +02:00
Fabian Dill
422ccdaa4c WebHost: remove some unused imports 2023-06-18 22:56:55 +02:00
Bicoloursnake
1e7c650159 Docs: Updating the macOS guide for 'New Terminal at Folder' (#1865)
* Update mac_en.md

Added an alternate option to simply terminal navigation

* Update worlds/generic/docs/mac_en.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-06-18 13:21:12 +02:00
Aaron Wagener
ab64173600 SoE/SNIClient: auto launch SNI before browser when SNIClient patched (#1861)
* auto launch SNI before browser

* launch emulator too :)

* don't infinitely await sni connection
2023-06-18 11:27:08 +02:00
Fabian Dill
36499b8983 Setup: delete outdated Enemizer and SNI files 2023-06-17 00:56:13 +02:00
NewSoupVi
923ff033b1 The Witness: Logic Fix: Vanilla First Wooden Beam (#1867) 2023-06-15 00:30:50 +02:00
Sunny Bat
599d0ac81b Raft: Small website/code touchups (#1866)
* Remove unnecessary Set

* Ocean theme

* Use create_items instead of generate_basic
2023-06-15 00:30:14 +02:00
Ziktofel
ce2433b247 SC2: Python 3.11 compatibility (#1821)
Co-authored-by: Salzkorn
2023-06-12 07:41:53 +02:00
Scipio Wright
f6cb90daf9 Noita: Region connection edits (#1855)
Shifts the Lake region to be connected to The Laboratory, so that the Lake boss is late game instead of early game.
Shifts the Below Lava Lake region to be connected to the Snowy Depths, so instead of being early game it's early-mid game (since that's when you would be expected to be able to have decent enough digging or a Sädekivi.
2023-06-05 19:32:33 +02:00
el-u
54b200451d Docs: Fix typo in world api.md (#1854) 2023-06-01 22:56:44 -05:00
Exempt-Medic
b98080afee Docs: Update YAML planning guide (#1845)
* [Docs] Update YAML planning guide

Changed wording for items accessibility to describe how it actually works. Reordered settings such as local_items and start_location_hints to match their order in templates. Fixed some grammatical errors.

* Fix typo

Co-authored-by: Zach Parks <zach@alliware.com>

* Update doc

Moved `accessibility`, `progression_balancing`, and `triggers` to game sections instead of root sections and reworded description accordingly. Updated version number. Fixed `progression_balancing` values in example YAMLs.

* Indented trigger to be part of ALTTP

---------

Co-authored-by: Zach Parks <zach@alliware.com>
2023-06-01 22:33:12 -05:00
Exempt-Medic
5401e485aa Blasphemous: Logic fixes for WotBC Cherub and Jondo upper west tree root (#1835) 2023-06-01 03:52:46 +02:00
Fabian Dill
58cf9783eb Tests: make names more unique 2023-06-01 01:45:24 +02:00
Fabian Dill
fad0fe16f4 Tests: sort custom loaded tests (#1851) 2023-06-01 01:44:54 +02:00
kindasneaki
c2884e9eb0 RoR2: Victory Conditions Doc Update (#1833) 2023-05-31 18:38:03 -05:00
Doug Hoskisson
1809823308 Zillion: cache key includes gun requirement (#1846)
The key for the logic cache was missing some important information, so it was yielding a cache hit when it should have been a miss.
2023-05-31 05:56:23 +02:00
lordlou
df7462efcc SMZ3 decoding fix (#1847) 2023-05-30 03:05:05 +02:00
FlySniper
00e3c44400 Wargroove: Fixed commander.json file never being closed by the mod (#1841)
The Wargroove mod didn't close the commander.json's file handle. The Wargroove mod will now close that file handle. The change for the mod can be viewed here: FlySniper/WargrooveArchipelagoMod@fc9aeb3
The change can be verified as present in this repository by viewing the binary data in the modAssets.dat file and searching for "commander.json"
2023-05-29 20:33:35 +02:00
agilbert1412
abf4b3bcbc Stardew valley: Fix package and imports for apworld linux (#1842)
- Fix csv load to use explicitly imported self package instead of keyword __package__
- Fix init.py having a relative import to outside of the apworld
2023-05-29 01:00:33 +02:00
Fabian Dill
c9f217943e LttP: fix patching crash if old always_apply adjuster settings were applied 2023-05-25 14:08:56 +02:00
Fabian Dill
e9f8b1ed28 WebHost: use Py3.11 compatible ponyorm 2023-05-25 14:07:21 +02:00
el-u
c46d8afcfa Core: clean up BaseClasses a bit (#1731) 2023-05-25 01:24:12 +02:00
ScootyPuffJr1
f4d9c294a3 [SM] Minor update to link in Options.py (#1831) 2023-05-23 16:30:39 +02:00
Exempt-Medic
42d8fb8409 [Blasphemous] Various logic fixes (#1830)
This makes a few changes to logic to better match the 1.3 rando's logic. This fixes instances where the wrong items were expected, fixes a typo of "Lorqiana", moves the expert logic on "PotSS: Second area ledge" to only apply if on expert, and adds a new route to "DC: Mea Culpa altar" via Linen of Golden Thread + Three Gnarled Tongues
2023-05-22 19:03:21 +02:00
axe-y
127d4812b5 DLCQuest: Fix Documentation Broken Link 2023-05-21 15:48:56 +02:00
Fabian Dill
527f30d91a Core: log race mode enabled 2023-05-21 05:02:14 +02:00
Fabian Dill
1d565b9aaf WebHost: add game to template export 2023-05-21 05:01:56 +02:00
Fabian Dill
6814bc158a WebHost: index columns used by landing page. 2023-05-21 05:01:29 +02:00
alwaysintreble
e80f3206b6 The Messenger: override start_inventory description (#1695)
* The Messenger: override start_inventory description

* use StartInventoryPool directly
2023-05-21 02:54:50 +02:00
el-u
54ea917c48 CI: treat all files as modified on new branches (#1826) 2023-05-20 21:57:38 +02:00
Exempt-Medic
5e9bf4b007 Docs: Update world api excluded/priority locations description (#1807)
* Update world api doc

Changed the description of excluded and priority locations to match how they appear in other places such as the options api doc

* Update world api.md
2023-05-20 20:04:26 +02:00
Fabian Dill
c8453035da LttP: extract Dungeon and Boss from core (#1787) 2023-05-20 19:57:48 +02:00
Fabian Dill
a2ddd5c9e8 LttP: deterministic shop_shuffle 2023-05-20 19:43:44 +02:00
Fabian Dill
97ba631b80 Core: update modules 2023-05-20 19:36:55 +02:00
black-sliver
be4c597c8d Logging: make sure level is applied for websockets 2023-05-20 19:27:12 +02:00
black-sliver
324d3cf042 Main: add __all__ and change wrong imports (#1824)
* Main: add __all__ and change wrong imports

* Adjusters: fix __version__ import
2023-05-20 19:21:39 +02:00
Fabian Dill
b1c5456d18 Subnautica: move mod exports to own module 2023-05-20 18:34:22 +02:00
Cybrou
f474b81f40 LADX: Add --no-magpie argument for disabling magpie bridge (#1788) 2023-05-20 15:30:33 +02:00
el-u
5255bc5cd8 CI: add a workflow to show flake8/mypy violations in modified files of a PR (#1513)
* CI: add a workflow to show flake8 violations in modified files of a PR

* modify a file to trigger the lint check

* CI: add a workflow to show mypy violations in modified files of a PR

* modify a file to trigger the type check

* Split flake8 and mypy into two parallel jobs; run a variant of the workflow on push event; modify a file to trigger the push workflow

* fail the task if there are syntax errors; remove old lint workflow
2023-05-20 14:40:51 +02:00
Exempt-Medic
18127a75f5 Blasphemous: Fixed logic errors in WotHP 2023-05-19 11:05:52 +02:00
espeon65536
899de428df ALttP: fix dungeon fill failures properly (#1812) 2023-05-18 15:31:12 +02:00
Fabian Dill
f401702e7c Core: skip ModuleUpdate in subprocess 2023-05-18 15:29:17 +02:00
JaredWeakStrike
68bfe1705d KH2: AntipointReset (#1815) 2023-05-18 15:28:35 +02:00
Fabian Dill
98b0bf7456 LttP: use local_early_items for Small Key HC in Standard Keyshuffle (#1799) 2023-05-15 09:34:56 +02:00
NewSoupVi
7674e62ba7 The Witness: Logic fix in response to broken seed (Expert Swamp) 2023-05-15 08:54:12 +02:00
zig-for
0b33c25b39 Fix pokemon lua on bizhawk 2.9 (#1794)
---------

Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
2023-05-11 17:52:29 +02:00
Scipio Wright
62f4e62d71 Docs: Add location count specifics to Noita (#1805)
Added specifics about the number of checks in the pool.
2023-05-10 17:49:05 +02:00
espeon65536
48add4687c alttp: remove triforce during dungeon item fill (#1801)
This ensures that even for minimal worlds, the locations will be checked appropriately.
2023-05-10 13:06:25 +02:00
JaredWeakStrike
cc08e853a0 KH2: Ability Sync Fix (#1804) 2023-05-10 13:04:43 +02:00
lordlou
7e3fa5058d SM: door color rando option doc (#1803)
Added precision in DoorsColorsRando docstring about beams being forced local items if enabled.
2023-05-09 03:12:24 +02:00
t3hf1gm3nt
c74577d708 [TLOZ] Fix start weapon locations (#1802)
* Fix starting weapon locations usage

Makes a fresh copy of starting weapon locations when get_pool_core is ran
Should fix the issue of dangerous_weapon_locations getting appended to the list for other worlds past the first world that has dangerous StartingPosition, as well as running into the error if ExpandedPool was different between players
Credit for fix goes to @Silvris in the AP Discord
2023-05-08 22:36:35 +02:00
JaredWeakStrike
a8b76b1310 KH2: Async fix and linter cleanup (#1796) 2023-05-07 04:49:37 +02:00
Zach Parks
c8ebad1dfe WebHost: Prevent dict type options with valid_keys from exporting with [] on weighted settings. (#1762)
Thanks HK.
2023-05-06 19:07:57 -05:00
Cyb3R
d3447a3983 Launcher: Fix multiprocessing in built Launcher (#1792) 2023-05-05 00:53:57 +02:00
Fabian Dill
11b2b5ed2f Core: call stages in sorted order (#1791) 2023-05-04 14:14:20 +02:00
Fabian Dill
a0464ecea1 Core: fix start_inventory_from_pool breaking if it's removing the last instance of an item from pool.
Core: fix start_inventory_from_pool removing arbitrary items from pool if quick abort branch is entered.
2023-05-04 03:10:52 +02:00
zig-for
97fd78ba1b LADX: fix bizhawk 2.9 (#1784) 2023-05-03 23:35:14 +02:00
Fabian Dill
a60f370224 Test: fix setting seed from the hash of the seed method, rather than calling it 2023-05-03 04:31:35 +02:00
Scipio Wright
0363630f61 Noita - Docs updates (#1789)
* Docs updates

* Update worlds/noita/docs/setup_en.md

Co-authored-by: Adam Heinermann <aheinerm@gmail.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Adam Heinermann <aheinerm@gmail.com>
2023-05-03 00:35:50 +02:00
black-sliver
39c7c7291e Core: store multidata/multisave enums by value on py3.11 (#1783)
This is what py3.10 did and should be better for AP than the new default
2023-05-02 08:23:39 +02:00
TheLynk
a368520200 Add New Translation for Adventure and Archipidle in french (#1749)
* Add new translation for Adventure and Archipidle in french

Add new translation for Adventure and Archipidle in french

* Add more store in setup page subnautica for more fairness

Add more store in setup page subnautica for more fairness

* tweak update merge #1685 for lua file

tweak update merge #1685 for lua file

* fix text

fix text

* fix wrong translation

fix wrong translation

* Yes it's better

Yes it's better

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2023-05-01 02:03:31 +02:00
Fabian Dill
5d25f908a4 Launcher: fix loading of mcicon (#1779)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-04-30 18:10:58 +02:00
Fabian Dill
9edab76567 WebHost: recycle generator processes 2023-04-30 16:34:03 +02:00
kindasneaki
91b60f2e21 [RoR2] Classic mode logic fix (#1775) 2023-04-29 09:07:42 +02:00
Jarno
41b59488e3 [Docs] Added lua lib (#1751)
* [Docs] Added lua lib

* Update docs/network protocol.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-04-29 00:10:43 +02:00
Fabian Dill
42da24cb5e WebHost: offer room owner log download link 2023-04-28 05:31:19 +02:00
zig-for
28c5e9ee65 LADX: Rework dungeon item fill (#1763) 2023-04-28 05:30:13 +02:00
alwaysintreble
b55174ccdf Docs: document option alias in the options doc (#1755)
* Docs: document option alias in the options doc

* give an example of alias and move it under option creation.

* use clearer example names
2023-04-27 09:33:49 +02:00
lordlou
7bcf299412 SM: missing foreign item filter fix (#1774) 2023-04-27 02:23:52 +02:00
Abacys
a7816d186f Launcher: Correcting minor formatting error (#1768)
Reformatting comment to comply with PEP format
2023-04-26 13:43:23 +02:00
Fabian Dill
9d40471dee Subnautica: add free samples option 2023-04-26 10:50:22 +02:00
zig-for
b704070de5 LADX: Fix palettes (#1767) 2023-04-26 10:49:38 +02:00
Fabian Dill
6c459066a7 Core: add generator_version to network protocol 2023-04-26 10:48:57 +02:00
Fabian Dill
4c3eaf2996 LttP: fix that collect can bypass requirements for ganon ped goal (#1771)
LttP: more pep8
2023-04-26 10:48:08 +02:00
TheBigSalarius
bb56f7b400 FF1: Added URange fix for Bizhawk 2.9 support
URange wasn't moved to common.lua (and no longer exists in connector_ff1.lua) when the lua files were changed for Bizhawk 2.9 socket change.
2023-04-26 10:47:25 +02:00
Doug Hoskisson
22ed7ff9c3 Zillion: fix empty 1st Sphere (#1770)
There was a low probability that the Zillion 1st sphere could be empty.
caused this test failure: https://github.com/ArchipelagoMW/Archipelago/actions/runs/4791795268/jobs/8522615992
2023-04-26 07:24:47 +02:00
axe-y
173513c9f4 DLCQuest: Generation bug fix (#1757)
* Fix documentation error

* init_creation

somehow this fix bug

* item_shuffle_fix

also a count as been corrected

* Fix_early_generation

* Update __init__.py

* Update __init__.py

fix version specific bug

* fix rule set for final boss

and did some reformation
(thanks kaito)

* Update Rules.py

the sword trio can now be in itself if before or actually themself

* Core: correct typing info for item_in_locations
Core: rename item_in_locations to item_name_in_location_names
Core: add actual item_name_in_locations

* item_shuffle_fix

also a count as been corrected

* Fix_early_generation

* fix rule set for final boss

and did some reformation
(thanks kaito)

* Update Rules.py

the sword trio can now be in itself if before or actually themself

* Fix the missing []
and switch to the good function

* - Cleanup and Black Sliver's suggestions

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Alex Gilbert <alexgilbert@yahoo.com>
2023-04-25 09:06:58 +02:00
Bicoloursnake
c0cf35edda StarCraft 2 macOS documentation (#1747)
* Adding SC2 macOS instructions

A few hours ago, I tested whether the client would run successfully on macOS (Send/Receive items, load maps, download maps, etc.). After the successful testing, I thought adding some documentation would be nice for those who want to play Archipelago on a macOS system.

* Don't need sudo

Turns out you don't need sudo to do the download_data, oops.

* Removed Extraneous Parantheses
2023-04-25 00:53:33 +02:00
Fabian Dill
dcc628f878 Core: correct typing info for item_in_locations
Core: rename item_in_locations to item_name_in_location_names
Core: add actual item_name_in_locations
2023-04-24 23:14:13 +02:00
Fabian Dill
b950af09a6 Factorio: remove tech_tree_layout_prerequisites from core 2023-04-24 23:11:25 +02:00
Fabian Dill
58aea7ca58 Multiserver: cleaner exit (#1743) 2023-04-23 22:21:28 +02:00
JaredWeakStrike
06a25a903e KH2: New Unit Test and better keyblade fill (#1744)
__init__:
 - Added exception for if the player has too many excluded abilities on keyblades.
 - Fixed Action Abilities only on keyblades from breaking.
 - Added proper support for ability quantity's instead of 1 of the ability 
 - Moved filling the localitems slot data to init instead of generate_output so I could easily unit test it

TestSlotData:
- Checks if the "localItems" part of slot data is filled. This is used for keeping track of local items and making sure nothing dupes
2023-04-23 22:20:43 +02:00
Alchav
62a265cc31 Pokémon R/B: logic and location name fixes (#1752)
Corrects incorrect name listed for location in rules.py leading to logic rules failing to apply.
Swaps location names for incorrectly-named trainersanity checks in Viridian Gym
2023-04-23 22:17:03 +02:00
lordlou
67c3076572 SM: comeback fix6 and some refactor (#1756)
refactored and cleaned a bit SMWorld class for best practices:
- moved content of Regions.py and Rules.py in SMWorld
- moved appropiate code to their dedicated World core functions
- moved some Entrances being created in generate_basic to create_regions
more comeback check fixes:
- fixed setting progression door openers items local if doors_colors_rando is used
- enable comeback check only for filling stage as later stages (progression balancing, accessibility and spoiler playthrough) are prone to fail with it
2023-04-23 22:16:01 +02:00
agilbert1412
ab5cb7adad Stardew Valley - Add alias for renamed Fishsanity option (#1758) 2023-04-23 21:59:46 +02:00
lordlou
5e84f91d2f SM: comeback fix5 (#1746) 2023-04-22 02:57:31 +02:00
zig-for
a7a17a5a4d Fix OOT? (#1721) 2023-04-20 09:23:04 +02:00
Ziktofel
a6ea3e1953 Sc2wol logic update (#1715)
Fixes some issues with typos and oversights logic generation in SC2

Rebalancing
Adds Vultures to Advanced tactics as train killers
Drops Firebats from basic unit pool and moves Goliaths from advanced tactics to standard
2023-04-20 09:13:34 +02:00
zig-for
0515acc8fe LADX: no pre fill (#1673)
* no pre fill
* redo trade logic
2023-04-20 09:12:53 +02:00
Fabian Dill
bf5c1cbbbf WebHost: add spoiler level field to generate form 2023-04-20 09:12:07 +02:00
alwaysintreble
c8fb46a5e6 The Messenger: Throw error for invalid names and replace _ with (#1728)
* Replace '_' with ' ' and throw error for other invalid names

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-04-20 09:11:15 +02:00
lordlou
a38a2903d5 SM: comeback fix4 (#1741) 2023-04-20 09:10:21 +02:00
JaredWeakStrike
4ef7e43521 KH2: client bug fixes (#1742)
Use item index instead of location and player to determine if the player should get the item.
Fixed getting stat increases on the title screen breaking stuff.
Changed local locations list from a list organized by world-id to a set.
Fixed the inventory slots to be the actual back of inventory.
Fixed recaching when not closing the client but switching slots .
Fixed getting a ability faster than the game so it dupes.
Removed verify location since it was never used.
2023-04-20 09:08:59 +02:00
Fabian Dill
e1f17fadfc Launcher: add icons where present and add headers to table 2023-04-20 05:59:49 +02:00
Adam Heinermann
4dc934729d Noita: implement new game (#1676)
* Noita: implement new game (#1676)

---------

Co-authored-by: DaftBrit <87314354+DaftBrit@users.noreply.github.com>
Co-authored-by: l.kelsall@b4rn.org.uk <l.kelsall@b4rn.org.uk>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>
Co-authored-by: Scipio Wright <lightdemonjoe4@gmail.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-04-19 22:21:56 -05:00
Zach Parks
722757e18a Clique: Better, less-obtuse, docs for Clique. (#1719) 2023-04-19 21:17:16 -05:00
kindasneaki
0ca3c5e6a2 [kivy] change the sizing for macOS (#1732) 2023-04-19 23:16:13 +02:00
agilbert1412
664bbd86bb Stardew Valley: Fix Mistake and Formatting in settings page (#1737) 2023-04-19 23:15:22 +02:00
axe-y
7a9d4272be Generation bug fix (#1740) 2023-04-19 23:14:46 +02:00
alwaysintreble
7559adbb14 LTTP: fix bad parens in logging 2023-04-19 04:32:15 +02:00
Trevor L
1a7bc4ffd4 Blasphemous: Fix logic for Laudes (for real this time) (#1727) 2023-04-18 02:03:06 +02:00
Fabian Dill
be74a4a71a LttP: update xxtea to 3.0.0 2023-04-17 22:56:54 +02:00
lordlou
cb634fa8d4 SM: failing generation fixes (#1726)
- fixed wrong condition in Collect to assign lastAP
- fixed possible infinite loop in generating output when many SM worlds are present
- fixed new VARIA code that changed a list used for every SM worlds and would throw if many SM worlds uses Aea rando and not AreaLayout
2023-04-17 05:46:19 +02:00
axe-y
f6758524d5 DLCQuest: fix loader bug (#1729) 2023-04-17 02:38:48 +02:00
Fabian Dill
f395a6d184 Docs: has_all and has_any (#1725)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-04-16 12:59:53 +02:00
agilbert1412
ea03c90152 Stardew Valley: Fix a logic bug and a documentation typo (#1722)
Typo in the documentation
Logic error for help wanted quests
2023-04-16 11:22:33 +02:00
Trevor L
50d9ab041a Blasphemous: Fix logic for Laudes (#1724) 2023-04-16 10:53:42 +02:00
axe-y
acd3cb45bf DLCQuest: Fix documentation error (#1720) 2023-04-16 05:04:47 +02:00
JaredWeakStrike
8a78062825 KH2: fixed lucky emblem required>lucky emblem amount (#1718) 2023-04-16 01:59:01 +02:00
Fabian Dill
599cd2c82e Launcher: add Discord links and generate yamls (#1716) 2023-04-16 01:57:52 +02:00
espeon65536
89ec31708e oot: bugfixes (#1709)
* oot client: check types of tables coming from lua script for safety
There was a reported bug with corrupted (?) slot data preventing locations sending. This should safeguard against any instances of that happening in the future, if it ever happens again.

* oot: repair minor hint issues
SMW has # in some location names which breaks ootr's poor text formatting system, so those need to be filtered out.
Also replaces "[X] for [player Y]" with "[player Y]'s X" as frequently requested.

* oot: update required client version

* oot client: fix patching filename bug

* oot: fix broken poachers saw item
how was I this stupid, seriously

* oot: sanitize player, location, and item names everywhere
2023-04-16 01:45:31 +02:00
Fabian Dill
ef211da27f Core: update modules 2023-04-16 01:43:24 +02:00
JaredWeakStrike
0122eb38ab KH2: Fix validAbilitys (#1714) 2023-04-15 22:04:08 +02:00
JaredWeakStrike
3d8bc0bb67 KH2: Init Cleanup and Keyblade Fix (#1713) 2023-04-15 21:17:23 +02:00
JusticePS
d85c13ef0e Adventure: Fix error in connector lua that was fairly harmless in BizHawk 2.8 but throws in 2.9 2023-04-15 21:16:11 +02:00
Doug Hoskisson
27cb93d319 Asset: make icon 512 x 512 (#1710) 2023-04-15 10:37:31 -05:00
black-sliver
b0e8c8db6b MultiServer: fix broken ping (#1711)
Ping argument seems to have changed in an update of websockets.
This uses the lib's default of 20s now.
2023-04-15 17:10:33 +02:00
zig-for
5a7d20d393 Lua: Further centralize code, fix Bizhawk 2.9 (#1685)
* Fixes the socket library for bizhawk 2.9/lua 5.4 by including another one in parallel
* Fixes lua 5.4 support by making socket.lua into a "modern" module (the `module` keyword is gone)
* Adds the linux version and 32 bit windows socket dlls because why not
* Merges common functions into `common.lua` - the only functional change of this should be that:
  * Some things that were locals are globals now - this can be changed, I just was lazy and it likely doesn't matter
  * `drawText` now uses middle/bottom for all prints - feel free to do what you like with that change
2023-04-15 09:17:33 +02:00
zig-for
808203a50f LADX: fix egg sprite in non-patched gfx mode (#1699) 2023-04-15 07:47:45 +02:00
zig-for
d8f79b4a42 LADX: Fix useless item being marked as progression (#1700) 2023-04-15 06:48:05 +02:00
zig-for
02ef6cee47 LADX: Support magpie tracker's sendfull button (#1701) 2023-04-15 06:47:36 +02:00
JaredWeakStrike
e716b50f8c KH2: Fixed Non Deterministic Generation (#1707) 2023-04-15 06:15:04 +02:00
agilbert1412
3fdf07677c Stardew Valley: Removed Pytest Requirement from everything (#1696) 2023-04-15 05:42:02 +02:00
t3hf1gm3nt
d3baca9251 [TLOZ] Add FileNotFoundError handling for base rom (#1708) 2023-04-15 05:40:48 +02:00
alwaysintreble
f52ca2571f Tests: Add tests for location name groups (#1706) 2023-04-14 20:11:01 +02:00
Fabian Dill
469807ba01 Core: remove outdated assert on push_item 2023-04-14 20:05:12 +02:00
alwaysintreble
8ada91939c LTTP: Fix Location Name Groups (#1705) 2023-04-14 20:04:20 +02:00
axe-y
054d14baa4 Fix DLCQuest Errors Generating on Latest Source, without any DLCQuest YAMLs (#1704) 2023-04-14 07:09:50 +02:00
Magnemania
f0324e60f8 SC2: Removed extra space from location (#1697) 2023-04-11 11:50:26 -05:00
zig-for
70ff19ac8c LADX: AP egg title screen (#1683) 2023-04-11 09:18:33 +02:00
Fabian Dill
b02b329181 DLCQuest: fix data_version 2023-04-11 08:47:19 +02:00
Nyx-Edelstein
8b7ffaf671 ALTTP: Add "oof" sound customization option (#709)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Fabian Dill <fabian.dill@web.de>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-04-10 21:31:57 -05:00
toasterparty
c711d803f8 [OC2] Enabled DLC Option (#1688)
- New OC2 option `DLCOptionSet`, which is a list of DLCs whose levels should or shouldn't be used for entrance randomizer (and mention in documentation). By default, DLC owners now need to enable DLCs in weighted settings.
- Throw user-friendly exceptions when contradictory settings are enabled
- Slightly relax generation requirements for sphere 1/2 level permutations
- Write entrance randomizer info in spoiler log
- Skip adding "Dark Green Ramp" to item pool if Kevin Levels are disabled
2023-04-11 03:43:29 +02:00
Zach Parks
3c3954f5e8 Core: Band-aid fixes start_inventory_from_pool causing generation failures if any world doesn't utilize it. (#1694)
* Core: Band-aid fixes `start_inventory_from_pool` causing generation failures if any world does utilize it.

* Core: Slightly better(?) solution

* Set default so it doesn't fail on WebHost.
2023-04-10 20:18:29 -05:00
Alchav
05d398a51d Pokémon R/B: Missing game corner logic (#1693)
* Game corner logic fix

* Fix for the fix
2023-04-10 20:16:38 -05:00
agilbert1412
5eadbc9840 Major Game Update: Stardew Valley v3.x.x - The BK Update (#1686)
This is a major update for Stardew Valley, for version 3.x.x.
Changes include a large number of new features, including Seasons Randomizer, SeedShuffle, Museumsanity, Friendsanity, Complete Collection Goal, Full House Goal, friendship multiplier

Co-authored-by: Jouramie <jouramie@hotmail.com>
2023-04-11 01:44:59 +02:00
Zach Parks
0c1e3097c3 WebHost: Set defaults for lists/sets on Weighted Settings page (#1692) 2023-04-10 18:01:54 -04:00
Fabian Dill
cdf7ca1dcc Core: allow ordered valid keys (#1690)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
2023-04-10 23:54:56 +02:00
alwaysintreble
77fbd0eb2b MultiServer: Notify clients of hint points (#1548)
* notify clients of their amount of hint points on initial connection and when hinting

* send in connect packet instead of sending a RoomUpdate on connect

* send hint_points update in `on_new_hint`

* add to connected packet docs

* hint_points isn't a new variable on RoomUpdate now

* note roomupdate can contain connected members

* add the hint point stuff to commonclient

* only show hint points when relevant and default to 0

* Revert "note roomupdate can contain connected members"

* remove hint_points from roomupdate args list and condense explanation of possible packet args

* updates from phar's review

* Small tweak to wording in RoomUpdate

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Phar <zach@alliware.com>
2023-04-10 14:44:20 -05:00
Fabian Dill
c7284f90d9 Core: implement start_inventory_from_pool (#1170)
* Core: implement start_inventory_from_pool

* Factorio/LttP/Subnautica: add start_inventory_from_pool Option
2023-04-10 21:13:33 +02:00
alwaysintreble
8d559daa35 LTTP: use server for counting locations instead of having 249/216 (#1648) 2023-04-10 21:12:06 +02:00
Ziktofel
e49ffc64f2 SC2: Rebalance item classes (#1512)
It's been a month, if Condor disapproves this can be reverted.
I don't really know what to do about the SC2WOL situation going forward.
2023-04-10 21:08:49 +02:00
alwaysintreble
94a02510c0 core: Region management helpers (#761) 2023-04-10 21:07:37 +02:00
Fabian Dill
9d73988030 Tests: check that options have a docstring (#823) 2023-04-10 04:33:47 +02:00
JaredWeakStrike
81411a191c KH2: init cleanup and random visit locking fix and docs update. (#1652)
Random Visit Locking wasn't copying correctly
init cleanup and moved itempool population to create_items
Updated docs due to a lot of people having issues setting it up.
2023-04-10 04:12:23 +02:00
lordlou
6059b5ef66 SM: 20221101 update (#1479)
This adds support to most of Varia's 20221101 update. Notably, added Options for:
- Objectives
- Tourian
- RelaxedRoundRobinCF

As well as previously unsupported Options:
- EscapeRando
- RemoveEscapeEnemies
- HideItems
2023-04-10 00:35:46 +02:00
black-sliver
0bc5a3bc8d Readme: add missing game DLC Quest (#1682) 2023-04-09 15:53:23 -05:00
black-sliver
11fdb29357 Doc: apworlds have to be all lower case
https://discord.com/channels/731205301247803413/731214280439103580/1094655639600508999
2023-04-09 21:48:22 +02:00
axe-y
bbef7a4cbc DLCQuest : implement new game (#1628)
adding DLC Quest as a new game
2023-04-09 21:06:59 +02:00
Fabian Dill
8e7bbb4ea8 Factorio: flatten science pack curve (#1660)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-04-09 20:58:24 +02:00
alwaysintreble
6628e8c85d Docs: Add some more details to running from source doc (#1680)
* make build tools step more obviously optional and give better directions

* review commit
2023-04-09 15:53:14 +02:00
lordlou
84402a1b55 SM and SMZ3 apworld support (#1677) 2023-04-08 22:52:34 +02:00
Fabian Dill
f4035b8621 MultiServer: remove remaining forfeit compat from network layer 2023-04-08 20:10:07 +02:00
Fabian Dill
bbf8546867 MultiServer: Flag for saving on datastore, create_as_hint scout and client state change 2023-04-08 20:09:51 +02:00
Zach Parks
67a22b8b43 Clique: Force priority location for "final location" and other minor tweaks. (#1583)
* Clique: The greatest game of all time

* Fix failing test

* Increment data_version for backwards compat

* Clique: Tweaked names and forced progression for "The Button"

* Clique: Just location/item definitions to class

* Clique: More tweaks.

* Clique: Fix derp moment.

* Clique: Fix derp moment, part dos.

* Clique: Suggested changes.

* Clique: Update domain

* Clique: Create derived classes for Item and Location

* Clique: simplify line
2023-04-07 19:05:16 -05:00
Trevor L
8e6ec85532 Blasphemous: Update docs, item creation (#1670)
* Blasphemous: Update docs, item creation

* Blasphemous: Remove get_pre_fill_items
2023-04-07 19:04:34 -05:00
Yussur Mustafa Oraji
8fc50510a0 sm64ex,v6: Use create_items for itempool modification (#1674) 2023-04-07 19:03:28 -05:00
Trevor L
aa6ad5d34f Hylics 2: Update item creation (#1671) 2023-04-07 20:01:26 +02:00
zig-for
ccb89dd65c WebHost: Fix upload of .archipelago file (#1657)
* Fix upload

* simple fix for slot data

* remove extra patch.data check

* remove extra parens

* Update WebHostLib/upload.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update WebHostLib/upload.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* parse -> process

* Update WebHostLib/upload.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-04-07 00:24:03 +02:00
agilbert1412
a86c0aa37d Stardew: Fix link in Setup Guide (#1672) 2023-04-06 20:07:09 +02:00
zig-for
ece6598b09 LADX: apworld (#1665) 2023-04-06 20:06:34 +02:00
alwaysintreble
eef8f7af1a The Messenger: Add Mega Time Shards and Quest 1 boss locations (#1661)
* implement mega shards

* create the option and locations, add to slot data and tests

* add boss refights as locations

* remove barma'thazel. it's apparently impossible to get to him

* remove barma'thazel again

* up max shard count to 85

* increment version

* dynamically alter the power seal pool

* revert host.yaml change

* two mega shards were missing from the maps

* add new checks to the info page

* add some more rules to skylands

* forgot to update my tests

* explicit imports, remove unnecessary typing, lower required client ver

* use generators for shard and seal creation
2023-04-06 10:48:30 +02:00
Chris Wilson
c626618221 Update ArchipIDLE's documentation and create items in create_items func. (#1669) 2023-04-06 00:08:41 -04:00
kindasneaki
47989325f8 [Webhost] header closing tag moved after mobile menu (#1650)
* Change archipelago mod download page

* Docs: change connecting to archipelago in RoR2 setup guide

* /header off by one
2023-04-05 19:27:56 -04:00
black-sliver
815e7e6b0a Core: default data_version to 0 (#1668)
* Core: default data_version to 0

This allows new (ap-)worlds to function with old clients without having to define a version.

* Blasphemous: fix data_version
2023-04-06 01:19:58 +02:00
Fabian Dill
a61a1f58c6 Network: allow filtering checked and missing by text fragment 2023-04-05 19:36:32 +02:00
black-sliver
4c24872264 CI: update ubuntu to 20.04
18.04 will not be supported starting 2023-04-01
2023-04-05 19:16:32 +02:00
Zach Parks
e778e49574 Core: Fix ZeroDivisionError if a world is contained 100% locked locations. (#1659)
* Core: Fix divide by zero error if all locations are priority

* Core: 100% makes more sense than 0% in this context

* Core: Revert a some "autoformatter" damage

* Core: Move comparisons a bit earlier.

Worlds that are 100% filled with locked locations should now be completely skipped in the progression balancing step now as we filter them out at the very beginning of the stage now. Also skips progression balancing if it turns out all locations are locked early before attempting to balance.
2023-04-05 12:11:34 -05:00
FlySniper
25f7413881 Wargroove: Client is not added to the Start Menu. (#1664)
* Wargroove - Fixed client not showing up in the windows search menu.
2023-04-05 00:07:15 -05:00
Alchav
397ce8343e Pokémon R/B: Pokédex option fixes (#1666)
* Pokémon R/B: Pokedex option fixes

* Pokémon R/B: Missing option display names
2023-04-04 23:59:59 -05:00
Trevor L
37fdc00517 Blasphemous: Small logic fixes (#1667) 2023-04-04 23:54:51 -05:00
alwaysintreble
03aa9b3604 Update AP icons (#1637) 2023-04-04 23:38:23 -05:00
JusticePS
8f52e4654f Adventure: Change user_path to local_path for locating basepatch file (#1639) 2023-04-04 19:32:03 +02:00
alwaysintreble
a86fd37860 Core: set convert_name_groups to true for LocationSet (#1663) 2023-04-04 19:29:20 +02:00
toasterparty
eb503adb13 [OC2] Only Calculate Priority Locations Once (#1662) 2023-04-04 06:53:05 +02:00
Fabian Dill
cbf72becc1 Subnautica: group items and removal of item pool option 2023-04-04 06:46:54 +02:00
alwaysintreble
cdd460ae15 The Messenger: some miscellaneous cleanup. Also found a logic bug. oops. (#1654) 2023-04-04 02:27:36 +02:00
toasterparty
ffd968d89d [OC2] Fix Overworld Access Logic for World 3 (#1655) 2023-04-04 02:26:24 +02:00
toasterparty
8d73746d5b [OC2] Spelling Mistakes (#1656) 2023-04-04 02:25:59 +02:00
zig-for
5ed56db48a LADX: Fix crash in item pick up with > 100 players (#1658) 2023-04-04 02:23:39 +02:00
PoryGone
5f447f4e6b SMW: Fix Option Tooltip (#1651) 2023-04-02 03:54:07 +02:00
Fabian Dill
f015cf4298 MultiServer: compat fix if checksum is not present (#1642) 2023-04-01 22:40:14 +02:00
Freya Arbjerg
e43bb99622 Fix broken styling of multitracker navigation (#1644) 2023-04-01 19:56:08 +02:00
JaredWeakStrike
34de5a57af KH2: updated the docs for options and faq (#1641) 2023-04-01 19:55:43 +02:00
Fabian Dill
510a460d84 Multiserver: location name groups fix (#1643) 2023-04-01 19:54:44 +02:00
Justin LaLone
6e271b643d Adventure: Added examples for automatically loading adventure_connector.lua
in BizHawk
2023-04-01 19:54:02 +02:00
Fabian Dill
8971340a66 Core: update version to 0.4.0 2023-03-31 14:43:05 +02:00
zig-for
30cfd3186c LADX: Fix local paths (#1634) 2023-03-31 14:05:51 +02:00
Chris Wilson
1dc4e2b44b Restore "random" option to weighted-settings (#1635)
* Restore "random" option to weighted-settings, adjust capitalization of hardcoded settings

* Set default value as "random" for Choice, TextChoice, and Toggle options with no default value
2023-03-30 16:01:31 -07:00
alwaysintreble
d5b4a91a13 The Messenger: Have users explicitly remove old version (#1632) 2023-03-30 23:18:56 +02:00
alwaysintreble
bf5282dfa8 add Toggle options back to player settings and remove unnecessary check (#1633) 2023-03-30 16:56:26 -04:00
agilbert1412
4eea91daab Stardew Valley: Improve Documentation (#1631) 2023-03-30 16:25:25 +02:00
Wilhelm Schürmann
20e80d06cf LADX: Add Lua connector for BizHawk (#1579)
This is a Lua script for BizHawk that implements the relevant parts of
the RetroArch networking API used by the Archipelago LADX Client.

socket.lua and core.dll are exact copies of the same files in
data/lua/OOT and various other folders. There is a PR consolidating
these into the base folder, which this commit is anticipating.

LADX "just works"(tm) when this is loaded in Bizhawk.
2023-03-30 15:55:38 +02:00
Rosalie-A
59b78528a9 TLoZ: Fix description and off-by-one error (#1625) 2023-03-30 15:31:16 +02:00
Fabian Dill
cd4fd18706 Core: update modules (#1612) 2023-03-30 15:30:43 +02:00
KernRat
af44c1ba3d Factorio: Fixed outdated energy link troughput in docs (#1626) 2023-03-30 15:30:25 +02:00
Alchav
3ef0a56ec2 Pokémon R/B: Another quiz fix 2023-03-30 15:29:55 +02:00
t3hf1gm3nt
4ff282a384 [TLOZ] Pols Voice Logic Fix (#1630)
* TLOZ: Pols Voice Logic Fix

Was informed that Pols Voice require certain weapons to kill that might not be guaranteed by the starting weapon. This is the only regular enemy in the game that cannot be killed by either any of the starting weapons or bombs which can be bought, so adding this rule should prevent any issues.
2023-03-30 15:29:06 +02:00
Chris Wilson
f3dad894ec Update ArchipIDLE item count to 200, and add a few more items (#1627) 2023-03-29 18:27:35 -07:00
Chris Wilson
a5373e3672 [WebHost] Add support for items-list, locations-list, and custom-list option types to weighted-settings (#1614)
* Add support for items-list, locations-list, and custom-list option types to weighted-settings

* Fix subclass detection for `items-list`, `locations-list`, and `custom-list` in weighted-settings

* Move specially handled options alongside each other in the UI, and split them into logical groupings

* Fix header text and location for Priority an Exclusion locations

* Add universally supported "random" option to `Choice` and `TextChoice` options in weighted-settings

* Update description text for exclusion items to clarify they also prevent helpful items.

* Be technically correct and call them `useful` items.
2023-03-29 14:37:39 -07:00
black-sliver
639606e0be worlds,clients,kvui: use user_path (#1622) 2023-03-29 20:14:45 +02:00
zig-for
bb79073ce7 LADX: Fix autotracking for shop items (#1623) 2023-03-29 14:56:10 +02:00
zig-for
53b3cd029e LADX: Add options for sword music and nag messages (#1621) 2023-03-29 14:48:10 +02:00
zig-for
99bd525c8e LADX: use verify() to verify sprites (#1620) 2023-03-29 14:42:08 +02:00
zig-for
d14ab97849 LADX: Fix gen failures with non-ascii player names, fix missing custom (#1619) 2023-03-29 14:41:11 +02:00
zig-for
f50e85b401 LADX: Fix repeated rupee adds overwriting instead of adding (#1618) 2023-03-29 14:40:19 +02:00
Fabian Dill
b64565594a kvui: block settings menu 2023-03-29 14:39:06 +02:00
JaredWeakStrike
ae7dad8bf9 Fixed Blacklist and python 3.8 support (#1616) 2023-03-28 18:02:06 +02:00
alwaysintreble
b7c74919b7 Spoiler: add player name to sphere 0 items in playthrough (#1607) 2023-03-27 19:42:15 +02:00
Jarno
a7f7f91aaf Timespinner: LOGIC FIX, RC BUG (#1610) 2023-03-27 19:17:50 +02:00
JaredWeakStrike
e62f989ce8 Kh2 rc2 fixes (#1608) 2023-03-27 19:17:06 +02:00
Chris Wilson
21c6c28755 [WebHost] Fix weighted-settings UI incorrectly populating range values (#1602)
* Fix a bug causing weighted-settings UI to incorrectly display range values with no default as having a value of 25.

* Do not set min and max values of range options to zero by default. This causes clutter in saved `.yaml` files.
2023-03-27 08:55:13 -07:00
Alchav
f0403b9c9d Pokémon R/B: Sort fix 2023-03-27 15:28:17 +02:00
Chris Wilson
f09f3663d6 [WebHost] Unify style and behavior of popover and mobile menus (#1596)
* [WebHost] Unify styles for popover and mobile menus. Adjust popover menu to function the same as the mobile menu, and toggle via JS.

* Remove class `first-link` in favor of CSS `:first-child`.

* Adjust mobile menu link font size and padding. Change wording of popover trigger text. Add border-right to popover menu. Change "Upload Host File" to "Host Game"

* Change mobile menu text to "Host Game"
2023-03-27 00:12:10 -04:00
Brooty Johnson
b5bd93c420 fixed duplicate location (#1604)
removed duplicate location for "PW: Vilhelm's Leggings"
2023-03-27 03:04:04 +02:00
Fabian Dill
90813c0f4b Test: explicitly make sure there is no double use of location Name or ID (#1605) 2023-03-26 16:04:13 -07:00
JusticePS
e2c4293a6d Adventure: Fix basepatch access from other working directories (#1600) 2023-03-26 19:34:18 +02:00
Payden K. Pringle
963c33c02a Docs: Update BizHawk Open Script wording to be clearer. (#1599) 2023-03-26 19:34:00 +02:00
black-sliver
7d603e7d8d CI: run build_exe twice (#1598)
* CI: run build_exe twice ...

... to find broken conditional imports

* CI: build again in venv
2023-03-26 00:54:56 +01:00
black-sliver
f2e1495d39 Setup: fix broken cx_freeze import 2023-03-26 00:20:14 +01:00
Tarokarr
7927b2ee25 LADX: Added: Credits for sprite sheets (#1594) 2023-03-25 22:29:59 +01:00
alwaysintreble
4f2b13a674 The Messenger, Docs: Change links for the release (#1595)
* change links to point to my fork

* Add documentation about reconnecting because that's necessary apparently

* change the bug report link

* be non ambiguous

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-25 22:11:51 +01:00
black-sliver
ffd7d5da74 ModuleUpdate/Setup: install pkg_resources, check pip, typing and cleanup (#1593)
* ModuleUpdater/setup: install pkg_resources and check for pip

plus minor cleanup in the github actions

* ModuleUpdate/setup: make flake8 happy

* ModuleUpdate/setup: make mypy happier
2023-03-25 19:54:42 +01:00
Trevor L
67eb370200 Blasphemous: Change ending option (#1592) 2023-03-25 19:37:25 +01:00
JaredWeakStrike
4456e36fbb KH2: fixes medal being shadow archive memory address (#1589) 2023-03-25 19:36:48 +01:00
ScootyPuffJr1
7fd9e71b3c TLOZ: Fix Broken Links to Player Settings Page 2023-03-25 19:36:13 +01:00
alwaysintreble
f4a68f1c3d Core: add an everywhere location group (#1582) 2023-03-25 19:35:19 +01:00
Fabian Dill
754a57cf69 WebHost: give active rooms a chance to reclaim their port 2023-03-25 19:34:09 +01:00
lordlou
384577e421 SM: cx_freeze fix (#1584) 2023-03-25 19:30:38 +01:00
alwaysintreble
0ed3865c30 The Messenger: Fix a missing location rule and missing known issue (#1586)
* fix missing rule

* document a missing known issue

* fix a break when shuffle seals is off

* test the thing i just fixed

* invert the if so it's a bit faster
2023-03-25 00:55:24 +01:00
zig-for
77b2ed54a6 LADX: Fix D6 keylogic (#1585)
* fix keylogic for d6

* markup required keys for keylogic

* add test

* Update __init__.py
2023-03-25 00:23:42 +01:00
PoryGone
0386d9f6d2 Fix SA2B Option Display Name (#1591) 2023-03-25 00:22:47 +01:00
Fabian Dill
7e52b6d8bb MultiServer: if there is a hint cost, don't make it 0 (#1581) 2023-03-24 23:14:34 +01:00
kindasneaki
03cf525b2c Webhost: Add dropdown menus (#1553)
* Change archipelago mod download page

* Docs: change connecting to archipelago in RoR2 setup guide

* dropdowns for links

* change some relative sizing

* change links and reorder links

* dropdowns for links

* change some relative sizing

* change links and reorder links

* mobile view was showing on desktop early

* add in missing relative font sizes

* clean up and add a temp downdown img

* move links around

* added cloud border

* move arrow to the left side
2023-03-23 22:11:39 -07:00
zig-for
e1f46d623c LADX: Pass in seed_name and auth separately (#1575) 2023-03-23 21:23:58 +01:00
zig-for
5bb6ff0ce0 LADX: Fixup missing descriptions (#1576) 2023-03-23 21:22:42 +01:00
zig-for
256f493ada LADX: fix web gen (#1574) 2023-03-23 14:53:48 +01:00
Chris Wilson
3ec2d45f4f [WebHost] Improve mobile styles for WebHost (#1571)
* Improve mobile styles for WebHost

- Mobile view works properly on mobile Chrome and Firefox
- Added thin-window view for desktop browsers that have been reduced in size
- Tested in Chrome and Firefox on mobile and desktop

* Improve style for small-width desktop popover
2023-03-23 00:20:34 -07:00
black-sliver
b3895750ab Docs: from source on macOS: update supported python versions 2023-03-22 19:50:30 +01:00
alwaysintreble
7591404151 OOT: set default for adult trade start if the set is empty 2023-03-22 18:39:32 +01:00
JusticePS
d48e1e447f Adventure: implement new game (#1531)
Adds Adventure for the Atari 2600, NTSC version. New randomizer, not based on prior works. Somewhat atypical of current AP rom patch games; The generator does not require the adventure rom, but writes some data to an .apadvn APContainer file that the client uses along with a base bsdiff patch to generate a final rom file.
2023-03-22 15:25:55 +01:00
JaredWeakStrike
206f8cf5ed KH2: fixed bugs of rc1 (#1565)
KH2Client:
- Now checks if the world id is in the list of checks. This fixed sending out stuff on the movie
- Cleaned up unused inports
- Not getting starting invo if the game is not open when you connect to the server

__init__:
-Cleaned up print statements
- Fixed the spoiler log not outputting the right amount of mcguffins after fixing them in the case of the player messing up their settings

Openkh:
-Fixed putting the correct dummy item on levels
2023-03-22 15:21:41 +01:00
Alchav
0c6b1827fe Pokemon R/B: Pokemon Tower Wild Pokemon logic 2023-03-22 15:20:23 +01:00
PoryGone
017f91c1b5 LADX: Remove duplicate Link's Awakening setup (#1573) 2023-03-22 15:19:29 +01:00
Jarno
95b01def6b Timespinner: RC bug, lake serene is dry, when Rising Tides is off (#1570)
* Tinmespinner: RC bug, lake serene is dry, when Rising Tides is off

* yes
2023-03-22 03:32:37 -07:00
black-sliver
5977e401d5 MultiServer: include 'Archipelago' in games, versions and checksums 2023-03-21 23:36:53 +01:00
PoryGone
21a3c74783 SA2B: v2.1 Content Update (#1563)
Changelog:

Features:
- New goal
  - Grand Prix
    - Complete all of the Kart Races to win!
- New optional Location Checks
  - Omosanity (Activating Omochao)
  - Kart Race Mode
- Ring Loss option
  - `Classic` - lose all rings on hit
  - `Modern` - lose 20 rings on hit
  - `OHKO` - instantly die on hit, regardless of ring count (shields still protect you)
- New Trap
  - Pong Trap

Quality of Life:
- SA2B is now distributed as an `.apworld`
- Maximum possible number of Emblems in item pool is increased from 180 to 250
- An indicator now shows on the Stage Select screen when `Cannon's Core` is available
- Certain traps (`Exposition` and `Pong`) are now possible to receive on `Route 101` and `Route 280`
- Certain traps (`Confusion`, `Chaos Control`, `Exposition` and `Pong`) are now possible to receive on `FinalHazard`

Bug Fixes:
- Actually swap Intermediate and Expert Chao Races correctly
- Don't always grant double score for killing Gold Beetles anymore
- Ensure upgrades are applied properly, even when received while dying
- Fix the Message Queue getting disordered when receiving many messages in quick succession
- Fix Logic errors
  - `City Escape - 3` (Hard Logic) now requires no upgrades
  - `Mission Street - Pipe 2` (Hard Logic) now requires no upgrades
  - `Crazy Gadget - Pipe 3` (Hard Logic) now requires no upgrades
  - `Egg Quarters - 3` (Hard Logic) now requires only `Rouge - Mystic Melody`
  - `Mad Space - 5` (Hard Logic) now requires no upgrades

Co-authored-by: RaspberrySpaceJam <tyler.summers@gmail.com>
2023-03-21 21:26:13 +01:00
Zach Parks
2fb9176511 Clique: The greatest game of all time. (#1566) 2023-03-21 21:23:45 +01:00
alwaysintreble
1c69fb3c3c The Messenger: Add more difficult logic options (#1550) 2023-03-21 21:21:27 +01:00
Fabian Dill
91502505a1 WebHost: fix type in states template 2023-03-21 20:05:37 +01:00
black-sliver
01c13ca243 Docs: some clarification in running from source 2023-03-21 18:28:45 +01:00
Fabian Dill
c2a8b842de Core: typo 2023-03-21 15:53:10 +01:00
alwaysintreble
856efebc39 Multiserver: Only update client status for a slot when the first enters and the last leaves (#1358) 2023-03-21 15:50:50 +01:00
Alchav
5a4203649d Pokémon R/B: Quiz fix 2023-03-21 15:46:11 +01:00
Alchav
ddb764a9b6 Pokémon R/B: Skip unnecessary sweeps for events 2023-03-21 15:46:11 +01:00
Rosalie-A
9f65f22fac TLoZ: Installer Info (#1554) 2023-03-21 15:45:31 +01:00
Magnemania
b7ff9b69ba WG: Added Client to Setup (#1556) 2023-03-21 14:18:59 +01:00
Fabian Dill
012e6ba24c WebHost: add Status to MultiTracker 2023-03-21 14:06:38 +01:00
The T
cd9d0bebc8 Add LADX to Readme.md (#1559)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-20 23:55:53 +01:00
Brooty Johnson
3fa6588637 TLoZ: Update instructions (#1558)
Added 'Optional Software' to instructions for setting up Z1
2023-03-20 23:45:36 +01:00
JaredWeakStrike
e6d16c905c KH2: Fixed inno_setup and fixed readme.md (#1557) 2023-03-20 23:43:29 +01:00
Fabian Dill
958829d491 Launcher: dynamic Launcher 2023-03-20 23:40:34 +01:00
KonoTyran
9ee37b0ec5 [Minecraft] Update MinecraftClient.py (#1524)
* add support for direct url for mod to download.
make a nice error message if the chosen release channel is empty.

* Rip out call to github api as it is no longer used.

* rework error message to be more descriptive, and provide basic troubleshooting.

fix command line data version not correctly overriding apmc data version.
2023-03-20 11:44:48 -07:00
zig-for
81a239325d Links Awakening: Implement New Game (#1334)
Adds Link's Awakening: DX. Fully imports and forks LADXR, with permission - https://github.com/daid/LADXR
2023-03-20 17:26:03 +01:00
JaredWeakStrike
67bf12369a KH2 game implementation (#1438) 2023-03-20 17:19:55 +01:00
toasterparty
d4b793902f [OC2] Overworld Logic (#1530) 2023-03-20 17:16:19 +01:00
Fabian Dill
6671b21a86 Core: Generic excluded fill (#1511) 2023-03-20 17:10:12 +01:00
el-u
6d13dc4944 lufia2ac: new features, bug fixes, and more (#1549)
### New features

- ***Architect mode***
  Usually the cave is randomized by the game, meaning that each attempt will produce a different dungeon. However, with this new feature the player can, between runs, opt into keeping the same cave. If activated, they will then encounter the same floor layouts, same enemy spawns, and same red chest contents as on their previous attempt.   

- ***Custom item pool***
  Previously, the multiworld item pool consisted entirely of random blue chest items because, well, the permanent checks are blue chests and that's what one would normally get from these. While blue chest items often greatly increase your odds against regular enemies, being able to defeat the Master can be contingent on having an appropriate equipment setup of red chest items (such as Dekar blade) or even enemy drops (such as Hidora rock), most of which cannot normally be obtained from blue chests.
  With the custom item pool option, players now have the freedom to place any cave item into the multiworld itempool for their world.

- ***Enemy floor number, enemy sprite, and enemy movement pattern randomization***
  Experienced players can deduce a lot of information about the opposition they will be facing, for example: Given the current floor number, one can know in advance which of the enemy types will have a chance to spawn on that floor. And when seeing a particular enemy sprite, one can already know which enemy types one might have to face in battle if one were to come in contact with it, and also how that enemy group will move through the dungeon.
  Three new randomization options are added for players who want to spice up their game: one can shuffle which enemy types appear on which floor, one can shuffle which sprite is used by which enemy type, and one can shuffle which movement pattern is used by which sprite.

- ***EXP modifier***
  Just a simple multiplier option to allow people to level up faster. (For technical reasons, the maximum amount of EXP that can be awarded for a single enemy is limited to 65535, but even with the maximum allowed modifier of 500% there are only 6 enemy types in the cave that can reach this cap.)


### Balance change

- ***proportionally adjust chest type distribution to accommodate increased blue chest chance***
  One of the main problems that became apparent in the current version has to do with the distribution of chest contents. The game considers 6 categories, namely: consumable (mostly non-restorative), consumable (restorative), blue chest item, spell, gear, and weapon. Since only blue chests count as multiworld locations, we want to have a mechanism to customize the blue chest chance.
  Given how the chest types are detetermined in game, a naive implementation of an increased blue chest chance causes only the consumable chance to be decreased in return. In practice, this has resulted in some players of worlds with a high blue chest chance struggling (more than usual) to keep their party alive because they were always low on comsumables that restore HP and MP.
  The new algorithm tries to avoid this one-sided effect by having an increase in blue chest chance resulting in a decrease of all other types, calculated in such a way that the relative distribution of the other 5 categories stays (approximately) the same.


### Bug fixes

- ***prevent using party member items if character is already in party***
  This should have been changed at the same time that 6eb00621e3 was made, but oh well... 

- ***fix glitched sprite when opening a chest immediately after receiving an item***
  When opening a chest right after receiving a multiworld item (such that there were two item get animations in the exact same iteration of the game main loop), the item from the chest would display an incorrect sprite in the wrong place. Fixed by cleaning up some relevant memory addresses after getting the multiworld item.

- ***fix death link***
  There was a condition in `deathlink_kill_player` that looked kinda smart (it checked the time against `last_death_link`), but actually wasn't smart at all because `deathlink_kill_player` is executed as an async task and the main thread will update `last_death_link` after creating the task, meaning that whether or not the incoming death link would actually be passed to the game seems to have been up to a race condition. Fixed by simply removing that check.


### Other

- ***add Lufia II Ancient Cave (and SMW) to the network diagram***
  These two games were missing from the SNES sector.

- ***implement get_filler_item_name***
  Place a restorative consumable instead of a completely random item. (Now the only known problem with item links in lufia2ac is... that noone has ever tested item links. But this should be an improvement at least. Anyway, now #1172 can come ;)
  And btw., if you think that the implementation of random selection in this method looks weird, that's because it is indeed weird. (It tries to recreate the algorithm that the game itself uses when it generates a replacement item for a chest that would contain a spell that the party already knows.)

- ***store all options in a dataclass***
  This is basically like using #993 (but without actual support from core). It makes the lufia2ac world code much nicer to maintain because one doesn't have to change 5 different places anymore when adding or renaming an option.

- ***remove master_hp.scale***
  I have to admit: `scale` was a mistake. Never have I seen a single option value cause so many user misconceptions. Some people assume it affects enemies other than the Master; some people assume it affects stats other than HP; and many people will just assume it is a magic option that will somehow counterbalance whatever settings combination they are currently trying to shoot themselves in the foot with.
  On top of that, the `scale` mechanism probably doesn't provide a good user experience even when used for its intended purpose (since having reached floor XY in general doesn't mean you will have the power to deplete XY% of the Masters usual HP; especially given that, due to the randomness of loot, you are never guaranteed to be able to defeat the vanilla Master even when you have cleared 100% of the floors).
  The intended target audience of the `master_hp` option are people who want to fight the Master (and know how to fight it), but also want to lessen (to a degree of their choosing) the harsh dependence on the specific equipment setups that are usually required to win this fight even when having done all 99 floors. They can achieve this by setting the `master_hp` option to a numeric value appropriate for the level of challenge they are seeking. Therefore, nothing of value should be lost by removing the special `scale` value from the `master_hp` option, while at the same time a major source of user confusion will be eliminated.

- ***typing***
  This (combined with the switch to the option dataclass) greatly reduces the typing problems in the lufia2ac world. The remaining typing errors mostly fall into 4 categories:
  1. Lambdas with defaults (which seem to be incorrectly reported as an error due to a mypy bug)
  1. Classmethods that return instances (which could probably be improved using PEP 673 "Self" types, but that would require Python 3.11 as the minimum supported version)
  1. Everything that inherits from TextChoice (which is a typing mess in core)
  1. Everything related to asar.py (which does not have proper typing and lies outside of this project)

## How was this tested?

https://discord.com/channels/731205301247803413/1080852357442707476 and others
2023-03-20 17:04:57 +01:00
Zach Parks
ff9f563d4a Deprecate data_version and introduce checksum for DataPackages. (#684)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-20 17:01:08 +01:00
Fabian Dill
d825576f12 Factorio: content update
Energy Link:
  * Transfer and Storage increased by 10X
  * Cost of building increased by roughly 10X
  * This is a way to address their effect on performance, as users now need a tenth of them to get the same throughput, it also differentiates them from Accumulators
5 new Traps:
  * Teleport Trap
  * Grenade Trap
  * Cluster Grenade Trap
  * Artillery Trap
  * Atomic Rocket Trap
When max science is lower than min science, the two are now swapped.
Max Evolution Trap count was changed from 25 -> 10.
New option: Ingredients Offset
  * When creating random recipes, use this many more or less ingredients in the new recipe.
2023-03-15 21:38:32 +01:00
Alchav
5d6184f1fd STS: update slot_seeds to per_slot_randoms 2023-03-15 09:10:35 +01:00
alwaysintreble
e433246f0c The Messenger: docs improvement (#1545)
* The Messenger: docs improvement

* more wordy mod link

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* indent

* revert accidental indent

oop

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-14 21:18:55 +01:00
black-sliver
3a190a8fb2 CI: more filters, update CodeQL (#1540)
* CI: fix and more greedy filtering

* CI: only run lint if *.py changed

* CI: only run CodeQL if supported file changed

* CI: fix unittests still triggering for build.yml

* CI: update CodeQL action

* CI: trigger codeql when changing the workflow
2023-03-14 19:29:20 +01:00
Alchav
4b7033fce7 Pokemon R/B: Version 3 final touches (#1542)
* Pokémon R/B: Dexsanity balls

* Pokémon R/B: Early Parcel improvement

* Pokémon R/B: Early Parcel dexsanity stuff only when dexsanity
2023-03-14 18:36:17 +01:00
lordlou
37499b40a1 SMZ3: shop check fix 2 (#1538) 2023-03-14 18:31:51 +01:00
black-sliver
ca2c0e6ce2 CI: update stuff (#1534)
* CI: skip SNI, skip unittests if not needed, run build for setup.py

* CI: update actions

* CI: update upload-artifact

Fixes more warnings
2023-03-14 01:32:00 +01:00
black-sliver
2a28a6de28 Pokemon: apply rename of location_item_name 2023-03-14 01:30:58 +01:00
alwaysintreble
573a1a8402 Core: Add a function to allow worlds to easily allow self-locking items (#1383)
* implement function to allow self locking items for items accessibility

* swap some lttp locations to use new functionality

* lambda capture `item_name` and `location`

* don't lambda capture location

* Revert weird visual indent

* make location.always_allow additive

* fix always_allow rule for multiple items

* don't need to lambda capture item_names

* oop

* move player assignment to the beginning

* always_allow should only be for that player so prevent non_local_items

* messenger got merged so have it use this

* Core: fix doc string indentation for allow_self_locking_items

* Core: fix doc string indentation for allow_self_locking_items, number two

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-14 00:55:34 +01:00
Jarno
060ee926e7 Core: type specified missing per_game_common_options (#1509) 2023-03-13 23:45:56 +01:00
Alchav
df55455fc0 Pokémon R/B: Version 3 (#1520)
* Coin items received or found in the Game Corner are now shuffled, locations require Coin Case
* Prizesanity option (shuffle Game Corner Prizes)
* DexSanity option: location checks for marking Pokémon as caught in your Pokédex. Also an option to set all Pokémon in your Pokédex as seen from the start, to aid in locating them.
* Option to randomize the layout of the Rock Tunnel.
* Area 1-to-1 mapping: When one instance of a Wild Pokémon in a given area is randomized, all instances of that Pokémon will be the same. So that if a route had 3 different Pokémon before, it will have 3 after randomization.
* Option to randomize the moves taught by TMs.
* Exact controls for TM/HM compatibility chances.
* Option to randomize Pokémon's pallets or set them based on primary type.
* Added Cinnabar Gym trainers to Trainersanity and randomized the quiz questions and answers. Getting a correct answer will flag the trainer as defeated so that you can obtain the Trainersanity check without defeating the trainer if you answer correctly.
2023-03-13 23:40:55 +01:00
Fabian Dill
4d7bd929bc WebHost: update modules 2023-03-13 21:34:24 +01:00
Fabian Dill
030e41363a Setup: update cx-Freeze 2023-03-13 21:34:07 +01:00
Qwazzy
4bc0e84a7f Docs: SM64 Guide update to explain how to launch the game with batch files (#768)
* Update setup_en.md

Added several sections in regards to opening the completed SM64 build with batch files instead of SM64PCLauncher.

* Update setup_en.md

* Update setup_en.md

* Update setup_en.md

* Update setup_en.md

* Apply suggestions from code review

Co-authored-by: Yussur Mustafa Oraji <N00byKing@hotmail.de>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* Apply more suggestions from code review

matches the original suggestion from SoldierofOrder

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Yussur Mustafa Oraji <N00byKing@hotmail.de>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
2023-03-13 00:58:17 +01:00
alwaysintreble
070a92e76c The Messenger: implement new game (#1494)
* initial commit of messenger integration

* setup no_logic and needed slot_data

* fix some typos and determinism

* make all of it deterministic

* add documentation

* swapped to non local items so change the fed data

* ~~deathlink~~

* satisfy the docs test

* update doc test to show expected name

* split custom classes into a separate file and fix an errant rule

* make access dependency test give more useful errors

* implement tests

* remove some unneccessary back entrances and make names clearer

* fix some big dumbs

* successful unit tests are good also some slight reorganizing

* add astral tea quest line, and potentially power seals as items

* if TYPE_CHECKING... aahhhhhh

* oop forgot to remove legacy code

* having the seed and leaves as actual items doesn't seem to do anything so remove them. locations still work though

* update setup guide with some changes

* Tower HQ was creating duplicate locations

* allow self locking items

* cleanup

* move self_locking_items function to core

* docstring

* implement choice of notes needed for music box

* test the default value

* don't create any starting inventory items

* make item creation faster

* change default accessibility and power seals options

* improve documentation

* precollected_items is a dict of Items...

* implement shop chest goal

* tests

* always assign total and required seals

* add new goals and set music box as requiring shop chest on shop chest goals instead of just setting it as the completion

* fix dumb test quirk

* implement music box skip as an option

* world rewrite/cleanup

* default to apworld and add game to readme

* revert bleeding commits from other PRs

* more bleeds

* fix some errors in options docstrings

* ???

* make my set rules method not have an awful name

* test cleanup

* add a test for item accessibility

* fix issues with tests

* make the self locking item behavior work correctly

* misc cleanup

* more general cleanup to be a good example

* quick rules rewrite

* more general cleanup and typing

* more speed, more clean

* bump data version

* make sure the locked item belongs to current player

* fix bad name and indent. call MessengerItem directly for events

* add poptracker pack to docs

* doc cleanup and "known issues" section that I probably won't be able to fix any time soon.

* missed some spots

* add another bug i forgot about

* be consistently wrong
2023-03-12 15:05:50 +01:00
Fabian Dill
39563cc347 WebHost: add game and Factorio to multitracker (#1526)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-12 12:38:13 +01:00
alwaysintreble
54cce4c392 LTTP: fix ice rod hunt boss shuffle (#1529) 2023-03-12 11:03:48 +01:00
el-u
426a81a065 oot/alttp: fix bugs found through MMBN3 testing (#1527) 2023-03-11 20:15:30 +01:00
alwaysintreble
04e6a8eae8 FFR: add option __doc__s 2023-03-11 13:38:27 +01:00
NewSoupVi
0cfdc973f6 The Witness - Expert logic bug (could lead to broken seeds) (#1525) 2023-03-11 10:09:09 +01:00
alwaysintreble
f3ca0a21c9 Docs: Add an option api doc (#1181)
* write up an option api doc

* address reviews

* some clarification

* add note about using schema

* Add ItemSet and formatting

* bulletpoint option defining

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* split random description to new sentence

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* use inclusive and parallel language for example

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* changes from review

* commas

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* capitalize Toggle

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* the sliver conventions

---------

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
2023-03-11 01:14:44 +01:00
Chris Wilson
5fef41eb97 [WebHost] Make site header mobile-friendly (#1523) 2023-03-10 17:21:17 -05:00
alwaysintreble
4068ba2f15 LTTP: add option __doc__s (#1521)
* LTTP: add option `__doc__`s

* review comments
2023-03-10 07:59:47 +01:00
NewSoupVi
b1599c557f The Witness: Death Link + Small bug fixes (#1515)
* Fully functional DeathLink implementation. But it's always on right now :D

* Death Link options. Last commit: All entity names being sent through slot_data

* Tutorial Gate Close logic fix

* Improved option tooltip wording

* Fixed shuffle_postgame: false not excluding some locations

* Link to latest stable client rather than full releases page
2023-03-10 07:58:00 +01:00
Fabian Dill
7fdf38b2ad WebHost: automatically fill PATCH_TARGET -> HOST_ADDRESS and re-use it for rooms (#1518) 2023-03-09 21:31:00 +01:00
Freya Arbjerg
2e76085cf1 WebHost: Fix generic tracker Datatables (#1519) 2023-03-09 19:24:38 +01:00
Fabian Dill
c61f467218 WebHost: fix location_name_group related spinup crash 2023-03-09 12:31:35 +01:00
KonoTyran
942d689093 [Slay the Spire] Enable support for modded characters, and add downfall support (#1368)
* add ability to choose custom characters in STS

* bump required protocol (client?) version.

* fix slot data fill.

* add downfall mode, as well as characters.

* small change in documentation for character choice as it now uses internal ID's instead of visible titles... because other languages are a thing.
2023-03-08 20:14:54 -08:00
espeon65536
5e1aa52373 Minecraft rewrite (#1493)
* Minecraft: rewrite to modern AP standards

* Fix gitignore to not try to ignore the entire minecraft world

* minecraft: clean up MC-specific tests

* minecraft: use pkgutil instead of open

* minecraft: ship as apworld

* mc: update region to new api

* Increase upper limit on advancement and egg shard goals

* mc: reduce egg shard count by 5 for structure compasses

* Minecraft: add more tests
Ensures data loading works; tests beatability with various options at their max setting; new tests for 1.19 advancements

* test improvements

* mc: typing and imports cleanup

* parens

* mc: condense filler item code and override get_filler_item_name
2023-03-08 20:13:52 -08:00
Freya Arbjerg
a95e51deda Add generic multiworld tracker, move lttp multiworld tracker (#1478)
Co-authored-by: Berserker
2023-03-08 22:39:15 +01:00
alwaysintreble
738319462d Spoiler: Don't double print if world overrides common options (#1505) 2023-03-08 22:19:38 +01:00
alwaysintreble
e3deb822ad Core: implement location_name_groups (#1502) 2023-03-08 22:15:28 +01:00
Jarno
d57314a407 Timespinner: Bring back starter progression item (#1508) 2023-03-08 20:05:30 +01:00
t3hf1gm3nt
5a8e6e61f5 TLOZ: Code Cleanup (#1514)
- consolidated declaration and population of level location lists
- moved floor_location_game_ids_late declaration for consistency
- moved generate_itempool to create_items, where it belongs
- mention that expanded pool includes take any caves in the option description again
- removed unnecessary StartingPosition check regarding Take Any Caves (leftover from older StartingPosition behavior I believe)
- use proper comparisons to option keys instead of hardcoded ints
2023-03-08 11:22:14 +01:00
Magnemania
17e90ce12c SC2: Greater variety on short generations (#1367)
Originally, short generations used an artificial cull to create balanced mission distributions. This resulted in campaigns that were somewhat too consistent, and on some standard settings combinations, this resulted in campaigns having The Outlaws as the second mission 100% of the time. It also caused generation to fail a bit too easily if the player excluded too many missions.

This removes the cull and adds an additional early Easy mission slot to all of the reduced sized campaigns.

When playing on No Build settings, this also pushes many of the missions down a difficulty level to ensure greater variety, and pushes additional missions down on Advanced Tactics.

Additional small fixes:

The in-world Excluded Missions validation check is replaced by the core OptionSet check.
Fixed issue with Existing Items not getting their upgrades locked with Units Always Have Upgrades on.
2023-03-07 14:14:49 +01:00
NewSoupVi
016157a0eb Witness: Fixed settings combination not rolling (see description)
Settings combination:
- EP Shuffle
- disable_non_randomized
- doors: panels or doors: none

An Event Item was being created that is inaccessible. This is fixed now.
(The fix makes sure that player_logic is not trying to create events for the sake of EPs that are disabled)

Note: These two sets should probably be merged anyway, they used to behave differenty but no longer really do. But that will require some extra care on the client side as well.
2023-03-07 09:13:54 +01:00
Fabian Dill
5b64c5f934 Subnautica: fix exported radiation logic (#1507) 2023-03-07 09:09:24 +01:00
alwaysintreble
414166f6a2 Core: Minor Options cleanup (#1182)
* Options.py cleanup

* TextChoice cleanup

* make Option.current_option_name a property

* title the TextChoce option names

* satisfy the linter

* a little more options cleanup

* move the typing import

* typing should be PlandoSettings

* fix incorrect conflict merging

* make imports local

* the tests seem to want me to import these twice though i hate it.

* changes from review. Make the various Location verifying Options `LocationSet`

* remove unnecessary fluff

* begrudgingly support get_current_option_name. Leave a comment that worlds shouldn't be touching this

* log a deprecation warning and return the property for `get_current_option_name()`

---------

Co-authored-by: beauxq <beauxq@yahoo.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-07 08:44:20 +01:00
beauxq
e6109394ad Zillion: use Option.current_key
and other minor fixes
2023-03-07 08:33:33 +01:00
Fabian Dill
8ca25fed63 Setup: clean up imports 2023-03-06 12:54:32 +01:00
0rganics
227d59ecfb WebHost: Add a ChecksFinder tracker (#1333)
Co-authored-by: Chris Wilson <chris@legendserver.info>
2023-03-05 14:17:04 +01:00
Fabian Dill
08c17c83d4 Setup: auto download SNI (#1312)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-05 14:10:05 +01:00
Rosalie-A
efb2ab4505 TLoZ: Implementing The Legend of Zelda (#1354)
Co-authored-by: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com>
Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com>
2023-03-05 13:31:31 +01:00
Joethepic
3a68ce3faa PKMN: Make Exp All early (#1422) 2023-03-05 10:08:32 +01:00
black-sliver
e78800d1bc Item Plando: make world selection deterministic 2023-03-05 02:16:55 +01:00
Jérémie Bolduc
96d7a3a64c Stardew Valley: Fix generation issue with Master Angler goal and vanilla tools (#1498)
* - Can Catch every fish doesn't need fishing rods if they are not shuffled

* add has_max_fishing_rod

* add test for master angler + vanilla tools

---------

Co-authored-by: Alex Gilbert <alexgilbert@yahoo.com>
2023-03-04 18:34:51 +01:00
recklesscoder
30b70b2055 Misc collected fixes (#1497) 2023-03-04 16:34:10 +01:00
Jarno
cd234fc04a Timespinner: Fixed Dry lake serene oddity (#1501) 2023-03-04 16:31:44 +01:00
zig-for
d74c4c4c94 Core: Remove ALTTP cruft from BaseClasses (#1451) 2023-03-04 08:23:52 +01:00
Jarno
a4b61118cf Timespinner: Refactorings + fix for #1460 (#1484) 2023-03-04 08:16:05 +01:00
FlySniper
9fa1f4e85f Wargroove: Fixed Wargroove Client not removing communication files (#1492) 2023-03-03 18:24:09 +01:00
black-sliver
3a926849a0 CI: run unittests on macos
this is to ensure dependencies can be installed and loaded on macos (on AMD64)
2023-03-03 18:22:31 +01:00
Alchav
798d823397 Core: Check for show_in_spoiler (#1500) 2023-03-03 17:22:46 +01:00
Fabian Dill
4ea582f14e Windows: allow arm64 setup (#1496) 2023-03-03 09:51:36 +01:00
alwaysintreble
21fb16291d Tests: test that the game is beatable for WorldTestBases (#1495)
* Tests: test that the game is beatable for WorldTestBases

* update docstring

* don't test the bases with default options for real this time

* invert the property so worlds can use it easier

* setup check should be or

* test class needs to always be constructed

* skip default tests before multiworld setup

* check if the calling method is in the base's __dict__

* shorter property and functional setup skipping

* shorter property and functional setup skipping
2023-03-03 00:30:40 +01:00
NewSoupVi
805f33c39e Witness: Bugfixes in response to beta tests (#1473)
* Make all Keep Pressure Plates logically required for the Laser Panel

* Added more Tutorial checks

* Added the remaining two Shipwreck Boat EPs to the exclude list for normal

* Improved itempool filling system, added warning if usefuls had to be eaten

* Moved creation of said warning string to utils

* Fixed logic bug causing broken seeds on Mountain Floor 2

* Hints system change

* Expert Logic Fix

* Fixed typo

* Better wording

* Added missing games to junk hints

* Made sure Entrance names are unique

* Fixed missing Obelisk Side

* Disable Non Randomized + EP Shuffle fix

* Fixed disable_non_randomized precompleted EPs being 'disabled' instead of 'precompleted'

* Fixed if/elif error

* Tutorial Gate Open local symbol item becomes local_early_item in expert instead

* Bump required client version. There is a beta client that sends 0.3.9.

* Removed print statement, oops

* Fixed itempool manipulation in pre_fill

* Replaced string concats with fstrings

* Improved make_warning_string function signature

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Improved performance on removing multiple items from multiworld itempool

* Comment

* Fixed errors with the code

* Made removal from itempool not fail unit test for multiple references

* Moved all item creation to create_items, got rid of itempool modifying system

* Colored Squares is no longer a good item, that's outdated

* Removed double if

* React to from_pool: false by removing a junk item

* Fixed warning if only Fnc Brain was removed

* Make use of string truthiness instead

* Made reading of plandoed items safer

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-03-03 00:08:24 +01:00
el-u
0cf8206660 launcher: add .apl2ac support 2023-03-01 22:56:14 +01:00
Fabian Dill
2c20b56478 Core: count the world types 2023-03-01 05:47:46 +01:00
FlySniper
1d2f7d8669 Wargroove: Fixed the find all dogs check activating prematurely (#1486) 2023-02-28 16:26:48 +01:00
Fabian Dill
0733775f2c Subnautica: Allow either utility room for progression 2023-02-28 11:22:47 +01:00
black-sliver
d6f3b27695 DKC3, SMW: use user_path for file
Same as for other games, this will resolve to ~/Archipelago on Linux, if the install folder is read-only
2023-02-28 09:51:32 +01:00
black-sliver
ce7e6bcf33 Readme: fix order 2023-02-28 09:15:23 +01:00
Jarno
2c4658a7e0 Docs: More games more fun 2023-02-27 23:19:33 +01:00
TheLynk
79b8733b13 OoT, MC: add new translation setup in french (#1410)
* add new translation

* Add translation for OOT Setup in french

* Update setup_fr.md

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>

* Update setup_fr.md

Fix treu to true

* Update worlds/oot/docs/setup_fr.md

Co-authored-by: Marech <marechal-l@gmx.com>

* Update OOT Init and Update Minecraft Init

* Fix formatting errors

---------

Co-authored-by: Ludovic Marechal <marechal-l@gmx.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-02-27 23:17:54 +01:00
alwaysintreble
9cb9cbe47d Tests: test that worlds don't create regions or locations after create_items (#1465)
* Tests: test that worlds don't create regions or locations after `create_items`

* recache during the location counts just to be extra safe

* adjust typing and use a Tuple instead of a list

* remove unused import
2023-02-27 02:13:24 +01:00
alwaysintreble
7cad53c31a Docs: add docstrings to the World class 2023-02-27 01:39:30 +01:00
alwaysintreble
f3bdf0c5ed Tests: test all state and empty state on world test bases (#1476)
* Tests: test all state and empty state on world test bases

* actually add the test methods to the dict

* only test if the world test base has non default options

* remove temp logging

* ditch the meta class and document methods

* Tests: WorldTestBase comment and docstring cleanup

* skip default tests if setUp or world_setup are modified and use a property

* negation hurts my head

* docstring

* use a better name for the property

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-02-27 01:24:54 +01:00
Jérémie Bolduc
af7d0dbf37 Stardew Valley: implement new game (#1455)
* Stardew Valley Archipelago implementation

* fix breaking changes

* - Added and Updated Documentation for the game

* Removed fun

* Remove entire idea of step, due to possible inconsistency with the main AP core

* Commented out the desired steps, fix renaming after rebase

* Fixed wording

* tests now passes on 3.8

* run flake8

* remove dependency so apworld work again

* remove dependency for real

* - Fix Formatting in the Game Page
- Removed disabled Option Descriptions for Entrance Randomizer
- Improved Game Page's description of the Arcade Machine buffs
- Trimmed down the text on the Options page for Arcade Machines, so that it is smaller

* - Removed blankspace

* remove player field

* remove None check in options

* document the scripts

* fix pytest warning

* use importlib.resources.files

* fix

* add version requirement to importlib_resources

* remove __init__.py from data folder

* increment data version

* let the __init__.py for 3.9

* use sorted() instead of list()

* replace frozenset from fish_data with tuples

* remove dependency on pytest

* - Add a bit of text to the guide to tell them about how to redeem some received items

* - Added a comment about which mod version to use

* change single quotes for double quotes

* Minimum client version both ways

* Changed version number to be more specific. The mod will handle deciding

---------

Co-authored-by: Alex Gilbert <alexgilbert@yahoo.com>
2023-02-27 01:19:15 +01:00
Fabian Dill
0286edf20c Subnautica: fix the test that I didn't mean to push yet 2023-02-26 09:51:02 +01:00
Fabian Dill
05e36cab1c Subnautica: increment version 2023-02-26 09:48:51 +01:00
Klenoa
50425985c4 Subnautica: Rename location check (id 33029) (#1120)
Update southwest grassy plateaus wreck to 'Grassy Plateaus Southwest Wreck - Databox' instead of 'Grassy Plateaus West Wreck - Databox'
2023-02-26 09:48:08 +01:00
Marech
062d6eeace DS3: Added DLC Items/Locations + corresponding option and added an option to enable materials/consumables/estus randomization (#1301)
- Added more progressive locations and associated items.
- Added an option to enable materials/consumables/estus randomization, some players complain about the number of locations and the randomness of those items.
- Added an option to add DLC Items and Locations to the pool, the player must own both the ASHES OF ARIANDEL and the RINGED CITY DLC.

Co-authored-by: Br00ty <83629348+Br00ty@users.noreply.github.com>
Co-authored-by: Friðberg Reynir Traustason <fridberg.traustason@gmail.com>
2023-02-26 06:35:03 +01:00
alwaysintreble
6c460bcbf7 LTTP: Move LTTP spoiler writing out of core (#1467) 2023-02-25 04:02:51 +01:00
toasterparty
b8659d28cc [OC2] DeathLink (#1470) 2023-02-24 08:32:15 +01:00
alwaysintreble
0b12d80008 Tracker: get game names from slot_info instead of multidata["games"] and render custom game names on generic tracker (#1453) 2023-02-24 08:30:11 +01:00
FlySniper
5966aa5327 Wargroove: Implement New Game (#1401)
This adds Wargroove to the list of supported games. Wargroove uses a custom non-linear campaign over the vanilla and double trouble campaigns. A Wargroove client has been added which does a lot of heavy lifting for the Wargroove implementation and must be always on during gameplay. The mod source files can be found here: https://github.com/FlySniper/WargrooveArchipelagoMod
2023-02-24 07:35:09 +01:00
Trevor L
7c68e91d4a Blasphemous: Implement new game (#1446)
Adds @BrandenEK's Blasphemous Randomizer as a new Archipelago game.
2023-02-24 07:33:09 +01:00
alwaysintreble
1d6ab13015 ArchipIDLE: add a completion condition instead of hard coding tests around a game (#1444) 2023-02-23 21:16:10 -05:00
CaitSith2
cb3d40624c Timespinner: Make RisingTidesOverrides consistent with normal yaml behaviour. (#1474)
* Make RisingTidesOverrides consistent with normal yaml behaviour.

* Each of the options can be either string directly specifying the option, or dictionary.
* If dictionary, ensure that at least one of the options is greater than zero.

* Made keys optional

* A lot less copy/pasta.

---------

Co-authored-by: Jarno Westhof <jarnowesthof@gmail.com>
2023-02-22 17:11:27 -08:00
alwaysintreble
0eb66957b1 SMW: change random in generate_output to use slot random 2023-02-20 18:43:23 +01:00
alwaysintreble
53e2232f29 Docs: document world docs and tests (#1463)
* Docs: document world docs and tests

* regions and items shouldn't be created after `create_items`

* Changes from review

* Restructure game info section

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* w

* urls can have extension probably

* reorder the methods by call order

* fix grammar mistake in ordered method list

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-02-19 23:16:56 +01:00
Fabian Dill
ecd2675ea8 Tests: check that Regions are reachable (#1034)
* Tests: check that Regions are reachable
try to prevent errors from unconnected/never reachable Regions

* Test region access (#1039)

* Tests: note oot's default unreachable regions

* [SM] Fixed failing testAllStateCanReachEverything (#1087)

* [SM] Fixed failing testAllStateCanReachEverything

- by adding exclusion for Regions used only when corresponding Starting Location is used
- by removing unnecessary VARIA Regions used only for EscapeRando (not supported in AP anyway)

* Update worlds/sm/Regions.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update worlds/sm/Rules.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update worlds/sm/Regions.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update test/general/TestReachability.py

---------

Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com>
Co-authored-by: lordlou <87331798+lordlou@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-02-19 23:09:54 +01:00
Jarno
fc2e555b4a Timespinner: many new stuffs (#1433)
* Timespinner: added RisingTides and DadPercent flags

* Implemented logic for DadPercent and RisingTides

* Fixed TODO's

* Logic fixes

* Fixed + removed LogicMixins

* Fixes

* More Fixes

* Added UnchainedKeys flag

* Fixed available items in pool with UnchainedKeys

* Fixed typing callable

* Fixed generation failures

* More refactorings

* Implemented traps

* Fixed more typo

* Fixed copy paste bug

* Fixed teleporter logic

* Fixed traps from pool

* Fixed pyramid gates bug that causes a crash on connecting

* Fixed seed reproduceability

* Fixed logic eye for eye spy
Now consider warp beacons as starter progression items

* Attempt to add tracker icons using table

* Replaced table layout with css grid

* Fixed tracker + added Timespinner was apworld capatible

* Updated archipelago items description

* updated URL

* Cleared up text

* Fixed based on self review of PR

* Fixed unit tests

* Fixed seed reproduceability when the traps yaml option is not provided

* Fixed logic for flooded basement

* Implemented Beserkers review result

I am not sure why, i guess this is just to make adding future games less conflicting?

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>

* Added two new options (thanks to WeffJebster)

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Addition review results

---------

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-02-19 21:22:30 +01:00
black-sliver
df020bb389 Style Guide: add world consistency 2023-02-19 19:34:45 +01:00
PoryGone
7760034ff7 DKC3 and SMW: Remove relative imports (#1472) 2023-02-19 09:10:32 +01:00
black-sliver
3e7794d5dc SoE: update evermizer to 044
* Fix energy core despawning when looting failed
* Fix fish guy dialog when cure was already obtained
see https://github.com/black-sliver/pyevermizer/releases/tag/v0.44.0 for more details
2023-02-18 15:18:51 +01:00
black-sliver
a3e8bb474a ModuleUpdater: allow new syntax, nicer output 2023-02-18 15:18:51 +01:00
kindasneaki
e4c95c940a RoR2: regions unreachable fix (#1459) 2023-02-17 22:08:18 +01:00
recklesscoder
daa1809a0f WebHost: Tweaks to search on tracker pages (#1307)
* WebHost: Tweaks to search on tracker pages
- Pressing `Ctrl+F` or `/` now focuses the search box.
- Typing now automatically focuses the search box.
- Pressing `Escape` now clears the search and scrolls to the top.

* WebHost/Trackers: Focus search box on load

* WebHost/Trackers: Remove overriding of Ctrl+F and /
2023-02-17 13:24:21 -05:00
Jarno
0a1261eb84 WebHost: Add tutorials to sitemap and hide settings link for games without settings (#1452)
* WebHost: Add tutorials to sitemap and hide settings link for games without settings

* Fixed some typing imports
2023-02-17 13:16:37 -05:00
toasterparty
b62be6f7f4 [OC2] Colored Ramp Button Items (#1466)
Before: 1 item activates all 4
After: 7 items activate 7 buttons, creating more divergent routes

Also, I consolidated the 6 filler emotes into a single "Emote Wheel" item to make space in the item pool.

I bumped my data version and min AP version to indicate this change.

The corresponding oc2-modding update is **v1.6.0**
2023-02-17 09:25:56 +01:00
toasterparty
ce2553a2b3 [OC2] Location Balancing (#1458) 2023-02-17 09:21:56 +01:00
Fabian Dill
18c4b4b1fe Subnautica: move code to be a better example 2023-02-17 08:50:22 +01:00
Fabian Dill
a85ca9cc87 Subnautica: add logic dumper for mod (#1386)
* Subnautica: add logic dumper for mod

* Subnautica: export more data

* Subnautica: fix some Cyclops logic
2023-02-16 00:40:19 +01:00
el-u
ad4846cedd core: clarify usage of classmethods in World class (#1449) 2023-02-16 00:28:02 +01:00
recklesscoder
b20be3ccec Docs/Factorio: Document EnergyLink (#1456)
* Docs/Factorio: Document EnergyLink

* Docs/Factorio: EnergyLink clarification
2023-02-16 00:25:46 +01:00
alwaysintreble
8af7908cd0 Tests: datapackage and more multiworld renaming (#1454)
* Tests: add a test that created items and locations exist in the datapackage

* move FF validation to `assert_generate` and remove test exclusion

* test created location addresses are correct

* make the assertion proper and more verbose

* make item count test ~~a bit faster~~ a lot nicer

* 120 blaze it

* name test multiworld setup better and fix another over 120 line in FFR
2023-02-15 22:46:10 +01:00
alwaysintreble
f078750b72 LTTP: make the enemizer check a property and only check for it once instead of per world (#1448)
* LTTP: do the enemizer check in `stage_assert_generate` and break after checking any world for enemizer succeeds

* use multiworld

* catch a missed `used_enemizer` check and add typing

* more typing
2023-02-14 22:22:39 +01:00
alwaysintreble
7cbeb8438b core: rip out RegionType and rework Region class (#814) 2023-02-14 01:06:43 +01:00
Fabian Dill
f7a0542898 kvui: limit UI side logs to by default 1000 messages 2023-02-13 09:02:19 +01:00
recklesscoder
cc61f16e57 Protocol: Improve machine-readability of prints (#1388)
* Protocol: Improve machine-readability of prints

* Factorio: Make use of new PrintJSON fields for echo detection.

* Protocol: Add message field to chat prints.
2023-02-13 03:17:25 +01:00
alwaysintreble
9e3c2e2464 Tests: test that exits to Regions are the parents of the Entrance (#1442) 2023-02-13 02:05:52 +01:00
Fabian Dill
f528175d8a Core: prepare server for removal of names in multidata (#1430) 2023-02-13 01:56:20 +01:00
Fabian Dill
803d7105a1 kivy: allow user-defined text colors in data/client.kv (#1429) 2023-02-13 01:55:43 +01:00
el-u
a40f6058b5 lufia2ac: fix mismatched exits/parent region 2023-02-13 00:46:46 +01:00
toasterparty
0ff3c693d5 [OC2] Relax Horde Logic for Horde H-8 and Winter H-4 (#1439) 2023-02-10 21:42:28 +01:00
Fabian Dill
873a374a69 SNIclient: connect fixes (#1436) 2023-02-07 10:16:39 +01:00
black-sliver
60584b7617 CI: more pip to fix the build 2023-02-07 10:10:27 +01:00
black-sliver
e24a85ca5c CI: update SNI to v0.0.88 2023-02-07 10:10:27 +01:00
lordlou
cc0540d3fb SMZ3: keysanity accessibility fix (#1428) 2023-02-07 03:14:03 +01:00
NewSoupVi
c360b9266c Witness: Renaming: Mill -> Stoneworks, correcting order for 'First, Second, ...', removed all instances of 'Door (Door)' (#1435) 2023-02-07 03:12:47 +01:00
beauxq
6148213e43 Zillion: fix name data overflow 2023-02-07 03:11:48 +01:00
Jarno
ff175008a1 Core: Phase out Print packets (#1364) 2023-02-05 22:06:38 +01:00
kindasneaki
cae1e683e2 RoR2: 1.20 content update (#1396)
## Adding in Explore Mode:

Features include:
* Added in `environments` to be items.
* `Location checks` are now `environment based` instead of being able to get them from anywhere.
* Added in support for the `DLC Survivors of the void` which include `Void Items` and `3 new maps` that come with it. (option added to use DLC)

---------

Co-authored-by: Dogpetkid <dogpetkid@gmail.com>
2023-02-05 21:51:03 +01:00
vgZerst
fb1a9e9c5a WebHost: add checks percent done column to tracker (#1376)
* WebHost: add checks percent done column to tracker

* WebHost: add checks percent done column to tracker
2023-02-04 00:04:00 -05:00
CaitSith2
555a0da46d Core: Rename the missed slot_seeds. (#1432)
* Rename the missed slot_seeds.

* Fixed a threaded context random error.
2023-02-03 19:39:18 +01:00
NewSoupVi
0817305d5b Witness: Added an option tooltip for "Environmental Puzzles Difficulty" option (+ another bugfix) (#1431)
* Added an option tooltip

* Fixed eclipse being on in EP difficulty normal
2023-02-03 19:38:54 +01:00
Fabian Dill
995c978628 Core: replace global random state with descriptive error (#1424)
* Core: replace global random state with descriptive error

* Core: make random a proxy object and rename slot_seeds
2023-02-02 01:14:23 +01:00
NewSoupVi
4de7ebd8b0 The Witness: v4 Content Update (#1338)
## New Features:

- EP Shuffle (Individual or Obelisk Sides, with varying difficulty levels)
- Ability to play without Puzzle Randomization (I.e. vanilla + AP layer)
- Pet the Dog to get a Puzzle Skip :) (No, really.)

## Changes:

- Starting inventory behavior improved (Consider starting items like doors and lasers logically even if they aren't part of the mode)
- Audio Log hint system improved (On low hint counts, you will no longer get the same locations hinted every time, i.e. always hints are shuffled)

## Fixes:

- Many fixes to symbol requirements
- Fixes to "shuffle_postgame" (What checks are evaluated as "postgame" in specific modes)
- Logically irrelevant doors are now "useful" instead of "progression"
2023-02-01 21:18:07 +01:00
PoryGone
3cef39a387 SMW & DKC3: Ship as .apworld (#1426) 2023-02-01 21:15:01 +01:00
el-u
ffff9ece55 core: properly declare from_any as an abstract classmethod 2023-02-01 21:14:11 +01:00
PoryGone
dc2aa5f41e SMW: v1.1 Content Update (#1344)
* Make Bowser unkillable on Egg Hunt

* Increment Data Package version

Changed a location name.

* Baseline for Bowser Rooms shuffling

* Add boss shuffle

* Remove extra space

* Overworld Palette Shuffle

* Fix Literature Trap typo

* Handle Queuing traps and new Timer Trap

* Fix trap name and actually create them

* Early Climb and Overworld Speed

* Add correct tooltip for Early Climb

* Tooltip text edit

* Address unconnected regions

* Add option to fully exclude Special Zone levels from the seed

* Fix Chocolate Island 4 Dragon Coins logic

* Update worlds/smw/Client.py to use `getattr`
2023-01-30 05:53:56 +01:00
black-sliver
428344b6bc setup: honor build automation ...
... and reorder imports to PEP it up
2023-01-30 02:33:41 +01:00
Fabian Dill
ea2175cb8a MultiServer: load old forfeit_mode if release_mode not present 2023-01-30 00:54:57 +01:00
Fabian Dill
11873e059a Setup: don't use dependency before it's installed 2023-01-29 22:36:04 +01:00
Fabian Dill
6c1023a88c Subnautica: fix swim_rule considers items property use (#1419) 2023-01-29 22:12:39 +01:00
The T
0be0732a2b WebHost: FAQ: change "seeds" to "a world" where world is the right term.
Berserker has frequently corrected, that each player's game is a single world, inside a larger seed; not that each player's game is a seed.
2023-01-29 22:11:53 +01:00
lordlou
c9aa283711 SMZ3: chest game fix (#1417)
Fixed DW Chest Game always sending checks for the 2 chests. The checks sent were the proper "Chest Game" location for the first time the player would open the second chest but all other times, it would send either the last check that was done or default to sending location 0x00 which is SM "Power Bomb (Crateria surface)".
2023-01-28 01:51:19 +01:00
Fabian Dill
cf2204a861 Factorio: add option "Tech Cost Distribution" (#1404)
* Factorio: add option "Tech Cost Distribution"

* TextClient: None out game on disconnect

* TextClient: disconnect is async
2023-01-27 15:38:12 -08:00
Fabian Dill
dfdcad28e5 TextcVient: game reset (#1416)
* TextClient: None out game on disconnect
2023-01-28 00:32:48 +01:00
Fabian Dill
ab4324c901 Factorio: add option "ramping tech cost" (#1403)
* Factorio: add option "ramping tech cost"

* Factorio: fix missing s

* Factorio: add display_name to ranmping tech costs
2023-01-27 15:30:05 -08:00
Fabian Dill
1e251dcdc0 Setup: new cx-Freeze just dropped 2023-01-27 20:06:56 +01:00
espeon65536
9c1f7bfea9 oot: remove special NL exceptions in entrance randomization
turns out they were causing lots of issues
2023-01-26 21:24:27 +01:00
KonoTyran
5393563700 MultiServer: Data Storage Additions #1411
adds 3 new operations to datastorage that allows adding and removing of elements from list and dicts.
2023-01-25 06:14:46 +01:00
toasterparty
28576f2b0d OC2: decrease default difficulty (#1413) 2023-01-25 01:04:13 +01:00
Fabian Dill
ba519fecd0 Setup: update some stuff to 6.14.0 cx-Freeze (#1412)
* Setup: update some stuff to 6.14.0 cx-Freeze

* Fix BuildCommand and replace include_files by cutom step

* setup.py: bit more cleanup for extra_libs

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2023-01-25 00:20:26 +01:00
espeon65536
86fb450ecc Core: recache all locations before locality rules
Some worlds would not trigger a recache, causing locations to be missed when setting locality rules.
2023-01-24 06:14:31 +01:00
SonicRPika
920240cb6f Pokemon Red and Blue: Fix to having all traps disabled (#1408) 2023-01-24 04:34:45 +01:00
Fabian Dill
53dd0d5a7d SC2: verify downloaded data is a zipfile 2023-01-24 03:54:23 +01:00
Fabian Dill
807f544b26 SC2: use warning log level for potentially broken map files 2023-01-24 03:45:43 +01:00
SoldierofOrder
1d1693df62 SC2Client: Changes to /download_data and feedback (#1347)
* SC2Client: Added feedback for users who have map files, but no version #.

* SC2Client: Fixed a missing space.

* SC2Client: /download_data now always forces a download.
2023-01-24 03:44:12 +01:00
Fabian Dill
51574959ec Setup: add moduleupdater prompt to setup.py 2023-01-24 03:42:55 +01:00
Fabian Dill
04f726aef2 kvui: always display all tab headers (#1399) 2023-01-24 03:42:34 +01:00
CaitSith2
8a4298e504 ALttP: Fix hint tile hints being potentially useless with item links. (#1400)
* ALttP: Fix hint tile hints being potentially useless with item links.

* use the set returned from world.get_player_groups(player)

* Move the group resolving to BaseClasses. Fix silver arrow hints as well.
2023-01-24 03:42:13 +01:00
Fabian Dill
e7f8f40464 Factorio: fix automation-level tech costs before automation (#1402)
* Factorio: fix automation-level tech costs before automation

* Factorio: remove double-rolling of science cost
2023-01-24 03:36:50 +01:00
Fabian Dill
847582ff5f Server: fix release_mode (#1407)
* Server: fix release_mode

* Core: actually rename forfeit to release across the program
2023-01-24 03:36:27 +01:00
recklesscoder
1a44f5cf1c CommonClient: Fix address pre-selection (#1406) 2023-01-23 04:59:51 +01:00
Fabian Dill
032bc75070 Core: demote logfile deletion loglevel to debug 2023-01-23 02:23:16 +01:00
Alchav
fb47483212 Pokémon R/B: Fix trainersanity location name 2023-01-23 00:38:38 +01:00
Alchav
d185df3972 Pokémon R/B: Use local random object when randomizing trainer parties in generate_output 2023-01-23 00:38:38 +01:00
lordlou
941dcb60e5 SM: fixed flawed and limited comeback check (#1398)
The issue at hand is fixing impossible seeds generated by a lack of properly checking if the player can come back to its previous region after reaching for a new location, as reported here: https://discord.com/channels/731205301247803413/1050529825212874763/1050529825212874763

The previous attempt at checking for comeback was only done against "Landing Site" and the custom start region which is a partial solution at best. For exemple, generating a single player plando seed with a custom starting location at "red_brinstar_elevator" with a forced red door at "RedTowerElevatorBottomLeft" and 2 Missiles set at "Morphing Ball" and "Energy Tank, Brinstar Ceiling" would generate an impossible seed where the player is expected to go through the green door "LandingSiteRight" with no Supers to go to the only possible next location "Power Bomb (red Brinstar spike room)". This is because the comeback check would pass because it would consider coming back to "Landing Site" enough.

The proposed solution is keeping a record of the last accessed region when collecting items. It would then be used as the source of the comeback check with the destination being the new location. This check had to be moved from can_fill() to can_reach() because the maximum_exploration_state of the AP filler only use can_reach().

Its still not perfect because collect() can be called in batch for many items at a time so the last accessed region will be set as the last collected item and will be used for the next comeback checks.

This was tested a bit with the given exemple above (its now failing generation) and by generating some 8 SM players seed with many door color rando, area rando and boss rando enabled.
2023-01-23 00:36:18 +01:00
Fabian Dill
25756831b7 Core: mark version as 0.3.8 2023-01-21 17:30:30 +01:00
Fabian Dill
9add1495d5 SSL support (#1340) 2023-01-21 17:29:27 +01:00
SonicRPika
34dba007dc Pokemon Red and Blue: Updates to trap weights and tracker support (#1395)
* Added cerulean_cave_condition to fill_slot_data

Added `cerulean_cave_condition` to the `fill_slot_data` function, for a poptracker feature being worked on as it was missing

* Added the potential for any traps to be disabled

Adding the ability to disable any kind of trap, for example if you want any status trap except being Poisoned. Will add a contingency to not try and roll a trap if they are all set to disabled.

* Added contingency to if all traps are disabled

Added a contingency to creating items such that it doesn't try to create a trap if all the traps are disabled

* Updated variable name

Edited name of variable to follow PEP 8 variable naming conventions
2023-01-20 18:49:12 +01:00
Fabian Dill
02d3eef565 Core: convert mixture of Plando Options and Settings into just Options 2023-01-19 17:20:23 +01:00
Fabian Dill
c839a76fe7 LttP: allow hinting and tracking "Take Any" type shops (#1392)
* LttP: allow hinting and tracking "Take Any" type shops
fix broken behaviour since bow/cave split


Co-authored-by: CaitSith2 <d_good@caitsith2.com>
2023-01-19 16:17:43 +01:00
alwaysintreble
29e1c3dcf4 LTTP: fix open pyramid for real this time (#1393) 2023-01-19 16:17:16 +01:00
recklesscoder
f6616da5a9 Docs/Subnautica: Updated console instructions, misc clarifications (#1394) 2023-01-19 16:09:08 +01:00
Fabian Dill
8678e02d54 Subnautica: correct doc string placement for early seaglide 2023-01-19 00:03:26 +01:00
Fabian Dill
2f37bedc92 Tests: ensure item name groups do not collide with item names (#1074) 2023-01-18 15:45:48 +01:00
Alchav
91fdfe3e17 Pokémon R/B: Add inheritance to "Completely Random" option as well 2023-01-18 04:26:40 +01:00
Alchav
a41b0051a6 Pokémon R/B: Fix TM/HM compatibility bug 2023-01-18 04:26:40 +01:00
alwaysintreble
b8abe9f980 Tests: add a test to check for dupe locations (#1378) 2023-01-15 20:18:32 +01:00
alwaysintreble
dd3ae5ecbd core: write the plando settings to the spoiler log (#1248)
Co-authored-by: Zach Parks <zach@alliware.com>
2023-01-15 18:10:26 +01:00
PoryGone
e96602d31b SA2B: Fix Gate region connections (#1384) 2023-01-15 17:55:36 +01:00
el-u
81d953daa3 alttp: add item rules for prize locations (#1380) 2023-01-14 14:29:54 +01:00
Alchav
bd774a454e Pokémon R/B: Fix Safari Zone Gate bug (#1381) 2023-01-14 04:59:09 +01:00
espeon65536
ca724c92ad oot: force itempool to higher settings if required by heart logic 2023-01-13 23:53:13 +01:00
espeon65536
11eebbbd32 Ocarina of Time: 0.3.7 hotfixes round 2 (#1351)
* oot: repair closed forest + dungeon ER

* oot: finally skip triforce pieces in balancing

* oot: fix mq_dungeons_mode set to mq or count

* oot: force 0.3.7 client
hopefully this makes people update

* oot: temp fix for skip-child-zelda crash
eventually I want to decide on a better fix for this though

* oot: remove skip-child-zelda item inside if tree

* oot: fix classification of some thieves hideout locations in tracker

* oot: fix regional shuffle for hideout keys and ganon boss key

* oot: properly attach hints to dungeon locations

* Fix entrance shuffle flag not being set correctly due to new dungeon shuffle option format
2023-01-12 20:20:49 +01:00
Doug Hoskisson
608794cded ZillionClient: fix manual disconnect (#1266) 2023-01-07 10:27:43 +01:00
eudaimonistic
816de5ff02 Docs: code_of_conduct.md (#1350)
Update to point of contact.
2023-01-07 10:24:41 +01:00
Fabian Dill
0b941e2268 LttP: attempt at preventing ghost location checks (#1355) 2023-01-07 10:22:15 +01:00
beauxq
57713cda50 Zillion: minor terrain logic update
standing on a moving walkway requires 2 columns of standing space
2023-01-07 10:12:45 +01:00
Jarno
f56cdd6ec3 Sudoku: Hints will no-longer duplicate (#1371) 2023-01-07 10:10:20 +01:00
t3hf1gm3nt
773c517757 update LTTP player template to add all universal AP options (#1372) 2023-01-07 10:09:33 +01:00
JoshuaEagles
2509b7fa3f SA2B: Add Linux section to setup guide (#1374) 2023-01-07 10:00:19 +01:00
toasterparty
10652d23e0 [OC2] Logic: fixes fails when horde levels/items are excluded from location pool (#1369) 2023-01-05 15:49:50 +01:00
JoshuaEagles
f0bc3d33ac Subnautica: add Linux note to setup guide (#1365) 2023-01-04 15:26:32 +01:00
toasterparty
92d1ed60c6 [OC2] Fix "Moon 1-5" never appearing in level pool (#1366) 2023-01-04 15:21:52 +01:00
Zach Parks
fe2b431821 MultiServer: Remove forced_auto_forfeit (#1363) 2023-01-02 19:26:34 -06:00
Zach Parks
0cc83698f9 Docs: Add special name keywords to docs. (#1353) 2023-01-02 14:42:47 -06:00
Alchav
428f643b07 Pokémon R/B: Fix Pokémon Tower 7F crash (#1362) 2023-01-02 13:29:44 -06:00
Fabian Dill
d4e2b75520 Clients: retry connection with ssl (#1341) 2023-01-02 20:24:54 +01:00
Fabian Dill
96cc7f79dc Subnautica: fix early seaglide 2023-01-02 20:24:14 +01:00
Fabian Dill
bdfbc7e14a Network: allow sending frozenset 2023-01-02 20:23:31 +01:00
Fabian Dill
94c6562f82 Tests: make sure DB overwrite actually takes 2023-01-02 20:23:00 +01:00
alwaysintreble
22fe31a141 Generate: fix default utils options (#1361) 2023-01-02 12:48:31 -06:00
alwaysintreble
72fa19ee1f MultiServer/WebHost: rename all references to forfeit and deprecate it (#1243)
* Webhost: rename all references to forfeit and deprecate it

* needed some renames in multiserver for all the commands to function

* remove forfeit commands

* support forfeit_mode for clients

* rename `forfeit_player` to `release_player`
2023-01-02 12:29:21 -06:00
Zach Parks
d899e918b4 Rogue Legacy: Fix early vendors and architect... again. (#1359) 2023-01-02 12:25:47 -06:00
Zach Parks
33d31c4f0f WebHost: Capitalize Special Range choices to keep consistency. (#1360) 2023-01-02 12:25:33 -06:00
Jarno
9c3c69702a WebHost: Fixed game order by title in Site Map (#1349) 2023-01-02 12:24:08 -06:00
Alchav
dae1a3e0f9 Pokemon R/B: Add Revive to better_shops (#1352) 2023-01-02 12:21:08 -06:00
Alchav
1f1ef10cfe [Pokémon R/B] Fix DeathLink softlock and increment data version (#1348) 2022-12-24 08:25:34 +01:00
Alchav
760af59308 [Pokemon R/B] Fix missing lift key logic 2022-12-23 09:39:21 +01:00
Alchav
3dd7e3e706 [Pokemon R/B] actually implement lose_money_on_blackout 2022-12-23 09:39:21 +01:00
espeon65536
189b129dca oot: repair closed forest + dungeon ER 2022-12-22 06:40:51 +01:00
Fabian Dill
092e8d14ad Core: Make apworlds function mostly before Python 3.10 2022-12-20 17:24:04 +01:00
el-u
4cfc73b582 lufia2ac: rename starting_capsule/starting_party options to default_capsule/default_party 2022-12-19 12:45:49 +01:00
el-u
ff9c11d772 lufia2ac: prevent using party member items if party is full 2022-12-19 12:45:49 +01:00
Alchav
b83aec5c12 [Pokemon R/B] add logic to Fighting Dojo and check for non-vanilla old_man setting for cinnabar gym 2022-12-19 07:04:26 +01:00
Alchav
caf63dd737 [Pokemon R/B] Allow 0 exp setting and add logic rule to Cinnabar Gym to ensure higher level Pokémon are catchable 2022-12-19 07:04:26 +01:00
espeon65536
395d35571c Ocarina of Time 0.3.7 hotfixes (#1336)
- Since multiprocessing and exceptions apparently don't interact well, any exceptions in `generate_output` are handled more gracefully now. The event is always set as well, so the process will no longer hang on an exception there.
- The triforce object file was renamed in 7.0. I didn't change the filename in the code, so it would crash on non-Windows systems.
- Some hint distributions just crash, so I'm temporarily removing them. It will take a while to port the relevant functionality and I'd rather not hold up the next version release.
2022-12-19 07:03:38 +01:00
Fabian Dill
e0be79639c Subnautica: 2.0 compatibility (#1329) 2022-12-17 17:42:02 +01:00
Fabian Dill
37b7f0d32d WebHost: fix LttP tracker crash 2022-12-17 15:22:29 +01:00
el-u
50677ee6a2 lufia2ac: document changes from the vanilla game 2022-12-17 09:26:26 +01:00
PoryGone
f8bc3359c7 SMW: Adjust Butter Bridge 2 - Dragon Coins logic (#1332) 2022-12-17 09:18:23 +01:00
toasterparty
6e537e17e6 [OC2] New Option - Shorter Horde Levels (#1328) 2022-12-16 16:52:15 +01:00
toasterparty
e853fc208b [OC2] Remove DLC items from item pool if vanilla level order (#1323) 2022-12-16 16:48:57 +01:00
toasterparty
1a36da33b4 [OC2] Horde Levels Logically Give 1-Star (#1322) 2022-12-16 16:48:12 +01:00
0rganics
56fc614588 SC2: Bugfix for web tracker (#1324) 2022-12-13 07:35:12 +01:00
espeon65536
47f1fcf382 OoT: Fix region_has_shortcuts crash when boss shuffle is on (#1325) 2022-12-13 07:34:17 +01:00
el-u
51c6be047f Lufia II Ancient Cave: implement new game (#1218)
Co-authored-by: wordfcuk <greili1985@gmail.com>
2022-12-12 02:36:18 +01:00
Fabian Dill
2c46c48ba9 WebHost: reduce tracker refresh delay (#1290) 2022-12-12 00:30:43 +01:00
Alchav
32820ba653 Pokemon R/B: Bug fixes and add trap weights (#1319)
* [Pokemon R/B] Move type chart rando to generate_early and add trap weights

* [Pokemon R/B] Update patching process on client to verify hash
2022-12-11 13:51:28 -06:00
Fabian Dill
6173bc6e03 Core: move create_playthrough under Spoiler as method (#1310)
Core: split create_playthrough, allowing skipping of paths
2022-12-11 13:48:26 -06:00
Magnemania
e71ea94fe5 SC2: Various bugfixes (#1267)
* SC2: Fixed nondeterminism resulting from early unit placement

* SC2: Renamed "world" arguments to "multiworld"

* SC2: Fixed All-In Ground including anti-air in defense score, fixed error in beats_protoss_deathball

* SC2: Fixed No Logic using logic on Beat events

* SC2: Fixed /unfinished command failing when All-In available
2022-12-11 13:46:24 -06:00
0rganics
e3f169b4c3 WebHost: Add SC2WoL game specific tracker (#1270) 2022-12-11 13:43:31 -06:00
kindasneaki
e4e74074f0 Docs: change connecting to archipelago in RoR2 setup guide (#1320)
* Change archipelago mod download page

* Docs: change connecting to archipelago in RoR2 setup guide
2022-12-11 13:38:11 -06:00
Fabian Dill
149630d532 Docs: add remote_start_inventory info in generate_output (#1316) 2022-12-11 14:14:27 +01:00
Fabian Dill
2dcfbff751 Tests: now autoload tests from /worlds/*/test (#1318)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-12-11 13:15:23 +01:00
PoryGone
ec45479c52 SMW: Fix some stages walking Mario onto invalid tile on recomplete (#1317)
If you beat a stage that was already beaten, Mario would sometimes walk onto a tile that he shouldn't.
2022-12-11 04:14:04 +01:00
espeon65536
aee0df5359 Ocarina of Time 7.0 (#1277)
## What is this fixing or adding?
- Adds the majority of OoTR 7.0 features:
  - Pot shuffle, Freestanding item shuffle, Crate shuffle, Beehive shuffle
  - Key rings mode
  - Dungeon shortcuts to speed up dungeons
  - "Regional" shuffle for dungeon items
  - New options for shop pricing in shopsanity
  - Expanded Ganon's Boss Key shuffle options
  - Pre-planted beans
  - Improved Chest Appearance Matches Contents mode
  - Blue Fire Arrows
  - Bonk self-damage
  - Finer control over MQ dungeons and spawn position randomization
- Several bugfixes as a result of the update:
  - Items recognized by the server and valid starting items are now in a 1-to-1 correspondence. In particular, starting with keys is now supported.
  - Entrance randomization success rate improved. Hopefully it is now at 100%. 

Co-authored-by: Zach Parks <zach@alliware.com>
2022-12-11 04:11:40 +01:00
Fabian Dill
2cdd03f786 Network: implement 0.4 marked compatibility removals (#757)
* world remote items handling
* players list when connecting
2022-12-11 02:59:17 +01:00
Zach Parks
ce42fda85f Rogue Legacy: Fix early vendors and architect not being added to pool. (#1314) 2022-12-10 17:24:05 -06:00
Fabian Dill
78a18dee4e Subnautica: give Early Seaglide a display name (#1313) 2022-12-10 11:00:18 -06:00
beauxq
b7d46004e2 Zillion: fix invalid slot data from race condition 2022-12-10 11:15:19 +01:00
Jarno
c3fe341736 Docs: slot_data typing (#1300)
* Docs: slot_data typing

* Properly escaped brackets [ ]
2022-12-09 10:24:08 +01:00
Fabian Dill
79bb43b77c Core: embed custom datapackage into .archipelago (#1288)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-12-08 21:23:31 +01:00
Zach Parks
bedc78d335 Rogue Legacy: Remove relative imports and move to .apworld. (#1304)
* Remove relative import and remove `set_rule` usage.

* Set Rogue Legacy to be .apworld.
2022-12-08 08:54:49 -06:00
t3hf1gm3nt
1b582e5b09 docs: update pokemon and oot setup guides about changing lua options (#1303)
- Pokemon guide was missing a point about changing the Lua option in Bizhawk.
- The same point in the OoT guide was missing a step about restarting Bizhawk after changing the Lua option, so updated that as well.
2022-12-08 10:52:52 +01:00
Alchav
f278dd95c5 [Pokémon R/B] Major bug fix + dialogue change (#1305)
* [Pokemon R/B] Major bug fixes

* [Pokemon R/B] Dialogue updates
2022-12-08 10:51:01 +01:00
PoryGone
92f75f3e03 SA2B: Add missing Whistle location (#1306) 2022-12-08 10:47:39 +01:00
Jarno
f5adc7bdc5 docs: world api fixed link (#1299) 2022-12-08 02:57:49 +01:00
Fabian Dill
78d4da53a7 Tests: verify and fix host.yaml/Utils.py match (#1302) 2022-12-08 02:06:34 +01:00
Alchav
e206c065bf Pokémon Red and Blue: Version 2 (#1282)
Adds Trainersanity option (Each non-scripted trainer has a location check, adding 317 locations)
Adds Randomize Pokedex option. It is required to obtain items from Oak's Aides.
Adds option to add all normal shop items to all normal shops.
Adds DeathLink option.
Adds traps.
Improves Type Chart randomization.
Items can be received during battle.
Stores start inventory in ROM. Only requests remote start inventory if patch is from v1.
Fixes logic bugs.
Various other improvements.
2022-12-08 00:38:34 +01:00
Fabian Dill
5273812039 Core: default distribute Factorio and Subnautica as .apworld (#1260) 2022-12-06 23:40:30 -06:00
Fabian Dill
7c3af68e59 ItemLinks: allow linking replacement items as well (#1274) 2022-12-06 23:37:47 -06:00
PoryGone
449973687b SA2B: v2.0 Content Update (#1294)
Changelog:

Features:
- Completely reworked mission progression system
  - Control of which mission types can be active per-gameplay-style
  - Control of how many missions are active per-gameplay-style
  - Mission order shuffle
- Two new Chaos Emerald Hunt goals
  - `Chaos Emerald Hunt` involves finding the seven Chaos Emeralds and beating Green Hill
  - `FinalHazard Chaos Emerald Hunt` is the same, but with the FinalHazard fight at the end of Green Hill
- New optional Location Checks
  - Keysanity (Chao Containers)
  - Whistlesanity (Animal Pipes and hidden whistle spots)
  - Beetlesanity (Destroying Gold Beetles)
- Option to require clearing all active Cannon's Core Missions for access to the Biolizard fight in `Biolizard` goal
- Hard Logic option
- More Music Options
  - Option to use SADX music
  - New `Singularity` music shuffle option
- Option to choose the Narrator theme 
- New Traps
  - Tiny Trap is now permanent within a level
  - Gravity Trap
  - Exposition Trap
  
Quality of Life:
- Significant revamp to Stage Select screen information conveyance
  - Icons are displayed for:
    - Relevant character's upgrades
    - Which location checks are active/checked
    - Chaos Emeralds found (if relevant)
    - Gate and Cannon's Core emblem costs
  - The above stage-specific info can also be viewed when paused in-level
    - The current mission is also displayed when paused
- Emblem Symbol on Mission Select subscreen now only displays if a high enough rank has been gotten on that mission to send the location check
- Hints including SA2B locations will now specify which Gate that level is located in
- Save file now stores slot name to help prevent false location checks in the case of one player having multiple SA2B slots in the same seed
- Chao Intermediate and Expert race sets are now swapped, per player feedback
  - Intermediate now includes Beginner + Challenge + Hero + Dark
  - Expert now includes Beginner + Challenge + Hero + Dark + Jewel
- New mod config option for the color of the Message Queue text

Bug Fixes:
- Fixed bug where game stops properly tracking items after 127 have been received.
- Several logic fixes
- Game now refers to `Knuckles - Shovel Claws` correctly
- Minor AP World code cleanup
2022-12-07 06:20:02 +01:00
Fabian Dill
f5638552cc Docs: add ClassVar marker to World class (#1224) 2022-12-06 23:12:56 -06:00
Fabian Dill
78ee19de51 Pokemon R/B: always bind file handling to client (#1261) 2022-12-06 23:02:54 -06:00
Zach Parks
82444229be Rogue Legacy: More refactoring and clean up. (#1297)
* Rogue Legacy: More refactoring and clean up.

* Also marked Blacksmith as Progression as it's used in a rule.

* Remove extra newline.

* Prevent divide by zero type error.

* Scratch last commit, got the math mixed in my head.

* Clarified name of rule regarding percentage of stat upgrades.

* Move early vendors/architect creation into `create_items` logic.

* Rename parameter in `create_region`.

* Rename local var in `create_region`.

* Removed accidental links in Markdown docs.

* Refactor `create_region` signature and caller.

* Remove redundant if-else.

* Revert change to if-else, and moved item_pool to function instead of obj var.

* Rename LegacyLogic to RLLogic.

* Remove LogicMixin for rules.
2022-12-06 20:49:55 -06:00
alwaysintreble
2cc03d003a Core: fix bug that caused world option overrides to fail (#1293)
* core: fix bug that caused world option overrides to fail

* copy paste sliver's better code that works as intended

* Fix whitespace

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-12-06 00:50:11 +01:00
recklesscoder
0e4fa378dd WebHost: Detect confusion of settings zip and seed zip (#1227) 2022-12-06 00:40:51 +01:00
Fabian Dill
ffc000ec91 Network: remove deprecated IgnoreGame tag 2022-12-05 23:20:19 +01:00
Fabian Dill
32b8f9f9f3 WebHost: restore old silent ignore of mimetypes in json getting (#1292)
* WebHost: restore old silent ignore of mimetypes in json getting of /api/generate

* Tests: add tests for /api/generate
2022-12-05 22:27:15 +01:00
Yoshi348
4412434976 meta.yaml: update progression balancing (#1283)
* Update meta.yaml to numeric progression balancing

Instead of the old on/off system

* normal/disabled
2022-12-05 22:26:44 +01:00
espeon65536
9bdbced51f Hylics 2: create victory location earlier to ensure it is cached correctly (#1291)
Fixes generation issues where the victory location could not be found from MultiWorld.get_locations
2022-12-04 21:04:01 -06:00
Fabian Dill
bd574ef261 WebHost: save datatables state (#1145)
* WebHost: save datatables state

* WebHost: Fix DataTables local storage keys.

Co-authored-by: recklesscoder <57289227+recklesscoder@users.noreply.github.com>
2022-12-04 20:39:07 -06:00
BordynConfused
45719eb7e0 Hylics2:Logic Fixes (#1281)
Added Juice Ranch and Worm Pod to logic

Extra parenthesis removed

* Delete log.txt
Not used

Transitioned all whitespace characters to Linux /n from Windows /r/n

Updated rules for Rescue Blerol 1 and 2 to avoid impossible seed generation.  Fixed member requirements for Vault Bomb. Added Juice Ranch: Fridge for consistency with other checks behind combat. Added party_shuffle rules to vault medallions, removed jail key requirement (not needed)
2022-12-05 02:25:16 +01:00
kindasneaki
d81fd280fa RoR2: Change risk of rain mod download page link (#1269) 2022-12-04 19:16:03 -06:00
Fabian Dill
6b57275859 Server: attempt to make nothing found message clearer (#1289) 2022-12-04 19:06:13 -06:00
Jarno
63f012cce7 Timespinner: Enter Sandman flag (#1263) 2022-12-04 22:02:46 +01:00
Fabian Dill
679cb3e197 Factorio: fix revealed tech tree hinting old location names 2022-12-04 21:29:35 +01:00
Fabian Dill
38b5a90c07 WebHost: update modules 2022-12-04 21:29:02 +01:00
Fabian Dill
203f17f0f6 Subnautica: prompt users to update 2022-12-04 21:28:44 +01:00
Fabian Dill
65995cd586 Network: implement read_only datastore keys: hints and slot_data (#1286)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-12-03 23:29:33 +01:00
PoryGone
64e2d55e92 SMW: Bug+logic fixes (#1279)
* Allow levels to scroll vertically without climb or run

* Account for needing Yoshi for VB2 Dragon Coins

* Don't allow messages on intro level
2022-12-02 06:25:02 +01:00
lordlou
ef66f64030 SMZ3: shop check fix (#1257) 2022-12-01 03:51:29 +01:00
NewSoupVi
e641c3ca1b The Witness: Fix Expert PP2 Access Logic 2022-12-01 03:46:11 +01:00
alwaysintreble
111c3186bd Core: change signatures in autoworld from world to multiworld (#1273) 2022-12-01 03:45:22 +01:00
toasterparty
f0e9080108 [OC2] Difficulty Adjustment/Documentation (#1278)
* Slightly relax difficulty of final level

* Table to help translate player skill to yaml setting
2022-12-01 03:44:08 +01:00
Jarno Westhof
fd8867c782 [Witness, Docs] Updated link to repository owner, as repository was transferred 2022-12-01 03:42:49 +01:00
Fabian Dill
f81d2653e0 Subnautica: implement swim_rule option and skip goal prog balancing (#1258) 2022-11-28 07:43:04 +01:00
Fabian Dill
1288f15e45 Core: Fill fix local logic conflict (#1271) 2022-11-28 07:03:09 +01:00
Fabian Dill
cde2a6e754 Core: log additional system info that may be relevant to errors (#1255) 2022-11-27 19:52:36 -06:00
Jarno
81dd1e359b Sudoku: Updated text about duplicated hints now the client has been improved (#1264) 2022-11-27 19:30:35 -06:00
alwaysintreble
8dffd87bee LttP: fix open pyramid settings parsing (#1253)
* lttp: fix open pyramid settings prasing

* accidentally left default changed when committing
2022-11-27 19:29:18 -06:00
Fabian Dill
67be80e59d Docs: datapackage typing (#1229)
* Docs: add ClassVar marker to World class

* Docs: add typing to network_data_package
2022-11-27 19:25:53 -06:00
recklesscoder
ff1f5569e7 Factorio: Option descriptions, peaceful mode setup, docs (#1233)
* Docs/Factorio: Flesh out lacking option descriptions.

* Docs/Factorio: Added instructions on peaceful mode.

* Docs/Factorio: Use subheadings for each "other setting".

* Docs/Factorio: Mention that `tech_tree_information: full` grants hints.

* Docs/Factorio: Instructions to use .yaml checker after editing your .yaml manually.
2022-11-27 04:01:10 +01:00
NewSoupVi
8b9b482972 The Witness: Logic Fix (Broken Seed reported)
Jungle Discard requires Arrows, not Triangles
2022-11-27 02:08:56 +01:00
tctompk
d0ce44cd38 Pokemon RB: Fix broken link in Pokemon setup instructions (#1254)
The setup_en.md file had an un-escaped URL that was breaking a link; this fixes the link by escaping the spaces in the URL.
2022-11-24 21:11:46 +01:00
PoryGone
aae78a8a12 Core: Add MultiServer command to check a specific location (#1242) 2022-11-20 22:38:34 +01:00
Fabian Dill
7a5e11e8d4 Core: streamline Multiworld.get_*_locations calls (#1234) 2022-11-20 20:50:32 +01:00
Zach Parks
a9ab53cb8b WebHost/Core: Defer creating slots that have patch files until after multidata is loaded and remove redundant code. (#1250)
* WebHost: On uploads, infer player name if missing in file name.

* Remove conditional logic for not including player name in file name.

* quick readability tweak to "fix"

* Refactored `upload_zip_to_db` to clean up redundancies and fix issues

* Rename `patches` to `files`

* fix comment
2022-11-20 13:39:52 -06:00
Fabian Dill
5ed8c2e1c0 Core: datetime-tag log files and delete old ones 2022-11-20 18:43:44 +01:00
Fabian Dill
67128ece38 LttP: make xxtea only required for race generation 2022-11-20 18:43:26 +01:00
Rome Reginelli
8aed24151f SNIClient: fix SNI not launching with old host.yaml (#1249)
This happens when switching from Appimage 0.3.5 to Appimage 0.3.6 without deleting the user's host.yaml
2022-11-20 11:14:13 +01:00
black-sliver
3e6c097348 SoE: update source wheel for py3.11 on windows 2022-11-18 01:43:53 +01:00
Fabian Dill
8ce3fd5518 Core: update cx-Freeze 2022-11-17 23:53:50 +01:00
Alchav
93a354cd81 [Core] Item plando early locations (and non-early locations) (#1228) 2022-11-17 17:40:44 +01:00
espeon65536
774581b7ba HK: fix crash if shop locations are at max and extra shop slots is nonzero 2022-11-17 17:36:42 +01:00
NewSoupVi
95f90851ac The Witness: Update docs, credits, junk hints (#1240)
* Updated Credits & AP-game hints

* Updated docs
2022-11-17 17:35:59 +01:00
PoryGone
1cd1bfea4d SMW: Prevent Killing Bowser on Yoshi Egg Hunt (#1241)
* Make Bowser unkillable on Egg Hunt

Changed a location name.
2022-11-16 20:20:55 +01:00
espeon65536
edd1fff4b7 Core: make early_items internal only (#1177)
Co-authored-by: beauxq <beauxq@yahoo.com>
2022-11-16 17:32:33 +01:00
Zach Parks
4d79920fa6 Merge pull request #944
* WebHost: Remove "Random" as an option and move to separate button in …

* Merge branch 'main' into randomize-button

* Tweaked color and changed text of tooltip.

* Merge branch 'main' into randomize-button
2022-11-12 22:03:44 -05:00
recklesscoder
7665935227 Merge pull request #1231
* Web/Style: Fix code block padding.
2022-11-12 21:58:11 -05:00
Zach Parks
5139475068 Slay the Spire: Correct tool tip for heart_run to match actual behaviour (#1236) 2022-11-12 18:52:36 -08:00
PoryGone
adcee639a2 SMW: Adjust Early Double Levels (#1238) 2022-11-13 01:33:22 +01:00
Magnemania
fde97fca5b SC2: Increment required client version (#1237) 2022-11-13 01:32:33 +01:00
recklesscoder
e108b67ca5 Docs/OoT: Add savestate fix and links to Bizhawk and Logic wiki pages. 2022-11-12 02:19:26 +01:00
NewSoupVi
17da06f763 The Witness: Fix "doors: panels" items referring to the wrong panels 2022-11-12 02:18:51 +01:00
PoryGone
2ff737175f SMW: Fix Baby Yoshi and Rope Jump Exploits (#1226)
* Fix Baby Yoshi and Rope Jump Exploits

* Fix logic error of Forest of Illusion 3 Secret Exit
2022-11-11 11:50:29 +01:00
NewSoupVi
b0b8268249 Witness: Expert logic broken seed fixes (by IHNN) (#1223)
Co-authored-by: metzner <unconfigured@null.spigotmc.org>
2022-11-10 04:05:00 +01:00
espeon65536
4e5c10ad66 OoT: make Bottles and Adult Trade Item hintable groups (#1222)
* OoT: make Bottles and Adult Trade Item hintable groups
2022-11-09 22:07:14 +01:00
Alchav
350e1e6287 Pokemon Red and Blue: lua updates (#1216) 2022-11-09 15:15:16 +01:00
Magnemania
63c0d027e7 SC2: Bugfixes for YAML Option Interactions (#1215)
Early Unit: Now respects Excluded Items when selecting a random unit.

Units Always Have Upgrades: Changed removal cascade behavior; now additionally checks to see if an associated item is already locked when attempting to remove, and locks associated items if so. Occasionally caused issues with starter items in the past, frequently caused issues with yaml-defined Locked Items.
2022-11-09 13:53:55 +01:00
jtoyoda
a014bb4ab7 FF1: Updating FFR Docs for new Archipelago location (#1221) 2022-11-08 18:58:29 -06:00
black-sliver
0d10fec395 DS3: update data_version (#1220) 2022-11-09 01:53:08 +01:00
Ludovic Marechal
0cbee4ac3e DS3: Add progressive locations, fix the randomize_weapons_level option and add some options ( Deathlink ) (#1206)
* Update items_data.py

added `Red and White Round Shield`, `Crystal Scroll`, `Magic Stoneplate Ring`, and `Outrider Knight` gear.

* Update locations_data.py

Added `US: Red and White Round Shield`, `CKG: Magic Stoneplate Ring`, `GA: Outrider Knight` set, and `GA: Crystal Scroll`

* Update __init__.py

Add `Karla's Ashes` requirements

* Update items_data.py

Add `Irithyll Rapier, Hollow's Ashes, Irina's Ashes, Karla's Ashes, Cornyx's Ashes, and Orbeck's Ashes`

* Update locations_data.py

Add `Irithyll Rapier, Hollow's Ashes, Irina's Ashes, Karla's Ashes, Orbeck's Ashes, and Cornyx's Ashes`

* Update items_data.py

removed "hollows ashes"

* Update locations_data.py

remove "hollows ashes"

* Revert "WebHost: Add the DarkSouls3 entry to upload and download the client file"

This reverts commit 5e7c2d4cee.

* ds3: setup progressive locations

* ds3: Use fill_slot_data instead of generate_output

* ds3: Add no_spell_requirements, no_equip_load and death_link option

* ds3: Add some progressive locations

* DS3: Increment data_version

* DS3: Fix item name in rule

* DS3: Set required client version to 0.3.6 and added offsets between items and location tables for backward compatibility

* DS3: Resolve Python 3.8 compatibility

* DS3: Removed useless region for locations IDs consistency

* DS3: Changed i in loop

* DS3: Remove AP.json from the documentation

* DS3: Put back json upload and download

* DS3: Avoid empty downloads

* DS3: Fix randomize_weapons_level option

* DS3: Remove options duplicate entries

* DS3: Change location rule according to review

Co-authored-by: Br00ty <83629348+Br00ty@users.noreply.github.com>
2022-11-09 01:17:43 +01:00
toasterparty
70cab99caf OC2: Fix typo in description (#1214) 2022-11-07 19:50:30 +01:00
Fabian Dill
c1e97bcbff WebHost: allow setting a generation time limit (#1209)
* WebHost: allow setting a generation time limit.
2022-11-06 21:37:11 +01:00
shadow42085
e2eaafbf70 WebHost: Update Super Metroid Tracker Links (#1211)
varia randomizer site updated
2022-11-06 14:31:02 -06:00
NewSoupVi
66d594e95b The Witness: Add required client version (#1212)
Co-authored-by: metzner <unconfigured@null.spigotmc.org>
2022-11-06 14:26:56 -06:00
Fabian Dill
a9bf0008ba Factorio: add rocket-silo as required technology (#1207) 2022-11-06 14:24:03 -06:00
NewSoupVi
f2426ae603 The Witness: Logic Fix (#1210)
Co-authored-by: metzner <unconfigured@null.spigotmc.org>
2022-11-06 17:31:53 +01:00
Zach Parks
462ddce72c Templates: Remove auto wordwrap, fix manual indentation, and reformat docstrings for world options in main. (#1201)
* Fix wrapping too early if docstring is within 120 characters and re-indent other lines.

* Remove auto-word wrapping and tweaked all worlds' option docstrings formatting.

Options should wrap around the 120 character mark to prevent template files from being too long horizontally. This also allows manual-indentation to work again.

* Fix missing '#' on empty lines in output from docstring.
2022-11-06 08:28:16 -06:00
Alchav
d10bb3c6c1 [Pokémon Red and Blue] more improvements (#1208)
* Generated patch includes base patch

* location ID range start match item ID start

* remove unused import

* Change Oak's Aides defaults to be more sync-friendly
2022-11-06 09:07:41 +01:00
Fabian Dill
61232ca756 Factorio: set useless technologies to be researched from the start (#1205) 2022-11-05 14:01:02 -05:00
AkumaGath17
8f325a4f2b [Minecraft] AP Tracker (Items & Advancements) Update to 1.19 (#1168)
* Tracker hud test

* Added Relevant Items Icon

* Minecraft Tracker Update Test

* Minecraft Tracker Update

* Minecraft Tracker Missing Advancements

* Removed Enchanted Books

* Revert fix

* Added Relevant Books

* Tracker Update

* Minecraft Tracker Update (Saddle 3D Model to Icon)

Co-authored-by: AkumaDark17 <akumaspartanmc@hotmail.com>
2022-11-05 06:41:03 -07:00
NewSoupVi
d28738a918 The Witness: Logic Fix & Generation Fix (#1204)
* Logic fix

* static object modification instead of copy error fixed

Co-authored-by: metzner <unconfigured@null.spigotmc.org>
2022-11-04 23:10:29 -05:00
PoryGone
1f3d048462 DKC3: Fix Generation Hang from missed self.world reference (#1203)
* Baseline patching and logic for DKC3

* Client can send, but not yet receive

* Alpha Test Baseline

* Bug Fixes and Starting Lives Option

* Finish BBH, add world hints

* Add music shuffle

* Boomer Costs Text

* Stubbed in Collect behaviour

* Adjust Gyrocopter option

* Add Bonus Coin junk replacement and tracker support

* Delete bad logs

* Undo host.yaml change

* Refactored SNIClient

* Make Swanky Free

* Fix Typo

* Undo SNIClient run_game hack

* Fix Typo

* Remove Bosses from Level Shuffle

* Remove duplicate kivy Data

* Add DKC3 Docs and increment Data version

* Remove dead code

* Fix mislabeled region

* Add Dark Souls 3 to README

* Always force Cog on Rocket Rush Flag

* Fix Single Ski lock and too many DK Coins

* Update Retroarch version number

* Don't send DKC3 through LttP Adjuster

* Comment Location ROM Table

* Change ROM Hash prefix to D3

* Remove redundant constructor

* Add ROM Change Safeguards

* Properly mark WRAM accesses

* Remove outdated region connect

* Fix syntax error

* Fix Game description

* Fix SNES Bank Access

* Add isso_setup for DKC3

* Double Quote strings

* Escape single quotes I guess

* Add two locations to Trade Sequence List

* Remove trace sequence locations from ROM data dict

* Prevent Krematoa Crash, add crash robustness

* Remove print statements

* Don't remove ctx.rom if save file dies

* Consolidate logic for readability

* Add link to tracker on DKC3 Setup doc

* Remove false information from setup guide

* Fix file extension in setup doc

* Bug Fixes

* Add KONGsanity and cheat options

* First Pass Collect behavior

* Fix level unlock data

* Make ! only indicate KONG letters on KONGsanity

* Fix Level Name in locations

* Adjust junk pool logic

* Fix Knautilus Connections

* Fix Wrinkly Softlock

* Fix missed world reference
2022-11-04 21:40:31 -05:00
PoryGone
b161a5241f SMW: Add link to Tracker in setup guide (#1202) 2022-11-04 21:36:58 -05:00
Fabian Dill
208a0c6b08 WebHost: prevent infinite spinup loop of Rooms 2022-11-04 20:07:28 +01:00
Jarno
c3c1ce5827 Sudoku: Fixed setup link (#1200) 2022-11-04 20:03:39 +01:00
recklesscoder
889bc9d1b4 FactorioClient: Warn about Windows console input. 2022-11-04 18:00:22 +01:00
recklesscoder
165a38dd58 Factorio: Added commands for checking energy link from CLI and in game. 2022-11-04 17:59:05 +01:00
recklesscoder
88088dd054 CommonClient & SNIClient: Fixes for reconnecting (#1193)
* CommonClient & SNIClient: Fixes for reconnecting.
- CommonClient: Allow manual reconnect by typing /connect.
- CommonClient: Don't prompt to reconnect if there is nothing to reconnect to.
- CommonClient: Hide the connection loss modal popup when attempting to connect again.
- CommonClient & SNIClient: Cancel auto-reconnect tasks when the user intervenes.

* (Fix imports for linting.)
2022-11-04 17:57:58 +01:00
Doug Hoskisson
c933fa7e34 Core: optimize early items and add unit test (#1197)
* optimize early items and add unit test

* move sorting list init closer to sorting
2022-11-04 17:56:47 +01:00
alwaysintreble
f1123f2662 Core: more useful error for entrance without region (#1186) 2022-11-03 15:17:34 +01:00
Fabian Dill
0f034ddcf7 Factorio: make client use CommonClient location lookup for technologies 2022-11-03 14:19:47 +01:00
recklesscoder
7f3eda4623 Subnautica: Fix creature_scan_logic: removed allowing Cuddlefish. 2022-11-03 10:48:50 +01:00
alwaysintreble
2b0e7f05da Docs: fix broken link in contributing.md (#1185) 2022-11-02 22:02:06 -05:00
recklesscoder
e204deab02 Factorio: Fix saving on exit on Windows.
Fix detecting of server shutdown.
2022-11-03 00:17:26 +01:00
Magnemania
56afd62175 SC2: Bugfix, incorrectly adding to an array rather than appending (#1183) 2022-11-02 17:01:51 -05:00
Alchav
44204ac9be Pokemon Red/Blue: Fix location name in Pokémon Red and Blue rules (#1184) 2022-11-02 17:01:32 -05:00
espeon65536
6c3852a2a9 OoT: lock fast-filled shop locations (#1188)
ensures that progression balancing doesn't move them later
2022-11-02 17:01:03 -05:00
Jarno
124ae198e4 Sudoku: Updated Sudoku docs now we use standalone deployment (#1180) 2022-11-02 20:21:34 +01:00
alwaysintreble
030b767751 LttP: fix triforce piece same reference (#1179) 2022-11-02 20:08:38 +01:00
NewSoupVi
5ca724a454 The Witness: Logic fix
A broken seed was reported where Colored Squares was on a panel locked by Colored Squares. This PR fixes this problem.
2022-11-02 20:07:43 +01:00
Jarno
af3b752093 WebHost: Fixed warning ".gitignore dropped, as it has no valid sprite data." (#1174)
* Fixed warning ".gitignore dropped, as it has no valid sprite data." on webhost

* Changed to exclude files starting with .
2022-11-02 19:06:00 +01:00
alwaysintreble
c378933274 Overcooked 2: Revert OC2Level world references to be named correctly, optimize location scouting. (#1175)
* fix oc2 level world references

* optimize the local items discovery a bit
2022-11-02 16:11:46 +01:00
Doug Hoskisson
da392239a0 MultiServer and clients: async coroutine starter in Utils.py (#1143)
* async coroutine starter in Utils.py

* refactor from static class to function

* async_start docstring
2022-11-02 15:51:35 +01:00
espeon65536
a6e1e14fee Ocarina of Time: Itemlinks and bugfixes (#1157)
* OoT: ER improvements
Include dungeon rewards in itempool to allow for ER improvement
Better validate_world function by checking for multi-entrance incompatibility more efficiently
Fix some generation failures by ensuring all entrances placed with logic
Introduce bias to some interior entrance placement to improve generation rate

* OoT: fix overworld ER spoiler information

* OoT: rewrite dungeon item placement algorithm
in particular, no longer assumes that exactly the number of vanilla keys is present, which lets it place more or fewer items.

* OoT: auto-send more locations
Now should autosend cows, DMT/DMC great fairies, medigoron, and bombchu salesman
This should be every check autosending. these ones are super weird for some reason and didn't get fixed with the others

* OoT: add items forced local by settings to AP's local_items

* OoT: fast-fill shop junk items

* OoT: ensure that Kokiri Shop is always reachable immediately in closed forest
hence Deku Shield can be bought to leave the forest

* OoT: randomize internal connect name
Connect name is now a random 16-character string.
This should prevent any issues with connecting to a room with the wrong ROM with probability almost 1.

* OoT: introduce TrackRandomRange for trials hint and mq dungeon maps

* OoT: enable proper itemlinking of songs and dungeon items, with restricted placements according to player settings

* OoT: barren hint oversight fix

* OoT: allow NL + ER to roll properly

* OoT: 3.8 compatibility
set and list builtins don't have proper typing support until 3.9, apparently

* OoT: remove Gerudo Membership Card location from the pool if fortress open and card not randomized
another long-standing bug squished

* OoT: exclude locations in the itemlink song fill if they aren't also priority

* OoT: prevent data bleed when client isn't closed between different game connections
I don't understand why people keep doing this

* OoT: linter appeasement
it was a real error though

* fixing merge conflicts is hard

* oot merge update #2

* OoT: removed accidentally duplicated code
2022-11-02 09:32:08 +01:00
alwaysintreble
95378233fc Templates: Update template output and add min and max comments for named special_range options. (#1164)
* add min and max comments for named special_range options

* comment all special range options and dictate the min and max in comment block

* make it cleaner

* make it cleanerer

* make it cleanererer

* Reformat template for more consistent comments.

* Fixed missing note on some special settings.

* Small tweak to template.

* Update playerSettings.yaml to match auto-generated template with all ALTTP options.

* Fix edge case with `special_range_cutoff` and revert playerSettings.yaml.

Co-authored-by: Zach Parks <zach@alliware.com>
2022-11-01 18:07:56 -05:00
Fabian Dill
85130f2bbd WebHost: update modules (#1176)
Fix bokeh 3.0.0 attribute removal and touch up stats.py while at it
2022-11-01 17:01:53 -05:00
Ludovic Marechal
ab9f3767e2 DS3: Use slot_data instead of the external Json file (#1155)
* Update items_data.py

added `Red and White Round Shield`, `Crystal Scroll`, `Magic Stoneplate Ring`, and `Outrider Knight` gear.

* Update locations_data.py

Added `US: Red and White Round Shield`, `CKG: Magic Stoneplate Ring`, `GA: Outrider Knight` set, and `GA: Crystal Scroll`

* Update __init__.py

Add `Karla's Ashes` requirements

* Update items_data.py

Add `Irithyll Rapier, Hollow's Ashes, Irina's Ashes, Karla's Ashes, Cornyx's Ashes, and Orbeck's Ashes`

* Update locations_data.py

Add `Irithyll Rapier, Hollow's Ashes, Irina's Ashes, Karla's Ashes, Orbeck's Ashes, and Cornyx's Ashes`

* Update items_data.py

removed "hollows ashes"

* Update locations_data.py

remove "hollows ashes"

* Revert "WebHost: Add the DarkSouls3 entry to upload and download the client file"

This reverts commit 5e7c2d4cee.

* ds3: Use fill_slot_data instead of generate_output

* DS3: Increment data_version

* DS3: Fix item name in rule

* DS3: Set required client version to 0.3.6 and added offsets between items and location tables for backward compatibility

* DS3: Resolve Python 3.8 compatibility

* DS3: Removed useless region for locations IDs consistency

* DS3: Changed i in loop

* DS3: Remove AP.json from the documentation

* DS3: Put back json upload and download

* DS3: Avoid empty downloads

(cherry picked from commit c4c485140d)

Co-authored-by: Br00ty <83629348+Br00ty@users.noreply.github.com>
2022-11-01 22:58:08 +01:00
Zach Parks
bf142b32c9 Rogue Legacy: Rename world to multiworld in local variables and function signatures. (#1169) 2022-11-01 16:14:09 -05:00
lordlou
05c06a57af SMZ3: completion condition fix and more info in spoiler (#1133)
* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* Fixed multiworld support patch not working with VariaRandomizer's

Added stage_fill_hook to set morph first in progitempool
Added back VariaRandomizer's standard patches

* + added missing files from variaRandomizer project

* + added missing variaRandomizer files (custom sprites)

+ started integrating VariaRandomizer options (WIP)

* Some fixes for player and server name display

- fixed player name of 16 characters reading too far in SM client
- fixed 12 bytes SM player name limit (now 16)
- fixed server name not being displayed in SM when using server cheat ( now displays RECEIVED FROM ARCHIPELAGO)
- request: temporarly changed default seed names displayed in SM main menu to OWTCH

* Fixed Goal completion not triggering in smClient

* integrated VariaRandomizer's options into AP (WIP)

- startAP is working
- door rando is working
- skillset is working

* - fixed itemsounds.ips crash by always including nofanfare.ips into multiworld.ips (itemsounds is now always applied and "itemsounds" preset must always be "off")

* skillset are now instanced per player instead of being a singleton class

* RomPatches are now instanced per player instead of being a singleton class

* DoorManager is now instanced per player instead of being a singleton class

* - fixed the last bugs that prevented generation of >1 SM world

* fixed crash when no skillset preset is specified in randoPreset (default to "casual")

* maxDifficulty support and itemsounds removal

- added support for maxDifficulty
- removed itemsounds patch as its always applied from multiworld patch for now

* Fixed bad merge

* Post merge adaptation

* fixed player name length fix that got lost with the merge

* fixed generation with other game type than SM

* added default randoPreset json for SM in playerSettings.yaml

* fixed broken SM client following merge

* beautified json skillset presets

* Fixed ArchipelagoSmClient not building

* Fixed conflict between mutliworld patch and beam_doors_plms patch

- doorsColorsRando now working

* SM generation now outputs APBP

- Fixed paths for patches and presets when frozen

* added missing file and fixed multithreading issue

* temporarily set data_version = 0

* more work

- added support for AP starting items
- fixed client crash with gamemode being None
- patch.py "compatible_version" is now 3

* commited missing asm files

fixed start item reserve breaking game (was using bad write offset when patching)

* Nothing item are now handled game-side. the game will now skip displaying a message box for received Nothing item (but the client will still receive it).

fixed crash in SMClient when loosing connection to SNI

* fixed No Energy Item missing its ID

fixed Plando

* merge post fixes

* fixed start item Grapple, XRay and Reserve HUD, as well as graphic beams (except ice palette color)

* fixed freeze in blue brinstar caused by Varia's custom PLM not being filled with proper Multiworld PLM address (altLocsAddresses)

* fixed start item x-ray HUD display

* Fixed start items being sent by the server (is all handled in ROM)

Start items are now not removed from itempool anymore
Nothing Item is now local_items so no player will ever pickup Nothing. Doing so reduces contribution of this world to the Multiworld the more Nothing there is though.
Fixed crash (and possibly passing but broken) at generation where the static list of IPSPatches used by all SM worlds was being modified

* fixed settings that could be applied to any SM players

* fixed auth to server only using player name (now does as ALTTP to authenticate)

* - fixed End Credits broken text

* added non SM item name display

* added all supported SM options in playerSettings.yaml

* fixed locations needing a list of parent regions (now generate a region for each location with one-way exits to each (previously) parent region

did some cleaning (mainly reverts on unnecessary core classes

* minor setting fixes and tweaks

- merged Area and lightArea settings
- made missileQty, superQty and powerBombQty use value from 10 to 90 and divide value by float(10) when generating
- fixed inverted layoutPatch setting

* added option start_inventory_removes_from_pool

fixed option names formatting
fixed lint errors
small code and repo cleanup

* Hopefully fixed ROR2 that could not send any items

* - fixed missing required change to ROR2

* fixed 0 hp when respawning without having ever saved (start items were not updating the save checksum)

* fixed typo with doors_colors_rando

* fixed checksum

* added custom sprites for off-world items (progression or not)

the original AP sprite was made with PierRoulette's SM Item Sprite Utility by ijwu

* - added missing change following upstream merge

- changed patch filename extension from apbp to apm3 so patch can be used with the new client

* added morph placement options: early means local and sphere 1

* fixed failing unit tests

* - fixed broken custom_preset options

* - big cleanup to remove unnecessary or unsupported features

* - more cleanup

* - moved sm_randomizer_rom and all always applied patches into an external project that outputs basepatch.ips

- small cleanup

* - added comment to refer to project for generating basepatch.ips (https://github.com/lordlou/SMBasepatch)

* fixed g4_skip patch that can be not applied if hud is enabled

* - fixed off world sprite that can have broken graphics (restricted to use only first 2 palette)

* - updated basepatch to reflect g4_skip removal

- moved more asm files to SMBasepatch project

* - tourian grey doors at baby metroid are now always flashing (allowing to go back if needed)

* fixed wrong path if using built as exe

* - cleaned exposed maxDifficulty options

- removed always enabled Knows

* Merged LttPClient and SMClient into SNIClient

* added varia_custom Preset Option that fetch a preset (read from a new varia_custom_preset Option) from varia's web service

* small doc precision

* - added death_link support

- fixed broken Goal Completion
- post merge fix

* - removed now useless presets

* - fixed bad internal mapping with maxDiff

- increases maxDiff if only Bosses is preventing beating the game

* - added support for lowercase custom preset sections (knows, settings and controller)

- fixed controller settings not applying to ROM

* - fixed death loop when dying with Door rando, bomb or speed booster as starting items

- varia's backup save should now be usable (automatically enabled when doing door rando)

* -added docstring for generated yaml

* fixed bad merge

* fixed broken infinity max difficulty

* commented debug prints

* adjusted credits to mark progression speed and difficulty as Non Available

* added support for more than 255 players (will print Archipelago for higher player number)

* fixed missing cleanup

* added support for 65535 different player names in ROM

* fixed generations failing when only bosses are unreachable

* - replaced setting maxDiff to infinity with a bool only affecting boss logics if only bosses are left to finish

* fixed failling generations when using 'fun' settings

Accessibility checks are forced to 'items' if restricted locations are used by VARIA following usage of 'fun' settings

* fixed debug logger

* removed unsupported "suits_restriction" option

* fixed generations failing when only bosses are unreachable (using a less intrusive approach for AP)

* - fixed deathlink emptying reserves

- added death_link_survive option that lets player survive when receiving a deathlink if the have non-empty reserves

* - merged death_link and death_link_survive options

* fixed death_link

* added a fallback default starting location instead of failing generation if an invalid one was chosen

* added Nothing and NoEnergy as hint blacklist

added missing NoEnergy as local items and removed it from progression

* added missing completion_condition when TowerCrystals is lower than GanonCrystals

added Rewards and Medallions infos to spoiler

* Update worlds/smz3/__init__.py

Yes, indeed. Thank you!

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2022-11-01 14:52:04 -05:00
beauxq
0f7adaaf7b Zillion: LAGPL license 2022-11-01 20:40:57 +01:00
Doug Hoskisson
962e48c078 Zillion: fix unreproducible seeds (#1166)
* fix zillion unreproducible seeds

* world to multiworld merge
2022-11-01 14:45:17 +01:00
black-sliver
95ea0541e6 Core: improve fulfills_accessibility performance 2022-11-01 14:08:24 +01:00
black-sliver
0ed3baabd4 Core: add generic handling of excluded locations
Currently there can be locations that are marked as excluded,
but don't have rules to enforce it, while fill has special handling
for excluded locations already.

This change removes special rules, and adds a generic rule instead.
2022-11-01 14:08:24 +01:00
black-sliver
2db55ac50b Core: make fulfills_accessibility deterministic and fix some typing 2022-11-01 14:08:24 +01:00
black-sliver
bea8d37a3c SoE: fix false positives in early sphere collection (#1165) 2022-11-01 13:14:38 +01:00
Alchav
813015e007 [Pokemon] Fixes and updates (#1108)
* [Pokemon] Logic fixes

* [Pokemon] Fix seed name length

* [Pokemon] Location name changes

* [Pokemon] Hidden Item Nurse Bed logic fix

* Badges Needed description update

* Ensure player name does not exceed 16 bytes

* Player name check fix

* Remove unique items in start_inventory from item pool

* Vending Machine Drinks will not be created as filler

* Skip trainer text

* Badges needed for viridian gym text

* Add slot data for trackers

* free fly map in slot data and old_man = vanilla allows more free fly maps

* Re-add mistakenly removed slot data item

* Add tracker link to setup doc

* Doc fix

* Fix base patch

* Change pre_fill to generate_basic so items are pre-filled before item links

* Rename some hidden locations

* missing file from commit and revert one errant location name change
2022-11-01 07:02:15 +01:00
recklesscoder
c1d7abd06e CommonClient: Some GUI polish (#1139)
* CommonClient: Focus text field when requesting input.

* CommonClient: Store and prefill last server address.

* CommonClient: Focus and select portion of server address upon start.

* CommonClient: Don't allow editing of address while connected.

* CommonClient: Don't make pressing Enter in the address bar disconnect you.

* CommonClient: Use TextInput.text_validate_unfocus over jank workaround.

* CommonClient: Fixed hang when closing after failed handshake.

* CommonClient: Made scrollbar wider and interactable.
2022-11-01 06:54:40 +01:00
strotlog
655f287d42 SM: Fix unobtainable items in remote items+item links combo (#1151)
* SM: fix using item links together with remote items

* SM: write 0 index for excess player ids

* some style and minor fixes (strotlog/Archipelago#1)

* more typing in SM patching

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2022-11-01 06:42:11 +01:00
Alchav
802119502d Core: Fix early items bug with priority locations (#1167) 2022-10-31 22:26:55 -05:00
alwaysintreble
2af510328e Core: rename world to multiworld (#931)
* rename references to `Multiworld` in core to `multiworld` instead of `world`

* fix smz3

* fix oot

* fix low hanging fruit

* revert mysteriously broken spacing in world api.md

* fix more randomly broken spacing

* hate

* that better be all of it

* begrudgingly move over smw

* ._.

* missed some worlds

* this is getting tedious now

* Missed some self.world definitions

Co-authored-by: espeon65536 <espeon65536@gmail.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2022-10-31 21:41:21 -05:00
black-sliver
87f4a97f1e Core: make player name case-insensitive 2022-10-30 17:12:00 +01:00
black-sliver
1bb99d391d Factorio: deterministic locations 2022-10-30 17:11:34 +01:00
Zach Parks
1cad51b1af Rogue Legacy: World folder clean up and generation improvements. (#1148)
* Minor cleanup and renaming of some files/functions.

* Rename `LegacyWorld` and `LegacyWeb` to RLWorld and RLWeb.

* Undo accidental change to comment.

* Undo accidental change to comment.

* Restructure Items.py format and combine all tables into one.

* Restructure Locations.py format and combine all tables into one.

* Split boss event items into separate boss entries.

* Remove definitions folder.

* Reformatted __init__.py for Rogue Legacy.

* Allow fairy chests to be disabled.

* Add working prefill logic for early vendors.

* Re-introduce Early Architect setting.

* Revamped rules and regions and can now generate games.

* Fix normal vendors breaking everything.

* Fix early vendor logic and add fairy chest logic to require Dragons or Enchantress + runes.

* Fix issue with duplicate items being created.

* Move event placement into __init__.py and fix duplicate Vendors.

* Tweak weights and spacing.

* Update documentation and include bug report link.

* Fix relative link for template file.

* Increase amount of chest locations in `location_table`.

* Correct a refactor rename gone wrong.

* Remove unused reference in imports.

* Tweak mistake in boss name in place_events.

* English is hard.

* Tweak some lines in __init__.py to use `.settings()` method.

* Add unique id tests for Rogue Legacy.

IDs are mixed around, so let's try to avoid accidentally using the same identifier twice.

* Fix typo in doc.

* Simplify `fill_slot_data`.

* Change prefix on `_place_events` to maintain convention.

* Remove items that are **not** progression from rules.
2022-10-29 22:15:06 -05:00
Doug Hoskisson
09d8c4b912 MultiServer: fix case sensitivity in server commands (#1156)
* fix case sensitivity in server commands

* improve ambiguous match breakout

* worried about accidentally swapping team and slot

* Remove now unused import
2022-10-30 00:41:07 +02:00
Br00ty
ed23a426ec DS3: fix shield item/location (#1158)
* fixed item id

fixed item id for "Blessed Red and White Shield"

* fixed location id

fixed location id for "US: Blessed Red and White Shield"
2022-10-29 19:06:56 +02:00
Ludovic Marechal
c711264d1a DS3: Added a few new items and locations (#1059)
* Update items_data.py

added `Red and White Round Shield`, `Crystal Scroll`, `Magic Stoneplate Ring`, and `Outrider Knight` gear.

* Update locations_data.py

Added `US: Red and White Round Shield`, `CKG: Magic Stoneplate Ring`, `GA: Outrider Knight` set, and `GA: Crystal Scroll`

* Update __init__.py

Add `Karla's Ashes` requirements

* Update items_data.py

Add `Irithyll Rapier, Hollow's Ashes, Irina's Ashes, Karla's Ashes, Cornyx's Ashes, and Orbeck's Ashes`

* Update locations_data.py

Add `Irithyll Rapier, Hollow's Ashes, Irina's Ashes, Karla's Ashes, Orbeck's Ashes, and Cornyx's Ashes`

* Update items_data.py

removed "hollows ashes"

* Update locations_data.py

remove "hollows ashes"

* DS3: Increment data_version

* DS3: Fix item name in rule

* DS3: Set required client version to 0.3.6 and added offsets between items and location tables for backward compatibility

* DS3: Resolve Python 3.8 compatibility

* DS3: Removed useless region for locations IDs consistency

* DS3: Changed i in loop

Co-authored-by: Br00ty <83629348+Br00ty@users.noreply.github.com>
2022-10-29 13:35:33 +02:00
black-sliver
3dfbbc5057 Doc: Clarify annotations in style guide (#1149)
* Doc: Clarify annotations in style guide

* Fix typo

* Update docs/style.md

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2022-10-28 23:02:23 +02:00
Doug Hoskisson
f298b8d6e7 Zillion: validate rescue item links (#1140) 2022-10-28 21:56:50 +02:00
Fabian Dill
53974d568b Factorio: revamped location system (#1147) 2022-10-28 21:00:06 +02:00
black-sliver
ec0389eefb CI: use build_exe in automated build 2022-10-28 20:59:06 +02:00
Ryan
0c54c47023 Docs: Add ArchipelagoRS to the Network Protocol docs (#1153) 2022-10-28 19:24:08 +02:00
CaitSith2
80db8a33af Don't leak info about what exists or not if player can't afford the hint (#1146) 2022-10-28 02:45:18 -07:00
AkumaGath17
e6c6b00109 Minecraft: Two by two logical requirement fix (#1152)
* [Minecraft] Two by two logical requirement fix

* Two by two update

* Two by Two logical fix [Description in order]

* Two by Two fix [Bucket only= False]

Along with the others isolated items checks
2022-10-28 10:34:46 +02:00
Alchav
813ea6ef8b [SM64] Separate Entrance Shuffle pools option and MIPS cost option improvement (#1137)
* Add separate pool option for entrance shuffle and swap MIPS costs if MIPS1Cost is greater

* Changes based on N00by's suggestions

* split into secret_entrance_ids and course_entrance_ids
2022-10-28 01:43:02 +02:00
Doug Hoskisson
c09e089f9d Docs: Zillion: RetroArch core and early_items recommendation. (#1150)
* add Genesis Plus GX to Zillion docs

* zillion early items recommendation
2022-10-28 01:35:18 +02:00
recklesscoder
cfff12d8d7 Factorio: Added ability to chat from within the game. (#1068)
* Factorio: Added ability to chat from within the game.
This also allows using commands such as !hint from within the game.

* Factorio: Only prepend player names to chat in multiplayer.

* Factorio: Mirror chat sent from the FactorioClient UI to the Factorio server.

* Factorio: Remove local coordinates from outgoing chat.

* Factorio: Added setting to disable bridging chat out.
Added client command to toggle this setting at run-time.

* Factorio: Added in-game command to toggle chat bridging setting at run-time.

* .

* Factorio: Document toggle for chat bridging feature.

* (Removed superfluous type annotations.)

* (Removed hard to read regex.)

* Docs/Factorio: Fix display of multiline code snippets.
2022-10-28 00:45:26 +02:00
recklesscoder
924f484be0 Factorio: Add optional filtering for item sends displayed in-game (#1142)
* Factorio: Added feature to filter item sends displayed in-game.

* Factorio: Document item send filter feature.

* Factorio: Fix item send filter for item links.

* (Removed superfluous type annotations.)

* CommonClient: Added is_uninteresting_item_send helper.
2022-10-28 00:07:57 +02:00
Doug Hoskisson
aeb78eaa10 Zillion: map tracker in client (#1136)
* Option RangeWithSpecialMax

* amendment to typing in web options

* compare string with number

* lots of work on zillion

* fix zillion fill logic

* fix a few more issues in zillion fill logic

* can make zillion patch and use it

* put multi items in zillion rom

* work on ZillionClient

* logging and auth in client

* work on sending and receiving items

* implement item_handling flag

* fix locations ids to NuktiServer package

* use rewrite of zri

* cache logic rule data for performance

* use new id maps

* fix some problems with the big recent merge

* ZillionClient: use new context manager for Memory class

* fix ItemClassification for Zillion items
and some debug statements for asserts,
documentation on running scripts for manual testing
type correction in CommonContext

* fix some issues in client, start on docs, put rescue and item ram addresses in slot data

* use new location name system
fix item locations getting out of sync in progression balancing

* zillion client can read slot name from game

* zillion: new item names

* remove extra unneeded import

* newer options (room gen and starting cards)

* update comment in zillion patch

* zillion non static regions

* change some logging, update some comments

* allow ZillionClient to exit in certain situations

* todo note to fix options doc strings

* don't force auto forfeit

* rework validation of floppy requirement and item counts
and fix race condition in generate_output

* reorganize Zillion component structure
with System class

* documentation updates for Zillion

* attempt inno_setup.iss

* remove todo comment for something done

* update comment

* rework item count zillion options
and some small cleanups

* fix location check count

* data package version 1

* Zillion can pass unit tests without rom

* fix freeze if closing ZillionClient while it's waiting for server login

* specify commit hash for zilliandomizer package

* some changes to options validation

* Zillion doors saved on multiworld server

* add missing function in inno_setup
and name of vanilla continues in options

* rework zillion sync task and context

* Apply documentation suggestions from SoldierofOrder

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* update zillion package

* workaround for asyncio udp bug

There is a bug in Python in Windows
https://github.com/python/cpython/issues/91227
that makes it so if I look for RetroArch before it's ready, it breaks the asyncio udp transport system.

As a workaround, we don't look for RetroArch until the user asks for it with /sms

* a few of the smaller suggestions from review

* logic only looks at my locations
instead of all the multiworld locations

* some adjustments from pull request discussion
and some unit tests

* patch webhost changes from pull request discussion

* zillion logic tests

* better vblr test

* test interaction of character rescue items with logic

* move unit tests to new worlds folder

* comment improvements

* fix minor logic issue
and add memory read timeout

* capitalization in option display names
Opa-Opa is a proper noun

* client toggle side panel with /map

* displays map

* fix map transparency

* fix broken launcher

* better way to specify grid container

* start kivy typing

* have a map that updates with item checks

but it breaks other parts of the UI

* fix layout bug

* aspect ratio of image
and some type checking details

* Fix loading of map for compiled builds

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <doughoskisson@novuslabs.com>
Co-authored-by: CaitSith2 <d_good@caitsith2.com>
2022-10-27 02:30:22 -07:00
toasterparty
6134578c60 Overcooked! 2: slightly relax 3-star logic (#1144) 2022-10-27 11:19:48 +02:00
Fabian Dill
b57ca33c31 Logging: more digits for IDs and counts (#1141)
* Logging: we now need 9 digits for IDs

* Logging: we now need {dynamic} digits for IDs

* Logging: we now need {dynamic} digits for counts
2022-10-27 09:18:25 +02:00
Alchav
4b18920819 Early Items option (#1086)
* Early Items option

* Early Items description update

* Change Early Items to dict

* Rewrite early items as extra fill steps

* Move if statement up

* Document early_items

* Move early_items handling before fill_hook

* Apply suggestions from code review

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* Subnautica pre-fill moved to early_items

* Subnautica early items fix

* Remove unit test bug workaround

* beauxq's pr

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2022-10-27 09:00:24 +02:00
Magnemania
700fe8b75e SC2: New Settings, Logic improvements (#1110)
* Switched mission item group to a list comprehension to fix missile shuffle errors

* Logic for reducing mission and item counts

* SC2: Piercing the Shroud/Maw of the Void requirements now DRY

* SC2: Logic for All-In, may need further refinement

* SC2: Additional mission orders and starting locations

* SC2: New Mission Order options for shorter campaigns and smaller item pools

* Using location table for hardcoded starter unit

* SC2: Options to curate random item pool and control early unit placement

* SC2: Proper All-In logic

* SC2: Grid, Mini Grid and Blitz mission orders

* SC2: Required Tactics and Unit Upgrade options, better connected item handling

* SC2: Client compatibility with Grid settings

* SC2: Mission rando now uses world random

* SC2: Alternate final missions, new logic, fixes

* SC2: Handling alternate final missions, identifying final mission on client

* SC2: Minor changes to handle edge-case generation failures

* SC2: Removed invalid type hints for Python 3.8

* Revert "SC2: Removed invalid type hints for Python 3.8"

This reverts commit 7851b9f7a3.

* SC2: Removed invalid type hints for Python 3.8

* SC2: Removed invalid type hints for Python 3.8

* SC2: Removed invalid type hints for Python 3.8

* SC2: Removed invalid type hints for Python 3.8

* SC2: Changed location loop to enumerate

* SC2: Passing category names through slot data

* SC2: Cleaned up unnecessary _create_items method

* SC2: Removed vestigial extra_locations field from MissionInfo

* SC2: Client backwards compatibility

* SC2: Fixed item generation issue where item is present in both locked and unlocked inventories

* SC2: Removed Missile Turret from defense rating on maps without air

* SC2: No logic locations point to same access rule

Co-authored-by: michaelasantiago <michael.alec.santiago@gmail.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2022-10-26 12:24:54 +02:00
PoryGone
d5efc71344 Core: SNI Client Refactor (#1083)
* First Pass removal of game-specific code

* SMW, DKC3, and SM hooked into AutoClient

* All SNES autoclients functional

* Fix ALttP Deathlink

* Don't default to being ALttP, and properly error check ctx.game

* Adjust variable naming

* In response to:
> we should probably document usage somewhere. I'm open to suggestions of where this should be documented.

I think the most valuable documentation for APIs is docstrings and full typing.

about websockets change in imports - from websockets documentation:
> For convenience, many public APIs can be imported from the websockets package. However, this feature is incompatible with static code analysis. It breaks autocompletion in an IDE or type checking with mypy. If you’re using such tools, use the real import paths.

* todo note for python 3.11
typing.NotRequired

* missed staging in previous commit

* added missing death Game States for DeathLink

Co-authored-by: beauxq <beauxq@users.noreply.github.com>
Co-authored-by: lordlou <87331798+lordlou@users.noreply.github.com>
2022-10-25 19:54:43 +02:00
Fabian Dill
6535836e5c Subnautica: don't override plando during pre_fill 2022-10-25 01:35:01 +02:00
lordlou
89d1a80e01 SM: morph first in pool remove (#1134)
* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* Fixed multiworld support patch not working with VariaRandomizer's

Added stage_fill_hook to set morph first in progitempool
Added back VariaRandomizer's standard patches

* + added missing files from variaRandomizer project

* + added missing variaRandomizer files (custom sprites)

+ started integrating VariaRandomizer options (WIP)

* Some fixes for player and server name display

- fixed player name of 16 characters reading too far in SM client
- fixed 12 bytes SM player name limit (now 16)
- fixed server name not being displayed in SM when using server cheat ( now displays RECEIVED FROM ARCHIPELAGO)
- request: temporarly changed default seed names displayed in SM main menu to OWTCH

* Fixed Goal completion not triggering in smClient

* integrated VariaRandomizer's options into AP (WIP)

- startAP is working
- door rando is working
- skillset is working

* - fixed itemsounds.ips crash by always including nofanfare.ips into multiworld.ips (itemsounds is now always applied and "itemsounds" preset must always be "off")

* skillset are now instanced per player instead of being a singleton class

* RomPatches are now instanced per player instead of being a singleton class

* DoorManager is now instanced per player instead of being a singleton class

* - fixed the last bugs that prevented generation of >1 SM world

* fixed crash when no skillset preset is specified in randoPreset (default to "casual")

* maxDifficulty support and itemsounds removal

- added support for maxDifficulty
- removed itemsounds patch as its always applied from multiworld patch for now

* Fixed bad merge

* Post merge adaptation

* fixed player name length fix that got lost with the merge

* fixed generation with other game type than SM

* added default randoPreset json for SM in playerSettings.yaml

* fixed broken SM client following merge

* beautified json skillset presets

* Fixed ArchipelagoSmClient not building

* Fixed conflict between mutliworld patch and beam_doors_plms patch

- doorsColorsRando now working

* SM generation now outputs APBP

- Fixed paths for patches and presets when frozen

* added missing file and fixed multithreading issue

* temporarily set data_version = 0

* more work

- added support for AP starting items
- fixed client crash with gamemode being None
- patch.py "compatible_version" is now 3

* commited missing asm files

fixed start item reserve breaking game (was using bad write offset when patching)

* Nothing item are now handled game-side. the game will now skip displaying a message box for received Nothing item (but the client will still receive it).

fixed crash in SMClient when loosing connection to SNI

* fixed No Energy Item missing its ID

fixed Plando

* merge post fixes

* fixed start item Grapple, XRay and Reserve HUD, as well as graphic beams (except ice palette color)

* fixed freeze in blue brinstar caused by Varia's custom PLM not being filled with proper Multiworld PLM address (altLocsAddresses)

* fixed start item x-ray HUD display

* Fixed start items being sent by the server (is all handled in ROM)

Start items are now not removed from itempool anymore
Nothing Item is now local_items so no player will ever pickup Nothing. Doing so reduces contribution of this world to the Multiworld the more Nothing there is though.
Fixed crash (and possibly passing but broken) at generation where the static list of IPSPatches used by all SM worlds was being modified

* fixed settings that could be applied to any SM players

* fixed auth to server only using player name (now does as ALTTP to authenticate)

* - fixed End Credits broken text

* added non SM item name display

* added all supported SM options in playerSettings.yaml

* fixed locations needing a list of parent regions (now generate a region for each location with one-way exits to each (previously) parent region

did some cleaning (mainly reverts on unnecessary core classes

* minor setting fixes and tweaks

- merged Area and lightArea settings
- made missileQty, superQty and powerBombQty use value from 10 to 90 and divide value by float(10) when generating
- fixed inverted layoutPatch setting

* added option start_inventory_removes_from_pool

fixed option names formatting
fixed lint errors
small code and repo cleanup

* Hopefully fixed ROR2 that could not send any items

* - fixed missing required change to ROR2

* fixed 0 hp when respawning without having ever saved (start items were not updating the save checksum)

* fixed typo with doors_colors_rando

* fixed checksum

* added custom sprites for off-world items (progression or not)

the original AP sprite was made with PierRoulette's SM Item Sprite Utility by ijwu

* - added missing change following upstream merge

- changed patch filename extension from apbp to apm3 so patch can be used with the new client

* added morph placement options: early means local and sphere 1

* fixed failing unit tests

* - fixed broken custom_preset options

* - big cleanup to remove unnecessary or unsupported features

* - more cleanup

* - moved sm_randomizer_rom and all always applied patches into an external project that outputs basepatch.ips

- small cleanup

* - added comment to refer to project for generating basepatch.ips (https://github.com/lordlou/SMBasepatch)

* fixed g4_skip patch that can be not applied if hud is enabled

* - fixed off world sprite that can have broken graphics (restricted to use only first 2 palette)

* - updated basepatch to reflect g4_skip removal

- moved more asm files to SMBasepatch project

* - tourian grey doors at baby metroid are now always flashing (allowing to go back if needed)

* fixed wrong path if using built as exe

* - cleaned exposed maxDifficulty options

- removed always enabled Knows

* Merged LttPClient and SMClient into SNIClient

* added varia_custom Preset Option that fetch a preset (read from a new varia_custom_preset Option) from varia's web service

* small doc precision

* - added death_link support

- fixed broken Goal Completion
- post merge fix

* - removed now useless presets

* - fixed bad internal mapping with maxDiff

- increases maxDiff if only Bosses is preventing beating the game

* - added support for lowercase custom preset sections (knows, settings and controller)

- fixed controller settings not applying to ROM

* - fixed death loop when dying with Door rando, bomb or speed booster as starting items

- varia's backup save should now be usable (automatically enabled when doing door rando)

* -added docstring for generated yaml

* fixed bad merge

* fixed broken infinity max difficulty

* commented debug prints

* adjusted credits to mark progression speed and difficulty as Non Available

* added support for more than 255 players (will print Archipelago for higher player number)

* fixed missing cleanup

* added support for 65535 different player names in ROM

* fixed generations failing when only bosses are unreachable

* - replaced setting maxDiff to infinity with a bool only affecting boss logics if only bosses are left to finish

* fixed failling generations when using 'fun' settings

Accessibility checks are forced to 'items' if restricted locations are used by VARIA following usage of 'fun' settings

* fixed debug logger

* removed unsupported "suits_restriction" option

* fixed generations failing when only bosses are unreachable (using a less intrusive approach for AP)

* - fixed deathlink emptying reserves

- added death_link_survive option that lets player survive when receiving a deathlink if the have non-empty reserves

* - merged death_link and death_link_survive options

* fixed death_link

* added a fallback default starting location instead of failing generation if an invalid one was chosen

* added Nothing and NoEnergy as hint blacklist

added missing NoEnergy as local items and removed it from progression

* removed now unecessary sorting of Morph balls at end of item pool

Its messing with priority locations feature.
2022-10-24 10:28:08 +02:00
beauxq
ad445629bd Zillion: fix unit tests
previous fix was incorrect
2022-10-24 01:28:06 +02:00
Doug Hoskisson
37c5865c0e Core: Options: fix shared default instances (#1130) 2022-10-23 18:28:09 +02:00
Doug Hoskisson
52726139b4 Zillion: support unicode player names (#1131)
* work on unicode and seed verification

* update zilliandomizer

* fix log message
2022-10-23 18:18:05 +02:00
Doug Hoskisson
24105ac249 Tests: fix random failures on Zillion tests (#1128)
* tests: fix random failures on Zillion tests

Normally there's a low probably that the game doesn't require a power-up that it usually requires.
This makes sure it always has that requirement for tests.

* better type narrowing
2022-10-22 03:42:16 +02:00
Alchav
f18df4c1df [Core] Fix priority location handling in accessibility corrections (#1121)
* Fix priority location handling in accessibility corrections

* Don't lock empty locations

* black sliver's suggested change

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-10-22 03:29:20 +02:00
black-sliver
04b6c31076 SoE: update to v042 and balancing changes (#1125)
* SoE: rebalancing and cleanup

* ModuleUpdate: make url detection more generic

* SoE: change item rules to depend on target player difficulty

* SoE: Update to pyevermizer 0.41.0

* adds footknight
* adds location difficulty

* SoE: minor optimization in item rule

if .. in is faster with sets

* SoE: drop support of patch format v3

* SoE: fix some typing and warnings

* SoE: cleanup imports
2022-10-21 23:26:40 +02:00
Fabian Dill
40c3ef35c7 LttP: fix Inverted Big Bomb Shop indirect connection rule 2022-10-21 23:25:52 +02:00
Fabian Dill
28483a6c14 Generate: don't try to include meta or filler weights file as player 2022-10-21 23:25:26 +02:00
ScootyPuffJr1
fa077defe0 [Factorio] Minor fix for typo on setup doc 2022-10-21 08:58:12 +02:00
Chris Wilson
47b4e2782b WebHost: Fix weighted-settings to not save full set of range options to localStorage (#1100) 2022-10-20 20:10:38 -05:00
Doug Hoskisson
265ee7098a New Game: Zillion (#1081)
* Option RangeWithSpecialMax

* amendment to typing in web options

* compare string with number

* lots of work on zillion

* fix zillion fill logic

* fix a few more issues in zillion fill logic

* can make zillion patch and use it

* put multi items in zillion rom

* work on ZillionClient

* logging and auth in client

* work on sending and receiving items

* implement item_handling flag

* fix locations ids to NuktiServer package

* use rewrite of zri

* cache logic rule data for performance

* use new id maps

* fix some problems with the big recent merge

* ZillionClient: use new context manager for Memory class

* fix ItemClassification for Zillion items
and some debug statements for asserts,
documentation on running scripts for manual testing
type correction in CommonContext

* fix some issues in client, start on docs, put rescue and item ram addresses in slot data

* use new location name system
fix item locations getting out of sync in progression balancing

* zillion client can read slot name from game

* zillion: new item names

* remove extra unneeded import

* newer options (room gen and starting cards)

* update comment in zillion patch

* zillion non static regions

* change some logging, update some comments

* allow ZillionClient to exit in certain situations

* todo note to fix options doc strings

* don't force auto forfeit

* rework validation of floppy requirement and item counts
and fix race condition in generate_output

* reorganize Zillion component structure
with System class

* documentation updates for Zillion

* attempt inno_setup.iss

* remove todo comment for something done

* update comment

* rework item count zillion options
and some small cleanups

* fix location check count

* data package version 1

* Zillion can pass unit tests without rom

* fix freeze if closing ZillionClient while it's waiting for server login

* specify commit hash for zilliandomizer package

* some changes to options validation

* Zillion doors saved on multiworld server

* add missing function in inno_setup
and name of vanilla continues in options

* rework zillion sync task and context

* Apply documentation suggestions from SoldierofOrder

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* update zillion package

* workaround for asyncio udp bug

There is a bug in Python in Windows
https://github.com/python/cpython/issues/91227
that makes it so if I look for RetroArch before it's ready, it breaks the asyncio udp transport system.

As a workaround, we don't look for RetroArch until the user asks for it with /sms

* a few of the smaller suggestions from review

* logic only looks at my locations
instead of all the multiworld locations

* some adjustments from pull request discussion
and some unit tests

* patch webhost changes from pull request discussion

* zillion logic tests

* better vblr test

* test interaction of character rescue items with logic

* move unit tests to new worlds folder

* comment improvements

* fix minor logic issue
and add memory read timeout

* capitalization in option display names
Opa-Opa is a proper noun

* redirect zz stdout to debug

* fix option validation bug making unbeatable seeds

* remove line that does nothing

* attach logic cache to world

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <doughoskisson@novuslabs.com>
2022-10-20 19:41:11 +02:00
black-sliver
ed76c13961 Core: assert that items have a single reference (#1075)
* Core: assert that items have a single reference

* Fix duplicate item reference in The Witness

* Ori: fix duplicate item references

* DKC3: fix duplicate item references

* RL: fix duplicate item references

* SA2B: fix duplicate item references

* SMW: fix duplicate item references

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
2022-10-20 10:42:33 +02:00
NewSoupVi
40b7e78178 The Witness: More Puzzle Skips, Softlock Fix (#1117)
* Softlock fix: 'Swamp Maze Controls' item also locks maze control other side now

* Increase default & max amount of Puzzle Skips (in response to client side changes to how they work)

* Logically require shortbox lasers to start the elevator
2022-10-20 05:05:36 +02:00
Sunny Bat
1900d9382a Raft: Update rules to account for navigation (#1118) 2022-10-19 08:47:33 +02:00
Doug Hoskisson
f12b73f487 Tests: world test base class (#1116)
* world test base class

* game not instance property

* I would have guessed that this only collected 1.

* game property

* move SoE tests into worlds

* don't force auto world setup
2022-10-18 18:54:41 +02:00
black-sliver
49ae79e5ce Tests: add fill tests for #1109 and #1114 (#1115)
* Test: for fill issues fixed in PR #1109

* Test: for double sweep collect fixed in PR #1114
2022-10-18 10:20:57 +02:00
Fabian Dill
4da6a0bb98 Fill: fix duplicate event pickups 2022-10-18 08:59:51 +02:00
Fabian Dill
af0cfc5a38 Fill: Priority locks when placing and does not swap. (#1099)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-10-18 01:07:06 +02:00
Fabian Dill
1aa3e431c8 LttP: fix ganons tower trash fill deleting items that did not fit (#1113) 2022-10-17 09:52:34 +02:00
Fabian Dill
b533ffb9e8 Locality: rewrite for linear memory consumption, from quadratic (#1091) 2022-10-17 03:22:02 +02:00
Fabian Dill
bb46ee7fc1 WebHost: optimize imports 2022-10-17 01:24:02 +02:00
black-sliver
acf7fda26a Main: Fill: more opportunities for swapping 2022-10-17 00:45:51 +02:00
Alchav
f7fc6fa7aa Core: Fix forbid_important_item_rule with parenthesis (#1107) 2022-10-16 07:32:17 +02:00
Alchav
5e97463bdc [Pokemon R/B] Fix inno_setup mistake (#1105) 2022-10-16 02:53:07 +02:00
Doug Hoskisson
ca9c3d05d6 Docs: information on Retrieved packet (#1101) 2022-10-15 13:44:39 +02:00
espeon65536
51f65f4b9e OoT: ER algorithm improvements (#1103)
* OoT: ER improvements
Include dungeon rewards in itempool to allow for ER improvement
Better validate_world function by checking for multi-entrance incompatibility more efficiently
Fix some generation failures by ensuring all entrances placed with logic
Introduce bias to some interior entrance placement to improve generation rate

* OoT: fix overworld ER spoiler information

* OoT: rewrite dungeon item placement algorithm
in particular, no longer assumes that exactly the number of vanilla keys is present, which lets it place more or fewer items.
2022-10-15 12:39:04 +02:00
recklesscoder
1f01404ca4 WebHost: Fixed scrolling to anchors (#1085)
* WebHost: Modernized anchor code

* WebHost: Fixed scrolling to anchors

* WebHost: Fixed scrolling to anchors when fonts are being loaded

* WebHost: Anchor PR changes requested by LegendaryLinux
2022-10-14 17:09:17 -04:00
Fabian Dill
bbb6ee89cf Hylics 2: add to readme (#1094) 2022-10-14 22:53:49 +02:00
Fabian Dill
0aea1e780f Fill: create minimal excluded location rule only once 2022-10-14 22:53:16 +02:00
Fabian Dill
722b3c5369 Core: make add_rule set if it finds an empty rule (#1093)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-10-14 22:52:45 +02:00
black-sliver
097ac189e4 SoE: add tests ... (#1097)
* SoE: add tests ...

... for goals, bronze axe and bronze spear+

* SoE: fix tests
2022-10-14 19:35:53 +02:00
toasterparty
7f3f886e41 Overcooked! 2: Implementation (#1046)
Overcooked! 2 is a couch co-op arcade game with a very high skill ceiling. It has a small but occult following, and the community craves a reason to keep coming back besides just grinding high scores. as such, this PR represents 3 major milestones in one:

 * The launch of OC2 Modding, a modding framework which is the first public mod for the game beyond simple RAM trainers
 * The launch of OC2 Randomizer
 * The integration of OC2 Randomizer in Archipelago
2022-10-13 19:57:50 +02:00
Alchav
3bd4ef3f3d [Pokémon R/B] Fixes (#1096)
* Prevent legendaries from being shuffled into restless soul encounter

* Prevent Poke Tower 6F wild mons from being same as restless soul

* fix non-deterministic generation
2022-10-13 19:55:21 +02:00
Yussur Mustafa Oraji
6b9073acd7 sm64ex: Update min client version 2022-10-13 11:36:04 +02:00
Jarno
e708bea819 [Sudoku] Added new BK mode game (#910)
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
2022-10-13 07:55:00 +02:00
Trevor L
b014ce082b Hylics 2: Implement new game (#1058) 2022-10-13 07:51:25 +02:00
Alchav
30a4bcbbbe [Pokemon Red and Blue] Initial implementation (#1016) 2022-10-13 07:45:52 +02:00
Alchav
0afb7096de Core: improve sweep_for_events efficiency (#1092) 2022-10-13 05:46:07 +02:00
alwaysintreble
f909576813 core: Generic boss plando handler (#1044)
* fix some blunders i made when implementing this

* move generic functions to core class

* move lttp specific stuff out and split up from_text a bit for more modularity

* slightly optimize from_text call order

* don't make changes on github apparently. reading hard

* Metaclass Magic

* do a check against the base class

* copy paste strikes again

* use option default instead of hardcoded "none". check locations and bosses aren't reused.

* throw dupe location error for lttp

* generic singularity support with a bool

* forgot to enable it for lttp

* better error handling

* PlandoBosses: fix inheritance of singularity

* Tests: PlandoBosses

* fix case insensitive tests

* Tests: cleanup PlandoBosses tests

* f in the chat

* oop

* split location into a different variable

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* pass the list of options as `option_list`

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-10-12 20:28:32 +02:00
lordlou
9f684b3dc0 SMZ3: Shield Upgrade typo fix (#1088)
fixed "Shield Uprade" typo

See lordlou/alttp_sm_combo_randomizer_rom@3da5313
2022-10-11 08:38:00 +02:00
recklesscoder
37a40499fa Docs/sm64ex: address common questions
- Added bolded note that one must use a new file with each new seed
- Added troubleshooting section for when one didn't use a new file
- Worded compilation step 8 more explicitly
- Added link to compilation options list
- Added link to TextClient for using commands
2022-10-10 09:14:13 +02:00
recklesscoder
099c4fca3c Docs: Polished Trigger and Plando guides (#1080)
* Docs: Polished Trigger and Plando guides

* Docs: Trigger/Plando guide polish PR suggestions by SoldierofOrder

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* Docs: More Trigger/Plando guide polish

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
2022-10-10 09:10:01 +02:00
Joethepic
106d630ad7 HK: changes dreamers to skip prog balance (#1077) 2022-10-10 02:26:33 +02:00
alwaysintreble
4c0c93b083 core: allow string defaults in yaml templates (#1051)
* allow string defaults in yaml templates

* have default_converter handle strings

* handle all default values in the yaml

* allow for random range options

* yaml dump dicts

* strip the whities

* rip out the converter

* accidentally stripped the dicts

* goodbye readability
2022-10-10 00:16:59 +02:00
Doug Hoskisson
3cbbf905d1 Docs: how to run web host and generate template yamls (#1071) 2022-10-09 04:20:01 +02:00
SoldierofOrder
414ebf2640 SC2: Add an automated installation process for the maps and mod within SC2Client. (#928) 2022-10-09 04:19:17 +02:00
NewSoupVi
3297be7902 The Witness: Expert & Hints (#1072) 2022-10-09 04:13:52 +02:00
recklesscoder
7b3ef012b9 Factorio: Prevent pipes from breaking on invalid UTF-8 in client (#1078) 2022-10-09 04:10:22 +02:00
black-sliver
af6a72c3c3 AppImage: provide LD_LIBRARY_PATH
this fixes libssl1.1 not being found
2022-10-07 22:24:14 +02:00
recklesscoder
38b7bdfe60 WebHost: Fixed some document titles (#1063) 2022-10-06 18:01:07 -04:00
Gertimoshka
4c266e6eff hostRoom.css Changes (#957)
* hostRoom.css Changes

Makes the console be a scrollable object, for easier use with commands

* Update hostRoom.css

* Requested Change

Requested Change
2022-10-06 17:53:20 -04:00
Fabian Dill
8a6c9ff4b8 WebHost: clear yaml template folder before populating it 2022-10-03 08:48:53 +02:00
Doug Hoskisson
fdd7ffb089 Core: move output file name logic into core (#1066)
* move output file name logic into core

I see the same logic with small variations in each different world implementation.
It seems to me, it would be better in the core to keep it consistent.

* missed a few

* remove review comment

* + smw

* double quote strings

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* revert change to DS3 output file name

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-10-02 16:53:18 +02:00
black-sliver
b8e467fbb8 ModuleUpdate: skip disabled/hidden folders (#1070)
* ModuleUpdate: skip non-worlds

* ModuleUpdate: don't skip _* folders

- _* folders may be used for libraries
- this means to properly disable a world, it has to be renamed with a preceding `.`
2022-10-01 17:38:39 +02:00
Fabian Dill
411cd51a92 SC2: dynamically create Beat <mission_name> Events, preventing copy-paste errors (#1023) 2022-10-01 16:22:24 +02:00
Fabian Dill
e9e15e854d SC2: make apworld compatible (#1024)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-10-01 15:24:05 +02:00
black-sliver
4943d26160 APWorld: make it behave more like a regular world
- set sys.modules so it can be imported with worlds.*
- overwrite __package__ so it can reference ..generic
- fix deprecation warning
2022-10-01 11:51:42 +02:00
Fabian Dill
060a04700d Core: allow generic access to indirect_connections (#1056) 2022-09-30 04:58:19 +02:00
Fabian Dill
61e39f355d Core remove legacy patch (#1047)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-30 00:36:30 +02:00
Yussur Mustafa Oraji
8ab0b410c3 sm64ex: Document new connection status notifications 2022-09-29 23:56:46 +02:00
Fabian Dill
d897aaade2 Docs: Ensure Discord links are permanent. (#1064) 2022-09-29 23:15:12 +02:00
black-sliver
0191df88d7 Doc: network protocol: clarify want_reply 2022-09-29 21:15:34 +02:00
recklesscoder
bee1fd9b5a Subnautica: Updated Setup Guide (#1062)
- Added sections for console commands and known issues.
- Updated "Resuming" section to reflect current functionality.
- Removed implication that one might have to create the QMods folder. (If it's missing, then you've already messed up step 1.)
- Renamed "Connect Menu" to "connect form" to be less confusing. Generally fixed word capitalization to conform to standard English. Minor wording changes.
2022-09-29 21:04:04 +02:00
Jarno
dd7d3a02a4 WebHost: Fixed Oculus Ring from showing up on tracker (#1065) 2022-09-29 20:18:21 +02:00
PoryGone
13edfa60be Super Mario World: Implement New Game (#1045) 2022-09-29 20:16:59 +02:00
Alchav
885c8d3fcc Fix minimal accessibility failures (#726) 2022-09-29 19:59:57 +02:00
black-sliver
e6a4925f0c Doc: update apclientpp to header-only (#1054) 2022-09-29 00:09:04 +02:00
Doug Hoskisson
c96b6d7b95 Core: some typing and docs in various parts of the interface (#1060)
* some typing and docs in various parts of the interface

* fix whitespace in docstring

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* suggested changes from discussion

* remove redundant import

* adjust type for json messages

* for options module detection:
 module.lower().endswith("options")

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-28 23:54:10 +02:00
alwaysintreble
8bc8b412a3 Core: fix unweighted options for meta files (#1053) 2022-09-28 23:02:42 +02:00
PoryGone
b4b9ff5d82 Docs: Update snes9x Links (#1048) 2022-09-27 13:26:33 +02:00
black-sliver
b21b5cceb8 Doc, SoE: Logic mixin: no underscore for public members (#1049)
* Doc: logic mixin, drop underscore, clarify

conventionally, we added a leading underscore to logic mixins' function
names. This is noisy in the warning section of IDEs. Leading underscores
should only be used for private/protected functions.

In addition, the use of self.world and/or requirement to (no) pass in stuff
was not made clear earlier.

* SoE: fix _ warnings for logic mixin
2022-09-25 18:00:22 +02:00
CaitSith2
813ee5ee3b Factorio: Add explicit support for factory-levels mod. (#1050)
* Factorio: Add explicit support for factory-levels mod.

* Fix inconsistent space/tabs
2022-09-24 02:43:00 -07:00
Fabian Dill
be1158ad78 Windows: update VC Redistributable to 14.32.31332 from 14.29.30037 2022-09-22 08:46:48 +02:00
black-sliver
6d5ddf3cad MultiServer: allow using IDs for hints 2022-09-20 18:38:31 +02:00
black-sliver
809bda02d1 Test: item/location name must not be numeric 2022-09-20 18:38:16 +02:00
black-sliver
2d5ec6ce22 Doc: item/location name must not be numeric 2022-09-20 18:38:16 +02:00
black-sliver
a95d0ce9ef Doc: clarify requirements.txt in world api.md 2022-09-20 09:48:30 +02:00
alwaysintreble
267d9234e5 core: fix options with "random" as default value not generating (#1033)
* core: fix options with "random" as default value not generating

when option is missing from the player yaml,

Using this in #893 and tested there.

* remove if

* OptionSets default to frozenset so handle that

* range had some specific instances of assuming default as a valid value so change this here to call the from_any

* isinstance instead of type

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-19 22:40:15 +02:00
black-sliver
4686881566 WebHost: CustomServer: use defaultdicts
also change non_hintable to defaultdict in MultiServer and add some typing
2022-09-19 01:20:36 +02:00
SoldierofOrder
101dab0ea4 SC2: Add helpful feedback when failing to locate SC2 (#1032)
* SC2: The client now throws a descriptive error when ExecuteInfo.txt exists but is empty, and offers more helpful suggestions when the file doesn't exist.

* SC2: Replaced the new RuntimeError with a warning in the logger to keep things consistent.

* Removed communism

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-18 16:48:36 +02:00
Fabian Dill
c2d69cb05e Core: add generic interface to add ER data to hints (#1014) 2022-09-18 14:30:43 +02:00
black-sliver
58f66e0f42 autoworld: don't load files/folders starting with '.' (#1030)
* autoworld: don't load files/folders starting with '.'

The imports fail if the folder has a '.' in the name, with a somewhat obscure error, and adding a '.' in front of it is what a linux user might expect to use when disabling a world temporarily.

* autoworld: use tuple to filter .* and _*
2022-09-18 13:02:05 +02:00
Fabian Dill
0215e1fa28 SC2: always show uncollected locations (#1007) 2022-09-18 12:40:35 +02:00
black-sliver
1c0a93acad doc: update use of relative/absolute imports
it matters for apworlds to function
2022-09-18 10:22:17 +02:00
NewSoupVi
4fcde135e5 The Witness: Renaming, Options, Logic Fixes (#1000)
Fixes to postgame detection for "shuffle_postgame"
Renamed many locations to be symbol-independent ("Outside Tutorial Dots Introduction" becomes "Outside Tutorial Shed Row"). This is to set up future alternate modes, like Sigma Expert, which use completely different symbols.
Renamed most door items to be shorter, more consistent, and less... stupid. ("Bunker Bunker Entry Door" -> "Bunker Entry")
Removed "shuffle_uncommon"
Many logic fixes
2022-09-18 04:20:59 +02:00
alwaysintreble
332dde154f core: new freetext and textchoice options (#728)
* add freetext and freetextchoice options

* fix textchoice. create plando_bosses bool so worlds can check if boss plando is enabled

* remove strange unneccessary \ escapes

* lttp: rip boss plando out of core

* fix broken text methods so they read the data correctly

* revert `None` key in boss_shuffle_options. fix failing tests

* lttp: rewrite boss plando

* lttp: rewrite boss shuffle

* add generic verification step and allow options to set a plando module

* add default typing to plando_options set

* use PlandoSettings intflag for lttp boss plando

* fix plandosettings boss flag check

* minor lttp init cleanup

* make suggested changes. account for "random" existing within plando boss options

* override eq operator

* Please document me!

* Forgot to mention it supports plando

* remove auto_display_name

* Throw warning alerting user to which shuffle is being used if plando is off. Set the remaining boss shuffle in init and boss placement cleanup

* move the convoluted string matching to `from_text`

* remove unneccessary text lowering and actually turn off plando option when it's disabled

* typing

* strong typing for verify method and reorder

* typing is your friend

* log warning correctly

* 3.8 support :(

* also list apparently

* rip out old boss shuffle spoiler code

* verification step for plando bosses and locations

* update plando guide to reference new supported behavior

* empty string is not `None`. remove unneccessary error throw

* Fix bad ordering

* validate boss_shuffle only contains a normal boss option at the end

* get random choice from a list dummy

* >:(

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* minor textchoice cleanup

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-17 02:55:33 +02:00
black-sliver
8d51205e8f Alttp: only check item.type for own items with retro_cave 2022-09-17 02:25:09 +02:00
black-sliver
ff05e9d7d5 MultiServer: produce nicer output ...
... for headless and when cancelling the file open dialog
2022-09-17 02:24:51 +02:00
N00byKing
516a52c041 sm64ex: Fix WDW 1Up Block Logic 2022-09-17 02:13:32 +02:00
Alchav
9daa64741b New, smarter fast_fill function (#646)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2022-09-17 02:06:25 +02:00
Fabian Dill
af11fa5150 Core: auto alias (#1022)
* Test: check that default templates can be parsed into Option objects
2022-09-16 00:32:30 +02:00
TheBigSalarius
156e9e0e43 FF1: Throw exception for Noverworld 2022-09-12 03:48:07 +02:00
Fabian Dill
ef46979bd8 SC2: fix client freezing on exit while SC2 is running (#1011) 2022-09-12 02:08:33 +02:00
Fabian Dill
b2aa251c47 SC2: fix bitflag overflow when multiple instances of an Item are acquired (#1008) 2022-09-12 01:51:25 +02:00
Fabian Dill
e204a0fce6 Subnautica: fix missed item and correct other item pool counts to fit it 2022-09-12 01:44:10 +02:00
TheBigSalarius
bb386d3bd7 FF1: fix FF1Client messaging and scoped lua messaging with printjson
Corrects the issue causing the client and lua messaging not displaying properly after the printjson changes
2022-09-12 01:19:51 +02:00
Fabian Dill
88a225764a FF1: fix printjson 2022-09-12 01:19:51 +02:00
espeon65536
99d2caa57d ALttP: remove link_palettes option (#1004)
* ALttP: remove link_palettes option
It doesn't work anyway so better to have it not visible.
2022-09-07 20:16:32 +02:00
lordlou
ade82e3d60 SM: varia tracker fix (#1006) 2022-09-06 19:56:23 +02:00
Fabian Dill
7c04e7e06f MultiServer: save goal completion flag 2022-09-05 22:11:26 +02:00
Fabian Dill
baf51e5959 SC2: fix Launching Mission: text pulling the unshuffled ID. (#1001)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-05 21:09:03 +02:00
toasterparty
8aad75ed23 Tests: Check for Holes in the Item Pool (#992)
* test for holes in the item pool

* Update test/general/TestItems.py

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>

* Update test/general/TestItems.py

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>

* Update test/general/TestItems.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update test/general/TestItems.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-09-05 10:02:40 +02:00
black-sliver
1792b66b3a CI: fix automated builds, update SNI and Enemizer
* Launcher.py always running ModuleUpdate breaks setup.py build --yes
* Use env variables in github workflows
* Update SNI and Enemizer versions in github workflows
* Minor cleanup in workflows
* Silence pycharm warning in Launcher.py
2022-09-05 09:23:08 +02:00
wildham0
5e8ac74b2a FFR: fix NoOverworld mode (#999)
* Add Sigil/Mark to item list
2022-09-05 09:21:00 +02:00
PoryGone
2acc129381 SA2B: Fix typo in doc string (#997) 2022-09-04 14:45:45 +02:00
lordlou
0cbb3c2839 SMZ3: data package fix (#996) 2022-09-03 23:52:09 +02:00
espeon65536
539d2e80f1 OoT: prevent glitched + mq dungeons
this combo is not allowed on main ootr, so we won't have it here either
2022-09-03 21:26:31 +02:00
lordlou
f9e28004a0 SMZ3: item link gt fill fix (#995) 2022-09-03 21:25:55 +02:00
Sunny Bat
b7cfcc9272 New features and fixes for Raft (#984)
* Add DeathLink, small logic changes

* Fix generation, rules, use bool for slotData

* Add more island options

* Update Shovel-related logic

* Update docs
2022-09-03 21:25:04 +02:00
Fabian Dill
4b6d46fd74 Core: update modules 2022-09-03 09:55:47 +02:00
Alchav
b45d8bf221 Patch: Save patch file extension in archipelago.json (#968) 2022-09-02 23:37:37 +02:00
Fabian Dill
f7d107fc0c Subnautica: add some more missed aggressive creatures 2022-09-02 09:06:33 +02:00
alwaysintreble
b14d694e1e templates: fix bug report label 2022-09-01 22:33:22 +02:00
skrawpie
8d2333006a Minecraft: Added shuffled recipe list to en_Minecraft.md (#980)
Co-authored-by: KonoTyran <Kono.Tyran@gmail.com>
2022-09-01 21:26:04 +02:00
Fabian Dill
e413619c26 Tests: verify that a world doesn't use the same ID multiple times (#985) 2022-09-01 21:25:06 +02:00
Yussur Mustafa Oraji
03f66a922d sm64ex: Fix a Location (#979) 2022-09-01 21:21:53 +02:00
black-sliver
b115bdafe7 CI/Doc: Use pytest subtests (#986)
* CI/Doc: use pytest-subtests

* CI: clean up pip installs a bit

* make lint and unittests install the same stuff
* make sure to install wheel, which is a recommended (not required) dependency for everything pip
2022-09-01 09:30:28 +02:00
lordlou
0444fdc379 SM: wasteland ap (#983) 2022-09-01 02:20:30 +02:00
Fabian Dill
c617bba959 SC2: client revamp (#967)
SC2 client now relies almost entirely on the map file and server for the locations and just facilitates them, should make it significantly more resilient to objectives being added or removed


* SC2: fix client crash on printjson messages with more [ than ]

* SC2: move text to queue, that actually clears memory

* SC2: Announce which mission is being loaded


Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-08-31 20:55:15 +02:00
lordlou
8da1cfeeb7 SM: remove events from data package (#973) 2022-08-31 06:14:17 +02:00
black-sliver
fcfc2c2e10 WebHost: fix local_path on python 3.8 (#981)
* WebHost: fix local_path on python 3.8

`__file__` is relative in 3.8, so `os.path.dirname(__file__)` ends up being an empty string breaking calls to `local_path()` (without arguments)

* WebHost: add comment to local_path override
2022-08-31 00:10:18 +02:00
espeon65536
a753905ee4 OoT bug fixes (#955)
* OoT: fix shop patching crash due to Item changes

* OoT: more informative failure in triforce piece replacement

* OoT: in triforce hunt, remove ganon BK from pool and lock the door

* OoT: no longer store trap information on the item
2022-08-30 20:54:40 +02:00
strotlog
2a7babce68 SM+SMZ3: don't abandon checks that happen while disconnected from AP (#946) 2022-08-30 17:16:21 +02:00
Fabian Dill
60d1a27079 Subnautica: revamp aggressive creature scans (#966)
* add forgotten aggressive creatures
* fix logic requirements
* added option to opt out of aggressive creature scans
2022-08-30 17:14:34 +02:00
Fabian Dill
4a2a184db1 Core: remove game-specific arguments from Generate (#971)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-08-30 17:12:33 +02:00
alwaysintreble
45fb735320 Clients: allow games without datapackage (#978) 2022-08-30 00:16:13 +02:00
PoryGone
3eb9e7050f DKC3: Fix Wrinkly Softlock (#963) 2022-08-29 20:04:02 +02:00
CaitSith2
26aed9351e Factorio: Fix a bug with single craft free samples. (#974) 2022-08-29 05:58:26 +02:00
Fabian Dill
b1ffbc49c9 LttPAdjuster: fix GUI for invalid sprite files (#885)
* LttPAdjuster: ignore invalid sprite files

* LttPAdjuster: ignore .gitignore in sprites

* LttPAdjuster: log and show message for invalid sprites

* Alttp: set sprite.valid to False for bad zspr and apsprite ...

... when throwing exceptions

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-08-28 18:30:19 +02:00
Fabian Dill
6d6111de2a Launcher: add ModuleUpdate 2022-08-27 11:13:33 +02:00
Fabian Dill
cc8ce32c61 Options: fix corner case where Toggle.value and Toggle.__int__ would be bool
Which lead to a connect failure in Raft
2022-08-27 11:12:28 +02:00
strotlog
4c94bb0ad5 WebHost: sort game list case-insensitively again 2022-08-26 18:20:37 +02:00
strotlog
af19180ff0 SM: Fix rolling saves, add SRAM features
- fix receiving items in an old save (issue #855) by moving receive queue's read pointer to a per-saveslot value
- clear SRAM over $70:2000, and invalidate save data, when booting a new seed number for the first time
- copy important ROM data to SRAM so future clients don't have to read ROM
2022-08-26 10:32:22 +02:00
CaitSith2
a175aa93e7 Factorio: Detect if more than one AP factorio mod is loaded. (#964) 2022-08-26 10:31:30 +02:00
Zach Parks
a78863fde1 Docs: Update community supported libraries in api doc (#788)
* Docs: Update client supported libraries in api doc

* left align table column

* Update table of languages to include Haxe lib and remarks

* Reformat table

* Changed verbiage on SNI remark
2022-08-26 02:12:37 -05:00
Fabian Dill
0d6cbd9093 Core: convert item name groups to frozenset
Some worlds define them in lists, this speeds up lookup via state.has_group() or similar
2022-08-24 00:19:27 +02:00
Magnemania
1aaf89ff2c SC2: Switched mission item group to a list comprehension to fix missile shuffle errors (#959) 2022-08-23 23:20:39 +02:00
Fabian Dill
295ea97544 Subnautica: increment client version 2022-08-23 23:19:46 +02:00
Fabian Dill
33103b209d WebHost: fix error on save 2022-08-23 23:19:19 +02:00
Fabian Dill
fab12dca0b SC2: add anti air to Devil's Playground Victory
People seem to be on the mission long enough to get attacked by Mutalisks, so Victory should require anti air.
Optional Objectives are doable quite comfortably before Mutalisks show up, allowing the anti-air to be on them for later in the mission.
2022-08-23 23:06:58 +02:00
Fabian Dill
c390801c4c Test: verify file webhost file creations work to some degree (#953)
WebHost: fix some file creation paths
2022-08-23 01:07:17 +02:00
Fabian Dill
e548abd332 Subnautica: use correct option parent class (#954)
* Subnautica: use correct option parent class

* Update Options.py
2022-08-22 19:02:29 -04:00
Jarno
0a5b24be2b [Core] Phase out Print packets and added Countdown type to print json (#812)
* [Core] Added Countdown type to print json to distinct the count down message from other types

* Added backward compatibility check

* Fixed review comments

* Updated header category

* Apply suggestions from code review

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

* Completely phased out Print in favor of PrintJson

* Updated docs to warn about phasing out of Print

* Removed faulty import

Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-08-23 01:02:10 +02:00
Chris Wilson
7f41cafffc Explaining the "Style Lockdown" (#940)
* First pass at a contribution guide for the website. Suggestions are welcome.

* Attempt to make the WebHost change guide describe the intent of the style restrictions more accurately.

* Try to improve the explanation of the intention behind the style restrictions.
2022-08-22 19:01:21 -04:00
alwaysintreble
d66f981be6 Github: templates and new user interface (#870)
* move some docs out of readme and link with the headers

* PR template

* bug report template

* task and feature request templates

* md cleanup

* forgot the template

* make expected results separate section

* move pr template to .github. remove assignment field on tasks

* add headers to pr template

* Requested changes

* suggested changes from @black-sliver and @SoldierofOrder

* Update docs/code_of_conduct.md

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* Update docs/contributing.md

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

* Update docs/contributing.md

Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>

Co-authored-by: Hussein Farran <hmfarran@gmail.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
2022-08-23 00:39:55 +02:00
alwaysintreble
b66a265726 Docs: Make webworld attribute descriptions docstrings instead of comments for nice IDE things (#929) 2022-08-22 23:50:16 +02:00
Fabian Dill
c695f91198 Subnautica: add Options to Creature Scans (#950) 2022-08-22 23:35:41 +02:00
CaitSith2
11cbc0b40b Factorio: Make the energy bridge a different color. (#952) 2022-08-22 23:30:42 +02:00
N00byKing
87d91aeef3 sm64ex: Option for 1Up Block Rando 2022-08-22 17:52:56 +02:00
Fabian Dill
6a6dfcbaff Core: add some types to generic.Rules 2022-08-22 17:51:06 +02:00
NewSoupVi
9553627136 Witness: More bug fixes (#937)
* Fixed disable_non_randomized and other bugs

* Slight performance & code sensibility increase

* Added River Shortcut to Garden as a disabled check in disable_non_randomized

* Changed no progression items exception to a warning

* Added a list of disabled panels to slot_data for disable_non_randomized, so the client can automatically disable the right panels in the future

* Made no progression exception conditional on playercount
2022-08-22 05:50:01 +02:00
PoryGone
a4a8894d22 Add /SNI to .gitignore (#949) 2022-08-22 01:20:35 +02:00
wordfcuk
bf217dcf85 RoR2: Fixed the link to the game settings page (#945) 2022-08-21 17:30:30 +02:00
CaitSith2
484ee9f065 OoT: More item.type bugs. (#930) 2022-08-21 01:55:41 +02:00
Zach Parks
bba82ccd6c WebHost: Remove "Wiki" link from footer (#943) 2022-08-20 19:17:23 -04:00
alwaysintreble
fb122df5f5 RoR2: code cleanup and styling consistency (#833)
* build locations dict dynamically from the TotalLocations option. Minor styling cleanup

* Minor items styling cleanup. remove unused event items

* minor options cleanup. clarify preset toggle slightly better

* make items.py more readable. add chaos weights dict to use as reference point for generation

* small rules styling and consistency cleanup

* create less regions and other init cleanup

* move region creation to less function calls and move revivals calculation

* typing

* use enum instead of hardcoded ints. fix bug i introduced

* better typing
2022-08-20 19:09:35 -04:00
KonoTyran
be8c3131d8 fix allay advancements requiring note block on the wrong one. (#896) 2022-08-20 19:02:50 -04:00
Fabian Dill
9341332379 WebHost: allow newlines in data-tooltip (#921)
* WebHost: allow newlines in data-tooltip

* WebHost: Tooltips: strip surrounding whitespace

* WebHost: unify tooltips behaviour

* WebHost: unify labels around tooltips

* WebHost: changing tooltips width to max-width to allow small tooltips to not have empty space.

* Minor modifications to tooltips

- Reduce tooltip target to (?) spans
- Set fixed width of 260px on tooltips
- Add space between : and (?) on player-settings
- Removed cursor:pointer on tooltips
- Fix labels for checkboxes on generate.html

Co-authored-by: Chris Wilson <chris@legendserver.info>
2022-08-20 18:58:46 -04:00
Fabian Dill
83bcb441bf Factorio: typo 2022-08-21 00:34:36 +02:00
PoryGone
a074d16297 DKC3 v1.1.0 (#938)
Features:

* KONGsanity option (Collect all KONG letters in each level for a check)
* Autosave option
* Difficulty option
* MERRY option
* Handle collected/co-op locations


Bugfixes:

 * Fixed Mekanos softlock
 * Prevent Brothers Bear giving extra Banana Birds
 * Fixed Banana Bird Mother check sending prematurely
 * Fix Logic bug with Krematoa level costs
2022-08-20 16:46:44 +02:00
TheCondor07
89ab4aff9c SC2: Logic changes and fixes, 6 new locations, 2 removed locations (#933) 2022-08-19 22:50:44 +02:00
lordlou
0ac67bfe76 Smz3 early sword fix (#939) 2022-08-19 15:02:39 +02:00
Fabian Dill
0d61192c67 Factorio: make apworld compatible(#935) 2022-08-18 01:33:40 +02:00
Fabian Dill
a1aa9c17ff Core: convert is_zip to zip_path 2022-08-18 01:20:30 +02:00
Henrique Gemignani Passos Lima
d0faa36eef Fix CommonClient.server_loop with nogui
When running client without a gui, ctx.ui is None
2022-08-18 01:18:01 +02:00
Fabian Dill
22c8153ba8 WebHost: fix indentation in tracker.py 2022-08-17 22:15:56 +02:00
CaitSith2
6602c580f4 Fix another item.type crash bug. (#927)
* Fix another item.type crash bug.

* Another location that can crash, in the instance of plando fixed.
2022-08-17 07:16:14 -07:00
Joethepic
431a9b7023 Docs: Mc: fix version in setup guide (#873)
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
2022-08-17 09:59:22 +02:00
Fabian Dill
d426226bce LttP: run optimize imports on __init__ 2022-08-16 23:57:59 +02:00
Fabian Dill
09afdc2553 Webhost: prevent tracker crashes with LttP key itemlinks (#922) 2022-08-16 23:57:26 +02:00
Fabian Dill
ca83905d9f Core: allow loading worlds from zip modules (#747)
* Core: allow loading worlds from zip modules
RoR2: make it zipimport compatible (remove relative imports beyond local top-level)

* WebHost: add support for .apworld
2022-08-15 23:52:03 +02:00
black-sliver
086295adbb AutoWorld: add preliminary .apworld specification (#903)
* AutoWorld: add preliminary .apworld specification

* Doc: apworld specification: fix typo
2022-08-15 23:47:32 +02:00
alwaysintreble
81cf1508e0 Core: Refactor Autoworld.options to Autoworld.option_definitions (#906)
* refactor `world.options` -> `world.option_definitions`

* rename world api reference

* missed some self.options
2022-08-15 23:46:59 +02:00
Fabian Dill
8484193151 Core: crash if non_local pool is too big 2022-08-15 23:36:07 +02:00
Fabian Dill
d10fbf8263 Minecraft: update requests 2022-08-15 23:35:51 +02:00
Fabian Dill
f73b3d71bf Factorio: fix typo 2022-08-15 23:03:03 +02:00
Fabian Dill
d48d775a59 Subnautica: fix 2 logic/locations bugs and add a bit of docs (#917) 2022-08-15 22:53:59 +02:00
Yussur Mustafa Oraji
f716bfc58f sm64ex: Fix Second Floor Door Cost (#909) 2022-08-15 17:29:35 +02:00
Jarno Westhof
97b388747a Docs: Added DS3 & DK3 to network graph 2022-08-15 16:56:55 +02:00
lordlou
898fa203ad Smz3 updated to version 11.3 (#886) 2022-08-15 16:48:13 +02:00
CaitSith2
c02c6ee58c Fix generation failure for Final Fantasy 1 and Dark Souls 3. (#907)
* Fix generation failure for Final Fantasy 1.

* Fix spoiler log giving "Location (Player x): Item (Player y)" for FF1.

* Dark Soul 3 Items/Locations now get player names in spoiler log.
2022-08-14 12:34:46 -07:00
black-sliver
23b04b5069 SM: correctly check if items are SM items 2022-08-14 13:38:52 +02:00
Ludovic Marechal
0ed0d17f38 DS3: Update the setup guide (#878)
* Merge pull request #1 from eudaimonistic/patch-2

Update setup_en.md

(cherry picked from commit 41567697fb89e74301afe651fbde0bafca5946e0)

* DS3: Update english documentation

* DS3: Add French setup guide

* DS3: Fix space formatting in doc

* DS3: Resolve comment
2022-08-14 00:07:36 +02:00
espeon65536
645ede869f OoT: Fix blind item.type reference (#905)
* oot: remove blind reference to item.type

* oot: logical reasoning is hard

* oot: fix blind item.type reference
2022-08-13 04:36:06 +02:00
black-sliver
f5e48c850d Utils: lazy decimal import
decimal is kinda big, there is no noticable difference in performance and the import is unused by webhost's customserver
2022-08-13 00:20:08 +02:00
Fabian Dill
9bd035a19d WebHost: make a fresh Room reload page once if port is not assigned yet 2022-08-12 16:01:02 +02:00
Fabian Dill
2e428f906c Core: document KeyedDefaultDict 2022-08-12 08:34:33 +02:00
black-sliver
b702ae482b Core: clean up Utils.py
* fix import order
* lazy import shutil
* lazy import jellyfish (also speed-up by 0.8%, probably because of inlining)
* yaml:
  * explicitely call Loader UnsafeLoader
  * use CDumper, twice as fast
  * stop leaking leak imported names load and load_all
* open_file: use absolute path
* replace quotes in touched code
* add some typing in touched code
* stringify type hinting for non-imports
* %s/.format -> f
* freeze safe_builtins
* remove double-caching in get_options()
* get rid of some warnings
2022-08-12 08:07:45 +02:00
black-sliver
b8ca41b45f Utils: SI: fix rounding problems (#895)
* Utils: SI: fix rounding problems

999.999 would give 1000.00 instead of 1.00k

* Tests: add Utils: SI tests
2022-08-12 00:46:11 +02:00
CaitSith2
adc16fdd3d Factorio: Don't send researches completed by editor extensions testing forces. (#894) 2022-08-11 18:11:34 +02:00
NewSoupVi
b32d0efe6d Witness: Logic fix for Treehouse in Doors (#892) 2022-08-11 15:57:33 +02:00
black-sliver
c96acbfa23 TextClient: receive all items
By popular demand, this makes /received work again.

Closes #887
2022-08-11 01:06:58 +02:00
black-sliver
ffe528467e Generate: remove period for easy copy&paste
Double-clicking in terminal may select the period, resulting in a bad filename in clipboard.
Also fixing quotes.
2022-08-11 01:06:43 +02:00
Fabian Dill
b989698740 WebHost: fix datapackage typo 2022-08-11 01:04:53 +02:00
Fabian Dill
29e0975832 Clients: prepare for removal of players key in RoomInfo 2022-08-11 00:48:38 +02:00
CaitSith2
e1e2526322 LttP: Do a check for enemizer much earlier in generation. (#875) 2022-08-10 22:21:52 +02:00
Fabian Dill
f2e83c37e9 WebHost: use title-typical sorting for game titles (#883) 2022-08-09 22:21:45 +02:00
Fabian Dill
debda5d111 MultiServer: swap auto-forfeit with auto-collect order
That way the forfeit for items for players that are still playing appear last in the log, which is the visible text in at least the py clients
2022-08-09 16:58:02 +02:00
alwaysintreble
2c4e819010 docs: plando update (#861)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-08-09 10:47:01 +02:00
alwaysintreble
b3700dabf2 Core: Fix meta.yaml and allow the None game category for common options (#845) 2022-08-09 02:29:00 +02:00
TheCondor07
fb2979d9ef SC2: Added Difficulty Override to Client (#863) 2022-08-09 00:20:51 +02:00
Fabian Dill
a378d62dfd SC2: fix Moebius Factor rescue condition (#882) 2022-08-08 23:20:18 +02:00
lordlou
eb5ba72cfc Smz3 min accessibility fix (#880) 2022-08-08 22:23:22 +02:00
Fabian Dill
c1e9d0ab4f WebHost: allow customserver to skip importing worlds subsystem for hosting a Room (#877)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-08-07 18:28:50 +02:00
black-sliver
181cc47079 Core: cleanup BaseClasses.Location
This is just cleanup and has virtually no performance impact.
2022-08-07 13:11:12 +02:00
Zach Parks
04eef669f9 StS: Add a description for the game. (#876) 2022-08-06 21:36:32 -05:00
PoryGone
9167e5363d DKC3: Correct File Extension in Setup Guide (#872) 2022-08-06 13:26:02 +02:00
Zach Parks
f1c5c9a148 WebHost: Fix OptionDicts that define valid_keys from outputting as [] on Weighted Settings export. (#874)
* WebHost: Fix OptionDicts that define valid_keys from outputting as [] on Weighted Settings export
2022-08-06 13:25:37 +02:00
Joethepic
69e5627cd7 HK: fix indentation on mimic grubs (#868) 2022-08-06 02:11:10 +02:00
PoryGone
ae3e6c29e3 DKC3: Add Link to Tracker from Setup Guide (#871) 2022-08-06 00:53:48 +02:00
black-sliver
f6da81ac70 Core: cleanup Item classes (#849) 2022-08-06 00:49:54 +02:00
Jarno Westhof
dd6e212519 [Core] Colorama fix 2022-08-05 17:17:40 +02:00
Fabian Dill
95bba50223 WebHost: fix filename rename in flask update 2022-08-05 17:16:26 +02:00
Fabian Dill
21f7c6c0ad Core: optimize away Item.world (#840)
* Core: optimize away Item.world

* Update test/general/TestFill.py

* Test: undo unnecessary changes

* lttp: remove two more Item.world writes

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-08-05 17:09:21 +02:00
Fabian Dill
d15c30f63b Stats: limit to recognized games 2022-08-05 17:01:02 +02:00
Fabian Dill
db5b7e5db9 Core: update version 2022-08-05 14:32:09 +02:00
black-sliver
7c808bb03b SMZ3: Fix Swamp Palace Entrace for minimal accessibility 2022-08-05 14:29:36 +02:00
black-sliver
530b6cc360 SMZ3: FixJunkFillGT making invalid placements 2022-08-05 14:29:22 +02:00
Fabian Dill
95012c004f Subnautica: update docs with resume info (#853)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: strotlog <49286967+strotlog@users.noreply.github.com>
2022-08-05 14:23:21 +02:00
Fabian Dill
59918b9dbc Core: patch stream_input to ignore non-parsable input (such as EOF encoded as 0xff) (#854) 2022-08-03 14:53:14 +02:00
alwaysintreble
b47cca4515 HK: Add bug report link (#824)
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-08-03 14:41:27 +02:00
CaitSith2
5f27019855 Add an optional path to factorio server-settings.json (#851)
* Add an optional path to factorio server-settings.json

* factorio: changes

* use forward slashs in host.yaml going forward.  (works on all OSes.)
* comment out the host.yaml server_settings option.
* assume that server_settings is NOT provided and explicitly check for its existence in factorio_client.
2022-08-01 14:57:30 -07:00
NewSoupVi
0b228834c2 The Witness: Logic fix (unbeatable seed) (#850) 2022-08-01 20:09:34 +02:00
Fabian Dill
57979b9287 WebHost: update flask (#804) 2022-08-01 12:41:15 +02:00
CaitSith2
4b85000960 Fixed a crafting category bug related to fluids. (#848) 2022-07-31 14:01:39 -07:00
Zach Parks
d1f34d088b WebHost: Add links to "Setup Guides" in Supported Games page (#847)
* WebHost: Add links to "Setup Guides" in Supported Games page

* Remove a hanging console.log() I left in
2022-07-31 11:17:26 -04:00
alwaysintreble
3bc9392e5b Core: have generation print plando settings as string instead of numbers (#843)
* have generation print plando settings as string instead of numbers

* Change to __str__

* Make to_string not a class method

* Suggested fix

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Fix the fix

* Better quotes

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-07-31 12:02:36 +02:00
lordlou
75165803a0 Sm smz3 create item fix (#844) 2022-07-31 11:08:41 +02:00
lordlou
afc9c772be Sm broken start location fix (#841)
* - fixed basepatches application order breaking (at least) starting location
2022-07-30 18:42:02 +02:00
PoryGone
07450bb83d Migrate DKC3 to APDeltaPatch (#838)
* Add DKC3 to APDeltaPatch

* Undo unintended commit

* More undoing

* Remove else clause
2022-07-29 01:51:22 +02:00
Fabian Dill
2ff7e83ad9 WebHost: make a deeply buried if tree for games a bit more automatic 2022-07-29 01:47:19 +02:00
black-sliver
d817fdcfdb Doc: move Running from source from wiki to docs (#797)
* Doc: move "Running from source" from wiki to docs/

* Doc: update links and reformat running from source

* Doc: implement suggestions in "Running from source"

thanks @alwaysintreble

* Doc: update link to "Running from source"

also link docs/ folder

* Doc: Running from source: Apply suggestions from code review

Co-authored-by: KonoTyran <Kono.Tyran@gmail.com>

Co-authored-by: KonoTyran <Kono.Tyran@gmail.com>
2022-07-29 01:18:59 +02:00
PoryGone
f3d966897f Prevent Krematoa Crash (#832)
* Prevent Krematoa Crash, add crash robustness

* Remove print statements

* Don't remove ctx.rom if save file dies

* Consolidate logic for readability
2022-07-29 01:13:00 +02:00
Jarno
9acaf1c279 [Docs] Further explained the mythical InvalidPacket (#828)
* [Docs] Further explained the mythical `InvalidPacket`

* Fixed header category

* Update docs/network protocol.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update docs/network protocol.md

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-07-29 01:11:52 +02:00
NewSoupVi
fd6a0b547f Witness: Fatal logic bug fix (#837)
* Renamed some event items

* Fatal logic bug: Door panels did not check their symbol items
2022-07-28 23:43:35 +02:00
lordlou
c02f355479 Smz3 no progression gt fix (#818) 2022-07-28 12:04:48 +02:00
black-sliver
7d9203ef84 CI: update SNI to 0.0.82 2022-07-28 07:55:53 +02:00
Fabian Dill
e849e4792d WebHost: games played per day plot per game on stats page (#827)
* WebHost: generate stats page palette for maximum hue difference between neighbours.

* WebHost: add per game played stats
2022-07-27 23:36:20 +02:00
Fabian Dill
4565b3af8d DKC3: fix missing default options in Utils.py 2022-07-27 23:34:14 +02:00
Fabian Dill
e5b868e0e9 WebHost: fix 30 days cutoff for stats (#826) 2022-07-27 23:09:40 +02:00
Fabian Dill
489450d3fa SNIClient: fix program not exiting if SNI does not exist nor is running 2022-07-27 22:45:53 +02:00
Fabian Dill
73afab67c8 LttP: fix deprecated use of isSet() (#831) 2022-07-27 22:21:06 +02:00
SoldierofOrder
c61f77029b SC2 docs: Extensive reworks and rewordings. (#809) 2022-07-26 16:53:30 +02:00
Fabian Dill
79702aba65 WebHost: flask caching did a rename 2022-07-26 09:53:18 +02:00
strotlog
1e366ff66f SM: smoother co-op, basepatch internal improvements (#793)
* SM: remote touch instantly, pull ips refactor and symbols

* SM: remove hard-coded ROM address writes

* SM: Full length player table, incl. receive-only player ids

+ apply PR feedback (correct graphic offset, readable data file paths)
2022-07-26 09:43:39 +02:00
Fabian Dill
a0482cf27e Archipidle: Fix forgotten version increment when a new item was added 2022-07-26 09:32:21 +02:00
Ludovic Marechal
288a623ab6 Update ds3 locations and items (#819)
* DS3: Add more rules to avoid softlocks, remove Path of the Dragon gesture location/item and remove useless comments

* DS3: Add more Hostile NPCs locations/items

* DS3: Add missing key items to the key items list
2022-07-26 09:31:16 +02:00
Alchav
3b2037a2d4 HK - focus location (#778) 2022-07-25 22:19:07 +02:00
Fabian Dill
ce536fa3ac Subnautica: fix Multipurpose Room not acquirable in valuable item pool
BaseRoomFragment doesn't exist in vanilla, so when valuable item pool marked it as scannable in vanilla location it did not work, as it's technically BaseRoom
BaseRoom is also required to install other modules into, modules that are already marked as useful, so logically if it's required for other useful stuff it should also be marked as useful
By switching from Fragment to non-fragment one now needs 1 out of 2 instead of 2 out of 2 items, which I consider a plus as well.
2022-07-25 22:17:42 +02:00
PoryGone
41883e44e7 DKC3 - Logic Softlock Fix (#817)
* Add two locations to Trade Sequence List

* Remove trace sequence locations from ROM data dict
2022-07-25 21:34:31 +02:00
Yussur Mustafa Oraji
c3ff201b90 sm64ex: Various Features (#790)
* sm64ex: Course and Secret Randomizer

* sm64ex: Allow higher star door costs, raise minimum amount of stars, deprecate ExtraStars

* sm64ex: Support setting MIPS costs

* sm64ex: Safeguard MIPS Costs
2022-07-25 18:39:31 +02:00
espeon65536
e6635cdd77 OOT updates (#821)
* oot: remove all escape characters in LogicTricks.py

* only attempt to connect to client once

* oot: don't kill player outside ToT or in market entrance
fixed camera makes the game crash outside ToT. added market entrance to be safe, it doesn't matter if you don't die there
2022-07-25 02:07:22 +02:00
NewSoupVi
cfc9d79c79 The Witness: Small changes in response to beta tests (#801)
* Option order and better tooltip

* Logic fix: Hedge Laser requires access to all Hedges

* Add item groups: Lasers, Symbols, Doors

* Update worlds/witness/items.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Comment for clarity

* Logic fix

* Another logic fix

Co-authored-by: metzner <unconfigured@null.spigotmc.org>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-07-23 12:42:14 +02:00
lordlou
fe2c355739 Sm beam door speedkeep fun accessibility (#785)
added speedkeep option
now forces accessibility to "minimal" instead of (to be deprecated) "item" when "fun" settings is used
2022-07-22 09:44:58 +02:00
alwaysintreble
04c3429839 LttP: Fix scam options (#806) 2022-07-22 00:04:41 -05:00
PoryGone
cabbe0aaf6 Donkey Kong Country 3 Implementation (#798)
* Baseline patching and logic for DKC3

* Client can send, but not yet receive

* Alpha Test Baseline

* Bug Fixes and Starting Lives Option

* Finish BBH, add world hints

* Add music shuffle

* Boomer Costs Text

* Stubbed in Collect behaviour

* Adjust Gyrocopter option

* Add Bonus Coin junk replacement and tracker support

* Delete bad logs

* Undo host.yaml change

* Refactored SNIClient

* Make Swanky Free

* Fix Typo

* Undo SNIClient run_game hack

* Fix Typo

* Remove Bosses from Level Shuffle

* Remove duplicate kivy Data

* Add DKC3 Docs and increment Data version

* Remove dead code

* Fix mislabeled region

* Add Dark Souls 3 to README

* Always force Cog on Rocket Rush Flag

* Fix Single Ski lock and too many DK Coins

* Update Retroarch version number

* Don't send DKC3 through LttP Adjuster

* Comment Location ROM Table

* Change ROM Hash prefix to D3

* Remove redundant constructor

* Add ROM Change Safeguards

* Properly mark WRAM accesses

* Remove outdated region connect

* Fix syntax error

* Fix Game description

* Fix SNES Bank Access

* Add isso_setup for DKC3

* Double Quote strings

* Escape single quotes I guess
2022-07-22 00:02:25 -05:00
Jolteon0163
a7787d87f9 Add to the ArchipIDLE items list (#807)
* Add to the ArchipIDLE items list

* Update Items.py

* Update Items.py
2022-07-21 18:08:07 -04:00
KonoTyran
79b851189f HK - Fix typos in option names
Fixed max charm and max geo cost display names.
2022-07-21 09:57:10 -07:00
Fabian Dill
9e972eafb2 Subnautica: Add DeathLink (#803) 2022-07-21 08:39:34 -05:00
Fabian Dill
53a995372f Subnautica: add missed PDA 2022-07-21 10:08:19 +02:00
Fabian Dill
17351021b3 Factorio: update rcon lib 2022-07-20 22:29:51 +02:00
Ludovic Marechal
8ff2c1b6f3 DS3: Add the Dark Souls 3 World into Archipelago (#769) 2022-07-20 12:48:14 +02:00
strotlog
45aea2c8ff ChecksFinder: Linux support via wine (#795)
* ChecksFinder: Linux support via wine

* ChecksFinder: account for custom $WINEPREFIX

* ChecksFinder: wine detection
2022-07-19 07:44:04 +02:00
Fabian Dill
9f5e40283a WebHost: reduce server uptime (#794)
* WebHost: attempt to improve wording of server resume
* WebHost: reduce default room timeout to 2 hours


Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-07-18 21:10:29 +02:00
lordlou
025309ec64 SMZ3: Pedestal hint (#792)
* - fixed missing pedestal and tablets hint text for foreign items (was "Don't waste yout time!", is now "A small victory!")

- small precision to SMZ3 and SM docs about "What does another world's item look like in Super Metroid"
2022-07-17 19:40:23 -05:00
NewSoupVi
bd4850b2b5 The Witness 0.3.4 features (#780)
New options:

Shuffle Doors: Many doors in the game will open on their own upon receiving an item ("key").
Variant - Shuffle Door/Control Panels: Many panels in the game that open doors or control devices in the world will be off until receiving their respective item ("key").
Shuffle Lasers: Lasers no longer activate by solving the laser panel, instead you will get an item that activates the laser.
Shuffle Symbols: Now that there is something else to shuffle (doors / door panels), you can turn off Symbol Rando.
Shuffle Postgame (replaces "Shuffle Hard"): The randomizer will now determine by your settings which panels are in the "postgame" - Meaning they can only be accessed after you can complete your win condition anyway.
2022-07-17 12:56:22 +02:00
Fabian Dill
472e114fb9 Final Fantasy: fix outdated advancement flag 2022-07-17 11:19:00 +02:00
espeon65536
828bcb1266 OoT: Fix gerudo_fortress on normal (#784) 2022-07-16 13:00:00 -05:00
t3hf1gm3nt
9897f4eb4b LTTP: Yaml Update (#765)
removes vendor option from hints, adds scam setting, and adds P option to shop shuffle.
2022-07-16 12:56:23 -05:00
Fabian Dill
e1ef820184 Subnautica: add creature scans 2022-07-16 19:54:55 +02:00
lordlou
b3ad766680 SMZ3: Item link support (#756)
* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* first working (most of the time) progression generation for SM using VariaRandomizer's rules, items, locations and accessPoint (as regions)

* first working single-world randomized SM rom patches

* - SM now displays message when getting an item outside for someone else (fills ROM item table)

This is dependant on modifications done to sm_randomizer_rom project

* First working MultiWorld SM

* some missing things:

- player name inject in ROM and get in client
- end game get from ROM in client
- send self item to server
- add player names table in ROM

* replaced CollectionState inheritance from SMBoolManager with a composition of an array of it (required to generation more than one SM world, which is still fails but is better)

* - reenabled balancing

* post rebase fixes

* updated SmClient.py

* + added VariaRandomizer LICENSE

* + added sm_randomizer_rom project (which builds sm.ips)

* Moved VariaRandomizer and sm_randomizer_rom projects inside worlds/sm and done some cleaning

* properly revert change made to CollectionState and more cleaning

* Fixed multiworld support patch not working with VariaRandomizer's

* missing file commit

* Fixed syntax error in unused code to satisfy Linter

* Revert "Fixed multiworld support patch not working with VariaRandomizer's"

This reverts commit fb3ca18528bb331995e3d3051648c8f84d04c08b.

* many fixes and improovement

- fixed seeded generation
- fixed broken logic when more than one SM world
- added missing rules for inter-area transitions
- added basic patch presence for logic
- added DoorManager init call to reflect present patches for logic
- moved CollectionState addition out of BaseClasses into SM world
- added condition to apply progitempool presorting only if SM world is present
- set Bosses item id to None to prevent them going into multidata
- now use get_game_players

* Fixed multiworld support patch not working with VariaRandomizer's

Added stage_fill_hook to set morph first in progitempool
Added back VariaRandomizer's standard patches

* + added missing files from variaRandomizer project

* + added missing variaRandomizer files (custom sprites)

+ started integrating VariaRandomizer options (WIP)

* Some fixes for player and server name display

- fixed player name of 16 characters reading too far in SM client
- fixed 12 bytes SM player name limit (now 16)
- fixed server name not being displayed in SM when using server cheat ( now displays RECEIVED FROM ARCHIPELAGO)
- request: temporarly changed default seed names displayed in SM main menu to OWTCH

* Fixed Goal completion not triggering in smClient

* integrated VariaRandomizer's options into AP (WIP)

- startAP is working
- door rando is working
- skillset is working

* - fixed itemsounds.ips crash by always including nofanfare.ips into multiworld.ips (itemsounds is now always applied and "itemsounds" preset must always be "off")

* skillset are now instanced per player instead of being a singleton class

* RomPatches are now instanced per player instead of being a singleton class

* DoorManager is now instanced per player instead of being a singleton class

* - fixed the last bugs that prevented generation of >1 SM world

* fixed crash when no skillset preset is specified in randoPreset (default to "casual")

* maxDifficulty support and itemsounds removal

- added support for maxDifficulty
- removed itemsounds patch as its always applied from multiworld patch for now

* Fixed bad merge

* Post merge adaptation

* fixed player name length fix that got lost with the merge

* fixed generation with other game type than SM

* added default randoPreset json for SM in playerSettings.yaml

* fixed broken SM client following merge

* beautified json skillset presets

* Fixed ArchipelagoSmClient not building

* Fixed conflict between mutliworld patch and beam_doors_plms patch

- doorsColorsRando now working

* SM generation now outputs APBP

- Fixed paths for patches and presets when frozen

* added missing file and fixed multithreading issue

* temporarily set data_version = 0

* more work

- added support for AP starting items
- fixed client crash with gamemode being None
- patch.py "compatible_version" is now 3

* commited missing asm files

fixed start item reserve breaking game (was using bad write offset when patching)

* Nothing item are now handled game-side. the game will now skip displaying a message box for received Nothing item (but the client will still receive it).

fixed crash in SMClient when loosing connection to SNI

* fixed No Energy Item missing its ID

fixed Plando

* merge post fixes

* fixed start item Grapple, XRay and Reserve HUD, as well as graphic beams (except ice palette color)

* fixed freeze in blue brinstar caused by Varia's custom PLM not being filled with proper Multiworld PLM address (altLocsAddresses)

* fixed start item x-ray HUD display

* Fixed start items being sent by the server (is all handled in ROM)

Start items are now not removed from itempool anymore
Nothing Item is now local_items so no player will ever pickup Nothing. Doing so reduces contribution of this world to the Multiworld the more Nothing there is though.
Fixed crash (and possibly passing but broken) at generation where the static list of IPSPatches used by all SM worlds was being modified

* fixed settings that could be applied to any SM players

* fixed auth to server only using player name (now does as ALTTP to authenticate)

* - fixed End Credits broken text

* added non SM item name display

* added all supported SM options in playerSettings.yaml

* fixed locations needing a list of parent regions (now generate a region for each location with one-way exits to each (previously) parent region

did some cleaning (mainly reverts on unnecessary core classes

* minor setting fixes and tweaks

- merged Area and lightArea settings
- made missileQty, superQty and powerBombQty use value from 10 to 90 and divide value by float(10) when generating
- fixed inverted layoutPatch setting

* added option start_inventory_removes_from_pool

fixed option names formatting
fixed lint errors
small code and repo cleanup

* Hopefully fixed ROR2 that could not send any items

* - fixed missing required change to ROR2

* fixed 0 hp when respawning without having ever saved (start items were not updating the save checksum)

* fixed typo with doors_colors_rando

* fixed checksum

* added custom sprites for off-world items (progression or not)

the original AP sprite was made with PierRoulette's SM Item Sprite Utility by ijwu

* - added missing change following upstream merge

- changed patch filename extension from apbp to apm3 so patch can be used with the new client

* added morph placement options: early means local and sphere 1

* fixed failing unit tests

* - fixed broken custom_preset options

* - big cleanup to remove unnecessary or unsupported features

* - more cleanup

* - moved sm_randomizer_rom and all always applied patches into an external project that outputs basepatch.ips

- small cleanup

* - added comment to refer to project for generating basepatch.ips (https://github.com/lordlou/SMBasepatch)

* fixed g4_skip patch that can be not applied if hud is enabled

* - fixed off world sprite that can have broken graphics (restricted to use only first 2 palette)

* - updated basepatch to reflect g4_skip removal

- moved more asm files to SMBasepatch project

* - tourian grey doors at baby metroid are now always flashing (allowing to go back if needed)

* fixed wrong path if using built as exe

* - cleaned exposed maxDifficulty options

- removed always enabled Knows

* Merged LttPClient and SMClient into SNIClient

* added varia_custom Preset Option that fetch a preset (read from a new varia_custom_preset Option) from varia's web service

* small doc precision

* - added death_link support

- fixed broken Goal Completion
- post merge fix

* - removed now useless presets

* - fixed bad internal mapping with maxDiff

- increases maxDiff if only Bosses is preventing beating the game

* - added support for lowercase custom preset sections (knows, settings and controller)

- fixed controller settings not applying to ROM

* - fixed death loop when dying with Door rando, bomb or speed booster as starting items

- varia's backup save should now be usable (automatically enabled when doing door rando)

* -added docstring for generated yaml

* fixed bad merge

* fixed broken infinity max difficulty

* commented debug prints

* adjusted credits to mark progression speed and difficulty as Non Available

* added support for more than 255 players (will print Archipelago for higher player number)

* fixed missing cleanup

* added support for 65535 different player names in ROM

* fixed generations failing when only bosses are unreachable

* - replaced setting maxDiff to infinity with a bool only affecting boss logics if only bosses are left to finish

* fixed failling generations when using 'fun' settings

Accessibility checks are forced to 'items' if restricted locations are used by VARIA following usage of 'fun' settings

* fixed debug logger

* removed unsupported "suits_restriction" option

* fixed generations failing when only bosses are unreachable (using a less intrusive approach for AP)

* - fixed deathlink emptying reserves

- added death_link_survive option that lets player survive when receiving a deathlink if the have non-empty reserves

* - merged death_link and death_link_survive options

* fixed death_link

* added a fallback default starting location instead of failing generation if an invalid one was chosen

* added Nothing and NoEnergy as hint blacklist

added missing NoEnergy as local items and removed it from progression

* - enabled local item dialog boxes for dungeon and keycard items when keysanity is used

* - fixed ItemLink support

* fixed shops sending checks

* Added get_filler_item_name() returning a random junk item

Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2022-07-16 12:47:26 -05:00
Fabian Dill
74b19dc1f5 WebHost: cleanup generate and hopefully fix SQL concurrency problems 2022-07-16 19:44:29 +02:00
Fabian Dill
449bc93307 Rogue Legacy: obliterate any outdated remnants before installer adds new files 2022-07-16 19:40:59 +02:00
Fabian Dill
622af17705 MultiServer: make !hint prefer non-local 2022-07-16 12:19:24 +02:00
Fabian Dill
a42f7f99fe Factorio: specify rcon version 2022-07-16 01:31:20 +02:00
black-sliver
3c6bd555b4 doc: add style guide (#746)
* doc: add style guide

* doc: style guide for python and markdown

* doc: consistent use of periods and explicit double quotes in style guide

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

* doc: better define string style in style guide

* doc: add format string literals to style guide

* doc: add HTML, CSS and JS to style guide

Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-07-15 23:52:35 +02:00
Rome Reginelli
a4211d5f11 Improve Risk of Rain 2 docs (#770)
* Improve Risk of Rain 2 docs

* RoR2: clarify custom item weight settings

* Update worlds/ror2/docs/en_Risk of Rain 2.md

Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-07-15 17:24:40 -04:00
Vale
090c5bcf00 RoR2: FinalStageDeath (#766)
Added a YAML option for 'FinalStageDeath', a toggle for 'death on the final boss stage counts as a win'. Defaults to on.

Co-authored-by: Vale <58179315+DelosIX@users.noreply.github.com>
2022-07-15 17:21:36 -04:00
alwaysintreble
82850d7f66 Ror2: reduce locations to 250 and mark legendary items as useful (#776)
* reduce total locations to 250

* minor styling cleanup. mark legendary items as useful

* 😡

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
2022-07-15 17:19:36 -04:00
Yussur Mustafa Oraji
86112351a6 sm64ex: Adapt area_connections slotdata Format (#767) 2022-07-15 20:04:26 +02:00
black-sliver
ce789d1e3e SoE: texts, energy core, fragments, useful (#777)
* fix missing fields in custom prog balancing option
* fix typos and pep8
* update and implement pyevermizer 0.41.3
  * allow randomizing energy core
  * add energy core fragments (turn in at Prof. Ruffleberg)
  * rename some items to avoid confusion
  * differentiate between progression and useful
* remove obsolete 'Bazooka' group
* don't add items to the pool that get removed
2022-07-15 18:01:07 +02:00
Fabian Dill
73fb1b8074 Subnautica: updates (#759)
* Subnautica: add more goals

* Subnautica: fix wrongly positioned Databox

* Subnautica: allow techs to remain vanilla

* Subnautica: make zipimport compatible

* Subnautica: force two Seaglide fragments into local sphere 1
2022-07-15 17:41:53 +02:00
black-sliver
8e15fe51b6 Put common options first (#774)
* this applies to yaml and webhost
* this allows overwriting common options from the world
2022-07-15 06:54:29 +02:00
Fabian Dill
aa954b776d MultiServer: add /status and allow status command to dynamically filter for Tags 2022-07-15 02:52:26 +02:00
jsd1982
76f6eb1434 SNIClient: update default SNI port from 8080 to 23074 2022-07-15 02:51:36 +02:00
Yussur Mustafa Oraji
e38308bac3 sm64ex: Allow setting Big Star Door requirements (#773)
* sm64ex: Allow setting Big Star Door requirements

* sm64ex: Lower requirements for StarsToFinish
2022-07-14 18:37:14 +02:00
SoldierofOrder
e804f592de SC2: Windows ".dll missing" fix and fix for finding SC2 install automatically (#721) 2022-07-14 09:51:00 +02:00
Fabian Dill
6e0a0c5c4a Core: skip second sanity check when pushing an item into a location (-O) (#745) 2022-07-14 09:46:03 +02:00
alwaysintreble
122590fc68 lttp: move open pyramid to new options system (#762) 2022-07-14 09:39:53 +02:00
lordlou
c806366469 Sm comeback too strict (#755) 2022-07-14 09:37:45 +02:00
Zach Parks
0d3bd6e2e8 gitignore general Windows/macOS files (#763) 2022-07-10 19:24:07 +00:00
Fabian Dill
beac0b1acd Requirements: update some modules 2022-07-10 19:37:50 +02:00
Bicoloursnake
1cc9c7a469 Doc: Add english mac guide (running from source) (#744)
* Create RunFromSourceGuideForMac.md

* Update and rename RunFromSourceGuideForMac.md to docs/RunFromSourceGuideForMac.md

* Clarified the source code download.

* Rename docs/RunFromSourceGuideForMac.md to worlds/generic/docs/RunFromSourceGuideForMac.md

* Update __init__.py

* Noted the case where a user might want EnemizerCLI

* Updated document to reflect requested changes

Updated to reflect the requested changes as well as including some information on virtual environments.

* Added Capital Letters to SNIClient.py

* Reworked Document Structure

Numeric order of lists now makes sense and changed the virtual environment section to match Archipelago tradition.

* Update __init__.py

* Minor Changes for clarity's sake

* Renamed file to make webhost happy

* Changed mac guide filename
2022-07-10 03:16:41 +02:00
CaitSith2
17db0805a7 Allow potentially all rocket-part ingredients to be fluids. (#753) 2022-07-09 12:35:38 +02:00
CaitSith2
2f53972c85 Factorio: fix accidental removal of fluids from make_balanced_recipe (#754) 2022-07-08 15:35:33 +02:00
black-sliver
9ac780102e Subnautica: display item_pool as Item Pool on the settings page 2022-07-07 21:22:24 +02:00
Fabian Dill
60b80083e0 LttP: fix shop inventory corruption in upgrade fairy 2022-07-07 13:25:17 +02:00
alwaysintreble
8597b04c41 WebHost: Advanced guide cleanup (#725)
* advanced yaml cleanup

* Update advanced_settings_en.md

* i hate this game now

* formatting reverting
2022-07-06 16:06:32 -05:00
Fabian Dill
6a60c46a99 Subnautica: fix generation crash on valuable item pool (#739) 2022-07-06 17:04:22 -04:00
Fabian Dill
5c2163a1a7 WebHost: fix comment typo 2022-07-06 22:32:33 +02:00
Hussein Farran
a49bcd618d Dev Docs: Add SA2B and SC2 to network diagram (#719)
* Add SA2B and SC2 to network diagram

* Remove jpg version of image.

* Fix png of image... Github web editor borked it

* Update network diagram.svg

* We're back to light mode, friends.

Use SVG and JPG that are valid and let you zoom in properly.
2022-07-06 16:12:53 -04:00
Doug Hoskisson
d76b41afe7 RL: Rename Rogue Legacy Folder (#452)
* rename rogue legacy
"`rogue-legacy` is not a valid python module name"

* revert rename of the documentation file
2022-07-06 14:18:28 -05:00
Sunny Bat
ab2b635a77 Update Raft for Final Chapter (#724) 2022-07-06 04:37:08 +02:00
strotlog
7072c7bd45 docs: fix 2 URLs (#738)
* URL of image in Alttp ES tutorial
* Link to RA download in SMZ3 EN tutorial
2022-07-04 10:49:25 +02:00
Alchav
530c5500c3 Break out of fill loop if locations is empty (#690) 2022-07-03 17:11:11 +02:00
Daniel Grace
8870b577d0 Hollow Knight June 2022 Updates (#720)
This is a combined PR for assorted Hollow Knight updates for June 2022 that have cleared testing. It supersedes any HK-exclusive PRs open by myself or @Alchav unless stated otherwise.

Summary of changes below:

 * Implement Split Claw, Split Cloak, Split Superdash, Randomize Nail, Randomize Focus, Randomize Swim and Elevator 
 * Pass options (@Alchav)
 * Add support for Deathlink with three different modes (@dewiniaid)
 * Add customizable additional shop slots per-shop (@Alchav) and overall (@dewiniaid)
 * Overhaul shop cost output to be more generic and account for all locations with standard costs (such as Stag Stations, Cornifer, and Divine) (@dewiniaid)
 * Add "CostSanity", allowing random prices using any cost type to be chosen for any location with a cost. (e.g. a Stag station requiring 15 grubs to obtain an item)
 * Item classification fixes (Map and Journal items are fillter, Mask Shards/Pale Ore/Vessel Fragments are useful) (@Alchav)
 * Fix Ijii -> Jiji (@Alchav )
 * General code quality updates

The above changes are only for the HK world.
2022-07-03 17:10:10 +02:00
Colin Lenzen
7d85ab471a [Timespinner] Rename flag and add tiered loot settings (#699) 2022-07-03 17:05:44 +02:00
Fabian Dill
3205cbf932 Generate: convert plando settings to an IntFlag with error reporting for unknown plando names (#735) 2022-07-03 14:11:52 +02:00
Fabian Dill
b9fb4de878 BaseClasses: make ItemClassification properties faster 2022-07-02 13:56:35 +02:00
Jarno Westhof
bcd7096e1d [The Witness] Update data_version as it was forgotten for 0.3.3
# Conflicts:
#	worlds/witness/docs/setup_en.md
2022-07-02 12:19:08 +02:00
strotlog
b206f2846a SNES games: use JPN as abbreviation for Japan/Japanese 2022-07-02 12:16:15 +02:00
CaitSith2
8a8bc6aa34 Factorio: Fix impossible seeds for rocket-part recipes as well. (#733) 2022-07-01 00:40:31 +02:00
black-sliver
bce7c258c3 CI: update Enemizer to 7.0.1 2022-06-30 22:55:05 +02:00
Fabian Dill
cea7278faf LttP: now that Enemizer allows for AP rom name, rename it. (#730)
* LttP: now that Enemizer allows for AP rom name, rename it.

* LttP: fix missing Enemizer message parenthesis
2022-06-30 10:00:37 -07:00
alwaysintreble
d7a9b98ce8 fix glossary link on sitemap 2022-06-29 22:08:38 +02:00
Alchav
7dcde12e2e Revert SC2 item classifications 2022-06-29 12:15:19 +02:00
black-sliver
ba2a5c4744 MC: add non-windows install to docs (#713)
* MC: add non-windows install to docs

* MC: better link naming for non-windows doc

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

* MC: doc change manual forge link to index

By removing the direct link to the version we avoid having to update it all the time and users will have to check the other version numbers for manual installation anyway.

Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-06-28 19:23:18 +02:00
espeon65536
39ac3c38bf sm64: only apply DDD 100 coin star rule if the location exists (#716) 2022-06-27 23:03:34 -07:00
alwaysintreble
61f751a1db docs: add common terms documentation to website (#680)
* docs: add common terms documentation to website

* minor cleanup

* some rewording and reformatting.

* tighten up world definition clarity

Co-authored-by: Rome Reginelli <mduo13@gmail.com>

* Clarify seed definition a bit better

Co-authored-by: Rome Reginelli <mduo13@gmail.com>

* add text for "out of logic" and that slot names must be unique

* rename common terms to glossary

Co-authored-by: Rome Reginelli <mduo13@gmail.com>
2022-06-27 23:34:47 -04:00
alwaysintreble
5f2193f2e4 ror2: update setup guide (#671)
* ror2: remove yaml template from guide and link to player settings page. Add documentation on chat client

* ror2: copy paste the good config description like everyone else.
2022-06-27 21:05:55 -04:00
Daniel Grace
98b714f84a HK: Add options for Deathlink. (#672) 2022-06-27 21:05:29 -04:00
alwaysintreble
2a0198b618 multiserver: allow !release as an alias for !forfeit (#693)
* multiserver: allow `!release` as an alias for `!forfeit`

* create `/release` command. Add some periods to messages that print in console and point users to release

* Add a missing space on line 1135

Co-authored-by: Chris Wilson <chris@legendserver.info>
2022-06-27 20:59:42 -04:00
The T
cd9f8f3119 SM64: DDD 100 Coins in Entrance Rando should expect sub removal (#711)
I brought this up in #super-mario-64, and the minor consensus is that 100 Coins is "possible", the same way Red Coins is possible.

According to a FAQ online, DDD has 106 coins. That means you are still required to get at least 5 of the red coins in order to get the 100 coin star. If we already have a rule stating the Red Coins require the sub to be removed (by reaching Bowser in the Fire Sea), it should apply to the 100 coins as well.

The consensus on it being "possible" was that it requires a very specific triple jump. There is no "Strict" category for this since it isn't caps/cannons-based, but it is extremely unreasonable to casual play. If you want to sequence break it, go for it, but I don't think it should be expected.
2022-06-27 07:43:48 -05:00
CaitSith2
37b569eca6 Changes: (#639)
* Changes:

* When client loses connection to the server through no fault of your own, it no longer forgets your username.
* It is now possible to do /connect archipelago://username:password@server:port or to paste archipelago://username:password@server:port into the connect bar and hit connect, and have both the username/password filled in that way.

* Switch checksfinder client to getting username from url if suppplied by url.

* Correct the print statement
2022-06-27 03:10:41 -07:00
Kippi00
d317111d20 Updates to ALTTP, SM, and SMZ3 guides (#703) 2022-06-27 09:40:01 +02:00
alwaysintreble
3f1d216d28 docs: add reference to text client and commands to a few setup guides (#694) 2022-06-26 21:52:24 -04:00
Joethepic
0ca3d73ae9 makes easier to find where to put the launch options for steam version v6 (#712)
* Update setup_en.md

* Update setup_en.md

* Update setup_en.md

* Update setup_en.md

* typo fix spaces clarification

Co-authored-by: Zach Parks <zach@alliware.com>

* Grammar corrections, clarifications, removed redundant explanations

* Markdown syntax fix

Co-authored-by: Zach Parks <zach@alliware.com>
Co-authored-by: Chris Wilson <chris@legendserver.info>
2022-06-26 19:08:16 -04:00
alwaysintreble
1972d531b9 MC: fix broken brewing image on minecraft tracker (#707) 2022-06-25 14:11:20 -05:00
alwaysintreble
5006c79a00 SM64: Add common mistake and troubleshooting to setup guide (#708) 2022-06-25 14:07:03 -05:00
Daniel Grace
8788ee1aa7 [HK] Further updates for White Palace logic, (#662) 2022-06-25 20:15:03 +02:00
Chris Wilson
17ba73b0b8 Rename author to authors for consistency 2022-06-25 19:10:20 +02:00
rsyh93
0407df83b7 SC2: add Linux setup to tutorial (#679)
also fixes some formatting
2022-06-25 14:12:30 +02:00
alwaysintreble
f140aadafe Alttp: fix broken msu es link (#702) 2022-06-25 13:15:57 +02:00
Grrmo
b41c6185e4 TS: Fix broken link to german setup guide (#700)
The German tutorial link pointed to the English version
2022-06-25 00:29:25 +02:00
NewSoupVi
aa3d7f5e21 Small Witness fixes (#698) 2022-06-24 19:25:23 +02:00
black-sliver
efadf6fdf4 UX: More errors (#697)
* SNIClient: adjuster, ignore missing Tk

* UI: add support for gtk/kde messagebox

* SNIClient: show error when patching fails
2022-06-23 19:26:30 +02:00
black-sliver
12863e9b04 CI: update enemizer and sni (#696) 2022-06-23 19:25:55 +02:00
Chris Wilson
1843618c99 Add stone theme to WebHost (#645)
* Add stone theme

* Fix h2 color, change rogue-legacy to stone theme (approved by Phar)

* Add stone theme preview to world api.md

* Different stone theme preview to match other images
2022-06-22 20:31:40 -04:00
alwaysintreble
4e5071fd68 core: add a link to FAQ to the repo readme 2022-06-22 16:30:43 +02:00
TheCondor07
6e918edce1 SC2: Updated apsc2 version required (#691) 2022-06-22 11:49:00 +02:00
Fabian Dill
80ff5a18b1 remove limit of 1000 Yotta-Joule in EnergyLink (#689) 2022-06-21 20:50:40 +02:00
Fabian Dill
d112cc585f Clients: fix /received calling a dict instead of indexing (#688) 2022-06-21 15:46:35 +02:00
Fabian Dill
3fec33f56c Clients: fix clients not requesting Archipelago DataPackage updates unless spectator is present. 2022-06-21 09:02:11 +02:00
Alchav
68674deb00 FF1 - classify some items as useful (#669) 2022-06-20 21:17:57 +02:00
PoryGone
a9e530721d SA2B v1.1.0 (#673)
Co-authored-by: RaspberrySpaceJam <tyler.summers@gmail.com>
2022-06-20 21:12:13 +02:00
black-sliver
03e9034a98 Server: minify cmd json
This saves between 7 and 15% where compression is unavailable.
2022-06-20 07:52:21 +02:00
Daniel Grace
6970c5ce97 HK: Bugfix shop requirements to be >= rather than >.
This was causing off-by-one errors, which were problematic if e.g. a Grubfather slot wanted all 46 grubs.
2022-06-20 07:46:25 +02:00
alwaysintreble
10b3803a7f ror2: correctly mark Dio's as progression and mark equipment as useful 2022-06-19 22:26:48 +02:00
Fabian Dill
a7e8c82633 Factorio: more condensed raw_recipes creation
(by black-sliver)
2022-06-19 21:55:03 +02:00
Fabian Dill
6d4c4295b3 Factorio: use resources data 2022-06-19 21:48:30 +02:00
black-sliver
47edc356ad api.md update and rename (#676)
* api.md: update for ItemClassification

* world api.md: rename from api.md
2022-06-19 15:19:46 +02:00
black-sliver
b551e3a2ad SoE: change default prog balancing to 30 2022-06-19 14:17:42 +02:00
black-sliver
a9c32bc2e2 MinecraftClient: Linux fixes (#668)
* MC: open file selector if client is run without apmc

* MC: linux fixes

* we don't use shell anymore
* use user_path for forge_dir. Unless read-only, this is the same as what cwd is set to.
2022-06-19 04:54:10 -07:00
alwaysintreble
60c7be87f8 lttp: update requirement version for lttp template yaml 2022-06-19 01:59:50 +02:00
Fabian Dill
2bac78b4a4 Factorio: manual crude-oil recipe seems no longer needed and actually messed with costs 2022-06-18 13:57:28 +02:00
Fabian Dill
c4769eeebb Factorio: load fluids from exported data 2022-06-18 13:40:10 +02:00
espeon65536
51341f6255 MC client: use user_path to fix appimage permissions 2022-06-18 13:21:54 +02:00
Daniel Grace
c7a32dc91b Sort hints by found/not found and then other world/own world. (#642)
This updates notify_hints() as follows:

  - Sort hints by their 'found' attribute in reverse during the first
    iteration, so items not found will show at the bottom.
  - Store a tuple of (hint, hint.as_network_message()) in concerns rather
    than just the hint so the raw hint data remains available for later
    sorting.
  - Do the logging.info call as part of this iteration instead of doing
    a second iteration pass that does nothing but logging.
  - Iterate over concerns (and look up connected clients) rather than
    iterating over all clients (and checking for concerns)
2022-06-18 09:19:08 +02:00
black-sliver
3623678c93 Launcher: always use kvui 2022-06-18 09:17:10 +02:00
Fabian Dill
a5d516e179 Factorio: fix impossible recipes requiring stacking non-stacking items
Factorio: speedup load time
2022-06-18 09:15:14 +02:00
black-sliver
2045905c9b setup.py: fix setuptools>=61 compatibility
Closes ArchipelagoMW/Archipelago#391
2022-06-17 15:09:58 +02:00
Fabian Dill
26c027a075 Core: downgrade item classification to int before writing to file 2022-06-17 06:10:30 +02:00
Fabian Dill
b86ee20f3f Core: fix ItemLinks setting advancement flag 2022-06-17 05:26:11 +02:00
Fabian Dill
50c75e9684 Core: increment version 2022-06-17 03:57:02 +02:00
Fabian Dill
d87c3d5323 LttP: update manual yaml 2022-06-17 03:48:54 +02:00
Fabian Dill
247f674749 Network remove roominfo players (#661) 2022-06-17 03:34:50 +02:00
Fabian Dill
74fe03414c HK: extractor now needs to check for BOM 2022-06-17 03:25:08 +02:00
Fabian Dill
65d213c494 kivy: include in frozen library zip 2022-06-17 03:24:38 +02:00
Fabian Dill
05a51346f9 LttP: fix Ganon's Tower trash prefill ignoring item_rules (#648) 2022-06-17 03:24:15 +02:00
Fabian Dill
6c525e1fe6 Core: move multiple Item properties into a single Flag (#638) 2022-06-17 03:23:27 +02:00
Fabian Dill
5be00e28dd Tests: always display all warnings
WebHost: fix a warning about new cache names
2022-06-17 03:22:43 +02:00
Fabian Dill
d81dbbd951 CommonClient: revamp DataPackage handling 2022-06-17 03:22:20 +02:00
Fabian Dill
83dee9d667 MultiServer: introduce LocationScouts create_as_hint -> only_new 2022-06-17 03:21:33 +02:00
NewSoupVi
7d79cff66f The Witness - 0.3.3 features and fixes (#617)
New option: "Early Secret Area" (Opens a door to the Challenge Area from the start of the game)
New option: Victory Conditions "Mountaintop Box Short" and "Mountaintop Box Long"
New options: Number of Lasers of Mountain, Number of Lasers for Challenge
New option & item: Add some number of "Puzzle Skips", which let you skip one puzzle in the game

Many logic fixes
2022-06-16 03:04:45 +02:00
Alchav
0a63bd0fc6 Meritous get_filler_item_name 2022-06-15 19:05:48 +02:00
Fabian Dill
55d8c8c928 Generate: ignore files starting with ., something about Macs having a .DS_STORE or something. (#656)
* Generate: ignore files starting with ., something about Macs having a .DS_STORE or something.

* Generate: .name is important
2022-06-14 18:10:41 -07:00
Fabian Dill
681f7041dc Tracker: fix order received column being empty 2022-06-14 08:13:02 -07:00
Kono Tyran
d5f15e6408 fix spaces in folder names failing to launch forge. 2022-06-14 06:56:47 -07:00
Fabian Dill
70d510dff8 Options: fix all games templates breaking due to invalid progression balancing 2022-06-14 03:56:02 +02:00
CaitSith2
2a5c128267 ChecksFinder Client refactored to import CommonClient components. 2022-06-14 01:38:10 +02:00
Daniel Grace
e5a1052089 Hollow Knight updates (goals, WP/POP, etc.) (#438)
* Hollow Knight updates:

- Add configurable goals (Any, THK, Siblings, Radiance)
  - Change base logic to require Opened_Black_Egg_Temple instead of
    requiring 3 dreamers.  This is future-proof for transition rando,
    where Black Egg might not have been located yet.
  - Add combat logic for THK and Radiance on par with Rando4's boss logic,
    so itemless HK shouldn't be required.
- Existing completion logic now uses Black_Egg_te

- Add White Palace options
  (Exclude, King Fragment Only, No Path of Pain, Include)
  - Excluded WP may still be required for King Fragment if Charms are
    not randomized
  - Simply don't place WP locations that are excluded
  - Distinguish between POP locations (required for POP), WP checks (
    actual item locations), WP transitions (relevant for future transition
    rando), and WP events (logically required to reach King Fragment)
  - Many transitions were listed twice.  Remove duplicates.
  - Sort transitions by scene

- For randomizable locations that have no logical significance when not
    randomized, simply skip adding them to the pool entirely for
    theoretically faster generation.

* Hollow Knight updates

  - Support random starting geo up to 1000 geo.
  - Always include locations rather than dropping unrandomized "logicless"
    ones, as it is required to best support same-slot coop.
2022-06-13 08:23:03 +02:00
Fabian Dill
8c64f6221e WebHost: update Flask-Limiter 2022-06-13 08:20:17 +02:00
Fabian Dill
0869a2acc3 SNIClient: prevent hang on exit if waiting on devices from SNI 2022-06-13 08:18:52 +02:00
Fabian Dill
e7ea827f02 Options: introduce SpecialRange (#630)
* Options: introduce SpecialRange

* Include SpecialRange data in player-settings and weighted-settings JSON files

* Add support for SpecialRange to player-settings pages

* Add support for SpecialRange options to weighted-settings. Also fixed a bug which would cause the page to crash if an unknown setting was detected.

Co-authored-by: Chris Wilson <chris@legendserver.info>
2022-06-12 17:33:14 -04:00
Joethepic
84b6ece31d Itemlink tutorial improvement (#611)
* Update Items.py

* Update advanced_settings_en.md

* Update Items.py

* Update advanced_settings_en.md

* Update advanced_settings_en.md

* improve consistency

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>

* fix formating on game setting in example

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>

* change version

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>

* Update advanced_settings_en.md

* Update advanced_settings_en.md

* Update advanced_settings_en.md

* tutorials: add description for game weight and properly document item links

* tutorials: add description for null replacement

* Update worlds/generic/docs/advanced_settings_en.md

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>

* Update advanced_settings_en.md

* Update advanced_settings_en.md

* Update worlds/generic/docs/advanced_settings_en.md

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

* Update worlds/generic/docs/advanced_settings_en.md

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

* Update worlds/generic/docs/advanced_settings_en.md

Co-authored-by: Hussein Farran <hmfarran@gmail.com>

Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-06-12 17:24:19 -04:00
Zach Parks
1bcc5b6582 WebHost: Allow "random" to be default option for toggles and choices. (#640) 2022-06-12 07:48:52 +02:00
KonoTyran
c8c025ac34 Minecraft 1.19 (#623) 2022-06-11 23:22:16 +02:00
CaitSith2
d82d70ac97 Fix the possibility of manually assigning 'random' via alias_random 2022-06-11 23:20:56 +02:00
alwaysintreble
3e86fd4e57 Tutorials: hide ArchipIDLE (#622)
* Don't copy files of hidden worlds

* tutorials: hardcode not generating ArchipIDLE tutorial files outside april

* tutorials: ignore hidden worlds unless it's 'Archipelago'

* add parenthesis to prevent ambiguity
2022-06-10 19:49:12 -04:00
Alchav
964eda13cc Fix LTTP filler items (#621) 2022-06-10 13:23:03 +02:00
CaitSith2
c16815b16d Fix Room log 2022-06-10 13:20:35 +02:00
Colin Lenzen
74ee8ec459 [Timespinner] Add Boss Randomization Settings (#598)
* [Timespinner] Add Boss Randomization Settings
2022-06-10 01:07:47 +02:00
t3hf1gm3nt
22ea72c1b2 OOT: Add note about common issue with lua option in the configuration step (#629)
* OOT: Add note about common issue with lua option in the configuration step

More and more people have issues with connecting with OoT because fresh installs of newer versions of Bizhawk show having "Lua+LuaInterface" selected when it actually loads "Nlua+KopiLua" instead until you toggle between the two options. Hopefully adding this bolded note will help new users avoid this problem in the future.
2022-06-10 00:48:05 +02:00
Zach Parks
613dc4184a ALTTP: Updates to setup documents (#628)
Co-authored-by: alwaysintreble <mmmcheese158@gmail.com>
2022-06-10 00:47:01 +02:00
Fabian Dill
9a471aff1b WebHost: request maximum amount of file handles from the system for autolauncher. (#625)
* WebHost: request maximum amount of file handles from the system for autolauncher.

* WebHostLib: wrap resource import into try to restore windows compatibility
2022-06-09 13:14:12 -07:00
Fabian Dill
e69e42cabc SNIClient: sort devices for consistent key
SNIClient: get rid of * import
2022-06-09 13:05:30 -07:00
Fabian Dill
1281426075 HK: allow shuffling charm costs, instead of randomizing. (#441) 2022-06-09 00:27:43 +02:00
Fabian Dill
8b1baafddf SC2: send ItemLink messages to ingame as well 2022-06-09 00:20:36 +02:00
Kippi00
ee65d7e5fa Document multi-game YAMLs (#619) 2022-06-08 18:15:47 -04:00
Chris Wilson
df0ae205cd Update LICENSE files for WebHost assets (#616) 2022-06-08 17:17:50 -04:00
Fabian Dill
1cbd384569 Generate: sort input files, preventing arbitrary order from OS layer. 2022-06-08 00:36:13 +02:00
Fabian Dill
e47527087e WebHost: some updates (#603)
* WebHost: Make custom server prefer ipv4 for display

* WebHost: Make server retry saving in case of connection issues

* WebHost: fix autolaunch guardians getting stuck waiting for the oldest two rooms.
Probably not related to the issues of the system itself getting stuck, but should be fixed anyway.

* WebHost: logfile is meant to be guarded by access cookie

* WebHost: set patch target to null if port is not valid, disabling auto-connect
2022-06-08 00:35:35 +02:00
Fabian Dill
517a2db9d8 Clients: some improvements (#602)
* Clients: some improvements
SNIClient is the only client that uses slow_mode, so its definition should be moved there.
type info for CommandProcessor was int for some reason.
Moved a lot of type info from init to class body, making it easier for type checkers to find.
getLogger("") and getLogger(None) is technically different, just happens that our root logger is "", fixed it in case of future confusion though.

* Logging: log that init_logging was run and what the current AP version is.
2022-06-08 00:34:45 +02:00
black-sliver
fbf993566d Clients: UX improvements (#615) 2022-06-07 00:15:08 +02:00
black-sliver
25bea47872 Appimage: include libssl (#613) 2022-06-05 22:52:16 +02:00
black-sliver
78f22e895e requirements: update cx-Freeze, fix compatibility
this conflicts with and replaces commit #f9b12b51080c7bbbf3d52c79453ac6c8222a03c5
2022-06-04 21:12:45 +02:00
black-sliver
fa3925cd74 Ui: add open_filename helper
* native look & feel on Linux (Gnome and KDE)
* falls back to tkinter
2022-06-04 21:12:45 +02:00
black-sliver
d9418d5ce1 Core: move is_linux, _macos, _windows to Utils.py 2022-06-04 21:12:45 +02:00
black-sliver
103f9e0b85 UI: add Utils.messagebox
automatically uses either new kvui.MessageBox or tkinter.messagebox
2022-06-04 21:12:45 +02:00
black-sliver
a2fc3d5b71 AppImage: better compatibility
* old startup script did not work with dash
* add missing libcrypt in cx_freeze
2022-06-04 21:12:45 +02:00
Kono Tyran
c66d64b9d8 update minecraft_en.md wording slightly and minecraft version 2022-06-04 11:32:51 -07:00
TheCondor07
0dd67f40ba SC2: UI update, Relegate No Build Option, and Filler Item Update (#606) 2022-06-03 20:18:36 +02:00
Fabian Dill
f5dc39ddf0 kvui: fix warning about "X missing in __all__" when importing from kivy.base instead of correct module 2022-06-03 07:57:57 -07:00
t3hf1gm3nt
6b47776b11 TS: Add region names to location names, and other location name clarifications (#570)
* Add region names to location names, and other location name clarification changes
2022-06-03 12:27:02 +02:00
strotlog
2b73c7f9e4 config: Use valid default enemizer_path on Linux (and Windows) 2022-06-02 02:15:05 +02:00
Fabian Dill
4558ac66fa SNIClient: run adjuster for new aplttp file type 2022-06-01 08:30:28 -07:00
Fabian Dill
d0a98949f5 LttP: split Retro into Retro Bows and Retro Caves (#588) 2022-06-01 08:29:21 -07:00
Fabian Dill
e13e7f286c Tracker: fix ItemLinks items not being attributed to inventory 2022-06-01 08:28:16 -07:00
Fabian Dill
0045e3f9f7 WebHost: update flask-caching 2022-06-01 08:26:30 -07:00
Fabian Dill
ff608b72a2 Tests: add test to check for typo'd item name group definitions (#594)
* Tests: add test to check for typo'd item name group definitions
Factorio: item *name* group was pointing to IDs instead.
Server: prevent crash when using Event-filled item name group

* Server: prevent crash when /hint'ing for an item name group with events
2022-06-01 08:25:40 -07:00
Fabian Dill
19c3c8056b Server: remove compat to ~0.2 unversioned save data
If the savegame was loaded in the last few months, it will have already been upgraded.
2022-06-01 08:21:54 -07:00
black-sliver
d31c24bbf7 Doc: deprecate datapackage_version 2022-05-30 09:52:12 +02:00
lordlou
768f9497fd Sm remote item fix (#592) 2022-05-30 07:12:01 +02:00
TheCondor07
20be691f36 SC2: GUI Mission Launcher (#586)
* SC2: Functioning Starcraft 2 Mission Launcher UI

* AutoWorld: add .__file__ attribute to AutoWorlds
This tries to help with a recurring easy to make mistake, where ./worlds/myworld does not exist in frozen form and is instead ./lib/worlds/myworld

* SC2: get .kv file path correctly when frozen too

Co-authored-by: TheCondor07 <TheCondorian07@gmail.com>
Co-authored-by: Fabian Dill <fabian.dill@web.de>
2022-05-30 07:11:01 +02:00
Berserker66
3dd3f045e6 WebHost: use non-blocking file lock on unix, just like windows 2022-05-29 08:00:28 -07:00
black-sliver
6d3538a35b AppImage: fix build (#589)
* CI: build: use ARCH= for AppImage

* WebHost: pin flask-caching

until https://github.com/pallets-eco/flask-caching/pull/352 is merged or fixed otherwise
2022-05-28 23:20:46 +02:00
Fabian Dill
1a0bfecb5f LttP: convert vendors hint into separate scams option 2022-05-28 20:08:06 +02:00
2455 changed files with 427680 additions and 79573 deletions

5
.coveragerc Normal file
View File

@@ -0,0 +1,5 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
if typing.TYPE_CHECKING:

35
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Bug Report
description: File a bug report.
title: "Bug: "
labels:
- bug / fix
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! If this bug occurred during local generation check your
Archipelago install for a log (probably `C:\ProgramData\Archipelago\logs`)
and upload it with this report, as well as all yaml files used.
- type: textarea
id: what-happened
attributes:
label: What happened?
validations:
required: true
- type: textarea
id: expected-results
attributes:
label: What were the expected results?
validations:
required: true
- type: dropdown
id: version
attributes:
label: Software
description: Where did this bug occur?
options:
- Website
- Local generation
- While playing
validations:
required: true

View File

@@ -0,0 +1,17 @@
name: Feature Request
description: Request a feature!
title: "Category: "
labels:
- enhancement
body:
- type: markdown
attributes:
value: |
Please replace `Category` in the title with what this feature will be targeting, such as Core generation,
website, documentation, or a game.
Note: this is not for requesting new games to be added. If you would like to request a game, the best place to
ask is about it is in the [discord](https://archipelago.gg/discord).
- type: textarea
id: feature
attributes:
label: What feature would you like to see?

10
.github/ISSUE_TEMPLATE/task.yaml vendored Normal file
View File

@@ -0,0 +1,10 @@
name: Task
description: Submit a task to be done. If this is not targeting core, it should likely be elsewhere.
title: "Core: "
labels:
- core
- enhancement
body:
- type: textarea
attributes:
label: What task needs to be completed?

31
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
'is: documentation':
- changed-files:
- all-globs-to-all-files: '{**/docs/**,**/README.md}'
'affects: webhost':
- changed-files:
- all-globs-to-any-file: 'WebHost.py'
- all-globs-to-any-file: 'WebHostLib/**/*'
'affects: core':
- changed-files:
- all-globs-to-any-file:
- '!*Client.py'
- '!README.md'
- '!LICENSE'
- '!*.yml'
- '!.gitignore'
- '!**/docs/**'
- '!typings/kivy/**'
- '!test/**'
- '!data/**'
- '!.run/**'
- '!.github/**'
- '!worlds_disabled/**'
- '!worlds/**'
- '!WebHost.py'
- '!WebHostLib/**'
- any-glob-to-any-file: # exceptions to the above rules of "stuff that isn't core"
- 'worlds/generic/**/*.py'
- 'worlds/*.py'
- 'CommonClient.py'

12
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,12 @@
Please format your title with what portion of the project this pull request is
targeting and what it's changing.
ex. "MyGame4: implement new game" or "Docs: add new guide for customizing MyGame3"
## What is this fixing or adding?
## How was this tested?
## If this makes graphical changes, please attach screenshots.

27
.github/pyright-config.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"include": [
"type_check.py",
"../worlds/AutoSNIClient.py",
"../Patch.py"
],
"exclude": [
"**/__pycache__"
],
"stubPath": "../typings",
"typeCheckingMode": "strict",
"reportImplicitOverride": "error",
"reportMissingImports": true,
"reportMissingTypeStubs": true,
"pythonVersion": "3.8",
"pythonPlatform": "Windows",
"executionEnvironments": [
{
"root": ".."
}
]
}

15
.github/type_check.py vendored Normal file
View File

@@ -0,0 +1,15 @@
from pathlib import Path
import subprocess
config = Path(__file__).parent / "pyright-config.json"
command = ("pyright", "-p", str(config))
print(" ".join(command))
try:
result = subprocess.run(command)
except FileNotFoundError as e:
print(f"{e} - Is pyright installed?")
exit(1)
exit(result.returncode)

View File

@@ -0,0 +1,80 @@
name: Analyze modified files
on:
pull_request:
paths:
- "**.py"
push:
paths:
- "**.py"
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.event.after }}
jobs:
flake8-or-mypy:
strategy:
fail-fast: false
matrix:
task: [flake8, mypy]
name: ${{ matrix.task }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Determine modified files (pull_request)"
if: github.event_name == 'pull_request'
run: |
git fetch origin $BASE $HEAD
DIFF=$(git diff --diff-filter=d --name-only $BASE...$HEAD -- "*.py")
echo "modified files:"
echo "$DIFF"
echo "diff=${DIFF//$'\n'/$' '}" >> $GITHUB_ENV
- name: "Determine modified files (push)"
if: github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000'
run: |
git fetch origin $BEFORE $AFTER
DIFF=$(git diff --diff-filter=d --name-only $BEFORE..$AFTER -- "*.py")
echo "modified files:"
echo "$DIFF"
echo "diff=${DIFF//$'\n'/$' '}" >> $GITHUB_ENV
- name: "Treat all files as modified (new branch)"
if: github.event_name == 'push' && github.event.before == '0000000000000000000000000000000000000000'
run: |
echo "diff=." >> $GITHUB_ENV
- uses: actions/setup-python@v5
if: env.diff != ''
with:
python-version: 3.8
- name: "Install dependencies"
if: env.diff != ''
run: |
python -m pip install --upgrade pip ${{ matrix.task }}
python ModuleUpdate.py --append "WebHostLib/requirements.txt" --force --yes
- name: "flake8: Stop the build if there are Python syntax errors or undefined names"
continue-on-error: false
if: env.diff != '' && matrix.task == 'flake8'
run: |
flake8 --count --select=E9,F63,F7,F82 --show-source --statistics ${{ env.diff }}
- name: "flake8: Lint modified files"
continue-on-error: true
if: env.diff != '' && matrix.task == 'flake8'
run: |
flake8 --count --max-complexity=14 --max-doc-length=120 --max-line-length=120 --statistics ${{ env.diff }}
- name: "mypy: Type check modified files"
continue-on-error: true
if: env.diff != '' && matrix.task == 'mypy'
run: |
mypy --follow-imports=silent --install-types --non-interactive --strict ${{ env.diff }}

View File

@@ -2,7 +2,24 @@
name: Build
on: workflow_dispatch
on:
push:
paths:
- '.github/workflows/build.yml'
- 'setup.py'
- 'requirements.txt'
- '*.iss'
pull_request:
paths:
- '.github/workflows/build.yml'
- 'setup.py'
- 'requirements.txt'
- '*.iss'
workflow_dispatch:
env:
ENEMIZER_VERSION: 7.1
APPIMAGETOOL_VERSION: 13
jobs:
# build-release-macos: # LF volunteer
@@ -10,73 +27,85 @@ jobs:
build-win-py38: # RCs will still be built and signed by hand
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Download run-time dependencies
run: |
Invoke-WebRequest -Uri https://github.com/alttpo/sni/releases/download/v0.0.79/sni-v0.0.79-windows-amd64.zip -OutFile sni.zip
Expand-Archive -Path sni.zip -DestinationPath SNI -Force
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/6.4/win-x64.zip -OutFile enemizer.zip
Invoke-WebRequest -Uri https://github.com/Ijwu/Enemizer/releases/download/${Env:ENEMIZER_VERSION}/win-x64.zip -OutFile enemizer.zip
Expand-Archive -Path enemizer.zip -DestinationPath EnemizerCLI -Force
- name: Build
run: |
python -m pip install --upgrade pip setuptools==60.10.0 # 61 does not work with the current layout
pip install -r requirements.txt
python setup.py build --yes
$NAME="$(ls build)".Split('.',2)[1]
python -m pip install --upgrade pip
python setup.py build_exe --yes
$NAME="$(ls build | Select-String -Pattern 'exe')".Split('.',2)[1]
$ZIP_NAME="Archipelago_$NAME.7z"
echo "$NAME -> $ZIP_NAME"
echo "ZIP_NAME=$ZIP_NAME" >> $Env:GITHUB_ENV
New-Item -Path dist -ItemType Directory -Force
cd build
Rename-Item exe.$NAME Archipelago
Rename-Item "exe.$NAME" Archipelago
7z a -mx=9 -mhe=on -ms "../dist/$ZIP_NAME" Archipelago
Rename-Item Archipelago "exe.$NAME" # inno_setup.iss expects the original name
- name: Store 7z
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ${{ env.ZIP_NAME }}
path: dist/${{ env.ZIP_NAME }}
retention-days: 7 # keep for 7 days, should be enough
- name: Build Setup
run: |
& "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe" inno_setup.iss /DNO_SIGNTOOL
if ( $? -eq $false ) {
Write-Error "Building setup failed!"
exit 1
}
$contents = Get-ChildItem -Path setups/*.exe -Force -Recurse
$SETUP_NAME=$contents[0].Name
echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV
- name: Store Setup
uses: actions/upload-artifact@v4
with:
name: ${{ env.SETUP_NAME }}
path: setups/${{ env.SETUP_NAME }}
retention-days: 7 # keep for 7 days, should be enough
build-ubuntu1804:
runs-on: ubuntu-18.04
build-ubuntu2004:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
# - copy code below to release.yml -
- uses: actions/checkout@v4
- name: Install base dependencies
run: |
sudo apt update
sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0
sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below
- name: Get a recent python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.11'
- name: Install build-time dependencies
run: |
echo "PYTHON=python3.9" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage
echo "PYTHON=python3.11" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
echo -e '#/bin/sh\n./squashfs-root/AppRun "$@"' > appimagetool
chmod a+rx appimagetool
- name: Download run-time dependencies
run: |
wget -nv https://github.com/black-sliver/sni/releases/download/v0.0.78-2/sni-v0.0.78-2-manylinux2014-amd64.tar.xz
tar xf sni-*.tar.xz
rm sni-*.tar.xz
mv sni-* SNI
wget -nv https://github.com/Ijwu/Enemizer/releases/download/6.4/ubuntu.16.04-x64.7z
wget -nv https://github.com/Ijwu/Enemizer/releases/download/$ENEMIZER_VERSION/ubuntu.16.04-x64.7z
7za x -oEnemizerCLI/ ubuntu.16.04-x64.7z
- name: Build
run: |
# pygobject is an optional dependency for kivy that's not in requirements
"${{ env.PYTHON }}" -m pip install --upgrade pip virtualenv PyGObject setuptools==60.10.0 # setuptools same as windows
# charset-normalizer was somehow incomplete in the github runner
"${{ env.PYTHON }}" -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python setup.py build --yes bdist_appimage --yes
"${{ env.PYTHON }}" -m pip install --upgrade pip PyGObject charset-normalizer
python setup.py build_exe --yes bdist_appimage --yes
echo -e "setup.py build output:\n `ls build`"
echo -e "setup.py dist output:\n `ls dist`"
cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd ..
@@ -84,14 +113,19 @@ jobs:
(cd build && DIR_NAME="`ls | grep exe`" && mv "$DIR_NAME" Archipelago && tar -czvf ../dist/$TAR_NAME Archipelago && mv Archipelago "$DIR_NAME")
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
# - copy code above to release.yml -
- name: Build Again
run: |
source venv/bin/activate
python setup.py build_exe --yes
- name: Store AppImage
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ${{ env.APPIMAGE_NAME }}
path: dist/${{ env.APPIMAGE_NAME }}
retention-days: 7
- name: Store .tar.gz
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ${{ env.TAR_NAME }}
path: dist/${{ env.TAR_NAME }}

View File

@@ -14,9 +14,17 @@ name: "CodeQL"
on:
push:
branches: [ main ]
paths:
- '**.py'
- '**.js'
- '.github/workflows/codeql-analysis.yml'
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
paths:
- '**.py'
- '**.js'
- '.github/workflows/codeql-analysis.yml'
schedule:
- cron: '44 8 * * 1'
@@ -35,11 +43,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,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@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -64,4 +72,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -0,0 +1,46 @@
name: Label Pull Request
on:
pull_request_target:
types: ['opened', 'reopened', 'synchronize', 'ready_for_review', 'converted_to_draft', 'closed']
branches: ['main']
permissions:
contents: read
pull-requests: write
jobs:
labeler:
name: 'Apply content-based labels'
if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize'
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
sync-labels: false
peer_review:
name: 'Apply peer review label'
needs: labeler
if: >-
(github.event.action == 'opened' || github.event.action == 'reopened' ||
github.event.action == 'ready_for_review') && !github.event.pull_request.draft
runs-on: ubuntu-latest
steps:
- name: 'Add label'
run: "gh pr edit \"$PR_URL\" --add-label 'waiting-on: peer-review'"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
unblock_draft_prs:
name: 'Remove waiting-on labels'
needs: labeler
if: github.event.action == 'converted_to_draft' || github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: 'Remove labels'
run: |-
gh pr edit "$PR_URL" --remove-label 'waiting-on: peer-review' \
--remove-label 'waiting-on: core-review' \
--remove-label 'waiting-on: world-maintainer' \
--remove-label 'waiting-on: author'
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,29 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: lint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

View File

@@ -7,6 +7,10 @@ on:
tags:
- '*.*.*'
env:
ENEMIZER_VERSION: 7.1
APPIMAGETOOL_VERSION: 13
jobs:
create-release:
runs-on: ubuntu-latest
@@ -14,7 +18,7 @@ jobs:
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # tag x.y.z will become "Archipelago x.y.z"
- name: Create Release
uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
with:
draft: true # don't publish right away, especially since windows build is added by hand
prerelease: false
@@ -25,45 +29,42 @@ jobs:
# build-release-windows: # this is done by hand because of signing
# build-release-macos: # LF volunteer
build-release-ubuntu1804:
runs-on: ubuntu-18.04
build-release-ubuntu2004:
runs-on: ubuntu-20.04
steps:
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
# - code below copied from build.yml -
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install base dependencies
run: |
sudo apt update
sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0
sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below
- name: Get a recent python
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.11'
- name: Install build-time dependencies
run: |
echo "PYTHON=python3.9" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage
echo "PYTHON=python3.11" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/AppImageKit/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
echo -e '#/bin/sh\n./squashfs-root/AppRun "$@"' > appimagetool
chmod a+rx appimagetool
- name: Download run-time dependencies
run: |
wget -nv https://github.com/black-sliver/sni/releases/download/v0.0.78-2/sni-v0.0.78-2-manylinux2014-amd64.tar.xz
tar xf sni-*.tar.xz
rm sni-*.tar.xz
mv sni-* SNI
wget -nv https://github.com/Ijwu/Enemizer/releases/download/6.4/ubuntu.16.04-x64.7z
wget -nv https://github.com/Ijwu/Enemizer/releases/download/$ENEMIZER_VERSION/ubuntu.16.04-x64.7z
7za x -oEnemizerCLI/ ubuntu.16.04-x64.7z
- name: Build
run: |
"${{ env.PYTHON }}" -m pip install --upgrade pip setuptools virtualenv PyGObject # pygobject should probably move to requirements
# pygobject is an optional dependency for kivy that's not in requirements
# charset-normalizer was somehow incomplete in the github runner
"${{ env.PYTHON }}" -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python setup.py build --yes bdist_appimage --yes
"${{ env.PYTHON }}" -m pip install --upgrade pip PyGObject charset-normalizer
python setup.py build_exe --yes bdist_appimage --yes
echo -e "setup.py build output:\n `ls build`"
echo -e "setup.py dist output:\n `ls dist`"
cd dist && export APPIMAGE_NAME="`ls *.AppImage`" && cd ..
@@ -73,7 +74,7 @@ jobs:
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
# - code above copied from build.yml -
- name: Add to Release
uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf
uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a
with:
draft: true # see above
prerelease: false

65
.github/workflows/scan-build.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Native Code Static Analysis
on:
push:
paths:
- '**.c'
- '**.cc'
- '**.cpp'
- '**.cxx'
- '**.h'
- '**.hh'
- '**.hpp'
- '**.pyx'
- 'setup.py'
- 'requirements.txt'
- '.github/workflows/scan-build.yml'
pull_request:
paths:
- '**.c'
- '**.cc'
- '**.cpp'
- '**.cxx'
- '**.h'
- '**.hh'
- '**.hpp'
- '**.pyx'
- 'setup.py'
- 'requirements.txt'
- '.github/workflows/scan-build.yml'
jobs:
scan-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install newer Clang
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh 17
- name: Install scan-build command
run: |
sudo apt install clang-tools-17
- name: Get a recent python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip -r requirements.txt
- name: scan-build
run: |
source venv/bin/activate
scan-build-17 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y
- name: Store report
if: failure()
uses: actions/upload-artifact@v4
with:
name: scan-build-reports
path: scan-build-reports

33
.github/workflows/strict-type-check.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: type check
on:
pull_request:
paths:
- "**.py"
- ".github/pyright-config.json"
- ".github/workflows/strict-type-check.yml"
- "**.pyi"
push:
paths:
- "**.py"
- ".github/pyright-config.json"
- ".github/workflows/strict-type-check.yml"
- "**.pyi"
jobs:
pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: "Install dependencies"
run: |
python -m pip install --upgrade pip pyright==1.1.358
python ModuleUpdate.py --append "WebHostLib/requirements.txt" --force --yes
- name: "pyright: strict check on specific files"
run: python .github/type_check.py

View File

@@ -3,7 +3,25 @@
name: unittests
on: [push, pull_request]
on:
push:
paths:
- '**'
- '!docs/**'
- '!setup.py'
- '!*.iss'
- '!.gitignore'
- '!.github/workflows/**'
- '.github/workflows/unittests.yml'
pull_request:
paths:
- '**'
- '!docs/**'
- '!setup.py'
- '!*.iss'
- '!.gitignore'
- '!.github/workflows/**'
- '.github/workflows/unittests.yml'
jobs:
build:
@@ -18,23 +36,27 @@ jobs:
- {version: '3.8'}
- {version: '3.9'}
- {version: '3.10'}
- {version: '3.11'}
include:
- python: {version: '3.8'} # win7 compat
os: windows-latest
- python: {version: '3.10'} # current
- python: {version: '3.11'} # current
os: windows-latest
- python: {version: '3.11'} # current
os: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python.version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python.version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
pip install pytest pytest-subtests pytest-xdist
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
python Launcher.py --update_settings # make sure host.yaml exists for tests
- name: Unittests
run: |
pytest test
pytest -n auto

47
.gitignore vendored
View File

@@ -4,15 +4,23 @@
*_Spoiler.txt
*.bmbp
*.apbp
*.apl2ac
*.apm3
*.apmc
*.apz5
*.aptloz
*.apemerald
*.pyc
*.pyd
*.sfc
*.z64
*.n64
*.nes
*.smc
*.sms
*.gb
*.gbc
*.gba
*.wixobj
*.lck
*.db3
@@ -20,14 +28,22 @@
*multisave
*.archipelago
*.apsave
*.BIN
*.puml
setups
build
bundle/components.wxs
dist
/prof/
README.html
.vs/
EnemizerCLI/
/Players/
/SNI/
/sni-*/
/appimagetool*
/host.yaml
/options.yaml
/config.yaml
/logs/
@@ -43,7 +59,9 @@ Output Logs/
/freeze_requirements.txt
/Archipelago.zip
/setup.ini
/installdelete.iss
/data/user.kv
/datapackage
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -116,17 +134,23 @@ target/
profile_default/
ipython_config.py
# vim editor
*.swp
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
.venv*
env/
venv/
/venv*/
ENV/
env.bak/
venv.bak/
.code-workspace
shell.nix
# Spyder project settings
.spyderproject
@@ -152,10 +176,25 @@ dmypy.json
# Cython debug symbols
cython_debug/
#minecraft server stuff
# Cython intermediates
_speedups.cpp
_speedups.html
# minecraft server stuff
jdk*/
minecraft*/
minecraft_versions.json
!worlds/minecraft/
#pyenv
# pyenv
.python-version
#undertale stuff
/Undertale/
# OS General Files
.DS_Store
.AppleDouble
.LSOverride
Thumbs.db
[Dd]esktop.ini

View File

@@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Archipelago Unittests" type="tests" factoryName="Unittests">
<module name="Archipelago" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="_new_pattern" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;$PROJECT_DIR$/test&quot;" />
<option name="_new_targetType" value="&quot;PATH&quot;" />
<method v="2" />
</configuration>
</component>

8
AHITClient.py Normal file
View File

@@ -0,0 +1,8 @@
from worlds.ahit.Client import launch
import Utils
import ModuleUpdate
ModuleUpdate.update()
if __name__ == "__main__":
Utils.init_logging("AHITClient", exception_logger="Client")
launch()

517
AdventureClient.py Normal file
View File

@@ -0,0 +1,517 @@
import asyncio
import hashlib
import json
import time
import os
import bsdiff4
import subprocess
import zipfile
from asyncio import StreamReader, StreamWriter, CancelledError
from typing import List
import Utils
from NetUtils import ClientStatus
from Utils import async_start
from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandProcessor, logger, \
get_base_parser
from worlds.adventure import AdventureDeltaPatch
from worlds.adventure.Locations import base_location_id
from worlds.adventure.Rom import AdventureForeignItemInfo, AdventureAutoCollectLocation, BatNoTouchLocation
from worlds.adventure.Items import base_adventure_item_id, standard_item_max, item_table
from worlds.adventure.Offsets import static_item_element_size, connector_port_offset
SYSTEM_MESSAGE_ID = 0
CONNECTION_TIMING_OUT_STATUS = \
"Connection timing out. Please restart your emulator, then restart connector_adventure.lua"
CONNECTION_REFUSED_STATUS = \
"Connection Refused. Please start your emulator and make sure connector_adventure.lua is running"
CONNECTION_RESET_STATUS = \
"Connection was reset. Please restart your emulator, then restart connector_adventure.lua"
CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
CONNECTION_CONNECTED_STATUS = "Connected"
CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
SCRIPT_VERSION = 1
class AdventureCommandProcessor(ClientCommandProcessor):
def __init__(self, ctx: CommonContext):
super().__init__(ctx)
def _cmd_2600(self):
"""Check 2600 Connection State"""
if isinstance(self.ctx, AdventureContext):
logger.info(f"2600 Status: {self.ctx.atari_status}")
def _cmd_aconnect(self):
"""Discard current atari 2600 connection state"""
if isinstance(self.ctx, AdventureContext):
self.ctx.atari_sync_task.cancel()
class AdventureContext(CommonContext):
command_processor = AdventureCommandProcessor
game = 'Adventure'
lua_connector_port: int = 17242
def __init__(self, server_address, password):
super().__init__(server_address, password)
self.freeincarnates_used: int = -1
self.freeincarnate_pending: int = 0
self.foreign_items: [AdventureForeignItemInfo] = []
self.autocollect_items: [AdventureAutoCollectLocation] = []
self.atari_streams: (StreamReader, StreamWriter) = None
self.atari_sync_task = None
self.messages = {}
self.locations_array = None
self.atari_status = CONNECTION_INITIAL_STATUS
self.awaiting_rom = False
self.display_msgs = True
self.deathlink_pending = False
self.set_deathlink = False
self.client_compatibility_mode = 0
self.items_handling = 0b111
self.checked_locations_sent: bool = False
self.port_offset = 0
self.bat_no_touch_locations: [BatNoTouchLocation] = []
self.local_item_locations = {}
self.dragon_speed_info = {}
options = Utils.get_options()
self.display_msgs = options["adventure_options"]["display_msgs"]
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(AdventureContext, self).server_auth(password_requested)
if not self.auth:
self.auth = self.player_name
if not self.auth:
self.awaiting_rom = True
logger.info('Awaiting connection to adventure_connector to get Player information')
return
await self.send_connect()
def _set_message(self, msg: str, msg_id: int):
if self.display_msgs:
self.messages[(time.time(), msg_id)] = msg
def on_package(self, cmd: str, args: dict):
if cmd == 'Connected':
self.locations_array = None
if Utils.get_options()["adventure_options"].get("death_link", False):
self.set_deathlink = True
async_start(self.get_freeincarnates_used())
elif cmd == "RoomInfo":
self.seed_name = args['seed_name']
elif cmd == 'Print':
msg = args['text']
if ': !' not in msg:
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "ReceivedItems":
msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}"
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "Retrieved":
if f"adventure_{self.auth}_freeincarnates_used" in args["keys"]:
self.freeincarnates_used = args["keys"][f"adventure_{self.auth}_freeincarnates_used"]
if self.freeincarnates_used is None:
self.freeincarnates_used = 0
self.freeincarnates_used += self.freeincarnate_pending
self.send_pending_freeincarnates()
elif cmd == "SetReply":
if args["key"] == f"adventure_{self.auth}_freeincarnates_used":
self.freeincarnates_used = args["value"]
if self.freeincarnates_used is None:
self.freeincarnates_used = 0
self.freeincarnates_used += self.freeincarnate_pending
self.send_pending_freeincarnates()
def on_deathlink(self, data: dict):
self.deathlink_pending = True
super().on_deathlink(data)
def run_gui(self):
from kvui import GameManager
class AdventureManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago Adventure Client"
self.ui = AdventureManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def get_freeincarnates_used(self):
if self.server and not self.server.socket.closed:
await self.send_msgs([{"cmd": "SetNotify", "keys": [f"adventure_{self.auth}_freeincarnates_used"]}])
await self.send_msgs([{"cmd": "Get", "keys": [f"adventure_{self.auth}_freeincarnates_used"]}])
def send_pending_freeincarnates(self):
if self.freeincarnate_pending > 0:
async_start(self.send_pending_freeincarnates_impl(self.freeincarnate_pending))
self.freeincarnate_pending = 0
async def send_pending_freeincarnates_impl(self, send_val: int) -> None:
await self.send_msgs([{"cmd": "Set", "key": f"adventure_{self.auth}_freeincarnates_used",
"default": 0, "want_reply": False,
"operations": [{"operation": "add", "value": send_val}]}])
async def used_freeincarnate(self) -> None:
if self.server and not self.server.socket.closed:
await self.send_msgs([{"cmd": "Set", "key": f"adventure_{self.auth}_freeincarnates_used",
"default": 0, "want_reply": True,
"operations": [{"operation": "add", "value": 1}]}])
else:
self.freeincarnate_pending = self.freeincarnate_pending + 1
def convert_item_id(ap_item_id: int):
static_item_index = ap_item_id - base_adventure_item_id
return static_item_index * static_item_element_size
def get_payload(ctx: AdventureContext):
current_time = time.time()
items = []
dragon_speed_update = {}
diff_a_locked = ctx.diff_a_mode > 0
diff_b_locked = ctx.diff_b_mode > 0
freeincarnate_count = 0
for item in ctx.items_received:
item_id_str = str(item.item)
if base_adventure_item_id < item.item <= standard_item_max:
items.append(convert_item_id(item.item))
elif item_id_str in ctx.dragon_speed_info:
if item.item in dragon_speed_update:
last_index = len(ctx.dragon_speed_info[item_id_str]) - 1
dragon_speed_update[item.item] = ctx.dragon_speed_info[item_id_str][last_index]
else:
dragon_speed_update[item.item] = ctx.dragon_speed_info[item_id_str][0]
elif item.item == item_table["Left Difficulty Switch"].id:
diff_a_locked = False
elif item.item == item_table["Right Difficulty Switch"].id:
diff_b_locked = False
elif item.item == item_table["Freeincarnate"].id:
freeincarnate_count = freeincarnate_count + 1
freeincarnates_available = 0
if ctx.freeincarnates_used >= 0:
freeincarnates_available = freeincarnate_count - (ctx.freeincarnates_used + ctx.freeincarnate_pending)
ret = json.dumps(
{
"items": items,
"messages": {f'{key[0]}:{key[1]}': value for key, value in ctx.messages.items()
if key[0] > current_time - 10},
"deathlink": ctx.deathlink_pending,
"dragon_speeds": dragon_speed_update,
"difficulty_a_locked": diff_a_locked,
"difficulty_b_locked": diff_b_locked,
"freeincarnates_available": freeincarnates_available,
"bat_logic": ctx.bat_logic
}
)
ctx.deathlink_pending = False
return ret
async def parse_locations(data: List, ctx: AdventureContext):
locations = data
# for loc_name, loc_data in location_table.items():
# if flags["EventFlag"][280] & 1 and not ctx.finished_game:
# await ctx.send_msgs([
# {"cmd": "StatusUpdate",
# "status": 30}
# ])
# ctx.finished_game = True
if locations == ctx.locations_array:
return
ctx.locations_array = locations
if locations is not None:
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": locations}])
def send_ap_foreign_items(adventure_context):
foreign_item_json_list = []
autocollect_item_json_list = []
bat_no_touch_locations_json_list = []
for fi in adventure_context.foreign_items:
foreign_item_json_list.append(fi.get_dict())
for fi in adventure_context.autocollect_items:
autocollect_item_json_list.append(fi.get_dict())
for ntl in adventure_context.bat_no_touch_locations:
bat_no_touch_locations_json_list.append(ntl.get_dict())
payload = json.dumps(
{
"foreign_items": foreign_item_json_list,
"autocollect_items": autocollect_item_json_list,
"local_item_locations": adventure_context.local_item_locations,
"bat_no_touch_locations": bat_no_touch_locations_json_list
}
)
print("sending foreign items")
msg = payload.encode()
(reader, writer) = adventure_context.atari_streams
writer.write(msg)
writer.write(b'\n')
def send_checked_locations_if_needed(adventure_context):
if not adventure_context.checked_locations_sent and adventure_context.checked_locations is not None:
if len(adventure_context.checked_locations) == 0:
return
checked_short_ids = []
for location in adventure_context.checked_locations:
checked_short_ids.append(location - base_location_id)
print("Sending checked locations")
payload = json.dumps(
{
"checked_locations": checked_short_ids,
}
)
msg = payload.encode()
(reader, writer) = adventure_context.atari_streams
writer.write(msg)
writer.write(b'\n')
adventure_context.checked_locations_sent = True
async def atari_sync_task(ctx: AdventureContext):
logger.info("Starting Atari 2600 connector. Use /2600 for status information")
while not ctx.exit_event.is_set():
try:
error_status = None
if ctx.atari_streams:
(reader, writer) = ctx.atari_streams
msg = get_payload(ctx).encode()
writer.write(msg)
writer.write(b'\n')
try:
await asyncio.wait_for(writer.drain(), timeout=1.5)
try:
# Data will return a dict with 1+ fields
# 1. A keepalive response of the Players Name (always)
# 2. romhash field with sha256 hash of the ROM memory region
# 3. locations, messages, and deathLink
# 4. freeincarnate, to indicate a freeincarnate was used
data = await asyncio.wait_for(reader.readline(), timeout=5)
data_decoded = json.loads(data.decode())
if 'scriptVersion' not in data_decoded or data_decoded['scriptVersion'] != SCRIPT_VERSION:
msg = "You are connecting with an incompatible Lua script version. Ensure your connector " \
"Lua and AdventureClient are from the same Archipelago installation."
logger.info(msg, extra={'compact_gui': True})
ctx.gui_error('Error', msg)
error_status = CONNECTION_RESET_STATUS
if ctx.seed_name and bytes(ctx.seed_name, encoding='ASCII') != ctx.seed_name_from_data:
msg = "The server is running a different multiworld than your client is. " \
"(invalid seed_name)"
logger.info(msg, extra={'compact_gui': True})
ctx.gui_error('Error', msg)
error_status = CONNECTION_RESET_STATUS
if 'romhash' in data_decoded:
if ctx.rom_hash.upper() != data_decoded['romhash'].upper():
msg = "The rom hash does not match the client rom hash data"
print("got " + data_decoded['romhash'])
print("expected " + str(ctx.rom_hash))
logger.info(msg, extra={'compact_gui': True})
ctx.gui_error('Error', msg)
error_status = CONNECTION_RESET_STATUS
if ctx.auth is None:
ctx.auth = ctx.player_name
if ctx.awaiting_rom:
await ctx.server_auth(False)
if 'locations' in data_decoded and ctx.game and ctx.atari_status == CONNECTION_CONNECTED_STATUS \
and not error_status and ctx.auth:
# Not just a keep alive ping, parse
async_start(parse_locations(data_decoded['locations'], ctx))
if 'deathLink' in data_decoded and data_decoded['deathLink'] > 0 and 'DeathLink' in ctx.tags:
dragon_name = "a dragon"
if data_decoded['deathLink'] == 1:
dragon_name = "Rhindle"
elif data_decoded['deathLink'] == 2:
dragon_name = "Yorgle"
elif data_decoded['deathLink'] == 3:
dragon_name = "Grundle"
print (ctx.auth + " has been eaten by " + dragon_name )
await ctx.send_death(ctx.auth + " has been eaten by " + dragon_name)
# TODO - also if player reincarnates with a dragon onscreen ' dies to avoid being eaten by '
if 'victory' in data_decoded and not ctx.finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
if 'freeincarnate' in data_decoded:
await ctx.used_freeincarnate()
if ctx.set_deathlink:
await ctx.update_death_link(True)
send_checked_locations_if_needed(ctx)
except asyncio.TimeoutError:
logger.debug("Read Timed Out, Reconnecting")
error_status = CONNECTION_TIMING_OUT_STATUS
writer.close()
ctx.atari_streams = None
except ConnectionResetError as e:
logger.debug("Read failed due to Connection Lost, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.atari_streams = None
except TimeoutError:
logger.debug("Connection Timed Out, Reconnecting")
error_status = CONNECTION_TIMING_OUT_STATUS
writer.close()
ctx.atari_streams = None
except ConnectionResetError:
logger.debug("Connection Lost, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.atari_streams = None
except CancelledError:
logger.debug("Connection Cancelled, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.atari_streams = None
pass
except Exception as e:
print("unknown exception " + e)
raise
if ctx.atari_status == CONNECTION_TENTATIVE_STATUS:
if not error_status:
logger.info("Successfully Connected to 2600")
ctx.atari_status = CONNECTION_CONNECTED_STATUS
ctx.checked_locations_sent = False
send_ap_foreign_items(ctx)
send_checked_locations_if_needed(ctx)
else:
ctx.atari_status = f"Was tentatively connected but error occurred: {error_status}"
elif error_status:
ctx.atari_status = error_status
logger.info("Lost connection to 2600 and attempting to reconnect. Use /2600 for status updates")
else:
try:
port = ctx.lua_connector_port + ctx.port_offset
logger.debug(f"Attempting to connect to 2600 on port {port}")
print(f"Attempting to connect to 2600 on port {port}")
ctx.atari_streams = await asyncio.wait_for(
asyncio.open_connection("localhost",
port),
timeout=10)
ctx.atari_status = CONNECTION_TENTATIVE_STATUS
except TimeoutError:
logger.debug("Connection Timed Out, Trying Again")
ctx.atari_status = CONNECTION_TIMING_OUT_STATUS
continue
except ConnectionRefusedError:
logger.debug("Connection Refused, Trying Again")
ctx.atari_status = CONNECTION_REFUSED_STATUS
continue
except CancelledError:
pass
except CancelledError:
pass
print("exiting atari sync task")
async def run_game(romfile):
auto_start = Utils.get_options()["adventure_options"].get("rom_start", True)
rom_args = Utils.get_options()["adventure_options"].get("rom_args")
if auto_start is True:
import webbrowser
webbrowser.open(romfile)
elif os.path.isfile(auto_start):
open_args = [auto_start, romfile]
if rom_args is not None:
open_args.insert(1, rom_args)
subprocess.Popen(open_args,
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
async def patch_and_run_game(patch_file, ctx):
base_name = os.path.splitext(patch_file)[0]
comp_path = base_name + '.a26'
try:
base_rom = AdventureDeltaPatch.get_source_data()
except Exception as msg:
logger.info(msg, extra={'compact_gui': True})
ctx.gui_error('Error', msg)
with open(Utils.local_path("data", "adventure_basepatch.bsdiff4"), "rb") as file:
basepatch = bytes(file.read())
base_patched_rom_data = bsdiff4.patch(base_rom, basepatch)
with zipfile.ZipFile(patch_file, 'r') as patch_archive:
if not AdventureDeltaPatch.check_version(patch_archive):
logger.error("apadvn version doesn't match this client. Make sure your generator and client are the same")
raise Exception("apadvn version doesn't match this client.")
ctx.foreign_items = AdventureDeltaPatch.read_foreign_items(patch_archive)
ctx.autocollect_items = AdventureDeltaPatch.read_autocollect_items(patch_archive)
ctx.local_item_locations = AdventureDeltaPatch.read_local_item_locations(patch_archive)
ctx.dragon_speed_info = AdventureDeltaPatch.read_dragon_speed_info(patch_archive)
ctx.seed_name_from_data, ctx.player_name = AdventureDeltaPatch.read_rom_info(patch_archive)
ctx.diff_a_mode, ctx.diff_b_mode = AdventureDeltaPatch.read_difficulty_switch_info(patch_archive)
ctx.bat_logic = AdventureDeltaPatch.read_bat_logic(patch_archive)
ctx.bat_no_touch_locations = AdventureDeltaPatch.read_bat_no_touch(patch_archive)
ctx.rom_deltas = AdventureDeltaPatch.read_rom_deltas(patch_archive)
ctx.auth = ctx.player_name
patched_rom_data = AdventureDeltaPatch.apply_rom_deltas(base_patched_rom_data, ctx.rom_deltas)
rom_hash = hashlib.sha256()
rom_hash.update(patched_rom_data)
ctx.rom_hash = rom_hash.hexdigest()
ctx.port_offset = patched_rom_data[connector_port_offset]
with open(comp_path, "wb") as patched_rom_file:
patched_rom_file.write(patched_rom_data)
async_start(run_game(comp_path))
if __name__ == '__main__':
Utils.init_logging("AdventureClient")
async def main():
parser = get_base_parser()
parser.add_argument('patch_file', default="", type=str, nargs="?",
help='Path to an ADVNTURE.BIN rom file')
parser.add_argument('port', default=17242, type=int, nargs="?",
help='port for adventure_connector connection')
args = parser.parse_args()
ctx = AdventureContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
ctx.atari_sync_task = asyncio.create_task(atari_sync_task(ctx), name="Adventure Sync")
if args.patch_file:
ext = args.patch_file.split(".")[len(args.patch_file.split(".")) - 1].lower()
if ext == "apadvn":
logger.info("apadvn file supplied, beginning patching process...")
async_start(patch_and_run_game(args.patch_file, ctx))
else:
logger.warning(f"Unknown patch file extension {ext}")
if args.port is int:
ctx.lua_connector_port = args.port
await ctx.exit_event.wait()
ctx.server_address = None
await ctx.shutdown()
if ctx.atari_sync_task:
await ctx.atari_sync_task
print("finished atari_sync_task (main)")
import colorama
colorama.init()
asyncio.run(main())
colorama.deinit()

File diff suppressed because it is too large Load Diff

9
BizHawkClient.py Normal file
View File

@@ -0,0 +1,9 @@
from __future__ import annotations
import ModuleUpdate
ModuleUpdate.update()
from worlds._bizhawk.context import launch
if __name__ == "__main__":
launch()

View File

@@ -1,229 +1,70 @@
from __future__ import annotations
import os
import logging
import asyncio
import urllib.parse
import sys
import typing
import time
import asyncio
import shutil
import websockets
import ModuleUpdate
ModuleUpdate.update()
import Utils
if __name__ == "__main__":
Utils.init_logging("ChecksFinderClient", exception_logger="Client")
from MultiServer import CommandProcessor
from NetUtils import Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission
from Utils import Version, stream_input
from worlds import network_data_package, AutoWorldRegister
from CommonClient import gui_enabled, console_loop, logger, server_autoreconnect, get_base_parser, \
keep_alive
from worlds.checksfinder import ChecksFinderWorld
from NetUtils import NetworkItem, ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
CommonContext, server_loop
class ClientCommandProcessor(CommandProcessor):
def __init__(self, ctx: CommonContext):
self.ctx = ctx
def output(self, text: str):
logger.info(text)
def _cmd_exit(self) -> bool:
"""Close connections and client"""
self.ctx.exit_event.set()
return True
def _cmd_connect(self, address: str = "") -> bool:
"""Connect to a MultiWorld Server"""
self.ctx.server_address = None
asyncio.create_task(self.ctx.connect(address if address else None), name="connecting")
return True
def _cmd_disconnect(self) -> bool:
"""Disconnect from a MultiWorld Server"""
self.ctx.server_address = None
asyncio.create_task(self.ctx.disconnect(), name="disconnecting")
return True
def _cmd_received(self) -> bool:
"""List all received items"""
logger.info(f'{len(self.ctx.items_received)} received items:')
for index, item in enumerate(self.ctx.items_received, 1):
self.output(f"{self.ctx.item_name_getter(item.item)} from {self.ctx.player_names[item.player]}")
return True
def _cmd_missing(self) -> bool:
"""List all missing location checks, from your local game state"""
if not self.ctx.game:
self.output("No game set, cannot determine missing checks.")
return False
count = 0
checked_count = 0
for location, location_id in AutoWorldRegister.world_types[self.ctx.game].location_name_to_id.items():
if location_id < 0:
continue
if location_id not in self.ctx.locations_checked:
if location_id in self.ctx.missing_locations:
self.output('Missing: ' + location)
count += 1
elif location_id in self.ctx.checked_locations:
self.output('Checked: ' + location)
count += 1
checked_count += 1
if count:
self.output(
f"Found {count} missing location checks{f'. {checked_count} location checks previously visited.' if checked_count else ''}")
else:
self.output("No missing location checks found.")
return True
def _cmd_items(self):
"""List all item names for the currently running game."""
self.output(f"Item Names for {self.ctx.game}")
for item_name in AutoWorldRegister.world_types[self.ctx.game].item_name_to_id:
self.output(item_name)
def _cmd_locations(self):
"""List all location names for the currently running game."""
self.output(f"Location Names for {self.ctx.game}")
for location_name in AutoWorldRegister.world_types[self.ctx.game].location_name_to_id:
self.output(location_name)
class ChecksFinderClientCommandProcessor(ClientCommandProcessor):
def _cmd_resync(self):
"""Manually trigger a resync."""
self.output(f"Syncing items.")
self.ctx.syncing = True
def _cmd_ready(self):
"""Send ready status to server."""
self.ctx.ready = not self.ctx.ready
if self.ctx.ready:
state = ClientStatus.CLIENT_READY
self.output("Readied up.")
else:
state = ClientStatus.CLIENT_CONNECTED
self.output("Unreadied.")
asyncio.create_task(self.ctx.send_msgs([{"cmd": "StatusUpdate", "status": state}]), name="send StatusUpdate")
def default(self, raw: str):
raw = self.ctx.on_user_say(raw)
if raw:
asyncio.create_task(self.ctx.send_msgs([{"cmd": "Say", "text": raw}]), name="send Say")
class CommonContext():
tags: typing.Set[str] = {"AP"}
starting_reconnect_delay: int = 5
current_reconnect_delay: int = starting_reconnect_delay
command_processor: int = ClientCommandProcessor
game = None
ui = None
keep_alive_task = None
items_handling: typing.Optional[int] = None
current_energy_link_value = 0 # to display in UI, gets set by server
class ChecksFinderContext(CommonContext):
command_processor: int = ChecksFinderClientCommandProcessor
game = "ChecksFinder"
items_handling = 0b111 # full remote
def __init__(self, server_address, password):
# server state
super(ChecksFinderContext, self).__init__(server_address, password)
self.send_index: int = 0
self.server_address = server_address
self.password = password
self.syncing = False
self.awaiting_bridge = False
self.server_task = None
self.server: typing.Optional[Endpoint] = None
self.server_version = Version(0, 0, 0)
self.hint_cost: typing.Optional[int] = None
self.games: typing.Dict[int, str] = {}
self.permissions = {
"forfeit": "disabled",
"collect": "disabled",
"remaining": "disabled",
}
# self.game_communication_path: files go in this path to pass data between us and the actual game
if "localappdata" in os.environ:
self.game_communication_path = os.path.expandvars(r"%localappdata%/ChecksFinder")
else:
# not windows. game is an exe so let's see if wine might be around to run it
if "WINEPREFIX" in os.environ:
wineprefix = os.environ["WINEPREFIX"]
elif shutil.which("wine") or shutil.which("wine-stable"):
wineprefix = os.path.expanduser("~/.wine") # default root of wine system data, deep in which is app data
else:
msg = "ChecksFinderClient couldn't detect system type. Unable to infer required game_communication_path"
logger.error("Error: " + msg)
Utils.messagebox("Error", msg, error=True)
sys.exit(1)
self.game_communication_path = os.path.join(
wineprefix,
"drive_c",
os.path.expandvars("users/$USER/Local Settings/Application Data/ChecksFinder"))
# own state
self.finished_game = False
self.ready = False
self.team = None
self.slot = None
self.auth = None
self.seed_name = None
self.locations_checked: typing.Set[int] = set() # local state
self.locations_scouted: typing.Set[int] = set()
self.items_received = []
self.missing_locations: typing.Set[int] = set()
self.checked_locations: typing.Set[int] = set() # server state
self.locations_info = {}
self.input_queue = asyncio.Queue()
self.input_requests = 0
self.last_death_link: float = time.time() # last send/received death link on AP layer
# game state
self.player_names: typing.Dict[int: str] = {0: "Archipelago"}
self.exit_event = asyncio.Event()
self.watcher_event = asyncio.Event()
self.slow_mode = False
self.jsontotextparser = JSONtoTextParser(self)
self.set_getters(network_data_package)
# execution
self.keep_alive_task = asyncio.create_task(keep_alive(self), name="Bouncy")
@property
def total_locations(self) -> typing.Optional[int]:
"""Will return None until connected."""
if self.checked_locations or self.missing_locations:
return len(self.checked_locations | self.missing_locations)
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(ChecksFinderContext, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()
async def connection_closed(self):
self.auth = None
self.items_received = []
self.locations_info = {}
self.server_version = Version(0, 0, 0)
if self.server and self.server.socket is not None:
await self.server.socket.close()
self.server = None
self.server_task = None
path = os.path.expandvars(r"%localappdata%/ChecksFinder")
for root, dirs, files in os.walk(path):
await super(ChecksFinderContext, self).connection_closed()
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root+"/"+file)
# noinspection PyAttributeOutsideInit
def set_getters(self, data_package: dict, network=False):
if not network: # local data; check if newer data was already downloaded
local_package = Utils.persistent_load().get("datapackage", {}).get("latest", {})
if local_package and local_package["version"] > network_data_package["version"]:
data_package: dict = local_package
elif network: # check if data from server is newer
if data_package["version"] > network_data_package["version"]:
Utils.persistent_store("datapackage", "latest", network_data_package)
item_lookup: dict = {}
locations_lookup: dict = {}
for game, gamedata in data_package["games"].items():
for item_name, item_id in gamedata["item_name_to_id"].items():
item_lookup[item_id] = item_name
for location_name, location_id in gamedata["location_name_to_id"].items():
locations_lookup[location_id] = location_name
def get_item_name_from_id(code: int):
return item_lookup.get(code, f'Unknown item (ID:{code})')
self.item_name_getter = get_item_name_from_id
def get_location_name_from_address(address: int):
return locations_lookup.get(address, f'Unknown location (ID:{address})')
self.location_name_getter = get_location_name_from_address
os.remove(root + "/" + file)
@property
def endpoints(self):
@@ -232,346 +73,52 @@ class CommonContext():
else:
return []
async def disconnect(self):
if self.server and not self.server.socket.closed:
await self.server.socket.close()
if self.server_task is not None:
await self.server_task
async def send_msgs(self, msgs):
if not self.server or not self.server.socket.open or self.server.socket.closed:
return
await self.server.socket.send(encode(msgs))
def consume_players_package(self, package: typing.List[tuple]):
self.player_names = {slot: name for team, slot, name, orig_name in package if self.team == team}
self.player_names[0] = "Archipelago"
def event_invalid_slot(self):
raise Exception('Invalid Slot; please verify that you have connected to the correct world.')
def event_invalid_game(self):
raise Exception('Invalid Game; please verify that you connected with the right game to the correct world.')
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
logger.info('Enter the password required to join this game:')
self.password = await self.console_input()
return self.password
async def send_connect(self, **kwargs):
payload = {
'cmd': 'Connect',
'password': self.password, 'name': self.auth, 'version': Utils.version_tuple,
'tags': self.tags, 'items_handling': self.items_handling,
'uuid': Utils.get_unique_identifier(), 'game': self.game
}
if kwargs:
payload.update(kwargs)
await self.send_msgs([payload])
async def console_input(self):
self.input_requests += 1
return await self.input_queue.get()
async def connect(self, address=None):
await self.disconnect()
self.server_task = asyncio.create_task(server_loop(self, address), name="server loop")
def on_print(self, args: dict):
logger.info(args["text"])
def on_print_json(self, args: dict):
if self.ui:
self.ui.print_json(args["data"])
else:
text = self.jsontotextparser(args["data"])
logger.info(text)
def on_package(self, cmd: str, args: dict):
pass
def on_user_say(self, text: str) -> typing.Optional[str]:
"""Gets called before sending a Say to the server from the user.
Returned text is sent, or sending is aborted if None is returned."""
return text
def update_permissions(self, permissions: typing.Dict[str, int]):
for permission_name, permission_flag in permissions.items():
try:
flag = Permission(permission_flag)
logger.info(f"{permission_name.capitalize()} permission: {flag.name}")
self.permissions[permission_name] = flag.name
except Exception as e: # safeguard against permissions that may be implemented in the future
logger.exception(e)
async def shutdown(self):
self.server_address = None
if self.server and not self.server.socket.closed:
await self.server.socket.close()
if self.server_task:
await self.server_task
while self.input_requests > 0:
self.input_queue.put_nowait(None)
self.input_requests -= 1
self.keep_alive_task.cancel()
path = os.path.expandvars(r"%localappdata%/ChecksFinder")
for root, dirs, files in os.walk(path):
await super(ChecksFinderContext, self).shutdown()
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
if file.find("obtain") <= -1:
os.remove(root+"/"+file)
# DeathLink hooks
def on_deathlink(self, data: dict):
"""Gets dispatched when a new DeathLink is triggered by another linked player."""
self.last_death_link = max(data["time"], self.last_death_link)
text = data.get("cause", "")
if text:
logger.info(f"DeathLink: {text}")
else:
logger.info(f"DeathLink: Received from {data['source']}")
async def send_death(self, death_text: str = ""):
if self.server and self.server.socket:
logger.info("DeathLink: Sending death to your friends...")
self.last_death_link = time.time()
await self.send_msgs([{
"cmd": "Bounce", "tags": ["DeathLink"],
"data": {
"time": self.last_death_link,
"source": self.player_names[self.slot],
"cause": death_text
}
}])
async def update_death_link(self, death_link):
old_tags = self.tags.copy()
if death_link:
self.tags.add("DeathLink")
else:
self.tags -= {"DeathLink"}
if old_tags != self.tags and self.server and not self.server.socket.closed:
await self.send_msgs([{"cmd": "ConnectUpdate", "tags": self.tags}])
async def server_loop(ctx: CommonContext, address=None):
cached_address = None
if ctx.server and ctx.server.socket:
logger.error('Already connected')
return
if address is None: # set through CLI or APBP
address = ctx.server_address
# Wait for the user to provide a multiworld server address
if not address:
logger.info('Please connect to an Archipelago server.')
return
address = f"ws://{address}" if "://" not in address else address
port = urllib.parse.urlparse(address).port or 38281
logger.info(f'Connecting to Archipelago server at {address}')
try:
socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None)
ctx.server = Endpoint(socket)
logger.info('Connected')
ctx.server_address = address
ctx.current_reconnect_delay = ctx.starting_reconnect_delay
async for data in ctx.server.socket:
for msg in decode(data):
await process_server_cmd(ctx, msg)
logger.warning('Disconnected from multiworld server, type /connect to reconnect')
except ConnectionRefusedError:
if cached_address:
logger.error('Unable to connect to multiworld server at cached address. '
'Please use the connect button above.')
else:
logger.exception('Connection refused by the multiworld server')
except websockets.InvalidURI:
logger.exception('Failed to connect to the multiworld server (invalid URI)')
except (OSError, websockets.InvalidURI):
logger.exception('Failed to connect to the multiworld server')
except Exception as e:
logger.exception('Lost connection to the multiworld server, type /connect to reconnect')
finally:
await ctx.connection_closed()
if ctx.server_address:
logger.info(f"... reconnecting in {ctx.current_reconnect_delay}s")
asyncio.create_task(server_autoreconnect(ctx), name="server auto reconnect")
ctx.current_reconnect_delay *= 2
async def process_server_cmd(ctx: CommonContext, args: dict):
try:
cmd = args["cmd"]
except:
logger.exception(f"Could not get command from {args}")
raise
if cmd == 'RoomInfo':
if ctx.seed_name and ctx.seed_name != args["seed_name"]:
logger.info("The server is running a different multiworld than your client is. (invalid seed_name)")
else:
logger.info('--------------------------------')
logger.info('Room Information:')
logger.info('--------------------------------')
version = args["version"]
ctx.server_version = tuple(version)
version = ".".join(str(item) for item in version)
logger.info(f'Server protocol version: {version}')
logger.info("Server protocol tags: " + ", ".join(args["tags"]))
if args['password']:
logger.info('Password required')
ctx.update_permissions(args.get("permissions", {}))
if "games" in args:
ctx.games = {x: game for x, game in enumerate(args["games"], start=1)}
logger.info(
f"A !hint costs {args['hint_cost']}% of your total location count as points"
f" and you get {args['location_check_points']}"
f" for each location checked. Use !hint for more information.")
ctx.hint_cost = int(args['hint_cost'])
ctx.check_points = int(args['location_check_points'])
if len(args['players']) < 1:
logger.info('No player connected')
else:
args['players'].sort()
current_team = -1
logger.info('Connected Players:')
for network_player in args['players']:
if network_player.team != current_team:
logger.info(f' Team #{network_player.team + 1}')
current_team = network_player.team
logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot))
if args["datapackage_version"] > network_data_package["version"] or args["datapackage_version"] == 0:
await ctx.send_msgs([{"cmd": "GetDataPackage"}])
await ctx.server_auth(args['password'])
elif cmd == 'DataPackage':
logger.info("Got new ID/Name Datapackage")
ctx.set_getters(args['data'], network=True)
elif cmd == 'ConnectionRefused':
errors = args["errors"]
if 'InvalidSlot' in errors:
ctx.event_invalid_slot()
elif 'InvalidGame' in errors:
ctx.event_invalid_game()
elif 'SlotAlreadyTaken' in errors:
raise Exception('Player slot already in use for that team')
elif 'IncompatibleVersion' in errors:
raise Exception('Server reported your client version as incompatible')
elif 'InvalidItemsHandling' in errors:
raise Exception('The item handling flags requested by the client are not supported')
# last to check, recoverable problem
elif 'InvalidPassword' in errors:
logger.error('Invalid password')
ctx.password = None
await ctx.server_auth(True)
elif errors:
raise Exception("Unknown connection errors: " + str(errors))
else:
raise Exception('Connection refused by the multiworld host, no reason provided')
elif cmd == 'Connected':
if not os.path.exists(os.path.expandvars(r"%localappdata%/ChecksFinder")):
os.mkdir(os.path.expandvars(r"%localappdata%/ChecksFinder"))
ctx.team = args["team"]
ctx.slot = args["slot"]
ctx.consume_players_package(args["players"])
msgs = []
if ctx.locations_checked:
msgs.append({"cmd": "LocationChecks",
"locations": list(ctx.locations_checked)})
if ctx.locations_scouted:
msgs.append({"cmd": "LocationScouts",
"locations": list(ctx.locations_scouted)})
if msgs:
await ctx.send_msgs(msgs)
if ctx.finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
# Get the server side view of missing as of time of connecting.
# This list is used to only send to the server what is reported as ACTUALLY Missing.
# This also serves to allow an easy visual of what locations were already checked previously
# when /missing is used for the client side view of what is missing.
ctx.missing_locations = set(args["missing_locations"])
ctx.checked_locations = set(args["checked_locations"])
for ss in ctx.checked_locations:
filename = f"send{ss}"
with open(os.path.expandvars(r"%localappdata%/ChecksFinder/"+filename), 'w') as f:
f.close()
elif cmd == 'ReceivedItems':
start_index = args["index"]
if start_index == 0:
ctx.items_received = []
elif start_index != len(ctx.items_received):
sync_msg = [{'cmd': 'Sync'}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks",
"locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
if start_index == len(ctx.items_received):
for item in args['items']:
filename = f"AP_{str(NetworkItem(*item).location)}PLR{str(NetworkItem(*item).player)}.item"
with open(os.path.expandvars(r"%localappdata%/ChecksFinder/"+filename), 'w') as f:
f.write(str(NetworkItem(*item).item))
f.close()
ctx.items_received.append(NetworkItem(*item))
ctx.watcher_event.set()
elif cmd == 'LocationInfo':
for item, location, player in args['locations']:
if location not in ctx.locations_info:
ctx.locations_info[location] = (item, player)
ctx.watcher_event.set()
elif cmd == "RoomUpdate":
if "players" in args:
ctx.consume_players_package(args["players"])
if "hint_points" in args:
ctx.hint_points = args['hint_points']
if "checked_locations" in args:
checked = set(args["checked_locations"])
ctx.checked_locations |= checked
ctx.missing_locations -= checked
for ss in ctx.checked_locations:
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.expandvars(r"%localappdata%/ChecksFinder/"+filename), 'w') as f:
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
if "permissions" in args:
ctx.update_permissions(args["permissions"])
if cmd in {"ReceivedItems"}:
start_index = args["index"]
if start_index != len(self.items_received):
for item in args['items']:
filename = f"AP_{str(NetworkItem(*item).location)}PLR{str(NetworkItem(*item).player)}.item"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.write(str(NetworkItem(*item).item))
f.close()
elif cmd == 'Print':
ctx.on_print(args)
if cmd in {"RoomUpdate"}:
if "checked_locations" in args:
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
elif cmd == 'PrintJSON':
ctx.on_print_json(args)
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager
elif cmd == 'InvalidPacket':
logger.warning(f"Invalid Packet of {args['type']}: {args['text']}")
class ChecksFinderManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago ChecksFinder Client"
elif cmd == "Bounced":
tags = args.get("tags", [])
# we can skip checking "DeathLink" in ctx.tags, as otherwise we wouldn't have been send this
if "DeathLink" in tags and ctx.last_death_link != args["data"]["time"]:
ctx.on_deathlink(args["data"])
elif cmd == "SetReply":
if args["key"] == "EnergyLink":
ctx.current_energy_link_value = args["value"]
if ctx.ui:
ctx.ui.set_new_energy_link_value()
else:
logger.debug(f"unknown command {cmd}")
ctx.on_package(cmd, args)
self.ui = ChecksFinderManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def game_watcher(ctx: CommonContext):
async def game_watcher(ctx: ChecksFinderContext):
from worlds.checksfinder.Locations import lookup_id_to_name
while not ctx.exit_event.is_set():
if ctx.syncing == True:
@@ -580,10 +127,9 @@ async def game_watcher(ctx: CommonContext):
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
ctx.syncing = False
path = os.path.expandvars(r"%localappdata%/ChecksFinder")
sending = []
victory = False
for root, dirs, files in os.walk(path):
for root, dirs, files in os.walk(ctx.game_communication_path):
for file in files:
if file.find("send") > -1:
st = file.split("send", -1)[1]
@@ -600,38 +146,12 @@ async def game_watcher(ctx: CommonContext):
if __name__ == '__main__':
# Text Mode to use !hint and such with games that have no text entry
class TextContext(CommonContext):
game = "ChecksFinder"
items_handling = 0b111 # full remote
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(TextContext, self).server_auth(password_requested)
if not self.auth:
logger.info('Enter slot name:')
self.auth = await self.console_input()
await self.send_connect()
def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.game = self.games.get(self.slot, None)
async def main(args):
ctx = TextContext(args.connect, args.password)
ctx = ChecksFinderContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
input_task = None
if gui_enabled:
from kvui import ChecksFinderManager
ctx.ui = ChecksFinderManager(ctx)
ui_task = asyncio.create_task(ctx.ui.async_run(), name="UI")
else:
ui_task = None
if sys.stdin:
input_task = asyncio.create_task(console_loop(ctx), name="Input")
ctx.run_gui()
ctx.run_cli()
progression_watcher = asyncio.create_task(
game_watcher(ctx), name="ChecksFinderProgressionWatcher")
@@ -641,11 +161,6 @@ if __name__ == '__main__':
await progression_watcher
await ctx.shutdown()
if ui_task:
await ui_task
if input_task:
input_task.cancel()
import colorama
@@ -653,8 +168,5 @@ if __name__ == '__main__':
args, rest = parser.parse_known_args()
colorama.init()
loop = asyncio.get_event_loop()
loop.run_until_complete(main(args))
loop.close()
asyncio.run(main(args))
colorama.deinit()

View File

@@ -1,10 +1,13 @@
from __future__ import annotations
import copy
import logging
import asyncio
import urllib.parse
import sys
import typing
import time
import functools
import ModuleUpdate
ModuleUpdate.update()
@@ -17,10 +20,15 @@ if __name__ == "__main__":
Utils.init_logging("TextClient", exception_logger="Client")
from MultiServer import CommandProcessor
from NetUtils import Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot
from Utils import Version, stream_input
from NetUtils import (Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot,
RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes)
from Utils import Version, stream_input, async_start
from worlds import network_data_package, AutoWorldRegister
import os
import ssl
if typing.TYPE_CHECKING:
import kvui
logger = logging.getLogger("Client")
@@ -28,6 +36,12 @@ logger = logging.getLogger("Client")
gui_enabled = not sys.stdout or "--nogui" not in sys.argv
@Utils.cache_argsless
def get_ssl_context():
import certifi
return ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=certifi.where())
class ClientCommandProcessor(CommandProcessor):
def __init__(self, ctx: CommonContext):
self.ctx = ctx
@@ -42,31 +56,45 @@ class ClientCommandProcessor(CommandProcessor):
def _cmd_connect(self, address: str = "") -> bool:
"""Connect to a MultiWorld Server"""
self.ctx.server_address = None
asyncio.create_task(self.ctx.connect(address if address else None), name="connecting")
if address:
self.ctx.server_address = None
self.ctx.username = None
elif not self.ctx.server_address:
self.output("Please specify an address.")
return False
async_start(self.ctx.connect(address if address else None), name="connecting")
return True
def _cmd_disconnect(self) -> bool:
"""Disconnect from a MultiWorld Server"""
self.ctx.server_address = None
asyncio.create_task(self.ctx.disconnect(), name="disconnecting")
async_start(self.ctx.disconnect(), name="disconnecting")
return True
def _cmd_received(self) -> bool:
"""List all received items"""
logger.info(f'{len(self.ctx.items_received)} received items:')
item: NetworkItem
self.output(f'{len(self.ctx.items_received)} received items, sorted by time:')
for index, item in enumerate(self.ctx.items_received, 1):
self.output(f"{self.ctx.item_name_getter(item.item)} from {self.ctx.player_names[item.player]}")
parts = []
add_json_item(parts, item.item, self.ctx.slot, item.flags)
add_json_text(parts, " from ")
add_json_location(parts, item.location, item.player)
add_json_text(parts, " by ")
add_json_text(parts, item.player, type=JSONTypes.player_id)
self.ctx.on_print_json({"data": parts, "cmd": "PrintJSON"})
return True
def _cmd_missing(self) -> bool:
"""List all missing location checks, from your local game state"""
def _cmd_missing(self, filter_text = "") -> bool:
"""List all missing location checks, from your local game state.
Can be given text, which will be used as filter."""
if not self.ctx.game:
self.output("No game set, cannot determine missing checks.")
return False
count = 0
checked_count = 0
for location, location_id in AutoWorldRegister.world_types[self.ctx.game].location_name_to_id.items():
if filter_text and filter_text not in location:
continue
if location_id < 0:
continue
if location_id not in self.ctx.locations_checked:
@@ -87,16 +115,40 @@ class ClientCommandProcessor(CommandProcessor):
def _cmd_items(self):
"""List all item names for the currently running game."""
if not self.ctx.game:
self.output("No game set, cannot determine existing items.")
return False
self.output(f"Item Names for {self.ctx.game}")
for item_name in AutoWorldRegister.world_types[self.ctx.game].item_name_to_id:
self.output(item_name)
def _cmd_item_groups(self):
"""List all item group names for the currently running game."""
if not self.ctx.game:
self.output("No game set, cannot determine existing item groups.")
return False
self.output(f"Item Group Names for {self.ctx.game}")
for group_name in AutoWorldRegister.world_types[self.ctx.game].item_name_groups:
self.output(group_name)
def _cmd_locations(self):
"""List all location names for the currently running game."""
if not self.ctx.game:
self.output("No game set, cannot determine existing locations.")
return False
self.output(f"Location Names for {self.ctx.game}")
for location_name in AutoWorldRegister.world_types[self.ctx.game].location_name_to_id:
self.output(location_name)
def _cmd_location_groups(self):
"""List all location group names for the currently running game."""
if not self.ctx.game:
self.output("No game set, cannot determine existing location groups.")
return False
self.output(f"Location Group Names for {self.ctx.game}")
for group_name in AutoWorldRegister.world_types[self.ctx.game].location_name_groups:
self.output(group_name)
def _cmd_ready(self):
"""Send ready status to server."""
self.ctx.ready = not self.ctx.ready
@@ -106,40 +158,88 @@ class ClientCommandProcessor(CommandProcessor):
else:
state = ClientStatus.CLIENT_CONNECTED
self.output("Unreadied.")
asyncio.create_task(self.ctx.send_msgs([{"cmd": "StatusUpdate", "status": state}]), name="send StatusUpdate")
async_start(self.ctx.send_msgs([{"cmd": "StatusUpdate", "status": state}]), name="send StatusUpdate")
def default(self, raw: str):
raw = self.ctx.on_user_say(raw)
if raw:
asyncio.create_task(self.ctx.send_msgs([{"cmd": "Say", "text": raw}]), name="send Say")
async_start(self.ctx.send_msgs([{"cmd": "Say", "text": raw}]), name="send Say")
class CommonContext():
class CommonContext:
# Should be adjusted as needed in subclasses
tags: typing.Set[str] = {"AP"}
game: typing.Optional[str] = None
items_handling: typing.Optional[int] = None
want_slot_data: bool = True # should slot_data be retrieved via Connect
# data package
# Contents in flux until connection to server is made, to download correct data for this multiworld.
item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
# defaults
starting_reconnect_delay: int = 5
current_reconnect_delay: int = starting_reconnect_delay
command_processor: int = ClientCommandProcessor
game: typing.Optional[str] = None
command_processor: typing.Type[CommandProcessor] = ClientCommandProcessor
ui = None
ui_task: typing.Optional[asyncio.Task] = None
input_task: typing.Optional[asyncio.Task] = None
keep_alive_task: typing.Optional[asyncio.Task] = None
items_handling: typing.Optional[int] = None
slot_info: typing.Dict[int, NetworkSlot]
current_energy_link_value: int = 0 # to display in UI, gets set by server
ui_task: typing.Optional["asyncio.Task[None]"] = None
input_task: typing.Optional["asyncio.Task[None]"] = None
keep_alive_task: typing.Optional["asyncio.Task[None]"] = None
server_task: typing.Optional["asyncio.Task[None]"] = None
autoreconnect_task: typing.Optional["asyncio.Task[None]"] = None
disconnected_intentionally: bool = False
server: typing.Optional[Endpoint] = None
server_version: Version = Version(0, 0, 0)
generator_version: Version = Version(0, 0, 0)
current_energy_link_value: typing.Optional[int] = None # to display in UI, gets set by server
max_size: int = 16*1024*1024 # 16 MB of max incoming packet size
def __init__(self, server_address, password):
last_death_link: float = time.time() # last send/received death link on AP layer
# remaining type info
slot_info: typing.Dict[int, NetworkSlot]
server_address: typing.Optional[str]
password: typing.Optional[str]
hint_cost: typing.Optional[int]
hint_points: typing.Optional[int]
player_names: typing.Dict[int, str]
finished_game: bool
ready: bool
team: typing.Optional[int]
slot: typing.Optional[int]
auth: typing.Optional[str]
seed_name: typing.Optional[str]
# locations
locations_checked: typing.Set[int] # local state
locations_scouted: typing.Set[int]
items_received: typing.List[NetworkItem]
missing_locations: typing.Set[int] # server state
checked_locations: typing.Set[int] # server state
server_locations: typing.Set[int] # all locations the server knows of, missing_location | checked_locations
locations_info: typing.Dict[int, NetworkItem]
# data storage
stored_data: typing.Dict[str, typing.Any]
stored_data_notification_keys: typing.Set[str]
# internals
# current message box through kvui
_messagebox: typing.Optional["kvui.MessageBox"] = None
# message box reporting a loss of connection
_messagebox_connection_loss: typing.Optional["kvui.MessageBox"] = None
def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None:
# server state
self.server_address = server_address
self.username = None
self.password = password
self.server_task = None
self.server: typing.Optional[Endpoint] = None
self.server_version = Version(0, 0, 0)
self.hint_cost: typing.Optional[int] = None
self.games: typing.Dict[int, str] = {}
self.hint_cost = None
self.slot_info = {}
self.permissions = {
"forfeit": "disabled",
"release": "disabled",
"collect": "disabled",
"remaining": "disabled",
}
@@ -152,30 +252,42 @@ class CommonContext():
self.auth = None
self.seed_name = None
self.locations_checked: typing.Set[int] = set() # local state
self.locations_scouted: typing.Set[int] = set()
self.locations_checked = set() # local state
self.locations_scouted = set()
self.items_received = []
self.missing_locations: typing.Set[int] = set()
self.checked_locations: typing.Set[int] = set() # server state
self.locations_info: typing.Dict[int, NetworkItem] = {}
self.missing_locations = set() # server state
self.checked_locations = set() # server state
self.server_locations = set() # all locations the server knows of, missing_location | checked_locations
self.locations_info = {}
self.stored_data = {}
self.stored_data_notification_keys = set()
self.input_queue = asyncio.Queue()
self.input_requests = 0
self.last_death_link: float = time.time() # last send/received death link on AP layer
# game state
self.player_names: typing.Dict[int: str] = {0: "Archipelago"}
self.player_names = {0: "Archipelago"}
self.exit_event = asyncio.Event()
self.watcher_event = asyncio.Event()
self.slow_mode = False
self.jsontotextparser = JSONtoTextParser(self)
self.set_getters(network_data_package)
self.rawjsontotextparser = RawJSONtoTextParser(self)
self.update_data_package(network_data_package)
# execution
self.keep_alive_task = asyncio.create_task(keep_alive(self), name="Bouncy")
@property
def suggested_address(self) -> str:
if self.server_address:
return self.server_address
return Utils.persistent_load().get("client", {}).get("last_server_address", "")
@functools.cached_property
def raw_text_parser(self) -> RawJSONtoTextParser:
return RawJSONtoTextParser(self)
@property
def total_locations(self) -> typing.Optional[int]:
"""Will return None until connected."""
@@ -183,9 +295,9 @@ class CommonContext():
return len(self.checked_locations | self.missing_locations)
async def connection_closed(self):
self.reset_server_state()
if self.server and self.server.socket is not None:
await self.server.socket.close()
self.reset_server_state()
def reset_server_state(self):
self.auth = None
@@ -194,52 +306,28 @@ class CommonContext():
self.items_received = []
self.locations_info = {}
self.server_version = Version(0, 0, 0)
self.generator_version = Version(0, 0, 0)
self.server = None
self.server_task = None
self.games = {}
self.hint_cost = None
self.permissions = {
"forfeit": "disabled",
"release": "disabled",
"collect": "disabled",
"remaining": "disabled",
}
# noinspection PyAttributeOutsideInit
def set_getters(self, data_package: dict, network=False):
if not network: # local data; check if newer data was already downloaded
local_package = Utils.persistent_load().get("datapackage", {}).get("latest", {})
if local_package and local_package["version"] > network_data_package["version"]:
data_package: dict = local_package
elif network: # check if data from server is newer
if data_package["version"] > network_data_package["version"]:
Utils.persistent_store("datapackage", "latest", network_data_package)
item_lookup: dict = {}
locations_lookup: dict = {}
for game, gamedata in data_package["games"].items():
for item_name, item_id in gamedata["item_name_to_id"].items():
item_lookup[item_id] = item_name
for location_name, location_id in gamedata["location_name_to_id"].items():
locations_lookup[location_id] = location_name
def get_item_name_from_id(code: int) -> str:
return item_lookup.get(code, f'Unknown item (ID:{code})')
self.item_name_getter = get_item_name_from_id
def get_location_name_from_address(address: int) -> str:
return locations_lookup.get(address, f'Unknown location (ID:{address})')
self.location_name_getter = get_location_name_from_address
async def disconnect(self):
async def disconnect(self, allow_autoreconnect: bool = False):
if not allow_autoreconnect:
self.disconnected_intentionally = True
if self.cancel_autoreconnect():
logger.info("Cancelled auto-reconnect.")
if self.server and not self.server.socket.closed:
await self.server.socket.close()
if self.server_task is not None:
await self.server_task
async def send_msgs(self, msgs):
async def send_msgs(self, msgs: typing.List[typing.Any]) -> None:
""" `msgs` JSON serializable """
if not self.server or not self.server.socket.open or self.server.socket.closed:
return
await self.server.socket.send(encode(msgs))
@@ -260,34 +348,73 @@ class CommonContext():
self.password = await self.console_input()
return self.password
async def send_connect(self, **kwargs):
async def get_username(self):
if not self.auth:
self.auth = self.username
if not self.auth:
logger.info('Enter slot name:')
self.auth = await self.console_input()
async def send_connect(self, **kwargs: typing.Any) -> None:
""" send `Connect` packet to log in to server """
payload = {
'cmd': 'Connect',
'password': self.password, 'name': self.auth, 'version': Utils.version_tuple,
'tags': self.tags, 'items_handling': self.items_handling,
'uuid': Utils.get_unique_identifier(), 'game': self.game
'uuid': Utils.get_unique_identifier(), 'game': self.game, "slot_data": self.want_slot_data,
}
if kwargs:
payload.update(kwargs)
await self.send_msgs([payload])
async def console_input(self):
async def console_input(self) -> str:
if self.ui:
self.ui.focus_textinput()
self.input_requests += 1
return await self.input_queue.get()
async def connect(self, address=None):
async def connect(self, address: typing.Optional[str] = None) -> None:
""" disconnect any previous connection, and open new connection to the server """
await self.disconnect()
self.server_task = asyncio.create_task(server_loop(self, address), name="server loop")
def cancel_autoreconnect(self) -> bool:
if self.autoreconnect_task:
self.autoreconnect_task.cancel()
self.autoreconnect_task = None
return True
return False
def slot_concerns_self(self, slot) -> bool:
if slot == self.slot:
return True
if slot in self.slot_info:
return self.slot in self.slot_info[slot].group_members
return False
def is_echoed_chat(self, print_json_packet: dict) -> bool:
return print_json_packet.get("type", "") == "Chat" \
and print_json_packet.get("team", None) == self.team \
and print_json_packet.get("slot", None) == self.slot
def is_uninteresting_item_send(self, print_json_packet: dict) -> bool:
"""Helper function for filtering out ItemSend prints that do not concern the local player."""
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 on_print(self, args: dict):
logger.info(args["text"])
def on_print_json(self, args: dict):
if self.ui:
self.ui.print_json(args["data"])
else:
text = self.jsontotextparser(args["data"])
logger.info(text)
# send copy to UI
self.ui.print_json(copy.deepcopy(args["data"]))
logging.getLogger("FileLog").info(self.rawjsontotextparser(copy.deepcopy(args["data"])),
extra={"NoStream": True})
logging.getLogger("StreamLog").info(self.jsontotextparser(copy.deepcopy(args["data"])),
extra={"NoFile": True})
def on_package(self, cmd: str, args: dict):
"""For custom package handling in subclasses."""
@@ -308,7 +435,9 @@ class CommonContext():
logger.exception(e)
async def shutdown(self):
self.server_address = None
self.server_address = ""
self.username = None
self.cancel_autoreconnect()
if self.server and not self.server.socket.closed:
await self.server.socket.close()
if self.server_task:
@@ -323,9 +452,81 @@ class CommonContext():
if self.input_task:
self.input_task.cancel()
# DataPackage
async def prepare_data_package(self, relevant_games: typing.Set[str],
remote_date_package_versions: typing.Dict[str, int],
remote_data_package_checksums: typing.Dict[str, str]):
"""Validate that all data is present for the current multiworld.
Download, assimilate and cache missing data from the server."""
# by documentation any game can use Archipelago locations/items -> always relevant
relevant_games.add("Archipelago")
needed_updates: typing.Set[str] = set()
for game in relevant_games:
if game not in remote_date_package_versions and game not in remote_data_package_checksums:
continue
remote_version: int = remote_date_package_versions.get(game, 0)
remote_checksum: typing.Optional[str] = remote_data_package_checksums.get(game)
if remote_version == 0 and not remote_checksum: # custom data package and no checksum for this game
needed_updates.add(game)
continue
local_version: int = network_data_package["games"].get(game, {}).get("version", 0)
local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum")
# no action required if local version is new enough
if (not remote_checksum and (remote_version > local_version or remote_version == 0)) \
or remote_checksum != local_checksum:
cached_game = Utils.load_data_package_for_checksum(game, remote_checksum)
cache_version: int = cached_game.get("version", 0)
cache_checksum: typing.Optional[str] = cached_game.get("checksum")
# download remote version if cache is not new enough
if (not remote_checksum and (remote_version > cache_version or remote_version == 0)) \
or remote_checksum != cache_checksum:
needed_updates.add(game)
else:
self.update_game(cached_game)
if needed_updates:
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
def update_game(self, game_package: dict):
for item_name, item_id in game_package["item_name_to_id"].items():
self.item_names[item_id] = item_name
for location_name, location_id in game_package["location_name_to_id"].items():
self.location_names[location_id] = location_name
def update_data_package(self, data_package: dict):
for game, game_data in data_package["games"].items():
self.update_game(game_data)
def consume_network_data_package(self, data_package: dict):
self.update_data_package(data_package)
current_cache = Utils.persistent_load().get("datapackage", {}).get("games", {})
current_cache.update(data_package["games"])
Utils.persistent_store("datapackage", "games", current_cache)
logger.info(f"Got new ID/Name DataPackage for {', '.join(data_package['games'])}")
for game, game_data in data_package["games"].items():
Utils.store_data_package_for_checksum(game, game_data)
# data storage
def set_notify(self, *keys: str) -> None:
"""Subscribe to be notified of changes to selected data storage keys.
The values can be accessed via the "stored_data" attribute of this context, which is a dictionary mapping the
names of the data storage keys to the latest values received from the server.
"""
if new_keys := (set(keys) - self.stored_data_notification_keys):
self.stored_data_notification_keys.update(new_keys)
async_start(self.send_msgs([{"cmd": "Get",
"keys": list(new_keys)},
{"cmd": "SetNotify",
"keys": list(new_keys)}]))
# DeathLink hooks
def on_deathlink(self, data: dict):
def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None:
"""Gets dispatched when a new DeathLink is triggered by another linked player."""
self.last_death_link = max(data["time"], self.last_death_link)
text = data.get("cause", "")
@@ -356,6 +557,34 @@ class CommonContext():
if old_tags != self.tags and self.server and not self.server.socket.closed:
await self.send_msgs([{"cmd": "ConnectUpdate", "tags": self.tags}])
def gui_error(self, title: str, text: typing.Union[Exception, str]) -> typing.Optional["kvui.MessageBox"]:
"""Displays an error messagebox"""
if not self.ui:
return None
title = title or "Error"
from kvui import MessageBox
if self._messagebox:
self._messagebox.dismiss()
# make "Multiple exceptions" look nice
text = str(text).replace('[Errno', '\n[Errno').strip()
# split long messages into title and text
parts = title.split('. ', 1)
if len(parts) == 1:
parts = title.split(', ', 1)
if len(parts) > 1:
text = parts[1] + '\n\n' + text
title = parts[0]
# display error
self._messagebox = MessageBox(title, text, error=True)
self._messagebox.open()
return self._messagebox
def handle_connection_loss(self, msg: str) -> None:
"""Helper for logging and displaying a loss of connection. Must be called from an except block."""
exc_info = sys.exc_info()
logger.exception(msg, exc_info=exc_info, extra={'compact_gui': True})
self._messagebox_connection_loss = self.gui_error(msg, exc_info[1])
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager
@@ -391,7 +620,7 @@ async def keep_alive(ctx: CommonContext, seconds_between_checks=100):
seconds_elapsed = 0
async def server_loop(ctx: CommonContext, address=None):
async def server_loop(ctx: CommonContext, address: typing.Optional[str] = None) -> None:
if ctx.server and ctx.server.socket:
logger.error('Already connected')
return
@@ -404,33 +633,63 @@ async def server_loop(ctx: CommonContext, address=None):
logger.info('Please connect to an Archipelago server.')
return
address = f"ws://{address}" if "://" not in address else address
port = urllib.parse.urlparse(address).port or 38281
ctx.cancel_autoreconnect()
if ctx._messagebox_connection_loss:
ctx._messagebox_connection_loss.dismiss()
ctx._messagebox_connection_loss = None
address = f"ws://{address}" if "://" not in address \
else address.replace("archipelago://", "ws://")
server_url = urllib.parse.urlparse(address)
if server_url.username:
ctx.username = server_url.username
if server_url.password:
ctx.password = server_url.password
def reconnect_hint() -> str:
return ", type /connect to reconnect" if ctx.server_address else ""
logger.info(f'Connecting to Archipelago server at {address}')
try:
socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None)
port = server_url.port or 38281 # raises ValueError if invalid
socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None,
ssl=get_ssl_context() if address.startswith("wss://") else None,
max_size=ctx.max_size)
if ctx.ui is not None:
ctx.ui.update_address_bar(server_url.netloc)
ctx.server = Endpoint(socket)
logger.info('Connected')
ctx.server_address = address
ctx.current_reconnect_delay = ctx.starting_reconnect_delay
ctx.disconnected_intentionally = False
async for data in ctx.server.socket:
for msg in decode(data):
await process_server_cmd(ctx, msg)
logger.warning('Disconnected from multiworld server, type /connect to reconnect')
logger.warning(f"Disconnected from multiworld server{reconnect_hint()}")
except websockets.InvalidMessage:
# probably encrypted
if address.startswith("ws://"):
# try wss
await server_loop(ctx, "ws" + address[1:])
else:
ctx.handle_connection_loss(f"Lost connection to the multiworld server due to InvalidMessage"
f"{reconnect_hint()}")
except ConnectionRefusedError:
logger.exception('Connection refused by the server. May not be running Archipelago on that address or port.')
ctx.handle_connection_loss("Connection refused by the server. "
"May not be running Archipelago on that address or port.")
except websockets.InvalidURI:
logger.exception('Failed to connect to the multiworld server (invalid URI)')
ctx.handle_connection_loss("Failed to connect to the multiworld server (invalid URI)")
except OSError:
logger.exception('Failed to connect to the multiworld server')
ctx.handle_connection_loss("Failed to connect to the multiworld server")
except Exception:
logger.exception('Lost connection to the multiworld server, type /connect to reconnect')
ctx.handle_connection_loss(f"Lost connection to the multiworld server{reconnect_hint()}")
finally:
await ctx.connection_closed()
if ctx.server_address:
logger.info(f"... reconnecting in {ctx.current_reconnect_delay}s")
asyncio.create_task(server_autoreconnect(ctx), name="server auto reconnect")
if ctx.server_address and ctx.username and not ctx.disconnected_intentionally:
logger.info(f"... automatically reconnecting in {ctx.current_reconnect_delay} seconds")
assert ctx.autoreconnect_task is None
ctx.autoreconnect_task = asyncio.create_task(server_autoreconnect(ctx), name="server auto reconnect")
ctx.current_reconnect_delay *= 2
@@ -448,22 +707,27 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
raise
if cmd == 'RoomInfo':
if ctx.seed_name and ctx.seed_name != args["seed_name"]:
logger.info("The server is running a different multiworld than your client is. (invalid seed_name)")
msg = "The server is running a different multiworld than your client is. (invalid seed_name)"
logger.info(msg, extra={'compact_gui': True})
ctx.gui_error('Error', msg)
else:
logger.info('--------------------------------')
logger.info('Room Information:')
logger.info('--------------------------------')
version = args["version"]
ctx.server_version = tuple(version)
version = ".".join(str(item) for item in version)
ctx.server_version = Version(*version)
logger.info(f'Server protocol version: {version}')
logger.info("Server protocol tags: " + ", ".join(args["tags"]))
if "generator_version" in args:
ctx.generator_version = Version(*args["generator_version"])
logger.info(f'Server protocol version: {ctx.server_version.as_simple_string()}, '
f'generator version: {ctx.generator_version.as_simple_string()}, '
f'tags: {", ".join(args["tags"])}')
else:
logger.info(f'Server protocol version: {ctx.server_version.as_simple_string()}, '
f'tags: {", ".join(args["tags"])}')
if args['password']:
logger.info('Password required')
ctx.update_permissions(args.get("permissions", {}))
if "games" in args:
ctx.games = {x: game for x, game in enumerate(args["games"], start=1)}
logger.info(
f"A !hint costs {args['hint_cost']}% of your total location count as points"
f" and you get {args['location_check_points']}"
@@ -471,33 +735,41 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
ctx.hint_cost = int(args['hint_cost'])
ctx.check_points = int(args['location_check_points'])
if len(args['players']) < 1:
logger.info('No player connected')
else:
args['players'].sort()
current_team = -1
logger.info('Connected Players:')
for network_player in args['players']:
if network_player.team != current_team:
logger.info(f' Team #{network_player.team + 1}')
current_team = network_player.team
logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot))
if args["datapackage_version"] > network_data_package["version"] or args["datapackage_version"] == 0:
await ctx.send_msgs([{"cmd": "GetDataPackage"}])
if "players" in args: # TODO remove when servers sending this are outdated
players = args.get("players", [])
if len(players) < 1:
logger.info('No player connected')
else:
players.sort()
current_team = -1
logger.info('Connected Players:')
for network_player in players:
if network_player.team != current_team:
logger.info(f' Team #{network_player.team + 1}')
current_team = network_player.team
logger.info(' %s (Player %d)' % (network_player.alias, network_player.slot))
# update data package
data_package_versions = args.get("datapackage_versions", {})
data_package_checksums = args.get("datapackage_checksums", {})
await ctx.prepare_data_package(set(args["games"]), data_package_versions, data_package_checksums)
await ctx.server_auth(args['password'])
elif cmd == 'DataPackage':
logger.info("Got new ID/Name Datapackage")
ctx.set_getters(args['data'], network=True)
ctx.consume_network_data_package(args['data'])
elif cmd == 'ConnectionRefused':
errors = args["errors"]
if 'InvalidSlot' in errors:
ctx.disconnected_intentionally = True
ctx.event_invalid_slot()
elif 'InvalidGame' in errors:
ctx.disconnected_intentionally = True
ctx.event_invalid_game()
elif 'IncompatibleVersion' in errors:
raise Exception('Server reported your client version as incompatible')
raise Exception('Server reported your client version as incompatible. '
'This probably means you have to update.')
elif 'InvalidItemsHandling' in errors:
raise Exception('The item handling flags requested by the client are not supported')
# last to check, recoverable problem
@@ -511,11 +783,14 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
raise Exception('Connection refused by the multiworld host, no reason provided')
elif cmd == 'Connected':
ctx.username = ctx.auth
ctx.team = args["team"]
ctx.slot = args["slot"]
# int keys get lost in JSON transfer
ctx.slot_info = {int(pid): data for pid, data in args["slot_info"].items()}
ctx.hint_points = args.get("hint_points", 0)
ctx.consume_players_package(args["players"])
ctx.stored_data_notification_keys.add(f"_read_hints_{ctx.team}_{ctx.slot}")
msgs = []
if ctx.locations_checked:
msgs.append({"cmd": "LocationChecks",
@@ -523,6 +798,11 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
if ctx.locations_scouted:
msgs.append({"cmd": "LocationScouts",
"locations": list(ctx.locations_scouted)})
if ctx.stored_data_notification_keys:
msgs.append({"cmd": "Get",
"keys": list(ctx.stored_data_notification_keys)})
msgs.append({"cmd": "SetNotify",
"keys": list(ctx.stored_data_notification_keys)})
if msgs:
await ctx.send_msgs(msgs)
if ctx.finished_game:
@@ -534,6 +814,10 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
# when /missing is used for the client side view of what is missing.
ctx.missing_locations = set(args["missing_locations"])
ctx.checked_locations = set(args["checked_locations"])
ctx.server_locations = ctx.missing_locations | ctx. checked_locations
server_url = urllib.parse.urlparse(ctx.server_address)
Utils.persistent_store("client", "last_server_address", server_url.netloc)
elif cmd == 'ReceivedItems':
start_index = args["index"]
@@ -582,8 +866,17 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
# we can skip checking "DeathLink" in ctx.tags, as otherwise we wouldn't have been send this
if "DeathLink" in tags and ctx.last_death_link != args["data"]["time"]:
ctx.on_deathlink(args["data"])
elif cmd == "Retrieved":
ctx.stored_data.update(args["keys"])
if ctx.ui and f"_read_hints_{ctx.team}_{ctx.slot}" in args["keys"]:
ctx.ui.update_hints()
elif cmd == "SetReply":
if args["key"] == "EnergyLink":
ctx.stored_data[args["key"]] = args["value"]
if ctx.ui and f"_read_hints_{ctx.team}_{ctx.slot}" == args["key"]:
ctx.ui.update_hints()
elif args["key"].startswith("EnergyLink"):
ctx.current_energy_link_value = args["value"]
if ctx.ui:
ctx.ui.set_new_energy_link_value()
@@ -613,7 +906,7 @@ async def console_loop(ctx: CommonContext):
logger.exception(e)
def get_base_parser(description=None):
def get_base_parser(description: typing.Optional[str] = None):
import argparse
parser = argparse.ArgumentParser(description=description)
parser.add_argument('--connect', default=None, help='Address of the multiworld host.')
@@ -623,27 +916,27 @@ def get_base_parser(description=None):
return parser
if __name__ == '__main__':
# Text Mode to use !hint and such with games that have no text entry
def run_as_textclient():
class TextContext(CommonContext):
tags = {"AP", "IgnoreGame", "TextOnly"}
# Text Mode to use !hint and such with games that have no text entry
tags = CommonContext.tags | {"TextOnly"}
game = "" # empty matches any game since 0.3.2
items_handling = 0 # don't receive any NetworkItems
items_handling = 0b111 # receive all items for /received
want_slot_data = False # Can't use game specific slot_data
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(TextContext, self).server_auth(password_requested)
if not self.auth:
logger.info('Enter slot name:')
self.auth = await self.console_input()
await self.get_username()
await self.send_connect()
def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.game = self.games.get(self.slot, None)
self.game = self.slot_info[self.slot].game
async def disconnect(self, allow_autoreconnect: bool = False):
self.game = ""
await super().disconnect(allow_autoreconnect)
async def main(args):
ctx = TextContext(args.connect, args.password)
@@ -657,7 +950,6 @@ if __name__ == '__main__':
await ctx.exit_event.wait()
await ctx.shutdown()
import colorama
parser = get_base_parser(description="Gameless Archipelago Client, for text interfacing.")
@@ -677,3 +969,8 @@ if __name__ == '__main__':
asyncio.run(main(args))
colorama.deinit()
if __name__ == '__main__':
logging.getLogger().setLevel(logging.INFO) # force log-level to work around log level resetting to WARNING
run_as_textclient()

View File

@@ -1,4 +1,5 @@
import asyncio
import copy
import json
import time
from asyncio import StreamReader, StreamWriter
@@ -6,14 +7,15 @@ from typing import List
import Utils
from CommonClient import CommonContext, server_loop, gui_enabled, console_loop, ClientCommandProcessor, logger, \
from Utils import async_start
from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandProcessor, logger, \
get_base_parser
SYSTEM_MESSAGE_ID = 0
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart ff1_connector.lua"
CONNECTION_REFUSED_STATUS = "Connection Refused. Please start your emulator and make sure ff1_connector.lua is running"
CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart ff1_connector.lua"
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_ff1.lua"
CONNECTION_REFUSED_STATUS = "Connection Refused. Please start your emulator and make sure connector_ff1.lua is running"
CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart connector_ff1.lua"
CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
CONNECTION_CONNECTED_STATUS = "Connected"
CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
@@ -31,7 +33,7 @@ class FF1CommandProcessor(ClientCommandProcessor):
logger.info(f"NES Status: {self.ctx.nes_status}")
def _cmd_toggle_msgs(self):
"""Toggle displaying messages in bizhawk"""
"""Toggle displaying messages in EmuHawk"""
global DISPLAY_MSGS
DISPLAY_MSGS = not DISPLAY_MSGS
logger.info(f"Messages are now {'enabled' if DISPLAY_MSGS else 'disabled'}")
@@ -39,6 +41,7 @@ class FF1CommandProcessor(ClientCommandProcessor):
class FF1Context(CommonContext):
command_processor = FF1CommandProcessor
game = 'Final Fantasy'
items_handling = 0b111 # full remote
def __init__(self, server_address, password):
@@ -48,7 +51,6 @@ class FF1Context(CommonContext):
self.messages = {}
self.locations_array = None
self.nes_status = CONNECTION_INITIAL_STATUS
self.game = 'Final Fantasy'
self.awaiting_rom = False
self.display_msgs = True
@@ -64,42 +66,37 @@ class FF1Context(CommonContext):
def _set_message(self, msg: str, msg_id: int):
if DISPLAY_MSGS:
self.messages[(time.time(), msg_id)] = msg
self.messages[time.time(), msg_id] = msg
def on_package(self, cmd: str, args: dict):
if cmd == 'Connected':
self.game = self.games.get(self.slot, None)
asyncio.create_task(parse_locations(self.locations_array, self, True))
async_start(parse_locations(self.locations_array, self, True))
elif cmd == 'Print':
msg = args['text']
if ': !' not in msg:
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == "ReceivedItems":
msg = f"Received {', '.join([self.item_name_getter(item.item) for item in args['items']])}"
self._set_message(msg, SYSTEM_MESSAGE_ID)
elif cmd == 'PrintJSON':
print_type = args['type']
item = args['item']
receiving_player_id = args['receiving']
receiving_player_name = self.player_names[receiving_player_id]
sending_player_id = item.player
sending_player_name = self.player_names[item.player]
if print_type == 'Hint':
msg = f"Hint: Your {self.item_name_getter(item.item)} is at" \
f" {self.player_names[item.player]}'s {self.location_name_getter(item.location)}"
self._set_message(msg, item.item)
elif print_type == 'ItemSend' and receiving_player_id != self.slot:
if sending_player_id == self.slot:
if receiving_player_id == self.slot:
msg = f"You found your own {self.item_name_getter(item.item)}"
else:
msg = f"You sent {self.item_name_getter(item.item)} to {receiving_player_name}"
else:
if receiving_player_id == sending_player_id:
msg = f"{sending_player_name} found their {self.item_name_getter(item.item)}"
else:
msg = f"{sending_player_name} sent {self.item_name_getter(item.item)} to " \
f"{receiving_player_name}"
def on_print_json(self, args: dict):
if self.ui:
self.ui.print_json(copy.deepcopy(args["data"]))
else:
text = self.jsontotextparser(copy.deepcopy(args["data"]))
logger.info(text)
relevant = args.get("type", None) in {"Hint", "ItemSend"}
if relevant:
item = args["item"]
# goes to this world
if self.slot_concerns_self(args["receiving"]):
relevant = True
# found in this world
elif self.slot_concerns_self(item.player):
relevant = True
# not related
else:
relevant = False
if relevant:
item = args["item"]
msg = self.raw_text_parser(copy.deepcopy(args["data"]))
self._set_message(msg, item.item)
def run_gui(self):
@@ -151,13 +148,13 @@ async def parse_locations(locations_array: List[int], ctx: FF1Context, force: bo
index -= 0x200
flag = 0x02
# print(f"Location: {ctx.location_name_getter(location)}")
# print(f"Location: {ctx.location_names[location]}")
# print(f"Index: {str(hex(index))}")
# print(f"value: {locations_array[index] & flag != 0}")
if locations_array[index] & flag != 0:
locations_checked.append(location)
if locations_checked:
# print([ctx.location_name_getter(location) for location in locations_checked])
# print([ctx.location_names[location] for location in locations_checked])
await ctx.send_msgs([
{"cmd": "LocationChecks",
"locations": locations_checked}
@@ -184,7 +181,7 @@ async def nes_sync_task(ctx: FF1Context):
# print(data_decoded)
if ctx.game is not None and 'locations' in data_decoded:
# Not just a keep alive ping, parse
asyncio.create_task(parse_locations(data_decoded['locations'], ctx, False))
async_start(parse_locations(data_decoded['locations'], ctx, False))
if not ctx.auth:
ctx.auth = ''.join([chr(i) for i in data_decoded['playerName'] if i != 0])
if ctx.auth == '':

View File

@@ -1,423 +1,12 @@
from __future__ import annotations
import os
import logging
import json
import string
import copy
import subprocess
import time
import random
import ModuleUpdate
ModuleUpdate.update()
import factorio_rcon
import colorama
import asyncio
from queue import Queue
from worlds.factorio.Client import check_stdin, launch
import Utils
if __name__ == "__main__":
Utils.init_logging("FactorioClient", exception_logger="Client")
from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, logger, gui_enabled, \
get_base_parser
from MultiServer import mark_raw
from NetUtils import NetworkItem, ClientStatus, JSONtoTextParser, JSONMessagePart
from worlds.factorio import Factorio
class FactorioCommandProcessor(ClientCommandProcessor):
ctx: FactorioContext
@mark_raw
def _cmd_factorio(self, text: str) -> bool:
"""Send the following command to the bound Factorio Server."""
if self.ctx.rcon_client:
# TODO: Print the command non-silently only for race seeds, or otherwise block anything but /factorio /save in race seeds.
self.ctx.print_to_game(f"/factorio {text}")
result = self.ctx.rcon_client.send_command(text)
if result:
self.output(result)
return True
return False
def _cmd_resync(self):
"""Manually trigger a resync."""
self.ctx.awaiting_bridge = True
class FactorioContext(CommonContext):
command_processor = FactorioCommandProcessor
game = "Factorio"
items_handling = 0b111 # full remote
# updated by spinup server
mod_version: Utils.Version = Utils.Version(0, 0, 0)
def __init__(self, server_address, password):
super(FactorioContext, self).__init__(server_address, password)
self.send_index: int = 0
self.rcon_client = None
self.awaiting_bridge = False
self.write_data_path = None
self.death_link_tick: int = 0 # last send death link on Factorio layer
self.factorio_json_text_parser = FactorioJSONtoTextParser(self)
self.energy_link_increment = 0
self.last_deplete = 0
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(FactorioContext, self).server_auth(password_requested)
if self.rcon_client:
await get_info(self, self.rcon_client) # retrieve current auth code
else:
raise Exception("Cannot connect to a server with unknown own identity, "
"bridge to Factorio first.")
await self.send_connect()
def on_print(self, args: dict):
super(FactorioContext, self).on_print(args)
if self.rcon_client:
self.print_to_game(args['text'])
def on_print_json(self, args: dict):
if self.rcon_client:
text = self.factorio_json_text_parser(copy.deepcopy(args["data"]))
self.print_to_game(text)
super(FactorioContext, self).on_print_json(args)
@property
def savegame_name(self) -> str:
return f"AP_{self.seed_name}_{self.auth}_Save.zip"
def print_to_game(self, text):
self.rcon_client.send_command(f"/ap-print [font=default-large-bold]Archipelago:[/font] "
f"{text}")
def on_deathlink(self, data: dict):
if self.rcon_client:
self.rcon_client.send_command(f"/ap-deathlink {data['source']}")
super(FactorioContext, self).on_deathlink(data)
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected", "RoomUpdate"}:
# catch up sync anything that is already cleared.
if "checked_locations" in args and args["checked_locations"]:
self.rcon_client.send_commands({item_name: f'/ap-get-technology ap-{item_name}-\t-1' for
item_name in args["checked_locations"]})
if cmd == "Connected" and self.energy_link_increment:
asyncio.create_task(self.send_msgs([{
"cmd": "SetNotify", "keys": ["EnergyLink"]
}]))
elif cmd == "SetReply":
if args["key"] == "EnergyLink":
if self.energy_link_increment and args.get("last_deplete", -1) == self.last_deplete:
# it's our deplete request
gained = int(args["original_value"] - args["value"])
gained_text = Utils.format_SI_prefix(gained) + "J"
if gained:
logger.debug(f"EnergyLink: Received {gained_text}. "
f"{Utils.format_SI_prefix(args['value'])}J remaining.")
self.rcon_client.send_command(f"/ap-energylink {gained}")
def run_gui(self):
from kvui import GameManager
class FactorioManager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("FactorioServer", "Factorio Server Log"),
("FactorioWatcher", "Bridge Data Log"),
]
base_title = "Archipelago Factorio Client"
self.ui = FactorioManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def game_watcher(ctx: FactorioContext):
bridge_logger = logging.getLogger("FactorioWatcher")
from worlds.factorio.Technologies import lookup_id_to_name
next_bridge = time.perf_counter() + 1
try:
while not ctx.exit_event.is_set():
# TODO: restore on-demand refresh
if ctx.rcon_client and time.perf_counter() > next_bridge:
next_bridge = time.perf_counter() + 1
ctx.awaiting_bridge = False
data = json.loads(ctx.rcon_client.send_command("/ap-sync"))
if data["slot_name"] != ctx.auth:
bridge_logger.warning(f"Connected World is not the expected one {data['slot_name']} != {ctx.auth}")
elif data["seed_name"] != ctx.seed_name:
bridge_logger.warning(
f"Connected Multiworld is not the expected one {data['seed_name']} != {ctx.seed_name}")
else:
data = data["info"]
research_data = data["research_done"]
research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
victory = data["victory"]
await ctx.update_death_link(data["death_link"])
if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
if ctx.locations_checked != research_data:
bridge_logger.debug(
f"New researches done: "
f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
death_link_tick = data.get("death_link_tick", 0)
if death_link_tick != ctx.death_link_tick:
ctx.death_link_tick = death_link_tick
if "DeathLink" in ctx.tags:
asyncio.create_task(ctx.send_death())
if ctx.energy_link_increment:
in_world_bridges = data["energy_bridges"]
if in_world_bridges:
in_world_energy = data["energy"]
if in_world_energy < (ctx.energy_link_increment * in_world_bridges):
# attempt to refill
ctx.last_deplete = time.time()
asyncio.create_task(ctx.send_msgs([{
"cmd": "Set", "key": "EnergyLink", "operations":
[{"operation": "add", "value": -ctx.energy_link_increment * in_world_bridges},
{"operation": "max", "value": 0}],
"last_deplete": ctx.last_deplete
}]))
# Above Capacity - (len(Bridges) * ENERGY_INCREMENT)
elif in_world_energy > (in_world_bridges * ctx.energy_link_increment * 5) - \
ctx.energy_link_increment*in_world_bridges:
value = ctx.energy_link_increment * in_world_bridges
asyncio.create_task(ctx.send_msgs([{
"cmd": "Set", "key": "EnergyLink", "operations":
[{"operation": "add", "value": value}]
}]))
ctx.rcon_client.send_command(
f"/ap-energylink -{value}")
logger.debug(f"EnergyLink: Sent {Utils.format_SI_prefix(value)}J")
await asyncio.sleep(0.1)
except Exception as e:
logging.exception(e)
logging.error("Aborted Factorio Server Bridge")
def stream_factorio_output(pipe, queue, process):
def queuer():
while process.poll() is None:
text = pipe.readline().strip()
if text:
queue.put_nowait(text)
from threading import Thread
thread = Thread(target=queuer, name="Factorio Output Queue", daemon=True)
thread.start()
return thread
async def factorio_server_watcher(ctx: FactorioContext):
savegame_name = os.path.abspath(ctx.savegame_name)
if not os.path.exists(savegame_name):
logger.info(f"Creating savegame {savegame_name}")
subprocess.run((
executable, "--create", savegame_name, "--preset", "archipelago"
))
factorio_process = subprocess.Popen((executable, "--start-server", ctx.savegame_name,
*(str(elem) for elem in server_args)),
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.DEVNULL,
encoding="utf-8")
factorio_server_logger.info("Started Factorio Server")
factorio_queue = Queue()
stream_factorio_output(factorio_process.stdout, factorio_queue, factorio_process)
stream_factorio_output(factorio_process.stderr, factorio_queue, factorio_process)
try:
while not ctx.exit_event.is_set():
if factorio_process.poll():
factorio_server_logger.info("Factorio server has exited.")
ctx.exit_event.set()
while not factorio_queue.empty():
msg = factorio_queue.get()
factorio_queue.task_done()
if not ctx.rcon_client and "Starting RCON interface at IP ADDR:" in msg:
ctx.rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
if not ctx.server:
logger.info("Established bridge to Factorio Server. "
"Ready to connect to Archipelago via /connect")
if not ctx.awaiting_bridge and "Archipelago Bridge Data available for game tick " in msg:
ctx.awaiting_bridge = True
factorio_server_logger.debug(msg)
else:
factorio_server_logger.info(msg)
if ctx.rcon_client:
commands = {}
while ctx.send_index < len(ctx.items_received):
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
item_id = transfer_item.item
player_name = ctx.player_names[transfer_item.player]
if item_id not in Factorio.item_id_to_name:
factorio_server_logger.error(f"Cannot send unknown item ID: {item_id}")
else:
item_name = Factorio.item_id_to_name[item_id]
factorio_server_logger.info(f"Sending {item_name} to Nauvis from {player_name}.")
commands[ctx.send_index] = f'/ap-get-technology {item_name}\t{ctx.send_index}\t{player_name}'
ctx.send_index += 1
if commands:
ctx.rcon_client.send_commands(commands)
await asyncio.sleep(0.1)
except Exception as e:
logging.exception(e)
logging.error("Aborted Factorio Server Bridge")
ctx.rcon_client = None
ctx.exit_event.set()
finally:
factorio_process.terminate()
factorio_process.wait(5)
async def get_info(ctx: FactorioContext, rcon_client: factorio_rcon.RCONClient):
info = json.loads(rcon_client.send_command("/ap-rcon-info"))
ctx.auth = info["slot_name"]
ctx.seed_name = info["seed_name"]
# 0.2.0 addition, not present earlier
death_link = bool(info.get("death_link", False))
ctx.energy_link_increment = info.get("energy_link", 0)
logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}")
if ctx.energy_link_increment and ctx.ui:
ctx.ui.enable_energy_link()
await ctx.update_death_link(death_link)
async def factorio_spinup_server(ctx: FactorioContext) -> bool:
savegame_name = os.path.abspath("Archipelago.zip")
if not os.path.exists(savegame_name):
logger.info(f"Creating savegame {savegame_name}")
subprocess.run((
executable, "--create", savegame_name
))
factorio_process = subprocess.Popen(
(executable, "--start-server", savegame_name, *(str(elem) for elem in server_args)),
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.DEVNULL,
encoding="utf-8")
factorio_server_logger.info("Started Information Exchange Factorio Server")
factorio_queue = Queue()
stream_factorio_output(factorio_process.stdout, factorio_queue, factorio_process)
stream_factorio_output(factorio_process.stderr, factorio_queue, factorio_process)
rcon_client = None
try:
while not ctx.auth:
while not factorio_queue.empty():
msg = factorio_queue.get()
factorio_server_logger.info(msg)
if "Loading mod AP-" in msg and msg.endswith("(data.lua)"):
parts = msg.split()
ctx.mod_version = Utils.Version(*(int(number) for number in parts[-2].split(".")))
elif "Write data path: " in msg:
ctx.write_data_path = Utils.get_text_between(msg, "Write data path: ", " [")
if "AppData" in ctx.write_data_path:
logger.warning("It appears your mods are loaded from Appdata, "
"this can lead to problems with multiple Factorio instances. "
"If this is the case, you will get a file locked error running Factorio.")
if not rcon_client and "Starting RCON interface at IP ADDR:" in msg:
rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
if ctx.mod_version == ctx.__class__.mod_version:
raise Exception("No Archipelago mod was loaded. Aborting.")
await get_info(ctx, rcon_client)
await asyncio.sleep(0.01)
except Exception as e:
logger.exception(e)
logger.error("Aborted Factorio Server Bridge")
ctx.exit_event.set()
else:
logger.info(
f"Got World Information from AP Mod {tuple(ctx.mod_version)} for seed {ctx.seed_name} in slot {ctx.auth}")
return True
finally:
factorio_process.terminate()
factorio_process.wait(5)
return False
async def main(args):
ctx = FactorioContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
factorio_server_task = asyncio.create_task(factorio_spinup_server(ctx), name="FactorioSpinupServer")
successful_launch = await factorio_server_task
if successful_launch:
factorio_server_task = asyncio.create_task(factorio_server_watcher(ctx), name="FactorioServer")
progression_watcher = asyncio.create_task(
game_watcher(ctx), name="FactorioProgressionWatcher")
await ctx.exit_event.wait()
ctx.server_address = None
await progression_watcher
await factorio_server_task
await ctx.shutdown()
class FactorioJSONtoTextParser(JSONtoTextParser):
def _handle_color(self, node: JSONMessagePart):
colors = node["color"].split(";")
for color in colors:
if color in self.color_codes:
node["text"] = f"[color=#{self.color_codes[color]}]{node['text']}[/color]"
return self._handle_text(node)
return self._handle_text(node)
if __name__ == '__main__':
parser = get_base_parser(description="Optional arguments to FactorioClient follow. "
"Remaining arguments get passed into bound Factorio instance."
"Refer to Factorio --help for those.")
parser.add_argument('--rcon-port', default='24242', type=int, help='Port to use to communicate with Factorio')
parser.add_argument('--rcon-password', help='Password to authenticate with RCON.')
args, rest = parser.parse_known_args()
colorama.init()
rcon_port = args.rcon_port
rcon_password = args.rcon_password if args.rcon_password else ''.join(
random.choice(string.ascii_letters) for x in range(32))
factorio_server_logger = logging.getLogger("FactorioServer")
options = Utils.get_options()
executable = options["factorio_options"]["executable"]
if not os.path.exists(os.path.dirname(executable)):
raise FileNotFoundError(f"Path {os.path.dirname(executable)} does not exist or could not be accessed.")
if os.path.isdir(executable): # user entered a path to a directory, let's find the executable therein
executable = os.path.join(executable, "factorio")
if not os.path.isfile(executable):
if os.path.isfile(executable + ".exe"):
executable = executable + ".exe"
else:
raise FileNotFoundError(f"Path {executable} is not an executable file.")
server_args = ("--rcon-port", rcon_port, "--rcon-password", rcon_password, *rest)
asyncio.run(main(args))
colorama.deinit()
check_stdin()
launch()

830
Fill.py

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +1,68 @@
from __future__ import annotations
import argparse
import logging
import random
import urllib.request
import urllib.parse
from typing import Set, Dict, Tuple, Callable, Any, Union
import os
from collections import Counter
import random
import string
import urllib.parse
import urllib.request
from collections import Counter
from typing import Any, Dict, Tuple, Union
from itertools import chain
import ModuleUpdate
ModuleUpdate.update()
import copy
import Utils
from worlds.alttp import Options as LttPOptions
from worlds.generic import PlandoConnection
from Utils import parse_yamls, version_tuple, __version__, tuplize_version, get_options, local_path, user_path
from worlds.alttp.EntranceRandomizer import parse_arguments
from Main import main as ERmain
from BaseClasses import seeddigits, get_seed
import Options
from worlds.alttp import Bosses
from BaseClasses import seeddigits, get_seed, PlandoOptions
from Main import main as ERmain
from settings import get_settings
from Utils import parse_yamls, version_tuple, __version__, tuplize_version
from worlds.alttp.EntranceRandomizer import parse_arguments
from worlds.alttp.Text import TextTable
from worlds.AutoWorld import AutoWorldRegister
import copy
categories = set(AutoWorldRegister.world_types)
from worlds.generic import PlandoConnection
from worlds import failed_world_loads
def mystery_argparse():
options = get_options()
defaults = options["generator"]
def resolve_path(path: str, resolver: Callable[[str], str]) -> str:
return path if os.path.isabs(path) else resolver(path)
options = get_settings()
defaults = options.generator
parser = argparse.ArgumentParser(description="CMD Generation Interface, defaults come from host.yaml.")
parser.add_argument('--weights_file_path', default=defaults["weights_file_path"],
help='Path to the weights file to use for rolling game settings, urls are also valid')
parser.add_argument('--samesettings', help='Rolls settings per weights file rather than per player',
parser.add_argument('--weights_file_path', default=defaults.weights_file_path,
help='Path to the weights file to use for rolling game options, urls are also valid')
parser.add_argument('--sameoptions', help='Rolls options per weights file rather than per player',
action='store_true')
parser.add_argument('--player_files_path', default=resolve_path(defaults["player_files_path"], user_path),
parser.add_argument('--player_files_path', default=defaults.player_files_path,
help="Input directory for player files.")
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
parser.add_argument('--multi', default=defaults["players"], type=lambda value: max(int(value), 1))
parser.add_argument('--spoiler', type=int, default=defaults["spoiler"])
parser.add_argument('--lttp_rom', default=options["lttp_options"]["rom_file"],
help="Path to the 1.0 JP LttP Baserom.") # absolute, relative to cwd or relative to app path
parser.add_argument('--sm_rom', default=options["sm_options"]["rom_file"],
help="Path to the 1.0 JP SM Baserom.")
parser.add_argument('--enemizercli', default=resolve_path(defaults["enemizer_path"], local_path))
parser.add_argument('--outputpath', default=resolve_path(options["general_options"]["output_path"], user_path),
parser.add_argument('--multi', default=defaults.players, type=lambda value: max(int(value), 1))
parser.add_argument('--spoiler', type=int, default=defaults.spoiler)
parser.add_argument('--outputpath', default=options.general_options.output_path,
help="Path to output folder. Absolute or relative to cwd.") # absolute or relative to cwd
parser.add_argument('--race', action='store_true', default=defaults["race"])
parser.add_argument('--meta_file_path', default=defaults["meta_file_path"])
parser.add_argument('--race', action='store_true', default=defaults.race)
parser.add_argument('--meta_file_path', default=defaults.meta_file_path)
parser.add_argument('--log_level', default='info', help='Sets log level')
parser.add_argument('--yaml_output', default=0, type=lambda value: max(int(value), 0),
help='Output rolled mystery results to yaml up to specified number (made for async multiworld)')
parser.add_argument('--plando', default=defaults["plando_options"],
parser.add_argument('--plando', default=defaults.plando_options,
help='List of options that can be set manually. Can be combined, for example "bosses, items"')
parser.add_argument("--skip_prog_balancing", action="store_true",
help="Skip progression balancing step during generation.")
parser.add_argument("--skip_output", action="store_true",
help="Skips generation assertion and output stages and skips multidata and spoiler output. "
"Intended for debugging and testing purposes.")
args = parser.parse_args()
if not os.path.isabs(args.weights_file_path):
args.weights_file_path = os.path.join(args.player_files_path, args.weights_file_path)
if not os.path.isabs(args.meta_file_path):
args.meta_file_path = os.path.join(args.player_files_path, args.meta_file_path)
args.plando: Set[str] = {arg.strip().lower() for arg in args.plando.split(",")}
args.plando: PlandoOptions = PlandoOptions.from_option_string(args.plando)
return args, options
@@ -75,12 +73,16 @@ def get_seed_name(random_source) -> str:
def main(args=None, callback=ERmain):
if not args:
args, options = mystery_argparse()
else:
options = get_settings()
seed = get_seed(args.seed)
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
random.seed(seed)
seed_name = get_seed_name(random)
if args.race:
logging.info("Race mode enabled. Using non-deterministic random source.")
random.seed() # reset to time-based random source
weights_cache: Dict[str, Tuple[Any, ...]] = {}
@@ -88,83 +90,97 @@ def main(args=None, callback=ERmain):
try:
weights_cache[args.weights_file_path] = read_weights_yamls(args.weights_file_path)
except Exception as e:
raise ValueError(f"File {args.weights_file_path} is destroyed. Please fix your yaml.") from e
print(f"Weights: {args.weights_file_path} >> "
f"{get_choice('description', weights_cache[args.weights_file_path][-1], 'No description specified')}")
raise ValueError(f"File {args.weights_file_path} is invalid. Please fix your yaml.") from e
logging.info(f"Weights: {args.weights_file_path} >> "
f"{get_choice('description', weights_cache[args.weights_file_path][-1], 'No description specified')}")
if args.meta_file_path and os.path.exists(args.meta_file_path):
try:
weights_cache[args.meta_file_path] = read_weights_yamls(args.meta_file_path)
meta_weights = read_weights_yamls(args.meta_file_path)[-1]
except Exception as e:
raise ValueError(f"File {args.meta_file_path} is destroyed. Please fix your yaml.") from e
meta_weights = weights_cache[args.meta_file_path][-1]
print(f"Meta: {args.meta_file_path} >> {get_choice('meta_description', meta_weights)}")
del(meta_weights["meta_description"])
if args.samesettings:
raise Exception("Cannot mix --samesettings with --meta")
raise ValueError(f"File {args.meta_file_path} is invalid. Please fix your yaml.") from e
logging.info(f"Meta: {args.meta_file_path} >> {get_choice('meta_description', meta_weights)}")
try: # meta description allows us to verify that the file named meta.yaml is intentionally a meta file
del(meta_weights["meta_description"])
except Exception as e:
raise ValueError("No meta description found for meta.yaml. Unable to verify.") from e
if args.sameoptions:
raise Exception("Cannot mix --sameoptions with --meta")
else:
meta_weights = None
player_id = 1
player_files = {}
for file in os.scandir(args.player_files_path):
fname = file.name
if file.is_file() and os.path.join(args.player_files_path, fname) not in {args.meta_file_path, args.weights_file_path}:
if file.is_file() and not fname.startswith(".") and \
os.path.join(args.player_files_path, fname) not in {args.meta_file_path, args.weights_file_path}:
path = os.path.join(args.player_files_path, fname)
try:
weights_cache[fname] = read_weights_yamls(path)
except Exception as e:
raise ValueError(f"File {fname} is destroyed. Please fix your yaml.") from e
else:
for yaml in weights_cache[fname]:
print(f"P{player_id} Weights: {fname} >> "
f"{get_choice('description', yaml, 'No description specified')}")
player_files[player_id] = fname
player_id += 1
raise ValueError(f"File {fname} is invalid. Please fix your yaml.") from e
args.multi = max(player_id-1, args.multi)
print(f"Generating for {args.multi} player{'s' if args.multi > 1 else ''}, {seed_name} Seed {seed} with plando: "
f"{', '.join(args.plando)}")
# 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())}
for filename, yaml_data in weights_cache.items():
if filename not in {args.meta_file_path, args.weights_file_path}:
for yaml in yaml_data:
logging.info(f"P{player_id} Weights: {filename} >> "
f"{get_choice('description', yaml, 'No description specified')}")
player_files[player_id] = filename
player_id += 1
args.multi = max(player_id - 1, args.multi)
if args.multi == 0:
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."
)
logging.info(f"Generating for {args.multi} player{'s' if args.multi > 1 else ''}, "
f"{seed_name} Seed {seed} with plando: {args.plando}")
if not weights_cache:
raise Exception(f"No weights found. Provide a general weights file ({args.weights_file_path}) or individual player files. "
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.")
erargs = parse_arguments(['--multi', str(args.multi)])
erargs.seed = seed
erargs.glitch_triforce = options["generator"]["glitch_triforce_room"]
erargs.plando_options = args.plando
erargs.spoiler = args.spoiler
erargs.race = args.race
erargs.outputname = seed_name
erargs.outputpath = args.outputpath
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level)
erargs.lttp_rom = args.lttp_rom
erargs.sm_rom = args.sm_rom
erargs.enemizercli = args.enemizercli
erargs.skip_prog_balancing = args.skip_prog_balancing
erargs.skip_output = args.skip_output
settings_cache: Dict[str, Tuple[argparse.Namespace, ...]] = \
{fname: ( tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.samesettings else None)
for fname, yamls in weights_cache.items()}
player_path_cache = {}
for player in range(1, args.multi + 1):
player_path_cache[player] = player_files.get(player, args.weights_file_path)
{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:
option = get_choice(key, category_dict)
option = roll_meta_option(key, category_name, category_dict)
if option is not None:
for player, path in player_path_cache.items():
for path in weights_cache:
for yaml in weights_cache[path]:
if category_name is None:
yaml[key] = option
for category in yaml:
if category in AutoWorldRegister.world_types and \
key in Options.CommonOptions.type_hints:
yaml[category][key] = option
elif category_name not in yaml:
logging.warning(f"Meta: Category {category_name} is not present in {path}.")
else:
yaml[category_name][key] = option
yaml[category_name][key] = option
player_path_cache = {}
for player in range(1, args.multi + 1):
player_path_cache[player] = player_files.get(player, args.weights_file_path)
name_counter = Counter()
erargs.player_settings = {}
erargs.player_options = {}
player = 1
while player <= args.multi:
@@ -188,15 +204,15 @@ def main(args=None, callback=ERmain):
elif not erargs.name[player]: # if name was not specified, generate it from filename
erargs.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
player += 1
except Exception as e:
raise ValueError(f"File {path} is destroyed. Please fix your yaml.") from e
raise ValueError(f"File {path} is invalid. Please fix your yaml.") from e
else:
raise RuntimeError(f'No weights specified for player {player}')
if len(set(erargs.name.values())) != len(erargs.name):
raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}")
if len(set(name.lower() for name in erargs.name.values())) != len(erargs.name):
raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in erargs.name.values())}")
if args.yaml_output:
import yaml
@@ -220,7 +236,7 @@ def main(args=None, callback=ERmain):
with open(os.path.join(args.outputpath if args.outputpath else ".", f"generate_{seed_name}.yaml"), "wt") as f:
yaml.dump(important, f)
callback(erargs, seed)
return callback(erargs, seed)
def read_weights_yamls(path) -> Tuple[Any, ...]:
@@ -279,71 +295,84 @@ class SafeDict(dict):
def handle_name(name: str, player: int, name_counter: Counter):
name_counter[name] += 1
name_counter[name.lower()] += 1
number = name_counter[name.lower()]
new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")])
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=name_counter[name],
NUMBER=(name_counter[name] if name_counter[
name] > 1 else ''),
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=number,
NUMBER=(number if number > 1 else ''),
player=player,
PLAYER=(player if player > 1 else '')))
new_name = new_name.strip()[:16]
# Run .strip twice for edge case where after the initial .slice new_name has a leading whitespace.
# Could cause issues for some clients that cannot handle the additional whitespace.
new_name = new_name.strip()[:16].strip()
if new_name == "Archipelago":
raise Exception(f"You cannot name yourself \"{new_name}\"")
return new_name
def prefer_int(input_data: str) -> Union[str, int]:
try:
return int(input_data)
except:
return input_data
available_boss_names: Set[str] = {boss.lower() for boss in Bosses.boss_table if boss not in
{'Agahnim', 'Agahnim2', 'Ganon'}}
available_boss_locations: Set[str] = {f"{loc.lower()}{f' {level}' if level else ''}" for loc, level in
Bosses.boss_location_table}
boss_shuffle_options = {None: 'none',
'none': 'none',
'basic': 'basic',
'full': 'full',
'chaos': 'chaos',
'singularity': 'singularity'
}
goals = {
'ganon': 'ganon',
'crystals': 'crystals',
'bosses': 'bosses',
'pedestal': 'pedestal',
'ganon_pedestal': 'ganonpedestal',
'triforce_hunt': 'triforcehunt',
'local_triforce_hunt': 'localtriforcehunt',
'ganon_triforce_hunt': 'ganontriforcehunt',
'local_ganon_triforce_hunt': 'localganontriforcehunt',
'ice_rod_hunt': 'icerodhunt',
}
def roll_percentage(percentage: Union[int, float]) -> bool:
"""Roll a percentage chance.
percentage is expected to be in range [0, 100]"""
return random.random() < (float(percentage) / 100)
def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> dict:
def update_weights(weights: dict, new_weights: dict, update_type: str, name: str) -> dict:
logging.debug(f'Applying {new_weights}')
new_options = set(new_weights) - set(weights)
weights.update(new_weights)
cleaned_weights = {}
for option in new_weights:
option_name = option.lstrip("+-")
if option.startswith("+") and option_name in weights:
cleaned_value = weights[option_name]
new_value = new_weights[option]
if isinstance(new_value, set):
cleaned_value.update(new_value)
elif isinstance(new_value, list):
cleaned_value.extend(new_value)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) + Counter(new_value))
else:
raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
cleaned_weights[option_name] = cleaned_value
elif option.startswith("-") and option_name in weights:
cleaned_value = weights[option_name]
new_value = new_weights[option]
if isinstance(new_value, set):
cleaned_value.difference_update(new_value)
elif isinstance(new_value, list):
for element in new_value:
cleaned_value.remove(element)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) - Counter(new_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]
new_options = set(cleaned_weights) - set(weights)
weights.update(cleaned_weights)
if new_options:
for new_option in new_options:
logging.warning(f'{type} Suboption "{new_option}" of "{name}" did not '
logging.warning(f'{update_type} Suboption "{new_option}" of "{name}" did not '
f'overwrite a root option. '
f'This is probably in error.')
return weights
def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
if not game:
return get_choice(option_key, category_dict)
if game in AutoWorldRegister.world_types:
game_world = AutoWorldRegister.world_types[game]
options = game_world.options_dataclass.type_hints
if option_key in options:
if options[option_key].supports_weighting:
return get_choice(option_key, category_dict)
return category_dict[option_key]
raise Options.OptionError(f"Error generating meta option {option_key} for {game}.")
def roll_linked_options(weights: dict) -> dict:
weights = copy.deepcopy(weights) # make sure we don't write back to other weights sets in same_settings
for option_set in weights["linked_options"]:
@@ -361,12 +390,12 @@ def roll_linked_options(weights: dict) -> dict:
else:
logging.debug(f"linked option {option_set['name']} skipped.")
except Exception as e:
raise ValueError(f"Linked option {option_set['name']} is destroyed. "
raise ValueError(f"Linked option {option_set['name']} is invalid. "
f"Please fix your linked option.") from e
return weights
def roll_triggers(weights: dict, triggers: list) -> dict:
def roll_triggers(weights: dict, triggers: list, valid_keys: set) -> dict:
weights = copy.deepcopy(weights) # make sure we don't write back to other weights sets in same_settings
weights["_Generator_Version"] = Utils.__version__
for i, option_set in enumerate(triggers):
@@ -389,71 +418,37 @@ def roll_triggers(weights: dict, triggers: list) -> dict:
if category_name:
currently_targeted_weights = currently_targeted_weights[category_name]
update_weights(currently_targeted_weights, category_options, "Triggered", option_set["option_name"])
valid_keys.add(key)
except Exception as e:
raise ValueError(f"Your trigger number {i + 1} is destroyed. "
raise ValueError(f"Your trigger number {i + 1} is invalid. "
f"Please fix your triggers.") from e
return weights
def get_plando_bosses(boss_shuffle: str, plando_options: Set[str]) -> str:
if boss_shuffle in boss_shuffle_options:
return boss_shuffle_options[boss_shuffle]
elif "bosses" in plando_options:
options = boss_shuffle.lower().split(";")
remainder_shuffle = "none" # vanilla
bosses = []
for boss in options:
if boss in boss_shuffle_options:
remainder_shuffle = boss_shuffle_options[boss]
elif "-" in boss:
loc, boss_name = boss.split("-")
if boss_name not in available_boss_names:
raise ValueError(f"Unknown Boss name {boss_name}")
if loc not in available_boss_locations:
raise ValueError(f"Unknown Boss Location {loc}")
level = ''
if loc.split(" ")[-1] in {"top", "middle", "bottom"}:
# split off level
loc = loc.split(" ")
level = f" {loc[-1]}"
loc = " ".join(loc[:-1])
loc = loc.title().replace("Of", "of")
if not Bosses.can_place_boss(boss_name.title(), loc, level):
raise ValueError(f"Cannot place {boss_name} at {loc}{level}")
bosses.append(boss)
elif boss not in available_boss_names:
raise ValueError(f"Unknown Boss name or Boss shuffle option {boss}.")
else:
bosses.append(boss)
return ";".join(bosses + [remainder_shuffle])
else:
raise Exception(f"Boss Shuffle {boss_shuffle} is unknown and boss plando is turned off.")
def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, option: type(Options.Option)):
if option_key in game_weights:
try:
def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, option: type(Options.Option), plando_options: PlandoOptions):
try:
if option_key in game_weights:
if not option.supports_weighting:
player_option = option.from_any(game_weights[option_key])
else:
player_option = option.from_any(get_choice(option_key, game_weights))
setattr(ret, option_key, player_option)
except Exception as e:
raise Exception(f"Error generating option {option_key} in {ret.game}") from e
del game_weights[option_key]
else:
if hasattr(player_option, "verify"):
player_option.verify(AutoWorldRegister.world_types[ret.game])
player_option = option.from_any(option.default) # call the from_any here to support default "random"
setattr(ret, option_key, player_option)
except Exception as e:
raise Options.OptionError(f"Error generating option {option_key} in {ret.game}") from e
else:
setattr(ret, option_key, option(option.default))
player_option.verify(AutoWorldRegister.world_types[ret.game], ret.name, plando_options)
def roll_settings(weights: dict, plando_options: Set[str] = frozenset(("bosses",))):
def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses):
if "linked_options" in weights:
weights = roll_linked_options(weights)
valid_trigger_names = set()
if "triggers" in weights:
weights = roll_triggers(weights, weights["triggers"])
weights = roll_triggers(weights, weights["triggers"], valid_trigger_names)
requirements = weights.get("requires", {})
if requirements:
@@ -461,171 +456,75 @@ def roll_settings(weights: dict, plando_options: Set[str] = frozenset(("bosses",
if tuplize_version(version) > version_tuple:
raise Exception(f"Settings reports required version of generator is at least {version}, "
f"however generator is of version {__version__}")
required_plando_options = requirements.get("plando", "")
if required_plando_options:
required_plando_options = set(option.strip() for option in required_plando_options.split(","))
required_plando_options -= plando_options
required_plando_options = PlandoOptions.from_option_string(requirements.get("plando", ""))
if required_plando_options not in plando_options:
if required_plando_options:
if len(required_plando_options) == 1:
raise Exception(f"Settings reports required plando module {', '.join(required_plando_options)}, "
f"which is not enabled.")
else:
raise Exception(f"Settings reports required plando modules {', '.join(required_plando_options)}, "
f"which are not enabled.")
raise Exception(f"Settings reports required plando module {str(required_plando_options)}, "
f"which is not enabled.")
ret = argparse.Namespace()
for option_key in Options.per_game_common_options:
if option_key in weights and option_key not in Options.common_options:
for option_key in Options.PerGameCommonOptions.type_hints:
if option_key in weights and option_key not in Options.CommonOptions.type_hints:
raise Exception(f"Option {option_key} has to be in a game's section, not on its own.")
ret.game = get_choice("game", weights)
if ret.game not in AutoWorldRegister.world_types:
picks = Utils.get_fuzzy_results(ret.game, list(AutoWorldRegister.world_types) + failed_world_loads, limit=1)[0]
if picks[0] in failed_world_loads:
raise Exception(f"No functional world found to handle game {ret.game}. "
f"Did you mean '{picks[0]}' ({picks[1]}% sure)? "
f"If so, it appears the world failed to initialize correctly.")
raise Exception(f"No world found to handle game {ret.game}. Did you mean '{picks[0]}' ({picks[1]}% sure)? "
f"Check your spelling or installation of that world.")
if ret.game not in weights:
raise Exception(f"No game options for selected game \"{ret.game}\" found.")
world_type = AutoWorldRegister.world_types[ret.game]
game_weights = weights[ret.game]
for weight in chain(game_weights, weights):
if weight.startswith("+"):
raise Exception(f"Merge tag cannot be used outside of trigger contexts. Found {weight}")
if weight.startswith("-"):
raise Exception(f"Remove tag cannot be used outside of trigger contexts. Found {weight}")
if "triggers" in game_weights:
weights = roll_triggers(weights, game_weights["triggers"])
weights = roll_triggers(weights, game_weights["triggers"], valid_trigger_names)
game_weights = weights[ret.game]
ret.name = get_choice('name', weights)
for option_key, option in Options.common_options.items():
for option_key, option in Options.CommonOptions.type_hints.items():
setattr(ret, option_key, option.from_any(get_choice(option_key, weights, option.default)))
if ret.game in AutoWorldRegister.world_types:
for option_key, option in world_type.options.items():
handle_option(ret, game_weights, option_key, option)
for option_key, option in Options.per_game_common_options.items():
# skip setting this option if already set from common_options, defaulting to root option
if not (option_key in Options.common_options and option_key not in game_weights):
handle_option(ret, game_weights, option_key, option)
if "items" in plando_options:
ret.plando_items = game_weights.get("plando_items", [])
if ret.game == "Minecraft" or ret.game == "Ocarina of Time":
# bad hardcoded behavior to make this work for now
ret.plando_connections = []
if "connections" in plando_options:
options = game_weights.get("plando_connections", [])
for placement in options:
if roll_percentage(get_choice("percentage", placement, 100)):
ret.plando_connections.append(PlandoConnection(
get_choice("entrance", placement),
get_choice("exit", placement),
get_choice("direction", placement)
))
elif ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights, plando_options)
else:
raise Exception(f"Unsupported game {ret.game}")
for option_key, option in world_type.options_dataclass.type_hints.items():
handle_option(ret, game_weights, option_key, option, plando_options)
for option_key in game_weights:
if option_key in {"triggers", *valid_trigger_names}:
continue
logging.warning(f"{option_key} is not a valid option name for {ret.game} and is not present in triggers.")
if PlandoOptions.items in plando_options:
ret.plando_items = game_weights.get("plando_items", [])
if ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights, plando_options)
if PlandoOptions.connections in plando_options:
ret.plando_connections = []
options = game_weights.get("plando_connections", [])
for placement in options:
if roll_percentage(get_choice("percentage", placement, 100)):
ret.plando_connections.append(PlandoConnection(
get_choice("entrance", placement),
get_choice("exit", placement),
get_choice("direction", placement, "both")
))
return ret
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
if "dungeon_items" in weights and get_choice_legacy('dungeon_items', weights, "none") != "none":
raise Exception(f"dungeon_items key in A Link to the Past was removed, but is present in these weights as {get_choice_legacy('dungeon_items', weights, False)}.")
glitches_required = get_choice_legacy('glitches_required', weights)
if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'hybrid_major_glitches', 'minor_glitches']:
logging.warning("Only NMG, OWG, HMG and No Logic supported")
glitches_required = 'none'
ret.logic = {None: 'noglitches', 'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches',
'minor_glitches': 'minorglitches', 'hybrid_major_glitches': 'hybridglitches'}[
glitches_required]
ret.dark_room_logic = get_choice_legacy("dark_room_logic", weights, "lamp")
if not ret.dark_room_logic: # None/False
ret.dark_room_logic = "none"
if ret.dark_room_logic == "sconces":
ret.dark_room_logic = "torches"
if ret.dark_room_logic not in {"lamp", "torches", "none"}:
raise ValueError(f"Unknown Dark Room Logic: \"{ret.dark_room_logic}\"")
entrance_shuffle = get_choice_legacy('entrance_shuffle', weights, 'vanilla')
if entrance_shuffle.startswith('none-'):
ret.shuffle = 'vanilla'
else:
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
goal = get_choice_legacy('goals', weights, 'ganon')
ret.goal = goals[goal]
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
# fast ganon + ganon at hole
ret.open_pyramid = get_choice_legacy('open_pyramid', weights, 'goal')
extra_pieces = get_choice_legacy('triforce_pieces_mode', weights, 'available')
ret.triforce_pieces_required = LttPOptions.TriforcePieces.from_any(get_choice_legacy('triforce_pieces_required', weights, 20))
# sum a percentage to required
if extra_pieces == 'percentage':
percentage = max(100, float(get_choice_legacy('triforce_pieces_percentage', weights, 150))) / 100
ret.triforce_pieces_available = int(round(ret.triforce_pieces_required * percentage, 0))
# vanilla mode (specify how many pieces are)
elif extra_pieces == 'available':
ret.triforce_pieces_available = LttPOptions.TriforcePieces.from_any(
get_choice_legacy('triforce_pieces_available', weights, 30))
# required pieces + fixed extra
elif extra_pieces == 'extra':
extra_pieces = max(0, int(get_choice_legacy('triforce_pieces_extra', weights, 10)))
ret.triforce_pieces_available = ret.triforce_pieces_required + extra_pieces
# change minimum to required pieces to avoid problems
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
ret.shop_shuffle = get_choice_legacy('shop_shuffle', weights, '')
if not ret.shop_shuffle:
ret.shop_shuffle = ''
ret.mode = get_choice_legacy("mode", weights)
ret.difficulty = get_choice_legacy('item_pool', weights)
ret.item_functionality = get_choice_legacy('item_functionality', weights)
boss_shuffle = get_choice_legacy('boss_shuffle', weights)
ret.shufflebosses = get_plando_bosses(boss_shuffle, plando_options)
ret.enemy_damage = {None: 'default',
'default': 'default',
'shuffled': 'shuffled',
'random': 'chaos', # to be removed
'chaos': 'chaos',
}[get_choice_legacy('enemy_damage', weights)]
ret.enemy_health = get_choice_legacy('enemy_health', weights)
ret.timer = {'none': False,
None: False,
False: False,
'timed': 'timed',
'timed_ohko': 'timed-ohko',
'ohko': 'ohko',
'timed_countdown': 'timed-countdown',
'display': 'display'}[get_choice_legacy('timer', weights, False)]
ret.countdown_start_time = int(get_choice_legacy('countdown_start_time', weights, 10))
ret.red_clock_time = int(get_choice_legacy('red_clock_time', weights, -2))
ret.blue_clock_time = int(get_choice_legacy('blue_clock_time', weights, 2))
ret.green_clock_time = int(get_choice_legacy('green_clock_time', weights, 4))
ret.dungeon_counters = get_choice_legacy('dungeon_counters', weights, 'default')
ret.shuffle_prizes = get_choice_legacy('shuffle_prizes', weights, "g")
ret.required_medallions = [get_choice_legacy("misery_mire_medallion", weights, "random"),
get_choice_legacy("turtle_rock_medallion", weights, "random")]
for index, medallion in enumerate(ret.required_medallions):
ret.required_medallions[index] = {"ether": "Ether", "quake": "Quake", "bombos": "Bombos", "random": "random"} \
.get(medallion.lower(), None)
if not ret.required_medallions[index]:
raise Exception(f"unknown Medallion {medallion} for {'misery mire' if index == 0 else 'turtle rock'}")
ret.plando_texts = {}
if "texts" in plando_options:
if PlandoOptions.texts in plando_options:
tt = TextTable()
tt.removeUnwantedText()
options = weights.get("plando_texts", [])
@@ -636,17 +535,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
raise Exception(f"No text target \"{at}\" found.")
ret.plando_texts[at] = str(get_choice_legacy("text", placement))
ret.plando_connections = []
if "connections" in plando_options:
options = weights.get("plando_connections", [])
for placement in options:
if roll_percentage(get_choice_legacy("percentage", placement, 100)):
ret.plando_connections.append(PlandoConnection(
get_choice_legacy("entrance", placement),
get_choice_legacy("exit", placement),
get_choice_legacy("direction", placement, "both")
))
ret.sprite_pool = weights.get('sprite_pool', [])
ret.sprite = get_choice_legacy('sprite', weights, "Link")
if 'random_sprite_on_event' in weights:
@@ -674,6 +562,15 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
if __name__ == '__main__':
import atexit
confirmation = atexit.register(input, "Press enter to close.")
main()
multiworld = main()
if __debug__:
import gc
import sys
import weakref
weak = weakref.ref(multiworld)
del multiworld
gc.collect() # need to collect to deref all hard references
assert not weak(), f"MultiWorld object was not de-allocated, it's referenced {sys.getrefcount(weak())} times." \
" This would be a memory leak."
# in case of error-free exit should not need confirmation
atexit.unregister(confirmation)

8
KH2Client.py Normal file
View File

@@ -0,0 +1,8 @@
import ModuleUpdate
import Utils
from worlds.kh2.Client import launch
ModuleUpdate.update()
if __name__ == '__main__':
Utils.init_logging("KH2Client", exception_logger="Client")
launch()

View File

@@ -10,25 +10,32 @@ Scroll down to components= to add components to the launcher as well as setup.py
import argparse
from os.path import isfile
import sys
from typing import Iterable, Sequence, Callable, Union, Optional
import subprocess
import itertools
from Utils import is_frozen, user_path, local_path, init_logging
from shutil import which
import shlex
from enum import Enum, auto
import logging
import multiprocessing
import shlex
import subprocess
import sys
import webbrowser
from os.path import isfile
from shutil import which
from typing import Sequence, Union, Optional
import Utils
import settings
from worlds.LauncherComponents import Component, components, Type, SuffixIdentifier, icon_paths
is_linux = sys.platform.startswith('linux')
is_macos = sys.platform == 'darwin'
is_windows = sys.platform in ("win32", "cygwin", "msys")
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
def open_host_yaml():
file = user_path('host.yaml')
file = settings.get_settings().filename
assert file, "host.yaml missing"
if is_linux:
exe = which('sensible-editor') or which('gedit') or \
which('xdg-open') or which('gnome-open') or which('kde-open')
@@ -37,166 +44,103 @@ def open_host_yaml():
exe = which("open")
subprocess.Popen([exe, file])
else:
import webbrowser
webbrowser.open(file)
def open_patch():
suffixes = []
for c in components:
if c.type == Type.CLIENT and \
isinstance(c.file_identifier, SuffixIdentifier) and \
(c.script_name is None or isfile(get_exe(c)[-1])):
suffixes += c.file_identifier.suffixes
try:
import tkinter
import tkinter.filedialog
filename = open_filename("Select patch", (("Patches", suffixes),))
except Exception as e:
logging.error("Could not load tkinter, which is likely not installed. "
"This attempt was made because Launcher.open_patch was used.")
raise e
messagebox("Error", str(e), error=True)
else:
root = tkinter.Tk()
root.withdraw()
suffixes = []
for c in components:
if isfile(get_exe(c)[-1]):
suffixes += c.file_identifier.suffixes if c.type == Type.CLIENT and \
isinstance(c.file_identifier, SuffixIdentifier) else []
filename = tkinter.filedialog.askopenfilename(filetypes=(('Patches', ' '.join(suffixes)),))
file, _, component = identify(filename)
file, component = identify(filename)
if file and component:
launch([*get_exe(component), file], component.cli)
exe = get_exe(component)
if exe is None or not isfile(exe[-1]):
exe = get_exe("Launcher")
launch([*exe, file], component.cli)
def generate_yamls():
from Options import generate_yaml_templates
target = Utils.user_path("Players", "Templates")
generate_yaml_templates(target, False)
open_folder(target)
def browse_files():
file = user_path()
open_folder(user_path())
def open_folder(folder_path):
if is_linux:
exe = which('xdg-open') or which('gnome-open') or which('kde-open')
subprocess.Popen([exe, file])
subprocess.Popen([exe, folder_path])
elif is_macos:
exe = which("open")
subprocess.Popen([exe, file])
subprocess.Popen([exe, folder_path])
else:
import webbrowser
webbrowser.open(file)
webbrowser.open(folder_path)
class Type(Enum):
TOOL = auto()
FUNC = auto() # not a real component
CLIENT = auto()
ADJUSTER = auto()
def update_settings():
from settings import get_settings
get_settings().save()
class SuffixIdentifier:
suffixes: Iterable[str]
def __init__(self, *args: str):
self.suffixes = args
def __call__(self, path: str):
if isinstance(path, str):
for suffix in self.suffixes:
if path.endswith(suffix):
return True
return False
class Component:
display_name: str
type: Optional[Type]
script_name: Optional[str]
frozen_name: Optional[str]
icon: str # just the name, no suffix
cli: bool
func: Optional[Callable]
file_identifier: Optional[Callable[[str], bool]]
def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None,
cli: bool = False, icon: str = 'icon', component_type: Type = None, func: Optional[Callable] = None,
file_identifier: Optional[Callable[[str], bool]] = None):
self.display_name = display_name
self.script_name = script_name
self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None
self.icon = icon
self.cli = cli
self.type = component_type or \
None if not display_name else \
Type.FUNC if func else \
Type.CLIENT if 'Client' in display_name else \
Type.ADJUSTER if 'Adjuster' in display_name else Type.TOOL
self.func = func
self.file_identifier = file_identifier
def handles_file(self, path: str):
return self.file_identifier(path) if self.file_identifier else False
components: Iterable[Component] = (
# Launcher
Component('', 'Launcher'),
# Core
Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True,
file_identifier=SuffixIdentifier('.archipelago', '.zip')),
Component('Generate', 'Generate', cli=True),
Component('Text Client', 'CommonClient', 'ArchipelagoTextClient'),
# SNI
Component('SNI Client', 'SNIClient',
file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3')),
Component('LttP Adjuster', 'LttPAdjuster'),
# Factorio
Component('Factorio Client', 'FactorioClient'),
# Minecraft
Component('Minecraft Client', 'MinecraftClient', icon='mcicon', cli=True,
file_identifier=SuffixIdentifier('.apmc')),
# Ocarina of Time
Component('OoT Client', 'OoTClient',
file_identifier=SuffixIdentifier('.apz5')),
Component('OoT Adjuster', 'OoTAdjuster'),
# FF1
Component('FF1 Client', 'FF1Client'),
# ChecksFinder
Component('ChecksFinder Client', 'ChecksFinderClient'),
# Starcraft 2
Component('Starcraft 2 Client', 'Starcraft2Client'),
components.extend([
# Functions
Component('Open host.yaml', func=open_host_yaml),
Component('Open Patch', func=open_patch),
Component('Browse Files', func=browse_files),
)
icon_paths = {
'icon': local_path('data', 'icon.ico' if is_windows else 'icon.png'),
'mcicon': local_path('data', 'mcicon.ico')
}
Component("Open host.yaml", func=open_host_yaml),
Component("Open Patch", func=open_patch),
Component("Generate Template Options", func=generate_yamls),
Component("Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/8Z65BR2")),
Component("Unrated/18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4")),
Component("Browse Files", func=browse_files),
])
def identify(path: Union[None, str]):
if path is None:
return None, None, None
return None, None
for component in components:
if component.handles_file(path):
return path, component.script_name, component
return (None, None, None) if '/' in path or '\\' in path else (None, path, None)
return path, component
elif path == component.display_name or path == component.script_name:
return None, component
return None, None
def get_exe(component: Union[str, Component]) -> Optional[Sequence[str]]:
if isinstance(component, str):
name = component
component = None
if name.startswith('Archipelago'):
if name.startswith("Archipelago"):
name = name[11:]
if name.endswith('.exe'):
if name.endswith(".exe"):
name = name[:-4]
if name.endswith('.py'):
if name.endswith(".py"):
name = name[:-3]
if not name:
return None
for c in components:
if c.script_name == name or c.frozen_name == f'Archipelago{name}':
if c.script_name == name or c.frozen_name == f"Archipelago{name}":
component = c
break
if not component:
return None
if is_frozen():
suffix = '.exe' if is_windows else ''
return [local_path(f'{component.frozen_name}{suffix}')]
suffix = ".exe" if is_windows else ""
return [local_path(f"{component.frozen_name}{suffix}")] if component.frozen_name else None
else:
return [sys.executable, local_path(f'{component.script_name}.py')]
return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None
def launch(exe, in_terminal=False):
@@ -217,24 +161,19 @@ def launch(exe, in_terminal=False):
def run_gui():
if not sys.stdout:
from kvui import App, ContainerLayout, GridLayout, Button, Label # this kills stdout
else:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout as ContainerLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget
from kivy.uix.image import AsyncImage
from kivy.uix.relativelayout import RelativeLayout
class Launcher(App):
base_title: str = "Archipelago Launcher"
container: ContainerLayout
grid: GridLayout
_tools = {c.display_name: c for c in components if c.type == Type.TOOL and isfile(get_exe(c)[-1])}
_clients = {c.display_name: c for c in components if c.type == Type.CLIENT and isfile(get_exe(c)[-1])}
_adjusters = {c.display_name: c for c in components if c.type == Type.ADJUSTER and isfile(get_exe(c)[-1])}
_funcs = {c.display_name: c for c in components if c.type == Type.FUNC}
_tools = {c.display_name: c for c in components if c.type == Type.TOOL}
_clients = {c.display_name: c for c in components if c.type == Type.CLIENT}
_adjusters = {c.display_name: c for c in components if c.type == Type.ADJUSTER}
_miscs = {c.display_name: c for c in components if c.type == Type.MISC}
def __init__(self, ctx=None):
self.title = self.base_title
@@ -246,64 +185,115 @@ def run_gui():
self.container = ContainerLayout()
self.grid = GridLayout(cols=2)
self.container.add_widget(self.grid)
self.grid.add_widget(Label(text="General", size_hint_y=None, height=40))
self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40))
tool_layout = ScrollBox()
tool_layout.layout.orientation = "vertical"
self.grid.add_widget(tool_layout)
client_layout = ScrollBox()
client_layout.layout.orientation = "vertical"
self.grid.add_widget(client_layout)
def build_button(component: Component) -> Widget:
"""
Builds a button widget for a given component.
Args:
component (Component): The component associated with the button.
Returns:
None. The button is added to the parent grid layout.
"""
button = Button(text=component.display_name, size_hint_y=None, height=40)
button.component = component
button.bind(on_release=self.component_action)
if component.icon != "icon":
image = AsyncImage(source=icon_paths[component.icon],
size=(38, 38), size_hint=(None, 1), pos=(5, 0))
box_layout = RelativeLayout(size_hint_y=None, height=40)
box_layout.add_widget(button)
box_layout.add_widget(image)
return box_layout
return button
button_layout = self.grid # make buttons fill the window
for (tool, client) in itertools.zip_longest(itertools.chain(
self._tools.items(), self._funcs.items(), self._adjusters.items()), self._clients.items()):
self._tools.items(), self._miscs.items(), self._adjusters.items()), self._clients.items()):
# column 1
if tool:
button = Button(text=tool[0])
button.component = tool[1]
button.bind(on_release=self.component_action)
button_layout.add_widget(button)
else:
button_layout.add_widget(Label())
tool_layout.layout.add_widget(build_button(tool[1]))
# column 2
if client:
button = Button(text=client[0])
button.component = client[1]
button.bind(on_press=self.component_action)
button_layout.add_widget(button)
else:
button_layout.add_widget(Label())
client_layout.layout.add_widget(build_button(client[1]))
return self.container
@staticmethod
def component_action(button):
if button.component.type == Type.FUNC:
if button.component.func:
button.component.func()
else:
launch(get_exe(button.component), button.component.cli)
def _stop(self, *largs):
# ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm.
# Closing the window explicitly cleans it up.
self.root_window.close()
super()._stop(*largs)
Launcher().run()
def run_component(component: Component, *args):
if component.func:
component.func(*args)
elif component.script_name:
subprocess.run([*get_exe(component.script_name), *args])
else:
logging.warning(f"Component {component} does not appear to be executable.")
def main(args: Optional[Union[argparse.Namespace, dict]] = None):
if isinstance(args, argparse.Namespace):
args = {k: v for k, v in args._get_kwargs()}
elif not args:
args = {}
if "Patch|Game|Component" in args:
file, component, _ = identify(args["Patch|Game|Component"])
if args.get("Patch|Game|Component", None) is not None:
file, component = identify(args["Patch|Game|Component"])
if file:
args['file'] = file
if component:
args['component'] = component
if not component:
logging.warning(f"Could not identify Component responsible for {args['Patch|Game|Component']}")
if args["update_settings"]:
update_settings()
if 'file' in args:
subprocess.run([*get_exe(args['component']), args['file'], *args['args']])
run_component(args["component"], args["file"], *args["args"])
elif 'component' in args:
subprocess.run([*get_exe(args['component']), *args['args']])
else:
run_component(args["component"], *args["args"])
elif not args["update_settings"]:
run_gui()
if __name__ == '__main__':
init_logging('Launcher')
Utils.freeze_support()
multiprocessing.set_start_method("spawn") # if launched process uses kivy, fork won't work
parser = argparse.ArgumentParser(description='Archipelago Launcher')
parser.add_argument('Patch|Game|Component', type=str, nargs='?',
help="Pass either a patch file, a generated game or the name of a component to run.")
parser.add_argument('args', nargs="*", help="Arguments to pass to component.")
run_group = parser.add_argument_group("Run")
run_group.add_argument("--update_settings", action="store_true",
help="Update host.yaml and exit.")
run_group.add_argument("Patch|Game|Component", type=str, nargs="?",
help="Pass either a patch file, a generated game or the name of a component to run.")
run_group.add_argument("args", nargs="*",
help="Arguments to pass to component.")
main(parser.parse_args())
from worlds.LauncherComponents import processes
for process in processes:
# we await all child processes to close before we tear down the process host
# this makes it feel like each one is its own program, as the Launcher is closed now
process.join()

701
LinksAwakeningClient.py Normal file
View File

@@ -0,0 +1,701 @@
import ModuleUpdate
ModuleUpdate.update()
import Utils
if __name__ == "__main__":
Utils.init_logging("LinksAwakeningContext", exception_logger="Client")
import asyncio
import base64
import binascii
import colorama
import io
import os
import re
import select
import shlex
import socket
import struct
import sys
import subprocess
import time
import typing
from CommonClient import (CommonContext, get_base_parser, gui_enabled, logger,
server_loop)
from NetUtils import ClientStatus
from worlds.ladx.Common import BASE_ID as LABaseID
from worlds.ladx.GpsTracker import GpsTracker
from worlds.ladx.ItemTracker import ItemTracker
from worlds.ladx.LADXR.checkMetadata import checkMetadataTable
from worlds.ladx.Locations import get_locations_to_id, meta_to_name
from worlds.ladx.Tracker import LocationTracker, MagpieBridge
class GameboyException(Exception):
pass
class RetroArchDisconnectError(GameboyException):
pass
class InvalidEmulatorStateError(GameboyException):
pass
class BadRetroArchResponse(GameboyException):
pass
def magpie_logo():
from kivy.uix.image import CoreImage
binary_data = """
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXN
SR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA
7DAcdvqGQAAADGSURBVDhPhVLBEcIwDHOYhjHCBuXHj2OTbAL8+
MEGZIxOQ1CinOOk0Op0bmo7tlXXeR9FJMYDLOD9mwcLjQK7+hSZ
wgcWMZJOAGeGKtChNHFL0j+FZD3jSCuo0w7l03wDrWdg00C4/aW
eDEYNenuzPOfPspBnxf0kssE80vN0L8361j10P03DK4x6FHabuV
ear8fHme+b17rwSjbAXeUMLb+EVTV2QHm46MWQanmnydA98KsVS
XkV+qFpGQXrLhT/fqraQeQLuplpNH5g+WkAAAAASUVORK5CYII="""
binary_data = base64.b64decode(binary_data)
data = io.BytesIO(binary_data)
return CoreImage(data, ext="png").texture
class LAClientConstants:
# Connector version
VERSION = 0x01
#
# Memory locations of LADXR
ROMGameID = 0x0051 # 4 bytes
SlotName = 0x0134
# Unused
# ROMWorldID = 0x0055
# ROMConnectorVersion = 0x0056
# RO: We should only act if this is higher then 6, as it indicates that the game is running normally
wGameplayType = 0xDB95
# RO: Starts at 0, increases every time an item is received from the server and processed
wLinkSyncSequenceNumber = 0xDDF6
wLinkStatusBits = 0xDDF7 # RW:
# Bit0: wLinkGive* contains valid data, set from script cleared from ROM.
wLinkHealth = 0xDB5A
wLinkGiveItem = 0xDDF8 # RW
wLinkGiveItemFrom = 0xDDF9 # RW
# All of these six bytes are unused, we can repurpose
# wLinkSendItemRoomHigh = 0xDDFA # RO
# wLinkSendItemRoomLow = 0xDDFB # RO
# wLinkSendItemTarget = 0xDDFC # RO
# wLinkSendItemItem = 0xDDFD # RO
# wLinkSendShopItem = 0xDDFE # RO, which item to send (1 based, order of the shop items)
# RO, which player to send to, but it's just the X position of the NPC used, so 0x18 is player 0
# wLinkSendShopTarget = 0xDDFF
wRecvIndex = 0xDDFD # Two bytes
wCheckAddress = 0xC0FF - 0x4
WRamCheckSize = 0x4
WRamSafetyValue = bytearray([0]*WRamCheckSize)
MinGameplayValue = 0x06
MaxGameplayValue = 0x1A
VictoryGameplayAndSub = 0x0102
class RAGameboy():
cache = []
cache_start = 0
cache_size = 0
last_cache_read = None
socket = None
def __init__(self, address, port) -> None:
self.address = address
self.port = port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
assert (self.socket)
self.socket.setblocking(False)
async def send_command(self, command, timeout=1.0):
self.send(f'{command}\n')
response_str = await self.async_recv()
self.check_command_response(command, response_str)
return response_str.rstrip()
async def get_retroarch_version(self):
return await self.send_command("VERSION")
async def get_retroarch_status(self):
return await self.send_command("GET_STATUS")
def set_cache_limits(self, cache_start, cache_size):
self.cache_start = cache_start
self.cache_size = cache_size
def send(self, b):
if type(b) is str:
b = b.encode('ascii')
self.socket.sendto(b, (self.address, self.port))
def recv(self):
select.select([self.socket], [], [])
response, _ = self.socket.recvfrom(4096)
return response
async def async_recv(self, timeout=1.0):
response = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(self.socket, 4096), timeout)
return response
async def check_safe_gameplay(self, throw=True):
async def check_wram():
check_values = await self.async_read_memory(LAClientConstants.wCheckAddress, LAClientConstants.WRamCheckSize)
if check_values != LAClientConstants.WRamSafetyValue:
if throw:
raise InvalidEmulatorStateError()
return False
return True
if not await check_wram():
if throw:
raise InvalidEmulatorStateError()
return False
gameplay_value = await self.async_read_memory(LAClientConstants.wGameplayType)
gameplay_value = gameplay_value[0]
# In gameplay or credits
if not (LAClientConstants.MinGameplayValue <= gameplay_value <= LAClientConstants.MaxGameplayValue) and gameplay_value != 0x1:
if throw:
logger.info("invalid emu state")
raise InvalidEmulatorStateError()
return False
if not await check_wram():
if throw:
raise InvalidEmulatorStateError()
return False
return True
# We're sadly unable to update the whole cache at once
# as RetroArch only gives back some number of bytes at a time
# So instead read as big as chunks at a time as we can manage
async def update_cache(self):
# First read the safety address - if it's invalid, bail
self.cache = []
if not await self.check_safe_gameplay():
return
cache = []
remaining_size = self.cache_size
while remaining_size:
block = await self.async_read_memory(self.cache_start + len(cache), remaining_size)
remaining_size -= len(block)
cache += block
if not await self.check_safe_gameplay():
return
self.cache = cache
self.last_cache_read = time.time()
async def read_memory_cache(self, addresses):
# TODO: can we just update once per frame?
if not self.last_cache_read or self.last_cache_read + 0.1 < time.time():
await self.update_cache()
if not self.cache:
return None
assert (len(self.cache) == self.cache_size)
for address in addresses:
assert self.cache_start <= address <= self.cache_start + self.cache_size
r = {address: self.cache[address - self.cache_start]
for address in addresses}
return r
async def async_read_memory_safe(self, address, size=1):
# whenever we do a read for a check, we need to make sure that we aren't reading
# garbage memory values - we also need to protect against reading a value, then the emulator resetting
#
# ...actually, we probably _only_ need the post check
# Check before read
if not await self.check_safe_gameplay():
return None
# Do read
r = await self.async_read_memory(address, size)
# Check after read
if not await self.check_safe_gameplay():
return None
return r
def check_command_response(self, command: str, response: bytes):
if command == "VERSION":
ok = re.match("\d+\.\d+\.\d+", response.decode('ascii')) is not None
else:
ok = response.startswith(command.encode())
if not ok:
logger.warning(f"Bad response to command {command} - {response}")
raise BadRetroArchResponse()
def read_memory(self, address, size=1):
command = "READ_CORE_MEMORY"
self.send(f'{command} {hex(address)} {size}\n')
response = self.recv()
self.check_command_response(command, response)
splits = response.decode().split(" ", 2)
# Ignore the address for now
if splits[2][:2] == "-1":
raise BadRetroArchResponse()
# TODO: check response address, check hex behavior between RA and BH
return bytearray.fromhex(splits[2])
async def async_read_memory(self, address, size=1):
command = "READ_CORE_MEMORY"
self.send(f'{command} {hex(address)} {size}\n')
response = await self.async_recv()
self.check_command_response(command, response)
response = response[:-1]
splits = response.decode().split(" ", 2)
try:
response_addr = int(splits[1], 16)
except ValueError:
raise BadRetroArchResponse()
if response_addr != address:
raise BadRetroArchResponse()
ret = bytearray.fromhex(splits[2])
if len(ret) > size:
raise BadRetroArchResponse()
return ret
def write_memory(self, address, bytes):
command = "WRITE_CORE_MEMORY"
self.send(f'{command} {hex(address)} {" ".join(hex(b) for b in bytes)}')
select.select([self.socket], [], [])
response, _ = self.socket.recvfrom(4096)
self.check_command_response(command, response)
splits = response.decode().split(" ", 3)
assert (splits[0] == command)
if splits[2] == "-1":
logger.info(splits[3])
class LinksAwakeningClient():
socket = None
gameboy = None
tracker = None
auth = None
game_crc = None
pending_deathlink = False
deathlink_debounce = True
recvd_checks = {}
retroarch_address = None
retroarch_port = None
gameboy = None
def msg(self, m):
logger.info(m)
s = f"SHOW_MSG {m}\n"
self.gameboy.send(s)
def __init__(self, retroarch_address="127.0.0.1", retroarch_port=55355):
self.retroarch_address = retroarch_address
self.retroarch_port = retroarch_port
pass
stop_bizhawk_spam = False
async def wait_for_retroarch_connection(self):
if not self.stop_bizhawk_spam:
logger.info("Waiting on connection to Retroarch...")
self.stop_bizhawk_spam = True
self.gameboy = RAGameboy(self.retroarch_address, self.retroarch_port)
while True:
try:
version = await self.gameboy.get_retroarch_version()
NO_CONTENT = b"GET_STATUS CONTENTLESS"
status = NO_CONTENT
core_type = None
GAME_BOY = b"game_boy"
while status == NO_CONTENT or core_type != GAME_BOY:
status = await self.gameboy.get_retroarch_status()
if status.count(b" ") < 2:
await asyncio.sleep(1.0)
continue
GET_STATUS, PLAYING, info = status.split(b" ", 2)
if status.count(b",") < 2:
await asyncio.sleep(1.0)
continue
core_type, rom_name, self.game_crc = info.split(b",", 2)
if core_type != GAME_BOY:
logger.info(
f"Core type should be '{GAME_BOY}', found {core_type} instead - wrong type of ROM?")
await asyncio.sleep(1.0)
continue
self.stop_bizhawk_spam = False
logger.info(f"Connected to Retroarch {version.decode('ascii', errors='replace')} "
f"running {rom_name.decode('ascii', errors='replace')}")
return
except (BlockingIOError, TimeoutError, ConnectionResetError):
await asyncio.sleep(1.0)
pass
async def reset_auth(self):
auth = binascii.hexlify(await self.gameboy.async_read_memory(0x0134, 12)).decode()
self.auth = auth
async def wait_and_init_tracker(self):
await self.wait_for_game_ready()
self.tracker = LocationTracker(self.gameboy)
self.item_tracker = ItemTracker(self.gameboy)
self.gps_tracker = GpsTracker(self.gameboy)
async def recved_item_from_ap(self, item_id, from_player, next_index):
# Don't allow getting an item until you've got your first check
if not self.tracker.has_start_item():
return
# Spin until we either:
# get an exception from a bad read (emu shut down or reset)
# beat the game
# the client handles the last pending item
status = (await self.gameboy.async_read_memory_safe(LAClientConstants.wLinkStatusBits))[0]
while not (await self.is_victory()) and status & 1 == 1:
time.sleep(0.1)
status = (await self.gameboy.async_read_memory_safe(LAClientConstants.wLinkStatusBits))[0]
item_id -= LABaseID
# The player name table only goes up to 100, so don't go past that
# Even if it didn't, the remote player _index_ byte is just a byte, so 255 max
if from_player > 100:
from_player = 100
next_index += 1
self.gameboy.write_memory(LAClientConstants.wLinkGiveItem, [
item_id, from_player])
status |= 1
status = self.gameboy.write_memory(LAClientConstants.wLinkStatusBits, [status])
self.gameboy.write_memory(LAClientConstants.wRecvIndex, struct.pack(">H", next_index))
should_reset_auth = False
async def wait_for_game_ready(self):
logger.info("Waiting on game to be in valid state...")
while not await self.gameboy.check_safe_gameplay(throw=False):
if self.should_reset_auth:
self.should_reset_auth = False
raise GameboyException("Resetting due to wrong archipelago server")
logger.info("Game connection ready!")
async def is_victory(self):
return (await self.gameboy.read_memory_cache([LAClientConstants.wGameplayType]))[LAClientConstants.wGameplayType] == 1
async def main_tick(self, item_get_cb, win_cb, deathlink_cb):
await self.tracker.readChecks(item_get_cb)
await self.item_tracker.readItems()
await self.gps_tracker.read_location()
current_health = (await self.gameboy.read_memory_cache([LAClientConstants.wLinkHealth]))[LAClientConstants.wLinkHealth]
if self.deathlink_debounce and current_health != 0:
self.deathlink_debounce = False
elif not self.deathlink_debounce and current_health == 0:
# logger.info("YOU DIED.")
await deathlink_cb()
self.deathlink_debounce = True
if self.pending_deathlink:
logger.info("Got a deathlink")
self.gameboy.write_memory(LAClientConstants.wLinkHealth, [0])
self.pending_deathlink = False
self.deathlink_debounce = True
if await self.is_victory():
await win_cb()
recv_index = struct.unpack(">H", await self.gameboy.async_read_memory(LAClientConstants.wRecvIndex, 2))[0]
# Play back one at a time
if recv_index in self.recvd_checks:
item = self.recvd_checks[recv_index]
await self.recved_item_from_ap(item.item, item.player, recv_index)
all_tasks = set()
def create_task_log_exception(awaitable) -> asyncio.Task:
async def _log_exception(awaitable):
try:
return await awaitable
except Exception as e:
logger.exception(e)
pass
finally:
all_tasks.remove(task)
task = asyncio.create_task(_log_exception(awaitable))
all_tasks.add(task)
class LinksAwakeningContext(CommonContext):
tags = {"AP"}
game = "Links Awakening DX"
items_handling = 0b101
want_slot_data = True
la_task = None
client = None
# TODO: does this need to re-read on reset?
found_checks = []
last_resend = time.time()
magpie_enabled = False
magpie = None
magpie_task = None
won = False
def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str], magpie: typing.Optional[bool]) -> None:
self.client = LinksAwakeningClient()
if magpie:
self.magpie_enabled = True
self.magpie = MagpieBridge()
super().__init__(server_address, password)
def run_gui(self) -> None:
import webbrowser
import kvui
from kvui import Button, GameManager
from kivy.uix.image import Image
class LADXManager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("Tracker", "Tracker"),
]
base_title = "Archipelago Links Awakening DX Client"
def build(self):
b = super().build()
if self.ctx.magpie_enabled:
button = Button(text="", size=(30, 30), size_hint_x=None,
on_press=lambda _: webbrowser.open('https://magpietracker.us/?enable_autotracker=1'))
image = Image(size=(16, 16), texture=magpie_logo())
button.add_widget(image)
def set_center(_, center):
image.center = center
button.bind(center=set_center)
self.connect_layout.add_widget(button)
return b
self.ui = LADXManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def send_checks(self):
message = [{"cmd": 'LocationChecks', "locations": self.found_checks}]
await self.send_msgs(message)
had_invalid_slot_data = None
def event_invalid_slot(self):
# The next time we try to connect, reset the game loop for new auth
self.had_invalid_slot_data = True
self.auth = None
# Don't try to autoreconnect, it will just fail
self.disconnected_intentionally = True
CommonContext.event_invalid_slot(self)
ENABLE_DEATHLINK = False
async def send_deathlink(self):
if self.ENABLE_DEATHLINK:
message = [{"cmd": 'Deathlink',
'time': time.time(),
'cause': 'Had a nightmare',
# 'source': self.slot_info[self.slot].name,
}]
await self.send_msgs(message)
async def send_victory(self):
if not self.won:
message = [{"cmd": "StatusUpdate",
"status": ClientStatus.CLIENT_GOAL}]
logger.info("victory!")
await self.send_msgs(message)
self.won = True
async def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None:
if self.ENABLE_DEATHLINK:
self.client.pending_deathlink = True
def new_checks(self, item_ids, ladxr_ids):
self.found_checks += item_ids
create_task_log_exception(self.send_checks())
if self.magpie_enabled:
create_task_log_exception(self.magpie.send_new_checks(ladxr_ids))
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(LinksAwakeningContext, self).server_auth(password_requested)
if self.had_invalid_slot_data:
# We are connecting when previously we had the wrong ROM or server - just in case
# re-read the ROM so that if the user had the correct address but wrong ROM, we
# allow a successful reconnect
self.client.should_reset_auth = True
self.had_invalid_slot_data = False
while self.client.auth == None:
await asyncio.sleep(0.1)
self.auth = self.client.auth
await self.send_connect()
def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.game = self.slot_info[self.slot].game
# TODO - use watcher_event
if cmd == "ReceivedItems":
for index, item in enumerate(args["items"], start=args["index"]):
self.client.recvd_checks[index] = item
async def sync(self):
sync_msg = [{'cmd': 'Sync'}]
await self.send_msgs(sync_msg)
item_id_lookup = get_locations_to_id()
async def run_game_loop(self):
def on_item_get(ladxr_checks):
checks = [self.item_id_lookup[meta_to_name(
checkMetadataTable[check.id])] for check in ladxr_checks]
self.new_checks(checks, [check.id for check in ladxr_checks])
async def victory():
await self.send_victory()
async def deathlink():
await self.send_deathlink()
if self.magpie_enabled:
self.magpie_task = asyncio.create_task(self.magpie.serve())
# yield to allow UI to start
await asyncio.sleep(0)
while True:
try:
# TODO: cancel all client tasks
if not self.client.stop_bizhawk_spam:
logger.info("(Re)Starting game loop")
self.found_checks.clear()
# On restart of game loop, clear all checks, just in case we swapped ROMs
# this isn't totally neccessary, but is extra safety against cross-ROM contamination
self.client.recvd_checks.clear()
await self.client.wait_for_retroarch_connection()
await self.client.reset_auth()
# If we find ourselves with new auth after the reset, reconnect
if self.auth and self.client.auth != self.auth:
# It would be neat to reconnect here, but connection needs this loop to be running
logger.info("Detected new ROM, disconnecting...")
await self.disconnect()
continue
if not self.client.recvd_checks:
await self.sync()
await self.client.wait_and_init_tracker()
while True:
await self.client.main_tick(on_item_get, victory, deathlink)
await asyncio.sleep(0.1)
now = time.time()
if self.last_resend + 5.0 < now:
self.last_resend = now
await self.send_checks()
if self.magpie_enabled:
try:
self.magpie.set_checks(self.client.tracker.all_checks)
await self.magpie.set_item_tracker(self.client.item_tracker)
await self.magpie.send_gps(self.client.gps_tracker)
except Exception:
# Don't let magpie errors take out the client
pass
if self.client.should_reset_auth:
self.client.should_reset_auth = False
raise GameboyException("Resetting due to wrong archipelago server")
except (GameboyException, asyncio.TimeoutError, TimeoutError, ConnectionResetError):
await asyncio.sleep(1.0)
def run_game(romfile: str) -> None:
auto_start = typing.cast(typing.Union[bool, str],
Utils.get_options()["ladx_options"].get("rom_start", True))
if auto_start is True:
import webbrowser
webbrowser.open(romfile)
elif isinstance(auto_start, str):
args = shlex.split(auto_start)
# Specify full path to ROM as we are going to cd in popen
full_rom_path = os.path.realpath(romfile)
args.append(full_rom_path)
try:
# set cwd so that paths to lua scripts are always relative to our client
if getattr(sys, 'frozen', False):
# The application is frozen
script_dir = os.path.dirname(sys.executable)
else:
script_dir = os.path.dirname(os.path.realpath(__file__))
subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=script_dir)
except FileNotFoundError:
logger.error(f"Couldn't launch ROM, {args[0]} is missing")
async def main():
parser = get_base_parser(description="Link's Awakening Client.")
parser.add_argument("--url", help="Archipelago connection url")
parser.add_argument("--no-magpie", dest='magpie', default=True, action='store_false', help="Disable magpie bridge")
parser.add_argument('diff_file', default="", type=str, nargs="?",
help='Path to a .apladx Archipelago Binary Patch file')
args = parser.parse_args()
if args.diff_file:
import Patch
logger.info("patch file was supplied - creating rom...")
meta, rom_file = Patch.create_rom_file(args.diff_file)
if "server" in meta and not args.connect:
args.connect = meta["server"]
logger.info(f"wrote rom file to {rom_file}")
ctx = LinksAwakeningContext(args.connect, args.password, args.magpie)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
# TODO: nothing about the lambda about has to be in a lambda
ctx.la_task = create_task_log_exception(ctx.run_game_loop())
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
# Down below run_gui so that we get errors out of the process
if args.diff_file:
run_game(rom_file)
await ctx.exit_event.wait()
await ctx.shutdown()
if __name__ == '__main__':
colorama.init()
asyncio.run(main())
colorama.deinit()

View File

@@ -25,15 +25,17 @@ ModuleUpdate.update()
from worlds.alttp.Rom import Sprite, LocalRom, apply_rom_settings, get_base_rom_bytes
from Utils import output_path, local_path, user_path, open_file, get_cert_none_ssl_context, persistent_store, \
get_adjuster_settings, tkinter_center_window, init_logging
from Patch import GAME_ALTTP
get_adjuster_settings, get_adjuster_settings_no_defaults, tkinter_center_window, init_logging
GAME_ALTTP = "A Link to the Past"
class AdjusterWorld(object):
def __init__(self, sprite_pool):
import random
self.sprite_pool = {1: sprite_pool}
self.slot_seeds = {1: random}
self.per_slot_randoms = {1: random}
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
@@ -41,15 +43,58 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action):
return textwrap.dedent(action.help)
# See argparse.BooleanOptionalAction
class BooleanOptionalActionWithDisable(argparse.Action):
def __init__(self,
option_strings,
dest,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None):
def main():
_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)
if option_string.startswith('--'):
option_string = '--disable' + option_string[2:]
_option_strings.append(option_string)
if help is not None and default is not None:
help += " (default: %(default)s)"
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith('--disable'))
def format_usage(self):
return ' | '.join(self.option_strings)
def get_argparser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('rom', nargs="?", default='AP_LttP.sfc', help='Path to an ALttP rom to adjust.')
parser.add_argument('--baserom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc',
help='Path to an ALttP JAP(1.0) rom to use as a base.')
help='Path to an ALttP Japan(1.0) rom to use as a base.')
parser.add_argument('--loglevel', default='info', const='info', nargs='?',
choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
parser.add_argument('--auto_apply', default='ask',
choices=['ask', 'always', 'never'], help='Whether or not to apply settings automatically in the future.')
parser.add_argument('--menuspeed', default='normal', const='normal', nargs='?',
choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
help='''\
@@ -59,7 +104,7 @@ def main():
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
parser.add_argument('--deathlink', help='Enable DeathLink system.', action='store_true')
parser.add_argument('--allowcollect', help='Allow collection of other player items', action='store_true')
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
parser.add_argument('--music', default=True, help='Enables/Disables game music.', action=BooleanOptionalActionWithDisable)
parser.add_argument('--triforcehud', default='hide_goal', const='hide_goal', nargs='?',
choices=['normal', 'hide_goal', 'hide_required', 'hide_both'],
help='''\
@@ -83,9 +128,6 @@ def main():
parser.add_argument('--ow_palettes', default='default',
choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy',
'sick'])
parser.add_argument('--link_palettes', default='default',
choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy',
'sick'])
parser.add_argument('--shield_palettes', default='default',
choices=['default', 'random', 'blackout', 'puke', 'classic', 'grayscale', 'negative', 'dizzy',
'sick'])
@@ -105,10 +147,23 @@ def main():
Alternatively, can be a ALttP Rom patched with a Link
sprite that will be extracted.
''')
parser.add_argument('--names', default='', type=str)
parser.add_argument('--sprite_pool', nargs='+', default=[], help='''
A list of sprites to pull from.
''')
parser.add_argument('--oof', help='''\
Path to a sound effect to replace Link's "oof" sound.
Needs to be in a .brr format and have a length of no
more than 2673 bytes, created from a 16-bit signed PCM
.wav at 12khz. https://github.com/boldowa/snesbrr
''')
parser.add_argument('--update_sprites', action='store_true', help='Update Sprite Database, then exit.')
args = parser.parse_args()
args.music = not args.disablemusic
return parser
def main():
parser = get_argparser()
args = parser.parse_args(namespace=get_adjuster_settings_no_defaults(GAME_ALTTP))
# set up logger
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[
args.loglevel]
@@ -124,6 +179,13 @@ def main():
if args.sprite is not None and not os.path.isfile(args.sprite) and not Sprite.get_sprite_from_name(args.sprite):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
sys.exit(1)
if args.oof is not None and not os.path.isfile(args.oof):
input('Could not find oof sound effect at given location. \nPress Enter to exit.')
sys.exit(1)
if args.oof is not None and os.path.getsize(args.oof) > 2673:
input('"oof" sound effect cannot exceed 2673 bytes. \nPress Enter to exit.')
sys.exit(1)
args, path = adjust(args=args)
if isinstance(args.sprite, Sprite):
@@ -139,7 +201,7 @@ def adjust(args):
vanillaRom = args.baserom
if not os.path.exists(vanillaRom) and not os.path.isabs(vanillaRom):
vanillaRom = local_path(vanillaRom)
if os.path.splitext(args.rom)[-1].lower() in {'.apbp', '.aplttp'}:
if os.path.splitext(args.rom)[-1].lower() == '.aplttp':
import Patch
meta, args.rom = Patch.create_rom_file(args.rom)
@@ -163,7 +225,7 @@ def adjust(args):
world = getattr(args, "world")
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.menuspeed, args.music,
args.sprite, palettes_options, reduceflashing=args.reduceflashing or racerom, world=world,
args.sprite, args.oof, palettes_options, reduceflashing=args.reduceflashing or racerom, world=world,
deathlink=args.deathlink, allowcollect=args.allowcollect)
path = output_path(f'{os.path.basename(args.rom)[:-4]}_adjusted.sfc')
rom.write_to_file(path)
@@ -178,7 +240,7 @@ def adjustGUI():
from tkinter import Tk, LEFT, BOTTOM, TOP, \
StringVar, Frame, Label, X, Entry, Button, filedialog, messagebox, ttk
from argparse import Namespace
from Main import __version__ as MWVersion
from Utils import __version__ as MWVersion
adjustWindow = Tk()
adjustWindow.wm_title("Archipelago %s LttP Adjuster" % MWVersion)
set_icon(adjustWindow)
@@ -195,7 +257,7 @@ def adjustGUI():
romEntry2 = Entry(romDialogFrame, textvariable=romVar2)
def RomSelect2():
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")])
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".aplttp")), ("All Files", "*")])
romVar2.set(rom)
romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2)
@@ -225,6 +287,7 @@ def adjustGUI():
guiargs.sprite = rom_vars.sprite
if rom_vars.sprite_pool:
guiargs.world = AdjusterWorld(rom_vars.sprite_pool)
guiargs.oof = rom_vars.oof
try:
guiargs, path = adjust(args=guiargs)
@@ -263,6 +326,7 @@ def adjustGUI():
else:
guiargs.sprite = rom_vars.sprite
guiargs.sprite_pool = rom_vars.sprite_pool
guiargs.oof = rom_vars.oof
persistent_store("adjuster", GAME_ALTTP, guiargs)
messagebox.showinfo(title="Success", message="Settings saved to persistent storage")
@@ -289,7 +353,7 @@ def run_sprite_update():
else:
top.withdraw()
task = BackgroundTaskProgress(top, update_sprites, "Updating Sprites", lambda succesful, resultmessage: done.set())
while not done.isSet():
while not done.is_set():
task.do_events()
logging.info("Done updating sprites")
@@ -300,6 +364,7 @@ def update_sprites(task, on_finish=None):
sprite_dir = user_path("data", "sprites", "alttpr")
os.makedirs(sprite_dir, exist_ok=True)
ctx = get_cert_none_ssl_context()
def finished():
task.close_window()
if on_finish:
@@ -478,11 +543,38 @@ class BackgroundTaskProgressNullWindow(BackgroundTask):
self.stop()
class AttachTooltip(object):
def __init__(self, parent, text):
self._parent = parent
self._text = text
self._window = None
parent.bind('<Enter>', lambda event : self.show())
parent.bind('<Leave>', lambda event : self.hide())
def show(self):
if self._window or not self._text:
return
self._window = Toplevel(self._parent)
#remove window bar controls
self._window.wm_overrideredirect(1)
#adjust positioning
x, y, *_ = self._parent.bbox("insert")
x = x + self._parent.winfo_rootx() + 20
y = y + self._parent.winfo_rooty() + 20
self._window.wm_geometry("+{0}+{1}".format(x,y))
#show text
label = Label(self._window, text=self._text, justify=LEFT)
label.pack(ipadx=1)
def hide(self):
if self._window:
self._window.destroy()
self._window = None
def get_rom_frame(parent=None):
adjuster_settings = get_adjuster_settings(GAME_ALTTP)
if not adjuster_settings:
adjuster_settings = Namespace()
adjuster_settings.baserom = "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"
romFrame = Frame(parent)
baseRomLabel = Label(romFrame, text='LttP Base Rom: ')
@@ -510,32 +602,8 @@ def get_rom_frame(parent=None):
return romFrame, romVar
def get_rom_options_frame(parent=None):
adjuster_settings = get_adjuster_settings(GAME_ALTTP)
defaults = {
"auto_apply": 'ask',
"music": True,
"reduceflashing": True,
"deathlink": False,
"sprite": None,
"quickswap": True,
"menuspeed": 'normal',
"heartcolor": 'red',
"heartbeep": 'normal',
"ow_palettes": 'default',
"uw_palettes": 'default',
"hud_palettes": 'default',
"sword_palettes": 'default',
"shield_palettes": 'default',
"sprite_pool": [],
"allowcollect": False,
}
if not adjuster_settings:
adjuster_settings = Namespace()
for key, defaultvalue in defaults.items():
if not hasattr(adjuster_settings, key):
setattr(adjuster_settings, key, defaultvalue)
romOptionsFrame = LabelFrame(parent, text="Rom options")
romOptionsFrame.columnconfigure(0, weight=1)
@@ -595,12 +663,50 @@ def get_rom_options_frame(parent=None):
spriteEntry.pack(side=LEFT)
spriteSelectButton.pack(side=LEFT)
oofDialogFrame = Frame(romOptionsFrame)
oofDialogFrame.grid(row=1, column=1)
baseOofLabel = Label(oofDialogFrame, text='"OOF" Sound:')
vars.oofNameVar = StringVar()
vars.oof = adjuster_settings.oof
def set_oof(oof_param):
nonlocal vars
if isinstance(oof_param, str) and os.path.isfile(oof_param) and os.path.getsize(oof_param) <= 2673:
vars.oof = oof_param
vars.oofNameVar.set(oof_param.rsplit('/',1)[-1])
else:
vars.oof = None
vars.oofNameVar.set('(unchanged)')
set_oof(adjuster_settings.oof)
oofEntry = Label(oofDialogFrame, textvariable=vars.oofNameVar)
def OofSelect():
nonlocal vars
oof_file = filedialog.askopenfilename(
filetypes=[("BRR files", ".brr"),
("All Files", "*")])
try:
set_oof(oof_file)
except Exception:
set_oof(None)
oofSelectButton = Button(oofDialogFrame, text='...', command=OofSelect)
AttachTooltip(oofSelectButton,
text="Select a .brr file no more than 2673 bytes.\n" + \
"This can be created from a <=0.394s 16-bit signed PCM .wav file at 12khz using snesbrr.")
baseOofLabel.pack(side=LEFT)
oofEntry.pack(side=LEFT)
oofSelectButton.pack(side=LEFT)
vars.quickSwapVar = IntVar(value=adjuster_settings.quickswap)
quickSwapCheckbutton = Checkbutton(romOptionsFrame, text="L/R Quickswapping", variable=vars.quickSwapVar)
quickSwapCheckbutton.grid(row=1, column=0, sticky=E)
menuspeedFrame = Frame(romOptionsFrame)
menuspeedFrame.grid(row=1, column=1, sticky=E)
menuspeedFrame.grid(row=6, column=1, sticky=E)
menuspeedLabel = Label(menuspeedFrame, text='Menu speed')
menuspeedLabel.pack(side=LEFT)
vars.menuspeedVar = StringVar()
@@ -724,7 +830,7 @@ def get_rom_options_frame(parent=None):
vars.auto_apply = StringVar(value=adjuster_settings.auto_apply)
autoApplyFrame = Frame(romOptionsFrame)
autoApplyFrame.grid(row=9, column=0, columnspan=2, sticky=W)
filler = Label(autoApplyFrame, text="Automatically apply last used settings on opening .apbp files")
filler = Label(autoApplyFrame, text="Automatically apply last used settings on opening .aplttp files")
filler.pack(side=TOP, expand=True, fill=X)
askRadio = Radiobutton(autoApplyFrame, text='Ask', variable=vars.auto_apply, value='ask')
askRadio.pack(side=LEFT, padx=5, pady=5)
@@ -751,6 +857,7 @@ class SpriteSelector():
self.window['pady'] = 5
self.spritesPerRow = 32
self.all_sprites = []
self.invalid_sprites = []
self.sprite_pool = spritePool
def open_custom_sprite_dir(_evt):
@@ -832,6 +939,13 @@ class SpriteSelector():
self.window.focus()
tkinter_center_window(self.window)
if self.invalid_sprites:
invalid = sorted(self.invalid_sprites)
logging.warning(f"The following sprites are invalid: {', '.join(invalid)}")
msg = f"{invalid[0]} "
msg += f"and {len(invalid)-1} more are invalid" if len(invalid) > 1 else "is invalid"
messagebox.showerror("Invalid sprites detected", msg, parent=self.window)
def remove_from_sprite_pool(self, button, spritename):
self.callback(("remove", spritename))
self.spritePoolButtons.buttons.remove(button)
@@ -890,13 +1004,20 @@ class SpriteSelector():
self.add_to_sprite_pool(sprite)
def icon_section(self, frame_label, path, no_results_label):
os.makedirs(path, exist_ok=True)
frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5)
frame.pack(side=TOP, fill=X)
sprites = []
for file in os.listdir(path):
sprites.append((file, Sprite(os.path.join(path, file))))
if file == '.gitignore':
continue
sprite = Sprite(os.path.join(path, file))
if sprite.valid:
sprites.append((file, sprite))
else:
self.invalid_sprites.append(file)
sprites.sort(key=lambda s: str.lower(s[1].name or "").strip())
@@ -1039,7 +1160,6 @@ class SpriteSelector():
def custom_sprite_dir(self):
return user_path("data", "sprites", "custom")
def get_image_for_sprite(sprite, gif_only: bool = False):
if not sprite.valid:
return None
@@ -1263,4 +1383,4 @@ class ToolTips(object):
if __name__ == '__main__':
main()
main()

376
MMBN3Client.py Normal file
View File

@@ -0,0 +1,376 @@
import asyncio
import hashlib
import json
import os
import multiprocessing
import subprocess
import zipfile
from asyncio import StreamReader, StreamWriter
import bsdiff4
from CommonClient import CommonContext, server_loop, gui_enabled, \
ClientCommandProcessor, logger, get_base_parser
import Utils
from NetUtils import ClientStatus
from worlds.mmbn3.Items import items_by_id
from worlds.mmbn3.Rom import get_base_rom_path
from worlds.mmbn3.Locations import all_locations, scoutable_locations
SYSTEM_MESSAGE_ID = 0
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_mmbn3.lua"
CONNECTION_REFUSED_STATUS = \
"Connection refused. Please start your emulator and make sure connector_mmbn3.lua is running"
CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart connector_mmbn3.lua"
CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
CONNECTION_CONNECTED_STATUS = "Connected"
CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
CONNECTION_INCORRECT_ROM = "Supplied Base Rom does not match US GBA Blue Version. Please provide the correct ROM version"
script_version: int = 2
debugEnabled = False
locations_checked = []
items_sent = []
itemIndex = 1
CHECKSUM_BLUE = "6fe31df0144759b34ad666badaacc442"
class MMBN3CommandProcessor(ClientCommandProcessor):
def __init__(self, ctx):
super().__init__(ctx)
def _cmd_gba(self):
"""Check GBA Connection State"""
if isinstance(self.ctx, MMBN3Context):
logger.info(f"GBA Status: {self.ctx.gba_status}")
def _cmd_debug(self):
"""Toggle the Debug Text overlay in ROM"""
global debugEnabled
debugEnabled = not debugEnabled
logger.info("Debug Overlay Enabled" if debugEnabled else "Debug Overlay Disabled")
class MMBN3Context(CommonContext):
command_processor = MMBN3CommandProcessor
game = "MegaMan Battle Network 3"
items_handling = 0b101 # full local except starting items
def __init__(self, server_address, password):
super().__init__(server_address, password)
self.gba_streams: (StreamReader, StreamWriter) = None
self.gba_sync_task = None
self.gba_status = CONNECTION_INITIAL_STATUS
self.awaiting_rom = False
self.location_table = {}
self.version_warning = False
self.auth_name = None
self.slot_data = dict()
self.patching_error = False
self.sent_hints = []
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(MMBN3Context, self).server_auth(password_requested)
if self.auth_name is None:
self.awaiting_rom = True
logger.info("No ROM detected, awaiting conection to Bizhawk to authenticate to the multiworld server")
return
logger.info("Attempting to decode from ROM... ")
self.awaiting_rom = False
self.auth = self.auth_name.decode("utf8").replace('\x00', '')
logger.info("Connecting as "+self.auth)
await self.send_connect(name=self.auth)
def run_gui(self):
from kvui import GameManager
class MMBN3Manager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago MegaMan Battle Network 3 Client"
self.ui = MMBN3Manager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def on_package(self, cmd: str, args: dict):
if cmd == 'Connected':
self.slot_data = args.get("slot_data", {})
print(self.slot_data)
class ItemInfo:
id = 0x00
sender = ""
type = ""
count = 1
itemName = "Unknown"
itemID = 0x00 # Item ID, Chip ID, etc.
subItemID = 0x00 # Code for chips, color for programs
itemIndex = 1
def __init__(self, id, sender, type):
self.id = id
self.sender = sender
self.type = type
def get_json(self):
json_data = {
"id": self.id,
"sender": self.sender,
"type": self.type,
"itemName": self.itemName,
"itemID": self.itemID,
"subItemID": self.subItemID,
"count": self.count,
"itemIndex": self.itemIndex
}
return json_data
def get_payload(ctx: MMBN3Context):
global debugEnabled
items_sent = []
for i, item in enumerate(ctx.items_received):
item_data = items_by_id[item.item]
new_item = ItemInfo(i, ctx.player_names[item.player], item_data.type)
new_item.itemIndex = i+1
new_item.itemName = item_data.itemName
new_item.type = item_data.type
new_item.itemID = item_data.itemID
new_item.subItemID = item_data.subItemID
new_item.count = item_data.count
items_sent.append(new_item)
return json.dumps({
"items": [item.get_json() for item in items_sent],
"debug": debugEnabled
})
async def parse_payload(payload: dict, ctx: MMBN3Context, force: bool):
# Game completion handling
if payload["gameComplete"] and not ctx.finished_game:
await ctx.send_msgs([{
"cmd": "StatusUpdate",
"status": ClientStatus.CLIENT_GOAL
}])
ctx.finished_game = True
# Locations handling
if ctx.location_table != payload["locations"]:
ctx.location_table = payload["locations"]
locs = [loc.id for loc in all_locations
if check_location_packet(loc, ctx.location_table)]
await ctx.send_msgs([{
"cmd": "LocationChecks",
"locations": locs
}])
# If trade hinting is enabled, send scout checks
if ctx.slot_data.get("trade_quest_hinting", 0) == 2:
trade_bits = [loc.id for loc in scoutable_locations
if check_location_scouted(loc, payload["locations"])]
scouted_locs = [loc for loc in trade_bits if loc not in ctx.sent_hints]
if len(scouted_locs) > 0:
ctx.sent_hints.extend(scouted_locs)
await ctx.send_msgs([{
"cmd": "LocationScouts",
"locations": scouted_locs,
"create_as_hint": 2
}])
def check_location_packet(location, memory):
if len(memory) == 0:
return False
# Our keys have to be strings to come through the JSON lua plugin so we have to turn our memory address into a string as well
location_key = hex(location.flag_byte)[2:]
byte = memory.get(location_key)
if byte is not None:
return byte & location.flag_mask
def check_location_scouted(location, memory):
if len(memory) == 0:
return False
location_key = hex(location.hint_flag)[2:]
byte = memory.get(location_key)
if byte is not None:
return byte & location.hint_flag_mask
async def gba_sync_task(ctx: MMBN3Context):
logger.info("Starting GBA connector. Use /gba for status information.")
if ctx.patching_error:
logger.error('Unable to Patch ROM. No ROM provided or ROM does not match US GBA Blue Version.')
while not ctx.exit_event.is_set():
error_status = None
if ctx.gba_streams:
(reader, writer) = ctx.gba_streams
msg = get_payload(ctx).encode()
writer.write(msg)
writer.write(b'\n')
try:
await asyncio.wait_for(writer.drain(), timeout=1.5)
try:
# Data will return a dict with up to four fields
# 1. str: player name (always)
# 2. int: script version (always)
# 3. dict[str, byte]: value of location's memory byte
# 4. bool: whether the game currently registers as complete
data = await asyncio.wait_for(reader.readline(), timeout=10)
data_decoded = json.loads(data.decode())
reported_version = data_decoded.get("scriptVersion", 0)
if reported_version >= script_version:
if ctx.game is not None and "locations" in data_decoded:
# Not just a keep alive ping, parse
asyncio.create_task((parse_payload(data_decoded, ctx, False)))
if not ctx.auth:
ctx.auth_name = bytes(data_decoded["playerName"])
if ctx.awaiting_rom:
logger.info("Awaiting data from ROM...")
await ctx.server_auth(False)
else:
if not ctx.version_warning:
logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}."
"Please update to the latest version."
"Your connection to the Archipelago server will not be accepted.")
ctx.version_warning = True
except asyncio.TimeoutError:
logger.debug("Read Timed Out, Reconnecting")
error_status = CONNECTION_TIMING_OUT_STATUS
writer.close()
ctx.gba_streams = None
except ConnectionResetError:
logger.debug("Read failed due to Connection Lost, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.gba_streams = None
except TimeoutError:
logger.debug("Connection Timed Out, Reconnecting")
error_status = CONNECTION_TIMING_OUT_STATUS
writer.close()
ctx.gba_streams = None
except ConnectionResetError:
logger.debug("Connection Lost, Reconnecting")
error_status = CONNECTION_RESET_STATUS
writer.close()
ctx.gba_streams = None
if ctx.gba_status == CONNECTION_TENTATIVE_STATUS:
if not error_status:
logger.info("Successfully Connected to GBA")
ctx.gba_status = CONNECTION_CONNECTED_STATUS
else:
ctx.gba_status = f"Was tentatively connected but error occurred: {error_status}"
elif error_status:
ctx.gba_status = error_status
logger.info("Lost connection to GBA and attempting to reconnect. Use /gba for status updates")
else:
try:
logger.debug("Attempting to connect to GBA")
ctx.gba_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 28922), timeout=10)
ctx.gba_status = CONNECTION_TENTATIVE_STATUS
except TimeoutError:
logger.debug("Connection Timed Out, Trying Again")
ctx.gba_status = CONNECTION_TIMING_OUT_STATUS
continue
except ConnectionRefusedError:
logger.debug("Connection Refused, Trying Again")
ctx.gba_status = CONNECTION_REFUSED_STATUS
continue
async def run_game(romfile):
options = Utils.get_options().get("mmbn3_options", None)
if options is None:
auto_start = True
else:
auto_start = options.get("rom_start", True)
if auto_start:
import webbrowser
webbrowser.open(romfile)
elif os.path.isfile(auto_start):
subprocess.Popen([auto_start, romfile],
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
async def patch_and_run_game(apmmbn3_file):
base_name = os.path.splitext(apmmbn3_file)[0]
with zipfile.ZipFile(apmmbn3_file, 'r') as patch_archive:
try:
with patch_archive.open("delta.bsdiff4", 'r') as stream:
patch_data = stream.read()
except KeyError:
raise FileNotFoundError("Patch file missing from archive.")
rom_file = get_base_rom_path()
with open(rom_file, 'rb') as rom:
rom_bytes = rom.read()
patched_bytes = bsdiff4.patch(rom_bytes, patch_data)
patched_rom_file = base_name+".gba"
with open(patched_rom_file, 'wb') as patched_rom:
patched_rom.write(patched_bytes)
asyncio.create_task(run_game(patched_rom_file))
def confirm_checksum():
rom_file = get_base_rom_path()
if not os.path.exists(rom_file):
return False
with open(rom_file, 'rb') as rom:
rom_bytes = rom.read()
basemd5 = hashlib.md5()
basemd5.update(rom_bytes)
return CHECKSUM_BLUE == basemd5.hexdigest()
if __name__ == "__main__":
Utils.init_logging("MMBN3Client")
async def main():
multiprocessing.freeze_support()
parser = get_base_parser()
parser.add_argument("patch_file", default="", type=str, nargs="?",
help="Path to an APMMBN3 file")
args = parser.parse_args()
checksum_matches = confirm_checksum()
if checksum_matches:
if args.patch_file:
asyncio.create_task(patch_and_run_game(args.patch_file))
ctx = MMBN3Context(args.connect, args.password)
if not checksum_matches:
ctx.patching_error = True
ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
ctx.gba_sync_task = asyncio.create_task(gba_sync_task(ctx), name="GBA Sync")
await ctx.exit_event.wait()
ctx.server_address = None
await ctx.shutdown()
if ctx.gba_sync_task:
await ctx.gba_sync_task
import colorama
colorama.init()
asyncio.run(main())
colorama.deinit()

649
Main.py
View File

@@ -1,162 +1,200 @@
import copy
import collections
from itertools import zip_longest, chain
import concurrent.futures
import logging
import os
import time
import zlib
import concurrent.futures
import pickle
import tempfile
import time
import zipfile
from typing import Dict, Tuple, Optional, Set
import zlib
from typing import Dict, List, Optional, Set, Tuple, Union
from BaseClasses import MultiWorld, CollectionState, Region, RegionType, LocationProgressType, Location
from worlds.alttp.Items import item_name_groups
from worlds.alttp.Regions import lookup_vanilla_location_to_entrance
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
from worlds.alttp.Shops import SHOP_ID_START, total_shop_slots, FillDisabledShopSlots
from Utils import output_path, get_options, __version__, version_tuple
from worlds.generic.Rules import locality_rules, exclusion_rules, group_locality_rules
import worlds
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
from Options import StartInventoryPool
from Utils import __version__, output_path, version_tuple, get_settings
from settings import get_settings
from worlds import AutoWorld
from worlds.generic.Rules import exclusion_rules, locality_rules
ordered_areas = (
'Light World', 'Dark World', 'Hyrule Castle', 'Agahnims Tower', 'Eastern Palace', 'Desert Palace',
'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace',
'Misery Mire', 'Turtle Rock', 'Ganons Tower', "Total"
)
__all__ = ["main"]
def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = None):
if not baked_server_options:
baked_server_options = get_options()["server_options"]
baked_server_options = get_settings().server_options.as_dict()
assert isinstance(baked_server_options, dict)
if args.outputpath:
os.makedirs(args.outputpath, exist_ok=True)
output_path.cached_path = args.outputpath
start = time.perf_counter()
# initialize the world
world = MultiWorld(args.multi)
# initialize the multiworld
multiworld = MultiWorld(args.multi)
logger = logging.getLogger()
world.set_seed(seed, args.race, str(args.outputname if args.outputname else world.seed))
multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
multiworld.plando_options = args.plando_options
multiworld.plando_items = args.plando_items.copy()
multiworld.plando_texts = args.plando_texts.copy()
multiworld.plando_connections = args.plando_connections.copy()
multiworld.game = args.game.copy()
multiworld.player_name = args.name.copy()
multiworld.sprite = args.sprite.copy()
multiworld.sprite_pool = args.sprite_pool.copy()
world.shuffle = args.shuffle.copy()
world.logic = args.logic.copy()
world.mode = args.mode.copy()
world.difficulty = args.difficulty.copy()
world.item_functionality = args.item_functionality.copy()
world.timer = args.timer.copy()
world.goal = args.goal.copy()
world.open_pyramid = args.open_pyramid.copy()
world.boss_shuffle = args.shufflebosses.copy()
world.enemy_health = args.enemy_health.copy()
world.enemy_damage = args.enemy_damage.copy()
world.beemizer_total_chance = args.beemizer_total_chance.copy()
world.beemizer_trap_chance = args.beemizer_trap_chance.copy()
world.timer = args.timer.copy()
world.countdown_start_time = args.countdown_start_time.copy()
world.red_clock_time = args.red_clock_time.copy()
world.blue_clock_time = args.blue_clock_time.copy()
world.green_clock_time = args.green_clock_time.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.triforce_pieces_available = args.triforce_pieces_available.copy()
world.triforce_pieces_required = args.triforce_pieces_required.copy()
world.shop_shuffle = args.shop_shuffle.copy()
world.shuffle_prizes = args.shuffle_prizes.copy()
world.sprite_pool = args.sprite_pool.copy()
world.dark_room_logic = args.dark_room_logic.copy()
world.plando_items = args.plando_items.copy()
world.plando_texts = args.plando_texts.copy()
world.plando_connections = args.plando_connections.copy()
world.required_medallions = args.required_medallions.copy()
world.game = args.game.copy()
world.player_name = args.name.copy()
world.enemizer = args.enemizercli
world.sprite = args.sprite.copy()
world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
multiworld.set_options(args)
multiworld.set_item_links()
multiworld.state = CollectionState(multiworld)
logger.info('Archipelago Version %s - Seed: %s\n', __version__, multiworld.seed)
world.set_options(args)
world.set_item_links()
world.state = CollectionState(world)
logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
logger.info("Found World Types:")
logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:")
longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types)
numlength = 8
max_item = 0
max_location = 0
for cls in AutoWorld.AutoWorldRegister.world_types.values():
if cls.item_id_to_name:
max_item = max(max_item, max(cls.item_id_to_name))
max_location = max(max_location, max(cls.location_id_to_name))
item_digits = len(str(max_item))
location_digits = len(str(max_location))
item_count = len(str(max(len(cls.item_names) for cls in AutoWorld.AutoWorldRegister.world_types.values())))
location_count = len(str(max(len(cls.location_names) for cls in AutoWorld.AutoWorldRegister.world_types.values())))
del max_item, max_location
for name, cls in AutoWorld.AutoWorldRegister.world_types.items():
if not cls.hidden:
logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} "
f"Items (IDs: {min(cls.item_id_to_name):{numlength}} - "
f"{max(cls.item_id_to_name):{numlength}}) | "
f"{len(cls.location_names):3} "
f"Locations (IDs: {min(cls.location_id_to_name):{numlength}} - "
f"{max(cls.location_id_to_name):{numlength}})")
if not cls.hidden and len(cls.item_names) > 0:
logger.info(f" {name:{longest_name}}: {len(cls.item_names):{item_count}} "
f"Items (IDs: {min(cls.item_id_to_name):{item_digits}} - "
f"{max(cls.item_id_to_name):{item_digits}}) | "
f"{len(cls.location_names):{location_count}} "
f"Locations (IDs: {min(cls.location_id_to_name):{location_digits}} - "
f"{max(cls.location_id_to_name):{location_digits}})")
AutoWorld.call_stage(world, "assert_generate")
del item_digits, location_digits, item_count, location_count
AutoWorld.call_all(world, "generate_early")
# This assertion method should not be necessary to run if we are not outputting any multidata.
if not args.skip_output:
AutoWorld.call_stage(multiworld, "assert_generate")
AutoWorld.call_all(multiworld, "generate_early")
logger.info('')
for player in world.player_ids:
for item_name, count in world.start_inventory[player].value.items():
for player in multiworld.player_ids:
for item_name, count in multiworld.worlds[player].options.start_inventory.value.items():
for _ in range(count):
world.push_precollected(world.create_item(item_name, player))
multiworld.push_precollected(multiworld.create_item(item_name, player))
for player in world.player_ids:
if player in world.get_game_players("A Link to the Past"):
# enforce pre-defined local items.
if world.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
world.local_items[player].value.add('Triforce Piece')
for item_name, count in getattr(multiworld.worlds[player].options,
"start_inventory_from_pool",
StartInventoryPool({})).value.items():
for _ in range(count):
multiworld.push_precollected(multiworld.create_item(item_name, player))
# remove from_pool items also from early items handling, as starting is plenty early.
early = multiworld.early_items[player].get(item_name, 0)
if early:
multiworld.early_items[player][item_name] = max(0, early-count)
remaining_count = count-early
if remaining_count > 0:
local_early = multiworld.early_local_items[player].get(item_name, 0)
if local_early:
multiworld.early_items[player][item_name] = max(0, local_early - remaining_count)
del local_early
del early
# Not possible to place pendants/crystals out side of boss prizes yet.
world.non_local_items[player].value -= item_name_groups['Pendants']
world.non_local_items[player].value -= item_name_groups['Crystals']
# items can't be both local and non-local, prefer local
world.non_local_items[player].value -= world.local_items[player].value
logger.info('Creating World.')
AutoWorld.call_all(world, "create_regions")
logger.info('Creating MultiWorld.')
AutoWorld.call_all(multiworld, "create_regions")
logger.info('Creating Items.')
AutoWorld.call_all(world, "create_items")
AutoWorld.call_all(multiworld, "create_items")
logger.info('Calculating Access Rules.')
if world.players > 1:
for player in world.player_ids:
locality_rules(world, player)
group_locality_rules(world)
for player in multiworld.player_ids:
# items can't be both local and non-local, prefer local
multiworld.worlds[player].options.non_local_items.value -= multiworld.worlds[player].options.local_items.value
multiworld.worlds[player].options.non_local_items.value -= set(multiworld.local_early_items[player])
AutoWorld.call_all(multiworld, "set_rules")
for player in multiworld.player_ids:
exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value)
multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value
for location_name in multiworld.worlds[player].options.priority_locations.value:
try:
location = multiworld.get_location(location_name, player)
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
if location_name not in multiworld.worlds[player].location_name_to_id:
raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e
else:
location.progress_type = LocationProgressType.PRIORITY
# Set local and non-local item rules.
if multiworld.players > 1:
locality_rules(multiworld)
else:
world.non_local_items[1].value = set()
world.local_items[1].value = set()
multiworld.worlds[1].options.non_local_items.value = set()
multiworld.worlds[1].options.local_items.value = set()
AutoWorld.call_all(multiworld, "generate_basic")
AutoWorld.call_all(world, "set_rules")
# remove starting inventory from pool items.
# Because some worlds don't actually create items during create_items this has to be as late as possible.
if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids):
new_items: List[Item] = []
depletion_pool: Dict[int, Dict[str, int]] = {
player: getattr(multiworld.worlds[player].options,
"start_inventory_from_pool",
StartInventoryPool({})).value.copy()
for player in multiworld.player_ids
}
for player, items in depletion_pool.items():
player_world: AutoWorld.World = multiworld.worlds[player]
for count in items.values():
for _ in range(count):
new_items.append(player_world.create_filler())
target: int = sum(sum(items.values()) for items in depletion_pool.values())
for i, item in enumerate(multiworld.itempool):
if depletion_pool[item.player].get(item.name, 0):
target -= 1
depletion_pool[item.player][item.name] -= 1
# quick abort if we have found all items
if not target:
new_items.extend(multiworld.itempool[i+1:])
break
else:
new_items.append(item)
for player in world.player_ids:
exclusion_rules(world, player, world.exclude_locations[player].value)
world.priority_locations[player].value -= world.exclude_locations[player].value
for location_name in world.priority_locations[player].value:
world.get_location(location_name, player).progress_type = LocationProgressType.PRIORITY
AutoWorld.call_all(world, "generate_basic")
# leftovers?
if target:
for player, remaining_items in depletion_pool.items():
remaining_items = {name: count for name, count in remaining_items.items() if count}
if remaining_items:
raise Exception(f"{multiworld.get_player_name(player)}"
f" is trying to remove items from their pool that don't exist: {remaining_items}")
assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change."
multiworld.itempool[:] = new_items
# temporary home for item links, should be moved out of Main
for group_id, group in world.groups.items():
def find_common_pool(players: Set[int], shared_pool: Set[str]):
advancement = set()
for group_id, group in multiworld.groups.items():
def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]]
]:
classifications: Dict[str, int] = collections.defaultdict(int)
counters = {player: {name: 0 for name in shared_pool} for player in players}
for item in world.itempool:
for item in multiworld.itempool:
if item.player in counters and item.name in shared_pool:
counters[item.player][item.name] += 1
if item.advancement:
advancement.add(item.name)
classifications[item.name] |= item.classification
for player in players.copy():
if all([counters[player][item] == 0 for item in shared_pool]):
players.remove(player)
del(counters[player])
del (counters[player])
if not players:
return None, None
@@ -168,28 +206,28 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
counters[player][item] = count
else:
for player in players:
del(counters[player][item])
return counters, advancement
del (counters[player][item])
return counters, classifications
common_item_count, common_advancement_items = find_common_pool(group["players"], group["item_pool"])
common_item_count, classifications = find_common_pool(group["players"], group["item_pool"])
if not common_item_count:
continue
new_itempool = []
new_itempool: List[Item] = []
for item_name, item_count in next(iter(common_item_count.values())).items():
advancement = item_name in common_advancement_items
for _ in range(item_count):
new_item = group["world"].create_item(item_name)
new_item.advancement = advancement
# mangle together all original classification bits
new_item.classification |= classifications[item_name]
new_itempool.append(new_item)
region = Region("Menu", RegionType.Generic, "ItemLink", group_id, world)
world.regions.append(region)
locations = region.locations = []
for item in world.itempool:
region = Region("Menu", group_id, multiworld, "ItemLink")
multiworld.regions.append(region)
locations = region.locations
for item in multiworld.itempool:
count = common_item_count.get(item.player, {}).get(item.name, 0)
if count:
loc = Location(group_id, f"Item Link: {item.name} -> {world.player_name[item.player]} {count}",
loc = Location(group_id, f"Item Link: {item.name} -> {multiworld.player_name[item.player]} {count}",
None, region)
loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \
state.has(item_name, group_id_, count_)
@@ -200,129 +238,75 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
else:
new_itempool.append(item)
itemcount = len(world.itempool)
world.itempool = new_itempool
itemcount = len(multiworld.itempool)
multiworld.itempool = new_itempool
while itemcount > len(world.itempool):
while itemcount > len(multiworld.itempool):
items_to_add = []
for player in group["players"]:
if group["link_replacement"]:
item_player = group_id
else:
item_player = player
if group["replacement_items"][player]:
items_to_add.append(AutoWorld.call_single(world, "create_item", player,
items_to_add.append(AutoWorld.call_single(multiworld, "create_item", item_player,
group["replacement_items"][player]))
else:
items_to_add.append(AutoWorld.call_single(world, "create_filler", player))
world.random.shuffle(items_to_add)
world.itempool.extend(items_to_add[:itemcount - len(world.itempool)])
items_to_add.append(AutoWorld.call_single(multiworld, "create_filler", item_player))
multiworld.random.shuffle(items_to_add)
multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)])
if any(world.item_links.values()):
world._recache()
world._all_state = None
if any(multiworld.item_links.values()):
multiworld._all_state = None
logger.info("Running Item Plando")
logger.info("Running Item Plando.")
for item in world.itempool:
item.world = world
distribute_planned(world)
distribute_planned(multiworld)
logger.info('Running Pre Main Fill.')
AutoWorld.call_all(world, "pre_fill")
AutoWorld.call_all(multiworld, "pre_fill")
logger.info(f'Filling the world with {len(world.itempool)} items.')
logger.info(f'Filling the multiworld with {len(multiworld.itempool)} items.')
if world.algorithm == 'flood':
flood_items(world) # different algo, biased towards early game progress items
elif world.algorithm == 'balanced':
distribute_items_restrictive(world)
if multiworld.algorithm == 'flood':
flood_items(multiworld) # different algo, biased towards early game progress items
elif multiworld.algorithm == 'balanced':
distribute_items_restrictive(multiworld, get_settings().generator.panic_method)
AutoWorld.call_all(world, 'post_fill')
AutoWorld.call_all(multiworld, 'post_fill')
if world.players > 1:
balance_multiworld_progression(world)
if multiworld.players > 1 and not args.skip_prog_balancing:
balance_multiworld_progression(multiworld)
else:
logger.info("Progression balancing skipped.")
# we're about to output using multithreading, so we're removing the global random state to prevent accidental use
multiworld.random.passthrough = False
if args.skip_output:
logger.info('Done. Skipped output/spoiler generation. Total Time: %s', time.perf_counter() - start)
return multiworld
logger.info(f'Beginning output...')
outfilebase = 'AP_' + world.seed_name
outfilebase = 'AP_' + multiworld.seed_name
output = tempfile.TemporaryDirectory()
with output as temp_dir:
with concurrent.futures.ThreadPoolExecutor(world.players + 2) as pool:
check_accessibility_task = pool.submit(world.fulfills_accessibility)
output_players = [player for player in multiworld.player_ids if AutoWorld.World.generate_output.__code__
is not multiworld.worlds[player].generate_output.__code__]
with concurrent.futures.ThreadPoolExecutor(len(output_players) + 2) as pool:
check_accessibility_task = pool.submit(multiworld.fulfills_accessibility)
output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)]
for player in world.player_ids:
output_file_futures = [pool.submit(AutoWorld.call_stage, multiworld, "generate_output", temp_dir)]
for player in output_players:
# skip starting a thread for methods that say "pass".
if AutoWorld.World.generate_output.__code__ is not world.worlds[player].generate_output.__code__:
output_file_futures.append(
pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
def get_entrance_to_region(region: Region):
for entrance in region.entrances:
if entrance.parent_region.type in (RegionType.DarkWorld, RegionType.LightWorld, RegionType.Generic):
return entrance
for entrance in region.entrances: # BFS might be better here, trying DFS for now.
return get_entrance_to_region(entrance.parent_region)
output_file_futures.append(
pool.submit(AutoWorld.call_single, multiworld, "generate_output", player, temp_dir))
# collect ER hint info
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
world.shuffle[player] != "vanilla" or world.retro[player]}
for region in world.regions:
if region.player in er_hint_data and region.locations:
main_entrance = get_entrance_to_region(region)
for location in region.locations:
if type(location.address) == int: # skips events and crystals
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
er_hint_data[region.player][location.address] = main_entrance.name
checks_in_area = {player: {area: list() for area in ordered_areas}
for player in range(1, world.players + 1)}
for player in range(1, world.players + 1):
checks_in_area[player]["Total"] = 0
for location in world.get_filled_locations():
if type(location.address) is int:
main_entrance = get_entrance_to_region(location.parent_region)
if location.game != "A Link to the Past":
checks_in_area[location.player]["Light World"].append(location.address)
elif location.parent_region.dungeon:
dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower',
'Inverted Ganons Tower': 'Ganons Tower'} \
.get(location.parent_region.dungeon.name, location.parent_region.dungeon.name)
checks_in_area[location.player][dungeonname].append(location.address)
elif location.parent_region.type == RegionType.LightWorld:
checks_in_area[location.player]["Light World"].append(location.address)
elif location.parent_region.type == RegionType.DarkWorld:
checks_in_area[location.player]["Dark World"].append(location.address)
elif main_entrance.parent_region.type == RegionType.LightWorld:
checks_in_area[location.player]["Light World"].append(location.address)
elif main_entrance.parent_region.type == RegionType.DarkWorld:
checks_in_area[location.player]["Dark World"].append(location.address)
checks_in_area[location.player]["Total"] += 1
oldmancaves = []
takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"]
for index, take_any in enumerate(takeanyregions):
for region in [world.get_region(take_any, player) for player in
world.get_game_players("A Link to the Past") if world.retro[player]]:
item = world.create_item(
region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'],
region.player)
player = region.player
location_id = SHOP_ID_START + total_shop_slots + index
main_entrance = get_entrance_to_region(region)
if main_entrance.parent_region.type == RegionType.LightWorld:
checks_in_area[player]["Light World"].append(location_id)
else:
checks_in_area[player]["Dark World"].append(location_id)
checks_in_area[player]["Total"] += 1
er_hint_data[player][location_id] = main_entrance.name
oldmancaves.append(((location_id, player), (item.code, player)))
FillDisabledShopSlots(world)
er_hint_data: Dict[int, Dict[int, str]] = {}
AutoWorld.call_all(multiworld, 'extend_hint_information', er_hint_data)
def write_multidata():
import NetUtils
@@ -331,62 +315,67 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
games = {}
minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions}
slot_info = {}
names = [[name for player, name in sorted(world.player_name.items())]]
for slot in world.player_ids:
player_world: AutoWorld.World = world.worlds[slot]
names = [[name for player, name in sorted(multiworld.player_name.items())]]
for slot in multiworld.player_ids:
player_world: AutoWorld.World = multiworld.worlds[slot]
minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version)
client_versions[slot] = player_world.required_client_version
games[slot] = world.game[slot]
slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], world.game[slot],
world.player_types[slot])
for slot, group in world.groups.items():
games[slot] = world.game[slot]
slot_info[slot] = NetUtils.NetworkSlot(group["name"], world.game[slot], world.player_types[slot],
games[slot] = multiworld.game[slot]
slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], multiworld.game[slot],
multiworld.player_types[slot])
for slot, group in multiworld.groups.items():
games[slot] = multiworld.game[slot]
slot_info[slot] = NetUtils.NetworkSlot(group["name"], multiworld.game[slot], multiworld.player_types[slot],
group_members=sorted(group["players"]))
precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int]
for player, world_precollected in world.precollected_items.items()}
precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))}
for player, world_precollected in multiworld.precollected_items.items()}
precollected_hints = {player: set() for player in range(1, multiworld.players + 1 + len(multiworld.groups))}
for slot in world.player_ids:
slot_data[slot] = world.worlds[slot].fill_slot_data()
for slot in multiworld.player_ids:
slot_data[slot] = multiworld.worlds[slot].fill_slot_data()
def precollect_hint(location):
entrance = er_hint_data.get(location.player, {}).get(location.address, "")
hint = NetUtils.Hint(location.item.player, location.player, location.address,
location.item.code, False, entrance, location.item.flags)
precollected_hints[location.player].add(hint)
if location.item.player not in world.groups:
if location.item.player not in multiworld.groups:
precollected_hints[location.item.player].add(hint)
else:
for player in world.groups[location.item.player]["players"]:
for player in multiworld.groups[location.item.player]["players"]:
precollected_hints[player].add(hint)
locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids}
for location in world.get_filled_locations():
locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in multiworld.player_ids}
for location in multiworld.get_filled_locations():
if type(location.address) == int:
assert location.item.code is not None, "item code None should be event, " \
"location.address should then also be None"
"location.address should then also be None. Location: " \
f" {location}"
assert location.address not in locations_data[location.player], (
f"Locations with duplicate address. {location} and "
f"{locations_data[location.player][location.address]}")
locations_data[location.player][location.address] = \
location.item.code, location.item.player, location.item.flags
if location.name in world.start_location_hints[location.player]:
if location.name in multiworld.worlds[location.player].options.start_location_hints:
precollect_hint(location)
elif location.item.name in world.start_hints[location.item.player]:
elif location.item.name in multiworld.worlds[location.item.player].options.start_hints:
precollect_hint(location)
elif any([location.item.name in world.start_hints[player]
for player in world.groups.get(location.item.player, {}).get("players", [])]):
elif any([location.item.name in multiworld.worlds[player].options.start_hints
for player in multiworld.groups.get(location.item.player, {}).get("players", [])]):
precollect_hint(location)
# embedded data package
data_package = {
game_world.game: worlds.network_data_package["games"][game_world.game]
for game_world in multiworld.worlds.values()
}
checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {}
multidata = {
"slot_data": slot_data,
"slot_info": slot_info,
"names": names, # TODO: remove around 0.2.5 in favor of slot_info
"games": games, # TODO: remove around 0.2.5 in favor of slot_info
"connect_names": {name: (0, player) for player, name in world.player_name.items()},
"remote_items": {player for player in world.player_ids if
world.worlds[player].remote_items},
"remote_start_inventory": {player for player in world.player_ids if
world.worlds[player].remote_start_inventory},
"connect_names": {name: (0, player) for player, name in multiworld.player_name.items()},
"locations": locations_data,
"checks_in_area": checks_in_area,
"server_options": baked_server_options,
@@ -396,9 +385,10 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
"version": tuple(version_tuple),
"tags": ["AP"],
"minimum_versions": minimum_versions,
"seed_name": world.seed_name
"seed_name": multiworld.seed_name,
"datapackage": data_package,
}
AutoWorld.call_all(world, "modify_multidata", multidata)
AutoWorld.call_all(multiworld, "modify_multidata", multidata)
multidata = zlib.compress(pickle.dumps(multidata), 9)
@@ -406,15 +396,14 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
f.write(bytes([3])) # version of format
f.write(multidata)
multidata_task = pool.submit(write_multidata)
output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result():
if not world.can_beat_game():
if not multiworld.can_beat_game():
raise Exception("Game appears as unbeatable. Aborting.")
else:
logger.warning("Location Accessibility requirements not fulfilled.")
# retrieve exceptions via .result() if they occurred.
multidata_task.result()
for i, future in enumerate(concurrent.futures.as_completed(output_file_futures), start=1):
if i % 10 == 0 or i == len(output_file_futures):
logger.info(f'Generating output files ({i}/{len(output_file_futures)}).')
@@ -422,157 +411,17 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
if args.spoiler > 1:
logger.info('Calculating playthrough.')
create_playthrough(world)
multiworld.spoiler.create_playthrough(create_paths=args.spoiler > 2)
if args.spoiler:
world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
multiworld.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
zipfilename = output_path(f"AP_{world.seed_name}.zip")
logger.info(f'Creating final archive at {zipfilename}.')
zipfilename = output_path(f"AP_{multiworld.seed_name}.zip")
logger.info(f"Creating final archive at {zipfilename}")
with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED,
compresslevel=9) as zf:
for file in os.scandir(temp_dir):
zf.write(file.path, arcname=file.name)
logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start)
return world
def create_playthrough(world):
"""Destructive to the world while it is run, damage gets repaired afterwards."""
# get locations containing progress items
prog_locations = {location for location in world.get_filled_locations() if location.item.advancement}
state_cache = [None]
collection_spheres = []
state = CollectionState(world)
sphere_candidates = set(prog_locations)
logging.debug('Building up collection spheres.')
while sphere_candidates:
# build up spheres of collection radius.
# Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
sphere = {location for location in sphere_candidates if state.can_reach(location)}
for location in sphere:
state.collect(location.item, True, location)
sphere_candidates -= sphere
collection_spheres.append(sphere)
state_cache.append(state.copy())
logging.debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere),
len(prog_locations))
if not sphere:
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([world.accessibility[location.item.player] != 'minimal' for location in sphere_candidates]):
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
f'Something went terribly wrong here.')
else:
world.spoiler.unreachables = sphere_candidates
break
# in the second phase, we cull each sphere such that the game is still beatable,
# reducing each range of influence to the bare minimum required inside it
restore_later = {}
for num, sphere in reversed(tuple(enumerate(collection_spheres))):
to_delete = set()
for location in sphere:
# we remove the item at location and check if game is still beatable
logging.debug('Checking if %s (Player %d) is required to beat the game.', location.item.name,
location.item.player)
old_item = location.item
location.item = None
if world.can_beat_game(state_cache[num]):
to_delete.add(location)
restore_later[location] = old_item
else:
# still required, got to keep it around
location.item = old_item
# cull entries in spheres for spoiler walkthrough at end
sphere -= to_delete
# second phase, sphere 0
removed_precollected = []
for item in (i for i in chain.from_iterable(world.precollected_items.values()) if i.advancement):
logging.debug('Checking if %s (Player %d) is required to beat the game.', item.name, item.player)
world.precollected_items[item.player].remove(item)
world.state.remove(item)
if not world.can_beat_game():
world.push_precollected(item)
else:
removed_precollected.append(item)
# we are now down to just the required progress items in collection_spheres. Unfortunately
# the previous pruning stage could potentially have made certain items dependant on others
# in the same or later sphere (because the location had 2 ways to access but the item originally
# used to access it was deemed not required.) So we need to do one final sphere collection pass
# to build up the correct spheres
required_locations = {item for sphere in collection_spheres for item in sphere}
state = CollectionState(world)
collection_spheres = []
while required_locations:
state.sweep_for_events(key_only=True)
sphere = set(filter(state.can_reach, required_locations))
for location in sphere:
state.collect(location.item, True, location)
required_locations -= sphere
collection_spheres.append(sphere)
logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres),
len(sphere), len(required_locations))
if not sphere:
raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}')
def flist_to_iter(node):
while node:
value, node = node
yield value
def get_path(state, region):
reversed_path_as_flist = state.path.get(region, (region, None))
string_path_flat = reversed(list(map(str, flist_to_iter(reversed_path_as_flist))))
# Now we combine the flat string list into (region, exit) pairs
pathsiter = iter(string_path_flat)
pathpairs = zip_longest(pathsiter, pathsiter)
return list(pathpairs)
world.spoiler.paths = {}
topology_worlds = (player for player in world.player_ids if world.worlds[player].topology_present)
for player in topology_worlds:
world.spoiler.paths.update(
{str(location): get_path(state, location.parent_region) for sphere in collection_spheres for location in
sphere if location.player == player})
if player in world.get_game_players("A Link to the Past"):
# If Pyramid Fairy Entrance needs to be reached, also path to Big Bomb Shop
# Maybe move the big bomb over to the Event system instead?
if any(exit_path == 'Pyramid Fairy' for path in world.spoiler.paths.values() for (_, exit_path) in path):
if world.mode[player] != 'inverted':
world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = \
get_path(state, world.get_region('Big Bomb Shop', player))
else:
world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = \
get_path(state, world.get_region('Inverted Big Bomb Shop', player))
# we can finally output our playthrough
world.spoiler.playthrough = {"0": sorted([str(item) for item in
chain.from_iterable(world.precollected_items.values())
if item.advancement])}
for i, sphere in enumerate(collection_spheres):
world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)}
# repair the world again
for location, item in restore_later.items():
location.item = item
for item in removed_precollected:
world.push_precollected(item)
return multiworld

View File

@@ -13,12 +13,12 @@ import logging
import requests
import Utils
from Utils import is_windows
atexit.register(input, "Press enter to exit.")
# 1 or more digits followed by m or g, then optional b
max_heap_re = re.compile(r"^\d+[mMgG][bB]?$")
is_windows = sys.platform in ("win32", "cygwin", "msys")
def prompt_yes_no(prompt):
@@ -77,49 +77,34 @@ def read_apmc_file(apmc_file):
return json.loads(b64decode(f.read()))
def update_mod(forge_dir, minecraft_version: str, get_prereleases=False):
def update_mod(forge_dir, url: str):
"""Check mod version, download new mod from GitHub releases page if needed. """
ap_randomizer = find_ap_randomizer_jar(forge_dir)
client_releases_endpoint = "https://api.github.com/repos/KonoTyran/Minecraft_AP_Randomizer/releases"
resp = requests.get(client_releases_endpoint)
if resp.status_code == 200: # OK
try:
latest_release = next(filter(lambda release: (not release['prerelease'] or get_prereleases) and
(minecraft_version in release['assets'][0]['name']),
resp.json()))
if ap_randomizer != latest_release['assets'][0]['name']:
logging.info(f"A new release of the Minecraft AP randomizer mod was found: "
f"{latest_release['assets'][0]['name']}")
if ap_randomizer is not None:
logging.info(f"Your current mod is {ap_randomizer}.")
else:
logging.info(f"You do not have the AP randomizer mod installed.")
if prompt_yes_no("Would you like to update?"):
old_ap_mod = os.path.join(forge_dir, 'mods', ap_randomizer) if ap_randomizer is not None else None
new_ap_mod = os.path.join(forge_dir, 'mods', latest_release['assets'][0]['name'])
logging.info("Downloading AP randomizer mod. This may take a moment...")
apmod_resp = requests.get(latest_release['assets'][0]['browser_download_url'])
if apmod_resp.status_code == 200:
with open(new_ap_mod, 'wb') as f:
f.write(apmod_resp.content)
logging.info(f"Wrote new mod file to {new_ap_mod}")
if old_ap_mod is not None:
os.remove(old_ap_mod)
logging.info(f"Removed old mod file from {old_ap_mod}")
else:
logging.error(f"Error retrieving the randomizer mod (status code {apmod_resp.status_code}).")
logging.error(f"Please report this issue on the Archipelago Discord server.")
sys.exit(1)
except StopIteration:
logging.warning(f"No compatible mod version found for {minecraft_version}.")
if not prompt_yes_no("Run server anyway?"):
sys.exit(0)
os.path.basename(url)
if ap_randomizer is not None:
logging.info(f"Your current mod is {ap_randomizer}.")
else:
logging.error(f"Error checking for randomizer mod updates (status code {resp.status_code}).")
logging.error(f"If this was not expected, please report this issue on the Archipelago Discord server.")
if not prompt_yes_no("Continue anyways?"):
sys.exit(0)
logging.info(f"You do not have the AP randomizer mod installed.")
if ap_randomizer != os.path.basename(url):
logging.info(f"A new release of the Minecraft AP randomizer mod was found: "
f"{os.path.basename(url)}")
if prompt_yes_no("Would you like to update?"):
old_ap_mod = os.path.join(forge_dir, 'mods', ap_randomizer) if ap_randomizer is not None else None
new_ap_mod = os.path.join(forge_dir, 'mods', os.path.basename(url))
logging.info("Downloading AP randomizer mod. This may take a moment...")
apmod_resp = requests.get(url)
if apmod_resp.status_code == 200:
with open(new_ap_mod, 'wb') as f:
f.write(apmod_resp.content)
logging.info(f"Wrote new mod file to {new_ap_mod}")
if old_ap_mod is not None:
os.remove(old_ap_mod)
logging.info(f"Removed old mod file from {old_ap_mod}")
else:
logging.error(f"Error retrieving the randomizer mod (status code {apmod_resp.status_code}).")
logging.error(f"Please report this issue on the Archipelago Discord server.")
sys.exit(1)
def check_eula(forge_dir):
@@ -196,8 +181,8 @@ def download_java(java: str):
def install_forge(directory: str, forge_version: str, java_version: str):
"""download and install forge"""
jdk = find_jdk(java_version)
if jdk is not None:
java_exe = find_jdk(java_version)
if java_exe is not None:
print(f"Downloading Forge {forge_version}...")
forge_url = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{forge_version}/forge-{forge_version}-installer.jar"
resp = requests.get(forge_url)
@@ -208,8 +193,7 @@ def install_forge(directory: str, forge_version: str, java_version: str):
with open(forge_install_jar, 'wb') as f:
f.write(resp.content)
print(f"Installing Forge...")
argstring = ' '.join([jdk, "-jar", "\"" + forge_install_jar + "\"", "--installServer", "\"" + directory + "\""])
install_process = Popen(argstring, shell=not is_windows)
install_process = Popen([java_exe, "-jar", forge_install_jar, "--installServer", directory])
install_process.wait()
os.remove(forge_install_jar)
@@ -228,15 +212,15 @@ def run_forge_server(forge_dir: str, java_version: str, heap_arg: str) -> Popen:
os_args = "win_args.txt" if is_windows else "unix_args.txt"
args_file = os.path.join(forge_dir, "libraries", "net", "minecraftforge", "forge", forge_version, os_args)
win_args = []
forge_args = []
with open(args_file) as argfile:
for line in argfile:
win_args.append(line.strip())
forge_args.extend(line.strip().split(" "))
argstring = ' '.join([java_exe, heap_arg] + win_args + ["-nogui"])
logging.info(f"Running Forge server: {argstring}")
args = [java_exe, heap_arg, *forge_args, "-nogui"]
logging.info(f"Running Forge server: {args}")
os.chdir(forge_dir)
return Popen(argstring, shell=not is_windows)
return Popen(args)
def get_minecraft_versions(version, release_channel="release"):
@@ -254,10 +238,10 @@ def get_minecraft_versions(version, release_channel="release"):
local = True
if local:
with open(Utils.local_path("minecraft_versions.json"), 'r') as f:
with open(Utils.user_path("minecraft_versions.json"), 'r') as f:
data = json.load(f)
else:
with open(Utils.local_path("minecraft_versions.json"), 'w') as f:
with open(Utils.user_path("minecraft_versions.json"), 'w') as f:
json.dump(data, f)
try:
@@ -265,8 +249,13 @@ def get_minecraft_versions(version, release_channel="release"):
return next(filter(lambda entry: entry["version"] == version, data[release_channel]))
else:
return resp.json()[release_channel][0]
except StopIteration:
logging.error(f"No compatible mod version found for client version {version}.")
except (StopIteration, KeyError):
logging.error(f"No compatible mod version found for client version {version} on \"{release_channel}\" channel.")
if release_channel != "release":
logging.error("Consider switching \"release_channel\" to \"release\" in your Host.yaml file")
else:
logging.error("No suitable mod found on the \"release\" channel. Please Contact us on discord to report this error.")
sys.exit(0)
def is_correct_forge(forge_dir) -> bool:
@@ -287,6 +276,8 @@ if __name__ == '__main__':
help="specify java version.")
parser.add_argument('--forge', '-f', metavar='1.18.2-40.1.0', dest='forge', type=str, default=False, action='store',
help="specify forge version. (Minecraft Version-Forge Version)")
parser.add_argument('--version', '-v', metavar='9', dest='data_version', type=int, action='store',
help="specify Mod data version to download.")
args = parser.parse_args()
apmc_file = os.path.abspath(args.apmc_file) if args.apmc_file else None
@@ -297,9 +288,12 @@ if __name__ == '__main__':
options = Utils.get_options()
channel = args.channel or options["minecraft_options"]["release_channel"]
apmc_data = None
data_version = None
data_version = args.data_version or None
if apmc_file is not None:
if apmc_file is None and not args.install:
apmc_file = Utils.open_filename('Select APMC file', (('APMC File', ('.apmc',)),))
if apmc_file is not None and data_version is None:
apmc_data = read_apmc_file(apmc_file)
data_version = apmc_data.get('client_version', '')
@@ -309,15 +303,18 @@ if __name__ == '__main__':
max_heap = options["minecraft_options"]["max_heap_size"]
forge_version = args.forge or versions["forge"]
java_version = args.java or versions["java"]
mod_url = versions["url"]
java_dir = find_jdk_dir(java_version)
if args.install:
if is_windows:
print("Installing Java and Minecraft Forge")
print("Installing Java")
download_java(java_version)
else:
if not is_correct_forge(forge_dir):
print("Installing Minecraft Forge")
install_forge(forge_dir, forge_version, java_version)
install_forge(forge_dir, forge_version, java_version)
else:
print("Correct Forge version already found, skipping install.")
sys.exit(0)
if apmc_data is None:
@@ -340,7 +337,7 @@ if __name__ == '__main__':
if not max_heap_re.match(max_heap):
raise Exception(f"Max heap size {max_heap} in incorrect format. Use a number followed by M or G, e.g. 512M or 2G.")
update_mod(forge_dir, f"MC{forge_version.split('-')[0]}", channel != "release")
update_mod(forge_dir, mod_url)
replace_apmc_files(forge_dir, apmc_file)
check_eula(forge_dir)
server_process = run_forge_server(forge_dir, java_version, max_heap)

View File

@@ -1,57 +1,142 @@
import os
import sys
import subprocess
import pkg_resources
import multiprocessing
import warnings
local_dir = os.path.dirname(__file__)
requirements_files = {os.path.join(local_dir, 'requirements.txt')}
if sys.version_info < (3, 8, 6):
raise RuntimeError("Incompatible Python Version. 3.8.7+ is supported.")
update_ran = getattr(sys, "frozen", False) # don't run update if environment is frozen/compiled
# don't run update if environment is frozen/compiled or if not the parent process (skip in subprocess)
_skip_update = bool(getattr(sys, "frozen", False) or multiprocessing.parent_process())
update_ran = _skip_update
class RequirementsSet(set):
def add(self, e):
global update_ran
update_ran &= _skip_update
super().add(e)
def update(self, *s):
global update_ran
update_ran &= _skip_update
super().update(*s)
local_dir = os.path.dirname(__file__)
requirements_files = RequirementsSet((os.path.join(local_dir, 'requirements.txt'),))
if not update_ran:
for entry in os.scandir(os.path.join(local_dir, "worlds")):
if entry.is_dir():
req_file = os.path.join(entry.path, "requirements.txt")
if os.path.exists(req_file):
requirements_files.add(req_file)
# skip .* (hidden / disabled) folders
if not entry.name.startswith("."):
if entry.is_dir():
req_file = os.path.join(entry.path, "requirements.txt")
if os.path.exists(req_file):
requirements_files.add(req_file)
def check_pip():
# detect if pip is available
try:
import pip # noqa: F401
except ImportError:
raise RuntimeError("pip not available. Please install pip.")
def confirm(msg: str):
try:
input(f"\n{msg}")
except KeyboardInterrupt:
print("\nAborting")
sys.exit(1)
def update_command():
check_pip()
for file in requirements_files:
subprocess.call([sys.executable, '-m', 'pip', 'install', '-r', file, '--upgrade'])
subprocess.call([sys.executable, "-m", "pip", "install", "-r", file, "--upgrade"])
def update(yes=False, force=False):
def install_pkg_resources(yes=False):
try:
import pkg_resources # noqa: F401
except ImportError:
check_pip()
if not yes:
confirm("pkg_resources not found, press enter to install it")
subprocess.call([sys.executable, "-m", "pip", "install", "--upgrade", "setuptools"])
def update(yes: bool = False, force: bool = False) -> None:
global update_ran
if not update_ran:
update_ran = True
if force:
update_command()
return
install_pkg_resources(yes=yes)
import pkg_resources
prev = "" # if a line ends in \ we store here and merge later
for req_file in requirements_files:
path = os.path.join(os.path.dirname(sys.argv[0]), req_file)
if not os.path.exists(path):
path = os.path.join(os.path.dirname(__file__), req_file)
with open(path) as requirementsfile:
for line in requirementsfile:
if line.startswith('https://'):
# extract name and version from url
wheel = line.split('/')[-1]
name, version, _ = wheel.split('-', 2)
line = f'{name}=={version}'
if not line or line.lstrip(" \t")[0] == "#":
if not prev:
continue # ignore comments
line = ""
elif line.rstrip("\r\n").endswith("\\"):
prev = prev + line.rstrip("\r\n")[:-1] + " " # continue on next line
continue
line = prev + line
line = line.split("--hash=")[0] # remove hashes from requirement for version checking
prev = ""
if line.startswith(("https://", "git+https://")):
# extract name and version for url
rest = line.split('/')[-1]
line = ""
if "#egg=" in rest:
# from egg info
rest, egg = rest.split("#egg=", 1)
egg = egg.split(";", 1)[0].rstrip()
if any(compare in egg for compare in ("==", ">=", ">", "<", "<=", "!=")):
warnings.warn(f"Specifying version as #egg={egg} will become unavailable in pip 25.0. "
"Use name @ url#version instead.", DeprecationWarning)
line = egg
else:
egg = ""
if "@" in rest and not line:
raise ValueError("Can't deduce version from requirement")
elif not line:
# from filename
rest = rest.replace(".zip", "-").replace(".tar.gz", "-")
name, version, _ = rest.split("-", 2)
line = f'{egg or name}=={version}'
elif "@" in line and "#" in line:
# PEP 508 does not allow us to specify a version, so we use custom syntax
# name @ url#version ; marker
name, rest = line.split("@", 1)
version = rest.split("#", 1)[1].split(";", 1)[0].rstrip()
line = f"{name.rstrip()}=={version}"
if ";" in rest: # keep marker
line += rest[rest.find(";"):]
requirements = pkg_resources.parse_requirements(line)
for requirement in requirements:
requirement = str(requirement)
for requirement in map(str, requirements):
try:
pkg_resources.require(requirement)
except pkg_resources.ResolutionError:
if not yes:
import traceback
traceback.print_exc()
input(f'Requirement {requirement} is not satisfied, press enter to install it')
confirm(f"Requirement {requirement} is not satisfied, press enter to install it")
update_command()
return

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,12 @@ from __future__ import annotations
import typing
import enum
import warnings
from json import JSONEncoder, JSONDecoder
import websockets
from Utils import Version
from Utils import ByValue, Version
class JSONMessagePart(typing.TypedDict, total=False):
@@ -20,7 +21,7 @@ class JSONMessagePart(typing.TypedDict, total=False):
flags: int
class ClientStatus(enum.IntEnum):
class ClientStatus(ByValue, enum.IntEnum):
CLIENT_UNKNOWN = 0
CLIENT_CONNECTED = 5
CLIENT_READY = 10
@@ -28,22 +29,22 @@ class ClientStatus(enum.IntEnum):
CLIENT_GOAL = 30
class SlotType(enum.IntFlag):
class SlotType(ByValue, enum.IntFlag):
spectator = 0b00
player = 0b01
group = 0b10
@property
def always_goal(self) -> bool:
"""Mark this slot has having reached its goal instantly."""
"""Mark this slot as having reached its goal instantly."""
return self.value != 0b01
class Permission(enum.IntFlag):
class Permission(ByValue, enum.IntFlag):
disabled = 0b000 # 0, completely disables access
enabled = 0b001 # 1, allows manual use
goal = 0b010 # 2, allows manual use after goal completion
auto = 0b110 # 6, forces use after goal completion, only works for forfeit
auto = 0b110 # 6, forces use after goal completion, only works for release
auto_enabled = 0b111 # 7, forces use after goal completion, allows manual use any time
@staticmethod
@@ -86,7 +87,7 @@ def _scan_for_TypedTuples(obj: typing.Any) -> typing.Any:
data = obj._asdict()
data["class"] = obj.__class__.__name__
return data
if isinstance(obj, (tuple, list, set)):
if isinstance(obj, (tuple, list, set, frozenset)):
return tuple(_scan_for_TypedTuples(o) for o in obj)
if isinstance(obj, dict):
return {key: _scan_for_TypedTuples(value) for key, value in obj.items()}
@@ -96,10 +97,11 @@ def _scan_for_TypedTuples(obj: typing.Any) -> typing.Any:
_encode = JSONEncoder(
ensure_ascii=False,
check_circular=False,
separators=(',', ':'),
).encode
def encode(obj):
def encode(obj: typing.Any) -> str:
return _encode(_scan_for_TypedTuples(obj))
@@ -108,7 +110,7 @@ def get_any_version(data: dict) -> Version:
return Version(int(data["major"]), int(data["minor"]), int(data["build"]))
whitelist = {
allowlist = {
"NetworkPlayer": NetworkPlayer,
"NetworkItem": NetworkItem,
"NetworkSlot": NetworkSlot
@@ -124,7 +126,7 @@ def _object_hook(o: typing.Any) -> typing.Any:
hook = custom_hooks.get(o.get("class", None), None)
if hook:
return hook(o)
cls = whitelist.get(o.get("class", None), None)
cls = allowlist.get(o.get("class", None), None)
if cls:
for key in tuple(o):
if key not in cls._fields:
@@ -235,7 +237,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
node["color"] = 'cyan'
elif flags & 0b001: # advancement
node["color"] = 'plum'
elif flags & 0b010: # never_exclude
elif flags & 0b010: # useful
node["color"] = 'slateblue'
elif flags & 0b100: # trap
node["color"] = 'salmon'
@@ -245,7 +247,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
def _handle_item_id(self, node: JSONMessagePart):
item_id = int(node["text"])
node["text"] = self.ctx.item_name_getter(item_id)
node["text"] = self.ctx.item_names[item_id]
return self._handle_item_name(node)
def _handle_location_name(self, node: JSONMessagePart):
@@ -254,7 +256,7 @@ class JSONtoTextParser(metaclass=HandlerMeta):
def _handle_location_id(self, node: JSONMessagePart):
item_id = int(node["text"])
node["text"] = self.ctx.location_name_getter(item_id)
node["text"] = self.ctx.location_names[item_id]
return self._handle_location_name(node)
def _handle_entrance_name(self, node: JSONMessagePart):
@@ -269,7 +271,7 @@ class RawJSONtoTextParser(JSONtoTextParser):
color_codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
'magenta': 35, 'cyan': 36, 'white': 37, 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
'blue_bg': 44, 'purple_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
'blue_bg': 44, 'magenta_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
def color_code(*args):
@@ -288,8 +290,8 @@ def add_json_item(parts: list, item_id: int, player: int = 0, item_flags: int =
parts.append({"text": str(item_id), "player": player, "flags": item_flags, "type": JSONTypes.item_id, **kwargs})
def add_json_location(parts: list, item_id: int, player: int = 0, **kwargs) -> None:
parts.append({"text": str(item_id), "player": player, "type": JSONTypes.location_id, **kwargs})
def add_json_location(parts: list, location_id: int, player: int = 0, **kwargs) -> None:
parts.append({"text": str(location_id), "player": player, "type": JSONTypes.location_id, **kwargs})
class Hint(typing.NamedTuple):
@@ -342,3 +344,85 @@ class Hint(typing.NamedTuple):
@property
def local(self):
return self.receiving_player == self.finding_player
class _LocationStore(dict, typing.MutableMapping[int, typing.Dict[int, typing.Tuple[int, int, int]]]):
def __init__(self, values: typing.MutableMapping[int, typing.Dict[int, typing.Tuple[int, int, int]]]):
super().__init__(values)
if not self:
raise ValueError(f"Rejecting game with 0 players")
if len(self) != max(self):
raise ValueError("Player IDs not continuous")
if len(self.get(0, {})):
raise ValueError("Invalid player id 0 for location")
def find_item(self, slots: typing.Set[int], seeked_item_id: int
) -> typing.Generator[typing.Tuple[int, int, int, int, int], None, None]:
for finding_player, check_data in self.items():
for location_id, (item_id, receiving_player, item_flags) in check_data.items():
if receiving_player in slots and item_id == seeked_item_id:
yield finding_player, location_id, item_id, receiving_player, item_flags
def get_for_player(self, slot: int) -> typing.Dict[int, typing.Set[int]]:
import collections
all_locations: typing.Dict[int, typing.Set[int]] = collections.defaultdict(set)
for source_slot, location_data in self.items():
for location_id, values in location_data.items():
if values[1] == slot:
all_locations[source_slot].add(location_id)
return all_locations
def get_checked(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int
) -> typing.List[int]:
checked = state[team, slot]
if not checked:
# This optimizes the case where everyone connects to a fresh game at the same time.
return []
return [location_id for
location_id in self[slot] if
location_id in checked]
def get_missing(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int
) -> typing.List[int]:
checked = state[team, slot]
if not checked:
# This optimizes the case where everyone connects to a fresh game at the same time.
return list(self[slot])
return [location_id for
location_id in self[slot] if
location_id not in checked]
def get_remaining(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int
) -> typing.List[int]:
checked = state[team, slot]
player_locations = self[slot]
return sorted([player_locations[location_id][0] for
location_id in player_locations if
location_id not in checked])
if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub
LocationStore = _LocationStore
else:
try:
from _speedups import LocationStore
import _speedups
import os.path
if os.path.isfile("_speedups.pyx") and os.path.getctime(_speedups.__file__) < os.path.getctime("_speedups.pyx"):
warnings.warn(f"{_speedups.__file__} outdated! "
f"Please rebuild with `cythonize -b -i _speedups.pyx` or delete it!")
except ImportError:
try:
import pyximport
pyximport.install()
except ImportError:
pyximport = None
try:
from _speedups import LocationStore
except ImportError:
warnings.warn("_speedups not available. Falling back to pure python LocationStore. "
"Install a matching C++ compiler for your platform to compile _speedups.")
LocationStore = _LocationStore

View File

@@ -3,6 +3,7 @@ import argparse
import logging
import random
import os
import zipfile
from itertools import chain
from BaseClasses import MultiWorld
@@ -43,7 +44,7 @@ def adjustGUI():
StringVar, IntVar, Checkbutton, Frame, Label, X, Entry, Button, \
OptionMenu, filedialog, messagebox, ttk
from argparse import Namespace
from Main import __version__ as MWVersion
from Utils import __version__ as MWVersion
window = tk.Tk()
window.wm_title(f"Archipelago {MWVersion} OoT Adjuster")
@@ -194,10 +195,10 @@ def set_icon(window):
window.tk.call('wm', 'iconphoto', window._w, logo)
def adjust(args):
# Create a fake world and OOTWorld to use as a base
world = MultiWorld(1)
world.slot_seeds = {1: random}
ootworld = OOTWorld(world, 1)
# Create a fake multiworld and OOTWorld to use as a base
multiworld = MultiWorld(1)
multiworld.per_slot_randoms = {1: random}
ootworld = OOTWorld(multiworld, 1)
# Set options in the fake OOTWorld
for name, option in chain(cosmetic_options.items(), sfx_options.items()):
result = getattr(args, name, None)
@@ -217,13 +218,18 @@ def adjust(args):
# Load up the ROM
rom = Rom(file=args.rom, force_use=True)
delete_zootdec = True
elif os.path.splitext(args.rom)[-1] == '.apz5':
elif os.path.splitext(args.rom)[-1] in ['.apz5', '.zpf']:
# Load vanilla ROM
rom = Rom(file=args.vanilla_rom, force_use=True)
apz5_file = args.rom
base_name = os.path.splitext(apz5_file)[0]
# Patch file
apply_patch_file(rom, args.rom)
apply_patch_file(rom, apz5_file,
sub_file=(os.path.basename(base_name) + '.zpf'
if zipfile.is_zipfile(apz5_file)
else None))
else:
raise Exception("Invalid file extension; requires .n64, .z64, .apz5")
raise Exception("Invalid file extension; requires .n64, .z64, .apz5, .zpf")
# Call patch_cosmetics
try:
patch_cosmetics(ootworld, rom)

View File

@@ -3,20 +3,23 @@ import json
import os
import multiprocessing
import subprocess
import zipfile
from asyncio import StreamReader, StreamWriter
from CommonClient import CommonContext, server_loop, gui_enabled, console_loop, \
# CommonClient import first to trigger ModuleUpdater
from CommonClient import CommonContext, server_loop, gui_enabled, \
ClientCommandProcessor, logger, get_base_parser
import Utils
from Utils import async_start
from worlds import network_data_package
from worlds.oot.Rom import Rom, compress_rom_file
from worlds.oot.N64Patch import apply_patch_file
from worlds.oot.Utils import data_path
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart oot_connector.lua"
CONNECTION_REFUSED_STATUS = "Connection refused. Please start your emulator and make sure oot_connector.lua is running"
CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart oot_connector.lua"
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_oot.lua"
CONNECTION_REFUSED_STATUS = "Connection refused. Please start your emulator and make sure connector_oot.lua is running"
CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart connector_oot.lua"
CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
CONNECTION_CONNECTED_STATUS = "Connected"
CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
@@ -48,7 +51,7 @@ deathlink_sent_this_death: we interacted with the multiworld on this death, wait
oot_loc_name_to_id = network_data_package["games"]["Ocarina of Time"]["location_name_to_id"]
script_version: int = 1
script_version: int = 3
def get_item_value(ap_id):
return ap_id - 66000
@@ -68,7 +71,7 @@ class OoTCommandProcessor(ClientCommandProcessor):
if isinstance(self.ctx, OoTContext):
self.ctx.deathlink_client_override = True
self.ctx.deathlink_enabled = not self.ctx.deathlink_enabled
asyncio.create_task(self.ctx.update_death_link(self.ctx.deathlink_enabled), name="Update Deathlink")
async_start(self.ctx.update_death_link(self.ctx.deathlink_enabled), name="Update Deathlink")
class OoTContext(CommonContext):
@@ -83,6 +86,9 @@ class OoTContext(CommonContext):
self.n64_status = CONNECTION_INITIAL_STATUS
self.awaiting_rom = False
self.location_table = {}
self.collectible_table = {}
self.collectible_override_flags_address = 0
self.collectible_offsets = {}
self.deathlink_enabled = False
self.deathlink_pending = False
self.deathlink_sent_this_death = False
@@ -94,7 +100,7 @@ class OoTContext(CommonContext):
await super(OoTContext, self).server_auth(password_requested)
if not self.auth:
self.awaiting_rom = True
logger.info('Awaiting connection to Bizhawk to get player information')
logger.info('Awaiting connection to EmuHawk to get player information')
return
await self.send_connect()
@@ -115,6 +121,13 @@ class OoTContext(CommonContext):
self.ui = OoTManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def on_package(self, cmd, args):
if cmd == 'Connected':
slot_data = args.get('slot_data', None)
if slot_data:
self.collectible_override_flags_address = slot_data.get('collectible_override_flags', 0)
self.collectible_offsets = slot_data.get('collectible_flag_offsets', {})
def get_payload(ctx: OoTContext):
if ctx.deathlink_enabled and ctx.deathlink_pending:
@@ -123,15 +136,32 @@ def get_payload(ctx: OoTContext):
else:
trigger_death = False
return json.dumps({
payload = json.dumps({
"items": [get_item_value(item.item) for item in ctx.items_received],
"playerNames": [name for (i, name) in ctx.player_names.items() if i != 0],
"triggerDeath": trigger_death
"triggerDeath": trigger_death,
"collectibleOverrides": ctx.collectible_override_flags_address,
"collectibleOffsets": ctx.collectible_offsets
})
return payload
async def parse_payload(payload: dict, ctx: OoTContext, force: bool):
# Refuse to do anything if ROM is detected as changed
if ctx.auth and payload['playerName'] != ctx.auth:
logger.warning("ROM change detected. Disconnecting and reconnecting...")
ctx.deathlink_enabled = False
ctx.deathlink_client_override = False
ctx.finished_game = False
ctx.location_table = {}
ctx.collectible_table = {}
ctx.deathlink_pending = False
ctx.deathlink_sent_this_death = False
ctx.auth = payload['playerName']
await ctx.send_connect()
return
# Turn on deathlink if it is on, and if the client hasn't overriden it
if payload['deathlinkActive'] and not ctx.deathlink_enabled and not ctx.deathlink_client_override:
await ctx.update_death_link(True)
@@ -146,11 +176,23 @@ async def parse_payload(payload: dict, ctx: OoTContext, force: bool):
ctx.finished_game = True
# Locations handling
if ctx.location_table != payload['locations']:
ctx.location_table = payload['locations']
locations = payload['locations']
collectibles = payload['collectibles']
# The Lua JSON library serializes an empty table into a list instead of a dict. Verify types for safety:
if isinstance(locations, list):
locations = {}
if isinstance(collectibles, list):
collectibles = {}
if ctx.location_table != locations or ctx.collectible_table != collectibles:
ctx.location_table = locations
ctx.collectible_table = collectibles
locs1 = [oot_loc_name_to_id[loc] for loc, b in ctx.location_table.items() if b]
locs2 = [int(loc) for loc, b in ctx.collectible_table.items() if b]
await ctx.send_msgs([{
"cmd": "LocationChecks",
"locations": [oot_loc_name_to_id[loc] for loc in ctx.location_table if ctx.location_table[loc]]
"locations": locs1 + locs2
}])
# Deathlink handling
@@ -176,20 +218,13 @@ async def n64_sync_task(ctx: OoTContext):
try:
await asyncio.wait_for(writer.drain(), timeout=1.5)
try:
# Data will return a dict with up to six fields:
# 1. str: player name (always)
# 2. int: script version (always)
# 3. bool: deathlink active (always)
# 4. dict[str, bool]: checked locations
# 5. bool: whether Link is currently at 0 HP
# 6. bool: whether the game currently registers as complete
data = await asyncio.wait_for(reader.readline(), timeout=10)
data_decoded = json.loads(data.decode())
reported_version = data_decoded.get('scriptVersion', 0)
if reported_version == script_version:
if reported_version >= script_version:
if ctx.game is not None and 'locations' in data_decoded:
# Not just a keep alive ping, parse
asyncio.create_task(parse_payload(data_decoded, ctx, False))
async_start(parse_payload(data_decoded, ctx, False))
if not ctx.auth:
ctx.auth = data_decoded['playerName']
if ctx.awaiting_rom:
@@ -255,17 +290,27 @@ async def run_game(romfile):
async def patch_and_run_game(apz5_file):
apz5_file = os.path.abspath(apz5_file)
base_name = os.path.splitext(apz5_file)[0]
decomp_path = base_name + '-decomp.z64'
comp_path = base_name + '.z64'
# Load vanilla ROM, patch file, compress ROM
rom = Rom(Utils.local_path(Utils.get_options()["oot_options"]["rom_file"]))
apply_patch_file(rom, apz5_file)
rom_file_name = Utils.get_options()["oot_options"]["rom_file"]
rom = Rom(rom_file_name)
sub_file = None
if zipfile.is_zipfile(apz5_file):
for name in zipfile.ZipFile(apz5_file).namelist():
if name.endswith('.zpf'):
sub_file = name
break
apply_patch_file(rom, apz5_file, sub_file=sub_file)
rom.write_to_file(decomp_path)
os.chdir(data_path("Compress"))
compress_rom_file(decomp_path, comp_path)
os.remove(decomp_path)
asyncio.create_task(run_game(comp_path))
async_start(run_game(comp_path))
if __name__ == '__main__':
@@ -281,7 +326,7 @@ if __name__ == '__main__':
if args.apz5_file:
logger.info("APZ5 file supplied, beginning patching process...")
asyncio.create_task(patch_and_run_game(args.apz5_file))
async_start(patch_and_run_game(args.apz5_file))
ctx = OoTContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop")

View File

@@ -1,12 +1,37 @@
from __future__ import annotations
import abc
import functools
import logging
import math
import numbers
import typing
import random
import typing
import enum
from copy import deepcopy
from dataclasses import dataclass
from schema import Schema, And, Or, Optional
from Utils import get_fuzzy_results
from schema import And, Optional, Or, Schema
from Utils import get_fuzzy_results, is_iterable_except_str
if typing.TYPE_CHECKING:
from BaseClasses import PlandoOptions
from worlds.AutoWorld import World
import pathlib
class OptionError(ValueError):
pass
class Visibility(enum.IntFlag):
none = 0b0000
template = 0b0001
simple_ui = 0b0010 # show option in simple menus, such as player-options
complex_ui = 0b0100 # show option in complex menus, such as weighted-options
spoiler = 0b1000
all = 0b1111
class AssembleOptions(abc.ABCMeta):
@@ -26,10 +51,36 @@ class AssembleOptions(abc.ABCMeta):
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
options.update(new_options)
# apply aliases, without name_lookup
options.update({name[6:].lower(): option_id for name, option_id in attrs.items() if
name.startswith("alias_")})
aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
name.startswith("alias_")}
assert (
name in {"Option", "VerifyKeys"} or # base abstract classes don't need default
"default" in attrs or
any(hasattr(base, "default") for base in bases)
), f"Option class {name} needs default value"
assert "random" not in aliases, "Choice option 'random' cannot be manually assigned."
# auto-alias Off and On being parsed as True and False
if "off" in options:
options["false"] = options["off"]
if "on" in options:
options["true"] = options["on"]
options.update(aliases)
if "verify" not in attrs:
# not overridden by class -> look up bases
verifiers = [f for f in (getattr(base, "verify", None) for base in bases) if f]
if len(verifiers) > 1: # verify multiple bases/mixins
def verify(self, *args, **kwargs) -> None:
for f in verifiers:
f(self, *args, **kwargs)
attrs["verify"] = verify
else:
assert verifiers, "class Option is supposed to implement def verify"
# auto-validate schema on __init__
if "schema" in attrs.keys():
@@ -64,7 +115,8 @@ T = typing.TypeVar('T')
class Option(typing.Generic[T], metaclass=AssembleOptions):
value: T
default = 0
default: typing.ClassVar[typing.Any] # something that __init__ will be able to convert to the correct type
visibility = Visibility.all
# convert option_name_long into Name Long as display_name, otherwise name_long is the result.
# Handled in get_option_name()
@@ -74,11 +126,12 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
supports_weighting = True
# filled by AssembleOptions:
name_lookup: typing.Dict[int, str]
options: typing.Dict[str, int]
name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore
# https://github.com/python/typing/discussions/1460 the reason for this type: ignore
options: typing.ClassVar[typing.Dict[str, int]]
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.get_current_option_name()})"
return f"{self.__class__.__name__}({self.current_option_name})"
def __hash__(self) -> int:
return hash(self.value)
@@ -87,8 +140,9 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
def current_key(self) -> str:
return self.name_lookup[self.value]
def get_current_option_name(self) -> str:
"""For display purposes."""
@property
def current_option_name(self) -> str:
"""For display purposes. Worlds should be using current_key."""
return self.get_option_name(self.value)
@classmethod
@@ -105,11 +159,56 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
return bool(self.value)
@classmethod
@abc.abstractmethod
def from_any(cls, data: typing.Any) -> Option[T]:
raise NotImplementedError
...
if typing.TYPE_CHECKING:
def verify(self, world: typing.Type[World], player_name: str, plando_options: PlandoOptions) -> None:
pass
else:
def verify(self, *args, **kwargs) -> None:
pass
class NumericOption(Option[int], numbers.Integral):
class FreeText(Option[str]):
"""Text option that allows users to enter strings.
Needs to be validated by the world or option definition."""
default = ""
def __init__(self, value: str):
assert isinstance(value, str), "value of FreeText must be a string"
self.value = value
@property
def current_key(self) -> str:
return self.value
@classmethod
def from_text(cls, text: str) -> FreeText:
return cls(text)
@classmethod
def from_any(cls, data: typing.Any) -> FreeText:
return cls.from_text(str(data))
@classmethod
def get_option_name(cls, value: str) -> str:
return value
def __eq__(self, other):
if isinstance(other, self.__class__):
return other.value == self.value
elif isinstance(other, str):
return other == self.value
else:
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
class NumericOption(Option[int], numbers.Integral, abc.ABC):
default = 0
# note: some of the `typing.Any`` here is a result of unresolved issue in python standards
# `int` is not a `numbers.Integral` according to the official typestubs
# (even though isinstance(5, numbers.Integral) == True)
@@ -141,6 +240,12 @@ class NumericOption(Option[int], numbers.Integral):
else:
return self.value > other
def __ge__(self, other: typing.Union[int, NumericOption]) -> bool:
if isinstance(other, NumericOption):
return self.value >= other.value
else:
return self.value >= other
def __bool__(self) -> bool:
return bool(self.value)
@@ -277,7 +382,8 @@ class Toggle(NumericOption):
default = 0
def __init__(self, value: int):
assert value == 0 or value == 1, "value of Toggle can only be 0 or 1"
# if user puts in an invalid value, make it valid
value = int(bool(value))
self.value = value
@classmethod
@@ -294,7 +400,7 @@ class Toggle(NumericOption):
if type(data) == str:
return cls.from_text(data)
else:
return cls(data)
return cls(int(data))
@classmethod
def get_option_name(cls, value):
@@ -364,6 +470,169 @@ class Choice(NumericOption):
__hash__ = Option.__hash__ # see https://docs.python.org/3/reference/datamodel.html#object.__hash__
class TextChoice(Choice):
"""Allows custom string input and offers choices. Choices will resolve to int and text will resolve to string"""
value: typing.Union[str, int]
def __init__(self, value: typing.Union[str, int]):
assert isinstance(value, str) or isinstance(value, int), \
f"{value} is not a valid option for {self.__class__.__name__}"
self.value = value
@property
def current_key(self) -> str:
if isinstance(self.value, str):
return self.value
return super().current_key
@classmethod
def from_text(cls, text: str) -> TextChoice:
if text.lower() == "random": # chooses a random defined option but won't use any free text options
return cls(random.choice(list(cls.name_lookup)))
for option_name, value in cls.options.items():
if option_name.lower() == text.lower():
return cls(value)
return cls(text)
@classmethod
def get_option_name(cls, value: T) -> str:
if isinstance(value, str):
return value
return super().get_option_name(value)
def __eq__(self, other: typing.Any):
if isinstance(other, self.__class__):
return other.value == self.value
elif isinstance(other, str):
if other in self.options:
return other == self.current_key
return other == self.value
elif isinstance(other, int):
assert other in self.name_lookup, f"compared against an int that could never be equal. {self} == {other}"
return other == self.value
elif isinstance(other, bool):
return other == bool(self.value)
else:
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
class BossMeta(AssembleOptions):
def __new__(mcs, name, bases, attrs):
if name != "PlandoBosses":
assert "bosses" in attrs, f"Please define valid bosses for {name}"
attrs["bosses"] = frozenset((boss.lower() for boss in attrs["bosses"]))
assert "locations" in attrs, f"Please define valid locations for {name}"
attrs["locations"] = frozenset((location.lower() for location in attrs["locations"]))
cls = super().__new__(mcs, name, bases, attrs)
assert not cls.duplicate_bosses or "singularity" in cls.options, f"Please define option_singularity for {name}"
return cls
class PlandoBosses(TextChoice, metaclass=BossMeta):
"""Generic boss shuffle option that supports plando. Format expected is
'location1-boss1;location2-boss2;shuffle_mode'.
If shuffle_mode is not provided in the string, this will be the default shuffle mode. Must override can_place_boss,
which passes a plando boss and location. Check if the placement is valid for your game here."""
bosses: typing.ClassVar[typing.Union[typing.Set[str], typing.FrozenSet[str]]]
locations: typing.ClassVar[typing.Union[typing.Set[str], typing.FrozenSet[str]]]
duplicate_bosses: bool = False
@classmethod
def from_text(cls, text: str):
# set all of our text to lower case for name checking
text = text.lower()
if text == "random":
return cls(random.choice(list(cls.options.values())))
for option_name, value in cls.options.items():
if option_name == text:
return cls(value)
options = text.split(";")
# since plando exists in the option verify the plando values given are valid
cls.validate_plando_bosses(options)
return cls.get_shuffle_mode(options)
@classmethod
def get_shuffle_mode(cls, option_list: typing.List[str]):
# find out what mode of boss shuffle we should use for placing bosses after plando
# and add as a string to look nice in the spoiler
if "random" in option_list:
shuffle = random.choice(list(cls.options))
option_list.remove("random")
options = ";".join(option_list) + f";{shuffle}"
boss_class = cls(options)
else:
for option in option_list:
if option in cls.options:
options = ";".join(option_list)
break
else:
if cls.duplicate_bosses and len(option_list) == 1:
if cls.valid_boss_name(option_list[0]):
# this doesn't exist in this class but it's a forced option for classes where this is called
options = option_list[0] + ";singularity"
else:
options = option_list[0] + f";{cls.name_lookup[cls.default]}"
else:
options = ";".join(option_list) + f";{cls.name_lookup[cls.default]}"
boss_class = cls(options)
return boss_class
@classmethod
def validate_plando_bosses(cls, options: typing.List[str]) -> None:
used_locations = []
used_bosses = []
for option in options:
# check if a shuffle mode was provided in the incorrect location
if option == "random" or option in cls.options:
if option != options[-1]:
raise ValueError(f"{option} option must be at the end of the boss_shuffle options!")
elif "-" in option:
location, boss = option.split("-")
if location in used_locations:
raise ValueError(f"Duplicate Boss Location {location} not allowed.")
if not cls.duplicate_bosses and boss in used_bosses:
raise ValueError(f"Duplicate Boss {boss} not allowed.")
used_locations.append(location)
used_bosses.append(boss)
if not cls.valid_boss_name(boss):
raise ValueError(f"{boss.title()} is not a valid boss name.")
if not cls.valid_location_name(location):
raise ValueError(f"{location.title()} is not a valid boss location name.")
if not cls.can_place_boss(boss, location):
raise ValueError(f"{location.title()} is not a valid location for {boss.title()} to be placed.")
else:
if cls.duplicate_bosses:
if not cls.valid_boss_name(option):
raise ValueError(f"{option} is not a valid boss name.")
else:
raise ValueError(f"{option.title()} is not formatted correctly.")
@classmethod
def can_place_boss(cls, boss: str, location: str) -> bool:
raise NotImplementedError
@classmethod
def valid_boss_name(cls, value: str) -> bool:
return value in cls.bosses
@classmethod
def valid_location_name(cls, value: str) -> bool:
return value in cls.locations
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
if isinstance(self.value, int):
return
from BaseClasses import PlandoOptions
if not (PlandoOptions.bosses & plando_options):
# plando is disabled but plando options were given so pull the option and change it to an int
option = self.value.split(";")[-1]
self.value = self.options[option]
logging.warning(f"The plando bosses module is turned off, so {self.name_lookup[self.value].title()} "
f"boss shuffle will be used for player {player_name}.")
class Range(NumericOption):
range_start = 0
range_end = 1
@@ -379,37 +648,9 @@ class Range(NumericOption):
def from_text(cls, text: str) -> Range:
text = text.lower()
if text.startswith("random"):
if text == "random-low":
return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_start), 0)))
elif text == "random-high":
return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_end), 0)))
elif text == "random-middle":
return cls(int(round(random.triangular(cls.range_start, cls.range_end), 0)))
elif text.startswith("random-range-"):
textsplit = text.split("-")
try:
random_range = [int(textsplit[len(textsplit) - 2]), int(textsplit[len(textsplit) - 1])]
except ValueError:
raise ValueError(f"Invalid random range {text} for option {cls.__name__}")
random_range.sort()
if random_range[0] < cls.range_start or random_range[1] > cls.range_end:
raise Exception(
f"{random_range[0]}-{random_range[1]} is outside allowed range "
f"{cls.range_start}-{cls.range_end} for option {cls.__name__}")
if text.startswith("random-range-low"):
return cls(int(round(random.triangular(random_range[0], random_range[1], random_range[0]))))
elif text.startswith("random-range-middle"):
return cls(int(round(random.triangular(random_range[0], random_range[1]))))
elif text.startswith("random-range-high"):
return cls(int(round(random.triangular(random_range[0], random_range[1], random_range[1]))))
else:
return cls(int(round(random.randint(random_range[0], random_range[1]))))
elif text == "random":
return cls(random.randint(cls.range_start, cls.range_end))
else:
raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. Acceptable values are: random, random-high, random-middle, random-low, random-range-low-<min>-<max>, random-range-middle-<min>-<max>, random-range-high-<min>-<max>, or random-range-<min>-<max>.")
return cls.weighted_range(text)
elif text == "default" and hasattr(cls, "default"):
return cls(cls.default)
return cls.from_any(cls.default)
elif text == "high":
return cls(cls.range_end)
elif text == "low":
@@ -420,11 +661,50 @@ class Range(NumericOption):
and text in ("true", "false"):
# these are the conditions where "true" and "false" make sense
if text == "true":
return cls(cls.default)
return cls.from_any(cls.default)
else: # "false"
return cls(0)
return cls(int(text))
@classmethod
def weighted_range(cls, text) -> Range:
if text == "random-low":
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_start))
elif text == "random-high":
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_end))
elif text == "random-middle":
return cls(cls.triangular(cls.range_start, cls.range_end))
elif text.startswith("random-range-"):
return cls.custom_range(text)
elif text == "random":
return cls(random.randint(cls.range_start, cls.range_end))
else:
raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. "
f"Acceptable values are: random, random-high, random-middle, random-low, "
f"random-range-low-<min>-<max>, random-range-middle-<min>-<max>, "
f"random-range-high-<min>-<max>, or random-range-<min>-<max>.")
@classmethod
def custom_range(cls, text) -> Range:
textsplit = text.split("-")
try:
random_range = [int(textsplit[len(textsplit) - 2]), int(textsplit[len(textsplit) - 1])]
except ValueError:
raise ValueError(f"Invalid random range {text} for option {cls.__name__}")
random_range.sort()
if random_range[0] < cls.range_start or random_range[1] > cls.range_end:
raise Exception(
f"{random_range[0]}-{random_range[1]} is outside allowed range "
f"{cls.range_start}-{cls.range_end} for option {cls.__name__}")
if text.startswith("random-range-low"):
return cls(cls.triangular(random_range[0], random_range[1], random_range[0]))
elif text.startswith("random-range-middle"):
return cls(cls.triangular(random_range[0], random_range[1]))
elif text.startswith("random-range-high"):
return cls(cls.triangular(random_range[0], random_range[1], random_range[1]))
else:
return cls(random.randint(random_range[0], random_range[1]))
@classmethod
def from_any(cls, data: typing.Any) -> Range:
if type(data) == int:
@@ -438,9 +718,43 @@ class Range(NumericOption):
def __str__(self) -> str:
return str(self.value)
@staticmethod
def triangular(lower: int, end: int, tri: typing.Optional[int] = None) -> int:
return int(round(random.triangular(lower, end, tri), 0))
class VerifyKeys:
valid_keys = frozenset()
class NamedRange(Range):
special_range_names: typing.Dict[str, int] = {}
"""Special Range names have to be all lowercase as matching is done with text.lower()"""
def __init__(self, value: int) -> None:
if value < self.range_start and value not in self.special_range_names.values():
raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__} " +
f"and is also not one of the supported named special values: {self.special_range_names}")
elif value > self.range_end and value not in self.special_range_names.values():
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__} " +
f"and is also not one of the supported named special values: {self.special_range_names}")
self.value = value
@classmethod
def from_text(cls, text: str) -> Range:
text = text.lower()
if text in cls.special_range_names:
return cls(cls.special_range_names[text])
return super().from_text(text)
class FreezeValidKeys(AssembleOptions):
def __new__(mcs, name, bases, attrs):
assert not "_valid_keys" in attrs, "'_valid_keys' gets set by FreezeValidKeys, define 'valid_keys' instead."
if "valid_keys" in attrs:
attrs["_valid_keys"] = frozenset(attrs["valid_keys"])
return super(FreezeValidKeys, mcs).__new__(mcs, name, bases, attrs)
class VerifyKeys(metaclass=FreezeValidKeys):
valid_keys: typing.Iterable = []
_valid_keys: frozenset # gets created by AssembleOptions from valid_keys
valid_keys_casefold: bool = False
convert_name_groups: bool = False
verify_item_name: bool = False
@@ -448,21 +762,26 @@ class VerifyKeys:
value: typing.Any
@classmethod
def verify_keys(cls, data):
def verify_keys(cls, data: typing.Iterable[str]) -> None:
if cls.valid_keys:
data = set(data)
dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data)
extra = dataset - cls.valid_keys
extra = dataset - cls._valid_keys
if extra:
raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. "
f"Allowed keys: {cls.valid_keys}.")
f"Allowed keys: {cls._valid_keys}.")
def verify(self, world):
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
if self.convert_name_groups and self.verify_item_name:
new_value = type(self.value)() # empty container of whatever value is
for item_name in self.value:
new_value |= world.item_name_groups.get(item_name, {item_name})
self.value = new_value
elif self.convert_name_groups and self.verify_location_name:
new_value = type(self.value)()
for loc_name in self.value:
new_value |= world.location_name_groups.get(loc_name, {loc_name})
self.value = new_value
if self.verify_item_name:
for item_name in self.value:
if item_name not in world.item_names:
@@ -479,12 +798,12 @@ class VerifyKeys:
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)")
class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys):
class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mapping[str, typing.Any]):
default = {}
supports_weighting = False
def __init__(self, value: typing.Dict[str, typing.Any]):
self.value = value
self.value = deepcopy(value)
@classmethod
def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict:
@@ -497,8 +816,14 @@ class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys):
def get_option_name(self, value):
return ", ".join(f"{key}: {v}" for key, v in value.items())
def __contains__(self, item):
return item in self.value
def __getitem__(self, item: str) -> typing.Any:
return self.value.__getitem__(item)
def __iter__(self) -> typing.Iterator[str]:
return self.value.__iter__()
def __len__(self) -> int:
return self.value.__len__()
class ItemDict(OptionDict):
@@ -511,11 +836,15 @@ class ItemDict(OptionDict):
class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
default = []
# Supports duplicate entries and ordering.
# If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead.
# Not a docstring so it doesn't get grabbed by the options system.
default = ()
supports_weighting = False
def __init__(self, value: typing.List[typing.Any]):
self.value = value or []
def __init__(self, value: typing.Iterable[typing.Any]):
self.value = list(deepcopy(value))
super(OptionList, self).__init__()
@classmethod
@@ -524,7 +853,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
@classmethod
def from_any(cls, data: typing.Any):
if type(data) == list:
if is_iterable_except_str(data):
cls.verify_keys(data)
return cls(data)
return cls.from_text(str(data))
@@ -540,8 +869,8 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys):
default = frozenset()
supports_weighting = False
def __init__(self, value: typing.Union[typing.Set[str, typing.Any], typing.List[str, typing.Any]]):
self.value = set(value)
def __init__(self, value: typing.Iterable[str]):
self.value = set(deepcopy(value))
super(OptionSet, self).__init__()
@classmethod
@@ -550,10 +879,7 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys):
@classmethod
def from_any(cls, data: typing.Any):
if type(data) == list:
cls.verify_keys(data)
return cls(data)
elif type(data) == set:
if is_iterable_except_str(data):
cls.verify_keys(data)
return cls(data)
return cls.from_text(str(data))
@@ -565,7 +891,9 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys):
return item in self.value
local_objective = Toggle # local triforce pieces, local dungeon prizes etc.
class ItemSet(OptionSet):
verify_item_name = True
convert_name_groups = True
class Accessibility(Choice):
@@ -581,24 +909,72 @@ class Accessibility(Choice):
default = 1
class ProgressionBalancing(Range):
class ProgressionBalancing(NamedRange):
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
[0-99, default 50] A lower setting means more getting stuck. A higher setting means less getting stuck."""
A lower setting means more getting stuck. A higher setting means less getting stuck."""
default = 50
range_start = 0
range_end = 99
display_name = "Progression Balancing"
special_range_names = {
"disabled": 0,
"normal": 50,
"extreme": 99,
}
common_options = {
"progression_balancing": ProgressionBalancing,
"accessibility": Accessibility
}
class OptionsMetaProperty(type):
def __new__(mcs,
name: str,
bases: typing.Tuple[type, ...],
attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty":
for attr_type in attrs.values():
assert not isinstance(attr_type, AssembleOptions), \
f"Options for {name} should be type hinted on the class, not assigned"
return super().__new__(mcs, name, bases, attrs)
@property
@functools.lru_cache(maxsize=None)
def type_hints(cls) -> typing.Dict[str, typing.Type[Option[typing.Any]]]:
"""Returns type hints of the class as a dictionary."""
return typing.get_type_hints(cls)
class ItemSet(OptionSet):
verify_item_name = True
convert_name_groups = True
@dataclass
class CommonOptions(metaclass=OptionsMetaProperty):
progression_balancing: ProgressionBalancing
accessibility: Accessibility
def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, typing.Any]:
"""
Returns a dictionary of [str, Option.value]
:param option_names: names of the options to return
:param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab`
"""
option_results = {}
for option_name in option_names:
if option_name in type(self).type_hints:
if casing == "snake":
display_name = option_name
elif casing == "camel":
split_name = [name.title() for name in option_name.split("_")]
split_name[0] = split_name[0].lower()
display_name = "".join(split_name)
elif casing == "pascal":
display_name = "".join([name.title() for name in option_name.split("_")])
elif casing == "kebab":
display_name = option_name.replace("_", "-")
else:
raise ValueError(f"{casing} is invalid casing for as_dict. "
"Valid names are 'snake', 'camel', 'pascal', 'kebab'.")
value = getattr(self, option_name).value
if isinstance(value, set):
value = sorted(value)
option_results[display_name] = value
else:
raise ValueError(f"{option_name} not found in {tuple(type(self).type_hints)}")
return option_results
class LocalItems(ItemSet):
@@ -617,27 +993,36 @@ class StartInventory(ItemDict):
display_name = "Start Inventory"
class StartInventoryPool(StartInventory):
"""Start with these items and don't place them in the world.
The game decides what the replacement items will be."""
verify_item_name = True
display_name = "Start Inventory from Pool"
class StartHints(ItemSet):
"""Start with these item's locations prefilled into the !hint command."""
display_name = "Start Hints"
class StartLocationHints(OptionSet):
class LocationSet(OptionSet):
verify_location_name = True
convert_name_groups = True
class StartLocationHints(LocationSet):
"""Start with these locations and their item prefilled into the !hint command"""
display_name = "Start Location Hints"
verify_location_name = True
class ExcludeLocations(OptionSet):
class ExcludeLocations(LocationSet):
"""Prevent these locations from having an important item"""
display_name = "Excluded Locations"
verify_location_name = True
class PriorityLocations(OptionSet):
class PriorityLocations(LocationSet):
"""Prevent these locations from having an unimportant item"""
display_name = "Priority Locations"
verify_location_name = True
class DeathLink(Toggle):
@@ -647,6 +1032,7 @@ class DeathLink(Toggle):
class ItemLinks(OptionList):
"""Share part of your item pool with other players."""
display_name = "Item Links"
default = []
schema = Schema([
{
@@ -655,7 +1041,8 @@ class ItemLinks(OptionList):
Optional("exclude"): [And(str, len)],
"replacement_item": Or(And(str, len), None),
Optional("local_items"): [And(str, len)],
Optional("non_local_items"): [And(str, len)]
Optional("non_local_items"): [And(str, len)],
Optional("link_replacement"): Or(None, bool),
}
])
@@ -677,8 +1064,9 @@ class ItemLinks(OptionList):
pool |= {item_name}
return pool
def verify(self, world):
super(ItemLinks, self).verify(world)
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
link: dict
super(ItemLinks, self).verify(world, player_name, plando_options)
existing_links = set()
for link in self.value:
if link["name"] in existing_links:
@@ -702,22 +1090,110 @@ class ItemLinks(OptionList):
intersection = local_items.intersection(non_local_items)
if intersection:
raise Exception(f"item_link {link['name']} has {intersection} items in both its local_items and non_local_items pool.")
raise Exception(f"item_link {link['name']} has {intersection} "
f"items in both its local_items and non_local_items pool.")
link.setdefault("link_replacement", None)
link["item_pool"] = list(pool)
class Removed(FreeText):
"""This Option has been Removed."""
default = ""
visibility = Visibility.none
def __init__(self, value: str):
if value:
raise Exception("Option removed, please update your options file.")
super().__init__(value)
per_game_common_options = {
**common_options, # can be overwritten per-game
"local_items": LocalItems,
"non_local_items": NonLocalItems,
"start_inventory": StartInventory,
"start_hints": StartHints,
"start_location_hints": StartLocationHints,
"exclude_locations": ExcludeLocations,
"priority_locations": PriorityLocations,
"item_links": ItemLinks
}
@dataclass
class PerGameCommonOptions(CommonOptions):
local_items: LocalItems
non_local_items: NonLocalItems
start_inventory: StartInventory
start_hints: StartHints
start_location_hints: StartLocationHints
exclude_locations: ExcludeLocations
priority_locations: PriorityLocations
item_links: ItemLinks
@dataclass
class DeathLinkMixin:
death_link: DeathLink
class OptionGroup(typing.NamedTuple):
"""Define a grouping of options."""
name: str
"""Name of the group to categorize these options in for display on the WebHost and in generated YAMLS."""
options: typing.List[typing.Type[Option[typing.Any]]]
"""Options to be in the defined group."""
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True):
import os
import yaml
from jinja2 import Template
from worlds import AutoWorldRegister
from Utils import local_path, __version__
full_path: str
os.makedirs(target_folder, exist_ok=True)
# clean out old
for file in os.listdir(target_folder):
full_path = os.path.join(target_folder, file)
if os.path.isfile(full_path) and full_path.endswith(".yaml"):
os.unlink(full_path)
def dictify_range(option: Range):
data = {option.default: 50}
for sub_option in ["random", "random-low", "random-high"]:
if sub_option != option.default:
data[sub_option] = 0
notes = {}
for name, number in getattr(option, "special_range_names", {}).items():
notes[name] = f"equivalent to {number}"
if number in data:
data[name] = data[number]
del data[number]
else:
data[name] = 0
return data, notes
for game_name, world in AutoWorldRegister.world_types.items():
if not world.hidden or generate_hidden:
option_groups = {option: option_group.name
for option_group in world.web.option_groups
for option in option_group.options}
ordered_groups = ["Game Options"]
[ordered_groups.append(group) for group in option_groups.values() if group not in ordered_groups]
grouped_options = {group: {} for group in ordered_groups}
for option_name, option in world.options_dataclass.type_hints.items():
if option.visibility >= Visibility.template:
grouped_options[option_groups.get(option, "Game Options")][option_name] = option
with open(local_path("data", "options.yaml")) as f:
file_data = f.read()
res = Template(file_data).render(
option_groups=grouped_options,
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
dictify_range=dictify_range,
)
del file_data
with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
f.write(res)
if __name__ == "__main__":

411
Patch.py
View File

@@ -1,420 +1,35 @@
from __future__ import annotations
import shutil
import json
import bsdiff4
import yaml
import os
import lzma
import threading
import concurrent.futures
import zipfile
import sys
from typing import Tuple, Optional, Dict, Any, Union, BinaryIO
from typing import Tuple, Optional, TypedDict
import ModuleUpdate
ModuleUpdate.update()
if __name__ == "__main__":
import ModuleUpdate
ModuleUpdate.update()
import Utils
current_patch_version = 4
from worlds.Files import AutoPatchRegister, APAutoPatchInterface
class AutoPatchRegister(type):
patch_types: Dict[str, APDeltaPatch] = {}
file_endings: Dict[str, APDeltaPatch] = {}
def __new__(cls, name: str, bases, dct: Dict[str, Any]):
# construct class
new_class = super().__new__(cls, name, bases, dct)
if "game" in dct:
AutoPatchRegister.patch_types[dct["game"]] = new_class
if not dct["patch_file_ending"]:
raise Exception(f"Need an expected file ending for {name}")
AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class
return new_class
@staticmethod
def get_handler(file: str) -> Optional[type(APDeltaPatch)]:
for file_ending, handler in AutoPatchRegister.file_endings.items():
if file.endswith(file_ending):
return handler
class APContainer:
"""A zipfile containing at least archipelago.json"""
version: int = current_patch_version
compression_level: int = 9
compression_method: int = zipfile.ZIP_DEFLATED
game: Optional[str] = None
# instance attributes:
path: Optional[str]
class RomMeta(TypedDict):
server: str
player: Optional[int]
player_name: str
server: str
def __init__(self, path: Optional[str] = None, player: Optional[int] = None,
player_name: str = "", server: str = ""):
self.path = path
self.player = player
self.player_name = player_name
self.server = server
def write(self, file: Optional[Union[str, BinaryIO]] = None):
if not self.path and not file:
raise FileNotFoundError(f"Cannot write {self.__class__.__name__} due to no path provided.")
with zipfile.ZipFile(file if file else self.path, "w", self.compression_method, True, self.compression_level) \
as zf:
if file:
self.path = zf.filename
self.write_contents(zf)
def write_contents(self, opened_zipfile: zipfile.ZipFile):
manifest = self.get_manifest()
try:
manifest = json.dumps(manifest)
except Exception as e:
raise Exception(f"Manifest {manifest} did not convert to json.") from e
else:
opened_zipfile.writestr("archipelago.json", manifest)
def read(self, file: Optional[Union[str, BinaryIO]] = None):
"""Read data into patch object. file can be file-like, such as an outer zip file's stream."""
if not self.path and not file:
raise FileNotFoundError(f"Cannot read {self.__class__.__name__} due to no path provided.")
with zipfile.ZipFile(file if file else self.path, "r") as zf:
if file:
self.path = zf.filename
self.read_contents(zf)
def read_contents(self, opened_zipfile: zipfile.ZipFile):
with opened_zipfile.open("archipelago.json", "r") as f:
manifest = json.load(f)
if manifest["compatible_version"] > self.version:
raise Exception(f"File (version: {manifest['compatible_version']}) too new "
f"for this handler (version: {self.version})")
self.player = manifest["player"]
self.server = manifest["server"]
self.player_name = manifest["player_name"]
def get_manifest(self) -> dict:
return {
"server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise
"player": self.player,
"player_name": self.player_name,
"game": self.game,
# minimum version of patch system expected for patching to be successful
"compatible_version": 4,
"version": current_patch_version,
}
class APDeltaPatch(APContainer, metaclass=AutoPatchRegister):
"""An APContainer that additionally has delta.bsdiff4
containing a delta patch to get the desired file, often a rom."""
hash = Optional[str] # base checksum of source file
patch_file_ending: str = ""
delta: Optional[bytes] = None
result_file_ending: str = ".sfc"
source_data: bytes
def __init__(self, *args, patched_path: str = "", **kwargs):
self.patched_path = patched_path
super(APDeltaPatch, self).__init__(*args, **kwargs)
def get_manifest(self) -> dict:
manifest = super(APDeltaPatch, self).get_manifest()
manifest["base_checksum"] = self.hash
manifest["result_file_ending"] = self.result_file_ending
return manifest
@classmethod
def get_source_data(cls) -> bytes:
"""Get Base data"""
raise NotImplementedError()
@classmethod
def get_source_data_with_cache(cls) -> bytes:
if not hasattr(cls, "source_data"):
cls.source_data = cls.get_source_data()
return cls.source_data
def write_contents(self, opened_zipfile: zipfile.ZipFile):
super(APDeltaPatch, self).write_contents(opened_zipfile)
# write Delta
opened_zipfile.writestr("delta.bsdiff4",
bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()),
compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression
def read_contents(self, opened_zipfile: zipfile.ZipFile):
super(APDeltaPatch, self).read_contents(opened_zipfile)
self.delta = opened_zipfile.read("delta.bsdiff4")
def patch(self, target: str):
"""Base + Delta -> Patched"""
if not self.delta:
self.read()
result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta)
with open(target, "wb") as f:
f.write(result)
# legacy patch handling follows:
GAME_ALTTP = "A Link to the Past"
GAME_SM = "Super Metroid"
GAME_SOE = "Secret of Evermore"
GAME_SMZ3 = "SMZ3"
supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3"}
preferred_endings = {
GAME_ALTTP: "apbp",
GAME_SM: "apm3",
GAME_SOE: "apsoe",
GAME_SMZ3: "apsmz"
}
def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes:
if game == GAME_ALTTP:
from worlds.alttp.Rom import JAP10HASH as HASH
elif game == GAME_SM:
from worlds.sm.Rom import JAP10HASH as HASH
elif game == GAME_SOE:
from worlds.soe.Patch import USHASH as HASH
elif game == GAME_SMZ3:
from worlds.alttp.Rom import JAP10HASH as ALTTPHASH
from worlds.sm.Rom import JAP10HASH as SMHASH
HASH = ALTTPHASH + SMHASH
else:
raise RuntimeError(f"Selected game {game} for base rom not found.")
patch = yaml.dump({"meta": metadata,
"patch": patch,
"game": game,
# minimum version of patch system expected for patching to be successful
"compatible_version": 3,
"version": current_patch_version,
"base_checksum": HASH})
return patch.encode(encoding="utf-8-sig")
def generate_patch(rom: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes:
if metadata is None:
metadata = {}
patch = bsdiff4.diff(get_base_rom_data(game), rom)
return generate_yaml(patch, metadata, game)
def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str = None,
player: int = 0, player_name: str = "", game: str = GAME_ALTTP) -> str:
meta = {"server": server, # allow immediate connection to server in multiworld. Empty string otherwise
"player_id": player,
"player_name": player_name}
bytes = generate_patch(load_bytes(rom_file_to_patch),
meta,
game)
target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + (
".apbp" if game == GAME_ALTTP else ".apsmz" if game == GAME_SMZ3 else ".apm3")
write_lzma(bytes, target)
return target
def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[dict, str, bytearray]:
data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig"))
game_name = data["game"]
if not ignore_version and data["compatible_version"] > current_patch_version:
raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.")
patched_data = bsdiff4.patch(get_base_rom_data(game_name), data["patch"])
rom_hash = patched_data[int(0x7FC0):int(0x7FD5)]
data["meta"]["hash"] = "".join(chr(x) for x in rom_hash)
target = os.path.splitext(patch_file)[0] + ".sfc"
return data["meta"], target, patched_data
def get_base_rom_data(game: str):
if game == GAME_ALTTP:
from worlds.alttp.Rom import get_base_rom_bytes
elif game == "alttp": # old version for A Link to the Past
from worlds.alttp.Rom import get_base_rom_bytes
elif game == GAME_SM:
from worlds.sm.Rom import get_base_rom_bytes
elif game == GAME_SOE:
from worlds.soe.Patch import get_base_rom_path
get_base_rom_bytes = lambda: bytes(read_rom(open(get_base_rom_path(), "rb")))
elif game == GAME_SMZ3:
from worlds.smz3.Rom import get_base_rom_bytes
else:
raise RuntimeError("Selected game for base rom not found.")
return get_base_rom_bytes()
def create_rom_file(patch_file: str) -> Tuple[dict, str]:
def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]:
auto_handler = AutoPatchRegister.get_handler(patch_file)
if auto_handler:
handler: APDeltaPatch = auto_handler(patch_file)
handler: APAutoPatchInterface = auto_handler(patch_file)
target = os.path.splitext(patch_file)[0]+handler.result_file_ending
handler.patch(target)
return {"server": handler.server,
"player": handler.player,
"player_name": handler.player_name}, target
else:
data, target, patched_data = create_rom_bytes(patch_file)
with open(target, "wb") as f:
f.write(patched_data)
return data, target
def update_patch_data(patch_data: bytes, server: str = "") -> bytes:
data = Utils.parse_yaml(lzma.decompress(patch_data).decode("utf-8-sig"))
data["meta"]["server"] = server
bytes = generate_yaml(data["patch"], data["meta"], data["game"])
return lzma.compress(bytes)
def load_bytes(path: str) -> bytes:
with open(path, "rb") as f:
return f.read()
def write_lzma(data: bytes, path: str):
with lzma.LZMAFile(path, 'wb') as f:
f.write(data)
def read_rom(stream, strip_header=True) -> bytearray:
"""Reads rom into bytearray and optionally strips off any smc header"""
buffer = bytearray(stream.read())
if strip_header and len(buffer) % 0x400 == 0x200:
return buffer[0x200:]
return buffer
raise NotImplementedError(f"No Handler for {patch_file} found.")
if __name__ == "__main__":
host = Utils.get_public_ipv4()
options = Utils.get_options()['server_options']
if options['host']:
host = options['host']
address = f"{host}:{options['port']}"
ziplock = threading.Lock()
print(f"Host for patches to be created is {address}")
with concurrent.futures.ThreadPoolExecutor() as pool:
for rom in sys.argv:
try:
if rom.endswith(".sfc"):
print(f"Creating patch for {rom}")
result = pool.submit(create_patch_file, rom, address)
result.add_done_callback(lambda task: print(f"Created patch {task.result()}"))
elif rom.endswith(".apbp"):
print(f"Applying patch {rom}")
data, target = create_rom_file(rom)
#romfile, adjusted = Utils.get_adjuster_settings(target)
adjuster_settings = Utils.get_adjuster_settings(GAME_ALTTP)
adjusted = False
if adjuster_settings:
import pprint
from worlds.alttp.Rom import get_base_rom_path
adjuster_settings.rom = target
adjuster_settings.baserom = get_base_rom_path()
adjuster_settings.world = None
whitelist = {"music", "menuspeed", "heartbeep", "heartcolor", "ow_palettes", "quickswap",
"uw_palettes", "sprite", "sword_palettes", "shield_palettes", "hud_palettes",
"reduceflashing", "deathlink"}
printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist}
if hasattr(adjuster_settings, "sprite_pool"):
sprite_pool = {}
for sprite in getattr(adjuster_settings, "sprite_pool"):
if sprite in sprite_pool:
sprite_pool[sprite] += 1
else:
sprite_pool[sprite] = 1
if sprite_pool:
printed_options["sprite_pool"] = sprite_pool
adjust_wanted = str('no')
if not hasattr(adjuster_settings, 'auto_apply') or 'ask' in adjuster_settings.auto_apply:
adjust_wanted = input(f"Last used adjuster settings were found. Would you like to apply these? \n"
f"{pprint.pformat(printed_options)}\n"
f"Enter yes, no, always or never: ")
if adjuster_settings.auto_apply == 'never': # never adjust, per user request
adjust_wanted = 'no'
elif adjuster_settings.auto_apply == 'always':
adjust_wanted = 'yes'
if adjust_wanted and "never" in adjust_wanted:
adjuster_settings.auto_apply = 'never'
Utils.persistent_store("adjuster", GAME_ALTTP, adjuster_settings)
elif adjust_wanted and "always" in adjust_wanted:
adjuster_settings.auto_apply = 'always'
Utils.persistent_store("adjuster", GAME_ALTTP, adjuster_settings)
if adjust_wanted and adjust_wanted.startswith("y"):
if hasattr(adjuster_settings, "sprite_pool"):
from LttPAdjuster import AdjusterWorld
adjuster_settings.world = AdjusterWorld(getattr(adjuster_settings, "sprite_pool"))
adjusted = True
import LttPAdjuster
_, romfile = LttPAdjuster.adjust(adjuster_settings)
if hasattr(adjuster_settings, "world"):
delattr(adjuster_settings, "world")
else:
adjusted = False
if adjusted:
try:
shutil.move(romfile, target)
romfile = target
except Exception as e:
print(e)
print(f"Created rom {romfile if adjusted else target}.")
if 'server' in data:
Utils.persistent_store("servers", data['hash'], data['server'])
print(f"Host is {data['server']}")
elif rom.endswith(".apm3"):
print(f"Applying patch {rom}")
data, target = create_rom_file(rom)
print(f"Created rom {target}.")
if 'server' in data:
Utils.persistent_store("servers", data['hash'], data['server'])
print(f"Host is {data['server']}")
elif rom.endswith(".apsmz"):
print(f"Applying patch {rom}")
data, target = create_rom_file(rom)
print(f"Created rom {target}.")
if 'server' in data:
Utils.persistent_store("servers", data['hash'], data['server'])
print(f"Host is {data['server']}")
elif rom.endswith(".zip"):
print(f"Updating host in patch files contained in {rom}")
def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str):
data = zfr.read(zfinfo)
if zfinfo.filename.endswith(".apbp") or zfinfo.filename.endswith(".apm3"):
data = update_patch_data(data, server)
with ziplock:
zfw.writestr(zfinfo, data)
return zfinfo.filename
futures = []
with zipfile.ZipFile(rom, "r") as zfr:
updated_zip = os.path.splitext(rom)[0] + "_updated.zip"
with zipfile.ZipFile(updated_zip, "w", compression=zipfile.ZIP_DEFLATED,
compresslevel=9) as zfw:
for zfname in zfr.namelist():
futures.append(pool.submit(_handle_zip_file_entry, zfr.getinfo(zfname), address))
for future in futures:
print(f"File {future.result()} added to {os.path.split(updated_zip)[1]}")
except:
import traceback
traceback.print_exc()
input("Press enter to close.")
for file in sys.argv[1:]:
meta_data, result_file = create_rom_file(file)
print(f"Patch with meta-data {meta_data} was written to {result_file}")

View File

@@ -25,7 +25,51 @@ Currently, the following games are supported:
* Hollow Knight
* The Witness
* Sonic Adventure 2: Battle
* Starcraft 2: Wings of Liberty
* Starcraft 2
* Donkey Kong Country 3
* Dark Souls 3
* Super Mario World
* Pokémon Red and Blue
* Hylics 2
* Overcooked! 2
* Zillion
* Lufia II Ancient Cave
* Blasphemous
* Wargroove
* Stardew Valley
* The Legend of Zelda
* The Messenger
* Kingdom Hearts 2
* The Legend of Zelda: Link's Awakening DX
* Clique
* Adventure
* DLC Quest
* Noita
* Undertale
* Bumper Stickers
* Mega Man Battle Network 3: Blue Version
* Muse Dash
* DOOM 1993
* Terraria
* Lingo
* Pokémon Emerald
* DOOM II
* Shivers
* Heretic
* Landstalker: The Treasures of King Nole
* Final Fantasy Mystic Quest
* TUNIC
* Kirby's Dream Land 3
* Celeste 64
* Zork Grand Inquisitor
* Castlevania 64
* A Short Hike
* Yoshi's Island
* Mario & Luigi: Superstar Saga
* Bomb Rush Cyberfunk
* Aquaria
* Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
* A Hat in Time
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
@@ -47,9 +91,9 @@ We recognize that there is a strong community of incredibly smart people that ha
Archipelago was directly forked from bonta0's `multiworld_31` branch of ALttPEntranceRandomizer (this project has a long legacy of its own, please check it out linked above) on January 12, 2020. The repository was then named to _MultiWorld-Utilities_ to better encompass its intended function. As Archipelago matured, then known as "Berserker's MultiWorld" by some, we found it necessary to transform our repository into a root level repository (as opposed to a 'forked repo') and change the name (which came later) to better reflect our project.
## Running Archipelago
For most people all you need to do is head over to the [releases](https://github.com/ArchipelagoMW/Archipelago/releases) page then download and run the appropriate installer. The installers function on Windows only.
For most people, all you need to do is head over to the [releases](https://github.com/ArchipelagoMW/Archipelago/releases) page then download and run the appropriate installer, or AppImage for Linux-based systems.
If you are running Archipelago from a non-Windows system then the likely scenario is that you are comfortable running source code directly. Please see our wiki page on [running Archipelago from source](https://github.com/ArchipelagoMW/Archipelago/wiki/Running-from-source).
If you are a developer or are running on a platform with no compiled releases available, please see our doc on [running Archipelago from source](docs/running%20from%20source.md).
## Related Repositories
This project makes use of multiple other projects. We wouldn't be here without these other repositories and the contributions of their developers, past and present.
@@ -59,23 +103,10 @@ This project makes use of multiple other projects. We wouldn't be here without t
* [Ocarina of Time Randomizer](https://github.com/TestRunnerSRL/OoT-Randomizer)
## Contributing
Contributions are welcome. We have a few asks of any new contributors.
For contribution guidelines, please see our [Contributing doc.](/docs/contributing.md)
* Ensure that all changes which affect logic are covered by unit tests.
* Do not introduce any unit test failures/regressions.
Otherwise, we tend to judge code on a case to case basis. It is a generally good idea to stick to PEP-8 guidelines to ensure consistency with existing code. (And to make the linter happy.)
For adding a new game to Archipelago please see the docs folder for the relevant information and feel free to ask any questions in the #archipelago-dev channel in our discord.
## FAQ
For Frequently asked questions, please see the website's [FAQ Page.](https://archipelago.gg/faq/en/)
## Code of Conduct
We conduct ourselves openly and inclusively here. Please do not contribute to an environment which makes other people uncomfortable. This means that we expect all contributors or participants here to:
* Be welcoming and inclusive in tone and language.
* Be respectful of others and their abilities.
* Show empathy when speaking with others.
* Be gracious and accept feedback and constructive criticism.
These guidelines apply to all channels of communication within this GitHub repository. Please be respectful in both public channels, such as issues, and private, such as private messaging or emails.
Any incidents of abuse may be reported directly to Ijwu at hmfarran@gmail.com.
Please refer to our [code of conduct.](/docs/code_of_conduct.md)

File diff suppressed because it is too large Load Diff

View File

@@ -1,646 +1,11 @@
from __future__ import annotations
import multiprocessing
import logging
import asyncio
import nest_asyncio
import ModuleUpdate
ModuleUpdate.update()
import sc2
from sc2.main import run_game
from sc2.data import Race
from sc2.bot_ai import BotAI
from sc2.player import Bot
from worlds.sc2wol.Regions import MissionInfo
from worlds.sc2wol.MissionTables import lookup_id_to_mission
from worlds.sc2wol.Items import lookup_id_to_name, item_table
from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET
from Utils import init_logging
from worlds.sc2.Client import launch
import Utils
if __name__ == "__main__":
init_logging("SC2Client", exception_logger="Client")
logger = logging.getLogger("Client")
sc2_logger = logging.getLogger("Starcraft2")
import colorama
from NetUtils import *
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser
nest_asyncio.apply()
class StarcraftClientProcessor(ClientCommandProcessor):
ctx: Context
missions_unlocked = False
def _cmd_disable_mission_check(self) -> bool:
"""Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play
the next mission in a chain the other player is doing."""
self.missions_unlocked = True
sc2_logger.info("Mission check has been disabled")
def _cmd_play(self, mission_id: str = "") -> bool:
"""Start a Starcraft 2 mission"""
options = mission_id.split()
num_options = len(options)
if num_options > 0:
mission_number = int(options[0])
if self.missions_unlocked or \
is_mission_available(mission_number, self.ctx.checked_locations, self.ctx.mission_req_table):
if self.ctx.sc2_run_task:
if not self.ctx.sc2_run_task.done():
sc2_logger.warning("Starcraft 2 Client is still running!")
self.ctx.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task
if self.ctx.slot is None:
sc2_logger.warning("Launching Mission without Archipelago authentication, "
"checks will not be registered to server.")
self.ctx.sc2_run_task = asyncio.create_task(starcraft_launch(self.ctx, mission_number),
name="Starcraft 2 Launch")
else:
sc2_logger.info(
"This mission is not currently unlocked. Use /unfinished or /available to see what is available.")
else:
sc2_logger.info(
"Mission ID needs to be specified. Use /unfinished or /available to view ids for available missions.")
return True
def _cmd_available(self) -> bool:
"""Get what missions are currently available to play"""
request_available_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui)
return True
def _cmd_unfinished(self) -> bool:
"""Get what missions are currently available to play and have not had all locations checked"""
request_unfinished_missions(self.ctx.checked_locations, self.ctx.mission_req_table, self.ctx.ui, self.ctx)
return True
class Context(CommonContext):
command_processor = StarcraftClientProcessor
game = "Starcraft 2 Wings of Liberty"
items_handling = 0b111
difficulty = -1
all_in_choice = 0
mission_req_table = None
items_rec_to_announce = []
rec_announce_pos = 0
items_sent_to_announce = []
sent_announce_pos = 0
announcements = []
announcement_pos = 0
sc2_run_task: typing.Optional[asyncio.Task] = None
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(Context, self).server_auth(password_requested)
if not self.auth:
logger.info('Enter slot name:')
self.auth = await self.console_input()
await self.send_connect()
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
self.difficulty = args["slot_data"]["game_difficulty"]
self.all_in_choice = args["slot_data"]["all_in_map"]
slot_req_table = args["slot_data"]["mission_req"]
self.mission_req_table = {}
for mission in slot_req_table:
self.mission_req_table[mission] = MissionInfo(**slot_req_table[mission])
if cmd in {"PrintJSON"}:
noted = False
if "receiving" in args:
if args["receiving"] == self.slot:
self.announcements.append(args["data"])
noted = True
if not noted and "item" in args:
if args["item"].player == self.slot:
self.announcements.append(args["data"])
def run_gui(self):
from kvui import GameManager
class SC2Manager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("Starcraft2", "Starcraft2"),
]
base_title = "Archipelago Starcraft 2 Client"
self.ui = SC2Manager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def shutdown(self):
await super(Context, self).shutdown()
if self.sc2_run_task:
self.sc2_run_task.cancel()
async def main():
multiprocessing.freeze_support()
parser = get_base_parser()
parser.add_argument('--name', default=None, help="Slot Name to connect as.")
args = parser.parse_args()
ctx = Context(args.connect, args.password)
ctx.auth = args.name
if ctx.server_task is None:
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
await ctx.exit_event.wait()
await ctx.shutdown()
maps_table = [
"ap_traynor01", "ap_traynor02", "ap_traynor03",
"ap_thanson01", "ap_thanson02", "ap_thanson03a", "ap_thanson03b",
"ap_ttychus01", "ap_ttychus02", "ap_ttychus03", "ap_ttychus04", "ap_ttychus05",
"ap_ttosh01", "ap_ttosh02", "ap_ttosh03a", "ap_ttosh03b",
"ap_thorner01", "ap_thorner02", "ap_thorner03", "ap_thorner04", "ap_thorner05s",
"ap_tzeratul01", "ap_tzeratul02", "ap_tzeratul03", "ap_tzeratul04",
"ap_tvalerian01", "ap_tvalerian02a", "ap_tvalerian02b", "ap_tvalerian03"
]
def calculate_items(items):
unit_unlocks = 0
armory1_unlocks = 0
armory2_unlocks = 0
upgrade_unlocks = 0
building_unlocks = 0
merc_unlocks = 0
lab_unlocks = 0
protoss_unlock = 0
minerals = 0
vespene = 0
for item in items:
data = lookup_id_to_name[item.item]
if item_table[data].type == "Unit":
unit_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Upgrade":
upgrade_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Armory 1":
armory1_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Armory 2":
armory2_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Building":
building_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Mercenary":
merc_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Laboratory":
lab_unlocks += (1 << item_table[data].number)
elif item_table[data].type == "Protoss":
protoss_unlock += (1 << item_table[data].number)
elif item_table[data].type == "Minerals":
minerals += item_table[data].number
elif item_table[data].type == "Vespene":
vespene += item_table[data].number
return [unit_unlocks, upgrade_unlocks, armory1_unlocks, armory2_unlocks, building_unlocks, merc_unlocks,
lab_unlocks, protoss_unlock, minerals, vespene]
def calc_difficulty(difficulty):
if difficulty == 0:
return 'C'
elif difficulty == 1:
return 'N'
elif difficulty == 2:
return 'H'
elif difficulty == 3:
return 'B'
return 'X'
async def starcraft_launch(ctx: Context, mission_id):
ctx.rec_announce_pos = len(ctx.items_rec_to_announce)
ctx.sent_announce_pos = len(ctx.items_sent_to_announce)
ctx.announcements_pos = len(ctx.announcements)
sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.")
run_game(sc2.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id),
name="Archipelago", fullscreen=True)], realtime=True)
class ArchipelagoBot(sc2.bot_ai.BotAI):
game_running = False
mission_completed = False
first_bonus = False
second_bonus = False
third_bonus = False
fourth_bonus = False
fifth_bonus = False
sixth_bonus = False
seventh_bonus = False
eight_bonus = False
ctx: Context = None
mission_id = 0
can_read_game = False
last_received_update = 0
def __init__(self, ctx: Context, mission_id):
self.ctx = ctx
self.mission_id = mission_id
super(ArchipelagoBot, self).__init__()
async def on_step(self, iteration: int):
game_state = 0
if iteration == 0:
start_items = calculate_items(self.ctx.items_received)
difficulty = calc_difficulty(self.ctx.difficulty)
await self.chat_send("ArchipelagoLoad {} {} {} {} {} {} {} {} {} {} {} {}".format(
difficulty,
start_items[0], start_items[1], start_items[2], start_items[3], start_items[4],
start_items[5], start_items[6], start_items[7], start_items[8], start_items[9],
self.ctx.all_in_choice))
self.last_received_update = len(self.ctx.items_received)
else:
if self.ctx.announcement_pos < len(self.ctx.announcements):
index = 0
message = ""
while index < len(self.ctx.announcements[self.ctx.announcement_pos]):
message += self.ctx.announcements[self.ctx.announcement_pos][index]["text"]
index += 1
index = 0
start_rem_pos = -1
# Remove unneeded [Color] tags
while index < len(message):
if message[index] == '[':
start_rem_pos = index
index += 1
elif message[index] == ']' and start_rem_pos > -1:
temp_msg = ""
if start_rem_pos > 0:
temp_msg = message[:start_rem_pos]
if index < len(message) - 1:
temp_msg += message[index + 1:]
message = temp_msg
index += start_rem_pos - index
start_rem_pos = -1
else:
index += 1
await self.chat_send("SendMessage " + message)
self.ctx.announcement_pos += 1
# Archipelago reads the health
for unit in self.all_own_units():
if unit.health_max == 38281:
game_state = int(38281 - unit.health)
self.can_read_game = True
if iteration == 160 and not game_state & 1:
await self.chat_send("SendMessage Warning: Archipelago unable to connect or has lost connection to " +
"Starcraft 2 (This is likely a map issue)")
if self.last_received_update < len(self.ctx.items_received):
current_items = calculate_items(self.ctx.items_received)
await self.chat_send("UpdateTech {} {} {} {} {} {} {} {}".format(
current_items[0], current_items[1], current_items[2], current_items[3], current_items[4],
current_items[5], current_items[6], current_items[7]))
self.last_received_update = len(self.ctx.items_received)
if game_state & 1:
if not self.game_running:
print("Archipelago Connected")
self.game_running = True
if self.can_read_game:
if game_state & (1 << 1) and not self.mission_completed:
if self.mission_id != 29:
print("Mission Completed")
await self.ctx.send_msgs([
{"cmd": 'LocationChecks', "locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id]}])
self.mission_completed = True
else:
print("Game Complete")
await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}])
self.mission_completed = True
if game_state & (1 << 2) and not self.first_bonus:
print("1st Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 1]}])
self.first_bonus = True
if not self.second_bonus and game_state & (1 << 3):
print("2nd Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 2]}])
self.second_bonus = True
if not self.third_bonus and game_state & (1 << 4):
print("3rd Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 3]}])
self.third_bonus = True
if not self.fourth_bonus and game_state & (1 << 5):
print("4th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 4]}])
self.fourth_bonus = True
if not self.fifth_bonus and game_state & (1 << 6):
print("5th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 5]}])
self.fifth_bonus = True
if not self.sixth_bonus and game_state & (1 << 7):
print("6th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 6]}])
self.sixth_bonus = True
if not self.seventh_bonus and game_state & (1 << 8):
print("6th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 7]}])
self.seventh_bonus = True
if not self.eight_bonus and game_state & (1 << 9):
print("6th Bonus Collected")
await self.ctx.send_msgs(
[{"cmd": 'LocationChecks',
"locations": [SC2WOL_LOC_ID_OFFSET + 100 * self.mission_id + 8]}])
self.eight_bonus = True
else:
await self.chat_send("LostConnection - Lost connection to game.")
mission_req_table = {
"Liberation Day": MissionInfo(1, 7, [], completion_critical=True),
"The Outlaws": MissionInfo(2, 2, [1], completion_critical=True),
"Zero Hour": MissionInfo(3, 4, [2], completion_critical=True),
"Evacuation": MissionInfo(4, 4, [3]),
"Outbreak": MissionInfo(5, 3, [4]),
"Safe Haven": MissionInfo(6, 1, [5], number=7),
"Haven's Fall": MissionInfo(7, 1, [5], number=7),
"Smash and Grab": MissionInfo(8, 5, [3], completion_critical=True),
"The Dig": MissionInfo(9, 4, [8], number=8, completion_critical=True),
"The Moebius Factor": MissionInfo(10, 9, [9], number=11, completion_critical=True),
"Supernova": MissionInfo(11, 5, [10], number=14, completion_critical=True),
"Maw of the Void": MissionInfo(12, 6, [11], completion_critical=True),
"Devil's Playground": MissionInfo(13, 3, [3], number=4),
"Welcome to the Jungle": MissionInfo(14, 4, [13]),
"Breakout": MissionInfo(15, 3, [14], number=8),
"Ghost of a Chance": MissionInfo(16, 6, [14], number=8),
"The Great Train Robbery": MissionInfo(17, 4, [3], number=6),
"Cutthroat": MissionInfo(18, 5, [17]),
"Engine of Destruction": MissionInfo(19, 6, [18]),
"Media Blitz": MissionInfo(20, 5, [19]),
"Piercing the Shroud": MissionInfo(21, 6, [20]),
"Whispers of Doom": MissionInfo(22, 4, [9]),
"A Sinister Turn": MissionInfo(23, 4, [22]),
"Echoes of the Future": MissionInfo(24, 3, [23]),
"In Utter Darkness": MissionInfo(25, 3, [24]),
"Gates of Hell": MissionInfo(26, 2, [12], completion_critical=True),
"Belly of the Beast": MissionInfo(27, 4, [26], completion_critical=True),
"Shatter the Sky": MissionInfo(28, 5, [26], completion_critical=True),
"All-In": MissionInfo(29, -1, [27, 28], completion_critical=True, or_requirements=True)
}
def calc_objectives_completed(mission, missions_info, locations_done, unfinished_locations, ctx):
objectives_complete = 0
if missions_info[mission].extra_locations > 0:
for i in range(missions_info[mission].extra_locations):
if (missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i) in locations_done:
objectives_complete += 1
else:
unfinished_locations[mission].append(ctx.location_name_getter(
missions_info[mission].id * 100 + SC2WOL_LOC_ID_OFFSET + i))
return objectives_complete
else:
return -1
def request_unfinished_missions(locations_done, location_table, ui, ctx):
if location_table:
message = "Unfinished Missions: "
unlocks = initialize_blank_mission_dict(location_table)
unfinished_locations = initialize_blank_mission_dict(location_table)
unfinished_missions = calc_unfinished_missions(locations_done, location_table, unlocks, unfinished_locations, ctx)
message += ", ".join(f"{mark_up_mission_name(mission, location_table, ui,unlocks)}[{location_table[mission].id}] " +
mark_up_objectives(
f"[{unfinished_missions[mission]}/{location_table[mission].extra_locations}]",
ctx, unfinished_locations, mission)
for mission in unfinished_missions)
if ui:
ui.log_panels['All'].on_message_markup(message)
ui.log_panels['Starcraft2'].on_message_markup(message)
else:
sc2_logger.info(message)
else:
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
def calc_unfinished_missions(locations_done, locations, unlocks, unfinished_locations, ctx):
unfinished_missions = []
locations_completed = []
available_missions = calc_available_missions(locations_done, locations, unlocks)
for name in available_missions:
if not locations[name].extra_locations == -1:
objectives_completed = calc_objectives_completed(name, locations, locations_done, unfinished_locations, ctx)
if objectives_completed < locations[name].extra_locations:
unfinished_missions.append(name)
locations_completed.append(objectives_completed)
else:
unfinished_missions.append(name)
locations_completed.append(-1)
return {unfinished_missions[i]: locations_completed[i] for i in range(len(unfinished_missions))}
def is_mission_available(mission_id_to_check, locations_done, locations):
unfinished_missions = calc_available_missions(locations_done, locations)
return any(mission_id_to_check == locations[mission].id for mission in unfinished_missions)
def mark_up_mission_name(mission, location_table, ui, unlock_table):
"""Checks if the mission is required for game completion and adds '*' to the name to mark that."""
if location_table[mission].completion_critical:
if ui:
message = "[color=AF99EF]" + mission + "[/color]"
else:
message = "*" + mission + "*"
else:
message = mission
if ui:
unlocks = unlock_table[mission]
if len(unlocks) > 0:
pre_message = f"[ref={list(location_table).index(mission)}|Unlocks: "
pre_message += ", ".join(f"{unlock}({location_table[unlock].id})" for unlock in unlocks)
pre_message += f"]"
message = pre_message + message + "[/ref]"
return message
def mark_up_objectives(message, ctx, unfinished_locations, mission):
formatted_message = message
if ctx.ui:
locations = unfinished_locations[mission]
pre_message = f"[ref={list(ctx.mission_req_table).index(mission)+30}|"
pre_message += "<br>".join(location for location in locations)
pre_message += f"]"
formatted_message = pre_message + message + "[/ref]"
return formatted_message
def request_available_missions(locations_done, location_table, ui):
if location_table:
message = "Available Missions: "
# Initialize mission unlock table
unlocks = initialize_blank_mission_dict(location_table)
missions = calc_available_missions(locations_done, location_table, unlocks)
message += \
", ".join(f"{mark_up_mission_name(mission, location_table, ui, unlocks)}[{location_table[mission].id}]"
for mission in missions)
if ui:
ui.log_panels['All'].on_message_markup(message)
ui.log_panels['Starcraft2'].on_message_markup(message)
else:
sc2_logger.info(message)
else:
sc2_logger.warning("No mission table found, you are likely not connected to a server.")
def calc_available_missions(locations_done, locations, unlocks=None):
available_missions = []
missions_complete = 0
# Get number of missions completed
for loc in locations_done:
if loc % 100 == 0:
missions_complete += 1
for name in locations:
# Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips
if unlocks:
for unlock in locations[name].required_world:
unlocks[list(locations)[unlock-1]].append(name)
if mission_reqs_completed(name, missions_complete, locations_done, locations):
available_missions.append(name)
return available_missions
def mission_reqs_completed(location_to_check, missions_complete, locations_done, locations):
"""Returns a bool signifying if the mission has all requirements complete and can be done
Keyword arguments:
locations_to_check -- the mission string name to check
missions_complete -- an int of how many missions have been completed
locations_done -- a list of the location ids that have been complete
locations -- a dict of MissionInfo for mission requirements for this world"""
if len(locations[location_to_check].required_world) >= 1:
# A check for when the requirements are being or'd
or_success = False
# Loop through required missions
for req_mission in locations[location_to_check].required_world:
req_success = True
# Check if required mission has been completed
if not (locations[list(locations)[req_mission-1]].id * 100 + SC2WOL_LOC_ID_OFFSET) in locations_done:
if not locations[location_to_check].or_requirements:
return False
else:
req_success = False
# Recursively check required mission to see if it's requirements are met, in case !collect has been done
if not mission_reqs_completed(list(locations)[req_mission-1], missions_complete, locations_done,
locations):
if not locations[location_to_check].or_requirements:
return False
else:
req_success = False
# If requirement check succeeded mark or as satisfied
if locations[location_to_check].or_requirements and req_success:
or_success = True
if locations[location_to_check].or_requirements:
# Return false if or requirements not met
if not or_success:
return False
# Check number of missions
if missions_complete >= locations[location_to_check].number:
return True
else:
return False
else:
return True
def initialize_blank_mission_dict(location_table):
unlocks = {}
for mission in list(location_table):
unlocks[mission] = []
return unlocks
if __name__ == '__main__':
colorama.init()
asyncio.run(main())
colorama.deinit()
Utils.init_logging("Starcraft2Client", exception_logger="Client")
launch()

512
UndertaleClient.py Normal file
View File

@@ -0,0 +1,512 @@
from __future__ import annotations
import os
import sys
import asyncio
import typing
import bsdiff4
import shutil
import Utils
from NetUtils import NetworkItem, ClientStatus
from worlds import undertale
from MultiServer import mark_raw
from CommonClient import CommonContext, server_loop, \
gui_enabled, ClientCommandProcessor, logger, get_base_parser
from Utils import async_start
class UndertaleCommandProcessor(ClientCommandProcessor):
def __init__(self, ctx):
super().__init__(ctx)
def _cmd_resync(self):
"""Manually trigger a resync."""
if isinstance(self.ctx, UndertaleContext):
self.output(f"Syncing items.")
self.ctx.syncing = True
def _cmd_patch(self):
"""Patch the game. Only use this command if /auto_patch fails."""
if isinstance(self.ctx, UndertaleContext):
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
self.ctx.patch_game()
self.output("Patched.")
def _cmd_savepath(self, directory: str):
"""Redirect to proper save data folder. This is necessary for Linux users to use before connecting."""
if isinstance(self.ctx, UndertaleContext):
self.ctx.save_game_folder = directory
self.output("Changed to the following directory: " + self.ctx.save_game_folder)
@mark_raw
def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
"""Patch the game automatically."""
if isinstance(self.ctx, UndertaleContext):
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
tempInstall = steaminstall
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
tempInstall = None
if tempInstall is None:
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
if not os.path.exists(tempInstall):
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
elif not os.path.exists(tempInstall):
tempInstall = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Undertale"
if not os.path.exists(tempInstall):
tempInstall = "C:\\Program Files\\Steam\\steamapps\\common\\Undertale"
if not os.path.exists(tempInstall) or not os.path.exists(tempInstall) or not os.path.isfile(os.path.join(tempInstall, "data.win")):
self.output("ERROR: Cannot find Undertale. Please rerun the command with the correct folder."
" command. \"/auto_patch (Steam directory)\".")
else:
for file_name in os.listdir(tempInstall):
if file_name != "steam_api.dll":
shutil.copy(os.path.join(tempInstall, file_name),
os.path.join(os.getcwd(), "Undertale", file_name))
self.ctx.patch_game()
self.output("Patching successful!")
def _cmd_online(self):
"""Toggles seeing other Undertale players."""
if isinstance(self.ctx, UndertaleContext):
self.ctx.update_online_mode(not ("Online" in self.ctx.tags))
if "Online" in self.ctx.tags:
self.output(f"Now online.")
else:
self.output(f"Now offline.")
def _cmd_deathlink(self):
"""Toggles deathlink"""
if isinstance(self.ctx, UndertaleContext):
self.ctx.deathlink_status = not self.ctx.deathlink_status
if self.ctx.deathlink_status:
self.output(f"Deathlink enabled.")
else:
self.output(f"Deathlink disabled.")
class UndertaleContext(CommonContext):
tags = {"AP", "Online"}
game = "Undertale"
command_processor = UndertaleCommandProcessor
items_handling = 0b111
route = None
pieces_needed = None
completed_routes = None
completed_count = 0
save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")
def __init__(self, server_address, password):
super().__init__(server_address, password)
self.pieces_needed = 0
self.finished_game = False
self.game = "Undertale"
self.got_deathlink = False
self.syncing = False
self.deathlink_status = False
self.tem_armor = False
self.completed_count = 0
self.completed_routes = {"pacifist": 0, "genocide": 0, "neutral": 0}
# self.save_game_folder: files go in this path to pass data between us and the actual game
self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")
def patch_game(self):
with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "rb") as f:
patchedFile = bsdiff4.patch(f.read(), undertale.data_path("patch.bsdiff"))
with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "wb") as f:
f.write(patchedFile)
os.makedirs(name=os.path.join(os.getcwd(), "Undertale", "Custom Sprites"), exist_ok=True)
with open(os.path.expandvars(os.path.join(os.getcwd(), "Undertale", "Custom Sprites",
"Which Character.txt")), "w") as f:
f.writelines(["// Put the folder name of the sprites you want to play as, make sure it is the only "
"line other than this one.\n", "frisk"])
f.close()
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super().server_auth(password_requested)
await self.get_username()
await self.send_connect()
def clear_undertale_files(self):
path = self.save_game_folder
self.finished_game = False
for root, dirs, files in os.walk(path):
for file in files:
if "check.spot" == file or "scout" == file:
os.remove(os.path.join(root, file))
elif file.endswith((".item", ".victory", ".route", ".playerspot", ".mad",
".youDied", ".LV", ".mine", ".flag", ".hint")):
os.remove(os.path.join(root, file))
async def connect(self, address: typing.Optional[str] = None):
self.clear_undertale_files()
await super().connect(address)
async def disconnect(self, allow_autoreconnect: bool = False):
self.clear_undertale_files()
await super().disconnect(allow_autoreconnect)
async def connection_closed(self):
self.clear_undertale_files()
await super().connection_closed()
async def shutdown(self):
self.clear_undertale_files()
await super().shutdown()
def update_online_mode(self, online):
old_tags = self.tags.copy()
if online:
self.tags.add("Online")
else:
self.tags -= {"Online"}
if old_tags != self.tags and self.server and not self.server.socket.closed:
async_start(self.send_msgs([{"cmd": "ConnectUpdate", "tags": self.tags}]))
def on_package(self, cmd: str, args: dict):
if cmd == "Connected":
self.game = self.slot_info[self.slot].game
async_start(process_undertale_cmd(self, cmd, args))
def run_gui(self):
from kvui import GameManager
class UTManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
base_title = "Archipelago Undertale Client"
self.ui = UTManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def on_deathlink(self, data: typing.Dict[str, typing.Any]):
self.got_deathlink = True
super().on_deathlink(data)
def to_room_name(place_name: str):
if place_name == "Old Home Exit":
return "room_ruinsexit"
elif place_name == "Snowdin Forest":
return "room_tundra1"
elif place_name == "Snowdin Town Exit":
return "room_fogroom"
elif place_name == "Waterfall":
return "room_water1"
elif place_name == "Waterfall Exit":
return "room_fire2"
elif place_name == "Hotland":
return "room_fire_prelab"
elif place_name == "Hotland Exit":
return "room_fire_precore"
elif place_name == "Core":
return "room_fire_core1"
async def process_undertale_cmd(ctx: UndertaleContext, cmd: str, args: dict):
if cmd == "Connected":
if not os.path.exists(ctx.save_game_folder):
os.mkdir(ctx.save_game_folder)
ctx.route = args["slot_data"]["route"]
ctx.pieces_needed = args["slot_data"]["key_pieces"]
ctx.tem_armor = args["slot_data"]["temy_armor_include"]
await ctx.send_msgs([{"cmd": "Get", "keys": [str(ctx.slot)+" RoutesDone neutral",
str(ctx.slot)+" RoutesDone pacifist",
str(ctx.slot)+" RoutesDone genocide"]}])
await ctx.send_msgs([{"cmd": "SetNotify", "keys": [str(ctx.slot)+" RoutesDone neutral",
str(ctx.slot)+" RoutesDone pacifist",
str(ctx.slot)+" RoutesDone genocide"]}])
if args["slot_data"]["only_flakes"]:
with open(os.path.join(ctx.save_game_folder, "GenoNoChest.flag"), "w") as f:
f.close()
if not args["slot_data"]["key_hunt"]:
ctx.pieces_needed = 0
if args["slot_data"]["rando_love"]:
filename = f"LOVErando.LV"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
f.close()
if args["slot_data"]["rando_stats"]:
filename = f"STATrando.LV"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
f.close()
filename = f"{ctx.route}.route"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
f.close()
filename = f"check.spot"
with open(os.path.join(ctx.save_game_folder, filename), "a") as f:
for ss in set(args["checked_locations"]):
f.write(str(ss-12000)+"\n")
f.close()
elif cmd == "LocationInfo":
for l in args["locations"]:
locationid = l.location
filename = f"{str(locationid-12000)}.hint"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
toDraw = ""
for i in range(20):
if i < len(str(ctx.item_names[l.item])):
toDraw += str(ctx.item_names[l.item])[i]
else:
break
f.write(toDraw)
f.close()
elif cmd == "Retrieved":
if str(ctx.slot)+" RoutesDone neutral" in args["keys"]:
if args["keys"][str(ctx.slot)+" RoutesDone neutral"] is not None:
ctx.completed_routes["neutral"] = args["keys"][str(ctx.slot)+" RoutesDone neutral"]
if str(ctx.slot)+" RoutesDone genocide" in args["keys"]:
if args["keys"][str(ctx.slot)+" RoutesDone genocide"] is not None:
ctx.completed_routes["genocide"] = args["keys"][str(ctx.slot)+" RoutesDone genocide"]
if str(ctx.slot)+" RoutesDone pacifist" in args["keys"]:
if args["keys"][str(ctx.slot) + " RoutesDone pacifist"] is not None:
ctx.completed_routes["pacifist"] = args["keys"][str(ctx.slot)+" RoutesDone pacifist"]
elif cmd == "SetReply":
if args["value"] is not None:
if str(ctx.slot)+" RoutesDone pacifist" == args["key"]:
ctx.completed_routes["pacifist"] = args["value"]
elif str(ctx.slot)+" RoutesDone genocide" == args["key"]:
ctx.completed_routes["genocide"] = args["value"]
elif str(ctx.slot)+" RoutesDone neutral" == args["key"]:
ctx.completed_routes["neutral"] = args["value"]
elif cmd == "ReceivedItems":
start_index = args["index"]
if start_index == 0:
ctx.items_received = []
elif start_index != len(ctx.items_received):
sync_msg = [{"cmd": "Sync"}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks",
"locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
if start_index == len(ctx.items_received):
counter = -1
placedWeapon = 0
placedArmor = 0
for item in args["items"]:
id = NetworkItem(*item).location
while NetworkItem(*item).location < 0 and \
counter <= id:
id -= 1
if NetworkItem(*item).location < 0:
counter -= 1
filename = f"{str(id)}PLR{str(NetworkItem(*item).player)}.item"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
if NetworkItem(*item).item == 77701:
if placedWeapon == 0:
f.write(str(77013-11000))
elif placedWeapon == 1:
f.write(str(77014-11000))
elif placedWeapon == 2:
f.write(str(77025-11000))
elif placedWeapon == 3:
f.write(str(77045-11000))
elif placedWeapon == 4:
f.write(str(77049-11000))
elif placedWeapon == 5:
f.write(str(77047-11000))
elif placedWeapon == 6:
if str(ctx.route) == "genocide" or str(ctx.route) == "all_routes":
f.write(str(77052-11000))
else:
f.write(str(77051-11000))
else:
f.write(str(77003-11000))
placedWeapon += 1
elif NetworkItem(*item).item == 77702:
if placedArmor == 0:
f.write(str(77012-11000))
elif placedArmor == 1:
f.write(str(77015-11000))
elif placedArmor == 2:
f.write(str(77024-11000))
elif placedArmor == 3:
f.write(str(77044-11000))
elif placedArmor == 4:
f.write(str(77048-11000))
elif placedArmor == 5:
if str(ctx.route) == "genocide":
f.write(str(77053-11000))
else:
f.write(str(77046-11000))
elif placedArmor == 6 and ((not str(ctx.route) == "genocide") or ctx.tem_armor):
if str(ctx.route) == "all_routes":
f.write(str(77053-11000))
elif str(ctx.route) == "genocide":
f.write(str(77064-11000))
else:
f.write(str(77050-11000))
elif placedArmor == 7 and ctx.tem_armor and not str(ctx.route) == "genocide":
f.write(str(77064-11000))
else:
f.write(str(77004-11000))
placedArmor += 1
else:
f.write(str(NetworkItem(*item).item-11000))
f.close()
ctx.items_received.append(NetworkItem(*item))
if [item.item for item in ctx.items_received].count(77000) >= ctx.pieces_needed > 0:
filename = f"{str(-99999)}PLR{str(0)}.item"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
f.write(str(77787 - 11000))
f.close()
filename = f"{str(-99998)}PLR{str(0)}.item"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
f.write(str(77789 - 11000))
f.close()
ctx.watcher_event.set()
elif cmd == "RoomUpdate":
if "checked_locations" in args:
filename = f"check.spot"
with open(os.path.join(ctx.save_game_folder, filename), "a") as f:
for ss in set(args["checked_locations"]):
f.write(str(ss-12000)+"\n")
f.close()
elif cmd == "Bounced":
tags = args.get("tags", [])
if "Online" in tags:
data = args.get("data", {})
if data["player"] != ctx.slot and data["player"] is not None:
filename = f"FRISK" + str(data["player"]) + ".playerspot"
with open(os.path.join(ctx.save_game_folder, filename), "w") as f:
f.write(str(data["x"]) + str(data["y"]) + str(data["room"]) + str(
data["spr"]) + str(data["frm"]))
f.close()
async def multi_watcher(ctx: UndertaleContext):
while not ctx.exit_event.is_set():
path = ctx.save_game_folder
for root, dirs, files in os.walk(path):
for file in files:
if "spots.mine" in file and "Online" in ctx.tags:
with open(os.path.join(root, file), "r") as mine:
this_x = mine.readline()
this_y = mine.readline()
this_room = mine.readline()
this_sprite = mine.readline()
this_frame = mine.readline()
mine.close()
message = [{"cmd": "Bounce", "tags": ["Online"],
"data": {"player": ctx.slot, "x": this_x, "y": this_y, "room": this_room,
"spr": this_sprite, "frm": this_frame}}]
await ctx.send_msgs(message)
await asyncio.sleep(0.1)
async def game_watcher(ctx: UndertaleContext):
while not ctx.exit_event.is_set():
await ctx.update_death_link(ctx.deathlink_status)
path = ctx.save_game_folder
if ctx.syncing:
for root, dirs, files in os.walk(path):
for file in files:
if ".item" in file:
os.remove(os.path.join(root, file))
sync_msg = [{"cmd": "Sync"}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
ctx.syncing = False
if ctx.got_deathlink:
ctx.got_deathlink = False
with open(os.path.join(ctx.save_game_folder, "WelcomeToTheDead.youDied"), "w") as f:
f.close()
sending = []
victory = False
found_routes = 0
for root, dirs, files in os.walk(path):
for file in files:
if "DontBeMad.mad" in file:
os.remove(os.path.join(root, file))
if "DeathLink" in ctx.tags:
await ctx.send_death()
if "scout" == file:
sending = []
try:
with open(os.path.join(root, file), "r") as f:
lines = f.readlines()
for l in lines:
if ctx.server_locations.__contains__(int(l)+12000):
sending = sending + [int(l.rstrip('\n'))+12000]
finally:
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": sending,
"create_as_hint": int(2)}])
os.remove(os.path.join(root, file))
if "check.spot" in file:
sending = []
try:
with open(os.path.join(root, file), "r") as f:
lines = f.readlines()
for l in lines:
sending = sending+[(int(l.rstrip('\n')))+12000]
finally:
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": sending}])
if "victory" in file and str(ctx.route) in file:
victory = True
if ".playerspot" in file and "Online" not in ctx.tags:
os.remove(os.path.join(root, file))
if "victory" in file:
if str(ctx.route) == "all_routes":
if "neutral" in file and ctx.completed_routes["neutral"] != 1:
await ctx.send_msgs([{"cmd": "Set", "key": str(ctx.slot)+" RoutesDone neutral",
"default": 0, "want_reply": True, "operations": [{"operation": "max",
"value": 1}]}])
elif "pacifist" in file and ctx.completed_routes["pacifist"] != 1:
await ctx.send_msgs([{"cmd": "Set", "key": str(ctx.slot)+" RoutesDone pacifist",
"default": 0, "want_reply": True, "operations": [{"operation": "max",
"value": 1}]}])
elif "genocide" in file and ctx.completed_routes["genocide"] != 1:
await ctx.send_msgs([{"cmd": "Set", "key": str(ctx.slot)+" RoutesDone genocide",
"default": 0, "want_reply": True, "operations": [{"operation": "max",
"value": 1}]}])
if str(ctx.route) == "all_routes":
found_routes += ctx.completed_routes["neutral"]
found_routes += ctx.completed_routes["pacifist"]
found_routes += ctx.completed_routes["genocide"]
if str(ctx.route) == "all_routes" and found_routes >= 3:
victory = True
ctx.locations_checked = sending
if (not ctx.finished_game) and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
await asyncio.sleep(0.1)
def main():
Utils.init_logging("UndertaleClient", exception_logger="Client")
async def _main():
ctx = UndertaleContext(None, None)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
asyncio.create_task(
game_watcher(ctx), name="UndertaleProgressionWatcher")
asyncio.create_task(
multi_watcher(ctx), name="UndertaleMultiplayerWatcher")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
await ctx.exit_event.wait()
await ctx.shutdown()
import colorama
colorama.init()
asyncio.run(_main())
colorama.deinit()
if __name__ == "__main__":
parser = get_base_parser(description="Undertale Client, for text interfacing.")
args = parser.parse_args()
main()

860
Utils.py

File diff suppressed because it is too large Load Diff

453
WargrooveClient.py Normal file
View File

@@ -0,0 +1,453 @@
from __future__ import annotations
import atexit
import os
import sys
import asyncio
import random
import shutil
from typing import Tuple, List, Iterable, Dict
from worlds.wargroove import WargrooveWorld
from worlds.wargroove.Items import item_table, faction_table, CommanderData, ItemData
import ModuleUpdate
ModuleUpdate.update()
import Utils
import json
import logging
if __name__ == "__main__":
Utils.init_logging("WargrooveClient", exception_logger="Client")
from NetUtils import NetworkItem, ClientStatus
from CommonClient import gui_enabled, logger, get_base_parser, ClientCommandProcessor, \
CommonContext, server_loop
wg_logger = logging.getLogger("WG")
class WargrooveClientCommandProcessor(ClientCommandProcessor):
def _cmd_resync(self):
"""Manually trigger a resync."""
self.output(f"Syncing items.")
self.ctx.syncing = True
def _cmd_commander(self, *commander_name: Iterable[str]):
"""Set the current commander to the given commander."""
if commander_name:
self.ctx.set_commander(' '.join(commander_name))
else:
if self.ctx.can_choose_commander:
commanders = self.ctx.get_commanders()
wg_logger.info('Unlocked commanders: ' +
', '.join((commander.name for commander, unlocked in commanders if unlocked)))
wg_logger.info('Locked commanders: ' +
', '.join((commander.name for commander, unlocked in commanders if not unlocked)))
else:
wg_logger.error('Cannot set commanders in this game mode.')
class WargrooveContext(CommonContext):
command_processor: int = WargrooveClientCommandProcessor
game = "Wargroove"
items_handling = 0b111 # full remote
current_commander: CommanderData = faction_table["Starter"][0]
can_choose_commander: bool = False
commander_defense_boost_multiplier: int = 0
income_boost_multiplier: int = 0
starting_groove_multiplier: float
faction_item_ids = {
'Starter': 0,
'Cherrystone': 52025,
'Felheim': 52026,
'Floran': 52027,
'Heavensong': 52028,
'Requiem': 52029,
'Outlaw': 52030
}
buff_item_ids = {
'Income Boost': 52023,
'Commander Defense Boost': 52024,
}
def __init__(self, server_address, password):
super(WargrooveContext, self).__init__(server_address, password)
self.send_index: int = 0
self.syncing = False
self.awaiting_bridge = False
# self.game_communication_path: files go in this path to pass data between us and the actual game
if "appdata" in os.environ:
options = Utils.get_options()
root_directory = os.path.join(options["wargroove_options"]["root_directory"])
data_directory = os.path.join("lib", "worlds", "wargroove", "data")
dev_data_directory = os.path.join("worlds", "wargroove", "data")
appdata_wargroove = os.path.expandvars(os.path.join("%APPDATA%", "Chucklefish", "Wargroove"))
if not os.path.isfile(os.path.join(root_directory, "win64_bin", "wargroove64.exe")):
print_error_and_close("WargrooveClient couldn't find wargroove64.exe. "
"Unable to infer required game_communication_path")
self.game_communication_path = os.path.join(root_directory, "AP")
if not os.path.exists(self.game_communication_path):
os.makedirs(self.game_communication_path)
self.remove_communication_files()
atexit.register(self.remove_communication_files)
if not os.path.isdir(appdata_wargroove):
print_error_and_close("WargrooveClient couldn't find Wargoove in appdata!"
"Boot Wargroove and then close it to attempt to fix this error")
if not os.path.isdir(data_directory):
data_directory = dev_data_directory
if not os.path.isdir(data_directory):
print_error_and_close("WargrooveClient couldn't find Wargoove mod and save files in install!")
shutil.copytree(data_directory, appdata_wargroove, dirs_exist_ok=True)
else:
print_error_and_close("WargrooveClient couldn't detect system type. "
"Unable to infer required game_communication_path")
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(WargrooveContext, self).server_auth(password_requested)
await self.get_username()
await self.send_connect()
async def connection_closed(self):
await super(WargrooveContext, self).connection_closed()
self.remove_communication_files()
self.checked_locations.clear()
self.server_locations.clear()
self.finished_game = False
@property
def endpoints(self):
if self.server:
return [self.server]
else:
return []
async def shutdown(self):
await super(WargrooveContext, self).shutdown()
self.remove_communication_files()
self.checked_locations.clear()
self.server_locations.clear()
self.finished_game = False
def remove_communication_files(self):
for root, dirs, files in os.walk(self.game_communication_path):
for file in files:
os.remove(root + "/" + file)
def on_package(self, cmd: str, args: dict):
if cmd in {"Connected"}:
filename = f"AP_settings.json"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
slot_data = args["slot_data"]
json.dump(args["slot_data"], f)
self.can_choose_commander = slot_data["can_choose_commander"]
print('can choose commander:', self.can_choose_commander)
self.starting_groove_multiplier = slot_data["starting_groove_multiplier"]
self.income_boost_multiplier = slot_data["income_boost"]
self.commander_defense_boost_multiplier = slot_data["commander_defense_boost"]
f.close()
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
self.update_commander_data()
self.ui.update_tracker()
random.seed(self.seed_name + str(self.slot))
# Our indexes start at 1 and we have 24 levels
for i in range(1, 25):
filename = f"seed{i}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.write(str(random.randint(0, 4294967295)))
f.close()
if cmd in {"RoomInfo"}:
self.seed_name = args["seed_name"]
if cmd in {"ReceivedItems"}:
received_ids = [item.item for item in self.items_received]
for network_item in self.items_received:
filename = f"AP_{str(network_item.item)}.item"
path = os.path.join(self.game_communication_path, filename)
# Newly-obtained items
if not os.path.isfile(path):
open(path, 'w').close()
# Announcing commander unlocks
item_name = self.item_names[network_item.item]
if item_name in faction_table.keys():
for commander in faction_table[item_name]:
logger.info(f"{commander.name} has been unlocked!")
with open(path, 'w') as f:
item_count = received_ids.count(network_item.item)
if self.buff_item_ids["Income Boost"] == network_item.item:
f.write(f"{item_count * self.income_boost_multiplier}")
elif self.buff_item_ids["Commander Defense Boost"] == network_item.item:
f.write(f"{item_count * self.commander_defense_boost_multiplier}")
else:
f.write(f"{item_count}")
f.close()
print_filename = f"AP_{str(network_item.item)}.item.print"
print_path = os.path.join(self.game_communication_path, print_filename)
if not os.path.isfile(print_path):
open(print_path, 'w').close()
with open(print_path, 'w') as f:
f.write("Received " +
self.item_names[network_item.item] +
" from " +
self.player_names[network_item.player])
f.close()
self.update_commander_data()
self.ui.update_tracker()
if cmd in {"RoomUpdate"}:
if "checked_locations" in args:
for ss in self.checked_locations:
filename = f"send{ss}"
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
f.close()
def run_gui(self):
"""Import kivy UI system and start running it as self.ui_task."""
from kvui import GameManager, HoverBehavior, ServerToolTip
from kivy.uix.tabbedpanel import TabbedPanelItem
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import AsyncImage, Image
from kivy.uix.stacklayout import StackLayout
from kivy.uix.label import Label
from kivy.properties import ColorProperty
from kivy.uix.image import Image
import pkgutil
class TrackerLayout(BoxLayout):
pass
class CommanderSelect(BoxLayout):
pass
class CommanderButton(ToggleButton):
pass
class FactionBox(BoxLayout):
pass
class CommanderGroup(BoxLayout):
pass
class ItemTracker(BoxLayout):
pass
class ItemLabel(Label):
pass
class WargrooveManager(GameManager):
logging_pairs = [
("Client", "Archipelago"),
("WG", "WG Console"),
]
base_title = "Archipelago Wargroove Client"
ctx: WargrooveContext
unit_tracker: ItemTracker
trigger_tracker: BoxLayout
boost_tracker: BoxLayout
commander_buttons: Dict[int, List[CommanderButton]]
tracker_items = {
"Swordsman": ItemData(None, "Unit", False),
"Dog": ItemData(None, "Unit", False),
**item_table
}
def build(self):
container = super().build()
panel = TabbedPanelItem(text="Wargroove")
panel.content = self.build_tracker()
self.tabs.add_widget(panel)
return container
def build_tracker(self) -> TrackerLayout:
try:
tracker = TrackerLayout(orientation="horizontal")
commander_select = CommanderSelect(orientation="vertical")
self.commander_buttons = {}
for faction, commanders in faction_table.items():
faction_box = FactionBox(size_hint=(None, None), width=100 * len(commanders), height=70)
commander_group = CommanderGroup()
commander_buttons = []
for commander in commanders:
commander_button = CommanderButton(text=commander.name, group="commanders")
if faction == "Starter":
commander_button.disabled = False
commander_button.bind(on_press=lambda instance: self.ctx.set_commander(instance.text))
commander_buttons.append(commander_button)
commander_group.add_widget(commander_button)
self.commander_buttons[faction] = commander_buttons
faction_box.add_widget(Label(text=faction, size_hint_x=None, pos_hint={'left': 1}, size_hint_y=None, height=10))
faction_box.add_widget(commander_group)
commander_select.add_widget(faction_box)
item_tracker = ItemTracker(padding=[0,20])
self.unit_tracker = BoxLayout(orientation="vertical")
other_tracker = BoxLayout(orientation="vertical")
self.trigger_tracker = BoxLayout(orientation="vertical")
self.boost_tracker = BoxLayout(orientation="vertical")
other_tracker.add_widget(self.trigger_tracker)
other_tracker.add_widget(self.boost_tracker)
item_tracker.add_widget(self.unit_tracker)
item_tracker.add_widget(other_tracker)
tracker.add_widget(commander_select)
tracker.add_widget(item_tracker)
self.update_tracker()
return tracker
except Exception as e:
print(e)
def update_tracker(self):
received_ids = [item.item for item in self.ctx.items_received]
for faction, item_id in self.ctx.faction_item_ids.items():
for commander_button in self.commander_buttons[faction]:
commander_button.disabled = not (faction == "Starter" or item_id in received_ids)
self.unit_tracker.clear_widgets()
self.trigger_tracker.clear_widgets()
for name, item in self.tracker_items.items():
if item.type in ("Unit", "Trigger"):
status_color = (1, 1, 1, 1) if item.code is None or item.code in received_ids else (0.6, 0.2, 0.2, 1)
label = ItemLabel(text=name, color=status_color)
if item.type == "Unit":
self.unit_tracker.add_widget(label)
else:
self.trigger_tracker.add_widget(label)
self.boost_tracker.clear_widgets()
extra_income = received_ids.count(52023) * self.ctx.income_boost_multiplier
extra_defense = received_ids.count(52024) * self.ctx.commander_defense_boost_multiplier
income_boost = ItemLabel(text="Extra Income: " + str(extra_income))
defense_boost = ItemLabel(text="Comm Defense: " + str(100 + extra_defense))
self.boost_tracker.add_widget(income_boost)
self.boost_tracker.add_widget(defense_boost)
self.ui = WargrooveManager(self)
data = pkgutil.get_data(WargrooveWorld.__module__, "Wargroove.kv").decode()
Builder.load_string(data)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
def update_commander_data(self):
if self.can_choose_commander:
faction_items = 0
faction_item_names = [faction + ' Commanders' for faction in faction_table.keys()]
for network_item in self.items_received:
if self.item_names[network_item.item] in faction_item_names:
faction_items += 1
starting_groove = (faction_items - 1) * self.starting_groove_multiplier
# Must be an integer larger than 0
starting_groove = int(max(starting_groove, 0))
data = {
"commander": self.current_commander.internal_name,
"starting_groove": starting_groove
}
else:
data = {
"commander": "seed",
"starting_groove": 0
}
filename = 'commander.json'
with open(os.path.join(self.game_communication_path, filename), 'w') as f:
json.dump(data, f)
if self.ui:
self.ui.update_tracker()
def set_commander(self, commander_name: str) -> bool:
"""Sets the current commander to the given one, if possible"""
if not self.can_choose_commander:
wg_logger.error("Cannot set commanders in this game mode.")
return
match_name = commander_name.lower()
for commander, unlocked in self.get_commanders():
if commander.name.lower() == match_name or commander.alt_name and commander.alt_name.lower() == match_name:
if unlocked:
self.current_commander = commander
self.syncing = True
wg_logger.info(f"Commander set to {commander.name}.")
self.update_commander_data()
return True
else:
wg_logger.error(f"Commander {commander.name} has not been unlocked.")
return False
else:
wg_logger.error(f"{commander_name} is not a recognized Wargroove commander.")
def get_commanders(self) -> List[Tuple[CommanderData, bool]]:
"""Gets a list of commanders with their unlocked status"""
commanders = []
received_ids = [item.item for item in self.items_received]
for faction in faction_table.keys():
unlocked = faction == 'Starter' or self.faction_item_ids[faction] in received_ids
commanders += [(commander, unlocked) for commander in faction_table[faction]]
return commanders
async def game_watcher(ctx: WargrooveContext):
from worlds.wargroove.Locations import location_table
while not ctx.exit_event.is_set():
if ctx.syncing == True:
sync_msg = [{'cmd': 'Sync'}]
if ctx.locations_checked:
sync_msg.append({"cmd": "LocationChecks", "locations": list(ctx.locations_checked)})
await ctx.send_msgs(sync_msg)
ctx.syncing = False
sending = []
victory = False
for root, dirs, files in os.walk(ctx.game_communication_path):
for file in files:
if file.find("send") > -1:
st = file.split("send", -1)[1]
sending = sending+[(int(st))]
os.remove(os.path.join(ctx.game_communication_path, file))
if file.find("victory") > -1:
victory = True
os.remove(os.path.join(ctx.game_communication_path, file))
ctx.locations_checked = sending
message = [{"cmd": 'LocationChecks', "locations": sending}]
await ctx.send_msgs(message)
if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
await asyncio.sleep(0.1)
def print_error_and_close(msg):
logger.error("Error: " + msg)
Utils.messagebox("Error", msg, error=True)
sys.exit(1)
if __name__ == '__main__':
async def main(args):
ctx = WargrooveContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="server loop")
if gui_enabled:
ctx.run_gui()
ctx.run_cli()
progression_watcher = asyncio.create_task(
game_watcher(ctx), name="WargrooveProgressionWatcher")
await ctx.exit_event.wait()
ctx.server_address = None
await progression_watcher
await ctx.shutdown()
import colorama
parser = get_base_parser(description="Wargroove Client, for text interfacing.")
args, rest = parser.parse_known_args()
colorama.init()
asyncio.run(main(args))
colorama.deinit()

View File

@@ -1,5 +1,4 @@
import os
import sys
import multiprocessing
import logging
import typing
@@ -11,30 +10,31 @@ ModuleUpdate.update()
# in case app gets imported by something like gunicorn
import Utils
import settings
Utils.local_path.cached_path = os.path.dirname(__file__)
from WebHostLib import app as raw_app
from waitress import serve
from WebHostLib.models import db
from WebHostLib.autolauncher import autohost, autogen
from WebHostLib.lttpsprites import update_sprites_lttp
from WebHostLib.options import create as create_options_files
from worlds.AutoWorld import AutoWorldRegister, WebWorld
Utils.local_path.cached_path = os.path.dirname(__file__) or "." # py3.8 is not abs. remove "." when dropping 3.8
settings.no_gui = True
configpath = os.path.abspath("config.yaml")
if not os.path.exists(configpath): # fall back to config.yaml in home
configpath = os.path.abspath(Utils.user_path('config.yaml'))
def get_app():
from WebHostLib import register, cache, app as raw_app
from WebHostLib.models import db
app = raw_app
if os.path.exists(configpath):
if os.path.exists(configpath) and not app.config["TESTING"]:
import yaml
app.config.from_file(configpath, yaml.safe_load)
logging.info(f"Updated config from {configpath}")
if not app.config["HOST_ADDRESS"]:
logging.info("Getting public IP, as HOST_ADDRESS is empty.")
app.config["HOST_ADDRESS"] = Utils.get_public_ipv4()
logging.info(f"HOST_ADDRESS was set to {app.config['HOST_ADDRESS']}")
register()
cache.init_app(app)
db.bind(**app.config["PONY"])
db.generate_mapping(create_tables=True)
return app
@@ -43,19 +43,40 @@ def get_app():
def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]:
import json
import shutil
import zipfile
zfile: zipfile.ZipInfo
from worlds.AutoWorld import AutoWorldRegister
worlds = {}
data = []
for game, world in AutoWorldRegister.world_types.items():
if hasattr(world.web, 'tutorials'):
if hasattr(world.web, 'tutorials') and (not world.hidden or game == 'Archipelago'):
worlds[game] = world
base_target_path = Utils.local_path("WebHostLib", "static", "generated", "docs")
for game, world in worlds.items():
# copy files from world's docs folder to the generated folder
source_path = Utils.local_path(os.path.dirname(sys.modules[world.__module__].__file__), 'docs')
target_path = Utils.local_path("WebHostLib", "static", "generated", "docs", game)
files = os.listdir(source_path)
for file in files:
os.makedirs(os.path.dirname(Utils.local_path(target_path, file)), exist_ok=True)
shutil.copyfile(Utils.local_path(source_path, file), Utils.local_path(target_path, file))
target_path = os.path.join(base_target_path, game)
os.makedirs(target_path, exist_ok=True)
if world.zip_path:
zipfile_path = world.zip_path
assert os.path.isfile(zipfile_path), f"{zipfile_path} is not a valid file(path)."
assert zipfile.is_zipfile(zipfile_path), f"{zipfile_path} is not a valid zipfile."
with zipfile.ZipFile(zipfile_path) as zf:
for zfile in zf.infolist():
if not zfile.is_dir() and "/docs/" in zfile.filename:
zfile.filename = os.path.basename(zfile.filename)
zf.extract(zfile, target_path)
else:
source_path = Utils.local_path(os.path.dirname(world.__file__), "docs")
files = os.listdir(source_path)
for file in files:
shutil.copyfile(Utils.local_path(source_path, file), Utils.local_path(target_path, file))
# build a json tutorial dict per game
game_data = {'gameTitle': game, 'tutorials': []}
for tutorial in world.web.tutorials:
@@ -67,7 +88,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
'language': tutorial.language,
'filename': game + '/' + tutorial.file_name,
'link': f'{game}/{tutorial.link}',
'authors': tutorial.author
'authors': tutorial.authors
}]
}
@@ -75,7 +96,6 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
for guide in game_data['tutorials']:
if guide and tutorial.tutorial_name == guide['name']:
guide['files'].append(current_tutorial['files'][0])
added = True
break
else:
game_data['tutorials'].append(current_tutorial)
@@ -86,7 +106,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
for games in data:
if 'Archipelago' in games['gameTitle']:
generic_data = data.pop(data.index(games))
sorted_data = [generic_data] + sorted(data, key=lambda entry: entry["gameTitle"].lower())
sorted_data = [generic_data] + Utils.title_sorted(data, key=lambda entry: entry["gameTitle"])
json.dump(sorted_data, json_target, indent=2, ensure_ascii=False)
return sorted_data
@@ -95,6 +115,11 @@ if __name__ == "__main__":
multiprocessing.freeze_support()
multiprocessing.set_start_method('spawn')
logging.basicConfig(format='[%(asctime)s] %(message)s', level=logging.INFO)
from WebHostLib.lttpsprites import update_sprites_lttp
from WebHostLib.autolauncher import autohost, autogen, stop
from WebHostLib.options import create as create_options_files
try:
update_sprites_lttp()
except Exception as e:
@@ -109,7 +134,15 @@ if __name__ == "__main__":
autogen(app.config)
if app.config["SELFHOST"]: # using WSGI, you just want to run get_app()
if app.config["DEBUG"]:
autohost(app.config)
app.run(debug=True, port=app.config["PORT"])
else:
from waitress import serve
serve(app, port=app.config["PORT"], threads=app.config["WAITRESS_THREADS"])
else:
from time import sleep
try:
while True:
sleep(1) # wait for process to be killed
except (SystemExit, KeyboardInterrupt):
pass
stop() # stop worker threads

46
WebHostLib/README.md Normal file
View File

@@ -0,0 +1,46 @@
# WebHost
## Contribution Guidelines
**Thank you for your interest in contributing to the Archipelago website!**
Much of the content on the website is generated automatically, but there are some things
that need a personal touch. For those things, we rely on contributions from both the core
team and the community. The current primary maintainer of the website is Farrak Kilhn.
He may be found on Discord as `Farrak Kilhn#0418`, or on GitHub as `LegendaryLinux`.
### Small Changes
Little changes like adding a button or a couple new select elements are perfectly fine.
Tweaks to style specific to a PR's content are also probably not a problem. For example, if
you build a new page which needs two side by side tables, and you need to write a CSS file
specific to your page, that is perfectly reasonable.
### Content Additions
Once you develop a new feature or add new content the website, make a pull request. It will
be reviewed by the community and there will probably be some discussion around it. Depending
on the size of the feature, and if new styles are required, there may be an additional step
before the PR is accepted wherein Farrak works with the designer to implement styles.
### Restrictions on Style Changes
A professional designer is paid to develop the styles and assets for the Archipelago website.
In an effort to maintain a consistent look and feel, pull requests which *exclusively*
change site styles are rejected. Please note this applies to code which changes the overall
look and feel of the site, not to small tweaks to CSS for your custom page. The intention
behind these restrictions is to maintain a curated feel for the design of the site. If
any PR affects the overall feel of the site but includes additive changes, there will
likely be a conversation about how to implement those changes without compromising the
curated site style. It is therefore worth noting there are a couple files which, if
changed in your pull request, will cause it to draw additional scrutiny.
These closely guarded files are:
- `globalStyles.css`
- `islandFooter.css`
- `landing.css`
- `markdown.css`
- `tooltip.css`
### Site Themes
There are several themes available for game pages. It is possible to request a new theme in
the `#art-and-design` channel on Discord. Because themes are created by the designer, they
are not free, and take some time to create. Farrak works closely with the designer to implement
these themes, and pays for the assets out of pocket. Therefore, only a couple themes per year
are added. If a proposed theme seems like a cool idea and the community likes it, there is a
good chance it will become a reality.

View File

@@ -1,16 +1,15 @@
import os
import uuid
import base64
import os
import socket
import uuid
import jinja2.exceptions
from pony.flask import Pony
from flask import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory
from flask import Flask
from flask_caching import Cache
from flask_compress import Compress
from worlds.AutoWorld import AutoWorldRegister
from pony.flask import Pony
from werkzeug.routing import BaseConverter
from .models import *
from Utils import title_sorted
UPLOAD_FOLDER = os.path.relpath('uploads')
LOGS_FOLDER = os.path.relpath('logs')
@@ -24,7 +23,10 @@ app.jinja_env.filters['all'] = all
app.config["SELFHOST"] = True # application process is in charge of running the websites
app.config["GENERATORS"] = 8 # maximum concurrent world gens
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms.
app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections
app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections
app.config["SELFGEN"] = True # application process is in charge of scheduling Generations.
app.config["DEBUG"] = False
app.config["PORT"] = 80
@@ -32,8 +34,10 @@ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 64 * 1024 * 1024 # 64 megabyte limit
# if you want to deploy, make sure you have a non-guessable secret key
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
# at what amount of worlds should scheduling be used, instead of rolling in the webthread
app.config["JOB_THRESHOLD"] = 2
# at what amount of worlds should scheduling be used, instead of rolling in the web-thread
app.config["JOB_THRESHOLD"] = 1
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
app.config["JOB_TIME"] = 600
app.config['SESSION_PERMANENT'] = True
# waitress uses one thread for I/O, these are for processing of views that then get sent
@@ -46,15 +50,13 @@ app.config["PONY"] = {
'create_db': True
}
app.config["MAX_ROLL"] = 20
app.config["CACHE_TYPE"] = "simple"
app.config["JSON_AS_ASCII"] = False
app.config["PATCH_TARGET"] = "archipelago.gg"
app.config["CACHE_TYPE"] = "SimpleCache"
app.config["HOST_ADDRESS"] = ""
app.config["ASSET_RIGHTS"] = False
cache = Cache(app)
cache = Cache()
Compress(app)
from werkzeug.routing import BaseConverter
class B64UUIDConverter(BaseConverter):
@@ -68,160 +70,20 @@ class B64UUIDConverter(BaseConverter):
# short UUID
app.url_map.converters["suuid"] = B64UUIDConverter
app.jinja_env.filters['suuid'] = lambda value: base64.urlsafe_b64encode(value.bytes).rstrip(b'=').decode('ascii')
app.jinja_env.filters["title_sorted"] = title_sorted
def get_world_theme(game_name: str):
if game_name in AutoWorldRegister.world_types:
return AutoWorldRegister.world_types[game_name].web.theme
return 'grass'
def register():
"""Import submodules, triggering their registering on flask routing.
Note: initializes worlds subsystem."""
# has automatic patch integration
import worlds.AutoWorld
import worlds.Files
app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: \
game_name in worlds.Files.AutoPatchRegister.patch_types
from WebHostLib.customserver import run_server_process
# to trigger app routing picking up on it
from . import tracker, upload, landing, check, generate, downloads, api, stats, misc, robots, options
@app.before_request
def register_session():
session.permanent = True # technically 31 days after the last visit
if not session.get("_id", None):
session["_id"] = uuid4() # uniquely identify each session without needing a login
@app.errorhandler(404)
@app.errorhandler(jinja2.exceptions.TemplateNotFound)
def page_not_found(err):
return render_template('404.html'), 404
# Start Playing Page
@app.route('/start-playing')
def start_playing():
return render_template(f"startPlaying.html")
@app.route('/weighted-settings')
def weighted_settings():
return render_template(f"weighted-settings.html")
# Player settings pages
@app.route('/games/<string:game>/player-settings')
def player_settings(game):
return render_template(f"player-settings.html", game=game, theme=get_world_theme(game))
# Game Info Pages
@app.route('/games/<string:game>/info/<string:lang>')
def game_info(game, lang):
return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
# List of supported games
@app.route('/games')
def games():
worlds = {}
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
worlds[game] = world
return render_template("supportedGames.html", worlds=worlds)
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
def tutorial(game, file, lang):
return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))
@app.route('/tutorial/')
def tutorial_landing():
worlds = {}
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
worlds[game] = world
return render_template("tutorialLanding.html")
@app.route('/faq/<string:lang>/')
def faq(lang):
return render_template("faq.html", lang=lang)
@app.route('/seed/<suuid:seed>')
def view_seed(seed: UUID):
seed = Seed.get(id=seed)
if not seed:
abort(404)
return render_template("viewSeed.html", seed=seed, slot_count=count(seed.slots))
@app.route('/new_room/<suuid:seed>')
def new_room(seed: UUID):
seed = Seed.get(id=seed)
if not seed:
abort(404)
room = Room(seed=seed, owner=session["_id"], tracker=uuid4())
commit()
return redirect(url_for("host_room", room=room.id))
def _read_log(path: str):
if os.path.exists(path):
with open(path, encoding="utf-8-sig") as log:
yield from log
else:
yield f"Logfile {path} does not exist. " \
f"Likely a crash during spinup of multiworld instance or it is still spinning up."
@app.route('/log/<suuid:room>')
def display_log(room: UUID):
return Response(_read_log(os.path.join("logs", str(room) + ".txt")), mimetype="text/plain;charset=UTF-8")
@app.route('/room/<suuid:room>', methods=['GET', 'POST'])
def host_room(room: UUID):
room = Room.get(id=room)
if room is None:
return abort(404)
if request.method == "POST":
if room.owner == session["_id"]:
cmd = request.form["cmd"]
if cmd:
Command(room=room, commandtext=cmd)
commit()
with db_session:
room.last_activity = datetime.utcnow() # will trigger a spinup, if it's not already running
return render_template("hostRoom.html", room=room)
@app.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(app.root_path, 'static/static'),
'favicon.ico', mimetype='image/vnd.microsoft.icon')
@app.route('/discord')
def discord():
return redirect("https://discord.gg/archipelago")
@app.route('/datapackage')
@cache.cached()
def get_datapackge():
"""A pretty print version of /api/datapackage"""
from worlds import network_data_package
import json
return Response(json.dumps(network_data_package, indent=4), mimetype="text/plain")
@app.route('/index')
@app.route('/sitemap')
def get_sitemap():
available_games = []
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
available_games.append(game)
return render_template("siteMap.html", games=available_games)
from WebHostLib.customserver import run_server_process
from . import tracker, upload, landing, check, generate, downloads, api, stats # to trigger app routing picking up on it
app.register_blueprint(api.api_endpoints)
app.register_blueprint(api.api_endpoints)

View File

@@ -1,11 +1,12 @@
"""API endpoints package."""
from uuid import UUID
from typing import List, Tuple
from uuid import UUID
from flask import Blueprint, abort
from flask import Blueprint, abort, url_for
from ..models import Room, Seed
import worlds.Files
from .. import cache
from ..models import Room, Seed
api_endpoints = Blueprint('api', __name__, url_prefix="/api")
@@ -21,28 +22,56 @@ def room_info(room: UUID):
room = Room.get(id=room)
if room is None:
return abort(404)
def supports_apdeltapatch(game: str):
return game in worlds.Files.AutoPatchRegister.patch_types
downloads = []
for slot in sorted(room.seed.slots):
if slot.data and not supports_apdeltapatch(slot.game):
slot_download = {
"slot": slot.player_id,
"download": url_for("download_slot_file", room_id=room.id, player_id=slot.player_id)
}
downloads.append(slot_download)
elif slot.data:
slot_download = {
"slot": slot.player_id,
"download": url_for("download_patch", patch_id=slot.id, room_id=room.id)
}
downloads.append(slot_download)
return {
"tracker": room.tracker,
"players": get_players(room.seed),
"last_port": room.last_port,
"last_activity": room.last_activity,
"timeout": room.timeout
"timeout": room.timeout,
"downloads": downloads,
}
@api_endpoints.route('/datapackage')
@cache.cached()
def get_datapackge():
def get_datapackage():
from worlds import network_data_package
return network_data_package
@api_endpoints.route('/datapackage_version')
@cache.cached()
def get_datapackge_versions():
from worlds import network_data_package, AutoWorldRegister
def get_datapackage_versions():
from worlds import AutoWorldRegister
version_package = {game: world.data_version for game, world in AutoWorldRegister.world_types.items()}
version_package["version"] = network_data_package["version"]
return version_package
@api_endpoints.route('/datapackage_checksum')
@cache.cached()
def get_datapackage_checksums():
from worlds import network_data_package
version_package = {
game: game_data["checksum"] for game, game_data in network_data_package["games"].items()
}
return version_package

View File

@@ -1,15 +1,16 @@
import json
import pickle
from uuid import UUID
from . import api_endpoints
from flask import request, session, url_for
from markupsafe import Markup
from pony.orm import commit
from WebHostLib import app, Generation, STATE_QUEUED, Seed, STATE_ERROR
from WebHostLib import app
from WebHostLib.check import get_yaml_data, roll_options
from WebHostLib.generate import get_meta
from WebHostLib.models import Generation, STATE_QUEUED, Seed, STATE_ERROR
from . import api_endpoints
@api_endpoints.route('/generate', methods=['POST'])
@@ -19,15 +20,20 @@ def generate_api():
race = False
meta_options_source = {}
if 'file' in request.files:
file = request.files['file']
options = get_yaml_data(file)
if type(options) == str:
files = request.files.getlist('file')
options = get_yaml_data(files)
if isinstance(options, Markup):
return {"text": options.striptags()}, 400
if isinstance(options, str):
return {"text": options}, 400
if "race" in request.form:
race = bool(0 if request.form["race"] in {"false"} else int(request.form["race"]))
meta_options_source = request.form
json_data = request.get_json()
# json_data is optional, we can have it silently fall to None as it used to do.
# See https://flask.palletsprojects.com/en/2.2.x/api/#flask.Request.get_json -> Changelog -> 2.1
json_data = request.get_json(silent=True)
if json_data:
meta_options_source = json_data
if 'weights' in json_data:
@@ -43,9 +49,8 @@ def generate_api():
if len(options) > app.config["MAX_ROLL"]:
return {"text": "Max size of multiworld exceeded",
"detail": app.config["MAX_ROLL"]}, 409
meta = get_meta(meta_options_source)
meta["race"] = race
results, gen_options = roll_options(options, meta["plando_options"])
meta = get_meta(meta_options_source, race)
results, gen_options = roll_options(options, set(meta["plando_options"]))
if any(type(result) == str for result in results.values()):
return {"text": str(results),
"detail": results}, 400

View File

@@ -1,6 +1,7 @@
from flask import session, jsonify
from pony.orm import select
from WebHostLib.models import *
from WebHostLib.models import Room, Seed
from . import api_endpoints, get_players

View File

@@ -1,75 +1,27 @@
from __future__ import annotations
import logging
import json
import logging
import multiprocessing
from datetime import timedelta, datetime
import concurrent.futures
import sys
import typing
import time
import os
from datetime import timedelta, datetime
from threading import Event, Thread
from uuid import UUID
from pony.orm import db_session, select, commit
from Utils import restricted_loads
from .locker import Locker, AlreadyRunningException
_stop_event = Event()
class CommonLocker():
"""Uses a file lock to signal that something is already running"""
lock_folder = "file_locks"
def __init__(self, lockname: str, folder=None):
if folder:
self.lock_folder = folder
os.makedirs(self.lock_folder, exist_ok=True)
self.lockname = lockname
self.lockfile = os.path.join(self.lock_folder, f"{self.lockname}.lck")
class AlreadyRunningException(Exception):
pass
if sys.platform == 'win32':
class Locker(CommonLocker):
def __enter__(self):
try:
if os.path.exists(self.lockfile):
os.unlink(self.lockfile)
self.fp = os.open(
self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
except OSError as e:
raise AlreadyRunningException() from e
def __exit__(self, _type, value, tb):
fp = getattr(self, "fp", None)
if fp:
os.close(self.fp)
os.unlink(self.lockfile)
else: # unix
import fcntl
class Locker(CommonLocker):
def __enter__(self):
try:
self.fp = open(self.lockfile, "wb")
fcntl.flock(self.fp.fileno(), fcntl.LOCK_EX)
except OSError as e:
raise AlreadyRunningException() from e
def __exit__(self, _type, value, tb):
fcntl.flock(self.fp.fileno(), fcntl.LOCK_UN)
self.fp.close()
def launch_room(room: Room, config: dict):
# requires db_session!
if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout):
multiworld = multiworlds.get(room.id, None)
if not multiworld:
multiworld = MultiworldInstance(room, config)
multiworld.start()
def stop():
"""Stops previously launched threads"""
global _stop_event
stop_event = _stop_event
_stop_event = Event() # new event for new threads
stop_event.set()
def handle_generation_success(seed_id):
@@ -106,33 +58,55 @@ def init_db(pony_config: dict):
db.generate_mapping()
def cleanup():
"""delete unowned user-content"""
with db_session:
# >>> bool(uuid.UUID(int=0))
# True
rooms = Room.select(lambda room: room.owner == UUID(int=0)).delete(bulk=True)
seeds = Seed.select(lambda seed: seed.owner == UUID(int=0) and not seed.rooms).delete(bulk=True)
slots = Slot.select(lambda slot: not slot.seed).delete(bulk=True)
# Command gets deleted by ponyorm Cascade Delete, as Room is Required
if rooms or seeds or slots:
logging.info(f"{rooms} Rooms, {seeds} Seeds and {slots} Slots have been deleted.")
def autohost(config: dict):
def keep_running():
stop_event = _stop_event
try:
with Locker("autohost"):
while 1:
time.sleep(0.1)
cleanup()
hosters = []
for x in range(config["HOSTERS"]):
hoster = MultiworldInstance(config, x)
hosters.append(hoster)
hoster.start()
while not stop_event.wait(0.1):
with db_session:
rooms = select(
room for room in Room if
room.last_activity >= datetime.utcnow() - timedelta(days=3))
for room in rooms:
launch_room(room, config)
# we have to filter twice, as the per-room timeout can't currently be PonyORM transpiled.
if room.last_activity >= datetime.utcnow() - timedelta(seconds=room.timeout + 5):
hosters[room.id.int % len(hosters)].start_room(room.id)
except AlreadyRunningException:
logging.info("Autohost reports as already running, not starting another.")
import threading
threading.Thread(target=keep_running, name="AP_Autohost").start()
Thread(target=keep_running, name="AP_Autohost").start()
def autogen(config: dict):
def keep_running():
stop_event = _stop_event
try:
with Locker("autogen"):
with multiprocessing.Pool(config["GENERATORS"], initializer=init_db,
initargs=(config["PONY"],)) as generator_pool:
initargs=(config["PONY"],), maxtasksperchild=10) as generator_pool:
with db_session:
to_start = select(generation for generation in Generation if generation.state == STATE_STARTED)
@@ -148,54 +122,69 @@ def autogen(config: dict):
commit()
select(generation for generation in Generation if generation.state == STATE_ERROR).delete()
while 1:
time.sleep(0.1)
while not stop_event.wait(0.1):
with db_session:
# for update locks the database row(s) during transaction, preventing writes from elsewhere
to_start = select(
generation for generation in Generation if generation.state == STATE_QUEUED)
generation for generation in Generation
if generation.state == STATE_QUEUED).for_update()
for generation in to_start:
launch_generator(generator_pool, generation)
except AlreadyRunningException:
logging.info("Autogen reports as already running, not starting another.")
import threading
threading.Thread(target=keep_running, name="AP_Autogen").start()
Thread(target=keep_running, name="AP_Autogen").start()
multiworlds = {}
guardians = concurrent.futures.ThreadPoolExecutor(2, thread_name_prefix="Guardian")
multiworlds: typing.Dict[type(Room.id), MultiworldInstance] = {}
class MultiworldInstance():
def __init__(self, room: Room, config: dict):
self.room_id = room.id
def __init__(self, config: dict, id: int):
self.room_ids = set()
self.process: typing.Optional[multiprocessing.Process] = None
multiworlds[self.room_id] = self
self.ponyconfig = config["PONY"]
self.cert = config["SELFLAUNCHCERT"]
self.key = config["SELFLAUNCHKEY"]
self.host = config["HOST_ADDRESS"]
self.rooms_to_start = multiprocessing.Queue()
self.rooms_shutting_down = multiprocessing.Queue()
self.name = f"MultiHoster{id}"
def start(self):
if self.process and self.process.is_alive():
return False
logging.info(f"Spinning up {self.room_id}")
self.process = multiprocessing.Process(group=None, target=run_server_process,
args=(self.room_id, self.ponyconfig),
name="MultiHost")
self.process.start()
self.guardian = guardians.submit(self._collect)
process = multiprocessing.Process(group=None, target=run_server_process,
args=(self.name, self.ponyconfig, get_static_server_data(),
self.cert, self.key, self.host,
self.rooms_to_start, self.rooms_shutting_down),
name=self.name)
process.start()
self.process = process
def start_room(self, room_id):
while not self.rooms_shutting_down.empty():
self.room_ids.remove(self.rooms_shutting_down.get(block=True, timeout=None))
if room_id in self.room_ids:
pass # should already be hosted currently.
else:
self.room_ids.add(room_id)
self.rooms_to_start.put(room_id)
def stop(self):
if self.process:
self.process.terminate()
self.process = None
def _collect(self):
def done(self):
return self.process and not self.process.is_alive()
def collect(self):
self.process.join() # wait for process to finish
self.process = None
self.guardian = None
from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed
from .customserver import run_server_process
from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed, Slot
from .customserver import run_server_process, get_static_server_data
from .generate import gen_game

View File

@@ -1,18 +1,15 @@
import os
import zipfile
from typing import *
import base64
from typing import Union, Dict, Set, Tuple
from flask import request, flash, redirect, url_for, session, render_template
from flask import request, flash, redirect, url_for, render_template
from markupsafe import Markup
from WebHostLib import app
from WebHostLib.upload import allowed_options, allowed_options_extensions, banned_file
banned_zip_contents = (".sfc",)
def allowed_file(filename):
return filename.endswith(('.txt', ".yaml", ".zip"))
from Generate import roll_settings
from Generate import roll_settings, PlandoOptions
from Utils import parse_yamls
@@ -23,13 +20,21 @@ def check():
if 'file' not in request.files:
flash('No file part')
else:
file = request.files['file']
options = get_yaml_data(file)
if type(options) == str:
files = request.files.getlist('file')
options = get_yaml_data(files)
if isinstance(options, str):
flash(options)
else:
results, _ = roll_options(options)
return render_template("checkResult.html", results=results)
if len(options) > 1:
# offer combined file back
combined_yaml = "\n---\n".join(f"# original filename: {file_name}\n{file_content.decode('utf-8-sig')}"
for file_name, file_content in options.items())
combined_yaml = base64.b64encode(combined_yaml.encode("utf-8-sig")).decode()
else:
combined_yaml = ""
return render_template("checkResult.html",
results=results, combined_yaml=combined_yaml)
return render_template("check.html")
@@ -38,34 +43,51 @@ def mysterycheck():
return redirect(url_for("check"), 301)
def get_yaml_data(file) -> Union[Dict[str, str], str]:
def get_yaml_data(files) -> Union[Dict[str, str], str, Markup]:
options = {}
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
return 'No selected file'
elif file and allowed_file(file.filename):
if file.filename.endswith(".zip"):
for uploaded_file in files:
if banned_file(uploaded_file.filename):
return ("Uploaded data contained a rom file, which is likely to contain copyrighted material. "
"Your file was deleted.")
# If the user does not select file, the browser will still submit an empty string without a file name.
elif uploaded_file.filename == "":
return "No selected file."
elif uploaded_file.filename in options:
return f"Conflicting files named {uploaded_file.filename} submitted."
elif uploaded_file and allowed_options(uploaded_file.filename):
if uploaded_file.filename.endswith(".zip"):
if not zipfile.is_zipfile(uploaded_file):
return f"Uploaded file {uploaded_file.filename} is not a valid .zip file and cannot be opened."
with zipfile.ZipFile(file, 'r') as zfile:
infolist = zfile.infolist()
uploaded_file.seek(0) # offset from is_zipfile check
with zipfile.ZipFile(uploaded_file, "r") as zfile:
for file in zfile.infolist():
# Remove folder pathing from str (e.g. "__MACOSX/" folder paths from archives created by macOS).
base_filename = os.path.basename(file.filename)
if base_filename.endswith(".archipelago"):
return Markup("Error: Your .zip file contains an .archipelago file. "
'Did you mean to <a href="/uploads">host a game</a>?')
elif base_filename.endswith(".zip"):
return "Nested .zip files inside a .zip are not supported."
elif banned_file(base_filename):
return ("Uploaded data contained a rom file, which is likely to contain copyrighted "
"material. Your file was deleted.")
# Ignore dot-files.
elif not base_filename.startswith(".") and allowed_options(base_filename):
options[file.filename] = zfile.open(file, "r").read()
else:
options[uploaded_file.filename] = uploaded_file.read()
for file in infolist:
if file.filename.endswith(banned_zip_contents):
return "Uploaded data contained a rom file, which is likely to contain copyrighted material. Your file was deleted."
elif file.filename.endswith((".yaml", ".json", ".yml", ".txt")):
options[file.filename] = zfile.open(file, "r").read()
else:
options = {file.filename: file.read()}
if not options:
return "Did not find a .yaml file to process."
return f"Did not find any valid files to process. Accepted formats: {allowed_options_extensions}"
return options
def roll_options(options: Dict[str, Union[dict, str]],
plando_options: Set[str] = frozenset({"bosses", "items", "connections", "texts"})) -> \
Tuple[Dict[str, Union[str, bool]], Dict[str, dict]]:
plando_options = set(plando_options)
plando_options = PlandoOptions.from_set(set(plando_options))
results = {}
rolled_results = {}
for filename, text in options.items():
@@ -86,7 +108,10 @@ def roll_options(options: Dict[str, Union[dict, str]],
rolled_results[f"{filename}/{i + 1}"] = roll_settings(yaml_data,
plando_options=plando_options)
except Exception as e:
results[filename] = f"Failed to generate mystery in {filename}: {e}"
if e.__cause__:
results[filename] = f"Failed to generate options in {filename}: {e} - {e.__cause__}"
else:
results[filename] = f"Failed to generate options in {filename}: {e}"
else:
results[filename] = True
return results, rolled_results

View File

@@ -1,27 +1,37 @@
from __future__ import annotations
import asyncio
import collections
import datetime
import functools
import logging
import websockets
import asyncio
import multiprocessing
import pickle
import random
import socket
import threading
import time
import random
import pickle
import typing
import sys
import websockets
from pony.orm import commit, db_session, select
import Utils
from .models import *
from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor
from Utils import get_public_ipv4, get_public_ipv6, restricted_loads
from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor, load_server_cert
from Utils import restricted_loads, cache_argsless
from .locker import Locker
from .models import Command, GameDataPackage, Room, db
class CustomClientMessageProcessor(ClientMessageProcessor):
ctx: WebHostContext
def _cmd_video(self, platform, user):
"""Set a link for your name in the WebHostLib tracker pointing to a video stream"""
def _cmd_video(self, platform: str, user: str):
"""Set a link for your name in the WebHostLib tracker pointing to a video stream.
Currently, only YouTube and Twitch platforms are supported.
"""
if platform.lower().startswith("t"): # twitch
self.ctx.video[self.client.team, self.client.slot] = "Twitch", user
self.ctx.save()
@@ -39,21 +49,35 @@ class CustomClientMessageProcessor(ClientMessageProcessor):
import MultiServer
MultiServer.client_message_processor = CustomClientMessageProcessor
del (MultiServer)
del MultiServer
class DBCommandProcessor(ServerCommandProcessor):
def output(self, text: str):
logging.info(text)
self.ctx.logger.info(text)
class WebHostContext(Context):
def __init__(self):
super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", "enabled", 0, 2)
room_id: int
def __init__(self, static_server_data: dict, logger: logging.Logger):
# static server data is used during _load_game_data to load required data,
# without needing to import worlds system, which takes quite a bit of memory
self.static_server_data = static_server_data
super(WebHostContext, self).__init__("", 0, "", "", 1,
40, True, "enabled", "enabled",
"enabled", 0, 2, logger=logger)
del self.static_server_data
self.main_loop = asyncio.get_running_loop()
self.video = {}
self.tags = ["AP", "WebHost"]
def _load_game_data(self):
for key, value in self.static_server_data.items():
# NOTE: attributes are mutable and shared, so they will have to be copied before being modified
setattr(self, key, value)
self.non_hintable_names = collections.defaultdict(frozenset, self.non_hintable_names)
def listen_to_db_commands(self):
cmdprocessor = DBCommandProcessor(self)
@@ -76,7 +100,40 @@ class WebHostContext(Context):
else:
self.port = get_random_port()
return self._load(self.decompress(room.seed.multidata), True)
multidata = self.decompress(room.seed.multidata)
game_data_packages = {}
static_gamespackage = self.gamespackage # this is shared across all rooms
static_item_name_groups = self.item_name_groups
static_location_name_groups = self.location_name_groups
self.gamespackage = {"Archipelago": static_gamespackage["Archipelago"]} # this may be modified by _load
self.item_name_groups = {}
self.location_name_groups = {}
for game in list(multidata.get("datapackage", {})):
game_data = multidata["datapackage"][game]
if "checksum" in game_data:
if static_gamespackage.get(game, {}).get("checksum") == game_data["checksum"]:
# non-custom. remove from multidata and use static data
# games package could be dropped from static data once all rooms embed data package
del multidata["datapackage"][game]
else:
row = GameDataPackage.get(checksum=game_data["checksum"])
if row: # None if rolled on >= 0.3.9 but uploaded to <= 0.3.8. multidata should be complete
game_data_packages[game] = Utils.restricted_loads(row.data)
continue
else:
self.logger.warning(f"Did not find game_data_package for {game}: {game_data['checksum']}")
self.gamespackage[game] = static_gamespackage.get(game, {})
self.item_name_groups[game] = static_item_name_groups.get(game, {})
self.location_name_groups[game] = static_location_name_groups.get(game, {})
if not game_data_packages:
# all static -> use the static dicts directly
self.gamespackage = static_gamespackage
self.item_name_groups = static_item_name_groups
self.location_name_groups = static_location_name_groups
return self._load(multidata, game_data_packages, True)
@db_session
def init_save(self, enabled: bool = True):
@@ -85,7 +142,7 @@ class WebHostContext(Context):
savegame_data = Room.get(id=self.room_id).multisave
if savegame_data:
self.set_save(restricted_loads(Room.get(id=self.room_id).multisave))
self._start_async_saving()
self._start_async_saving(atexit_save=False)
threading.Thread(target=self.listen_to_db_commands, daemon=True).start()
@db_session
@@ -94,7 +151,7 @@ class WebHostContext(Context):
room.multisave = pickle.dumps(self.get_save())
# saving only occurs on activity, so we can "abuse" this information to mark this as last_activity
if not exit_save: # we don't want to count a shutdown as activity, which would restart the server again
room.last_activity = datetime.utcnow()
room.last_activity = datetime.datetime.utcnow()
return True
def get_save(self) -> dict:
@@ -107,45 +164,143 @@ def get_random_port():
return random.randint(49152, 65535)
def run_server_process(room_id, ponyconfig: dict):
@cache_argsless
def get_static_server_data() -> dict:
import worlds
data = {
"non_hintable_names": {},
"gamespackage": worlds.network_data_package["games"],
"item_name_groups": {world_name: world.item_name_groups for world_name, world in
worlds.AutoWorldRegister.world_types.items()},
"location_name_groups": {world_name: world.location_name_groups for world_name, world in
worlds.AutoWorldRegister.world_types.items()},
}
for world_name, world in worlds.AutoWorldRegister.world_types.items():
data["non_hintable_names"][world_name] = world.hint_blacklist
return data
def set_up_logging(room_id) -> logging.Logger:
import os
# logger setup
logger = logging.getLogger(f"RoomLogger {room_id}")
# this *should* be empty, but just in case.
for handler in logger.handlers[:]:
logger.removeHandler(handler)
handler.close()
file_handler = logging.FileHandler(
os.path.join(Utils.user_path("logs"), f"{room_id}.txt"),
"a",
encoding="utf-8-sig")
file_handler.setFormatter(logging.Formatter("[%(asctime)s]: %(message)s"))
logger.setLevel(logging.INFO)
logger.addHandler(file_handler)
return logger
def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
cert_file: typing.Optional[str], cert_key_file: typing.Optional[str],
host: str, rooms_to_run: multiprocessing.Queue, rooms_shutting_down: multiprocessing.Queue):
Utils.init_logging(name)
try:
import resource
except ModuleNotFoundError:
pass # unix only module
else:
# Each Server is another file handle, so request as many as we can from the system
file_limit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
# set soft limit to hard limit
resource.setrlimit(resource.RLIMIT_NOFILE, (file_limit, file_limit))
del resource, file_limit
# establish DB connection for multidata and multisave
db.bind(**ponyconfig)
db.generate_mapping(check_tables=False)
async def main():
Utils.init_logging(str(room_id), write_mode="a")
ctx = WebHostContext()
ctx.load(room_id)
ctx.init_save()
if "worlds" in sys.modules:
raise Exception("Worlds system should not be loaded in the custom server.")
try:
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, ctx.port, ping_timeout=None,
ping_interval=None)
import gc
ssl_context = load_server_cert(cert_file, cert_key_file) if cert_file else None
del cert_file, cert_key_file, ponyconfig
gc.collect() # free intermediate objects used during setup
await ctx.server
except Exception: # likely port in use - in windows this is OSError, but I didn't check the others
ctx.server = websockets.serve(functools.partial(server, ctx=ctx), ctx.host, 0, ping_timeout=None,
ping_interval=None)
loop = asyncio.get_event_loop()
await ctx.server
for wssocket in ctx.server.ws_server.sockets:
socketname = wssocket.getsockname()
if wssocket.family == socket.AF_INET6:
logging.info(f'Hosting game at [{get_public_ipv6()}]:{socketname[1]}')
async def start_room(room_id):
with Locker(f"RoomLocker {room_id}"):
try:
logger = set_up_logging(room_id)
ctx = WebHostContext(static_server_data, logger)
ctx.load(room_id)
ctx.init_save()
try:
ctx.server = websockets.serve(
functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
await ctx.server
except OSError: # likely port in use
ctx.server = websockets.serve(
functools.partial(server, ctx=ctx), ctx.host, 0, ssl=ssl_context)
await ctx.server
port = 0
for wssocket in ctx.server.ws_server.sockets:
socketname = wssocket.getsockname()
if wssocket.family == socket.AF_INET6:
# Prefer IPv4, as most users seem to not have working ipv6 support
if not port:
port = socketname[1]
elif wssocket.family == socket.AF_INET:
port = socketname[1]
if port:
ctx.logger.info(f'Hosting game at {host}:{port}')
with db_session:
room = Room.get(id=ctx.room_id)
room.last_port = port
else:
ctx.logger.exception("Could not determine port. Likely hosting failure.")
with db_session:
room = Room.get(id=ctx.room_id)
room.last_port = socketname[1]
elif wssocket.family == socket.AF_INET:
logging.info(f'Hosting game at {get_public_ipv4()}:{socketname[1]}')
with db_session:
ctx.auto_shutdown = Room.get(id=room_id).timeout
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
await ctx.shutdown_task
logging.info("Shutting down")
ctx.auto_shutdown = Room.get(id=room_id).timeout
ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
await ctx.shutdown_task
from .autolauncher import Locker
with Locker(room_id):
asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
if ctx.saving:
ctx._save()
except Exception as e:
with db_session:
room = Room.get(id=room_id)
room.last_port = -1
logger.exception(e)
raise
else:
if ctx.saving:
ctx._save()
finally:
try:
with (db_session):
# ensure the Room does not spin up again on its own, minute of safety buffer
room = Room.get(id=room_id)
room.last_activity = datetime.datetime.utcnow() - \
datetime.timedelta(minutes=1, seconds=room.timeout)
logging.info(f"Shutting down room {room_id} on {name}.")
finally:
await asyncio.sleep(5)
rooms_shutting_down.put(room_id)
class Starter(threading.Thread):
def run(self):
while 1:
next_room = rooms_to_run.get(block=True, timeout=None)
asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
logging.info(f"Starting room {next_room} on {name}.")
from WebHostLib import LOGS_FOLDER
starter = Starter()
starter.daemon = True
starter.start()
loop.run_forever()

View File

@@ -1,12 +1,13 @@
import zipfile
import json
import zipfile
from io import BytesIO
from flask import send_file, Response, render_template
from pony.orm import select
from Patch import update_patch_data, preferred_endings, AutoPatchRegister
from WebHostLib import app, Slot, Room, Seed, cache
from worlds.Files import AutoPatchRegister
from . import app, cache
from .models import Slot, Room, Seed
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
@@ -25,25 +26,23 @@ def download_patch(room_id, patch_id):
with zipfile.ZipFile(filelike, "a") as zf:
with zf.open("archipelago.json", "r") as f:
manifest = json.load(f)
manifest["server"] = f"{app.config['PATCH_TARGET']}:{last_port}"
manifest["server"] = f"{app.config['HOST_ADDRESS']}:{last_port}" if last_port else None
with zipfile.ZipFile(new_file, "w") as new_zip:
for file in zf.infolist():
if file.filename == "archipelago.json":
new_zip.writestr("archipelago.json", json.dumps(manifest))
else:
new_zip.writestr(file.filename, zf.read(file), file.compress_type, 9)
if "patch_file_ending" in manifest:
patch_file_ending = manifest["patch_file_ending"]
else:
patch_file_ending = AutoPatchRegister.patch_types[patch.game].patch_file_ending
fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](room_id)}" \
f"{AutoPatchRegister.patch_types[patch.game].patch_file_ending}"
f"{patch_file_ending}"
new_file.seek(0)
return send_file(new_file, as_attachment=True, attachment_filename=fname)
return send_file(new_file, as_attachment=True, download_name=fname)
else:
patch_data = update_patch_data(patch.data, server=f"{app.config['PATCH_TARGET']}:{last_port}")
patch_data = BytesIO(patch_data)
fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](room_id)}." \
f"{preferred_endings[patch.game]}"
return send_file(patch_data, as_attachment=True, attachment_filename=fname)
return "Old Patch file, no longer compatible."
@app.route("/dl_spoiler/<suuid:seed_id>")
@@ -55,7 +54,7 @@ def download_spoiler(seed_id):
def download_slot_file(room_id, player_id: int):
room = Room.get(id=room_id)
slot_data: Slot = select(patch for patch in room.seed.slots if
patch.player_id == player_id).first()
patch.player_id == player_id).first()
if not slot_data:
return "Slot Data not found"
@@ -65,22 +64,38 @@ def download_slot_file(room_id, player_id: int):
if slot_data.game == "Minecraft":
from worlds.minecraft import mc_update_output
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.apmc"
data = mc_update_output(slot_data.data, server=app.config['PATCH_TARGET'], port=room.last_port)
return send_file(io.BytesIO(data), as_attachment=True, attachment_filename=fname)
data = mc_update_output(slot_data.data, server=app.config['HOST_ADDRESS'], port=room.last_port)
return send_file(io.BytesIO(data), as_attachment=True, download_name=fname)
elif slot_data.game == "Factorio":
with zipfile.ZipFile(io.BytesIO(slot_data.data)) as zf:
for name in zf.namelist():
if name.endswith("info.json"):
fname = name.rsplit("/", 1)[0]+".zip"
fname = name.rsplit("/", 1)[0] + ".zip"
elif slot_data.game == "Ocarina of Time":
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.apz5"
stream = io.BytesIO(slot_data.data)
if zipfile.is_zipfile(stream):
with zipfile.ZipFile(stream) as zf:
for name in zf.namelist():
if name.endswith(".zpf"):
fname = name.rsplit(".", 1)[0] + ".apz5"
else: # pre-ootr-7.0 support
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.apz5"
elif slot_data.game == "VVVVVV":
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_SP.apv6"
elif slot_data.game == "Zillion":
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_SP.apzl"
elif slot_data.game == "Super Mario 64":
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_SP.apsm64ex"
elif slot_data.game == "Dark Souls III":
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}.json"
elif slot_data.game == "Kingdom Hearts 2":
fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.zip"
elif slot_data.game == "Final Fantasy Mystic Quest":
fname = f"AP+{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.apmq"
else:
return "Game download not supported."
return send_file(io.BytesIO(slot_data.data), as_attachment=True, attachment_filename=fname)
return send_file(io.BytesIO(slot_data.data), as_attachment=True, download_name=fname)
@app.route("/templates")
@cache.cached()
@@ -90,4 +105,4 @@ def list_yaml_templates():
for world_name, world in AutoWorldRegister.world_types.items():
if not world.hidden:
files.append(world_name)
return render_template("templates.html", files=files)
return render_template("templates.html", files=files)

View File

@@ -1,27 +1,28 @@
import os
import tempfile
import random
import concurrent.futures
import json
import os
import pickle
import random
import tempfile
import zipfile
from collections import Counter
from typing import Dict, Optional as TypeOptional
from Utils import __version__
from typing import Any, Dict, List, Optional, Union
from flask import request, flash, redirect, url_for, session, render_template
from flask import flash, redirect, render_template, request, session, url_for
from pony.orm import commit, db_session
from worlds.alttp.EntranceRandomizer import parse_arguments
from BaseClasses import get_seed, seeddigits
from Generate import PlandoOptions, handle_name
from Main import main as ERmain
from BaseClasses import seeddigits, get_seed
from Generate import handle_name
import pickle
from .models import *
from Utils import __version__
from WebHostLib import app
from worlds.alttp.EntranceRandomizer import parse_arguments
from .check import get_yaml_data, roll_options
from .models import Generation, STATE_ERROR, STATE_QUEUED, Seed, UUID
from .upload import upload_zip_to_db
def get_meta(options_source: dict) -> dict:
def get_meta(options_source: dict, race: bool = False) -> Dict[str, Union[List[str], Dict[str, Any]]]:
plando_options = {
options_source.get("plando_bosses", ""),
options_source.get("plando_items", ""),
@@ -30,16 +31,29 @@ def get_meta(options_source: dict) -> dict:
}
plando_options -= {""}
meta = {
server_options = {
"hint_cost": int(options_source.get("hint_cost", 10)),
"forfeit_mode": options_source.get("forfeit_mode", "goal"),
"release_mode": options_source.get("release_mode", "goal"),
"remaining_mode": options_source.get("remaining_mode", "disabled"),
"collect_mode": options_source.get("collect_mode", "disabled"),
"item_cheat": bool(int(options_source.get("item_cheat", 1))),
"server_password": options_source.get("server_password", None),
"plando_options": list(plando_options)
}
return meta
generator_options = {
"spoiler": int(options_source.get("spoiler", 0)),
"race": race
}
if race:
server_options["item_cheat"] = False
server_options["remaining_mode"] = "disabled"
generator_options["spoiler"] = 0
return {
"server_options": server_options,
"plando_options": list(plando_options),
"generator_options": generator_options,
}
@app.route('/generate', methods=['GET', 'POST'])
@@ -50,77 +64,78 @@ def generate(race=False):
if 'file' not in request.files:
flash('No file part')
else:
file = request.files['file']
options = get_yaml_data(file)
if type(options) == str:
files = request.files.getlist('file')
options = get_yaml_data(files)
if isinstance(options, str):
flash(options)
else:
meta = get_meta(request.form)
meta["race"] = race
results, gen_options = roll_options(options, meta["plando_options"])
if race:
meta["item_cheat"] = False
meta["remaining_mode"] = "disabled"
if any(type(result) == str for result in results.values()):
return render_template("checkResult.html", results=results)
elif len(gen_options) > app.config["MAX_ROLL"]:
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players for now. "
f"If you have a larger group, please generate it yourself and upload it.")
elif len(gen_options) >= app.config["JOB_THRESHOLD"]:
gen = Generation(
options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}),
# convert to json compatible
meta=json.dumps(meta),
state=STATE_QUEUED,
owner=session["_id"])
commit()
return redirect(url_for("wait_seed", seed=gen.id))
else:
try:
seed_id = gen_game({name: vars(options) for name, options in gen_options.items()},
meta=meta, owner=session["_id"].int)
except BaseException as e:
from .autolauncher import handle_generation_failure
handle_generation_failure(e)
return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": " + str(e)))
return redirect(url_for("view_seed", seed=seed_id))
meta = get_meta(request.form, race)
return start_generation(options, meta)
return render_template("generate.html", race=race, version=__version__)
def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=None, sid=None):
if not meta:
meta: Dict[str, object] = {}
def start_generation(options: Dict[str, Union[dict, str]], meta: Dict[str, Any]):
results, gen_options = roll_options(options, set(meta["plando_options"]))
meta.setdefault("hint_cost", 10)
race = meta.get("race", False)
del (meta["race"])
plando_options = meta.get("plando", {"bosses", "items", "connections", "texts"})
del (meta["plando_options"])
try:
if any(type(result) == str for result in results.values()):
return render_template("checkResult.html", results=results)
elif len(gen_options) > app.config["MAX_ROLL"]:
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. "
f"If you have a larger group, please generate it yourself and upload it.")
elif len(gen_options) >= app.config["JOB_THRESHOLD"]:
gen = Generation(
options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}),
# convert to json compatible
meta=json.dumps(meta),
state=STATE_QUEUED,
owner=session["_id"])
commit()
return redirect(url_for("wait_seed", seed=gen.id))
else:
try:
seed_id = gen_game({name: vars(options) for name, options in gen_options.items()},
meta=meta, owner=session["_id"].int)
except BaseException as e:
from .autolauncher import handle_generation_failure
handle_generation_failure(e)
return render_template("seedError.html", seed_error=(e.__class__.__name__ + ": " + str(e)))
return redirect(url_for("view_seed", seed=seed_id))
def gen_game(gen_options: dict, meta: Optional[Dict[str, Any]] = None, owner=None, sid=None):
if not meta:
meta: Dict[str, Any] = {}
meta.setdefault("server_options", {}).setdefault("hint_cost", 10)
race = meta.setdefault("generator_options", {}).setdefault("race", False)
def task():
target = tempfile.TemporaryDirectory()
playercount = len(gen_options)
seed = get_seed()
random.seed(seed)
if race:
random.seed() # reset to time-based random source
random.seed() # use time-based random source
else:
random.seed(seed)
seedname = "W" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits))
erargs = parse_arguments(['--multi', str(playercount)])
erargs.seed = seed
erargs.name = {x: "" for x in range(1, playercount + 1)} # only so it can be overwrittin in mystery
erargs.spoiler = 0 if race else 2
erargs.name = {x: "" for x in range(1, playercount + 1)} # only so it can be overwritten in mystery
erargs.spoiler = meta["generator_options"].get("spoiler", 0)
erargs.race = race
erargs.outputname = seedname
erargs.outputpath = target.name
erargs.teams = 1
erargs.plando_options = ", ".join(plando_options)
erargs.plando_options = PlandoOptions.from_set(meta.setdefault("plando_options",
{"bosses", "items", "connections", "texts"}))
erargs.skip_prog_balancing = False
erargs.skip_output = False
name_counter = Counter()
for player, (playerfile, settings) in enumerate(gen_options.items(), 1):
@@ -136,9 +151,26 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
if len(set(erargs.name.values())) != len(erargs.name):
raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}")
ERmain(erargs, seed, baked_server_options=meta)
ERmain(erargs, seed, baked_server_options=meta["server_options"])
return upload_to_db(target.name, sid, owner, race)
thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)
thread = thread_pool.submit(task)
try:
return thread.result(app.config["JOB_TIME"])
except concurrent.futures.TimeoutError as e:
if sid:
with db_session:
gen = Generation.get(id=sid)
if gen is not None:
gen.state = STATE_ERROR
meta = json.loads(gen.meta)
meta["error"] = (
"Allowed time for Generation exceeded, please consider generating locally instead. " +
e.__class__.__name__ + ": " + str(e))
gen.meta = json.dumps(meta)
commit()
except BaseException as e:
if sid:
with db_session:
@@ -148,7 +180,6 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
meta = json.loads(gen.meta)
meta["error"] = (e.__class__.__name__ + ": " + str(e))
gen.meta = json.dumps(meta)
commit()
raise

View File

@@ -1,7 +1,11 @@
from datetime import timedelta, datetime
from flask import render_template
from pony.orm import count
from WebHostLib import app, cache
from .models import *
from datetime import timedelta
from .models import Room, Seed
@app.route('/', methods=['GET', 'POST'])
@cache.cached(timeout=300) # cache has to appear under app route for caching to work

51
WebHostLib/locker.py Normal file
View File

@@ -0,0 +1,51 @@
import os
import sys
class CommonLocker:
"""Uses a file lock to signal that something is already running"""
lock_folder = "file_locks"
def __init__(self, lockname: str, folder=None):
if folder:
self.lock_folder = folder
os.makedirs(self.lock_folder, exist_ok=True)
self.lockname = lockname
self.lockfile = os.path.join(self.lock_folder, f"{self.lockname}.lck")
class AlreadyRunningException(Exception):
pass
if sys.platform == 'win32':
class Locker(CommonLocker):
def __enter__(self):
try:
if os.path.exists(self.lockfile):
os.unlink(self.lockfile)
self.fp = os.open(
self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
except OSError as e:
raise AlreadyRunningException() from e
def __exit__(self, _type, value, tb):
fp = getattr(self, "fp", None)
if fp:
os.close(self.fp)
os.unlink(self.lockfile)
else: # unix
import fcntl
class Locker(CommonLocker):
def __enter__(self):
try:
self.fp = open(self.lockfile, "wb")
fcntl.flock(self.fp.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as e:
raise AlreadyRunningException() from e
def __exit__(self, _type, value, tb):
fcntl.flock(self.fp.fileno(), fcntl.LOCK_UN)
self.fp.close()

View File

@@ -32,7 +32,7 @@ def update_sprites_lttp():
spriteData = []
for file in os.listdir(input_dir):
for file in (file for file in os.listdir(input_dir) if not file.startswith(".")):
sprite = Sprite(os.path.join(input_dir, file))
if not sprite.name:

174
WebHostLib/misc.py Normal file
View File

@@ -0,0 +1,174 @@
import datetime
import os
from typing import List, Dict, Union
import jinja2.exceptions
from flask import request, redirect, url_for, render_template, Response, session, abort, send_from_directory
from pony.orm import count, commit, db_session
from worlds.AutoWorld import AutoWorldRegister
from . import app, cache
from .models import Seed, Room, Command, UUID, uuid4
def get_world_theme(game_name: str):
if game_name in AutoWorldRegister.world_types:
return AutoWorldRegister.world_types[game_name].web.theme
return 'grass'
@app.before_request
def register_session():
session.permanent = True # technically 31 days after the last visit
if not session.get("_id", None):
session["_id"] = uuid4() # uniquely identify each session without needing a login
@app.errorhandler(404)
@app.errorhandler(jinja2.exceptions.TemplateNotFound)
def page_not_found(err):
return render_template('404.html'), 404
# Start Playing Page
@app.route('/start-playing')
@cache.cached()
def start_playing():
return render_template(f"startPlaying.html")
# Game Info Pages
@app.route('/games/<string:game>/info/<string:lang>')
@cache.cached()
def game_info(game, lang):
return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
# List of supported games
@app.route('/games')
@cache.cached()
def games():
worlds = {}
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
worlds[game] = world
return render_template("supportedGames.html", worlds=worlds)
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
@cache.cached()
def tutorial(game, file, lang):
return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))
@app.route('/tutorial/')
@cache.cached()
def tutorial_landing():
return render_template("tutorialLanding.html")
@app.route('/faq/<string:lang>/')
@cache.cached()
def faq(lang):
return render_template("faq.html", lang=lang)
@app.route('/glossary/<string:lang>/')
@cache.cached()
def terms(lang):
return render_template("glossary.html", lang=lang)
@app.route('/seed/<suuid:seed>')
def view_seed(seed: UUID):
seed = Seed.get(id=seed)
if not seed:
abort(404)
return render_template("viewSeed.html", seed=seed, slot_count=count(seed.slots))
@app.route('/new_room/<suuid:seed>')
def new_room(seed: UUID):
seed = Seed.get(id=seed)
if not seed:
abort(404)
room = Room(seed=seed, owner=session["_id"], tracker=uuid4())
commit()
return redirect(url_for("host_room", room=room.id))
def _read_log(path: str):
if os.path.exists(path):
with open(path, encoding="utf-8-sig") as log:
yield from log
else:
yield f"Logfile {path} does not exist. " \
f"Likely a crash during spinup of multiworld instance or it is still spinning up."
@app.route('/log/<suuid:room>')
def display_log(room: UUID):
room = Room.get(id=room)
if room is None:
return abort(404)
if room.owner == session["_id"]:
file_path = os.path.join("logs", str(room.id) + ".txt")
if os.path.exists(file_path):
return Response(_read_log(file_path), mimetype="text/plain;charset=UTF-8")
return "Log File does not exist."
return "Access Denied", 403
@app.route('/room/<suuid:room>', methods=['GET', 'POST'])
def host_room(room: UUID):
room: Room = Room.get(id=room)
if room is None:
return abort(404)
if request.method == "POST":
if room.owner == session["_id"]:
cmd = request.form["cmd"]
if cmd:
Command(room=room, commandtext=cmd)
commit()
return redirect(url_for("host_room", room=room.id))
now = datetime.datetime.utcnow()
# indicate that the page should reload to get the assigned port
should_refresh = not room.last_port and now - room.creation_time < datetime.timedelta(seconds=3)
with db_session:
room.last_activity = now # will trigger a spinup, if it's not already running
return render_template("hostRoom.html", room=room, should_refresh=should_refresh)
@app.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(app.root_path, "static", "static"),
'favicon.ico', mimetype='image/vnd.microsoft.icon')
@app.route('/discord')
def discord():
return redirect("https://discord.gg/8Z65BR2")
@app.route('/datapackage')
@cache.cached()
def get_datapackage():
"""A pretty print version of /api/datapackage"""
from worlds import network_data_package
import json
return Response(json.dumps(network_data_package, indent=4), mimetype="text/plain")
@app.route('/index')
@app.route('/sitemap')
@cache.cached()
def get_sitemap():
available_games: List[Dict[str, Union[str, bool]]] = []
for game, world in AutoWorldRegister.world_types.items():
if not world.hidden:
has_settings: bool = isinstance(world.web.options_page, bool) and world.web.options_page
available_games.append({ 'title': game, 'has_settings': has_settings })
return render_template("siteMap.html", games=available_games)

View File

@@ -1,6 +1,6 @@
from datetime import datetime
from uuid import UUID, uuid4
from pony.orm import *
from pony.orm import Database, PrimaryKey, Required, Set, Optional, buffer, LongStr
db = Database()
@@ -21,14 +21,15 @@ class Slot(db.Entity):
class Room(db.Entity):
id = PrimaryKey(UUID, default=uuid4)
last_activity = Required(datetime, default=lambda: datetime.utcnow(), index=True)
creation_time = Required(datetime, default=lambda: datetime.utcnow())
creation_time = Required(datetime, default=lambda: datetime.utcnow(), index=True) # index used by landing page
owner = Required(UUID, index=True)
commands = Set('Command')
seed = Required('Seed', index=True)
multisave = Optional(buffer, lazy=True)
show_spoiler = Required(int, default=0) # 0 -> never, 1 -> after completion, -> 2 always
timeout = Required(int, default=lambda: 6 * 60 * 60) # seconds since last activity to shutdown
timeout = Required(int, default=lambda: 2 * 60 * 60) # seconds since last activity to shutdown
tracker = Optional(UUID, index=True)
# Port special value -1 means the server errored out. Another attempt can be made with a page refresh
last_port = Optional(int, default=lambda: 0)
@@ -37,7 +38,7 @@ class Seed(db.Entity):
rooms = Set(Room)
multidata = Required(bytes, lazy=True)
owner = Required(UUID, index=True)
creation_time = Required(datetime, default=lambda: datetime.utcnow())
creation_time = Required(datetime, default=lambda: datetime.utcnow(), index=True) # index used by landing page
slots = Set(Slot)
spoiler = Optional(LongStr, lazy=True)
meta = Required(LongStr, default=lambda: "{\"race\": false}") # additional meta information/tags
@@ -55,3 +56,8 @@ class Generation(db.Entity):
options = Required(buffer, lazy=True)
meta = Required(LongStr, default=lambda: "{\"race\": false}")
state = Required(int, default=0, index=True)
class GameDataPackage(db.Entity):
checksum = PrimaryKey(str)
data = Required(bytes)

View File

@@ -1,143 +1,226 @@
import logging
import os
from Utils import __version__
from jinja2 import Template
import yaml
import collections.abc
import json
import os
from textwrap import dedent
from typing import Dict, Union
import yaml
from flask import redirect, render_template, request, Response
from worlds.AutoWorld import AutoWorldRegister
import Options
target_folder = os.path.join("WebHostLib", "static", "generated")
handled_in_js = {"start_inventory", "local_items", "non_local_items", "start_hints", "start_location_hints",
"exclude_locations"}
from Utils import local_path
from worlds.AutoWorld import AutoWorldRegister
from . import app, cache
def create():
os.makedirs(os.path.join(target_folder, 'configs'), exist_ok=True)
def create() -> None:
target_folder = local_path("WebHostLib", "static", "generated")
yaml_folder = os.path.join(target_folder, "configs")
def dictify_range(option):
data = {option.range_start: 0, option.range_end: 0, "random": 0, "random-low": 0, "random-high": 0,
option.default: 50}
notes = {
option.range_start: "minimum value",
option.range_end: "maximum value"
}
return data, notes
Options.generate_yaml_templates(yaml_folder)
def default_converter(default_value):
if isinstance(default_value, (set, frozenset)):
return list(default_value)
return default_value
weighted_settings = {
"baseOptions": {
"description": "Generated by https://archipelago.gg/",
"name": "Player",
"game": {},
},
"games": {},
}
def get_world_theme(game_name: str) -> str:
if game_name in AutoWorldRegister.world_types:
return AutoWorldRegister.world_types[game_name].web.theme
return 'grass'
for game_name, world in AutoWorldRegister.world_types.items():
all_options = {**world.options, **Options.per_game_common_options}
res = Template(open(os.path.join("WebHostLib", "templates", "options.yaml")).read()).render(
options=all_options,
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
dictify_range=dictify_range, default_converter=default_converter,
)
def render_options_page(template: str, world_name: str, is_complex: bool = False) -> Union[Response, str]:
visibility_flag = Options.Visibility.complex_ui if is_complex else Options.Visibility.simple_ui
world = AutoWorldRegister.world_types[world_name]
if world.hidden or world.web.options_page is False:
return redirect("games")
with open(os.path.join(target_folder, 'configs', game_name + ".yaml"), "w") as f:
f.write(res)
option_groups = {option: option_group.name
for option_group in world.web.option_groups
for option in option_group.options}
ordered_groups = ["Game Options", *[group.name for group in world.web.option_groups]]
grouped_options = {group: {} for group in ordered_groups}
for option_name, option in world.options_dataclass.type_hints.items():
# Exclude settings from options pages if their visibility is disabled
if visibility_flag in option.visibility:
grouped_options[option_groups.get(option, "Game Options")][option_name] = option
# Generate JSON files for player-settings pages
player_settings = {
"baseOptions": {
"description": "Generated by https://archipelago.gg/",
"game": game_name,
"name": "Player",
},
}
return render_template(
template,
world_name=world_name,
world=world,
option_groups=grouped_options,
issubclass=issubclass,
Options=Options,
theme=get_world_theme(world_name),
)
game_options = {}
for option_name, option in all_options.items():
if option_name in handled_in_js:
pass
elif option.options:
game_options[option_name] = this_option = {
"type": "select",
"displayName": option.display_name if hasattr(option, "display_name") else option_name,
"description": option.__doc__ if option.__doc__ else "Please document me!",
"defaultValue": None,
"options": []
}
def generate_game(options: Dict[str, Union[dict, str]]) -> Union[Response, str]:
from .generate import start_generation
return start_generation(options, {"plando_options": ["items", "connections", "texts", "bosses"]})
for sub_option_id, sub_option_name in option.name_lookup.items():
this_option["options"].append({
"name": option.get_option_name(sub_option_id),
"value": sub_option_name,
})
if sub_option_id == option.default:
this_option["defaultValue"] = sub_option_name
def send_yaml(player_name: str, formatted_options: dict) -> Response:
response = Response(yaml.dump(formatted_options, sort_keys=False))
response.headers["Content-Type"] = "text/yaml"
response.headers["Content-Disposition"] = f"attachment; filename={player_name}.yaml"
return response
this_option["options"].append({
"name": "Random",
"value": "random",
})
elif hasattr(option, "range_start") and hasattr(option, "range_end"):
game_options[option_name] = {
"type": "range",
"displayName": option.display_name if hasattr(option, "display_name") else option_name,
"description": option.__doc__ if option.__doc__ else "Please document me!",
"defaultValue": option.default if hasattr(option, "default") else option.range_start,
"min": option.range_start,
"max": option.range_end,
}
@app.template_filter("dedent")
def filter_dedent(text: str) -> str:
return dedent(text).strip("\n ")
elif getattr(option, "verify_item_name", False):
game_options[option_name] = {
"type": "items-list",
"displayName": option.display_name if hasattr(option, "display_name") else option_name,
"description": option.__doc__ if option.__doc__ else "Please document me!",
}
elif getattr(option, "verify_location_name", False):
game_options[option_name] = {
"type": "locations-list",
"displayName": option.display_name if hasattr(option, "display_name") else option_name,
"description": option.__doc__ if option.__doc__ else "Please document me!",
}
@app.template_test("ordered")
def test_ordered(obj):
return isinstance(obj, collections.abc.Sequence)
elif hasattr(option, "valid_keys"):
if option.valid_keys:
game_options[option_name] = {
"type": "custom-list",
"displayName": option.display_name if hasattr(option, "display_name") else option_name,
"description": option.__doc__ if option.__doc__ else "Please document me!",
"options": list(option.valid_keys),
}
@app.route("/games/<string:game>/option-presets", methods=["GET"])
@cache.cached()
def option_presets(game: str) -> Response:
world = AutoWorldRegister.world_types[game]
class SetEncoder(json.JSONEncoder):
def default(self, obj):
from collections.abc import Set
if isinstance(obj, Set):
return list(obj)
return json.JSONEncoder.default(self, obj)
json_data = json.dumps(world.web.options_presets, cls=SetEncoder)
response = Response(json_data)
response.headers["Content-Type"] = "application/json"
return response
@app.route("/weighted-options")
def weighted_options_old():
return redirect("games", 301)
@app.route("/games/<string:game>/weighted-options")
@cache.cached()
def weighted_options(game: str):
return render_options_page("weightedOptions/weightedOptions.html", game, is_complex=True)
@app.route("/games/<string:game>/generate-weighted-yaml", methods=["POST"])
def generate_weighted_yaml(game: str):
if request.method == "POST":
intent_generate = False
options = {}
for key, val in request.form.items():
if "||" not in key:
if len(str(val)) == 0:
continue
options[key] = val
else:
logging.debug(f"{option} not exported to Web Settings.")
if int(val) == 0:
continue
player_settings["gameOptions"] = game_options
[option, setting] = key.split("||")
options.setdefault(option, {})[setting] = int(val)
os.makedirs(os.path.join(target_folder, 'player-settings'), exist_ok=True)
# Error checking
if "name" not in options:
return "Player name is required."
with open(os.path.join(target_folder, 'player-settings', game_name + ".json"), "w") as f:
json.dump(player_settings, f, indent=2, separators=(',', ': '))
# Remove POST data irrelevant to YAML
if "intent-generate" in options:
intent_generate = True
del options["intent-generate"]
if "intent-export" in options:
del options["intent-export"]
if not world.hidden and world.web.settings_page is True:
weighted_settings["baseOptions"]["game"][game_name] = 0
weighted_settings["games"][game_name] = {}
weighted_settings["games"][game_name]["gameSettings"] = game_options
weighted_settings["games"][game_name]["gameItems"] = tuple(world.item_names)
weighted_settings["games"][game_name]["gameLocations"] = tuple(world.location_names)
# Properly format YAML output
player_name = options["name"]
del options["name"]
with open(os.path.join(target_folder, 'weighted-settings.json'), "w") as f:
json.dump(weighted_settings, f, indent=2, separators=(',', ': '))
formatted_options = {
"name": player_name,
"game": game,
"description": f"Generated by https://archipelago.gg/ for {game}",
game: options,
}
if intent_generate:
return generate_game({player_name: formatted_options})
else:
return send_yaml(player_name, formatted_options)
# Player options pages
@app.route("/games/<string:game>/player-options")
@cache.cached()
def player_options(game: str):
return render_options_page("playerOptions/playerOptions.html", game, is_complex=False)
# YAML generator for player-options
@app.route("/games/<string:game>/generate-yaml", methods=["POST"])
def generate_yaml(game: str):
if request.method == "POST":
options = {}
intent_generate = False
for key, val in request.form.items(multi=True):
if key in options:
if not isinstance(options[key], list):
options[key] = [options[key]]
options[key].append(val)
else:
options[key] = val
# Detect and build ItemDict options from their name pattern
for key, val in options.copy().items():
key_parts = key.rsplit("||", 2)
if key_parts[-1] == "qty":
if key_parts[0] not in options:
options[key_parts[0]] = {}
if val != "0":
options[key_parts[0]][key_parts[1]] = int(val)
del options[key]
# Detect random-* keys and set their options accordingly
for key, val in options.copy().items():
if key.startswith("random-"):
options[key.removeprefix("random-")] = "random"
del options[key]
# Error checking
if not options["name"]:
return "Player name is required."
# Remove POST data irrelevant to YAML
preset_name = 'default'
if "intent-generate" in options:
intent_generate = True
del options["intent-generate"]
if "intent-export" in options:
del options["intent-export"]
if "game-options-preset" in options:
preset_name = options["game-options-preset"]
del options["game-options-preset"]
# Properly format YAML output
player_name = options["name"]
del options["name"]
description = f"Generated by https://archipelago.gg/ for {game}"
if preset_name != 'default' and preset_name != 'custom':
description += f" using {preset_name} preset"
formatted_options = {
"name": player_name,
"game": game,
"description": description,
game: options,
}
if intent_generate:
return generate_game({player_name: formatted_options})
else:
return send_yaml(player_name, formatted_options)

View File

@@ -1,7 +1,9 @@
flask>=2.1.2
pony>=0.7.16
waitress>=2.1.1
flask-caching>=1.10.1
Flask-Compress>=1.12
Flask-Limiter>=2.4.5.1
bokeh>=2.4.3
flask>=3.0.0
pony>=0.7.17
waitress>=2.1.2
Flask-Caching>=2.1.0
Flask-Compress>=1.14
Flask-Limiter>=3.5.0
bokeh>=3.1.1; python_version <= '3.8'
bokeh>=3.3.2; python_version >= '3.9'
markupsafe>=2.1.3

14
WebHostLib/robots.py Normal file
View File

@@ -0,0 +1,14 @@
from WebHostLib import app
from flask import abort
from . import cache
@cache.cached()
@app.route('/robots.txt')
def robots():
# If this host is not official, do not allow search engine crawling
if not app.config["ASSET_RIGHTS"]:
return app.send_static_file('robots.txt')
# Send 404 if the host has affirmed this to be the official WebHost
abort(404)

View File

@@ -4,6 +4,7 @@ window.addEventListener('load', () => {
"ordering": true,
"info": false,
"dom": "t",
"stateSave": true,
});
console.log(tables);
});

View File

@@ -0,0 +1,40 @@
window.addEventListener('load', () => {
// Mobile menu handling
const menuButton = document.getElementById('base-header-mobile-menu-button');
const mobileMenu = document.getElementById('base-header-mobile-menu');
menuButton.addEventListener('click', (evt) => {
evt.preventDefault();
evt.stopPropagation();
if (!mobileMenu.style.display || mobileMenu.style.display === 'none') {
return mobileMenu.style.display = 'flex';
}
mobileMenu.style.display = 'none';
});
window.addEventListener('resize', () => {
mobileMenu.style.display = 'none';
});
// Popover handling
const popoverText = document.getElementById('base-header-popover-text');
const popoverMenu = document.getElementById('base-header-popover-menu');
popoverText.addEventListener('click', (evt) => {
evt.preventDefault();
evt.stopPropagation();
if (!popoverMenu.style.display || popoverMenu.style.display === 'none') {
return popoverMenu.style.display = 'flex';
}
popoverMenu.style.display = 'none';
});
document.body.addEventListener('click', () => {
mobileMenu.style.display = 'none';
popoverMenu.style.display = 'none';
});
});

View File

@@ -0,0 +1,49 @@
window.addEventListener('load', () => {
// Reload tracker every 60 seconds
const url = window.location;
setInterval(() => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
// Create a fake DOM using the returned HTML
const domParser = new DOMParser();
const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html');
// Update item tracker
document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML;
// Update only counters in the location-table
let counters = document.getElementsByClassName('counter');
const fakeCounters = fakeDOM.getElementsByClassName('counter');
for (let i = 0; i < counters.length; i++) {
counters[i].innerHTML = fakeCounters[i].innerHTML;
}
};
ajax.open('GET', url);
ajax.send();
}, 60000)
// Collapsible advancement sections
const categories = document.getElementsByClassName("location-category");
for (let i = 0; i < categories.length; i++) {
let hide_id = categories[i].id.split('-')[0];
if (hide_id == 'Total') {
continue;
}
categories[i].addEventListener('click', function() {
// Toggle the advancement list
document.getElementById(hide_id).classList.toggle("hide");
// Change text of the header
const tab_header = document.getElementById(hide_id+'-header').children[0];
const orig_text = tab_header.innerHTML;
let new_text;
if (orig_text.includes("▼")) {
new_text = orig_text.replace("▼", "▲");
}
else {
new_text = orig_text.replace("▲", "▼");
}
tab_header.innerHTML = new_text;
});
}
});

View File

@@ -26,24 +26,22 @@ window.addEventListener('load', () => {
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
for (let i=0; i < headers.length; i++){
const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
headers[i].setAttribute('id', headerId);
headers[i].addEventListener('click', () =>
window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
header.setAttribute('id', headerId);
header.addEventListener('click', () => {
window.location.hash = `#${headerId}`;
header.scrollIntoView();
});
}
// Manually scroll the user to the appropriate header if anchor navigation is used
if (scrollTargetIndex > -1) {
try{
const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
} catch(error) {
console.error(error);
document.fonts.ready.finally(() => {
if (window.location.hash) {
const scrollTarget = document.getElementById(window.location.hash.substring(1));
scrollTarget?.scrollIntoView();
}
}
});
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =

View File

@@ -2,13 +2,62 @@
## What is a randomizer?
A randomizer is a modification of a video game which reorganizes the items required to progress through the game. A
normal play-through of a game might require you to use item A to unlock item B, then C, and so forth. In a randomized
A randomizer is a modification of a game which reorganizes the items required to progress through that game. A
normal play-through might require you to use item A to unlock item B, then C, and so forth. In a randomized
game, you might first find item C, then A, then B.
This transforms games from a linear experience into a puzzle, presenting players with a new challenge each time they
play a randomized game. Putting items in non-standard locations can require the player to think about the game world and
the items they encounter in new and interesting ways.
This transforms the game from a linear experience into a puzzle, presenting players with a new challenge each time they
play. Putting items in non-standard locations can require the player to think about the game world and the items they
encounter in new and interesting ways.
## What is a multiworld?
While a randomizer shuffles a game, a multiworld randomizer shuffles that game for multiple players. For example, in a
two player multiworld, players A and B each get their own randomized version of a game, called a world. In each
player's game, they may find items which belong to the other player. If player A finds an item which belongs to
player B, the item will be sent to player B's world over the internet. This creates a cooperative experience, requiring
players to rely upon each other to complete their game.
## What does multi-game mean?
While a multiworld game traditionally requires all players to be playing the same game, a multi-game multiworld allows
players to randomize any of the supported games, and send items between them. This allows players of different
games to interact with one another in a single multiplayer environment. Archipelago supports multi-game multiworld.
Here is a list of our [Supported Games](https://archipelago.gg/games).
## Can I generate a single-player game with Archipelago?
Yes. All of our supported games can be generated as single-player experiences both on the website and by installing
the Archipelago generator software. The fastest way to do this is on the website. Find the Supported Game you wish to
play, open the Settings Page, pick your settings, and click Generate Game.
## How do I get started?
We have a [Getting Started](https://archipelago.gg/tutorial/Archipelago/setup/en) guide that will help you get the
software set up. You can use that guide to learn how to generate multiworlds. There are also basic instructions for
including multiple games, and hosting multiworlds on the website for ease and convenience.
If you are ready to start randomizing games, or want to start playing your favorite randomizer with others, please join
our discord server at the [Archipelago Discord](https://discord.gg/8Z65BR2). There are always people ready to answer
any questions you might have.
## What are some common terms I should know?
As randomizers and multiworld randomizers have been around for a while now, there are quite a few common terms used
by the communities surrounding them. A list of Archipelago jargon and terms commonly used by the community can be
found in the [Glossary](/glossary/en).
## Does everyone need to be connected at the same time?
There are two different play-styles that are common for Archipelago multiworld sessions. These sessions can either
be considered synchronous (or "sync"), where everyone connects and plays at the same time, or asynchronous (or "async"),
where players connect and play at their own pace. The setup for both is identical. The difference in play-style is how
you and your friends choose to organize and play your multiworld. Most groups decide on the format before creating
their multiworld.
If a player must leave early, they can use Archipelago's release system. When a player releases their game, all items
in that game belonging to other players are sent out automatically. This allows other players to continue to play
uninterrupted. Here is a list of all of our [Server Commands](https://archipelago.gg/tutorial/Archipelago/commands/en).
## What happens if an item is placed somewhere it is impossible to get?
@@ -17,47 +66,15 @@ is to ensure items necessary to complete the game will be accessible to the play
rules allowing certain items to be placed in normally unreachable locations, provided the player has indicated they are
comfortable exploiting certain glitches in the game.
## What is a multi-world?
While a randomizer shuffles a game, a multi-world randomizer shuffles that game for multiple players. For example, in a
two player multi-world, players A and B each get their own randomized version of a game, called seeds. In each player's
game, they may find items which belong to the other player. If player A finds an item which belongs to player B, the
item will be sent to player B's world over the internet.
This creates a cooperative experience during multi-world games, requiring players to rely upon each other to complete
their game.
## What happens if a person has to leave early?
If a player must leave early, they can use Archipelago's forfeit system. When a player forfeits their game, all the
items in that game which belong to other players are sent out automatically, so other players can continue to play.
## What does multi-game mean?
While a multi-world game traditionally requires all players to be playing the same game, a multi-game multi-world allows
players to randomize any of a number of supported games, and send items between them. This allows players of different
games to interact with one another in a single multiplayer environment.
## Can I generate a single-player game with Archipelago?
Yes. All our supported games can be generated as single-player experiences, and so long as you download the software,
the website is not required to generate them.
## How do I get started?
If you are ready to start randomizing games, or want to start playing your favorite randomizer with others, please join
our discord server at the [Archipelago Discord](https://discord.gg/archipelago). There are always people ready to answer
any questions you might have.
## I want to add a game to the Archipelago randomizer. How do I do that?
The best way to get started is to take a look at our code on GitHub
at [Archipelago GitHub Page](https://github.com/ArchipelagoMW/Archipelago).
The best way to get started is to take a look at our code on GitHub:
[Archipelago GitHub Page](https://github.com/ArchipelagoMW/Archipelago).
There you will find examples of games in the worlds folder
at [/worlds Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds).
There, you will find examples of games in the `worlds` folder:
[/worlds Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds).
You may also find developer documentation in the docs folder
at [/docs Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/docs).
You may also find developer documentation in the `docs` folder:
[/docs Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/docs).
If you have more questions, feel free to ask in the **#archipelago-dev** channel on our Discord.

View File

@@ -0,0 +1,94 @@
# Multiworld Glossary
There are a lot of common terms used when playing in different game randomizer communities and in multiworld as well.
This document serves as a lookup for common terms that may be used by users in the community or in various other
documentation.
## Item
Items are what get shuffled around in your world or other worlds that you then receive. This could be a sword, a stat
upgrade, a spell, or any other potential receivable for your game.
## Location
Locations are where items are placed in your game. Whenever you interact with a location, you or another player will
then receive an item. A location could be a chest, an enemy drop, a shop purchase, or any other interactable that can
contain items in your game.
## Check
A check is a common term for when you "check", or pick up, a location. In terms of Archipelago this is usually used for
when a player goes to a location and sends its item, or "checks" the location. Players will often reference their now
randomized locations as checks.
## Slot
A slot is the player name and number assigned during generation. The number of slots is equal to the number of players,
or "worlds", created. Each name must be unique as these are used to identify the slot user.
## World
World in terms of Archipelago can mean multiple things and is used interchangeably in many situations.
* During gameplay, a world is a single instance of a game, occupying one player "slot". However,
Archipelago allows multiple players to connect to the same slot; then those players can share a world
and complete it cooperatively. For games with native cooperative play, you can also play together and
share a world that way, usually with only one player connected to the multiworld.
* On the programming side, a world typically represents the package that integrates Archipelago with a
particular game. For example this could be the entire `worlds/factorio` directory.
## RNG
Acronym for "Random Number Generator." Archipelago uses its own custom Random object with a unique seed per generation,
or, if running from source, a seed can be supplied and this seed will control all randomization during generation as all
game worlds will have access to it.
## Seed
A "seed" is a number used to initialize a pseudorandom number generator. Whenever you generate a new game on Archipelago
this is a new "seed" as it has unique item placement, and you can create multiple "rooms" on the Archipelago site from a
single seed. Using the same seed results in the random placement being the same.
## Room
Whenever you generate a seed on the Archipelago website you will be put on a seed page that contains all the seed info
with a link to the spoiler if one exists and will show how many unique rooms exist per seed. Each room has its own
unique identifier that is separate from the seed. The room page is where you can find information to connect to the
multiworld and download any patches if necessary. If you have a particularly fun or interesting seed, and you want to
share it with somebody you can link them to this seed page, where they can generate a new room to play it! For seeds
generated with race mode enabled, the seed page will only show rooms created by the unique user so the seed page is
perfectly safe to share for racing purposes.
## Logic
Base behavior of all seeds generated by Archipelago is they are expected to be completable based on the requirements of
the settings. This is done by using "logic" in order to determine valid locations to place items while still being able
to reach said location without this item. For the purposes of the randomizer a location is considered "in logic" if you
can reach it with your current toolset of items or skills based on settings. Some players are able to obtain locations
"out of logic" by performing various glitches or tricks that the settings may not account for and tend to mention this
when sending out an item they obtained this way.
## Progression
Certain items will allow access to more locations and are considered progression items as they "progress" the seed.
## Trash
A term used for "filler" items that have no bearing on the generation and are either marginally useful for the player
or useless. These items can be very useful depending on the player but are never very important and as such are usually
termed trash.
## Burger King / BK Mode
A term used in multiworlds when a player is unable to continue to progress and is awaiting an item. The term came to be
after a player, allegedly, was unable to progress during a multiworld and went to Burger King while waiting to receive
items from other players.
* "Logical BK" is when the player is unable to progress according to the settings of their game but may still be able to do
things that would be "out of logic" by the generation.
* "Hard / full BK" is when the player is completely unable to progress even with tricks they may know and are unable to
continue to play, aside from doing something like killing enemies for experience or money.
## Sphere
Archipelago calculates the game playthrough by using a "sphere" system where it has a state for each player and checks
to see what the players are able to reach with their current items. Any location that is reachable with the current
state of items is a "sphere." For the purposes of Archipelago it starts playthrough calculation by distributing sphere 0
items which are items that are either forced in the player's inventory by the game or placed in the `start_inventory` in
their settings. Sphere 1 is then all accessible locations the players can reach with all the items they received from
sphere 0, or their starting inventory. The playthrough continues in this fashion calculating a number of spheres until
all players have completed their goal.
## Scouts / Scouting
In some games there are locations that have visible items even if the item itself is unobtainable at the current time.
Some games utilize a scouting feature where when the player "sees" the item it will give a free hint for the item in the
client letting the players know what the exact item is, since if the item was for that game it would know but the item
being foreign is a lot harder to represent visually.

View File

@@ -26,24 +26,22 @@ window.addEventListener('load', () => {
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
for (let i=0; i < headers.length; i++){
const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
headers[i].setAttribute('id', headerId);
headers[i].addEventListener('click', () =>
window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
header.setAttribute('id', headerId);
header.addEventListener('click', () => {
window.location.hash = `#${headerId}`;
header.scrollIntoView();
});
}
// Manually scroll the user to the appropriate header if anchor navigation is used
if (scrollTargetIndex > -1) {
try{
const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
} catch(error) {
console.error(error);
document.fonts.ready.finally(() => {
if (window.location.hash) {
const scrollTarget = document.getElementById(window.location.hash.substring(1));
scrollTarget?.scrollIntoView();
}
}
});
}).catch((error) => {
console.error(error);
gameInfo.innerHTML =

View File

@@ -0,0 +1,51 @@
window.addEventListener('load', () => {
const tutorialWrapper = document.getElementById('glossary-wrapper');
new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
if (ajax.status === 404) {
reject("Sorry, the glossary page is not available in that language yet.");
return;
}
if (ajax.status !== 200) {
reject("Something went wrong while loading the glossary.");
return;
}
resolve(ajax.responseText);
};
ajax.open('GET', `${window.location.origin}/static/assets/faq/` +
`glossary_${tutorialWrapper.getAttribute('data-lang')}.md`, true);
ajax.send();
}).then((results) => {
// Populate page with HTML generated from markdown
showdown.setOption('tables', true);
showdown.setOption('strikethrough', true);
showdown.setOption('literalMidWordUnderscores', true);
tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results);
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
header.setAttribute('id', headerId);
header.addEventListener('click', () => {
window.location.hash = `#${headerId}`;
header.scrollIntoView();
});
}
// Manually scroll the user to the appropriate header if anchor navigation is used
document.fonts.ready.finally(() => {
if (window.location.hash) {
const scrollTarget = document.getElementById(window.location.hash.substring(1));
scrollTarget?.scrollIntoView();
}
});
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
`<h2>This page is out of logic!</h2>
<h3>Click <a href="${window.location.origin}">here</a> to return to safety.</h3>`;
});
});

View File

@@ -1,20 +0,0 @@
window.addEventListener('load', () => {
const url = window.location;
setInterval(() => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
// Create a fake DOM using the returned HTML
const domParser = new DOMParser();
const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html');
// Update item and location trackers
document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML;
document.getElementById('location-table').innerHTML = fakeDOM.getElementById('location-table').innerHTML;
};
ajax.open('GET', url);
ajax.send();
}, 15000)
});

View File

@@ -0,0 +1,6 @@
window.addEventListener('load', () => {
$(".table-wrapper").scrollsync({
y_sync: true,
x_sync: true
});
});

View File

@@ -1,247 +0,0 @@
let gameName = null;
window.addEventListener('load', () => {
gameName = document.getElementById('player-settings').getAttribute('data-game');
// Update game name on page
document.getElementById('game-name').innerText = gameName;
fetchSettingData().then((results) => {
let settingHash = localStorage.getItem(`${gameName}-hash`);
if (!settingHash) {
// If no hash data has been set before, set it now
settingHash = md5(JSON.stringify(results));
localStorage.setItem(`${gameName}-hash`, settingHash);
localStorage.removeItem(gameName);
}
if (settingHash !== md5(JSON.stringify(results))) {
showUserMessage("Your settings are out of date! Click here to update them! Be aware this will reset " +
"them all to default.");
document.getElementById('user-message').addEventListener('click', resetSettings);
}
// Page setup
createDefaultSettings(results);
buildUI(results);
adjustHeaderWidth();
// Event listeners
document.getElementById('export-settings').addEventListener('click', () => exportSettings());
document.getElementById('generate-race').addEventListener('click', () => generateGame(true));
document.getElementById('generate-game').addEventListener('click', () => generateGame());
// Name input field
const playerSettings = JSON.parse(localStorage.getItem(gameName));
const nameInput = document.getElementById('player-name');
nameInput.addEventListener('keyup', (event) => updateBaseSetting(event));
nameInput.value = playerSettings.name;
}).catch(() => {
const url = new URL(window.location.href);
window.location.replace(`${url.protocol}//${url.hostname}/page-not-found`);
})
});
const resetSettings = () => {
localStorage.removeItem(gameName);
localStorage.removeItem(`${gameName}-hash`)
window.location.reload();
};
const fetchSettingData = () => new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
if (ajax.status !== 200) {
reject(ajax.responseText);
return;
}
try{ resolve(JSON.parse(ajax.responseText)); }
catch(error){ reject(error); }
};
ajax.open('GET', `${window.location.origin}/static/generated/player-settings/${gameName}.json`, true);
ajax.send();
});
const createDefaultSettings = (settingData) => {
if (!localStorage.getItem(gameName)) {
const newSettings = {
[gameName]: {},
};
for (let baseOption of Object.keys(settingData.baseOptions)){
newSettings[baseOption] = settingData.baseOptions[baseOption];
}
for (let gameOption of Object.keys(settingData.gameOptions)){
newSettings[gameName][gameOption] = settingData.gameOptions[gameOption].defaultValue;
}
localStorage.setItem(gameName, JSON.stringify(newSettings));
}
};
const buildUI = (settingData) => {
// Game Options
const leftGameOpts = {};
const rightGameOpts = {};
Object.keys(settingData.gameOptions).forEach((key, index) => {
if (index < Object.keys(settingData.gameOptions).length / 2) { leftGameOpts[key] = settingData.gameOptions[key]; }
else { rightGameOpts[key] = settingData.gameOptions[key]; }
});
document.getElementById('game-options-left').appendChild(buildOptionsTable(leftGameOpts));
document.getElementById('game-options-right').appendChild(buildOptionsTable(rightGameOpts));
};
const buildOptionsTable = (settings, romOpts = false) => {
const currentSettings = JSON.parse(localStorage.getItem(gameName));
const table = document.createElement('table');
const tbody = document.createElement('tbody');
Object.keys(settings).forEach((setting) => {
const tr = document.createElement('tr');
// td Left
const tdl = document.createElement('td');
const label = document.createElement('label');
label.setAttribute('for', setting);
label.setAttribute('data-tooltip', settings[setting].description);
label.innerText = `${settings[setting].displayName}:`;
tdl.appendChild(label);
tr.appendChild(tdl);
// td Right
const tdr = document.createElement('td');
let element = null;
switch(settings[setting].type){
case 'select':
element = document.createElement('div');
element.classList.add('select-container');
let select = document.createElement('select');
select.setAttribute('id', setting);
select.setAttribute('data-key', setting);
if (romOpts) { select.setAttribute('data-romOpt', '1'); }
settings[setting].options.forEach((opt) => {
const option = document.createElement('option');
option.setAttribute('value', opt.value);
option.innerText = opt.name;
if ((isNaN(currentSettings[gameName][setting]) &&
(parseInt(opt.value, 10) === parseInt(currentSettings[gameName][setting]))) ||
(opt.value === currentSettings[gameName][setting]))
{
option.selected = true;
}
select.appendChild(option);
});
select.addEventListener('change', (event) => updateGameSetting(event));
element.appendChild(select);
break;
case 'range':
element = document.createElement('div');
element.classList.add('range-container');
let range = document.createElement('input');
range.setAttribute('type', 'range');
range.setAttribute('data-key', setting);
range.setAttribute('min', settings[setting].min);
range.setAttribute('max', settings[setting].max);
range.value = currentSettings[gameName][setting];
range.addEventListener('change', (event) => {
document.getElementById(`${setting}-value`).innerText = event.target.value;
updateGameSetting(event);
});
element.appendChild(range);
let rangeVal = document.createElement('span');
rangeVal.classList.add('range-value');
rangeVal.setAttribute('id', `${setting}-value`);
rangeVal.innerText = currentSettings[gameName][setting] ?? settings[setting].defaultValue;
element.appendChild(rangeVal);
break;
default:
console.error(`Ignoring unknown setting type: ${settings[setting].type} with name ${setting}`);
return;
}
tdr.appendChild(element);
tr.appendChild(tdr);
tbody.appendChild(tr);
});
table.appendChild(tbody);
return table;
};
const updateBaseSetting = (event) => {
const options = JSON.parse(localStorage.getItem(gameName));
options[event.target.getAttribute('data-key')] = isNaN(event.target.value) ?
event.target.value : parseInt(event.target.value);
localStorage.setItem(gameName, JSON.stringify(options));
};
const updateGameSetting = (event) => {
const options = JSON.parse(localStorage.getItem(gameName));
options[gameName][event.target.getAttribute('data-key')] = isNaN(event.target.value) ?
event.target.value : parseInt(event.target.value, 10);
localStorage.setItem(gameName, JSON.stringify(options));
};
const exportSettings = () => {
const settings = JSON.parse(localStorage.getItem(gameName));
if (!settings.name || settings.name.toLowerCase() === 'player' || settings.name.trim().length === 0) {
return showUserMessage('You must enter a player name!');
}
const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`);
download(`${document.getElementById('player-name').value}.yaml`, yamlText);
};
/** Create an anchor and trigger a download of a text file. */
const download = (filename, text) => {
const downloadLink = document.createElement('a');
downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text))
downloadLink.setAttribute('download', filename);
downloadLink.style.display = 'none';
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
};
const generateGame = (raceMode = false) => {
const settings = JSON.parse(localStorage.getItem(gameName));
if (!settings.name || settings.name.toLowerCase() === 'player' || settings.name.trim().length === 0) {
return showUserMessage('You must enter a player name!');
}
axios.post('/api/generate', {
weights: { player: settings },
presetData: { player: settings },
playerCount: 1,
race: raceMode ? '1' : '0',
}).then((response) => {
window.location.href = response.data.url;
}).catch((error) => {
let userMessage = 'Something went wrong and your game could not be generated.';
if (error.response.data.text) {
userMessage += ' ' + error.response.data.text;
}
showUserMessage(userMessage);
console.error(error);
});
};
const showUserMessage = (message) => {
const userMessage = document.getElementById('user-message');
userMessage.innerText = message;
userMessage.classList.add('visible');
window.scrollTo(0, 0);
userMessage.addEventListener('click', () => {
userMessage.classList.remove('visible');
userMessage.addEventListener('click', hideUserMessage);
});
};
const hideUserMessage = () => {
const userMessage = document.getElementById('user-message');
userMessage.classList.remove('visible');
userMessage.removeEventListener('click', hideUserMessage);
};

View File

@@ -0,0 +1,335 @@
let presets = {};
window.addEventListener('load', async () => {
// Load settings from localStorage, if available
loadSettings();
// Fetch presets if available
await fetchPresets();
// Handle changes to range inputs
document.querySelectorAll('input[type=range]').forEach((range) => {
const optionName = range.getAttribute('id');
range.addEventListener('change', () => {
document.getElementById(`${optionName}-value`).innerText = range.value;
// Handle updating named range selects to "custom" if appropriate
const select = document.querySelector(`select[data-option-name=${optionName}]`);
if (select) {
let updated = false;
select?.childNodes.forEach((option) => {
if (option.value === range.value) {
select.value = range.value;
updated = true;
}
});
if (!updated) {
select.value = 'custom';
}
}
});
});
// Handle changes to named range selects
document.querySelectorAll('.named-range-container select').forEach((select) => {
const optionName = select.getAttribute('data-option-name');
select.addEventListener('change', (evt) => {
document.getElementById(optionName).value = evt.target.value;
document.getElementById(`${optionName}-value`).innerText = evt.target.value;
});
});
// Handle changes to randomize checkboxes
document.querySelectorAll('.randomize-checkbox').forEach((checkbox) => {
const optionName = checkbox.getAttribute('data-option-name');
checkbox.addEventListener('change', () => {
const optionInput = document.getElementById(optionName);
const namedRangeSelect = document.querySelector(`select[data-option-name=${optionName}]`);
const customInput = document.getElementById(`${optionName}-custom`);
if (checkbox.checked) {
optionInput.setAttribute('disabled', '1');
namedRangeSelect?.setAttribute('disabled', '1');
if (customInput) {
customInput.setAttribute('disabled', '1');
}
} else {
optionInput.removeAttribute('disabled');
namedRangeSelect?.removeAttribute('disabled');
if (customInput) {
customInput.removeAttribute('disabled');
}
}
});
});
// Handle changes to TextChoice input[type=text]
document.querySelectorAll('.text-choice-container input[type=text]').forEach((input) => {
const optionName = input.getAttribute('data-option-name');
input.addEventListener('input', () => {
const select = document.getElementById(optionName);
const optionValues = [];
select.childNodes.forEach((option) => optionValues.push(option.value));
select.value = (optionValues.includes(input.value)) ? input.value : 'custom';
});
});
// Handle changes to TextChoice select
document.querySelectorAll('.text-choice-container select').forEach((select) => {
const optionName = select.getAttribute('id');
select.addEventListener('change', () => {
document.getElementById(`${optionName}-custom`).value = '';
});
});
// Update the "Option Preset" select to read "custom" when changes are made to relevant inputs
const presetSelect = document.getElementById('game-options-preset');
document.querySelectorAll('input, select').forEach((input) => {
if ( // Ignore inputs which have no effect on yaml generation
(input.id === 'player-name') ||
(input.id === 'game-options-preset') ||
(input.classList.contains('group-toggle')) ||
(input.type === 'submit')
) {
return;
}
input.addEventListener('change', () => {
presetSelect.value = 'custom';
});
});
// Handle changes to presets select
document.getElementById('game-options-preset').addEventListener('change', choosePreset);
// Save settings to localStorage when form is submitted
document.getElementById('options-form').addEventListener('submit', (evt) => {
const playerName = document.getElementById('player-name');
if (!playerName.value.trim()) {
evt.preventDefault();
window.scrollTo(0, 0);
showUserMessage('You must enter a player name!');
}
saveSettings();
});
});
// Save all settings to localStorage
const saveSettings = () => {
const options = {
inputs: {},
checkboxes: {},
};
document.querySelectorAll('input, select').forEach((input) => {
if (input.type === 'submit') {
// Ignore submit inputs
}
else if (input.type === 'checkbox') {
options.checkboxes[input.id] = input.checked;
}
else {
options.inputs[input.id] = input.value
}
});
const game = document.getElementById('player-options').getAttribute('data-game');
localStorage.setItem(game, JSON.stringify(options));
};
// Load all options from localStorage
const loadSettings = () => {
const game = document.getElementById('player-options').getAttribute('data-game');
const options = JSON.parse(localStorage.getItem(game));
if (options) {
if (!options.inputs || !options.checkboxes) {
localStorage.removeItem(game);
return;
}
// Restore value-based inputs and selects
Object.keys(options.inputs).forEach((key) => {
try{
document.getElementById(key).value = options.inputs[key];
const rangeValue = document.getElementById(`${key}-value`);
if (rangeValue) {
rangeValue.innerText = options.inputs[key];
}
} catch (err) {
console.error(`Unable to restore value to input with id ${key}`);
}
});
// Restore checkboxes
Object.keys(options.checkboxes).forEach((key) => {
try{
if (options.checkboxes[key]) {
document.getElementById(key).setAttribute('checked', '1');
}
} catch (err) {
console.error(`Unable to restore value to input with id ${key}`);
}
});
}
// Ensure any input for which the randomize checkbox is checked by default, the relevant inputs are disabled
document.querySelectorAll('.randomize-checkbox').forEach((checkbox) => {
const optionName = checkbox.getAttribute('data-option-name');
if (checkbox.checked) {
const input = document.getElementById(optionName);
if (input) {
input.setAttribute('disabled', '1');
}
const customInput = document.getElementById(`${optionName}-custom`);
if (customInput) {
customInput.setAttribute('disabled', '1');
}
}
});
};
/**
* Fetch the preset data for this game and apply the presets if localStorage indicates one was previously chosen
* @returns {Promise<void>}
*/
const fetchPresets = async () => {
const response = await fetch('option-presets');
presets = await response.json();
const presetSelect = document.getElementById('game-options-preset');
presetSelect.removeAttribute('disabled');
const game = document.getElementById('player-options').getAttribute('data-game');
const presetToApply = localStorage.getItem(`${game}-preset`);
const playerName = localStorage.getItem(`${game}-player`);
if (presetToApply) {
localStorage.removeItem(`${game}-preset`);
presetSelect.value = presetToApply;
applyPresets(presetToApply);
}
if (playerName) {
document.getElementById('player-name').value = playerName;
localStorage.removeItem(`${game}-player`);
}
};
/**
* Clear the localStorage for this game and set a preset to be loaded upon page reload
* @param evt
*/
const choosePreset = (evt) => {
if (evt.target.value === 'custom') { return; }
const game = document.getElementById('player-options').getAttribute('data-game');
localStorage.removeItem(game);
localStorage.setItem(`${game}-player`, document.getElementById('player-name').value);
if (evt.target.value !== 'default') {
localStorage.setItem(`${game}-preset`, evt.target.value);
}
document.querySelectorAll('#options-form input, #options-form select').forEach((input) => {
if (input.id === 'player-name') { return; }
input.removeAttribute('value');
});
window.location.replace(window.location.href);
};
const applyPresets = (presetName) => {
// Ignore the "default" preset, because it gets set automatically by Jinja
if (presetName === 'default') {
saveSettings();
return;
}
if (!presets[presetName]) {
console.error(`Unknown preset ${presetName} chosen`);
return;
}
const preset = presets[presetName];
Object.keys(preset).forEach((optionName) => {
const optionValue = preset[optionName];
// Handle List and Set options
if (Array.isArray(optionValue)) {
document.querySelectorAll(`input[type=checkbox][name=${optionName}]`).forEach((checkbox) => {
if (optionValue.includes(checkbox.value)) {
checkbox.setAttribute('checked', '1');
} else {
checkbox.removeAttribute('checked');
}
});
return;
}
// Handle Dict options
if (typeof(optionValue) === 'object' && optionValue !== null) {
const itemNames = Object.keys(optionValue);
document.querySelectorAll(`input[type=number][data-option-name=${optionName}]`).forEach((input) => {
const itemName = input.getAttribute('data-item-name');
input.value = (itemNames.includes(itemName)) ? optionValue[itemName] : 0
});
return;
}
// Identify all possible elements
const normalInput = document.getElementById(optionName);
const customInput = document.getElementById(`${optionName}-custom`);
const rangeValue = document.getElementById(`${optionName}-value`);
const randomizeInput = document.getElementById(`random-${optionName}`);
const namedRangeSelect = document.getElementById(`${optionName}-select`);
// It is possible for named ranges to use name of a value rather than the value itself. This is accounted for here
let trueValue = optionValue;
if (namedRangeSelect) {
namedRangeSelect.querySelectorAll('option').forEach((opt) => {
if (opt.innerText.startsWith(optionValue)) {
trueValue = opt.value;
}
});
namedRangeSelect.value = trueValue;
}
// Handle options whose presets are "random"
if (optionValue === 'random') {
normalInput.setAttribute('disabled', '1');
randomizeInput.setAttribute('checked', '1');
if (customInput) {
customInput.setAttribute('disabled', '1');
}
if (rangeValue) {
rangeValue.innerText = normalInput.value;
}
if (namedRangeSelect) {
namedRangeSelect.setAttribute('disabled', '1');
}
return;
}
// Handle normal (text, number, select, etc.) and custom inputs (custom inputs exist with TextChoice only)
normalInput.value = trueValue;
normalInput.removeAttribute('disabled');
randomizeInput.removeAttribute('checked');
if (customInput) {
document.getElementById(`${optionName}-custom`).removeAttribute('disabled');
}
if (rangeValue) {
rangeValue.innerText = trueValue;
}
});
saveSettings();
};
const showUserMessage = (text) => {
const userMessage = document.getElementById('user-message');
userMessage.innerText = text;
userMessage.addEventListener('click', hideUserMessage);
userMessage.style.display = 'block';
};
const hideUserMessage = () => {
const userMessage = document.getElementById('user-message');
userMessage.removeEventListener('click', hideUserMessage);
userMessage.style.display = 'none';
};

View File

@@ -0,0 +1,49 @@
window.addEventListener('load', () => {
// Reload tracker every 15 seconds
const url = window.location;
setInterval(() => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
// Create a fake DOM using the returned HTML
const domParser = new DOMParser();
const fakeDOM = domParser.parseFromString(ajax.responseText, 'text/html');
// Update item tracker
document.getElementById('inventory-table').innerHTML = fakeDOM.getElementById('inventory-table').innerHTML;
// Update only counters in the location-table
let counters = document.getElementsByClassName('counter');
const fakeCounters = fakeDOM.getElementsByClassName('counter');
for (let i = 0; i < counters.length; i++) {
counters[i].innerHTML = fakeCounters[i].innerHTML;
}
};
ajax.open('GET', url);
ajax.send();
}, 15000)
// Collapsible advancement sections
const categories = document.getElementsByClassName("location-category");
for (let category of categories) {
let hide_id = category.id.split('_')[0];
if (hide_id === 'Total') {
continue;
}
category.addEventListener('click', function() {
// Toggle the advancement list
document.getElementById(hide_id).classList.toggle("hide");
// Change text of the header
const tab_header = document.getElementById(hide_id+'_header').children[0];
const orig_text = tab_header.innerHTML;
let new_text;
if (orig_text.includes("▼")) {
new_text = orig_text.replace("▼", "▲");
}
else {
new_text = orig_text.replace("▲", "▼");
}
tab_header.innerHTML = new_text;
});
}
});

View File

@@ -0,0 +1,44 @@
window.addEventListener('load', () => {
// Add toggle listener to all elements with .collapse-toggle
const toggleButtons = document.querySelectorAll('details');
// Handle game filter input
const gameSearch = document.getElementById('game-search');
gameSearch.value = '';
gameSearch.addEventListener('input', (evt) => {
if (!evt.target.value.trim()) {
// If input is empty, display all games as collapsed
return toggleButtons.forEach((header) => {
header.style.display = null;
header.removeAttribute('open');
});
}
// Loop over all the games
toggleButtons.forEach((header) => {
// If the game name includes the search string, display the game. If not, hide it
if (header.getAttribute('data-game').toLowerCase().includes(evt.target.value.toLowerCase())) {
header.style.display = null;
header.setAttribute('open', '1');
} else {
header.style.display = 'none';
header.removeAttribute('open');
}
});
});
document.getElementById('expand-all').addEventListener('click', expandAll);
document.getElementById('collapse-all').addEventListener('click', collapseAll);
});
const expandAll = () => {
document.querySelectorAll('details').forEach((detail) => {
detail.setAttribute('open', '1');
});
};
const collapseAll = () => {
document.querySelectorAll('details').forEach((detail) => {
detail.removeAttribute('open');
});
};

View File

@@ -1,108 +0,0 @@
const adjustTableHeight = () => {
const tablesContainer = document.getElementById('tables-container');
const upperDistance = tablesContainer.getBoundingClientRect().top;
const containerHeight = window.innerHeight - upperDistance;
tablesContainer.style.maxHeight = `calc(${containerHeight}px - 1rem)`;
const tableWrappers = document.getElementsByClassName('table-wrapper');
for(let i=0; i < tableWrappers.length; i++){
const maxHeight = (window.innerHeight - upperDistance) / 2;
tableWrappers[i].style.maxHeight = `calc(${maxHeight}px - 1rem)`;
}
};
window.addEventListener('load', () => {
const tables = $(".table").DataTable({
paging: false,
info: false,
dom: "t",
columnDefs: [
{
targets: 'hours',
render: function (data, type, row) {
if (type === "sort" || type === 'type') {
if (data === "None")
return -1;
return parseInt(data);
}
if (data === "None")
return data;
let hours = Math.floor(data / 3600);
let minutes = Math.floor((data - (hours * 3600)) / 60);
if (minutes < 10) {minutes = "0"+minutes;}
return hours+':'+minutes;
}
},
{
targets: 'number',
render: function (data, type, row) {
if (type === "sort" || type === 'type') {
return parseFloat(data);
}
return data;
}
},
{
targets: 'fraction',
render: function (data, type, row) {
let splitted = data.split("/", 1);
let current = splitted[0]
if (type === "sort" || type === 'type') {
return parseInt(current);
}
return data;
}
},
],
// DO NOT use the scrollX or scrollY options. They cause DataTables to split the thead from
// the tbody and render two separate tables.
});
document.getElementById('search').addEventListener('keyup', (event) => {
tables.search(event.target.value);
console.info(tables.search());
tables.draw();
});
const update = () => {
const target = $("<div></div>");
const tracker = document.getElementById('tracker-wrapper').getAttribute('data-tracker');
target.load("/tracker/" + tracker, function (response, status) {
if (status === "success") {
target.find(".table").each(function (i, new_table) {
const new_trs = $(new_table).find("tbody>tr");
const old_table = tables.eq(i);
const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
old_table.clear();
old_table.rows.add(new_trs).draw();
$(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
$(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
});
$("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
} else {
console.log("Failed to connect to Server, in order to update Table Data.");
console.log(response);
}
})
}
setInterval(update, 30000);
window.addEventListener('resize', () => {
adjustTableHeight();
tables.draw();
});
$(".table-wrapper").scrollsync({
y_sync: true,
x_sync: true
});
adjustTableHeight();
});

View File

@@ -0,0 +1,171 @@
const adjustTableHeight = () => {
const tablesContainer = document.getElementById('tables-container');
if (!tablesContainer)
return;
const upperDistance = tablesContainer.getBoundingClientRect().top;
const tableWrappers = document.getElementsByClassName('table-wrapper');
for (let i = 0; i < tableWrappers.length; i++) {
// Ensure we are starting from maximum size prior to calculation.
tableWrappers[i].style.height = null;
tableWrappers[i].style.maxHeight = null;
// Set as a reasonable height, but still allows the user to resize element if they desire.
const currentHeight = tableWrappers[i].offsetHeight;
const maxHeight = (window.innerHeight - upperDistance) / Math.min(tableWrappers.length, 4);
if (currentHeight > maxHeight) {
tableWrappers[i].style.height = `calc(${maxHeight}px - 1rem)`;
}
tableWrappers[i].style.maxHeight = `${currentHeight}px`;
}
};
/**
* Convert an integer number of seconds into a human readable HH:MM format
* @param {Number} seconds
* @returns {string}
*/
const secondsToHours = (seconds) => {
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds - (hours * 3600)) / 60).toString().padStart(2, '0');
return `${hours}:${minutes}`;
};
window.addEventListener('load', () => {
const tables = $(".table").DataTable({
paging: false,
info: false,
dom: "t",
stateSave: true,
stateSaveCallback: function(settings, data) {
delete data.search;
localStorage.setItem(`DataTables_${settings.sInstance}_/tracker`, JSON.stringify(data));
},
stateLoadCallback: function(settings) {
return JSON.parse(localStorage.getItem(`DataTables_${settings.sInstance}_/tracker`));
},
footerCallback: function(tfoot, data, start, end, display) {
if (tfoot) {
const activityData = this.api().column('lastActivity:name').data().toArray().filter(x => !isNaN(x));
Array.from(tfoot?.children).find(td => td.classList.contains('last-activity')).innerText =
(activityData.length) ? secondsToHours(Math.min(...activityData)) : 'None';
}
},
columnDefs: [
{
targets: 'last-activity',
name: 'lastActivity'
},
{
targets: 'hours',
render: function (data, type, row) {
if (type === "sort" || type === 'type') {
if (data === "None")
return Number.MAX_VALUE;
return parseInt(data);
}
if (data === "None")
return data;
return secondsToHours(data);
}
},
{
targets: 'number',
render: function (data, type, row) {
if (type === "sort" || type === 'type') {
return parseFloat(data);
}
return data;
}
},
{
targets: 'fraction',
render: function (data, type, row) {
let splitted = data.split("/", 1);
let current = splitted[0]
if (type === "sort" || type === 'type') {
return parseInt(current);
}
return data;
}
},
],
// DO NOT use the scrollX or scrollY options. They cause DataTables to split the thead from
// the tbody and render two separate tables.
});
const searchBox = document.getElementById("search");
searchBox.value = tables.search();
searchBox.focus();
searchBox.select();
const doSearch = () => {
tables.search(searchBox.value);
tables.draw();
};
searchBox.addEventListener("keyup", doSearch);
window.addEventListener("keydown", (event) => {
if (!event.ctrlKey && !event.altKey && event.key.length === 1 && document.activeElement !== searchBox) {
searchBox.focus();
searchBox.select();
}
if (!event.ctrlKey && !event.altKey && !event.shiftKey && event.key === "Escape") {
if (searchBox.value !== "") {
searchBox.value = "";
doSearch();
}
searchBox.blur();
if (!document.getElementById("tables-container"))
window.scroll(0, 0);
event.preventDefault();
}
});
const tracker = document.getElementById('tracker-wrapper').getAttribute('data-tracker');
const target_second = document.getElementById('tracker-wrapper').getAttribute('data-second') + 3;
function getSleepTimeSeconds(){
// -40 % 60 is -40, which is absolutely wrong and should burn
var sleepSeconds = (((target_second - new Date().getSeconds()) % 60) + 60) % 60;
return sleepSeconds || 60;
}
const update = () => {
const target = $("<div></div>");
console.log("Updating Tracker...");
target.load(location.href, function (response, status) {
if (status === "success") {
target.find(".table").each(function (i, new_table) {
const new_trs = $(new_table).find("tbody>tr");
const footer_tr = $(new_table).find("tfoot>tr");
const old_table = tables.eq(i);
const topscroll = $(old_table.settings()[0].nScrollBody).scrollTop();
const leftscroll = $(old_table.settings()[0].nScrollBody).scrollLeft();
old_table.clear();
if (footer_tr.length) {
$(old_table.table).find("tfoot").html(footer_tr);
}
old_table.rows.add(new_trs);
old_table.draw();
$(old_table.settings()[0].nScrollBody).scrollTop(topscroll);
$(old_table.settings()[0].nScrollBody).scrollLeft(leftscroll);
});
$("#multi-stream-link").replaceWith(target.find("#multi-stream-link"));
} else {
console.log("Failed to connect to Server, in order to update Table Data.");
console.log(response);
}
})
setTimeout(update, getSleepTimeSeconds()*1000);
}
setTimeout(update, getSleepTimeSeconds()*1000);
window.addEventListener('resize', () => {
adjustTableHeight();
tables.draw();
});
adjustTableHeight();
});

View File

@@ -27,25 +27,28 @@ window.addEventListener('load', () => {
tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results);
adjustHeaderWidth();
const title = document.querySelector('h1')
if (title) {
document.title = title.textContent;
}
// Reset the id of all header divs to something nicer
const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
for (let i=0; i < headers.length; i++){
const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
headers[i].setAttribute('id', headerId);
headers[i].addEventListener('click', () =>
window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
header.setAttribute('id', headerId);
header.addEventListener('click', () => {
window.location.hash = `#${headerId}`;
header.scrollIntoView();
});
}
// Manually scroll the user to the appropriate header if anchor navigation is used
if (scrollTargetIndex > -1) {
try{
const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
} catch(error) {
console.error(error);
document.fonts.ready.finally(() => {
if (window.location.hash) {
const scrollTarget = document.getElementById(window.location.hash.substring(1));
scrollTarget?.scrollIntoView();
}
}
});
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =

View File

@@ -23,6 +23,7 @@ window.addEventListener('load', () => {
games.forEach((game) => {
const gameTitle = document.createElement('h2');
gameTitle.innerText = game.gameTitle;
gameTitle.id = `${encodeURIComponent(game.gameTitle)}`;
tutorialDiv.appendChild(gameTitle);
game.tutorials.forEach((tutorial) => {
@@ -65,6 +66,15 @@ window.addEventListener('load', () => {
showError();
console.error(error);
}
// Check if we are on an anchor when coming in, and scroll to it.
const hash = window.location.hash;
if (hash) {
const offset = 128; // To account for navbar banner at top of page.
window.scrollTo(0, 0);
const rect = document.getElementById(hash.slice(1)).getBoundingClientRect();
window.scrollTo(rect.left, rect.top - offset);
}
};
ajax.open('GET', `${window.location.origin}/static/generated/tutorials.json`, true);
ajax.send();

View File

@@ -6,6 +6,7 @@ window.addEventListener('load', () => {
"order": [[ 3, "desc" ]],
"info": false,
"dom": "t",
"stateSave": true,
});
$("#seeds-table").DataTable({
"paging": false,
@@ -13,5 +14,6 @@ window.addEventListener('load', () => {
"order": [[ 2, "desc" ]],
"info": false,
"dom": "t",
"stateSave": true,
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
let deletedOptions = {};
window.addEventListener('load', () => {
const worldName = document.querySelector('#weighted-options').getAttribute('data-game');
// Generic change listener. Detecting unique qualities and acting on them here reduces initial JS initialisation time
// and handles dynamically created elements
document.addEventListener('change', (evt) => {
// Handle updates to range inputs
if (evt.target.type === 'range') {
// Update span containing range value. All ranges have a corresponding `{rangeId}-value` span
document.getElementById(`${evt.target.id}-value`).innerText = evt.target.value;
// If the changed option was the name of a game, determine whether to show or hide that game's div
if (evt.target.id.startsWith('game||')) {
const gameName = evt.target.id.split('||')[1];
const gameDiv = document.getElementById(`${gameName}-container`);
if (evt.target.value > 0) {
gameDiv.classList.remove('hidden');
} else {
gameDiv.classList.add('hidden');
}
}
}
});
// Generic click listener
document.addEventListener('click', (evt) => {
// Handle creating new rows for Range options
if (evt.target.classList.contains('add-range-option-button')) {
const optionName = evt.target.getAttribute('data-option');
addRangeRow(optionName);
}
// Handle deleting range rows
if (evt.target.classList.contains('range-option-delete')) {
const targetRow = document.querySelector(`tr[data-row="${evt.target.getAttribute('data-target')}"]`);
setDeletedOption(
targetRow.getAttribute('data-option-name'),
targetRow.getAttribute('data-value'),
);
targetRow.parentElement.removeChild(targetRow);
}
});
// Listen for enter presses on inputs intended to add range rows
document.addEventListener('keydown', (evt) => {
if (evt.key === 'Enter') {
evt.preventDefault();
}
if (evt.key === 'Enter' && evt.target.classList.contains('range-option-value')) {
const optionName = evt.target.getAttribute('data-option');
addRangeRow(optionName);
}
});
// Detect form submission
document.getElementById('weighted-options-form').addEventListener('submit', (evt) => {
// Save data to localStorage
const weightedOptions = {};
document.querySelectorAll('input[name]').forEach((input) => {
const keys = input.getAttribute('name').split('||');
// Determine keys
const optionName = keys[0] ?? null;
const subOption = keys[1] ?? null;
// Ensure keys exist
if (!weightedOptions[optionName]) { weightedOptions[optionName] = {}; }
if (subOption && !weightedOptions[optionName][subOption]) {
weightedOptions[optionName][subOption] = null;
}
if (subOption) { return weightedOptions[optionName][subOption] = determineValue(input); }
if (optionName) { return weightedOptions[optionName] = determineValue(input); }
});
localStorage.setItem(`${worldName}-weights`, JSON.stringify(weightedOptions));
localStorage.setItem(`${worldName}-deletedOptions`, JSON.stringify(deletedOptions));
});
// Remove all deleted values as specified by localStorage
deletedOptions = JSON.parse(localStorage.getItem(`${worldName}-deletedOptions`) || '{}');
Object.keys(deletedOptions).forEach((optionName) => {
deletedOptions[optionName].forEach((value) => {
const targetRow = document.querySelector(`tr[data-row="${value}-row"]`);
targetRow.parentElement.removeChild(targetRow);
});
});
// Populate all settings from localStorage on page initialisation
const previousSettingsJson = localStorage.getItem(`${worldName}-weights`);
if (previousSettingsJson) {
const previousSettings = JSON.parse(previousSettingsJson);
Object.keys(previousSettings).forEach((option) => {
if (typeof previousSettings[option] === 'string') {
return document.querySelector(`input[name="${option}"]`).value = previousSettings[option];
}
Object.keys(previousSettings[option]).forEach((value) => {
const input = document.querySelector(`input[name="${option}||${value}"]`);
if (!input?.type) {
return console.error(`Unable to populate option with name ${option}||${value}.`);
}
switch (input.type) {
case 'checkbox':
input.checked = (parseInt(previousSettings[option][value], 10) === 1);
break;
case 'range':
input.value = parseInt(previousSettings[option][value], 10);
break;
case 'number':
input.value = previousSettings[option][value].toString();
break;
default:
console.error(`Found unsupported input type: ${input.type}`);
}
});
});
}
});
const addRangeRow = (optionName) => {
const inputQuery = `input[type=number][data-option="${optionName}"].range-option-value`;
const inputTarget = document.querySelector(inputQuery);
const newValue = inputTarget.value;
if (!/^-?\d+$/.test(newValue)) {
alert('Range values must be a positive or negative integer!');
return;
}
inputTarget.value = '';
const tBody = document.querySelector(`table[data-option="${optionName}"].range-rows tbody`);
const tr = document.createElement('tr');
tr.setAttribute('data-row', `${optionName}-${newValue}-row`);
tr.setAttribute('data-option-name', optionName);
tr.setAttribute('data-value', newValue);
const tdLeft = document.createElement('td');
tdLeft.classList.add('td-left');
const label = document.createElement('label');
label.setAttribute('for', `${optionName}||${newValue}`);
label.innerText = newValue.toString();
tdLeft.appendChild(label);
tr.appendChild(tdLeft);
const tdMiddle = document.createElement('td');
tdMiddle.classList.add('td-middle');
const range = document.createElement('input');
range.setAttribute('type', 'range');
range.setAttribute('min', '0');
range.setAttribute('max', '50');
range.setAttribute('value', '0');
range.setAttribute('id', `${optionName}||${newValue}`);
range.setAttribute('name', `${optionName}||${newValue}`);
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
const tdRight = document.createElement('td');
tdRight.classList.add('td-right');
const valueSpan = document.createElement('span');
valueSpan.setAttribute('id', `${optionName}||${newValue}-value`);
valueSpan.innerText = '0';
tdRight.appendChild(valueSpan);
tr.appendChild(tdRight);
const tdDelete = document.createElement('td');
const deleteSpan = document.createElement('span');
deleteSpan.classList.add('range-option-delete');
deleteSpan.classList.add('js-required');
deleteSpan.setAttribute('data-target', `${optionName}-${newValue}-row`);
deleteSpan.innerText = '❌';
tdDelete.appendChild(deleteSpan);
tr.appendChild(tdDelete);
tBody.appendChild(tr);
// Remove this option from the set of deleted options if it exists
unsetDeletedOption(optionName, newValue);
};
/**
* Determines the value of an input element, or returns a 1 or 0 if the element is a checkbox
*
* @param {object} input - The input element.
* @returns {number} The value of the input element.
*/
const determineValue = (input) => {
switch (input.type) {
case 'checkbox':
return (input.checked ? 1 : 0);
case 'range':
return parseInt(input.value, 10);
default:
return input.value;
}
};
/**
* Sets the deleted option value for a given world and option name.
* If the world or option does not exist, it creates the necessary entries.
*
* @param {string} optionName - The name of the option.
* @param {*} value - The value to be set for the deleted option.
* @returns {void}
*/
const setDeletedOption = (optionName, value) => {
deletedOptions[optionName] = deletedOptions[optionName] || [];
deletedOptions[optionName].push(`${optionName}-${value}`);
};
/**
* Removes a specific value from the deletedOptions object.
*
* @param {string} optionName - The name of the option.
* @param {*} value - The value to be removed
* @returns {void}
*/
const unsetDeletedOption = (optionName, value) => {
if (!deletedOptions.hasOwnProperty(optionName)) { return; }
if (deletedOptions[optionName].includes(`${optionName}-${value}`)) {
deletedOptions[optionName].splice(deletedOptions[optionName].indexOf(`${optionName}-${value}`), 1);
}
if (deletedOptions[optionName].length === 0) {
delete deletedOptions[optionName];
}
};

View File

@@ -0,0 +1,20 @@
User-agent: Googlebot
Disallow: /
User-agent: APIs-Google
Disallow: /
User-agent: AdsBot-Google-Mobile
Disallow: /
User-agent: AdsBot-Google-Mobile
Disallow: /
User-agent: Mediapartners-Google
Disallow: /
User-agent: Google-Safety
Disallow: /
User-agent: *
Disallow: /

View File

@@ -1,4 +1,3 @@
Copyright 2022 Berserker66 (Fabian Dill)
Copyright 2022 LegendaryLinux (Chris Wilson)
All rights reserved.

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

View File

@@ -0,0 +1,3 @@
Copyright 2022 LegendaryLinux (Chris Wilson)
All rights reserved.

View File

@@ -1,4 +1,3 @@
Copyright 2022 Berserker66 (Fabian Dill)
Copyright 2022 LegendaryLinux (Chris Wilson)
All rights reserved.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -1,4 +1,3 @@
Copyright 2022 Berserker66 (Fabian Dill)
Copyright 2022 LegendaryLinux (Chris Wilson)
All rights reserved.

View File

@@ -0,0 +1,30 @@
#player-tracker-wrapper{
margin: 0;
}
#inventory-table{
padding: 8px 10px 2px 6px;
background-color: #42b149;
border-radius: 4px;
border: 2px solid black;
}
#inventory-table tr.column-headers td {
font-size: 1rem;
padding: 0 5rem 0 0;
}
#inventory-table td{
padding: 0 0.5rem 0.5rem;
font-family: LexendDeca-Light, monospace;
font-size: 2.5rem;
color: #ffffff;
}
#inventory-table td img{
vertical-align: middle;
}
.hide {
display: none;
}

View File

@@ -56,7 +56,3 @@
#file-input{
display: none;
}
.interactive{
color: #ffef00;
}

Some files were not shown because too many files have changed in this diff Show More