mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Merge pull request #3180 from volundmush/dig_hook
Building Command Hook Improvements
This commit is contained in:
commit
80a17cab87
4 changed files with 230 additions and 73 deletions
|
|
@ -2,6 +2,7 @@
|
|||
Building and world design commands
|
||||
"""
|
||||
import re
|
||||
import typing
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.paginator import Paginator
|
||||
|
|
@ -110,6 +111,14 @@ class ObjManipCommand(COMMAND_DEFAULT_CLASS):
|
|||
# OBS - this is just a parent - it's not intended to actually be
|
||||
# included in a commandset on its own!
|
||||
|
||||
# used by get_object_typeclass as defaults.
|
||||
default_typeclasses = {
|
||||
"object": settings.BASE_OBJECT_TYPECLASS,
|
||||
"character": settings.BASE_CHARACTER_TYPECLASS,
|
||||
"room": settings.BASE_ROOM_TYPECLASS,
|
||||
"exit": settings.BASE_EXIT_TYPECLASS,
|
||||
}
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
We need to expand the default parsing to get all
|
||||
|
|
@ -164,6 +173,48 @@ class ObjManipCommand(COMMAND_DEFAULT_CLASS):
|
|||
self.lhs_objattr = obj_attrs[0]
|
||||
self.rhs_objattr = obj_attrs[1]
|
||||
|
||||
def get_object_typeclass(
|
||||
self, obj_type: str = "object", typeclass: str = None, method: str = "cmd_create", **kwargs
|
||||
) -> tuple[typing.Optional["Builder"], list[str]]:
|
||||
"""
|
||||
This hook is called by build commands to determine which typeclass to use for a specific purpose. For instance,
|
||||
when using dig, the system can use this to autodetect which kind of Room typeclass to use based on where the
|
||||
builder is currently located.
|
||||
|
||||
Note: Although intended to be used with typeclasses, as long as this hook returns a class with a create method,
|
||||
which accepts the same API as DefaultObject.create(), build commands and other places should take it.
|
||||
|
||||
Args:
|
||||
obj_type (str, optional): The type of object that is being created. Defaults to "object". Evennia provides
|
||||
"room", "exit", and "character" by default, but this can be extended.
|
||||
typeclass (str, optional): The typeclass that was requested by the player. Defaults to None.
|
||||
Can also be an actual class.
|
||||
method (str, optional): The method that is calling this hook. Defaults to "cmd_create".
|
||||
Others are "cmd_dig", "cmd_open", "cmd_tunnel", etc.
|
||||
|
||||
Returns:
|
||||
results_tuple (tuple[Optional[Builder], list[str]]): A tuple containing the typeclass to use and a list of
|
||||
errors. (which might be empty.)
|
||||
"""
|
||||
|
||||
found_typeclass = typeclass or self.default_typeclasses.get(obj_type, None)
|
||||
if not found_typeclass:
|
||||
return None, [f"No typeclass found for object type '{obj_type}'."]
|
||||
|
||||
try:
|
||||
type_class = (
|
||||
class_from_module(found_typeclass, settings.TYPECLASS_PATHS)
|
||||
if isinstance(found_typeclass, str)
|
||||
else found_typeclass
|
||||
)
|
||||
except ImportError:
|
||||
return None, [f"Typeclass '{found_typeclass}' could not be imported."]
|
||||
|
||||
if not hasattr(type_class, "create"):
|
||||
return None, [f"Typeclass '{found_typeclass}' is not creatable."]
|
||||
|
||||
return type_class, []
|
||||
|
||||
|
||||
class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
|
|
@ -194,6 +245,8 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
|||
locks = "cmd:perm(setobjalias) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
method_type = "cmd_create"
|
||||
|
||||
def func(self):
|
||||
"""Set the aliases."""
|
||||
|
||||
|
|
@ -579,10 +632,6 @@ class CmdCreate(ObjManipCommand):
|
|||
locks = "cmd:perm(create) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
# lockstring of newly created objects, for easy overloading.
|
||||
# Will be formatted with the {id} of the creating object.
|
||||
new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Creates the object.
|
||||
|
|
@ -600,22 +649,23 @@ class CmdCreate(ObjManipCommand):
|
|||
string = ""
|
||||
name = objdef["name"]
|
||||
aliases = objdef["aliases"]
|
||||
typeclass = objdef["option"]
|
||||
|
||||
# create object (if not a valid typeclass, the default
|
||||
# object typeclass will automatically be used)
|
||||
lockstring = self.new_obj_lockstring.format(id=caller.id)
|
||||
obj = create.create_object(
|
||||
typeclass,
|
||||
name,
|
||||
caller,
|
||||
home=caller,
|
||||
aliases=aliases,
|
||||
locks=lockstring,
|
||||
report_to=caller,
|
||||
obj_typeclass, errors = self.get_object_typeclass(
|
||||
obj_type="object", typeclass=objdef["option"]
|
||||
)
|
||||
if errors:
|
||||
self.msg(errors)
|
||||
if not obj_typeclass:
|
||||
continue
|
||||
|
||||
obj, errors = obj_typeclass.create(
|
||||
name, caller, home=caller, aliases=aliases, report_to=caller, caller=caller
|
||||
)
|
||||
if errors:
|
||||
self.msg(errors)
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
if aliases:
|
||||
string = (
|
||||
f"You create a new {obj.typename}: {obj.name} (aliases: {', '.join(aliases)})."
|
||||
|
|
@ -896,6 +946,8 @@ class CmdDig(ObjManipCommand):
|
|||
locks = "cmd:perm(dig) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
method_type = "cmd_dig"
|
||||
|
||||
# lockstring of newly created rooms, for easy overloading.
|
||||
# Will be formatted with the {id} of the creating object.
|
||||
new_room_lockstring = (
|
||||
|
|
@ -924,22 +976,32 @@ class CmdDig(ObjManipCommand):
|
|||
location = caller.location
|
||||
|
||||
# Create the new room
|
||||
typeclass = room["option"]
|
||||
if not typeclass:
|
||||
typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
room_typeclass, errors = self.get_object_typeclass(
|
||||
obj_type="room", typeclass=room["option"], method=self.method_type
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating room:|n %s" % errors)
|
||||
if not room_typeclass:
|
||||
return
|
||||
|
||||
# create room
|
||||
new_room = create.create_object(
|
||||
typeclass, room["name"], aliases=room["aliases"], report_to=caller
|
||||
new_room, errors = room_typeclass.create(
|
||||
room["name"],
|
||||
aliases=room["aliases"],
|
||||
report_to=caller,
|
||||
caller=caller,
|
||||
method=self.method_type,
|
||||
)
|
||||
lockstring = self.new_room_lockstring.format(id=caller.id)
|
||||
new_room.locks.add(lockstring)
|
||||
if errors:
|
||||
self.msg("|rError creating room:|n %s" % errors)
|
||||
if not new_room:
|
||||
return
|
||||
|
||||
alias_string = ""
|
||||
if new_room.aliases.all():
|
||||
alias_string = " (%s)" % ", ".join(new_room.aliases.all())
|
||||
room_string = (
|
||||
f"Created room {new_room}({new_room.dbref}){alias_string} of type {typeclass}."
|
||||
)
|
||||
|
||||
room_string = f"Created room {new_room}({new_room.dbref}){alias_string} of type {new_room}."
|
||||
|
||||
# create exit to room
|
||||
|
||||
|
|
@ -954,19 +1016,28 @@ class CmdDig(ObjManipCommand):
|
|||
exit_to_string = "\nYou cannot create an exit from a None-location."
|
||||
else:
|
||||
# Build the exit to the new room from the current one
|
||||
typeclass = to_exit["option"]
|
||||
if not typeclass:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
|
||||
new_to_exit = create.create_object(
|
||||
typeclass,
|
||||
to_exit["name"],
|
||||
location,
|
||||
aliases=to_exit["aliases"],
|
||||
locks=lockstring,
|
||||
destination=new_room,
|
||||
report_to=caller,
|
||||
exit_typeclass, errors = self.get_object_typeclass(
|
||||
obj_type="exit", typeclass=to_exit["option"], method=self.method_type
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating exit:|n %s" % errors)
|
||||
if not exit_typeclass:
|
||||
return
|
||||
|
||||
new_to_exit, errors = exit_typeclass.create(
|
||||
to_exit["name"],
|
||||
location=location,
|
||||
destination=new_room,
|
||||
aliases=to_exit["aliases"],
|
||||
report_to=caller,
|
||||
caller=caller,
|
||||
method=self.method_type,
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating exit:|n %s" % errors)
|
||||
if not new_to_exit:
|
||||
return
|
||||
|
||||
alias_string = ""
|
||||
if new_to_exit.aliases.all():
|
||||
alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all())
|
||||
|
|
@ -985,18 +1056,26 @@ class CmdDig(ObjManipCommand):
|
|||
elif not location:
|
||||
exit_back_string = "\nYou cannot create an exit back to a None-location."
|
||||
else:
|
||||
typeclass = back_exit["option"]
|
||||
if not typeclass:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
new_back_exit = create.create_object(
|
||||
typeclass,
|
||||
back_exit["name"],
|
||||
new_room,
|
||||
aliases=back_exit["aliases"],
|
||||
locks=lockstring,
|
||||
destination=location,
|
||||
report_to=caller,
|
||||
exit_typeclass, errors = self.get_object_typeclass(
|
||||
obj_type="exit", typeclass=back_exit["option"], method=self.method_type
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating exit:|n %s" % errors)
|
||||
if not exit_typeclass:
|
||||
return
|
||||
new_back_exit, errors = exit_typeclass.create(
|
||||
back_exit["name"],
|
||||
location=new_room,
|
||||
destination=location,
|
||||
aliases=back_exit["aliases"],
|
||||
report_to=caller,
|
||||
caller=caller,
|
||||
method=self.method_type,
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating exit:|n %s" % errors)
|
||||
if not new_back_exit:
|
||||
return
|
||||
alias_string = ""
|
||||
if new_back_exit.aliases.all():
|
||||
alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all())
|
||||
|
|
@ -1042,6 +1121,8 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
|||
locks = "cmd: perm(tunnel) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
method_type = "cmd_tunnel"
|
||||
|
||||
# store the direction, full name and its opposite
|
||||
directions = {
|
||||
"n": ("north", "s"),
|
||||
|
|
@ -1428,6 +1509,8 @@ class CmdOpen(ObjManipCommand):
|
|||
locks = "cmd:perm(open) or perm(Builder)"
|
||||
help_category = "Building"
|
||||
|
||||
method_type = "cmd_open"
|
||||
|
||||
new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)"
|
||||
|
||||
# a custom member method to chug out exits and do checks
|
||||
|
|
@ -1474,17 +1557,25 @@ class CmdOpen(ObjManipCommand):
|
|||
|
||||
else:
|
||||
# exit does not exist before. Create a new one.
|
||||
lockstring = self.new_obj_lockstring.format(id=caller.id)
|
||||
if not typeclass:
|
||||
typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
exit_obj = create.create_object(
|
||||
typeclass,
|
||||
key=exit_name,
|
||||
exit_typeclass, errors = self.get_object_typeclass(
|
||||
obj_type="exit", typeclass=typeclass, method=self.method_type
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating exit:|n %s" % errors)
|
||||
if not exit_typeclass:
|
||||
return
|
||||
exit_obj, errors = exit_typeclass.create(
|
||||
exit_name,
|
||||
location=location,
|
||||
aliases=exit_aliases,
|
||||
locks=lockstring,
|
||||
report_to=caller,
|
||||
caller=caller,
|
||||
method=self.method_type,
|
||||
)
|
||||
if errors:
|
||||
self.msg("|rError creating exit:|n %s" % errors)
|
||||
if not exit_obj:
|
||||
return
|
||||
if exit_obj:
|
||||
# storing a destination is what makes it an exit!
|
||||
exit_obj.destination = destination
|
||||
|
|
|
|||
|
|
@ -725,16 +725,17 @@ class TestAccount(BaseEvenniaCommandTest):
|
|||
|
||||
class TestBuilding(BaseEvenniaCommandTest):
|
||||
def test_create(self):
|
||||
name = settings.BASE_OBJECT_TYPECLASS.rsplit(".", 1)[1]
|
||||
typeclass = settings.BASE_OBJECT_TYPECLASS
|
||||
name = typeclass.rsplit(".", 1)[1]
|
||||
self.call(
|
||||
building.CmdCreate(),
|
||||
"/d TestObj1", # /d switch is abbreviated form of /drop
|
||||
f"/d TestObj1:{typeclass}", # /d switch is abbreviated form of /drop
|
||||
"You create a new %s: TestObj1." % name,
|
||||
)
|
||||
self.call(building.CmdCreate(), "", "Usage: ")
|
||||
self.call(
|
||||
building.CmdCreate(),
|
||||
"TestObj1;foo;bar",
|
||||
f"TestObj1;foo;bar:{typeclass}",
|
||||
"You create a new %s: TestObj1 (aliases: foo, bar)." % name,
|
||||
)
|
||||
|
||||
|
|
@ -2082,7 +2083,7 @@ class TestBatchProcess(BaseEvenniaCommandTest):
|
|||
# cannot test batchcode here, it must run inside the server process
|
||||
self.call(
|
||||
batchprocess.CmdBatchCommands(),
|
||||
"batchprocessor.example_batch_cmds",
|
||||
"batchprocessor.example_batch_cmds_test",
|
||||
"Running Batch-command processor - Automatic mode for"
|
||||
" batchprocessor.example_batch_cmds",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# This is an example batch build file for Evennia.
|
||||
#
|
||||
# This version is stripped down to work better with the test system.
|
||||
# It avoids teleporting the button. For the full version look at the
|
||||
# other example_batch_cmds.ev file.
|
||||
|
||||
# This creates a red button
|
||||
|
||||
create/drop button:red_button.RedButton
|
||||
|
||||
# This comment ends input for @create
|
||||
# Next command:
|
||||
|
||||
set button/desc =
|
||||
This is a large red button. Now and then
|
||||
it flashes in an evil, yet strangely tantalizing way.
|
||||
|
||||
A big sign sits next to it. It says:
|
||||
|
||||
|
||||
-----------
|
||||
|
||||
Press me!
|
||||
|
||||
-----------
|
||||
|
||||
|
||||
... It really begs to be pressed, doesn't it? You
|
||||
know you want to!
|
||||
|
||||
# This ends the @set command. Note that line breaks and extra spaces
|
||||
# in the argument are not considered. A completely empty line
|
||||
# translates to a \n newline in the command; two empty lines will thus
|
||||
# create a new paragraph. (note that few commands support it though, you
|
||||
# mainly want to use it for descriptions).
|
||||
|
|
@ -8,6 +8,7 @@ This is the v1.0 develop version (for ref in doc building).
|
|||
|
||||
"""
|
||||
import time
|
||||
import typing
|
||||
from collections import defaultdict
|
||||
import typing
|
||||
|
||||
|
|
@ -222,7 +223,6 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
{exits}{characters}{things}
|
||||
{footer}
|
||||
"""
|
||||
|
||||
# on-object properties
|
||||
|
||||
@lazy_property
|
||||
|
|
@ -1013,7 +1013,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
obj.move_to(home, move_type="teleport")
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, **kwargs):
|
||||
def create(
|
||||
cls,
|
||||
key: str,
|
||||
account: "DefaultAccount" = None,
|
||||
caller: "DefaultObject" = None,
|
||||
method: str = "create",
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Creates a basic object with default parameters, unless otherwise
|
||||
specified or extended.
|
||||
|
|
@ -1022,11 +1029,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
Args:
|
||||
key (str): Name of the new object.
|
||||
account (Account): Account to attribute this object to.
|
||||
|
||||
|
||||
Keyword Args:
|
||||
account (Account): Account to attribute this object to.
|
||||
caller (DefaultObject): The object which is creating this one.
|
||||
description (str): Brief description for this object.
|
||||
ip (str): IP address of creator (for object auditing).
|
||||
method (str): The method of creation. Defaults to "create".
|
||||
|
||||
Returns:
|
||||
object (Object): A newly created object of the given typeclass.
|
||||
|
|
@ -2786,7 +2796,14 @@ class DefaultRoom(DefaultObject):
|
|||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, **kwargs):
|
||||
def create(
|
||||
cls,
|
||||
key: str,
|
||||
account: "DefaultAccount" = None,
|
||||
caller: DefaultObject = None,
|
||||
method: str = "create",
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Creates a basic Room with default parameters, unless otherwise
|
||||
specified or extended.
|
||||
|
|
@ -2795,13 +2812,15 @@ class DefaultRoom(DefaultObject):
|
|||
|
||||
Args:
|
||||
key (str): Name of the new Room.
|
||||
account (obj, optional): Account to associate this Room with. If
|
||||
given, it will be given specific control/edit permissions to this
|
||||
object (along with normal Admin perms). If not given, default
|
||||
|
||||
Keyword Args:
|
||||
account (DefaultAccount, optional): Account to associate this Room with. If
|
||||
given, it will be given specific control/edit permissions to this
|
||||
object (along with normal Admin perms). If not given, default
|
||||
caller (DefaultObject): The object which is creating this one.
|
||||
description (str): Brief description for this object.
|
||||
ip (str): IP address of creator (for object auditing).
|
||||
method (str): The method used to create the room. Defaults to "create".
|
||||
|
||||
Returns:
|
||||
room (Object): A newly created Room of the given typeclass.
|
||||
|
|
@ -2992,7 +3011,16 @@ class DefaultExit(DefaultObject):
|
|||
# Command hooks
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, source, dest, account=None, **kwargs):
|
||||
def create(
|
||||
cls,
|
||||
key: str,
|
||||
location: DefaultRoom = None,
|
||||
destination: DefaultRoom = None,
|
||||
account: "DefaultAccount" = None,
|
||||
caller: DefaultObject = None,
|
||||
method: str = "create",
|
||||
**kwargs,
|
||||
) -> tuple[typing.Optional["DefaultExit"], list[str]]:
|
||||
"""
|
||||
Creates a basic Exit with default parameters, unless otherwise
|
||||
specified or extended.
|
||||
|
|
@ -3002,13 +3030,14 @@ class DefaultExit(DefaultObject):
|
|||
Args:
|
||||
key (str): Name of the new Exit, as it should appear from the
|
||||
source room.
|
||||
account (obj): Account to associate this Exit with.
|
||||
source (Room): The room to create this exit in.
|
||||
dest (Room): The room to which this exit should go.
|
||||
location (Room): The room to create this exit in.
|
||||
|
||||
Keyword Args:
|
||||
account (obj): Account to associate this Exit with.
|
||||
caller (ObjectDB): the Object creating this Object.
|
||||
description (str): Brief description for this object.
|
||||
ip (str): IP address of creator (for object auditing).
|
||||
destination (Room): The room to which this exit should go.
|
||||
|
||||
Returns:
|
||||
exit (Object): A newly created Room of the given typeclass.
|
||||
|
|
@ -3031,8 +3060,8 @@ class DefaultExit(DefaultObject):
|
|||
kwargs["report_to"] = kwargs.pop("report_to", account)
|
||||
|
||||
# Set to/from rooms
|
||||
kwargs["location"] = source
|
||||
kwargs["destination"] = dest
|
||||
kwargs["location"] = location
|
||||
kwargs["destination"] = destination
|
||||
|
||||
description = kwargs.pop("description", "")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue