Compare commits

..

51 Commits

Author SHA1 Message Date
NewSoupVi
e92bd65f08 Simplify sentence 2025-09-09 01:00:23 +02:00
NewSoupVi
3c7331b206 Reword 2025-09-09 00:58:55 +02:00
NewSoupVi
71475230ba Core: Only error in playthrough generation if game is not beatable
The current flow of accessibility works like this:

```
if fulfills_accessibility fails:
    if multiworld can be beaten:
        log a warning
    else:
        raise Exception

if playthrough is enabled:
    if any progression items are not reachable:
        raise Exception
```

This means that if you do a generation where the game is beatable but some full players' items are not reachable, it doesn't crash on accessibility check, but then crashes on playthrough. This means that **whether it crashes depends on whether you have playthrough enabled or not**.

Imo, erroring on something accessibility-related is outside of the scope of create_playthrough. Create_playthrough only needs to care about whether it can fulfill its own goal - Building a minimal playthrough to everyone's victory.
The actual accessibility check should take care of the accessibility.
2025-09-08 23:22:22 +02:00
qwint
17dad8313e Test: Remove most dependencies on lttp (#5338)
* removes the last dependencies on lttp in tests

* removing test.bases.TestBase from docs as well

* rename bases

* move imports to bases
2025-09-08 21:36:26 +02:00
qwint
63f3512829 Core: adds a custom KeyError for invalid item names (#4223)
* adds a custom KeyError for raising on world.create_item() if the passed in name is invalid

* Update __init__.py

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
2025-09-08 11:11:46 +02:00
lgbarrere
1b200fb20b Choo-Choo Charles: implement new game and documentations (#5287)
* Add cccharles world to AP

> The logic has been tested, the game can be completed
> The logic is simple and it does not take into account options
! The documentations are a work in progress

* Update documentations

> Redacted French and English Setup Guides
> Redacted French and English Game Pages

* Handling PR#5287 remarks

> Revert unexpected changes on .run\Archipelago Unittests.run.xml (base Archipelago file)
> Fixed typo "querty" -> "qwerty" in fr and eng Game Pages
> Adding "Game page in other languages" section to eng Game Page documentation
> Improved Steam path in fr and eng Setup Guides

* Handled PR remarks + fixes

> Added get_filler_item_name() to remove warnings
> Fixed irrelevant links for documentations
> Used the Player Options page instead of the default YAML on GitHub
> Reworded all locations to make them simple and clear
> Split some locations that can be linked with an entrance rule
> Reworked all options
> Updated regions according to locations
> Replaced unnecessary rules by rules on entrances

* Empty Options.py

Only the base options are handled yet, "work in progress" features removed.

* Handled PR remark

> Fixed specific UT name

* Handled PR remarks

> UT updated by replacing depreciated features

* Add start_inventory_from_pool as option

This start_inventory_from_pool option is like regular start inventory but it takes items from the pool and replaces them with fillers

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

* Handled PR remarks

> Mainly fixed editorial and minor issues without impact on UT results (still passed)

* Update the guides according to releases

> Updated the depreciated guides because the may to release the Mod has been changed
> Removed the fixed issues from 'Known Issues'
> Add the "Mod Download" section to simplify the others sections.

* Handled PR remark

> base_id reduced to ensure it fits to signed int (32 bits) in case of future AP improvements

* Handled PR remarks

> Set topology_present to False because unnecessary
> Added an exception in case of unknown item instead of using filler classification
> Fixed an issue that caused the "Bug Spray" to be considered as filler
> Reworked the test_claire_breakers() test to ensure the lighthouse mission can only be finished if at least 4 breakers are collected

* Added Choo-Choo Charles to README.md

* CCCharles: Added rules to win

> The victory could be accessed from sphere 1, this is now fixed by adding the following items as requirements:
- Temple Key
- Green Egg
- Blue Egg
- Red Egg

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2025-09-08 10:37:51 +02:00
Duck
8a091c9e02 Kivy: Fix MessageBox popups (#5193) 2025-09-06 19:21:36 +02:00
black-sliver
c3c517a200 DS3: use yaml.safe_load (#5360) 2025-09-06 19:09:41 +02:00
black-sliver
c5b404baa8 CI: only trigger release action for bare semver (#5065) 2025-09-05 23:33:13 +02:00
NewSoupVi
77cab13827 ArchipIDLE: Remove game #5422 2025-09-05 23:20:37 +02:00
Scipio Wright
31b2eed1f9 TUNIC: Make the local_fill option show up on the website #5348 2025-09-05 17:09:33 +02:00
Fabian Dill
e23720a977 LttP: shuffle around gitignore (#5307) 2025-09-05 16:48:39 +02:00
Fabian Dill
90058ee175 CommonClient: fix /items, /locations and /missing not working if the datapackage is local (#5350) 2025-09-05 16:48:15 +02:00
Ziktofel
5c6dbdd98f SC2: Update docs for Linux launch script to follow the core client migration (#5407) 2025-09-05 16:44:28 +02:00
Ziktofel
8c2d246a53 SC2: Restrict allow Orphan to missions that already require that (#5405)
* Restrict Allow Orphan for items to missions that already require that

* Add test for build mission orphan behavior

* Update item lists for Allow Orphan flag

* Update the unit test to clear that BotB is not in the mission list

* Update unit test name
2025-09-05 16:44:01 +02:00
qwint
0d26b6426f Core: Remove lttp module requirement from generation #5384 2025-09-05 16:42:12 +02:00
Alchav
b9fb5c8b44 Super Mario Land 2: Remove erroneous Coinsanity checks #5364
Co-authored-by: alchav <alchav@jalchavware.com>
2025-09-05 16:37:31 +02:00
Scipio Wright
e518e41f67 Hollow Knight: Make the connecting header separate from the yaml one (#5353)
* Update setup_en.md

* Update setup_pt_br.md
2025-09-05 16:34:57 +02:00
qwint
7a38e44e64 Test: Deprecate TestBase (#5339)
* deprecate TestBase and fix the last use of it in main

* actually delete it because test discovery also imports it lmao
2025-09-05 16:24:20 +02:00
Jérémie Bolduc
64d3c55d62 Stardew Valley: Add money logic to traveling merchant (#5327)
* add rule to traveling merchant region

* add a test so kaito is happy
2025-09-05 16:23:25 +02:00
Kaito Sinclaire
89be26a33a Heretic: Update Steam URL (#5304) 2025-09-05 16:22:11 +02:00
Kaito Sinclaire
5b5e2c3567 SMZ3: Fix distribution of SM prizes (#5303) 2025-09-05 16:21:08 +02:00
Exempt-Medic
ef59a5ee11 TUNIC: Change non_local_items Earlier (#5249) 2025-09-04 19:04:21 -04:00
Scipio Wright
b0b3e3668f TUNIC: The Big Refactor (#5195)
* Make it actually return false if it gets to the backup lists and fails them

* Fix stuff after merge

* Add outlet regions, create new regions as needed for them

* Put together part of decoupled and direction pairs

* make direction pairs work

* Make decoupled work

* Make fixed shop work again

* Fix a few minor bugs

* Fix a few minor bugs

* Fix plando

* god i love programming

* Reorder portal list

* Update portal sorter for variable shops

* Add missing parameter

* Some cleanup of prints and functions

* Fix typo

* it's aliiiiiive

* Make seed groups not sync decoupled

* Add test with full-shop plando

* Fix bug with vanilla portals

* Handle plando connections and direction pair errors

* Update plando checking for decoupled

* Fix typo

* Fix exception text to be shorter

* Add some more comments

* Add todo note

* Remove unused safety thing

* Remove extra plando connections definition in options

* Make seed groups in decoupled with overlapping but not fully overlapped plando connections interact nicely without messing with what the entrances look like in the spoiler log

* Fix weird edge case that is technically user error

* Add note to fixed shop

* Fix parsing shop names in UT

* Remove debug print

* Actually make UT work

* multiworld. to world.

* Fix typo from merge

* Make it so the shops show up in the entrance hints

* Fix bug in ladder storage rules

* Remove blank line

* # Conflicts:
#	worlds/tunic/__init__.py
#	worlds/tunic/er_data.py
#	worlds/tunic/er_rules.py
#	worlds/tunic/er_scripts.py
#	worlds/tunic/rules.py
#	worlds/tunic/test/test_access.py

* Fix issues after merge

* Update plando connections stuff in docs

* Make early bushes only contain grass

* Fix library mistake

* Backport changes to grass rando (#20)

* Backport changes to grass rando

* add_rule instead of set_rule for the special cases, add special cases for back of swamp laurels area cause I should've made a new region for the swamp upper entrance

* Remove item name group for grass

* Update grass rando option descriptions

- Also ignore grass fill for single player games

* Ignore grass fill option for solo rando

* Update er_rules.py

* Fix pre fill issue

* Remove duplicate option

* Add excluded grass locations back

* Hide grass fill option from simple ui options page

* Check for start with sword before setting grass rules

* Update worlds/tunic/options.py

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

* has_stick -> has_melee

* has_stick -> has_melee

* Add a failsafe for direction pairing

* Fix playthrough crash bug

* Remove init from logicmixin

* Updates per code review (thanks hesto)

* has_stick to has_melee in newer update

* has_stick to has_melee in newer update

* Exclude grass from get_filler_item_name

- non-grass rando games were accidentally seeing grass items get shuffled in as filler, which is funny but probably shouldn't happen

* Update worlds/tunic/__init__.py

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

* Apply suggestions from code review

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

* change the rest of grass_fill to local_fill

* Filter out grass from filler_items

* remove -> discard

* Update worlds/tunic/__init__.py

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

* Starting out

* Rules for breakable regions

* # Conflicts:
#	worlds/tunic/__init__.py
#	worlds/tunic/combat_logic.py
#	worlds/tunic/er_data.py
#	worlds/tunic/er_rules.py
#	worlds/tunic/er_scripts.py

* Cleanup more stuff after merge

* Revert "Cleanup more stuff after merge"

This reverts commit a6ee9a93da.

* Revert "# Conflicts:"

This reverts commit c74ccd74a4.

* Cleanup more stuff after merge

* change has_stick to has_melee

* Update grass list with combat logic regions

* More fixes from combat logic merge

* Fix some dumb stuff (#21)

* Reorganize pre fill for grass

* make the rest of it work, it's pr ready, boom

* Make it work in not pot shuffle

* Merge grass rando

* multiworld -> world get_location, use has_any

* Swap out region for West Garden Before Terry grass

* Adjust west garden rules to add west combat region

* Adjust grass regions for south checkpoint grass

* Adjust grass regions for after terry grass

* Adjust grass regions for west combat grass

* Adjust grass regions for dagger house grass

* Adjust grass regions for south checkpoint grass, adjust regions and rules for some related locations

* Finish the remainder of the west garden grass, reformat ruined atoll a little

* More hex quest updates

- Implement page ability shuffle for hex quest
- Fix keys behind bosses if hex goal is less than 3
- Added check to fix conflicting hex quest options
- Add option to slot data

* Change option comparison

* Change option checking and fix some stuff

- also keep prayer first on low hex counts

* Update option defaulting

* Update option checking

* Fix option assignment again

* Merge in hex hunt

* Merge in changes

* Clean up imports

* Add ability type to UT stuff

* merge it all

* Make local fill work across pot and grass (to be adjusted later)

* Make separate pools for the grass and non-grass fills

* Fix id overlap

* Update option description

* Fix default

* Reorder localfill option desc

* Load the purgatory ones in

* Adjustments after merge

* Fully remove logicrules

* Fix UT support with fixed shop option

* Add breakable shuffle to the ut stuff

* Make it load in a specific number of locations

* Add Silent's spoiler log ability thing

* Fix for groups

* Fix for groups

* Fix typo

* Fix hex quest UT support

* Use .get

* UT fixes, classification fixes

* Rename some locations

* Adjust guard house names

* Adjust guard house names

* Rework create_item

* Fix for plando connections

* Rename, add new breakables

* Rename more stuff

* Time to rename them again

* Fix issue with fixed shop + decoupled

* Put in an exception to catch that error in the future

* Update create_item to match main

* Update spoiler log lines for hex abilities

* Burn the signs down

* Bring over the combat logic fix

* Merge in combat logic fix

* Silly static method thing

* Move a few areas to before well instead of east forest

* Add an all_random hidden option for dev stuff

* Port over changes from main

* Fix west courtyard pot regions

* Remove debug prints

* Fix fortress courtyard and beneath the fortress loc groups again

* Add exception handling to deal with duplicate apworlds

* Fix typo

* More missing loc group conversions

* Initial fuse shuffle stuff

* Fix gun missing from combat_items, add new for combat logic cache, very slight refactor of check_combat_reqs to let it do the changeover in a less complicated fashion, fix area being a boss area rather than non-boss area for a check

* Add fuse shuffle logic

* reorder atoll statue rule

* Update traversal reqs

* Remove fuse shuffle from temple door

* Combine rules and option checking

* Add bell shuffle; fix fuse location groups

* Fix portal rules not requiring prayer

* Merge the grass laurels exit grass PR

* Merge in fortress bridge PR

* Do a little clean up

* Fix a regression

* Update after merge

* Some more stuff

* More Silent changes

* Update more info section in game info page

* Fix rules for atoll and swamp fuses

* Precollect cathedral fuse in ER

* actually just make the fuse useful instead of progression

* Add it to the swamp and cath rules too

* Fix cath fuse name

* Minor fixes and edits

* Some UT stuff

* Fix a couple more groups

* Move a bunch of UT stuff to its own file

* Fix up a couple UT things

* Couple minor ER fixes

* Formatting change

* UT poptracker stuff enabled since it's optional in one of the releases

* Add author string to world class

* Adjust local fill option name

* Update ut_stuff to match the PR

* Add exception handling for UT with old apworld

* Fix missing tracker_world

* Remove extra entrance from cath main -> elevator

Entry <-> Elev exists,
Entry <-> Main exists
So no connection is needed between Main and Elev

* Fix so that decoupled doesn't incorrectly use get_portal_info and get_paired_portal

* Fix so that decoupled doesn't incorrectly use get_portal_info and get_paired_portal

* Update for breakables poptracker

* Backup and warnings instead

* Update typing

* Delete old regions and rules, move stuff to logic_helpers and constants

* Delete now much less useful tests

* Fix breakables map tracking

* Add more comments to init

* Add todo to grass.py

* Fix up tests

* Pull out fuse and bell shuffle

* Pull out fuse and bell shuffle

* Update worlds/tunic/options.py

Co-authored-by: qwint <qwint.42@gmail.com>

* Update worlds/tunic/logic_helpers.py

Co-authored-by: qwint <qwint.42@gmail.com>

* {} -> () in state functions

* {} -> () in state functions

* Change {} -> () in state functions, use constant for gun

* Remove floating constants in er_data

* Finish hard deprecating FixedShop

* Finish hard deprecating FixedShop

* Fix zig skip showing up in decoupled fixed shop

---------

Co-authored-by: silent-destroyer <osilentdestroyer@gmail.com>
Co-authored-by: Silent <110704408+silent-destroyer@users.noreply.github.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: qwint <qwint.42@gmail.com>
2025-09-05 00:44:32 +02:00
PoryGone
42ace29db4 Celeste 64: Logic Fixes #5417 2025-09-04 21:53:55 +02:00
qwint
03992c43d9 Docs: update mac install instructions to reflect 3.13 support (#5411) 2025-09-04 16:58:24 +02:00
PoryGone
e342a20fde Celeste (Open World): Post-merge Logic Fix (#5415)
* APWorld Skeleton

* Hair Color Rando and first items

* All interactable items

* Checkpoint Items and Locations

* First pass sample intermediate data

* Bulk of Region/location code

* JSON Data Parser

* New items and Level Item mapping

* Data Parsing fixes and most of 1a data

* 1a complete data and region/location/item creation fixes

* Add Key Location type and ID output

* Add options to slot data

* 1B Level Data

* Added Location logging

* Add Goal Area Options

* 1c Level Data

* Old Site A B C level data

* Key/Binosanity and Hair Length options

* Key Item/Location and Clutter Event handling

* Remove generic 'keys' item

* 3a level data

* 3b and 3c level data

* Chapter 4 level data

* Chapter 5 Logic Data

* Chapter 5 level data

* Trap Support

* Add TrapLink Support

* Chapter 6 A/B/C Level Data

* Add active_levels to slot_data

* Item and Location Name Groups + style cleanups

* Chapter 7 Level Data and Items, Gemsanity option

* Goal Area and victory handling

* Fix slot_data

* Add Core Level Data

* Carsanity

* Farewell Level Data and ID Range Update

* Farewell level data and handling

* Music Shuffle

* Require Cassettes

* Change default trap expiration action to Deaths

* Handle Poetry

* Mod versioning

* Rename folder, general cleanup

* Additional Cleanup

* Handle Farewell Golden Goal when Include Goldens is off

* Better handling of Farewell Golden

* Update Docs

* Beta test bug fixes

* Bump to v1.0.0

* Update Changelog

* Several Logic tweaks

* Update APWorld Version

* Add Celeste (Open World) to README

* Peer review changes

* Logic Fixes:

* Adjust Mirror Temple B Key logic

* Increment APWorld version

* Fix several logic bugs

* Add missing link

* Add Item Name Groups for common alternative item names

* Account for Madeline's post-Celeste hair-dying activities

* Account for ignored member variable and hardcoded color in Celeste codebase

* Add Blue Clouds to the logic of reaching Farewell - intro-02-launch

* Type checking workaround

* Bump version number

* Adjust Setup Guide

* Minor typing fixes

* Logic and PR fixes

* Increment APWorld Version

* Use more world helpers

* Core review

* CODEOWNERS

* Minor logic fix and insert APWorld version into spoiler

* Fix merge error
2025-09-04 03:50:59 +02:00
threeandthreee
3c28db0800 LADX: Drop a marin text option that makes patching fail #5398 2025-09-03 21:02:32 +02:00
qwint
8f88152532 MultiServer: Validate CreateHints status arg #5408 2025-09-03 21:01:56 +02:00
qwint
7a1311984f Docs: Update max py version to 3.13 #5410 2025-09-03 21:00:36 +02:00
Ziktofel
a9f594d6b2 SC2: Remove Starcraft2Client.py as Launcher.py got upgraded to work under Python 3.13 (#5406) 2025-09-02 23:50:29 +02:00
Ziktofel
5f1835c546 SC2: Content update (#5312)
Feature highlights:
- Adds many content to the SC2 game
- Allows custom mission order
- Adds race-swapped missions for build missions (except Epilogue and NCO)
- Allows War Council Nerfs (Protoss units can get pre - War Council State, alternative units get another custom nerf to match the power level of base units)
- Revamps Predator's upgrade tree (never was considered strategically important)
- Adds some units and upgrades
- Locked and excluded items can specify quantity
- Key mode (if opt-in, missions require keys to be unlocked on top of their regular regular requirements
- Victory caches - Victory locations can grant multiple items to the multiworld instead of one 
- The generator is more resilient for generator failures as it validates logic for item excludes
- Fixes the following issues:
  - https://github.com/ArchipelagoMW/Archipelago/issues/3531 
  - https://github.com/ArchipelagoMW/Archipelago/issues/3548
2025-09-02 17:40:58 +02:00
Duck
2359cceb64 AHiT: Add Death Link amnesty options (#4694)
* Add basic death link amnesty option

* Add death wish amnesty option
2025-09-02 05:29:31 +02:00
Rhenaud Dubois
a0a1c5d4c0 Pokemon Emerald: Added Pokemon Gen 3 Adjuster data (#5145)
* Added Pokemon Gen 3 Adjuster data

* Updated extracted data

* Commented out adjuster docs for now

* Replace <b> in the docs markers with **
2025-09-02 01:56:52 +02:00
qwint
14d65fdf28 Docs: Add doc for shared cache (#5129)
* adds doc file describing what the shared cache is, how to use it, and what you can currently expect in it

* Update docs/shared_cache.md

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

---------

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>
2025-09-02 01:54:34 +02:00
qwint
5fd9570368 Docs: Add section about adding Components (#5097)
* kinda driven by wanting to test the labeling change in prod but also components are a weird part of the ecosystem and could use more documentation.

* additional text describing launch/launch_subprocess and their use

* Update docs/adding games.md

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

---------

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>
2025-09-02 01:53:58 +02:00
PoryGone
c753fbff2d Celeste (Open World): Implement New Game (#4937)
* APWorld Skeleton

* Hair Color Rando and first items

* All interactable items

* Checkpoint Items and Locations

* First pass sample intermediate data

* Bulk of Region/location code

* JSON Data Parser

* New items and Level Item mapping

* Data Parsing fixes and most of 1a data

* 1a complete data and region/location/item creation fixes

* Add Key Location type and ID output

* Add options to slot data

* 1B Level Data

* Added Location logging

* Add Goal Area Options

* 1c Level Data

* Old Site A B C level data

* Key/Binosanity and Hair Length options

* Key Item/Location and Clutter Event handling

* Remove generic 'keys' item

* 3a level data

* 3b and 3c level data

* Chapter 4 level data

* Chapter 5 Logic Data

* Chapter 5 level data

* Trap Support

* Add TrapLink Support

* Chapter 6 A/B/C Level Data

* Add active_levels to slot_data

* Item and Location Name Groups + style cleanups

* Chapter 7 Level Data and Items, Gemsanity option

* Goal Area and victory handling

* Fix slot_data

* Add Core Level Data

* Carsanity

* Farewell Level Data and ID Range Update

* Farewell level data and handling

* Music Shuffle

* Require Cassettes

* Change default trap expiration action to Deaths

* Handle Poetry

* Mod versioning

* Rename folder, general cleanup

* Additional Cleanup

* Handle Farewell Golden Goal when Include Goldens is off

* Better handling of Farewell Golden

* Update Docs

* Beta test bug fixes

* Bump to v1.0.0

* Update Changelog

* Several Logic tweaks

* Update APWorld Version

* Add Celeste (Open World) to README

* Peer review changes

* Logic Fixes:

* Adjust Mirror Temple B Key logic

* Increment APWorld version

* Fix several logic bugs

* Add missing link

* Add Item Name Groups for common alternative item names

* Account for Madeline's post-Celeste hair-dying activities

* Account for ignored member variable and hardcoded color in Celeste codebase

* Add Blue Clouds to the logic of reaching Farewell - intro-02-launch

* Type checking workaround

* Bump version number

* Adjust Setup Guide

* Minor typing fixes

* Logic and PR fixes

* Increment APWorld Version

* Use more world helpers

* Core review

* CODEOWNERS
2025-08-31 23:31:09 +02:00
Jérémie Bolduc
cdf7165ab4 Stardew Valley: Use new asserts in tests (#4621)
* changes

* cherry pick stuff

* use newly create methods more

* use new assets to ease readability

* remove unneeded assert

* add assert region adapters

* use new asserts yay

* self review

* self review

* review

* replace parrot express with transportation constant

* bullshit commit again

* revert a bunch of off topic changes

* these changes seems to be on topic

* revert some undesired merge changes

* review imports

* use type instead of instance in some options

* properly return super

* review

* change one str to use a constnat
2025-08-31 16:21:23 +02:00
Etsuna
893acd2f02 Webserver: fix activity_timers for api tracker.py (#5385) 2025-08-31 14:12:32 +02:00
sgrunt
34aaa44b1f Timespinner: add support for spider traps from new client release (#4848)
Co-authored-by: sgrunt <sgrunt1987@gmail.com>
2025-08-31 01:09:22 +02:00
NewSoupVi
f2461a2fea WebHost: Ensure that OptionSets and OptionLists get exported to yaml, even when nothing is selected (#5240)
* Ensure that OptionSets and OptionLists get exported to yaml, even if nothing is selected

* forgot ItemSet and LocationSet

* Make it even less likely for there to be overlap
2025-08-30 22:33:43 +02:00
Justus Lind
bb2ecb8a97 Muse Dash: Change Exception to Option Error and Update to Muse Radio FM106 (#5374)
* Change Exception to OptionError

* Update to Muse Radio FM106.

* Add Scipio's suggestion.

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

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2025-08-29 17:41:29 +02:00
Rosalie
439be48f36 [TLOZ] Remove deprecated Utils.get_options call, part 2 (#5371)
* Updated to remove deprecated call.

* Removed unused argument.

* Removed errant client calls to Utils.get_options, and fixed call in Rom.py that was passing an argument.
2025-08-27 19:28:42 +02:00
PoryGone
750c8a9810 Stop using get_options (#5341) 2025-08-27 15:21:53 +02:00
lordlou
e11b40c94b [SM, SMZ3] get options deprecation (#5257)
* - 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

* replaced deprecated usage of Utils.get_options with settings.get_settings in SM and SMZ3
2025-08-27 15:21:28 +02:00
Rosalie
be51fb9ba9 [TLOZ] Updated to remove deprecated call. (#5266)
* Updated to remove deprecated call.

* Removed unused argument.
2025-08-27 15:20:51 +02:00
Ishigh1
e1fca86cf8 Core: Improved GER's caching of visited nodes during initialization (#5366)
* Moved the visited update

* Renamed visited to seen
2025-08-27 02:36:47 +02:00
black-sliver
1fa342b085 Core: add python 3.13 support (#5357)
* Core: fix freeze support for py3.13+

Loading Utils now patches multiprocessing.freeze_support()
Utils.freeze_support() is now deprecated

* WebHost: use pony fork on py3.13

* CI: test with py3.13
2025-08-25 17:36:39 +00:00
NewSoupVi
d146d90131 Core: Fix Priority Fill *not* crashing when it should, in cases where there is no deprioritized progression #5363 2025-08-25 17:52:04 +02:00
NewSoupVi
d5bdac02b7 Docs: Add deprioritized to AP API doc (#5355)
Did this on my phone while in the bathroom :)
2025-08-24 02:54:49 +02:00
Exempt-Medic
dfd7cbf0c5 Tests: Standardize World Exclusions, Strengthen LCS Test (#4423) 2025-08-23 18:36:25 -04:00
215 changed files with 103524 additions and 15552 deletions

View File

@@ -5,7 +5,7 @@ name: Release
on:
push:
tags:
- '*.*.*'
- 'v?[0-9]+.[0-9]+.[0-9]*'
env:
ENEMIZER_VERSION: 7.1

View File

@@ -41,12 +41,13 @@ jobs:
python:
- {version: '3.11.2'} # Change to '3.11' around 2026-06-10
- {version: '3.12'}
- {version: '3.13'}
include:
- python: {version: '3.11'} # old compat
os: windows-latest
- python: {version: '3.12'} # current
- python: {version: '3.13'} # current
os: windows-latest
- python: {version: '3.12'} # current
- python: {version: '3.13'} # current
os: macos-latest
steps:
@@ -74,7 +75,7 @@ jobs:
os:
- ubuntu-latest
python:
- {version: '3.12'} # current
- {version: '3.13'} # current
steps:
- uses: actions/checkout@v4

View File

@@ -1719,9 +1719,10 @@ class Spoiler:
logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (
location.item.name, location.item.player, location.name, location.player) for location in
sphere_candidates])
if any([multiworld.worlds[location.item.player].options.accessibility != 'minimal' for location in sphere_candidates]):
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
f'Something went terribly wrong here.')
if not multiworld.has_beaten_game(state):
raise RuntimeError("During playthrough generation, the game was determined to be unbeatable. "
"Something went terribly wrong here. "
f"Unreachable progression items: {sphere_candidates}")
else:
self.unreachables = sphere_candidates
break

View File

@@ -99,17 +99,6 @@ class ClientCommandProcessor(CommandProcessor):
self.ctx.on_print_json({"data": parts, "cmd": "PrintJSON"})
return True
def get_current_datapackage(self) -> dict[str, typing.Any]:
"""
Return datapackage for current game if known.
:return: The datapackage for the currently registered game. If not found, an empty dictionary will be returned.
"""
if not self.ctx.game:
return {}
checksum = self.ctx.checksums[self.ctx.game]
return Utils.load_data_package_for_checksum(self.ctx.game, checksum)
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."""
@@ -119,8 +108,8 @@ class ClientCommandProcessor(CommandProcessor):
count = 0
checked_count = 0
lookup = self.get_current_datapackage().get("location_name_to_id", {})
for location, location_id in lookup.items():
lookup = self.ctx.location_names[self.ctx.game]
for location_id, location in lookup.items():
if filter_text and filter_text not in location:
continue
if location_id < 0:
@@ -141,11 +130,10 @@ class ClientCommandProcessor(CommandProcessor):
self.output("No missing location checks found.")
return True
def output_datapackage_part(self, key: str, name: str) -> bool:
def output_datapackage_part(self, name: typing.Literal["Item Names", "Location Names"]) -> bool:
"""
Helper to digest a specific section of this game's datapackage.
:param key: The dictionary key in the datapackage.
:param name: Printed to the user as context for the part.
:return: Whether the process was successful.
@@ -154,23 +142,20 @@ class ClientCommandProcessor(CommandProcessor):
self.output(f"No game set, cannot determine {name}.")
return False
lookup = self.get_current_datapackage().get(key)
if lookup is None:
self.output("datapackage not yet loaded, try again")
return False
lookup = self.ctx.item_names if name == "Item Names" else self.ctx.location_names
lookup = lookup[self.ctx.game]
self.output(f"{name} for {self.ctx.game}")
for key in lookup:
self.output(key)
for name in lookup.values():
self.output(name)
return True
def _cmd_items(self) -> bool:
"""List all item names for the currently running game."""
return self.output_datapackage_part("item_name_to_id", "Item Names")
return self.output_datapackage_part("Item Names")
def _cmd_locations(self) -> bool:
"""List all location names for the currently running game."""
return self.output_datapackage_part("location_name_to_id", "Location Names")
return self.output_datapackage_part("Location Names")
def output_group_part(self, group_key: typing.Literal["item_name_groups", "location_name_groups"],
filter_key: str,

View File

@@ -549,10 +549,12 @@ def distribute_items_restrictive(multiworld: MultiWorld,
if prioritylocations and regular_progression:
# retry with one_item_per_player off because some priority fills can fail to fill with that optimization
# deprioritized items are still not in the mix, so they need to be collected into state first.
# allow_partial should only be set if there is deprioritized progression to fall back on.
priority_retry_state = sweep_from_pool(multiworld.state, deprioritized_progression)
fill_restrictive(multiworld, priority_retry_state, prioritylocations, regular_progression,
single_player_placement=single_player, swap=False, on_place=mark_for_locking,
name="Priority Retry", one_item_per_player=False, allow_partial=True)
name="Priority Retry", one_item_per_player=False,
allow_partial=bool(deprioritized_progression))
if prioritylocations and deprioritized_progression:
# There are no more regular progression items that can be placed on any priority locations.

View File

@@ -166,19 +166,10 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
f"A mix is also permitted.")
from worlds.AutoWorld import AutoWorldRegister
from worlds.alttp.EntranceRandomizer import parse_arguments
erargs = parse_arguments(['--multi', str(args.multi)])
erargs.seed = seed
erargs.plando_options = args.plando
erargs.spoiler = args.spoiler
erargs.race = args.race
erargs.outputname = seed_name
erargs.outputpath = args.outputpath
erargs.skip_prog_balancing = args.skip_prog_balancing
erargs.skip_output = args.skip_output
erargs.spoiler_only = args.spoiler_only
erargs.name = {}
erargs.csv_output = args.csv_output
args.outputname = seed_name
args.sprite = dict.fromkeys(range(1, args.multi+1), None)
args.sprite_pool = dict.fromkeys(range(1, args.multi+1), None)
args.name = {}
settings_cache: dict[str, tuple[argparse.Namespace, ...]] = \
{fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None)
@@ -205,7 +196,7 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
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_options = {}
args.player_options = {}
player = 1
while player <= args.multi:
@@ -218,21 +209,21 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
for k, v in vars(settingsObject).items():
if v is not None:
try:
getattr(erargs, k)[player] = v
getattr(args, k)[player] = v
except AttributeError:
setattr(erargs, k, {player: v})
setattr(args, k, {player: v})
except Exception as e:
raise Exception(f"Error setting {k} to {v} for player {player}") from e
# name was not specified
if player not in erargs.name:
if player not in args.name:
if path == args.weights_file_path:
# weights file, so we need to make the name unique
erargs.name[player] = f"Player{player}"
args.name[player] = f"Player{player}"
else:
# use the filename
erargs.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
args.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
args.name[player] = handle_name(args.name[player], player, name_counter)
player += 1
except Exception as e:
@@ -240,10 +231,10 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
else:
raise RuntimeError(f'No weights specified for player {player}')
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 len(set(name.lower() for name in args.name.values())) != len(args.name):
raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in args.name.values())}")
return erargs, seed
return args, seed
def read_weights_yamls(path) -> tuple[Any, ...]:

View File

@@ -484,7 +484,7 @@ def main(args: argparse.Namespace | dict | None = None):
if __name__ == '__main__':
init_logging('Launcher')
Utils.freeze_support()
multiprocessing.freeze_support()
multiprocessing.set_start_method("spawn") # if launched process uses kivy, fork won't work
parser = argparse.ArgumentParser(
description='Archipelago Launcher',

View File

@@ -37,7 +37,7 @@ def main(args, seed=None, baked_server_options: dict[str, object] | None = None)
logger = logging.getLogger()
multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
multiworld.plando_options = args.plando_options
multiworld.plando_options = args.plando
multiworld.game = args.game.copy()
multiworld.player_name = args.name.copy()
multiworld.sprite = args.sprite.copy()

View File

@@ -1961,6 +1961,16 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
if not locations:
await ctx.send_msgs(client, [{"cmd": "InvalidPacket", "type": "arguments",
"text": "CreateHints: No locations specified.", "original_cmd": cmd}])
return
try:
status = HintStatus(status)
except ValueError as err:
await ctx.send_msgs(client,
[{"cmd": "InvalidPacket", "type": "arguments",
"text": f"Unknown Status: {err}",
"original_cmd": cmd}])
return
hints = []

View File

@@ -20,7 +20,6 @@ Currently, the following games are supported:
* Meritous
* Super Metroid/Link to the Past combo randomizer (SMZ3)
* ChecksFinder
* ArchipIDLE
* Hollow Knight
* The Witness
* Sonic Adventure 2: Battle
@@ -81,6 +80,8 @@ Currently, the following games are supported:
* Super Mario Land 2: 6 Golden Coins
* shapez
* Paint
* Celeste (Open World)
* Choo-Choo Charles
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

View File

@@ -1,11 +0,0 @@
from __future__ import annotations
import ModuleUpdate
ModuleUpdate.update()
from worlds.sc2.Client import launch
import Utils
if __name__ == "__main__":
Utils.init_logging("Starcraft2Client", exception_logger="Client")
launch()

View File

@@ -940,15 +940,15 @@ class DeprecateDict(dict):
def _extend_freeze_support() -> None:
"""Extend multiprocessing.freeze_support() to also work on Non-Windows for spawn."""
# upstream issue: https://github.com/python/cpython/issues/76327
"""Extend multiprocessing.freeze_support() to also work on Non-Windows and without setting spawn method first."""
# original upstream issue: https://github.com/python/cpython/issues/76327
# code based on https://github.com/pyinstaller/pyinstaller/blob/develop/PyInstaller/hooks/rthooks/pyi_rth_multiprocessing.py#L26
import multiprocessing
import multiprocessing.spawn
def _freeze_support() -> None:
"""Minimal freeze_support. Only apply this if frozen."""
from subprocess import _args_from_interpreter_flags
from subprocess import _args_from_interpreter_flags # noqa
# Prevent `spawn` from trying to read `__main__` in from the main script
multiprocessing.process.ORIGINAL_DIR = None
@@ -975,17 +975,23 @@ def _extend_freeze_support() -> None:
multiprocessing.spawn.spawn_main(**kwargs)
sys.exit()
if not is_windows and is_frozen():
multiprocessing.freeze_support = multiprocessing.spawn.freeze_support = _freeze_support
def _noop() -> None:
pass
multiprocessing.freeze_support = multiprocessing.spawn.freeze_support = _freeze_support if is_frozen() else _noop
def freeze_support() -> None:
"""This behaves like multiprocessing.freeze_support but also works on Non-Windows."""
"""This now only calls multiprocessing.freeze_support since we are patching freeze_support on module load."""
import multiprocessing
_extend_freeze_support()
deprecate("Use multiprocessing.freeze_support() instead")
multiprocessing.freeze_support()
_extend_freeze_support()
def visualize_regions(root_region: Region, file_name: str, *,
show_entrance_names: bool = False, show_locations: bool = True, show_other_regions: bool = True,
linetype_ortho: bool = True, regions_to_highlight: set[Region] | None = None) -> None:

View File

@@ -99,11 +99,11 @@ if __name__ == "__main__":
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:
from WebHostLib.lttpsprites import update_sprites_lttp
update_sprites_lttp()
except Exception as e:
logging.exception(e)

View File

@@ -112,7 +112,9 @@ def tracker_data(tracker: UUID) -> dict[str, Any]:
client_activity_timers: tuple[tuple[int, int], float] = tracker_data._multisave.get("client_activity_timers", ())
for (team, player), timestamp in client_activity_timers:
# use index since we can rely on order
activity_timers[team]["player_timers"][player - 1]["time"] = datetime.fromtimestamp(timestamp, timezone.utc)
# FIX: key is "players" (not "player_timers")
activity_timers[team]["players"][player - 1]["time"] = datetime.fromtimestamp(timestamp, timezone.utc)
connection_timers: list[dict[str, int | list[PlayerTimer]]] = []
"""Time of last connection per player. Returned as RFC 1123 format and null if no connection has been made."""

View File

@@ -12,12 +12,11 @@ from flask import flash, redirect, render_template, request, session, url_for
from pony.orm import commit, db_session
from BaseClasses import get_seed, seeddigits
from Generate import PlandoOptions, handle_name
from Generate import PlandoOptions, handle_name, mystery_argparse
from Main import main as ERmain
from Utils import __version__, restricted_dumps
from WebHostLib import app
from settings import ServerOptions, GeneratorOptions
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
@@ -129,36 +128,39 @@ def gen_game(gen_options: dict, meta: dict[str, Any] | None = None, owner=None,
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 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 = PlandoOptions.from_set(meta.setdefault("plando_options",
{"bosses", "items", "connections", "texts"}))
erargs.skip_prog_balancing = False
erargs.skip_output = False
erargs.spoiler_only = False
erargs.csv_output = False
args = mystery_argparse()
args.multi = playercount
args.seed = seed
args.name = {x: "" for x in range(1, playercount + 1)} # only so it can be overwritten in mystery
args.spoiler = meta["generator_options"].get("spoiler", 0)
args.race = race
args.outputname = seedname
args.outputpath = target.name
args.teams = 1
args.plando_options = PlandoOptions.from_set(meta.setdefault("plando_options",
{"bosses", "items", "connections", "texts"}))
args.skip_prog_balancing = False
args.skip_output = False
args.spoiler_only = False
args.csv_output = False
args.sprite = dict.fromkeys(range(1, args.multi+1), None)
args.sprite_pool = dict.fromkeys(range(1, args.multi+1), None)
name_counter = Counter()
for player, (playerfile, settings) in enumerate(gen_options.items(), 1):
for k, v in settings.items():
if v is not None:
if hasattr(erargs, k):
getattr(erargs, k)[player] = v
if hasattr(args, k):
getattr(args, k)[player] = v
else:
setattr(erargs, k, {player: v})
setattr(args, k, {player: v})
if not erargs.name[player]:
erargs.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0]
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["server_options"])
if not args.name[player]:
args.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0]
args.name[player] = handle_name(args.name[player], player, name_counter)
if len(set(args.name.values())) != len(args.name):
raise Exception(f"Names have to be unique. Names: {Counter(args.name.values())}")
ERmain(args, seed, baked_server_options=meta["server_options"])
return upload_to_db(target.name, sid, owner, race)
thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)

View File

@@ -3,10 +3,10 @@ import threading
import json
from Utils import local_path, user_path
from worlds.alttp.Rom import Sprite
def update_sprites_lttp():
from worlds.alttp.Rom import Sprite
from tkinter import Tk
from LttPAdjuster import get_image_for_sprite
from LttPAdjuster import BackgroundTaskProgress

View File

@@ -155,7 +155,9 @@ def generate_weighted_yaml(game: str):
options = {}
for key, val in request.form.items():
if "||" not in key:
if val == "_ensure-empty-list":
options[key] = {}
elif "||" not in key:
if len(str(val)) == 0:
continue
@@ -212,8 +214,11 @@ 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 val == "_ensure-empty-list":
options[key] = []
elif options.get(key):
if not isinstance(options[key], list):
options[key] = [options[key]]
options[key].append(val)

View File

@@ -1,6 +1,7 @@
flask>=3.1.1
werkzeug>=3.1.3
pony>=0.7.19
pony>=0.7.19; python_version <= '3.12'
pony @ git+https://github.com/black-sliver/pony@7feb1221953b7fa4a6735466bf21a8b4d35e33ba#0.7.19; python_version >= '3.13'
waitress>=3.0.2
Flask-Caching>=2.3.0
Flask-Compress>=1.17

View File

@@ -1,49 +1,43 @@
let updateSection = (sectionName, fakeDOM) => {
document.getElementById(sectionName).innerHTML = fakeDOM.getElementById(sectionName).innerHTML;
}
window.addEventListener('load', () => {
// Reload tracker every 15 seconds
const url = window.location;
setInterval(() => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
// Reload tracker every 60 seconds (sync'd)
const url = window.location;
// Note: This synchronization code is adapted from code in trackerCommon.js
const targetSecond = parseInt(document.getElementById('player-tracker').getAttribute('data-second')) + 3;
console.log("Target second of refresh: " + targetSecond);
// 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;
}
let getSleepTimeSeconds = () => {
// -40 % 60 is -40, which is absolutely wrong and should burn
var sleepSeconds = (((targetSecond - new Date().getSeconds()) % 60) + 60) % 60;
return sleepSeconds || 60;
};
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;
});
}
let updateTracker = () => {
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 dynamic sections
updateSection('player-info', fakeDOM);
updateSection('section-filler', fakeDOM);
updateSection('section-terran', fakeDOM);
updateSection('section-zerg', fakeDOM);
updateSection('section-protoss', fakeDOM);
updateSection('section-nova', fakeDOM);
updateSection('section-kerrigan', fakeDOM);
updateSection('section-keys', fakeDOM);
updateSection('section-locations', fakeDOM);
};
ajax.open('GET', url);
ajax.send();
updater = setTimeout(updateTracker, getSleepTimeSeconds() * 1000);
};
window.updater = setTimeout(updateTracker, getSleepTimeSeconds() * 1000);
});

View File

@@ -1,160 +1,279 @@
#player-tracker-wrapper{
margin: 0;
*{
margin: 0;
font-family: "JuraBook", monospace;
}
body{
--icon-size: 36px;
--item-class-padding: 4px;
}
a{
color: #1ae;
}
#tracker-table td {
vertical-align: top;
/* Section colours */
#player-info{
background-color: #37a;
}
.player-tracker{
max-width: 100%;
}
.tracker-section{
background-color: grey;
}
#terran-items{
background-color: #3a7;
}
#zerg-items{
background-color: #d94;
}
#protoss-items{
background-color: #37a;
}
#nova-items{
background-color: #777;
}
#kerrigan-items{
background-color: #a37;
}
#keys{
background-color: #aa2;
}
.inventory-table-area{
border: 2px solid #000000;
border-radius: 4px;
padding: 3px 10px 3px 10px;
/* Sections */
.section-body{
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: flex-start;
padding-bottom: 3px;
}
.section-body-2{
display: flex;
flex-direction: column;
}
.tracker-section:has(input.collapse-section[type=checkbox]:checked) .section-body,
.tracker-section:has(input.collapse-section[type=checkbox]:checked) .section-body-2{
display: none;
}
.section-title{
position: relative;
border-bottom: 3px solid black;
/* Prevent text selection */
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
input[type="checkbox"]{
position: absolute;
cursor: pointer;
opacity: 0;
z-index: 1;
width: 100%;
height: 100%;
}
.section-title:hover h2{
text-shadow: 0 0 4px #ddd;
}
.f {
display: flex;
overflow: hidden;
}
.inventory-table-area:has(.inventory-table-terran) {
width: 690px;
background-color: #525494;
/* Acquire item filters */
.tracker-section img{
height: 100%;
width: var(--icon-size);
height: var(--icon-size);
background-color: black;
}
.unacquired, .lvl-0 .f{
filter: grayscale(100%) contrast(80%) brightness(42%) blur(0.5px);
}
.spacer{
width: var(--icon-size);
height: var(--icon-size);
}
.inventory-table-area:has(.inventory-table-zerg) {
width: 360px;
background-color: #9d60d2;
/* Item groups */
.item-class{
display: flex;
flex-flow: column;
justify-content: center;
padding: var(--item-class-padding);
}
.item-class-header{
display: flex;
flex-flow: row;
}
.item-class-upgrades{
/* Note: {display: flex; flex-flow: column wrap} */
/* just breaks on Firefox (width does not scale to content) */
display: grid;
grid-template-rows: repeat(4, auto);
grid-auto-flow: column;
}
.inventory-table-area:has(.inventory-table-protoss) {
width: 400px;
background-color: #d2b260;
/* Subsections */
.section-toc{
display: flex;
flex-direction: row;
}
.toc-box{
position: relative;
padding-left: 15px;
padding-right: 15px;
}
.toc-box:hover{
text-shadow: 0 0 7px white;
}
.ss-header{
position: relative;
text-align: center;
writing-mode: sideways-lr;
user-select: none;
padding-top: 5px;
font-size: 115%;
}
.tracker-section:has(input.ss-1-toggle:checked) .ss-1{
display: none;
}
.tracker-section:has(input.ss-2-toggle:checked) .ss-2{
display: none;
}
.tracker-section:has(input.ss-3-toggle:checked) .ss-3{
display: none;
}
.tracker-section:has(input.ss-4-toggle:checked) .ss-4{
display: none;
}
.tracker-section:has(input.ss-5-toggle:checked) .ss-5{
display: none;
}
.tracker-section:has(input.ss-6-toggle:checked) .ss-6{
display: none;
}
.tracker-section:has(input.ss-7-toggle:checked) .ss-7{
display: none;
}
.tracker-section:has(input.ss-1-toggle:hover) .ss-1{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
.tracker-section:has(input.ss-2-toggle:hover) .ss-2{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
.tracker-section:has(input.ss-3-toggle:hover) .ss-3{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
.tracker-section:has(input.ss-4-toggle:hover) .ss-4{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
.tracker-section:has(input.ss-5-toggle:hover) .ss-5{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
.tracker-section:has(input.ss-6-toggle:hover) .ss-6{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
.tracker-section:has(input.ss-7-toggle:hover) .ss-7{
background-color: #fff5;
box-shadow: 0 0 1px 1px white;
}
#tracker-table .inventory-table td{
width: 40px;
height: 40px;
text-align: center;
vertical-align: middle;
/* Progressive items */
.progressive{
max-height: var(--icon-size);
display: contents;
}
.inventory-table td.title{
padding-top: 10px;
height: 20px;
font-family: "JuraBook", monospace;
font-size: 16px;
font-weight: bold;
.lvl-0 > :nth-child(2),
.lvl-0 > :nth-child(3),
.lvl-0 > :nth-child(4),
.lvl-0 > :nth-child(5){
display: none;
}
.lvl-1 > :nth-child(2),
.lvl-1 > :nth-child(3),
.lvl-1 > :nth-child(4),
.lvl-1 > :nth-child(5){
display: none;
}
.lvl-2 > :nth-child(1),
.lvl-2 > :nth-child(3),
.lvl-2 > :nth-child(4),
.lvl-2 > :nth-child(5){
display: none;
}
.lvl-3 > :nth-child(1),
.lvl-3 > :nth-child(2),
.lvl-3 > :nth-child(4),
.lvl-3 > :nth-child(5){
display: none;
}
.lvl-4 > :nth-child(1),
.lvl-4 > :nth-child(2),
.lvl-4 > :nth-child(3),
.lvl-4 > :nth-child(5){
display: none;
}
.lvl-5 > :nth-child(1),
.lvl-5 > :nth-child(2),
.lvl-5 > :nth-child(3),
.lvl-5 > :nth-child(4){
display: none;
}
.inventory-table img{
height: 100%;
max-width: 40px;
max-height: 40px;
border: 1px solid #000000;
filter: grayscale(100%) contrast(75%) brightness(20%);
background-color: black;
/* Filler item counters */
.item-counter{
display: table;
text-align: center;
padding: var(--item-class-padding);
}
.item-count{
display: table-cell;
vertical-align: middle;
padding-left: 3px;
padding-right: 15px;
}
.inventory-table img.acquired{
filter: none;
background-color: black;
/* Hidden items */
.hidden-class:not(:has(img.acquired)){
display: none;
}
.hidden-item:not(.acquired){
display:none;
}
.inventory-table .tint-terran img.acquired {
filter: sepia(100%) saturate(300%) brightness(130%) hue-rotate(120deg)
/* Keys */
#keys ol, #keys ul{
columns: 3;
-webkit-columns: 3;
-moz-columns: 3;
}
#keys li{
padding-right: 15pt;
}
.inventory-table .tint-protoss img.acquired {
filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(180deg)
/* Locations */
#section-locations{
padding-left: 5px;
}
@media only screen and (min-width: 120ch){
#section-locations ul{
columns: 2;
-webkit-columns: 2;
-moz-columns: 2;
}
}
#locations li.checked{
list-style-type: "✔ ";
}
.inventory-table .tint-level-1 img.acquired {
filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg)
}
.inventory-table .tint-level-2 img.acquired {
filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(120deg)
}
.inventory-table .tint-level-3 img.acquired {
filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(240deg)
}
.inventory-table div.counted-item {
position: relative;
}
.inventory-table div.item-count {
width: 160px;
text-align: left;
color: black;
font-family: "JuraBook", monospace;
font-weight: bold;
}
#location-table{
border: 2px solid #000000;
border-radius: 4px;
background-color: #87b678;
padding: 10px 3px 3px;
font-family: "JuraBook", monospace;
font-size: 16px;
font-weight: bold;
cursor: default;
}
#location-table table{
width: 100%;
}
#location-table th{
vertical-align: middle;
text-align: left;
padding-right: 10px;
}
#location-table td{
padding-top: 2px;
padding-bottom: 2px;
line-height: 20px;
}
#location-table td.counter {
text-align: right;
font-size: 14px;
}
#location-table td.toggle-arrow {
text-align: right;
}
#location-table tr#Total-header {
font-weight: bold;
}
#location-table img{
height: 100%;
max-width: 30px;
max-height: 30px;
}
#location-table tbody.locations {
font-size: 16px;
}
#location-table td.location-name {
padding-left: 16px;
}
#location-table td:has(.location-column) {
vertical-align: top;
}
#location-table .location-column {
width: 100%;
height: 100%;
}
#location-table .location-column .spacer {
min-height: 24px;
}
.hide {
display: none;
}
/* Allowing scrolling down a little further */
.bottom-padding{
min-height: 33vh;
}

File diff suppressed because it is too large Load Diff

View File

@@ -134,6 +134,7 @@
{% macro OptionList(option_name, option) %}
{{ OptionTitle(option_name, option) }}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="option-container">
{% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %}
<div class="option-entry">
@@ -146,6 +147,7 @@
{% macro LocationSet(option_name, option) %}
{{ OptionTitle(option_name, option) }}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="option-container">
{% for group_name in world.location_name_groups.keys()|sort %}
{% if group_name != "Everywhere" %}
@@ -169,6 +171,7 @@
{% macro ItemSet(option_name, option) %}
{{ OptionTitle(option_name, option) }}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="option-container">
{% for group_name in world.item_name_groups.keys()|sort %}
{% if group_name != "Everything" %}
@@ -192,6 +195,7 @@
{% macro OptionSet(option_name, option) %}
{{ OptionTitle(option_name, option) }}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="option-container">
{% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %}
<div class="option-entry">

File diff suppressed because it is too large Load Diff

View File

@@ -139,6 +139,7 @@
{% endmacro %}
{% macro OptionList(option_name, option) %}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="list-container">
{% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %}
<div class="list-entry">
@@ -158,6 +159,7 @@
{% endmacro %}
{% macro LocationSet(option_name, option, world) %}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="set-container">
{% for group_name in world.location_name_groups.keys()|sort %}
{% if group_name != "Everywhere" %}
@@ -180,6 +182,7 @@
{% endmacro %}
{% macro ItemSet(option_name, option, world) %}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="set-container">
{% for group_name in world.item_name_groups.keys()|sort %}
{% if group_name != "Everything" %}
@@ -202,6 +205,7 @@
{% endmacro %}
{% macro OptionSet(option_name, option) %}
<input type="hidden" id="{{ option_name }}-{{ key }}-hidden" name="{{ option_name }}" value="_ensure-empty-list"/>
<div class="set-container">
{% for key in (option.valid_keys if option.valid_keys is ordered else option.valid_keys|sort) %}
<div class="set-entry">

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ from worlds.tloz.Items import item_game_ids
from worlds.tloz.Locations import location_ids
from worlds.tloz import Items, Locations, Rom
from settings import get_settings
SYSTEM_MESSAGE_ID = 0
CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_tloz.lua"
@@ -341,13 +343,12 @@ if __name__ == '__main__':
# Text Mode to use !hint and such with games that have no text entry
Utils.init_logging("ZeldaClient")
options = Utils.get_options()
DISPLAY_MSGS = options["tloz_options"]["display_msgs"]
DISPLAY_MSGS = get_settings()["tloz_options"]["display_msgs"]
async def run_game(romfile: str) -> None:
auto_start = typing.cast(typing.Union[bool, str],
Utils.get_options()["tloz_options"].get("rom_start", True))
get_settings()["tloz_options"].get("rom_start", True))
if auto_start is True:
import webbrowser
webbrowser.open(romfile)

View File

@@ -220,6 +220,8 @@
<MessageBoxLabel>:
theme_text_color: "Custom"
text_color: 1, 1, 1, 1
<MessageBox>:
height: self.content.texture_size[1] + 80
<ScrollBox>:
layout: layout
bar_width: "12dp"
@@ -233,8 +235,3 @@
spacing: 10
size_hint_y: None
height: self.minimum_height
<MessageBoxLabel>:
valign: "middle"
halign: "center"
text_size: self.width, None
height: self.texture_size[1]

View File

@@ -1,7 +0,0 @@
author: Nintendo
data: null
game: A Link to the Past
min_format_version: 1
name: Link
format_version: 1
sprite_version: 1

View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

@@ -21,9 +21,6 @@
# Aquaria
/worlds/aquaria/ @tioui
# ArchipIDLE
/worlds/archipidle/ @LegendaryLinux
# Blasphemous
/worlds/blasphemous/ @TRPG0
@@ -42,9 +39,15 @@
# Celeste 64
/worlds/celeste64/ @PoryGone
# Celeste (Open World)
/worlds/celeste_open_world/ @PoryGone
# ChecksFinder
/worlds/checksfinder/ @SunCatMC
# Choo-Choo Charles
/worlds/cccharles/ @Yaranorgoth
# Civilization VI
/worlds/civ6/ @hesto2

View File

@@ -62,6 +62,24 @@ if possible.
* If your client appears in the Archipelago Launcher, you may define an icon for it that differentiates it from
other clients. The icon size is 48x48 pixels, but smaller or larger images will scale to that size.
### Launcher Integration
If you have a python client or want to utilize the integration features of the Archipelago Launcher (ex. Slot links in
webhost) you can define a Component to be a part of the Launcher. `LauncherComponents.components` can be appended to
with additional Components in order to automatically add them to the Launcher. Most Components only need a
`display_name` and `func`, but `supports_uri` and `game_name` can be defined to support launching by webhost links,
`icon` and `description` can be used to customize display in the Launcher UI, and `file_identifier` can be used to
launch by file.
Additionally, if you use `func` you have access to LauncherComponent.launch or launch_subprocess to run your
function as a subprocesses that can be utilized side by side other clients.
```py
def my_func(*args: str):
from .client import run_client
LauncherComponent.launch(run_client, name="My Client", args=args)
```
## World
The world is your game integration for the Archipelago generator, webhost, and multiworld server. It contains all the

View File

@@ -10,7 +10,7 @@ What you'll need:
* [Python 3.11.9 or newer](https://www.python.org/downloads/), not the Windows Store version
* On Windows, please consider only using the latest supported version in production environments since security
updates for older versions are not easily available.
* Python 3.12.x is currently the newest supported version
* Python 3.13.x is currently the newest supported version
* pip: included in downloads from python.org, separate in many Linux distributions
* Matching C compiler
* possibly optional, read operating system specific sections

18
docs/shared_cache.md Normal file
View File

@@ -0,0 +1,18 @@
# Shared Cache
Archipelago maintains a shared folder of information that can be persisted for a machine and reused across Libraries.
It can be found at the User Cache Directory for appname `Archipelago` in the `Cache` subfolder
(ex. `%LOCALAPPDATA%/Archipelago/Cache`).
## Common Cache
The Common Cache `common.json` can be used to store any generic data that is expected to be shared across programs
for the same User.
* `uuid`: A UUID identifier used to identify clients as from the same user/machine, to be sent in the Connect packet
## Data Package Cache
The `datapackage` folder in the shared cache folder is used to store datapackages by game and checksum to be reused
in order to save network traffic. The expected structure is `datapackage/Game Name/checksum_value.json` with the
contents of each json file being the no-whitespace datapackage contents.

View File

@@ -82,10 +82,10 @@ overridden. For more information on what methods are available to your class, ch
#### Alternatives to WorldTestBase
Unit tests can also be created using [TestBase](/test/bases.py#L16) or
[unittest.TestCase](https://docs.python.org/3/library/unittest.html#unittest.TestCase) depending on your use case. These
may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for
testing portions of your code that can be tested without relying on a multiworld to be created first.
Unit tests can also be created using
[unittest.TestCase](https://docs.python.org/3/library/unittest.html#unittest.TestCase) directly. These may be useful
for generating a multiworld under very specific constraints without using the generic world setup, or for testing
portions of your code that can be tested without relying on a multiworld to be created first.
#### Parametrization
@@ -102,8 +102,7 @@ for multiple inputs) the base test. Some important things to consider when attem
* Classes inheriting from `WorldTestBase`, including those created by the helpers in `test.param`, will run all
base tests by default, make sure the produced tests actually do what you aim for and do not waste a lot of
extra CPU time. Consider using `TestBase` or `unittest.TestCase` directly
or setting `WorldTestBase.run_default_tests` to False.
extra CPU time. Consider using `unittest.TestCase` directly or setting `WorldTestBase.run_default_tests` to False.
#### Performance Considerations

View File

@@ -74,13 +74,12 @@ class EntranceLookup:
if entrance in self._expands_graph_cache:
return self._expands_graph_cache[entrance]
visited = set()
seen = {entrance.connected_region}
q: deque[Region] = deque()
q.append(entrance.connected_region)
while q:
region = q.popleft()
visited.add(region)
# check if the region itself is progression
if region in region.multiworld.indirect_connections:
@@ -103,7 +102,8 @@ class EntranceLookup:
and exit_ in self._usable_exits):
self._expands_graph_cache[entrance] = True
return True
elif exit_.connected_region and exit_.connected_region not in visited:
elif exit_.connected_region and exit_.connected_region not in seen:
seen.add(exit_.connected_region)
q.append(exit_.connected_region)
self._expands_graph_cache[entrance] = False

View File

@@ -720,13 +720,11 @@ class MessageBoxLabel(MDLabel):
class MessageBox(Popup):
def __init__(self, title, text, error=False, **kwargs):
label = MessageBoxLabel(text=text)
label = MessageBoxLabel(text=text, padding=("6dp", "0dp"))
separator_color = [217 / 255, 129 / 255, 122 / 255, 1.] if error else [47 / 255., 167 / 255., 212 / 255, 1.]
super().__init__(title=title, content=label, size_hint=(0.5, None), width=max(100, int(label.width) + 40),
separator_color=separator_color, **kwargs)
self.height += max(0, label.height - 18)
class MDNavigationItemBase(MDNavigationItem):

View File

@@ -65,7 +65,6 @@ from Cython.Build import cythonize
non_apworlds: set[str] = {
"A Link to the Past",
"Adventure",
"ArchipIDLE",
"Archipelago",
"Lufia II Ancient Cave",
"Meritous",

View File

@@ -1,3 +0,0 @@
from .bases import TestBase, WorldTestBase
from warnings import warn
warn("TestBase was renamed to bases", DeprecationWarning)

View File

@@ -9,98 +9,7 @@ from test.general import gen_steps
from worlds import AutoWorld
from worlds.AutoWorld import World, call_all
from BaseClasses import Location, MultiWorld, CollectionState, ItemClassification, Item
from worlds.alttp.Items import item_factory
class TestBase(unittest.TestCase):
multiworld: MultiWorld
_state_cache = {}
def get_state(self, items):
if (self.multiworld, tuple(items)) in self._state_cache:
return self._state_cache[self.multiworld, tuple(items)]
state = CollectionState(self.multiworld)
for item in items:
item.classification = ItemClassification.progression
state.collect(item, prevent_sweep=True)
state.sweep_for_advancements()
state.update_reachable_regions(1)
self._state_cache[self.multiworld, tuple(items)] = state
return state
def get_path(self, state, region):
def flist_to_iter(node):
while node:
value, node = node
yield value
from itertools import zip_longest
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)
def run_location_tests(self, access_pool):
for i, (location, access, *item_pool) in enumerate(access_pool):
items = item_pool[0]
all_except = item_pool[1] if len(item_pool) > 1 else None
state = self._get_items(item_pool, all_except)
path = self.get_path(state, self.multiworld.get_location(location, 1).parent_region)
with self.subTest(msg="Reach Location", location=location, access=access, items=items,
all_except=all_except, path=path, entry=i):
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access,
f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
# check for partial solution
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
for missing_item in item_pool[0]:
with self.subTest(msg="Location reachable without required item", location=location,
items=item_pool[0], missing_item=missing_item, entry=i):
state = self._get_items_partial(item_pool, missing_item)
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False,
f"failed {self.multiworld.get_location(location, 1)}: succeeded with "
f"{missing_item} removed from: {item_pool}")
def run_entrance_tests(self, access_pool):
for i, (entrance, access, *item_pool) in enumerate(access_pool):
items = item_pool[0]
all_except = item_pool[1] if len(item_pool) > 1 else None
state = self._get_items(item_pool, all_except)
path = self.get_path(state, self.multiworld.get_entrance(entrance, 1).parent_region)
with self.subTest(msg="Reach Entrance", entrance=entrance, access=access, items=items,
all_except=all_except, path=path, entry=i):
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), access)
# check for partial solution
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
for missing_item in item_pool[0]:
with self.subTest(msg="Entrance reachable without required item", entrance=entrance,
items=item_pool[0], missing_item=missing_item, entry=i):
state = self._get_items_partial(item_pool, missing_item)
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False,
f"failed {self.multiworld.get_entrance(entrance, 1)} with: {item_pool}")
def _get_items(self, item_pool, all_except):
if all_except and len(all_except) > 0:
items = self.multiworld.itempool[:]
items = [item for item in items if
item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
items.extend(item_factory(item_pool[0], self.multiworld.worlds[1]))
else:
items = item_factory(item_pool[0], self.multiworld.worlds[1])
return self.get_state(items)
def _get_items_partial(self, item_pool, missing_item):
new_items = item_pool[0].copy()
new_items.remove(missing_item)
items = item_factory(new_items, self.multiworld.worlds[1])
return self.get_state(items)
from BaseClasses import Location, MultiWorld, CollectionState, Item
class WorldTestBase(unittest.TestCase):

View File

@@ -37,10 +37,11 @@ class TestImplemented(unittest.TestCase):
def test_slot_data(self):
"""Tests that if a world creates slot data, it's json serializable."""
for game_name, world_type in AutoWorldRegister.world_types.items():
# has an await for generate_output which isn't being called
if game_name in {"Ocarina of Time"}:
continue
# has an await for generate_output which isn't being called
excluded_games = ("Ocarina of Time",)
worlds_to_test = {game: world
for game, world in AutoWorldRegister.world_types.items() if game not in excluded_games}
for game_name, world_type in worlds_to_test.items():
multiworld = setup_solo_multiworld(world_type)
with self.subTest(game=game_name, seed=multiworld.seed):
distribute_items_restrictive(multiworld)

View File

@@ -150,8 +150,7 @@ class TestBase(unittest.TestCase):
"""Test that worlds don't modify the locality of items after duplicates are resolved"""
gen_steps = ("generate_early",)
additional_steps = ("create_regions", "create_items", "set_rules", "connect_entrances", "generate_basic", "pre_fill")
worlds_to_test = {game: world for game, world in AutoWorldRegister.world_types.items()}
for game_name, world_type in worlds_to_test.items():
for game_name, world_type in AutoWorldRegister.world_types.items():
with self.subTest("Game", game=game_name):
multiworld = setup_solo_multiworld(world_type, gen_steps)
local_items = multiworld.worlds[1].options.local_items.value.copy()

View File

@@ -33,7 +33,10 @@ class TestBase(unittest.TestCase):
def test_location_creation_steps(self):
"""Tests that Regions and Locations aren't created after `create_items`."""
gen_steps = ("generate_early", "create_regions", "create_items")
for game_name, world_type in AutoWorldRegister.world_types.items():
excluded_games = ("Ocarina of Time", "Pokemon Red and Blue")
worlds_to_test = {game: world
for game, world in AutoWorldRegister.world_types.items() if game not in excluded_games}
for game_name, world_type in worlds_to_test.items():
with self.subTest("Game", game_name=game_name):
multiworld = setup_solo_multiworld(world_type, gen_steps)
region_count = len(multiworld.get_regions())
@@ -54,13 +57,13 @@ class TestBase(unittest.TestCase):
call_all(multiworld, "generate_basic")
self.assertEqual(region_count, len(multiworld.get_regions()),
f"{game_name} modified region count during generate_basic")
self.assertGreaterEqual(location_count, len(multiworld.get_locations()),
self.assertEqual(location_count, len(multiworld.get_locations()),
f"{game_name} modified locations count during generate_basic")
call_all(multiworld, "pre_fill")
self.assertEqual(region_count, len(multiworld.get_regions()),
f"{game_name} modified region count during pre_fill")
self.assertGreaterEqual(location_count, len(multiworld.get_locations()),
self.assertEqual(location_count, len(multiworld.get_locations()),
f"{game_name} modified locations count during pre_fill")
def test_location_group(self):

View File

@@ -22,6 +22,10 @@ if TYPE_CHECKING:
perf_logger = logging.getLogger("performance")
class InvalidItemError(KeyError):
pass
class AutoWorldRegister(type):
world_types: Dict[str, Type[World]] = {}
__file__: str

View File

@@ -229,8 +229,6 @@ components: List[Component] = [
Component('Zelda 1 Client', 'Zelda1Client', file_identifier=SuffixIdentifier('.aptloz')),
# ChecksFinder
Component('ChecksFinder Client', 'ChecksFinderClient'),
# Starcraft 2
Component('Starcraft 2 Client', 'Starcraft2Client'),
# Zillion
Component('Zillion Client', 'ZillionClient',
file_identifier=SuffixIdentifier('.apzl')),

View File

@@ -19,7 +19,7 @@ class GameData:
"""
:param data:
"""
self.abilities: Dict[int, AbilityData] = {}
self.abilities: Dict[int, AbilityData] = {a.ability_id: AbilityData(self, a) for a in data.abilities if a.available}
self.units: Dict[int, UnitTypeData] = {u.unit_id: UnitTypeData(self, u) for u in data.units if u.available}
self.upgrades: Dict[int, UpgradeData] = {u.upgrade_id: UpgradeData(self, u) for u in data.upgrades}
# Cached UnitTypeIds so that conversion does not take long. This needs to be moved elsewhere if a new GameData object is created multiple times per game
@@ -40,7 +40,7 @@ class AbilityData:
self._proto = proto
# What happens if we comment this out? Should this not be commented out? What is its purpose?
assert self.id != 0
# assert self.id != 0 # let the world burn
def __repr__(self) -> str:
return f"AbilityData(name={self._proto.button_name})"

View File

@@ -623,6 +623,23 @@ class ParadeTrapWeight(Range):
default = 20
class DeathLinkAmnesty(Range):
"""Amount of forgiven deaths before sending a Death Link.
0 means that every death will send a Death Link."""
display_name = "Death Link Amnesty"
range_start = 0
range_end = 20
default = 0
class DWDeathLinkAmnesty(Range):
"""Amount of forgiven deaths before sending a Death Link during Death Wish levels."""
display_name = "Death Wish Amnesty"
range_start = 0
range_end = 30
default = 5
@dataclass
class AHITOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
@@ -700,6 +717,8 @@ class AHITOptions(PerGameCommonOptions):
ParadeTrapWeight: ParadeTrapWeight
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty
dw_death_link_amnesty: DWDeathLinkAmnesty
ahit_option_groups: Dict[str, List[Any]] = {
@@ -769,4 +788,6 @@ slot_data_options: List[str] = [
"MaxPonCost",
"death_link",
"death_link_amnesty",
"dw_death_link_amnesty",
]

View File

@@ -1,22 +0,0 @@
import unittest
from argparse import Namespace
from BaseClasses import MultiWorld, CollectionState
from worlds import AutoWorldRegister
class LTTPTestBase(unittest.TestCase):
def world_setup(self):
from worlds.alttp.Options import Medallion
self.multiworld = MultiWorld(1)
self.multiworld.game[1] = "A Link to the Past"
self.multiworld.set_seed(None)
args = Namespace()
for name, option in AutoWorldRegister.world_types["A Link to the Past"].options_dataclass.type_hints.items():
setattr(args, name, {1: option.from_any(getattr(option, "default"))})
self.multiworld.set_options(args)
self.multiworld.state = CollectionState(self.multiworld)
self.world = self.multiworld.worlds[1]
# by default medallion access is randomized, for unittests we set it to vanilla
self.world.options.misery_mire_medallion.value = Medallion.option_ether
self.world.options.turtle_rock_medallion.value = Medallion.option_quake

113
worlds/alttp/test/bases.py Normal file
View File

@@ -0,0 +1,113 @@
import unittest
from argparse import Namespace
from BaseClasses import MultiWorld, CollectionState, ItemClassification
from worlds import AutoWorldRegister
from ..Items import item_factory
class TestBase(unittest.TestCase):
multiworld: MultiWorld
_state_cache = {}
def get_state(self, items):
if (self.multiworld, tuple(items)) in self._state_cache:
return self._state_cache[self.multiworld, tuple(items)]
state = CollectionState(self.multiworld)
for item in items:
item.classification = ItemClassification.progression
state.collect(item, prevent_sweep=True)
state.sweep_for_advancements()
state.update_reachable_regions(1)
self._state_cache[self.multiworld, tuple(items)] = state
return state
def get_path(self, state, region):
def flist_to_iter(node):
while node:
value, node = node
yield value
from itertools import zip_longest
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)
def run_location_tests(self, access_pool):
for i, (location, access, *item_pool) in enumerate(access_pool):
items = item_pool[0]
all_except = item_pool[1] if len(item_pool) > 1 else None
state = self._get_items(item_pool, all_except)
path = self.get_path(state, self.multiworld.get_location(location, 1).parent_region)
with self.subTest(msg="Reach Location", location=location, access=access, items=items,
all_except=all_except, path=path, entry=i):
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access,
f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
# check for partial solution
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
for missing_item in item_pool[0]:
with self.subTest(msg="Location reachable without required item", location=location,
items=item_pool[0], missing_item=missing_item, entry=i):
state = self._get_items_partial(item_pool, missing_item)
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False,
f"failed {self.multiworld.get_location(location, 1)}: succeeded with "
f"{missing_item} removed from: {item_pool}")
def run_entrance_tests(self, access_pool):
for i, (entrance, access, *item_pool) in enumerate(access_pool):
items = item_pool[0]
all_except = item_pool[1] if len(item_pool) > 1 else None
state = self._get_items(item_pool, all_except)
path = self.get_path(state, self.multiworld.get_entrance(entrance, 1).parent_region)
with self.subTest(msg="Reach Entrance", entrance=entrance, access=access, items=items,
all_except=all_except, path=path, entry=i):
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), access)
# check for partial solution
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
for missing_item in item_pool[0]:
with self.subTest(msg="Entrance reachable without required item", entrance=entrance,
items=item_pool[0], missing_item=missing_item, entry=i):
state = self._get_items_partial(item_pool, missing_item)
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False,
f"failed {self.multiworld.get_entrance(entrance, 1)} with: {item_pool}")
def _get_items(self, item_pool, all_except):
if all_except and len(all_except) > 0:
items = self.multiworld.itempool[:]
items = [item for item in items if
item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)]
items.extend(item_factory(item_pool[0], self.multiworld.worlds[1]))
else:
items = item_factory(item_pool[0], self.multiworld.worlds[1])
return self.get_state(items)
def _get_items_partial(self, item_pool, missing_item):
new_items = item_pool[0].copy()
new_items.remove(missing_item)
items = item_factory(new_items, self.multiworld.worlds[1])
return self.get_state(items)
class LTTPTestBase(unittest.TestCase):
def world_setup(self):
from worlds.alttp.Options import Medallion
self.multiworld = MultiWorld(1)
self.multiworld.game[1] = "A Link to the Past"
self.multiworld.set_seed(None)
args = Namespace()
for name, option in AutoWorldRegister.world_types["A Link to the Past"].options_dataclass.type_hints.items():
setattr(args, name, {1: option.from_any(getattr(option, "default"))})
self.multiworld.set_options(args)
self.multiworld.state = CollectionState(self.multiworld)
self.world = self.multiworld.worlds[1]
# by default medallion access is randomized, for unittests we set it to vanilla
self.world.options.misery_mire_medallion.value = Medallion.option_ether
self.world.options.turtle_rock_medallion.value = Medallion.option_quake

View File

@@ -5,7 +5,7 @@ from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from worlds.alttp.Regions import create_regions
from worlds.alttp.Shops import create_shops
from worlds.alttp.test import LTTPTestBase
from worlds.alttp.test.bases import LTTPTestBase
class TestDungeon(LTTPTestBase):

View File

@@ -1,13 +1,12 @@
from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.EntranceShuffle import link_inverted_entrances
from worlds.alttp.InvertedRegions import create_inverted_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from worlds.alttp.Regions import mark_light_world_regions
from worlds.alttp.Shops import create_shops
from test.bases import TestBase
from ...Dungeons import get_dungeon_item_pool
from ...EntranceShuffle import link_inverted_entrances
from ...InvertedRegions import create_inverted_regions
from ...ItemPool import difficulties
from ...Items import item_factory
from ...Regions import mark_light_world_regions
from ...Shops import create_shops
from worlds.alttp.test import LTTPTestBase
from ..bases import LTTPTestBase, TestBase
class TestInverted(TestBase, LTTPTestBase):

View File

@@ -4,7 +4,7 @@ from worlds.alttp.EntranceShuffle import connect_entrance, Inverted_LW_Entrances
from worlds.alttp.InvertedRegions import create_inverted_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Rules import set_inverted_big_bomb_rules
from worlds.alttp.test import LTTPTestBase
from worlds.alttp.test.bases import LTTPTestBase
class TestInvertedBombRules(LTTPTestBase):

View File

@@ -1,14 +1,13 @@
from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.EntranceShuffle import link_inverted_entrances
from worlds.alttp.InvertedRegions import create_inverted_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from worlds.alttp.Options import GlitchesRequired
from worlds.alttp.Regions import mark_light_world_regions
from worlds.alttp.Shops import create_shops
from test.bases import TestBase
from ...Dungeons import get_dungeon_item_pool
from ...EntranceShuffle import link_inverted_entrances
from ...InvertedRegions import create_inverted_regions
from ...ItemPool import difficulties
from ...Items import item_factory
from ...Options import GlitchesRequired
from ...Regions import mark_light_world_regions
from ...Shops import create_shops
from worlds.alttp.test import LTTPTestBase
from ..bases import LTTPTestBase, TestBase
class TestInvertedMinor(TestBase, LTTPTestBase):

View File

@@ -1,14 +1,13 @@
from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.EntranceShuffle import link_inverted_entrances
from worlds.alttp.InvertedRegions import create_inverted_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from worlds.alttp.Options import GlitchesRequired
from worlds.alttp.Regions import mark_light_world_regions
from worlds.alttp.Shops import create_shops
from test.bases import TestBase
from ...Dungeons import get_dungeon_item_pool
from ...EntranceShuffle import link_inverted_entrances
from ...InvertedRegions import create_inverted_regions
from ...ItemPool import difficulties
from ...Items import item_factory
from ...Options import GlitchesRequired
from ...Regions import mark_light_world_regions
from ...Shops import create_shops
from worlds.alttp.test import LTTPTestBase
from ..bases import LTTPTestBase, TestBase
class TestInvertedOWG(TestBase, LTTPTestBase):

View File

@@ -1,5 +1,5 @@
from worlds.alttp.ItemPool import difficulties
from test.bases import TestBase
from ...ItemPool import difficulties
from ..bases import TestBase
base_items = 41
extra_counts = (15, 15, 10, 5, 25)

View File

@@ -1,11 +1,10 @@
from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.InvertedRegions import mark_dark_world_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from test.bases import TestBase
from worlds.alttp.Options import GlitchesRequired
from ...Dungeons import get_dungeon_item_pool
from ...InvertedRegions import mark_dark_world_regions
from ...ItemPool import difficulties
from ...Items import item_factory
from ...Options import GlitchesRequired
from worlds.alttp.test import LTTPTestBase
from ..bases import LTTPTestBase, TestBase
class TestMinor(TestBase, LTTPTestBase):

View File

@@ -1,11 +1,10 @@
from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.InvertedRegions import mark_dark_world_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from test.bases import TestBase
from worlds.alttp.Options import GlitchesRequired
from ...Dungeons import get_dungeon_item_pool
from ...InvertedRegions import mark_dark_world_regions
from ...ItemPool import difficulties
from ...Items import item_factory
from ...Options import GlitchesRequired
from worlds.alttp.test import LTTPTestBase
from ..bases import LTTPTestBase, TestBase
class TestVanillaOWG(TestBase, LTTPTestBase):

View File

@@ -1,5 +1,5 @@
from worlds.alttp.Shops import shop_table
from test.bases import TestBase
from ...Shops import shop_table
from ..bases import TestBase
class TestSram(TestBase):

View File

@@ -1,10 +1,10 @@
from worlds.alttp.Dungeons import get_dungeon_item_pool
from worlds.alttp.InvertedRegions import mark_dark_world_regions
from worlds.alttp.ItemPool import difficulties
from worlds.alttp.Items import item_factory
from test.bases import TestBase
from worlds.alttp.Options import GlitchesRequired
from worlds.alttp.test import LTTPTestBase
from ...Dungeons import get_dungeon_item_pool
from ...InvertedRegions import mark_dark_world_regions
from ...ItemPool import difficulties
from ...Items import item_factory
from ...Options import GlitchesRequired
from ..bases import LTTPTestBase, TestBase
class TestVanilla(TestBase, LTTPTestBase):

View File

@@ -1,315 +0,0 @@
item_table = (
'An Old GeoCities Profile',
'Very Funny Joke',
'Motivational Video',
'Staples Easy Button',
'One Million Dollars',
'Replica Master Sword',
'VHS Copy of Jurassic Park',
'32GB USB Drive',
'Pocket Protector',
'Leftover Parts from IKEA Furniture',
'Half-Empty Ink Cartridge for a Printer',
'Watch Battery',
'Towel',
'Scarf',
'2012 Magic the Gathering Core Set Starter Box',
'Poke\'mon Booster Pack',
'USB Speakers',
'Eco-Friendly Spork',
'Cheeseburger',
'Brand New Car',
'Hunting Knife',
'Zippo Lighter',
'Red Shirt',
'One-Up Mushroom',
'Nokia N-GAGE',
'2-Liter of Sprite',
'Free trial of the critically acclaimed MMORPG Final Fantasy XIV, including the entirety of A Realm Reborn and the award winning Heavensward and Stormblood expansions up to level 70 with no restrictions on playtime!',
'Can of Compressed Air',
'Striped Kitten',
'USB Power Adapter',
'Fortune Cookie',
'Nintendo Power Glove',
'The Lampshade of No Real Significance',
'Kneepads of Allure',
'Get Out of Jail Free Card',
'Box Set of Stargate SG-1 Season 4',
'The Missing Left Sock',
'Poster Tube',
'Electronic Picture Frame',
'Bottle of Shampoo',
'Your Mission, Should You Choose To Accept It',
'Fanny Pack',
'Robocop T-Shirt',
'Suspiciously Small Monocle',
'Table Saw',
'Cookies and Cream Milkshake',
'Deflated Accordion',
'Grandma\'s Homemade Pie',
'Invisible Lego on the Floor',
'Pitfall Trap',
'Flathead Screwdriver',
'Leftover Pizza',
'Voodoo Doll that Looks Like You',
'Pink Shoelaces',
'Half a Bottle of Scotch',
'Reminder Not to Forget Aginah',
'Medicine Ball',
'Yoga Mat',
'Chocolate Orange',
'Old Concert Tickets',
'The Pick of Destiny',
'McGuffin',
'Just a Regular McMuffin',
'34 Tacos',
'Duct Tape',
'Copy of Untitled Goose Game',
'Partially Used Bed Bath & Beyond Gift Card',
'Mostly Popped Bubble Wrap',
'Expired Driver\'s License',
'The Look, You Know the One',
'Transformers Lunch Box',
'MP3 Player',
'Dry Sharpie',
'Chalkboard Eraser',
'Overhead Projector',
'Physical Copy of the Japanese 1.0 Link to the Past',
'Collectable Action Figure',
'Box Set of The Lord of the Rings Books',
'Lite-Bright',
'Stories from the Good-Old-Days',
'Un-Reproducable Bug Reports',
'Autographed Copy of Shaq-Fu',
'Game-Winning Baseball',
'Portable Battery Bank',
'Blockbuster Membership Card',
'Offensive Bumper Sticker',
'Last Sunday\'s Crossword Puzzle',
'Rubik\'s Cube',
'Your First Grey Hair',
'Embarrassing Childhood Photo',
'Abandoned Sphere One Check',
'The Internet',
'Late-Night Cartoons',
'The Correct Usage of a Semicolon',
'Microsoft Windows 95 Resource Kit',
'Car-Phone',
'Walkman Radio',
'Relevant XKCD Comic',
'Razor Scooter',
'Set of Beyblades',
'Box of Pogs',
'Beanie-Baby Collection',
'Laser Tag Gun',
'Radio Controlled Car',
'Boogie Board',
'Air Jordans',
'Rubber Duckie',
'The Last Cookie in the Cookie Jar',
'Tin-Foil Hat',
'Button-Up Shirt',
'Designer Brand Bag',
'Trapper Keeper',
'Fake Moustache',
'Colored Pencils',
'Pair of 3D Glasses',
'Pair of Movie Tickets',
'Refrigerator Magnets',
'NASCAR Dinner Plates',
'The Final Boss',
'Unskippable Cutscenes',
'24 Rolls of Toilet Paper',
'Canned Soup',
'Warm Blanket',
'3D Printer',
'Jetpack',
'Hoverboard',
'Joycons with No Drift',
'Double Rainbow',
'Ping Pong Ball',
'Area 51 Arcade Cabinet',
'Elephant in the Room',
'The Pink Panther',
'Denim Shorts',
'Tennis Racket',
'Collection of Stuffed Animals',
'Old Cell Phone',
'Nintendo Virtual Boy',
'Box of 5.25 Inch Floppy Disks',
'Bag of Miscellaneous Wires',
'Garden Shovel',
'Leather Gloves',
'Knife of +9 VS Ogres',
'Old, Smelly Cheese',
'Linksys BEFSR41 Router',
'Ethernet Cables for a LAN Party',
'Mechanical Pencil',
'Book of Graph Paper',
'300 Sheets of Printer Paper',
'One AAA Battery',
'Box of Old Game Controllers',
'Sega Dreamcast',
'Mario\'s Overalls',
'Betamax Player',
'Stray Lego',
'Chocolate Chip Pancakes',
'Two Blueberry Muffins',
'Nintendo 64 Controller with a Perfect Thumbstick',
'Cuckoo Crossing the Road',
'One Eyed, One Horned, Flying Purple People-Eater',
'Love Potion Number Nine',
'Wireless Headphones',
'Festive Keychain',
'Bundle of Twisted Cables',
'Plank of Wood',
'Broken Ant Farm',
'Thirty-six American Dollars',
'Can of Shaving Cream',
'Blue Hair Dye',
'Mug Engraved with the AP Logo',
'Tube of Toothpaste',
'Album of Elevator Music',
'Headlight Fluid',
'Tickets to the Renaissance Faire',
'Bag of Golf Balls',
'Box of Packing Peanuts',
'Bottle of Peanut Butter',
'Breath of the Wild Cookbook',
'Stardew Valley Cookbook',
'Thirteen Angry Chickens',
'Bowl of Cereal',
'Rubber Snake',
'Stale Sunflower Seeds',
'Alarm Clock Without a Snooze Button',
'Wet Pineapple',
'Set of Scented Candles',
'Adorable Stuffed Animal',
'The Broodwitch',
'Old Photo Album',
'Trade Quest Item',
'Pair of Fancy Boots',
'Shoddy Pickaxe',
'Adventurer\'s Sword',
'Cute Puppy',
'Box of Matches',
'Set of Allen Wrenches',
'Glass of Water',
'Magic Shaggy Carpet',
'Macaroni and Cheese',
'Chocolate Chip Cookie Dough Ice Cream',
'Fresh Strawberries',
'Delicious Tacos',
'The Krabby Patty Recipe',
'Map to Waldo\'s Location',
'Stray Cat',
'Ham and Cheese Sandwich',
'DVD Player',
'Motorcycle Helmet',
'Fake Flowers',
'6-Pack of Sponges',
'Heated Pants',
'Empty Glass Bottle',
'Brown Paper Bag',
'Model Train Set',
'TV Remote',
'RC Car',
'Super Soaker 9000',
'Giant Sunglasses',
'World\'s Smallest Violin',
'Pile of Fresh Warm Laundry',
'Half-Empty Ice Cube Tray',
'Bob Ross Afro Wig',
'Empty Cardboard Box',
'Packet of Soy Sauce',
'Solutions to a Math Test',
'Pencil Eraser',
'The Great Pumpkin',
'Very Expensive Toaster',
'Pack of Colored Sharpies',
'Bag of Chocolate Chips',
'Grandma\'s Homemade Cookies',
'Collection of Bottle Caps',
'Pack of Playing Cards',
'Boom Box',
'Toy Sail Boat',
'Smooth Nail File',
'Colored Chalk',
'Missing Button',
'Rubber Band Ball',
'Joystick',
'Galaga Arcade Cabinet',
'Anime Mouse Pad',
'Orange and Yellow Glow Sticks',
'Odd Bookmark',
'Stray Dice',
'Tooth Picks',
'Dirty Dishes',
'Poke\'mon Card Game Rule Book (Gen 1)',
'Salt Shaker',
'Digital Thermometer',
'Infinite Improbability Drive',
'Fire Extinguisher',
'Beeping Smoke Alarm',
'Greasy Spatula',
'Progressive Auto Insurance',
'Mace Windu\'s Purple Lightsaber',
'An Old Fixer-Upper',
'Gamer Chair',
'Comfortable Reclining Chair',
'Shirt Covered in Dog Hair',
'Angry Praying Mantis',
'Card Games on Motorcycles',
'Trucker Hat',
'The DK Rap',
'Three Great Balls',
'Some Very Sus Behavior',
'Glass of Orange Juice',
'Turkey Bacon',
'Bald Barbie Doll',
'Developer Commentary',
'Subscription to Nintendo Power Magazine',
'DeLorean Time Machine',
'Unkillable Cockroach',
'Dungeons & Dragons Rulebook',
'Boxed Copy of Quest 64',
'James Bond\'s Gadget Wristwatch',
'Tube of Go-Gurt',
'Digital Watch',
'Laser Pointer',
'The Secret Cow Level',
'AOL Free Trial CD-ROM',
'E.T. for Atari 2600',
'Season 2 of Knight Rider',
'Spam E-Mails',
'Half-Life 3 Release Date',
'Source Code of Jurassic Park',
'Moldy Cheese',
'Comic Book Collection',
'Hardcover Copy of Scott Pilgrim VS the World',
'Old Gym Shorts',
'Very Cool Sunglasses',
'Your High School Yearbook Picture',
'Written Invitation to Prom',
'The Star Wars Holiday Special',
'Oil Change Coupon',
'Finger Guns',
'Box of Tabletop Games',
'Sock Puppets',
'The Dog of Wisdom',
'Surprised Chipmunk',
'Stonks',
'A Shrubbery',
'Roomba with a Knife',
'Wet Cat',
'The missing moderator, Frostwares',
'1,793 Crossbows',
'Holographic First Edition Charizard (Gen 1)',
'VR Headset',
'Archipelago 1.0 Release Date',
'Strand of Galadriel\'s Hair',
'Can of Meow-Mix',
'Shake-Weight',
'DVD Collection of Billy Mays Infomercials',
'Old CD Key',
)

View File

@@ -1,28 +0,0 @@
from BaseClasses import MultiWorld
from worlds.AutoWorld import LogicMixin
class ArchipIDLELogic(LogicMixin):
def _archipidle_location_is_accessible(self, player_id, items_required):
return sum(self.prog_items[player_id].values()) >= items_required
def set_rules(world: MultiWorld, player: int):
for i in range(16, 31):
world.get_location(f"IDLE item number {i}", player).access_rule = lambda \
state: state._archipidle_location_is_accessible(player, 4)
for i in range(31, 51):
world.get_location(f"IDLE item number {i}", player).access_rule = lambda \
state: state._archipidle_location_is_accessible(player, 10)
for i in range(51, 101):
world.get_location(f"IDLE item number {i}", player).access_rule = lambda \
state: state._archipidle_location_is_accessible(player, 20)
for i in range(101, 201):
world.get_location(f"IDLE item number {i}", player).access_rule = lambda \
state: state._archipidle_location_is_accessible(player, 40)
world.completion_condition[player] =\
lambda state: state.can_reach(world.get_location("IDLE item number 200", player), "Location", player)

View File

@@ -1,128 +0,0 @@
from BaseClasses import Item, MultiWorld, Region, Location, Entrance, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
from datetime import datetime
from .Items import item_table
from .Rules import set_rules
class ArchipIDLEWebWorld(WebWorld):
theme = 'partyTime'
tutorials = [
Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing Archipidle',
language='English',
file_name='guide_en.md',
link='guide/en',
authors=['Farrak Kilhn']
),
Tutorial(
tutorial_name='Guide d installation',
description='Un guide pour jouer à Archipidle',
language='Français',
file_name='guide_fr.md',
link='guide/fr',
authors=['TheLynk']
)
]
class ArchipIDLEWorld(World):
"""
An idle game which sends a check every thirty to sixty seconds, up to two hundred checks.
"""
game = "ArchipIDLE"
topology_present = False
hidden = (datetime.now().month != 4) # ArchipIDLE is only visible during April
web = ArchipIDLEWebWorld()
item_name_to_id = {}
start_id = 9000
for item in item_table:
item_name_to_id[item] = start_id
start_id += 1
location_name_to_id = {}
start_id = 9000
for i in range(1, 201):
location_name_to_id[f"IDLE item number {i}"] = start_id
start_id += 1
def set_rules(self):
set_rules(self.multiworld, self.player)
def create_item(self, name: str) -> Item:
return Item(name, ItemClassification.progression, self.item_name_to_id[name], self.player)
def create_items(self):
item_pool = [
ArchipIDLEItem(
item_table[0],
ItemClassification.progression,
self.item_name_to_id[item_table[0]],
self.player
)
]
for i in range(40):
item_pool.append(ArchipIDLEItem(
item_table[1],
ItemClassification.progression,
self.item_name_to_id[item_table[1]],
self.player
))
for i in range(40):
item_pool.append(ArchipIDLEItem(
item_table[2],
ItemClassification.filler,
self.item_name_to_id[item_table[2]],
self.player
))
item_table_copy = list(item_table[3:])
self.random.shuffle(item_table_copy)
for i in range(119):
item_pool.append(ArchipIDLEItem(
item_table_copy[i],
ItemClassification.progression if i < 9 else ItemClassification.filler,
self.item_name_to_id[item_table_copy[i]],
self.player
))
self.multiworld.itempool += item_pool
def create_regions(self):
self.multiworld.regions += [
create_region(self.multiworld, self.player, 'Menu', None, ['Entrance to IDLE Zone']),
create_region(self.multiworld, self.player, 'IDLE Zone', self.location_name_to_id)
]
# link up our region with the entrance we just made
self.multiworld.get_entrance('Entrance to IDLE Zone', self.player)\
.connect(self.multiworld.get_region('IDLE Zone', self.player))
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(item_table)
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
region = Region(name, player, world)
if locations:
for location_name in locations.keys():
location = ArchipIDLELocation(player, location_name, locations[location_name], region)
region.locations.append(location)
if exits:
for _exit in exits:
region.exits.append(Entrance(player, _exit, region))
return region
class ArchipIDLEItem(Item):
game = "ArchipIDLE"
class ArchipIDLELocation(Location):
game: str = "ArchipIDLE"

View File

@@ -1,13 +0,0 @@
# ArchipIDLE
## What is this game?
ArchipIDLE was originally the 2022 Archipelago April Fools' Day joke. It is an idle game that sends a location check
on regular intervals. Updated annually with more items, gimmicks, and features, the game is visible
only during the month of April.
## Where is the options page?
The [player options page for this game](../player-options) contains all the options you need to configure
and export a config file.

View File

@@ -1,12 +0,0 @@
# ArchipIdle Setup Guide
## Joining a MultiWorld Game
1. Generate a `.yaml` file from the [ArchipIDLE Player Options Page](/games/ArchipIDLE/player-options)
2. Open the ArchipIDLE Client in your web browser by either:
- Navigate to the [ArchipIDLE Client](http://idle.multiworld.link)
- Download the client and run it locally from the
[ArchipIDLE GitHub Releases Page](https://github.com/ArchipelagoMW/archipidle/releases)
3. Enter the server address in the `Server Address` field and press enter
4. Enter your slot name when prompted. This should be the same as the `name` you entered on the
options page above, or the `name` field in your yaml file.
5. Click the "Begin!" button.

View File

@@ -1,11 +0,0 @@
# Guide de configuration d'ArchipIdle
## Rejoindre une partie MultiWorld
1. Générez un fichier `.yaml` à partir de la [page des paramètres du lecteur ArchipIDLE](/games/ArchipIDLE/player-options)
2. Ouvrez le client ArchipIDLE dans votre navigateur Web en :
- Accédez au [Client ArchipIDLE](http://idle.multiworld.link)
- Téléchargez le client et exécutez-le localement à partir du [Page des versions d'ArchipIDLE GitHub](https://github.com/ArchipelagoMW/archipidle/releases)
3. Entrez l'adresse du serveur dans le champ `Server Address` et appuyez sur Entrée
4. Entrez votre nom d'emplacement lorsque vous y êtes invité. Il doit être le même que le `name` que vous avez saisi sur le
page de configuration ci-dessus, ou le champ `name` dans votre fichier yaml.
5. Cliquez sur "Commencer !" bouton.

View File

@@ -6,7 +6,6 @@
import typing
from BaseClasses import Item, ItemClassification
from worlds.alttp import ALTTPWorld
class BumpStikLttPText(typing.NamedTuple):
@@ -117,13 +116,17 @@ item_table = {
item: offset + x for x, item in enumerate(LttPCreditsText.keys())
}
ALTTPWorld.pedestal_credit_texts.update({item_table[name]: f"and the {texts.pedestal}"
for name, texts in LttPCreditsText.items()})
ALTTPWorld.sickkid_credit_texts.update(
{item_table[name]: texts.sickkid for name, texts in LttPCreditsText.items()})
ALTTPWorld.magicshop_credit_texts.update(
{item_table[name]: texts.magicshop for name, texts in LttPCreditsText.items()})
ALTTPWorld.zora_credit_texts.update(
{item_table[name]: texts.zora for name, texts in LttPCreditsText.items()})
ALTTPWorld.fluteboy_credit_texts.update(
{item_table[name]: texts.fluteboy for name, texts in LttPCreditsText.items()})
try:
from worlds.alttp import ALTTPWorld
ALTTPWorld.pedestal_credit_texts.update({item_table[name]: f"and the {texts.pedestal}"
for name, texts in LttPCreditsText.items()})
ALTTPWorld.sickkid_credit_texts.update(
{item_table[name]: texts.sickkid for name, texts in LttPCreditsText.items()})
ALTTPWorld.magicshop_credit_texts.update(
{item_table[name]: texts.magicshop for name, texts in LttPCreditsText.items()})
ALTTPWorld.zora_credit_texts.update(
{item_table[name]: texts.zora for name, texts in LttPCreditsText.items()})
ALTTPWorld.fluteboy_credit_texts.update(
{item_table[name]: texts.fluteboy for name, texts in LttPCreditsText.items()})
except ModuleNotFoundError:
pass

View File

@@ -0,0 +1,2 @@
# CCCharles base ID
base_id = 66600000

167
worlds/cccharles/Items.py Normal file
View File

@@ -0,0 +1,167 @@
from BaseClasses import Item
from .BaseID import base_id
class CCCharlesItem(Item):
game = "Choo-Choo Charles"
optional_items = {
"Scraps": base_id + 1,
"30 Scraps Reward": base_id + 2,
"25 Scraps Reward": base_id + 3,
"35 Scraps Reward": base_id + 4,
"40 Scraps Reward": base_id + 5,
"South Mine Key": base_id + 6,
"North Mine Key": base_id + 7,
"Mountain Ruin Key": base_id + 8,
"Barn Key": base_id + 9,
"Candice's Key": base_id + 10,
"Dead Fish": base_id + 11,
"Lockpicks": base_id + 12,
"Ancient Tablet": base_id + 13,
"Blue Box": base_id + 14,
"Page Drawing": base_id + 15,
"Journal": base_id + 16,
"Timed Dynamite": base_id + 17,
"Box of Rockets": base_id + 18,
"Breaker": base_id + 19,
"Broken Bob": base_id + 20,
"Employment Contracts": base_id + 21,
"Mob Camp Key": base_id + 22,
"Jar of Pickles": base_id + 23
}
useless_items = {
"Orange Paint Can": base_id + 24,
"Green Paint Can": base_id + 25,
"White Paint Can": base_id + 26,
"Pink Paint Can": base_id + 27,
"Grey Paint Can": base_id + 28,
"Blue Paint Can": base_id + 29,
"Black Paint Can": base_id + 30,
"Lime Paint Can": base_id + 31,
"Teal Paint Can": base_id + 32,
"Red Paint Can": base_id + 33,
"Purple Paint Can": base_id + 34,
"The Boomer": base_id + 35,
"Bob": base_id + 36
}
progression_items = {
"Green Egg": base_id + 37,
"Blue Egg": base_id + 38,
"Red Egg": base_id + 39,
"Remote Explosive": base_id + 40,
"Remote Explosive x8": base_id + 41, # Originally, Paul gives 8 explosives at once
"Temple Key": base_id + 42,
"Bug Spray": base_id + 43 # Should only be considered progressive in Nightmare Mode
}
item_groups = {
"Weapons": {
"Bug Spray",
"The Boomer",
"Bob"
},
"Paint Can": {
"Orange Paint Can",
"Green Paint Can",
"White Paint Can",
"Pink Paint Can",
"Grey Paint Can",
"Blue Paint Can",
"Black Paint Can",
"Lime Paint Can",
"Teal Paint Can",
"Red Paint Can",
"Purple Paint Can"
},
"Train Upgrade": {
"Scraps",
"30 Scraps Reward",
"25 Scraps Reward",
"40 Scraps Reward"
},
"Dungeon Keys": {
"South Mine Key",
"North Mine Key",
"Mountain Ruin Key"
},
"Building Keys": {
"Barn Key",
"Candice's Key",
"Mob Camp Key",
"Temple Key"
},
"Mission Items": {
"Dead Fish",
"Lockpicks",
"Ancient Tablet",
"Blue Box",
"Page Drawing",
"Journal",
"Timed Dynamite",
"Box of Rockets",
"Breaker",
"Broken Bob",
"Employment Contracts",
"Jar of Pickles",
"Remote Explosive",
"Remote Explosive x8"
},
"Eggs": {
"Green Egg",
"Blue Egg",
"Red Egg"
}
}
# All items excepted the duplications (no item amount)
unique_item_dict = {**optional_items, **useless_items, **progression_items}
# All 691 items to add to the item pool
full_item_list = []
full_item_list += ["Scraps"] * 637 # 636 + 1 as Scrap Reward (from Ronny)
full_item_list += ["30 Scraps Reward"] * 3
full_item_list += ["25 Scraps Reward"] * 1
full_item_list += ["35 Scraps Reward"] * 2
full_item_list += ["40 Scraps Reward"] * 1
full_item_list += ["South Mine Key"] * 1
full_item_list += ["North Mine Key"] * 1
full_item_list += ["Mountain Ruin Key"] * 1
full_item_list += ["Barn Key"] * 1
full_item_list += ["Candice's Key"] * 1
full_item_list += ["Dead Fish"] * 1
full_item_list += ["Lockpicks"] * 1
full_item_list += ["Ancient Tablet"] * 1
full_item_list += ["Blue Box"] * 1
full_item_list += ["Page Drawing"] * 8
full_item_list += ["Journal"] * 1
full_item_list += ["Timed Dynamite"] * 1
full_item_list += ["Box of Rockets"] * 1
full_item_list += ["Breaker"] * 4
full_item_list += ["Broken Bob"] * 1
full_item_list += ["Employment Contracts"] * 1
full_item_list += ["Mob Camp Key"] * 1
full_item_list += ["Jar of Pickles"] * 1
full_item_list += ["Orange Paint Can"] * 1
full_item_list += ["Green Paint Can"] * 1
full_item_list += ["White Paint Can"] * 1
full_item_list += ["Pink Paint Can"] * 1
full_item_list += ["Grey Paint Can"] * 1
full_item_list += ["Blue Paint Can"] * 1
full_item_list += ["Black Paint Can"] * 1
full_item_list += ["Lime Paint Can"] * 1
full_item_list += ["Teal Paint Can"] * 1
full_item_list += ["Red Paint Can"] * 1
full_item_list += ["Purple Paint Can"] * 1
full_item_list += ["The Boomer"] * 1
full_item_list += ["Bob"] * 1
full_item_list += ["Green Egg"] * 1
full_item_list += ["Blue Egg"] * 1
full_item_list += ["Red Egg"] * 1
full_item_list += ["Remote Explosive x8"] * 1
full_item_list += ["Temple Key"] * 1
full_item_list += ["Bug Spray"] * 1

View File

@@ -0,0 +1,914 @@
from BaseClasses import Location
from .BaseID import base_id
class CCCharlesLocation(Location):
game = "Choo-Choo Charles"
# "First Station":
# /!\ NOT CONSIDERED YET: train_keypickup Train_KeyPickup Photorealistic_Island (X=-8816.341 Y=23392.416 Z=10219.855)
loc_start_camp = {
"Start Camp Scraps 1": base_id + 1000, # ItemPickup139_5 Camp (X=24006.348 Y=53777.297 Z=10860.107)
"Start Camp Scraps 2": base_id + 1001 # ItemPickup140_8 Camp (X=23951.754 Y=54897.230 Z=10895.235)
}
loc_tony_tiddle_mission = {
"Barn Tony Tiddle Mission Start": base_id + 1002 # (dialog 5) -> barn_key (1)
}
loc_barn = {
"Barn Scraps 1": base_id + 1003, # ItemPickup12 Photorealistic_Island (X=70582.805 Y=52591.066 Z=11976.719)
"Barn Scraps 2": base_id + 1004, # ItemPickup4_2 Photorealistic_Island (X=70536.641 Y=51890.633 Z=11986.488)
"Barn Scraps 3": base_id + 1005, # ItemPickup6 Photorealistic_Island (X=70750.336 Y=52275.828 Z=11994.434)
"Barn Scraps 4": base_id + 1006, # ItemPickup11 Photorealistic_Island (X=70937.719 Y=52989.066 Z=12003.523)
"Barn Scraps 5": base_id + 1007, # ItemPickup5 Photorealistic_Island (X=71303.508 Y=52232.188 Z=12003.997)
"Barn Scraps 6": base_id + 1008, # ItemPickup7 Photorealistic_Island (X=71678.672 Y=52825.531 Z=11977.212)
"Barn Scraps 7": base_id + 1009, # ItemPickup8 Photorealistic_Island (X=71506.961 Y=52357.293 Z=12362.159)
"Barn Scraps 8": base_id + 1010, # ItemPickup9 Photorealistic_Island (X=71029.875 Y=52384.613 Z=12362.159)
"Barn Scraps 9": base_id + 1011 # ItemPickup10 Photorealistic_Island (X=71129.594 Y=52600.262 Z=12364.142)
}
loc_candice_mission = {
"Tutorial House Candice Mission Start": base_id + 1012 # (dialog 3) -> candice_key (2)
}
loc_tutorial_house = {
"Tutorial House Scraps 1": base_id + 1013, # ItemPickup17_2 Photorealistic_Island (X=74745.852 Y=73865.555 Z=11426.619)
"Tutorial House Scraps 2": base_id + 1014, # ItemPickup18_2 Photorealistic_Island (X=74864.102 Y=73900.094 Z=11426.619)
"Tutorial House Scraps 3": base_id + 1015, # ItemPickup14_2 Photorealistic_Island (X=74877.625 Y=73738.594 Z=11422.057) \!/ Existing match 4
"Tutorial House Scraps 4": base_id + 1016, # ItemPickup15_8 Photorealistic_Island (X=75068.992 Y=73971.133 Z=11426.619)
"Tutorial House Scraps 5": base_id + 1017, # ItemPickup21_1 Photorealistic_Island (X=74923.500 Y=73571.648 Z=11426.619)
"Tutorial House Scraps 6": base_id + 1018, # ItemPickup19 Photorealistic_Island (X=75194.906 Y=73495.719 Z=11426.619)
"Tutorial House Scraps 7": base_id + 1019, # ItemPickup13_3 Photorealistic_Island (X=75320.102 Y=73446.352 Z=11487.376)
"Tutorial House Scraps 8": base_id + 1020, # ItemPickup20 Photorealistic_Island (X=75298.680 Y=73580.531 Z=11426.619)
"Tutorial House Scraps 9": base_id + 1021 # ItemPickup16_3 Photorealistic_Island (X=75310.008 Y=73770.742 Z=11489.709)
}
loc_swamp_edges = {
"Swamp Edges Scraps 1": base_id + 1022, # ItemPickup465_67 Swamp_EnvironmentDetails (X=81964.398 Y=72167.305 Z=10116.385)
"Swamp Edges Scraps 2": base_id + 1023, # ItemPickup460_52 Swamp_EnvironmentDetails (X=89674.047 Y=71610.008 Z=9482.095)
"Swamp Edges Scraps 3": base_id + 1024, # ItemPickup459_49 Swamp_EnvironmentDetails (X=91637.156 Y=73345.672 Z=9492.019)
"Swamp Edges Scraps 4": base_id + 1025, # ItemPickup458_46 Swamp_EnvironmentDetails (X=94601.117 Y=75064.117 Z=9567.464)
"Swamp Edges Scraps 5": base_id + 1026, # ItemPickup457_43 Swamp_EnvironmentDetails (X=95536.641 Y=72622.969 Z=9512.531)
"Swamp Edges Scraps 6": base_id + 1027, # ItemPickup456_40 Swamp_EnvironmentDetails (X=96419.922 Y=65508.676 Z=9838.949)
"Swamp Edges Scraps 7": base_id + 1028, # ItemPickup455_37 Swamp_EnvironmentDetails (X=98158.680 Y=63191.629 Z=10477.084)
"Swamp Edges Scraps 8": base_id + 1029, # ItemPickup55_29 Swamp_EnvironmentDetails (X=93421.820 Y=59200.461 Z=9545.312)
"Swamp Edges Scraps 9": base_id + 1030, # ItemPickup453_31 Swamp_EnvironmentDetails(X=92951.648 Y=56453.527 Z=9560.638)
"Swamp Edges Scraps 10": base_id + 1031, # ItemPickup454_34 Swamp_EnvironmentDetails (X=96943.297 Y=58754.043 Z=10728.124)
"Swamp Edges Scraps 11": base_id + 1032, # ItemPickup452_28 Swamp_EnvironmentDetails (X=95000.617 Y=53070.859 Z=10258.078)
"Swamp Edges Scraps 12": base_id + 1033, # ItemPickup451_25 Swamp_EnvironmentDetails (X=91390.703 Y=53628.707 Z=9498.378)
"Swamp Edges Scraps 13": base_id + 1034, # ItemPickup53_23 Swamp_EnvironmentDetails (X=87628.742 Y=51614.957 Z=9487.013)
"Swamp Edges Scraps 14": base_id + 1035, # ItemPickup448_16 Swamp_EnvironmentDetails (X=89785.992 Y=48603.844 Z=9573.859)
"Swamp Edges Scraps 15": base_id + 1036, # ItemPickup447_11 Swamp_EnvironmentDetails (X=89925.383 Y=46288.707 Z=9499.904)
"Swamp Edges Scraps 16": base_id + 1037, # ItemPickup446_8 Swamp_EnvironmentDetails (X=90848.938 Y=43133.535 Z=9729.535)
"Swamp Edges Scraps 17": base_id + 1038, # ItemPickup445_5 Swamp_EnvironmentDetails (X=87382.383 Y=42475.191 Z=9509.929)
"Swamp Edges Scraps 18": base_id + 1039, # ItemPickup444_2 Swamp_EnvironmentDetails (X=87481.820 Y=39316.820 Z=9757.511)
"Swamp Edges Scraps 19": base_id + 1040, # ItemPickup54_26 Swamp_EnvironmentDetails (X=86039.180 Y=37135.004 Z=9826.263)
"Swamp Edges Scraps 20": base_id + 1041, # ItemPickup475_97 Swamp_EnvironmentDetails (X=81798.609 Y=36766.922 Z=9479.318)
"Swamp Edges Scraps 21": base_id + 1042, # ItemPickup474_94 Swamp_EnvironmentDetails (X=79254.055 Y=40120.293 Z=9879.539)
"Swamp Edges Scraps 22": base_id + 1043, # ItemPickup473_91 Swamp_EnvironmentDetails (X=82251.773 Y=42454.027 Z=9482.057)
"Swamp Edges Scraps 23": base_id + 1044, # ItemPickup472_88 Swamp_EnvironmentDetails (X=84903.977 Y=48323.543 Z=9503.382)
"Swamp Edges Scraps 24": base_id + 1045, # ItemPickup471_85 Swamp_EnvironmentDetails (X=84238.609 Y=51239.547 Z=9529.745)
"Swamp Edges Scraps 25": base_id + 1046, # ItemPickup470_82 Swamp_EnvironmentDetails (X=84439.063 Y=53501.563 Z=9491.291)
"Swamp Edges Scraps 26": base_id + 1047, # ItemPickup52_20 Swamp_EnvironmentDetails (X=83025.086 Y=53275.348 Z=9694.177)
"Swamp Edges Scraps 27": base_id + 1048, # ItemPickup469_79 Swamp_EnvironmentDetails (X=79827.055 Y=54791.504 Z=10121.452)
"Swamp Edges Scraps 28": base_id + 1049, # ItemPickup468_76 Swamp_EnvironmentDetails (X=82266.461 Y=58126.316 Z=9660.493)
"Swamp Edges Scraps 29": base_id + 1050, # ItemPickup467_73 Swamp_EnvironmentDetails (X=75911.297 Y=65155.836 Z=10660.832)
"Swamp Edges Scraps 30": base_id + 1051, # ItemPickup466_70 Swamp_EnvironmentDetails (X=81171.641 Y=66836.125 Z=9673.756)
"Swamp Edges Scraps 31": base_id + 1052, # ItemPickup449_19 Swamp_EnvironmentDetails (X=95254.992 Y=40910.563 Z=10503.727)
"Swamp Edges Scraps 32": base_id + 1053 # ItemPickup450_22 Swamp_EnvironmentDetails (X=93992.992 Y=50773.484 Z=10238.064)
}
loc_swamp_mission = {
"Swamp Shack Scraps 1": base_id + 1054, # ItemPickup51_17 Swamp_EnvironmentDetails (X=87685.797 Y=69754.008 Z=9629.617)
"Swamp Shack Scraps 2": base_id + 1055, # ItemPickup461_55 Swamp_EnvironmentDetails (X=87308.883 Y=69096.789 Z=9624.543)
"Swamp Islet Scraps 1": base_id + 1056, # ItemPickup462_58 Swamp_EnvironmentDetails (X=88101.219 Y=64553.148 Z=9557.692)
"Swamp Islet Scraps 2": base_id + 1057, # ItemPickup463_61 Swamp_EnvironmentDetails (X=87100.922 Y=63590.965 Z=9582.900)
"Swamp Islet Scraps 3": base_id + 1058, # ItemPickup464_64 Swamp_EnvironmentDetails (X=86399.656 Y=64290.805 Z=9493.576)
"Swamp Islet Dead Fish": base_id + 1059, # Swamp_FishPickup Swamp_EnvironmentDetails (X=87288.945 Y=64278.273 Z=9550.320)
"Swamp Lizbeth Murkwater Mission End": base_id + 1060 # (dialog 2) -> 30_scraps_reward
}
loc_junkyard_area = {
"Junkyard Area Scraps 1": base_id + 1061, # ItemPickup185_29 Junkyard_Details1 (X=94184.391 Y=89760.258 Z=9331.188)
"Junkyard Area Scraps 2": base_id + 1062, # ItemPickup177_5 Junkyard_Details1 (X=91919.469 Y=89681.602 Z=9407.639)
"Junkyard Area Scraps 3": base_id + 1063, # ItemPickup46_5 Junkyard_Details (X=91696.078 Y=90453.563 Z=9480.997)
"Junkyard Area Scraps 4": base_id + 1064, # ItemPickup178_8 Junkyard_Details1 (X=92453.719 Y=91142.531 Z=9398.951)
"Junkyard Area Scraps 5": base_id + 1065, # ItemPickup182_20 Junkyard_Details1 (X=88645.453 Y=90374.930 Z=9507.291)
"Junkyard Area Scraps 6": base_id + 1066, # ItemPickup48_11 Junkyard_Details (X=88461.953 Y=92077.531 Z=9712.173)
"Junkyard Area Scraps 7": base_id + 1067, # ItemPickup49_14 Junkyard_Details (X=91521.555 Y=93773.641 Z=9421.457)
"Junkyard Area Scraps 8": base_id + 1068, # ItemPickup50_17 Junkyard_Details (X=94741.484 Y=92565.938 Z=9221.093)
"Junkyard Area Scraps 9": base_id + 1069, # ItemPickup186_32 Junkyard_Details1 (X=95256.008 Y=91356.789 Z=9251.082)
"Junkyard Area Scraps 10": base_id + 1070, # ItemPickup45_2 Junkyard_Details (X=94289.664 Y=89951.477 Z=9367.076)
"Junkyard Area Daryl Mission Start": base_id + 1071, # (dialog 4) -> Lockpicks (4)
"Junkyard Area Chest Ancient Tablet": base_id + 1072, # Junkyard_TabletPickup Junkyard_Details (X=90715.367 Y=92168.563 Z=9402.729)
"Junkyard Area Daryl Mission End": base_id + 1073 # (dialog 3) -> 25_scraps_reward
}
loc_south_house = {
"South House Scraps 1": base_id + 1074, # ItemPickup361_26 Secret12_ExteriorDetails (X=85865.969 Y=103869.656 Z=9453.063)
"South House Scraps 2": base_id + 1075, # ItemPickup360_23 Secret12_ExteriorDetails (X=84403.742 Y=107229.039 Z=9067.245)
"South House Scraps 3": base_id + 1076, # ItemPickup359_20 Secret12_ExteriorDetails (X=83389.789 Y=108817.992 Z=8752.255)
"South House Scraps 4": base_id + 1077, # ItemPickup353_2 Secret12_ExteriorDetails (X=82413.547 Y=109697.477 Z=8637.677)
"South House Scraps 5": base_id + 1078, # ItemPickup354_5 Secret12_ExteriorDetails (X=83000.359 Y=110323.664 Z=8560.229)
"South House Scraps 6": base_id + 1079, # ItemPickup358_17 Secret12_ExteriorDetails (X=82072.625 Y=110482.664 Z=8682.441)
"South House Scraps 7": base_id + 1080, # ItemPickup24_30 Secret12_Details (X=81970.766 Y=111082.117 Z=8647.703)
"South House Scraps 8": base_id + 1081, # ItemPickup356_11 Secret12_ExteriorDetails (X=80915.375 Y=108689.758 Z=8377.754)
"South House Scraps 9": base_id + 1082, # ItemPickup355_8 Secret12_ExteriorDetails (X=81762.180 Y=111371.023 Z=7876.312)
"South House Scraps 10": base_id + 1083, # ItemPickup357_14 Secret12_ExteriorDetails (X=80663.336 Y=113306.695 Z=7226.475)
"South House Scraps 11": base_id + 1084, # ItemPickup23_21 Secret12_Details (X=80520.367 Y=113747.039 Z=7252.808)
"South House Scraps 12": base_id + 1085, # ItemPickup22_18 Secret12_Details (X=80830.273 Y=113871.383 Z=7201.687)
"South House Chest Scraps 1": base_id + 1086, # ItemPickup21 Secret12_Details (X=82079.922 Y=110808.602 Z=8739.324)
"South House Chest Scraps 2": base_id + 1087, # ItemPickup18 Secret12_Details (X=82102.664 Y=110813.664 Z=8726.308)
"South House Chest Scraps 3": base_id + 1088, # ItemPickup17 Secret12_Details (X=82091.547 Y=110810.906 Z=8721.354) \!/ Existing match 1
"South House Chest Scraps 4": base_id + 1089, # ItemPickup16 Secret12_Details (X=82102.664 Y=110813.664 Z=8708.337) KO
"South House Chest Scraps 5": base_id + 1090, # ItemPickup14 Secret12_Details (X=82091.516 Y=110810.898 Z=8701.793) \!/ Existing match 3
"South House Chest Scraps 6": base_id + 1091 # ItemPickup13_7 Secret12_Details (X=82102.664 Y=110813.625 Z=8688.776)
}
loc_junkyard_shed = {
"Junkyard Shed Helen Mission Start": base_id + 1092, # (dialog 8) -> south_mine_key (6)
"Junkyard Shed Scraps 1": base_id + 1093, # ItemPickup424_23 Settlement_A_House_1 (X=98303.992 Y=84476.016 Z=9376.540)
"Junkyard Shed Scraps 2": base_id + 1094, # ItemPickup419_8 Settlement_A_House_1 (X=98174.680 Y=84067.383 Z=9249.197)
"Junkyard Shed Scraps 3": base_id + 1095, # ItemPickup418_5 Settlement_A_House_1 (X=97948.977 Y=83354.656 Z=9339.430)
"Junkyard Shed Scraps 4": base_id + 1096, # ItemPickup417_2 Settlement_A_House_1 (X=98208.391 Y=83088.047 Z=9273.632)
"Junkyard Shed Scraps 5": base_id + 1097, # ItemPickup420_11 Settlement_A_House_1 (X=97757.773 Y=82995.656 Z=9298.597)
"Junkyard Shed Scraps 6": base_id + 1098, # ItemPickup422_17 Settlement_A_House_1 (X=98776.102 Y=80881.133 Z=9286.782)
"Junkyard Shed Scraps 7": base_id + 1099, # ItemPickup421_14 Settlement_A_House_1 (X=99198.508 Y=82057.820 Z=9248.227)
"Junkyard Shed Scraps 8": base_id + 1100 # ItemPickup423_20 Settlement_A_House_1 (X=99208.617 Y=84383.125 Z=9257.880)
}
loc_military_base = {
"Military Base Sgt Flint Mission End": base_id + 1101, # (dialog 2) -> bug_spray
"Military Base Scraps 1": base_id + 1102, # ItemPickup134_17 Bugspray_Main (X=105743.531 Y=83017.492 Z=9423.290)
"Military Base Scraps 2": base_id + 1103, # ItemPickup129_2 Bugspray_Main (X=108495.805 Y=81616.992 Z=9139.340)
"Military Base Scraps 3": base_id + 1104, # ItemPickup135_20 Bugspray_Main (X=108709.219 Y=85981.016 Z=9650.472)
"Military Base Scraps 4": base_id + 1105, # ItemPickup130_5 Bugspray_Main (X=112004.195 Y=83811.313 Z=8887.996)
"Military Base Scraps 5": base_id + 1106, # ItemPickup131_8 Bugspray_Main (X=110904.867 Y=82024.781 Z=9581.007)
"Military Base Scraps 6": base_id + 1107, # ItemPickup132_11 Bugspray_Main (X=112458.563 Y=81967.945 Z=9850.968)
"Military Base Scraps 7": base_id + 1108, # ItemPickup22_9 Bugspray_Details (X=112541.695 Y=81345.875 Z=9896.940)
"Military Base Scraps 8": base_id + 1109, # ItemPickup133_14 Bugspray_Main (X=111943.391 Y=79970.016 Z=10025.820)
"Military Base Scraps 9": base_id + 1110, # ItemPickup24_8 Bugspray_Details (X=112074.063 Y=83533.398 Z=9008.831)
"Military Base Scraps 10": base_id + 1111, # ItemPickup23_2 Bugspray_Details (X=110738.523 Y=85389.852 Z=9082.626)
"Military Base Scraps 11": base_id + 1112, # ItemPickup136_23 Bugspray_Main (X=112962.594 Y=85872.922 Z=8638.805)
"Military Base Scraps 12": base_id + 1113, # ItemPickup137_26 Bugspray_Main (X=116230.563 Y=84357.602 Z=8580.226)
"Military Base Orange Paint Can": base_id + 1114 # PaintCan_5 Bugspray_Details (X=111916.102 Y=83066.195 Z=9094.554)
}
loc_south_mine_outside = {
"South Mine Outside Scraps 1": base_id + 1115, # ItemPickup20_1 Mine_1_OutsideMain (X=114794.375 Y=57211.855 Z=8523.348)
"South Mine Outside Scraps 2": base_id + 1116, # ItemPickup15_2 Mine_1_OutsideDetails (X=112523.438 Y=57693.836 Z=8639.382)
"South Mine Outside Scraps 3": base_id + 1117, # ItemPickup22_5 Mine_1_OutsideMain (X=112348.586 Y=59174.289 Z=8945.143)
"South Mine Outside Scraps 4": base_id + 1118, # ItemPickup13_2 Mine_1_OutsideDetails (X=110989.156 Y=57840.090 Z=8700.936)
"South Mine Outside Scraps 5": base_id + 1119, # ItemPickup16_1 Mine_1_OutsideDetails (X=110487.281 Y=54528.535 Z=8589.910)
"South Mine Outside Scraps 6": base_id + 1120, # ItemPickup18_1 Mine_1_OutsideMain (X=113727.297 Y=54791.703 Z=8424.460)
"South Mine Outside Scraps 7": base_id + 1121 # ItemPickup17_3 Mine_1_OutsideMain (X=113965.211 Y=53289.539 Z=8402.346)
}
loc_south_mine_inside = {
"South Mine Inside Scraps 1": base_id + 1122, # ItemPickup23_4 Mine_1_Interior_1 (X=108659.945 Y=58712.691 Z=8763.015)
"South Mine Inside Scraps 2": base_id + 1123, # ItemPickup24_0 Mine_1_Interior_1 (X=104954.602 Y=61540.488 Z=7876.374)
"South Mine Inside Scraps 3": base_id + 1124, # ItemPickup26_0 Mine_1_Interior_1 (X=104436.758 Y=64091.211 Z=7872.767)
"South Mine Inside Scraps 4": base_id + 1125, # ItemPickup290_20 Mine_1_Interior_2 (X=101356.625 Y=66110.906 Z=8034.738)
"South Mine Inside Scraps 5": base_id + 1126, # ItemPickup287_8 Mine_1_Interior_2 (X=96888.820 Y=64458.559 Z=7917.468)
"South Mine Inside Scraps 6": base_id + 1127, # ItemPickup289_14 Mine_1_Interior_2 (X=95863.180 Y=63252.902 Z=7847.054)
"South Mine Inside Scraps 7": base_id + 1128, # ItemPickup288_11 Mine_1_Interior_2 (X=97337.219 Y=62921.438 Z=7884.393)
"South Mine Inside Scraps 8": base_id + 1129, # ItemPickup285_2 Mine_1_Interior_2 (X=96689.203 Y=61880.895 Z=7806.810)
"South Mine Inside Scraps 9": base_id + 1130, # ItemPickup286_5 Mine_1_Interior_2 (X=98403.227 Y=62812.531 Z=7880.947)
"South Mine Inside Green Egg": base_id + 1131, # ItemPickup14_2 Mine_1_Interior_2 (X=96753.219 Y=62909.504 Z=8030.018) \!/ Existing match 4
"South Mine Inside Green Paint Can": base_id + 1132 # PaintCan_2 Mine_1_Interior_1 (X=108293.281 Y=64192.094 Z=7872.770) \!/ Existing match 5
}
loc_middle_station = {
"Middle Station White Paint Can": base_id + 1133, # PaintCan_2 Station_Details1 (X=34554.141 Y=-7395.408 Z=11897.556) \!/ Existing match 5
"Middle Station Scraps 1": base_id + 1134, # ItemPickup431_20 Station_BuildingDetails (X=37710.504 Y=-6462.562 Z=11356.691)
"Middle Station Scraps 2": base_id + 1135, # ItemPickup425_2 Station_BuildingDetails (X=37034.340 Y=-4923.256 Z=11348.328)
"Middle Station Scraps 3": base_id + 1136, # ItemPickup427_8 Station_BuildingDetails (X=36689.164 Y=-3727.466 Z=11353.597)
"Middle Station Scraps 4": base_id + 1137, # ItemPickup426_5 Station_BuildingDetails (X=37207.629 Y=-3393.977 Z=11379.110)
"Middle Station Scraps 5": base_id + 1138, # ItemPickup429_14 Station_BuildingDetails (X=37988.219 Y=-3365.906 Z=11350.225)
"Middle Station Scraps 6": base_id + 1139, # ItemPickup428_11 Station_BuildingDetails (X=36956.242 Y=-2746.948 Z=11353.506)
"Middle Station Scraps 7": base_id + 1140, # ItemPickup430_17 Station_BuildingDetails (X=36638.492 Y=-6410.017 Z=11353.546)
"Middle Station Scraps 8": base_id + 1141, # ItemPickup433_26 Station_BuildingDetails (X=35931.168 Y=-7558.021 Z=11899.232)
"Middle Station Scraps 9": base_id + 1142, # ItemPickup434 Station_BuildingDetails (X=35636.855 Y=-7628.500 Z=11903.627)
"Middle Station Scraps 10": base_id + 1143, # ItemPickup435 Station_BuildingDetails (X=34894.152 Y=-7537.087 Z=11903.627)
"Middle Station Scraps 11": base_id + 1144, # ItemPickup13_4 Station_BuildingDetails (X=33505.609 Y=-7742.843 Z=11898.971)
"Middle Station Scraps 12": base_id + 1145, # ItemPickup440_5 Station_Details1 (X=37394.004 Y=-8395.084 Z=11389.296)
"Middle Station Scraps 13": base_id + 1146, # ItemPickup432_23 Station_BuildingDetails (X=36040.695 Y=-8068.016 Z=11456.609)
"Middle Station Scraps 14": base_id + 1147, # ItemPickup16_4 Station_BuildingDetails (X=35360.320 Y=-8441.443 Z=11457.823)
"Middle Station Scraps 15": base_id + 1148, # ItemPickup439_2 Station_Details1 (X=36311.324 Y=-9563.938 Z=11468.039)
"Middle Station Scraps 16": base_id + 1149, # ItemPickup442_11 Station_Details1 (X=33335.656 Y=-13872.785 Z=11189.906)
"Middle Station Scraps 17": base_id + 1150, # ItemPickup441_8 Station_Details1 (X=33129.984 Y=-14073.978 Z=11189.906)
"Middle Station Scraps 18": base_id + 1151, # ItemPickup436_31 Station_BuildingDetails (X=33587.488 Y=-7828.651 Z=11529.446)
"Middle Station Scraps 19": base_id + 1152, # ItemPickup14_3 Station_BuildingDetails (X=34007.254 Y=-7749.381 Z=11533.760)
"Middle Station Scraps 20": base_id + 1153, # ItemPickup443_14 Station_Details1 (X=31457.752 Y=-7120.744 Z=11421.197)
"Middle Station Theodore Mission End": base_id + 1154 # (dialog 2) -> 35_scraps_reward
# "Middle Station Scraps Glitch 1" ItemPickup437_34 (X=34217.613 Y=-9481.271 Z=11505.686) /!\ Glitched scrap
# "Middle Station Scraps Glitch 2" ItemPickup438_37 (X=36101.633 Y=-10459.024 Z=11385.937) /!\ Glitched scrap
}
loc_canyon = {
"Canyon Scraps 1": base_id + 1155, # ItemPickup156_47 Canyon_Main (X=29432.162 Y=-3164.300 Z=11540.294)
"Canyon Scraps 2": base_id + 1156, # ItemPickup155_44 Canyon_Main (X=26331.086 Y=3036.740 Z=11701.688)
"Canyon Scraps 3": base_id + 1157, # ItemPickup154_41 Canyon_Main (X=22688.129 Y=3906.730 Z=12249.182)
"Canyon Scraps 4": base_id + 1158, # ItemPickup147_20 Canyon_Main (X=20546.193 Y=4371.471 Z=12128.874)
"Canyon Scraps 5": base_id + 1159, # ItemPickup148_23 Canyon_Main (X=20006.584 Y=4928.478 Z=12174.837)
"Canyon Scraps 6": base_id + 1160, # ItemPickup146_17 Canyon_Main (X=19251.633 Y=3798.014 Z=12170.390)
"Canyon Scraps 7": base_id + 1161, # ItemPickup149_26 Canyon_Main (X=18302.678 Y=7323.849 Z=12595.085)
"Canyon Scraps 8": base_id + 1162, # ItemPickup150_29 Canyon_Main (X=19019.563 Y=8172.146 Z=12640.462)
"Canyon Scraps 9": base_id + 1163, # ItemPickup142_5 Canyon_Main (X=18001.689 Y=11138.320 Z=13035.360)
"Canyon Scraps 10": base_id + 1164, # ItemPickup143_8 Canyon_Main (X=16381.525 Y=7191.394 Z=13682.453)
"Canyon Scraps 11": base_id + 1165, # ItemPickup144_11 Canyon_Main (X=18294.928 Y=7870.372 Z=14350.015)
"Canyon Scraps 12": base_id + 1166, # ItemPickup31_2 CanyonCamp_Details (X=20730.520 Y=8032.158 Z=14439.826)
"Canyon Scraps 13": base_id + 1167, # ItemPickup145_14 Canyon_Main (X=24752.658 Y=7959.624 Z=14363.087)
"Canyon Scraps 14": base_id + 1168, # ItemPickup141_2 Canyon_Main (X=20181.992 Y=13816.017 Z=14897.407)
"Canyon Scraps 15": base_id + 1169, # ItemPickup151_32 Canyon_Main (X=23172.160 Y=2842.120 Z=12954.566)
"Canyon Scraps 16": base_id + 1170, # ItemPickup152_35 Canyon_Main (X=22307.621 Y=-1180.840 Z=12451.548)
"Canyon Scraps 17": base_id + 1171, # ItemPickup153_38 Canyon_Main (X=28473.596 Y=6741.842 Z=13314.166)
"Canyon Blue Box": base_id + 1172 # Canyon_BlueBoxPickup CanyonCamp_Details (X=20338.525 Y=4989.111 Z=12323.649)
}
loc_watchtower = {
"Watchtower Scraps 1": base_id + 1173, # ItemPickup373_13 Secret2_WatchTowerDetails (X=32760.389 Y=-28814.084 Z=10997.447)
"Watchtower Scraps 2": base_id + 1174, # ItemPickup22_6 Secret2_WatchTowerDetails (X=32801.668 Y=-31660.041 Z=10643.390)
"Watchtower Scraps 3": base_id + 1175, # ItemPickup372_10 Secret2_WatchTowerDetails (X=31018.063 Y=-33375.313 Z=11100.126)
"Watchtower Scraps 4": base_id + 1176, # ItemPickup22_16 Secret2_WatchTowerDetails (X=33308.215 Y=-35928.578 Z=10614.347)
"Watchtower Scraps 5": base_id + 1177, # ItemPickup22_2 Secret2_WatchTowerDetails (X=34304.262 Y=-33446.063 Z=10674.936)
"Watchtower Scraps 6": base_id + 1178, # ItemPickup370_2 Secret2_WatchTowerDetails (X=32869.453 Y=-33184.094 Z=10612.040)
"Watchtower Scraps 7": base_id + 1179, # ItemPickup374_16 Secret2_WatchTowerDetails (X=33210.707 Y=-32097.611 Z=11211.031)
"Watchtower Scraps 8": base_id + 1180, # ItemPickup22_10 Secret2_WatchTowerDetails (X=33246.262 Y=-32046.697 Z=11851.025)
"Watchtower Scraps 9": base_id + 1181, # ItemPickup22_8 Secret2_WatchTowerDetails (X=33553.156 Y=-31810.645 Z=11849.521)
"Watchtower Scraps 10": base_id + 1182, # ItemPickup371_7 Secret2_WatchTowerDetails (X=36151.621 Y=-31791.633 Z=11093.785)
"Watchtower Pink Paint Can": base_id + 1183 # PaintCan_2 Secret2_WatchTowerDetails (X=33069.133 Y=-32168.045 Z=11859.582) \!/ Existing match 5
}
loc_boulder_field = {
"Boulder Field Page Drawing 1": base_id + 1184, # Pages_Drawing1 Pages_Environment_Details (X=46232.703 Y=-37052.875 Z=9531.116)
"Boulder Field Page Drawing 2": base_id + 1185, # Pages_Drawing2 Pages_Environment_Details (X=51854.980 Y=-31332.070 Z=9804.927)
"Boulder Field Page Drawing 3": base_id + 1186, # Pages_Drawing3 Pages_Environment_Details (X=47595.750 Y=-29931.740 Z=9308.014)
"Boulder Field Page Drawing 4": base_id + 1187, # Pages_Drawing4 Pages_Environment_Details (X=43819.680 Y=-30378.770 Z=9706.599)
"Boulder Field Page Drawing 5": base_id + 1188, # Pages_Drawing5 Pages_Environment_Details (X=47494.746 Y=-20884.781 Z=9812.398)
"Boulder Field Page Drawing 6": base_id + 1189, # Pages_Drawing6 Pages_Environment_Details (X=43725.148 Y=-21952.570 Z=9744.351)
"Boulder Field Page Drawing 7": base_id + 1190, # Pages_Drawing7 Pages_Environment_Details (X=44752.465 Y=-16362.510 Z=10147.004)
"Boulder Field Page Drawing 8": base_id + 1191, # Pages_Drawing8 Pages_Environment_Details (X=50496.270 Y=-26090.533 Z=9835.365)
"Boulder Field Scraps 1": base_id + 1192, # ItemPickup293_8 Pages_Environment_Main (X=41385.406 Y=-32281.871 Z=10240.781)
"Boulder Field Scraps 2": base_id + 1193, # ItemPickup987321 Pages_House_Main (X=46654.969 Y=-38859.254 Z=9920.861)
"Boulder Field Scraps 3": base_id + 1194, # ItemPickup516121 Pages_House_Main (X=44765.836 Y=-41675.559 Z=9938.179)
"Boulder Field Scraps 4": base_id + 1195, # ItemPickup291_2 Pages_Environment_Main (X=50088.270 Y=-30669.107 Z=9267.371)
"Boulder Field Scraps 5": base_id + 1196, # ItemPickup303_38 Pages_Environment_Main (X=48014.609 Y=-28971.115 Z=9199.659)
"Boulder Field Scraps 6": base_id + 1197, # ItemPickup302_35 Pages_Environment_Main (X=50190.266 Y=-26243.977 Z=9648.289)
"Boulder Field Scraps 7": base_id + 1198, # ItemPickup305_44 Pages_Environment_Main (X=47802.246 Y=-22594.684 Z=9631.879)
"Boulder Field Scraps 8": base_id + 1199, # ItemPickup294_11 Pages_Environment_Main (X=44345.996 Y=-23408.535 Z=9659.643)
"Boulder Field Scraps 9": base_id + 1200, # ItemPickup295_14 Pages_Environment_Main (X=41620.590 Y=-22982.641 Z=9720.177)
"Boulder Field Scraps 10": base_id + 1201, # ItemPickup300_29 Pages_Environment_Main (X=52003.172 Y=-19163.049 Z=9925.105)
"Boulder Field Scraps 11": base_id + 1202, # ItemPickup301_32 Pages_Environment_Main (X=51422.176 Y=-22319.322 Z=10663.813)
"Boulder Field Scraps 12": base_id + 1203, # ItemPickup296_17 Pages_Environment_Main (X=43527.176 Y=-17952.570 Z=10812.458)
"Boulder Field Scraps 13": base_id + 1204, # ItemPickup297_20 Pages_Environment_Main (X=45241.871 Y=-15847.636 Z=9952.198)
"Boulder Field Scraps 14": base_id + 1205, # ItemPickup298_23 Pages_Environment_Main (X=46238.027 Y=-18407.420 Z=10199.825)
"Boulder Field Scraps 15": base_id + 1206, # ItemPickup299_26 Pages_Environment_Main (X=49835.617 Y=-17379.959 Z=9810.836)
"Boulder Field Scraps 16": base_id + 1207, # ItemPickup306_47 Pages_Environment_Main (X=45144.594 Y=-33817.090 Z=10136.658)
"Boulder Field Scraps 17": base_id + 1208, # ItemPickup292_5 Pages_Environment_Main (X=44336.184 Y=-37162.367 Z=9789.548)
"Boulder Field Scraps 18": base_id + 1209 # ItemPickup304_41 Pages_Environment_Main (X=44490.160 Y=-26442.754 Z=9974.022)
}
loc_haunted_house = {
"Haunted House Sasha Mission End": base_id + 1210, # (dialog 2) -> 40_scraps_reward
"Haunted House Scraps 1": base_id + 1211, # ItemPickup1309876 Pages_House_Main (X=42900.188 Y=-43760.617 Z=9900.531)
"Haunted House Scraps 2": base_id + 1212, # ItemPickup22213 Pages_House_Main (X=43608.078 Y=-44642.434 Z=9922.888)
"Haunted House Scraps 3": base_id + 1213, # ItemPickup5423189 Pages_House_Main (X=43992.387 Y=-44259.336 Z=9877.623)
"Haunted House Scraps 4": base_id + 1214, # ItemPickup1312312 Pages_House_Main (X=43340.012 Y=-45362.617 Z=9882.796)
"Haunted House Scraps 5": base_id + 1215, # ItemPickup1596 Pages_House_Main (X=45105.383 Y=-45980.879 Z=9854.796)
"Haunted House Scraps 6": base_id + 1216 # ItemPickup8624 Pages_House_Main (X=45888.406 Y=-46050.246 Z=9555.326)
}
loc_santiago_house = {
"Santiago House Scraps 1": base_id + 1217, # ItemPickup342_19 PortNPCHouse_Details (X=37271.445 Y=-46075.598 Z=10648.827)
"Santiago House Scraps 2": base_id + 1218, # ItemPickup37_5 PortNPCHouse_Details (X=38330.512 Y=-47184.668 Z=10387.618)
"Santiago House Scraps 3": base_id + 1219, # ItemPickup337_2 PortNPCHouse_Details (X=35720.422 Y=-49536.328 Z=10098.503)
"Santiago House Scraps 4": base_id + 1220, # ItemPickup344_25 PortNPCHouse_Details (X=35466.285 Y=-50363.078 Z=10098.504)
"Santiago House Scraps 5": base_id + 1221, # ItemPickup338_7 PortNPCHouse_Details (X=34274.289 Y=-49947.578 Z=10098.501)
"Santiago House Scraps 6": base_id + 1222, # ItemPickup341_16 PortNPCHouse_Details (X=35584.359 Y=-48195.172 Z=10323.833)
"Santiago House Scraps 7": base_id + 1223, # ItemPickup340_13 PortNPCHouse_Details (X=35019.766 Y=-49904.113 Z=10124.169)
"Santiago House Scraps 8": base_id + 1224, # ItemPickup339_10 PortNPCHouse_Details (X=35527.711 Y=-49614.801 Z=10124.016)
"Santiago House Scraps 9": base_id + 1225, # ItemPickup36_2 PortNPCHouse_Details (X=34471.707 Y=-49497.000 Z=10199.790)
"Santiago House Scraps 10": base_id + 1226, # ItemPickup343_22 PortNPCHouse_Details (X=37920.277 Y=-51867.754 Z=9847.511)
"Santiago House Journal": base_id + 1227 # Port_Journal_Pickup PortNPCHouse_Details (X=34690.777 Y=-49788.359 Z=10214.353)
}
loc_port = {
"Port Grey Paint Can": base_id + 1228, # PaintCan_13 Port_Details (X=74641.648 Y=-11320.948 Z=7551.767)
"Port Scraps 1": base_id + 1229, # ItemPickup334_32 Port_Main (X=67315.281 Y=-13828.055 Z=10101.339)
"Port Scraps 2": base_id + 1230, # ItemPickup335_35 Port_Main (X=67679.508 Y=-14127.952 Z=10061.037)
"Port Scraps 3": base_id + 1231, # ItemPickup336_38 Port_Main (X=67062.219 Y=-15626.003 Z=10065.956)
"Port Scraps 4": base_id + 1232, # ItemPickup21_15 Port_Main (X=66140.914 Y=-16079.730 Z=10092.268)
"Port Scraps 5": base_id + 1233, # ItemPickup18_12 Port_Main (X=66824.719 Y=-14729.157 Z=10125.234)
"Port Scraps 6": base_id + 1234, # ItemPickup333_29 Port_Main (X=69777.258 Y=-8371.526 Z=9391.735)
"Port Scraps 7": base_id + 1235, # ItemPickup332_26 Port_Main (X=70339.695 Y=-11066.703 Z=8912.465)
"Port Scraps 8": base_id + 1236, # ItemPickup331_23 Port_Main (X=72729.508 Y=-7048.998 Z=8245.522)
"Port Scraps 9": base_id + 1237, # ItemPickup329_17 Port_Main (X=75896.070 Y=-8705.214 Z=7514.992)
"Port Scraps 10": base_id + 1238, # ItemPickup330_20 Port_Main (X=74264.211 Y=-10553.446 Z=7520.141)
"Port Scraps 11": base_id + 1239, # ItemPickup17_9 Port_Main (X=74328.117 Y=-11423.852 Z=7511.827)
"Port Scraps 12": base_id + 1240, # ItemPickup328_14 Port_Main (X=76753.164 Y=-10744.933 Z=7437.174)
"Port Scraps 13": base_id + 1241, # ItemPickup326_8 Port_Main (X=77330.414 Y=-11640.151 Z=7189.003)
"Port Scraps 14": base_id + 1242, # ItemPickup327_11 Port_Main (X=76403.516 Y=-12484.995 Z=7440.368)
"Port Scraps 15": base_id + 1243, # ItemPickup324_2 Port_Main (X=78651.977 Y=-12233.159 Z=7439.514)
"Port Scraps 16": base_id + 1244, # ItemPickup14_5 Port_Main (X=80336.297 Y=-12276.590 Z=7436.639)
"Port Scraps 17": base_id + 1245, # ItemPickup325_5 Port_Main (X=79845.086 Y=-13410.705 Z=7440.597)
"Port Scraps 18": base_id + 1246, # ItemPickup13_2 Port_Main (X=76156.719 Y=-12816.718 Z=7439.269) KO
"Port Scraps 19": base_id + 1247, # ItemPickup16 Port_Main (X=80754.914 Y=-14055.545 Z=7445.339) KO
"Port Santiago Mission End": base_id + 1248 # (dialog 3) -> 35_scraps_reward
}
loc_trench_house = {
"Trench House Scraps 1": base_id + 1249, # ItemPickup157_2 DeadWoodsEnvironment (X=76340.328 Y=-42886.191 Z=9567.521)
"Trench House Scraps 2": base_id + 1250, # ItemPickup158_5 DeadWoodsEnvironment (X=76013.594 Y=-44140.141 Z=9413.147)
"Trench House Scraps 3": base_id + 1251, # ItemPickup159_11 DeadWoodsEnvironment (X=74408.320 Y=-45424.000 Z=9446.966)
"Trench House Scraps 4": base_id + 1252, # ItemPickup69_14 Secret8_BasementHouseDetails (X=75196.344 Y=-48321.504 Z=9453.302)
"Trench House Scraps 5": base_id + 1253, # ItemPickup160_14 DeadWoodsEnvironment (X=73467.273 Y=-48995.738 Z=9355.070)
"Trench House Scraps 6": base_id + 1254, # ItemPickup163_23 DeadWoodsEnvironment (X=76418.469 Y=-53239.539 Z=9276.892)
"Trench House Scraps 7": base_id + 1255, # ItemPickup173_56 DeadWoodsEnvironment (X=70719.875 Y=-54290.117 Z=9357.084)
"Trench House Scraps 8": base_id + 1256, # ItemPickup165_29 DeadWoodsEnvironment (X=70075.938 Y=-53041.973 Z=9675.481)
"Trench House Scraps 9": base_id + 1257, # ItemPickup162_20 DeadWoodsEnvironment (X=74745.711 Y=-52304.027 Z=9073.130)
"Trench House Scraps 10": base_id + 1258, # ItemPickup68_11 Secret8_BasementHouseDetails (X=74519.750 Y=-53603.063 Z=9078.054)
"Trench House Scraps 11": base_id + 1259, # ItemPickup161_17 DeadWoodsEnvironment (X=73747.492 Y=-52589.906 Z=9104.748)
"Trench House Scraps 12": base_id + 1260, # ItemPickup67_8 Secret8_BasementHouseDetails (X=74333.125 Y=-52847.961 Z=9124.773)
"Trench House Scraps 13": base_id + 1261, # ItemPickup66_5 Secret8_BasementHouseDetails (X=74062.195 Y=-52663.043 Z=9122.827)
"Trench House Scraps 14": base_id + 1262, # ItemPickup65_2 Secret8_BasementHouseDetails (X=74820.492 Y=-51350.051 Z=7956.387)
"Trench House Scraps 15": base_id + 1263, # ItemPickup164_26 DeadWoodsEnvironment (X=75286.289 Y=-51164.098 Z=7957.081)
"Trench House Scraps 16": base_id + 1264, # ItemPickup174_59 DeadWoodsEnvironment (X=68413.258 Y=-56872.816 Z=9349.443)
"Trench House Scraps 17": base_id + 1265, # ItemPickup175_62 DeadWoodsEnvironment (X=67281.281 Y=-59201.371 Z=9254.457)
"Trench House Scraps 18": base_id + 1266, # ItemPickup172_50 DeadWoodsEnvironment (X=69064.219 Y=-48796.352 Z=9770.164)
"Trench House Chest Scraps 1": base_id + 1267, # ItemPickup75123123 Secret8_BasementHouseDetails (X=75042.141 Y=-50830.891 Z=8005.156)
"Trench House Chest Scraps 2": base_id + 1268, # ItemPickup741123 Secret8_BasementHouseDetails (X=75066.516 Y=-50824.398 Z=7995.000)
"Trench House Chest Scraps 3": base_id + 1269, # ItemPickup729842 Secret8_BasementHouseDetails (X=75072.789 Y=-50818.441 Z=7986.979)
"Trench House Chest Scraps 4": base_id + 1270, # ItemPickup711123 Secret8_BasementHouseDetails (X=75038.656 Y=-50827.566 Z=7973.354)
"Trench House Chest Scraps 5": base_id + 1271, # ItemPickup73 Secret8_BasementHouseDetails (X=75060.406 Y=-50828.102 Z=7965.915)
"Trench House Chest Scraps 6": base_id + 1272 # ItemPickup7075674 Secret8_BasementHouseDetails (X=75056.648 Y=-50818.125 Z=7959.868)
}
loc_doll_woods = {
"Doll Woods Scraps 1": base_id + 1273, # ItemPickup78_2 DeadWoodsDolls (X=60126.234 Y=-49668.906 Z=9970.880)
"Doll Woods Scraps 2": base_id + 1274, # ItemPickup166_32 DeadWoodsEnvironment (X=59854.066 Y=-47313.121 Z=10376.684)
"Doll Woods Scraps 3": base_id + 1275, # ItemPickup80_8 DeadWoodsDolls (X=59130.613 Y=-49597.789 Z=9930.675)
"Doll Woods Scraps 4": base_id + 1276, # ItemPickup168_38 DeadWoodsEnvironment (X=59785.973 Y=-51269.684 Z=10180.019)
"Doll Woods Scraps 5": base_id + 1277, # ItemPickup81_11 DeadWoodsDolls (X=58226.449 Y=-52660.801 Z=10576.626)
"Doll Woods Scraps 6": base_id + 1278, # ItemPickup167_35 DeadWoodsEnvironment (X=56243.176 Y=-49097.793 Z=10869.889)
"Doll Woods Scraps 7": base_id + 1279, # ItemPickup79_5 DeadWoodsDolls (X=59481.672 Y=-45288.137 Z=10897.672)
"Doll Woods Scraps 8": base_id + 1280, # ItemPickup170_44 DeadWoodsEnvironment (X=63807.668 Y=-44674.734 Z=10337.434)
"Doll Woods Scraps 9": base_id + 1281, # ItemPickup171_47 DeadWoodsEnvironment (X=68406.664 Y=-45721.813 Z=10021.356)
"Doll Woods Scraps 10": base_id + 1282 # ItemPickup169_41 DeadWoodsEnvironment (X=62898.469 Y=-47565.703 Z=10744.431)
}
loc_lost_stairs = {
"Lost Stairs Scraps 1": base_id + 1283, # ItemPickup29_2 Secret1_Stairs2 (X=47087.617 Y=-53476.547 Z=9103.093)
"Lost Stairs Scraps 2": base_id + 1284 # ItemPickup30_5 Secret1_Stairs2 (X=47162.238 Y=-55318.094 Z=9127.096)
}
loc_east_house = {
"East House Scraps 1": base_id + 1285, # ItemPickup409_5 Secret7_CliffHouseEnvironment (X=97507.664 Y=-53201.270 Z=9174.678)
"East House Scraps 2": base_id + 1286, # ItemPickup408_2 Secret7_CliffHouseEnvironment (X=98511.242 Y=-53899.414 Z=9016.314)
"East House Scraps 3": base_id + 1287, # ItemPickup410_8 Secret7_CliffHouseEnvironment (X=100688.102 Y=-54197.578 Z=8919.432)
"East House Scraps 4": base_id + 1288, # ItemPickup411_11 Secret7_CliffHouseEnvironment (X=103149.773 Y=-54659.980 Z=9002.535)
"East House Scraps 5": base_id + 1289, # ItemPickup416_26 Secret7_CliffHouseEnvironment (X=107458.172 Y=-55683.793 Z=9429.004)
"East House Scraps 6": base_id + 1290, # ItemPickup25_2 Secret7_CliffHouseEnvironment (X=109034.164 Y=-54360.703 Z=9495.910)
"East House Scraps 7": base_id + 1291, # ItemPickup413_17 Secret7_CliffHouseEnvironment (X=109245.148 Y=-55045.242 Z=9553.601)
"East House Scraps 8": base_id + 1292, # ItemPickup414_20 Secret7_CliffHouseEnvironment (X=112556.445 Y=-55851.754 Z=10049.954)
"East House Scraps 9": base_id + 1293, # ItemPickup415_23 Secret7_CliffHouseEnvironment (X=113131.469 Y=-56822.508 Z=10038.047)
"East House Scraps 10": base_id + 1294, # ItemPickup2599786 Secret7_CliffHouseDetails (X=112279.828 Y=-56743.781 Z=10029.549)
"East House Scraps 11": base_id + 1295, # ItemPickup253321 Secret7_CliffHouseDetails (X=112445.508 Y=-56280.320 Z=10059.164)
"East House Scraps 12": base_id + 1296, # ItemPickup2532323 Secret7_CliffHouseDetails (X=112562.211 Y=-56736.332 Z=10454.907)
"East House Scraps 13": base_id + 1297, # ItemPickup257655 Secret7_CliffHouseDetails (X=109313.320 Y=-58221.316 Z=9501.283)
"East House Scraps 14": base_id + 1298, # ItemPickup412_14 Secret7_CliffHouseEnvironment (X=104077.805 Y=-55987.301 Z=9066.847)
"East House Chest Scraps 1": base_id + 1299, # ItemPickup76246 Secret7_CliffHouseDetails (X=112317.242 Y=-55820.805 Z=10497.336)
"East House Chest Scraps 2": base_id + 1300, # ItemPickup76245 Secret7_CliffHouseDetails (X=112326.086 Y=-55808.477 Z=10485.685)
"East House Chest Scraps 3": base_id + 1301, # ItemPickup85131 Secret7_CliffHouseDetails (X=112329.031 Y=-55828.438 Z=10478.107)
"East House Chest Scraps 4": base_id + 1302, # ItemPickup56124 Secret7_CliffHouseDetails (X=112315.922 Y=-55820.102 Z=10466.683)
"East House Chest Scraps 5": base_id + 1303 # ItemPickup25123123123 Secret7_CliffHouseDetails (X=112337.922 Y=-55821.848 Z=10456.924)
}
loc_rockets_testing_ground = {
"Rockets Testing Ground Timed Dynamite": base_id + 1304, # Boomer_DynamitePickup Boomer_RangeDetails (X=76476.609 Y=-65286.738 Z=8303.742)
"Rockets Testing Ground Scraps 1": base_id + 1305, # ItemPickup105_14 Boomer_HouseDetails (X=88925.570 Y=-63375.051 Z=8563.354)
"Rockets Testing Ground Scraps 2": base_id + 1306, # ItemPickup106_17 Boomer_HouseDetails (X=84234.016 Y=-64475.551 Z=8382.108)
"Rockets Testing Ground Scraps 3": base_id + 1307, # ItemPickup114_23 Boomer_RangeDetails (X=79349.438 Y=-64225.480 Z=8384.219)
"Rockets Testing Ground Scraps 4": base_id + 1308, # ItemPickup22_0 Boomer_RangeDetails (X=79831.070 Y=-65847.766 Z=8301.337)
"Rockets Testing Ground Scraps 5": base_id + 1309, # ItemPickup109_5 Boomer_RangeDetails (X=76526.500 Y=-65394.875 Z=8223.883)
"Rockets Testing Ground Scraps 6": base_id + 1310, # ItemPickup108_2 Boomer_RangeDetails (X=76237.977 Y=-67087.414 Z=8361.979)
"Rockets Testing Ground Scraps 7": base_id + 1311, # ItemPickup115_26 Boomer_RangeDetails (X=78857.672 Y=-67802.227 Z=8257.150)
"Rockets Testing Ground Scraps 8": base_id + 1312, # ItemPickup110_8 Boomer_RangeDetails (X=74878.570 Y=-62927.297 Z=8749.549)
"Rockets Testing Ground Scraps 9": base_id + 1313, # ItemPickup111_11 Boomer_RangeDetails (X=74542.641 Y=-61301.082 Z=9493.931)
"Rockets Testing Ground Scraps 10": base_id + 1314 # ItemPickup23_0 Boomer_RangeDetails (X=77020.859 Y=-62031.320 Z=8873.663)
# "Rockets Testing Ground Scraps Glitch 1" ItemPickup107_20 (X=81308.406 Y=-63482.320 Z=8533.338) /!\ Glitched scrap
}
loc_rockets_testing_bunker = {
"Rockets Testing Bunker Scraps 1": base_id + 1315, # ItemPickup113_20 Boomer_RangeDetails (X=77552.094 Y=-61144.559 Z=8523.195)
"Rockets Testing Bunker Scraps 2": base_id + 1316, # ItemPickup112_14 Boomer_RangeDetails (X=77670.227 Y=-62029.941 Z=8570.785)
"Rockets Testing Bunker Box of Rockets": base_id + 1317 # Boomer_RocketsPickup Boomer_RangeDetails (X=77330.086 Y=-61504.324 Z=8523.195)
}
loc_workshop = {
"Workshop Scraps 1": base_id + 1318, # ItemPickup103_8 Boomer_HouseDetails (X=93550.773 Y=-61901.797 Z=8828.551)
"Workshop Scraps 2": base_id + 1319, # ItemPickup102_5 Boomer_HouseDetails (X=93508.047 Y=-64009.910 Z=8783.468)
"Workshop Scraps 3": base_id + 1320, # ItemPickup101_2 Boomer_HouseDetails (X=92011.648 Y=-65572.281 Z=8736.709)
"Workshop Scraps 4": base_id + 1321, # ItemPickup24_2 Boomer_HouseDetails (X=92311.594 Y=-63045.211 Z=8749.977)
"Workshop Scraps 5": base_id + 1322, # ItemPickup104_11 Boomer_HouseDetails (X=91392.734 Y=-63527.629 Z=8709.268)
"Workshop Scraps 6": base_id + 1323, # ItemPickup25_1 Boomer_HouseDetails (X=92986.789 Y=-63012.047 Z=9235.383)
"Workshop John Smith Mission End": base_id + 1324 # (dialog 2) -> the_boomer
}
loc_east_tower = {
"Greg Mission Start": base_id + 1325, # (dialog 6) -> north_mine_key
"East Tower Scraps 1": base_id + 1326, # ItemPickup231_17 Mine2_NPCHouseDetails (X=95448.250 Y=-67249.156 Z=8607.896)
"East Tower Scraps 2": base_id + 1327, # ItemPickup228_8 Mine2_NPCHouseDetails (X=96339.242 Y=-66374.828 Z=8650.519)
"East Tower Scraps 3": base_id + 1328, # ItemPickup229_11 Mine2_NPCHouseDetails (X=98540.711 Y=-67173.656 Z=8418.825)
"East Tower Scraps 4": base_id + 1329, # ItemPickup230_14 Mine2_NPCHouseDetails (X=97276.414 Y=-68495.008 Z=8337.229)
"East Tower Scraps 5": base_id + 1330, # ItemPickup227_5 Mine2_NPCHouseDetails (X=96470.820 Y=-66540.859 Z=8953.763)
"East Tower Scraps 6": base_id + 1331 # ItemPickup226_2 Mine2_NPCHouseDetails (X=96141.555 Y=-67013.445 Z=9399.308)
}
loc_lighthouse = {
"Lighthouse Scraps 1": base_id + 1332, # ItemPickup200_41 LighthouseTerrainMain (X=100072.813 Y=-68645.688 Z=8150.313)
"Lighthouse Scraps 2": base_id + 1333, # ItemPickup198_35 LighthouseTerrainMain (X=105340.594 Y=-70828.602 Z=8436.780)
"Lighthouse Scraps 3": base_id + 1334, # ItemPickup199_38 LighthouseTerrainMain (X=103851.688 Y=-73396.625 Z=7973.290)
"Lighthouse Scraps 4": base_id + 1335, # ItemPickup196_29 LighthouseTerrainMain (X=107040.711 Y=-74021.555 Z=8303.216)
"Lighthouse Scraps 5": base_id + 1336, # ItemPickup197_32 LighthouseTerrainMain (X=110566.859 Y=-77435.961 Z=7642.565)
"Lighthouse Scraps 6": base_id + 1337, # ItemPickup32_2 LighthouseShed_Main (X=111451.352 Y=-77351.117 Z=7633.413)
"Lighthouse Scraps 7": base_id + 1338, # ItemPickup35_11 Lighthouse_Main (X=113078.500 Y=-78618.281 Z=7180.793)
"Lighthouse Scraps 8": base_id + 1339, # ItemPickup192_17 LighthouseTerrainMain (X=113396.305 Y=-80315.383 Z=7184.260)
"Lighthouse Scraps 9": base_id + 1340, # ItemPickup193_20 LighthouseTerrainMain (X=114057.484 Y=-81517.836 Z=7245.034)
"Lighthouse Scraps 10": base_id + 1341, # ItemPickup194_23 LighthouseTerrainMain (X=110915.156 Y=-78376.609 Z=7676.131)
"Lighthouse Scraps 11": base_id + 1342, # ItemPickup195_26 LighthouseTerrainMain (X=109341.703 Y=-79014.469 Z=8075.679)
"Lighthouse Scraps 12": base_id + 1343, # ItemPickup33_5 Lighthouse_Details (X=107006.578 Y=-81377.711 Z=8821.629)
"Lighthouse Scraps 13": base_id + 1344, # ItemPickup191_14 LighthouseTerrainMain (X=109240.195 Y=-82951.375 Z=8194.619)
"Lighthouse Scraps 14": base_id + 1345, # ItemPickup190_11 LighthouseTerrainMain (X=106295.719 Y=-84190.578 Z=8581.896)
"Lighthouse Scraps 15": base_id + 1346, # ItemPickup189_8 LighthouseTerrainMain (X=104233.883 Y=-84663.328 Z=7806.311)
"Lighthouse Scraps 16": base_id + 1347, # ItemPickup188_5 LighthouseTerrainMain (X=103209.227 Y=-81564.047 Z=8140.578)
"Lighthouse Scraps 17": base_id + 1348, # ItemPickup187_2 LighthouseTerrainMain (X=104795.555 Y=-81344.758 Z=8775.158)
"Lighthouse Scraps 18": base_id + 1349, # ItemPickup34_8 Lighthouse_Main (X=100843.914 Y=-78038.539 Z=7197.542)
"Lighthouse Breaker 1": base_id + 1350, # ItemPickup13_2 LighthouseShed_Main (X=110781.164 Y=-77296.813 Z=7757.248) \!/ Existing match 6
"Lighthouse Breaker 2": base_id + 1351, # ItemPickup14 LighthouseShed_Main (X=110899.227 Y=-77239.031 Z=7757.134) \!/ Existing match 3
"Lighthouse Breaker 3": base_id + 1352, # ItemPickup16 LighthouseShed_Main (X=110948.547 Y=-77253.336 Z=7757.134) \!/ Existing match 2
"Lighthouse Breaker 4": base_id + 1353, # ItemPickup17 LighthouseShed_Main (X=111001.078 Y=-77205.047 Z=7757.134) \!/ Existing match 1
"Lighthouse Claire Mission End": base_id + 1354 # (dialog 2) -> 30_scraps_reward
}
loc_north_mine_outside = {
"North Mine Outside Scraps 1": base_id + 1355, # ItemPickup241_31 Mine2_OutsideDetails (X=-52376.746 Y=-101857.492 Z=10542.841)
"North Mine Outside Scraps 2": base_id + 1356, # ItemPickup242_34 Mine2_OutsideDetails (X=-53786.742 Y=-102067.789 Z=10858.948)
"North Mine Outside Scraps 3": base_id + 1357, # ItemPickup239_25 Mine2_OutsideDetails (X=-57502.777 Y=-105475.336 Z=10609.405)
"North Mine Outside Scraps 4": base_id + 1358, # ItemPickup16_2 Mine2_OutsideDetails (X=-58102.102 Y=-104007.906 Z=11146.535)
"North Mine Outside Scraps 5": base_id + 1359, # ItemPickup238_20 Mine2_OutsideDetails (X=-59474.840 Y=-105053.734 Z=11213.524)
"North Mine Outside Scraps 6": base_id + 1360, # ItemPickup240_28 Mine2_OutsideDetails (X=-55011.750 Y=-104936.359 Z=9935.366)
"North Mine Outside Scraps 7": base_id + 1361, # ItemPickup236_14 Mine2_OutsideDetails (X=-55594.863 Y=-107667.594 Z=9596.611)
"North Mine Outside Scraps 8": base_id + 1362, # ItemPickup15_1 Mine2_Interior2 (X=-56632.578 Y=-109503.406 Z=9280.788)
"North Mine Outside Scraps 9": base_id + 1363, # ItemPickup234_8 Mine2_OutsideDetails (X=-54645.418 Y=-110747.602 Z=9553.452)
"North Mine Outside Scraps 10": base_id + 1364, # ItemPickup232_2 Mine2_OutsideDetails (X=-51561.340 Y=-113574.813 Z=9414.959)
"North Mine Outside Scraps 11": base_id + 1365, # ItemPickup233_5 Mine2_OutsideDetails (X=-54072.105 Y=-112672.031 Z=10077.665)
"North Mine Outside Scraps 12": base_id + 1366, # ItemPickup237_17 Mine2_OutsideDetails (X=-58042.758 Y=-108748.656 Z=9693.470)
"North Mine Outside Scraps 13": base_id + 1367, # ItemPickup235_11 Mine2_OutsideDetails (X=-55717.227 Y=-110610.414 Z=9487.879)
"North Mine Outside Scraps 14": base_id + 1368 # ItemPickup13_2 Mine2_Interior2 (X=-52235.836 Y=-114501.117 Z=9462.438) KO
}
loc_north_mine_inside = {
"North Mine Inside Scraps 1": base_id + 1369, # ItemPickup17_2 Mine2_Interior1 (X=-58433.055 Y=-104081.570 Z=9378.083) KO
"North Mine Inside Scraps 2": base_id + 1370, # ItemPickup246_2 Mine2_Interior1 (X=-58987.199 Y=-103262.906 Z=9186.494)
"North Mine Inside Scraps 3": base_id + 1371, # ItemPickup247_5 Mine2_Interior1 (X=-58812.801 Y=-99259.570 Z=8847.714)
"North Mine Inside Scraps 4": base_id + 1372, # ItemPickup248_8 Mine2_Interior1 (X=-56634.379 Y=-99529.563 Z=8851.877)
"North Mine Inside Scraps 5": base_id + 1373, # ItemPickup22_4 Mine2_Interior1 (X=-55604.477 Y=-98342.906 Z=8842.766)
"North Mine Inside Scraps 6": base_id + 1374, # ItemPickup250_14 Mine2_Interior1 (X=-54824.535 Y=-98526.492 Z=8852.156)
"North Mine Inside Scraps 7": base_id + 1375, # ItemPickup21_14 Mine2_Interior1 (X=-54887.254 Y=-99047.141 Z=8849.855)
"North Mine Inside Scraps 8": base_id + 1376, # ItemPickup20_2 Mine2_Interior1 (X=-55610.020 Y=-101877.961 Z=9081.042)
"North Mine Inside Scraps 9": base_id + 1377, # ItemPickup19_2 Mine2_Interior1 (X=-56519.340 Y=-101375.008 Z=9001.270)
"North Mine Inside Scraps 10": base_id + 1378, # ItemPickup249_11 Mine2_Interior1 (X=-53329.922 Y=-99469.773 Z=8848.643)
"North Mine Inside Scraps 11": base_id + 1379, # ItemPickup251_17 Mine2_Interior1 (X=-52814.828 Y=-96286.969 Z=8851.372)
"North Mine Inside Scraps 12": base_id + 1380, # ItemPickup24_1 Mine2_Interior1 (X=-52605.957 Y=-96535.156 Z=8940.480)
"North Mine Inside Scraps 13": base_id + 1381, # ItemPickup23_20 Mine2_Interior1 (X=-53237.699 Y=-96609.461 Z=8846.201)
"North Mine Inside Scraps 14": base_id + 1382, # ItemPickup18_5 Mine2_Interior1 (X=-58543.488 Y=-95879.695 Z=8981.646)
"North Mine Inside Blue Egg": base_id + 1383, # Mine2_Egg Mine2_Interior2 (X=-53592.195 Y=-99177.500 Z=8975.387)
"North Mine Inside Blue Paint Can": base_id + 1384 # PaintCan_2 Mine2_Interior2 (X=-56133.391 Y=-101870.047 Z=9004.720) \!/ Existing match 5
# "North Mine Inside Secret Gear" ItemPickup26_2 (X=-55546.859 Y=-98209.852 Z=8429.085) /!\ Inaccessible gear
}
loc_wood_bridge = {
"Wood Bridge Scraps 1": base_id + 1385, # ItemPickup127_35 Bridge_Details (X=-66790.141 Y=-110340.367 Z=10454.417)
"Wood Bridge Scraps 2": base_id + 1386, # ItemPickup18613654 Bridge_Details (X=-68364.586 Y=-111691.625 Z=10444.172)
"Wood Bridge Scraps 3": base_id + 1387, # ItemPickup1311221 Bridge_StructureDetails (X=-69013.555 Y=-112353.977 Z=10399.942)
"Wood Bridge Scraps 4": base_id + 1388, # ItemPickup93564 Bridge_StructureDetails (X=-70398.797 Y=-112916.945 Z=10372.192)
"Wood Bridge Scraps 5": base_id + 1389, # ItemPickup161323 Bridge_Details (X=-71336.172 Y=-106966.672 Z=8430.104)
"Wood Bridge Scraps 6": base_id + 1390, # ItemPickup128_38 Bridge_Details (X=-72776.086 Y=-107813.102 Z=8305.589)
"Wood Bridge Scraps 7": base_id + 1391, # ItemPickup17975 Bridge_Details (X=-75224.648 Y=-108280.867 Z=7929.499)
"Wood Bridge Scraps 8": base_id + 1392, # ItemPickup126_32 Bridge_Details (X=-68112.172 Y=-105119.656 Z=9458.937)
"Wood Bridge Scraps 9": base_id + 1393, # ItemPickup118_8 Bridge_Details (X=-71847.625 Y=-103623.203 Z=10707.521)
"Wood Bridge Scraps 10": base_id + 1394, # ItemPickup116_2 Bridge_Details (X=-71812.219 Y=-107256.094 Z=10780.134)
"Wood Bridge Scraps 11": base_id + 1395, # ItemPickup134321 Bridge_Details (X=-72011.570 Y=-109054.547 Z=10866.852)
"Wood Bridge Scraps 12": base_id + 1396, # ItemPickup117_5 Bridge_Details (X=-72862.430 Y=-106144.852 Z=10329.061)
"Wood Bridge Scraps 13": base_id + 1397 # ItemPickup1494567 Bridge_Details (X=-71843.117 Y=-107174.133 Z=10367.116)
}
loc_museum = {
"Museum Scraps 1": base_id + 1398, # ItemPickup119_11 Bridge_Details (X=-69687.773 Y=-100002.406 Z=10806.339)
"Museum Scraps 2": base_id + 1399, # ItemPickup120_14 Bridge_Details (X=-68035.195 Y=-99480.672 Z=11049.731)
"Museum Scraps 3": base_id + 1400, # ItemPickup125_29 Bridge_Details (X=-66912.641 Y=-99976.750 Z=11064.357)
"Museum Scraps 4": base_id + 1401, # ItemPickup13532 Bridge_HouseDetails (X=-64901.117 Y=-99624.953 Z=11176.359)
"Museum Scraps 5": base_id + 1402, # ItemPickup121_17 Bridge_Details (X=-66082.328 Y=-98105.555 Z=11089.308)
"Museum Scraps 6": base_id + 1403, # ItemPickup21765 Bridge_HouseDetails (X=-67402.742 Y=-97735.133 Z=11153.927)
"Museum Scraps 7": base_id + 1404, # ItemPickup124_26 Bridge_Details (X=-66716.031 Y=-98282.508 Z=11195.624)
"Museum Scraps 8": base_id + 1405, # ItemPickup122_20 Bridge_Details (X=-66582.703 Y=-99092.461 Z=11630.082)
"Museum Scraps 9": base_id + 1406, # ItemPickup123_23 Bridge_Details (X=-66798.164 Y=-99550.266 Z=11547.321)
"Museum Scraps 10": base_id + 1407, # ItemPickup183423 Bridge_HouseDetails (X=-66850.336 Y=-99682.844 Z=11543.618)
"Museum Scraps 11": base_id + 1408, # ItemPickup244_40 Mine2_OutsideDetails (X=-60156.828 Y=-98516.953 Z=11811.422)
"Museum Scraps 12": base_id + 1409, # ItemPickup245_43 Mine2_OutsideDetails (X=-61195.203 Y=-98262.422 Z=11779.118)
"Museum Paul Mission Start": base_id + 1410, # (dialog 6) -> remote_explosive (x8)
"Museum Paul Mission End": base_id + 1411 # (dialog 3) -> temple_key
}
loc_barbed_shelter = {
"Barbed Shelter Gertrude Mission Start": base_id + 1412, # (dialog 4) -> broken_bob
"Barbed Shelter Scraps 1": base_id + 1413, # ItemPickup100_8 Bob_NPCHouseMain (X=-72525.500 Y=-89333.734 Z=9820.663)
"Barbed Shelter Scraps 2": base_id + 1414, # ItemPickup98_2 Bob_NPCHouseMain (X=-74870.758 Y=-88576.641 Z=9836.814)
"Barbed Shelter Scraps 3": base_id + 1415, # ItemPickup22_1 Bob_NPCHouseDetails (X=-76193.914 Y=-88038.836 Z=9818.776)
"Barbed Shelter Scraps 4": base_id + 1416, # ItemPickup99_5 Bob_NPCHouseMain (X=-74494.859 Y=-87609.969 Z=9837.866)
"Barbed Shelter Scraps 5": base_id + 1417 # ItemPickup23_3 Bob_NPCHouseDetails (X=-74826.930 Y=-88402.039 Z=9929.854)
}
loc_west_beach = {
"West Beach Chest Scraps 1": base_id + 1418, # ItemPickup9122346 Secret11_Details (X=-85934.047 Y=-89532.547 Z=7383.054)
"West Beach Chest Scraps 2": base_id + 1419, # ItemPickup84 Secret11_Details (X=-85933.977 Y=-89532.977 Z=7369.364)
"West Beach Chest Scraps 3": base_id + 1420, # ItemPickup9099877 Secret11_Details (X=-85951.000 Y=-89527.023 Z=7367.054)
"West Beach Chest Scraps 4": base_id + 1421, # ItemPickup89086423 Secret11_Details (X=-85932.461 Y=-89533.148 Z=7354.001)
"West Beach Chest Scraps 5": base_id + 1422, # ItemPickup83 Secret11_Details (X=-85950.930 Y=-89527.453 Z=7353.365)
"West Beach Chest Scraps 6": base_id + 1423, # ItemPickup82_2 Secret11_Details (X=-85932.391 Y=-89533.578 Z=7340.312)
"West Beach Scraps 1": base_id + 1424, # ItemPickup87_13 Secret11_Details (X=-84489.945 Y=-91235.977 Z=8360.803)
"West Beach Scraps 2": base_id + 1425, # ItemPickup349_2 Secret11_Details1 (X=-84386.320 Y=-90391.789 Z=8376.434)
"West Beach Scraps 3": base_id + 1426, # ItemPickup86_10 Secret11_Details (X=-84714.773 Y=-89876.992 Z=7707.064)
"West Beach Scraps 4": base_id + 1427, # ItemPickup350_5 Secret11_Details1 (X=-85478.672 Y=-90648.414 Z=7708.377)
"West Beach Scraps 5": base_id + 1428, # ItemPickup85_7 Secret11_Details (X=-86276.633 Y=-90674.289 Z=7532.364)
"West Beach Scraps 6": base_id + 1429, # ItemPickup88_16 Secret11_Details (X=-84363.055 Y=-87497.938 Z=7582.647)
"West Beach Scraps 7": base_id + 1430, # ItemPickup351_8 Secret11_Details1 (X=-86556.266 Y=-89748.484 Z=7297.274)
"West Beach Scraps 8": base_id + 1431 # ItemPickup352_11 Secret11_Details1 (X=-83210.836 Y=-92551.953 Z=8460.213)
}
loc_church = {
"Church Black Paint Can": base_id + 1432, # PaintCan_4 Secret5_ChurchDetails (X=-67628.172 Y=-83801.375 Z=9865.983)
"Church Scraps 1": base_id + 1433, # ItemPickup391_11 Secret5_ChurchDetails (X=-64009.039 Y=-84252.156 Z=10258.335)
"Church Scraps 2": base_id + 1434, # ItemPickup389_5 Secret5_ChurchDetails (X=-66870.719 Y=-85202.180 Z=9843.936)
"Church Scraps 3": base_id + 1435, # ItemPickup388_2 Secret5_ChurchDetails (X=-68588.352 Y=-84041.867 Z=9790.541)
"Church Scraps 4": base_id + 1436, # ItemPickup396_26 Secret5_ChurchDetails (X=-67595.797 Y=-82120.094 Z=9818.303)
"Church Scraps 5": base_id + 1437, # ItemPickup390_8 Secret5_ChurchDetails (X=-67291.000 Y=-83324.836 Z=9774.942)
"Church Scraps 6": base_id + 1438, # ItemPickup392_14 Secret5_ChurchDetails (X=-65849.070 Y=-80676.477 Z=9895.943)
"Church Scraps 7": base_id + 1439, # ItemPickup395_23 Secret5_ChurchDetails (X=-65170.266 Y=-79155.227 Z=9904.275)
"Church Scraps 8": base_id + 1440, # ItemPickup24_4 Secret5_GraveyardMain (X=-64837.563 Y=-80885.305 Z=9906.755)
"Church Scraps 9": base_id + 1441, # ItemPickup22_3 Secret5_ChurchDetails (X=-68248.359 Y=-83578.008 Z=9807.300)
"Church Scraps 10": base_id + 1442, # ItemPickup23_5 Secret5_ChurchDetails (X=-67086.102 Y=-84605.086 Z=9805.521)
"Church Scraps 11": base_id + 1443, # ItemPickup393_17 Secret5_ChurchDetails (X=-67901.930 Y=-83477.625 Z=9812.613)
"Church Scraps 12": base_id + 1444 # ItemPickup394_20 Secret5_ChurchDetails (X=-65834.344 Y=-84192.102 Z=9987.823)
}
loc_west_cottage = {
"West Cottage Gale Mission Start": base_id + 1445, # (dialog 10) -> mountain_ruin_key
"West Cottage Scraps 1": base_id + 1446, # ItemPickup15_3 Mine3_NPCHouse (X=-74407.695 Y=-81781.250 Z=10120.775)
"West Cottage Scraps 2": base_id + 1447, # ItemPickup283_5 Mine3_NPCHouseDetails (X=-73784.695 Y=-79414.359 Z=10128.285)
"West Cottage Scraps 3": base_id + 1448, # ItemPickup13_1 Mine3_NPCHouse (X=-73992.391 Y=-78600.094 Z=10162.495)
"West Cottage Scraps 4": base_id + 1449, # ItemPickup284_8 Mine3_NPCHouseDetails (X=-71623.000 Y=-75998.023 Z=10275.477)
"West Cottage Scraps 5": base_id + 1450 # ItemPickup282_2 Mine3_NPCHouseDetails (X=-72626.453 Y=-79391.070 Z=10211.037)
}
loc_caravan = {
"Caravan Scraps 1": base_id + 1451, # ItemPickup348_11 Secret10_PAth (X=-52638.109 Y=-43924.395 Z=10579.809)
"Caravan Scraps 2": base_id + 1452, # ItemPickup347_8 Secret10_PAth (X=-50203.695 Y=-42865.672 Z=10778.871)
"Caravan Scraps 3": base_id + 1453, # ItemPickup346_5 Secret10_PAth (X=-48467.738 Y=-42018.488 Z=10818.758)
"Caravan Scraps 4": base_id + 1454, # ItemPickup77_14 Secret10_Details (X=-46325.219 Y=-41707.512 Z=11003.229)
"Caravan Scraps 5": base_id + 1455, # ItemPickup345_2 Secret10_PAth (X=-44557.043 Y=-40652.930 Z=11076.221)
"Caravan Scraps 6": base_id + 1456, # ItemPickup76_11 Secret10_Details (X=-43380.664 Y=-38207.152 Z=11165.370)
"Caravan Scraps 7": base_id + 1457, # ItemPickup73_2 Secret10_Details (X=-42919.410 Y=-38797.738 Z=11265.633)
"Caravan Scraps 8": base_id + 1458, # ItemPickup74_5 Secret10_Details (X=-42787.523 Y=-38601.820 Z=11254.003)
"Caravan Scraps 9": base_id + 1459, # ItemPickup75_8 Secret10_Details (X=-42711.363 Y=-39141.523 Z=11173.905)
"Caravan Chest Scraps 1": base_id + 1460, # ItemPickup71561 Secret10_Details (X=-42910.668 Y=-38297.309 Z=11233.402)
"Caravan Chest Scraps 2": base_id + 1461, # ItemPickup078654 Secret10_Details (X=-42904.344 Y=-38307.332 Z=11219.678)
"Caravan Chest Scraps 3": base_id + 1462, # ItemPickup02345 Secret10_Details (X=-42904.965 Y=-38280.383 Z=11208.191)
"Caravan Chest Scraps 4": base_id + 1463, # ItemPickup-6546483648 Secret10_Details (X=-42911.680 Y=-38315.254 Z=11204.225)
"Caravan Chest Scraps 5": base_id + 1464 # ItemPickup176752623547 Secret10_Details (X=-42905.090 Y=-38279.828 Z=11192.738)
}
loc_trailer_cabin = {
"Trailer Cabin Scraps 1": base_id + 1465, # ItemPickup493_17 TrailerCabin_Details (X=-50702.449 Y=-38850.020 Z=10810.316)
"Trailer Cabin Scraps 2": base_id + 1466, # ItemPickup489_5 TrailerCabin_Details (X=-51365.684 Y=-38502.379 Z=10875.761)
"Trailer Cabin Scraps 3": base_id + 1467, # ItemPickup491_11 TrailerCabin_Details (X=-52397.570 Y=-37530.145 Z=10873.624)
"Trailer Cabin Scraps 4": base_id + 1468, # ItemPickup490_8 TrailerCabin_Details (X=-50625.746 Y=-37916.758 Z=10886.909)
"Trailer Cabin Scraps 5": base_id + 1469, # ItemPickup488_2 TrailerCabin_Details (X=-51201.051 Y=-37467.137 Z=10910.795)
"Trailer Cabin Scraps 6": base_id + 1470 # ItemPickup492_14 TrailerCabin_Details (X=-51891.320 Y=-40549.492 Z=10675.211)
}
loc_towers = {
"Towers Scraps 1": base_id + 1471, # ItemPickup486_32 Towers_Environment_Details (X=-24434.766 Y=-25708.373 Z=11200.865)
"Towers Scraps 2": base_id + 1472, # ItemPickup483_23 Towers_Environment_Details (X=-20970.262 Y=-25678.754 Z=11731.241)
"Towers Scraps 3": base_id + 1473, # ItemPickup481_17 Towers_Environment_Details (X=-19812.230 Y=-27768.301 Z=12051.623)
"Towers Scraps 4": base_id + 1474, # ItemPickup484_26 Towers_Environment_Details (X=-19940.912 Y=-25411.576 Z=12035.366)
"Towers Scraps 5": base_id + 1475, # ItemPickup41_17 Towers_Environment (X=-18596.791 Y=-25100.035 Z=12290.350)
"Towers Scraps 6": base_id + 1476, # ItemPickup482_20 Towers_Environment_Details (X=-23302.396 Y=-23270.324 Z=12036.164)
"Towers Scraps 7": base_id + 1477, # ItemPickup487_35 Towers_Environment_Details (X=-22955.039 Y=-27576.859 Z=11211.258)
"Towers Scraps 8": base_id + 1478, # ItemPickup478_8 Towers_Environment_Details (X=-21485.520 Y=-29634.893 Z=11787.103)
"Towers Scraps 9": base_id + 1479, # ItemPickup477_5 Towers_Environment_Details (X=-23667.957 Y=-29825.240 Z=12035.269)
"Towers Scraps 10": base_id + 1480, # ItemPickup39_11 Towers_Environment (X=-25361.008 Y=-29794.301 Z=12026.073)
"Towers Scraps 11": base_id + 1481, # ItemPickup476_2 Towers_Environment_Details (X=-26549.584 Y=-32768.133 Z=12289.732)
"Towers Scraps 12": base_id + 1482, # ItemPickup38_8 Towers_Environment (X=-27240.127 Y=-27404.748 Z=12027.208)
"Towers Scraps 13": base_id + 1483, # ItemPickup40_14 Towers_Environment (X=-23231.639 Y=-27799.158 Z=11829.792)
"Towers Scraps 14": base_id + 1484, # ItemPickup485_29 Towers_Environment_Details (X=-22949.568 Y=-26146.012 Z=11702.730)
"Towers Scraps 15": base_id + 1485, # ItemPickup479_11 Towers_Environment_Details (X=-19726.715 Y=-32464.682 Z=12118.678)
"Towers Scraps 16": base_id + 1486, # ItemPickup1366543 Tower_BuildingsExteriorDetails (X=-23495.104 Y=-27644.689 Z=11872.844)
"Towers Scraps 17": base_id + 1487, # ItemPickup139978 Tower_BuildingsExteriorDetails (X=-23512.971 Y=-27493.051 Z=12218.543)
"Towers Scraps 18": base_id + 1488, # ItemPickup42_20 Towers_Environment (X=-22731.439 Y=-26331.393 Z=12102.758)
"Towers Scraps 19": base_id + 1489, # ItemPickup131123 Tower_BuildingsExteriorDetails (X=-22599.641 Y=-26454.590 Z=11752.040)
"Towers Scraps 20": base_id + 1490, # ItemPickup196987 Tower_BuildingsExteriorDetails (X=-22589.721 Y=-26397.414 Z=12571.282)
"Towers Scraps 21": base_id + 1491, # ItemPickup138787 Tower_BuildingsExteriorDetails (X=-22163.268 Y=-26775.938 Z=13107.048)
"Towers Scraps 22": base_id + 1492, # ItemPickup43_23 Towers_Environment (X=-21996.184 Y=-26754.393 Z=13105.997)
"Towers Scraps 23": base_id + 1493, # ItemPickup837454 Tower_BuildingsExteriorDetails (X=-24068.221 Y=-27874.443 Z=12819.666)
"Towers Scraps 24": base_id + 1494, # ItemPickup44_29 Towers_Environment (X=-23525.330 Y=-27770.035 Z=12612.871)
"Towers Scraps 25": base_id + 1495, # ItemPickup18932 Tower_BuildingsExteriorDetails (X=-23472.215 Y=-27617.404 Z=13213.256)
"Towers Scraps 26": base_id + 1496, # ItemPickup188348 Tower_BuildingsExteriorDetails (X=-23981.588 Y=-27984.385 Z=13219.854)
"Towers Scraps 27": base_id + 1497, # ItemPickup480_14 Towers_Environment_Details (X=-18696.230 Y=-34511.277 Z=12704.238)
"Towers Lime Paint Can": base_id + 1498, # PaintCan_2 Towers_BuildingsDetails (X=-22288.555 Y=-26022.281 Z=11835.892) \!/ Existing match 5
"Towers Employment Contracts": base_id + 1499, # Towers_Files_Pickup Tower_BuildingsExteriorDetails (X=-24081.414 Y=-27637.459 Z=13679.163)
"Towers Ronny Mission End": base_id + 1500 # (dialog 3) -> 1_scraps_reward
}
loc_north_beach = {
"North Beach Chest Scraps 1": base_id + 1501, # ItemPickup9254 Secret3_CampDetails (X=-74444.648 Y=-130627.672 Z=8793.757)
"North Beach Chest Scraps 2": base_id + 1502, # ItemPickup14523 Secret3_CampDetails (X=-74426.539 Y=-130626.547 Z=8781.948)
"North Beach Chest Scraps 3": base_id + 1503, # ItemPickup084537 Secret3_CampDetails (X=-74448.852 Y=-130632.367 Z=8772.555)
"North Beach Chest Scraps 4": base_id + 1504, # ItemPickup17754234 Secret3_CampDetails (X=-74425.523 Y=-130622.875 Z=8755.526)
"North Beach Scraps 1": base_id + 1505, # ItemPickup376_5 Secret3_CampDetails (X=-75003.078 Y=-131084.016 Z=8729.731)
"North Beach Scraps 2": base_id + 1506, # ItemPickup378_11 Secret3_CampDetails (X=-75477.758 Y=-129413.750 Z=9082.617)
"North Beach Scraps 3": base_id + 1507, # ItemPickup377_8 Secret3_CampDetails (X=-75608.453 Y=-130483.430 Z=8909.659)
"North Beach Scraps 4": base_id + 1508, # ItemPickup375_2 Secret3_CampDetails (X=-74143.469 Y=-131117.953 Z=8752.130)
"North Beach Scraps 5": base_id + 1509, # ItemPickup387_6 Secret4_BeachHouseMain (X=-80847.078 Y=-135628.719 Z=7915.444)
"North Beach Scraps 6": base_id + 1510, # ItemPickup386_3 Secret4_BeachHouseMain (X=-84044.789 Y=-137591.000 Z=7359.172)
"North Beach Scraps 7": base_id + 1511, # ItemPickup379_2 Secret4_BeachHouseDetails (X=-83992.836 Y=-132933.531 Z=7941.225)
"North Beach Scraps 8": base_id + 1512, # ItemPickup380_5 Secret4_BeachHouseDetails (X=-87355.734 Y=-134216.438 Z=7237.310)
"North Beach Scraps 9": base_id + 1513, # ItemPickup384_17 Secret4_BeachHouseDetails (X=-89213.844 Y=-134625.922 Z=7185.133)
"North Beach Scraps 10": base_id + 1514, # ItemPickup25_0 Secret4_BeachHouseDetails (X=-88423.430 Y=-135922.969 Z=7290.801)
"North Beach Scraps 11": base_id + 1515, # ItemPickup381_8 Secret4_BeachHouseDetails (X=-87668.977 Y=-136850.359 Z=7275.346)
"North Beach Scraps 12": base_id + 1516, # ItemPickup383_14 Secret4_BeachHouseDetails (X=-90241.328 Y=-136381.000 Z=7199.622)
"North Beach Scraps 13": base_id + 1517, # ItemPickup27_8 Secret4_BeachHouseDetails (X=-91728.680 Y=-135288.203 Z=7319.699)
"North Beach Scraps 14": base_id + 1518, # ItemPickup26_2 Secret4_BeachHouseDetails (X=-88789.039 Y=-135461.719 Z=7305.800)
"North Beach Scraps 15": base_id + 1519, # ItemPickup382_11 Secret4_BeachHouseDetails (X=-88572.078 Y=-135970.734 Z=7302.674)
"North Beach Teal Paint Can": base_id + 1520 # PaintCan_2 Secret4_BeachHouseDetails (X=-91706.805 Y=-134988.453 Z=7347.827) \!/ Existing match 5
# "North Beach Scraps Glitch 1" ItemPickup385_20 (X=-77651.938 Y=-139721.453 Z=7261.729) /!\ Glitched scrap
}
loc_mine_shaft = {
"Mine Shaft Chest Scraps 1": base_id + 1521, # ItemPickup0934569 Secret13_Details (X=-17360.789 Y=-74064.367 Z=8038.313)
"Mine Shaft Chest Scraps 2": base_id + 1522, # ItemPickup17234455622 Secret13_Details (X=-17360.814 Y=-74064.367 Z=8024.187)
"Mine Shaft Chest Scraps 3": base_id + 1523, # ItemPickup856743 Secret13_Details (X=-17361.059 Y=-74049.234 Z=8015.940)
"Mine Shaft Chest Scraps 4": base_id + 1524, # ItemPickup16456456 Secret13_Details (X=-17336.465 Y=-74087.063 Z=8009.707)
"Mine Shaft Chest Scraps 5": base_id + 1525, # ItemPickup234743 Secret13_Details (X=-17349.941 Y=-74075.484 Z=8004.179)
"Mine Shaft Chest Scraps 6": base_id + 1526, # ItemPickup1434563456 Secret13_Details (X=-17361.084 Y=-74049.234 Z=8001.814)
"Mine Shaft Chest Scraps 7": base_id + 1527, # ItemPickup13634563456 Secret13_Details (X=-17349.967 Y=-74075.484 Z=7990.054)
"Mine Shaft Scraps 1": base_id + 1528, # ItemPickup369_23 Secret13_Details (X=-16985.645 Y=-70377.273 Z=10837.127)
"Mine Shaft Scraps 2": base_id + 1529, # ItemPickup368_20 Secret13_Details (X=-18292.045 Y=-71003.563 Z=10975.308)
"Mine Shaft Scraps 3": base_id + 1530, # ItemPickup367_17 Secret13_Details (X=-16150.797 Y=-72075.289 Z=10609.141)
"Mine Shaft Scraps 4": base_id + 1531, # ItemPickup24456463 Secret13_EntranceDetails1 (X=-18404.480 Y=-71894.367 Z=10968.230)
"Mine Shaft Scraps 5": base_id + 1532, # ItemPickup2565644 Secret13_Details (X=-17777.268 Y=-71467.172 Z=10441.745)
"Mine Shaft Scraps 6": base_id + 1533, # ItemPickup366_14 Secret13_Details (X=-17630.971 Y=-72800.641 Z=8842.322)
"Mine Shaft Scraps 7": base_id + 1534, # ItemPickup18_8 Secret13_Details (X=-17979.719 Y=-73413.563 Z=8118.260)
"Mine Shaft Scraps 8": base_id + 1535, # ItemPickup21_11 Secret13_Details (X=-16554.467 Y=-74139.781 Z=7892.738)
"Mine Shaft Scraps 9": base_id + 1536, # ItemPickup365_11 Secret13_Details (X=-16343.033 Y=-74617.555 Z=7298.177)
"Mine Shaft Scraps 10": base_id + 1537, # ItemPickup22123 Secret13_Details (X=-12390.332 Y=-77620.320 Z=7324.504)
"Mine Shaft Scraps 11": base_id + 1538, # ItemPickup364_8 Secret13_Details (X=-10969.702 Y=-77961.477 Z=7297.171)
"Mine Shaft Scraps 12": base_id + 1539, # ItemPickup363_5 Secret13_Details (X=-9888.596 Y=-78208.930 Z=7269.473)
"Mine Shaft Scraps 13": base_id + 1540, # ItemPickup362_2 Secret13_Details (X=-8865.696 Y=-79063.977 Z=7247.677)
"Mine Shaft Scraps 14": base_id + 1541 # ItemPickup238976 Secret13_ExitDetails (X=-8143.984 Y=-79764.617 Z=7222.096)
}
loc_mob_camp = {
"Mob Camp Key": base_id + 1542, # ItemPickup29_0 Bob_CampDetails2 (X=-29114.480 Y=-53608.520 Z=12839.528)
"Mob Camp Scraps 1": base_id + 1543, # ItemPickup25_5 Bob_CampDetails2 (X=-27373.525 Y=-53008.668 Z=12706.465)
"Mob Camp Scraps 2": base_id + 1544, # ItemPickup26_1 Bob_CampDetails2 (X=-28786.941 Y=-53009.762 Z=12760.727)
"Mob Camp Scraps 3": base_id + 1545, # ItemPickup13_14 Mine3_Mountain (X=-29650.207 Y=-53328.070 Z=12724.598)
"Mob Camp Scraps 4": base_id + 1546, # ItemPickup90_5 Bob_CampDetails2 (X=-31515.057 Y=-55533.324 Z=12344.274)
"Mob Camp Scraps 5": base_id + 1547, # ItemPickup49513294 Mine3_Mountain (X=-31847.229 Y=-55152.352 Z=11574.255)
"Mob Camp Scraps 6": base_id + 1548, # ItemPickup92_14 Bob_CampDetails2 (X=-31323.533 Y=-55432.871 Z=11245.139)
"Mob Camp Scraps 7": base_id + 1549, # ItemPickup91_8 Bob_CampDetails2 (X=-31583.443 Y=-55475.805 Z=11234.112)
"Mob Camp Scraps 8": base_id + 1550, # ItemPickup95_23 Bob_CampDetails2 (X=-32925.406 Y=-57157.605 Z=11086.322)
"Mob Camp Scraps 9": base_id + 1551, # ItemPickup96_26 Bob_CampDetails2 (X=-33052.488 Y=-58560.098 Z=11101.021)
"Mob Camp Scraps 10": base_id + 1552, # ItemPickup13121212 Mine3_Mountain (X=-32422.406 Y=-60145.063 Z=11203.182)
"Mob Camp Scraps 11": base_id + 1553, # ItemPickup93_17 Bob_CampDetails2 (X=-30891.457 Y=-60046.465 Z=11237.567)
"Mob Camp Scraps 12": base_id + 1554, # ItemPickup97_29 Bob_CampDetails2 (X=-31888.428 Y=-59222.645 Z=11179.302)
"Mob Camp Scraps 13": base_id + 1555, # ItemPickup62156 Mine3_Mountain (X=-31161.750 Y=-57410.789 Z=11279.820)
"Mob Camp Scraps 14": base_id + 1556, # ItemPickup24_3 Bob_CampDetails2 (X=-31256.545 Y=-59865.809 Z=11904.155)
"Mob Camp Scraps 15": base_id + 1557, # ItemPickup27_0 Bob_CampDetails2 (X=-31757.953 Y=-57179.258 Z=11295.150)
"Mob Camp Scraps 16": base_id + 1558 # ItemPickup89_2 Bob_CampDetails2 (X=-29137.043 Y=-54824.797 Z=12418.673)
}
loc_mob_camp_locked_room = {
"Mob Camp Locked Room Scraps 1": base_id + 1559, # ItemPickup94_20 Bob_CampDetails2 (X=-31736.459 Y=-59761.465 Z=11211.379)
"Mob Camp Locked Room Scraps 2": base_id + 1560, # ItemPickup28_14 Bob_CampDetails2 (X=-32150.889 Y=-59879.594 Z=11297.058)
"Mob Camp Locked Room Stolen Bob": base_id + 1561 # Bob_Clickbox (X=-31771.172 Y=-59892.449 Z=11323.562)
}
loc_mine_elevator_exit = {
"Mine Elevator Exit Scraps 1": base_id + 1562, # ItemPickup266_19 Mine3_ExitCampDetails (X=-29587.271 Y=-42650.797 Z=12515.042)
"Mine Elevator Exit Scraps 2": base_id + 1563, # ItemPickup265_16 Mine3_ExitCampDetails (X=-30727.555 Y=-42715.438 Z=12485.763)
"Mine Elevator Exit Scraps 3": base_id + 1564, # ItemPickup267_22 Mine3_ExitCampDetails (X=-29814.680 Y=-43722.777 Z=12518.878)
"Mine Elevator Exit Scraps 4": base_id + 1565, # ItemPickup261_2 Mine3_ExitCampDetails (X=-30983.000 Y=-42943.754 Z=12474.396)
"Mine Elevator Exit Scraps 5": base_id + 1566, # ItemPickup262_5 Mine3_ExitCampDetails (X=-31824.576 Y=-43997.270 Z=12345.908)
"Mine Elevator Exit Scraps 6": base_id + 1567, # ItemPickup268_25 Mine3_ExitCampDetails (X=-32553.924 Y=-44761.855 Z=12341.698)
"Mine Elevator Exit Scraps 7": base_id + 1568, # ItemPickup263_10 Mine3_ExitCampDetails (X=-33598.023 Y=-44369.297 Z=12438.430)
"Mine Elevator Exit Scraps 8": base_id + 1569, # ItemPickup264_13 Mine3_ExitCampDetails (X=-31947.459 Y=-42017.137 Z=12384.311)
"Mine Elevator Exit Scraps 9": base_id + 1570 # ItemPickup269_28 Mine3_ExitCampDetails (X=-30127.123 Y=-46004.891 Z=12229.104)
}
loc_mountain_ruin_outside = {
"Mountain Ruin Outside Scraps 1": base_id + 1571, # ItemPickup112345556 Mine3_Outside (X=-2218.456 Y=-43914.789 Z=11238.952)
"Mountain Ruin Outside Scraps 2": base_id + 1572, # ItemPickup35621 Mine3_Outside (X=-2402.206 Y=-45494.766 Z=11244.650)
"Mountain Ruin Outside Scraps 3": base_id + 1573, # ItemPickup13000 Mine3_Outside (X=-2781.385 Y=-47365.313 Z=11235.389)
"Mountain Ruin Outside Scraps 4": base_id + 1574, # ItemPickup995959 Mine3_Outside (X=-2961.431 Y=-45445.973 Z=11255.289)
"Mountain Ruin Outside Scraps 5": base_id + 1575, # ItemPickup136999 Mine3_Outside (X=-5873.435 Y=-46300.633 Z=11228.667)
"Mountain Ruin Outside Scraps 6": base_id + 1576, # ItemPickup1589994 Mine3_Outside (X=-1823.357 Y=-47071.813 Z=11161.523)
"Mountain Ruin Outside Scraps 7": base_id + 1577, # ItemPickup09871230948 Mine3_Outside (X=-3478.155 Y=-49094.965 Z=11083.230)
"Mountain Ruin Outside Scraps 8": base_id + 1578, # ItemPickup258_20 Mine3_Camp (X=-4606.678 Y=-50246.180 Z=11613.938)
"Mountain Ruin Outside Scraps 9": base_id + 1579, # ItemPickup257_17 Mine3_Camp (X=-5454.638 Y=-53508.004 Z=12062.944)
"Mountain Ruin Outside Scraps 10": base_id + 1580, # ItemPickup18_3 Mine3_Camp (X=-8192.042 Y=-53726.535 Z=11947.394)
"Mountain Ruin Outside Scraps 11": base_id + 1581, # ItemPickup252_2 Mine3_Camp (X=-9409.834 Y=-53970.621 Z=11923.256)
"Mountain Ruin Outside Scraps 12": base_id + 1582, # ItemPickup254_8 Mine3_Camp (X=-8977.424 Y=-57134.637 Z=12232.596)
"Mountain Ruin Outside Scraps 13": base_id + 1583, # ItemPickup19_1 Mine3_Camp (X=-10449.292 Y=-56481.938 Z=12274.706)
"Mountain Ruin Outside Scraps 14": base_id + 1584, # ItemPickup255_11 Mine3_Camp (X=-10490.690 Y=-56584.301 Z=12281.096)
"Mountain Ruin Outside Scraps 15": base_id + 1585, # ItemPickup256_14 Mine3_Camp (X=-11600.937 Y=-55831.191 Z=12388.329)
"Mountain Ruin Outside Scraps 16": base_id + 1586, # ItemPickup259_23 Mine3_Camp (X=-3307.077 Y=-49973.461 Z=11282.041)
"Mountain Ruin Outside Scraps 17": base_id + 1587 # ItemPickup260_26 Mine3_Camp (X=-8878.345 Y=-49816.922 Z=13700.768)
}
loc_mountain_ruin_inside = {
"Mountain Ruin Inside Scraps 1": base_id + 1588, # ItemPickup253_5 Mine3_Camp (X=-10647.446 Y=-52039.848 Z=11925.344)
"Mountain Ruin Inside Scraps 2": base_id + 1589, # ItemPickup270_2 Mine3_Interior1 (X=-12834.990 Y=-48683.176 Z=10200.083)
"Mountain Ruin Inside Scraps 3": base_id + 1590, # ItemPickup271_5 Mine3_Interior1 (X=-15748.594 Y=-44925.523 Z=10199.975)
"Mountain Ruin Inside Scraps 4": base_id + 1591, # ItemPickup20_3 Mine3_Interior1 (X=-15312.152 Y=-43957.055 Z=10350.783)
"Mountain Ruin Inside Scraps 5": base_id + 1592, # ItemPickup273_11 Mine3_Interior1 (X=-16709.586 Y=-44997.316 Z=10198.888)
"Mountain Ruin Inside Scraps 6": base_id + 1593, # ItemPickup272_8 Mine3_Interior1 (X=-17412.561 Y=-46041.977 Z=10349.650)
"Mountain Ruin Inside Scraps 7": base_id + 1594, # ItemPickup274_14 Mine3_Interior1 (X=-17452.596 Y=-43804.941 Z=10201.436)
"Mountain Ruin Inside Scraps 8": base_id + 1595, # ItemPickup21_2 Mine3_Interior1 (X=-18119.473 Y=-42001.453 Z=10198.855)
"Mountain Ruin Inside Scraps 9": base_id + 1596, # ItemPickup276_20 Mine3_Interior1 (X=-18975.068 Y=-42906.488 Z=10279.690)
"Mountain Ruin Inside Scraps 10": base_id + 1597, # ItemPickup277_26 Mine3_Interior1 (X=-19727.902 Y=-41581.039 Z=10199.614)
"Mountain Ruin Inside Scraps 11": base_id + 1598, # ItemPickup275_17 Mine3_Interior1 (X=-19187.891 Y=-40516.941 Z=10201.049)
"Mountain Ruin Inside Scraps 12": base_id + 1599, # ItemPickup278_29 Mine3_Interior1 (X=-22470.986 Y=-41602.875 Z=10073.847)
"Mountain Ruin Inside Scraps 13": base_id + 1600, # ItemPickup279_2 Mine3_Interior2 (X=-23717.383 Y=-42597.141 Z=7455.452)
"Mountain Ruin Inside Scraps 14": base_id + 1601, # ItemPickup22_7 Mine3_Interior2 (X=-24494.582 Y=-44598.086 Z=7433.633)
"Mountain Ruin Inside Scraps 15": base_id + 1602, # ItemPickup280_5 Mine3_Interior2 (X=-26783.293 Y=-42331.145 Z=7446.357)
"Mountain Ruin Inside Scraps 16": base_id + 1603, # ItemPickup281_8 Mine3_Interior2 (X=-28636.996 Y=-46745.340 Z=7430.463)
"Mountain Ruin Inside Scraps 17": base_id + 1604, # ItemPickup23_1 Mine3_Interior2 (X=-27752.643 Y=-47453.973 Z=7445.047)
"Mountain Ruin Inside Red Egg": base_id + 1605, # Mine3_EggPickup Mine3_Interior2 (X=-28254.400 Y=-45702.844 Z=7551.568)
"Mountain Ruin Inside Red Paint Can": base_id + 1606 # PaintCan_2 Mine3_Interior2 (X=-31391.293 Y=-44953.223 Z=7448.648) \!/ Existing match 5
}
loc_pickle_val = {
"Pickle Val Scraps 1": base_id + 1607, # ItemPickup56_2 Pickles_HouseDetails (X=60402.582 Y=25252.434 Z=11151.982)
"Pickle Val Scraps 2": base_id + 1608, # ItemPickup57_5 Pickles_HouseDetails (X=58717.516 Y=24457.180 Z=11343.938)
"Pickle Val Scraps 3": base_id + 1609, # ItemPickup311_14 Pickles_EnvironmentDetails (X=56455.688 Y=22324.875 Z=11655.192)
"Pickle Val Scraps 4": base_id + 1610, # ItemPickup310_11 Pickles_EnvironmentDetails (X=52888.387 Y=22541.955 Z=12428.012)
"Pickle Val Scraps 5": base_id + 1611, # ItemPickup58_2 Pickles_EnvironmentDetails (X=50374.863 Y=22073.027 Z=12591.468)
"Pickle Val Scraps 6": base_id + 1612, # ItemPickup309_8 Pickles_EnvironmentDetails (X=45985.988 Y=23063.826 Z=13043.497)
"Pickle Val Scraps 7": base_id + 1613, # ItemPickup316_27 Pickles_EnvironmentDetails (X=45533.469 Y=24876.168 Z=13015.539)
"Pickle Val Scraps 8": base_id + 1614, # ItemPickup317_30 Pickles_EnvironmentDetails (X=44928.848 Y=30913.396 Z=14273.230)
"Pickle Val Scraps 9": base_id + 1615, # ItemPickup321_42 Pickles_EnvironmentDetails (X=41572.684 Y=37403.539 Z=13527.379)
"Pickle Val Scraps 10": base_id + 1616, # ItemPickup61_11 Pickles_EnvironmentDetails (X=42266.566 Y=30556.238 Z=13584.196)
"Pickle Val Scraps 11": base_id + 1617, # ItemPickup314_21 Pickles_EnvironmentDetails (X=43356.219 Y=29465.746 Z=13449.486)
"Pickle Val Scraps 12": base_id + 1618, # ItemPickup323_48 Pickles_EnvironmentDetails (X=40893.461 Y=30669.398 Z=13465.039)
"Pickle Val Scraps 13": base_id + 1619, # ItemPickup318_33 Pickles_EnvironmentDetails (X=41276.863 Y=32313.068 Z=13480.846)
"Pickle Val Scraps 14": base_id + 1620, # ItemPickup319_36 Pickles_EnvironmentDetails (X=38630.031 Y=30471.996 Z=13571.207)
"Pickle Val Scraps 15": base_id + 1621, # ItemPickup320_39 Pickles_EnvironmentDetails (X=38596.938 Y=30050.293 Z=13559.574)
"Pickle Val Scraps 16": base_id + 1622, # ItemPickup60_8 Pickles_EnvironmentDetails (X=42042.520 Y=25136.820 Z=13077.339)
"Pickle Val Scraps 17": base_id + 1623, # ItemPickup308_5 Pickles_EnvironmentDetails (X=43770.914 Y=23307.234 Z=12374.002)
"Pickle Val Scraps 18": base_id + 1624, # ItemPickup59_5 Pickles_EnvironmentDetails (X=44672.641 Y=20936.520 Z=12198.017)
"Pickle Val Scraps 19": base_id + 1625, # ItemPickup307_2 Pickles_EnvironmentDetails (X=42844.730 Y=22869.387 Z=12832.900)
"Pickle Val Scraps 20": base_id + 1626, # ItemPickup315_24 Pickles_EnvironmentDetails (X=43397.758 Y=26367.248 Z=13005.097)
"Pickle Val Scraps 21": base_id + 1627, # ItemPickup322_45 Pickles_EnvironmentDetails (X=39352.430 Y=32668.316 Z=14494.778)
"Pickle Val Scraps 22": base_id + 1628, # ItemPickup313 Pickles_EnvironmentDetails (X=59688.781 Y=25266.756 Z=11425.324)
"Pickle Val Scraps 23": base_id + 1629, # ItemPickup312_17 Pickles_EnvironmentDetails (X=59197.871 Y=24760.773 Z=11425.324)
"Pickle Val Purple Paint Can": base_id + 1630, # PaintCan_7 Pickles_EnvironmentDetails (X=40394.855 Y=31546.465 Z=13472.573)
"Pickle Val Jar of Pickles": base_id + 1631, # Pickles_Pickup Pickles_Cave (X=38227.535 Y=30234.848 Z=13593.506)
"Pickle Val Pickle Lady Mission End": base_id + 1632 # (dialog 4) -> 30_scraps_reward
}
loc_shrine_near_temple = {
"Shrine Near Temple Scraps 1": base_id + 1633, # ItemPickup3 Photorealistic_Island (X=-4675.183 Y=-21143.846 Z=11984.782)
"Shrine Near Temple Scraps 2": base_id + 1634, # ItemPickup_2 Photorealistic_Island (X=-4994.117 Y=-20496.621 Z=11874.112)
"Shrine Near Temple Scraps 3": base_id + 1635 # ItemPickup2 Photorealistic_Island (X=-4196.896 Y=-20477.025 Z=11882.833)
}
loc_morse_bunker = {
"Morse Bunker Chest Scraps 1": base_id + 1636, # ItemPickup6512 Secret6_BunkerDetails (X=-47961.078 Y=-85433.031 Z=10615.777)
"Morse Bunker Chest Scraps 2": base_id + 1637, # ItemPickup9363 Secret6_BunkerDetails (X=-47958.617 Y=-85444.805 Z=10604.365)
"Morse Bunker Chest Scraps 3": base_id + 1638, # ItemPickup921436 Secret6_BunkerDetails (X=-47953.637 Y=-85426.172 Z=10592.347)
"Morse Bunker Chest Scraps 4": base_id + 1639, # ItemPickup0128704 Secret6_BunkerDetails (X=-47958.691 Y=-85444.805 Z=10587.060)
"Morse Bunker Chest Scraps 5": base_id + 1640, # ItemPickup64_8 Secret6_BunkerDetails (X=-47953.617 Y=-85426.172 Z=10574.149)
"Morse Bunker Scraps 1": base_id + 1641, # ItemPickup397_2 Secret6_BunkerDetails (X=-48336.863 Y=-85458.453 Z=10574.537)
"Morse Bunker Scraps 2": base_id + 1642, # ItemPickup62_2 Secret6_BunkerDetails (X=-48253.844 Y=-84944.609 Z=10573.793)
"Morse Bunker Scraps 3": base_id + 1643, # ItemPickup63_5 Secret6_BunkerDetails (X=-47272.422 Y=-84549.945 Z=10543.671)
"Morse Bunker Scraps 4": base_id + 1644, # ItemPickup398_5 Secret6_BunkerDetails (X=-47080.836 Y=-85268.281 Z=10564.355)
"Morse Bunker Scraps 5": base_id + 1645, # ItemPickup406_31 Secret6_BunkerDetails (X=-47913.855 Y=-85234.258 Z=10819.314)
"Morse Bunker Scraps 6": base_id + 1646, # ItemPickup405_28 Secret6_BunkerDetails (X=-46410.227 Y=-83293.742 Z=10361.746)
"Morse Bunker Scraps 7": base_id + 1647, # ItemPickup399_8 Secret6_BunkerDetails (X=-45204.199 Y=-84841.211 Z=10329.200)
"Morse Bunker Scraps 8": base_id + 1648, # ItemPickup401_14 Secret6_BunkerDetails (X=-43895.801 Y=-83794.750 Z=10265.661)
"Morse Bunker Scraps 9": base_id + 1649, # ItemPickup402_17 Secret6_BunkerDetails (X=-45163.078 Y=-80832.828 Z=10090.777)
"Morse Bunker Scraps 10": base_id + 1650, # ItemPickup403_20 Secret6_BunkerDetails (X=-46782.027 Y=-82284.805 Z=10289.180)
"Morse Bunker Scraps 11": base_id + 1651, # ItemPickup404_23 Secret6_BunkerDetails (X=-49240.047 Y=-83654.961 Z=10898.540)
"Morse Bunker Scraps 12": base_id + 1652, # ItemPickup407_34 Secret6_BunkerDetails (X=-46571.930 Y=-85962.773 Z=10697.339)
"Morse Bunker Scraps 13": base_id + 1653 # ItemPickup400_11 Secret6_BunkerDetails (X=-43472.793 Y=-85983.805 Z=10310.322)
}
loc_prism_temple = {
"Prism Temple Chest Scraps 1": base_id + 1654, # ItemPickup16632 MainShrine_DetailsREPAIRED (X=12659.641 Y=-27827.016 Z=10930.621)
"Prism Temple Chest Scraps 2": base_id + 1655, # ItemPickup148864 MainShrine_DetailsREPAIRED (X=12648.021 Y=-27825.189 Z=10916.296)
"Prism Temple Chest Scraps 3": base_id + 1656, # ItemPickup13123 MainShrine_DetailsREPAIRED (X=12665.557 Y=-27836.633 Z=10905.999)
"Prism Temple Scraps 1": base_id + 1657, # ItemPickup225_77 MainShrine_ExteriorDetails (X=5281.087 Y=-16620.387 Z=10520.609)
"Prism Temple Scraps 2": base_id + 1658, # ItemPickup223_71 MainShrine_ExteriorDetails (X=15032.147 Y=-16788.352 Z=10992.200)
"Prism Temple Scraps 3": base_id + 1659, # ItemPickup222_68 MainShrine_ExteriorDetails (X=16591.920 Y=-18725.771 Z=11004.751)
"Prism Temple Scraps 4": base_id + 1660, # ItemPickup135123 MainShrine_DetailsREPAIRED (X=17940.854 Y=-20726.746 Z=10999.944)
"Prism Temple Scraps 5": base_id + 1661, # ItemPickup220_62 MainShrine_ExteriorDetails (X=18187.346 Y=-21390.066 Z=11025.650)
"Prism Temple Scraps 6": base_id + 1662, # ItemPickup218_56 MainShrine_ExteriorDetails (X=19218.670 Y=-24017.619 Z=10906.966)
"Prism Temple Scraps 7": base_id + 1663, # ItemPickup209_29 MainShrine_ExteriorDetails (X=7710.977 Y=-23469.254 Z=11012.888)
"Prism Temple Scraps 8": base_id + 1664, # ItemPickup138765 MainShrine_DetailsREPAIRED (X=8160.002 Y=-22335.266 Z=11016.638)
"Prism Temple Scraps 9": base_id + 1665, # ItemPickup210_32 MainShrine_ExteriorDetails (X=8637.903 Y=-24295.850 Z=10929.299)
"Prism Temple Scraps 10": base_id + 1666, # ItemPickup1112 MainShrine_DetailsREPAIRED (X=7923.367 Y=-25204.055 Z=10832.877)
"Prism Temple Scraps 11": base_id + 1667, # ItemPickup214_44 MainShrine_ExteriorDetails (X=10694.420 Y=-27246.365 Z=10569.074)
"Prism Temple Scraps 12": base_id + 1668, # ItemPickup215_47 MainShrine_ExteriorDetails (X=12892.631 Y=-26743.068 Z=10868.578)
"Prism Temple Scraps 13": base_id + 1669, # ItemPickup213_41 MainShrine_ExteriorDetails (X=12483.535 Y=-27253.867 Z=10876.461)
"Prism Temple Scraps 14": base_id + 1670, # ItemPickup212_38 MainShrine_ExteriorDetails (X=13259.813 Y=-27092.033 Z=10887.968)
"Prism Temple Scraps 15": base_id + 1671, # ItemPickup211_35 MainShrine_ExteriorDetails (X=9303.245 Y=-25057.908 Z=10943.273)
"Prism Temple Scraps 16": base_id + 1672, # ItemPickup201_2 MainShrine_ExteriorDetails (X=11750.875 Y=-22999.371 Z=12426.734)
"Prism Temple Scraps 17": base_id + 1673, # ItemPickup203_8 MainShrine_ExteriorDetails (X=12966.615 Y=-23568.324 Z=12519.387)
"Prism Temple Scraps 18": base_id + 1674, # ItemPickup204_11 MainShrine_ExteriorDetails (X=14093.343 Y=-22393.182 Z=12426.847)
"Prism Temple Scraps 19": base_id + 1675, # ItemPickup206_17 MainShrine_ExteriorDetails (X=13767.980 Y=-21269.568 Z=12425.791)
"Prism Temple Scraps 20": base_id + 1676, # ItemPickup207_23 MainShrine_ExteriorDetails (X=12882.754 Y=-20027.527 Z=12516.095)
"Prism Temple Scraps 21": base_id + 1677, # ItemPickup114535 MainShrine_DetailsREPAIRED (X=11883.399 Y=-20801.344 Z=12428.452)
"Prism Temple Scraps 22": base_id + 1678, # ItemPickup208_26 MainShrine_ExteriorDetails (X=13281.792 Y=-21302.344 Z=12902.728)
"Prism Temple Scraps 23": base_id + 1679, # ItemPickup205_14 MainShrine_ExteriorDetails (X=14190.678 Y=-23671.621 Z=11781.533)
"Prism Temple Scraps 24": base_id + 1680, # ItemPickup8903 MainShrine_DetailsREPAIRED (X=14311.736 Y=-22347.758 Z=11765.781)
"Prism Temple Scraps 25": base_id + 1681, # ItemPickup654 MainShrine_DetailsREPAIRED (X=13826.154 Y=-19923.131 Z=11755.189)
"Prism Temple Scraps 26": base_id + 1682, # ItemPickup224_74 MainShrine_ExteriorDetails (X=12443.228 Y=-18577.926 Z=11240.455)
"Prism Temple Scraps 27": base_id + 1683, # ItemPickup202_5 MainShrine_ExteriorDetails (X=10993.180 Y=-23783.047 Z=11754.121)
"Prism Temple Scraps 28": base_id + 1684, # ItemPickup13098 MainShrine_DetailsREPAIRED (X=16762.963 Y=-23634.342 Z=11031.588)
"Prism Temple Scraps 29": base_id + 1685, # ItemPickup221_65 MainShrine_ExteriorDetails (X=17804.979 Y=-23512.395 Z=11066.884)
"Prism Temple Scraps 30": base_id + 1686, # ItemPickup17123123565 MainShrine_DetailsREPAIRED (X=16998.229 Y=-23241.652 Z=11055.539)
"Prism Temple Scraps 31": base_id + 1687, # ItemPickup10812783 MainShrine_DetailsREPAIRED (X=17613.518 Y=-23799.813 Z=11057.277)
"Prism Temple Scraps 32": base_id + 1688, # ItemPickup216_50 MainShrine_ExteriorDetails (X=15342.375 Y=-24807.357 Z=11024.192)
"Prism Temple Scraps 33": base_id + 1689, # ItemPickup217_53 MainShrine_ExteriorDetails (X=15963.284 Y=-24834.156 Z=11021.444)
"Prism Temple Scraps 34": base_id + 1690 # ItemPickup219_59 MainShrine_ExteriorDetails (X=21563.559 Y=-23705.184 Z=10895.696)
}
# All locations
location_table: dict[str, int] = {
**loc_start_camp,
**loc_tony_tiddle_mission,
**loc_barn,
**loc_candice_mission,
**loc_tutorial_house,
**loc_swamp_edges,
**loc_swamp_mission,
**loc_junkyard_area,
**loc_south_house,
**loc_junkyard_shed,
**loc_military_base,
**loc_south_mine_outside,
**loc_south_mine_inside,
**loc_middle_station,
**loc_canyon,
**loc_watchtower,
**loc_boulder_field,
**loc_haunted_house,
**loc_santiago_house,
**loc_port,
**loc_trench_house,
**loc_doll_woods,
**loc_lost_stairs,
**loc_east_house,
**loc_rockets_testing_ground,
**loc_rockets_testing_bunker,
**loc_workshop,
**loc_east_tower,
**loc_lighthouse,
**loc_north_mine_outside,
**loc_north_mine_inside,
**loc_wood_bridge,
**loc_museum,
**loc_barbed_shelter,
**loc_west_beach,
**loc_church,
**loc_west_cottage,
**loc_caravan,
**loc_trailer_cabin,
**loc_towers,
**loc_north_beach,
**loc_mine_shaft,
**loc_mob_camp,
**loc_mob_camp_locked_room,
**loc_mine_elevator_exit,
**loc_mountain_ruin_outside,
**loc_mountain_ruin_inside,
**loc_prism_temple,
**loc_pickle_val,
**loc_shrine_near_temple,
**loc_morse_bunker
}

View File

@@ -0,0 +1,7 @@
from dataclasses import dataclass
from Options import PerGameCommonOptions, StartInventoryPool
@dataclass
class CCCharlesOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool

290
worlds/cccharles/Regions.py Normal file
View File

@@ -0,0 +1,290 @@
from BaseClasses import MultiWorld, Region, ItemClassification
from .Items import CCCharlesItem
from .Options import CCCharlesOptions
from .Locations import (
CCCharlesLocation, loc_start_camp, loc_tony_tiddle_mission, loc_barn, loc_candice_mission, \
loc_tutorial_house, loc_swamp_edges, loc_swamp_mission, loc_junkyard_area, loc_south_house, \
loc_junkyard_shed, loc_military_base, loc_south_mine_outside, loc_south_mine_inside, \
loc_middle_station, loc_canyon, loc_watchtower, loc_boulder_field, loc_haunted_house, \
loc_santiago_house, loc_port, loc_trench_house, loc_doll_woods, loc_lost_stairs, loc_east_house, \
loc_rockets_testing_ground, loc_rockets_testing_bunker, loc_workshop, loc_east_tower, \
loc_lighthouse, loc_north_mine_outside, loc_north_mine_inside, loc_wood_bridge, loc_museum, \
loc_barbed_shelter, loc_west_beach, loc_church, loc_west_cottage, loc_caravan, loc_trailer_cabin, \
loc_towers, loc_north_beach, loc_mine_shaft, loc_mob_camp, loc_mob_camp_locked_room, \
loc_mine_elevator_exit, loc_mountain_ruin_outside, loc_mountain_ruin_inside, loc_prism_temple, \
loc_pickle_val, loc_shrine_near_temple, loc_morse_bunker
)
def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
menu_region = Region("Menu", player, world, "Aranearum")
world.regions.append(menu_region)
start_camp_region = Region("Start Camp", player, world)
start_camp_region.add_locations(loc_start_camp, CCCharlesLocation)
world.regions.append(start_camp_region)
tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, world)
tony_tiddle_mission_region.add_locations(loc_tony_tiddle_mission, CCCharlesLocation)
world.regions.append(tony_tiddle_mission_region)
barn_region = Region("Barn", player, world)
barn_region.add_locations(loc_barn, CCCharlesLocation)
world.regions.append(barn_region)
candice_mission_region = Region("Candice Mission", player, world)
candice_mission_region.add_locations(loc_candice_mission, CCCharlesLocation)
world.regions.append(candice_mission_region)
tutorial_house_region = Region("Tutorial House", player, world)
tutorial_house_region.add_locations(loc_tutorial_house, CCCharlesLocation)
world.regions.append(tutorial_house_region)
swamp_edges_region = Region("Swamp Edges", player, world)
swamp_edges_region.add_locations(loc_swamp_edges, CCCharlesLocation)
world.regions.append(swamp_edges_region)
swamp_mission_region = Region("Swamp Mission", player, world)
swamp_mission_region.add_locations(loc_swamp_mission, CCCharlesLocation)
world.regions.append(swamp_mission_region)
junkyard_area_region = Region("Junkyard Area", player, world)
junkyard_area_region.add_locations(loc_junkyard_area, CCCharlesLocation)
world.regions.append(junkyard_area_region)
south_house_region = Region("South House", player, world)
south_house_region.add_locations(loc_south_house, CCCharlesLocation)
world.regions.append(south_house_region)
junkyard_shed_region = Region("Junkyard Shed", player, world)
junkyard_shed_region.add_locations(loc_junkyard_shed, CCCharlesLocation)
world.regions.append(junkyard_shed_region)
military_base_region = Region("Military Base", player, world)
military_base_region.add_locations(loc_military_base, CCCharlesLocation)
world.regions.append(military_base_region)
south_mine_outside_region = Region("South Mine Outside", player, world)
south_mine_outside_region.add_locations(loc_south_mine_outside, CCCharlesLocation)
world.regions.append(south_mine_outside_region)
south_mine_inside_region = Region("South Mine Inside", player, world)
south_mine_inside_region.add_locations(loc_south_mine_inside, CCCharlesLocation)
world.regions.append(south_mine_inside_region)
middle_station_region = Region("Middle Station", player, world)
middle_station_region.add_locations(loc_middle_station, CCCharlesLocation)
world.regions.append(middle_station_region)
canyon_region = Region("Canyon", player, world)
canyon_region.add_locations(loc_canyon, CCCharlesLocation)
world.regions.append(canyon_region)
watchtower_region = Region("Watchtower", player, world)
watchtower_region.add_locations(loc_watchtower, CCCharlesLocation)
world.regions.append(watchtower_region)
boulder_field_region = Region("Boulder Field", player, world)
boulder_field_region.add_locations(loc_boulder_field, CCCharlesLocation)
world.regions.append(boulder_field_region)
haunted_house_region = Region("Haunted House", player, world)
haunted_house_region.add_locations(loc_haunted_house, CCCharlesLocation)
world.regions.append(haunted_house_region)
santiago_house_region = Region("Santiago House", player, world)
santiago_house_region.add_locations(loc_santiago_house, CCCharlesLocation)
world.regions.append(santiago_house_region)
port_region = Region("Port", player, world)
port_region.add_locations(loc_port, CCCharlesLocation)
world.regions.append(port_region)
trench_house_region = Region("Trench House", player, world)
trench_house_region.add_locations(loc_trench_house, CCCharlesLocation)
world.regions.append(trench_house_region)
doll_woods_region = Region("Doll Woods", player, world)
doll_woods_region.add_locations(loc_doll_woods, CCCharlesLocation)
world.regions.append(doll_woods_region)
lost_stairs_region = Region("Lost Stairs", player, world)
lost_stairs_region.add_locations(loc_lost_stairs, CCCharlesLocation)
world.regions.append(lost_stairs_region)
east_house_region = Region("East House", player, world)
east_house_region.add_locations(loc_east_house, CCCharlesLocation)
world.regions.append(east_house_region)
rockets_testing_ground_region = Region("Rockets Testing Ground", player, world)
rockets_testing_ground_region.add_locations(loc_rockets_testing_ground, CCCharlesLocation)
world.regions.append(rockets_testing_ground_region)
rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, world)
rockets_testing_bunker_region.add_locations(loc_rockets_testing_bunker, CCCharlesLocation)
world.regions.append(rockets_testing_bunker_region)
workshop_region = Region("Workshop", player, world)
workshop_region.add_locations(loc_workshop, CCCharlesLocation)
world.regions.append(workshop_region)
east_tower_region = Region("East Tower", player, world)
east_tower_region.add_locations(loc_east_tower, CCCharlesLocation)
world.regions.append(east_tower_region)
lighthouse_region = Region("Lighthouse", player, world)
lighthouse_region.add_locations(loc_lighthouse, CCCharlesLocation)
world.regions.append(lighthouse_region)
north_mine_outside_region = Region("North Mine Outside", player, world)
north_mine_outside_region.add_locations(loc_north_mine_outside, CCCharlesLocation)
world.regions.append(north_mine_outside_region)
north_mine_inside_region = Region("North Mine Inside", player, world)
north_mine_inside_region.add_locations(loc_north_mine_inside, CCCharlesLocation)
world.regions.append(north_mine_inside_region)
wood_bridge_region = Region("Wood Bridge", player, world)
wood_bridge_region.add_locations(loc_wood_bridge, CCCharlesLocation)
world.regions.append(wood_bridge_region)
museum_region = Region("Museum", player, world)
museum_region.add_locations(loc_museum, CCCharlesLocation)
world.regions.append(museum_region)
barbed_shelter_region = Region("Barbed Shelter", player, world)
barbed_shelter_region.add_locations(loc_barbed_shelter, CCCharlesLocation)
world.regions.append(barbed_shelter_region)
west_beach_region = Region("West Beach", player, world)
west_beach_region.add_locations(loc_west_beach, CCCharlesLocation)
world.regions.append(west_beach_region)
church_region = Region("Church", player, world)
church_region.add_locations(loc_church, CCCharlesLocation)
world.regions.append(church_region)
west_cottage_region = Region("West Cottage", player, world)
west_cottage_region.add_locations(loc_west_cottage, CCCharlesLocation)
world.regions.append(west_cottage_region)
caravan_region = Region("Caravan", player, world)
caravan_region.add_locations(loc_caravan, CCCharlesLocation)
world.regions.append(caravan_region)
trailer_cabin_region = Region("Trailer Cabin", player, world)
trailer_cabin_region.add_locations(loc_trailer_cabin, CCCharlesLocation)
world.regions.append(trailer_cabin_region)
towers_region = Region("Towers", player, world)
towers_region.add_locations(loc_towers, CCCharlesLocation)
world.regions.append(towers_region)
north_beach_region = Region("North beach", player, world)
north_beach_region.add_locations(loc_north_beach, CCCharlesLocation)
world.regions.append(north_beach_region)
mine_shaft_region = Region("Mine Shaft", player, world)
mine_shaft_region.add_locations(loc_mine_shaft, CCCharlesLocation)
world.regions.append(mine_shaft_region)
mob_camp_region = Region("Mob Camp", player, world)
mob_camp_region.add_locations(loc_mob_camp, CCCharlesLocation)
world.regions.append(mob_camp_region)
mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, world)
mob_camp_locked_room_region.add_locations(loc_mob_camp_locked_room, CCCharlesLocation)
world.regions.append(mob_camp_locked_room_region)
mine_elevator_exit_region = Region("Mine Elevator Exit", player, world)
mine_elevator_exit_region.add_locations(loc_mine_elevator_exit, CCCharlesLocation)
world.regions.append(mine_elevator_exit_region)
mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, world)
mountain_ruin_outside_region.add_locations(loc_mountain_ruin_outside, CCCharlesLocation)
world.regions.append(mountain_ruin_outside_region)
mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, world)
mountain_ruin_inside_region.add_locations(loc_mountain_ruin_inside, CCCharlesLocation)
world.regions.append(mountain_ruin_inside_region)
prism_temple_region = Region("Prism Temple", player, world)
prism_temple_region.add_locations(loc_prism_temple, CCCharlesLocation)
world.regions.append(prism_temple_region)
pickle_val_region = Region("Pickle Val", player, world)
pickle_val_region.add_locations(loc_pickle_val, CCCharlesLocation)
world.regions.append(pickle_val_region)
shrine_near_temple_region = Region("Shrine Near Temple", player, world)
shrine_near_temple_region.add_locations(loc_shrine_near_temple, CCCharlesLocation)
world.regions.append(shrine_near_temple_region)
morse_bunker_region = Region("Morse Bunker", player, world)
morse_bunker_region.add_locations(loc_morse_bunker, CCCharlesLocation)
world.regions.append(morse_bunker_region)
# Place "Victory" event at "Final Boss" location
loc_final_boss = CCCharlesLocation(player, "Final Boss", None, prism_temple_region)
loc_final_boss.place_locked_item(CCCharlesItem("Victory", ItemClassification.progression, None, player))
prism_temple_region.locations.append(loc_final_boss)
# Connect the Regions by named Entrances that must have access Rules
menu_region.connect(start_camp_region)
menu_region.connect(tony_tiddle_mission_region)
menu_region.connect(barn_region)
tony_tiddle_mission_region.connect(barn_region, "Barn Door")
menu_region.connect(candice_mission_region)
menu_region.connect(tutorial_house_region)
candice_mission_region.connect(tutorial_house_region, "Tutorial House Door")
menu_region.connect(swamp_edges_region)
menu_region.connect(swamp_mission_region)
menu_region.connect(junkyard_area_region)
menu_region.connect(south_house_region)
menu_region.connect(junkyard_shed_region)
menu_region.connect(military_base_region)
menu_region.connect(south_mine_outside_region)
menu_region.connect(south_mine_inside_region)
south_mine_outside_region.connect(south_mine_inside_region, "South Mine Gate")
menu_region.connect(middle_station_region)
menu_region.connect(canyon_region)
menu_region.connect(watchtower_region)
menu_region.connect(boulder_field_region)
menu_region.connect(haunted_house_region)
menu_region.connect(santiago_house_region)
menu_region.connect(port_region)
menu_region.connect(trench_house_region)
menu_region.connect(doll_woods_region)
menu_region.connect(lost_stairs_region)
menu_region.connect(east_house_region)
menu_region.connect(rockets_testing_ground_region)
menu_region.connect(rockets_testing_bunker_region)
rockets_testing_ground_region.connect(rockets_testing_bunker_region, "Stuck Bunker Door")
menu_region.connect(workshop_region)
menu_region.connect(east_tower_region)
menu_region.connect(lighthouse_region)
menu_region.connect(north_mine_outside_region)
menu_region.connect(north_mine_inside_region)
north_mine_outside_region.connect(north_mine_inside_region, "North Mine Gate")
menu_region.connect(wood_bridge_region)
menu_region.connect(museum_region)
menu_region.connect(barbed_shelter_region)
menu_region.connect(west_beach_region)
menu_region.connect(church_region)
menu_region.connect(west_cottage_region)
menu_region.connect(caravan_region)
menu_region.connect(trailer_cabin_region)
menu_region.connect(towers_region)
menu_region.connect(north_beach_region)
menu_region.connect(mine_shaft_region)
menu_region.connect(mob_camp_region)
menu_region.connect(mob_camp_locked_room_region)
mob_camp_region.connect(mob_camp_locked_room_region, "Mob Camp Locked Door")
menu_region.connect(mine_elevator_exit_region)
menu_region.connect(mountain_ruin_outside_region)
menu_region.connect(mountain_ruin_inside_region)
mountain_ruin_outside_region.connect(mountain_ruin_inside_region, "Mountain Ruin Gate")
menu_region.connect(prism_temple_region)
menu_region.connect(pickle_val_region)
menu_region.connect(shrine_near_temple_region)
menu_region.connect(morse_bunker_region)

215
worlds/cccharles/Rules.py Normal file
View File

@@ -0,0 +1,215 @@
from BaseClasses import MultiWorld
from ..generic.Rules import set_rule
from .Options import CCCharlesOptions
# Go mode: Green Egg + Blue Egg + Red Egg + Temple Key + Bug Spray (+ Remote Explosive x8 but the base game ignores it)
def set_rules(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
# Tony Tiddle
set_rule(world.get_entrance("Barn Door", player),
lambda state: state.has("Barn Key", player))
# Candice
set_rule(world.get_entrance("Tutorial House Door", player),
lambda state: state.has("Candice's Key", player))
# Lizbeth Murkwater
set_rule(world.get_location("Swamp Lizbeth Murkwater Mission End", player),
lambda state: state.has("Dead Fish", player))
# Daryl
set_rule(world.get_location("Junkyard Area Chest Ancient Tablet", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Junkyard Area Daryl Mission End", player),
lambda state: state.has("Ancient Tablet", player))
# South House
set_rule(world.get_location("South House Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("South House Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("South House Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("South House Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("South House Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("South House Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player))
# South Mine
set_rule(world.get_entrance("South Mine Gate", player),
lambda state: state.has("South Mine Key", player))
set_rule(world.get_location("South Mine Inside Green Paint Can", player),
lambda state: state.has("Lockpicks", player))
# Theodore
set_rule(world.get_location("Middle Station Theodore Mission End", player),
lambda state: state.has("Blue Box", player))
# Watchtower
set_rule(world.get_location("Watchtower Pink Paint Can", player),
lambda state: state.has("Lockpicks", player))
# Sasha
set_rule(world.get_location("Haunted House Sasha Mission End", player),
lambda state: state.has("Page Drawing", player, 8))
# Santiago
set_rule(world.get_location("Port Santiago Mission End", player),
lambda state: state.has("Journal", player))
# Trench House
set_rule(world.get_location("Trench House Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Trench House Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Trench House Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Trench House Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Trench House Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Trench House Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player))
# East House
set_rule(world.get_location("East House Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("East House Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("East House Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("East House Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("East House Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
# Rocket Testing Bunker
set_rule(world.get_entrance("Stuck Bunker Door", player),
lambda state: state.has("Timed Dynamite", player))
# John Smith
set_rule(world.get_location("Workshop John Smith Mission End", player),
lambda state: state.has("Box of Rockets", player))
# Claire
set_rule(world.get_location("Lighthouse Claire Mission End", player),
lambda state: state.has("Breaker", player, 4))
# North Mine
set_rule(world.get_entrance("North Mine Gate", player),
lambda state: state.has("North Mine Key", player))
set_rule(world.get_location("North Mine Inside Blue Paint Can", player),
lambda state: state.has("Lockpicks", player))
# Paul
set_rule(world.get_location("Museum Paul Mission End", player),
lambda state: state.has("Remote Explosive x8", player))
# lambda state: state.has("Remote Explosive", player, 8)) # TODO: Add an option to split remote explosives
# West Beach
set_rule(world.get_location("West Beach Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("West Beach Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("West Beach Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("West Beach Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("West Beach Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("West Beach Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player))
# Caravan
set_rule(world.get_location("Caravan Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Caravan Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Caravan Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Caravan Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Caravan Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
# Ronny
set_rule(world.get_location("Towers Ronny Mission End", player),
lambda state: state.has("Employment Contracts", player))
# North Beach
set_rule(world.get_location("North Beach Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("North Beach Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("North Beach Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("North Beach Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
# Mine Shaft
set_rule(world.get_location("Mine Shaft Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Mine Shaft Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Mine Shaft Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Mine Shaft Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Mine Shaft Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Mine Shaft Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Mine Shaft Chest Scraps 7", player),
lambda state: state.has("Lockpicks", player))
# Mob Camp
set_rule(world.get_entrance("Mob Camp Locked Door", player),
lambda state: state.has("Mob Camp Key", player))
set_rule(world.get_location("Mob Camp Locked Room Stolen Bob", player),
lambda state: state.has("Broken Bob", player))
# Mountain Ruin
set_rule(world.get_entrance("Mountain Ruin Gate", player),
lambda state: state.has("Mountain Ruin Key", player))
set_rule(world.get_location("Mountain Ruin Inside Red Paint Can", player),
lambda state: state.has("Lockpicks", player))
# Prism Temple
set_rule(world.get_location("Prism Temple Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Prism Temple Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Prism Temple Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
# Pickle Lady
set_rule(world.get_location("Pickle Val Jar of Pickles", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Pickle Val Pickle Lady Mission End", player),
lambda state: state.has("Jar of Pickles", player))
# Morse Bunker
set_rule(world.get_location("Morse Bunker Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Morse Bunker Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Morse Bunker Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Morse Bunker Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player))
set_rule(world.get_location("Morse Bunker Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player))
# Add rules to reach the "Go mode"
set_rule(world.get_location("Final Boss", player),
lambda state: state.has("Temple Key", player)
and state.has("Green Egg", player)
and state.has("Blue Egg", player)
and state.has("Red Egg", player))
world.completion_condition[player] = lambda state: state.has("Victory", player)

View File

@@ -0,0 +1,171 @@
from .Items import CCCharlesItem, unique_item_dict, full_item_list, item_groups
from .Locations import location_table
from .Options import CCCharlesOptions
from .Rules import set_rules
from .Regions import create_regions
from BaseClasses import Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
class CCCharlesWeb(WebWorld):
"""
Choo-Choo Charles is a horror game.
A devil spider train from hell called Charles chases any person it finds on an island.
The goal is to gather scraps to upgrade a train to fight Charles and travel by train to find 3 eggs
to lead Charles to a brutal death and save the island.
"""
theme = "stone"
setup_en = Tutorial(
"Multiworld Setup Guide",
"A guide to setup Choo-Choo Charles for the Archipelago MultiWorld Randomizer.",
"English",
"setup_en.md",
"setup/en",
["Yaranorgoth"]
)
setup_fr = Tutorial(
"Guide d'Installation Multiworld",
"Un guide pour mettre en place Choo-Choo Charles pour le Randomiseur Multiworld Archipelago",
"Français",
"setup_fr.md",
"setup/fr",
["Yaranorgoth"]
)
tutorials = [setup_en, setup_fr]
game_info_languages = ["en", "fr"]
rich_text_options_doc = True
class CCCharlesWorld(World):
"""
An independent 3D horror game, taking place on an island.
The main gameplay consists of traveling and fighting a monster on board a train.
Upgrading the train requires leaving the train to gather resources with the threat of encountering the monster.
"""
game = "Choo-Choo Charles"
web = CCCharlesWeb()
item_name_to_id = unique_item_dict
location_name_to_id = location_table
item_name_groups = item_groups
# Options the player can set
options_dataclass = CCCharlesOptions
# Typing hints for all the options we defined
options: CCCharlesOptions
topology_present = False # Hide path to required location checks in spoiler
def create_regions(self) -> None:
create_regions(self.multiworld, self.options, self.player)
def create_item(self, name: str) -> CCCharlesItem:
item_id = unique_item_dict[name]
match name:
case "Scraps":
classification = ItemClassification.useful
case "30 Scraps Reward":
classification = ItemClassification.useful
case "25 Scraps Reward":
classification = ItemClassification.useful
case "35 Scraps Reward":
classification = ItemClassification.useful
case "40 Scraps Reward":
classification = ItemClassification.useful
case "South Mine Key":
classification = ItemClassification.progression
case "North Mine Key":
classification = ItemClassification.progression
case "Mountain Ruin Key":
classification = ItemClassification.progression
case "Barn Key":
classification = ItemClassification.progression
case "Candice's Key":
classification = ItemClassification.progression
case "Dead Fish":
classification = ItemClassification.progression
case "Lockpicks":
classification = ItemClassification.progression
case "Ancient Tablet":
classification = ItemClassification.progression
case "Blue Box":
classification = ItemClassification.progression
case "Page Drawing":
classification = ItemClassification.progression
case "Journal":
classification = ItemClassification.progression
case "Timed Dynamite":
classification = ItemClassification.progression
case "Box of Rockets":
classification = ItemClassification.progression
case "Breaker":
classification = ItemClassification.progression
case "Broken Bob":
classification = ItemClassification.progression
case "Employment Contracts":
classification = ItemClassification.progression
case "Mob Camp Key":
classification = ItemClassification.progression
case "Jar of Pickles":
classification = ItemClassification.progression
case "Orange Paint Can":
classification = ItemClassification.filler
case "Green Paint Can":
classification = ItemClassification.filler
case "White Paint Can":
classification = ItemClassification.filler
case "Pink Paint Can":
classification = ItemClassification.filler
case "Grey Paint Can":
classification = ItemClassification.filler
case "Blue Paint Can":
classification = ItemClassification.filler
case "Black Paint Can":
classification = ItemClassification.filler
case "Lime Paint Can":
classification = ItemClassification.filler
case "Teal Paint Can":
classification = ItemClassification.filler
case "Red Paint Can":
classification = ItemClassification.filler
case "Purple Paint Can":
classification = ItemClassification.filler
case "The Boomer":
classification = ItemClassification.filler
case "Bob":
classification = ItemClassification.filler
case "Green Egg":
classification = ItemClassification.progression
case "Blue Egg":
classification = ItemClassification.progression
case "Red Egg":
classification = ItemClassification.progression
case "Remote Explosive":
classification = ItemClassification.progression
case "Remote Explosive x8":
classification = ItemClassification.progression
case "Temple Key":
classification = ItemClassification.progression
case "Bug Spray":
classification = ItemClassification.progression
case _: # Should not occur
raise Exception("Unexpected case met: classification cannot be set for unknown item \"" + name + "\"")
return CCCharlesItem(name, classification, item_id, self.player)
def create_items(self) -> None:
self.multiworld.itempool += [self.create_item(item) for item in full_item_list]
def set_rules(self) -> None:
set_rules(self.multiworld, self.options, self.player)
def get_filler_item_name(self) -> str:
return "Scraps"

View File

@@ -0,0 +1,39 @@
# Choo-Choo Charles
## Game page in other languages
* [Français](fr)
## Where is the options page?
The [Player Options page](../player-options) contains all the options to configure and export a yaml config file.
## What does randomization do to this game?
All scraps or any collectable item on the ground (except from Loot Crates) and items received from NPCs missions are considered as locations to check.
## What is the goal of Choo-Choo Charles when randomized?
Beating the evil train from Hell named "Charles".
## How is the game managed in Nightmare mode?
At death, the player has to restart a brand-new game, giving him the choice to stay under the Nightmare mode or continuing with the Normal mode if considered too hard.
In this case, all collected items will be redistributed in the inventory and the missions states will be kept.
The Deathlink is not implemented yet. When this option will be available, a choice will be provided to:
* Disable the Deathlink
* Enable the soft Deathlink with respawn at Player Train when a Deathlink event is received
* Enable the hard Deathlink with removal of the game save when a Deathlink event is received
## What does another world's item look like in Choo-Choo Charles?
Items appearance are kept unchanged.
Any hint that cannot be normally represented in the game is replaced by the miniaturized "DeathDuck" Easter Egg that can be seen out from the physical wall limits of the original game.
## How is the player informed by an item transmission and hints?
A message appears in game to inform what item is sent or received, including which world and what player the item comes from.
The same method is used for hints.
## Is it possible to use hints in the game?
No, this is a work in progress.
The following options will be possible once the implementations are available:
At any moment, the player can press one of the following keys to display a console in the game:
* "~" or "`" (qwerty)
* "²" (azerty)
* "F10"
Then, a hint can be revealed by typing "/hint [player] <item>".

View File

@@ -0,0 +1,36 @@
# Choo-Choo Charles
## Où est la page d'options ?
La [page d'options du joueur pour ce jeu](../player-options) contient toutes les options pour configurer et exporter un fichier de configuration yaml.
## Qu'est ce que la randomisation fait au jeu ?
Tous les débrits ou n'importe quel objet ramassable au sol (excepté les Caisses à Butin) et objets reçus par les missions de PNJs sont considérés comme emplacements à vérifier.
## Quel est le but de Choo-Choo Charles lorsqu'il est randomisé ?
Vaincre le train démoniaque de l'Enfer nommé "Charles".
## Comment le jeu est-il géré en mode Nightmare ?
À sa mort, le joueur doit relancer une toute nouvelle partie, lui donnant la possisilité de rester en mode Nightmare ou de poursuivre la partie en mode Normal s'il considère la partie trop difficile.
Dans ce cas, tous les objets collectés seront redistribués dans l'inventaire et les états des missions seront conservés.
Le Deathlink n'est pas implémenté pour l'instant. Lorsque cette option sera disponible, un choix sera fourni pour :
* Désactiver le Deathlink
* Activer le Deathlink modéré avec réapparition au Train du Joueur lorsqu'un évènement Deathlink est reçu
* Activer le Deathlink strict avec suppression de la sauvegarde lorsqu'un évènement Deathlink est reçu
## À quoi ressemble un objet d'un autre monde dans Choo-Choo Charles ?
Les apparances des objets sont conservés.
Tout indice qui ne peut pas être représenté normalement dans le jeu est remplacé par l'Easter Egg "DeadDuck" miniaturisé qui peut être vu en dehors des limites murales physiques du jeu original.
## Comment le joueur est-il informé par une transmission d'objet et des indices ?
Un message apparaît en jeu pour informer quel objet est envoyé ou reçu, incluant de quel monde et de quel joueur vient l'objet.
La même méthode est utilisée pour les indices.
## Est-il possible d'utiliser les indices dans le jeu ?
Non, ceci est un travail en cours.
Les options suivantes seront possibles une fois les implémentations disponibles :
À n'importe quel moment, le joueur peu appuyer sur l'une des touches suivantes pour afficher la console dans le jeu :
* "~" (qwerty)
* "²" (azerty)
* "F10"
Puis, un indice peut être révélé en tapant "/hint [player] <item>"

View File

@@ -0,0 +1,52 @@
# Choo-Choo Charles MultiWorld Setup Guide
This page is a simplified guide of the [Choo-Choo Charles Multiworld Randomizer Mod page](https://github.com/lgbarrere/CCCharles-Random?tab=readme-ov-file#cccharles-random).
## Requirements and Required Softwares
* A computer running Windows (the Mod is not handled by Linux or Mac)
* [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
* A legal copy of the Choo-Choo Charles original game (can be found on [Steam](https://store.steampowered.com/app/1766740/ChooChoo_Charles/))
## Mod Installation for playing
### Mod Download
All the required files of the Mod can be found in the [Releases](https://github.com/lgbarrere/CCCharles-Random/releases).
To use the Mod, download and unzip **CCCharles_Random.zip** somewhere safe, then follow the instructions in the next sections of this guide. This archive contains:
* The **Obscure/** folder loading the Mod itself, it runs the code handling all the randomized elements
* The **cccharles.apworld** file containing the randomization logic, used by the host to generate a random seed with the others games
### Game Setup
The Mod can be installed and played by following these steps (see the [Mod Download](setup_en#mod-download) section to get **CCCharles_Random.zip**):
1. Copy the **Obscure/** folder from **CCCharles_Random.zip** to **\<GameFolder\>** (where the **Obscure/** folder and **Obscure.exe** are placed)
2. Launch the game, if "OFFLINE" is visible in the upper-right corner of the screen, the Mod is working
### Create a Config (.yaml) File
The purpose of a YAML file is described in the [Basic Multiworld Setup Guide](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game).
The [Player Options page](/games/Choo-Choo%20Charles/player-options) allows to configure personal options and export a config YAML file.
## Joining a MultiWorld Game
Before playing, it is highly recommended to check out the **[Known Issues](setup_en#known-issues)** section
* The game console must be opened to type Archipelago commands, press "F10" key or "`" (or "~") key in querty ("²" key in azerty)
* Type ``/connect <IP> <PlayerName>`` with \<IP\> and \<PlayerName\> found on the hosting Archipelago web page in the form ``archipelago.gg:XXXXX`` and ``CCCharles``
* Disconnection is automatic at game closure but can be manually done with ``/disconnect``
## Hosting a MultiWorld or Single-Player Game
See the [Mod Download](setup_en#mod-download) section to get the **cccharles.apworld** file.
In this section, **Archipelago/** refers to the path where [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) is installed locally.
Follow these steps to host a remote multiplayer or a local single-player session:
1. Double-click the **cccharles.apworld** to automatically install the world randomization logic
2. Put the **CCCharles.yaml** to **Archipelago/Players/** with the YAML of each player to host
3. Launch the Archipelago launcher and click "Generate" to configure a game with the YAMLs in **Archipelago/output/**
4. For a multiplayer session, go to the [Archipelago HOST GAME page](https://archipelago.gg/uploads)
5. Click "Upload File" and select the generated **AP_\<seed\>.zip** in **Archipelago/output/**
6. Send the generated room page to each player
For a local single-player session, click "Host" in the Archipelago launcher by using the generated **AP_\<seed\>.zip** in **Archipelago/output/**
## Known Issues
### Major issues
No major issue found.
### Minor issues
* The current version of the command parser does not accept console commands with a player names containing whitespaces. It is recommended to use underscores "_" instead, for instance: CCCharles_Player_1.

View File

@@ -0,0 +1,52 @@
# Guide d'Installation du MultiWorld Choo-Choo Charles
Cette page est un guide simplifié de la [page du Mod Randomiseur Multiworld de Choo-Choo Charles](https://github.com/lgbarrere/CCCharles-Random?tab=readme-ov-file#cccharles-random).
## Exigences et Logiciels Nécessaires
* Un ordinateur utilisant Windows (le Mod n'est pas utilisable sous Linux ou Mac)
* [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
* Une copie légale du jeu original Choo-Choo Charles (peut être trouvé sur [Steam](https://store.steampowered.com/app/1766740/ChooChoo_Charles/)
## Installation du Mod pour jouer
### Téléchargement du Mod
Tous les fichiers nécessaires du Mod se trouvent dans les [Releases](https://github.com/lgbarrere/CCCharles-Random/releases).
Pour utiliser le Mod, télécharger et désarchiver **CCCharles_Random.zip** à un endroit sûr, puis suivre les instructions dans les sections suivantes de ce guide. Cette archive contient :
* Le dossier **Obscure/** qui charge le Mod lui-même, il lance le code qui gère tous les éléments randomisés
* Le fichier **cccharles.apworld** qui contient la logique de randomisation, utilisé par l'hôte pour générer une graine aléatoire avec les autres jeux
### Préparation du Jeu
Le Mod peut être installé et joué en suivant les étapes suivantes (voir la section [Téléchargement du Mod](setup_fr#téléchargement-du-mod) pour récupérer **CCCharles_Random.zip**) :
1. Copier le dossier **Obscure/** de **CCCharles_Random.zip** vers **\<GameFolder\>** (où se situent le dossier **Obscure/** et **Obscure.exe**)
2. Lancer le jeu, si "OFFLINE" est visible dans le coin en haut à droite de l'écran, le Mod est actif
### Créer un Fichier de Configuration (.yaml)
L'objectif d'un fichier YAML est décrit dans le [Guide d'Installation Basique du Multiworld](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game) (en anglais).
La [page d'Options Joueur](/games/Choo-Choo%20Charles/player-options) permet de configurer des options personnelles et exporter un fichier de configuration YAML.
## Rejoindre une Partie MultiWorld
Avant de jouer, il est fortement recommandé de consulter la section **[Problèmes Connus](setup_fr#probl%C3%A8mes-connus)**.
* La console du jeu doit être ouverte pour taper des commandes Archipelago, appuyer sur la touche "F10" ou "`" (ou "~") en querty (touche "²" en azerty)
* Taper ``/connect <IP> <NomDuJoueur>`` avec \<IP\> et \<NomDuJoueur\> trouvés sur la page web d'hébergement Archipelago sous la forme ``archipelago.gg:XXXXX`` et ``CCCharles``
* La déconnexion est automatique à la fermeture du jeu mais peut être faite manuellement avec ``/disconnect``
## Héberger une partie MultiWorld ou un Seul Joueur
Voir la section [Téléchargement du Mod](setup_fr#téléchargement-du-mod) pour récupérer le fichier **cccharles.apworld**.
Dans cette section, **Archipelago/** fait référence au chemin d'accès où [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) est installé localement.
Suivre ces étapes pour héberger une session multijoueur à distance ou locale pour un seul joueur :
1. Double-cliquer sur **cccharles.apworld** pour installer automatiquement la logique de randomisation du monde
2. Placer le **CCCharles.yaml** dans **Archipelago/Players/** avec le YAML de chaque joueur à héberger
3. Exécuter le lanceur Archipelago et cliquer sur "Generate" pour configurer une partie avec les YAML dans **Archipelago/output/**
4. Pour une session multijoueur, aller à la [page Archipelago HOST GAME](https://archipelago.gg/uploads)
5. Cliquer sur "Upload File" et selectionner le **AP_\<seed\>.zip** généré dans **Archipelago/output/**
6. Envoyer la page de la partie générée à chaque joueur
Pour une session locale à un seul joueur, cliquer sur "Host" dans le lanceur Archipelago en utilisant **AP_\<seed\>.zip** généré dans **Archipelago/output/**
## Problèmes Connus
### Problèmes majeurs
Aucun problème majeur trouvé.
### Problèmes mineurs
* La version actuelle de l'analyseur de commandes n'accepte pas des commandes de la console dont le nom du joueur contient des espaces. Il est recommandé d'utiliser des soulignés "_" à la place, par exemple : CCCharles_Player_1.

View File

@@ -0,0 +1,27 @@
from BaseClasses import CollectionState
from .bases import CCCharlesTestBase
class TestAccess(CCCharlesTestBase):
def test_claire_breakers(self) -> None:
"""Test locations that require 4 Breakers"""
lighthouse_claire_mission_end = self.world.get_location("Lighthouse Claire Mission End")
state = CollectionState(self.multiworld)
self.collect_all_but("Breaker")
breakers_in_pool = self.get_items_by_name("Breaker")
self.assertGreaterEqual(len(breakers_in_pool), 4) # Check at least 4 Breakers are in the item pool
for breaker in breakers_in_pool[:3]:
state.collect(breaker) # Collect 3 Breakers into state
self.assertFalse(
lighthouse_claire_mission_end.can_reach(state),
"Lighthouse Claire Mission End should not be reachable with only three Breakers"
)
state.collect(breakers_in_pool[3]) # Collect 4th breaker into state
self.assertTrue(
lighthouse_claire_mission_end.can_reach(state),
"Lighthouse Claire Mission End should have been reachable with four Breakers"
)

View File

View File

@@ -0,0 +1,5 @@
from test.bases import WorldTestBase
class CCCharlesTestBase(WorldTestBase):
game = "Choo-Choo Charles"

View File

@@ -27,7 +27,7 @@ strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.strawberry_8: Celeste64LocationData(RegionName.nw_girders_island, celeste_64_base_id + 0x07),
LocationName.strawberry_9: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x08),
LocationName.strawberry_10: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x09),
LocationName.strawberry_11: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x0A),
LocationName.strawberry_11: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0A),
LocationName.strawberry_12: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x0B),
LocationName.strawberry_13: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0C),
LocationName.strawberry_14: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0D),

View File

@@ -82,12 +82,10 @@ location_hard_moves_logic: Dict[str, List[List[str]]] = {
[ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_15: [[ItemName.feather],
[ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
LocationName.strawberry_17: [[ItemName.double_dash_refill]],
LocationName.strawberry_18: [[ItemName.air_dash, ItemName.climb],
[ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_19: [[ItemName.air_dash, ItemName.skid_jump],
[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash],
[ItemName.spring, ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_19: [[ItemName.air_dash]],
LocationName.strawberry_20: [[ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],

View File

@@ -0,0 +1,47 @@
# Celeste - Changelog
## v1.0 - First Stable Release
### Features:
- Goal is to collect a certain number of Strawberries, finish your chosen Goal Area, and reach the credits in the Epilogue
- Locations included:
- Level Clears
- Strawberries
- Crystal Hearts
- Cassettes
- Golden Strawberries
- Keys
- Checkpoints
- Summit Gems
- Cars
- Binoculars
- Rooms
- Items included:
- 34 different interactable objects
- Keys
- Checkpoints
- Summit Gems
- Crystal Hearts
- Cassettes
- Traps
- Bald Trap
- Literature Trap
- Stun Trap
- Invisible Trap
- Fast Trap
- Slow Trap
- Ice Trap
- Reverse Trap
- Screen Flip Trap
- Laughter Trap
- Hiccup Trap
- Zoom Trap
- Aesthetic Options:
- Music Shuffle
- Require Cassette items to hear music
- Hair Length/Color options
- Death Link
- Amnesty option to select how many deaths must occur to send a DeathLink
- Trap Link

View File

@@ -0,0 +1,264 @@
from typing import NamedTuple, Optional
from BaseClasses import Item, ItemClassification
from .Names import ItemName
level_item_lists: dict[str, set[str]] = {
"0a": set(),
"1a": {ItemName.springs, ItemName.traffic_blocks, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"1b": {ItemName.springs, ItemName.traffic_blocks, ItemName.dash_refills, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"1c": {ItemName.traffic_blocks, ItemName.dash_refills, ItemName.coins},
"2a": {ItemName.springs, ItemName.dream_blocks, ItemName.traffic_blocks, ItemName.strawberry_seeds, ItemName.dash_refills, ItemName.coins},
"2b": {ItemName.springs, ItemName.dream_blocks, ItemName.dash_refills, ItemName.coins, ItemName.blue_cassette_blocks},
"2c": {ItemName.springs, ItemName.dream_blocks, ItemName.dash_refills, ItemName.coins},
"3a": {ItemName.springs, ItemName.moving_platforms, ItemName.sinking_platforms, ItemName.dash_refills, ItemName.coins, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"3b": {ItemName.springs, ItemName.dash_refills, ItemName.sinking_platforms, ItemName.coins, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"3c": {ItemName.dash_refills, ItemName.sinking_platforms, ItemName.coins},
"4a": {ItemName.blue_clouds, ItemName.blue_boosters, ItemName.moving_platforms, ItemName.coins, ItemName.strawberry_seeds, ItemName.springs, ItemName.move_blocks, ItemName.pink_clouds, ItemName.white_block, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"4b": {ItemName.blue_boosters, ItemName.moving_platforms, ItemName.move_blocks, ItemName.springs, ItemName.coins, ItemName.blue_clouds, ItemName.pink_clouds, ItemName.dash_refills, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"4c": {ItemName.blue_boosters, ItemName.move_blocks, ItemName.dash_refills, ItemName.pink_clouds},
"5a": {ItemName.swap_blocks, ItemName.red_boosters, ItemName.dash_switches, ItemName.dash_refills, ItemName.coins, ItemName.springs, ItemName.torches, ItemName.seekers, ItemName.theo_crystal, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"5b": {ItemName.swap_blocks, ItemName.red_boosters, ItemName.dash_switches, ItemName.dash_refills, ItemName.coins, ItemName.springs, ItemName.torches, ItemName.seekers, ItemName.theo_crystal, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"5c": {ItemName.swap_blocks, ItemName.red_boosters, ItemName.dash_switches, ItemName.dash_refills},
"6a": {ItemName.feathers, ItemName.kevin_blocks, ItemName.dash_refills, ItemName.bumpers, ItemName.springs, ItemName.coins, ItemName.badeline_boosters, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"6b": {ItemName.feathers, ItemName.kevin_blocks, ItemName.dash_refills, ItemName.bumpers, ItemName.coins, ItemName.springs, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"6c": {ItemName.feathers, ItemName.kevin_blocks, ItemName.dash_refills, ItemName.bumpers},
"7a": {ItemName.springs, ItemName.dash_refills, ItemName.badeline_boosters, ItemName.traffic_blocks, ItemName.coins, ItemName.dream_blocks, ItemName.sinking_platforms, ItemName.blue_boosters, ItemName.blue_clouds, ItemName.pink_clouds, ItemName.move_blocks, ItemName.moving_platforms, ItemName.swap_blocks, ItemName.red_boosters, ItemName.dash_switches, ItemName.feathers, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"7b": {ItemName.springs, ItemName.dash_refills, ItemName.badeline_boosters, ItemName.traffic_blocks, ItemName.coins, ItemName.dream_blocks, ItemName.moving_platforms, ItemName.blue_boosters, ItemName.blue_clouds, ItemName.pink_clouds, ItemName.move_blocks, ItemName.swap_blocks, ItemName.red_boosters, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"7c": {ItemName.springs, ItemName.dash_refills, ItemName.badeline_boosters, ItemName.coins, ItemName.pink_clouds},
# Epilogue
"8a": set(),
# Core
"9a": {ItemName.springs, ItemName.dash_refills, ItemName.fire_ice_balls, ItemName.bumpers, ItemName.core_toggles, ItemName.core_blocks, ItemName.coins, ItemName.badeline_boosters, ItemName.feathers, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"9b": {ItemName.springs, ItemName.dash_refills, ItemName.fire_ice_balls, ItemName.bumpers, ItemName.core_toggles, ItemName.core_blocks, ItemName.coins, ItemName.badeline_boosters, ItemName.dream_blocks, ItemName.moving_platforms, ItemName.blue_clouds, ItemName.swap_blocks, ItemName.kevin_blocks, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks},
"9c": {ItemName.dash_refills, ItemName.bumpers, ItemName.core_toggles, ItemName.core_blocks, ItemName.traffic_blocks, ItemName.dream_blocks, ItemName.pink_clouds, ItemName.swap_blocks, ItemName.kevin_blocks},
# Farewell Pre/Post Empty Space
"10a": {ItemName.blue_clouds, ItemName.badeline_boosters, ItemName.dash_refills, ItemName.double_dash_refills, ItemName.swap_blocks, ItemName.springs, ItemName.pufferfish, ItemName.coins, ItemName.dream_blocks, ItemName.jellyfish, ItemName.red_boosters, ItemName.dash_switches, ItemName.move_blocks, ItemName.breaker_boxes, ItemName.traffic_blocks},
"10b": {ItemName.dream_blocks, ItemName.badeline_boosters, ItemName.bird, ItemName.dash_refills, ItemName.double_dash_refills, ItemName.kevin_blocks, ItemName.coins, ItemName.traffic_blocks, ItemName.move_blocks, ItemName.blue_boosters, ItemName.springs, ItemName.feathers, ItemName.swap_blocks, ItemName.red_boosters, ItemName.core_blocks, ItemName.fire_ice_balls, ItemName.kevin_blocks, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks, ItemName.yellow_cassette_blocks, ItemName.green_cassette_blocks, ItemName.breaker_boxes, ItemName.pufferfish, ItemName.jellyfish},
"10c": {ItemName.badeline_boosters, ItemName.double_dash_refills, ItemName.springs, ItemName.pufferfish, ItemName.jellyfish},
}
level_cassette_items: dict[str, str] = {
"0a": ItemName.prologue_cassette,
"1a": ItemName.fc_a_cassette,
"1b": ItemName.fc_b_cassette,
"1c": ItemName.fc_c_cassette,
"2a": ItemName.os_a_cassette,
"2b": ItemName.os_b_cassette,
"2c": ItemName.os_c_cassette,
"3a": ItemName.cr_a_cassette,
"3b": ItemName.cr_b_cassette,
"3c": ItemName.cr_c_cassette,
"4a": ItemName.gr_a_cassette,
"4b": ItemName.gr_b_cassette,
"4c": ItemName.gr_c_cassette,
"5a": ItemName.mt_a_cassette,
"5b": ItemName.mt_b_cassette,
"5c": ItemName.mt_c_cassette,
"6a": ItemName.ref_a_cassette,
"6b": ItemName.ref_b_cassette,
"6c": ItemName.ref_c_cassette,
"7a": ItemName.sum_a_cassette,
"7b": ItemName.sum_b_cassette,
"7c": ItemName.sum_c_cassette,
"8a": ItemName.epilogue_cassette,
"9a": ItemName.core_a_cassette,
"9b": ItemName.core_b_cassette,
"9c": ItemName.core_c_cassette,
"10a":ItemName.farewell_cassette,
}
celeste_base_id: int = 0xCA10000
class CelesteItem(Item):
game = "Celeste"
class CelesteItemData(NamedTuple):
code: Optional[int] = None
type: ItemClassification = ItemClassification.filler
collectable_item_data_table: dict[str, CelesteItemData] = {
ItemName.strawberry: CelesteItemData(celeste_base_id + 0x0, ItemClassification.progression_skip_balancing),
ItemName.raspberry: CelesteItemData(celeste_base_id + 0x1, ItemClassification.filler),
}
goal_item_data_table: dict[str, CelesteItemData] = {
ItemName.house_keys: CelesteItemData(celeste_base_id + 0x10, ItemClassification.progression_skip_balancing),
}
trap_item_data_table: dict[str, CelesteItemData] = {
ItemName.bald_trap: CelesteItemData(celeste_base_id + 0x20, ItemClassification.trap),
ItemName.literature_trap: CelesteItemData(celeste_base_id + 0x21, ItemClassification.trap),
ItemName.stun_trap: CelesteItemData(celeste_base_id + 0x22, ItemClassification.trap),
ItemName.invisible_trap: CelesteItemData(celeste_base_id + 0x23, ItemClassification.trap),
ItemName.fast_trap: CelesteItemData(celeste_base_id + 0x24, ItemClassification.trap),
ItemName.slow_trap: CelesteItemData(celeste_base_id + 0x25, ItemClassification.trap),
ItemName.ice_trap: CelesteItemData(celeste_base_id + 0x26, ItemClassification.trap),
ItemName.reverse_trap: CelesteItemData(celeste_base_id + 0x28, ItemClassification.trap),
ItemName.screen_flip_trap: CelesteItemData(celeste_base_id + 0x29, ItemClassification.trap),
ItemName.laughter_trap: CelesteItemData(celeste_base_id + 0x2A, ItemClassification.trap),
ItemName.hiccup_trap: CelesteItemData(celeste_base_id + 0x2B, ItemClassification.trap),
ItemName.zoom_trap: CelesteItemData(celeste_base_id + 0x2C, ItemClassification.trap),
}
checkpoint_item_data_table: dict[str, CelesteItemData] = {}
key_item_data_table: dict[str, CelesteItemData] = {}
gem_item_data_table: dict[str, CelesteItemData] = {}
interactable_item_data_table: dict[str, CelesteItemData] = {
ItemName.springs: CelesteItemData(celeste_base_id + 0x2000 + 0x00, ItemClassification.progression),
ItemName.traffic_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x01, ItemClassification.progression),
ItemName.pink_cassette_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x02, ItemClassification.progression),
ItemName.blue_cassette_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x03, ItemClassification.progression),
ItemName.dream_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x04, ItemClassification.progression),
ItemName.coins: CelesteItemData(celeste_base_id + 0x2000 + 0x05, ItemClassification.progression),
ItemName.strawberry_seeds: CelesteItemData(celeste_base_id + 0x2000 + 0x1F, ItemClassification.progression),
ItemName.sinking_platforms: CelesteItemData(celeste_base_id + 0x2000 + 0x20, ItemClassification.progression),
ItemName.moving_platforms: CelesteItemData(celeste_base_id + 0x2000 + 0x06, ItemClassification.progression),
ItemName.blue_boosters: CelesteItemData(celeste_base_id + 0x2000 + 0x07, ItemClassification.progression),
ItemName.blue_clouds: CelesteItemData(celeste_base_id + 0x2000 + 0x08, ItemClassification.progression),
ItemName.move_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x09, ItemClassification.progression),
ItemName.white_block: CelesteItemData(celeste_base_id + 0x2000 + 0x21, ItemClassification.progression),
ItemName.swap_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x0A, ItemClassification.progression),
ItemName.red_boosters: CelesteItemData(celeste_base_id + 0x2000 + 0x0B, ItemClassification.progression),
ItemName.torches: CelesteItemData(celeste_base_id + 0x2000 + 0x22, ItemClassification.useful),
ItemName.theo_crystal: CelesteItemData(celeste_base_id + 0x2000 + 0x0C, ItemClassification.progression),
ItemName.feathers: CelesteItemData(celeste_base_id + 0x2000 + 0x0D, ItemClassification.progression),
ItemName.bumpers: CelesteItemData(celeste_base_id + 0x2000 + 0x0E, ItemClassification.progression),
ItemName.kevin_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x0F, ItemClassification.progression),
ItemName.pink_clouds: CelesteItemData(celeste_base_id + 0x2000 + 0x10, ItemClassification.progression),
ItemName.badeline_boosters: CelesteItemData(celeste_base_id + 0x2000 + 0x11, ItemClassification.progression),
ItemName.fire_ice_balls: CelesteItemData(celeste_base_id + 0x2000 + 0x12, ItemClassification.progression),
ItemName.core_toggles: CelesteItemData(celeste_base_id + 0x2000 + 0x13, ItemClassification.progression),
ItemName.core_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x14, ItemClassification.progression),
ItemName.pufferfish: CelesteItemData(celeste_base_id + 0x2000 + 0x15, ItemClassification.progression),
ItemName.jellyfish: CelesteItemData(celeste_base_id + 0x2000 + 0x16, ItemClassification.progression),
ItemName.breaker_boxes: CelesteItemData(celeste_base_id + 0x2000 + 0x17, ItemClassification.progression),
ItemName.dash_refills: CelesteItemData(celeste_base_id + 0x2000 + 0x18, ItemClassification.progression),
ItemName.double_dash_refills: CelesteItemData(celeste_base_id + 0x2000 + 0x19, ItemClassification.progression),
ItemName.yellow_cassette_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x1A, ItemClassification.progression),
ItemName.green_cassette_blocks: CelesteItemData(celeste_base_id + 0x2000 + 0x1B, ItemClassification.progression),
ItemName.bird: CelesteItemData(celeste_base_id + 0x2000 + 0x23, ItemClassification.progression),
ItemName.dash_switches: CelesteItemData(celeste_base_id + 0x2000 + 0x1C, ItemClassification.progression),
ItemName.seekers: CelesteItemData(celeste_base_id + 0x2000 + 0x1D, ItemClassification.progression),
}
cassette_item_data_table: dict[str, CelesteItemData] = {
ItemName.prologue_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x00, ItemClassification.filler),
ItemName.fc_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x01, ItemClassification.filler),
ItemName.fc_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x02, ItemClassification.filler),
ItemName.fc_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x03, ItemClassification.filler),
ItemName.os_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x04, ItemClassification.filler),
ItemName.os_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x05, ItemClassification.filler),
ItemName.os_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x06, ItemClassification.filler),
ItemName.cr_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x07, ItemClassification.filler),
ItemName.cr_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x08, ItemClassification.filler),
ItemName.cr_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x09, ItemClassification.filler),
ItemName.gr_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x0A, ItemClassification.filler),
ItemName.gr_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x0B, ItemClassification.filler),
ItemName.gr_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x0C, ItemClassification.filler),
ItemName.mt_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x0D, ItemClassification.filler),
ItemName.mt_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x0E, ItemClassification.filler),
ItemName.mt_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x0F, ItemClassification.filler),
ItemName.ref_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x10, ItemClassification.filler),
ItemName.ref_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x11, ItemClassification.filler),
ItemName.ref_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x12, ItemClassification.filler),
ItemName.sum_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x13, ItemClassification.filler),
ItemName.sum_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x14, ItemClassification.filler),
ItemName.sum_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x15, ItemClassification.filler),
ItemName.epilogue_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x16, ItemClassification.filler),
ItemName.core_a_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x17, ItemClassification.filler),
ItemName.core_b_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x18, ItemClassification.filler),
ItemName.core_c_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x19, ItemClassification.filler),
ItemName.farewell_cassette: CelesteItemData(celeste_base_id + 0x1000 + 0x1A, ItemClassification.filler),
}
crystal_heart_item_data_table: dict[str, CelesteItemData] = {
ItemName.crystal_heart_1: CelesteItemData(celeste_base_id + 0x3000 + 0x00, ItemClassification.filler),
ItemName.crystal_heart_2: CelesteItemData(celeste_base_id + 0x3000 + 0x01, ItemClassification.filler),
ItemName.crystal_heart_3: CelesteItemData(celeste_base_id + 0x3000 + 0x02, ItemClassification.filler),
ItemName.crystal_heart_4: CelesteItemData(celeste_base_id + 0x3000 + 0x03, ItemClassification.filler),
ItemName.crystal_heart_5: CelesteItemData(celeste_base_id + 0x3000 + 0x04, ItemClassification.filler),
ItemName.crystal_heart_6: CelesteItemData(celeste_base_id + 0x3000 + 0x05, ItemClassification.filler),
ItemName.crystal_heart_7: CelesteItemData(celeste_base_id + 0x3000 + 0x06, ItemClassification.filler),
ItemName.crystal_heart_8: CelesteItemData(celeste_base_id + 0x3000 + 0x07, ItemClassification.filler),
ItemName.crystal_heart_9: CelesteItemData(celeste_base_id + 0x3000 + 0x08, ItemClassification.filler),
ItemName.crystal_heart_10: CelesteItemData(celeste_base_id + 0x3000 + 0x09, ItemClassification.filler),
ItemName.crystal_heart_11: CelesteItemData(celeste_base_id + 0x3000 + 0x0A, ItemClassification.filler),
ItemName.crystal_heart_12: CelesteItemData(celeste_base_id + 0x3000 + 0x0B, ItemClassification.filler),
ItemName.crystal_heart_13: CelesteItemData(celeste_base_id + 0x3000 + 0x0C, ItemClassification.filler),
ItemName.crystal_heart_14: CelesteItemData(celeste_base_id + 0x3000 + 0x0D, ItemClassification.filler),
ItemName.crystal_heart_15: CelesteItemData(celeste_base_id + 0x3000 + 0x0E, ItemClassification.filler),
ItemName.crystal_heart_16: CelesteItemData(celeste_base_id + 0x3000 + 0x0F, ItemClassification.filler),
}
def add_checkpoint_to_table(id: int, name: str):
checkpoint_item_data_table[name] = CelesteItemData(id, ItemClassification.progression)
def add_key_to_table(id: int, name: str):
key_item_data_table[name] = CelesteItemData(id, ItemClassification.progression)
def add_gem_to_table(id: int, name: str):
gem_item_data_table[name] = CelesteItemData(id, ItemClassification.progression)
def generate_item_data_table() -> dict[str, CelesteItemData]:
return {**collectable_item_data_table,
**goal_item_data_table,
**trap_item_data_table,
**checkpoint_item_data_table,
**key_item_data_table,
**gem_item_data_table,
**cassette_item_data_table,
**crystal_heart_item_data_table,
**interactable_item_data_table}
def generate_item_table() -> dict[str, int]:
return {name: data.code for name, data in generate_item_data_table().items() if data.code is not None}
def generate_item_groups() -> dict[str, list[str]]:
item_groups: dict[str, list[str]] = {
"Collectables": list(collectable_item_data_table.keys()),
"Traps": list(trap_item_data_table.keys()),
"Checkpoints": list(checkpoint_item_data_table.keys()),
"Keys": list(key_item_data_table.keys()),
"Gems": list(gem_item_data_table.keys()),
"Cassettes": list(cassette_item_data_table.keys()),
"Crystal Hearts": list(crystal_heart_item_data_table.keys()),
"Interactables": list(interactable_item_data_table.keys()),
# Commonly mistaken names
"Green Boosters": [ItemName.blue_boosters],
"Green Bubbles": [ItemName.blue_boosters],
"Blue Bubbles": [ItemName.blue_boosters],
"Red Bubbles": [ItemName.red_boosters],
"Touch Switches": [ItemName.coins],
}
return item_groups

View File

@@ -0,0 +1,208 @@
from __future__ import annotations
from enum import IntEnum
from BaseClasses import CollectionState
goal_area_option_to_name: dict[int, str] = {
0: "7a",
1: "7b",
2: "7c",
3: "9a",
4: "9b",
5: "9c",
6: "10a",
7: "10b",
8: "10c",
}
goal_area_option_to_display_name: dict[int, str] = {
0: "The Summit A",
1: "The Summit B",
2: "The Summit C",
3: "Core A",
4: "Core B",
5: "Core C",
6: "Farewell",
7: "Farewell",
8: "Farewell",
}
goal_area_to_location_name: dict[str, str] = {
"7a": "The Summit A - Level Clear",
"7b": "The Summit B - Level Clear",
"7c": "The Summit C - Level Clear",
"9a": "Core A - Level Clear",
"9b": "Core B - Level Clear",
"9c": "Core C - Level Clear",
"10a": "Farewell - Crystal Heart?",
"10b": "Farewell - Level Clear",
"10c": "Farewell - Golden Strawberry",
}
class LocationType(IntEnum):
strawberry = 0
golden_strawberry = 1
cassette = 2
crystal_heart = 3
checkpoint = 4
level_clear = 5
key = 6
binoculars = 7
room_enter = 8
clutter = 9
gem = 10
car = 11
class DoorDirection(IntEnum):
up = 0
right = 1
down = 2
left = 3
special = 4
class Door:
name: str
room_name: str
room: Room
dir: DoorDirection
blocked: bool
closes_behind: bool
region: PreRegion
def __init__(self, name: str, room_name: str, dir: DoorDirection, blocked: bool, closes_behind: bool):
self.name = name
self.room_name = room_name
self.dir = dir
self.blocked = blocked
self.closes_behind = closes_behind
# Find PreRegion later using our name once we know it exists
class PreRegion:
name: str
room_name: str
room: Room
connections: list[RegionConnection]
locations: list[LevelLocation]
def __init__(self, name: str, room_name: str, connections: list[RegionConnection], locations: list[LevelLocation]):
self.name = name
self.room_name = room_name
self.connections = connections.copy()
self.locations = locations.copy()
for loc in self.locations:
loc.region = self
class RegionConnection:
source_name: str
source: PreRegion
destination_name: str
destination: PreRegion
possible_access: list[list[str]]
def __init__(self, source_name: str, destination_name: str, possible_access: list[list[str]] = []):
self.source_name = source_name
self.destination_name = destination_name
self.possible_access = possible_access.copy()
class LevelLocation:
name: str
display_name: str
region_name: str
region: PreRegion
loc_type: LocationType
possible_access: list[list[str]]
def __init__(self, name: str, display_name: str, region_name: str, loc_type: LocationType, possible_access: list[list[str]] = []):
self.name = name
self.display_name = display_name
self.region_name = region_name
self.loc_type = loc_type
self.possible_access = possible_access.copy()
class Room:
level_name: str
name: str
display_name: str
regions: list[PreRegion]
doors: list[Door]
checkpoint: str
checkpoint_region: str
def __init__(self, level_name: str, name: str, display_name: str, regions: list[PreRegion], doors: list[Door], checkpoint: str = None, checkpoint_region: str = None):
self.level_name = level_name
self.name = name
self.display_name = display_name
self.regions = regions.copy()
self.doors = doors.copy()
self.checkpoint = checkpoint
self.checkpoint_region = checkpoint_region
from .data.CelesteLevelData import all_regions
for reg in self.regions:
reg.room = self
for reg_con in reg.connections:
reg_con.source = reg
reg_con.destination = all_regions[reg_con.destination_name]
for door in self.doors:
door.room = self
class RoomConnection:
level_name: str
source: Door
dest: Door
two_way: bool
def __init__(self, level_name: str, source: Door, dest: Door):
self.level_name = level_name
self.source = source
self.dest = dest
self.two_way = not self.dest.closes_behind
if (self.source.dir == DoorDirection.left and self.dest.dir != DoorDirection.right or
self.source.dir == DoorDirection.right and self.dest.dir != DoorDirection.left or
self.source.dir == DoorDirection.up and self.dest.dir != DoorDirection.down or
self.source.dir == DoorDirection.down and self.dest.dir != DoorDirection.up):
raise Exception(f"Door {source.name} ({self.source.dir}) and Door {dest.name} ({self.dest.dir}) have mismatched directions.")
class Level:
name: str
display_name: str
rooms: list[Room]
room_connections: list[RoomConnection]
def __init__(self, name: str, display_name: str, rooms: list[Room], room_connections: list[RoomConnection]):
self.name = name
self.display_name = display_name
self.rooms = rooms.copy()
self.room_connections = room_connections.copy()
def load_logic_data() -> dict[str, Level]:
from .data.CelesteLevelData import all_levels
#for _, level in all_levels.items():
# print(level.display_name)
#
# for room in level.rooms:
# print(" " + room.display_name)
#
# for region in room.regions:
# print(" " + region.name)
#
# for location in region.locations:
# print(" " + location.display_name)
return all_levels

View File

@@ -0,0 +1,281 @@
from typing import NamedTuple, Optional, TYPE_CHECKING
from BaseClasses import Location, Region
from worlds.generic.Rules import set_rule
from .Levels import Level, LocationType
from .Names import ItemName
if TYPE_CHECKING:
from . import CelesteOpenWorld
else:
CelesteOpenWorld = object
celeste_base_id: int = 0xCA10000
class CelesteLocation(Location):
game = "Celeste"
class CelesteLocationData(NamedTuple):
region: str
address: Optional[int] = None
checkpoint_location_data_table: dict[str, CelesteLocationData] = {}
key_location_data_table: dict[str, CelesteLocationData] = {}
location_id_offsets: dict[LocationType, int | None] = {
LocationType.strawberry: celeste_base_id,
LocationType.golden_strawberry: celeste_base_id + 0x1000,
LocationType.cassette: celeste_base_id + 0x2000,
LocationType.car: celeste_base_id + 0x2A00,
LocationType.crystal_heart: celeste_base_id + 0x3000,
LocationType.checkpoint: celeste_base_id + 0x4000,
LocationType.level_clear: celeste_base_id + 0x5000,
LocationType.key: celeste_base_id + 0x6000,
LocationType.gem: celeste_base_id + 0x6A00,
LocationType.binoculars: celeste_base_id + 0x7000,
LocationType.room_enter: celeste_base_id + 0x8000,
LocationType.clutter: None,
}
def generate_location_table() -> dict[str, int]:
from .Levels import Level, LocationType, load_logic_data
level_data: dict[str, Level] = load_logic_data()
location_table = {}
location_counts: dict[LocationType, int] = {
LocationType.strawberry: 0,
LocationType.golden_strawberry: 0,
LocationType.cassette: 0,
LocationType.car: 0,
LocationType.crystal_heart: 0,
LocationType.checkpoint: 0,
LocationType.level_clear: 0,
LocationType.key: 0,
LocationType.gem: 0,
LocationType.binoculars: 0,
LocationType.room_enter: 0,
}
for _, level in level_data.items():
for room in level.rooms:
if room.name != "10b_GOAL":
location_table[room.display_name] = location_id_offsets[LocationType.room_enter] + location_counts[LocationType.room_enter]
location_counts[LocationType.room_enter] += 1
if room.checkpoint is not None and room.checkpoint != "Start":
checkpoint_id: int = location_id_offsets[LocationType.checkpoint] + location_counts[LocationType.checkpoint]
checkpoint_name: str = level.display_name + " - " + room.checkpoint
location_table[checkpoint_name] = checkpoint_id
location_counts[LocationType.checkpoint] += 1
checkpoint_location_data_table[checkpoint_name] = CelesteLocationData(level.display_name, checkpoint_id)
from .Items import add_checkpoint_to_table
add_checkpoint_to_table(checkpoint_id, checkpoint_name)
for region in room.regions:
for location in region.locations:
if location_id_offsets[location.loc_type] is not None:
location_id = location_id_offsets[location.loc_type] + location_counts[location.loc_type]
location_table[location.display_name] = location_id
location_counts[location.loc_type] += 1
if location.loc_type == LocationType.key:
from .Items import add_key_to_table
add_key_to_table(location_id, location.display_name)
if location.loc_type == LocationType.gem:
from .Items import add_gem_to_table
add_gem_to_table(location_id, location.display_name)
return location_table
def create_regions_and_locations(world: CelesteOpenWorld):
menu_region = Region("Menu", world.player, world.multiworld)
world.multiworld.regions.append(menu_region)
world.active_checkpoint_names: list[str] = []
world.goal_checkpoint_names: dict[str, str] = dict()
world.active_key_names: list[str] = []
world.active_gem_names: list[str] = []
world.active_clutter_names: list[str] = []
for _, level in world.level_data.items():
if level.name not in world.active_levels:
continue
for room in level.rooms:
room_region = Region(room.name + "_room", world.player, world.multiworld)
world.multiworld.regions.append(room_region)
for pre_region in room.regions:
region = Region(pre_region.name, world.player, world.multiworld)
world.multiworld.regions.append(region)
for level_location in pre_region.locations:
if level_location.loc_type == LocationType.golden_strawberry:
if level_location.display_name == "Farewell - Golden Strawberry":
if not world.options.goal_area == "farewell_golden":
continue
elif not world.options.include_goldens:
continue
if level_location.loc_type == LocationType.car and not world.options.carsanity:
continue
if level_location.loc_type == LocationType.binoculars and not world.options.binosanity:
continue
if level_location.loc_type == LocationType.key:
world.active_key_names.append(level_location.display_name)
if level_location.loc_type == LocationType.gem:
world.active_gem_names.append(level_location.display_name)
location_rule = None
if len(level_location.possible_access) == 1:
only_access = level_location.possible_access[0]
if len(only_access) == 1:
only_item = level_location.possible_access[0][0]
def location_rule_func(state, only_item=only_item):
return state.has(only_item, world.player)
location_rule = location_rule_func
else:
def location_rule_func(state, only_access=only_access):
return state.has_all(only_access, world.player)
location_rule = location_rule_func
elif len(level_location.possible_access) > 0:
def location_rule_func(state, level_location=level_location):
for sublist in level_location.possible_access:
if state.has_all(sublist, world.player):
return True
return False
location_rule = location_rule_func
if level_location.loc_type == LocationType.clutter:
world.active_clutter_names.append(level_location.display_name)
location = CelesteLocation(world.player, level_location.display_name, None, region)
if location_rule is not None:
set_rule(location, location_rule)
region.locations.append(location)
continue
location = CelesteLocation(world.player, level_location.display_name, world.location_name_to_id[level_location.display_name], region)
if location_rule is not None:
set_rule(location, location_rule)
region.locations.append(location)
for pre_region in room.regions:
region = world.get_region(pre_region.name)
for connection in pre_region.connections:
connection_rule = None
if len(connection.possible_access) == 1:
only_access = connection.possible_access[0]
if len(only_access) == 1:
only_item = connection.possible_access[0][0]
def connection_rule_func(state, only_item=only_item):
return state.has(only_item, world.player)
connection_rule = connection_rule_func
else:
def connection_rule_func(state, only_access=only_access):
return state.has_all(only_access, world.player)
connection_rule = connection_rule_func
elif len(connection.possible_access) > 0:
def connection_rule_func(state, connection=connection):
for sublist in connection.possible_access:
if state.has_all(sublist, world.player):
return True
return False
connection_rule = connection_rule_func
if connection_rule is None:
region.add_exits([connection.destination_name])
else:
region.add_exits([connection.destination_name], {connection.destination_name: connection_rule})
region.add_exits([room_region.name])
if room.checkpoint != None:
if room.checkpoint == "Start":
if world.options.lock_goal_area and (level.name == world.goal_area or (level.name[:2] == world.goal_area[:2] == "10")):
world.goal_start_region: str = room.checkpoint_region
elif level.name == "8a":
world.epilogue_start_region: str = room.checkpoint_region
else:
menu_region.add_exits([room.checkpoint_region])
else:
checkpoint_location_name = level.display_name + " - " + room.checkpoint
world.active_checkpoint_names.append(checkpoint_location_name)
checkpoint_rule = lambda state, checkpoint_location_name=checkpoint_location_name: state.has(checkpoint_location_name, world.player)
room_region.add_locations({
checkpoint_location_name: world.location_name_to_id[checkpoint_location_name]
}, CelesteLocation)
if world.options.lock_goal_area and (level.name == world.goal_area or (level.name[:2] == world.goal_area[:2] == "10")):
world.goal_checkpoint_names[room.checkpoint_region] = checkpoint_location_name
else:
menu_region.add_exits([room.checkpoint_region], {room.checkpoint_region: checkpoint_rule})
if world.options.roomsanity:
if room.name != "10b_GOAL":
room_location_name = room.display_name
room_region.add_locations({
room_location_name: world.location_name_to_id[room_location_name]
}, CelesteLocation)
for room_connection in level.room_connections:
source_region = world.get_region(room_connection.source.name)
source_region.add_exits([room_connection.dest.name])
if room_connection.two_way:
dest_region = world.get_region(room_connection.dest.name)
dest_region.add_exits([room_connection.source.name])
if level.name == "10b":
# Manually connect the two parts of Farewell
source_region = world.get_region("10a_e-08_east")
source_region.add_exits(["10b_f-door_west"])
if level.name == "10c":
# Manually connect the Golden room of Farewell
golden_items: list[str] = [ItemName.traffic_blocks, ItemName.dash_refills, ItemName.double_dash_refills, ItemName.dream_blocks, ItemName.swap_blocks, ItemName.move_blocks, ItemName.blue_boosters, ItemName.springs, ItemName.feathers, ItemName.coins, ItemName.red_boosters, ItemName.kevin_blocks, ItemName.core_blocks, ItemName.fire_ice_balls, ItemName.badeline_boosters, ItemName.bird, ItemName.breaker_boxes, ItemName.pufferfish, ItemName.jellyfish, ItemName.pink_cassette_blocks, ItemName.blue_cassette_blocks, ItemName.yellow_cassette_blocks, ItemName.green_cassette_blocks]
golden_rule = lambda state: state.has_all(golden_items, world.player)
source_region_end = world.get_region("10b_j-19_top")
source_region_end.add_exits(["10c_end-golden_bottom"], {"10c_end-golden_bottom": golden_rule})
source_region_moon = world.get_region("10b_j-16_east")
source_region_moon.add_exits(["10c_end-golden_bottom"], {"10c_end-golden_bottom": golden_rule})
source_region_golden = world.get_region("10c_end-golden_top")
source_region_golden.add_exits(["10b_GOAL_main"])
location_data_table: dict[str, int] = generate_location_table()
def generate_location_groups() -> dict[str, list[str]]:
from .Levels import Level, LocationType, load_logic_data
level_data: dict[str, Level] = load_logic_data()
location_groups: dict[str, list[str]] = {
"Strawberries": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.strawberry] and id < location_id_offsets[LocationType.golden_strawberry]],
"Golden Strawberries": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.golden_strawberry] and id < location_id_offsets[LocationType.cassette]],
"Cassettes": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.cassette] and id < location_id_offsets[LocationType.car]],
"Cars": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.car] and id < location_id_offsets[LocationType.crystal_heart]],
"Crystal Hearts": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.crystal_heart] and id < location_id_offsets[LocationType.checkpoint]],
"Checkpoints": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.checkpoint] and id < location_id_offsets[LocationType.level_clear]],
"Level Clears": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.level_clear] and id < location_id_offsets[LocationType.key]],
"Keys": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.key] and id < location_id_offsets[LocationType.gem]],
"Gems": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.gem] and id < location_id_offsets[LocationType.binoculars]],
"Binoculars": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.binoculars] and id < location_id_offsets[LocationType.room_enter]],
"Rooms": [name for name, id in location_data_table.items() if id >= location_id_offsets[LocationType.room_enter]],
}
for level in level_data.values():
location_groups.update({level.display_name: [loc_name for loc_name, id in location_data_table.items() if level.display_name in loc_name]})
return location_groups

View File

@@ -0,0 +1,210 @@
# Collectables
strawberry = "Strawberry"
raspberry = "Raspberry"
# Goal Items
house_keys = "Granny's House Keys"
victory = "Victory"
# Traps
bald_trap = "Bald Trap"
literature_trap = "Literature Trap"
stun_trap = "Stun Trap"
invisible_trap = "Invisible Trap"
fast_trap = "Fast Trap"
slow_trap = "Slow Trap"
ice_trap = "Ice Trap"
reverse_trap = "Reverse Trap"
screen_flip_trap = "Screen Flip Trap"
laughter_trap = "Laughter Trap"
hiccup_trap = "Hiccup Trap"
zoom_trap = "Zoom Trap"
# Movement
dash = "Dash"
u_dash = "Up Dash"
r_dash = "Right Dash"
d_dash = "Down Dash"
l_dash = "Left Dash"
ur_dash = "Up-Right Dash"
dr_dash = "Down-Right Dash"
dl_dash = "Down-Left Dash"
ul_dash = "Up-Left Dash"
# Interactables
springs = "Springs"
traffic_blocks = "Traffic Blocks"
pink_cassette_blocks = "Pink Cassette Blocks"
blue_cassette_blocks = "Blue Cassette Blocks"
dream_blocks = "Dream Blocks"
coins = "Coins"
strawberry_seeds = "Strawberry Seeds"
sinking_platforms = "Sinking Platforms"
moving_platforms = "Moving Platforms"
blue_boosters = "Blue Boosters"
blue_clouds = "Blue Clouds"
move_blocks = "Move Blocks"
white_block = "White Block"
swap_blocks = "Swap Blocks"
red_boosters = "Red Boosters"
torches = "Torches"
theo_crystal = "Theo Crystal"
feathers = "Feathers"
bumpers = "Bumpers"
kevin_blocks = "Kevins"
pink_clouds = "Pink Clouds"
badeline_boosters = "Badeline Boosters"
fire_ice_balls = "Fire and Ice Balls"
core_toggles = "Core Toggles"
core_blocks = "Core Blocks"
pufferfish = "Pufferfish"
jellyfish = "Jellyfish"
breaker_boxes = "Breaker Boxes"
dash_refills = "Dash Refills"
double_dash_refills = "Double Dash Refills"
yellow_cassette_blocks = "Yellow Cassette Blocks"
green_cassette_blocks = "Green Cassette Blocks"
dash_switches = "Dash Switches"
seekers = "Seekers"
bird = "Bird"
brown_clutter = "Celestial Resort A - Brown Clutter"
green_clutter = "Celestial Resort A - Green Clutter"
pink_clutter = "Celestial Resort A - Pink Clutter"
cannot_access = "Cannot Access"
# Checkpoints
fc_a_checkpoint_1 = "Forsaken City A - Crossing"
fc_a_checkpoint_2 = "Forsaken City A - Chasm"
fc_b_checkpoint_1 = "Forsaken City B - Contraption"
fc_b_checkpoint_2 = "Forsaken City B - Scrap Pit"
os_a_checkpoint_1 = "Old Site A - Intervention"
os_a_checkpoint_2 = "Old Site A - Awake"
os_b_checkpoint_1 = "Old Site B - Combination Lock"
os_b_checkpoint_2 = "Old Site B - Dream Altar"
cr_a_checkpoint_1 = "Celestial Resort A - Huge Mess"
cr_a_checkpoint_2 = "Celestial Resort A - Elevator Shaft"
cr_a_checkpoint_3 = "Celestial Resort A - Presidential Suite"
cr_b_checkpoint_1 = "Celestial Resort B - Staff Quarters"
cr_b_checkpoint_2 = "Celestial Resort B - Library"
cr_b_checkpoint_3 = "Celestial Resort B - Rooftop"
gr_a_checkpoint_1 = "Golden Ridge A - Shrine"
gr_a_checkpoint_2 = "Golden Ridge A - Old Trail"
gr_a_checkpoint_3 = "Golden Ridge A - Cliff Face"
gr_b_checkpoint_1 = "Golden Ridge B - Stepping Stones"
gr_b_checkpoint_2 = "Golden Ridge B - Gusty Canyon"
gr_b_checkpoint_3 = "Golden Ridge B - Eye of the Storm"
mt_a_checkpoint_1 = "Mirror Temple A - Depths"
mt_a_checkpoint_2 = "Mirror Temple A - Unravelling"
mt_a_checkpoint_3 = "Mirror Temple A - Search"
mt_a_checkpoint_4 = "Mirror Temple A - Rescue"
mt_b_checkpoint_1 = "Mirror Temple B - Central Chamber"
mt_b_checkpoint_2 = "Mirror Temple B - Through the Mirror"
mt_b_checkpoint_3 = "Mirror Temple B - Mix Master"
ref_a_checkpoint_1 = "Reflection A - Lake"
ref_a_checkpoint_2 = "Reflection A - Hollows"
ref_a_checkpoint_3 = "Reflection A - Reflection"
ref_a_checkpoint_4 = "Reflection A - Rock Bottom"
ref_a_checkpoint_5 = "Reflection A - Resolution"
ref_b_checkpoint_1 = "Reflection B - Reflection"
ref_b_checkpoint_2 = "Reflection B - Rock Bottom"
ref_b_checkpoint_3 = "Reflection B - Reprieve"
sum_a_checkpoint_1 = "The Summit A - 500 M"
sum_a_checkpoint_2 = "The Summit A - 1000 M"
sum_a_checkpoint_3 = "The Summit A - 1500 M"
sum_a_checkpoint_4 = "The Summit A - 2000 M"
sum_a_checkpoint_5 = "The Summit A - 2500 M"
sum_a_checkpoint_6 = "The Summit A - 3000 M"
sum_b_checkpoint_1 = "The Summit B - 500 M"
sum_b_checkpoint_2 = "The Summit B - 1000 M"
sum_b_checkpoint_3 = "The Summit B - 1500 M"
sum_b_checkpoint_4 = "The Summit B - 2000 M"
sum_b_checkpoint_5 = "The Summit B - 2500 M"
sum_b_checkpoint_6 = "The Summit B - 3000 M"
core_a_checkpoint_1 = "Core A - Into the Core"
core_a_checkpoint_2 = "Core A - Hot and Cold"
core_a_checkpoint_3 = "Core A - Heart of the Mountain"
core_b_checkpoint_1 = "Core B - Into the Core"
core_b_checkpoint_2 = "Core B - Burning or Freezing"
core_b_checkpoint_3 = "Core B - Heartbeat"
farewell_checkpoint_1 = "Farewell - Singular"
farewell_checkpoint_2 = "Farewell - Power Source"
farewell_checkpoint_3 = "Farewell - Remembered"
farewell_checkpoint_4 = "Farewell - Event Horizon"
farewell_checkpoint_5 = "Farewell - Determination"
farewell_checkpoint_6 = "Farewell - Stubbornness"
farewell_checkpoint_7 = "Farewell - Reconcilliation"
farewell_checkpoint_8 = "Farewell - Farewell"
# Cassettes
prologue_cassette = "Prologue Cassette"
fc_a_cassette = "Forsaken City Cassette - A Side"
fc_b_cassette = "Forsaken City Cassette - B Side"
fc_c_cassette = "Forsaken City Cassette - C Side"
os_a_cassette = "Old Site Cassette - A Side"
os_b_cassette = "Old Site Cassette - B Side"
os_c_cassette = "Old Site Cassette - C Side"
cr_a_cassette = "Celestial Resort Cassette - A Side"
cr_b_cassette = "Celestial Resort Cassette - B Side"
cr_c_cassette = "Celestial Resort Cassette - C Side"
gr_a_cassette = "Golden Ridge Cassette - A Side"
gr_b_cassette = "Golden Ridge Cassette - B Side"
gr_c_cassette = "Golden Ridge Cassette - C Side"
mt_a_cassette = "Mirror Temple Cassette - A Side"
mt_b_cassette = "Mirror Temple Cassette - B Side"
mt_c_cassette = "Mirror Temple Cassette - C Side"
ref_a_cassette = "Reflection Cassette - A Side"
ref_b_cassette = "Reflection Cassette - B Side"
ref_c_cassette = "Reflection Cassette - C Side"
sum_a_cassette = "The Summit Cassette - A Side"
sum_b_cassette = "The Summit Cassette - B Side"
sum_c_cassette = "The Summit Cassette - C Side"
epilogue_cassette = "Epilogue Cassette"
core_a_cassette = "Core Cassette - A Side"
core_b_cassette = "Core Cassette - B Side"
core_c_cassette = "Core Cassette - C Side"
farewell_cassette = "Farewell Cassette"
# Crystal Hearts
crystal_heart_1 = "Crystal Heart 1"
crystal_heart_2 = "Crystal Heart 2"
crystal_heart_3 = "Crystal Heart 3"
crystal_heart_4 = "Crystal Heart 4"
crystal_heart_5 = "Crystal Heart 5"
crystal_heart_6 = "Crystal Heart 6"
crystal_heart_7 = "Crystal Heart 7"
crystal_heart_8 = "Crystal Heart 8"
crystal_heart_9 = "Crystal Heart 9"
crystal_heart_10 = "Crystal Heart 10"
crystal_heart_11 = "Crystal Heart 11"
crystal_heart_12 = "Crystal Heart 12"
crystal_heart_13 = "Crystal Heart 13"
crystal_heart_14 = "Crystal Heart 14"
crystal_heart_15 = "Crystal Heart 15"
crystal_heart_16 = "Crystal Heart 16"

View File

@@ -0,0 +1,528 @@
from dataclasses import dataclass
import random
from Options import Choice, Range, DefaultOnToggle, Toggle, TextChoice, DeathLink, OptionGroup, PerGameCommonOptions, OptionError
from worlds.AutoWorld import World
class DeathLinkAmnesty(Range):
"""
How many deaths it takes to send a DeathLink
"""
display_name = "Death Link Amnesty"
range_start = 1
range_end = 30
default = 10
class TrapLink(Toggle):
"""
Whether your received traps are linked to other players
You will also receive any linked traps from other players with Trap Link enabled,
if you have a weight above "none" set for that trap
"""
display_name = "Trap Link"
class GoalArea(Choice):
"""
What Area must be cleared to gain access to the Epilogue and complete the game
"""
display_name = "Goal Area"
option_the_summit_a = 0
option_the_summit_b = 1
option_the_summit_c = 2
option_core_a = 3
option_core_b = 4
option_core_c = 5
option_empty_space = 6
option_farewell = 7
option_farewell_golden = 8
default = 0
class LockGoalArea(DefaultOnToggle):
"""
Determines whether your Goal Area will be locked until you receive your required Strawberries, or only the Epilogue
"""
display_name = "Lock Goal Area"
class GoalAreaCheckpointsanity(Toggle):
"""
Determines whether the Checkpoints in your Goal Area will be shuffled into the item pool (if Checkpointsanity is active)
"""
display_name = "Goal Area Checkpointsanity"
class TotalStrawberries(Range):
"""
Maximum number of how many Strawberries can exist
"""
display_name = "Total Strawberries"
range_start = 0
range_end = 202
default = 50
class StrawberriesRequiredPercentage(Range):
"""
Percentage of existing Strawberries you must receive to access your Goal Area (if Lock Goal Area is active) and the Epilogue
"""
display_name = "Strawberries Required Percentage"
range_start = 0
range_end = 100
default = 80
class Checkpointsanity(Toggle):
"""
Determines whether Checkpoints will be shuffled into the item pool
"""
display_name = "Checkpointsanity"
class Binosanity(Toggle):
"""
Determines whether using Binoculars sends location checks
"""
display_name = "Binosanity"
class Keysanity(Toggle):
"""
Determines whether individual Keys are shuffled into the item pool
"""
display_name = "Keysanity"
class Gemsanity(Toggle):
"""
Determines whether Summit Gems are shuffled into the item pool
"""
display_name = "Gemsanity"
class Carsanity(Toggle):
"""
Determines whether riding on cars grants location checks
"""
display_name = "Carsanity"
class Roomsanity(Toggle):
"""
Determines whether entering individual rooms sends location checks
"""
display_name = "Roomsanity"
class IncludeGoldens(Toggle):
"""
Determines whether collecting Golden Strawberries sends location checks
"""
display_name = "Include Goldens"
class IncludeCore(Toggle):
"""
Determines whether Chapter 8 - Core Levels will be included
"""
display_name = "Include Core"
class IncludeFarewell(Choice):
"""
Determines how much of Chapter 9 - Farewell Level will be included
"""
display_name = "Include Farewell"
option_none = 0
option_empty_space = 1
option_farewell = 2
default = 0
class IncludeBSides(Toggle):
"""
Determines whether the B-Side Levels will be included
"""
display_name = "Include B-Sides"
class IncludeCSides(Toggle):
"""
Determines whether the C-Side Levels will be included
"""
display_name = "Include C-Sides"
class JunkFillPercentage(Range):
"""
Replace a percentage of non-required Strawberries in the item pool with junk items
"""
display_name = "Junk Fill Percentage"
range_start = 0
range_end = 100
default = 50
class TrapFillPercentage(Range):
"""
Replace a percentage of junk items in the item pool with random traps
"""
display_name = "Trap Fill Percentage"
range_start = 0
range_end = 100
default = 0
class TrapExpirationAction(Choice):
"""
The type of action which causes traps to wear off
"""
display_name = "Trap Expiration Action"
option_return_to_menu = 0
option_deaths = 1
option_new_screens = 2
default = 1
class TrapExpirationAmount(Range):
"""
The amount of the selected Trap Expiration Action that must occur for the trap to wear off
"""
display_name = "Trap Expiration Amount"
range_start = 1
range_end = 10
default = 5
class BaseTrapWeight(Choice):
"""
Base Class for Trap Weights
"""
option_none = 0
option_low = 1
option_medium = 2
option_high = 4
default = 2
class BaldTrapWeight(BaseTrapWeight):
"""
Likelihood of receiving a trap which makes Maddy bald
"""
display_name = "Bald Trap Weight"
class LiteratureTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the player to read literature
"""
display_name = "Literature Trap Weight"
class StunTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which briefly stuns Maddy
"""
display_name = "Stun Trap Weight"
class InvisibleTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which turns Maddy invisible
"""
display_name = "Invisible Trap Weight"
class FastTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which increases the game speed
"""
display_name = "Fast Trap Weight"
class SlowTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which decreases the game speed
"""
display_name = "Slow Trap Weight"
class IceTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the level to become slippery
"""
display_name = "Ice Trap Weight"
class ReverseTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the controls to be reversed
"""
display_name = "Reverse Trap Weight"
class ScreenFlipTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the screen to be flipped
"""
display_name = "Screen Flip Trap Weight"
class LaughterTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes Maddy to laugh uncontrollably
"""
display_name = "Laughter Trap Weight"
class HiccupTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes Maddy to hiccup uncontrollably
"""
display_name = "Hiccup Trap Weight"
class ZoomTrapWeight(BaseTrapWeight):
"""
Likelihood of a receiving a trap which causes the camera to focus on Maddy
"""
display_name = "Zoom Trap Weight"
class MusicShuffle(Choice):
"""
Music shuffle type
None: No Music is shuffled
Consistent: Each music track is consistently shuffled throughout the game
Singularity: The entire game uses one song for levels
"""
display_name = "Music Shuffle"
option_none = 0
option_consistent = 1
option_singularity = 2
default = 0
class RequireCassettes(Toggle):
"""
Determines whether you must receive a level's Cassette Item to hear that level's music
"""
display_name = "Require Cassettes"
class MadelineHairLength(Choice):
"""
How long Madeline's hair is
"""
display_name = "Madeline Hair Length"
option_very_short = 1
option_short = 2
option_default = 4
option_long = 7
option_very_long = 10
option_absurd = 20
default = 4
class ColorChoice(TextChoice):
option_strawberry = 0xAC3232
option_empty = 0x44B7FF
option_double = 0xFF6DEF
option_golden = 0xFFD65C
option_baddy = 0x9B3FB5
option_fire_red = 0xFF0000
option_maroon = 0x800000
option_salmon = 0xFF3A65
option_orange = 0xD86E0A
option_lime_green = 0x8DF920
option_bright_green = 0x0DAF05
option_forest_green = 0x132818
option_royal_blue = 0x0036BF
option_brown = 0xB78726
option_black = 0x000000
option_white = 0xFFFFFF
option_grey = 0x808080
option_any_color = -1
@classmethod
def from_text(cls, text: str) -> Choice:
text = text.lower()
if text == "random":
choice_list = list(cls.name_lookup)
choice_list.remove(cls.option_any_color)
return cls(random.choice(choice_list))
return super().from_text(text)
class MadelineOneDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has one dash
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline One Dash Hair Color"
default = ColorChoice.option_strawberry
class MadelineTwoDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has two dashes
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline Two Dash Hair Color"
default = ColorChoice.option_double
class MadelineNoDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has no dashes
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline No Dash Hair Color"
default = ColorChoice.option_empty
class MadelineFeatherHairColor(ColorChoice):
"""
What color Madeline's hair is when she has a feather
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline Feather Hair Color"
default = ColorChoice.option_golden
celeste_option_groups = [
OptionGroup("Goal Options", [
GoalArea,
LockGoalArea,
GoalAreaCheckpointsanity,
TotalStrawberries,
StrawberriesRequiredPercentage,
]),
OptionGroup("Location Options", [
Checkpointsanity,
Binosanity,
Keysanity,
Gemsanity,
Carsanity,
Roomsanity,
IncludeGoldens,
IncludeCore,
IncludeFarewell,
IncludeBSides,
IncludeCSides,
]),
OptionGroup("Junk and Traps", [
JunkFillPercentage,
TrapFillPercentage,
TrapExpirationAction,
TrapExpirationAmount,
BaldTrapWeight,
LiteratureTrapWeight,
StunTrapWeight,
InvisibleTrapWeight,
FastTrapWeight,
SlowTrapWeight,
IceTrapWeight,
ReverseTrapWeight,
ScreenFlipTrapWeight,
LaughterTrapWeight,
HiccupTrapWeight,
ZoomTrapWeight,
]),
OptionGroup("Aesthetic Options", [
MusicShuffle,
RequireCassettes,
MadelineHairLength,
MadelineOneDashHairColor,
MadelineTwoDashHairColor,
MadelineNoDashHairColor,
MadelineFeatherHairColor,
]),
]
def resolve_options(world: World):
# One Dash Hair
if isinstance(world.options.madeline_one_dash_hair_color.value, str):
try:
world.madeline_one_dash_hair_color = int(world.options.madeline_one_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_one_dash_hair_color`:"
f"{world.options.madeline_one_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_one_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_one_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_one_dash_hair_color = world.options.madeline_one_dash_hair_color.value
# Two Dash Hair
if isinstance(world.options.madeline_two_dash_hair_color.value, str):
try:
world.madeline_two_dash_hair_color = int(world.options.madeline_two_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_two_dash_hair_color`:"
f"{world.options.madeline_two_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_two_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_two_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_two_dash_hair_color = world.options.madeline_two_dash_hair_color.value
# No Dash Hair
if isinstance(world.options.madeline_no_dash_hair_color.value, str):
try:
world.madeline_no_dash_hair_color = int(world.options.madeline_no_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_no_dash_hair_color`:"
f"{world.options.madeline_no_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_no_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_no_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_no_dash_hair_color = world.options.madeline_no_dash_hair_color.value
# Feather Hair
if isinstance(world.options.madeline_feather_hair_color.value, str):
try:
world.madeline_feather_hair_color = int(world.options.madeline_feather_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_feather_hair_color`:"
f"{world.options.madeline_feather_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_feather_hair_color.value == ColorChoice.option_any_color:
world.madeline_feather_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_feather_hair_color = world.options.madeline_feather_hair_color.value
@dataclass
class CelesteOptions(PerGameCommonOptions):
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty
trap_link: TrapLink
goal_area: GoalArea
lock_goal_area: LockGoalArea
goal_area_checkpointsanity: GoalAreaCheckpointsanity
total_strawberries: TotalStrawberries
strawberries_required_percentage: StrawberriesRequiredPercentage
junk_fill_percentage: JunkFillPercentage
trap_fill_percentage: TrapFillPercentage
trap_expiration_action: TrapExpirationAction
trap_expiration_amount: TrapExpirationAmount
bald_trap_weight: BaldTrapWeight
literature_trap_weight: LiteratureTrapWeight
stun_trap_weight: StunTrapWeight
invisible_trap_weight: InvisibleTrapWeight
fast_trap_weight: FastTrapWeight
slow_trap_weight: SlowTrapWeight
ice_trap_weight: IceTrapWeight
reverse_trap_weight: ReverseTrapWeight
screen_flip_trap_weight: ScreenFlipTrapWeight
laughter_trap_weight: LaughterTrapWeight
hiccup_trap_weight: HiccupTrapWeight
zoom_trap_weight: ZoomTrapWeight
checkpointsanity: Checkpointsanity
binosanity: Binosanity
keysanity: Keysanity
gemsanity: Gemsanity
carsanity: Carsanity
roomsanity: Roomsanity
include_goldens: IncludeGoldens
include_core: IncludeCore
include_farewell: IncludeFarewell
include_b_sides: IncludeBSides
include_c_sides: IncludeCSides
music_shuffle: MusicShuffle
require_cassettes: RequireCassettes
madeline_hair_length: MadelineHairLength
madeline_one_dash_hair_color: MadelineOneDashHairColor
madeline_two_dash_hair_color: MadelineTwoDashHairColor
madeline_no_dash_hair_color: MadelineNoDashHairColor
madeline_feather_hair_color: MadelineFeatherHairColor

View File

@@ -0,0 +1,361 @@
from copy import deepcopy
import math
from typing import TextIO
from BaseClasses import ItemClassification, Location, MultiWorld, Region, Tutorial
from Utils import visualize_regions
from worlds.AutoWorld import WebWorld, World
from .Items import CelesteItem, generate_item_table, generate_item_data_table, generate_item_groups, level_item_lists, level_cassette_items,\
cassette_item_data_table, crystal_heart_item_data_table, trap_item_data_table
from .Locations import CelesteLocation, location_data_table, generate_location_groups, checkpoint_location_data_table, location_id_offsets
from .Names import ItemName
from .Options import CelesteOptions, celeste_option_groups, resolve_options
from .Levels import Level, LocationType, load_logic_data, goal_area_option_to_name, goal_area_option_to_display_name, goal_area_to_location_name
class CelesteOpenWebWorld(WebWorld):
theme = "ice"
setup_en = Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Celeste (Open World) in Archipelago.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["PoryGone"]
)
tutorials = [setup_en]
option_groups = celeste_option_groups
class CelesteOpenWorld(World):
"""
Celeste (Open World) is a randomizer for the original Celeste. In this acclaimed platformer created by ExOK Games, you control Madeline as she attempts to climb the titular mountain, meeting friends and obstacles along the way. Progression is found in unlocking the ability to interact with various objects in the areas, such as springs, traffic blocks, feathers, and many more. Please be safe on the climb.
"""
# Class Data
game = "Celeste (Open World)"
web = CelesteOpenWebWorld()
options_dataclass = CelesteOptions
options: CelesteOptions
apworld_version = 10005
level_data: dict[str, Level] = load_logic_data()
location_name_to_id: dict[str, int] = location_data_table
location_name_groups: dict[str, list[str]] = generate_location_groups()
item_name_to_id: dict[str, int] = generate_item_table()
item_name_groups: dict[str, list[str]] = generate_item_groups()
# Instance Data
madeline_one_dash_hair_color: int
madeline_two_dash_hair_color: int
madeline_no_dash_hair_color: int
madeline_feather_hair_color: int
active_levels: set[str]
active_items: set[str]
def generate_early(self) -> None:
if not self.player_name.isascii():
raise RuntimeError(f"Invalid player_name {self.player_name} for game {self.game}. Name must be ascii.")
resolve_options(self)
self.goal_area: str = goal_area_option_to_name[self.options.goal_area.value]
self.active_levels = {"0a", "1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a"}
if self.options.include_core:
self.active_levels.add("9a")
if self.options.include_farewell >= 1:
self.active_levels.add("10a")
if self.options.include_farewell == 2:
self.active_levels.add("10b")
if self.options.include_b_sides:
self.active_levels.update({"1b", "2b", "3b", "4b", "5b", "6b", "7b"})
if self.options.include_core:
self.active_levels.add("9b")
if self.options.include_c_sides:
self.active_levels.update({"1c", "2c", "3c", "4c", "5c", "6c", "7c"})
if self.options.include_core:
self.active_levels.add("9c")
self.active_levels.add(self.goal_area)
if self.goal_area == "10c":
self.active_levels.add("10a")
self.active_levels.add("10b")
elif self.goal_area == "10b":
self.active_levels.add("10a")
self.active_items = set()
for level in self.active_levels:
self.active_items.update(level_item_lists[level])
def create_regions(self) -> None:
from .Locations import create_regions_and_locations
create_regions_and_locations(self)
def create_item(self, name: str, force_useful: bool = False) -> CelesteItem:
item_data_table = generate_item_data_table()
if name == ItemName.strawberry and force_useful:
return CelesteItem(name, ItemClassification.useful, item_data_table[name].code, self.player)
elif name in item_data_table:
return CelesteItem(name, item_data_table[name].type, item_data_table[name].code, self.player)
else:
return CelesteItem(name, ItemClassification.progression, None, self.player)
def create_items(self) -> None:
item_pool: list[CelesteItem] = []
location_count: int = len(self.get_locations())
goal_area_location_count: int = sum(goal_area_option_to_display_name[self.options.goal_area] in loc.name for loc in self.get_locations())
# Goal Items
goal_item_loc: Location = self.get_location(goal_area_to_location_name[self.goal_area])
goal_item_loc.place_locked_item(self.create_item(ItemName.house_keys))
location_count -= 1
epilogue_region: Region = self.get_region(self.epilogue_start_region)
epilogue_region.add_locations({ItemName.victory: None }, CelesteLocation)
victory_loc: Location = self.get_location(ItemName.victory)
victory_loc.place_locked_item(self.create_item(ItemName.victory))
# Checkpoints
for item_name in self.active_checkpoint_names:
if self.options.checkpointsanity:
if not self.options.goal_area_checkpointsanity and goal_area_option_to_display_name[self.options.goal_area] in item_name:
checkpoint_loc: Location = self.get_location(item_name)
checkpoint_loc.place_locked_item(self.create_item(item_name))
location_count -= 1
else:
item_pool.append(self.create_item(item_name))
else:
checkpoint_loc: Location = self.get_location(item_name)
checkpoint_loc.place_locked_item(self.create_item(item_name))
location_count -= 1
# Keys
if self.options.keysanity:
item_pool += [self.create_item(item_name) for item_name in self.active_key_names]
else:
for item_name in self.active_key_names:
key_loc: Location = self.get_location(item_name)
key_loc.place_locked_item(self.create_item(item_name))
location_count -= 1
# Summit Gems
if self.options.gemsanity:
item_pool += [self.create_item(item_name) for item_name in self.active_gem_names]
else:
for item_name in self.active_gem_names:
gem_loc: Location = self.get_location(item_name)
gem_loc.place_locked_item(self.create_item(item_name))
location_count -= 1
# Clutter Events
for item_name in self.active_clutter_names:
clutter_loc: Location = self.get_location(item_name)
clutter_loc.place_locked_item(self.create_item(item_name))
location_count -= 1
# Interactables
item_pool += [self.create_item(item_name) for item_name in sorted(self.active_items)]
# Strawberries
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - goal_area_location_count - len(item_pool))
self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
menu_region = self.get_region("Menu")
if getattr(self, "goal_start_region", None):
menu_region.add_exits([self.goal_start_region], {self.goal_start_region: lambda state: state.has(ItemName.strawberry, self.player, self.strawberries_required)})
if getattr(self, "goal_checkpoint_names", None):
for region_name, location_name in self.goal_checkpoint_names.items():
checkpoint_rule = lambda state, location_name=location_name: state.has(location_name, self.player) and state.has(ItemName.strawberry, self.player, self.strawberries_required)
menu_region.add_exits([region_name], {region_name: checkpoint_rule})
menu_region.add_exits([self.epilogue_start_region], {self.epilogue_start_region: lambda state: (state.has(ItemName.strawberry, self.player, self.strawberries_required) and state.has(ItemName.house_keys, self.player))})
item_pool += [self.create_item(ItemName.strawberry) for _ in range(self.strawberries_required)]
# Filler and Traps
non_required_strawberries = (real_total_strawberries - self.strawberries_required)
replacement_filler_count = math.floor(non_required_strawberries * (self.options.junk_fill_percentage.value / 100.0))
remaining_extra_strawberries = non_required_strawberries - replacement_filler_count
item_pool += [self.create_item(ItemName.strawberry, True) for _ in range(remaining_extra_strawberries)]
trap_weights = []
trap_weights += ([ItemName.bald_trap] * self.options.bald_trap_weight.value)
trap_weights += ([ItemName.literature_trap] * self.options.literature_trap_weight.value)
trap_weights += ([ItemName.stun_trap] * self.options.stun_trap_weight.value)
trap_weights += ([ItemName.invisible_trap] * self.options.invisible_trap_weight.value)
trap_weights += ([ItemName.fast_trap] * self.options.fast_trap_weight.value)
trap_weights += ([ItemName.slow_trap] * self.options.slow_trap_weight.value)
trap_weights += ([ItemName.ice_trap] * self.options.ice_trap_weight.value)
trap_weights += ([ItemName.reverse_trap] * self.options.reverse_trap_weight.value)
trap_weights += ([ItemName.screen_flip_trap] * self.options.screen_flip_trap_weight.value)
trap_weights += ([ItemName.laughter_trap] * self.options.laughter_trap_weight.value)
trap_weights += ([ItemName.hiccup_trap] * self.options.hiccup_trap_weight.value)
trap_weights += ([ItemName.zoom_trap] * self.options.zoom_trap_weight.value)
total_filler_count: int = (location_count - len(item_pool))
# Cassettes
if self.options.require_cassettes:
shuffled_active_levels = sorted(self.active_levels)
self.random.shuffle(shuffled_active_levels)
for level_name in shuffled_active_levels:
if level_name == "10b" or level_name == "10c":
continue
if level_name not in self.multiworld.precollected_items[self.player]:
if total_filler_count > 0:
item_pool.append(self.create_item(level_cassette_items[level_name]))
total_filler_count -= 1
else:
self.multiworld.push_precollected(self.create_item(level_cassette_items[level_name]))
# Crystal Hearts
for name in crystal_heart_item_data_table.keys():
if total_filler_count > 0:
if name not in self.multiworld.precollected_items[self.player]:
item_pool.append(self.create_item(name))
total_filler_count -= 1
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(total_filler_count * (self.options.trap_fill_percentage.value / 100.0))
total_filler_count -= trap_count
item_pool += [self.create_item(ItemName.raspberry) for _ in range(total_filler_count)]
trap_pool = []
for i in range(trap_count):
trap_item = self.random.choice(trap_weights)
trap_pool.append(self.create_item(trap_item))
item_pool += trap_pool
self.multiworld.itempool += item_pool
def get_filler_item_name(self) -> str:
return ItemName.raspberry
def set_rules(self) -> None:
self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.victory, self.player)
def fill_slot_data(self):
return {
"apworld_version": self.apworld_version,
"min_mod_version": 10000,
"death_link": self.options.death_link.value,
"death_link_amnesty": self.options.death_link_amnesty.value,
"trap_link": self.options.trap_link.value,
"active_levels": self.active_levels,
"goal_area": self.goal_area,
"lock_goal_area": self.options.lock_goal_area.value,
"strawberries_required": self.strawberries_required,
"checkpointsanity": self.options.checkpointsanity.value,
"binosanity": self.options.binosanity.value,
"keysanity": self.options.keysanity.value,
"gemsanity": self.options.gemsanity.value,
"carsanity": self.options.carsanity.value,
"roomsanity": self.options.roomsanity.value,
"include_goldens": self.options.include_goldens.value,
"include_core": self.options.include_core.value,
"include_farewell": self.options.include_farewell.value,
"include_b_sides": self.options.include_b_sides.value,
"include_c_sides": self.options.include_c_sides.value,
"trap_expiration_action": self.options.trap_expiration_action.value,
"trap_expiration_amount": self.options.trap_expiration_amount.value,
"active_traps": self.output_active_traps(),
"madeline_hair_length": self.options.madeline_hair_length.value,
"madeline_one_dash_hair_color": self.madeline_one_dash_hair_color,
"madeline_two_dash_hair_color": self.madeline_two_dash_hair_color,
"madeline_no_dash_hair_color": self.madeline_no_dash_hair_color,
"madeline_feather_hair_color": self.madeline_feather_hair_color,
"music_shuffle": self.options.music_shuffle.value,
"music_map": self.generate_music_data(),
"require_cassettes": self.options.require_cassettes.value,
"chosen_poem": self.random.randint(0, 119),
}
@classmethod
def stage_write_spoiler_header(cls, multiworld: MultiWorld, spoiler_handle: TextIO):
major: int = cls.apworld_version // 10000
minor: int = (cls.apworld_version % 10000) // 100
bugfix: int = (cls.apworld_version % 100)
spoiler_handle.write(f"\nCeleste (Open World) APWorld v{major}.{minor}.{bugfix}\n")
def output_active_traps(self) -> dict[int, int]:
trap_data = {}
trap_data[0x20] = self.options.bald_trap_weight.value
trap_data[0x21] = self.options.literature_trap_weight.value
trap_data[0x22] = self.options.stun_trap_weight.value
trap_data[0x23] = self.options.invisible_trap_weight.value
trap_data[0x24] = self.options.fast_trap_weight.value
trap_data[0x25] = self.options.slow_trap_weight.value
trap_data[0x26] = self.options.ice_trap_weight.value
trap_data[0x28] = self.options.reverse_trap_weight.value
trap_data[0x29] = self.options.screen_flip_trap_weight.value
trap_data[0x2A] = self.options.laughter_trap_weight.value
trap_data[0x2B] = self.options.hiccup_trap_weight.value
trap_data[0x2C] = self.options.zoom_trap_weight.value
return trap_data
def generate_music_data(self) -> dict[int, int]:
if self.options.music_shuffle == "consistent":
musiclist_o = list(range(0, 48))
musiclist_s = musiclist_o.copy()
self.random.shuffle(musiclist_s)
return dict(zip(musiclist_o, musiclist_s))
elif self.options.music_shuffle == "singularity":
musiclist_o = list(range(0, 48))
musiclist_s = [self.random.choice(musiclist_o)] * len(musiclist_o)
return dict(zip(musiclist_o, musiclist_s))
else:
musiclist_o = list(range(0, 48))
musiclist_s = musiclist_o.copy()
return dict(zip(musiclist_o, musiclist_s))
# Useful Debugging tools, kept around for later.
#@classmethod
#def stage_assert_generate(cls, _multiworld: MultiWorld) -> None:
# with open("./worlds/celeste_open_world/data/IDs.txt", "w") as f:
# print("Items:", file=f)
# for name in sorted(CelesteOpenWorld.item_name_to_id, key=CelesteOpenWorld.item_name_to_id.get):
# id = CelesteOpenWorld.item_name_to_id[name]
# print(f"{{ 0x{id:X}, \"{name}\" }},", file=f)
# print("\nLocations:", file=f)
# for name in sorted(CelesteOpenWorld.location_name_to_id, key=CelesteOpenWorld.location_name_to_id.get):
# id = CelesteOpenWorld.location_name_to_id[name]
# print(f"{{ 0x{id:X}, \"{name}\" }},", file=f)
# print("\nLocations 2:", file=f)
# for name in sorted(CelesteOpenWorld.location_name_to_id, key=CelesteOpenWorld.location_name_to_id.get):
# id = CelesteOpenWorld.location_name_to_id[name]
# print(f"{{ \"{name}\", 0x{id:X} }},", file=f)
#
#def generate_output(self, output_directory: str):
# visualize_regions(self.get_region("Menu"), f"Player{self.player}.puml", show_entrance_names=False,
# regions_to_highlight=self.multiworld.get_all_state(self.player).reachable_regions[self.player])

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
if __name__ == "__main__":
import json
all_doors: list[str] = []
all_region_connections: list[str] = []
all_locations: list[str] = []
all_regions: list[str] = []
all_room_connections: list[str] = []
all_rooms: list[str] = []
all_levels: list[str] = []
data_file = open('CelesteLevelData.json')
level_data = json.load(data_file)
data_file.close()
# Levels
for level in level_data["levels"]:
level_str = (f" \"{level['name']}\": Level(\"{level['name']}\", "
f"\"{level['display_name']}\", "
f"[room for _, room in all_rooms.items() if room.level_name == \"{level['name']}\"], "
f"[room_con for _, room_con in all_room_connections.items() if room_con.level_name == \"{level['name']}\"]),"
)
all_levels.append(level_str)
# Rooms
for room in level["rooms"]:
room_full_name = f"{level['name']}_{room['name']}"
room_full_display_name = f"{level['display_name']} - Room {room['name']}"
room_str = (f" \"{room_full_name}\": Room(\"{level['name']}\", "
f"\"{room_full_name}\", \"{room_full_display_name}\", "
f"[reg for _, reg in all_regions.items() if reg.room_name == \"{room_full_name}\"], "
f"[door for _, door in all_doors.items() if door.room_name == \"{room_full_name}\"]"
)
if "checkpoint" in room and room["checkpoint"] != "":
room_str += f", \"{room['checkpoint']}\", \"{room_full_name}_{room['checkpoint_region']}\""
room_str += "),"
all_rooms.append(room_str)
# Regions
for region in room["regions"]:
region_full_name = f"{room_full_name}_{region['name']}"
region_str = (f" \"{region_full_name}\": PreRegion(\"{region_full_name}\", "
f"\"{room_full_name}\", "
f"[reg_con for _, reg_con in all_region_connections.items() if reg_con.source_name == \"{region_full_name}\"], "
f"[loc for _, loc in all_locations.items() if loc.region_name == \"{region_full_name}\"]),"
)
all_regions.append(region_str)
# Locations
if "locations" in region:
for location in region["locations"]:
location_full_name = f"{room_full_name}_{location['name']}"
location_display_name = location['display_name']
if (location['type'] == "strawberry" and location_display_name != "Moon Berry") or location['type'] == "binoculars" :
location_display_name = f"Room {room['name']} {location_display_name}"
location_full_display_name = f"{level['display_name']} - {location_display_name}"
location_str = (f" \"{location_full_name}\": LevelLocation(\"{location_full_name}\", "
f"\"{location_full_display_name}\", \"{region_full_name}\", "
f"LocationType.{location['type']}, ["
)
if "rule" in location:
for possible_access in location['rule']:
location_str += f"["
for item in possible_access:
if "Key" in item or "Gem" in item:
location_str += f"\"{level['display_name']} - {item}\", "
else:
location_str += f"ItemName.{item}, "
location_str += f"], "
elif "rules" in location:
raise Exception(f"Location {location_full_name} uses 'rules' instead of 'rule")
location_str += "]),"
all_locations.append(location_str)
# Region Connections
for reg_con in region["connections"]:
dest_region_full_name = f"{room_full_name}_{reg_con['dest']}"
reg_con_full_name = f"{region_full_name}---{dest_region_full_name}"
reg_con_str = f" \"{reg_con_full_name}\": RegionConnection(\"{region_full_name}\", \"{dest_region_full_name}\", ["
for possible_access in reg_con['rule']:
reg_con_str += f"["
for item in possible_access:
if "Key" in item or "Gem" in item:
reg_con_str += f"\"{level['display_name']} - {item}\", "
else:
reg_con_str += f"ItemName.{item}, "
reg_con_str += f"], "
reg_con_str += "]),"
all_region_connections.append(reg_con_str)
for door in room["doors"]:
door_full_name = f"{room_full_name}_{door['name']}"
door_str = (f" \"{door_full_name}\": Door(\"{door_full_name}\", "
f"\"{room_full_name}\", "
f"DoorDirection.{door['direction']}, "
)
door_str += "True, " if door["blocked"] else "False, "
door_str += "True)," if door["closes_behind"] else "False),"
all_doors.append(door_str)
all_regions.append("")
all_region_connections.append("")
all_doors.append("")
all_locations.append("")
all_rooms.append("")
# Room Connections
for room_con in level["room_connections"]:
source_door_full_name = f"{level['name']}_{room_con['source_room']}_{room_con['source_door']}"
dest_door_full_name = f"{level['name']}_{room_con['dest_room']}_{room_con['dest_door']}"
room_con_str = (f" \"{source_door_full_name}---{dest_door_full_name}\": RoomConnection(\"{level['name']}\", "
f"all_doors[\"{source_door_full_name}\"], "
f"all_doors[\"{dest_door_full_name}\"]),"
)
all_room_connections.append(room_con_str)
all_room_connections.append("")
all_levels.append("")
import sys
out_file = open("CelesteLevelData.py", "w")
sys.stdout = out_file
print("# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MANUALLY EDIT.")
print("")
print("from ..Levels import Level, Room, PreRegion, LevelLocation, RegionConnection, RoomConnection, Door, DoorDirection, LocationType")
print("from ..Names import ItemName")
print("")
print("all_doors: dict[str, Door] = {")
for line in all_doors:
print(line)
print("}")
print("")
print("all_region_connections: dict[str, RegionConnection] = {")
for line in all_region_connections:
print(line)
print("}")
print("")
print("all_locations: dict[str, LevelLocation] = {")
for line in all_locations:
print(line)
print("}")
print("")
print("all_regions: dict[str, PreRegion] = {")
for line in all_regions:
print(line)
print("}")
print("")
print("all_room_connections: dict[str, RoomConnection] = {")
for line in all_room_connections:
print(line)
print("}")
print("")
print("all_rooms: dict[str, Room] = {")
for line in all_rooms:
print(line)
print("}")
print("")
print("all_levels: dict[str, Level] = {")
for line in all_levels:
print(line)
print("}")
print("")
out_file.close()

View File

@@ -0,0 +1,98 @@
# Celeste Open World
## What is this game?
**Celeste (Open World)** is a Randomizer for the original Celeste. In this acclaimed platformer created by ExOK Games, you control Madeline as she attempts to climb the titular mountain, meeting friends and obstacles along the way.
This randomizer takes an "Open World" approach. All of your active areas are open to you from the start. Progression is found in unlocking the ability to interact with various objects in the areas, such as springs, traffic blocks, feathers, and many more. One area can be selected as your "Goal Area", requiring you to clear that area before you can access the Epilogue and finish the game. Additionally, you can be required to receive a customizable amount of `Strawberry` items to access the Epilogue and optionally to access your Goal Area as well.
There are a variety of progression, location, and aesthetic options available. Please be safe on the climb.
## Where is the options page?
The [player options page for this game](../player-options) contains all the options you need to configure and export a config file.
## What does randomization do to this game?
By default, the Prologue, the A-Side levels for Chapters 1-7, and the Epilogue are included in the randomizer. Using options, B- and C-Sides can also be included, as can the Core and Farewell chapters. One level is chosen via an option to be the "Goal Area". Obtaining the required amount of Strawberry items from the multiworld and clearing this Goal Area will grant access to the Epilogue and the Credits, which is the goal of the randomizer.
## What items get shuffled?
The main collectable in this game is Strawberries, which you must collect to complete the game.
16 Crystal Heart items are included as filler items (Heart Gates are disabled in this mod). Any additional space in the item pool is filled by Raspberries, which do nothing, and Traps.
The following interactable items are included in the item pool, so long as any active level includes them:
- Springs
- Dash Refills
- Traffic Blocks
- Pink Cassette Blocks
- Blue Cassette Blocks
- Dream Blocks
- Coins
- Strawberry Seeds
- Sinking Platforms
- Moving Platforms
- Blue Clouds
- Pink Clouds
- Blue Boosters
- Red Boosters
- Move Blocks
- White Block
- Swap Blocks
- Dash Switches
- Torches
- Theo Crystal
- Feathers
- Bumpers
- Kevins
- Badeline Boosters
- Fire and Ice Balls
- Core Toggles
- Core Blocks
- Pufferfish
- Jellyfish
- Double Dash Refills
- Breaker Boxes
- Yellow Cassette Blocks
- Green Cassette Blocks
- Bird
Additionally, the following items can optionally be included in the Item Pool:
- Keys
- Checkpoints
- Summit Gems
- One Cassette per active level
Finally, the following Traps can be optionally included in the Item Pool:
- Bald Trap
- Literature Trap
- Stun Trap
- Invisible Trap
- Fast Trap
- Slow Trap
- Ice Trap
- Reverse Trap
- Screen Flip Trap
- Laughter Trap
- Hiccup Trap
- Zoom Trap
## What locations get shuffled?
By default, the locations in Celeste (Open World) which can contain items are:
- Level Clears
- Strawberries
- Crystal Hearts
- Cassettes
Additionally, the following locations can optionally be included in the Location Pool:
- Golden Strawberries
- Keys
- Checkpoints
- Summit Gems
- Cars
- Binoculars
- Rooms
## How can I get started?
To get started playing Celeste (Open World) in Archipelago, [go to the setup guide for this game](../../../tutorial/Celeste%20(Open%20World)/guide/en)

View File

@@ -0,0 +1,20 @@
# Celeste (Open World) Setup Guide
## Required Software
- The latest version of Celeste (1.4) from any official PC game distributor
- Olympus (Celeste Mod Manager) from: [Olympus Download Page](https://everestapi.github.io/)
- The latest version of the Archipelago Open World mod for Celeste from: [GitHub Release](https://github.com/PoryGoneDev/Celeste-Archipelago-Open-World/releases)
## Installation Procedures (Windows/Linux)
1. Install the latest version of Celeste (v1.4) on PC
2. Install `Olympus` (mod manager/launcher) and `Everest` (mod loader) per its instructions: [Olympus Setup Instructions](https://everestapi.github.io/)
3. Place the `Archipelago_Open_World.zip` from the GitHub release into the `mods` folder in your Celeste install
4. (Recommended) From the main menu, enter `Mod Options` and set `Debug Mode` to `Everest` or `Always`. This will give you access to a rudimentary Text Client which can be toggled with the `~` key.
## Joining a MultiWorld Game
1. Load Everest from the Olympus Launcher with the Archipelago Open World mod enabled
2. Enter the Connection Menu via the `Connect` button on the main menu
3. Use the keyboard to enter your connection information, then press the Connect button
4. Once connected, you can use the Debug Menu (opened with `~`) as a Text Client, by typing "`!ap `" followed by what you would normally enter into a Text Client

View File

@@ -22,7 +22,7 @@ if __name__ == '__main__':
response = requests.get(url)
if response.status_code != 200:
raise Exception(f"Got {response.status_code} when downloading static randomizer locations")
annotations = yaml.load(response.text, Loader=yaml.Loader)
annotations = yaml.safe_load(response.text)
static_to_archi_regions = {
area['Name']: area['Archipelago']

View File

@@ -1,3 +1,4 @@
import Utils
from Utils import read_snes_rom
from worlds.AutoWorld import World
@@ -735,9 +736,9 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
return base_rom_bytes
def get_base_rom_path(file_name: str = "") -> str:
options = Utils.get_options()
if not file_name:
file_name = options["dkc3_options"]["rom_file"]
from settings import get_settings
file_name = get_settings()["dkc3_options"]["rom_file"]
if not os.path.exists(file_name):
file_name = Utils.user_path(file_name)
return file_name

View File

@@ -3,7 +3,7 @@ import logging
from BaseClasses import Item, Tutorial, ItemClassification
from ..AutoWorld import World, WebWorld
from ..AutoWorld import InvalidItemError, World, WebWorld
from NetUtils import SlotType
@@ -47,7 +47,7 @@ class GenericWorld(World):
def create_item(self, name: str) -> Item:
if name == "Nothing":
return Item(name, ItemClassification.filler, -1, self.player)
raise KeyError(name)
raise InvalidItemError(name)
class PlandoItem(NamedTuple):

View File

@@ -3,7 +3,7 @@ Archipelago does not have a compiled release on macOS. However, it is possible t
## Prerequisite Software
Here is a list of software to install and source code to download.
1. Python 3.11 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/).
**Python 3.13 is not supported yet.**
**Python 3.14 is not supported yet.**
2. Xcode from the [macOS App Store](https://apps.apple.com/us/app/xcode/id497799835).
3. The source code from the [Archipelago releases page](https://github.com/ArchipelagoMW/Archipelago/releases).
4. The asset with darwin in the name from the [SNI Github releases page](https://github.com/alttpo/sni/releases).

Some files were not shown because too many files have changed in this diff Show More