refactor character code structure

This commit is contained in:
lunacb 2023-01-17 17:51:04 -05:00
parent 8a458b21b5
commit 08facf7d00
10 changed files with 203 additions and 216 deletions

View File

@ -23,8 +23,7 @@ from evennia.contrib.game_systems.clothing import ClothedCharacterCmdSet
from evennia.contrib.game_systems.multidescer import CmdMultiDesc
from evennia.contrib.grid import simpledoor
from evennia.contrib.grid.ingame_map_display import MapDisplayCmdSet
from lib.pronounsub import PronounAdminCommand
from lib.pronounsub import PronounsCommand
from lib.pronounsub.commands import *
from lib.rpsystem.rpsystem import RPSystemCmdSet

0
lib/__init__.py Normal file
View File

View File

@ -1,4 +0,0 @@
from .pronounsub import PronounCharacter
from .pronounsub import PronounsCommand
from .pronounsub import PronounAdminCommand
from .db import PronounDbScript

View File

@ -0,0 +1,49 @@
from .common import *
# This should be called before every use of a Character to make sure it's
# initialized right.
def object_init(character):
if character.db.pronouns == None:
character.db.pronouns = DEFAULT_CHARACTER_PRONOUNS[:]
if character.db.pronoun_specs == None:
character.db.pronoun_specs = []
def msg(character, text=None, from_obj=None, session=None, **kwargs):
object_init(character)
"""
Emits something to a session attached to the object.
Overloads the default msg() implementation to include
gender-aware markers in output.
Args:
text (str or tuple, optional): The message to send. This
is treated internally like any send-command, so its
value can be a tuple if sending multiple arguments to
the `text` oob command.
from_obj (obj, optional): object that is sending. If
given, at_msg_send will be called
session (Session or list, optional): session or list of
sessions to relay to, if any. If set, will
force send regardless of MULTISESSION_MODE.
Note:
`at_msg_receive` will be called on this Object.
All extra kwargs will be passed on to the protocol.
"""
if text is None:
super().msg(from_obj=from_obj, session=session, **kwargs)
return
try:
repl = lambda a: pronoun_repl(character, a)
if text and isinstance(text, tuple):
text = (RE_GENDER_PRONOUN.sub(repl, text[0]), *text[1:])
else:
text = RE_GENDER_PRONOUN.sub(repl, text)
except TypeError:
pass
except Exception as e:
logger.log_trace(e)
return (text, from_obj, session, kwargs)

View File

@ -1,81 +1,5 @@
"""
Pronounsub
Griatch 2015
Copyright (c) 2023 lunacb <lunacb@disroot.org>
This is a gender-aware Character class that allows people to set pronouns and
use them in text.
Usage
When in use, messages can contain special tags to indicate pronouns gendered
based on the one being addressed. Capitalization will be retained.
- `|s`, `|S`: Subjective form, like They
- `|o`, `|O`: Objective form, like Them
- `|p`, `|P`: Possessive form, like Their
- `|a`, `|A`: Absolute Possessive form, like Theirs
For example,
```
char.msg("%s falls on |p face with a thud." % char.key)
"Tom falls on their face with a thud"
```
To use, have DefaultCharacter inherit from this, or change
setting.DEFAULT_CHARACTER to point to this class.
The `pronouns` command is used to set pronouns. It needs to be added to the
default cmdset before it becomes available.
"""
import random
from evennia import Command, DefaultCharacter
from evennia import InterruptCommand
from evennia.utils import logger
from evennia.utils.create import create_script
from evennia.utils.search import search_script
from .consts import *
# in-game command for setting the gender
def parse_specific_pronoun(pronoun):
specific = [ p.strip() for p in pronoun.split(",") ]
if len(specific) != len(PRONOUN_FORMS):
return None
new = {}
for i in range(len(specific)):
new[PRONOUN_FORMS[i]] = specific[i]
return new
def get_pronoun_list():
search = search_script("pronoun_db")
if not any(search):
script = create_script("lib.pronounsub.PronounDbScript", key="pronoun_db")
else:
script = search[0]
return script.db.pronouns
def specific_pronoun_from_character(character, caller=None):
pronoun_list = get_pronoun_list()[:]
if caller != None:
# TODO: reorder
pronoun_list += caller.db.pronoun_specs
for pronoun in pronoun_list:
if character in pronoun.values():
return pronoun
return None
def stringify_specific_pronoun(spec):
return ",".join([ spec[form] for form in PRONOUN_FORMS ])
from .common import *
from .character import object_init
class PronounAdminCommand(Command):
"""
@ -221,7 +145,7 @@ class PronounsCommand(Command):
def parse(self):
caller = self.caller
caller.pronoun_object_init()
object_init(caller)
args = self.args.strip().lower().split()
if len(args) == 1:
@ -254,7 +178,7 @@ class PronounsCommand(Command):
def func(self):
caller = self.caller
caller.pronoun_object_init()
object_init(caller)
command = self.args[0]
@ -275,10 +199,10 @@ class PronounsCommand(Command):
"details. |/")
caller.db.pronouns = pronouns
caller.msg(f"Your pronouns have been set to {caller._stringify_pronouns()}.")
caller.msg(f"Your pronouns have been set to {stringify_pronouns(caller)}.")
case "get":
caller.msg(f"Your pronouns are {caller._stringify_pronouns()}.")
caller.msg(f"Your pronouns are {stringify_pronouns(caller)}.")
case "list_known":
caller.msg("Known user-defined pronouns:")
@ -299,101 +223,3 @@ class PronounsCommand(Command):
if pronoun in caller.db.pronoun_specs:
caller.db.pronoun_specs.remove(pronoun)
self.caller.msg(f"Remobed the pronoun {stringify_specific_pronoun(pronoun)}.")
class PronounCharacter(DefaultCharacter):
"""
This is a Character class aware of gender.
"""
# This should be called before every use of a Character to make sure it's
# initialized right.
def pronoun_object_init(self):
if self.db.pronouns == None:
self.db.pronouns = DEFAULT_CHARACTER_PRONOUNS[:]
if self.db.pronoun_specs == None:
self.db.pronoun_specs = []
def at_object_creation(self):
"""
Called once when the object is created.
"""
super().at_object_creation()
self.pronoun_object_init()
def _stringify_pronouns(self):
pronouns = self.attributes.get("pronouns", default=DEFAULT_CHARACTER_PRONOUNS[:])
return "/".join(pronouns)
def _get_pronoun(self, regex_match):
"""
Get pronoun from the pronoun marker in the text. This is used as
the callable for the re.sub function.
Args:
regex_match (MatchObject): the regular expression match.
Notes:
- `|s`, `|S`: Subjective form, like They
- `|o`, `|O`: Objective form, like Them
- `|p`, `|P`: Possessive form, like Their
- `|a`, `|A`: Absolute Possessive form, like Theirs
"""
typ = regex_match.group()[1] # "s", "O" etc
dup_specifics = []
for pronoun in self.attributes.get("pronouns", default=DEFAULT_CHARACTER_PRONOUNS[:]):
specific = specific_pronoun_from_character(pronoun, self)
if specific != None:
dup_specifics.append(specific)
specifics = []
for s in dup_specifics:
if not s in specifics:
specifics.append(s)
if len(specifics) == 0:
choice = DEFAULT_SPECIFIC_NEUTRAL_PRONOUNS
else:
choice = random.choice(specifics)
pronoun = choice[typ.lower()]
return pronoun.capitalize() if typ.isupper() else pronoun
def msg(self, text=None, from_obj=None, session=None, **kwargs):
self.pronoun_object_init()
"""
Emits something to a session attached to the object.
Overloads the default msg() implementation to include
gender-aware markers in output.
Args:
text (str or tuple, optional): The message to send. This
is treated internally like any send-command, so its
value can be a tuple if sending multiple arguments to
the `text` oob command.
from_obj (obj, optional): object that is sending. If
given, at_msg_send will be called
session (Session or list, optional): session or list of
sessions to relay to, if any. If set, will
force send regardless of MULTISESSION_MODE.
Notes:
`at_msg_receive` will be called on this Object.
All extra kwargs will be passed on to the protocol.
"""
if text is None:
super().msg(from_obj=from_obj, session=session, **kwargs)
return
try:
if text and isinstance(text, tuple):
text = (RE_GENDER_PRONOUN.sub(self._get_pronoun, text[0]), *text[1:])
else:
text = RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
except TypeError:
pass
except Exception as e:
logger.log_trace(e)
super().msg(text, from_obj=from_obj, session=session, **kwargs)

137
lib/pronounsub/common.py Normal file
View File

@ -0,0 +1,137 @@
"""
Pronounsub
Griatch 2015
Copyright (c) 2023 lunacb <lunacb@disroot.org>
This is a gender-aware Character class that allows people to set pronouns and
use them in text.
Usage
When in use, messages can contain special tags to indicate pronouns gendered
based on the one being addressed. Capitalization will be retained.
- `|s`, `|S`: Subjective form, like They
- `|o`, `|O`: Objective form, like Them
- `|p`, `|P`: Possessive form, like Their
- `|a`, `|A`: Absolute Possessive form, like Theirs
For example,
```
char.msg("%s falls on |p face with a thud." % char.key)
"Tom falls on their face with a thud"
```
To use, have DefaultCharacter inherit from this, or change
setting.DEFAULT_CHARACTER to point to this class.
The `pronouns` command is used to set pronouns. It needs to be added to the
default cmdset before it becomes available.
"""
import copy
import random
import re
from evennia import Command, DefaultCharacter
from evennia import DefaultScript
from evennia import InterruptCommand
from evennia.utils import logger
from evennia.utils.create import create_script
from evennia.utils.search import search_script
class PronounDbScript(DefaultScript):
def at_script_creation(self):
self.db.pronouns = copy.deepcopy(DEFAULT_SPECIFIC_PRONOUNS)
DEFAULT_SPECIFIC_NEUTRAL_PRONOUNS = {"s": "they", "o": "them", "p": "their", "a": "theirs"}
DEFAULT_SPECIFIC_PRONOUNS = [
{"s": "he", "o": "him", "p": "his", "a": "his"},
{"s": "she", "o": "her", "p": "her", "a": "hers"},
{"s": "it", "o": "it", "p": "its", "a": "its"},
{"s": "they", "o": "them", "p": "their", "a": "theirs"}
]
DEFAULT_CHARACTER_PRONOUNS = [ "they", "them" ]
PRONOUN_FORMS = [ "s", "o", "p", "a" ]
RE_GENDER_PRONOUN = re.compile(r"(?<!\|)\|(?!\|)[sSoOpPaA]")
# in-game command for setting the gender
def parse_specific_pronoun(pronoun):
specific = [ p.strip() for p in pronoun.split(",") ]
if len(specific) != len(PRONOUN_FORMS):
return None
new = {}
for i in range(len(specific)):
new[PRONOUN_FORMS[i]] = specific[i]
return new
def get_pronoun_list():
search = search_script("pronoun_db")
if not any(search):
script = create_script("lib.pronounsub.PronounDbScript", key="pronoun_db")
else:
script = search[0]
return script.db.pronouns
def specific_pronoun_from_character(character, caller=None):
pronoun_list = get_pronoun_list()[:]
if caller != None:
pronoun_list = caller.db.pronoun_specs + pronoun_list
for pronoun in pronoun_list:
if character in pronoun.values():
return pronoun
return None
def stringify_specific_pronoun(spec):
return ",".join([ spec[form] for form in PRONOUN_FORMS ])
def stringify_pronouns(character):
pronouns = character.attributes.get("pronouns", default=DEFAULT_CHARACTER_PRONOUNS[:])
return "/".join(pronouns)
def pronoun_repl(character, regex_match):
"""
Get pronoun from the pronoun marker in the text. This is used as
the callable for the re.sub function.
Args:
regex_match (MatchObject): the regular expression match.
Notes:
- `|s`, `|S`: Subjective form, like They
- `|o`, `|O`: Objective form, like Them
- `|p`, `|P`: Possessive form, like Their
- `|a`, `|A`: Absolute Possessive form, like Theirs
"""
typ = regex_match.group()[1] # "s", "O" etc
dup_specifics = []
for pronoun in character.attributes.get("pronouns", default=DEFAULT_CHARACTER_PRONOUNS[:]):
specific = specific_pronoun_from_character(pronoun, character)
if specific != None:
dup_specifics.append(specific)
specifics = []
for s in dup_specifics:
if not s in specifics:
specifics.append(s)
if len(specifics) == 0:
choice = DEFAULT_SPECIFIC_NEUTRAL_PRONOUNS
else:
choice = random.choice(specifics)
pronoun = choice[typ.lower()]
return pronoun.capitalize() if typ.isupper() else pronoun

View File

@ -1,16 +0,0 @@
import re
DEFAULT_SPECIFIC_NEUTRAL_PRONOUNS = {"s": "they", "o": "them", "p": "their", "a": "theirs"}
DEFAULT_SPECIFIC_PRONOUNS = [
{"s": "he", "o": "him", "p": "his", "a": "his"},
{"s": "she", "o": "her", "p": "her", "a": "hers"},
{"s": "it", "o": "it", "p": "its", "a": "its"},
{"s": "they", "o": "them", "p": "their", "a": "theirs"}
]
DEFAULT_CHARACTER_PRONOUNS = [ "they", "them" ]
PRONOUN_FORMS = [ "s", "o", "p", "a" ]
RE_GENDER_PRONOUN = re.compile(r"(?<!\|)\|(?!\|)[sSoOpPaA]")

View File

@ -1,8 +0,0 @@
import copy
from evennia import DefaultScript
from .consts import *
class PronounDbScript(DefaultScript):
def at_script_creation(self):
self.db.pronouns = copy.deepcopy(DEFAULT_SPECIFIC_PRONOUNS)

View File

@ -157,8 +157,9 @@ from django.conf import settings
from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command
from evennia.contrib.game_systems.clothing import ClothedCharacter
from evennia.objects.models import ObjectDB
from evennia.objects.objects import DefaultCharacter, DefaultObject
from evennia.objects.objects import DefaultObject
from evennia.utils import ansi, logger
from evennia.utils.utils import (
iter_to_str,
@ -1606,7 +1607,7 @@ class ContribRPRoom(ContribRPObject):
pass
class ContribRPCharacter(DefaultCharacter, ContribRPObject):
class ContribRPCharacter(ClothedCharacter, ContribRPObject):
"""
This is a character class that has poses, sdesc and recog.
"""

View File

@ -8,14 +8,17 @@ creation commands.
"""
from lib.rpsystem import ContribRPCharacter
from lib.pronounsub import PronounCharacter
from evennia.contrib.game_systems.clothing import ClothedCharacter
from lib.pronounsub import character as pronounsub
# rpsystem
class Character(ContribRPCharacter, ClothedCharacter, PronounCharacter):
class Character(ContribRPCharacter):
def at_object_creation(self):
pronounsub.object_init(self)
super().at_object_creation()
def msg(self, text=None, from_obj=None, session=None, **kwargs):
# TODO: Don't do this. See https://git.disroot.org/vantablack/vantaMOO/issues/29
text, from_obj, session, kwargs = pronounsub.msg(self, text, from_obj, session, **kwargs)
super().msg(text, from_obj, session, **kwargs)