mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-05-04 14:03:30 -07:00
619 lines
24 KiB
Markdown
619 lines
24 KiB
Markdown
# Rule Builder
|
|
|
|
This document describes the API provided for the rule builder. Using this API provides you with with a simple interface
|
|
to define rules and the following advantages:
|
|
|
|
- Rule classes that avoid all the common pitfalls
|
|
- Logic optimization
|
|
- Automatic result caching (opt-in)
|
|
- Serialization/deserialization
|
|
- Human-readable logic explanations for players
|
|
|
|
## Overview
|
|
|
|
The rule builder consists of 3 main parts:
|
|
|
|
1. The rules, which are classes that inherit from `rule_builder.rules.Rule`. These are what you write for your logic.
|
|
They can be combined and take into account your world's options. There are a number of default rules listed below,
|
|
and you can create as many custom rules for your world as needed. When assigning the rules to a location or entrance
|
|
they must be resolved.
|
|
2. Resolved rules, which are classes that inherit from `rule_builder.rules.Rule.Resolved`. These are the optimized rules
|
|
specific to one player that are set as a location or entrance's access rule. You generally shouldn't be directly
|
|
creating these but they'll be created when assigning rules to locations or entrances. These are what power the
|
|
human-readable logic explanations.
|
|
3. The optional rule builder world subclass `CachedRuleBuilderWorld`, which is a class your world can inherit from
|
|
instead of `World`. It adds a caching system to the rules that will lazy evaluate and cache the result.
|
|
|
|
## Usage
|
|
|
|
For the most part the only difference in usage is instead of writing lambdas for your logic, you write static Rule
|
|
objects. You then must use `world.set_rule` to assign the rule to a location or entrance.
|
|
|
|
```python
|
|
# In your world's create_regions method
|
|
location = MyWorldLocation(...)
|
|
self.set_rule(location, Has("A Big Gun"))
|
|
```
|
|
|
|
The rule builder comes with a number of rules by default:
|
|
|
|
- `True_`: Always returns true
|
|
- `False_`: Always returns false
|
|
- `And`: Checks that all child rules are true (also provided by `&` operator)
|
|
- `Or`: Checks that at least one child rule is true (also provided by `|` operator)
|
|
- `Has`: Checks that the player has the given item with the given count (default 1)
|
|
- `HasAll`: Checks that the player has all given items
|
|
- `HasAny`: Checks that the player has at least one of the given items
|
|
- `HasAllCounts`: Checks that the player has all of the counts for the given items
|
|
- `HasAnyCount`: Checks that the player has any of the counts for the given items
|
|
- `HasFromList`: Checks that the player has some number of given items
|
|
- `HasFromListUnique`: Checks that the player has some number of given items, ignoring duplicates of the same item
|
|
- `HasGroup`: Checks that the player has some number of items from a given item group
|
|
- `HasGroupUnique`: Checks that the player has some number of items from a given item group, ignoring duplicates of the
|
|
same item
|
|
- `CanReachLocation`: Checks that the player can logically reach the given location
|
|
- `CanReachRegion`: Checks that the player can logically reach the given region
|
|
- `CanReachEntrance`: Checks that the player can logically reach the given entrance
|
|
|
|
You can combine these rules together to describe the logic required for something. For example, to check if a player
|
|
either has `Movement ability` or they have both `Key 1` and `Key 2`, you can do:
|
|
|
|
```python
|
|
rule = Has("Movement ability") | HasAll("Key 1", "Key 2")
|
|
```
|
|
|
|
> ⚠️ Composing rules with the `and` and `or` keywords will not work. You must use the bitwise `&` and `|` operators. In
|
|
> order to catch mistakes, the rule builder will not let you do boolean operations. As a consequence, in order to check
|
|
> if a rule is defined you must use `if rule is not None`.
|
|
|
|
### Assigning rules
|
|
|
|
When assigning the rule you must use the `set_rule` helper to correctly resolve and register the rule.
|
|
|
|
```python
|
|
self.set_rule(location_or_entrance, rule)
|
|
```
|
|
|
|
There is also a `create_entrance` helper that will resolve the rule, check if it's `False`, and if not create the
|
|
entrance and set the rule. This allows you to skip creating entrances that will never be valid. You can also specify
|
|
`force_creation=True` if you would like to create the entrance even if the rule is `False`.
|
|
|
|
```python
|
|
self.create_entrance(from_region, to_region, rule)
|
|
```
|
|
|
|
> ⚠️ If you use a `CanReachLocation` rule on an entrance, you will either have to create the locations first, or specify
|
|
> the location's parent region name with the `parent_region_name` argument of `CanReachLocation`.
|
|
|
|
You can also set a rule for your world's completion condition:
|
|
|
|
```python
|
|
self.set_completion_rule(rule)
|
|
```
|
|
|
|
### Restricting options
|
|
|
|
Every rule allows you to specify which options it's applicable for. You can provide the argument `options` which is an
|
|
iterable of `OptionFilter` instances. When resolved, if no filters are provided or all of them pass then the rule will
|
|
resolve as normal. Otherwise, the rule will be replaced with `True` or `False` depending on what `filtered_resolution`
|
|
is set to, which defaults to `False`.
|
|
|
|
```python
|
|
rule1 = Has(
|
|
"Fast Travel Spell",
|
|
options=[OptionFilter(RandoFastTravel, RandoFastTravel.option_true)],
|
|
)
|
|
rule2 = Has(
|
|
"Starting Party Member",
|
|
options=[OptionFilter(RandoParty, 1)], # option attributes are suggested but any value works
|
|
filtered_resolution=True,
|
|
)
|
|
```
|
|
|
|
If you want a comparison that isn't equals, you can specify with the `operator` argument. The following operators are
|
|
allowed:
|
|
|
|
- `eq`: `option_value == filter_value`
|
|
- `ne`: `option_value != filter_value`
|
|
- `gt`: `option_value > filter_value`
|
|
- `lt`: `option_value < filter_value`
|
|
- `ge`: `option_value >= filter_value`
|
|
- `le`: `option_value <= filter_value`
|
|
- `in`: `option_value in filter_value`
|
|
- `contains`: `filter_value in option_value` (note reversed operands)
|
|
|
|
```python
|
|
rule1 = Has("Movement Ability", options=[OptionFilter(SkipsLevel, SkipsLevel.option_hard, operator="lt")])
|
|
rule2 = Has("Item", options=[OptionFilter(ChoiceOption, [1, 5], operator="in")])
|
|
```
|
|
|
|
To check if the player has received the switch item if switches are randomized, or if they can reach the switch when not
|
|
randomized:
|
|
|
|
```python
|
|
rule = (
|
|
Has("Red switch", options=[OptionFilter(SwitchRando, 1)])
|
|
| CanReachLocation("Red switch", options=[OptionFilter(SwitchRando, 0)])
|
|
)
|
|
```
|
|
|
|
To add an extra logic requirement on the easiest difficulty which is ignored for other difficulties:
|
|
|
|
```python
|
|
rule = (
|
|
# ...the rest of the logic
|
|
& Has("QoL item", options=[OptionFilter(Difficulty, Difficulty.option_easy)], filtered_resolution=True)
|
|
)
|
|
```
|
|
|
|
If you would like to provide option filters when reusing or composing rules, you can use the `Filtered` helper rule:
|
|
|
|
```python
|
|
common_rule = Has("A") | HasAny("B", "C")
|
|
...
|
|
rule = (
|
|
Filtered(common_rule, options=[OptionFilter(Opt, 0)])
|
|
| Filtered(Has("X") | CanReachRegion("Y"), options=[OptionFilter(Opt, 1)])
|
|
)
|
|
```
|
|
|
|
For convenience, you can also use the `&` and `|` operators to apply options to rules:
|
|
|
|
```python
|
|
common_rule = Has("A")
|
|
easy_filter = [OptionFilter(Difficulty, Difficulty.option_easy)]
|
|
common_rule_only_on_easy = common_rule & easy_filter
|
|
common_rule_skipped_on_easy = common_rule | easy_filter
|
|
```
|
|
|
|
Combining the above, you can easily bypass a requirement based on option choices:
|
|
|
|
```python
|
|
rule = Has("Some Upgrade") | OptionFilter(CombatDifficulty, CombatDifficulty.option_medium, operator="ge")
|
|
```
|
|
|
|
### Field resolvers
|
|
|
|
When creating rules you may sometimes need to set a field to a value that depends on the world instance. You can use a
|
|
`FieldResolver` to define how to populate that field when the rule is being resolved.
|
|
|
|
There are two build-in field resolvers:
|
|
|
|
- `FromOption`: Resolves to the value of the given option
|
|
- `FromWorldAttr`: Resolves to the value of the given world instance attribute, can specify a dotted path `a.b.c` to get
|
|
a nested attribute or dict item
|
|
|
|
```python
|
|
world.options.mcguffin_count = 5
|
|
world.precalculated_value = 99
|
|
rule = (
|
|
Has("A", count=FromOption(McguffinCount))
|
|
| HasGroup("Important items", count=FromWorldAttr("precalculated_value"))
|
|
)
|
|
# Results in Has("A", count=5) | HasGroup("Important items", count=99)
|
|
```
|
|
|
|
You can define your own resolvers by creating a class that inherits from `FieldResolver`, provides your game name, and
|
|
implements a `resolve` function:
|
|
|
|
```python
|
|
@dataclasses.dataclass(frozen=True)
|
|
class FromCustomResolution(FieldResolver, game="MyGame"):
|
|
modifier: str
|
|
|
|
@override
|
|
def resolve(self, world: "World") -> Any:
|
|
return some_math_calculation(world, self.modifier)
|
|
|
|
|
|
rule = Has("Combat Level", count=FromCustomResolution("combat"))
|
|
```
|
|
|
|
If you want to support rule serialization and your resolver contains non-serializable properties you may need to
|
|
override `to_dict` or `from_dict`.
|
|
|
|
## Enabling caching
|
|
|
|
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your
|
|
rules.
|
|
|
|
```python
|
|
class MyWorld(CachedRuleBuilderWorld):
|
|
game = "My Game"
|
|
```
|
|
|
|
If your world's logic is very simple and you don't have many nested rules, the caching system may have more overhead
|
|
cost than time it saves. You'll have to benchmark your own world to see if it should be enabled or not.
|
|
|
|
### Item name mapping
|
|
|
|
If you have multiple real items that map to a single logic item, add a `item_mapping` class dict to your world that maps
|
|
actual item names to real item names so the cache system knows what to invalidate.
|
|
|
|
For example, if you have multiple `Currency x<num>` items on locations, but your rules only check a singular logical
|
|
`Currency` item, eg `Has("Currency", 1000)`, you'll want to map each numerical currency item to the single logical
|
|
`Currency`.
|
|
|
|
```python
|
|
class MyWorld(CachedRuleBuilderWorld):
|
|
item_mapping = {
|
|
"Currency x10": "Currency",
|
|
"Currency x50": "Currency",
|
|
"Currency x100": "Currency",
|
|
"Currency x500": "Currency",
|
|
}
|
|
```
|
|
|
|
## Defining custom rules
|
|
|
|
You can create a custom rule by creating a class that inherits from `Rule` or any of the default rules. You must provide
|
|
the game name as an argument to the class. It's recommended to use the `@dataclass` decorator to reduce boilerplate, and
|
|
to also provide your world as a type argument to add correct type checking to the `_instantiate` method.
|
|
|
|
You must provide or inherit a `Resolved` child class that defines an `_evaluate` method. This class will automatically
|
|
be converted into a frozen `dataclass`. If your world has caching enabled you may need to define one or more
|
|
dependencies functions as outlined below.
|
|
|
|
To add a rule that checks if the user has enough mcguffins to goal, with a randomized requirement:
|
|
|
|
```python
|
|
@dataclasses.dataclass()
|
|
class CanGoal(Rule["MyWorld"], game="My Game"):
|
|
@override
|
|
def _instantiate(self, world: "MyWorld") -> Rule.Resolved:
|
|
# caching_enabled only needs to be passed in when your world inherits from CachedRuleBuilderWorld
|
|
return self.Resolved(world.required_mcguffins, player=world.player, caching_enabled=True)
|
|
|
|
class Resolved(Rule.Resolved):
|
|
goal: int
|
|
|
|
@override
|
|
def _evaluate(self, state: CollectionState) -> bool:
|
|
return state.has("McGuffin", self.player, count=self.goal)
|
|
|
|
@override
|
|
def item_dependencies(self) -> dict[str, set[int]]:
|
|
# this function is only required if you have caching enabled
|
|
return {"McGuffin": {id(self)}}
|
|
|
|
@override
|
|
def explain_json(self, state: CollectionState | None = None) -> list[JSONMessagePart]:
|
|
# this method can be overridden to display custom explanations
|
|
return [
|
|
{"type": "text", "text": "Goal with "},
|
|
{"type": "color", "color": "green" if state and self(state) else "salmon", "text": str(self.goal)},
|
|
{"type": "text", "text": " McGuffins"},
|
|
]
|
|
```
|
|
|
|
Your custom rule can also resolve to builtin rules instead of needing to define your own:
|
|
|
|
```python
|
|
@dataclasses.dataclass()
|
|
class ComplicatedFilter(Rule["MyWorld"], game="My Game"):
|
|
def _instantiate(self, world: "MyWorld") -> Rule.Resolved:
|
|
if world.some_precalculated_bool:
|
|
return Has("Item 1").resolve(world)
|
|
if world.options.some_option:
|
|
return CanReachRegion("Region 1").resolve(world)
|
|
return False_().resolve(world)
|
|
```
|
|
|
|
### Item dependencies
|
|
|
|
If your world inherits from `CachedRuleBuilderWorld` and there are items that when collected will affect the result of
|
|
your rule evaluation, it must define an `item_dependencies` function that returns a mapping of the item name to the id
|
|
of your rule. These dependencies will be combined to inform the caching system. It may be worthwhile to define this
|
|
function even when caching is disabled as more things may use it in the future.
|
|
|
|
```python
|
|
@dataclasses.dataclass()
|
|
class MyRule(Rule["MyWorld"], game="My Game"):
|
|
class Resolved(Rule.Resolved):
|
|
item_name: str
|
|
|
|
@override
|
|
def item_dependencies(self) -> dict[str, set[int]]:
|
|
return {self.item_name: {id(self)}}
|
|
```
|
|
|
|
All of the default `Has*` rules define this function already.
|
|
|
|
### Region dependencies
|
|
|
|
If your custom rule references other regions, it must define a `region_dependencies` function that returns a mapping of
|
|
region names to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
|
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
|
caching system if applicable.
|
|
|
|
```python
|
|
@dataclasses.dataclass()
|
|
class MyRule(Rule["MyWorld"], game="My Game"):
|
|
class Resolved(Rule.Resolved):
|
|
region_name: str
|
|
|
|
@override
|
|
def region_dependencies(self) -> dict[str, set[int]]:
|
|
return {self.region_name: {id(self)}}
|
|
```
|
|
|
|
The default `CanReachLocation`, `CanReachRegion`, and `CanReachEntrance` rules define this function already.
|
|
|
|
### Location dependencies
|
|
|
|
If your custom rule references other locations, it must define a `location_dependencies` function that returns a mapping
|
|
of the location name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
|
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
|
caching system if applicable.
|
|
|
|
```python
|
|
@dataclasses.dataclass()
|
|
class MyRule(Rule["MyWorld"], game="My Game"):
|
|
class Resolved(Rule.Resolved):
|
|
location_name: str
|
|
|
|
@override
|
|
def location_dependencies(self) -> dict[str, set[int]]:
|
|
return {self.location_name: {id(self)}}
|
|
```
|
|
|
|
The default `CanReachLocation` rule defines this function already.
|
|
|
|
### Entrance dependencies
|
|
|
|
If your custom rule references other entrances, it must define a `entrance_dependencies` function that returns a mapping
|
|
of the entrance name to the id of your rule regardless of if your world inherits from `CachedRuleBuilderWorld`. These
|
|
dependencies will be combined to register indirect connections when you set this rule on an entrance and inform the
|
|
caching system if applicable.
|
|
|
|
```python
|
|
@dataclasses.dataclass()
|
|
class MyRule(Rule["MyWorld"], game="My Game"):
|
|
class Resolved(Rule.Resolved):
|
|
entrance_name: str
|
|
|
|
@override
|
|
def entrance_dependencies(self) -> dict[str, set[int]]:
|
|
return {self.entrance_name: {id(self)}}
|
|
```
|
|
|
|
The default `CanReachEntrance` rule defines this function already.
|
|
|
|
### Rule explanations
|
|
|
|
Resolved rules have a default implementation for `explain_json` and `explain_str` functions. The former optionally
|
|
accepts a `CollectionState` and returns a list of `JSONMessagePart` appropriate for `print_json` in a client. It will
|
|
display a human-readable message that explains what the rule requires. The latter is similar but returns a string. It is
|
|
useful when debugging. There is also a `__str__` method defined to check what a rule is without a state.
|
|
|
|
To implement a custom message with a custom rule, override the `explain_json` and/or `explain_str` method on your
|
|
`Resolved` class:
|
|
|
|
```python
|
|
class MyRule(Rule, game="My Game"):
|
|
class Resolved(Rule.Resolved):
|
|
@override
|
|
def explain_json(self, state: CollectionState | None = None) -> list[JSONMessagePart]:
|
|
has_item = state and state.has("growth spurt", self.player)
|
|
color = "yellow"
|
|
start = "You must be "
|
|
if has_item:
|
|
start = "You are "
|
|
color = "green"
|
|
elif state is not None:
|
|
start = "You are not "
|
|
color = "salmon"
|
|
return [
|
|
{"type": "text", "text": start},
|
|
{"type": "color", "color": color, "text": "THIS"},
|
|
{"type": "text", "text": " tall to beat the game"},
|
|
]
|
|
|
|
@override
|
|
def explain_str(self, state: CollectionState | None = None) -> str:
|
|
if state is None:
|
|
return str(self)
|
|
if state.has("growth spurt", self.player):
|
|
return "You ARE this tall and can beat the game"
|
|
return "You are not THIS tall and cannot beat the game"
|
|
|
|
@override
|
|
def __str__(self) -> str:
|
|
return "You must be THIS tall to beat the game"
|
|
```
|
|
|
|
### Cache control
|
|
|
|
By default your custom rule will work through the cache system as any other rule if caching is enabled. There are two
|
|
class attributes on the `Resolved` class you can override to change this behavior.
|
|
|
|
- `force_recalculate`: Setting this to `True` will cause your custom rule to skip going through the caching system and
|
|
always recalculate when being evaluated. When a rule with this flag enabled is composed with `And` or `Or` it will
|
|
cause any parent rules to always force recalculate as well. Use this flag when it's difficult to determine when your
|
|
rule should be marked as stale.
|
|
- `skip_cache`: Setting this to `True` will also cause your custom rule to skip going through the caching system when
|
|
being evaluated. However, it will **not** affect any other rules when composed with `And` or `Or`, so it must still
|
|
define its `*_dependencies` functions as required. Use this flag when the evaluation of this rule is trivial and the
|
|
overhead of the caching system will slow it down.
|
|
|
|
### Caveats
|
|
|
|
- Ensure you are passing `caching_enabled=True` in your `_instantiate` function when creating resolved rule instances if
|
|
your world has opted into caching.
|
|
- Resolved rules are forced to be frozen dataclasses. They and all their attributes must be immutable and hashable.
|
|
- If your rule creates child rules ensure they are being resolved through the world rather than creating `Resolved`
|
|
instances directly.
|
|
|
|
## Serialization
|
|
|
|
The rule builder is intended to be written first in Python for optimization and type safety. To facilitate exporting the
|
|
rules to a client or tracker, rules have a `to_dict` method that returns a JSON-compatible dict. Since the location and
|
|
entrance logic structure varies greatly from world to world, the actual JSON dumping is left up to the world dev.
|
|
|
|
The dict contains a `rule` key with the name of the rule, an `options` key with the rule's list of option filters, and
|
|
an `args` key that contains any other arguments the individual rule has. For example, this is what a simple `Has` rule
|
|
would look like:
|
|
|
|
```python
|
|
{
|
|
"rule": "Has",
|
|
"options": [],
|
|
"args": {
|
|
"item_name": "Some item",
|
|
"count": 1,
|
|
},
|
|
}
|
|
```
|
|
|
|
For `And` and `Or` rules, instead of an `args` key, they have a `children` key containing a list of their child rules in
|
|
the same serializable format:
|
|
|
|
```python
|
|
{
|
|
"rule": "And",
|
|
"options": [],
|
|
"children": [
|
|
..., # each serialized rule
|
|
]
|
|
}
|
|
```
|
|
|
|
A full example is as follows:
|
|
|
|
```python
|
|
rule = And(
|
|
Has("a", options=[OptionFilter(ToggleOption, 0)]),
|
|
Or(Has("b", count=2), CanReachRegion("c"), options=[OptionFilter(ToggleOption, 1)]),
|
|
)
|
|
assert rule.to_dict() == {
|
|
"rule": "And",
|
|
"options": [],
|
|
"children": [
|
|
{
|
|
"rule": "Has",
|
|
"options": [
|
|
{
|
|
"option": "worlds.my_world.options.ToggleOption",
|
|
"value": 0,
|
|
"operator": "eq",
|
|
},
|
|
],
|
|
"args": {
|
|
"item_name": "a",
|
|
"count": 1,
|
|
},
|
|
},
|
|
{
|
|
"rule": "Or",
|
|
"options": [
|
|
{
|
|
"option": "worlds.my_world.options.ToggleOption",
|
|
"value": 1,
|
|
"operator": "eq",
|
|
},
|
|
],
|
|
"children": [
|
|
{
|
|
"rule": "Has",
|
|
"options": [],
|
|
"args": {
|
|
"item_name": "b",
|
|
"count": 2,
|
|
},
|
|
},
|
|
{
|
|
"rule": "CanReachRegion",
|
|
"options": [],
|
|
"args": {
|
|
"region_name": "c",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
### Custom serialization
|
|
|
|
To define a different format for your custom rules, override the `to_dict` function:
|
|
|
|
```python
|
|
class BasicLogicRule(Rule, game="My Game"):
|
|
items = ("one", "two")
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
# Return whatever format works best for you
|
|
return {
|
|
"logic": "basic",
|
|
"items": self.items,
|
|
}
|
|
```
|
|
|
|
If your logic has been done in custom JSON first, you can define a `from_dict` class method on your rules to parse it
|
|
correctly:
|
|
|
|
```python
|
|
class BasicLogicRule(Rule, game="My Game"):
|
|
@classmethod
|
|
def from_dict(cls, data: Mapping[str, Any], world_cls: type[World]) -> Self:
|
|
items = data.get("items", ())
|
|
return cls(*items)
|
|
```
|
|
|
|
## APIs
|
|
|
|
This section is provided for reference, refer to the above sections for examples.
|
|
|
|
### World API
|
|
|
|
These are properties and helpers that are available to you in your world.
|
|
|
|
#### Methods
|
|
|
|
- `rule_from_dict(data)`: Create a rule instance from a deserialized dict representation
|
|
- `register_rule_builder_dependencies()`: Register all rules that depend on location or entrance access with the
|
|
inherited dependencies, gets called automatically after set_rules
|
|
- `set_rule(spot: Location | Entrance, rule: Rule)`: Resolve a rule, register its dependencies, and set it on the given
|
|
location or entrance
|
|
- `set_completion_rule(rule: Rule)`: Sets the completion condition for this world
|
|
- `create_entrance(from_region: Region, to_region: Region, rule: Rule | None, name: str | None = None, force_creation: bool = False)`:
|
|
Attempt to create an entrance from `from_region` to `to_region`, skipping creation if `rule` is defined and evaluates
|
|
to `False_()` unless force_creation is `True`
|
|
|
|
#### CachedRuleBuilderWorld Properties
|
|
|
|
The following property is only available when inheriting from `CachedRuleBuilderWorld`
|
|
|
|
- `item_mapping: dict[str, str]`: A mapping of actual item name to logical item name
|
|
|
|
### Rule API
|
|
|
|
These are properties and helpers that you can use or override for custom rules.
|
|
|
|
- `_instantiate(world: World)`: Create a new resolved rule instance, override for custom rules as required
|
|
- `to_dict()`: Create a JSON-compatible dict representation of this rule, override if you want to customize your rule's
|
|
serialization
|
|
- `from_dict(data, world_cls: type[World])`: Return a new rule instance from a deserialized representation, override if
|
|
you've overridden `to_dict`
|
|
- `__str__()`: Basic string representation of a rule, useful for debugging
|
|
|
|
#### Resolved rule API
|
|
|
|
- `player: int`: The slot this rule is resolved for
|
|
- `_evaluate(state: CollectionState)`: Evaluate this rule against the given state, override this to define the logic for
|
|
this rule
|
|
- `item_dependencies()`: A mapping of item name to set of ids, override this if your custom rule depends on item
|
|
collection
|
|
- `region_dependencies()`: A mapping of region name to set of ids, override this if your custom rule depends on reaching
|
|
regions
|
|
- `location_dependencies()`: A mapping of location name to set of ids, override this if your custom rule depends on
|
|
reaching locations
|
|
- `entrance_dependencies()`: A mapping of entrance name to set of ids, override this if your custom rule depends on
|
|
reaching entrances
|
|
- `explain_json(state: CollectionState | None = None)`: Return a list of printJSON messages describing this rule's logic
|
|
(and if state is defined its evaluation) in a human readable way, override to explain custom rules
|
|
- `explain_str(state: CollectionState | None = None)`: Return a string describing this rule's logic (and if state is
|
|
defined its evaluation) in a human readable way, override to explain custom rules, more useful for debugging
|
|
- `__str__()`: A string describing this rule's logic without its evaluation, override to explain custom rules
|