2021-07-12 13:33:20 +00:00
|
|
|
from __future__ import annotations
|
2021-07-12 11:54:47 +00:00
|
|
|
from typing import Dict, Set, Tuple
|
|
|
|
|
|
|
|
from BaseClasses import MultiWorld, Item, CollectionState
|
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):
|
2021-07-12 13:33:20 +00:00
|
|
|
world_types:Dict[str, World] = {}
|
2021-06-11 12:22:44 +00:00
|
|
|
|
|
|
|
def __new__(cls, name, bases, dct):
|
2021-07-12 13:33:20 +00:00
|
|
|
dct["all_names"] = dct["item_names"] | dct["location_names"] | set(dct.get("item_name_groups", {}))
|
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-12 16:47:58 +00:00
|
|
|
# construct class
|
2021-06-11 12:22:44 +00:00
|
|
|
new_class = super().__new__(cls, name, bases, dct)
|
|
|
|
if "game" in dct:
|
|
|
|
AutoWorldRegister.world_types[dct["game"]] = new_class
|
|
|
|
return new_class
|
|
|
|
|
|
|
|
|
|
|
|
def call_single(world: MultiWorld, method_name: str, player: int):
|
|
|
|
method = getattr(world.worlds[player], method_name)
|
2021-06-11 16:02:48 +00:00
|
|
|
return method()
|
2021-06-11 12:22:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
def call_all(world: MultiWorld, method_name: str):
|
|
|
|
for player in world.player_ids:
|
|
|
|
call_single(world, method_name, player)
|
|
|
|
|
|
|
|
|
|
|
|
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."""
|
|
|
|
|
2021-06-11 16:02:48 +00:00
|
|
|
world: MultiWorld
|
|
|
|
player: int
|
2021-06-25 21:32:13 +00:00
|
|
|
options: dict = {}
|
2021-07-08 09:07:41 +00:00
|
|
|
topology_present: bool = False # indicate if world type has any meaningful layout/pathing
|
2021-07-12 13:11:48 +00:00
|
|
|
item_names: Set[str] = frozenset() # set of all potential item names
|
2021-07-12 11:54:47 +00:00
|
|
|
# maps item group names to sets of items. Example: "Weapons" -> {"Sword", "Bow"}
|
|
|
|
item_name_groups: Dict[str, Set[str]] = {}
|
2021-07-12 13:11:48 +00:00
|
|
|
location_names: Set[str] = frozenset() # set of all potential location names
|
2021-07-12 13:33:20 +00:00
|
|
|
all_names: Set[str] = frozenset() # gets automatically populated with all item, item group and location names
|
|
|
|
|
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] = {}
|
|
|
|
|
|
|
|
# reverse, automatically generated
|
|
|
|
item_id_to_name: Dict[int, str] = {}
|
|
|
|
location_id_to_name: Dict[int, str] = {}
|
|
|
|
|
|
|
|
data_version = 1 # increment this every time something in your world's names/id mappings changes.
|
|
|
|
|
2021-07-12 13:33:20 +00:00
|
|
|
hint_blacklist: Set[str] = frozenset() # any names that should not be hintable
|
2021-06-11 16:02:48 +00:00
|
|
|
|
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-06-11 16:02:48 +00:00
|
|
|
def __init__(self, world: MultiWorld, player: int):
|
|
|
|
self.world = world
|
|
|
|
self.player = player
|
|
|
|
|
2021-07-08 03:09:34 +00:00
|
|
|
# overwritable methods that get called by Main.py, sorted by execution order
|
|
|
|
def create_regions(self):
|
2021-06-11 16:02:48 +00:00
|
|
|
pass
|
2021-06-11 12:22:44 +00:00
|
|
|
|
2021-06-11 16:02:48 +00:00
|
|
|
def set_rules(self):
|
2021-06-11 12:22:44 +00:00
|
|
|
pass
|
|
|
|
|
2021-07-08 03:09:34 +00:00
|
|
|
def generate_basic(self):
|
2021-06-11 12:22:44 +00:00
|
|
|
pass
|
|
|
|
|
2021-06-11 16:02:48 +00:00
|
|
|
def generate_output(self):
|
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
|
|
|
|
2021-07-12 11:54:47 +00:00
|
|
|
def get_required_client_version(self) -> Tuple[int, int, int]:
|
2021-07-08 03:09:34 +00:00
|
|
|
return 0, 0, 3
|
|
|
|
|
|
|
|
# end of Main.py calls
|
|
|
|
|
2021-07-12 11:54:47 +00:00
|
|
|
def collect(self, state: CollectionState, item: Item) -> bool:
|
2021-07-08 03:09:34 +00:00
|
|
|
"""Collect an item into state. For speed reasons items that aren't logically useful get skipped."""
|
2021-07-04 13:47:11 +00:00
|
|
|
if item.advancement:
|
|
|
|
state.prog_items[item.name, item.player] += 1
|
2021-07-04 14:18:21 +00:00
|
|
|
return True # indicate that a logical state change has occured
|
2021-07-04 13:47:11 +00:00
|
|
|
return False
|
|
|
|
|
2021-07-12 11:54:47 +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
|