mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 17:56:32 +01:00
More fixes to xyzmaps. Add goto
This commit is contained in:
parent
de66313ec9
commit
5edda10e81
6 changed files with 144 additions and 38 deletions
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue