Add xyzgrid support commands
This commit is contained in:
parent
1c06363bbe
commit
9706d14293
9 changed files with 345 additions and 100 deletions
|
|
@ -4,6 +4,7 @@ Building and world design commands
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q, Min, Max
|
from django.db.models import Q, Min, Max
|
||||||
|
from evennia import InterruptCommand
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.locks.lockhandler import LockException
|
from evennia.locks.lockhandler import LockException
|
||||||
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
||||||
|
|
@ -1487,40 +1488,33 @@ class CmdOpen(ObjManipCommand):
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return exit_obj
|
return exit_obj
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
super().parse()
|
||||||
|
self.location = self.caller.location
|
||||||
|
if not self.args or not self.rhs:
|
||||||
|
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
||||||
|
"[,<return exit>[;alias..][:typeclass]]] "
|
||||||
|
"= <destination>")
|
||||||
|
raise InterruptCommand
|
||||||
|
if not self.location:
|
||||||
|
self.caller.msg("You cannot create an exit from a None-location.")
|
||||||
|
raise InterruptCommand
|
||||||
|
self.destination = self.caller.search(self.rhs, global_search=True)
|
||||||
|
if not self.destination:
|
||||||
|
raise InterruptCommand
|
||||||
|
self.exit_name = self.lhs_objs[0]["name"]
|
||||||
|
self.exit_aliases = self.lhs_objs[0]["aliases"]
|
||||||
|
self.exit_typeclass = self.lhs_objs[0]["option"]
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
This is where the processing starts.
|
This is where the processing starts.
|
||||||
Uses the ObjManipCommand.parser() for pre-processing
|
Uses the ObjManipCommand.parser() for pre-processing
|
||||||
as well as the self.create_exit() method.
|
as well as the self.create_exit() method.
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
|
||||||
|
|
||||||
if not self.args or not self.rhs:
|
|
||||||
string = "Usage: open <new exit>[;alias...][:typeclass][,<return exit>[;alias..][:typeclass]]] "
|
|
||||||
string += "= <destination>"
|
|
||||||
caller.msg(string)
|
|
||||||
return
|
|
||||||
|
|
||||||
# We must have a location to open an exit
|
|
||||||
location = caller.location
|
|
||||||
if not location:
|
|
||||||
caller.msg("You cannot create an exit from a None-location.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# obtain needed info from cmdline
|
|
||||||
|
|
||||||
exit_name = self.lhs_objs[0]["name"]
|
|
||||||
exit_aliases = self.lhs_objs[0]["aliases"]
|
|
||||||
exit_typeclass = self.lhs_objs[0]["option"]
|
|
||||||
dest_name = self.rhs
|
|
||||||
|
|
||||||
# first, check so the destination exists.
|
|
||||||
destination = caller.search(dest_name, global_search=True)
|
|
||||||
if not destination:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create exit
|
# Create exit
|
||||||
ok = self.create_exit(exit_name, location, destination, exit_aliases, exit_typeclass)
|
ok = self.create_exit(self.exit_name, self.location, self.destination,
|
||||||
|
self.exit_aliases, self.exit_typeclass)
|
||||||
if not ok:
|
if not ok:
|
||||||
# an error; the exit was not created, so we quit.
|
# an error; the exit was not created, so we quit.
|
||||||
return
|
return
|
||||||
|
|
@ -1529,9 +1523,8 @@ class CmdOpen(ObjManipCommand):
|
||||||
back_exit_name = self.lhs_objs[1]["name"]
|
back_exit_name = self.lhs_objs[1]["name"]
|
||||||
back_exit_aliases = self.lhs_objs[1]["aliases"]
|
back_exit_aliases = self.lhs_objs[1]["aliases"]
|
||||||
back_exit_typeclass = self.lhs_objs[1]["option"]
|
back_exit_typeclass = self.lhs_objs[1]["option"]
|
||||||
self.create_exit(
|
self.create_exit(back_exit_name, self.destination, self.location, back_exit_aliases,
|
||||||
back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass
|
back_exit_typeclass)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _convert_from_string(cmd, strobj):
|
def _convert_from_string(cmd, strobj):
|
||||||
|
|
@ -2981,28 +2974,31 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
locks = "cmd:perm(teleport) or perm(Builder)"
|
locks = "cmd:perm(teleport) or perm(Builder)"
|
||||||
help_category = "Building"
|
help_category = "Building"
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""
|
||||||
|
Breaking out searching here to make this easier to override.
|
||||||
|
|
||||||
|
"""
|
||||||
|
super().parse()
|
||||||
|
self.obj_to_teleport = self.caller
|
||||||
|
self.destination = None
|
||||||
|
if self.lhs:
|
||||||
|
self.obj_to_teleport = self.caller.search(self.lhs, global_search=True)
|
||||||
|
if not self.obj_to_teleport:
|
||||||
|
self.caller.msg("Did not find object to teleport.")
|
||||||
|
raise InterruptCommand
|
||||||
|
if self.rhs:
|
||||||
|
self.destination = self.caller.search(self.rhs, global_search=True)
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Performs the teleport"""
|
"""Performs the teleport"""
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
args = self.args
|
obj_to_teleport = self.obj_to_teleport
|
||||||
lhs, rhs = self.lhs, self.rhs
|
destination = self.destination
|
||||||
switches = self.switches
|
|
||||||
|
|
||||||
# setting switches
|
if "tonone" in self.switches:
|
||||||
tel_quietly = "quiet" in switches
|
|
||||||
to_none = "tonone" in switches
|
|
||||||
to_loc = "loc" in switches
|
|
||||||
|
|
||||||
if to_none:
|
|
||||||
# teleporting to None
|
# teleporting to None
|
||||||
if not args:
|
|
||||||
obj_to_teleport = caller
|
|
||||||
else:
|
|
||||||
obj_to_teleport = caller.search(lhs, global_search=True)
|
|
||||||
if not obj_to_teleport:
|
|
||||||
caller.msg("Did not find object to teleport.")
|
|
||||||
return
|
|
||||||
if obj_to_teleport.has_account:
|
if obj_to_teleport.has_account:
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"Cannot teleport a puppeted object "
|
"Cannot teleport a puppeted object "
|
||||||
|
|
@ -3011,53 +3007,44 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
caller.msg("Teleported %s -> None-location." % obj_to_teleport)
|
caller.msg("Teleported %s -> None-location." % obj_to_teleport)
|
||||||
if obj_to_teleport.location and not tel_quietly:
|
if obj_to_teleport.location and "quiet" not in self.switches:
|
||||||
obj_to_teleport.location.msg_contents(
|
obj_to_teleport.location.msg_contents(
|
||||||
"%s teleported %s into nothingness." % (caller, obj_to_teleport), exclude=caller
|
"%s teleported %s into nothingness." % (caller, obj_to_teleport), exclude=caller
|
||||||
)
|
)
|
||||||
obj_to_teleport.location = None
|
obj_to_teleport.location = None
|
||||||
return
|
return
|
||||||
|
|
||||||
# not teleporting to None location
|
if not self.args:
|
||||||
if not args and not to_none:
|
caller.msg("Usage: teleport[/switches] [<obj> =] <target or (X,Y,Z)>||home")
|
||||||
caller.msg("Usage: teleport[/switches] [<obj> =] <target_loc>||home")
|
|
||||||
return
|
|
||||||
|
|
||||||
if rhs:
|
|
||||||
obj_to_teleport = caller.search(lhs, global_search=True)
|
|
||||||
destination = caller.search(rhs, global_search=True)
|
|
||||||
else:
|
|
||||||
obj_to_teleport = caller
|
|
||||||
destination = caller.search(lhs, global_search=True)
|
|
||||||
if not obj_to_teleport:
|
|
||||||
caller.msg("Did not find object to teleport.")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not destination:
|
if not destination:
|
||||||
caller.msg("Destination not found.")
|
caller.msg("Destination not found.")
|
||||||
return
|
return
|
||||||
if to_loc:
|
|
||||||
|
if "loc" in self.switches:
|
||||||
destination = destination.location
|
destination = destination.location
|
||||||
if not destination:
|
if not destination:
|
||||||
caller.msg("Destination has no location.")
|
caller.msg("Destination has no location.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj_to_teleport == destination:
|
if obj_to_teleport == destination:
|
||||||
caller.msg("You can't teleport an object inside of itself!")
|
caller.msg("You can't teleport an object inside of itself!")
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj_to_teleport == destination.location:
|
if obj_to_teleport == destination.location:
|
||||||
caller.msg("You can't teleport an object inside something it holds!")
|
caller.msg("You can't teleport an object inside something it holds!")
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj_to_teleport.location and obj_to_teleport.location == destination:
|
if obj_to_teleport.location and obj_to_teleport.location == destination:
|
||||||
caller.msg("%s is already at %s." % (obj_to_teleport, destination))
|
caller.msg("%s is already at %s." % (obj_to_teleport, destination))
|
||||||
return
|
return
|
||||||
use_destination = True
|
|
||||||
if "intoexit" in self.switches:
|
|
||||||
use_destination = False
|
|
||||||
|
|
||||||
# try the teleport
|
# try the teleport
|
||||||
if obj_to_teleport.move_to(
|
if obj_to_teleport.move_to(
|
||||||
destination, quiet=tel_quietly, emit_to_obj=caller, use_destination=use_destination
|
destination, quiet="quiet" in self.switches,
|
||||||
):
|
emit_to_obj=caller, use_destination="intoexit" not in self.switches):
|
||||||
|
|
||||||
if obj_to_teleport == caller:
|
if obj_to_teleport == caller:
|
||||||
caller.msg("Teleported to %s." % destination)
|
caller.msg("Teleported to %s." % destination)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
187
evennia/contrib/xyzgrid/commands.py
Normal file
187
evennia/contrib/xyzgrid/commands.py
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
XYZ-aware commands
|
||||||
|
|
||||||
|
Just add the XYZGridCmdSet to the default character cmdset to override
|
||||||
|
the commands with XYZ-aware equivalents.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia import InterruptCommand
|
||||||
|
from evennia import MuxCommand, CmdSet
|
||||||
|
from evennia.commands.default import building, general
|
||||||
|
from evennia.contrib.xyzgrid.xyzroom import XYZRoom
|
||||||
|
from evennia.utils.utils import inherits_from
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
teleport object to another location
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
tel/switch [<object> to||=] <target location>
|
||||||
|
tel/switch [<object> to||=] (X,Y[,Z])
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
tel Limbo
|
||||||
|
tel/quiet box = Limbo
|
||||||
|
tel/tonone box
|
||||||
|
tel (3, 3, the small cave)
|
||||||
|
tel (4, 1) # on the same map
|
||||||
|
|
||||||
|
Switches:
|
||||||
|
quiet - don't echo leave/arrive messages to the source/target
|
||||||
|
locations for the move.
|
||||||
|
intoexit - if target is an exit, teleport INTO
|
||||||
|
the exit object instead of to its destination
|
||||||
|
tonone - if set, teleport the object to a None-location. If this
|
||||||
|
switch is set, <target location> is ignored.
|
||||||
|
Note that the only way to retrieve
|
||||||
|
an object from a None location is by direct #dbref
|
||||||
|
reference. A puppeted object cannot be moved to None.
|
||||||
|
loc - teleport object to the target's location instead of its contents
|
||||||
|
|
||||||
|
Teleports an object somewhere. If no object is given, you yourself are
|
||||||
|
teleported to the target location. If (X,Y) or (X,Y,Z) coordinates
|
||||||
|
are given, the target is a location on the XYZGrid.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
MuxCommand.parse(self)
|
||||||
|
self.obj_to_teleport = self.caller
|
||||||
|
self.destination = None
|
||||||
|
rhs = self.rhs
|
||||||
|
if self.lhs:
|
||||||
|
self.obj_to_teleport = self.caller.search(self.lhs, global_search=True)
|
||||||
|
if not self.obj_to_teleport:
|
||||||
|
self.caller.msg("Did not find object to teleport.")
|
||||||
|
raise InterruptCommand
|
||||||
|
if rhs:
|
||||||
|
if all(char in rhs for char in ("(", ")", ",")):
|
||||||
|
# search by (X,Y) or (X,Y,Z)
|
||||||
|
X, Y, *Z = rhs.split(",", 2)
|
||||||
|
if Z:
|
||||||
|
# Z was specified
|
||||||
|
Z = Z[0]
|
||||||
|
else:
|
||||||
|
# use current location's Z, if it exists
|
||||||
|
try:
|
||||||
|
xyz = self.caller.xyz
|
||||||
|
except AttributeError:
|
||||||
|
self.caller.msg("Z-coordinate is also required since you are not currently "
|
||||||
|
"in a room with a Z coordinate of its own.")
|
||||||
|
raise InterruptCommand
|
||||||
|
else:
|
||||||
|
Z = xyz[2]
|
||||||
|
# search by coordinate
|
||||||
|
X, Y, Z = str(X).strip(), str(Y).strip(), str(Z).strip()
|
||||||
|
try:
|
||||||
|
self.obj_to_teleport = XYZRoom.objects.get_xyz(xyz=(X, Y, Z))
|
||||||
|
except XYZRoom.DoesNotExist:
|
||||||
|
self.caller.msg("Found no target XYZRoom at ({X},{Y},{Y}).")
|
||||||
|
raise InterruptCommand
|
||||||
|
else:
|
||||||
|
# regular search
|
||||||
|
self.destination = self.caller.search(rhs, global_search=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdXYZOpen(building.CmdOpen):
|
||||||
|
"""
|
||||||
|
open a new exit from the current room
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
open <new exit>[;alias;..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = <destination>
|
||||||
|
open <new exit>[;alias;..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = (X,Y,Z)
|
||||||
|
|
||||||
|
Handles the creation of exits. If a destination is given, the exit
|
||||||
|
will point there. The destination can also be given as an (X,Y,Z) coordinate on the
|
||||||
|
XYZGrid - this command is used to link non-grid rooms to the grid and vice-versa.
|
||||||
|
|
||||||
|
The <return exit> argument sets up an exit at the destination leading back to the current room.
|
||||||
|
Apart from (X,Y,Z) coordinate, destination name can be given both as a #dbref and a name, if
|
||||||
|
that name is globally unique.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
open kitchen = Kitchen
|
||||||
|
open north, south = Town Center
|
||||||
|
open cave mouth;cave = (3, 4, the small cave)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
building.ObjManipCommand.parse(self)
|
||||||
|
|
||||||
|
self.location = self.caller.location
|
||||||
|
if not self.args or not self.rhs:
|
||||||
|
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
||||||
|
"[,<return exit>[;alias..][:typeclass]]] "
|
||||||
|
"= <destination>")
|
||||||
|
raise InterruptCommand
|
||||||
|
if not self.location:
|
||||||
|
self.caller.msg("You cannot create an exit from a None-location.")
|
||||||
|
raise InterruptCommand
|
||||||
|
|
||||||
|
if all(char in self.rhs for char in ("(", ")", ",")):
|
||||||
|
# search by (X,Y) or (X,Y,Z)
|
||||||
|
X, Y, *Z = self.rhs.split(",", 2)
|
||||||
|
if not Z:
|
||||||
|
self.caller.msg("A full (X,Y,Z) coordinate must be given for the destination.")
|
||||||
|
raise InterruptCommand
|
||||||
|
Z = Z[0]
|
||||||
|
# search by coordinate
|
||||||
|
X, Y, Z = str(X).strip(), str(Y).strip(), str(Z).strip()
|
||||||
|
try:
|
||||||
|
self.destination = XYZRoom.objects.get_xyz(xyz=(X, Y, Z))
|
||||||
|
except XYZRoom.DoesNotExist:
|
||||||
|
self.caller.msg("Found no target XYZRoom at ({X},{Y},{Y}).")
|
||||||
|
raise InterruptCommand
|
||||||
|
else:
|
||||||
|
# regular search query
|
||||||
|
self.destination = self.caller.search(self.rhs, global_search=True)
|
||||||
|
if not self.destination:
|
||||||
|
raise InterruptCommand
|
||||||
|
|
||||||
|
self.exit_name = self.lhs_objs[0]["name"]
|
||||||
|
self.exit_aliases = self.lhs_objs[0]["aliases"]
|
||||||
|
self.exit_typeclass = self.lhs_objs[0]["option"]
|
||||||
|
|
||||||
|
|
||||||
|
class XYZGridCmdSet(CmdSet):
|
||||||
|
"""
|
||||||
|
Cmdset for easily adding the above cmds to the character cmdset.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = "xyzgrid_cmdset"
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
self.add(CmdXYZLook())
|
||||||
|
self.add(CmdXYZTeleport())
|
||||||
|
self.add(CmdXYZOpen())
|
||||||
|
|
@ -31,7 +31,10 @@ PARENT = {
|
||||||
"desc": "An empty room."
|
"desc": "An empty room."
|
||||||
}
|
}
|
||||||
|
|
||||||
# -------------------- map 1 - the large tree
|
|
||||||
|
# ---------------------------------------- map1
|
||||||
|
# The large tree
|
||||||
|
#
|
||||||
# this exemplifies the various map symbols
|
# this exemplifies the various map symbols
|
||||||
# but is not heavily prototyped
|
# but is not heavily prototyped
|
||||||
|
|
||||||
|
|
@ -39,11 +42,11 @@ 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
|
||||||
|
|
||||||
9 #-------#-#-------I
|
8 #-------#-#-------I
|
||||||
\ /
|
\ /
|
||||||
8 #-#---# #-t
|
7 #-#---# #-t
|
||||||
|\ |
|
|\ |
|
||||||
7 #i#-#b--#-t
|
6 #i#-#b--#-t
|
||||||
| |
|
| |
|
||||||
5 o-#---#
|
5 o-#---#
|
||||||
\ /
|
\ /
|
||||||
|
|
@ -68,7 +71,7 @@ class TransitionToCave(map_legend.MapTransitionMapNode):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
symbol = 'T'
|
symbol = 'T'
|
||||||
target_map_xyz = (2, 3, 'small cave')
|
target_map_xyz = (1, 0, 'the small cave')
|
||||||
|
|
||||||
|
|
||||||
# extends the default legend
|
# extends the default legend
|
||||||
|
|
@ -110,15 +113,15 @@ PROTOTYPES_MAP1 = {
|
||||||
"key": "Dense foilage",
|
"key": "Dense foilage",
|
||||||
"desc": "The foilage to the east is extra dense. It will take forever to get through it."
|
"desc": "The foilage to the east is extra dense. It will take forever to get through it."
|
||||||
},
|
},
|
||||||
(5, 7): {
|
(5, 6): {
|
||||||
"key": "On a huge branch",
|
"key": "On a huge branch",
|
||||||
"desc": "To the east is a glowing light, may be a teleporter."
|
"desc": "To the east is a glowing light, may be a teleporter."
|
||||||
},
|
},
|
||||||
(9, 8): {
|
(9, 7): {
|
||||||
"key": "On an enormous branch",
|
"key": "On an enormous branch",
|
||||||
"desc": "To the east is a glowing light, may be a teleporter."
|
"desc": "To the east is a glowing light, may be a teleporter."
|
||||||
},
|
},
|
||||||
(10, 9): {
|
(10, 8): {
|
||||||
"key": "A gorgeous view",
|
"key": "A gorgeous view",
|
||||||
"desc": "The view from here is breathtaking, showing the forest stretching far and wide."
|
"desc": "The view from here is breathtaking, showing the forest stretching far and wide."
|
||||||
},
|
},
|
||||||
|
|
@ -144,7 +147,8 @@ XYMAP_DATA_MAP1 = {
|
||||||
"prototypes": PROTOTYPES_MAP1
|
"prototypes": PROTOTYPES_MAP1
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------- map2 definitions - small cave
|
# -------------------------------------- map2
|
||||||
|
# The small cave
|
||||||
# this gives prototypes for every room
|
# this gives prototypes for every room
|
||||||
|
|
||||||
MAP2 = r"""
|
MAP2 = r"""
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,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'.")
|
||||||
|
|
||||||
|
import uuid
|
||||||
from evennia.prototypes import spawner
|
from evennia.prototypes import spawner
|
||||||
from evennia.utils.utils import make_iter
|
from evennia.utils.utils import make_iter
|
||||||
from .utils import MAPSCAN, REVERSE_DIRECTIONS, MapParserError, BIGVAL
|
from .utils import MAPSCAN, REVERSE_DIRECTIONS, MapParserError, BIGVAL
|
||||||
|
|
@ -22,6 +23,9 @@ NodeTypeclass = None
|
||||||
ExitTypeclass = None
|
ExitTypeclass = None
|
||||||
|
|
||||||
|
|
||||||
|
UUID_XYZ_NAMESPACE = uuid.uuid5(uuid.UUID(int=0), "xyzgrid")
|
||||||
|
|
||||||
|
|
||||||
# Nodes/Links
|
# Nodes/Links
|
||||||
|
|
||||||
class MapNode:
|
class MapNode:
|
||||||
|
|
@ -135,6 +139,18 @@ class MapNode:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
"""log messages using the xygrid parent"""
|
||||||
|
self.xymap.xyzgrid.log(msg)
|
||||||
|
|
||||||
|
def generate_prototype_key(self):
|
||||||
|
"""
|
||||||
|
Generate a deterministic prototype key to allow for users to apply prototypes without
|
||||||
|
needing a separate new name for every one.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return str(uuid.uuid5(UUID_XYZ_NAMESPACE, str((self.X, self.Y, self.Z))))
|
||||||
|
|
||||||
def build_links(self):
|
def build_links(self):
|
||||||
"""
|
"""
|
||||||
This is called by the map parser when this node is encountered. It tells the node
|
This is called by the map parser when this node is encountered. It tells the node
|
||||||
|
|
@ -261,20 +277,26 @@ class MapNode:
|
||||||
return
|
return
|
||||||
|
|
||||||
xyz = self.get_spawn_xyz()
|
xyz = self.get_spawn_xyz()
|
||||||
print("xyz:", xyz, self.node_index)
|
|
||||||
|
|
||||||
|
self.log(f" spawning/updating room at xyz={xyz}")
|
||||||
try:
|
try:
|
||||||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||||
except NodeTypeclass.DoesNotExist:
|
except NodeTypeclass.DoesNotExist:
|
||||||
# create a new entity with proper coordinates etc
|
# create a new entity with proper coordinates etc
|
||||||
nodeobj, err = NodeTypeclass.create(
|
nodeobj, err = NodeTypeclass.create(
|
||||||
self.prototype.get('key', 'An Empty room'),
|
self.prototype.get('key', 'An empty room'),
|
||||||
xyz=xyz
|
xyz=xyz
|
||||||
)
|
)
|
||||||
if err:
|
if err:
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
|
|
||||||
|
if not self.prototype.get('prototype_key'):
|
||||||
|
# make sure there is a prototype_key in prototype
|
||||||
|
self.prototype['prototype_key'] = self.generate_prototype_key()
|
||||||
|
|
||||||
# apply prototype to node. This will not override the XYZ tags since
|
# apply prototype to node. This will not override the XYZ tags since
|
||||||
# these are not in the prototype and exact=False
|
# these are not in the prototype and exact=False
|
||||||
|
|
||||||
spawner.batch_update_objects_with_prototype(
|
spawner.batch_update_objects_with_prototype(
|
||||||
self.prototype, objects=[nodeobj], exact=False)
|
self.prototype, objects=[nodeobj], exact=False)
|
||||||
|
|
||||||
|
|
@ -309,6 +331,9 @@ class MapNode:
|
||||||
if link.spawn_aliases
|
if link.spawn_aliases
|
||||||
else self.direction_spawn_defaults.get(direction, ('unknown',))
|
else self.direction_spawn_defaults.get(direction, ('unknown',))
|
||||||
)
|
)
|
||||||
|
if not link.prototype.get('prototype_key'):
|
||||||
|
# generate a deterministic prototype_key if it doesn't exist
|
||||||
|
link.prototype['prototype_key'] = self.generate_prototype_key()
|
||||||
maplinks[key.lower()] = (key, aliases, direction, link)
|
maplinks[key.lower()] = (key, aliases, direction, link)
|
||||||
|
|
||||||
# we need to search for exits in all directions since some
|
# we need to search for exits in all directions since some
|
||||||
|
|
@ -323,6 +348,8 @@ class MapNode:
|
||||||
|
|
||||||
if differing_key not in maplinks:
|
if differing_key not in maplinks:
|
||||||
# an exit without a maplink - delete the exit-object
|
# an exit without a maplink - delete the exit-object
|
||||||
|
self.log(f" deleting exit at xyz={xyz}, direction={direction}")
|
||||||
|
|
||||||
linkobjs.pop(differing_key).delete()
|
linkobjs.pop(differing_key).delete()
|
||||||
else:
|
else:
|
||||||
# missing in linkobjs - create a new exit
|
# missing in linkobjs - create a new exit
|
||||||
|
|
@ -341,6 +368,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}")
|
||||||
|
|
||||||
# apply prototypes to catch any changes
|
# apply prototypes to catch any changes
|
||||||
for key, linkobj in linkobjs.items():
|
for key, linkobj in linkobjs.items():
|
||||||
|
|
@ -537,6 +565,17 @@ class MapLink:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
def generate_prototype_key(self, *args):
|
||||||
|
"""
|
||||||
|
Generate a deterministic prototype key to allow for users to apply prototypes without
|
||||||
|
needing a separate new name for every one.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args (str): These are used to further seed the key.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return str(uuid.uuid5(UUID_XYZ_NAMESPACE, str((self.X, self.Y, self.Z, *args))))
|
||||||
|
|
||||||
def traverse(self, start_direction, _weight=0, _linklen=1, _steps=None):
|
def traverse(self, start_direction, _weight=0, _linklen=1, _steps=None):
|
||||||
"""
|
"""
|
||||||
Recursively traverse the links out of this LinkNode.
|
Recursively traverse the links out of this LinkNode.
|
||||||
|
|
|
||||||
|
|
@ -355,8 +355,15 @@ class _MapTest(TestCase):
|
||||||
self.grid.add_maps(self.map_data)
|
self.grid.add_maps(self.map_data)
|
||||||
self.map = self.grid.get_map(self.map_data['zcoord'])
|
self.map = self.grid.get_map(self.map_data['zcoord'])
|
||||||
|
|
||||||
|
# output to console
|
||||||
|
# def _log(msg):
|
||||||
|
# print(msg)
|
||||||
|
# self.grid.log = _log
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.grid.delete()
|
self.grid.delete()
|
||||||
|
xyzroom.XYZRoom.objects.all().delete()
|
||||||
|
xyzroom.XYZExit.objects.all().delete()
|
||||||
|
|
||||||
|
|
||||||
class TestMap1(_MapTest):
|
class TestMap1(_MapTest):
|
||||||
|
|
@ -1055,7 +1062,6 @@ class TestMapStressTest(TestCase):
|
||||||
"""
|
"""
|
||||||
Xmax, Ymax = gridsize
|
Xmax, Ymax = gridsize
|
||||||
grid = self._get_grid(Xmax, Ymax)
|
grid = self._get_grid(Xmax, Ymax)
|
||||||
# print(f"\n\n{grid}\n")
|
|
||||||
t0 = time()
|
t0 = time()
|
||||||
mapobj = xymap.XYMap({'map': grid}, Z="testmap")
|
mapobj = xymap.XYMap({'map': grid}, Z="testmap")
|
||||||
mapobj.parse()
|
mapobj.parse()
|
||||||
|
|
@ -1239,13 +1245,17 @@ class TestXYZGridTransition(TestCase):
|
||||||
|
|
||||||
class TestBuildExampleGrid(TestCase):
|
class TestBuildExampleGrid(TestCase):
|
||||||
"""
|
"""
|
||||||
Test building the map_example
|
Test building the map_example (this takes about 30s)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# build and populate grid
|
# build and populate grid
|
||||||
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
|
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
|
||||||
|
|
||||||
|
def _log(msg):
|
||||||
|
print(msg)
|
||||||
|
self.grid.log = _log
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.grid.delete()
|
self.grid.delete()
|
||||||
|
|
||||||
|
|
@ -1262,15 +1272,15 @@ class TestBuildExampleGrid(TestCase):
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
room1a = xyzroom.XYZRoom.objects.get_xyz(xyz=(3, 0, 'the large tree'))
|
room1a = xyzroom.XYZRoom.objects.get_xyz(xyz=(3, 0, 'the large tree'))
|
||||||
room1b = xyzroom.XYZRoom.objects.get_xyz(xyz=(10, 9, 'the large tree'))
|
room1b = xyzroom.XYZRoom.objects.get_xyz(xyz=(10, 8, 'the large tree'))
|
||||||
room2a = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, 'small cave'))
|
room2a = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 0, 'the small cave'))
|
||||||
room2b = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 3, 'small cave'))
|
room2b = xyzroom.XYZRoom.objects.get_xyz(xyz=(1, 3, 'the small cave'))
|
||||||
|
|
||||||
self.assertEqual(room1a.key, "Dungeon Entrance")
|
self.assertEqual(room1a.key, "Dungeon Entrance")
|
||||||
self.assertTrue(room1a.desc.startswith("To the west"))
|
self.assertTrue(room1a.db.desc.startswith("To the west"))
|
||||||
self.assertEqual(room1b.key, "A gorgeous view")
|
self.assertEqual(room1b.key, "A gorgeous view")
|
||||||
self.assertTrue(room1b.desc.startswith("The view from here is breathtaking."))
|
self.assertTrue(room1b.db.desc.startswith("The view from here is breathtaking,"))
|
||||||
self.assertEqual(room2a.key, "The entrance")
|
self.assertEqual(room2a.key, "The entrance")
|
||||||
self.assertTrue(room2a.desc.startswith("This is the entrance to"))
|
self.assertTrue(room2a.db.desc.startswith("This is the entrance to"))
|
||||||
self.assertEqual(room2b.key, "North-west corner of the atrium")
|
self.assertEqual(room2b.key, "North-west corner of the atrium")
|
||||||
self.assertTrue(room2b.desc.startswith("Sunlight sifts down"))
|
self.assertTrue(room2b.db.desc.startswith("Sunlight sifts down"))
|
||||||
|
|
|
||||||
|
|
@ -494,17 +494,19 @@ class XYMap:
|
||||||
for iy, node_or_link in ydct.items():
|
for iy, node_or_link in ydct.items():
|
||||||
display_map[iy][ix] = node_or_link.get_display_symbol()
|
display_map[iy][ix] = node_or_link.get_display_symbol()
|
||||||
|
|
||||||
# validate and make sure all nodes/links have prototypes
|
|
||||||
for node in node_index_map.values():
|
for node in node_index_map.values():
|
||||||
node_coord = (node.X, node.Y)
|
# override node-prototypes, ignore if no prototype
|
||||||
# load prototype from override, or use default
|
# is defined (some nodes should not be spawned)
|
||||||
node.prototype = self.prototypes.get(
|
if node.prototype:
|
||||||
node_coord, self.prototypes.get(('*', '*'), node.prototype))
|
node_coord = (node.X, node.Y)
|
||||||
# do the same for links (x, y, direction) coords
|
# load prototype from override, or use default
|
||||||
for direction, maplink in node.first_links.items():
|
node.prototype = self.prototypes.get(
|
||||||
maplink.prototype = self.prototypes.get(
|
node_coord, self.prototypes.get(('*', '*'), node.prototype))
|
||||||
node_coord + (direction,),
|
# do the same for links (x, y, direction) coords
|
||||||
self.prototypes.get(('*', '*', '*'), maplink.prototype))
|
for direction, maplink in node.first_links.items():
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ MAP_XDEST_TAG_CATEGORY = "exit_dest_x_coordinate"
|
||||||
MAP_YDEST_TAG_CATEGORY = "exit_dest_y_coordinate"
|
MAP_YDEST_TAG_CATEGORY = "exit_dest_y_coordinate"
|
||||||
MAP_ZDEST_TAG_CATEGORY = "exit_dest_z_coordinate"
|
MAP_ZDEST_TAG_CATEGORY = "exit_dest_z_coordinate"
|
||||||
|
|
||||||
|
GET_XYZGRID = None
|
||||||
|
|
||||||
|
|
||||||
class XYZManager(ObjectManager):
|
class XYZManager(ObjectManager):
|
||||||
"""
|
"""
|
||||||
|
|
@ -236,6 +238,13 @@ class XYZRoom(DefaultRoom):
|
||||||
x, y, z = self.xyz
|
x, y, z = self.xyz
|
||||||
return f"<XYZRoom '{self.db_key}', XYZ=({x},{y},{z})>"
|
return f"<XYZRoom '{self.db_key}', XYZ=({x},{y},{z})>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xyzgrid(self):
|
||||||
|
global GET_XYZGRID
|
||||||
|
if not GET_XYZGRID:
|
||||||
|
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid as GET_XYZGRID
|
||||||
|
return GET_XYZGRID()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xyz(self):
|
def xyz(self):
|
||||||
if not hasattr(self, "_xyz"):
|
if not hasattr(self, "_xyz"):
|
||||||
|
|
@ -310,6 +319,13 @@ class XYZExit(DefaultExit):
|
||||||
xd, yd, zd = self.xyz_destination
|
xd, yd, zd = self.xyz_destination
|
||||||
return f"<XYZExit '{self.db_key}', XYZ=({x},{y},{z})->({xd},{yd},{zd})>"
|
return f"<XYZExit '{self.db_key}', XYZ=({x},{y},{z})->({xd},{yd},{zd})>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xyzgrid(self):
|
||||||
|
global GET_XYZGRID
|
||||||
|
if not GET_XYZGRID:
|
||||||
|
from evennia.contrib.xyzgrid.xyzgrid import get_xyzgrid as GET_XYZGRID
|
||||||
|
return GET_XYZGRID()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xyz(self):
|
def xyz(self):
|
||||||
if not hasattr(self, "_xyz"):
|
if not hasattr(self, "_xyz"):
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,8 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
homogenizations like adding missing prototype_keys and setting a default typeclass.
|
homogenizations like adding missing prototype_keys and setting a default typeclass.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not prototype or not isinstance(prototype, dict):
|
if not prototype or isinstance(prototype, str):
|
||||||
return {}
|
return prototype
|
||||||
|
|
||||||
reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ())
|
reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ COMMAND_RATE_WARNING = "You entered commands too fast. Wait a moment and try aga
|
||||||
# custom, extra commands to add to the `evennia` launcher. This is a dict
|
# custom, extra commands to add to the `evennia` launcher. This is a dict
|
||||||
# of {'cmdname': 'path.to.callable', ...}, where the callable will be passed
|
# of {'cmdname': 'path.to.callable', ...}, where the callable will be passed
|
||||||
# any extra args given on the command line. For example `evennia cmdname foo bar`.
|
# any extra args given on the command line. For example `evennia cmdname foo bar`.
|
||||||
CUSTOM_LAUNCHER_COMMANDS = {}
|
EXTRA_LAUNCHER_COMMANDS = {}
|
||||||
|
|
||||||
# Determine how large of a string can be sent to the server in number
|
# Determine how large of a string can be sent to the server in number
|
||||||
# of characters. If they attempt to enter a string over this character
|
# of characters. If they attempt to enter a string over this character
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue