diff --git a/evennia/contrib/tutorials/evadventure/dungeon.py b/evennia/contrib/tutorials/evadventure/dungeon.py index af30168e05..717133b678 100644 --- a/evennia/contrib/tutorials/evadventure/dungeon.py +++ b/evennia/contrib/tutorials/evadventure/dungeon.py @@ -75,7 +75,12 @@ class EvAdventureDungeonExit(DefaultExit): """ - dungeon_orchestrator = AttributeProperty(None, autocreate=False) + def at_object_creation(self): + """ + We want to block progressing forward unless the room is clear. + + """ + self.locks.add("traverse:not tag(not_clear, dungeon_room)") def at_traverse(self, traversing_object, target_location, **kwargs): """ @@ -84,7 +89,9 @@ class EvAdventureDungeonExit(DefaultExit): """ if target_location == self.location: - self.destination = target_location = self.dungeon_orchestrator.new_room(self) + self.destination = target_location = self.location.db.dungeon_orchestrator.new_room( + self + ) super().at_traverse(traversing_object, target_location, **kwargs) @@ -129,14 +136,16 @@ class EvAdventureDungeonOrchestrator(DefaultScript): ) self.unvisited_exits.append(out_exit.id) - def _generate_room(self, depth, coords): + def _generate_dungeon_room(self, depth, coords): # TODO - determine what type of room to create here based on location and depth room_typeclass = EvAdventureDungeonRoom new_room = create.create_object( room_typeclass, key="Dungeon room", - tags=((self.key, "dungeon_room"),), - attributes=(("xy_coord", coords, "dungeon_xygrid"),), + attributes=( + ("xy_coords", coords, "dungeon_xygrid"), + ("dungeon_orchestrator", self), + ), ) return new_room @@ -170,7 +179,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript): # depth achieved. depth = int(sqrt(new_x**2 + new_y**2)) - new_room = self._generate_room(depth, (new_x, new_y)) + new_room = self._generate_dungeon_room(depth, (new_x, new_y)) self.xy_grid[(new_x, new_y)] = new_room @@ -182,7 +191,14 @@ class EvAdventureDungeonOrchestrator(DefaultScript): aliases=_EXIT_ALIASES.get(back_exit_key, ()), location=new_room, destination=from_exit.location, - attributes=(("desc", "A dark passage."),), + attributes=( + ( + "desc", + "A dark passage.", + ), + ), + # we default to allowing back-tracking (also used for fleeing) + locks=("traverse: true()",), ) # figure out what other exits should be here, if any @@ -205,8 +221,8 @@ class EvAdventureDungeonOrchestrator(DefaultScript): direction = available_directions.pop(0) dx, dy = _EXIT_GRID_SHIFT[direction] target_coord = (new_x + dx, new_y + dy) - if target_coord not in self.xy_grid: - # no room there - make an exit to it + if target_coord not in self.xy_grid and target_coord != (0, 0): + # no room there (and not back to start room) - make an exit to it self.create_out_exit(new_room, direction) # we create this to avoid other rooms linking here, but don't create the # room yet @@ -215,6 +231,8 @@ class EvAdventureDungeonOrchestrator(DefaultScript): self.highest_depth = max(self.highest_depth, depth) + return new_room + # -------------------------------------------------- # Start room @@ -232,16 +250,11 @@ class EvAdventureStartRoomExit(DefaultExit): """ - # we store the orchestrator like this since we don't want to actually manipulate it, - # but only use the reference to know when to create a new room - dungeon_orchestrator = AttributeProperty(None, autocreate=False) - def reset_exit(self): """ Flush the exit, so next traversal creates a new dungeon branch. """ - self.dungeon_orchestrator = None self.destination = self.location def at_traverse(self, traversing_object, target_location, **kwargs): @@ -249,12 +262,13 @@ class EvAdventureStartRoomExit(DefaultExit): When traversing create a new orchestrator if one is not already assigned. """ - if target_location == self.location or self.dungeon_orchestrator is None: - self.dungeon_orchestrator = create.create_script( + if target_location == self.location: + # make a global orchestrator script for this dungeon branch + dungeon_orchestrator = create.create_script( EvAdventureDungeonOrchestrator, key=f"dungeon_orchestrator_{self.key}_{datetime.utcnow()}", ) - self.destination = target_location = self.dungeon_orchestrator.new_room(self) + self.destination = target_location = dungeon_orchestrator.new_room(self) super().at_traverse(traversing_object, target_location, **kwargs) diff --git a/evennia/contrib/tutorials/evadventure/rooms.py b/evennia/contrib/tutorials/evadventure/rooms.py index 241b063633..f7be9c705a 100644 --- a/evennia/contrib/tutorials/evadventure/rooms.py +++ b/evennia/contrib/tutorials/evadventure/rooms.py @@ -5,7 +5,7 @@ EvAdventure rooms. """ -from evennia import DefaultRoom +from evennia import AttributeProperty, DefaultRoom, TagProperty class EvAdventureRoom(DefaultRoom): @@ -37,3 +37,29 @@ class EvAdventureDungeonRoom(EvAdventureRoom): allow_combat = True allow_death = True + + # dungeon generation attributes; set when room is created + back_exit = AttributeProperty(None, autocreate=False) + dungeon_orchestrator = AttributeProperty(None, autocreate=False) + xy_coords = AttributeProperty(None, autocreate=False) + + def at_object_creation(self): + """ + Set the `not_clear` tag on the room. This is removed when the room is + 'cleared', whatever that means for each room. + + We put this here rather than in the room-creation code so we can override + easier (for example we may want an empty room which auto-clears). + + """ + self.tags.add("not_clear") + + def get_display_footer(self, looker, **kwargs): + """ + Show if the room is 'cleared' or not as part of its description. + + """ + if self.tags.get("not_clear", "dungeon_room"): + # this tag is cleared when the room is resolved, whatever that means. + return "|rThe path forwards is blocked!|n" + return "" diff --git a/evennia/contrib/tutorials/evadventure/tests/test_dungeon.py b/evennia/contrib/tutorials/evadventure/tests/test_dungeon.py index 4b04743ad5..ec0f94947f 100644 --- a/evennia/contrib/tutorials/evadventure/tests/test_dungeon.py +++ b/evennia/contrib/tutorials/evadventure/tests/test_dungeon.py @@ -56,7 +56,7 @@ class TestDungeon(EvAdventureMixin, BaseEvenniaTest): if exi.key == direction: # by setting target to old-location we trigger the # special behavior of this Exit type - exi.at_traverse(self.character, old_location) + exi.at_traverse(self.character, exi.destination) break return self.character.location @@ -74,7 +74,7 @@ class TestDungeon(EvAdventureMixin, BaseEvenniaTest): self.assertTrue(inherits_from(new_room_north, EvAdventureDungeonRoom)) # check if Orchestrator was created - orchestrator = self.start_north.scripts.get(dungeon.EvAdventureDungeonOrchestrator) + orchestrator = new_room_north.db.dungeon_orchestrator self.assertTrue(bool(orchestrator)) self.assertTrue(orchestrator.key.startswith("dungeon_orchestrator_north_")) diff --git a/evennia/objects/models.py b/evennia/objects/models.py index e5efa66c58..a282bafe0f 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -14,15 +14,15 @@ the database object. Like everything else, they can be accessed transparently through the decorating TypeClass. """ from collections import defaultdict + from django.conf import settings -from django.db import models from django.core.exceptions import ObjectDoesNotExist from django.core.validators import validate_comma_separated_integer_list - -from evennia.typeclasses.models import TypedObject +from django.db import models from evennia.objects.manager import ObjectDBManager +from evennia.typeclasses.models import TypedObject from evennia.utils import logger -from evennia.utils.utils import make_iter, dbref, lazy_property +from evennia.utils.utils import dbref, lazy_property, make_iter class ContentsHandler: