Docs: minor updates to recommend modern PEP8 (#2384)

* docs: update world api for modern PEP8 conventions

* docs: update options api for modern PEP8 styling

* missed a spot
This commit is contained in:
Aaron Wagener 2023-11-15 10:07:42 -06:00 committed by GitHub
parent 2af5410301
commit bf8432faa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 46 deletions

View File

@ -29,6 +29,7 @@ The zip can contain arbitrary files in addition what was specified above.
## Caveats
Imports from other files inside the apworld have to use relative imports.
Imports from other files inside the apworld have to use relative imports. e.g. `from .options import MyGameOptions`
Imports from AP base have to use absolute imports, e.g. Options.py and worlds/AutoWorld.py.
Imports from AP base have to use absolute imports, e.g. `from Options import Toggle` or
`from worlds.AutoWorld import World`

View File

@ -31,7 +31,7 @@ As an example, suppose we want an option that lets the user start their game wit
create our option class (with a docstring), give it a `display_name`, and add it to our game's options dataclass:
```python
# Options.py
# options.py
from dataclasses import dataclass
from Options import Toggle, PerGameCommonOptions

View File

@ -286,11 +286,11 @@ See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requireme
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 MyGameOptions` from your `__init__.py` will load
`world/[world_name]/Options.py` and make its `MyGameOptions` accessible.
e.g. `from .options import MyGameOptions` from your `__init__.py` will load
`world/[world_name]/options.py` and make its `MyGameOptions` accessible.
When imported names pile up it may be easier to use `from . import Options`
and access the variable as `Options.MyGameOptions`.
When imported names pile up it may be easier to use `from . import options`
and access the variable as `options.MyGameOptions`.
Imports from directories outside your world should use absolute imports.
Correct use of relative / absolute imports is required for zipped worlds to
@ -311,7 +311,7 @@ class MyGameItem(Item):
game: str = "My Game"
```
By convention this class definition will either be placed in your `__init__.py`
or your `Items.py`. For a more elaborate example see `worlds/oot/Items.py`.
or your `items.py`. For a more elaborate example see `worlds/oot/Items.py`.
### Your location type
@ -323,15 +323,15 @@ class MyGameLocation(Location):
game: str = "My Game"
# override constructor to automatically mark event locations as such
def __init__(self, player: int, name = "", code = None, parent = None):
def __init__(self, player: int, name = "", code = None, parent = None) -> None:
super(MyGameLocation, self).__init__(player, name, code, parent)
self.event = code is None
```
in your `__init__.py` or your `Locations.py`.
in your `__init__.py` or your `locations.py`.
### Options
By convention options are defined in `Options.py` and will be used when parsing
By convention options are defined in `options.py` and will be used when parsing
the players' yaml files.
Each option has its own class, inherits from a base option type, has a docstring
@ -347,7 +347,7 @@ For more see `Options.py` in AP's base directory.
#### Toggle, DefaultOnToggle
Those don't need any additional properties defined. After parsing the option,
These don't need any additional properties defined. After parsing the option,
its `value` will either be True or False.
#### Range
@ -373,7 +373,7 @@ default = 0
#### Sample
```python
# Options.py
# options.py
from dataclasses import dataclass
from Options import Toggle, Range, Choice, PerGameCommonOptions
@ -412,7 +412,7 @@ class MyGameOptions(PerGameCommonOptions):
# __init__.py
from worlds.AutoWorld import World
from .Options import MyGameOptions # import the options dataclass
from .options import MyGameOptions # import the options dataclass
class MyGameWorld(World):
@ -429,9 +429,9 @@ class MyGameWorld(World):
import settings
import typing
from .Options import MyGameOptions # the options we defined earlier
from .Items import mygame_items # data used below to add items to the World
from .Locations import mygame_locations # same as above
from .options import MyGameOptions # the options we defined earlier
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
@ -490,7 +490,7 @@ The world has to provide the following things for generation
* additions to the regions list: at least one called "Menu"
* 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
* applying `self.multiworld.push_precollected` for world defined start inventory
* `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.
@ -500,31 +500,32 @@ In addition, the following methods can be implemented and are called in this ord
* `stage_assert_generate(cls, multiworld)` is a class method called at the start of
generation to check the existence of prerequisite files, usually a ROM for
games which require one.
* `def generate_early(self)`
called per player before any items or locations are created. You can set
properties on your world here. Already has access to player options and RNG.
* `def create_regions(self)`
* `generate_early(self)`
called per player before any items or locations are created. You can set properties on your world here. Already has
access to player options and RNG. This is the earliest step where the world should start setting up for the current
multiworld as any steps before this, the multiworld itself is still getting set up
* `create_regions(self)`
called to place player's regions and their locations into the MultiWorld's regions list. If it's
hard to separate, this can be done during `generate_early` or `create_items` as well.
* `def create_items(self)`
* `create_items(self)`
called to place player's items into the MultiWorld's itempool. After this step all regions and items have to be in
the MultiWorld's regions and itempool, and these lists should not be modified afterwards.
* `def set_rules(self)`
* `set_rules(self)`
called to set access and item rules on locations and entrances.
Locations have to be defined before this, or rule application can miss them.
* `def generate_basic(self)`
* `generate_basic(self)`
called after the previous steps. Some placement and player specific
randomizations can be done here.
* `pre_fill`, `fill_hook` and `post_fill` are called to modify item placement
* `pre_fill(self)`, `fill_hook(self)` and `post_fill(self)` are called to modify item placement
before, during and after the regular fill process, before `generate_output`.
If items need to be placed during pre_fill, these items can be determined
and created using `get_prefill_items`
* `def generate_output(self, output_directory: str)` that creates the output
* `generate_output(self, output_directory: str)` that creates the output
files if there is output to be generated. When this is
called, `self.multiworld.get_locations(self.player)` has all locations for the player, with
attribute `item` pointing to the item.
`location.item.player` can be used to see if it's a local item.
* `fill_slot_data` and `modify_multidata` can be used to modify the data that
* `fill_slot_data(self)` and `modify_multidata(self, multidata: Dict[str, Any])` can be used to modify the data that
will be used by the server to host the MultiWorld.
@ -541,9 +542,9 @@ def generate_early(self) -> None:
```python
# we need a way to know if an item provides progress in the game ("key item")
# this can be part of the items definition, or depend on recipe randomization
from .Items import is_progression # this is just a dummy
from .items import is_progression # this is just a dummy
def create_item(self, item: str):
def create_item(self, item: str) -> MyGameItem:
# This is called when AP wants to create an item by name (for plando) or
# when you call it from your own code.
classification = ItemClassification.progression if is_progression(item) else \
@ -551,7 +552,7 @@ def create_item(self, item: str):
return MyGameItem(item, classification, self.item_name_to_id[item],
self.player)
def create_event(self, event: str):
def create_event(self, event: str) -> MyGameItem:
# while we are at it, we can also add a helper to create events
return MyGameItem(event, True, None, self.player)
```
@ -644,7 +645,7 @@ def generate_basic(self) -> None:
```python
from worlds.generic.Rules import add_rule, set_rule, forbid_item
from Items import get_item_type
from .items import get_item_type
def set_rules(self) -> None:
@ -713,12 +714,12 @@ Please do this with caution and only when necessary.
#### Sample
```python
# Logic.py
# logic.py
from worlds.AutoWorld import LogicMixin
class MyGameLogic(LogicMixin):
def mygame_has_key(self, player: int):
def mygame_has_key(self, player: int) -> bool:
# Arguments above are free to choose
# MultiWorld can be accessed through self.multiworld, explicitly passing in
# MyGameWorld instance for easy options access is also a valid approach
@ -728,11 +729,11 @@ class MyGameLogic(LogicMixin):
# __init__.py
from worlds.generic.Rules import set_rule
import .Logic # apply the mixin by importing its file
import .logic # apply the mixin by importing its file
class MyGameWorld(World):
# ...
def set_rules(self):
def set_rules(self) -> None:
set_rule(self.multiworld.get_location("A Door", self.player),
lambda state: state.mygame_has_key(self.player))
```
@ -740,10 +741,10 @@ class MyGameWorld(World):
### Generate Output
```python
from .Mod import generate_mod
from .mod import generate_mod
def generate_output(self, output_directory: str):
def generate_output(self, output_directory: str) -> None:
# How to generate the mod or ROM highly depends on the game
# if the mod is written in Lua, Jinja can be used to fill a template
# if the mod reads a json file, `json.dump()` can be used to generate that
@ -758,12 +759,10 @@ def generate_output(self, output_directory: str):
# make sure to mark as not remote_start_inventory when connecting if stored in rom/mod
"starter_items": [item.name for item
in self.multiworld.precollected_items[self.player]],
"final_boss_hp": self.final_boss_hp,
# store option name "easy", "normal" or "hard" for difficuly
"difficulty": self.options.difficulty.current_key,
# store option value True or False for fixing a glitch
"fix_xyz_glitch": self.options.fix_xyz_glitch.value,
}
# add needed option results to the dictionary
data.update(self.options.as_dict("final_boss_hp", "difficulty", "fix_xyz_glitch"))
# point to a ROM specified by the installation
src = self.settings.rom_file
# or point to worlds/mygame/data/mod_template
@ -787,7 +786,7 @@ data already exists on the server. The most common usage of slot data is to send
to be aware of.
```python
def fill_slot_data(self):
def fill_slot_data(self) -> Dict[str, Any]:
# in order for our game client to handle the generated seed correctly we need to know what the user selected
# for their difficulty and final boss HP
# a dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting
@ -839,14 +838,14 @@ from . import MyGameTestBase
class TestChestAccess(MyGameTestBase):
def test_sword_chests(self):
def test_sword_chests(self) -> None:
"""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 test_any_weapon_chests(self):
def test_any_weapon_chests(self) -> None:
"""Test locations that require any weapon"""
locations = [f"Chest{i}" for i in range(3, 6)]
items = [["Sword"], ["Axe"], ["Spear"]]