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:
parent
2af5410301
commit
bf8432faa7
|
@ -29,6 +29,7 @@ The zip can contain arbitrary files in addition what was specified above.
|
||||||
|
|
||||||
## Caveats
|
## 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`
|
||||||
|
|
|
@ -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:
|
create our option class (with a docstring), give it a `display_name`, and add it to our game's options dataclass:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Options.py
|
# options.py
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from Options import Toggle, PerGameCommonOptions
|
from Options import Toggle, PerGameCommonOptions
|
||||||
|
|
|
@ -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
|
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 MyGameOptions` from your `__init__.py` will load
|
e.g. `from .options import MyGameOptions` from your `__init__.py` will load
|
||||||
`world/[world_name]/Options.py` and make its `MyGameOptions` accessible.
|
`world/[world_name]/options.py` and make its `MyGameOptions` 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.MyGameOptions`.
|
and access the variable as `options.MyGameOptions`.
|
||||||
|
|
||||||
Imports from directories outside your world should use absolute imports.
|
Imports from directories outside your world should use absolute imports.
|
||||||
Correct use of relative / absolute imports is required for zipped worlds to
|
Correct use of relative / absolute imports is required for zipped worlds to
|
||||||
|
@ -311,7 +311,7 @@ class MyGameItem(Item):
|
||||||
game: str = "My Game"
|
game: str = "My Game"
|
||||||
```
|
```
|
||||||
By convention this class definition will either be placed in your `__init__.py`
|
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
|
### Your location type
|
||||||
|
|
||||||
|
@ -323,15 +323,15 @@ class MyGameLocation(Location):
|
||||||
game: str = "My Game"
|
game: str = "My Game"
|
||||||
|
|
||||||
# override constructor to automatically mark event locations as such
|
# 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)
|
super(MyGameLocation, self).__init__(player, name, code, parent)
|
||||||
self.event = code is None
|
self.event = code is None
|
||||||
```
|
```
|
||||||
in your `__init__.py` or your `Locations.py`.
|
in your `__init__.py` or your `locations.py`.
|
||||||
|
|
||||||
### Options
|
### 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.
|
the players' yaml files.
|
||||||
|
|
||||||
Each option has its own class, inherits from a base option type, has a docstring
|
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
|
#### 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.
|
its `value` will either be True or False.
|
||||||
|
|
||||||
#### Range
|
#### Range
|
||||||
|
@ -373,7 +373,7 @@ default = 0
|
||||||
|
|
||||||
#### Sample
|
#### Sample
|
||||||
```python
|
```python
|
||||||
# Options.py
|
# options.py
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from Options import Toggle, Range, Choice, PerGameCommonOptions
|
from Options import Toggle, Range, Choice, PerGameCommonOptions
|
||||||
|
@ -412,7 +412,7 @@ class MyGameOptions(PerGameCommonOptions):
|
||||||
# __init__.py
|
# __init__.py
|
||||||
|
|
||||||
from worlds.AutoWorld import World
|
from worlds.AutoWorld import World
|
||||||
from .Options import MyGameOptions # import the options dataclass
|
from .options import MyGameOptions # import the options dataclass
|
||||||
|
|
||||||
|
|
||||||
class MyGameWorld(World):
|
class MyGameWorld(World):
|
||||||
|
@ -429,9 +429,9 @@ class MyGameWorld(World):
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
import typing
|
import typing
|
||||||
from .Options import MyGameOptions # the options we defined earlier
|
from .options import MyGameOptions # the options we defined earlier
|
||||||
from .Items import mygame_items # data used below to add items to the World
|
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
|
||||||
|
|
||||||
|
@ -490,7 +490,7 @@ The world has to provide the following things for generation
|
||||||
* additions to the regions list: at least one called "Menu"
|
* additions to the regions list: at least one called "Menu"
|
||||||
* 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 world defined 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.
|
||||||
|
@ -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
|
* `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
|
generation to check the existence of prerequisite files, usually a ROM for
|
||||||
games which require one.
|
games which require one.
|
||||||
* `def generate_early(self)`
|
* `generate_early(self)`
|
||||||
called per player before any items or locations are created. You can set
|
called per player before any items or locations are created. You can set properties on your world here. Already has
|
||||||
properties on your world here. Already has access to player options and RNG.
|
access to player options and RNG. This is the earliest step where the world should start setting up for the current
|
||||||
* `def create_regions(self)`
|
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
|
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.
|
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
|
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.
|
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.
|
called to set access and item rules on locations and entrances.
|
||||||
Locations have to be defined before this, or rule application can miss them.
|
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
|
called after the previous steps. Some placement and player specific
|
||||||
randomizations can be done here.
|
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`.
|
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
|
If items need to be placed during pre_fill, these items can be determined
|
||||||
and created using `get_prefill_items`
|
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
|
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
|
called, `self.multiworld.get_locations(self.player)` has all locations for the player, with
|
||||||
attribute `item` pointing to the item.
|
attribute `item` pointing to the item.
|
||||||
`location.item.player` can be used to see if it's a local 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.
|
will be used by the server to host the MultiWorld.
|
||||||
|
|
||||||
|
|
||||||
|
@ -541,9 +542,9 @@ def generate_early(self) -> None:
|
||||||
```python
|
```python
|
||||||
# we need a way to know if an item provides progress in the game ("key item")
|
# 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
|
# 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
|
# This is called when AP wants to create an item by name (for plando) or
|
||||||
# when you call it from your own code.
|
# when you call it from your own code.
|
||||||
classification = ItemClassification.progression if is_progression(item) else \
|
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],
|
return MyGameItem(item, classification, self.item_name_to_id[item],
|
||||||
self.player)
|
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
|
# while we are at it, we can also add a helper to create events
|
||||||
return MyGameItem(event, True, None, self.player)
|
return MyGameItem(event, True, None, self.player)
|
||||||
```
|
```
|
||||||
|
@ -644,7 +645,7 @@ def generate_basic(self) -> None:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
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:
|
def set_rules(self) -> None:
|
||||||
|
@ -713,12 +714,12 @@ Please do this with caution and only when necessary.
|
||||||
#### Sample
|
#### Sample
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Logic.py
|
# logic.py
|
||||||
|
|
||||||
from worlds.AutoWorld import LogicMixin
|
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) -> bool:
|
||||||
# Arguments above are free to choose
|
# Arguments above are free to choose
|
||||||
# MultiWorld can be accessed through self.multiworld, 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
|
||||||
|
@ -728,11 +729,11 @@ class MyGameLogic(LogicMixin):
|
||||||
# __init__.py
|
# __init__.py
|
||||||
|
|
||||||
from worlds.generic.Rules import set_rule
|
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):
|
class MyGameWorld(World):
|
||||||
# ...
|
# ...
|
||||||
def set_rules(self):
|
def set_rules(self) -> None:
|
||||||
set_rule(self.multiworld.get_location("A Door", self.player),
|
set_rule(self.multiworld.get_location("A Door", self.player),
|
||||||
lambda state: state.mygame_has_key(self.player))
|
lambda state: state.mygame_has_key(self.player))
|
||||||
```
|
```
|
||||||
|
@ -740,10 +741,10 @@ class MyGameWorld(World):
|
||||||
### Generate Output
|
### Generate Output
|
||||||
|
|
||||||
```python
|
```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
|
# 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 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
|
# 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
|
# make sure to mark as not remote_start_inventory when connecting if stored in rom/mod
|
||||||
"starter_items": [item.name for item
|
"starter_items": [item.name for item
|
||||||
in self.multiworld.precollected_items[self.player]],
|
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
|
# 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
|
||||||
|
@ -787,7 +786,7 @@ data already exists on the server. The most common usage of slot data is to send
|
||||||
to be aware of.
|
to be aware of.
|
||||||
|
|
||||||
```python
|
```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
|
# 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
|
# 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
|
# 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):
|
class TestChestAccess(MyGameTestBase):
|
||||||
def test_sword_chests(self):
|
def test_sword_chests(self) -> None:
|
||||||
"""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 test_any_weapon_chests(self):
|
def test_any_weapon_chests(self) -> None:
|
||||||
"""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