Docs: clean up world api.md a bit (#1958)

This commit is contained in:
el-u 2023-07-09 18:04:24 +02:00 committed by GitHub
parent 07d74ac186
commit ab22b11bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 43 additions and 42 deletions

View File

@ -1,7 +1,7 @@
# apworld Specification
Archipelago depends on worlds to provide game-specific details like items, locations and output generation.
Those are located in the `worlds/` folder (source) or `<insall dir>/lib/worlds/` (when installed).
Those are located in the `worlds/` folder (source) or `<install dir>/lib/worlds/` (when installed).
See [world api.md](world%20api.md) for details.
apworld provides a way to package and ship a world that is not part of the main distribution by placing a `*.apworld`

View File

@ -22,8 +22,8 @@ allows using WebSockets.
## Coding style
AP follows all the PEPs. When in doubt use an IDE with coding style
linter, for example PyCharm Community Edition.
AP follows [style.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/style.md).
When in doubt use an IDE with coding style linter, for example PyCharm Community Edition.
## Docstrings
@ -44,7 +44,7 @@ class MyGameWorld(World):
## Definitions
This section will cover various classes and objects you can use for your world.
While some of the attributes and methods are mentioned here not all of them are,
While some of the attributes and methods are mentioned here, not all of them are,
but you can find them in `BaseClasses.py`.
### World Class
@ -56,11 +56,12 @@ game.
### WebWorld Class
A `WebWorld` class contains specific attributes and methods that can be modified
for your world specifically on the webhost.
for your world specifically on the webhost:
`settings_page` which can be changed to a link instead of an AP generated settings page.
`settings_page`, which can be changed to a link instead of an AP generated settings page.
`theme` to be used for your game specific AP pages. Available themes:
| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
|---|---|---|---|---|---|---|---|
| <img src="img/theme_dirt.JPG" width="100"> | <img src="img/theme_grass.JPG" width="100"> | <img src="img/theme_grassFlowers.JPG" width="100"> | <img src="img/theme_ice.JPG" width="100"> | <img src="img/theme_jungle.JPG" width="100"> | <img src="img/theme_ocean.JPG" width="100"> | <img src="img/theme_partyTime.JPG" width="100"> | <img src="img/theme_stone.JPG" width="100"> |
@ -75,26 +76,27 @@ prefixed with the same string as defined here. Default already has 'en'.
### MultiWorld Object
The `MultiWorld` object references the whole multiworld (all items and locations
for all players) and is accessible through `self.world` inside a `World` object.
for all players) and is accessible through `self.multiworld` inside a `World` object.
### Player
The player is just an integer in AP and is accessible through `self.player`
inside a World object.
inside a `World` object.
### Player Options
Players provide customized settings for their World in the form of yamls.
Those are accessible through `self.world.<option_name>[self.player]`. A dict
Those are accessible through `self.multiworld.<option_name>[self.player]`. A dict
of valid options has to be provided in `self.option_definitions`. Options are automatically
added to the `World` object for easy access.
### World Options
### World Settings
Any AP installation can provide settings for a world, for example a ROM file, accessible through `self.settings.option`
or `cls.settings.option` (new API) or `Utils.get_options()["<world>_options"]["<option>"]` (deprecated).
Any AP installation can provide settings for a world, for example a ROM file, accessible through
`self.settings.<setting_name>` or `cls.settings.<setting_name>` (new API)
or `Utils.get_options()["<world>_options"]["<setting_name>"]` (deprecated).
Users can set those in their `host.yaml` file. Some options may automatically open a file browser if a file is missing.
Users can set those in their `host.yaml` file. Some settings may automatically open a file browser if a file is missing.
Refer to [settings api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/settings%20api.md)
for details.
@ -135,10 +137,10 @@ same ID. Name must not be numeric (has to contain at least 1 letter or symbol).
Special items with ID `None` can mark events (read below).
Other classifications include
* filler: a regular item or trash item
* useful: generally quite useful, but not required for anything logical
* trap: negative impact on the player
* skip_balancing: add to progression to skip balancing; e.g. currency or tokens
* `filler`: a regular item or trash item
* `useful`: generally quite useful, but not required for anything logical
* `trap`: negative impact on the player
* `skip_balancing`: add to `progression` to skip balancing; e.g. currency or tokens
### Events
@ -162,10 +164,10 @@ or more event locations based on player options.
Regions are logical groups of locations that share some common access rules. If
location logic is written from scratch, using regions greatly simplifies the
definition and allow to somewhat easily implement things like entrance
definition and allows to somewhat easily implement things like entrance
randomizer in logic.
Regions have a list called `exits` which are `Entrance` objects representing
Regions have a list called `exits`, which are `Entrance` objects representing
transitions to other regions.
There has to be one special region "Menu" from which the logic unfolds. AP
@ -182,7 +184,7 @@ They can be static (regular logic) or be defined/connected during generation
### Access Rules
An access rule is a function that returns `True` or `False` for a `Location` or
`Entrance` based on the the current `state` (items that can be collected).
`Entrance` based on the current `state` (items that can be collected).
### Item Rules
@ -199,14 +201,14 @@ the `/worlds` directory. The starting point for the package is `__init__.py`.
Conventionally, your world class is placed in that file.
World classes must inherit from the `World` class in `/worlds/AutoWorld.py`,
which can be imported as `worlds.AutoWorld.World` from your package.
which can be imported as `from worlds.AutoWorld import World` from your package.
AP will pick up your world automatically due to the `AutoWorld` implementation.
### Requirements
If your world needs specific python packages, they can be listed in
`world/[world_name]/requirements.txt`. ModuleUpdate.py will automatically
`worlds/<world_name>/requirements.txt`. ModuleUpdate.py will automatically
pick up and install them.
See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requirements-file-format).
@ -217,7 +219,7 @@ AP will only import the `__init__.py`. Depending on code size it makes sense to
use multiple files and use relative imports to access them.
e.g. `from .Options import mygame_options` from your `__init__.py` will load
`world/[world_name]/Options.py` and make its `mygame_options` accesible.
`worlds/<world_name>/Options.py` and make its `mygame_options` accessible.
When imported names pile up it may be easier to use `from . import Options`
and access the variable as `Options.mygame_options`.
@ -228,12 +230,12 @@ function, see [apworld specification.md](apworld%20specification.md).
### Your Item Type
Each world uses its own subclass of `BaseClasses.Item`. The constuctor can be
Each world uses its own subclass of `BaseClasses.Item`. The constructor can be
overridden to attach additional data to it, e.g. "price in shop".
Since the constructor is only ever called from your code, you can add whatever
arguments you like to the constructor.
In its simplest form we only set the game name and use the default constuctor
In its simplest form we only set the game name and use the default constructor
```python
from BaseClasses import Item
@ -268,7 +270,7 @@ Each option has its own class, inherits from a base option type, has a docstring
to describe it and a `display_name` property for display on the website and in
spoiler logs.
The actual name as used in the yaml is defined in a `dict[str, Option]`, that is
The actual name as used in the yaml is defined in a `Dict[str, AssembleOptions]`, that is
assigned to the world under `self.option_definitions`.
Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, `Range`.
@ -329,10 +331,10 @@ class FixXYZGlitch(Toggle):
display_name = "Fix XYZ Glitch"
# By convention we call the options dict variable `<world>_options`.
mygame_options: typing.Dict[str, type(Option)] = {
mygame_options: typing.Dict[str, AssembleOptions] = {
"difficulty": Difficulty,
"final_boss_hp": FinalBossHP,
"fix_xyz_glitch": FixXYZGlitch
"fix_xyz_glitch": FixXYZGlitch,
}
```
```python
@ -359,7 +361,6 @@ from .Items import mygame_items # data used below to add items to the World
from .Locations import mygame_locations # same as above
from worlds.AutoWorld import World
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
from Utils import get_options, output_path
class MyGameItem(Item): # or from Items import MyGameItem
@ -385,7 +386,7 @@ class MyGameWorld(World):
topology_present = True # show path to required location checks in spoiler
# ID of first item and location, could be hard-coded but code may be easier
# to read with this as a propery.
# to read with this as a property.
base_id = 1234
# Instead of dynamic numbering, IDs could be part of data.
@ -400,7 +401,7 @@ class MyGameWorld(World):
# Items can be grouped using their names to allow easy checking if any item
# from that group has been collected. Group names can also be used for !hint
item_name_groups = {
"weapons": {"sword", "lance"}
"weapons": {"sword", "lance"},
}
```
@ -414,7 +415,7 @@ The world has to provide the following things for generation
* locations placed inside those regions
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
* applying `self.multiworld.push_precollected` for start inventory
* `required_client_version: Tuple(int, int, int)`
* `required_client_version: Tuple[int, int, int]`
Optional client version as tuple of 3 ints to make sure the client is compatible to
this world (e.g. implements all required features) when connecting.
@ -587,7 +588,7 @@ def set_rules(self) -> None:
# require one item from an item group
add_rule(self.multiworld.get_location("Chest3", self.player),
lambda state: state.has_group("weapons", self.player))
# state also has .item_count() for items, .has_any() and.has_all() for sets
# state also has .item_count() for items, .has_any() and .has_all() for sets
# and .count_group() for groups
# set_rule is likely to be a bit faster than add_rule
@ -625,7 +626,7 @@ public members with `mygame_`.
More advanced uses could be to add additional variables to the state object,
override `World.collect(self, state, item)` and `remove(self, state, item)`
to update the state object, and check those added variables in added methods.
Please do this with caution and only when neccessary.
Please do this with caution and only when necessary.
#### Sample
@ -637,7 +638,7 @@ from worlds.AutoWorld import LogicMixin
class MyGameLogic(LogicMixin):
def mygame_has_key(self, player: int):
# Arguments above are free to choose
# MultiWorld can be accessed through self.world, explicitly passing in
# MultiWorld can be accessed through self.multiworld, explicitly passing in
# MyGameWorld instance for easy options access is also a valid approach
return self.has("key", player) # or whatever
```
@ -650,8 +651,8 @@ import .Logic # apply the mixin by importing its file
class MyGameWorld(World):
# ...
def set_rules(self):
set_rule(self.world.get_location("A Door", self.player),
lamda state: state.mygame_has_key(self.player))
set_rule(self.multiworld.get_location("A Door", self.player),
lambda state: state.mygame_has_key(self.player))
```
### Generate Output
@ -679,14 +680,14 @@ def generate_output(self, output_directory: str):
# store option name "easy", "normal" or "hard" for difficuly
"difficulty": self.multiworld.difficulty[self.player].current_key,
# store option value True or False for fixing a glitch
"fix_xyz_glitch": self.multiworld.fix_xyz_glitch[self.player].value
"fix_xyz_glitch": self.multiworld.fix_xyz_glitch[self.player].value,
}
# point to a ROM specified by the installation
src = self.settings.rom_file
# or point to worlds/mygame/data/mod_template
src = os.path.join(os.path.dirname(__file__), "data", "mod_template")
# generate output path
mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}"
mod_name = self.multiworld.get_out_file_name_base(self.player)
out_file = os.path.join(output_directory, mod_name + ".zip")
# generate the file
generate_mod(src, out_file, data)
@ -735,14 +736,14 @@ from . import MyGameTestBase
class TestChestAccess(MyGameTestBase):
def testSwordChests(self):
def test_sword_chests(self):
"""Test locations that require a sword"""
locations = ["Chest1", "Chest2"]
items = [["Sword"]]
# this will test that each location can't be accessed without the "Sword", but can be accessed once obtained.
self.assertAccessDependency(locations, items)
def testAnyWeaponChests(self):
def test_any_weapon_chests(self):
"""Test locations that require any weapon"""
locations = [f"Chest{i}" for i in range(3, 6)]
items = [["Sword"], ["Axe"], ["Spear"]]