mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00

* Docs: add a "missable locations" question to apworld_dev_faq.md Basically turning the conversation at https://discord.com/channels/731205301247803413/1214608557077700720/1368996789260128388 into a FAQ entry. * feedback * qwint feedback * Update docs/apworld_dev_faq.md Co-authored-by: Scipio Wright <scipiowright@gmail.com> --------- Co-authored-by: Scipio Wright <scipiowright@gmail.com>
143 lines
9.1 KiB
Markdown
143 lines
9.1 KiB
Markdown
# APWorld Dev FAQ
|
|
|
|
This document is meant as a reference tool to show solutions to common problems when developing an apworld.
|
|
It is not intended to answer every question about Archipelago and it assumes you have read the other docs,
|
|
including [Contributing](contributing.md), [Adding Games](<adding games.md>), and [World API](<world api.md>).
|
|
|
|
---
|
|
|
|
### My game has a restrictive start that leads to fill errors
|
|
|
|
A "restrictive start" here means having a combination of very few sphere 1 locations and potentially requiring more
|
|
than one item to get a player to sphere 2.
|
|
|
|
One way to fix this is to hint to the Generator that an item needs to be in sphere one with local_early_items.
|
|
Here, `1` represents the number of "Sword" items the Generator will attempt to place in sphere one.
|
|
```py
|
|
early_item_name = "Sword"
|
|
self.multiworld.local_early_items[self.player][early_item_name] = 1
|
|
```
|
|
|
|
Some alternative ways to try to fix this problem are:
|
|
* Add more locations to sphere one of your world, potentially only when there would be a restrictive start
|
|
* Pre-place items yourself, such as during `create_items`
|
|
* Put items into the player's starting inventory using `push_precollected`
|
|
* Raise an exception, such as an `OptionError` during `generate_early`, to disallow options that would lead to a
|
|
restrictive start
|
|
|
|
---
|
|
|
|
### I have multiple options that change the item/location pool counts and need to make sure I am not submitting more/fewer items than locations
|
|
|
|
In an ideal situation your system for producing locations and items wouldn't leave any opportunity for them to be
|
|
unbalanced. But in real, complex situations, that might be unfeasible.
|
|
|
|
If that's the case, you can create extra filler based on the difference between your unfilled locations and your
|
|
itempool by comparing [get_unfilled_locations](https://github.com/ArchipelagoMW/Archipelago/blob/main/BaseClasses.py#:~:text=get_unfilled_locations)
|
|
to your list of items to submit
|
|
|
|
Note: to use self.create_filler(), self.get_filler_item_name() should be defined to only return valid filler item names
|
|
```py
|
|
total_locations = len(self.multiworld.get_unfilled_locations(self.player))
|
|
item_pool = self.create_non_filler_items()
|
|
|
|
for _ in range(total_locations - len(item_pool)):
|
|
item_pool.append(self.create_filler())
|
|
|
|
self.multiworld.itempool += item_pool
|
|
```
|
|
|
|
A faster alternative to the `for` loop would be to use a
|
|
[list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions):
|
|
```py
|
|
item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))]
|
|
```
|
|
|
|
---
|
|
|
|
### I learned about indirect conditions in the world API document, but I want to know more. What are they and why are they necessary?
|
|
|
|
The world API document mentions how to use `multiworld.register_indirect_condition` to register indirect conditions and
|
|
**when** you should use them, but not *how* they work and *why* they are necessary. This is because the explanation is
|
|
quite complicated.
|
|
|
|
Region sweep (the algorithm that determines which regions are reachable) is a Breadth-First Search of the region graph.
|
|
It starts from the origin region, checks entrances one by one, and adds newly reached regions and their entrances to
|
|
the queue until there is nothing more to check.
|
|
|
|
For performance reasons, AP only checks every entrance once. However, if an entrance's access_rule depends on region
|
|
access, then the following may happen:
|
|
1. The entrance is checked and determined to be nontraversable because the region in its access_rule hasn't been
|
|
reached yet during the graph search.
|
|
2. Then, the region in its access_rule is determined to be reachable.
|
|
|
|
This entrance *would* be in logic if it were rechecked, but it won't be rechecked this cycle.
|
|
To account for this case, AP would have to recheck all entrances every time a new region is reached until no new
|
|
regions are reached.
|
|
|
|
An indirect condition is how you can manually define that a specific entrance needs to be rechecked during region sweep
|
|
if a specific region is reached during it.
|
|
This keeps most of the performance upsides. Even in a game making heavy use of indirect conditions (ex: The Witness),
|
|
using them is significantly faster than just "rechecking each entrance until nothing new is found".
|
|
The reason entrance access rules using `location.can_reach` and `entrance.can_reach` are also affected is because they
|
|
call `region.can_reach` on their respective parent/source region.
|
|
|
|
We recognize it can feel like a trap since it will not alert you when you are missing an indirect condition,
|
|
and that some games have very complex access rules.
|
|
As of [PR #3682 (Core: Region handling customization)](https://github.com/ArchipelagoMW/Archipelago/pull/3682)
|
|
being merged, it is possible for a world to opt out of indirect conditions entirely, instead using the system of
|
|
checking each entrance whenever a region has been reached, although this does come with a performance cost.
|
|
Opting out of using indirect conditions should only be used by games that *really* need it. For most games, it should
|
|
be reasonable to know all entrance → region dependencies, making indirect conditions preferred because they are
|
|
much faster.
|
|
|
|
---
|
|
|
|
### I uploaded the generated output of my world to the webhost and webhost is erroring on corrupted multidata
|
|
|
|
The error `Could not load multidata. File may be corrupted or incompatible.` occurs when uploading a locally generated
|
|
file where there is an issue with the multidata contained within it. It may come with a description like
|
|
`(No module named 'worlds.myworld')` or `(global 'worlds.myworld.names.ItemNames' is forbidden)`
|
|
|
|
Pickling is a way to compress python objects such that they can be decompressed and be used to rebuild the
|
|
python objects. This means that if one of your custom class instances ends up in the multidata, the server would not
|
|
be able to load that custom class to decompress the data, which can fail either because the custom class is unknown
|
|
(because it cannot load your world module) or the class it's attempting to import to decompress is deemed unsafe.
|
|
|
|
Common situations where this can happen include:
|
|
* Using Option instances directly in slot_data. Ex: using `options.option_name` instead of `options.option_name.value`.
|
|
Also, consider using the `options.as_dict("option_name", "option_two")` helper.
|
|
* Using enums as Location/Item names in the datapackage. When building out `location_name_to_id` and `item_name_to_id`,
|
|
make sure that you are not using your enum class for either the names or ids in these mappings.
|
|
|
|
---
|
|
|
|
### Some locations are technically possible to check with few or no items, but they'd be very tedious or frustrating. How do worlds deal with this?
|
|
|
|
Sometimes the game can be modded to skip these locations or make them less tedious. But when this issue is due to a fundamental aspect of the game, then the general answer is "soft logic" (and its subtypes like "combat logic", "money logic", etc.). For example: you can logically require that a player have several helpful items before fighting the final boss, even if a skilled player technically needs no items to beat it. Randomizer logic should describe what's *fun* rather than what's technically possible.
|
|
|
|
Concrete examples of soft logic include:
|
|
- Defeating a boss might logically require health upgrades, damage upgrades, certain weapons, etc. that aren't strictly necessary.
|
|
- Entering a high-level area might logically require access to enough other parts of the game that checking other locations should naturally get the player to the soft-required level.
|
|
- Buying expensive shop items might logically require access to a place where you can quickly farm money, or logically require access to enough parts of the game that checking other locations should naturally generate enough money without grinding.
|
|
|
|
Remember that all items referenced by logic (however hard or soft) must be `progression`. Since you typically don't want to turn a ton of `filler` items into `progression` just for this, it's common to e.g. write money logic using only the rare "$100" item, so the dozens of "$1" and "$10" items in your world can remain `filler`.
|
|
|
|
---
|
|
|
|
### What if my game has "missable" or "one-time-only" locations or region connections?
|
|
|
|
Archipelago logic assumes that once a region or location becomes reachable, it stays reachable forever, no matter what
|
|
the player does in-game. Slightly more formally: Receiving an AP item must never cause a region connection or location
|
|
to "go out of logic" (become unreachable when it was previously reachable), and receiving AP items is the only kind of
|
|
state change that AP logic acknowledges. No other actions or events can change reachability.
|
|
|
|
So when the game itself does not follow this assumption, the options are:
|
|
- Modify the game to make that location/connection repeatable
|
|
- If there are both missable and repeatable ways to check the location/traverse the connection, then write logic for
|
|
only the repeatable ways
|
|
- Don't generate the missable location/connection at all
|
|
- For connections, any logical regions will still need to be reachable through other, *repeatable* connections
|
|
- For locations, this may require game changes to remove the vanilla item if it affects logic
|
|
- Decide that resetting the save file is part of the game's logic, and warn players about that
|