Finished launcher, testing example
This commit is contained in:
parent
a1438150c0
commit
1c06363bbe
10 changed files with 504 additions and 170 deletions
|
|
@ -16,6 +16,8 @@ Use `evennia xyzgrid help` for usage help.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os.path import join as pathjoin
|
||||||
|
from django.conf import settings
|
||||||
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid
|
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid
|
||||||
|
|
||||||
_HELP_SHORT = """
|
_HELP_SHORT = """
|
||||||
|
|
@ -37,7 +39,7 @@ evennia xyzgrid init
|
||||||
First start of the grid. This will create the XYZGrid global script. No maps are loaded yet!
|
First start of the grid. This will create the XYZGrid global script. No maps are loaded yet!
|
||||||
It's safe to run this command multiple times; the grid will only be initialized once.
|
It's safe to run this command multiple times; the grid will only be initialized once.
|
||||||
|
|
||||||
evennia xyzgrid add path.to.xymap.module
|
evennia xyzgrid add path.to.xymap.module [,path, path,...]
|
||||||
|
|
||||||
Add one or more XYmaps (each a string-map representing one Z position along with prototypes
|
Add one or more XYmaps (each a string-map representing one Z position along with prototypes
|
||||||
etc). The module will be parsed for
|
etc). The module will be parsed for
|
||||||
|
|
@ -45,11 +47,12 @@ evennia xyzgrid add path.to.xymap.module
|
||||||
- a XYMAP_DATA a dict
|
- a XYMAP_DATA a dict
|
||||||
{"map": mapstring, "zcoord": mapname/zcoord, "legend": dict, "prototypes": dict}
|
{"map": mapstring, "zcoord": mapname/zcoord, "legend": dict, "prototypes": dict}
|
||||||
describing one single XYmap, or
|
describing one single XYmap, or
|
||||||
- a XYMAP_LIST - a list of multiple dicts on the XYMAP_DATA form. This allows to load
|
- a XYMAP_DATA_LIST - a list of multiple dicts on the XYMAP_DATA form. This allows to load
|
||||||
multiple maps from the same module.
|
multiple maps from the same module.
|
||||||
|
|
||||||
Note that adding a map does *not* build it. If maps are linked to one another, you should add
|
Note that adding a map does *not* build it. If maps are linked to one another, you should add
|
||||||
all linked maps before building, or you'll get errors when spawning the linking exits.
|
all linked maps before running 'build', or you'll get errors when creating transitional exits
|
||||||
|
between maps.
|
||||||
|
|
||||||
evennia xyzgrid build
|
evennia xyzgrid build
|
||||||
|
|
||||||
|
|
@ -106,38 +109,77 @@ def _option_list(**suboptions):
|
||||||
print(str(xymap))
|
print(str(xymap))
|
||||||
|
|
||||||
|
|
||||||
def _option_init(**suboptions):
|
def _option_init(*suboptions):
|
||||||
"""
|
"""
|
||||||
Initialize a new grid. Will fail if a Grid already exists.
|
Initialize a new grid. Will fail if a Grid already exists.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
grid = get_xyzgrid()
|
grid = get_xyzgrid()
|
||||||
print(f"The grid is initalized as the Script 'XYZGrid'({grid.dbref})")
|
print(f"The grid is initalized as the Script '{grid.key}'({grid.dbref})")
|
||||||
|
|
||||||
def _option_add(**suboptions):
|
|
||||||
|
def _option_add(*suboptions):
|
||||||
"""
|
"""
|
||||||
Add a new map to the grid.
|
Add one or more map to the grid. Supports `add path,path,path,...`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
grid = get_xyzgrid()
|
||||||
|
xymap_data_list = []
|
||||||
|
for path in suboptions:
|
||||||
|
xymap_data_list.expand(grid.maps_from_module(path))
|
||||||
|
grid.add_maps(*xymap_data_list)
|
||||||
|
|
||||||
def _option_build(**suboptions):
|
|
||||||
|
def _option_build(*suboptions):
|
||||||
"""
|
"""
|
||||||
Build the grid or part of it.
|
Build the grid or part of it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
grid = get_xyzgrid()
|
||||||
|
|
||||||
def _option_initpath(**suboptions):
|
# override grid's logger to echo directly to console
|
||||||
|
def _log(self, msg):
|
||||||
|
print(msg)
|
||||||
|
grid.log = _log
|
||||||
|
|
||||||
|
if suboptions:
|
||||||
|
opts = ''.join(suboptions).strip('()')
|
||||||
|
# coordinate tuple
|
||||||
|
try:
|
||||||
|
x, y, z = (part.strip() for part in opts.split(","))
|
||||||
|
except ValueError:
|
||||||
|
print("Build coordinate must be given as (X, Y, Z) tuple, where '*' act "
|
||||||
|
"wild cards and Z is the mapname/z-coord of the map to load.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
x, y, z = '*', '*', '*'
|
||||||
|
|
||||||
|
grid.spawn(xyz=(x, y, z))
|
||||||
|
|
||||||
|
|
||||||
|
def _option_initpath(*suboptions):
|
||||||
"""
|
"""
|
||||||
Initialize the pathfinding matrices for grid or part of it.
|
(Re)Initialize the pathfinding matrices for grid or part of it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
grid = get_xyzgrid()
|
||||||
def _option_delete(**suboptions):
|
xymaps = grid.all_rooms()
|
||||||
"""
|
nmaps = len(xymaps)
|
||||||
Delete the grid or parts of it.
|
for inum, xymap in enumerate(grid.all_rooms()):
|
||||||
|
print(f"Rebuilding pathfinding matrix for xymap Z={xymap.Z} ({inum+1}/{nmaps}) ...")
|
||||||
|
xymap.calculate_path_matrix(force=True)
|
||||||
|
|
||||||
|
cachepath = pathjoin(settings.GAMEDIR, "server", ".cache")
|
||||||
|
print(f"... done. Data cached to {cachepath}.")
|
||||||
|
|
||||||
|
|
||||||
|
def _option_delete(*suboptions):
|
||||||
|
"""
|
||||||
|
Delete the grid or parts of it. Allows mapname,mapname, ...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
grid = get_xyzgrid()
|
||||||
if not suboptions:
|
if not suboptions:
|
||||||
repl = input("WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
repl = input("WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
||||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||||
|
|
@ -146,16 +188,35 @@ def _option_delete(**suboptions):
|
||||||
print("Aborted.")
|
print("Aborted.")
|
||||||
else:
|
else:
|
||||||
print("Deleting grid ...")
|
print("Deleting grid ...")
|
||||||
grid = get_xyzgrid()
|
|
||||||
grid.delete()
|
grid.delete()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
zcoords = (part.strip() for part in suboptions)
|
||||||
|
err = False
|
||||||
|
for zcoord in zcoords:
|
||||||
|
if not grid.get_map(zcoord):
|
||||||
|
print(f"Mapname/zcoord {zcoord} is not a part of the grid.")
|
||||||
|
err = True
|
||||||
|
if err:
|
||||||
|
print("Valid mapnames/zcoords are\n:", "\n ".join(
|
||||||
|
xymap.Z for xymap in grid.all_rooms()))
|
||||||
|
return
|
||||||
|
repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding "
|
||||||
|
"rooms/exits!"
|
||||||
|
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||||
|
"\nThis can't be undone. Are you sure you want to continue? Y/[N]?")
|
||||||
|
if repl.lower() not in ('yes', 'y'):
|
||||||
|
print("Aborted.")
|
||||||
|
else:
|
||||||
|
print("Deleting selected xymaps ...")
|
||||||
|
|
||||||
|
grid.remove_map(*zcoords, remove_objects=True)
|
||||||
|
|
||||||
|
|
||||||
def xyzcommand(*args):
|
def xyzcommand(*args):
|
||||||
"""
|
"""
|
||||||
Evennia launcher command. This is made available as `evennia xyzgrid` on the command line,
|
Evennia launcher command. This is made available as `evennia xyzgrid` on the command line,
|
||||||
once `settings.EXTRA_LAUNCHER_COMMANDS` is updated.
|
once added to `settings.EXTRA_LAUNCHER_COMMANDS`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not args:
|
if not args:
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,263 @@
|
||||||
MAP = r"""
|
"""
|
||||||
|
Example xymaps to use with the XYZgrid contrib. Build outside of the game using
|
||||||
|
the `evennia xyzgrid` launcher command.
|
||||||
|
|
||||||
|
First add the launcher extension in your mygame/server/conf/settings.py:
|
||||||
|
|
||||||
|
EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.xyzgrid.launchcmd.xyzcommand'
|
||||||
|
|
||||||
|
Then
|
||||||
|
|
||||||
|
evennia xyzgrid init
|
||||||
|
evennia xyzgrid add evennia.contrib.xyzgrid.map_example
|
||||||
|
evennia xyzgrid build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.contrib.xyzgrid import map_legend
|
||||||
|
|
||||||
|
# default prototype parent. It's important that
|
||||||
|
# the typeclass inherits from the XYZRoom (or XYZExit)
|
||||||
|
# the map_legend.XYZROOM_PARENT and XYZEXIT_PARENTS can also
|
||||||
|
# be used as a shortcut.
|
||||||
|
|
||||||
|
PARENT = {
|
||||||
|
"key": "An empty room",
|
||||||
|
"prototype_key": "xyzmap_room_map1",
|
||||||
|
"typeclass": "evennia.contrib.xyzgrid.xyzroom.XYZRoom",
|
||||||
|
"desc": "An empty room."
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------- map 1 - the large tree
|
||||||
|
# this exemplifies the various map symbols
|
||||||
|
# but is not heavily prototyped
|
||||||
|
|
||||||
|
MAP1 = r"""
|
||||||
1
|
1
|
||||||
+ 0 1 2 3 4 5 6 7 8 9 0
|
+ 0 1 2 3 4 5 6 7 8 9 0
|
||||||
|
|
||||||
10 #-#-#-#-#
|
9 #-------#-#-------I
|
||||||
| | \
|
\ /
|
||||||
9 #---+---#-#-----I
|
8 #-#---# #-t
|
||||||
\ | /
|
|
||||||
8 #-#-#-#-# #
|
|
||||||
|\ |
|
|\ |
|
||||||
7 #i#-#-#+#-----#-t
|
7 #i#-#b--#-t
|
||||||
| |
|
| |
|
||||||
6 #i#-#---#-#-#-#-#
|
5 o-#---#
|
||||||
| |x|x|
|
\ /
|
||||||
5 o-#-#-# #-#-#
|
4 o-o-#-#
|
||||||
\ / |x|x|
|
/ d
|
||||||
4 o-o-#-# #-#-#
|
3 #-----+-------#
|
||||||
/ /
|
| d
|
||||||
3 #-# / #
|
2 | |
|
||||||
\ / d
|
v u
|
||||||
2 o-o-#-# |
|
1 #---#>#-#
|
||||||
| | u
|
/
|
||||||
1 #-#-#># #
|
0 T-#
|
||||||
^ |
|
|
||||||
0 T-----#-# #-t
|
|
||||||
|
|
||||||
+ 0 1 2 3 4 5 6 7 8 9 0
|
+ 0 1 2 3 4 5 6 7 8 9 0
|
||||||
1
|
1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# use default legend
|
|
||||||
LEGEND = {
|
class TransitionToCave(map_legend.MapTransitionMapNode):
|
||||||
|
"""
|
||||||
|
A transition from map2 to map1
|
||||||
|
|
||||||
|
"""
|
||||||
|
symbol = 'T'
|
||||||
|
target_map_xyz = (2, 3, 'small cave')
|
||||||
|
|
||||||
|
|
||||||
|
# extends the default legend
|
||||||
|
LEGEND_MAP1 = {
|
||||||
|
'T': TransitionToCave
|
||||||
}
|
}
|
||||||
|
|
||||||
PARENT = {
|
|
||||||
"key": "An empty dungeon room",
|
|
||||||
"prototype_key": "dungeon_doom_prot",
|
|
||||||
"typeclass": "evennia.contrib.xyzgrid.xyzrooms.XYZRoom",
|
|
||||||
"desc": "Air is cold and stale in this barren room."
|
|
||||||
}
|
|
||||||
|
|
||||||
# link coordinates to rooms
|
# link coordinates to rooms
|
||||||
ROOMS = {
|
PROTOTYPES_MAP1 = {
|
||||||
(1, 0): {
|
# node/room prototypes
|
||||||
|
(3, 0): {
|
||||||
"key": "Dungeon Entrance",
|
"key": "Dungeon Entrance",
|
||||||
"prototype_parent": PARENT,
|
"desc": "To the west, a narrow opening leads into darkness."
|
||||||
"desc": "A dark entrance."
|
|
||||||
},
|
},
|
||||||
(4, 0): {
|
(4, 1): {
|
||||||
"key": "Antechamber",
|
"key": "Under the foilage of a giant tree",
|
||||||
"prototype_parent": PARENT,
|
"desc": "High above the branches of a giant tree blocs out the sunlight. A slide "
|
||||||
"desc": "A small antechamber",
|
"leading down from the upper branches ends here."
|
||||||
|
},
|
||||||
|
(4, 4): {
|
||||||
|
"key": "The slide",
|
||||||
|
"desc": "A slide leads down to the ground from here. It looks like a one-way trip."
|
||||||
|
},
|
||||||
|
(6, 1): {
|
||||||
|
"key": "Thorny path",
|
||||||
|
"desc": "To the east is a pathway of thorns. If you get through, you don't think you'll be "
|
||||||
|
"able to get back here the same way."
|
||||||
|
},
|
||||||
|
(8, 1): {
|
||||||
|
"key": "By a large tree",
|
||||||
|
"desc": "You are standing at the root of a great tree."
|
||||||
|
},
|
||||||
|
(8, 3): {
|
||||||
|
"key": "At the top of the tree",
|
||||||
|
"desc": "You are at the top of the tree."
|
||||||
|
},
|
||||||
|
(3, 7): {
|
||||||
|
"key": "Dense foilage",
|
||||||
|
"desc": "The foilage to the east is extra dense. It will take forever to get through it."
|
||||||
|
},
|
||||||
|
(5, 7): {
|
||||||
|
"key": "On a huge branch",
|
||||||
|
"desc": "To the east is a glowing light, may be a teleporter."
|
||||||
|
},
|
||||||
|
(9, 8): {
|
||||||
|
"key": "On an enormous branch",
|
||||||
|
"desc": "To the east is a glowing light, may be a teleporter."
|
||||||
|
},
|
||||||
|
(10, 9): {
|
||||||
|
"key": "A gorgeous view",
|
||||||
|
"desc": "The view from here is breathtaking, showing the forest stretching far and wide."
|
||||||
|
},
|
||||||
|
# default rooms
|
||||||
|
('*', '*'): {
|
||||||
|
"key": "Among the branches of a giant tree",
|
||||||
|
"desc": "These branches are wide enough to easily walk on. There's green all around."
|
||||||
|
},
|
||||||
|
# directional prototypes
|
||||||
|
(3, 0, 'w'): {
|
||||||
|
"desc": "A dark passage into the underworld."
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for prot in PROTOTYPES_MAP1.values():
|
||||||
|
prot['prototype_parent'] = PARENT
|
||||||
|
|
||||||
|
|
||||||
|
XYMAP_DATA_MAP1 = {
|
||||||
|
"zcoord": "the large tree",
|
||||||
|
"map": MAP1,
|
||||||
|
"legend": LEGEND_MAP1,
|
||||||
|
"prototypes": PROTOTYPES_MAP1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------- map2 definitions - small cave
|
||||||
|
# this gives prototypes for every room
|
||||||
|
|
||||||
|
MAP2 = r"""
|
||||||
|
+ 0 1 2 3
|
||||||
|
|
||||||
|
3 #-#-#
|
||||||
|
|x|
|
||||||
|
2 #-#-#
|
||||||
|
| \
|
||||||
|
1 #---#
|
||||||
|
| /
|
||||||
|
0 T-#-#
|
||||||
|
|
||||||
|
+ 0 1 2 3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# custom map node
|
||||||
|
class TransitionToLargeTree(map_legend.MapTransitionMapNode):
|
||||||
|
"""
|
||||||
|
A transition from map1 to map2
|
||||||
|
|
||||||
|
"""
|
||||||
|
symbol = 'T'
|
||||||
|
target_map_xyz = (3, 0, 'the large tree')
|
||||||
|
|
||||||
|
|
||||||
|
# this extends the default legend (that defines #,-+ etc)
|
||||||
|
LEGEND_MAP2 = {
|
||||||
|
"T": TransitionToLargeTree
|
||||||
|
}
|
||||||
|
|
||||||
|
# prototypes for specific locations
|
||||||
|
PROTOTYPES_MAP2 = {
|
||||||
|
# node/rooms prototype overrides
|
||||||
|
(1, 0): {
|
||||||
|
"key": "The entrance",
|
||||||
|
"desc": "This is the entrance to a small cave leading into the ground. "
|
||||||
|
"Light sifts in from the outside, while cavernous passages disappear "
|
||||||
|
"into darkness."
|
||||||
|
},
|
||||||
|
(2, 0): {
|
||||||
|
"key": "A gruesome sight.",
|
||||||
|
"desc": "Something was killed here recently. The smell is unbearable."
|
||||||
|
},
|
||||||
|
(1, 1): {
|
||||||
|
"key": "A dark pathway",
|
||||||
|
"desc": "The path splits three ways here. To the north a faint light can be seen."
|
||||||
|
},
|
||||||
|
(3, 2): {
|
||||||
|
"key": "Stagnant water",
|
||||||
|
"desc": "A pool of stagnant, black water dominates this small chamber. To the nortwest "
|
||||||
|
"a faint light can be seen."
|
||||||
|
},
|
||||||
|
(0, 2): {
|
||||||
|
"key": "A dark alcove",
|
||||||
|
"desc": "This alcove is empty."
|
||||||
|
},
|
||||||
|
(1, 2): {
|
||||||
|
"key": "South-west corner of the atrium",
|
||||||
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
|
"between the stones."
|
||||||
|
},
|
||||||
|
(2, 2): {
|
||||||
|
"key": "South-east corner of the atrium",
|
||||||
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
|
"between the stones."
|
||||||
|
},
|
||||||
|
(1, 3): {
|
||||||
|
"key": "North-west corner of the atrium",
|
||||||
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
|
"between the stones."
|
||||||
|
},
|
||||||
|
(2, 3): {
|
||||||
|
"key": "North-east corner of the atrium",
|
||||||
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
|
"between the stones. To the east is a dark passage."
|
||||||
|
},
|
||||||
|
(3, 3): {
|
||||||
|
"key": "Craggy crevice",
|
||||||
|
"desc": "This is the deepest part of the dungeon. The path shrinks away and there "
|
||||||
|
"is no way to continue deeper."
|
||||||
|
},
|
||||||
|
# default fallback for undefined nodes
|
||||||
|
('*', '*'): {
|
||||||
|
"key": "A dark room",
|
||||||
|
"desc": "A dark, but empty, room."
|
||||||
|
},
|
||||||
|
# directional prototypes
|
||||||
|
(1, 0, 'w'): {
|
||||||
|
"desc": "A narrow path to the fresh air of the outside world."
|
||||||
|
},
|
||||||
|
# directional fallbacks for unset directions
|
||||||
|
('*', '*', '*'): {
|
||||||
|
"desc": "A dark passage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# this is required by the prototypes, but we add it all at once so we don't
|
||||||
|
# need to add it to every line above
|
||||||
|
for prot in PROTOTYPES_MAP2.values():
|
||||||
|
prot['prototype_parent'] = PARENT
|
||||||
|
|
||||||
MAP_DATA = {
|
|
||||||
"name": "Dungeon of Doom",
|
XYMAP_DATA_MAP2 = {
|
||||||
"map": MAP,
|
"map": MAP2,
|
||||||
"legend": LEGEND,
|
"zcoord": "the small cave",
|
||||||
"rooms": ROOMS,
|
"legend": LEGEND_MAP2,
|
||||||
|
"prototypes": PROTOTYPES_MAP2
|
||||||
}
|
}
|
||||||
|
|
||||||
XYMAP_LIST = [
|
# This is read by the parser
|
||||||
MAP_DATA
|
XYMAP_DATA_LIST = [
|
||||||
|
XYMAP_DATA_MAP1,
|
||||||
|
XYMAP_DATA_MAP2
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ class MapNode:
|
||||||
'sw': ('southwest', 'sw', 'south-west'),
|
'sw': ('southwest', 'sw', 'south-west'),
|
||||||
'w': ('west', 'w'),
|
'w': ('west', 'w'),
|
||||||
'nw': ('northwest', 'nw', 'north-west'),
|
'nw': ('northwest', 'nw', 'north-west'),
|
||||||
'd' : ('down', 'd', 'do'),
|
'd': ('down', 'd', 'do'),
|
||||||
'u' : ('up', 'u'),
|
'u': ('up', 'u'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, x, y, Z, node_index=0, xymap=None):
|
def __init__(self, x, y, Z, node_index=0, xymap=None):
|
||||||
|
|
@ -261,6 +261,7 @@ class MapNode:
|
||||||
return
|
return
|
||||||
|
|
||||||
xyz = self.get_spawn_xyz()
|
xyz = self.get_spawn_xyz()
|
||||||
|
print("xyz:", xyz, self.node_index)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||||
|
|
@ -944,9 +945,10 @@ class SmartMapLink(MapLink):
|
||||||
directions[direction] = REVERSE_DIRECTIONS[direction]
|
directions[direction] = REVERSE_DIRECTIONS[direction]
|
||||||
else:
|
else:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"must have exactly two connections - either "
|
"must have exactly two connections - either directly to "
|
||||||
f"two nodes or unambiguous link directions. Found neighbor(s) in directions "
|
"two nodes or connecting directly to one node and with exactly one other "
|
||||||
f"{list(neighbors.keys())}.", self)
|
f"link direction. The neighbor(s) in directions {list(neighbors.keys())} do "
|
||||||
|
"not fulfill these criteria.", self)
|
||||||
|
|
||||||
self.directions = directions
|
self.directions = directions
|
||||||
return self.directions.get(start_direction)
|
return self.directions.get(start_direction)
|
||||||
|
|
@ -1027,7 +1029,7 @@ class InvisibleSmartMapLink(SmartMapLink):
|
||||||
class BasicMapNode(MapNode):
|
class BasicMapNode(MapNode):
|
||||||
"""Basic map Node"""
|
"""Basic map Node"""
|
||||||
symbol = "#"
|
symbol = "#"
|
||||||
prototype = "xyz_room_prototype"
|
prototype = "xyz_room"
|
||||||
|
|
||||||
|
|
||||||
class MapTransitionMapNode(TransitionMapNode):
|
class MapTransitionMapNode(TransitionMapNode):
|
||||||
|
|
@ -1043,35 +1045,35 @@ class InterruptMapNode(MapNode):
|
||||||
symbol = "I"
|
symbol = "I"
|
||||||
display_symbol = "#"
|
display_symbol = "#"
|
||||||
interrupt_path = True
|
interrupt_path = True
|
||||||
prototype = "xyz_room_prototype"
|
prototype = "xyz_room"
|
||||||
|
|
||||||
|
|
||||||
class NSMapLink(MapLink):
|
class NSMapLink(MapLink):
|
||||||
"""Two-way, North-South link"""
|
"""Two-way, North-South link"""
|
||||||
symbol = "|"
|
symbol = "|"
|
||||||
directions = {"n": "s", "s": "n"}
|
directions = {"n": "s", "s": "n"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class EWMapLink(MapLink):
|
class EWMapLink(MapLink):
|
||||||
"""Two-way, East-West link"""
|
"""Two-way, East-West link"""
|
||||||
symbol = "-"
|
symbol = "-"
|
||||||
directions = {"e": "w", "w": "e"}
|
directions = {"e": "w", "w": "e"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class NESWMapLink(MapLink):
|
class NESWMapLink(MapLink):
|
||||||
"""Two-way, NorthWest-SouthWest link"""
|
"""Two-way, NorthWest-SouthWest link"""
|
||||||
symbol = "/"
|
symbol = "/"
|
||||||
directions = {"ne": "sw", "sw": "ne"}
|
directions = {"ne": "sw", "sw": "ne"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class SENWMapLink(MapLink):
|
class SENWMapLink(MapLink):
|
||||||
"""Two-way, SouthEast-NorthWest link"""
|
"""Two-way, SouthEast-NorthWest link"""
|
||||||
symbol = "\\"
|
symbol = "\\"
|
||||||
directions = {"se": "nw", "nw": "se"}
|
directions = {"se": "nw", "nw": "se"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class PlusMapLink(MapLink):
|
class PlusMapLink(MapLink):
|
||||||
|
|
@ -1079,7 +1081,7 @@ class PlusMapLink(MapLink):
|
||||||
symbol = "+"
|
symbol = "+"
|
||||||
directions = {"s": "n", "n": "s",
|
directions = {"s": "n", "n": "s",
|
||||||
"e": "w", "w": "e"}
|
"e": "w", "w": "e"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class CrossMapLink(MapLink):
|
class CrossMapLink(MapLink):
|
||||||
|
|
@ -1087,35 +1089,35 @@ class CrossMapLink(MapLink):
|
||||||
symbol = "x"
|
symbol = "x"
|
||||||
directions = {"ne": "sw", "sw": "ne",
|
directions = {"ne": "sw", "sw": "ne",
|
||||||
"se": "nw", "nw": "se"}
|
"se": "nw", "nw": "se"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class NSOneWayMapLink(MapLink):
|
class NSOneWayMapLink(MapLink):
|
||||||
"""One-way North-South link"""
|
"""One-way North-South link"""
|
||||||
symbol = "v"
|
symbol = "v"
|
||||||
directions = {"n": "s"}
|
directions = {"n": "s"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class SNOneWayMapLink(MapLink):
|
class SNOneWayMapLink(MapLink):
|
||||||
"""One-way South-North link"""
|
"""One-way South-North link"""
|
||||||
symbol = "^"
|
symbol = "^"
|
||||||
directions = {"s": "n"}
|
directions = {"s": "n"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class EWOneWayMapLink(MapLink):
|
class EWOneWayMapLink(MapLink):
|
||||||
"""One-way East-West link"""
|
"""One-way East-West link"""
|
||||||
symbol = "<"
|
symbol = "<"
|
||||||
directions = {"e": "w"}
|
directions = {"e": "w"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class WEOneWayMapLink(MapLink):
|
class WEOneWayMapLink(MapLink):
|
||||||
"""One-way West-East link"""
|
"""One-way West-East link"""
|
||||||
symbol = ">"
|
symbol = ">"
|
||||||
directions = {"w": "e"}
|
directions = {"w": "e"}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class UpMapLink(SmartMapLink):
|
class UpMapLink(SmartMapLink):
|
||||||
|
|
@ -1125,7 +1127,7 @@ class UpMapLink(SmartMapLink):
|
||||||
# all movement over this link is 'up', regardless of where on the xygrid we move.
|
# all movement over this link is 'up', regardless of where on the xygrid we move.
|
||||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class DownMapLink(UpMapLink):
|
class DownMapLink(UpMapLink):
|
||||||
|
|
@ -1134,14 +1136,14 @@ class DownMapLink(UpMapLink):
|
||||||
# all movement over this link is 'down', regardless of where on the xygrid we move.
|
# all movement over this link is 'down', regardless of where on the xygrid we move.
|
||||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class InterruptMapLink(InvisibleSmartMapLink):
|
class InterruptMapLink(InvisibleSmartMapLink):
|
||||||
"""A (still passable) link that causes the pathfinder to stop before crossing."""
|
"""A (still passable) link that causes the pathfinder to stop before crossing."""
|
||||||
symbol = "i"
|
symbol = "i"
|
||||||
interrupt_path = True
|
interrupt_path = True
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class BlockedMapLink(InvisibleSmartMapLink):
|
class BlockedMapLink(InvisibleSmartMapLink):
|
||||||
|
|
@ -1154,7 +1156,7 @@ class BlockedMapLink(InvisibleSmartMapLink):
|
||||||
symbol = 'b'
|
symbol = 'b'
|
||||||
weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL,
|
weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL,
|
||||||
's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL}
|
's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL}
|
||||||
prototype = "xyz_exit_prototype"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class RouterMapLink(SmartRerouterMapLink):
|
class RouterMapLink(SmartRerouterMapLink):
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
"""
|
|
||||||
Maprunner
|
|
||||||
|
|
||||||
This is a stand-alone program for baking and preparing grid-maps.
|
|
||||||
|
|
||||||
## Baking
|
|
||||||
|
|
||||||
The Dijkstra algorithm is very powerful for pathfinding, but for very large grids it can be slow
|
|
||||||
to build the initial distance-matrix. As an example, for an extreme case of 10 000 nodes, all
|
|
||||||
connected along all 8 cardinal directions, there are so many possible combinations that it
|
|
||||||
takes about 25 seconds on medium hardware to build the matrix. 40 000 nodes takes about 9 minutes.
|
|
||||||
|
|
||||||
Once the matrix is built, pathfinding across the entire grid is a <0.1s operation however. So as
|
|
||||||
long as the grid doesn't change, it's a good idea to pre-build it. Pre-building like this is
|
|
||||||
often referred to as 'baking' the asset.
|
|
||||||
|
|
||||||
This program will build and run the Dijkstra on a given map and store the result as a
|
|
||||||
serialized binary file in the `mygame/server/.cache/ directory. If it exists, the Map
|
|
||||||
will load this file. If the map changed since it was saved, the file will be automatically
|
|
||||||
be rebuilt.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,35 +1,32 @@
|
||||||
"""
|
"""
|
||||||
Prototypes for building the XYZ-grid into actual game-rooms.
|
Default prototypes for building the XYZ-grid into actual game-rooms.
|
||||||
|
|
||||||
Add this to mygame/conf/settings/settings.py:
|
Add this to mygame/conf/settings/settings.py:
|
||||||
|
|
||||||
PROTOTYPE_MODULES += ['evennia.contrib.xyzgrid.prototypes']
|
PROTOTYPE_MODULES += ['evennia.contrib.xyzgrid.prototypes']
|
||||||
|
|
||||||
|
The prototypes can then be used in mapping prototypes as
|
||||||
|
|
||||||
|
{'prototype_parent': 'xyz_room', ...}
|
||||||
|
|
||||||
|
and/or
|
||||||
|
|
||||||
|
{'prototype_parent': 'xyz_exit', ...}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Note - the XYZRoom/exit parents track the XYZ coordinates automatically
|
# required by the prototype importer
|
||||||
# so we don't need to add custom tags to them here.
|
|
||||||
_ROOM_PARENT = {
|
|
||||||
'prototype_tags': ("xyzroom", ),
|
|
||||||
'typeclass': 'evennia.contrib.xyzgrid.xyzroom.XYZRoom'
|
|
||||||
}
|
|
||||||
|
|
||||||
_EXIT_PARENT = {
|
|
||||||
'prototype_tags': ("xyzexit", ),
|
|
||||||
'typeclass': 'evennia.contrib.xyzgrid.xyzroom.XYZExit'
|
|
||||||
}
|
|
||||||
|
|
||||||
PROTOTYPE_LIST = [
|
PROTOTYPE_LIST = [
|
||||||
{
|
{
|
||||||
'prototype_key': 'xyz_room_prototype',
|
'prototype_key': 'xyz_room',
|
||||||
'prototype_parent': _ROOM_PARENT,
|
'typeclass': 'evennia.contrib.xyzgrid.xyzroom.XYZRoom',
|
||||||
'key': "A non-descript room",
|
'prototype_tags': ("xyzroom", ),
|
||||||
},{
|
'key': "A room",
|
||||||
'prototype_key': 'xyz_transition_room_prototype',
|
'desc': "An empty room."
|
||||||
'prototype_parent': _ROOM_PARENT,
|
}, {
|
||||||
'typeclass': 'evennia.contrib.xyzgrid.xyzroom.XYZMapTransitionRoom',
|
'prototype_key': 'xyz_exit',
|
||||||
},{
|
'prototype_tags': ("xyzexit", ),
|
||||||
'prototype_key': 'xyz_exit_prototype',
|
'typeclass': 'evennia.contrib.xyzgrid.xyzroom.XYZExit',
|
||||||
'prototype_parent': _EXIT_PARENT,
|
'desc': "An exit."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -349,11 +349,11 @@ class _MapTest(TestCase):
|
||||||
map_data = {'map': MAP1, 'zcoord': "map1"}
|
map_data = {'map': MAP1, 'zcoord': "map1"}
|
||||||
map_display = MAP1_DISPLAY
|
map_display = MAP1_DISPLAY
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Set up grid and map"""
|
"""Set up grid and map"""
|
||||||
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
|
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
|
||||||
self.grid.add_maps(self.map_data)
|
self.grid.add_maps(self.map_data)
|
||||||
self.map = self.grid.get(self.map_data['zcoord'])
|
self.map = self.grid.get_map(self.map_data['zcoord'])
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.grid.delete()
|
self.grid.delete()
|
||||||
|
|
@ -1155,7 +1155,7 @@ class TestXYZGrid(TestCase):
|
||||||
|
|
||||||
def test_str_output(self):
|
def test_str_output(self):
|
||||||
"""Check the display_map"""
|
"""Check the display_map"""
|
||||||
xymap = self.grid.get(self.zcoord)
|
xymap = self.grid.get_map(self.zcoord)
|
||||||
stripped_map = "\n".join(line.rstrip() for line in str(xymap).split('\n'))
|
stripped_map = "\n".join(line.rstrip() for line in str(xymap).split('\n'))
|
||||||
self.assertEqual(MAP1_DISPLAY, stripped_map)
|
self.assertEqual(MAP1_DISPLAY, stripped_map)
|
||||||
|
|
||||||
|
|
@ -1215,7 +1215,7 @@ class TestXYZGridTransition(TestCase):
|
||||||
test shortest-path calculations throughout the grid.
|
test shortest-path calculations throughout the grid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
directions, _ = self.grid.get('map12a').get_shortest_path(startcoord, endcoord)
|
directions, _ = self.grid.get_map('map12a').get_shortest_path(startcoord, endcoord)
|
||||||
self.assertEqual(expected_directions, tuple(directions))
|
self.assertEqual(expected_directions, tuple(directions))
|
||||||
|
|
||||||
def test_spawn(self):
|
def test_spawn(self):
|
||||||
|
|
@ -1236,3 +1236,41 @@ class TestXYZGridTransition(TestCase):
|
||||||
# make sure exits traverse the maps
|
# make sure exits traverse the maps
|
||||||
self.assertEqual(east_exit.db_destination, room2)
|
self.assertEqual(east_exit.db_destination, room2)
|
||||||
self.assertEqual(west_exit.db_destination, room1)
|
self.assertEqual(west_exit.db_destination, room1)
|
||||||
|
|
||||||
|
class TestBuildExampleGrid(TestCase):
|
||||||
|
"""
|
||||||
|
Test building the map_example
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
# build and populate grid
|
||||||
|
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.grid.delete()
|
||||||
|
|
||||||
|
def test_build(self):
|
||||||
|
"""
|
||||||
|
Build the map example.
|
||||||
|
|
||||||
|
"""
|
||||||
|
mapdatas = self.grid.maps_from_module("evennia.contrib.xyzgrid.map_example")
|
||||||
|
self.assertEqual(len(mapdatas), 2)
|
||||||
|
|
||||||
|
self.grid.add_maps(*mapdatas)
|
||||||
|
self.grid.spawn()
|
||||||
|
|
||||||
|
# testing
|
||||||
|
room1a = xyzroom.XYZRoom.objects.get_xyz(xyz=(3, 0, 'the large tree'))
|
||||||
|
room1b = xyzroom.XYZRoom.objects.get_xyz(xyz=(10, 9, 'the large tree'))
|
||||||
|
room2a = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, 'small cave'))
|
||||||
|
room2b = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 3, 'small cave'))
|
||||||
|
|
||||||
|
self.assertEqual(room1a.key, "Dungeon Entrance")
|
||||||
|
self.assertTrue(room1a.desc.startswith("To the west"))
|
||||||
|
self.assertEqual(room1b.key, "A gorgeous view")
|
||||||
|
self.assertTrue(room1b.desc.startswith("The view from here is breathtaking."))
|
||||||
|
self.assertEqual(room2a.key, "The entrance")
|
||||||
|
self.assertTrue(room2a.desc.startswith("This is the entrance to"))
|
||||||
|
self.assertEqual(room2b.key, "North-west corner of the atrium")
|
||||||
|
self.assertTrue(room2b.desc.startswith("Sunlight sifts down"))
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class MapError(RuntimeError):
|
||||||
prefix = ""
|
prefix = ""
|
||||||
if node_or_link:
|
if node_or_link:
|
||||||
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
||||||
f"at XY=({node_or_link.X:g},{node_or_link.Y:g}) ")
|
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) ")
|
||||||
self.node_or_link = node_or_link
|
self.node_or_link = node_or_link
|
||||||
self.message = f"{prefix}{error}"
|
self.message = f"{prefix}{error}"
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ as up and down. These are indicated in code as 'n', 'ne', 'e', 'se', 's', 'sw',
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# in module passed to 'Map' class. It will either a dict
|
# in module passed to 'Map' class
|
||||||
# MAP_DATA with keys 'map' and (optionally) 'legend', or
|
|
||||||
# the MAP/LEGEND variables directly.
|
|
||||||
|
|
||||||
MAP = r'''
|
MAP = r'''
|
||||||
1
|
1
|
||||||
|
|
@ -47,10 +45,11 @@ as up and down. These are indicated in code as 'n', 'ne', 'e', 'se', 's', 'sw',
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
LEGEND = {'#': xyzgrid.MapNode, '|': xyzgrid.NSMapLink,...}
|
LEGEND = {'#': xyzgrid.MapNode, '|': xyzgrid.NSMapLink,...}
|
||||||
|
|
||||||
# optional, for more control
|
# read by parser if XYMAP_DATA_LIST doesn't exist
|
||||||
MAP_DATA = {
|
XYMAP_DATA = {
|
||||||
"map": MAP,
|
"map": MAP,
|
||||||
"legend": LEGEND,
|
"legend": LEGEND,
|
||||||
"zcoord": "City of Foo",
|
"zcoord": "City of Foo",
|
||||||
|
|
@ -62,6 +61,11 @@ as up and down. These are indicated in code as 'n', 'ne', 'e', 'se', 's', 'sw',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# will be parsed first, allows for multiple map-data dicts from one module
|
||||||
|
XYMAP_DATA_LIST = [
|
||||||
|
XYMAP_DATA
|
||||||
|
]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The two `+` signs in the upper/lower left corners are required and marks the edge of the map area.
|
The two `+` signs in the upper/lower left corners are required and marks the edge of the map area.
|
||||||
|
|
@ -102,7 +106,7 @@ except ImportError as err:
|
||||||
f"{err}\nThe XYZgrid contrib requires "
|
f"{err}\nThe XYZgrid contrib requires "
|
||||||
"the SciPy package. Install with `pip install scipy'.")
|
"the SciPy package. Install with `pip install scipy'.")
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils.utils import variable_from_module, mod_import
|
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.prototypes import prototypes as protlib
|
from evennia.prototypes import prototypes as protlib
|
||||||
|
|
||||||
|
|
@ -140,7 +144,6 @@ DEFAULT_LEGEND = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# Map parser implementation
|
# Map parser implementation
|
||||||
|
|
||||||
|
|
@ -309,13 +312,18 @@ class XYMap:
|
||||||
else:
|
else:
|
||||||
# read from contents of module
|
# read from contents of module
|
||||||
mod = mod_import(map_module_or_dict)
|
mod = mod_import(map_module_or_dict)
|
||||||
mapdata = variable_from_module(mod, "MAP_DATA")
|
mapdata_list = variable_from_module(mod, "XYMAP_DATA_LIST")
|
||||||
|
if mapdata_list and self.Z:
|
||||||
|
# use the stored Z value to figure out which map data we want
|
||||||
|
mapping = {mapdata.get("zcoord") for mapdata in mapdata_list}
|
||||||
|
mapdata = mapping.get(self.Z, {})
|
||||||
|
|
||||||
if not mapdata:
|
if not mapdata:
|
||||||
# try to read mapdata directly from global variables
|
mapdata = variable_from_module(mod, "XYMAP_DATA")
|
||||||
mapdata['zcoord'] = variable_from_module(mod, "ZCOORD", default=self.name)
|
|
||||||
mapdata['map'] = variable_from_module(mod, "MAP")
|
if not mapdata:
|
||||||
mapdata['legend'] = variable_from_module(mod, "LEGEND", default=DEFAULT_LEGEND)
|
raise MapError("No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
||||||
mapdata['prototypes'] = variable_from_module(mod, "PROTOTYPES", default={})
|
f"{map_module_or_dict}.")
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
|
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
|
||||||
|
|
@ -330,10 +338,9 @@ class XYMap:
|
||||||
"`.display_symbol` property to change how it is "
|
"`.display_symbol` property to change how it is "
|
||||||
"displayed.")
|
"displayed.")
|
||||||
if 'map' not in mapdata or not mapdata['map']:
|
if 'map' not in mapdata or not mapdata['map']:
|
||||||
raise MapError("No map found. Add 'map' key to map-data (MAP_DATA) dict or "
|
raise MapError("No map found. Add 'map' key to map-data dict.")
|
||||||
"add variable MAP to a module passed into the parser.")
|
|
||||||
for key, prototype in mapdata.get('prototypes', {}).items():
|
for key, prototype in mapdata.get('prototypes', {}).items():
|
||||||
if not is_iter(key) and (2 <= len(key) <= 3):
|
if not (is_iter(key) and (2 <= len(key) <= 3)):
|
||||||
raise MapError(f"Prototype override key {key} is malformed: It must be a "
|
raise MapError(f"Prototype override key {key} is malformed: It must be a "
|
||||||
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
||||||
"where direction is a supported direction string ('n', 'ne', etc).")
|
"where direction is a supported direction string ('n', 'ne', etc).")
|
||||||
|
|
@ -491,10 +498,13 @@ class XYMap:
|
||||||
for node in node_index_map.values():
|
for node in node_index_map.values():
|
||||||
node_coord = (node.X, node.Y)
|
node_coord = (node.X, node.Y)
|
||||||
# load prototype from override, or use default
|
# load prototype from override, or use default
|
||||||
node.prototype = self.prototypes.get(node_coord, node.prototype)
|
node.prototype = self.prototypes.get(
|
||||||
|
node_coord, self.prototypes.get(('*', '*'), node.prototype))
|
||||||
# do the same for links (x, y, direction) coords
|
# do the same for links (x, y, direction) coords
|
||||||
for direction, maplink in node.first_links.items():
|
for direction, maplink in node.first_links.items():
|
||||||
maplink.prototype = self.prototypes.get(node_coord + (direction,), maplink.prototype)
|
maplink.prototype = self.prototypes.get(
|
||||||
|
node_coord + (direction,),
|
||||||
|
self.prototypes.get(('*', '*', '*'), maplink.prototype))
|
||||||
|
|
||||||
# store
|
# store
|
||||||
self.display_map = display_map
|
self.display_map = display_map
|
||||||
|
|
@ -547,13 +557,16 @@ class XYMap:
|
||||||
points, xmin, xmax, ymin, ymax = _scan_neighbors(center_node, [], dist=dist)
|
points, xmin, xmax, ymin, ymax = _scan_neighbors(center_node, [], dist=dist)
|
||||||
return list(set(points)), xmin, xmax, ymin, ymax
|
return list(set(points)), xmin, xmax, ymin, ymax
|
||||||
|
|
||||||
def calculate_path_matrix(self):
|
def calculate_path_matrix(self, force=False):
|
||||||
"""
|
"""
|
||||||
Solve the pathfinding problem using Dijkstra's algorithm. This will try to
|
Solve the pathfinding problem using Dijkstra's algorithm. This will try to
|
||||||
load the solution from disk if possible.
|
load the solution from disk if possible.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force (bool, optional): If the cache should always be rebuilt.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.pathfinder_baked_filename and isfile(self.pathfinder_baked_filename):
|
if not force and self.pathfinder_baked_filename and isfile(self.pathfinder_baked_filename):
|
||||||
# check if the solution for this grid was already solved previously.
|
# check if the solution for this grid was already solved previously.
|
||||||
|
|
||||||
mapstr, dist_matrix, pathfinding_routes = "", None, None
|
mapstr, dist_matrix, pathfinding_routes = "", None, None
|
||||||
|
|
@ -569,7 +582,6 @@ class XYMap:
|
||||||
# we can re-use the stored data!
|
# we can re-use the stored data!
|
||||||
self.dist_matrix = dist_matrix
|
self.dist_matrix = dist_matrix
|
||||||
self.pathfinding_routes = pathfinding_routes
|
self.pathfinding_routes = pathfinding_routes
|
||||||
return
|
|
||||||
|
|
||||||
# build a matrix representing the map graph, with 0s as impassable areas
|
# build a matrix representing the map graph, with 0s as impassable areas
|
||||||
|
|
||||||
|
|
@ -615,7 +627,7 @@ class XYMap:
|
||||||
wildcard = '*'
|
wildcard = '*'
|
||||||
spawned = []
|
spawned = []
|
||||||
|
|
||||||
for node in self.node_index_map.values():
|
for node in sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X)):
|
||||||
if (x in (wildcard, node.X)) and (y in (wildcard, node.Y)):
|
if (x in (wildcard, node.X)) and (y in (wildcard, node.Y)):
|
||||||
node.spawn()
|
node.spawn()
|
||||||
spawned.append(node)
|
spawned.append(node)
|
||||||
|
|
@ -643,7 +655,7 @@ class XYMap:
|
||||||
wildcard = '*'
|
wildcard = '*'
|
||||||
|
|
||||||
if not nodes:
|
if not nodes:
|
||||||
nodes = self.node_index_map.values()
|
nodes = sorted(self.node_index_map.values(), key=lambda n: (n.Z, n.Y, n.X))
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if (x in (wildcard, node.X)) and (y in (wildcard, node.Y)):
|
if (x in (wildcard, node.X)) and (y in (wildcard, node.Y)):
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ The grid has three main functions:
|
||||||
"""
|
"""
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
from evennia.utils.utils import variable_from_module, make_iter
|
||||||
from .xymap import XYMap
|
from .xymap import XYMap
|
||||||
from .xyzroom import XYZRoom
|
from .xyzroom import XYZRoom, XYZExit
|
||||||
|
|
||||||
|
|
||||||
class XYZGrid(DefaultScript):
|
class XYZGrid(DefaultScript):
|
||||||
|
|
@ -40,7 +41,7 @@ class XYZGrid(DefaultScript):
|
||||||
self.reload()
|
self.reload()
|
||||||
return self.ndb.grid
|
return self.ndb.grid
|
||||||
|
|
||||||
def get(self, zcoord):
|
def get_map(self, zcoord):
|
||||||
"""
|
"""
|
||||||
Get a specific xymap.
|
Get a specific xymap.
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ class XYZGrid(DefaultScript):
|
||||||
"""
|
"""
|
||||||
return self.grid.get(zcoord)
|
return self.grid.get(zcoord)
|
||||||
|
|
||||||
def all(self):
|
def all_maps(self):
|
||||||
"""
|
"""
|
||||||
Get all xymaps stored in the grid.
|
Get all xymaps stored in the grid.
|
||||||
|
|
||||||
|
|
@ -63,20 +64,55 @@ class XYZGrid(DefaultScript):
|
||||||
"""
|
"""
|
||||||
return self.grid
|
return self.grid
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
logger.log_info(f"|grid| {msg}")
|
||||||
|
|
||||||
|
def get_room(xyz, **kwargs):
|
||||||
|
"""
|
||||||
|
Get room object from XYZ coordinate.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xyz (tuple): X,Y,Z coordinate of room to fetch.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
XYZRoom: The found room.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
XYZRoom.DoesNotExist: If room is not found.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This assumes the room was previously built.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return XYZRoom.objects.get_xyz(xyz=xyz, **kwargs)
|
||||||
|
|
||||||
|
def get_exit(xyz, name='north', **kwargs):
|
||||||
|
"""
|
||||||
|
Get exit object at coordinate.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xyz (tuple): X,Y,Z coordinate of the room the
|
||||||
|
exit leads out of.
|
||||||
|
name (str): The full name of the exit, e.g. 'north' or 'northwest'.
|
||||||
|
|
||||||
|
"""
|
||||||
|
kwargs['db_key'] = name
|
||||||
|
return XYZExit.objects.get_xyz_exit(xyz=xyz, **kwargs)
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
"""
|
"""
|
||||||
Reload and rebuild the grid. This is done on a server reload and is also necessary if adding
|
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.
|
a new map since this may introduce new between-map traversals.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.log_info("[grid] (Re)loading grid ...")
|
self.log("(Re)loading grid ...")
|
||||||
self.ndb.grid = {}
|
self.ndb.grid = {}
|
||||||
nmaps = 0
|
nmaps = 0
|
||||||
# generate all Maps - this will also initialize their components
|
# generate all Maps - this will also initialize their components
|
||||||
# and bake any pathfinding paths (or load from disk-cache)
|
# and bake any pathfinding paths (or load from disk-cache)
|
||||||
for zcoord, mapdata in self.db.map_data.items():
|
for zcoord, mapdata in self.db.map_data.items():
|
||||||
|
|
||||||
logger.log_info(f"[grid] Loading map '{zcoord}'...")
|
self.log(f"Loading map '{zcoord}'...")
|
||||||
xymap = XYMap(dict(mapdata), Z=zcoord, xyzgrid=self)
|
xymap = XYMap(dict(mapdata), Z=zcoord, xyzgrid=self)
|
||||||
xymap.parse()
|
xymap.parse()
|
||||||
xymap.calculate_path_matrix()
|
xymap.calculate_path_matrix()
|
||||||
|
|
@ -84,7 +120,7 @@ class XYZGrid(DefaultScript):
|
||||||
nmaps += 1
|
nmaps += 1
|
||||||
|
|
||||||
# store
|
# store
|
||||||
logger.log_info(f"[grid] Loaded and linked {nmaps} map(s).")
|
self.log(f"Loaded and linked {nmaps} map(s).")
|
||||||
|
|
||||||
def at_init(self):
|
def at_init(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -94,6 +130,27 @@ class XYZGrid(DefaultScript):
|
||||||
"""
|
"""
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
|
def maps_from_module(self, module):
|
||||||
|
"""
|
||||||
|
Load map data from module. The loader will look for a dict XYMAP_DATA or a list of
|
||||||
|
XYMAP_DATA_LIST (a list of XYMAP_DATA dicts). Each XYMAP_DATA dict should contain
|
||||||
|
`{"xymap": mapstring, "zcoord": mapname/zcoord, "legend": dict, "prototypes": dict}`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module (module or str): A module or python-path to a module containing
|
||||||
|
map data as either `XYMAP_DATA` or `XYMAP_DATA_LIST` variables.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of zero, one or more xy-map data dicts loaded from the module.
|
||||||
|
|
||||||
|
"""
|
||||||
|
map_data_list = variable_from_module(module, "XYMAP_DATA_LIST")
|
||||||
|
if not map_data_list:
|
||||||
|
map_data_list = variable_from_module(module, "XYMAP_DATA")
|
||||||
|
if map_data_list:
|
||||||
|
map_data_list = make_iter(map_data_list)
|
||||||
|
return map_data_list
|
||||||
|
|
||||||
def add_maps(self, *mapdatas):
|
def add_maps(self, *mapdatas):
|
||||||
"""
|
"""
|
||||||
Add map or maps to the grid.
|
Add map or maps to the grid.
|
||||||
|
|
@ -176,12 +233,12 @@ class XYZGrid(DefaultScript):
|
||||||
|
|
||||||
# first build all nodes/rooms
|
# first build all nodes/rooms
|
||||||
for zcoord, xymap in xymaps.items():
|
for zcoord, xymap in xymaps.items():
|
||||||
logger.log_info(f"[grid] spawning/updating nodes for {zcoord} ...")
|
self.log(f"spawning/updating nodes for {zcoord} ...")
|
||||||
xymap.spawn_nodes(xy=(x, y))
|
xymap.spawn_nodes(xy=(x, y))
|
||||||
|
|
||||||
# next build all links between nodes (including between maps)
|
# next build all links between nodes (including between maps)
|
||||||
for zcoord, xymap in xymaps.items():
|
for zcoord, xymap in xymaps.items():
|
||||||
logger.log_info(f"[grid] spawning/updating links for {zcoord} ...")
|
self.log(f"spawning/updating links for {zcoord} ...")
|
||||||
xymap.spawn_links(xy=(x, y), directions=directions)
|
xymap.spawn_links(xy=(x, y), directions=directions)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ used as stand-alone XYZ-coordinate-aware rooms.
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from evennia.objects.objects import DefaultRoom, DefaultExit
|
from evennia.objects.objects import DefaultRoom, DefaultExit
|
||||||
from evennia.objects.manager import ObjectManager
|
from evennia.objects.manager import ObjectManager
|
||||||
from evennia.utils.utils import inherits_from
|
|
||||||
|
|
||||||
# name of all tag categories. Note that the Z-coordinate is
|
# name of all tag categories. Note that the Z-coordinate is
|
||||||
# the `map_name` of the XYZgrid
|
# the `map_name` of the XYZgrid
|
||||||
|
|
@ -50,8 +49,6 @@ class XYZManager(ObjectManager):
|
||||||
x, y, z = xyz
|
x, y, z = xyz
|
||||||
wildcard = '*'
|
wildcard = '*'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self
|
self
|
||||||
.filter_family(**kwargs)
|
.filter_family(**kwargs)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue