Improved generation of Player Characters using charcreate

This commit is contained in:
Andrew Bastien 2023-05-05 18:31:07 -04:00
parent f6b43b0416
commit dfd5ee3f8e
3 changed files with 80 additions and 56 deletions

View file

@ -12,16 +12,16 @@ instead for most things).
"""
import re
import time
import typing
from random import getrandbits
import evennia
from django.conf import settings
from django.contrib.auth import authenticate, password_validation
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
import evennia
from evennia.accounts.manager import AccountManager
from evennia.accounts.models import AccountDB
from evennia.commands.cmdsethandler import CmdSetHandler
@ -272,6 +272,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
objects = AccountManager()
# Used by account.create_character() to choose default typeclass for characters.
player_character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# properties
@lazy_property
def cmdset(self):
@ -315,7 +318,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
"""
pass
def at_post_remove_character(self, character: "DefaultAccount"):
def at_post_remove_character(self, character: "DefaultCharacter"):
"""
Called after a character is removed from this account's list of playable characters.
@ -790,6 +793,40 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
logger.log_sec(f"Password successfully changed for {self}.")
self.at_password_change()
def get_character_slots(self) -> typing.Optional[int]:
"""
Returns the number of character slots this account has.
By default, that's settings.MAX_NR_CHARACTERS but this makes it easy to override.
Maybe for your game, players can be rewarded with more slots, somehow.
If it returns None, then there are no limits on character slots.
"""
return settings.MAX_NR_CHARACTERS
def get_available_character_slots(self) -> typing.Optional[int]:
"""
Returns the number of character slots this account has available.
"""
if (slots := self.get_character_slots()) is None:
return None
return max(0, slots - len(self.characters))
def check_available_slots(self, **kwargs) -> typing.Optional[str]:
"""
Helper method used to determine if an account can create additional characters using
the character slot system.
Returns:
str (optional): An error message regarding the status of slots. If present, this
will halt character creation. If not, character creation can proceed.
"""
if (slots := self.get_available_character_slots()) is not None:
if slots <= 0:
if not (self.is_superuser or self.check_permstring("Developer")):
plural = "" if (max_slots := self.get_character_slots()) == 1 else "s"
return f"You may only have a maximum of {max_slots} character{plural}."
def create_character(self, *args, **kwargs):
"""
Create a character linked to this account.
@ -807,16 +844,17 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
list or None: A list of errors, or None.
"""
# check character slot usage.
if (slot_check := self.check_available_slots()):
return None, [slot_check]
# parse inputs
character_key = kwargs.pop("key", self.key)
character_ip = kwargs.pop("ip", self.db.creator_ip)
character_permissions = kwargs.pop("permissions", self.permissions)
# Load the appropriate Character class
character_typeclass = kwargs.pop("typeclass", None)
character_typeclass = (
character_typeclass if character_typeclass else settings.BASE_CHARACTER_TYPECLASS
)
character_typeclass = kwargs.pop("typeclass", self.player_character_typeclass)
Character = class_from_module(character_typeclass)
if "location" not in kwargs:
@ -832,12 +870,29 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
**kwargs,
)
if character:
# Update playable character list
self.at_post_create_character(character, ip=character_ip)
return character, errs
def at_post_create_character(self, character, **kwargs):
"""
An overloadable hook method that allows for further customization of newly created characters.
"""
if character not in self.characters:
self.characters.add(character)
# We need to set this to have @ic auto-connect to this character
# We need to set this to have @ic auto-connect to this character
if len(self.characters) == 1:
self.db._last_puppet = character
return character, errs
character.locks.add(
f"puppet:id({character.id}) or pid({self.id}) or perm(Developer) or pperm(Developer);delete:id({self.id}) or"
" perm(Admin)"
)
logger.log_sec(
f"Character Created: {character} (Caller: {self}, IP: {kwargs.get('ip', None)})."
)
@classmethod
def create(cls, *args, **kwargs):
@ -960,10 +1015,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if account and _AUTO_CREATE_CHARACTER_WITH_ACCOUNT:
# Auto-create a character to go with this account
character, errs = account.create_character(
typeclass=kwargs.get("character_typeclass")
)
call_kwargs = {}
if "character_typeclass" in kwargs:
call_kwargs["character_typeclass"] = kwargs["character_typeclass"]
character, errs = account.create_character(**call_kwargs)
if errs:
errors.extend(errs)

View file

@ -146,53 +146,20 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
self.msg("Usage: charcreate <charname> [= description]")
return
key = self.lhs
desc = self.rhs
description = self.rhs or "This is a character."
if _MAX_NR_CHARACTERS is not None:
if (
not account.is_superuser
and not account.check_permstring("Developer")
and account.characters
and len(account.characters) >= _MAX_NR_CHARACTERS
):
plural = "" if _MAX_NR_CHARACTERS == 1 else "s"
self.msg(f"You may only have a maximum of {_MAX_NR_CHARACTERS} character{plural}.")
return
from evennia.objects.models import ObjectDB
new_character, errors = self.account.create_character(key=key, description=description, ip=self.session.address)
typeclass = settings.BASE_CHARACTER_TYPECLASS
if ObjectDB.objects.filter(db_typeclass_path=typeclass, db_key__iexact=key):
# check if this Character already exists. Note that we are only
# searching the base character typeclass here, not any child
# classes.
self.msg(f"|rA character named '|w{key}|r' already exists.|n")
if errors:
self.msg(errors)
if not new_character:
return
# create the character
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
new_character = create.create_object(
typeclass, key=key, location=start_location, home=default_home, permissions=permissions
)
# only allow creator (and developers) to puppet this char
new_character.locks.add(
"puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer);delete:id(%i) or"
" perm(Admin)" % (new_character.id, account.id, account.id)
)
account.characters.add(new_character)
if desc:
new_character.db.desc = desc
elif not new_character.db.desc:
new_character.db.desc = "This is a character."
self.msg(
f"Created new character {new_character.key}. Use |wic {new_character.key}|n to enter"
" the game as this character."
)
logger.log_sec(
f"Character Created: {new_character} (Caller: {account}, IP: {self.session.address})."
)
class CmdCharDelete(COMMAND_DEFAULT_CLASS):

View file

@ -2548,8 +2548,9 @@ class DefaultCharacter(DefaultObject):
try:
# Check to make sure account does not have too many chars
if account:
if len(account.characters) >= settings.MAX_NR_CHARACTERS:
errors.append(_("There are too many characters associated with this account."))
avail = account.check_available_slots()
if avail:
errors.append(avail)
return obj, errors
# Create the Character
@ -2604,7 +2605,8 @@ class DefaultCharacter(DefaultObject):
@classmethod
def validate_name(cls, name):
"""Validate the character name prior to creating. Overload this function to add custom validators
"""
Validate the character name prior to creating. Overload this function to add custom validators
Args:
name (str) : The name of the character