evennia/evennia/contrib/xyzgrid/xyzgrid.py
2021-07-04 18:03:07 +02:00

165 lines
5.8 KiB
Python

"""
The grid
This represents the full XYZ grid, which consists of
- 2D `Map`-objects parsed from Map strings and Map-legend components. Each represents one
Z-coordinate or location.
- `Prototypes` for how to build each XYZ component into 'real' rooms and exits.
- Actual in-game rooms and exits, mapped to the game based on Map data.
The grid has three main functions:
- Building new rooms/exits from scratch based on one or more Maps.
- Updating the rooms/exits tied to an existing Map when the Map string
of that map changes.
- Fascilitate communication between the in-game entities and their Map.
"""
import itertools
from evennia.scripts.scripts import DefaultScript
from evennia.utils import logger
from .xymap import XYMap
from .xyzroom import XYZRoom, XYZExit
class XYZGrid(DefaultScript):
"""
Main grid class. This organizes the Maps based on their name/Z-coordinate.
"""
def at_script_creation(self):
"""
What we store persistently is data used to create each map (the legends, names etc)
"""
self.db.map_data = {}
@property
def grid(self):
if self.ndb.grid is None:
self.reload()
return self.ndb.grid
def get(self, mapname, default=None):
return self.grid.get(mapname, default)
def reload(self):
"""
Reload and rebuild the grid. This is done on a server reload and is also necessary if adding
a new map since this may introduce new between-map traversals.
"""
logger.log_info("[grid] (Re)loading grid ...")
self.ndb.grid = {}
nmaps = 0
# generate all Maps - this will also initialize their components
# and bake any pathfinding paths (or load from disk-cache)
for zcoord, mapdata in self.db.map_data.items():
logger.log_info(f"[grid] Loading map '{zcoord}'...")
xymap = XYMap(dict(mapdata), Z=zcoord, xyzgrid=self)
xymap.parse()
xymap.calculate_path_matrix()
self.ndb.grid[zcoord] = xymap
nmaps += 1
# store
logger.log_info(f"[grid] Loaded and linked {nmaps} map(s).")
def at_init(self):
"""
Called when the script loads into memory (on creation or after a reload). This will load all
map data into memory.
"""
self.reload()
def add_maps(self, *mapdatas):
"""
Add map or maps to the grid.
Args:
*mapdatas (dict): Each argument is a dict structure
`{"map": <mapstr>, "legend": <legenddict>, "name": <name>,
"prototypes": <dict-of-dicts>}`. The `prototypes are
coordinate-specific overrides for nodes/links on the map, keyed with their
(X,Y) coordinate within that map.
Raises:
RuntimeError: If mapdata is malformed.
"""
for mapdata in mapdatas:
zcoord = mapdata.get('zcoord')
if not zcoord:
raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.")
self.db.map_data[zcoord] = mapdata
def remove_map(self, *zcoords, remove_objects=True):
"""
Remove an XYmap from the grid.
Args:
*zoords (str): The zcoords/XYmaps to remove.
remove_objects (bool, optional): If the synced database objects (rooms/exits) should
be removed alongside this map.
"""
# from evennia import set_trace;set_trace()
for zcoord in zcoords:
if zcoord in self.db.map_data:
self.db.map_data.pop(zcoord)
if remove_objects:
# we can't batch-delete because we want to run the .delete
# method that also wipes exits and moves content to save locations
for xyzroom in XYZRoom.objects.filter_xyz(xyz=('*', '*', zcoord)):
xyzroom.delete()
self.reload()
def delete(self):
"""
Clear the entire grid, including database entities.
"""
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
def spawn(self, xyz=('*', '*', '*'), directions=None):
"""
Create/recreate/update the in-game grid based on the stored Maps or for a specific Map
or coordinate.
Args:
xyz (tuple, optional): An (X,Y,Z) coordinate, where Z is the name of the map. `'*'`
acts as a wildcard.
directions (list, optional): A list of cardinal directions ('n', 'ne' etc).
Spawn exits only the given direction. If unset, all needed directions are spawned.
Examples:
- `xyz=('*', '*', '*')` (default) - spawn/update all maps.
- `xyz=(1, 3, 'foo')` - sync a specific element of map 'foo' only.
- `xyz=('*', '*', 'foo') - sync all elements of map 'foo'
- `xyz=(1, 3, '*') - sync all (1,3) coordinates on all maps (rarely useful)
- `xyz=(1, 3, 'foo')`, `direction='ne'` - sync only the north-eastern exit
out of the specific node on map 'foo'.
"""
x, y, z = xyz
wildcard = '*'
if z == wildcard:
xymaps = self.grid
elif self.ndb.grid and z in self.ndb.grid:
xymaps = {z: self.grid[z]}
else:
raise RuntimeError(f"The 'z' coordinate/name '{z}' is not found on the grid.")
# first build all nodes/rooms
for zcoord, xymap in xymaps.items():
logger.log_info(f"[grid] spawning/updating nodes for {zcoord} ...")
xymap.spawn_nodes(xy=(x, y))
# next build all links between nodes (including between maps)
for zcoord, xymap in xymaps.items():
logger.log_info(f"[grid] spawning/updating links for {zcoord} ...")
xymap.spawn_links(xy=(x, y), directions=directions)