Fix dungeon behavior
This commit is contained in:
parent
6a4b14fb83
commit
a471f0fd86
5 changed files with 314 additions and 74 deletions
|
|
@ -20,16 +20,12 @@ You can also build/rebuild individiaul #CODE blocks in the `batchcode/interactiv
|
||||||
# this is loaded at the top of every #CODE block
|
# this is loaded at the top of every #CODE block
|
||||||
|
|
||||||
from evennia import DefaultExit, create_object, search_object
|
from evennia import DefaultExit, create_object, search_object
|
||||||
from evennia.contrib.tutorials import evadventure
|
|
||||||
from evennia.contrib.tutorials.evadventure import npcs
|
from evennia.contrib.tutorials.evadventure import npcs
|
||||||
from evennia.contrib.tutorials.evadventure.combat_turnbased import EvAdventureCombatHandler
|
from evennia.contrib.tutorials.evadventure.dungeon import (
|
||||||
from evennia.contrib.tutorials.evadventure.objects import (
|
EvAdventureDungeonStartRoom,
|
||||||
EvAdventureConsumable,
|
EvAdventureDungeonStartRoomExit,
|
||||||
EvAdventureObject,
|
|
||||||
EvAdventureObjectFiller,
|
|
||||||
EvAdventureRunestone,
|
|
||||||
EvAdventureWeapon,
|
|
||||||
)
|
)
|
||||||
|
from evennia.contrib.tutorials.evadventure.objects import EvAdventureWeapon
|
||||||
from evennia.contrib.tutorials.evadventure.rooms import EvAdventurePvPRoom, EvAdventureRoom
|
from evennia.contrib.tutorials.evadventure.rooms import EvAdventurePvPRoom, EvAdventureRoom
|
||||||
|
|
||||||
# CODE
|
# CODE
|
||||||
|
|
@ -66,7 +62,7 @@ create_object(
|
||||||
# with a static enemy
|
# with a static enemy
|
||||||
|
|
||||||
combat_room = create_object(EvAdventurePvPRoom, key="Combat Arena", aliases=("evtechdemo#01",))
|
combat_room = create_object(EvAdventurePvPRoom, key="Combat Arena", aliases=("evtechdemo#01",))
|
||||||
# link to/back to hub
|
# link to/back to/from hub
|
||||||
hub_room = search_object("evtechdemo#00")[0]
|
hub_room = search_object("evtechdemo#00")[0]
|
||||||
create_object(
|
create_object(
|
||||||
DefaultExit, key="combat test", aliases=("combat",), location=hub_room, destination=combat_room
|
DefaultExit, key="combat test", aliases=("combat",), location=hub_room, destination=combat_room
|
||||||
|
|
@ -84,3 +80,64 @@ combat_room_enemy = create_object(
|
||||||
)
|
)
|
||||||
weapon_stick = create_object(EvAdventureWeapon, key="stick", attributes=(("damage_roll", "1d2"),))
|
weapon_stick = create_object(EvAdventureWeapon, key="stick", attributes=(("damage_roll", "1d2"),))
|
||||||
combat_room_enemy.weapon = weapon_stick
|
combat_room_enemy.weapon = weapon_stick
|
||||||
|
|
||||||
|
|
||||||
|
# CODE
|
||||||
|
|
||||||
|
# A dungeon start room for testing the dynamic dungeon generation.
|
||||||
|
|
||||||
|
dungeon_start_room = create_object(
|
||||||
|
EvAdventureDungeonStartRoom,
|
||||||
|
key="Dungeon start room",
|
||||||
|
aliases=("evtechdemo#02",),
|
||||||
|
attributes=(("desc", "A central room, with dark exits leading to mysterious fates."),),
|
||||||
|
)
|
||||||
|
# link to/back to/from hub
|
||||||
|
hub_room = search_object("evtechdemo#00")[0]
|
||||||
|
create_object(
|
||||||
|
DefaultExit,
|
||||||
|
key="dungeon test",
|
||||||
|
aliases=("dungeon",),
|
||||||
|
location=hub_room,
|
||||||
|
destination=dungeon_start_room,
|
||||||
|
)
|
||||||
|
create_object(
|
||||||
|
DefaultExit,
|
||||||
|
key="Back to Hub",
|
||||||
|
aliases=("back", "hub"),
|
||||||
|
location=dungeon_start_room,
|
||||||
|
destination=hub_room,
|
||||||
|
)
|
||||||
|
|
||||||
|
# add special exits out of the dungeon start room.
|
||||||
|
# These must have one of the 8 cardinal directions
|
||||||
|
# we point these exits back to the same location, which
|
||||||
|
# is what the system will use to trigger generating a new room
|
||||||
|
create_object(
|
||||||
|
EvAdventureDungeonStartRoomExit,
|
||||||
|
key="north",
|
||||||
|
aliases=("n",),
|
||||||
|
location=dungeon_start_room,
|
||||||
|
destination=dungeon_start_room,
|
||||||
|
)
|
||||||
|
create_object(
|
||||||
|
EvAdventureDungeonStartRoomExit,
|
||||||
|
key="east",
|
||||||
|
aliases=("e",),
|
||||||
|
location=dungeon_start_room,
|
||||||
|
destination=dungeon_start_room,
|
||||||
|
)
|
||||||
|
create_object(
|
||||||
|
EvAdventureDungeonStartRoomExit,
|
||||||
|
key="south",
|
||||||
|
aliases=("s",),
|
||||||
|
location=dungeon_start_room,
|
||||||
|
destination=dungeon_start_room,
|
||||||
|
)
|
||||||
|
create_object(
|
||||||
|
EvAdventureDungeonStartRoomExit,
|
||||||
|
key="west",
|
||||||
|
aliases=("w",),
|
||||||
|
location=dungeon_start_room,
|
||||||
|
destination=dungeon_start_room,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,14 @@ decided to go in that direction. Each room is tagged with the specific 'instance
|
||||||
id of that particular branch of dungon. When no characters remain in the branch,
|
id of that particular branch of dungon. When no characters remain in the branch,
|
||||||
the branch is deleted.
|
the branch is deleted.
|
||||||
|
|
||||||
|
Each room in the dungeon starts with a Tag `not_clear`; while this is set, all exits out
|
||||||
|
of the room (not the one they came from) is blocked. When whatever problem the room
|
||||||
|
offers has been solved (such as a puzzle or a battle), the tag is removed and the player(s)
|
||||||
|
can choose which exit to leave through.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
from random import randint, random, shuffle
|
from random import randint, random, shuffle
|
||||||
|
|
||||||
|
|
@ -25,12 +30,21 @@ from evennia.typeclasses.attributes import AttributeProperty
|
||||||
from evennia.utils import create, search
|
from evennia.utils import create, search
|
||||||
from evennia.utils.utils import inherits_from
|
from evennia.utils.utils import inherits_from
|
||||||
|
|
||||||
from .rooms import EvAdventureDungeonRoom
|
from .rooms import EvAdventureRoom
|
||||||
|
|
||||||
# aliases for cardinal directions
|
# aliases for cardinal directions
|
||||||
|
_AVAILABLE_DIRECTIONS = [
|
||||||
|
"north",
|
||||||
|
"east",
|
||||||
|
"south",
|
||||||
|
"west",
|
||||||
|
# commented out to make the dungeon simpler to navigate
|
||||||
|
# "northeast", "southeast", "southwest", "northwest",
|
||||||
|
]
|
||||||
|
|
||||||
_EXIT_ALIASES = {
|
_EXIT_ALIASES = {
|
||||||
"north": ("n",),
|
"north": ("n",),
|
||||||
"east": ("w",),
|
"east": ("e",),
|
||||||
"south": ("s",),
|
"south": ("s",),
|
||||||
"west": ("w",),
|
"west": ("w",),
|
||||||
"northeast": ("ne",),
|
"northeast": ("ne",),
|
||||||
|
|
@ -64,10 +78,53 @@ _EXIT_GRID_SHIFT = {
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
# Dungeon orchestrator and rooms
|
# Dungeon orchestrator and room / exits
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureDungeonRoom(EvAdventureRoom):
|
||||||
|
"""
|
||||||
|
Dangerous dungeon room.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@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
|
||||||
|
'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", category="dungeon_room")
|
||||||
|
|
||||||
|
def get_display_footer(self, looker, **kwargs):
|
||||||
|
"""
|
||||||
|
Show if the room is 'cleared' or not as part of its description.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.is_room_clear:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return "|rThe path forwards is blocked!|n"
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureDungeonExit(DefaultExit):
|
class EvAdventureDungeonExit(DefaultExit):
|
||||||
"""
|
"""
|
||||||
Dungeon exit. This will not create the target room until it's traversed.
|
Dungeon exit. This will not create the target room until it's traversed.
|
||||||
|
|
@ -80,7 +137,7 @@ class EvAdventureDungeonExit(DefaultExit):
|
||||||
We want to block progressing forward unless the room is clear.
|
We want to block progressing forward unless the room is clear.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.locks.add("traverse:not tag(not_clear, dungeon_room)")
|
self.locks.add("traverse:not objloctag(not_clear, dungeon_room)")
|
||||||
|
|
||||||
def at_traverse(self, traversing_object, target_location, **kwargs):
|
def at_traverse(self, traversing_object, target_location, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -92,8 +149,60 @@ class EvAdventureDungeonExit(DefaultExit):
|
||||||
self.destination = target_location = self.location.db.dungeon_orchestrator.new_room(
|
self.destination = target_location = self.location.db.dungeon_orchestrator.new_room(
|
||||||
self
|
self
|
||||||
)
|
)
|
||||||
|
if self.id in self.location.dungeon_orchestrator.unvisited_exits:
|
||||||
|
self.location.dungeon_orchestrator.unvisited_exits.remove(self.id)
|
||||||
|
|
||||||
super().at_traverse(traversing_object, target_location, **kwargs)
|
super().at_traverse(traversing_object, target_location, **kwargs)
|
||||||
|
|
||||||
|
def at_failed_traverse(self, traversing_object, **kwargs):
|
||||||
|
"""
|
||||||
|
Called when failing to traverse.
|
||||||
|
|
||||||
|
"""
|
||||||
|
traversing_object.msg("You can't get through this way yet!")
|
||||||
|
|
||||||
|
|
||||||
|
def room_generator(dungeon_orchestrator, depth, coords):
|
||||||
|
"""
|
||||||
|
Plugin room generator
|
||||||
|
|
||||||
|
This default one returns the same empty room.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dungeon_orchestrator (EvAdventureDungeonOrchestrator): The current orchestrator.
|
||||||
|
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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
room_typeclass = EvAdventureDungeonRoom
|
||||||
|
|
||||||
|
# simple map of depth to name and desc of room
|
||||||
|
name_depth_map = {
|
||||||
|
1: ("Water-logged passage", "This earth-walled passage is dripping of water."),
|
||||||
|
2: ("Passage with roots", "Roots are pushing through the earth walls."),
|
||||||
|
3: ("Hardened clay passage", "The walls of this passage is of hardened clay."),
|
||||||
|
4: ("Clay with stones", "This passage has clay with pieces of stone embedded."),
|
||||||
|
5: ("Stone passage", "Walls are crumbling stone, with roots passing through it."),
|
||||||
|
6: ("Stone hallway", "Walls are cut from rough stone."),
|
||||||
|
7: ("Stone rooms", "A stone room, built from crude and heavy blocks."),
|
||||||
|
8: ("Granite hall", "The walls are of well-fitted granite blocks."),
|
||||||
|
9: ("Marble passages", "The walls are blank and shiny marble."),
|
||||||
|
10: ("Furnished rooms", "The marble walls have tapestries and furnishings."),
|
||||||
|
}
|
||||||
|
key, desc = name_depth_map.get(depth, ("Dark rooms", "There is very dark here."))
|
||||||
|
|
||||||
|
new_room = create.create_object(
|
||||||
|
room_typeclass,
|
||||||
|
key=key,
|
||||||
|
attributes=(
|
||||||
|
("desc", desc),
|
||||||
|
("xy_coords", coords),
|
||||||
|
("dungeon_orchestrator", dungeon_orchestrator),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return new_room
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureDungeonOrchestrator(DefaultScript):
|
class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
"""
|
"""
|
||||||
|
|
@ -104,15 +213,22 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# this determines how branching the dungeon will be
|
# this determines how branching the dungeon will be
|
||||||
max_unexplored_exits = 5
|
max_unexplored_exits = 2
|
||||||
max_new_exits_per_room = 3
|
max_new_exits_per_room = 2
|
||||||
|
|
||||||
rooms = AttributeProperty(list())
|
rooms = AttributeProperty(list())
|
||||||
unvisited_exits = AttributeProperty(list())
|
unvisited_exits = AttributeProperty(list())
|
||||||
highest_depth = AttributeProperty(0)
|
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
|
||||||
|
room_generator = AttributeProperty(None, autocreate=False)
|
||||||
|
|
||||||
# (x,y): room coordinates used up by orchestrator
|
# (x,y): room coordinates used up by orchestrator
|
||||||
xy_grid = AttributeProperty(dict())
|
xy_grid = AttributeProperty(dict())
|
||||||
|
start_room = AttributeProperty(None, autocreate=False)
|
||||||
|
|
||||||
def register_exit_traversed(self, exit):
|
def register_exit_traversed(self, exit):
|
||||||
"""
|
"""
|
||||||
|
|
@ -136,27 +252,29 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
)
|
)
|
||||||
self.unvisited_exits.append(out_exit.id)
|
self.unvisited_exits.append(out_exit.id)
|
||||||
|
|
||||||
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",
|
|
||||||
attributes=(
|
|
||||||
("xy_coords", coords, "dungeon_xygrid"),
|
|
||||||
("dungeon_orchestrator", self),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return new_room
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""
|
"""
|
||||||
Clean up the entire dungeon along with the orchestrator.
|
Clean up the entire dungeon along with the orchestrator.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# first secure all characters in this branch back to the start room
|
||||||
|
characters = search.search_object_by_tag(self.key, category="dungeon_character")
|
||||||
|
start_room = self.start_room
|
||||||
|
for character in characters:
|
||||||
|
start_room.msg_contents(
|
||||||
|
"Suddenly someone stumbles out of a dark exit, covered in dust!"
|
||||||
|
)
|
||||||
|
character.location = start_room
|
||||||
|
character.msg(
|
||||||
|
"|rAfter a long time of silence, the room suddenly rumbles and then collapses! "
|
||||||
|
"All turns dark ...|n\n\nThen you realize you are back where you started."
|
||||||
|
)
|
||||||
|
character.tags.remove(self.key, category="dungeon_character")
|
||||||
|
# next delete all rooms in the dungeon (this will also delete exits)
|
||||||
rooms = search.search_object_by_tag(self.key, category="dungeon_room")
|
rooms = search.search_object_by_tag(self.key, category="dungeon_room")
|
||||||
for room in rooms:
|
for room in rooms:
|
||||||
room.delete()
|
room.delete()
|
||||||
|
# finally delete the orchestrator itself
|
||||||
super().delete()
|
super().delete()
|
||||||
|
|
||||||
def new_room(self, from_exit):
|
def new_room(self, from_exit):
|
||||||
|
|
@ -167,11 +285,12 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
from_exit (Exit): The exit leading to this new room.
|
from_exit (Exit): The exit leading to this new room.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.last_updated = datetime.utcnow()
|
||||||
# figure out coordinate of old room and figure out what coord the
|
# figure out coordinate of old room and figure out what coord the
|
||||||
# new one would get
|
# new one would get
|
||||||
source_location = from_exit.location
|
source_location = from_exit.location
|
||||||
x, y = source_location.attributes.get("xy_coord", category="dungeon_xygrid", default=(0, 0))
|
x, y = source_location.attributes.get("xy_coords", default=(0, 0))
|
||||||
dx, dy = _EXIT_GRID_SHIFT.get(from_exit.key, (1, 0))
|
dx, dy = _EXIT_GRID_SHIFT.get(from_exit.key, (0, 1))
|
||||||
new_x, new_y = (x + dx, y + dy)
|
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
|
# the dungeon's depth acts as a measure of the current difficulty level. This is the radial
|
||||||
|
|
@ -179,7 +298,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
# depth achieved.
|
# depth achieved.
|
||||||
depth = int(sqrt(new_x**2 + new_y**2))
|
depth = int(sqrt(new_x**2 + new_y**2))
|
||||||
|
|
||||||
new_room = self._generate_dungeon_room(depth, (new_x, new_y))
|
new_room = self.room_generator(self, depth, (new_x, new_y))
|
||||||
|
|
||||||
self.xy_grid[(new_x, new_y)] = new_room
|
self.xy_grid[(new_x, new_y)] = new_room
|
||||||
|
|
||||||
|
|
@ -210,7 +329,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
if n_exits > 1:
|
if n_exits > 1:
|
||||||
n_exits = randint(1, n_exits)
|
n_exits = randint(1, n_exits)
|
||||||
available_directions = [
|
available_directions = [
|
||||||
direction for direction in _EXIT_ALIASES if direction != back_exit_key
|
direction for direction in _AVAILABLE_DIRECTIONS if direction != back_exit_key
|
||||||
]
|
]
|
||||||
# randomize order of exits
|
# randomize order of exits
|
||||||
shuffle(available_directions)
|
shuffle(available_directions)
|
||||||
|
|
@ -239,7 +358,7 @@ class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureStartRoomExit(DefaultExit):
|
class EvAdventureDungeonStartRoomExit(DefaultExit):
|
||||||
"""
|
"""
|
||||||
Traversing this exit will either lead to an existing dungeon branch or create
|
Traversing this exit will either lead to an existing dungeon branch or create
|
||||||
a new one.
|
a new one.
|
||||||
|
|
@ -264,11 +383,18 @@ class EvAdventureStartRoomExit(DefaultExit):
|
||||||
"""
|
"""
|
||||||
if target_location == self.location:
|
if target_location == self.location:
|
||||||
# make a global orchestrator script for this dungeon branch
|
# make a global orchestrator script for this dungeon branch
|
||||||
|
self.location.room_generator
|
||||||
dungeon_orchestrator = create.create_script(
|
dungeon_orchestrator = create.create_script(
|
||||||
EvAdventureDungeonOrchestrator,
|
EvAdventureDungeonOrchestrator,
|
||||||
key=f"dungeon_orchestrator_{self.key}_{datetime.utcnow()}",
|
key=f"dungeon_orchestrator_{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_orchestrator.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")
|
||||||
|
|
||||||
super().at_traverse(traversing_object, target_location, **kwargs)
|
super().at_traverse(traversing_object, target_location, **kwargs)
|
||||||
|
|
||||||
|
|
@ -280,7 +406,7 @@ class EvAdventureStartRoomResetter(DefaultScript):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
self.key = "evadventure_startroom_resetter"
|
self.key = "evadventure_dungeon_startroom_resetter"
|
||||||
|
|
||||||
def at_repeat(self):
|
def at_repeat(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -289,21 +415,63 @@ class EvAdventureStartRoomResetter(DefaultScript):
|
||||||
"""
|
"""
|
||||||
room = self.obj
|
room = self.obj
|
||||||
for exi in room.exits:
|
for exi in room.exits:
|
||||||
if inherits_from(exi, EvAdventureStartRoomExit) and random() < 0.5:
|
if inherits_from(exi, EvAdventureDungeonStartRoomExit) and random() < 0.5:
|
||||||
exi.reset_exit()
|
exi.reset_exit()
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureDungeonRoomStart(EvAdventureDungeonRoom):
|
class EvAdventureDungeonBranchDeleter(DefaultScript):
|
||||||
"""
|
"""
|
||||||
Exits leading out of the start room, (except one leading outside) will lead to a different
|
Cleanup script. After some time a dungeon branch will 'collapse', forcing all players in it
|
||||||
dungeon-branch, and after a certain time, the given exit will instead spawn a new branch. This
|
back to the start room.
|
||||||
room is responsible for cycling these exits regularly.
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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
|
||||||
|
one leading back outside) each create/links to a separate dungeon branch/instance.
|
||||||
|
|
||||||
|
- A script will reset each exit every 5 mins; after that time, entering the exit will spawn
|
||||||
|
a new branch-instance instead of leading to the one before.
|
||||||
|
- Another script will check age of branch instance every hour; once an instance has been
|
||||||
|
inactive for a week, it will 'collapse', forcing everyone inside back to the start room.
|
||||||
|
|
||||||
The actual exits should be created in the build script.
|
The actual exits should be created in the build script.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
recycle_time = 5 * 60 # seconds
|
recycle_time = 60 * 5 # 5 mins
|
||||||
|
branch_check_time = 60 * 60 # one hour
|
||||||
|
branch_max_life = 60 * 60 * 24 * 7 # 1 week
|
||||||
|
|
||||||
|
# allow for a custom room_generator function
|
||||||
|
room_generator = AttributeProperty(room_generator, autocreate=False)
|
||||||
|
|
||||||
|
def get_display_footer(self, looker, **kwargs):
|
||||||
|
return (
|
||||||
|
"|yYou sense that if you want to team up, "
|
||||||
|
"you must all pick the same path from here ... or you'll quickly get separated.|n"
|
||||||
|
)
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
# want to set the script interval on creation time, so we use create_script with obj=self
|
# want to set the script interval on creation time, so we use create_script with obj=self
|
||||||
|
|
@ -311,3 +479,17 @@ class EvAdventureDungeonRoomStart(EvAdventureDungeonRoom):
|
||||||
create.create_script(
|
create.create_script(
|
||||||
EvAdventureStartRoomResetter, obj=self, interval=self.recycle_time, autostart=True
|
EvAdventureStartRoomResetter, obj=self, interval=self.recycle_time, autostart=True
|
||||||
)
|
)
|
||||||
|
create.create_script(
|
||||||
|
EvAdventureDungeonBranchDeleter,
|
||||||
|
obj=self,
|
||||||
|
interval=self.branch_check_time,
|
||||||
|
autostart=True,
|
||||||
|
attributes=(("branch_max_life", self.branch_max_life),),
|
||||||
|
)
|
||||||
|
|
||||||
|
def at_object_receive(self, obj, source_location, **kwargs):
|
||||||
|
"""
|
||||||
|
Make sure to clean the dungeon branch-tag from characters when leaving a dungeon branch.
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj.tags.remove(category="dungeon_character")
|
||||||
|
|
|
||||||
|
|
@ -28,38 +28,9 @@ class EvAdventurePvPRoom(DefaultRoom):
|
||||||
allow_combat = True
|
allow_combat = True
|
||||||
allow_pvp = True
|
allow_pvp = True
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureDungeonRoom(EvAdventureRoom):
|
|
||||||
"""
|
|
||||||
Dangerous dungeon room.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
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):
|
def get_display_footer(self, looker, **kwargs):
|
||||||
"""
|
"""
|
||||||
Show if the room is 'cleared' or not as part of its description.
|
Show if the room is 'cleared' or not as part of its description.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.tags.get("not_clear", "dungeon_room"):
|
return "|yNon-lethal PvP combat is allowed here!|n"
|
||||||
# this tag is cleared when the room is resolved, whatever that means.
|
|
||||||
return "|rThe path forwards is blocked!|n"
|
|
||||||
return ""
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class TestDungeon(EvAdventureMixin, BaseEvenniaTest):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super().setUp()
|
super().setUp()
|
||||||
droomclass = dungeon.EvAdventureDungeonRoomStart
|
droomclass = dungeon.EvAdventureDungeonStartRoom
|
||||||
droomclass.recycle_time = 0 # disable the tick
|
droomclass.recycle_time = 0 # disable the tick
|
||||||
|
|
||||||
self.start_room = create_object(droomclass, key="bottom of well")
|
self.start_room = create_object(droomclass, key="bottom of well")
|
||||||
|
|
@ -36,14 +36,14 @@ class TestDungeon(EvAdventureMixin, BaseEvenniaTest):
|
||||||
self.start_room.scripts.get("evadventure_startroom_resetter")[0].interval, -1
|
self.start_room.scripts.get("evadventure_startroom_resetter")[0].interval, -1
|
||||||
)
|
)
|
||||||
self.start_north = create_object(
|
self.start_north = create_object(
|
||||||
dungeon.EvAdventureStartRoomExit,
|
dungeon.EvAdventureDungeonStartRoomExit,
|
||||||
key="north",
|
key="north",
|
||||||
location=self.start_room,
|
location=self.start_room,
|
||||||
destination=self.start_room,
|
destination=self.start_room,
|
||||||
)
|
)
|
||||||
self.start_north
|
self.start_north
|
||||||
self.start_south = create_object(
|
self.start_south = create_object(
|
||||||
dungeon.EvAdventureStartRoomExit,
|
dungeon.EvAdventureDungeonStartRoomExit,
|
||||||
key="south",
|
key="south",
|
||||||
location=self.start_room,
|
location=self.start_room,
|
||||||
destination=self.start_room,
|
destination=self.start_room,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ a certain object type.
|
||||||
|
|
||||||
|
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import utils
|
from evennia.utils import utils
|
||||||
|
|
||||||
|
|
@ -466,6 +467,7 @@ def tag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
category.
|
category.
|
||||||
If accessing_obj has the ".obj" property (such as is the case for
|
If accessing_obj has the ".obj" property (such as is the case for
|
||||||
a command), then accessing_obj.obj is used instead.
|
a command), then accessing_obj.obj is used instead.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if hasattr(accessing_obj, "obj"):
|
if hasattr(accessing_obj, "obj"):
|
||||||
accessing_obj = accessing_obj.obj
|
accessing_obj = accessing_obj.obj
|
||||||
|
|
@ -474,6 +476,34 @@ def tag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
return bool(accessing_obj.tags.get(tagkey, category=category))
|
return bool(accessing_obj.tags.get(tagkey, category=category))
|
||||||
|
|
||||||
|
|
||||||
|
def objtag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Usage:
|
||||||
|
objtag(tagkey)
|
||||||
|
objtag(tagkey, category):
|
||||||
|
|
||||||
|
Only true if `accessed_obj` has the given tag and optional category.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return tag(accessed_obj, None, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def objloctag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Usage:
|
||||||
|
objloctag(tagkey)
|
||||||
|
objloctag(tagkey, category):
|
||||||
|
|
||||||
|
Only true if `accessed_obj.location` has the given tag and optional category.
|
||||||
|
If obj has no location, this lockfunc fails.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return tag(accessed_obj.location, None, *args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage:
|
Usage:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue