Run black reformatter on code

This commit is contained in:
Griatch 2022-02-08 13:03:52 +01:00
parent 4582eb4085
commit bd3e31bf3c
178 changed files with 4511 additions and 3385 deletions

View file

@ -2,4 +2,3 @@
Contribs related to moving in and manipulating the game world and grid.
"""

View file

@ -18,17 +18,18 @@ import random
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
# island surrounded by water (≈). By giving no instructions for the water
# characters we effectively skip it and create no rooms for those squares.
EXAMPLE1_MAP = '''\
EXAMPLE1_MAP = """\
n
n
'''
"""
def example1_build_forest(x, y, **kwargs):
'''A basic example of build instructions. Make sure to include **kwargs
in the arguments and return an instance of the room for exit generation.'''
"""A basic example of build instructions. Make sure to include **kwargs
in the arguments and return an instance of the room for exit generation."""
# Create a room and provide a basic description.
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
@ -42,7 +43,7 @@ def example1_build_forest(x, y, **kwargs):
def example1_build_mountains(x, y, **kwargs):
'''A room that is a little more advanced'''
"""A room that is a little more advanced"""
# Create the room.
room = create_object(rooms.Room, key="mountains" + str(x) + str(y))
@ -68,7 +69,7 @@ def example1_build_mountains(x, y, **kwargs):
def example1_build_temple(x, y, **kwargs):
'''A unique room that does not need to be as general'''
"""A unique room that does not need to be as general"""
# Create the room.
room = create_object(rooms.Room, key="temple" + str(x) + str(y))
@ -115,7 +116,7 @@ EXAMPLE1_LEGEND = {
# This is the same layout as Example 1 but included are characters for exits.
# We can use these characters to determine which rooms should be connected.
EXAMPLE2_MAP = '''\
EXAMPLE2_MAP = """\
--
@ -125,11 +126,11 @@ EXAMPLE2_MAP = '''\
--
'''
"""
def example2_build_forest(x, y, **kwargs):
'''A basic room'''
"""A basic room"""
# If on anything other than the first iteration - Do nothing.
if kwargs["iteration"] > 0:
return None
@ -143,7 +144,7 @@ def example2_build_forest(x, y, **kwargs):
def example2_build_verticle_exit(x, y, **kwargs):
'''Creates two exits to and from the two rooms north and south.'''
"""Creates two exits to and from the two rooms north and south."""
# If on the first iteration - Do nothing.
if kwargs["iteration"] == 0:
return
@ -164,7 +165,7 @@ def example2_build_verticle_exit(x, y, **kwargs):
def example2_build_horizontal_exit(x, y, **kwargs):
'''Creates two exits to and from the two rooms east and west.'''
"""Creates two exits to and from the two rooms east and west."""
# If on the first iteration - Do nothing.
if kwargs["iteration"] == 0:
return

View file

@ -5,7 +5,7 @@ Wilderness contrib - titeuf87, 2017
from .wilderness import create_wilderness # noqa
from .wilderness import enter_wilderness # noqa
from .wilderness import get_new_coordinates # noqa
from .wilderness import get_new_coordinates # noqa
from .wilderness import WildernessScript # noqa
from .wilderness import WildernessExit # noqa
from .wilderness import WildernessRoom # noqa

View file

@ -58,6 +58,7 @@ class CmdXYZTeleport(building.CmdTeleport):
are given, the target is a location on the XYZGrid.
"""
def _search_by_xyz(self, inp):
inp = inp.strip("()")
X, Y, *Z = inp.split(",", 2)
@ -69,8 +70,10 @@ class CmdXYZTeleport(building.CmdTeleport):
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.")
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]
@ -134,9 +137,11 @@ class CmdXYZOpen(building.CmdOpen):
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 or (X,Y,Z)>")
self.caller.msg(
"Usage: open <new exit>[;alias...][:typeclass]"
"[,<return exit>[;alias..][:typeclass]]] "
"= <destination or (X,Y,Z)>"
)
raise InterruptCommand
if not self.location:
self.caller.msg("You cannot create an exit from a None-location.")
@ -184,6 +189,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
Builders can optionally specify a specific grid coordinate (X,Y) to go to.
"""
key = "goto"
aliases = "path"
help_category = "General"
@ -207,11 +213,19 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
def _search_by_key_and_alias(self, inp, xyz_start):
Z = xyz_start[2]
candidates = list(XYZRoom.objects.filter_xyz(xyz=('*', '*', Z)))
candidates = list(XYZRoom.objects.filter_xyz(xyz=("*", "*", Z)))
return self.caller.search(inp, candidates=candidates)
def _auto_step(self, caller, session, target=None,
xymap=None, directions=None, step_sequence=None, step=True):
def _auto_step(
self,
caller,
session,
target=None,
xymap=None,
directions=None,
step_sequence=None,
step=True,
):
path_data = caller.ndb.xy_path_data
@ -221,8 +235,12 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
# stop any old task in its tracks
path_data.task.cancel()
path_data = caller.ndb.xy_path_data = PathData(
target=target, xymap=xymap, directions=directions,
step_sequence=step_sequence, task=None)
target=target,
xymap=xymap,
directions=directions,
step_sequence=step_sequence,
task=None,
)
if step and path_data:
@ -285,7 +303,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
xymap=path_data.xymap,
directions=directions,
step_sequence=step_sequence,
task=None
task=None,
)
# the map can itself tell the stepper to stop the auto-step prematurely
interrupt_node_or_link = None
@ -301,7 +319,8 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
# the exit name does not need to be the same as the cardinal direction!
exit_name, *_ = first_link.spawn_aliases.get(
direction, current_node.direction_spawn_defaults.get(direction, ('unknown', )))
direction, current_node.direction_spawn_defaults.get(direction, ("unknown",))
)
exit_obj = caller.search(exit_name)
if not exit_obj:
@ -315,13 +334,15 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
# premature stop of pathfind-step because of map node/link of interrupt type
if hasattr(interrupt_node_or_link, "node_index"):
message = exit_obj.destination.attributes.get(
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg)
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg
)
# we move into the node/room and then stop
caller.execute_cmd(exit_name, session=session)
else:
# if the link is interrupted we don't cross it at all
message = exit_obj.attributes.get(
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg)
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg
)
caller.msg(message)
return
@ -335,7 +356,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
xymap=path_data.xymap,
directions=path_data.directions,
step_sequence=path_data.step_sequence,
task=delay(self.auto_step_delay, self._auto_step, caller, session)
task=delay(self.auto_step_delay, self._auto_step, caller, session),
)
def func(self):
@ -344,7 +365,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
"""
caller = self.caller
goto_mode = self.cmdname == 'goto'
goto_mode = self.cmdname == "goto"
# check if we have an existing path
path_data = caller.ndb.xy_path_data
@ -359,8 +380,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
caller.msg(f"Aborted auto-walking to {target_name}.")
return
# goto/path-command will show current path
current_path = list_to_string(
[f"|w{step}|n" for step in path_data.directions])
current_path = list_to_string([f"|w{step}|n" for step in path_data.directions])
moving = "(moving)" if task and task.active() else ""
caller.msg(f"Path to {target_name}{moving}: {current_path}")
else:
@ -405,12 +425,21 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
xy_end = xyz_end[:2]
directions, step_sequence = xymap.get_shortest_path(xy_start, xy_end)
caller.msg(f"There are {len(directions)} steps to {target.get_display_name(caller)}: "
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n")
caller.msg(
f"There are {len(directions)} steps to {target.get_display_name(caller)}: "
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n"
)
# create data for display and start stepping if we used goto
self._auto_step(caller, self.session, target=target, xymap=xymap,
directions=directions, step_sequence=step_sequence, step=goto_mode)
self._auto_step(
caller,
self.session,
target=target,
xymap=xymap,
directions=directions,
step_sequence=step_sequence,
step=goto_mode,
)
class CmdMap(COMMAND_DEFAULT_CLASS):
@ -424,6 +453,7 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
This is a builder-command.
"""
key = "map"
locks = "cmd:perm(Builders)"
@ -453,8 +483,10 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
xymap = xyzgrid.get_map(Z)
if not xymap:
self.caller.msg(f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
"available maps/Zcoords.")
self.caller.msg(
f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
"available maps/Zcoords."
)
return
self.caller.msg(ansi.raw(xymap.mapstring))
@ -465,6 +497,7 @@ class XYZGridCmdSet(CmdSet):
Cmdset for easily adding the above cmds to the character cmdset.
"""
key = "xyzgrid_cmdset"
def at_cmdset_creation(self):

View file

@ -81,14 +81,13 @@ class TransitionToCave(xymap_legend.TransitionMapNode):
into a room but only acts as a target for finding the exit's destination.
"""
symbol = 'T'
target_map_xyz = (1, 0, 'the small cave')
symbol = "T"
target_map_xyz = (1, 0, "the small cave")
# extends the default legend
LEGEND_MAP1 = {
'T': TransitionToCave
}
LEGEND_MAP1 = {"T": TransitionToCave}
# link coordinates to rooms
@ -96,70 +95,62 @@ PROTOTYPES_MAP1 = {
# node/room prototypes
(3, 0): {
"key": "Dungeon Entrance",
"desc": "To the east, a narrow opening leads into darkness."
"desc": "To the east, a narrow opening leads into darkness.",
},
(4, 1): {
"key": "Under the foilage of a giant tree",
"desc": "High above the branches of a giant tree blocks out the sunlight. A slide "
"leading down from the upper branches ends here."
"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."
"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."
"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."
"desc": "The foilage to the east is extra dense. It will take forever to get through it.",
},
(5, 6): {
"key": "On a huge branch",
"desc": "To the east is a glowing light, may be a teleporter to a higher branch."
"desc": "To the east is a glowing light, may be a teleporter to a higher branch.",
},
(9, 7): {
"key": "On an enormous branch",
"desc": "To the west is a glowing light. It may be a teleporter to a lower branch."
"desc": "To the west is a glowing light. It may be a teleporter to a lower branch.",
},
(10, 8): {
"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.",
},
# 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."
"desc": "These branches are wide enough to easily walk on. There's green all around.",
},
# directional prototypes
(3, 0, 'e'): {
"desc": "A dark passage into the underworld."
},
(3, 0, "e"): {"desc": "A dark passage into the underworld."},
}
for key, prot in PROTOTYPES_MAP1.items():
if len(key) == 2:
# we don't want to give exits the room typeclass!
prot['prototype_parent'] = ROOM_PARENT
prot["prototype_parent"] = ROOM_PARENT
else:
prot['prototype_parent'] = EXIT_PARENT
prot["prototype_parent"] = EXIT_PARENT
XYMAP_DATA_MAP1 = {
"zcoord": "the large tree",
"map": MAP1,
"legend": LEGEND_MAP1,
"prototypes": PROTOTYPES_MAP1
"prototypes": PROTOTYPES_MAP1,
}
# -------------------------------------- map2
@ -188,14 +179,13 @@ class TransitionToLargeTree(xymap_legend.TransitionMapNode):
into a room by only acts as a target for finding the exit's destination.
"""
symbol = 'T'
target_map_xyz = (3, 0, 'the large tree')
symbol = "T"
target_map_xyz = (3, 0, "the large tree")
# this extends the default legend (that defines #,-+ etc)
LEGEND_MAP2 = {
"T": TransitionToLargeTree
}
LEGEND_MAP2 = {"T": TransitionToLargeTree}
# prototypes for specific locations
PROTOTYPES_MAP2 = {
@ -203,64 +193,54 @@ PROTOTYPES_MAP2 = {
(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."
"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."
"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."
"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."
"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."
"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."
"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."
"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."
"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."
"is no way to continue deeper.",
},
# default fallback for undefined nodes
('*', '*'): {
"key": "A dark room",
"desc": "A dark, but empty, room."
},
("*", "*"): {"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."
},
(1, 0, "w"): {"desc": "A narrow path to the fresh air of the outside world."},
# directional fallbacks for unset directions
('*', '*', '*'): {
"desc": "A dark passage"
}
("*", "*", "*"): {"desc": "A dark passage"},
}
# this is required by the prototypes, but we add it all at once so we don't
@ -268,9 +248,9 @@ PROTOTYPES_MAP2 = {
for key, prot in PROTOTYPES_MAP2.items():
if len(key) == 2:
# we don't want to give exits the room typeclass!
prot['prototype_parent'] = ROOM_PARENT
prot["prototype_parent"] = ROOM_PARENT
else:
prot['prototype_parent'] = EXIT_PARENT
prot["prototype_parent"] = EXIT_PARENT
XYMAP_DATA_MAP2 = {
@ -278,14 +258,8 @@ XYMAP_DATA_MAP2 = {
"zcoord": "the small cave",
"legend": LEGEND_MAP2,
"prototypes": PROTOTYPES_MAP2,
"options": {
"map_visual_range": 1,
"map_mode": 'scan'
}
"options": {"map_visual_range": 1, "map_mode": "scan"},
}
# This is read by the parser
XYMAP_DATA_LIST = [
XYMAP_DATA_MAP1,
XYMAP_DATA_MAP2
]
XYMAP_DATA_LIST = [XYMAP_DATA_MAP1, XYMAP_DATA_MAP2]

View file

@ -160,11 +160,12 @@ _TOPICS_MAP = {
"add": _HELP_ADD,
"spawn": _HELP_SPAWN,
"initpath": _HELP_INITPATH,
"delete": _HELP_DELETE
"delete": _HELP_DELETE,
}
evennia._init()
def _option_help(*suboptions):
"""
Show help <command> aid.
@ -188,6 +189,7 @@ def _option_list(*suboptions):
# override grid's logger to echo directly to console
def _log(msg):
print(msg)
xyzgrid.log = _log
xymap_data = xyzgrid.grid
@ -210,7 +212,7 @@ def _option_list(*suboptions):
if not xymap:
print(f"No XYMap with Z='{zcoord}' was found on grid.")
else:
nrooms = xyzgrid.get_room(('*', '*', zcoord)).count()
nrooms = xyzgrid.get_room(("*", "*", zcoord)).count()
nnodes = len(xymap.node_index_map)
print("\n" + str(repr(xymap)) + ":\n")
checkwarning = True
@ -218,22 +220,29 @@ def _option_list(*suboptions):
print(f"{nrooms} / {nnodes} rooms are spawned.")
checkwarning = False
elif nrooms < nnodes:
print(f"{nrooms} / {nnodes} rooms are spawned\n"
"Note: Transitional nodes are *not* spawned (they just point \n"
"to another map), so the 'missing room(s)' may just be from such nodes.")
print(
f"{nrooms} / {nnodes} rooms are spawned\n"
"Note: Transitional nodes are *not* spawned (they just point \n"
"to another map), so the 'missing room(s)' may just be from such nodes."
)
elif nrooms > nnodes:
print(f"{nrooms} / {nnodes} rooms are spawned\n"
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync.")
print(
f"{nrooms} / {nnodes} rooms are spawned\n"
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync."
)
else:
print(f"{nrooms} / {nnodes} rooms are spawned\n")
if checkwarning:
print("Note: This check is not complete; it does not consider changed map "
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
"is calculated only during a spawn.")
print(
"Note: This check is not complete; it does not consider changed map "
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
"is calculated only during a spawn."
)
print("\nDisplayed map (as appearing in-game):\n\n" + ansi.parse_ansi(str(xymap)))
print("\nRaw map string (including axes and invisible nodes/links):\n"
+ str(xymap.mapstring))
print(
"\nRaw map string (including axes and invisible nodes/links):\n" + str(xymap.mapstring)
)
print(f"\nCustom map options: {xymap.options}\n")
legend = []
for key, node_or_link in xymap.legend.items():
@ -260,6 +269,7 @@ def _option_add(*suboptions):
# override grid's logger to echo directly to console
def _log(msg):
print(msg)
grid.log = _log
xymap_data_list = []
@ -290,35 +300,44 @@ def _option_spawn(*suboptions):
# override grid's logger to echo directly to console
def _log(msg):
print(msg)
grid.log = _log
if suboptions:
opts = ''.join(suboptions).strip('()')
opts = "".join(suboptions).strip("()")
# coordinate tuple
try:
x, y, z = (part.strip() for part in opts.split(","))
except ValueError:
print("spawn 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.")
print(
"spawn 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 = '*', '*', '*'
x, y, z = "*", "*", "*"
if x == y == z == '*':
inp = input("This will (re)spawn the entire grid. If it was built before, it may spawn \n"
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
"continue? [Y]/N? ")
if x == y == z == "*":
inp = input(
"This will (re)spawn the entire grid. If it was built before, it may spawn \n"
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
"continue? [Y]/N? "
)
else:
inp = input("This will spawn/delete objects in the database matching grid coordinates \n"
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? ")
if inp.lower() in ('no', 'n'):
inp = input(
"This will spawn/delete objects in the database matching grid coordinates \n"
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? "
)
if inp.lower() in ("no", "n"):
print("Aborted.")
return
print("Beginner-Tutorial spawn ...")
grid.spawn(xyz=(x, y, z))
print("... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
"modified an existing grid.")
print(
"... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
"modified an existing grid."
)
def _option_initpath(*suboptions):
@ -331,6 +350,7 @@ def _option_initpath(*suboptions):
# override grid's logger to echo directly to console
def _log(msg):
print(msg)
grid.log = _log
xymaps = grid.all_maps()
@ -354,19 +374,24 @@ def _option_delete(*suboptions):
# override grid's logger to echo directly to console
def _log(msg):
print(msg)
grid.log = _log
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]? ")
if repl.lower() not in ('yes', 'y'):
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]? "
)
if repl.lower() not in ("yes", "y"):
print("Aborted.")
return
print("Deleting grid ...")
grid.delete()
print("... done.\nPlease reload the server now; otherwise "
"removed rooms may linger in cache.")
print(
"... done.\nPlease reload the server now; otherwise "
"removed rooms may linger in cache."
)
return
zcoords = (part.strip() for part in suboptions)
@ -376,21 +401,24 @@ def _option_delete(*suboptions):
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()))
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\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]? ")
if repl.lower() not in ('yes', 'y'):
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]? "
)
if repl.lower() not in ("yes", "y"):
print("Aborted.")
return
print("Deleting selected xymaps ...")
grid.remove_map(*zcoords, remove_objects=True)
print("... done.\nPlease reload the server to refresh room caches."
"\nAlso remember to remove any links from remaining maps pointing to deleted maps.")
print(
"... done.\nPlease reload the server to refresh room caches."
"\nAlso remember to remove any links from remaining maps pointing to deleted maps."
)
def xyzcommand(*args):
@ -405,20 +433,19 @@ def xyzcommand(*args):
option, *suboptions = args
if option in ('help', 'h'):
if option in ("help", "h"):
_option_help(*suboptions)
if option in ('list', 'show'):
if option in ("list", "show"):
_option_list(*suboptions)
elif option == 'init':
elif option == "init":
_option_init(*suboptions)
elif option == 'add':
elif option == "add":
_option_add(*suboptions)
elif option == 'spawn':
elif option == "spawn":
_option_spawn(*suboptions)
elif option == 'initpath':
elif option == "initpath":
_option_initpath(*suboptions)
elif option == 'delete':
elif option == "delete":
_option_delete(*suboptions)
else:
print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.")

View file

@ -27,19 +27,19 @@ except AttributeError:
exit_override = {}
room_prototype = {
'prototype_key': 'xyz_room',
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom',
'prototype_tags': ("xyzroom", ),
'key': "A room",
'desc': "An empty room."
"prototype_key": "xyz_room",
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom",
"prototype_tags": ("xyzroom",),
"key": "A room",
"desc": "An empty room.",
}
room_prototype.update(room_override)
exit_prototype = {
'prototype_key': 'xyz_exit',
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit',
'prototype_tags': ("xyzexit", ),
'desc': "An exit."
"prototype_key": "xyz_exit",
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit",
"prototype_tags": ("xyzexit",),
"desc": "An exit.",
}
exit_prototype.update(exit_override)

File diff suppressed because it is too large Load diff

View file

@ -30,13 +30,15 @@ MAPSCAN = {
# errors for Map system
class MapError(RuntimeError):
class MapError(RuntimeError):
def __init__(self, error="", node_or_link=None):
prefix = ""
if node_or_link:
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) ")
prefix = (
f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
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.message = f"{prefix}{error}"
super().__init__(self.message)
@ -52,4 +54,5 @@ class MapTransition(RuntimeWarning):
leads to another map.
"""
pass

View file

@ -104,7 +104,8 @@ try:
except ImportError as err:
raise ImportError(
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 evennia.utils.utils import variable_from_module, mod_import, is_iter
from evennia.utils import logger
@ -122,9 +123,7 @@ _CACHE_DIR = settings.CACHE_DIR
_LOADED_PROTOTYPES = None
_XYZROOMCLASS = None
MAP_DATA_KEYS = [
"zcoord", "map", "legend", "prototypes", "options", "module_path"
]
MAP_DATA_KEYS = ["zcoord", "map", "legend", "prototypes", "options", "module_path"]
DEFAULT_LEGEND = xymap_legend.LEGEND
@ -172,11 +171,11 @@ class XYMap:
but recommended for readability!
"""
mapcorner_symbol = '+'
mapcorner_symbol = "+"
max_pathfinding_length = 500
empty_symbol = ' '
empty_symbol = " "
# we normally only accept one single character for the legend key
legend_key_exceptions = ("\\")
legend_key_exceptions = "\\"
def __init__(self, map_module_or_dict, Z="map", xyzgrid=None):
"""
@ -210,7 +209,9 @@ class XYMap:
if not _LOADED_PROTOTYPES:
# inject default prototypes, but don't override prototype-keys loaded from
# settings, if they exist (that means the user wants to replace the defaults)
protlib.load_module_prototypes("evennia.contrib.grid.xyzgrid.prototypes", override=False)
protlib.load_module_prototypes(
"evennia.contrib.grid.xyzgrid.prototypes", override=False
)
_LOADED_PROTOTYPES = True
self.Z = Z
@ -264,7 +265,7 @@ class XYMap:
nnodes = 0
if self.node_index_map:
nnodes = len(self.node_index_map)
return (f"<XYMap(Z={self.Z}), {self.max_X + 1}x{self.max_Y + 1}, {nnodes} nodes>")
return f"<XYMap(Z={self.Z}), {self.max_X + 1}x{self.max_Y + 1}, {nnodes} nodes>"
def log(self, msg):
if self.xyzgrid:
@ -317,34 +318,41 @@ class XYMap:
mapdata = variable_from_module(mod, "XYMAP_DATA")
if not mapdata:
raise MapError("No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
f"{map_module_or_dict}.")
raise MapError(
"No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
f"{map_module_or_dict}."
)
# validate
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
raise MapError(f"Mapdata has keys {list(mapdata)}, but only "
f"keys {MAP_DATA_KEYS} are allowed.")
raise MapError(
f"Mapdata has keys {list(mapdata)}, but only " f"keys {MAP_DATA_KEYS} are allowed."
)
for key in mapdata.get('legend', DEFAULT_LEGEND):
for key in mapdata.get("legend", DEFAULT_LEGEND):
if not key or len(key) > 1:
if key not in self.legend_key_exceptions:
raise MapError(f"Map-legend key '{key}' is invalid: All keys must "
"be exactly one character long. Use the node/link's "
"`.display_symbol` property to change how it is "
"displayed.")
if 'map' not in mapdata or not mapdata['map']:
raise MapError(
f"Map-legend key '{key}' is invalid: All keys must "
"be exactly one character long. Use the node/link's "
"`.display_symbol` property to change how it is "
"displayed."
)
if "map" not in mapdata or not mapdata["map"]:
raise MapError("No map found. Add 'map' key to map-data dict.")
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)):
raise MapError(f"Prototype override key {key} is malformed: It must be a "
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
"where direction is a supported direction string ('n', 'ne', etc).")
raise MapError(
f"Prototype override key {key} is malformed: It must be a "
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
"where direction is a supported direction string ('n', 'ne', etc)."
)
# store/update result
self.Z = mapdata.get('zcoord', self.Z)
self.mapstring = mapdata['map']
self.prototypes = mapdata.get('prototypes', {})
self.options = mapdata.get('options', {})
self.Z = mapdata.get("zcoord", self.Z)
self.mapstring = mapdata["map"]
self.prototypes = mapdata.get("prototypes", {})
self.options = mapdata.get("options", {})
# merge the custom legend onto the default legend to allow easily
# overriding only parts of it
@ -357,8 +365,9 @@ class XYMap:
# nothing more to do
continue
# we need to load the prototype dict onto each for ease of access. Note that
proto = protlib.search_prototype(prototype, require_single=True,
no_db=_NO_DB_PROTOTYPES)[0]
proto = protlib.search_prototype(
prototype, require_single=True, no_db=_NO_DB_PROTOTYPES
)[0]
node_or_link_class.prototype = proto
def parse(self):
@ -391,7 +400,8 @@ class XYMap:
raise MapParserError(
f"The mapstring must have at least two '{mapcorner_symbol}' "
"symbols marking the upper- and bottom-left corners of the "
"grid area.")
"grid area."
)
# find the the position (in the string as a whole) of the top-left corner-marker
maplines = mapstring.split("\n")
@ -406,13 +416,15 @@ class XYMap:
# find the position (in the string as a whole) of the bottom-left corner-marker
# this is always in a stright line down from the first marker
botleft_marker_x, botleft_marker_y = topleft_marker_x, -1
for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1:]):
for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1 :]):
if line.find(mapcorner_symbol) == topleft_marker_x:
break
if botleft_marker_y == -1:
raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
"Make sure it lines up with the top-left corner-marker "
f"(found at column {topleft_marker_x} of the string).")
raise MapParserError(
f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
"Make sure it lines up with the top-left corner-marker "
f"(found at column {topleft_marker_x} of the string)."
)
# the actual coordinate is dy below the topleft marker so we need to shift
botleft_marker_y += topleft_marker_y + 1
@ -443,8 +455,7 @@ class XYMap:
mapnode_or_link_class = self.legend.get(char)
if not mapnode_or_link_class:
raise MapParserError(
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) "
"is not found in LEGEND."
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) " "is not found in LEGEND."
)
if hasattr(mapnode_or_link_class, "node_index"):
# A mapnode. Mapnodes can only be placed on even grid positions, where
@ -454,7 +465,8 @@ class XYMap:
raise MapParserError(
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) marks a "
"MapNode but is located between integer (X,Y) positions (only "
"Links can be placed between coordinates)!")
"Links can be placed between coordinates)!"
)
# save the node to several different maps for different uses
# in both coordinate systems
@ -462,14 +474,17 @@ class XYMap:
max_X, max_Y = max(max_X, iX), max(max_Y, iY)
node_index += 1
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[node_index] = \
mapnode_or_link_class(x=ix, y=iy, Z=self.Z,
node_index=node_index, symbol=char, xymap=self)
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[
node_index
] = mapnode_or_link_class(
x=ix, y=iy, Z=self.Z, node_index=node_index, symbol=char, xymap=self
)
else:
# we have a link at this xygrid position (this is ok everywhere)
xygrid[ix][iy] = mapnode_or_link_class(x=ix, y=iy, Z=self.Z, symbol=char,
xymap=self)
xygrid[ix][iy] = mapnode_or_link_class(
x=ix, y=iy, Z=self.Z, symbol=char, xymap=self
)
# store the symbol mapping for transition lookups
symbol_map[char].append(xygrid[ix][iy])
@ -499,20 +514,23 @@ class XYMap:
node_coord = (node.X, node.Y)
# load prototype from override, or use default
try:
node.prototype = flatten_prototype(self.prototypes.get(
node_coord,
self.prototypes.get(('*', '*'), node.prototype)),
no_db=_NO_DB_PROTOTYPES
node.prototype = flatten_prototype(
self.prototypes.get(
node_coord, self.prototypes.get(("*", "*"), node.prototype)
),
no_db=_NO_DB_PROTOTYPES,
)
except Exception as err:
raise MapParserError(f"Room prototype malformed: {err}", node)
# do the same for links (x, y, direction) coords
for direction, maplink in node.first_links.items():
try:
maplink.prototype = flatten_prototype(self.prototypes.get(
node_coord + (direction,),
self.prototypes.get(('*', '*', '*'), maplink.prototype)),
no_db=_NO_DB_PROTOTYPES
maplink.prototype = flatten_prototype(
self.prototypes.get(
node_coord + (direction,),
self.prototypes.get(("*", "*", "*"), maplink.prototype),
),
no_db=_NO_DB_PROTOTYPES,
)
except Exception as err:
raise MapParserError(f"Exit prototype malformed: {err}", maplink)
@ -539,8 +557,10 @@ class XYMap:
This performs a depth-first pass down the the given dist.
"""
def _scan_neighbors(start_node, points, dist=2,
xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0):
def _scan_neighbors(
start_node, points, dist=2, xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0
):
x0, y0 = start_node.x, start_node.y
points.append((x0, y0))
@ -558,9 +578,15 @@ class XYMap:
ymin, ymax = min(ymin, y), max(ymax, y)
points, xmin, xmax, ymin, ymax = _scan_neighbors(
end_node, points, dist=dist,
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
depth=depth + 1)
end_node,
points,
dist=dist,
xmin=xmin,
ymin=ymin,
xmax=xmax,
ymax=ymax,
depth=depth + 1,
)
return points, xmin, xmax, ymin, ymax
@ -581,14 +607,16 @@ class XYMap:
# check if the solution for this grid was already solved previously.
mapstr, dist_matrix, pathfinding_routes = "", None, None
with open(self.pathfinder_baked_filename, 'rb') as fil:
with open(self.pathfinder_baked_filename, "rb") as fil:
try:
mapstr, dist_matrix, pathfinding_routes = pickle.load(fil)
except Exception:
logger.log_trace()
if (mapstr == self.mapstring
and dist_matrix is not None
and pathfinding_routes is not None):
if (
mapstr == self.mapstring
and dist_matrix is not None
and pathfinding_routes is not None
):
# this is important - it means the map hasn't changed so
# we can re-use the stored data!
self.dist_matrix = dist_matrix
@ -606,16 +634,20 @@ class XYMap:
# solve using Dijkstra's algorithm
self.dist_matrix, self.pathfinding_routes = dijkstra(
pathfinding_matrix, directed=True,
return_predecessors=True, limit=self.max_pathfinding_length)
pathfinding_matrix,
directed=True,
return_predecessors=True,
limit=self.max_pathfinding_length,
)
if self.pathfinder_baked_filename:
# try to cache the results
with open(self.pathfinder_baked_filename, 'wb') as fil:
pickle.dump((self.mapstring, self.dist_matrix, self.pathfinding_routes),
fil, protocol=4)
with open(self.pathfinder_baked_filename, "wb") as fil:
pickle.dump(
(self.mapstring, self.dist_matrix, self.pathfinding_routes), fil, protocol=4
)
def spawn_nodes(self, xy=('*', '*')):
def spawn_nodes(self, xy=("*", "*")):
"""
Convert the nodes of this XYMap into actual in-world rooms by spawning their
related prototypes in the correct coordinate positions. This must be done *first*
@ -638,12 +670,14 @@ class XYMap:
if not _XYZROOMCLASS:
from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom as _XYZROOMCLASS
x, y = xy
wildcard = '*'
wildcard = "*"
spawned = []
# find existing nodes, in case some rooms need to be removed
map_coords = [(node.X, node.Y) for node in
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))]
map_coords = [
(node.X, node.Y)
for node in sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))
]
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
roomX, roomY, _ = existing_room.xyz
if (roomX, roomY) not in map_coords:
@ -657,7 +691,7 @@ class XYMap:
spawned.append(node)
return spawned
def spawn_links(self, xy=('*', '*'), nodes=None, directions=None):
def spawn_links(self, xy=("*", "*"), nodes=None, directions=None):
"""
Convert links of this XYMap into actual in-game exits by spawning their related
prototypes. It's possible to only spawn a specic exit by specifying the node and
@ -676,7 +710,7 @@ class XYMap:
"""
x, y = xy
wildcard = '*'
wildcard = "*"
if not nodes:
nodes = sorted(self.node_index_map.values(), key=lambda n: (n.Z, n.Y, n.X))
@ -706,8 +740,10 @@ class XYMap:
iX, iY = xy
if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)):
raise MapError(f"get_node_from_coord got coordinate {xy} which is "
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
raise MapError(
f"get_node_from_coord got coordinate {xy} which is "
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y})."
)
try:
return self.XYgrid[iX][iY]
except KeyError:
@ -753,8 +789,10 @@ class XYMap:
istartnode = startnode.node_index
inextnode = endnode.node_index
except AttributeError:
raise MapError(f"Map.get_shortest_path received start/end nodes {startnode} and "
f"{endnode}. They must both be MapNodes (not Links)")
raise MapError(
f"Map.get_shortest_path received start/end nodes {startnode} and "
f"{endnode}. They must both be MapNodes (not Links)"
)
if self.pathfinding_routes is None:
self.calculate_path_matrix()
@ -782,13 +820,18 @@ class XYMap:
return directions, path
def get_visual_range(self, xy, dist=2, mode='nodes',
character='@',
target=None,
target_path_style="|y{display_symbol}|n",
max_size=None,
indent=0,
return_str=True):
def get_visual_range(
self,
xy,
dist=2,
mode="nodes",
character="@",
target=None,
target_path_style="|y{display_symbol}|n",
max_size=None,
indent=0,
return_str=True,
):
"""
Get a part of the grid centered on a specific point and extended a certain number
of nodes or grid points in every direction.
@ -876,7 +919,7 @@ class XYMap:
# nothing but ourselves or emptiness
return character if character else self.empty_symbol
elif mode == 'nodes':
elif mode == "nodes":
# dist measures only full, reachable nodes.
points, xmin, xmax, ymin, ymax = self._get_topology_around_coord(xy, dist=dist)
@ -888,7 +931,7 @@ class XYMap:
for (ix0, iy0) in points:
gridmap[iy0 - ymin][ix0 - xmin] = display_map[iy0][ix0]
elif mode == 'scan':
elif mode == "scan":
# scan-mode - dist measures individual grid points
xmin, xmax = max(0, ix - dist), min(width, ix + dist + 1)
@ -897,8 +940,10 @@ class XYMap:
gridmap = [line[xmin:xmax] for line in display_map[ymin:ymax]]
else:
raise MapError(f"Map.get_visual_range 'mode' was '{mode}' "
"- it must be either 'scan' or 'nodes'.")
raise MapError(
f"Map.get_visual_range 'mode' was '{mode}' "
"- it must be either 'scan' or 'nodes'."
)
if character:
gridmap[iyc][ixc] = character # correct indexing; it's a list of lines
@ -906,8 +951,7 @@ class XYMap:
# stylize path to target
def _default_callable(node):
return target_path_style.format(
display_symbol=node.get_display_symbol())
return target_path_style.format(display_symbol=node.get_display_symbol())
if callable(target_path_style):
_target_path_style = target_path_style
@ -916,7 +960,7 @@ class XYMap:
_, path = self.get_shortest_path(xy, target)
maxstep = dist if mode == 'nodes' else dist / 2
maxstep = dist if mode == "nodes" else dist / 2
nsteps = 0
for node_or_link in path[1:]:
if hasattr(node_or_link, "node_index"):

View file

@ -14,7 +14,8 @@ try:
except ImportError as err:
raise ImportError(
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 collections import defaultdict
@ -33,6 +34,7 @@ UUID_XYZ_NAMESPACE = uuid.uuid5(uuid.UUID(int=0), "xyzgrid")
# Nodes/Links
class MapNode:
"""
This represents a 'room' node on the map. Note that the map system deals with two grids, the
@ -62,8 +64,9 @@ class MapNode:
for various reasons, mostly map-transitions).
"""
# symbol used to identify this link on the map
symbol = '#'
symbol = "#"
# if printing this node should show another symbol. If set
# to the empty string, use `symbol`.
display_symbol = None
@ -79,16 +82,16 @@ class MapNode:
multilink = True
# default values to use if the exit doesn't have a 'spawn_aliases' iterable
direction_spawn_defaults = {
'n': ('north', 'n'),
'ne': ('northeast', 'ne', 'north-east'),
'e': ('east', 'e'),
'se': ('southeast', 'se', 'south-east'),
's': ('south', 's'),
'sw': ('southwest', 'sw', 'south-west'),
'w': ('west', 'w'),
'nw': ('northwest', 'nw', 'north-west'),
'd': ('down', 'd', 'do'),
'u': ('up', 'u'),
"n": ("north", "n"),
"ne": ("northeast", "ne", "north-east"),
"e": ("east", "e"),
"se": ("southeast", "se", "south-east"),
"s": ("south", "s"),
"sw": ("southwest", "sw", "south-west"),
"w": ("west", "w"),
"nw": ("northwest", "nw", "north-west"),
"d": ("down", "d", "do"),
"u": ("up", "u"),
}
def __init__(self, x, y, Z, node_index=0, symbol=None, xymap=None):
@ -202,7 +205,9 @@ class MapNode:
if first_step_name in self.closest_neighbor_names:
raise MapParserError(
f"has more than one outgoing direction '{first_step_name}'. "
"All directions out of a node must be unique.", self)
"All directions out of a node must be unique.",
self,
)
self.closest_neighbor_names[first_step_name] = direction
node_index = end_node.node_index
@ -215,8 +220,9 @@ class MapNode:
# used for building the shortest path. Note that we store the
# aliased link directions here, for quick display by the
# shortest-route solver
shortest_route = self.shortest_route_to_node.get(
node_index, ("", [], BIGVAL))[2]
shortest_route = self.shortest_route_to_node.get(node_index, ("", [], BIGVAL))[
2
]
if weight < shortest_route:
self.shortest_route_to_node[node_index] = (first_step_name, steps, weight)
@ -280,11 +286,9 @@ class MapNode:
str or tuple: The key of the spawned exit, or a tuple (key, alias, alias, ...)
"""
key, *aliases = (
self.first_links[direction]
.spawn_aliases.get(
direction, self.direction_spawn_defaults.get(
direction, ('unknown', ))))
key, *aliases = self.first_links[direction].spawn_aliases.get(
direction, self.direction_spawn_defaults.get(direction, ("unknown",))
)
if return_aliases:
return (key, *aliases)
return key
@ -313,28 +317,24 @@ class MapNode:
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
except django_exceptions.ObjectDoesNotExist:
# create a new entity with proper coordinates etc
tclass = self.prototype['typeclass']
tclass = (f' ({tclass})'
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom'
else '')
self.log(f" spawning room at xyz={xyz}{tclass}")
nodeobj, err = NodeTypeclass.create(
self.prototype.get('key', 'An empty room'),
xyz=xyz
tclass = self.prototype["typeclass"]
tclass = (
f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else ""
)
self.log(f" spawning room at xyz={xyz}{tclass}")
nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
if err:
raise RuntimeError(err)
else:
self.log(f" updating existing room (if changed) at xyz={xyz}")
if not self.prototype.get('prototype_key'):
if not self.prototype.get("prototype_key"):
# make sure there is a prototype_key in prototype
self.prototype['prototype_key'] = self.generate_prototype_key()
self.prototype["prototype_key"] = self.generate_prototype_key()
# apply prototype to node. This will not override the XYZ tags since
# these are not in the prototype and exact=False
spawner.batch_update_objects_with_prototype(
self.prototype, objects=[nodeobj], exact=False)
spawner.batch_update_objects_with_prototype(self.prototype, objects=[nodeobj], exact=False)
def spawn_links(self, directions=None):
"""
@ -364,9 +364,9 @@ class MapNode:
for direction, link in self.first_links.items():
key, *aliases = self.get_exit_spawn_name(direction)
if not link.prototype.get('prototype_key'):
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()
link.prototype["prototype_key"] = self.generate_prototype_key()
maplinks[key.lower()] = (key, aliases, direction, link)
# remove duplicates
@ -380,8 +380,7 @@ class MapNode:
# we need to search for exits in all directions since some
# may have been removed since last sync
linkobjs = {exi.db_key.lower(): exi
for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
linkobjs = {exi.db_key.lower(): exi for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
# figure out if the topology changed between grid and map (will always
# build all exits first run)
@ -411,16 +410,19 @@ class MapNode:
raise RuntimeError(err)
linkobjs[key.lower()] = exi
prot = maplinks[key.lower()][3].prototype
tclass = prot['typeclass']
tclass = (f' ({tclass})'
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit'
else '')
tclass = prot["typeclass"]
tclass = (
f" ({tclass})"
if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit"
else ""
)
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
# apply prototypes to catch any changes
for key, linkobj in linkobjs.items():
spawner.batch_update_objects_with_prototype(
maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False)
maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False
)
def unspawn(self):
"""
@ -466,8 +468,9 @@ class TransitionMapNode(MapNode):
actual rooms (`#`) on the other map (NOT to the `T`s)!
"""
symbol = 'T'
display_symbol = ' '
symbol = "T"
display_symbol = " "
# X,Y,Z coordinates of target node
taget_map_xyz = (None, None, None)
@ -477,10 +480,13 @@ class TransitionMapNode(MapNode):
the exit to this node (since the prototype is None, this node itself will not be built).
"""
if any(True for coord in self.target_map_xyz if coord in (None, 'unset')):
raise MapParserError(f"(Z={self.xymap.Z}) has not defined its "
"`.target_map_xyz` property. It must point "
"to another valid xymap (Z coordinate).", self)
if any(True for coord in self.target_map_xyz if coord in (None, "unset")):
raise MapParserError(
f"(Z={self.xymap.Z}) has not defined its "
"`.target_map_xyz` property. It must point "
"to another valid xymap (Z coordinate).",
self,
)
return self.target_map_xyz
@ -548,6 +554,7 @@ class MapLink:
`node.get_exit_spawn_name(direction)`
"""
# symbol for identifying this link on the map
symbol = ""
# if `None`, use .symbol
@ -661,7 +668,9 @@ class MapLink:
return None, 0, None
raise MapParserError(
f"was connected to from the direction {start_direction}, but "
"is not set up to link in that direction.", self)
"is not set up to link in that direction.",
self,
)
# note that if `get_direction` returns an unknown direction, this will be equivalent
# to pointing to an empty location, which makes sense
@ -674,8 +683,7 @@ class MapLink:
next_target = self.at_empty_target(start_direction, end_direction)
if not next_target:
raise MapParserError(
f"points to empty space in the direction {end_direction}!", self)
raise MapParserError(f"points to empty space in the direction {end_direction}!", self)
_weight += self.get_weight(start_direction, _weight)
if _steps is None:
@ -688,13 +696,16 @@ class MapLink:
return (
next_target,
_weight / max(1, _linklen) if self.average_long_link_weights else _weight,
_steps
_steps,
)
else:
# we hit another link. Progress recursively.
return next_target.traverse(
REVERSE_DIRECTIONS.get(end_direction, end_direction),
_weight=_weight, _linklen=_linklen + 1, _steps=_steps)
_weight=_weight,
_linklen=_linklen + 1,
_steps=_steps,
)
def get_linked_neighbors(self, directions=None):
"""
@ -720,8 +731,7 @@ class MapLink:
# there is is something there, we need to check if it is either
# a map node or a link connecting in our direction
node_or_link = xygrid[end_x][end_y]
if (node_or_link.multilink
or node_or_link.get_direction(direction)):
if node_or_link.multilink or node_or_link.get_direction(direction):
links[direction] = node_or_link
return links
@ -845,7 +855,8 @@ class SmartRerouterMapLink(MapLink):
for direction in unhandled_links_copy:
if REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
directions[direction] = REVERSE_DIRECTIONS[
unhandled_links.pop(unhandled_links.index(direction))]
unhandled_links.pop(unhandled_links.index(direction))
]
# check if we have any non-cross-through paths left to handle
n_unhandled = len(unhandled_links)
@ -856,7 +867,8 @@ class SmartRerouterMapLink(MapLink):
if n_unhandled != 2:
links = ", ".join(unhandled_links)
raise MapParserError(
f"cannot determine how to connect in/out directions {links}.", self)
f"cannot determine how to connect in/out directions {links}.", self
)
directions[unhandled_links[0]] = unhandled_links[1]
directions[unhandled_links[1]] = unhandled_links[0]
@ -865,6 +877,7 @@ class SmartRerouterMapLink(MapLink):
return self.directions.get(start_direction)
class SmartTeleporterMapLink(MapLink):
"""
The teleport link works by connecting to nowhere - and will then continue
@ -889,10 +902,11 @@ class SmartTeleporterMapLink(MapLink):
-#-t-# - invalid, only one connected link is allowed.
"""
symbol = 't'
symbol = "t"
# usually invisible
display_symbol = ' '
direction_name = 'teleport'
display_symbol = " "
direction_name = "teleport"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -932,7 +946,9 @@ class SmartTeleporterMapLink(MapLink):
if len(found_teleporters) > 1:
raise MapParserError(
"found too many matching teleporters (must be exactly one more): "
f"{found_teleporters}", self)
f"{found_teleporters}",
self,
)
other_teleporter = found_teleporters[0]
# link the two so we don't need to scan again for the other one
@ -952,9 +968,10 @@ class SmartTeleporterMapLink(MapLink):
if len(neighbors) != 1:
raise MapParserError("must have exactly one link connected to it.", self)
direction, link = next(iter(neighbors.items()))
if hasattr(link, 'node_index'):
raise MapParserError("can only connect to a Link. Found {link} in "
"direction {direction}.", self)
if hasattr(link, "node_index"):
raise MapParserError(
"can only connect to a Link. Found {link} in " "direction {direction}.", self
)
# the string 'teleport' will not be understood by the traverser, leading to
# this being interpreted as an empty target and the `at_empty_target`
# hook firing when trying to traverse this link.
@ -962,12 +979,10 @@ class SmartTeleporterMapLink(MapLink):
if start_direction == direction_name:
# called while traversing another teleport
# - we must make sure we can always access/leave the teleport.
self.directions = {direction_name: direction,
direction: direction_name}
self.directions = {direction_name: direction, direction: direction_name}
else:
# called while traversing a normal link
self.directions = {start_direction: direction_name,
direction_name: direction}
self.directions = {start_direction: direction_name, direction_name: direction}
return self.directions.get(start_direction)
@ -1016,6 +1031,7 @@ class SmartMapLink(MapLink):
# |
"""
multilink = True
def get_direction(self, start_direction):
@ -1027,8 +1043,11 @@ class SmartMapLink(MapLink):
if not self.directions:
directions = {}
neighbors = self.get_linked_neighbors()
nodes = [direction for direction, neighbor in neighbors.items()
if hasattr(neighbor, 'node_index')]
nodes = [
direction
for direction, neighbor in neighbors.items()
if hasattr(neighbor, "node_index")
]
if len(nodes) == 2:
# prefer link to these two nodes
@ -1042,7 +1061,9 @@ class SmartMapLink(MapLink):
"must have exactly two connections - either directly to "
"two nodes or connecting directly to one node and with exactly one other "
f"link direction. The neighbor(s) in directions {list(neighbors.keys())} do "
"not fulfill these criteria.", self)
"not fulfill these criteria.",
self,
)
self.directions = directions
return self.directions.get(start_direction)
@ -1071,20 +1092,26 @@ class InvisibleSmartMapLink(SmartMapLink):
# this allows for normal movement directions even if the invisible-node
# is marked with a different symbol.
direction_aliases = {
'n': 'n', 'ne': 'ne', 'e': 'e', 'se': 'se',
's': 's', 'sw': 'sw', 'w': 'w', 'nw': 'nw'
"n": "n",
"ne": "ne",
"e": "e",
"se": "se",
"s": "s",
"sw": "sw",
"w": "w",
"nw": "nw",
}
# replace current link position with what the smart links "should" look like
display_symbol_aliases = {
(('n', 's'), ('s', 'n')): '|',
(('n', 's'),): 'v',
(('s', 'n')): '^',
(('e', 'w'), ('w', 'e')): '-',
(('e', 'w'),): '>',
(('w', 'e'),): '<',
(('nw', 'se'), ('sw', 'ne')): '\\',
(('ne', 'sw'), ('sw', 'ne')): '/',
(("n", "s"), ("s", "n")): "|",
(("n", "s"),): "v",
(("s", "n")): "^",
(("e", "w"), ("w", "e")): "-",
(("e", "w"),): ">",
(("w", "e"),): "<",
(("nw", "se"), ("sw", "ne")): "\\",
(("ne", "sw"), ("sw", "ne")): "/",
}
def get_display_symbol(self):
@ -1098,12 +1125,10 @@ class InvisibleSmartMapLink(SmartMapLink):
"""
if not hasattr(self, "_cached_display_symbol"):
legend = self.xymap.legend
default_symbol = (
self.symbol if self.display_symbol is None else self.display_symbol)
default_symbol = self.symbol if self.display_symbol is None else self.display_symbol
self._cached_display_symbol = default_symbol
dirtuple = tuple((key, self.directions[key])
for key in sorted(self.directions.keys()))
dirtuple = tuple((key, self.directions[key]) for key in sorted(self.directions.keys()))
replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol)
@ -1112,16 +1137,19 @@ class InvisibleSmartMapLink(SmartMapLink):
if node_or_link_class:
# initiate class in the current location and run get_display_symbol
# to get what it would show.
self._cached_display_symbol = (
node_or_link_class(self.x, self.y, self.Z).get_display_symbol())
self._cached_display_symbol = node_or_link_class(
self.x, self.y, self.Z
).get_display_symbol()
return self._cached_display_symbol
# ----------------------------------
# Default nodes and link classes
class BasicMapNode(MapNode):
"""A map node/room"""
symbol = "#"
prototype = "xyz_room"
@ -1129,20 +1157,25 @@ class BasicMapNode(MapNode):
class InterruptMapNode(MapNode):
"""A point of interest node/room. Pathfinder will ignore but auto-stepper will
stop here if passing through. Beginner-Tutorial from here is fine."""
symbol = "I"
display_symbol = "#"
interrupt_path = True
prototype = "xyz_room"
class MapTransitionNode(TransitionMapNode):
"""Transition-target node to other map. This is not actually spawned in-game."""
symbol = "T"
display_symbol = " "
prototype = None # important to leave None!
target_map_xyz = (None, None, None) # must be set manually
class NSMapLink(MapLink):
"""Two-way, North-South link"""
symbol = "|"
display_symbol = "||"
directions = {"n": "s", "s": "n"}
@ -1151,6 +1184,7 @@ class NSMapLink(MapLink):
class EWMapLink(MapLink):
"""Two-way, East-West link"""
symbol = "-"
directions = {"e": "w", "w": "e"}
prototype = "xyz_exit"
@ -1158,6 +1192,7 @@ class EWMapLink(MapLink):
class NESWMapLink(MapLink):
"""Two-way, NorthWest-SouthWest link"""
symbol = "/"
directions = {"ne": "sw", "sw": "ne"}
prototype = "xyz_exit"
@ -1165,6 +1200,7 @@ class NESWMapLink(MapLink):
class SENWMapLink(MapLink):
"""Two-way, SouthEast-NorthWest link"""
symbol = "\\"
directions = {"se": "nw", "nw": "se"}
prototype = "xyz_exit"
@ -1172,22 +1208,23 @@ class SENWMapLink(MapLink):
class PlusMapLink(MapLink):
"""Two-way, crossing North-South and East-West links"""
symbol = "+"
directions = {"s": "n", "n": "s",
"e": "w", "w": "e"}
directions = {"s": "n", "n": "s", "e": "w", "w": "e"}
prototype = "xyz_exit"
class CrossMapLink(MapLink):
"""Two-way, crossing NorthEast-SouthWest and SouthEast-NorthWest links"""
symbol = "x"
directions = {"ne": "sw", "sw": "ne",
"se": "nw", "nw": "se"}
directions = {"ne": "sw", "sw": "ne", "se": "nw", "nw": "se"}
prototype = "xyz_exit"
class NSOneWayMapLink(MapLink):
"""One-way North-South link"""
symbol = "v"
directions = {"n": "s"}
prototype = "xyz_exit"
@ -1195,6 +1232,7 @@ class NSOneWayMapLink(MapLink):
class SNOneWayMapLink(MapLink):
"""One-way South-North link"""
symbol = "^"
directions = {"s": "n"}
prototype = "xyz_exit"
@ -1202,6 +1240,7 @@ class SNOneWayMapLink(MapLink):
class EWOneWayMapLink(MapLink):
"""One-way East-West link"""
symbol = "<"
directions = {"e": "w"}
prototype = "xyz_exit"
@ -1209,6 +1248,7 @@ class EWOneWayMapLink(MapLink):
class WEOneWayMapLink(MapLink):
"""One-way West-East link"""
symbol = ">"
directions = {"w": "e"}
prototype = "xyz_exit"
@ -1216,21 +1256,39 @@ class WEOneWayMapLink(MapLink):
class UpMapLink(SmartMapLink):
"""Up direction. Note that this stays on the same z-coord so it's a 'fake' up."""
symbol = 'u'
symbol = "u"
# 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,
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
direction_aliases = {
"n": symbol,
"ne": symbol,
"e": symbol,
"se": symbol,
"s": symbol,
"sw": symbol,
"w": symbol,
"nw": symbol,
}
spawn_aliases = {direction: ("up", "u") for direction in direction_aliases}
prototype = "xyz_exit"
class DownMapLink(UpMapLink):
"""Down direction. Note that this stays on the same z-coord, so it's a 'fake' down."""
symbol = 'd'
symbol = "d"
# 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}
direction_aliases = {
"n": symbol,
"ne": symbol,
"e": symbol,
"se": symbol,
"s": symbol,
"sw": symbol,
"w": symbol,
"nw": symbol,
}
spawn_aliases = {direction: ("down", "d") for direction in direction_aliases}
prototype = "xyz_exit"
@ -1238,6 +1296,7 @@ class DownMapLink(UpMapLink):
class InterruptMapLink(InvisibleSmartMapLink):
"""A (still passable) link. Pathfinder will treat this as any link, but auto-stepper
will always abort before crossing this link - so this must be crossed manually."""
symbol = "i"
interrupt_path = True
prototype = "xyz_exit"
@ -1250,14 +1309,24 @@ class BlockedMapLink(InvisibleSmartMapLink):
link in any paths.
"""
symbol = 'b'
weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL,
's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL}
symbol = "b"
weights = {
"n": BIGVAL,
"ne": BIGVAL,
"e": BIGVAL,
"se": BIGVAL,
"s": BIGVAL,
"sw": BIGVAL,
"w": BIGVAL,
"nw": BIGVAL,
}
prototype = "xyz_exit"
class RouterMapLink(SmartRerouterMapLink):
"""A link that connects other links to build 'knees', pass-throughs etc."""
symbol = "o"
@ -1266,7 +1335,8 @@ class TeleporterMapLink(SmartTeleporterMapLink):
Teleporter links. Must appear in pairs on the same xy map. To make it one-way, add additional
one-way link out of the teleporter on one side.
"""
symbol = 't'
symbol = "t"
# all map components; used as base if not overridden
@ -1291,5 +1361,5 @@ LEGEND = {
"d": DownMapLink,
"b": BlockedMapLink,
"i": InterruptMapLink,
't': TeleporterMapLink,
"t": TeleporterMapLink,
}

View file

@ -28,6 +28,7 @@ class XYZGrid(DefaultScript):
Main grid class. This organizes the Maps based on their name/Z-coordinate.
"""
def at_script_creation(self):
"""
What we store persistently is data used to create each map (the legends, names etc)
@ -88,7 +89,7 @@ class XYZGrid(DefaultScript):
"""
return XYZRoom.objects.filter_xyz(xyz=xyz, **kwargs)
def get_exit(self, xyz, name='north', **kwargs):
def get_exit(self, xyz, name="north", **kwargs):
"""
Get one or more exit object at coordinate.
@ -102,7 +103,7 @@ class XYZGrid(DefaultScript):
Queryset: A queryset of XYZExit(s) found.
"""
kwargs['db_key'] = name
kwargs["db_key"] = name
return XYZExit.objects.filter_xyz_exit(xyz=xyz, **kwargs)
def maps_from_module(self, module_path):
@ -127,7 +128,7 @@ class XYZGrid(DefaultScript):
if not mapdata:
self.log(f"Could not find or load map from {module_path}.")
return
mapdata['module_path'] = module_path
mapdata["module_path"] = module_path
return map_data_list
def reload(self):
@ -154,9 +155,9 @@ class XYZGrid(DefaultScript):
# we reload the map from module
new_mapdata = loaded_mapdata.get(zcoord)
if not new_mapdata:
if 'module_path' in old_mapdata:
for mapdata in self.maps_from_module(old_mapdata['module_path']):
loaded_mapdata[mapdata['zcoord']] = mapdata
if "module_path" in old_mapdata:
for mapdata in self.maps_from_module(old_mapdata["module_path"]):
loaded_mapdata[mapdata["zcoord"]] = mapdata
else:
# nowhere to reload from - use what we have
loaded_mapdata[zcoord] = old_mapdata
@ -198,7 +199,7 @@ class XYZGrid(DefaultScript):
"""
for mapdata in mapdatas:
zcoord = mapdata.get('zcoord')
zcoord = mapdata.get("zcoord")
if not zcoord:
raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.")
@ -220,7 +221,7 @@ class XYZGrid(DefaultScript):
if remove_objects:
# we can't batch-delete because we want to run the .delete
# method that also wipes exits and moves content to save locations
for xyzroom in XYZRoom.objects.filter_xyz(xyz=('*', '*', zcoord)):
for xyzroom in XYZRoom.objects.filter_xyz(xyz=("*", "*", zcoord)):
xyzroom.delete()
self.reload()
@ -234,7 +235,7 @@ class XYZGrid(DefaultScript):
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
super().delete()
def spawn(self, xyz=('*', '*', '*'), directions=None):
def spawn(self, xyz=("*", "*", "*"), directions=None):
"""
Create/recreate/update the in-game grid based on the stored Maps or for a specific Map
or coordinate.
@ -255,7 +256,7 @@ class XYZGrid(DefaultScript):
"""
x, y, z = xyz
wildcard = '*'
wildcard = "*"
if z == wildcard:
xymaps = self.grid
@ -293,8 +294,10 @@ def get_xyzgrid(print_errors=True):
xyzgrid.reload()
return xyzgrid
elif len(xyzgrid) > 1:
("Warning: More than one XYZGrid instances were found. This is an error and "
"only the first one will be used. Delete the other one(s) manually.")
(
"Warning: More than one XYZGrid instances were found. This is an error and "
"only the first one will be used. Delete the other one(s) manually."
)
xyzgrid = xyzgrid[0]
try:
if not xyzgrid.ndb.loaded:

View file

@ -31,7 +31,8 @@ class XYZManager(ObjectManager):
efficiently querying the room in the database based on XY coordinates.
"""
def filter_xyz(self, xyz=('*', '*', '*'), **kwargs):
def filter_xyz(self, xyz=("*", "*", "*"), **kwargs):
"""
Filter queryset based on XYZ position on the grid. The Z-position is the name of the XYMap
Set a coordinate to `'*'` to act as a wildcard (setting all coords to `*` will thus find
@ -49,23 +50,28 @@ class XYZManager(ObjectManager):
"""
x, y, z = xyz
wildcard = '*'
wildcard = "*"
return (
self
.filter_family(**kwargs)
self.filter_family(**kwargs)
.filter(
Q() if x == wildcard
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
Q()
if x == wildcard
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
)
.filter(
Q() if y == wildcard
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
Q()
if y == wildcard
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
)
.filter(
Q() if z == wildcard
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
Q()
if z == wildcard
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
)
)
def get_xyz(self, xyz=(0, 0, 'map'), **kwargs):
def get_xyz(self, xyz=(0, 0, "map"), **kwargs):
"""
Always return a single matched entity directly. This accepts no `*`-wildcards.
This will also find children of XYZRooms on the given coordinates.
@ -93,8 +99,9 @@ class XYZManager(ObjectManager):
# error - mimic default get() behavior but with a little more info
x, y, z = xyz
inp = (f"Query: xyz=({x},{y},{z}), " +
",".join(f"{key}={val}" for key, val in kwargs.items()))
inp = f"Query: xyz=({x},{y},{z}), " + ",".join(
f"{key}={val}" for key, val in kwargs.items()
)
if ncount > 1:
raise self.model.MultipleObjectsReturned(inp)
else:
@ -108,8 +115,7 @@ class XYZExitManager(XYZManager):
"""
def filter_xyz_exit(self, xyz=('*', '*', '*'),
xyz_destination=('*', '*', '*'), **kwargs):
def filter_xyz_exit(self, xyz=("*", "*", "*"), xyz_destination=("*", "*", "*"), **kwargs):
"""
Used by exits (objects with a source and -destination property).
Find all exits out of a source or to a particular destination. This will also find
@ -138,32 +144,43 @@ class XYZExitManager(XYZManager):
"""
x, y, z = xyz
xdest, ydest, zdest = xyz_destination
wildcard = '*'
wildcard = "*"
return (
self
.filter_family(**kwargs)
self.filter_family(**kwargs)
.filter(
Q() if x == wildcard
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
Q()
if x == wildcard
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
)
.filter(
Q() if y == wildcard
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
Q()
if y == wildcard
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
)
.filter(
Q() if z == wildcard
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
Q()
if z == wildcard
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
)
.filter(
Q() if xdest == wildcard
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY))
Q()
if xdest == wildcard
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
)
.filter(
Q() if ydest == wildcard
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY))
Q()
if ydest == wildcard
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY)
)
.filter(
Q() if zdest == wildcard
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY))
Q()
if zdest == wildcard
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY)
)
)
def get_xyz_exit(self, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'), **kwargs):
def get_xyz_exit(self, xyz=(0, 0, "map"), xyz_destination=(0, 0, "map"), **kwargs):
"""
Used by exits (objects with a source and -destination property). Get a single
exit. All source/destination coordinates (as well as the map's name) are required.
@ -199,8 +216,7 @@ class XYZExitManager(XYZManager):
try:
return (
self
.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
self.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
.filter(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
.filter(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
.filter(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
@ -209,10 +225,12 @@ class XYZExitManager(XYZManager):
.get(**kwargs)
)
except self.model.DoesNotExist:
inp = (f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," +
",".join(f"{key}={val}" for key, val in kwargs.items()))
raise self.model.DoesNotExist(f"{self.model.__name__} "
f"matching query {inp} does not exist.")
inp = f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," + ",".join(
f"{key}={val}" for key, val in kwargs.items()
)
raise self.model.DoesNotExist(
f"{self.model.__name__} " f"matching query {inp} does not exist."
)
class XYZRoom(DefaultRoom):
@ -244,10 +262,10 @@ class XYZRoom(DefaultRoom):
# default settings for map visualization
map_display = True
map_mode = 'nodes' # or 'scan'
map_mode = "nodes" # or 'scan'
map_visual_range = 2
map_character_symbol = "|g@|n"
map_align = 'c'
map_align = "c"
map_target_path_style = "|y{display_symbol}|n"
map_fill_all = True
map_separator_char = "|x~|n"
@ -267,8 +285,10 @@ class XYZRoom(DefaultRoom):
z = self.tags.get(category=MAP_Z_TAG_CATEGORY, return_list=False)
if x is None or y is None or z is None:
# don't cache unfinished coordinate (probably tags have not finished saving)
return tuple(int(coord) if coord is not None and coord.isdigit() else coord
for coord in (x, y, z))
return tuple(
int(coord) if coord is not None and coord.isdigit() else coord
for coord in (x, y, z)
)
# cache result, convert to correct types (tags are strings)
self._xyz = tuple(int(coord) if coord.isdigit() else coord for coord in (x, y, z))
@ -290,7 +310,7 @@ class XYZRoom(DefaultRoom):
return self._xymap
@classmethod
def create(cls, key, account=None, xyz=(0, 0, 'map'), **kwargs):
def create(cls, key, account=None, xyz=(0, 0, "map"), **kwargs):
"""
Creation method aware of XYZ coordinates.
@ -316,14 +336,18 @@ class XYZRoom(DefaultRoom):
try:
x, y, z = xyz
except ValueError:
return None, [f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
"coordinate of ints/strings."]
return None, [
f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
"coordinate of ints/strings."
]
existing_query = cls.objects.filter_xyz(xyz=(x, y, z))
if existing_query.exists():
existing_room = existing_query.first()
return None, [f"XYRoom XYZ=({x},{y},{z}) already exists "
f"(existing room is named '{existing_room.db_key}')!"]
return None, [
f"XYRoom XYZ=({x},{y},{z}) already exists "
f"(existing room is named '{existing_room.db_key}')!"
]
tags = (
(str(x), MAP_X_TAG_CATEGORY),
@ -410,26 +434,29 @@ class XYZRoom(DefaultRoom):
xyz = self.xyz
xymap = self.xyzgrid.get_map(xyz[2])
if xymap and kwargs.get('map_display', xymap.options.get("map_display", self.map_display)):
if xymap and kwargs.get("map_display", xymap.options.get("map_display", self.map_display)):
# show the near-area map.
map_character_symbol = kwargs.get(
'map_character_symbol',
xymap.options.get("map_character_symbol", self.map_character_symbol))
"map_character_symbol",
xymap.options.get("map_character_symbol", self.map_character_symbol),
)
map_visual_range = kwargs.get(
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range))
map_mode = kwargs.get(
"map_mode", xymap.options.get("map_mode", self.map_mode))
map_align = kwargs.get(
"map_align", xymap.options.get("map_align", self.map_align))
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range)
)
map_mode = kwargs.get("map_mode", xymap.options.get("map_mode", self.map_mode))
map_align = kwargs.get("map_align", xymap.options.get("map_align", self.map_align))
map_target_path_style = kwargs.get(
"map_target_path_style",
xymap.options.get("map_target_path_style", self.map_target_path_style))
xymap.options.get("map_target_path_style", self.map_target_path_style),
)
map_area_client = kwargs.get(
"map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all))
"map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all)
)
map_separator_char = kwargs.get(
"map_separator_char",
xymap.options.get("map_separator_char", self.map_separator_char))
xymap.options.get("map_separator_char", self.map_separator_char),
)
client_width, _ = looker.sessions.get()[0].get_client_size()
@ -438,15 +465,14 @@ class XYZRoom(DefaultRoom):
if map_area_client:
display_width = client_width
else:
display_width = max(map_width,
max(len(line) for line in room_desc.split("\n")))
display_width = max(map_width, max(len(line) for line in room_desc.split("\n")))
# align map
map_indent = 0
sep_width = display_width
if map_align == 'r':
if map_align == "r":
map_indent = max(0, display_width - map_width)
elif map_align == 'c':
elif map_align == "c":
map_indent = max(0, (display_width - map_width) // 2)
# data set by the goto/path-command, for displaying the shortest path
@ -462,7 +488,7 @@ class XYZRoom(DefaultRoom):
target_path_style=map_target_path_style,
character=map_character_symbol,
max_size=(display_width, None),
indent=map_indent
indent=map_indent,
)
sep = map_separator_char * sep_width
map_display = f"{sep}|n\n{map_display}\n{sep}"
@ -523,8 +549,16 @@ class XYZExit(DefaultExit):
return self._xyz_destination
@classmethod
def create(cls, key, account=None, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'),
location=None, destination=None, **kwargs):
def create(
cls,
key,
account=None,
xyz=(0, 0, "map"),
xyz_destination=(0, 0, "map"),
location=None,
destination=None,
**kwargs,
):
"""
Creation method aware of coordinates.
@ -559,23 +593,33 @@ class XYZExit(DefaultExit):
return None, ["XYExit.create need either `xyz=(X,Y,Z)` coordinate or a `location`."]
else:
source = XYZRoom.objects.get_xyz(xyz=(x, y, z))
tags.extend(((str(x), MAP_X_TAG_CATEGORY),
(str(y), MAP_Y_TAG_CATEGORY),
(str(z), MAP_Z_TAG_CATEGORY)))
tags.extend(
(
(str(x), MAP_X_TAG_CATEGORY),
(str(y), MAP_Y_TAG_CATEGORY),
(str(z), MAP_Z_TAG_CATEGORY),
)
)
if destination:
dest = destination
else:
try:
xdest, ydest, zdest = xyz_destination
except ValueError:
return None, ["XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
"or a `destination`."]
return None, [
"XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
"or a `destination`."
]
else:
dest = XYZRoom.objects.get_xyz(xyz=(xdest, ydest, zdest))
tags.extend(((str(xdest), MAP_XDEST_TAG_CATEGORY),
(str(ydest), MAP_YDEST_TAG_CATEGORY),
(str(zdest), MAP_ZDEST_TAG_CATEGORY)))
tags.extend(
(
(str(xdest), MAP_XDEST_TAG_CATEGORY),
(str(ydest), MAP_YDEST_TAG_CATEGORY),
(str(zdest), MAP_ZDEST_TAG_CATEGORY),
)
)
return DefaultExit.create(
key, source, dest,
account=account, tags=tags, typeclass=cls, **kwargs)
key, source, dest, account=account, tags=tags, typeclass=cls, **kwargs
)