Finished the procedural dungon tutorial

This commit is contained in:
Griatch 2024-03-24 15:17:37 +01:00
parent 8085aa30db
commit b5571fb5fd
2 changed files with 799 additions and 75 deletions

View file

@ -78,7 +78,7 @@ _EXIT_GRID_SHIFT = {
# --------------------------------------------------
# Dungeon orchestrator and room / exits
# Dungeon branch and room / exits
# --------------------------------------------------
@ -92,17 +92,9 @@ class EvAdventureDungeonRoom(EvAdventureRoom):
allow_death = AttributeProperty(True, autocreate=False)
# dungeon generation attributes; set when room is created
back_exit = AttributeProperty(None, autocreate=False)
dungeon_orchestrator = AttributeProperty(None, autocreate=False)
dungeon_branch = AttributeProperty(None, autocreate=False)
xy_coords = AttributeProperty(None, autocreate=False)
@property
def is_room_clear(self):
return not bool(self.tags.get("not_clear", category="dungeon_room"))
def clear_room(self):
self.tags.remove("not_clear", category="dungeon_room")
def at_object_creation(self):
"""
Set the `not_clear` tag on the room. This is removed when the room is
@ -113,6 +105,13 @@ class EvAdventureDungeonRoom(EvAdventureRoom):
"""
self.tags.add("not_clear", category="dungeon_room")
def clear_room(self):
self.tags.remove("not_clear", category="dungeon_room")
@property
def is_room_clear(self):
return not bool(self.tags.get("not_clear", category="dungeon_room"))
def get_display_footer(self, looker, **kwargs):
"""
@ -140,17 +139,17 @@ class EvAdventureDungeonExit(DefaultExit):
def at_traverse(self, traversing_object, target_location, **kwargs):
"""
Called when traversing. `target_location` will be None if the
target was not yet created. It checks the current location to get the
dungeon-orchestrator in use.
Called when traversing. `target_location` will be pointing back to ourselves if the target
was not yet created. It checks the current location to get the dungeon-branch in use.
"""
dungeon_branch = self.location.db.dungeon_branch
if target_location == self.location:
self.destination = target_location = self.location.db.dungeon_orchestrator.new_room(
# destination points back to us - create a new room
self.destination = target_location = dungeon_branch.new_room(
self
)
if self.id in self.location.dungeon_orchestrator.unvisited_exits:
self.location.dungeon_orchestrator.unvisited_exits.remove(self.id)
dungeon_branch.register_exit_traversed(self)
super().at_traverse(traversing_object, target_location, **kwargs)
@ -162,14 +161,14 @@ class EvAdventureDungeonExit(DefaultExit):
traversing_object.msg("You can't get through this way yet!")
def room_generator(dungeon_orchestrator, depth, coords):
def room_generator(dungeon_branch, depth, coords):
"""
Plugin room generator
This default one returns the same empty room.
Args:
dungeon_orchestrator (EvAdventureDungeonOrchestrator): The current orchestrator.
dungeon_branch (EvAdventureDungeonBranch): The current dungeon branch.
depth (int): The 'depth' of the dungeon (radial distance from start room) this
new room will be placed at.
coords (tuple): The `(x,y)` coords that the new room will be created at.
@ -198,15 +197,15 @@ def room_generator(dungeon_orchestrator, depth, coords):
attributes=(
("desc", desc),
("xy_coords", coords),
("dungeon_orchestrator", dungeon_orchestrator),
("dungeon_branch", dungeon_branch),
),
)
return new_room
class EvAdventureDungeonOrchestrator(DefaultScript):
class EvAdventureDungeonBranch(DefaultScript):
"""
One script is created per dungeon 'branch' created. The orchestrator is
One script is created per dungeon 'branch' created. The branch is
responsible for determining what is created next when a character enters an
exit within the dungeon.
@ -218,15 +217,14 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
rooms = AttributeProperty(list())
unvisited_exits = AttributeProperty(list())
highest_depth = AttributeProperty(0)
last_updated = AttributeProperty(datetime.utcnow())
# the room-generator function; copied from the same-name value on the start-room when the
# orchestrator is first created
# branch is first created
room_generator = AttributeProperty(None, autocreate=False)
# (x,y): room coordinates used up by orchestrator
# (x,y): room coordinates used up by branch
xy_grid = AttributeProperty(dict())
start_room = AttributeProperty(None, autocreate=False)
@ -254,7 +252,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
def delete(self):
"""
Clean up the entire dungeon along with the orchestrator.
Clean up the entire dungeon along with the branch.
"""
# first secure all characters in this branch back to the start room
@ -274,7 +272,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
rooms = search.search_object_by_tag(self.key, category="dungeon_room")
for room in rooms:
room.delete()
# finally delete the orchestrator itself
# finally delete the branch itself
super().delete()
def new_room(self, from_exit):
@ -294,7 +292,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
new_x, new_y = (x + dx, y + dy)
# the dungeon's depth acts as a measure of the current difficulty level. This is the radial
# distance from the (0, 0) (the entrance). The Orchestrator also tracks the highest
# distance from the (0, 0) (the entrance). The branch also tracks the highest
# depth achieved.
depth = int(sqrt(new_x**2 + new_y**2))
@ -348,8 +346,6 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
self.xy_grid[target_coord] = None
break
self.highest_depth = max(self.highest_depth, depth)
return new_room
@ -378,27 +374,54 @@ class EvAdventureDungeonStartRoomExit(DefaultExit):
def at_traverse(self, traversing_object, target_location, **kwargs):
"""
When traversing create a new orchestrator if one is not already assigned.
When traversing create a new branch if one is not already assigned.
"""
if target_location == self.location:
# make a global orchestrator script for this dungeon branch
# make a global branch script for this dungeon branch
self.location.room_generator
dungeon_orchestrator = create.create_script(
EvAdventureDungeonOrchestrator,
key=f"dungeon_orchestrator_{self.key}_{datetime.utcnow()}",
dungeon_branch = create.create_script(
EvAdventureDungeonBranch,
key=f"dungeon_branch_{self.key}_{datetime.utcnow()}",
attributes=(
("start_room", self.location),
("room_generator", self.location.room_generator),
),
)
self.destination = target_location = dungeon_orchestrator.new_room(self)
self.destination = target_location = dungeon_branch.new_room(self)
# make sure to tag character when entering so we can find them again later
traversing_object.tags.add(dungeon_orchestrator.key, category="dungeon_character")
traversing_object.tags.add(dungeon_branch.key, category="dungeon_character")
super().at_traverse(traversing_object, target_location, **kwargs)
class EvAdventureDungeonBranchDeleter(DefaultScript):
"""
Cleanup script. After some time a dungeon branch will 'collapse', forcing all players in it
back to the start room.
"""
# set at creation time when the start room is created
branch_max_life = AttributeProperty(0, autocreate=False)
def at_script_creation(self):
self.key = "evadventure_dungeon_branch_deleter"
def at_repeat(self):
"""
Go through all dungeon-branchs and find which ones are too old.
"""
max_dt = timedelta(seconds=self.branch_max_life)
max_allowed_date = datetime.utcnow() - max_dt
for branch in EvAdventureDungeonBranch.objects.all():
if branch.last_updated < max_allowed_date:
# branch is too old; tell it to clean up and delete itself
branch.delete()
class EvAdventureStartRoomResetter(DefaultScript):
"""
Simple ticker-script. Introduces a chance of the room's exits cycling every interval.
@ -419,33 +442,6 @@ class EvAdventureStartRoomResetter(DefaultScript):
exi.reset_exit()
class EvAdventureDungeonBranchDeleter(DefaultScript):
"""
Cleanup script. After some time a dungeon branch will 'collapse', forcing all players in it
back to the start room.
"""
# set at creation time when the start room is created
branch_max_life = AttributeProperty(0, autocreate=False)
def at_script_creation(self):
self.key = "evadventure_dungeon_branch_deleter"
def at_repeat(self):
"""
Go through all dungeon-orchestrators and find which ones are too old.
"""
max_dt = timedelta(seconds=self.branch_max_life)
max_allowed_date = datetime.utcnow() - max_dt
for orchestrator in EvAdventureDungeonOrchestrator.objects.all():
if orchestrator.last_updated < max_allowed_date:
# orchestrator is too old; tell it to clean up and delete itself
orchestrator.delete()
class EvAdventureDungeonStartRoom(EvAdventureDungeonRoom):
"""
The start room is the only permanent part of the dungeon. Exits leading from this room (except