2021-07-12 13:33:20 +00:00
|
|
|
from __future__ import annotations
|
2022-02-05 14:49:19 +00:00
|
|
|
|
|
|
|
import logging
|
2022-05-11 18:05:53 +00:00
|
|
|
from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Union, NamedTuple
|
2021-07-12 11:54:47 +00:00
|
|
|
|
2022-05-11 18:05:53 +00:00
|
|
|
from BaseClasses import MultiWorld, Item, CollectionState, Location, Tutorial
|
2021-10-19 21:23:48 +00:00
|
|
|
from Options import Option
|
2021-06-11 12:22:44 +00:00
|
|
|
|
2021-06-11 16:02:48 +00:00
|
|
|
|
2021-06-11 12:22:44 +00:00
|
|
|
class AutoWorldRegister(type):
|
2022-05-03 20:14:03 +00:00
|
|
|
world_types: Dict[str, type(World)] = {}
|
2021-06-11 12:22:44 +00:00
|
|
|
|
2022-05-03 20:14:03 +00:00
|
|
|
def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister:
|
2022-04-03 17:08:50 +00:00
|
|
|
if "web" in dct:
|
|
|
|
assert isinstance(dct["web"], WebWorld), "WebWorld has to be instantiated."
|
2021-07-12 16:47:58 +00:00
|
|
|
# filter out any events
|
|
|
|
dct["item_name_to_id"] = {name: id for name, id in dct["item_name_to_id"].items() if id}
|
|
|
|
dct["location_name_to_id"] = {name: id for name, id in dct["location_name_to_id"].items() if id}
|
|
|
|
# build reverse lookups
|
2021-07-12 16:05:46 +00:00
|
|
|
dct["item_id_to_name"] = {code: name for name, code in dct["item_name_to_id"].items()}
|
|
|
|
dct["location_id_to_name"] = {code: name for name, code in dct["location_name_to_id"].items()}
|
2021-07-29 18:27:41 +00:00
|
|
|
|
|
|
|
# build rest
|
|
|
|
dct["item_names"] = frozenset(dct["item_name_to_id"])
|
2022-02-05 15:55:11 +00:00
|
|
|
dct["item_name_groups"] = dct.get("item_name_groups", {})
|
|
|
|
dct["item_name_groups"]["Everything"] = dct["item_names"]
|
2021-07-29 18:27:41 +00:00
|
|
|
dct["location_names"] = frozenset(dct["location_name_to_id"])
|
2022-01-16 01:20:37 +00:00
|
|
|
dct["all_item_and_group_names"] = frozenset(dct["item_names"] | set(dct.get("item_name_groups", {})))
|
2021-07-29 18:27:41 +00:00
|
|
|
|
2022-04-08 09:16:36 +00:00
|
|
|
# move away from get_required_client_version function
|
|
|
|
if "game" in dct:
|
|
|
|
assert "get_required_client_version" not in dct, f"{name}: required_client_version is an attribute now"
|
|
|
|
# set minimum required_client_version from bases
|
|
|
|
if "required_client_version" in dct and bases:
|
|
|
|
for base in bases:
|
|
|
|
if "required_client_version" in base.__dict__:
|
2022-04-28 16:03:44 +00:00
|
|
|
dct["required_client_version"] = max(dct["required_client_version"],
|
|
|
|
base.__dict__["required_client_version"])
|
2022-04-08 09:16:36 +00:00
|
|
|
|
2021-07-12 16:47:58 +00:00
|
|
|
# construct class
|
2022-05-03 20:14:03 +00:00
|
|
|
new_class = super().__new__(mcs, name, bases, dct)
|
2021-06-11 12:22:44 +00:00
|
|
|
if "game" in dct:
|
|
|
|
AutoWorldRegister.world_types[dct["game"]] = new_class
|
|
|
|
return new_class
|
|
|
|
|
2021-07-21 16:08:15 +00:00
|
|
|
|
2021-07-15 11:31:33 +00:00
|
|
|
class AutoLogicRegister(type):
|
2022-04-28 16:03:44 +00:00
|
|
|
def __new__(cls, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoLogicRegister:
|
2021-07-15 11:31:33 +00:00
|
|
|
new_class = super().__new__(cls, name, bases, dct)
|
2022-04-28 16:03:44 +00:00
|
|
|
function: Callable[..., Any]
|
2021-07-16 10:23:05 +00:00
|
|
|
for item_name, function in dct.items():
|
2022-02-17 06:07:34 +00:00
|
|
|
if item_name == "copy_mixin":
|
|
|
|
CollectionState.additional_copy_functions.append(function)
|
|
|
|
elif item_name == "init_mixin":
|
|
|
|
CollectionState.additional_init_functions.append(function)
|
|
|
|
elif not item_name.startswith("__"):
|
2021-07-16 10:23:05 +00:00
|
|
|
if hasattr(CollectionState, item_name):
|
|
|
|
raise Exception(f"Name conflict on Logic Mixin {name} trying to overwrite {item_name}")
|
|
|
|
setattr(CollectionState, item_name, function)
|
2021-07-15 11:31:33 +00:00
|
|
|
return new_class
|
2021-06-11 12:22:44 +00:00
|
|
|
|
2021-07-21 16:08:15 +00:00
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def call_single(world: MultiWorld, method_name: str, player: int, *args: Any) -> Any:
|
2021-06-11 12:22:44 +00:00
|
|
|
method = getattr(world.worlds[player], method_name)
|
2021-07-21 16:08:15 +00:00
|
|
|
return method(*args)
|
2021-06-11 12:22:44 +00:00
|
|
|
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def call_all(world: MultiWorld, method_name: str, *args: Any) -> None:
|
|
|
|
world_types: Set[AutoWorldRegister] = set()
|
2021-06-11 12:22:44 +00:00
|
|
|
for player in world.player_ids:
|
2021-08-09 04:50:11 +00:00
|
|
|
world_types.add(world.worlds[player].__class__)
|
2021-07-21 16:08:15 +00:00
|
|
|
call_single(world, method_name, player, *args)
|
2021-10-06 09:32:49 +00:00
|
|
|
|
2021-08-09 04:50:11 +00:00
|
|
|
for world_type in world_types:
|
|
|
|
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
|
|
|
if stage_callable:
|
2021-08-09 07:15:41 +00:00
|
|
|
stage_callable(world, *args)
|
|
|
|
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def call_stage(world: MultiWorld, method_name: str, *args: Any) -> None:
|
2021-08-09 07:15:41 +00:00
|
|
|
world_types = {world.worlds[player].__class__ for player in world.player_ids}
|
|
|
|
for world_type in world_types:
|
|
|
|
stage_callable = getattr(world_type, f"stage_{method_name}", None)
|
|
|
|
if stage_callable:
|
|
|
|
stage_callable(world, *args)
|
2021-06-11 12:22:44 +00:00
|
|
|
|
|
|
|
|
2022-02-20 20:54:00 +00:00
|
|
|
class WebWorld:
|
|
|
|
"""Webhost integration"""
|
|
|
|
# display a settings page. Can be a link to an out-of-ap settings tool too.
|
|
|
|
settings_page: Union[bool, str] = True
|
|
|
|
|
2022-05-11 18:05:53 +00:00
|
|
|
# docs folder will be scanned for game info pages using this list in the format '{language}_{game_name}.md'
|
|
|
|
game_info_languages: List[str] = ['en']
|
|
|
|
|
|
|
|
# docs folder will also be scanned for tutorial guides given the relevant information in this list. Each Tutorial
|
|
|
|
# class is to be used for one guide.
|
|
|
|
tutorials: List[Tutorial]
|
|
|
|
|
Website Style Upgrade (#353)
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Seed download page improvements
* Add styles to weighted-settings page
* Minor adjustments to styles
* Revert base theme to grass
* Add more items to ArchipIDLE
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Seed download page improvements
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Add styles to weighted-settings page
* Minor adjustments to styles
* Revert base theme to grass
* Add more items to ArchipIDLE
* Improve Archipidle item name
* [WebHost] Update background images, waiting on jungle.png, added partyTime theme
* [WebHost] Fix tab ordering on landing page, remove islands on screen scale, fix tutorial page width scaling
* [WebHost] Final touches to WebHost
* Improve get_world_theme function, add partyTime theme to ArchipIDLE WebWorld
* Remove sending_visible from AutoWorld
* AP Ocarina of Time Client (#352)
* Core: update jinja (#351)
* some typing and cleaning, mostly in Fill.py (#349)
* some typing and cleaning, mostly in Fill.py
* address missing Option types
* resolve a few TODOs discussed in pull request
* SM: Optimize a bit (#350)
* SM: Optimize a bit
* SM: init bosses only once
* New World Order (#355)
* Core: update jinja
* SM: Optimize a bit
* AutoWorld: import worlds in alphabetical order, to be predictable rather than arbitrary
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
* Remove references to Z5Client in English OoT setup guide
* Prevent markdown code blocks from overflowing their container
Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
2022-03-29 00:12:17 +00:00
|
|
|
# Choose a theme for your /game/* pages
|
|
|
|
# Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime
|
|
|
|
theme = "grass"
|
|
|
|
|
2022-04-12 21:37:05 +00:00
|
|
|
# display a link to a bug report page, most likely a link to a GitHub issue page.
|
|
|
|
bug_report_page: Optional[str]
|
|
|
|
|
2022-02-20 20:54:00 +00:00
|
|
|
|
2021-06-11 12:22:44 +00:00
|
|
|
class World(metaclass=AutoWorldRegister):
|
|
|
|
"""A World object encompasses a game's Items, Locations, Rules and additional data or functionality required.
|
|
|
|
A Game should have its own subclass of World in which it defines the required data structures."""
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
options: Dict[str, Option[Any]] = {} # link your Options mapping
|
2021-08-09 04:50:11 +00:00
|
|
|
game: str # name the game
|
2021-07-08 09:07:41 +00:00
|
|
|
topology_present: bool = False # indicate if world type has any meaningful layout/pathing
|
2022-04-28 16:03:44 +00:00
|
|
|
|
|
|
|
# gets automatically populated with all item and item group names
|
|
|
|
all_item_and_group_names: FrozenSet[str] = frozenset()
|
2021-07-12 13:33:20 +00:00
|
|
|
|
2021-07-12 16:05:46 +00:00
|
|
|
# map names to their IDs
|
|
|
|
item_name_to_id: Dict[str, int] = {}
|
|
|
|
location_name_to_id: Dict[str, int] = {}
|
|
|
|
|
2021-07-29 18:27:41 +00:00
|
|
|
# maps item group names to sets of items. Example: "Weapons" -> {"Sword", "Bow"}
|
|
|
|
item_name_groups: Dict[str, Set[str]] = {}
|
2021-07-12 16:05:46 +00:00
|
|
|
|
2021-08-22 02:22:34 +00:00
|
|
|
# increment this every time something in your world's names/id mappings changes.
|
|
|
|
# While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be
|
|
|
|
# retrieved by clients on every connection.
|
2021-09-30 17:51:07 +00:00
|
|
|
data_version: int = 1
|
2021-07-12 16:05:46 +00:00
|
|
|
|
2022-04-08 09:16:36 +00:00
|
|
|
# override this if changes to a world break forward-compatibility of the client
|
|
|
|
# The base version of (0, 1, 6) is provided for backwards compatibility and does *not* need to be updated in the
|
|
|
|
# future. Protocol level compatibility check moved to MultiServer.min_client_version.
|
|
|
|
required_client_version: Tuple[int, int, int] = (0, 1, 6)
|
|
|
|
|
|
|
|
# update this if the resulting multidata breaks forward-compatibility of the server
|
|
|
|
required_server_version: Tuple[int, int, int] = (0, 2, 4)
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
hint_blacklist: FrozenSet[str] = frozenset() # any names that should not be hintable
|
2021-06-11 16:02:48 +00:00
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
# NOTE: remote_items and remote_start_inventory are now available in the network protocol for the client to set.
|
|
|
|
# These values will be removed.
|
2021-07-13 17:14:57 +00:00
|
|
|
# if a world is set to remote_items, then it just needs to send location checks to the server and the server
|
|
|
|
# sends back the items
|
|
|
|
# if a world is set to remote_items = False, then the server never sends an item where receiver == finder,
|
|
|
|
# the client finds its own items in its own world.
|
|
|
|
remote_items: bool = True
|
|
|
|
|
2021-09-23 01:48:37 +00:00
|
|
|
# If remote_start_inventory is true, the start_inventory/world.precollected_items is sent on connection,
|
|
|
|
# otherwise the world implementation is in charge of writing the items to their output data.
|
|
|
|
remote_start_inventory: bool = True
|
|
|
|
|
2021-08-31 14:04:37 +00:00
|
|
|
# For games where after a victory it is impossible to go back in and get additional/remaining Locations checked.
|
|
|
|
# this forces forfeit: auto for those games.
|
|
|
|
forced_auto_forfeit: bool = False
|
|
|
|
|
2021-08-27 18:46:23 +00:00
|
|
|
# Hide World Type from various views. Does not remove functionality.
|
2021-09-30 17:51:07 +00:00
|
|
|
hidden: bool = False
|
2021-08-27 18:46:23 +00:00
|
|
|
|
2021-07-15 11:31:33 +00:00
|
|
|
# autoset on creation:
|
|
|
|
world: MultiWorld
|
|
|
|
player: int
|
|
|
|
|
2021-07-29 18:27:41 +00:00
|
|
|
# automatically generated
|
|
|
|
item_id_to_name: Dict[int, str]
|
|
|
|
location_id_to_name: Dict[int, str]
|
|
|
|
|
|
|
|
item_names: Set[str] # set of all potential item names
|
|
|
|
location_names: Set[str] # set of all potential location names
|
|
|
|
|
2022-02-20 20:54:00 +00:00
|
|
|
web: WebWorld = WebWorld()
|
|
|
|
|
2021-06-11 16:02:48 +00:00
|
|
|
def __init__(self, world: MultiWorld, player: int):
|
|
|
|
self.world = world
|
|
|
|
self.player = player
|
|
|
|
|
2021-07-22 13:51:50 +00:00
|
|
|
# overridable methods that get called by Main.py, sorted by execution order
|
2021-11-06 15:17:10 +00:00
|
|
|
# can also be implemented as a classmethod and called "stage_<original_name>",
|
2021-08-09 04:50:11 +00:00
|
|
|
# in that case the MultiWorld object is passed as an argument and it gets called once for the entire multiworld.
|
|
|
|
# An example of this can be found in alttp as stage_pre_fill
|
2022-04-30 01:37:28 +00:00
|
|
|
@classmethod
|
|
|
|
def assert_generate(cls) -> None:
|
|
|
|
"""Checks that a game is capable of generating, usually checks for some base file like a ROM.
|
|
|
|
Not run for unittests since they don't produce output"""
|
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def generate_early(self) -> None:
|
2021-07-15 06:50:08 +00:00
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def create_regions(self) -> None:
|
2021-06-11 16:02:48 +00:00
|
|
|
pass
|
2021-06-11 12:22:44 +00:00
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def create_items(self) -> None:
|
2021-07-22 13:51:50 +00:00
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def set_rules(self) -> None:
|
2021-06-11 12:22:44 +00:00
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def generate_basic(self) -> None:
|
2021-06-11 12:22:44 +00:00
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def pre_fill(self) -> None:
|
2021-08-09 04:50:11 +00:00
|
|
|
"""Optional method that is supposed to be used for special fill stages. This is run *after* plando."""
|
|
|
|
pass
|
|
|
|
|
2021-10-30 05:52:03 +00:00
|
|
|
@classmethod
|
2022-04-28 16:03:44 +00:00
|
|
|
def fill_hook(cls,
|
|
|
|
progitempool: List[Item],
|
|
|
|
nonexcludeditempool: List[Item],
|
|
|
|
localrestitempool: Dict[int, List[Item]],
|
|
|
|
nonlocalrestitempool: Dict[int, List[Item]],
|
|
|
|
restitempool: List[Item],
|
|
|
|
fill_locations: List[Location]) -> None:
|
2021-08-10 07:03:44 +00:00
|
|
|
"""Special method that gets called as part of distribute_items_restrictive (main fill).
|
|
|
|
This gets called once per present world type."""
|
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def post_fill(self) -> None:
|
2021-08-29 23:16:04 +00:00
|
|
|
"""Optional Method that is called after regular fill. Can be used to do adjustments before output generation."""
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def generate_output(self, output_directory: str) -> None:
|
2021-07-08 03:09:34 +00:00
|
|
|
"""This method gets called from a threadpool, do not use world.random here.
|
|
|
|
If you need any last-second randomization, use MultiWorld.slot_seeds[slot] instead."""
|
2021-06-11 12:22:44 +00:00
|
|
|
pass
|
2021-06-26 22:23:42 +00:00
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot
|
2021-07-21 16:08:15 +00:00
|
|
|
"""Fill in the slot_data field in the Connected network package."""
|
|
|
|
return {}
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata?
|
2021-08-09 07:15:41 +00:00
|
|
|
"""For deeper modification of server multidata."""
|
|
|
|
pass
|
|
|
|
|
2021-11-02 11:29:29 +00:00
|
|
|
# Spoiler writing is optional, these may not get called.
|
2022-04-28 16:03:44 +00:00
|
|
|
def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
|
2021-11-02 11:29:29 +00:00
|
|
|
"""Write to the spoiler header. If individual it's right at the end of that player's options,
|
|
|
|
if as stage it's right under the common header before per-player options."""
|
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def write_spoiler(self, spoiler_handle: TextIO) -> None:
|
2021-11-02 11:29:29 +00:00
|
|
|
"""Write to the spoiler "middle", this is after the per-player options and before locations,
|
|
|
|
meant for useful or interesting info."""
|
|
|
|
pass
|
|
|
|
|
2022-04-28 16:03:44 +00:00
|
|
|
def write_spoiler_end(self, spoiler_handle: TextIO) -> None:
|
2021-11-02 11:29:29 +00:00
|
|
|
"""Write to the end of the spoiler"""
|
|
|
|
pass
|
2022-02-17 06:07:34 +00:00
|
|
|
|
2021-10-19 21:23:48 +00:00
|
|
|
# end of ordered Main.py calls
|
2021-07-08 03:09:34 +00:00
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
def create_item(self, name: str) -> Item:
|
|
|
|
"""Create an item for this world type and player.
|
|
|
|
Warning: this may be called with self.world = None, for example by MultiServer"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
|
|
"""Called when the item pool needs to be filled with additional items to match location count."""
|
|
|
|
logging.warning(f"World {self} is generating a filler item without custom filler pool.")
|
2022-04-28 16:03:44 +00:00
|
|
|
return self.world.random.choice(tuple(self.item_name_to_id.keys()))
|
2022-02-05 14:49:19 +00:00
|
|
|
|
|
|
|
# decent place to implement progressive items, in most cases can stay as-is
|
2021-10-19 21:23:48 +00:00
|
|
|
def collect_item(self, state: CollectionState, item: Item, remove: bool = False) -> Optional[str]:
|
2021-08-10 07:47:28 +00:00
|
|
|
"""Collect an item name into state. For speed reasons items that aren't logically useful get skipped.
|
2021-08-21 04:55:08 +00:00
|
|
|
Collect None to skip item.
|
2022-02-05 14:49:19 +00:00
|
|
|
:param state: CollectionState to collect into
|
|
|
|
:param item: Item to decide on if it should be collected into state
|
2021-08-21 04:55:08 +00:00
|
|
|
:param remove: indicate if this is meant to remove from state instead of adding."""
|
2021-07-04 13:47:11 +00:00
|
|
|
if item.advancement:
|
2021-08-10 07:47:28 +00:00
|
|
|
return item.name
|
2022-04-28 16:03:44 +00:00
|
|
|
return None
|
2021-07-04 13:47:11 +00:00
|
|
|
|
2022-02-13 22:02:18 +00:00
|
|
|
# called to create all_state, return Items that are created during pre_fill
|
|
|
|
def get_pre_fill_items(self) -> List[Item]:
|
|
|
|
return []
|
|
|
|
|
2021-09-22 06:02:15 +00:00
|
|
|
# following methods should not need to be overridden.
|
2021-08-10 07:47:28 +00:00
|
|
|
def collect(self, state: CollectionState, item: Item) -> bool:
|
|
|
|
name = self.collect_item(state, item)
|
|
|
|
if name:
|
2022-02-05 14:49:19 +00:00
|
|
|
state.prog_items[name, self.player] += 1
|
2021-08-10 07:47:28 +00:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def remove(self, state: CollectionState, item: Item) -> bool:
|
2021-08-21 04:55:08 +00:00
|
|
|
name = self.collect_item(state, item, True)
|
2021-08-10 07:47:28 +00:00
|
|
|
if name:
|
2022-02-05 14:49:19 +00:00
|
|
|
state.prog_items[name, self.player] -= 1
|
|
|
|
if state.prog_items[name, self.player] < 1:
|
|
|
|
del (state.prog_items[name, self.player])
|
2021-08-10 07:47:28 +00:00
|
|
|
return True
|
|
|
|
return False
|
2021-07-16 10:23:05 +00:00
|
|
|
|
2022-03-20 15:07:51 +00:00
|
|
|
def create_filler(self) -> Item:
|
|
|
|
return self.create_item(self.get_filler_item_name())
|
2022-02-05 14:49:19 +00:00
|
|
|
|
2021-08-21 04:55:08 +00:00
|
|
|
|
2021-07-15 11:31:33 +00:00
|
|
|
# any methods attached to this can be used as part of CollectionState,
|
|
|
|
# please use a prefix as all of them get clobbered together
|
|
|
|
class LogicMixin(metaclass=AutoLogicRegister):
|
2021-07-16 10:23:05 +00:00
|
|
|
pass
|