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 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.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
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()
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
class CmdXYZTeleport(building.CmdTeleport):
@ -184,6 +161,106 @@ class CmdXYZOpen(building.CmdOpen):
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):
"""
Cmdset for easily adding the above cmds to the character cmdset.
@ -194,3 +271,4 @@ class XYZGridCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(CmdXYZTeleport())
self.add(CmdXYZOpen())
self.add(CmdGoto())

View file

@ -320,7 +320,7 @@ def _option_delete(*suboptions):
if not suboptions:
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."
"\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'):
print("Aborted.")
return
@ -342,7 +342,7 @@ def _option_delete(*suboptions):
repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
"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]?")
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
if repl.lower() not in ('yes', 'y'):
print("Aborted.")
return
@ -378,3 +378,6 @@ def xyzcommand(*args):
_option_initpath(*suboptions)
elif option == 'delete':
_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 = {}
for direction, link in self.first_links.items():
key, *aliases = (
make_iter(link.spawn_aliases)
link.spawn_aliases.get(direction, ('unknown',))
if link.spawn_aliases
else self.direction_spawn_defaults.get(direction, ('unknown',))
)
@ -368,7 +369,7 @@ class MapNode:
if err:
raise RuntimeError(err)
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
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.
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': 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"

View file

@ -772,7 +772,8 @@ class XYMap:
def get_visual_range(self, xy, dist=2, mode='nodes',
character='@',
target=None, target_path_style="|y{display_symbol}|n",
target=None,
target_path_style="|y{display_symbol}|n",
max_size=None,
indent=0,
return_str=True):
@ -797,7 +798,7 @@ class XYMap:
(or the beginning of said path, if outside of visual range) will be
marked according to `target_path_style`.
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
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

View file

@ -8,9 +8,9 @@ used as stand-alone XYZ-coordinate-aware rooms.
"""
from django.db.models import Q
from django.conf import settings
from evennia.objects.objects import DefaultRoom, DefaultExit
from evennia.objects.manager import ObjectManager
from evennia.utils.utils import make_iter
# name of all tag categories. Note that the Z-coordinate is
# the `map_name` of the XYZgrid
@ -328,6 +328,25 @@ class XYZRoom(DefaultRoom):
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):
"""
Displays the map in addition to the room description
@ -411,12 +430,16 @@ class XYZRoom(DefaultRoom):
elif map_align == 'c':
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
map_display = xymap.get_visual_range(
(xyz[0], xyz[1]),
dist=visual_range,
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),
indent=map_indent
)

View file

@ -1260,7 +1260,7 @@ class DbHolder:
_GA(self, _GA(self, "name")).remove(attrname)
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)