Run black reformatter on code
This commit is contained in:
parent
4582eb4085
commit
bd3e31bf3c
178 changed files with 4511 additions and 3385 deletions
|
|
@ -2,4 +2,3 @@
|
|||
Contribs related to moving in and manipulating the game world and grid.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue