Docs: clean up world api.md a bit (#1958)
This commit is contained in:
parent
07d74ac186
commit
ab22b11bac
|
@ -1,7 +1,7 @@
|
||||||
# apworld Specification
|
# apworld Specification
|
||||||
|
|
||||||
Archipelago depends on worlds to provide game-specific details like items, locations and output generation.
|
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.
|
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`
|
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
|
## Coding style
|
||||||
|
|
||||||
AP follows all the PEPs. When in doubt use an IDE with coding style
|
AP follows [style.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/style.md).
|
||||||
linter, for example PyCharm Community Edition.
|
When in doubt use an IDE with coding style linter, for example PyCharm Community Edition.
|
||||||
|
|
||||||
|
|
||||||
## Docstrings
|
## Docstrings
|
||||||
|
@ -44,7 +44,7 @@ class MyGameWorld(World):
|
||||||
## Definitions
|
## Definitions
|
||||||
|
|
||||||
This section will cover various classes and objects you can use for your world.
|
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`.
|
but you can find them in `BaseClasses.py`.
|
||||||
|
|
||||||
### World Class
|
### World Class
|
||||||
|
@ -56,11 +56,12 @@ game.
|
||||||
### WebWorld Class
|
### WebWorld Class
|
||||||
|
|
||||||
A `WebWorld` class contains specific attributes and methods that can be modified
|
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:
|
`theme` to be used for your game specific AP pages. Available themes:
|
||||||
|
|
||||||
| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
|
| 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"> |
|
| <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
|
### MultiWorld Object
|
||||||
|
|
||||||
The `MultiWorld` object references the whole multiworld (all items and locations
|
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
|
### Player
|
||||||
|
|
||||||
The player is just an integer in AP and is accessible through `self.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
|
### Player Options
|
||||||
|
|
||||||
Players provide customized settings for their World in the form of yamls.
|
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
|
of valid options has to be provided in `self.option_definitions`. Options are automatically
|
||||||
added to the `World` object for easy access.
|
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`
|
Any AP installation can provide settings for a world, for example a ROM file, accessible through
|
||||||
or `cls.settings.option` (new API) or `Utils.get_options()["<world>_options"]["<option>"]` (deprecated).
|
`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)
|
Refer to [settings api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/settings%20api.md)
|
||||||
for details.
|
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).
|
Special items with ID `None` can mark events (read below).
|
||||||
|
|
||||||
Other classifications include
|
Other classifications include
|
||||||
* filler: a regular item or trash item
|
* `filler`: a regular item or trash item
|
||||||
* useful: generally quite useful, but not required for anything logical
|
* `useful`: generally quite useful, but not required for anything logical
|
||||||
* trap: negative impact on the player
|
* `trap`: negative impact on the player
|
||||||
* skip_balancing: add to progression to skip balancing; e.g. currency or tokens
|
* `skip_balancing`: add to `progression` to skip balancing; e.g. currency or tokens
|
||||||
|
|
||||||
### Events
|
### 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
|
Regions are logical groups of locations that share some common access rules. If
|
||||||
location logic is written from scratch, using regions greatly simplifies the
|
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.
|
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.
|
transitions to other regions.
|
||||||
|
|
||||||
There has to be one special region "Menu" from which the logic unfolds. AP
|
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
|
### Access Rules
|
||||||
|
|
||||||
An access rule is a function that returns `True` or `False` for a `Location` or
|
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
|
### 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.
|
Conventionally, your world class is placed in that file.
|
||||||
|
|
||||||
World classes must inherit from the `World` class in `/worlds/AutoWorld.py`,
|
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.
|
AP will pick up your world automatically due to the `AutoWorld` implementation.
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
If your world needs specific python packages, they can be listed in
|
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.
|
pick up and install them.
|
||||||
|
|
||||||
See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requirements-file-format).
|
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.
|
use multiple files and use relative imports to access them.
|
||||||
|
|
||||||
e.g. `from .Options import mygame_options` from your `__init__.py` will load
|
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`
|
When imported names pile up it may be easier to use `from . import Options`
|
||||||
and access the variable as `Options.mygame_options`.
|
and access the variable as `Options.mygame_options`.
|
||||||
|
@ -228,12 +230,12 @@ function, see [apworld specification.md](apworld%20specification.md).
|
||||||
|
|
||||||
### Your Item Type
|
### 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".
|
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
|
Since the constructor is only ever called from your code, you can add whatever
|
||||||
arguments you like to the constructor.
|
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
|
```python
|
||||||
from BaseClasses import Item
|
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
|
to describe it and a `display_name` property for display on the website and in
|
||||||
spoiler logs.
|
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`.
|
assigned to the world under `self.option_definitions`.
|
||||||
|
|
||||||
Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, `Range`.
|
Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, `Range`.
|
||||||
|
@ -329,10 +331,10 @@ class FixXYZGlitch(Toggle):
|
||||||
display_name = "Fix XYZ Glitch"
|
display_name = "Fix XYZ Glitch"
|
||||||
|
|
||||||
# By convention we call the options dict variable `<world>_options`.
|
# 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,
|
"difficulty": Difficulty,
|
||||||
"final_boss_hp": FinalBossHP,
|
"final_boss_hp": FinalBossHP,
|
||||||
"fix_xyz_glitch": FixXYZGlitch
|
"fix_xyz_glitch": FixXYZGlitch,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
```python
|
```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 .Locations import mygame_locations # same as above
|
||||||
from worlds.AutoWorld import World
|
from worlds.AutoWorld import World
|
||||||
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
|
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
|
||||||
from Utils import get_options, output_path
|
|
||||||
|
|
||||||
|
|
||||||
class MyGameItem(Item): # or from Items import MyGameItem
|
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
|
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
|
# 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
|
base_id = 1234
|
||||||
# Instead of dynamic numbering, IDs could be part of data.
|
# 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
|
# 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
|
# from that group has been collected. Group names can also be used for !hint
|
||||||
item_name_groups = {
|
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
|
* locations placed inside those regions
|
||||||
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
|
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
|
||||||
* applying `self.multiworld.push_precollected` for start inventory
|
* 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
|
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.
|
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
|
# require one item from an item group
|
||||||
add_rule(self.multiworld.get_location("Chest3", self.player),
|
add_rule(self.multiworld.get_location("Chest3", self.player),
|
||||||
lambda state: state.has_group("weapons", 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
|
# and .count_group() for groups
|
||||||
# set_rule is likely to be a bit faster than add_rule
|
# 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,
|
More advanced uses could be to add additional variables to the state object,
|
||||||
override `World.collect(self, state, item)` and `remove(self, state, item)`
|
override `World.collect(self, state, item)` and `remove(self, state, item)`
|
||||||
to update the state object, and check those added variables in added methods.
|
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
|
#### Sample
|
||||||
|
|
||||||
|
@ -637,7 +638,7 @@ from worlds.AutoWorld import LogicMixin
|
||||||
class MyGameLogic(LogicMixin):
|
class MyGameLogic(LogicMixin):
|
||||||
def mygame_has_key(self, player: int):
|
def mygame_has_key(self, player: int):
|
||||||
# Arguments above are free to choose
|
# 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
|
# MyGameWorld instance for easy options access is also a valid approach
|
||||||
return self.has("key", player) # or whatever
|
return self.has("key", player) # or whatever
|
||||||
```
|
```
|
||||||
|
@ -650,8 +651,8 @@ import .Logic # apply the mixin by importing its file
|
||||||
class MyGameWorld(World):
|
class MyGameWorld(World):
|
||||||
# ...
|
# ...
|
||||||
def set_rules(self):
|
def set_rules(self):
|
||||||
set_rule(self.world.get_location("A Door", self.player),
|
set_rule(self.multiworld.get_location("A Door", self.player),
|
||||||
lamda state: state.mygame_has_key(self.player))
|
lambda state: state.mygame_has_key(self.player))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generate Output
|
### Generate Output
|
||||||
|
@ -679,14 +680,14 @@ def generate_output(self, output_directory: str):
|
||||||
# store option name "easy", "normal" or "hard" for difficuly
|
# store option name "easy", "normal" or "hard" for difficuly
|
||||||
"difficulty": self.multiworld.difficulty[self.player].current_key,
|
"difficulty": self.multiworld.difficulty[self.player].current_key,
|
||||||
# store option value True or False for fixing a glitch
|
# 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
|
# point to a ROM specified by the installation
|
||||||
src = self.settings.rom_file
|
src = self.settings.rom_file
|
||||||
# or point to worlds/mygame/data/mod_template
|
# or point to worlds/mygame/data/mod_template
|
||||||
src = os.path.join(os.path.dirname(__file__), "data", "mod_template")
|
src = os.path.join(os.path.dirname(__file__), "data", "mod_template")
|
||||||
# generate output path
|
# 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")
|
out_file = os.path.join(output_directory, mod_name + ".zip")
|
||||||
# generate the file
|
# generate the file
|
||||||
generate_mod(src, out_file, data)
|
generate_mod(src, out_file, data)
|
||||||
|
@ -735,14 +736,14 @@ from . import MyGameTestBase
|
||||||
|
|
||||||
|
|
||||||
class TestChestAccess(MyGameTestBase):
|
class TestChestAccess(MyGameTestBase):
|
||||||
def testSwordChests(self):
|
def test_sword_chests(self):
|
||||||
"""Test locations that require a sword"""
|
"""Test locations that require a sword"""
|
||||||
locations = ["Chest1", "Chest2"]
|
locations = ["Chest1", "Chest2"]
|
||||||
items = [["Sword"]]
|
items = [["Sword"]]
|
||||||
# this will test that each location can't be accessed without the "Sword", but can be accessed once obtained.
|
# this will test that each location can't be accessed without the "Sword", but can be accessed once obtained.
|
||||||
self.assertAccessDependency(locations, items)
|
self.assertAccessDependency(locations, items)
|
||||||
|
|
||||||
def testAnyWeaponChests(self):
|
def test_any_weapon_chests(self):
|
||||||
"""Test locations that require any weapon"""
|
"""Test locations that require any weapon"""
|
||||||
locations = [f"Chest{i}" for i in range(3, 6)]
|
locations = [f"Chest{i}" for i in range(3, 6)]
|
||||||
items = [["Sword"], ["Axe"], ["Spear"]]
|
items = [["Sword"], ["Axe"], ["Spear"]]
|
||||||
|
|
Loading…
Reference in New Issue