Merge pull request #2904 from Henddher/issue_2695

fix: xyzgrid spawn doesn't call at_object_creation
This commit is contained in:
Griatch 2022-10-19 22:29:35 +02:00 committed by GitHub
commit 4ba7df7f61
3 changed files with 105 additions and 24 deletions

View file

@ -53,28 +53,31 @@ Exits: northeast and east
(then go back to your mygame/ folder) (then go back to your mygame/ folder)
This will install all optional requirements of Evennia. This will install all optional requirements of Evennia.
2. Import and add the `evennia.contrib.commands.XYZGridCmdSet` to the 2. Import and [add] the `evennia.contrib.grid.xyzgrid.commands.XYZGridCmdSet` to the
`CharacterCmdset` cmdset in `mygame/commands.default_cmds.py`. Reload `CharacterCmdset` cmdset in `mygame/commands.default_cmds.py`. Reload
the server. This makes the `map`, `goto/path` and the modified `teleport` and the server. This makes the `map`, `goto/path` and the modified `teleport` and
`open` commands available in-game. `open` commands available in-game.
[add]: docs/source/Command-Sets.md#defining-command-sets
3. Edit `mygame/server/conf/settings.py` and add 3. Edit `mygame/server/conf/settings.py` and add
EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.launchcmd.xyzcommand' EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.grid.xyzgrid.launchcmd.xyzcommand'
PROTOTYPE_MODULES += ['evennia.contrib.grid.xyzgrid.prototypes']
and
PROTOTYPE_MODULES += [evennia.contrib.grid.xyzgrid.prototypes]
This will add the new ability to enter `evennia xyzgrid <option>` on the This will add the new ability to enter `evennia xyzgrid <option>` on the
command line. It will also make the `xyz_room` and `xyz_exit` prototypes command line. It will also make the `xyz_room` and `xyz_exit` prototypes
available for use as prototype-parents when spawning the grid. available for use as prototype-parents when spawning the grid.
4. Run `evennia xyzgrid help` for available options. 4. Run `evennia xyzgrid help` for available options.
5. (Optional): By default, the xyzgrid will only spawn module-based 5. (Optional): By default, the xyzgrid will only spawn module-based
[prototypes](../Components/Prototypes.md). This is an optimization and usually makes sense [prototypes]. This is an optimization and usually makes sense
since the grid is entirely defined outside the game anyway. If you want to since the grid is entirely defined outside the game anyway. If you want to
also make use of in-game (db-) created prototypes, add also make use of in-game (db-) created prototypes, add
`XYZGRID_USE_DB_PROTOTYPES = True` to settings. `XYZGRID_USE_DB_PROTOTYPES = True` to settings.
[prototypes]: ../Components/Prototypes.md
## Overview ## Overview

View file

@ -3,6 +3,7 @@
Tests for the XYZgrid system. Tests for the XYZgrid system.
""" """
from unittest import mock
from random import randint from random import randint
from parameterized import parameterized from parameterized import parameterized
@ -1415,3 +1416,78 @@ class TestBuildExampleGrid(BaseEvenniaTest):
self.assertTrue(room2a.db.desc.startswith("This is the entrance to")) self.assertTrue(room2a.db.desc.startswith("This is the entrance to"))
self.assertEqual(room2b.key, "North-west corner of the atrium") self.assertEqual(room2b.key, "North-west corner of the atrium")
self.assertTrue(room2b.db.desc.startswith("Sunlight sifts down")) self.assertTrue(room2b.db.desc.startswith("Sunlight sifts down"))
mock_room_callbacks = mock.MagicMock()
mock_exit_callbacks = mock.MagicMock()
class TestXyzRoom(xyzroom.XYZRoom):
def at_object_creation(self):
mock_room_callbacks.at_object_creation()
class TestXyzExit(xyzroom.XYZExit):
def at_object_creation(self):
mock_exit_callbacks.at_object_creation()
MAP_DATA = {
"map": """
+ 0 1
0 #-#
+ 0 1
""",
"zcoord": "map1",
"prototypes": {
("*", "*"): {
"key": "room",
"desc": "A room.",
"prototype_parent": "xyz_room",
},
("*", "*", "*"): {
"desc": "A passage.",
"prototype_parent": "xyz_exit",
}
},
"options": {
"map_visual_range": 1,
"map_mode": "scan",
}
}
class TestCallbacks(BaseEvenniaTest):
def setUp(self):
super().setUp()
mock_room_callbacks.reset_mock()
mock_exit_callbacks.reset_mock()
def setup_grid(self, map_data):
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
def _log(msg):
print(msg)
self.grid.log = _log
self.map_data = map_data
self.grid.add_maps(map_data)
def tearDown(self):
super().tearDown()
self.grid.delete()
def test_typeclassed_xyzroom_and_xyzexit_with_at_object_creation_are_called(self):
map_data = dict(MAP_DATA)
for prototype_key, prototype_value in map_data["prototypes"].items():
if len(prototype_key) == 2:
prototype_value["typeclass"] = "evennia.contrib.grid.xyzgrid.tests.TestXyzRoom"
if len(prototype_key) == 3:
prototype_value["typeclass"] = "evennia.contrib.grid.xyzgrid.tests.TestXyzExit"
self.setup_grid(map_data)
self.grid.spawn()
# Two rooms and 2 exits, Each one should have gotten one `at_object_creation` callback.
self.assertEqual(mock_room_callbacks.at_object_creation.mock_calls, [mock.call(), mock.call()])
self.assertEqual(mock_exit_callbacks.at_object_creation.mock_calls, [mock.call(), mock.call()])

View file

@ -22,8 +22,9 @@ from collections import defaultdict
from django.core import exceptions as django_exceptions from django.core import exceptions as django_exceptions
from evennia.prototypes import spawner from evennia.prototypes import spawner
from evennia.utils.utils import class_from_module
from .utils import MAPSCAN, REVERSE_DIRECTIONS, MapParserError, BIGVAL from .utils import MAPSCAN, REVERSE_DIRECTIONS, MapParserError, BIGVAL, MapError
NodeTypeclass = None NodeTypeclass = None
ExitTypeclass = None ExitTypeclass = None
@ -316,13 +317,14 @@ class MapNode:
try: try:
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz) nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
except django_exceptions.ObjectDoesNotExist: except django_exceptions.ObjectDoesNotExist:
# create a new entity with proper coordinates etc # create a new entity, using the specified typeclass (if there's one) and
tclass = self.prototype["typeclass"] # with proper coordinates etc
tclass = ( typeclass = self.prototype.get("typeclass")
f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else "" if typeclass is None:
) raise MapError(f"The prototype {self.prototype} for this node has no 'typeclass' key.", self)
self.log(f" spawning room at xyz={xyz}{tclass}") self.log(f" spawning room at xyz={xyz} ({typeclass})")
nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz) Typeclass = class_from_module(typeclass)
nodeobj, err = Typeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
if err: if err:
raise RuntimeError(err) raise RuntimeError(err)
else: else:
@ -400,7 +402,14 @@ class MapNode:
continue continue
exitnode = self.links[direction] exitnode = self.links[direction]
exi, err = ExitTypeclass.create( prot = maplinks[key.lower()][3].prototype
typeclass = prot.get("typeclass")
if typeclass is None:
raise MapError(f"The prototype {self.prototype} for this node has no 'typeclass' key.", self)
self.log(f" spawning/updating exit xyz={xyz}, direction={key} ({typeclass})")
Typeclass = class_from_module(typeclass)
exi, err = Typeclass.create(
key, key,
xyz=xyz, xyz=xyz,
xyz_destination=exitnode.get_spawn_xyz(), xyz_destination=exitnode.get_spawn_xyz(),
@ -408,15 +417,8 @@ class MapNode:
) )
if err: if err:
raise RuntimeError(err) raise RuntimeError(err)
linkobjs[key.lower()] = exi 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 ""
)
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
# apply prototypes to catch any changes # apply prototypes to catch any changes
for key, linkobj in linkobjs.items(): for key, linkobj in linkobjs.items():