Trying a new approach. Introduced DefaultObject.get_object_typeclass() and cleaned up .create() hooks. Building commands now use the new logic.

This commit is contained in:
Andrew Bastien 2023-11-01 17:35:51 -04:00
parent 7746ff1663
commit 0da7f962c2
2 changed files with 178 additions and 150 deletions

View file

@ -579,10 +579,6 @@ class CmdCreate(ObjManipCommand):
locks = "cmd:perm(create) or perm(Builder)" locks = "cmd:perm(create) or perm(Builder)"
help_category = "Building" 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): def func(self):
""" """
Creates the object. Creates the object.
@ -600,26 +596,26 @@ class CmdCreate(ObjManipCommand):
string = "" string = ""
name = objdef["name"] name = objdef["name"]
aliases = objdef["aliases"] aliases = objdef["aliases"]
typeclass = objdef["option"]
# create object (if not a valid typeclass, the default obj_typeclass, errors = caller.get_object_typeclass(obj_type="object", typeclass=objdef["option"])
# object typeclass will automatically be used) if errors:
lockstring = self.new_obj_lockstring.format(id=caller.id) self.msg(errors)
if (err := caller.can_build_object()): if not obj_typeclass:
caller.msg(err) continue
return
obj = create.create_object( obj, errors = obj_typeclass.create(
typeclass,
name, name,
caller, caller,
home=caller, home=caller,
aliases=aliases, aliases=aliases,
locks=lockstring,
report_to=caller, report_to=caller,
creator=caller
) )
if errors:
self.msg(errors)
if not obj: if not obj:
continue continue
obj.at_object_constructed(caller)
if aliases: if aliases:
string = ( string = (
f"You create a new {obj.typename}: {obj.name} (aliases: {', '.join(aliases)})." f"You create a new {obj.typename}: {obj.name} (aliases: {', '.join(aliases)})."
@ -928,25 +924,27 @@ class CmdDig(ObjManipCommand):
location = caller.location location = caller.location
# Create the new room # Create the new room
typeclass = room["option"] room_typeclass, errors = caller.get_object_typeclass(obj_type="room", typeclass=room["option"], method="dig")
if not typeclass: if errors:
typeclass = settings.BASE_ROOM_TYPECLASS self.msg("|rError creating room:|n %s" % errors)
if not room_typeclass:
return
# create room # create room
if (err := caller.can_build_object()): new_room, errors = room_typeclass.create(
caller.msg(err) room["name"], aliases=room["aliases"], report_to=caller, creator=caller, method="dig"
return
new_room = create.create_object(
typeclass, room["name"], aliases=room["aliases"], report_to=caller
) )
lockstring = self.new_room_lockstring.format(id=caller.id) if errors:
new_room.locks.add(lockstring) self.msg("|rError creating room:|n %s" % errors)
if not new_room:
return
alias_string = "" alias_string = ""
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())
new_room.at_object_constructed(caller)
room_string = ( room_string = (
f"Created room {new_room}({new_room.dbref}){alias_string} of type {typeclass}." f"Created room {new_room}({new_room.dbref}){alias_string} of type {new_room}."
) )
# create exit to room # create exit to room
@ -962,21 +960,27 @@ 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
typeclass = to_exit["option"] exit_typeclass, errors = caller.get_object_typeclass(obj_type="exit", typeclass=to_exit["option"],
if not typeclass: method="dig")
typeclass = settings.BASE_EXIT_TYPECLASS if errors:
if (err := caller.can_build_object()): self.msg("|rError creating exit:|n %s" % errors)
caller.msg(err) if not exit_typeclass:
return return
new_to_exit = create.create_object(
typeclass, new_to_exit, errors = exit_typeclass.create(
to_exit["name"], to_exit["name"],
location, location=location,
aliases=to_exit["aliases"],
locks=lockstring,
destination=new_room, destination=new_room,
aliases=to_exit["aliases"],
report_to=caller, report_to=caller,
creator=caller,
method="dig"
) )
if errors:
self.msg("|rError creating exit:|n %s" % errors)
if not new_to_exit:
return
alias_string = "" alias_string = ""
if new_to_exit.aliases.all(): if new_to_exit.aliases.all():
alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all()) alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all())
@ -984,7 +988,6 @@ class CmdDig(ObjManipCommand):
f"\nCreated Exit from {location.name} to {new_room.name}:" f"\nCreated Exit from {location.name} to {new_room.name}:"
f" {new_to_exit}({new_to_exit.dbref}){alias_string}." f" {new_to_exit}({new_to_exit.dbref}){alias_string}."
) )
new_to_exit.at_object_constructed(caller)
# Create exit back from new room # Create exit back from new room
@ -996,21 +999,25 @@ 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:
typeclass = back_exit["option"] exit_typeclass, errors = caller.get_object_typeclass(obj_type="exit", typeclass=back_exit["option"],
if not typeclass: method="dig")
typeclass = settings.BASE_EXIT_TYPECLASS if errors:
if (err := caller.can_build_object()): self.msg("|rError creating exit:|n %s" % errors)
caller.msg(err) if not exit_typeclass:
return return
new_back_exit = create.create_object( new_back_exit, errors = exit_typeclass.create(
typeclass,
back_exit["name"], back_exit["name"],
new_room, location=new_room,
aliases=back_exit["aliases"],
locks=lockstring,
destination=location, destination=location,
aliases=back_exit["aliases"],
report_to=caller, report_to=caller,
creator=caller,
method="dig"
) )
if errors:
self.msg("|rError creating exit:|n %s" % errors)
if not new_back_exit:
return
alias_string = "" alias_string = ""
if new_back_exit.aliases.all(): if new_back_exit.aliases.all():
alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all()) alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all())
@ -1018,7 +1025,6 @@ class CmdDig(ObjManipCommand):
f"\nCreated Exit back from {new_room.name} to {location.name}:" f"\nCreated Exit back from {new_room.name} to {location.name}:"
f" {new_back_exit}({new_back_exit.dbref}){alias_string}." f" {new_back_exit}({new_back_exit.dbref}){alias_string}."
) )
new_back_exit.at_object_constructed(caller)
caller.msg(f"{room_string}{exit_to_string}{exit_back_string}") caller.msg(f"{room_string}{exit_to_string}{exit_back_string}")
if new_room and "teleport" in self.switches: if new_room and "teleport" in self.switches:
caller.move_to(new_room, move_type="teleport") caller.move_to(new_room, move_type="teleport")
@ -1489,20 +1495,23 @@ class CmdOpen(ObjManipCommand):
else: else:
# exit does not exist before. Create a new one. # exit does not exist before. Create a new one.
lockstring = self.new_obj_lockstring.format(id=caller.id) exit_typeclass, errors = caller.get_object_typeclass(obj_type="exit", typeclass=typeclass, method="open")
if not typeclass: if errors:
typeclass = settings.BASE_EXIT_TYPECLASS self.msg("|rError creating exit:|n %s" % errors)
if (err := caller.can_build_object()): if not exit_typeclass:
caller.msg(err)
return return
exit_obj = create.create_object( exit_obj, errors = exit_typeclass.create(
typeclass, exit_name,
key=exit_name,
location=location, location=location,
aliases=exit_aliases, aliases=exit_aliases,
locks=lockstring,
report_to=caller, report_to=caller,
creator=caller,
method="open"
) )
if errors:
self.msg("|rError creating exit:|n %s" % errors)
if not exit_obj:
return
if exit_obj: if exit_obj:
# storing a destination is what makes it an exit! # storing a destination is what makes it an exit!
exit_obj.destination = destination exit_obj.destination = destination
@ -1515,7 +1524,6 @@ class CmdOpen(ObjManipCommand):
f"Created new Exit '{exit_name}' from {location.name} to" f"Created new Exit '{exit_name}' from {location.name} to"
f" {destination.name}{string}." f" {destination.name}{string}."
) )
exit_obj.at_object_constructed(caller)
else: else:
string = f"Error: Exit '{exit.name}' not created." string = f"Error: Exit '{exit.name}' not created."
# emit results # emit results

View file

@ -8,6 +8,7 @@ This is the v1.0 develop version (for ref in doc building).
""" """
import time import time
import typing
from collections import defaultdict from collections import defaultdict
import inflect import inflect
@ -222,6 +223,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
{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
@ -1012,7 +1020,8 @@ 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, account=None, **kwargs): def create(cls, key: str, account: "DefaultAccount" = None, creator: "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.
@ -1021,11 +1030,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
Args: Args:
key (str): Name of the new object. key (str): Name of the new object.
account (Account): Account to attribute this object to.
Keyword Args: Keyword Args:
account (Account): Account to attribute this object to.
creator (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".
Returns: Returns:
object (Object): A newly created object of the given typeclass. object (Object): A newly created object of the given typeclass.
@ -1598,37 +1610,42 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
""" """
pass pass
def can_build_object(self): 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 the build command to determine if This hook is called by build commands to determine which typeclass to use for a specific purpose. For instance,
self can build a new object. This is called before the when using dig, the system can use this to autodetect which kind of Room typeclass to use based on where the
object is created. As it receives no arguments, it builder is currently located.
can only be used to determine if self is allowed to build
anything in the current context.
For instance, a room may want to limit its number of exits, Note: Although intended to be used with typeclasses, as long as this hook returns a class with a create method,
or the builder might have an enforced quota limit for rooms which accepts the same API as DefaultObject.create(), build commands and other places should take it.
they can add to their custom dungeon.
Returns:
can_build (str or None): If self is allowed to build objects.
return a string as the error if there is a problem. If
this returns True, the build will abort.
"""
pass
def at_object_constructed(self, builder):
"""
Called when the object is constructed by a builder.
This is used to implement custom logic for building,
such as quota tracking systems, auto-tagging of rooms,
creation of logical groups of rooms like Zones or
dungeons, etc.
Args: Args:
builder (Object): The object that constructed this object. 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.)
""" """
pass
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):
""" """
@ -1696,7 +1713,6 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
""" """
pass pass
def at_server_reload(self): def at_server_reload(self):
""" """
This hook is called whenever the server is shutting down for This hook is called whenever the server is shutting down for
@ -2752,7 +2768,6 @@ 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(
@ -2814,7 +2829,8 @@ class DefaultRoom(DefaultObject):
) )
@classmethod @classmethod
def create(cls, key, account=None, **kwargs): def create(cls, key: str, account: "DefaultAccount" = None, creator: 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.
@ -2823,13 +2839,15 @@ class DefaultRoom(DefaultObject):
Args: Args:
key (str): Name of the new Room. 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: 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
creator (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".
Returns: Returns:
room (Object): A newly created Room of the given typeclass. room (Object): A newly created Room of the given typeclass.
@ -3020,7 +3038,8 @@ class DefaultExit(DefaultObject):
# Command hooks # Command hooks
@classmethod @classmethod
def create(cls, key, source, dest, account=None, **kwargs): def create(cls, key: str, location: DefaultRoom = None, destination: DefaultRoom = None, account: "DefaultAccount" = None, creator: 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.
@ -3030,13 +3049,14 @@ class DefaultExit(DefaultObject):
Args: Args:
key (str): Name of the new Exit, as it should appear from the key (str): Name of the new Exit, as it should appear from the
source room. source room.
account (obj): Account to associate this Exit with. location (Room): The room to create this exit in.
source (Room): The room to create this exit in.
dest (Room): The room to which this exit should go.
Keyword Args: Keyword Args:
account (obj): Account to associate this Exit with.
creator (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.
Returns: Returns:
exit (Object): A newly created Room of the given typeclass. exit (Object): A newly created Room of the given typeclass.
@ -3059,8 +3079,8 @@ class DefaultExit(DefaultObject):
kwargs["report_to"] = kwargs.pop("report_to", account) kwargs["report_to"] = kwargs.pop("report_to", account)
# Set to/from rooms # Set to/from rooms
kwargs["location"] = source kwargs["location"] = location
kwargs["destination"] = dest kwargs["destination"] = destination
description = kwargs.pop("description", "") description = kwargs.pop("description", "")