Moved the new hook to ObjManipCommand

This commit is contained in:
Andrew Bastien 2023-11-04 17:40:52 -04:00
parent 0da7f962c2
commit 5278ecb730
2 changed files with 168 additions and 128 deletions

View file

@ -2,6 +2,7 @@
Building and world design commands Building and world design commands
""" """
import re import re
import typing
from django.conf import settings from django.conf import settings
from django.core.paginator import Paginator 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 # OBS - this is just a parent - it's not intended to actually be
# included in a commandset on its own! # 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): def parse(self):
""" """
We need to expand the default parsing to get all 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.lhs_objattr = obj_attrs[0]
self.rhs_objattr = obj_attrs[1] 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)
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): class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
""" """
@ -194,6 +245,8 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
locks = "cmd:perm(setobjalias) or perm(Builder)" locks = "cmd:perm(setobjalias) or perm(Builder)"
help_category = "Building" help_category = "Building"
method_type = "cmd_create"
def func(self): def func(self):
"""Set the aliases.""" """Set the aliases."""
@ -597,19 +650,16 @@ class CmdCreate(ObjManipCommand):
name = objdef["name"] name = objdef["name"]
aliases = objdef["aliases"] aliases = objdef["aliases"]
obj_typeclass, errors = caller.get_object_typeclass(obj_type="object", typeclass=objdef["option"]) obj_typeclass, errors = self.get_object_typeclass(
obj_type="object", typeclass=objdef["option"]
)
if errors: if errors:
self.msg(errors) self.msg(errors)
if not obj_typeclass: if not obj_typeclass:
continue continue
obj, errors = obj_typeclass.create( obj, errors = obj_typeclass.create(
name, name, caller, home=caller, aliases=aliases, report_to=caller, caller=caller
caller,
home=caller,
aliases=aliases,
report_to=caller,
creator=caller
) )
if errors: if errors:
self.msg(errors) self.msg(errors)
@ -896,6 +946,8 @@ class CmdDig(ObjManipCommand):
locks = "cmd:perm(dig) or perm(Builder)" locks = "cmd:perm(dig) or perm(Builder)"
help_category = "Building" help_category = "Building"
method_type = "cmd_dig"
# lockstring of newly created rooms, for easy overloading. # lockstring of newly created rooms, for easy overloading.
# Will be formatted with the {id} of the creating object. # Will be formatted with the {id} of the creating object.
new_room_lockstring = ( new_room_lockstring = (
@ -924,7 +976,9 @@ class CmdDig(ObjManipCommand):
location = caller.location location = caller.location
# Create the new room # Create the new room
room_typeclass, errors = caller.get_object_typeclass(obj_type="room", typeclass=room["option"], method="dig") room_typeclass, errors = self.get_object_typeclass(
obj_type="room", typeclass=room["option"], method=self.method_type
)
if errors: if errors:
self.msg("|rError creating room:|n %s" % errors) self.msg("|rError creating room:|n %s" % errors)
if not room_typeclass: if not room_typeclass:
@ -932,7 +986,11 @@ class CmdDig(ObjManipCommand):
# create room # create room
new_room, errors = room_typeclass.create( new_room, errors = room_typeclass.create(
room["name"], aliases=room["aliases"], report_to=caller, creator=caller, method="dig" room["name"],
aliases=room["aliases"],
report_to=caller,
caller=caller,
method=self.method_type,
) )
if errors: if errors:
self.msg("|rError creating room:|n %s" % errors) self.msg("|rError creating room:|n %s" % errors)
@ -943,9 +1001,7 @@ class CmdDig(ObjManipCommand):
if new_room.aliases.all(): if new_room.aliases.all():
alias_string = " (%s)" % ", ".join(new_room.aliases.all()) alias_string = " (%s)" % ", ".join(new_room.aliases.all())
room_string = ( room_string = f"Created room {new_room}({new_room.dbref}){alias_string} of type {new_room}."
f"Created room {new_room}({new_room.dbref}){alias_string} of type {new_room}."
)
# create exit to room # create exit to room
@ -960,8 +1016,9 @@ class CmdDig(ObjManipCommand):
exit_to_string = "\nYou cannot create an exit from a None-location." exit_to_string = "\nYou cannot create an exit from a None-location."
else: else:
# Build the exit to the new room from the current one # Build the exit to the new room from the current one
exit_typeclass, errors = caller.get_object_typeclass(obj_type="exit", typeclass=to_exit["option"], exit_typeclass, errors = self.get_object_typeclass(
method="dig") obj_type="exit", typeclass=to_exit["option"], method=self.method_type
)
if errors: if errors:
self.msg("|rError creating exit:|n %s" % errors) self.msg("|rError creating exit:|n %s" % errors)
if not exit_typeclass: if not exit_typeclass:
@ -973,8 +1030,8 @@ class CmdDig(ObjManipCommand):
destination=new_room, destination=new_room,
aliases=to_exit["aliases"], aliases=to_exit["aliases"],
report_to=caller, report_to=caller,
creator=caller, caller=caller,
method="dig" method=self.method_type,
) )
if errors: if errors:
self.msg("|rError creating exit:|n %s" % errors) self.msg("|rError creating exit:|n %s" % errors)
@ -999,8 +1056,9 @@ class CmdDig(ObjManipCommand):
elif not location: elif not location:
exit_back_string = "\nYou cannot create an exit back to a None-location." exit_back_string = "\nYou cannot create an exit back to a None-location."
else: else:
exit_typeclass, errors = caller.get_object_typeclass(obj_type="exit", typeclass=back_exit["option"], exit_typeclass, errors = self.get_object_typeclass(
method="dig") obj_type="exit", typeclass=back_exit["option"], method=self.method_type
)
if errors: if errors:
self.msg("|rError creating exit:|n %s" % errors) self.msg("|rError creating exit:|n %s" % errors)
if not exit_typeclass: if not exit_typeclass:
@ -1011,8 +1069,8 @@ class CmdDig(ObjManipCommand):
destination=location, destination=location,
aliases=back_exit["aliases"], aliases=back_exit["aliases"],
report_to=caller, report_to=caller,
creator=caller, caller=caller,
method="dig" method=self.method_type,
) )
if errors: if errors:
self.msg("|rError creating exit:|n %s" % errors) self.msg("|rError creating exit:|n %s" % errors)
@ -1063,6 +1121,8 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
locks = "cmd: perm(tunnel) or perm(Builder)" locks = "cmd: perm(tunnel) or perm(Builder)"
help_category = "Building" help_category = "Building"
method_type = "cmd_tunnel"
# store the direction, full name and its opposite # store the direction, full name and its opposite
directions = { directions = {
"n": ("north", "s"), "n": ("north", "s"),
@ -1449,6 +1509,8 @@ class CmdOpen(ObjManipCommand):
locks = "cmd:perm(open) or perm(Builder)" locks = "cmd:perm(open) or perm(Builder)"
help_category = "Building" help_category = "Building"
method_type = "cmd_open"
new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)" 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 # a custom member method to chug out exits and do checks
@ -1495,7 +1557,9 @@ class CmdOpen(ObjManipCommand):
else: else:
# exit does not exist before. Create a new one. # exit does not exist before. Create a new one.
exit_typeclass, errors = caller.get_object_typeclass(obj_type="exit", typeclass=typeclass, method="open") exit_typeclass, errors = self.get_object_typeclass(
obj_type="exit", typeclass=typeclass, method=self.method_type
)
if errors: if errors:
self.msg("|rError creating exit:|n %s" % errors) self.msg("|rError creating exit:|n %s" % errors)
if not exit_typeclass: if not exit_typeclass:
@ -1505,8 +1569,8 @@ class CmdOpen(ObjManipCommand):
location=location, location=location,
aliases=exit_aliases, aliases=exit_aliases,
report_to=caller, report_to=caller,
creator=caller, caller=caller,
method="open" method=self.method_type,
) )
if errors: if errors:
self.msg("|rError creating exit:|n %s" % errors) self.msg("|rError creating exit:|n %s" % errors)

View file

@ -222,14 +222,6 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
{exits}{characters}{things} {exits}{characters}{things}
{footer} {footer}
""" """
default_typeclasses = {
"object": settings.BASE_OBJECT_TYPECLASS,
"character": settings.BASE_CHARACTER_TYPECLASS,
"room": settings.BASE_ROOM_TYPECLASS,
"exit": settings.BASE_EXIT_TYPECLASS,
}
# on-object properties # on-object properties
@lazy_property @lazy_property
@ -272,9 +264,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
""" """
return ( return (
self.db_account self.db_account
and self.db_account.is_superuser and self.db_account.is_superuser
and not self.db_account.attributes.get("_quell") and not self.db_account.attributes.get("_quell")
) )
def contents_get(self, exclude=None, content_type=None): def contents_get(self, exclude=None, content_type=None):
@ -320,22 +312,22 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# main methods # main methods
def search( def search(
self, self,
searchdata, searchdata,
global_search=False, global_search=False,
use_nicks=True, use_nicks=True,
typeclass=None, typeclass=None,
location=None, location=None,
attribute_name=None, attribute_name=None,
quiet=False, quiet=False,
exact=False, exact=False,
candidates=None, candidates=None,
use_locks=True, use_locks=True,
nofound_string=None, nofound_string=None,
multimatch_string=None, multimatch_string=None,
use_dbref=None, use_dbref=None,
tags=None, tags=None,
stacked=0, stacked=0,
): ):
""" """
Returns an Object matching a search string/condition Returns an Object matching a search string/condition
@ -439,10 +431,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
) )
if global_search or ( if global_search or (
is_string is_string
and searchdata.startswith("#") and searchdata.startswith("#")
and len(searchdata) > 1 and len(searchdata) > 1
and searchdata[1:].isdigit() and searchdata[1:].isdigit()
): ):
# only allow exact matching if searching the entire database # only allow exact matching if searching the entire database
# or unique #dbrefs # or unique #dbrefs
@ -677,13 +669,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
func(obj, **kwargs) func(obj, **kwargs)
def msg_contents( def msg_contents(
self, self,
text=None, text=None,
exclude=None, exclude=None,
from_obj=None, from_obj=None,
mapping=None, mapping=None,
raise_funcparse_errors=False, raise_funcparse_errors=False,
**kwargs, **kwargs,
): ):
""" """
Emits a message to all objects inside this object. Emits a message to all objects inside this object.
@ -796,15 +788,15 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
receiver.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs) receiver.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
def move_to( def move_to(
self, self,
destination, destination,
quiet=False, quiet=False,
emit_to_obj=None, emit_to_obj=None,
use_destination=True, use_destination=True,
to_none=False, to_none=False,
move_hooks=True, move_hooks=True,
move_type="move", move_type="move",
**kwargs, **kwargs,
): ):
""" """
Moves this object to a new location. Moves this object to a new location.
@ -895,7 +887,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# check if source location lets us go # check if source location lets us go
try: try:
if source_location and not source_location.at_pre_object_leave( if source_location and not source_location.at_pre_object_leave(
self, destination, **kwargs self, destination, **kwargs
): ):
return False return False
except Exception as err: except Exception as err:
@ -904,7 +896,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# check if destination accepts us # check if destination accepts us
try: try:
if destination and not destination.at_pre_object_receive( if destination and not destination.at_pre_object_receive(
self, source_location, **kwargs self, source_location, **kwargs
): ):
return False return False
except Exception as err: except Exception as err:
@ -1020,8 +1012,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
obj.move_to(home, move_type="teleport") obj.move_to(home, move_type="teleport")
@classmethod @classmethod
def create(cls, key: str, account: "DefaultAccount" = None, creator: "DefaultObject" = None, method: str = "create", def create(
**kwargs): cls,
key: str,
account: "DefaultAccount" = None,
caller: "DefaultObject" = None,
method: str = "create",
**kwargs,
):
""" """
Creates a basic object with default parameters, unless otherwise Creates a basic object with default parameters, unless otherwise
specified or extended. specified or extended.
@ -1034,7 +1032,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
Keyword Args: Keyword Args:
account (Account): Account to attribute this object to. account (Account): Account to attribute this object to.
creator (DefaultObject): The object which is creating this one. caller (DefaultObject): The object which is creating this one.
description (str): Brief description for this object. description (str): Brief description for this object.
ip (str): IP address of creator (for object auditing). ip (str): IP address of creator (for object auditing).
method (str): The method of creation. Defaults to "create". method (str): The method of creation. Defaults to "create".
@ -1185,7 +1183,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
return True return True
def access( def access(
self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs
): ):
""" """
Determines if another object has permission to access this object Determines if another object has permission to access this object
@ -1610,43 +1608,6 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
""" """
pass pass
def get_object_typeclass(self, obj_type: str = "object", typeclass: str = None, method: str = "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 "create". Others are "dig", "open",
"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) 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, []
def at_pre_puppet(self, account, session=None, **kwargs): def at_pre_puppet(self, account, session=None, **kwargs):
""" """
Called just before an Account connects to this object to puppet Called just before an Account connects to this object to puppet
@ -2128,8 +2089,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
obj obj
for obj in obj_list for obj in obj_list
if obj != looker if obj != looker
and obj.access(looker, "view") and obj.access(looker, "view")
and obj.access(looker, "search", default=True) and obj.access(looker, "search", default=True)
] ]
return { return {
@ -2386,13 +2347,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
at_before_say = at_pre_say at_before_say = at_pre_say
def at_say( def at_say(
self, self,
message, message,
msg_self=None, msg_self=None,
msg_location=None, msg_location=None,
receivers=None, receivers=None,
msg_receivers=None, msg_receivers=None,
**kwargs, **kwargs,
): ):
""" """
Display the actual say (or whisper) of self. Display the actual say (or whisper) of self.
@ -2768,6 +2729,7 @@ class DefaultCharacter(DefaultObject):
if not self.sessions.count(): if not self.sessions.count():
# only remove this char from grid if no sessions control it anymore. # only remove this char from grid if no sessions control it anymore.
if self.location: if self.location:
def message(obj, from_obj): def message(obj, from_obj):
obj.msg( obj.msg(
_("{name} has left the game{reason}.").format( _("{name} has left the game{reason}.").format(
@ -2829,8 +2791,14 @@ class DefaultRoom(DefaultObject):
) )
@classmethod @classmethod
def create(cls, key: str, account: "DefaultAccount" = None, creator: DefaultObject = None, method: str = "create", def create(
**kwargs): cls,
key: str,
account: "DefaultAccount" = None,
caller: DefaultObject = None,
method: str = "create",
**kwargs,
):
""" """
Creates a basic Room with default parameters, unless otherwise Creates a basic Room with default parameters, unless otherwise
specified or extended. specified or extended.
@ -2844,7 +2812,7 @@ class DefaultRoom(DefaultObject):
account (DefaultAccount, optional): Account to associate this Room with. If account (DefaultAccount, optional): Account to associate this Room with. If
given, it will be given specific control/edit permissions to this given, it will be given specific control/edit permissions to this
object (along with normal Admin perms). If not given, default object (along with normal Admin perms). If not given, default
creator (DefaultObject): The object which is creating this one. caller (DefaultObject): The object which is creating this one.
description (str): Brief description for this object. description (str): Brief description for this object.
ip (str): IP address of creator (for object auditing). ip (str): IP address of creator (for object auditing).
method (str): The method used to create the room. Defaults to "create". method (str): The method used to create the room. Defaults to "create".
@ -3038,8 +3006,16 @@ class DefaultExit(DefaultObject):
# Command hooks # Command hooks
@classmethod @classmethod
def create(cls, key: str, location: DefaultRoom = None, destination: DefaultRoom = None, account: "DefaultAccount" = None, creator: DefaultObject = None, def create(
method: str = "create", **kwargs) -> tuple[typing.Optional["DefaultExit"], list[str]]: 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 Creates a basic Exit with default parameters, unless otherwise
specified or extended. specified or extended.
@ -3053,7 +3029,7 @@ class DefaultExit(DefaultObject):
Keyword Args: Keyword Args:
account (obj): Account to associate this Exit with. account (obj): Account to associate this Exit with.
creator (ObjectDB): the Object creating this Object. caller (ObjectDB): the Object creating this Object.
description (str): Brief description for this object. description (str): Brief description for this object.
ip (str): IP address of creator (for object auditing). ip (str): IP address of creator (for object auditing).
destination (Room): The room to which this exit should go. destination (Room): The room to which this exit should go.