More fixes to xyzmaps. Add goto

This commit is contained in:
Griatch 2021-07-13 00:52:53 +02:00
parent de66313ec9
commit 5edda10e81
6 changed files with 144 additions and 38 deletions

View file

@ -7,38 +7,15 @@ the commands with XYZ-aware equivalents.
""" """
from django.conf import settings
from evennia import InterruptCommand from evennia import InterruptCommand
from evennia import default_cmds, CmdSet from evennia import default_cmds, CmdSet
from evennia.commands.default import building, general from evennia.commands.default import building
from evennia.contrib.xyzgrid.xyzroom import XYZRoom from evennia.contrib.xyzgrid.xyzroom import XYZRoom
from evennia.utils.utils import inherits_from from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid
from evennia.utils.utils import list_to_string, class_from_module, make_iter
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
class CmdXYZLook(general.CmdLook):
character = '@'
visual_range = 2
map_mode = 'nodes' # or 'scan'
def func(self):
"""
Add xymap display before the normal look command.
"""
location = self.caller.location
if inherits_from(location, XYZRoom):
xyz = location.xyz
xymap = location.xyzgrid.get_map(xyz[2])
map_display = xymap.get_visual_range(
(xyz[0], xyz[1]),
dist=self.visual_range,
mode=self.map_mode)
maxw = min(xymap.max_x, self.client_width())
sep = "~" * maxw
map_display = f"|x{sep}|n\n{map_display}\n|x{sep}"
self.msg((map_display, {"type": "xymap"}), options=None)
# now run the normal look command
super().func()
class CmdXYZTeleport(building.CmdTeleport): class CmdXYZTeleport(building.CmdTeleport):
@ -184,6 +161,106 @@ class CmdXYZOpen(building.CmdOpen):
self.exit_typeclass = self.lhs_objs[0]["option"] self.exit_typeclass = self.lhs_objs[0]["option"]
class CmdGoto(COMMAND_DEFAULT_CLASS):
"""
Go to a named location in this area.
Usage:
goto <location> - get path and start walking
path <location> - just check the path
goto - abort current goto
path - show current path
This will find the shortest route to a location in your current area and
start automatically walk you there. Builders can also specify a specific grid
coordinate (X,Y).
"""
key = "goto"
aliases = "path"
help_category = "General"
locks = "cmd:all()"
def _search_by_xyz(self, inp, xyz_start):
inp = inp.strip("()")
X, Y = inp.split(",", 2)
Z = xyz_start[2]
# search by coordinate
X, Y, Z = str(X).strip(), str(Y).strip(), str(Z).strip()
try:
return XYZRoom.objects.get_xyz(xyz=(X, Y, Z))
except XYZRoom.DoesNotExist:
self.caller.msg(f"Could not find a room at ({X},{Y}) (Z={Z}).")
return None
def _search_by_key_and_alias(self, inp, xyz_start):
Z = xyz_start[2]
candidates = list(XYZRoom.objects.filter_xyz(xyz=('*', '*', Z)))
return self.caller.search(inp, candidates=candidates)
def func(self):
"""
Implement command
"""
caller = self.caller
current_target, *current_path = make_iter(caller.ndb.xy_current_goto)
goto_mode = self.cmdname == 'goto'
if not self.args:
if current_target:
if goto_mode:
caller.ndb.xy_current_goto_target = None
caller.msg("Aborted goto.")
else:
caller.msg(f"Remaining steps: {list_to_string(current_path)}")
else:
caller.msg("Usage: goto <location>")
return
xyzgrid = get_xyzgrid()
try:
xyz_start = caller.location.xyz
except AttributeError:
self.caller.msg("Cannot path-find since the current location is not on the grid.")
return
allow_xyz_query = caller.locks.check_lockstring(caller, "perm(Builder)")
if allow_xyz_query and all(char in self.args for char in ("(", ")", ",")):
# search by (X,Y)
target = self._search_by_xyz(self.args, xyz_start)
if not target:
return
else:
# search by normal key/alias
target = self._search_by_key_and_alias(self.args, xyz_start)
if not target:
return
try:
xyz_end = target.xyz
except AttributeError:
self.caller.msg("Target location is not on the grid and cannot be auto-walked to.")
return
xymap = xyzgrid.get_map(xyz_start[2])
# we only need the xy coords once we have the map
xy_start = xyz_start[:2]
xy_end = xyz_end[:2]
shortest_path, _ = xymap.get_shortest_path(xy_start, xy_end)
caller.msg(f"There are {len(shortest_path)} steps to {target.get_display_name(caller)}: "
f"|w{list_to_string(shortest_path, endsep='|nand finally|w')}|n")
# store for use by the return_appearance hook on the XYZRoom
caller.ndb.xy_current_goto = (xy_end, shortest_path)
if self.cmdname == "goto":
# start actually walking right away
self.msg("Walking ... eventually")
pass
class XYZGridCmdSet(CmdSet): class XYZGridCmdSet(CmdSet):
""" """
Cmdset for easily adding the above cmds to the character cmdset. Cmdset for easily adding the above cmds to the character cmdset.
@ -194,3 +271,4 @@ class XYZGridCmdSet(CmdSet):
def at_cmdset_creation(self): def at_cmdset_creation(self):
self.add(CmdXYZTeleport()) self.add(CmdXYZTeleport())
self.add(CmdXYZOpen()) self.add(CmdXYZOpen())
self.add(CmdGoto())

View file

@ -320,7 +320,7 @@ def _option_delete(*suboptions):
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."
"\nThis can't be undone. Are you sure you want to continue? Y/[N]?") "\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
if repl.lower() not in ('yes', 'y'): if repl.lower() not in ('yes', 'y'):
print("Aborted.") print("Aborted.")
return return
@ -342,7 +342,7 @@ def _option_delete(*suboptions):
repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n" repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
"rooms/exits!" "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."
"\nThis can't be undone. Are you sure you want to continue? Y/[N]?") "\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
if repl.lower() not in ('yes', 'y'): if repl.lower() not in ('yes', 'y'):
print("Aborted.") print("Aborted.")
return return
@ -378,3 +378,6 @@ def xyzcommand(*args):
_option_initpath(*suboptions) _option_initpath(*suboptions)
elif option == 'delete': elif option == 'delete':
_option_delete(*suboptions) _option_delete(*suboptions)
else:
print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.")

View file

@ -326,8 +326,9 @@ class MapNode:
maplinks = {} maplinks = {}
for direction, link in self.first_links.items(): for direction, link in self.first_links.items():
key, *aliases = ( key, *aliases = (
make_iter(link.spawn_aliases) link.spawn_aliases.get(direction, ('unknown',))
if link.spawn_aliases if link.spawn_aliases
else self.direction_spawn_defaults.get(direction, ('unknown',)) else self.direction_spawn_defaults.get(direction, ('unknown',))
) )
@ -368,7 +369,7 @@ class MapNode:
if err: if err:
raise RuntimeError(err) raise RuntimeError(err)
linkobjs[key.lower()] = exi linkobjs[key.lower()] = exi
self.log(f" spawning/updating exit xyz={xyz}, direction={direction}") self.log(f" spawning/updating exit xyz={xyz}, direction={key}")
# apply prototypes to catch any changes # apply prototypes to catch any changes
for key, linkobj in linkobjs.items(): for key, linkobj in linkobjs.items():
@ -1175,7 +1176,7 @@ 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}
spawn_aliases = {direction: ("down", "do") for direction in direction_aliases} spawn_aliases = {direction: ("down", "d") for direction in direction_aliases}
prototype = "xyz_exit" prototype = "xyz_exit"

View file

@ -772,7 +772,8 @@ class XYMap:
def get_visual_range(self, xy, dist=2, mode='nodes', def get_visual_range(self, xy, dist=2, mode='nodes',
character='@', character='@',
target=None, target_path_style="|y{display_symbol}|n", target=None,
target_path_style="|y{display_symbol}|n",
max_size=None, max_size=None,
indent=0, indent=0,
return_str=True): return_str=True):
@ -797,7 +798,7 @@ class XYMap:
(or the beginning of said path, if outside of visual range) will be (or the beginning of said path, if outside of visual range) will be
marked according to `target_path_style`. marked according to `target_path_style`.
target_path_style (str or callable, optional): This is use for marking the path target_path_style (str or callable, optional): This is use for marking the path
found when `path_to_coord` is given. If a string, it accepts a formatting marker found when `target` is given. If a string, it accepts a formatting marker
`display_symbol` which will be filled with the `display_symbol` of each node/link `display_symbol` which will be filled with the `display_symbol` of each node/link
the path passes through. This allows e.g. to color the path. If a callable, this the path passes through. This allows e.g. to color the path. If a callable, this
will receive the MapNode or MapLink object for every step of the path and and will receive the MapNode or MapLink object for every step of the path and and

View file

@ -8,9 +8,9 @@ used as stand-alone XYZ-coordinate-aware rooms.
""" """
from django.db.models import Q from django.db.models import Q
from django.conf import settings
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 make_iter
# 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
@ -328,6 +328,25 @@ class XYZRoom(DefaultRoom):
return DefaultRoom.create(key, account=account, tags=tags, typeclass=cls, **kwargs) return DefaultRoom.create(key, account=account, tags=tags, typeclass=cls, **kwargs)
def get_display_name(self, looker, **kwargs):
"""
Shows both the #dbref and the xyz coord to staff.
Args:
looker (TypedObject): The object or account that is looking
at/getting inforamtion for this object.
Returns:
name (str): A string containing the name of the object,
including the DBREF and XYZ coord if this user is
privileged to control the room.
"""
if self.locks.check_lockstring(looker, "perm(Builder)"):
x, y, z = self.xyz
return f"{self.name}[#{self.id}({x},{y},{z})]"
return self.name
def return_appearance(self, looker, **kwargs): def return_appearance(self, looker, **kwargs):
""" """
Displays the map in addition to the room description Displays the map in addition to the room description
@ -411,12 +430,16 @@ class XYZRoom(DefaultRoom):
elif map_align == 'c': elif map_align == 'c':
map_indent = max(0, (display_width - map_width) // 2) map_indent = max(0, (display_width - map_width) // 2)
goto_target, *current_path = make_iter(looker.ndb.xy_current_goto)
# get visual range display from map # get visual range display from map
map_display = xymap.get_visual_range( map_display = xymap.get_visual_range(
(xyz[0], xyz[1]), (xyz[0], xyz[1]),
dist=visual_range, dist=visual_range,
mode=map_mode, mode=map_mode,
character=character_symbol, target=goto_target,
target_path_style="|y{display_symbol}|n",
character=f"|g{character_symbol}|n",
max_size=(display_width, None), max_size=(display_width, None),
indent=map_indent indent=map_indent
) )

View file

@ -1260,7 +1260,7 @@ class DbHolder:
_GA(self, _GA(self, "name")).remove(attrname) _GA(self, _GA(self, "name")).remove(attrname)
def get_all(self): def get_all(self):
return _GA(self, _GA(self, "name")).get_all_attributes() return _GA(self, _GA(self, "name")).backend.get_all_attributes()
all = property(get_all) all = property(get_all)