Skip to content

Commit

Permalink
Docs: clean up world api.md a bit (ArchipelagoMW#1958)
Browse files Browse the repository at this point in the history
  • Loading branch information
el-u authored Jul 9, 2023
1 parent 07d74ac commit ab22b11
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 42 deletions.
2 changes: 1 addition & 1 deletion docs/apworld specification.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
83 changes: 42 additions & 41 deletions docs/world api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"> |
Expand All @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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

Expand All @@ -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).
Expand All @@ -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`.
Expand All @@ -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

Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.

Expand All @@ -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"},
}
```

Expand All @@ -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.

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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
```
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"]]
Expand Down

0 comments on commit ab22b11

Please sign in to comment.