Docs: clean up world api.md a bit (#1958)
This commit is contained in:
parent
07d74ac186
commit
ab22b11bac
|
@ -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`
|
||||
|
|
|
@ -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"]]
|
||||
|
|
Loading…
Reference in New Issue