Rework at_before_say/at_say to handle whispers and using kwargs.

This commit is contained in:
Griatch 2017-07-21 12:17:47 +02:00
commit c43059c75f
153 changed files with 4418 additions and 2931 deletions

338
bin/project_rename.py Normal file
View file

@ -0,0 +1,338 @@
"""
Project rename utility
Created for the Player->Account renaming
Griatch 2017, released under the BSD license.
"""
from __future__ import print_function
import re
import sys
import os
import fnmatch
ANSI_HILITE = "\033[1m"
ANSI_RED = "\033[31m"
ANSI_GREEN = "\033[32m"
ANSI_YELLOW = "\033[33m"
ANSI_NORMAL = "\033[0m"
USE_COLOR = True
FAKE_MODE = False
# if these words are longer than output word, retain given case
CASE_WORD_EXCEPTIONS = ('an', )
_HELP_TEXT = """This program interactively renames words in all files of your project. It's
currently renaming {sources} to {targets}.
If it wants to replace text in a file, it will show all lines (and line numbers) it wants to
replace, each directly followed by the suggested replacement.
If a rename is not okay, you can de-select it by entering 'i' followed by one or more
comma-separated line numbers. You cannot ignore partial lines, those you need to remember to change
manually later.
[q]uit - exits the program immediately.
[h]elp - this help.
[s]kip file - make no changes at all in this file, continue on to the next.
[i]ignore lines - specify line numbers to not change.
[c]lear ignores - this reverts all your ignores if you make a mistake.
[a]accept/save file - apply all accepted renames and continue on to the next file.
(return to continue)
"""
# Helper functions
def _green(string):
if USE_COLOR:
return "%s%s%s" % (ANSI_GREEN, string, ANSI_NORMAL)
return string
def _yellow(string):
if USE_COLOR:
return "%s%s%s" % (ANSI_YELLOW, string, ANSI_NORMAL)
return string
def _red(string):
if USE_COLOR:
return "%s%s%s" % (ANSI_HILITE + ANSI_RED, string, ANSI_NORMAL)
return string
def _case_sensitive_replace(string, old, new):
"""
Replace text, retaining exact case.
Args:
string (str): String in which to perform replacement.
old (str): Word or substring to replace.
new (str): What to replace `old` with.
Returns:
repl_string (str): Version of string where instances of
`old` has been replaced with `new`, retaining case.
"""
def repl(match):
current = match.group()
# treat multi-word sentences word-by-word
old_words = current.split(" ")
new_words = new.split(" ")
out = []
for old_word, new_word in zip(old_words, new_words):
result = []
all_upper = True
for ind, chr in enumerate(old_word):
if ind >= len(new):
break
if chr.isupper():
result.append(new_word[ind].upper())
else:
result.append(new_word[ind].lower())
all_upper = False
# special cases - keep remaing case)
if new_word.lower() in CASE_WORD_EXCEPTIONS:
result.append(new_word[ind+1:])
# append any remaining characters from new
elif all_upper:
result.append(new_word[ind+1:].upper())
else:
result.append(new_word[ind+1:].lower())
out.append("".join(result))
# if we have more new words than old ones, just add them verbatim
out.extend([new_word for ind, new_word in enumerate(new_words) if ind >= len(old_words)])
return " ".join(out)
regex = re.compile(re.escape(old), re.I)
return regex.sub(repl, string)
def rename_in_tree(path, in_list, out_list, excl_list, fileend_list, is_interactive):
"""
Rename across a recursive directory structure.
Args:
path (str): Root directory to traverse. All subdirectories
will be visited.
in_list (list): List of src words to replace.
out_list (list): Matching list of words to replace with.
excl_list (list): List of paths to exclude.
fileend_list (list): List of file endings to accept. If
not given, accept all file endings.
is_interactive (bool): If we should stop to ask about the
replacements in each file.
"""
repl_mapping = zip(in_list, out_list)
for root, dirs, files in os.walk(path):
print("\ndir: %s\n" % root)
if any(fnmatch.fnmatch(root, excl) for excl in excl_list):
print("%s skipped (excluded)." % root)
continue
for file in files:
full_path = os.path.join(root, file)
if any(fnmatch.fnmatch(full_path, excl) for excl in excl_list):
print("%s skipped (excluded)." % full_path)
continue
if not fileend_list or any(file.endswith(ending) for ending in fileend_list):
rename_in_file(full_path, in_list, out_list, is_interactive)
# rename file - always ask
new_file = file
for src, dst in repl_mapping:
new_file = _case_sensitive_replace(new_file, src, dst)
if new_file != file:
inp = raw_input(_green("Rename %s\n -> %s\n Y/[N]? > " % (file, new_file)))
if inp.upper() == 'Y':
new_full_path = os.path.join(root, new_file)
try:
os.rename(full_path, new_full_path)
except OSError as err:
raw_input(_red("Could not rename - %s (return to skip)" % err))
else:
print("... Renamed.")
else:
print("... Skipped.")
# rename the dir
new_root = root
for src, dst in repl_mapping:
new_root = _case_sensitive_replace(new_root, src, dst)
if new_root != root:
inp = raw_input(_green("Dir Rename %s\n -> %s\n Y/[N]? > " % (root, new_root)))
if inp.upper() == 'Y':
new_full_path = os.path.join(root, new_file)
try:
os.rename(root, new_root)
except OSError as err:
raw_input(_red("Could not rename - %s (return to skip)" % err))
else:
print("... Renamed.")
else:
print("... Skipped.")
def rename_in_file(path, in_list, out_list, is_interactive):
"""
Args:
path (str): Path to file in which to perform renaming.
in_list (list): List of src words to replace.
out_list (list): Matching list of words to replace with.
is_interactive (bool): If we should stop to ask about the
replacements in each file.
"""
print("-- %s" % path)
org_text = ""
new_text = None
if os.path.isdir(path):
print("%s is a directory. You should use the --recursive option." % path)
sys.exit()
with open(path, 'r') as fil:
org_text = fil.read()
repl_mapping = zip(in_list, out_list)
if not is_interactive:
# just replace everything immediately
new_text = org_text
for src, dst in repl_mapping:
new_text = _case_sensitive_replace(new_text, src, dst)
if new_text != org_text:
if FAKE_MODE:
print(" ... Saved changes to %s. (faked)" % path)
else:
with open(path, 'w') as fil:
fil.write(new_text)
print(" ... Saved changes to %s." % path)
else:
# interactive mode
while True:
renamed = {}
org_lines = org_text.split("\n")
for iline, old_line in enumerate(org_lines):
new_line = old_line
for src, dst in repl_mapping:
new_line = _case_sensitive_replace(new_line, src, dst)
if new_line != old_line:
renamed[iline] = new_line
if not renamed:
# no changes
print(" ... no changes to %s." % path)
return
while True:
for iline, renamed_line in sorted(renamed.items(), key=lambda tup: tup[0]):
print("%3i orig: %s" % (iline + 1, org_lines[iline]))
print(" new : %s" % (_yellow(renamed_line)))
print(_green("%s (%i lines changed)" % (path, len(renamed))))
ret = raw_input(_green("Choose: "
"[q]uit, "
"[h]elp, "
"[s]kip file, "
"[i]gnore lines, "
"[c]lear ignores, "
"[a]ccept/save file: ".lower()))
if ret == "s":
# skip file entirely
print(" ... Skipping file %s." % path)
return
elif ret == "c":
# clear ignores - rerun rename
break
elif ret == "a":
# save result
for iline, renamed_line in renamed.items():
org_lines[iline] = renamed_line
if FAKE_MODE:
print(" ... Saved file %s (faked)" % path)
return
with open(path, 'w') as fil:
fil.writelines("\n".join(org_lines))
print(" ... Saved file %s" % path)
return
elif ret == "q":
print("Quit renaming program.")
sys.exit()
elif ret == "h":
raw_input(_HELP_TEXT.format(sources=in_list, targets=out_list))
elif ret.startswith("i"):
# ignore one or more lines
ignores = [int(ind)-1 for ind in ret[1:].split(',') if ind.strip().isdigit()]
if not ignores:
raw_input("Ignore example: i 2,7,34,133\n (return to continue)")
continue
for ign in ignores:
renamed.pop(ign, None)
continue
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Rename text in a source tree, or a single file")
parser.add_argument('-i', '--input', action='append',
help="Source word to rename (quote around multiple words)")
parser.add_argument('-o', '--output', action='append',
help="Word to rename a matching src-word to")
parser.add_argument('-x', '--exc', action='append',
help="File path patterns to exclude")
parser.add_argument('-a', '--auto', action='store_true',
help="Automatic mode, don't ask to rename")
parser.add_argument('-r', '--recursive', action='store_true',
help="Recurse subdirs")
parser.add_argument('-f', '--fileending', action='append',
help="Change which file endings to allow (default .py)")
parser.add_argument('--nocolor', action='store_true',
help="Turn off in-program color")
parser.add_argument('--fake', action='store_true',
help="Simulate run but don't actually save")
parser.add_argument('path',
help="File or directory in which to rename text")
args = parser.parse_args()
in_list, out_list, exc_list, fileend_list = args.input, args.output, args.exc, args.fileending
if not (in_list and out_list):
print('At least one source- and destination word must be given.')
sys.exit()
if len(in_list) != len(out_list):
print('Number of sources must be identical to the number of destination arguments.')
sys.exit()
exc_list = exc_list or []
fileend_list = fileend_list or [".py"]
is_interactive = not args.auto
is_recursive = args.recursive
USE_COLOR = not args.nocolor
FAKE_MODE = args.fake
if is_recursive:
rename_in_tree(args.path, in_list, out_list, exc_list, fileend_list, is_interactive)
else:
rename_in_file(args.path, in_list, out_list, is_interactive)

View file

@ -25,7 +25,7 @@ from builtins import object
# Typeclasses
DefaultPlayer = None
DefaultAccount = None
DefaultGuest = None
DefaultObject = None
DefaultCharacter = None
@ -36,7 +36,7 @@ DefaultScript = None
# Database models
ObjectDB = None
PlayerDB = None
AccountDB = None
ScriptDB = None
ChannelDB = None
Msg = None
@ -51,7 +51,7 @@ InterruptCommand = None
# search functions
search_object = None
search_script = None
search_player = None
search_account = None
search_channel = None
search_message = None
search_help = None
@ -60,7 +60,7 @@ search_tag = None
# create functions
create_object = None
create_script = None
create_player = None
create_account = None
create_channel = None
create_message = None
create_help_entry = None
@ -117,17 +117,17 @@ def _init():
Evennia has fully initialized all its models. It sets up the API
in a safe environment where all models are available already.
"""
global DefaultPlayer, DefaultObject, DefaultGuest, DefaultCharacter
global DefaultAccount, DefaultObject, DefaultGuest, DefaultCharacter
global DefaultRoom, DefaultExit, DefaultChannel, DefaultScript
global ObjectDB, PlayerDB, ScriptDB, ChannelDB, Msg
global ObjectDB, AccountDB, ScriptDB, ChannelDB, Msg
global Command, CmdSet, default_cmds, syscmdkeys, InterruptCommand
global search_object, search_script, search_player, search_channel, search_help, search_tag
global create_object, create_script, create_player, create_channel, create_message, create_help_entry
global search_object, search_script, search_account, search_channel, search_help, search_tag
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
global settings,lockfuncs, logger, utils, gametime, ansi, spawn, managers
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER
from .players.players import DefaultPlayer
from .players.players import DefaultGuest
from .accounts.accounts import DefaultAccount
from .accounts.accounts import DefaultGuest
from .objects.objects import DefaultObject
from .objects.objects import DefaultCharacter
from .objects.objects import DefaultRoom
@ -137,7 +137,7 @@ def _init():
# Database models
from .objects.models import ObjectDB
from .players.models import PlayerDB
from .accounts.models import AccountDB
from .scripts.models import ScriptDB
from .comms.models import ChannelDB
from .comms.models import Msg
@ -149,7 +149,7 @@ def _init():
# search functions
from .utils.search import search_object
from .utils.search import search_script
from .utils.search import search_player
from .utils.search import search_account
from .utils.search import search_message
from .utils.search import search_channel
from .utils.search import search_help
@ -158,7 +158,7 @@ def _init():
# create functions
from .utils.create import create_object
from .utils.create import create_script
from .utils.create import create_player
from .utils.create import create_account
from .utils.create import create_channel
from .utils.create import create_message
from .utils.create import create_help_entry
@ -202,7 +202,7 @@ def _init():
Links to instantiated database managers.
helpentry - HelpEntry.objects
players - PlayerDB.objects
accounts - AccountDB.objects
scripts - ScriptDB.objects
msgs - Msg.objects
channels - Channel.objects
@ -213,7 +213,7 @@ def _init():
"""
from .help.models import HelpEntry
from .players.models import PlayerDB
from .accounts.models import AccountDB
from .scripts.models import ScriptDB
from .comms.models import Msg, ChannelDB
from .objects.models import ObjectDB
@ -223,7 +223,7 @@ def _init():
# create container's properties
helpentries = HelpEntry.objects
players = PlayerDB.objects
accounts = AccountDB.objects
scripts = ScriptDB.objects
msgs = Msg.objects
channels = ChannelDB.objects
@ -232,7 +232,7 @@ def _init():
attributes = Attribute.objects
tags = Tag.objects
# remove these so they are not visible as properties
del HelpEntry, PlayerDB, ScriptDB, Msg, ChannelDB
del HelpEntry, AccountDB, ScriptDB, Msg, ChannelDB
#del ExternalChannelConnection
del ObjectDB, ServerConfig, Tag, Attribute
@ -250,10 +250,10 @@ def _init():
"""
from .commands.default.cmdset_character import CharacterCmdSet
from .commands.default.cmdset_player import PlayerCmdSet
from .commands.default.cmdset_account import AccountCmdSet
from .commands.default.cmdset_unloggedin import UnloggedinCmdSet
from .commands.default.cmdset_session import SessionCmdSet
from .commands.default.muxcommand import MuxCommand, MuxPlayerCommand
from .commands.default.muxcommand import MuxCommand, MuxAccountCommand
def __init__(self):
"populate the object with commands"
@ -265,14 +265,14 @@ def _init():
from .commands.default import (admin, batchprocess,
building, comms, general,
player, help, system, unloggedin)
account, help, system, unloggedin)
add_cmds(admin)
add_cmds(building)
add_cmds(batchprocess)
add_cmds(building)
add_cmds(comms)
add_cmds(general)
add_cmds(player)
add_cmds(account)
add_cmds(help)
add_cmds(system)
add_cmds(unloggedin)
@ -293,7 +293,7 @@ def _init():
CMD_MULTIMATCH - multiple command matches were found
CMD_CHANNEL - the command name is a channel name
CMD_LOGINSTART - this command will be called as the very
first command when a player connects to
first command when an account connects to
the server.
To access in code, do 'from evennia import syscmdkeys' then

View file

@ -0,0 +1,6 @@
"""
This sub-package defines the out-of-character entities known as
Accounts. These are equivalent to 'accounts' and can puppet one or
more Objects depending on settings. An Account has no in-game existence.
"""

View file

@ -1,5 +1,5 @@
"""
Typeclass for Player objects
Typeclass for Account objects
Note that this object is primarily intended to
store OOC information, not game info! This
@ -15,8 +15,8 @@ import time
from django.conf import settings
from django.utils import timezone
from evennia.typeclasses.models import TypeclassBase
from evennia.players.manager import PlayerManager
from evennia.players.models import PlayerDB
from evennia.accounts.manager import AccountManager
from evennia.accounts.models import AccountDB
from evennia.objects.models import ObjectDB
from evennia.comms.models import ChannelDB
from evennia.commands import cmdhandler
@ -31,32 +31,32 @@ from evennia.commands.cmdsethandler import CmdSetHandler
from django.utils.translation import ugettext as _
from future.utils import with_metaclass
__all__ = ("DefaultPlayer",)
__all__ = ("DefaultAccount",)
_SESSIONS = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_MULTISESSION_MODE = settings.MULTISESSION_MODE
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_CMDSET_PLAYER = settings.CMDSET_PLAYER
_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT
_CONNECT_CHANNEL = None
class PlayerSessionHandler(object):
class AccountSessionHandler(object):
"""
Manages the session(s) attached to a player.
Manages the session(s) attached to an account.
"""
def __init__(self, player):
def __init__(self, account):
"""
Initializes the handler.
Args:
player (Player): The Player on which this handler is defined.
account (Account): The Account on which this handler is defined.
"""
self.player = player
self.account = account
def get(self, sessid=None):
"""
@ -75,9 +75,9 @@ class PlayerSessionHandler(object):
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
if sessid:
return make_iter(_SESSIONS.session_from_player(self.player, sessid))
return make_iter(_SESSIONS.session_from_account(self.account, sessid))
else:
return _SESSIONS.sessions_from_player(self.player)
return _SESSIONS.sessions_from_account(self.account)
def all(self):
"""
@ -100,19 +100,19 @@ class PlayerSessionHandler(object):
return len(self.get())
class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
"""
This is the base Typeclass for all Players. Players represent
This is the base Typeclass for all Accounts. Accounts represent
the person playing the game and tracks account info, password
etc. They are OOC entities without presence in-game. A Player
etc. They are OOC entities without presence in-game. An Account
can connect to a Character Object in order to "enter" the
game.
Player Typeclass API:
Account Typeclass API:
* Available properties (only available on initiated typeclass objects)
- key (string) - name of player
- key (string) - name of account
- name (string)- wrapper for user.username
- aliases (list of strings) - aliases to the object. Will be saved to
database as AliasDB entries but returned as strings.
@ -120,9 +120,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
- date_created (string) - time stamp of object creation
- permissions (list of strings) - list of permission strings
- user (User, read-only) - django User authorization object
- obj (Object) - game object controlled by player. 'character' can also
- obj (Object) - game object controlled by account. 'character' can also
be used.
- sessions (list of Sessions) - sessions connected to this player
- sessions (list of Sessions) - sessions connected to this account
- is_superuser (bool, read-only) - if the connected user is a superuser
* Handlers
@ -142,7 +142,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
- execute_cmd(raw_string)
- search(ostring, global_search=False, attribute_name=None,
use_nicks=False, location=None,
ignore_errors=False, player=False)
ignore_errors=False, account=False)
- is_typeclass(typeclass, exact=False)
- swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
- access(accessing_obj, access_type='read', default=False, no_superuser_bypass=False)
@ -151,7 +151,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
* Hook methods
basetype_setup()
at_player_creation()
at_account_creation()
> note that the following hooks are also found on Objects and are
usually handled on the character level:
@ -169,7 +169,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
objects = PlayerManager()
objects = AccountManager()
# properties
@lazy_property
@ -186,14 +186,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
@lazy_property
def sessions(self):
return PlayerSessionHandler(self)
return AccountSessionHandler(self)
# session-related methods
def disconnect_session_from_player(self, session):
def disconnect_session_from_account(self, session):
"""
Access method for disconnecting a given session from the
player (connection happens automatically in the
account (connection happens automatically in the
sessionhandler)
Args:
@ -235,9 +235,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# no access
self.msg("You don't have permission to puppet '%s'." % obj.key)
return
if obj.player:
if obj.account:
# object already puppeted
if obj.player == self:
if obj.account == self:
if obj.sessions.count():
# we may take over another of our sessions
# output messages to the affected sessions
@ -252,9 +252,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
self.msg(txt1 % obj.name, session=session)
self.msg(txt2 % obj.name, session=obj.sessions.all())
self.unpuppet_object(obj.sessions.get())
elif obj.player.is_connected:
# controlled by another player
self.msg("|c%s|R is already puppeted by another Player." % obj.key)
elif obj.account.is_connected:
# controlled by another account
self.msg("|c%s|R is already puppeted by another Account." % obj.key)
return
# do the puppeting
@ -262,14 +262,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# cleanly unpuppet eventual previous object puppeted by this session
self.unpuppet_object(session)
# if we get to this point the character is ready to puppet or it
# was left with a lingering player/session reference from an unclean
# was left with a lingering account/session reference from an unclean
# server kill or similar
obj.at_pre_puppet(self, session=session)
# do the connection
obj.sessions.add(session)
obj.player = self
obj.account = self
session.puid = obj.id
session.puppet = obj
# validate/start persistent scripts on object
@ -299,7 +299,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
obj.at_pre_unpuppet()
obj.sessions.remove(session)
if not obj.sessions.count():
del obj.player
del obj.account
obj.at_post_unpuppet(self, session=session)
# Just to be sure we're always clear.
session.puppet = None
@ -314,9 +314,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def get_puppet(self, session):
"""
Get an object puppeted by this session through this player. This is
Get an object puppeted by this session through this account. This is
the main method for retrieving the puppeted object from the
player's end.
account's end.
Args:
session (Session): Find puppeted object based on this session
@ -333,7 +333,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Returns:
puppets (list): All puppeted objects currently controlled
by this Player.
by this Account.
"""
return list(set(session.puppet for session in self.sessions.all() if session.puppet))
@ -359,7 +359,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def delete(self, *args, **kwargs):
"""
Deletes the player permanently.
Deletes the account permanently.
Notes:
`*args` and `**kwargs` are passed on to the base delete
@ -375,12 +375,12 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
except RuntimeError:
# no puppet to disconnect from
pass
session.sessionhandler.disconnect(session, reason=_("Player being deleted."))
session.sessionhandler.disconnect(session, reason=_("Account being deleted."))
self.scripts.stop()
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
super(DefaultPlayer, self).delete(*args, **kwargs)
super(DefaultAccount, self).delete(*args, **kwargs)
# methods inherited from database model
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
@ -391,7 +391,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Args:
text (str, optional): text data to send
from_obj (Object or Player, optional): Object sending. If given,
from_obj (Object or Account, optional): Object sending. If given,
its at_msg_send() hook will be called.
session (Session or list, optional): Session object or a list of
Sessions to receive this send. If given, overrules the
@ -411,7 +411,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
pass
try:
if not self.at_msg_receive(text=text, **kwargs):
# abort message to this player
# abort message to this account
return
except Exception:
# this may not be assigned.
@ -426,9 +426,9 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def execute_cmd(self, raw_string, session=None, **kwargs):
"""
Do something as this player. This method is never called normally,
but only when the player object itself is supposed to execute the
command. It takes player nicks into account, but not nicks of
Do something as this account. This method is never called normally,
but only when the account object itself is supposed to execute the
command. It takes account nicks into account, but not nicks of
eventual puppets.
Args:
@ -445,33 +445,33 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=False)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=False)
if not session and _MULTISESSION_MODE in (0, 1):
# for these modes we use the first/only session
sessions = self.sessions.get()
session = sessions[0] if sessions else None
return cmdhandler.cmdhandler(self, raw_string,
callertype="player", session=session, **kwargs)
callertype="account", session=session, **kwargs)
def search(self, searchdata, return_puppet=False, search_object=False,
typeclass=None, nofound_string=None, multimatch_string=None, **kwargs):
"""
This is similar to `DefaultObject.search` but defaults to searching
for Players only.
for Accounts only.
Args:
searchdata (str or int): Search criterion, the Player's
searchdata (str or int): Search criterion, the Account's
key or dbref to search for.
return_puppet (bool, optional): Instructs the method to
return matches as the object the Player controls rather
than the Player itself (or None) if nothing is puppeted).
return matches as the object the Account controls rather
than the Account itself (or None) if nothing is puppeted).
search_object (bool, optional): Search for Objects instead of
Players. This is used by e.g. the @examine command when
Accounts. This is used by e.g. the @examine command when
wanting to examine Objects while OOC.
typeclass (Player typeclass, optional): Limit the search
typeclass (Account typeclass, optional): Limit the search
only to this particular typeclass. This can be used to
limit to specific player typeclasses or to limit the search
limit to specific account typeclasses or to limit the search
to a particular Object typeclass if `search_object` is True.
nofound_string (str, optional): A one-time error message
to echo if `searchdata` leads to no matches. If not given,
@ -481,7 +481,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
If not given, will fall back to the default handler.
Return:
match (Player, Object or None): A single Player or Object match.
match (Account, Object or None): A single Account or Object match.
Notes:
Extra keywords are ignored, but are allowed in call in
order to make API more consistent with
@ -496,7 +496,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
if search_object:
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass)
else:
matches = PlayerDB.objects.player_search(searchdata, typeclass=typeclass)
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string)
@ -527,7 +527,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
result (bool): Result of access check.
"""
result = super(DefaultPlayer, self).access(accessing_obj, access_type=access_type,
result = super(DefaultAccount, self).access(accessing_obj, access_type=access_type,
default=default, no_superuser_bypass=no_superuser_bypass)
self.at_access(result, accessing_obj, access_type, **kwargs)
return result
@ -554,31 +554,31 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
return time.time() - float(min(conn))
return None
# player hooks
# account hooks
def basetype_setup(self):
"""
This sets up the basic properties for a player. Overload this
with at_player_creation rather than changing this method.
This sets up the basic properties for an account. Overload this
with at_account_creation rather than changing this method.
"""
# A basic security setup
lockstring = "examine:perm(Wizards);edit:perm(Wizards);" \
"delete:perm(Wizards);boot:perm(Wizards);msg:all()"
lockstring = "examine:perm(Admin);edit:perm(Admin);" \
"delete:perm(Admin);boot:perm(Admin);msg:all()"
self.locks.add(lockstring)
# The ooc player cmdset
self.cmdset.add_default(_CMDSET_PLAYER, permanent=True)
# The ooc account cmdset
self.cmdset.add_default(_CMDSET_ACCOUNT, permanent=True)
def at_player_creation(self):
def at_account_creation(self):
"""
This is called once, the very first time the player is created
This is called once, the very first time the account is created
(i.e. first time they register with the game). It's a good
place to store attributes all players should have, like
place to store attributes all accounts should have, like
configuration values etc.
"""
# set an (empty) attribute holding the characters this player has
# set an (empty) attribute holding the characters this account has
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" \
"attrcreate:perm(Admins)"
self.attributes.add("_playable_characters", [], lockstring=lockstring)
@ -590,8 +590,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
that is, whenever it its typeclass is cached from memory. This
happens on-demand first time the object is used or activated
in some way after being created but also after each server
restart or reload. In the case of player objects, this usually
happens the moment the player logs in or reconnects after a
restart or reload. In the case of account objects, this usually
happens the moment the account logs in or reconnects after a
reload.
"""
@ -601,7 +601,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# typeclass. You can often ignore these and rely on the character
# ones instead, unless you are implementing a multi-character game
# and have some things that should be done regardless of which
# character is currently connected to this player.
# character is currently connected to this account.
def at_first_save(self):
"""
@ -611,11 +611,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
self.basetype_setup()
self.at_player_creation()
self.at_account_creation()
permissions = settings.PERMISSION_PLAYER_DEFAULT
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
if hasattr(self, "_createdict"):
# this will only be set if the utils.create_player
# this will only be set if the utils.create_account
# function was used to create the object.
cdict = self._createdict
if cdict.get("locks"):
@ -624,11 +624,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
permissions = cdict["permissions"]
del self._createdict
self.permissions.add(permissions)
self.permissions.batch_add(*permissions)
def at_access(self, result, accessing_obj, access_type, **kwargs):
"""
This is triggered after an access-call on this Player has
This is triggered after an access-call on this Account has
completed.
Args:
@ -653,33 +653,41 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def at_cmdset_get(self, **kwargs):
"""
Called just *before* cmdsets on this player are requested by
Called just *before* cmdsets on this account are requested by
the command handler. The cmdsets are available as
`self.cmdset`. If changes need to be done on the fly to the
cmdset before passing them on to the cmdhandler, this is the
place to do it. This is called also if the player currently
place to do it. This is called also if the account currently
have no cmdsets. kwargs are usually not used unless the
cmdset is generated dynamically.
"""
pass
def at_first_login(self):
def at_first_login(self, **kwargs):
"""
Called the very first time this player logs into the game.
Called the very first time this account logs into the game.
Note that this is called *before* at_pre_login, so no session
is established and usually no character is yet assigned at
this point. This hook is intended for player-specific setup
this point. This hook is intended for account-specific setup
like configurations.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_pre_login(self):
def at_pre_login(self, **kwargs):
"""
Called every time the user logs in, just before the actual
login-state is set.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
@ -706,13 +714,15 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
else:
logger.log_info("[%s]: %s" % (now, message))
def at_post_login(self, session=None):
def at_post_login(self, session=None, **kwargs):
"""
Called at the end of the login process, just before letting
the player loose.
the account loose.
Args:
session (Session, optional): Session logging in, if any.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
This is called *before* an eventual Character's
@ -747,14 +757,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
return
elif _MULTISESSION_MODE in (2, 3):
# In this mode we by default end up at a character selection
# screen. We execute look on the player.
# screen. We execute look on the account.
# we make sure to clean up the _playable_characers list in case
# any was deleted in the interim.
self.db._playable_characters = [char for char in self.db._playable_characters if char]
self.msg(self.at_look(target=self.db._playable_characters,
session=session))
def at_failed_login(self, session):
def at_failed_login(self, session, **kwargs):
"""
Called by the login process if a user account is targeted correctly
but provided with an invalid password. By default it does nothing,
@ -762,42 +772,60 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Args:
session (session): Session logging in.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_disconnect(self, reason=None):
def at_disconnect(self, reason=None, **kwargs):
"""
Called just before user is disconnected.
Args:
reason (str, optional): The reason given for the disconnect,
(echoed to the connection channel by default).
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
reason = reason and "(%s)" % reason or ""
self._send_to_connect_channel("|R%s disconnected %s|n" % (self.key, reason))
def at_post_disconnect(self):
def at_post_disconnect(self, **kwargs):
"""
This is called *after* disconnection is complete. No messages
can be relayed to the player from here. After this call, the
player should not be accessed any more, making this a good
spot for deleting it (in the case of a guest player account,
can be relayed to the account from here. After this call, the
account should not be accessed any more, making this a good
spot for deleting it (in the case of a guest account account,
for example).
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_message_receive(self, message, from_obj=None):
def at_message_receive(self, message, from_obj=None, **kwargs):
"""
This is currently unused.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
return True
def at_message_send(self, message, to_object):
def at_message_send(self, message, to_object, **kwargs):
"""
This is currently unused.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
@ -817,7 +845,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
pass
def at_look(self, target=None, session=None):
def at_look(self, target=None, session=None, **kwargs):
"""
Called when this object executes a look. It allows to customize
just what this means.
@ -826,6 +854,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
target (Object or list, optional): An object or a list
objects to inspect.
session (Session, optional): The session doing this look.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
look_string (str): A prepared look string, ready to send
@ -893,18 +923,20 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
return look_string
class DefaultGuest(DefaultPlayer):
class DefaultGuest(DefaultAccount):
"""
This class is used for guest logins. Unlike Players, Guests and
This class is used for guest logins. Unlike Accounts, Guests and
their characters are deleted after disconnection.
"""
def at_post_login(self, session=None):
def at_post_login(self, session=None, **kwargs):
"""
In theory, guests only have one character regardless of which
MULTISESSION_MODE we're in. They don't get a choice.
Args:
session (Session, optional): Session connecting.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
self._send_to_connect_channel("|G%s connected|n" % self.key)
@ -922,9 +954,14 @@ class DefaultGuest(DefaultPlayer):
print "deleting Character:", character
character.delete()
def at_post_disconnect(self):
def at_post_disconnect(self, **kwargs):
"""
Once having disconnected, destroy the guest's characters and
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
super(DefaultGuest, self).at_post_disconnect()
characters = self.db._playable_characters

View file

@ -9,19 +9,19 @@ from django.conf import settings
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from evennia.players.models import PlayerDB
from evennia.accounts.models import AccountDB
from evennia.typeclasses.admin import AttributeInline, TagInline
from evennia.utils import create
# handle the custom User editor
class PlayerDBChangeForm(UserChangeForm):
class AccountDBChangeForm(UserChangeForm):
"""
Modify the playerdb class.
Modify the accountdb class.
"""
class Meta(object):
model = PlayerDB
model = AccountDB
fields = '__all__'
username = forms.RegexField(
@ -44,19 +44,19 @@ class PlayerDBChangeForm(UserChangeForm):
username = self.cleaned_data['username']
if username.upper() == self.instance.username.upper():
return username
elif PlayerDB.objects.filter(username__iexact=username):
raise forms.ValidationError('A player with that name '
elif AccountDB.objects.filter(username__iexact=username):
raise forms.ValidationError('An account with that name '
'already exists.')
return self.cleaned_data['username']
class PlayerDBCreationForm(UserCreationForm):
class AccountDBCreationForm(UserCreationForm):
"""
Create a new PlayerDB instance.
Create a new AccountDB instance.
"""
class Meta(object):
model = PlayerDB
model = AccountDB
fields = '__all__'
username = forms.RegexField(
@ -76,24 +76,24 @@ class PlayerDBCreationForm(UserCreationForm):
Cleanup username.
"""
username = self.cleaned_data['username']
if PlayerDB.objects.filter(username__iexact=username):
raise forms.ValidationError('A player with that name already '
if AccountDB.objects.filter(username__iexact=username):
raise forms.ValidationError('An account with that name already '
'exists.')
return username
class PlayerForm(forms.ModelForm):
class AccountForm(forms.ModelForm):
"""
Defines how to display Players
Defines how to display Accounts
"""
class Meta(object):
model = PlayerDB
model = AccountDB
fields = '__all__'
db_key = forms.RegexField(
label="Username",
initial="PlayerDummy",
initial="AccountDummy",
max_length=30,
regex=r'^[\w. @+-]+$',
required=False,
@ -101,32 +101,32 @@ class PlayerForm(forms.ModelForm):
error_messages={
'invalid': "This value may contain only letters, spaces, numbers"
" and @/./+/-/_ characters."},
help_text="This should be the same as the connected Player's key "
help_text="This should be the same as the connected Account's key "
"name. 30 characters or fewer. Letters, spaces, digits and "
"@/./+/-/_ only.")
db_typeclass_path = forms.CharField(
label="Typeclass",
initial=settings.BASE_PLAYER_TYPECLASS,
initial=settings.BASE_ACCOUNT_TYPECLASS,
widget=forms.TextInput(
attrs={'size': '78'}),
help_text="Required. Defines what 'type' of entity this is. This "
"variable holds a Python path to a module with a valid "
"Evennia Typeclass. Defaults to "
"settings.BASE_PLAYER_TYPECLASS.")
"settings.BASE_ACCOUNT_TYPECLASS.")
db_permissions = forms.CharField(
label="Permissions",
initial=settings.PERMISSION_PLAYER_DEFAULT,
initial=settings.PERMISSION_ACCOUNT_DEFAULT,
required=False,
widget=forms.TextInput(
attrs={'size': '78'}),
help_text="In-game permissions. A comma-separated list of text "
"strings checked by certain locks. They are often used for "
"hierarchies, such as letting a Player have permission "
"'Wizards', 'Builders' etc. A Player permission can be "
"hierarchies, such as letting an Account have permission "
"'Admin', 'Builder' etc. An Account permission can be "
"overloaded by the permissions of a controlled Character. "
"Normal players use 'Players' by default.")
"Normal accounts use 'Accounts' by default.")
db_lock_storage = forms.CharField(
label="Locks",
@ -137,64 +137,64 @@ class PlayerForm(forms.ModelForm):
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
db_cmdset_storage = forms.CharField(
label="cmdset",
initial=settings.CMDSET_PLAYER,
initial=settings.CMDSET_ACCOUNT,
widget=forms.TextInput(attrs={'size': '78'}),
required=False,
help_text="python path to player cmdset class (set in "
"settings.CMDSET_PLAYER by default)")
help_text="python path to account cmdset class (set in "
"settings.CMDSET_ACCOUNT by default)")
class PlayerInline(admin.StackedInline):
class AccountInline(admin.StackedInline):
"""
Inline creation of Player
Inline creation of Account
"""
model = PlayerDB
template = "admin/players/stacked.html"
form = PlayerForm
model = AccountDB
template = "admin/accounts/stacked.html"
form = AccountForm
fieldsets = (
("In-game Permissions and Locks",
{'fields': ('db_lock_storage',),
#{'fields': ('db_permissions', 'db_lock_storage'),
'description': "<i>These are permissions/locks for in-game use. "
"They are unrelated to website access rights.</i>"}),
("In-game Player data",
("In-game Account data",
{'fields': ('db_typeclass_path', 'db_cmdset_storage'),
'description': "<i>These fields define in-game-specific properties "
"for the Player object in-game.</i>"}))
"for the Account object in-game.</i>"}))
extra = 1
max_num = 1
class PlayerTagInline(TagInline):
class AccountTagInline(TagInline):
"""
Inline Player Tags.
Inline Account Tags.
"""
model = PlayerDB.db_tags.through
related_field = "playerdb"
model = AccountDB.db_tags.through
related_field = "accountdb"
class PlayerAttributeInline(AttributeInline):
class AccountAttributeInline(AttributeInline):
"""
Inline Player Attributes.
Inline Account Attributes.
"""
model = PlayerDB.db_attributes.through
related_field = "playerdb"
model = AccountDB.db_attributes.through
related_field = "accountdb"
class PlayerDBAdmin(BaseUserAdmin):
class AccountDBAdmin(BaseUserAdmin):
"""
This is the main creation screen for Users/players
This is the main creation screen for Users/accounts
"""
list_display = ('username', 'email', 'is_staff', 'is_superuser')
form = PlayerDBChangeForm
add_form = PlayerDBCreationForm
inlines = [PlayerTagInline, PlayerAttributeInline]
form = AccountDBChangeForm
add_form = AccountDBCreationForm
inlines = [AccountTagInline, AccountAttributeInline]
fieldsets = (
(None, {'fields': ('username', 'password', 'email')}),
('Website profile', {
@ -240,16 +240,16 @@ class PlayerDBAdmin(BaseUserAdmin):
"""
obj.save()
if not change:
#calling hooks for new player
obj.set_class_from_typeclass(typeclass_path=settings.BASE_PLAYER_TYPECLASS)
#calling hooks for new account
obj.set_class_from_typeclass(typeclass_path=settings.BASE_ACCOUNT_TYPECLASS)
obj.basetype_setup()
obj.at_player_creation()
obj.at_account_creation()
def response_add(self, request, obj, post_url_continue=None):
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
if '_continue' in request.POST:
return HttpResponseRedirect(reverse("admin:players_playerdb_change", args=[obj.id]))
return HttpResponseRedirect(reverse("admin:players_playerdb_change", args=[obj.id]))
return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))
return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))
admin.site.register(PlayerDB, PlayerDBAdmin)
admin.site.register(AccountDB, AccountDBAdmin)

View file

@ -1,12 +1,12 @@
"""
Bots are a special child typeclasses of
Player that are controlled by the server.
Account that are controlled by the server.
"""
from __future__ import print_function
import time
from django.conf import settings
from evennia.players.players import DefaultPlayer
from evennia.accounts.accounts import DefaultAccount
from evennia.scripts.scripts import DefaultScript
from evennia.utils import search
from evennia.utils import utils
@ -48,7 +48,7 @@ class BotStarter(DefaultScript):
"""
if not self.db.started:
self.player.start()
self.account.start()
self.db.started = True
def at_repeat(self):
@ -63,7 +63,7 @@ class BotStarter(DefaultScript):
global _SESSIONS
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
for session in _SESSIONS.sessions_from_player(self.player):
for session in _SESSIONS.sessions_from_account(self.account):
session.update_session_counters(idle=True)
def at_server_reload(self):
@ -85,7 +85,7 @@ class BotStarter(DefaultScript):
# Bot base class
class Bot(DefaultPlayer):
class Bot(DefaultAccount):
"""
A Bot will start itself when the server starts (it will generally
not do so on a reload - that will be handled by the normal Portal
@ -101,7 +101,7 @@ class Bot(DefaultPlayer):
# the text encoding to use.
self.db.encoding = "utf-8"
# A basic security setup
lockstring = "examine:perm(Wizards);edit:perm(Wizards);delete:perm(Wizards);boot:perm(Wizards);msg:false()"
lockstring = "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);boot:perm(Admin);msg:false()"
self.locks.add(lockstring)
# set the basics of being a bot
script_key = "%s" % self.key
@ -211,7 +211,7 @@ class IRCBot(Bot):
Retrive the nick list from the connected channel.
Args:
caller (Object or Player): The requester of the list. This will
caller (Object or Account): The requester of the list. This will
be stored and echoed to when the irc network replies with the
requested info.
@ -231,7 +231,7 @@ class IRCBot(Bot):
Fire a ping to the IRC server.
Args:
caller (Object or Player): The requester of the ping.
caller (Object or Account): The requester of the ping.
"""
if not hasattr(self, "_ping_callers"):
@ -242,7 +242,7 @@ class IRCBot(Bot):
def reconnect(self):
"""
Force a protocol-side reconnect of the client without
having to destroy/recreate the bot "player".
having to destroy/recreate the bot "account".
"""
super(IRCBot, self).msg(reconnect="")
@ -323,8 +323,8 @@ class IRCBot(Bot):
for sess in _SESSIONS.get_sessions():
delta_cmd = t0 - sess.cmd_last_visible
delta_conn = t0 - session.conn_time
player = sess.get_player()
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % player.name, width=25),
account = sess.get_account()
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % account.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1)))
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower()))

View file

@ -1,25 +1,22 @@
"""
The managers for the custom Player object and permissions.
The managers for the custom Account object and permissions.
"""
import datetime
from django.utils import timezone
from django.contrib.auth.models import UserManager
#from functools import update_wrapper
from evennia.typeclasses.managers import (returns_typeclass_list, returns_typeclass,
TypedObjectManager, TypeclassManager)
from evennia.utils.utils import make_iter
__all__ = ("PlayerManager",)
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager)
__all__ = ("AccountManager",)
#
# Player Manager
# Account Manager
#
class PlayerDBManager(TypedObjectManager, UserManager):
class AccountDBManager(TypedObjectManager, UserManager):
"""
This PlayerManager implements methods for searching
and manipulating Players directly from the database.
This AccountManager implements methods for searching
and manipulating Accounts directly from the database.
Evennia-specific search methods (will return Characters if
possible or a Typeclass/list of Typeclassed objects, whereas
@ -30,49 +27,47 @@ class PlayerDBManager(TypedObjectManager, UserManager):
get_dbref_range
object_totals
typeclass_search
num_total_players
get_connected_players
get_recently_created_players
get_recently_connected_players
get_player_from_email
get_player_from_uid
get_player_from_name
player_search (equivalent to evennia.search_player)
num_total_accounts
get_connected_accounts
get_recently_created_accounts
get_recently_connected_accounts
get_account_from_email
get_account_from_uid
get_account_from_name
account_search (equivalent to evennia.search_account)
#swap_character
"""
def num_total_players(self):
def num_total_accounts(self):
"""
Get total number of players.
Get total number of accounts.
Returns:
count (int): The total number of registered players.
count (int): The total number of registered accounts.
"""
return self.count()
@returns_typeclass_list
def get_connected_players(self):
def get_connected_accounts(self):
"""
Get all currently connected players.
Get all currently connected accounts.
Returns:
count (list): Player objects with currently
count (list): Account objects with currently
connected sessions.
"""
return self.filter(db_is_connected=True)
@returns_typeclass_list
def get_recently_created_players(self, days=7):
def get_recently_created_accounts(self, days=7):
"""
Get players recently created.
Get accounts recently created.
Args:
days (int, optional): How many days in the past "recently" means.
Returns:
players (list): The Players created the last `days` interval.
accounts (list): The Accounts created the last `days` interval.
"""
end_date = timezone.now()
@ -80,16 +75,15 @@ class PlayerDBManager(TypedObjectManager, UserManager):
start_date = end_date - tdelta
return self.filter(date_joined__range=(start_date, end_date))
@returns_typeclass_list
def get_recently_connected_players(self, days=7):
def get_recently_connected_accounts(self, days=7):
"""
Get players recently connected to the game.
Get accounts recently connected to the game.
Args:
days (int, optional): Number of days backwards to check
Returns:
players (list): The Players connected to the game in the
accounts (list): The Accounts connected to the game in the
last `days` interval.
"""
@ -99,31 +93,29 @@ class PlayerDBManager(TypedObjectManager, UserManager):
return self.filter(last_login__range=(
start_date, end_date)).order_by('-last_login')
@returns_typeclass
def get_player_from_email(self, uemail):
def get_account_from_email(self, uemail):
"""
Search player by
Returns a player object based on email address.
Search account by
Returns an account object based on email address.
Args:
uemail (str): An email address to search for.
Returns:
player (Player): A found player, if found.
account (Account): A found account, if found.
"""
return self.filter(email__iexact=uemail)
@returns_typeclass
def get_player_from_uid(self, uid):
def get_account_from_uid(self, uid):
"""
Get a player by id.
Get an account by id.
Args:
uid (int): Player database id.
uid (int): Account database id.
Returns:
player (Player): The result.
account (Account): The result.
"""
try:
@ -131,16 +123,15 @@ class PlayerDBManager(TypedObjectManager, UserManager):
except self.model.DoesNotExist:
return None
@returns_typeclass
def get_player_from_name(self, uname):
def get_account_from_name(self, uname):
"""
Get player object based on name.
Get account object based on name.
Args:
uname (str): The Player name to search for.
uname (str): The Account name to search for.
Returns:
player (Player): The found player.
account (Account): The found account.
"""
try:
@ -148,10 +139,9 @@ class PlayerDBManager(TypedObjectManager, UserManager):
except self.model.DoesNotExist:
return None
@returns_typeclass_list
def search_player(self, ostring, exact=True, typeclass=None):
def search_account(self, ostring, exact=True, typeclass=None):
"""
Searches for a particular player by name or
Searches for a particular account by name or
database id.
Args:
@ -161,7 +151,7 @@ class PlayerDBManager(TypedObjectManager, UserManager):
otherwise also match also keys containing the `ostring`
(non-case-sensitive fuzzy match).
typeclass (str or Typeclass, optional): Limit the search only to
players of this typeclass.
accounts of this typeclass.
"""
dbref = self.dbref(ostring)
@ -183,7 +173,8 @@ class PlayerDBManager(TypedObjectManager, UserManager):
else:
return self.filter(**query)
# back-compatibility alias
player_search = search_player
account_search = search_account
class PlayerManager(PlayerDBManager, TypeclassManager):
class AccountManager(AccountDBManager, TypeclassManager):
pass

View file

@ -15,7 +15,7 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='PlayerDB',
name='AccountDB',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
@ -32,7 +32,7 @@ class Migration(migrations.Migration):
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
('db_is_connected', models.BooleanField(default=False, help_text=b'If player is connected to game or not', verbose_name=b'is_connected')),
('db_is_connected', models.BooleanField(default=False, help_text=b'If account is connected to game or not', verbose_name=b'is_connected')),
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name=b'cmdset')),
('db_is_bot', models.BooleanField(default=False, help_text=b'Used to identify irc/rss bots', verbose_name=b'is_bot')),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
@ -41,8 +41,8 @@ class Migration(migrations.Migration):
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
],
options={
'verbose_name': 'Player',
'verbose_name_plural': 'Players',
'verbose_name': 'Account',
'verbose_name_plural': 'Accounts',
},
bases=(models.Model,),
),

View file

@ -4,15 +4,15 @@ from __future__ import unicode_literals
from django.db import models, migrations
def convert_defaults(apps, schema_editor):
PlayerDB = apps.get_model("players", "PlayerDB")
for player in PlayerDB.objects.filter(db_typeclass_path="src.players.player.Player"):
player.db_typeclass_path = "typeclasses.players.Player"
player.save()
AccountDB = apps.get_model("accounts", "AccountDB")
for account in AccountDB.objects.filter(db_typeclass_path="src.accounts.account.Account"):
account.db_typeclass_path = "typeclasses.accounts.Account"
account.save()
class Migration(migrations.Migration):
dependencies = [
('players', '0001_initial'),
('accounts', '0001_initial'),
]
operations = [

View file

@ -7,18 +7,18 @@ from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('players', '0002_move_defaults'),
('accounts', '0002_move_defaults'),
]
operations = [
migrations.CreateModel(
name='DefaultPlayer',
name='DefaultAccount',
fields=[
],
options={
'proxy': True,
},
bases=('players.playerdb',),
bases=('accounts.accountdb',),
),
migrations.CreateModel(
name='DefaultGuest',
@ -27,10 +27,10 @@ class Migration(migrations.Migration):
options={
'proxy': True,
},
bases=('players.defaultplayer',),
bases=('accounts.defaultaccount',),
),
migrations.AlterModelOptions(
name='playerdb',
options={'verbose_name': 'Player'},
name='accountdb',
options={'verbose_name': 'Account'},
),
]

View file

@ -2,14 +2,14 @@
from __future__ import unicode_literals
from django.db import models, migrations
import evennia.players.manager
import evennia.accounts.manager
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('players', '0003_auto_20150209_2234'),
('accounts', '0003_auto_20150209_2234'),
]
operations = [
@ -17,31 +17,31 @@ class Migration(migrations.Migration):
name='DefaultGuest',
),
migrations.DeleteModel(
name='DefaultPlayer',
name='DefaultAccount',
),
migrations.AlterModelManagers(
name='playerdb',
name='accountdb',
managers=[
(b'objects', evennia.players.manager.PlayerDBManager()),
(b'objects', evennia.accounts.manager.AccountDBManager()),
],
),
migrations.AlterField(
model_name='playerdb',
model_name='accountdb',
name='email',
field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
),
migrations.AlterField(
model_name='playerdb',
model_name='accountdb',
name='groups',
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
),
migrations.AlterField(
model_name='playerdb',
model_name='accountdb',
name='last_login',
field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
),
migrations.AlterField(
model_name='playerdb',
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
),

View file

@ -9,12 +9,12 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('players', '0004_auto_20150403_2339'),
('accounts', '0004_auto_20150403_2339'),
]
operations = [
migrations.AlterField(
model_name='playerdb',
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'),
),

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-06 17:31
from __future__ import unicode_literals
import django.contrib.auth.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_auto_20160905_0902'),
]
operations = [
migrations.AlterField(
model_name='accountdb',
name='db_attributes',
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
),
migrations.AlterField(
model_name='accountdb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
),
migrations.AlterField(
model_name='accountdb',
name='username',
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'),
),
]

View file

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-03 19:17
from __future__ import unicode_literals
from django.apps import apps as global_apps
from django.db import migrations
def forwards(apps, schema_editor):
try:
PlayerDB = apps.get_model('players', 'PlayerDB')
except LookupError:
# playerdb not available. Skip.
return
AccountDB = apps.get_model('accounts', 'AccountDB')
for player in PlayerDB.objects.all():
account = AccountDB(id=player.id,
password=player.password,
is_superuser=player.is_superuser,
last_login=player.last_login,
username=player.username,
first_name=player.first_name,
last_name=player.last_name,
email=player.email,
is_staff=player.is_staff,
is_active=player.is_active,
date_joined=player.date_joined,
db_key=player.db_key,
db_typeclass_path=player.db_typeclass_path,
db_date_created=player.db_date_created,
db_lock_storage=player.db_lock_storage,
db_is_connected=player.db_is_connected,
db_cmdset_storage=player.db_cmdset_storage,
db_is_bot=player.db_is_bot)
account.save()
for group in player.groups.all():
account.groups.add(group)
for user_permission in player.user_permissions.all():
account.user_permissions.add(user_permission)
for attr in player.db_attributes.all():
account.db_attributes.add(attr)
for tag in player.db_tags.all():
account.db_tags.add(tag)
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_auto_20170606_1731'),
]
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop)
]
if global_apps.is_installed('players'):
dependencies.append(('players', '0006_auto_20170606_1731'))

View file

@ -1,16 +1,16 @@
"""
Player
Account
The player class is an extension of the default Django user class,
The account class is an extension of the default Django user class,
and is customized for the needs of Evennia.
We use the Player to store a more mud-friendly style of permission
We use the Account to store a more mud-friendly style of permission
system as well as to allow the admin more flexibility by storing
attributes on the Player. Within the game we should normally use the
Player manager's methods to create users so that permissions are set
attributes on the Account. Within the game we should normally use the
Account manager's methods to create users so that permissions are set
correctly.
To make the Player model more flexible for your own game, it can also
To make the Account model more flexible for your own game, it can also
persistently store attributes of its own. This is ideal for extra
account info and OOC account configuration variables etc.
@ -22,11 +22,11 @@ from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.encoding import smart_str
from evennia.players.manager import PlayerDBManager
from evennia.accounts.manager import AccountDBManager
from evennia.typeclasses.models import TypedObject
from evennia.utils.utils import make_iter
__all__ = ("PlayerDB",)
__all__ = ("AccountDB",)
#_ME = _("me")
#_SELF = _("self")
@ -42,11 +42,11 @@ _TYPECLASS = None
#------------------------------------------------------------
#
# PlayerDB
# AccountDB
#
#------------------------------------------------------------
class PlayerDB(TypedObject, AbstractUser):
class AccountDB(TypedObject, AbstractUser):
"""
This is a special model using Django's 'profile' functionality
and extends the default Django User model. It is defined as such
@ -66,18 +66,18 @@ class PlayerDB(TypedObject, AbstractUser):
- db - persistent attribute storage
- ndb - non-persistent attribute storage
The PlayerDB adds the following properties:
The AccountDB adds the following properties:
- is_connected - If any Session is currently connected to this Player
- is_connected - If any Session is currently connected to this Account
- name - alias for user.username
- sessions - sessions connected to this player
- is_superuser - bool if this player is a superuser
- is_bot - bool if this player is a bot and not a real player
- sessions - sessions connected to this account
- is_superuser - bool if this account is a superuser
- is_bot - bool if this account is a bot and not a real account
"""
#
# PlayerDB Database model setup
# AccountDB Database model setup
#
# inherited fields (from TypedObject):
# db_key, db_typeclass_path, db_date_created, db_permissions
@ -90,19 +90,19 @@ class PlayerDB(TypedObject, AbstractUser):
# database storage of persistant cmdsets.
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True,
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.")
# marks if this is a "virtual" bot player object
# marks if this is a "virtual" bot account object
db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots")
# Database manager
objects = PlayerDBManager()
objects = AccountDBManager()
# defaults
__settingsclasspath__ = settings.BASE_SCRIPT_TYPECLASS
__defaultclasspath__ = "evennia.players.players.DefaultPlayer"
__applabel__ = "players"
__defaultclasspath__ = "evennia.accounts.accounts.DefaultAccount"
__applabel__ = "accounts"
class Meta(object):
verbose_name = 'Player'
verbose_name = 'Account'
# cmdset_storage property
# This seems very sensitive to caching, so leaving it be for now /Griatch
@ -136,10 +136,10 @@ class PlayerDB(TypedObject, AbstractUser):
#
def __str__(self):
return smart_str("%s(player %s)" % (self.name, self.dbid))
return smart_str("%s(account %s)" % (self.name, self.dbid))
def __unicode__(self):
return u"%s(player#%s)" % (self.name, self.dbid)
return u"%s(account#%s)" % (self.name, self.dbid)
#@property
def __username_get(self):

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
This sub-package contains Evennia's command system. It handles
everything related to parsing input from the player, building cmdsets
everything related to parsing input from the account, building cmdsets
and executing the code associated with a found command class.
commands.default contains all the default "mux-like" commands of

View file

@ -13,7 +13,7 @@ command line. The processing of a command works as follows:
- object cmdsets: all objects at caller's location are scanned for non-empty
cmdsets. This includes cmdsets on exits.
- caller: the caller is searched for its own currently active cmdset.
- player: lastly the cmdsets defined on caller.player are added.
- account: lastly the cmdsets defined on caller.account are added.
3. The collected cmdsets are merged together to a combined, current cmdset.
4. If the input string is empty -> check for CMD_NOINPUT command in
current cmdset or fallback to error message. Exit.
@ -85,7 +85,7 @@ CMD_LOGINSTART = "__unloggedin_look_command"
_SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
# Output strings. The first is the IN_GAME_ERRORS return, the second
# is the normal "production message to echo to the player.
# is the normal "production message to echo to the account.
_ERROR_UNTRAPPED = (
"""
@ -128,7 +128,7 @@ likely file a bug report with the Evennia project.
""")
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
"reached for '{raw_string}' ({cmdclass})."
"reached for '{raw_cmdname}' ({cmdclass})."
# delayed imports
@ -210,7 +210,7 @@ def _process_input(caller, prompt, result, cmd, generator):
part of yielding from a Command's `func`.
Args:
caller (Character, Player or Session): the caller.
caller (Character, Account or Session): the caller.
prompt (basestring): The sent prompt.
result (basestring): The unprocessed answer.
cmd (Command): The command itself.
@ -248,20 +248,20 @@ class ErrorReported(Exception):
# Helper function
@inlineCallbacks
def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string):
"""
Gather all relevant cmdsets and merge them.
Args:
caller (Session, Player or Object): The entity executing the command. Which
caller (Session, Account or Object): The entity executing the command. Which
type of object this is depends on the current game state; for example
when the user is not logged in, this will be a Session, when being OOC
it will be a Player and when puppeting an object this will (often) be
it will be an Account and when puppeting an object this will (often) be
a Character Object. In the end it depends on where the cmdset is stored.
session (Session or None): The Session associated with caller, if any.
player (Player or None): The calling Player associated with caller, if any.
account (Account or None): The calling Account associated with caller, if any.
obj (Object or None): The Object associated with caller, if any.
callertype (str): This identifies caller as either "player", "object" or "session"
callertype (str): This identifies caller as either "account", "object" or "session"
to avoid having to do this check internally.
raw_string (str): The input string. This is only used for error reporting.
@ -272,18 +272,18 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
Notes:
The cdmsets are merged in order or generality, so that the
Object's cmdset is merged last (and will thus take precedence
over same-named and same-prio commands on Player and Session).
over same-named and same-prio commands on Account and Session).
"""
try:
@inlineCallbacks
def _get_channel_cmdset(player_or_obj):
def _get_channel_cmdset(account_or_obj):
"""
Helper-method; Get channel-cmdsets
"""
# Create cmdset for all player's available channels
# Create cmdset for all account's available channels
try:
channel_cmdset = yield CHANNELHANDLER.get_cmdset(player_or_obj)
channel_cmdset = yield CHANNELHANDLER.get_cmdset(account_or_obj)
returnValue([channel_cmdset])
except Exception:
_msg_err(caller, _ERROR_CMDSETS)
@ -313,8 +313,8 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
_GA(lobj, "at_cmdset_get")(caller=caller)
except Exception:
logger.log_trace()
# the call-type lock is checked here, it makes sure a player
# is not seeing e.g. the commands on a fellow player (which is why
# the call-type lock is checked here, it makes sure an account
# is not seeing e.g. the commands on a fellow account (which is why
# the no_superuser_bypass must be True)
local_obj_cmdsets = \
yield list(chain.from_iterable(
@ -355,9 +355,9 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
# we are calling the command from the session level
report_to = session
current, cmdsets = yield _get_cmdsets(session)
if player: # this automatically implies logged-in
pcurrent, player_cmdsets = yield _get_cmdsets(player)
cmdsets += player_cmdsets
if account: # this automatically implies logged-in
pcurrent, account_cmdsets = yield _get_cmdsets(account)
cmdsets += account_cmdsets
current = current + pcurrent
if obj:
ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
@ -374,13 +374,13 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
channel_cmdsets = yield _get_channel_cmdset(obj)
cmdsets += channel_cmdsets
if not current.no_channels:
channel_cmdsets = yield _get_channel_cmdset(player)
channel_cmdsets = yield _get_channel_cmdset(account)
cmdsets += channel_cmdsets
elif callertype == "player":
# we are calling the command from the player level
report_to = player
current, cmdsets = yield _get_cmdsets(player)
elif callertype == "account":
# we are calling the command from the account level
report_to = account
current, cmdsets = yield _get_cmdsets(account)
if obj:
ocurrent, obj_cmdsets = yield _get_cmdsets(obj)
current = current + ocurrent
@ -395,7 +395,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
# also objs may have channels
cmdsets += yield _get_channel_cmdset(obj)
if not current.no_channels:
cmdsets += yield _get_channel_cmdset(player)
cmdsets += yield _get_channel_cmdset(account)
elif callertype == "object":
# we are calling the command from the object level
@ -472,22 +472,22 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
This is the main mechanism that handles any string sent to the engine.
Args:
called_by (Session, Player or Object): Object from which this
called_by (Session, Account or Object): Object from which this
command was called. which this was called from. What this is
depends on the game state.
raw_string (str): The command string as given on the command line.
_testing (bool, optional): Used for debug purposes and decides if we
should actually execute the command or not. If True, the
command instance will be returned.
callertype (str, optional): One of "session", "player" or
callertype (str, optional): One of "session", "account" or
"object". These are treated in decending order, so when the
Session is the caller, it will merge its own cmdset into
cmdsets from both Player and eventual puppeted Object (and
cmdsets in its room etc). A Player will only include its own
cmdsets from both Account and eventual puppeted Object (and
cmdsets in its room etc). An Account will only include its own
cmdset and the Objects and so on. Merge order is the same
order, so that Object cmdsets are merged in last, giving them
precendence for same-name and same-prio commands.
session (Session, optional): Relevant if callertype is "player" - the session will help
session (Session, optional): Relevant if callertype is "account" - the session will help
retrieve the correct cmdsets from puppeted objects.
cmdobj (Command, optional): If given a command instance, this will be executed using
`called_by` as the caller, `raw_string` representing its arguments and (optionally)
@ -513,20 +513,22 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
"""
@inlineCallbacks
def _run_command(cmd, cmdname, args, raw_string, cmdset, session, player):
def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account):
"""
Helper function: This initializes and runs the Command
instance once the parser has identified it as either a normal
command or one of the system commands.
Args:
cmd (Command): Command object.
cmdname (str): Name of command.
args (str): Extra text entered after the identified command.
raw_string (str): Full input string.
cmd (Command): Command object
cmdname (str): Name of command
args (str): extra text entered after the identified command
raw_cmdname (str): Name of Command, unaffected by eventual
prefix-stripping (if no prefix-stripping, this is the same
as cmdname).
cmdset (CmdSet): Command sert the command belongs to (if any)..
session (Session): Session of caller (if any).
player (Player): Player of caller (if any).
account (Account): Account of caller (if any).
Returns:
deferred (Deferred): this will fire with the return of the
@ -540,12 +542,14 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
try:
# Assign useful variables to the instance
cmd.caller = caller
cmd.cmdstring = cmdname
cmd.cmdname = cmdname
cmd.raw_cmdname = raw_cmdname
cmd.cmdstring = cmdname # deprecated
cmd.args = args
cmd.cmdset = cmdset
cmd.session = session
cmd.player = player
cmd.raw_string = raw_string
cmd.account = account
cmd.raw_string = unformatted_raw_string
#cmd.obj # set via on-object cmdset handler for each command,
# since this may be different for every command when
# merging multuple cmdsets
@ -566,7 +570,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
_COMMAND_NESTING[called_by] += 1
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT,
raw_string=raw_string,
raw_cmdname=raw_cmdname,
cmdclass=cmd.__class__)
raise RuntimeError(err)
@ -614,13 +618,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
raw_string = to_unicode(raw_string, force_string=True)
session, player, obj = session, None, None
session, account, obj = session, None, None
if callertype == "session":
session = called_by
player = session.player
account = session.account
obj = session.puppet
elif callertype == "player":
player = called_by
elif callertype == "account":
account = called_by
if session:
obj = yield session.puppet
elif callertype == "object":
@ -629,32 +633,32 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
raise RuntimeError("cmdhandler: callertype %s is not valid." % callertype)
# the caller will be the one to receive messages and excert its permissions.
# we assign the caller with preference 'bottom up'
caller = obj or player or session
# The error_to is the default recipient for errors. Tries to make sure a player
caller = obj or account or session
# The error_to is the default recipient for errors. Tries to make sure an account
# does not get spammed for errors while preserving character mirroring.
error_to = obj or session or player
error_to = obj or session or account
try: # catch bugs in cmdhandler itself
try: # catch special-type commands
if cmdobj:
# the command object is already given
cmd = cmdobj() if callable(cmdobj) else cmdobj
cmdname = cmdobj_key if cmdobj_key else cmd.key
args = raw_string
unformatted_raw_string = "%s%s" % (cmdname, args)
cmdset = None
session = session
player = player
account = account
else:
# no explicit cmdobject given, figure it out
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
cmdset = yield get_and_merge_cmdsets(caller, session, account, obj,
callertype, raw_string)
if not cmdset:
# this is bad and shouldn't happen.
raise NoCmdSets
# store the completely unmodified raw string - including
# whitespace and eventual prefixes-to-be-stripped.
unformatted_raw_string = raw_string
raw_string = raw_string.strip()
if not raw_string:
@ -685,7 +689,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
if len(matches) == 1:
# We have a unique command match. But it may still be invalid.
match = matches[0]
cmdname, args, cmd = match[0], match[1], match[2]
cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
if not matches:
# No commands match our entered command
@ -718,7 +722,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
raise ExecSystemCommand(cmd, sysarg)
# A normal command.
ret = yield _run_command(cmd, cmdname, args, unformatted_raw_string, cmdset, session, player)
ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account)
returnValue(ret)
except ErrorReported as exc:
@ -734,7 +738,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
if syscmd:
ret = yield _run_command(syscmd, syscmd.key, sysarg,
unformatted_raw_string, cmdset, session, player)
unformatted_raw_string, cmdset, session, account)
returnValue(ret)
elif sysarg:
# return system arg

View file

@ -12,6 +12,7 @@ from django.conf import settings
from evennia.utils.logger import log_trace
_MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
_CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
def cmdparser(raw_string, cmdset, caller, match_index=None):
"""
@ -21,7 +22,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
Args:
raw_string (str): The unparsed text entered by the caller.
cmdset (CmdSet): The merged, currently valid cmdset
caller (Session, Player or Object): The caller triggering this parsing.
caller (Session, Account or Object): The caller triggering this parsing.
match_index (int, optional): Index to pick a given match in a
list of same-named command matches. If this is given, it suggests
this is not the first time this function was called: normally
@ -46,7 +47,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
"""
def create_match(cmdname, string, cmdobj):
def create_match(cmdname, string, cmdobj, raw_cmdname):
"""
Builds a command match by splitting the incoming string and
evaluating the quality of the match.
@ -55,51 +56,79 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
cmdname (str): Name of command to check for.
string (str): The string to match against.
cmdobj (str): The full Command instance.
raw_cmdname (str, optional): If CMD_IGNORE_PREFIX is set and the cmdname starts with
one of the prefixes to ignore, this contains the raw, unstripped cmdname,
otherwise it is None.
Returns:
match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio), where
match (tuple): This is on the form (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname), where
`cmdname` is the command's name and `args` the rest of the incoming string,
without said command name. `cmdobj` is the Command instance, the cmdlen is
the same as len(cmdname) and mratio is a measure of how big a part of the
full input string the cmdname takes up - an exact match would be 1.0.
full input string the cmdname takes up - an exact match would be 1.0. Finally,
the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping.
"""
cmdlen, strlen = len(unicode(cmdname)), len(unicode(string))
mratio = 1 - (strlen - cmdlen) / (1.0 * strlen)
args = string[cmdlen:]
return (cmdname, args, cmdobj, cmdlen, mratio)
return (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname)
def build_matches(raw_string, include_prefixes=False):
l_raw_string = raw_string.lower()
matches = []
try:
if include_prefixes:
# use the cmdname as-is
for cmd in cmdset:
matches.extend([create_match(cmdname, raw_string, cmd, cmdname)
for cmdname in [cmd.key] + cmd.aliases
if cmdname and l_raw_string.startswith(cmdname.lower())
and (not cmd.arg_regex or
cmd.arg_regex.match(l_raw_string[len(cmdname):]))])
else:
# strip prefixes set in settings
for cmd in cmdset:
for raw_cmdname in [cmd.key] + cmd.aliases:
cmdname = raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_cmdname) > 1 else raw_cmdname
if cmdname and l_raw_string.startswith(cmdname.lower()) and \
(not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])):
matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname))
except Exception:
log_trace("cmdhandler error. raw_input:%s" % raw_string)
return matches
def try_num_prefixes(raw_string):
if not matches:
# no matches found
num_ref_match = _MULTIMATCH_REGEX.match(raw_string)
if num_ref_match:
# the user might be trying to identify the command
# with a #num-command style syntax. We expect the regex to
# contain the groups "number" and "name".
mindex, new_raw_string = num_ref_match.group("number"), num_ref_match.group("name")
return mindex, new_raw_string
return None, None
if not raw_string:
return []
matches = []
# match everything that begins with a matching cmdname.
l_raw_string = raw_string.lower()
for cmd in cmdset:
try:
matches.extend([create_match(cmdname, raw_string, cmd)
for cmdname in [cmd.key] + cmd.aliases
if cmdname and l_raw_string.startswith(cmdname.lower())
and (not cmd.arg_regex or
cmd.arg_regex.match(l_raw_string[len(cmdname):]))])
except Exception:
log_trace("cmdhandler error. raw_input:%s" % raw_string)
# find mathces, first using the full name
matches = build_matches(raw_string, include_prefixes=True)
if not matches:
# no matches found
num_ref_match = _MULTIMATCH_REGEX.match(raw_string)
if num_ref_match:
# the user might be trying to identify the command
# with a #num-command style syntax. We expect the regex to
# contain the groups "number" and "name".
mindex, new_raw_string = num_ref_match.group("number"), num_ref_match.group("name")
return cmdparser(new_raw_string, cmdset,
caller, match_index=int(mindex))
# try to match a number 1-cmdname, 2-cmdname etc
mindex, new_raw_string = try_num_prefixes(raw_string)
if mindex is not None:
return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex))
if _CMD_IGNORE_PREFIXES:
# still no match. Try to strip prefixes
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string
matches = build_matches(raw_string, include_prefixes=False)
# only select command matches we are actually allowed to call.
matches = [match for match in matches if match[2].access(caller, 'cmd')]
# try to bring the number of matches down to 1
if len(matches) > 1:
# See if it helps to analyze the match with preserved case but only if
# it leaves at least one match.
@ -129,4 +158,3 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
# no matter what we have at this point, we have to return it.
return matches

View file

@ -4,7 +4,7 @@ A Command Set (CmdSet) holds a set of commands. The Cmdsets can be
merged and combined to create new sets of commands in a
non-destructive way. This makes them very powerful for implementing
custom game states where different commands (or different variations
of commands) are available to the players depending on circumstance.
of commands) are available to the accounts depending on circumstance.
The available merge operations are partly borrowed from mathematical
Set theory.
@ -110,9 +110,9 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
merger (i.e. A above) automatically taking
precedence. But if allow_duplicates is true, the
result will be a merger with more than one of each
name match. This will usually lead to the player
name match. This will usually lead to the account
receiving a multiple-match error higher up the road,
but can be good for things like cmdsets on non-player
but can be good for things like cmdsets on non-account
objects in a room, to allow the system to warn that
more than one 'ball' in the room has the same 'kick'
command defined on it, so it may offer a chance to
@ -134,7 +134,7 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
commands
no_channels - ignore the name of channels when matching against
commands (WARNING- this is dangerous since the
player can then not even ask staff for help if
account can then not even ask staff for help if
something goes wrong)
@ -167,9 +167,9 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
Creates a new CmdSet instance.
Args:
cmdsetobj (Session, Player, Object, optional): This is the database object
cmdsetobj (Session, Account, Object, optional): This is the database object
to which this particular instance of cmdset is related. It
is often a character but may also be a regular object, Player
is often a character but may also be a regular object, Account
or Session.
key (str, optional): The idenfier for this cmdset. This
helps if wanting to selectively remov cmdsets.

View file

@ -9,8 +9,8 @@ intelligent container that, when added to other CmdSet make sure that
same-name commands are treated correctly (usually so there are no
doublets). This temporary but up-to-date merger of CmdSet is jointly
called the Current Cmset. It is this Current CmdSet that the
commandhandler looks through whenever a player enters a command (it
also adds CmdSets from objects in the room in real-time). All player
commandhandler looks through whenever an account enters a command (it
also adds CmdSets from objects in the room in real-time). All account
objects have a 'default cmdset' containing all the normal in-game mud
commands (look etc).
@ -19,12 +19,12 @@ So what is all this cmdset complexity good for?
In its simplest form, a CmdSet has no commands, only a key name. In
this case the cmdset's use is up to each individual game - it can be
used by an AI module for example (mobs in cmdset 'roam' move from room
to room, in cmdset 'attack' they enter combat with players).
to room, in cmdset 'attack' they enter combat with accounts).
Defining commands in cmdsets offer some further powerful game-design
consequences however. Here are some examples:
As mentioned above, all players always have at least the Default
As mentioned above, all accounts always have at least the Default
CmdSet. This contains the set of all normal-use commands in-game,
stuff like look and @desc etc. Now assume our players end up in a dark
room. You don't want the player to be able to do much in that dark
@ -37,7 +37,7 @@ and have this completely replace the default cmdset.
Another example: Say you want your players to be able to go
fishing. You could implement this as a 'fish' command that fails
whenever the player has no fishing rod. Easy enough. But what if you
whenever the account has no fishing rod. Easy enough. But what if you
want to make fishing more complex - maybe you want four-five different
commands for throwing your line, reeling in, etc? Most players won't
(we assume) have fishing gear, and having all those detailed commands
@ -48,7 +48,7 @@ for a minor thing like fishing?
So instead you put all those detailed fishing commands into their own
CommandSet called 'Fishing'. Whenever the player gives the command
'fish' (presumably the code checks there is also water nearby), only
THEN this CommandSet is added to the Cmdhandler of the player. The
THEN this CommandSet is added to the Cmdhandler of the account. The
'throw' command (which normally throws rocks) is replaced by the
custom 'fishing variant' of throw. What has happened is that the
Fishing CommandSet was merged on top of the Default ones, and due to
@ -128,7 +128,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
Args:
path (str): The path to the command set to load.
cmdsetobj (CmdSet): The database object/typeclass on which this cmdset is to be
assigned (this can be also channels and exits, as well as players
assigned (this can be also channels and exits, as well as accounts
but there will always be such an object)
emit_to_obj (Object, optional): If given, error is emitted to
this object (in addition to logging)

View file

@ -152,14 +152,14 @@ class Command(with_metaclass(CommandMeta, object)):
is_exit = False
# define the command not only by key but by the regex form of its arguments
arg_regex = settings.COMMAND_DEFAULT_ARG_REGEX
# whether self.msg sends to all sessions of a related player/object (default
# whether self.msg sends to all sessions of a related account/object (default
# is to only send to the session sending the command).
msg_all_sessions = settings.COMMAND_DEFAULT_MSG_ALL_SESSIONS
# auto-set (by Evennia on command instantiation) are:
# obj - which object this command is defined on
# session - which session is responsible for triggering this command. Only set
# if triggered by a player.
# if triggered by an account.
def __init__(self, **kwargs):
"""
@ -307,7 +307,7 @@ class Command(with_metaclass(CommandMeta, object)):
session=None, **kwargs):
"""
This is a shortcut instead of calling msg() directly on an
object - it will detect if caller is an Object or a Player and
object - it will detect if caller is an Object or an Account and
also appends self.session automatically if self.msg_all_sessions is False.
Args:
@ -340,7 +340,7 @@ class Command(with_metaclass(CommandMeta, object)):
Args:
raw_string (str): Execute this string as a command input.
session (Session, optional): If not given, the current command's Session will be used.
obj (Object or Player, optional): Object or Player on which to call the execute_cmd.
obj (Object or Account, optional): Object or Account on which to call the execute_cmd.
If not given, self.caller will be used.
Kwargs:
@ -443,7 +443,7 @@ class Command(with_metaclass(CommandMeta, object)):
commands the caller can use.
Args:
caller (Object or Player): the caller asking for help on the command.
caller (Object or Account): the caller asking for help on the command.
cmdset (CmdSet): the command set (if you need additional commands).
Returns:

View file

@ -1,19 +1,19 @@
"""
Player (OOC) commands. These are stored on the Player object
and self.caller is thus always a Player, not an Object/Character.
Account (OOC) commands. These are stored on the Account object
and self.caller is thus always an Account, not an Object/Character.
These commands go in the PlayerCmdset and are accessible also
These commands go in the AccountCmdset and are accessible also
when puppeting a Character (although with lower priority)
These commands use the player_caller property which tells the command
These commands use the account_caller property which tells the command
parent (MuxCommand, usually) to setup caller correctly. They use
self.player to make sure to always use the player object rather than
self.account to make sure to always use the account object rather than
self.caller (which change depending on the level you are calling from)
The property self.character can be used to access the character when
these commands are triggered with a connected character (such as the
case of the @ooc command), it is None if we are OOC.
Note that under MULTISESSION_MODE > 2, Player commands should use
Note that under MULTISESSION_MODE > 2, Account commands should use
self.msg() and similar methods to reroute returns to the correct
method. Otherwise all text will be returned to all connected sessions.
@ -36,7 +36,7 @@ __all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit",
"CmdColorTest", "CmdQuell")
class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
"""
Custom parent (only) parsing for OOC looking, sets a "playable"
property on the command based on the parsing.
@ -46,19 +46,19 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
def parse(self):
"""Custom parsing"""
super(MuxPlayerLookCommand, self).parse()
super(MuxAccountLookCommand, self).parse()
if _MULTISESSION_MODE < 2:
# only one character allowed - not used in this mode
self.playable = None
return
playable = self.player.db._playable_characters
playable = self.account.db._playable_characters
if playable is not None:
# clean up list if character object was deleted in between
if None in playable:
playable = [character for character in playable if character]
self.player.db._playable_characters = playable
self.account.db._playable_characters = playable
# store playable property
if self.args:
self.playable = dict((utils.to_str(char.key.lower()), char)
@ -67,13 +67,13 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
self.playable = playable
# Obs - these are all intended to be stored on the Player, and as such,
# use self.player instead of self.caller, just to be sure. Also self.msg()
# Obs - these are all intended to be stored on the Account, and as such,
# use self.account instead of self.caller, just to be sure. Also self.msg()
# is used to make sure returns go to the right session
# note that this is inheriting from MuxPlayerLookCommand,
# note that this is inheriting from MuxAccountLookCommand,
# and has the .playable property.
class CmdOOCLook(MuxPlayerLookCommand):
class CmdOOCLook(MuxAccountLookCommand):
"""
look while out-of-character
@ -84,7 +84,7 @@ class CmdOOCLook(MuxPlayerLookCommand):
"""
# This is an OOC version of the look command. Since a
# Player doesn't have an in-game existence, there is no
# Account doesn't have an in-game existence, there is no
# concept of location or "self". If we are controlling
# a character, pass control over to normal look.
@ -94,7 +94,7 @@ class CmdOOCLook(MuxPlayerLookCommand):
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""implement the ooc look command"""
@ -104,8 +104,8 @@ class CmdOOCLook(MuxPlayerLookCommand):
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
return
# call on-player look helper method
self.msg(self.player.at_look(target=self.playable, session=self.session))
# call on-account look helper method
self.msg(self.account.at_look(target=self.playable, session=self.session))
class CmdCharCreate(COMMAND_DEFAULT_CLASS):
@ -121,15 +121,15 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
if you want.
"""
key = "@charcreate"
locks = "cmd:pperm(Players)"
locks = "cmd:pperm(Account)"
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""create the new character"""
player = self.player
account = self.account
if not self.args:
self.msg("Usage: @charcreate <charname> [= description]")
return
@ -138,9 +138,9 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
if not player.is_superuser and \
(player.db._playable_characters and
len(player.db._playable_characters) >= charmax):
if not account.is_superuser and \
(account.db._playable_characters and
len(account.db._playable_characters) >= charmax):
self.msg("You may only create a maximum of %i characters." % charmax)
return
from evennia.objects.models import ObjectDB
@ -156,19 +156,19 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
# create the character
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
permissions = settings.PERMISSION_PLAYER_DEFAULT
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 immortals) to puppet this char
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
(new_character.id, player.id))
player.db._playable_characters.append(new_character)
# 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)" %
(new_character.id, account.id))
account.db._playable_characters.append(new_character)
if desc:
new_character.db.desc = desc
elif not new_character.db.desc:
new_character.db.desc = "This is a Player."
new_character.db.desc = "This is an Account."
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
% (new_character.key, new_character.key))
@ -183,19 +183,19 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
Permanently deletes one of your characters.
"""
key = "@chardelete"
locks = "cmd:pperm(Players)"
locks = "cmd:pperm(Account)"
help_category = "General"
def func(self):
"""delete the character"""
player = self.player
account = self.account
if not self.args:
self.msg("Usage: @chardelete <charactername>")
return
# use the playable_characters list to search
match = [char for char in utils.make_iter(player.db._playable_characters)
match = [char for char in utils.make_iter(account.db._playable_characters)
if char.key.lower() == self.args.lower()]
if not match:
self.msg("You have no such character to delete.")
@ -219,9 +219,9 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
del caller.ndb._char_to_delete
match = match[0]
player.ndb._char_to_delete = match
account.ndb._char_to_delete = match
prompt = "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?"
get_input(player, prompt % match.key, _callback)
get_input(account, prompt % match.key, _callback)
class CmdIC(COMMAND_DEFAULT_CLASS):
@ -234,12 +234,12 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
Go in-character (IC) as a given Character.
This will attempt to "become" a different object assuming you have
the right to do so. Note that it's the PLAYER character that puppets
the right to do so. Note that it's the ACCOUNT character that puppets
characters/objects and which needs to have the correct permission!
You cannot become an object that is already controlled by another
player. In principle <character> can be any in-game object as long
as you the player have access right to puppet it.
account. In principle <character> can be any in-game object as long
as you the account have access right to puppet it.
"""
key = "@ic"
@ -249,24 +249,24 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""
Main puppet method
"""
player = self.player
account = self.account
session = self.session
new_character = None
if not self.args:
new_character = player.db._last_puppet
new_character = account.db._last_puppet
if not new_character:
self.msg("Usage: @ic <character>")
return
if not new_character:
# search for a matching character
new_character = [char for char in search.object_search(self.args) if char.access(player, "puppet")]
new_character = [char for char in search.object_search(self.args) if char.access(account, "puppet")]
if not new_character:
self.msg("That is not a valid character choice.")
return
@ -277,15 +277,15 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
else:
new_character = new_character[0]
try:
player.puppet_object(session, new_character)
player.db._last_puppet = new_character
account.puppet_object(session, new_character)
account.db._last_puppet = new_character
except RuntimeError as exc:
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
# note that this is inheriting from MuxPlayerLookCommand,
# note that this is inheriting from MuxAccountLookCommand,
# and as such has the .playable property.
class CmdOOC(MuxPlayerLookCommand):
class CmdOOC(MuxAccountLookCommand):
"""
stop puppeting and go ooc
@ -298,30 +298,30 @@ class CmdOOC(MuxPlayerLookCommand):
"""
key = "@ooc"
locks = "cmd:pperm(Players)"
locks = "cmd:pperm(Account)"
aliases = "@unpuppet"
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""Implement function"""
player = self.player
account = self.account
session = self.session
old_char = player.get_puppet(session)
old_char = account.get_puppet(session)
if not old_char:
string = "You are already OOC."
self.msg(string)
return
player.db._last_puppet = old_char
account.db._last_puppet = old_char
# disconnect
try:
player.unpuppet_object(session)
account.unpuppet_object(session)
self.msg("\n|GYou go OOC.|n\n")
if _MULTISESSION_MODE < 2:
@ -329,7 +329,7 @@ class CmdOOC(MuxPlayerLookCommand):
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
return
self.msg(player.at_look(target=self.playable, session=session))
self.msg(account.at_look(target=self.playable, session=session))
except RuntimeError as exc:
self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
@ -350,19 +350,19 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""Implement function"""
player = self.player
sessions = player.sessions.all()
account = self.account
sessions = account.sessions.all()
table = evtable.EvTable("|wsessid",
"|wprotocol",
"|whost",
"|wpuppet/character",
"|wlocation")
for sess in sorted(sessions, key=lambda x: x.sessid):
char = player.get_puppet(sess)
char = account.get_puppet(sess)
table.add_row(str(sess.sessid), str(sess.protocol_key),
type(sess.address) == tuple and sess.address[0] or sess.address,
char and str(char) or "None",
@ -387,27 +387,27 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""
Get all connected players by polling session.
Get all connected accounts by polling session.
"""
player = self.player
account = self.account
session_list = SESSIONS.get_sessions()
session_list = sorted(session_list, key=lambda o: o.player.key)
session_list = sorted(session_list, key=lambda o: o.account.key)
if self.cmdstring == "doing":
show_session_data = False
else:
show_session_data = player.check_permstring("Immortals") or player.check_permstring("Wizards")
show_session_data = account.check_permstring("Developer") or account.check_permstring("Admins")
nplayers = (SESSIONS.player_count())
naccounts = (SESSIONS.account_count())
if show_session_data:
# privileged info
table = evtable.EvTable("|wPlayer Name",
table = evtable.EvTable("|wAccount Name",
"|wOn for",
"|wIdle",
"|wPuppeting",
@ -420,10 +420,10 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
player = session.get_player()
account = session.get_account()
puppet = session.get_puppet()
location = puppet.location.key if puppet and puppet.location else "None"
table.add_row(utils.crop(player.name, width=25),
table.add_row(utils.crop(account.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
utils.crop(puppet.key if puppet else "None", width=25),
@ -433,19 +433,19 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
isinstance(session.address, tuple) and session.address[0] or session.address)
else:
# unprivileged
table = evtable.EvTable("|wPlayer name", "|wOn for", "|wIdle")
table = evtable.EvTable("|wAccount name", "|wOn for", "|wIdle")
for session in session_list:
if not session.logged_in:
continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
player = session.get_player()
table.add_row(utils.crop(player.key, width=25),
account = session.get_account()
table.add_row(utils.crop(account.key, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1))
is_one = nplayers == 1
self.msg("|wPlayers:|n\n%s\n%s unique account%s logged in."
% (table, "One" if is_one else nplayers, "" if is_one else "s"))
is_one = naccounts == 1
self.msg("|wAccounts:|n\n%s\n%s unique account%s logged in."
% (table, "One" if is_one else naccounts, "" if is_one else "s"))
class CmdOption(COMMAND_DEFAULT_CLASS):
@ -470,7 +470,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""
@ -585,15 +585,15 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
# a valid setting
if "save" in self.switches:
# save this option only
saved_options = self.player.attributes.get("_saved_protocol_flags", default={})
saved_options = self.account.attributes.get("_saved_protocol_flags", default={})
saved_options.update(optiondict)
self.player.attributes.add("_saved_protocol_flags", saved_options)
self.account.attributes.add("_saved_protocol_flags", saved_options)
for key in optiondict:
self.msg("|gSaved option %s.|n" % key)
if "clear" in self.switches:
# clear this save
for key in optiondict:
self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None)
self.account.attributes.get("_saved_protocol_flags", {}).pop(key, None)
self.msg("|gCleared saved %s." % key)
self.session.update_flags(**optiondict)
@ -608,27 +608,27 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
Changes your password. Make sure to pick a safe one.
"""
key = "@password"
locks = "cmd:pperm(Players)"
locks = "cmd:pperm(Account)"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""hook function."""
player = self.player
account = self.account
if not self.rhs:
self.msg("Usage: @password <oldpass> = <newpass>")
return
oldpass = self.lhslist[0] # Both of these are
newpass = self.rhslist[0] # already stripped by parse()
if not player.check_password(oldpass):
if not account.check_password(oldpass):
self.msg("The specified old password isn't correct.")
elif len(newpass) < 3:
self.msg("Passwords must be at least three characters long.")
else:
player.set_password(newpass)
player.save()
account.set_password(newpass)
account.save()
self.msg("Password changed.")
@ -646,30 +646,29 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
game. Use the /all switch to disconnect from all sessions.
"""
key = "@quit"
aliases = "quit"
locks = "cmd:all()"
# this is used by the parent
player_caller = True
account_caller = True
def func(self):
"""hook function"""
player = self.player
account = self.account
if 'all' in self.switches:
player.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session)
for session in player.sessions.all():
player.disconnect_session_from_player(session)
account.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session)
for session in account.sessions.all():
account.disconnect_session_from_account(session)
else:
nsess = len(player.sessions.all())
nsess = len(account.sessions.all())
if nsess == 2:
player.msg("|RQuitting|n. One session is still connected.", session=self.session)
account.msg("|RQuitting|n. One session is still connected.", session=self.session)
elif nsess > 2:
player.msg("|RQuitting|n. %i sessions are still connected." % (nsess-1), session=self.session)
account.msg("|RQuitting|n. %i sessions are still connected." % (nsess-1), session=self.session)
else:
# we are quitting the last available session
player.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session)
player.disconnect_session_from_player(self.session)
account.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session)
account.disconnect_session_from_account(self.session)
class CmdColorTest(COMMAND_DEFAULT_CLASS):
@ -686,12 +685,19 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
color - if not you will see rubbish appear.
"""
key = "@color"
aliases = "color"
locks = "cmd:all()"
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
# the slices of the ANSI_PARSER lists to use for retrieving the
# relevant color tags to display. Replace if using another schema.
# This command can only show one set of markup.
slice_bright_fg = slice(7, 15) # from ANSI_PARSER.ansi_map
slice_dark_fg = slice(15, 23) # from ANSI_PARSER.ansi_map
slice_dark_bg = slice(-8, None) # from ANSI_PARSER.ansi_map
slice_bright_bg = slice(None, None) # from ANSI_PARSER.ansi_xterm256_bright_bg_map
def table_format(self, table):
"""
@ -718,14 +724,16 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
ap = ansi.ANSI_PARSER
# ansi colors
# show all ansi color-related codes
col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]]
col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]]
col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ext_ansi_map[-8:]]
col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_bright_bgs[-8:]]
col2.extend(["" for _ in range(len(col1)-len(col2))])
table = utils.format_table([col1, col2, col4, col3])
bright_fg = ["%s%s|n" % (code, code.replace("|", "||"))
for code, _ in ap.ansi_map[self.slice_bright_fg]]
dark_fg = ["%s%s|n" % (code, code.replace("|", "||"))
for code, _ in ap.ansi_map[self.slice_dark_fg]]
dark_bg = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_map[self.slice_dark_bg]]
bright_bg = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_xterm256_bright_bg_map[self.slice_bright_bg]]
dark_fg.extend(["" for _ in range(len(bright_fg)-len(dark_fg))])
table = utils.format_table([bright_fg, dark_fg, bright_bg, dark_bg])
string = "ANSI colors:"
for row in table:
string += "\n " + " ".join(row)
@ -776,30 +784,30 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
class CmdQuell(COMMAND_DEFAULT_CLASS):
"""
use character's permissions instead of player's
use character's permissions instead of account's
Usage:
quell
unquell
Normally the permission level of the Player is used when puppeting a
Normally the permission level of the Account is used when puppeting a
Character/Object to determine access. This command will switch the lock
system to make use of the puppeted Object's permissions instead. This is
useful mainly for testing.
Hierarchical permission quelling only work downwards, thus a Player cannot
Hierarchical permission quelling only work downwards, thus an Account cannot
use a higher-permission Character to escalate their permission level.
Use the unquell command to revert back to normal operation.
"""
key = "@quell"
aliases = ["@unquell"]
locks = "cmd:pperm(Players)"
locks = "cmd:pperm(Account)"
help_category = "General"
# this is used by the parent
player_caller = True
account_caller = True
def _recache_locks(self, player):
def _recache_locks(self, account):
"""Helper method to reset the lockhandler on an already puppeted object"""
if self.session:
char = self.session.puppet
@ -808,31 +816,31 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
# the lock caches (otherwise the superuser status change
# won't be visible until repuppet)
char.locks.reset()
player.locks.reset()
account.locks.reset()
def func(self):
"""Perform the command"""
player = self.player
permstr = player.is_superuser and " (superuser)" or "(%s)" % (", ".join(player.permissions.all()))
account = self.account
permstr = account.is_superuser and " (superuser)" or "(%s)" % (", ".join(account.permissions.all()))
if self.cmdstring == '@unquell':
if not player.attributes.get('_quell'):
self.msg("Already using normal Player permissions %s." % permstr)
if not account.attributes.get('_quell'):
self.msg("Already using normal Account permissions %s." % permstr)
else:
player.attributes.remove('_quell')
self.msg("Player permissions %s restored." % permstr)
account.attributes.remove('_quell')
self.msg("Account permissions %s restored." % permstr)
else:
if player.attributes.get('_quell'):
self.msg("Already quelling Player %s permissions." % permstr)
if account.attributes.get('_quell'):
self.msg("Already quelling Account %s permissions." % permstr)
return
player.attributes.add('_quell', True)
account.attributes.add('_quell', True)
puppet = self.session.puppet
if puppet:
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
cpermstr += "\n(Note: If this is higher than Player permissions %s," \
cpermstr += "\n(Note: If this is higher than Account permissions %s," \
" the lowest of the two will be used.)" % permstr
cpermstr += "\nUse @unquell to return to normal permission usage."
self.msg(cpermstr)
else:
self.msg("Quelling Player permissions%s. Use @unquell to get them back." % permstr)
self._recache_locks(player)
self.msg("Quelling Account permissions%s. Use @unquell to get them back." % permstr)
self._recache_locks(account)

View file

@ -16,27 +16,27 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
# limit members for API inclusion
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer",
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelAccount",
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
class CmdBoot(COMMAND_DEFAULT_CLASS):
"""
kick a player from the server.
kick an account from the server.
Usage
@boot[/switches] <player obj> [: reason]
@boot[/switches] <account obj> [: reason]
Switches:
quiet - Silently boot without informing player
quiet - Silently boot without informing account
sid - boot by session id instead of name or dbref
Boot a player object from the server. If a reason is
Boot an account object from the server. If a reason is
supplied it will be echoed to the user unless /quiet is set.
"""
key = "@boot"
locks = "cmd:perm(boot) or perm(Wizards)"
locks = "cmd:perm(boot) or perm(Admin)"
help_category = "Admin"
def func(self):
@ -45,7 +45,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
args = self.args
if not args:
caller.msg("Usage: @boot[/switches] <player> [:reason]")
caller.msg("Usage: @boot[/switches] <account> [:reason]")
return
if ':' in args:
@ -64,10 +64,10 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
boot_list.append(sess)
break
else:
# Boot by player object
pobj = search.player_search(args)
# Boot by account object
pobj = search.account_search(args)
if not pobj:
caller.msg("Player %s was not found." % args)
caller.msg("Account %s was not found." % args)
return
pobj = pobj[0]
if not pobj.access(caller, 'boot'):
@ -75,12 +75,12 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
caller.msg(string)
return
# we have a bootable object with a connected user
matches = SESSIONS.sessions_from_player(pobj)
matches = SESSIONS.sessions_from_account(pobj)
for match in matches:
boot_list.append(match)
if not boot_list:
caller.msg("No matching sessions found. The Player does not seem to be online.")
caller.msg("No matching sessions found. The Account does not seem to be online.")
return
# Carry out the booting of the sessions in the boot list.
@ -93,7 +93,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
for session in boot_list:
session.msg(feedback)
session.player.disconnect_session_from_player(session)
session.account.disconnect_session_from_account(session)
# regex matching IP addresses with wildcards, eg. 233.122.4.*
@ -118,7 +118,7 @@ def list_bans(banlist):
class CmdBan(COMMAND_DEFAULT_CLASS):
"""
ban a player from the server
ban an account from the server
Usage:
@ban [<name or ip> [: reason]]
@ -128,8 +128,8 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
This command bans a user from accessing the game. Supply an optional
reason to be able to later remember why the ban was put in place.
It is often preferable to ban a player from the server than to
delete a player with @delplayer. If banned by name, that player
It is often preferable to ban an account from the server than to
delete an account with @delaccount. If banned by name, that account
account can no longer be logged into.
IP (Internet Protocol) address banning allows blocking all access
@ -151,7 +151,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
"""
key = "@ban"
aliases = ["@bans"]
locks = "cmd:perm(ban) or perm(Immortals)"
locks = "cmd:perm(ban) or perm(Developer)"
help_category = "Admin"
def func(self):
@ -206,19 +206,19 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
class CmdUnban(COMMAND_DEFAULT_CLASS):
"""
remove a ban from a player
remove a ban from an account
Usage:
@unban <banid>
This will clear a player name/ip ban previously set with the @ban
This will clear an account name/ip ban previously set with the @ban
command. Use this command without an argument to view a numbered
list of bans. Use the numbers in this list to select which one to
unban.
"""
key = "@unban"
locks = "cmd:perm(unban) or perm(Immortals)"
locks = "cmd:perm(unban) or perm(Developer)"
help_category = "Admin"
def func(self):
@ -249,23 +249,23 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
(num, " ".join([s for s in ban[:2]])))
class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
class CmdDelAccount(COMMAND_DEFAULT_CLASS):
"""
delete a player from the server
delete an account from the server
Usage:
@delplayer[/switch] <name> [: reason]
@delaccount[/switch] <name> [: reason]
Switch:
delobj - also delete the player's currently
delobj - also delete the account's currently
assigned in-game object.
Completely deletes a user from the server database,
making their nick and e-mail again available.
"""
key = "@delplayer"
locks = "cmd:perm(delplayer) or perm(Immortals)"
key = "@delaccount"
locks = "cmd:perm(delaccount) or perm(Developer)"
help_category = "Admin"
def func(self):
@ -274,49 +274,49 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
caller = self.caller
args = self.args
if hasattr(caller, 'player'):
caller = caller.player
if hasattr(caller, 'account'):
caller = caller.account
if not args:
self.msg("Usage: @delplayer <player/user name or #id> [: reason]")
self.msg("Usage: @delaccount <account/user name or #id> [: reason]")
return
reason = ""
if ':' in args:
args, reason = [arg.strip() for arg in args.split(':', 1)]
# We use player_search since we want to be sure to find also players
# We use account_search since we want to be sure to find also accounts
# that lack characters.
players = search.player_search(args)
accounts = search.account_search(args)
if not players:
self.msg('Could not find a player by that name.')
if not accounts:
self.msg('Could not find an account by that name.')
return
if len(players) > 1:
if len(accounts) > 1:
string = "There were multiple matches:\n"
string += "\n".join(" %s %s" % (player.id, player.key) for player in players)
string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts)
self.msg(string)
return
# one single match
player = players.pop()
account = accounts.pop()
if not player.access(caller, 'delete'):
string = "You don't have the permissions to delete that player."
if not account.access(caller, 'delete'):
string = "You don't have the permissions to delete that account."
self.msg(string)
return
uname = player.username
# boot the player then delete
self.msg("Informing and disconnecting player ...")
uname = account.username
# boot the account then delete
self.msg("Informing and disconnecting account ...")
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
if reason:
string += " Reason given:\n '%s'" % reason
player.msg(string)
player.delete()
self.msg("Player %s was successfully deleted." % uname)
account.msg(string)
account.delete()
self.msg("Account %s was successfully deleted." % uname)
class CmdEmit(COMMAND_DEFAULT_CLASS):
@ -330,18 +330,18 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
Switches:
room : limit emits to rooms only (default)
players : limit emits to players only
accounts : limit emits to accounts only
contents : send to the contents of matched objects too
Emits a message to the selected objects or to
your immediate surroundings. If the object is a room,
send to its contents. @remit and @pemit are just
limited forms of @emit, for sending to rooms and
to players respectively.
to accounts respectively.
"""
key = "@emit"
aliases = ["@pemit", "@remit"]
locks = "cmd:perm(emit) or perm(Builders)"
locks = "cmd:perm(emit) or perm(Builder)"
help_category = "Admin"
def func(self):
@ -359,7 +359,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
return
rooms_only = 'rooms' in self.switches
players_only = 'players' in self.switches
accounts_only = 'accounts' in self.switches
send_to_contents = 'contents' in self.switches
# we check which command was used to force the switches
@ -367,7 +367,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
rooms_only = True
send_to_contents = True
elif self.cmdstring == '@pemit':
players_only = True
accounts_only = True
if not self.rhs:
message = self.args
@ -384,8 +384,8 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
if rooms_only and obj.location is not None:
caller.msg("%s is not a room. Ignored." % objname)
continue
if players_only and not obj.has_player:
caller.msg("%s has no active player. Ignored." % objname)
if accounts_only and not obj.has_account:
caller.msg("%s has no active account. Ignored." % objname)
continue
if obj.access(caller, 'tell'):
obj.msg(message)
@ -400,16 +400,16 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
class CmdNewPassword(COMMAND_DEFAULT_CLASS):
"""
change the password of a player
change the password of an account
Usage:
@userpassword <user obj> = <new password>
Set a player's password.
Set an account's password.
"""
key = "@userpassword"
locks = "cmd:perm(newpassword) or perm(Wizards)"
locks = "cmd:perm(newpassword) or perm(Admin)"
help_category = "Admin"
def func(self):
@ -421,36 +421,36 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
self.msg("Usage: @userpassword <user obj> = <new password>")
return
# the player search also matches 'me' etc.
player = caller.search_player(self.lhs)
if not player:
# the account search also matches 'me' etc.
account = caller.search_account(self.lhs)
if not account:
return
player.set_password(self.rhs)
player.save()
self.msg("%s - new password set to '%s'." % (player.name, self.rhs))
if player.character != caller:
player.msg("%s has changed your password to '%s'." % (caller.name,
account.set_password(self.rhs)
account.save()
self.msg("%s - new password set to '%s'." % (account.name, self.rhs))
if account.character != caller:
account.msg("%s has changed your password to '%s'." % (caller.name,
self.rhs))
class CmdPerm(COMMAND_DEFAULT_CLASS):
"""
set the permissions of a player/object
set the permissions of an account/object
Usage:
@perm[/switch] <object> [= <permission>[,<permission>,...]]
@perm[/switch] *<player> [= <permission>[,<permission>,...]]
@perm[/switch] *<account> [= <permission>[,<permission>,...]]
Switches:
del : delete the given permission from <object> or <player>.
player : set permission on a player (same as adding * to name)
del : delete the given permission from <object> or <account>.
account : set permission on an account (same as adding * to name)
This command sets/clears individual permission strings on an object
or player. If no permission is given, list all permissions on <object>.
or account. If no permission is given, list all permissions on <object>.
"""
key = "@perm"
aliases = "@setperm"
locks = "cmd:perm(perm) or perm(Immortals)"
locks = "cmd:perm(perm) or perm(Developer)"
help_category = "Admin"
def func(self):
@ -465,11 +465,11 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
caller.msg(string)
return
playermode = 'player' in self.switches or lhs.startswith('*')
accountmode = 'account' in self.switches or lhs.startswith('*')
lhs = lhs.lstrip("*")
if playermode:
obj = caller.search_player(lhs)
if accountmode:
obj = caller.search_account(lhs)
else:
obj = caller.search(lhs, global_search=True)
if not obj:
@ -485,19 +485,19 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
string += "<None>"
else:
string += ", ".join(obj.permissions.all())
if (hasattr(obj, 'player') and
hasattr(obj.player, 'is_superuser') and
obj.player.is_superuser):
if (hasattr(obj, 'account') and
hasattr(obj.account, 'is_superuser') and
obj.account.is_superuser):
string += "\n(... but this object is currently controlled by a SUPERUSER! "
string += "All access checks are passed automatically.)"
caller.msg(string)
return
# we supplied an argument on the form obj = perm
locktype = "edit" if playermode else "control"
locktype = "edit" if accountmode else "control"
if not obj.access(caller, locktype):
caller.msg("You are not allowed to edit this %s's permissions."
% ("player" if playermode else "object"))
% ("account" if accountmode else "object"))
return
caller_result = []
@ -528,7 +528,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
caller_result.append("\nPermission '%s' is already defined on %s." % (rhs, obj.name))
else:
obj.permissions.add(perm)
plystring = "the Player" if playermode else "the Object/Character"
plystring = "the Account" if accountmode else "the Object/Character"
caller_result.append("\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring))
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
% (caller.name, obj.name, plystring, rhs))
@ -544,10 +544,10 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
Usage:
@wall <message>
Announces a message to all connected players.
Announces a message to all connected accounts.
"""
key = "@wall"
locks = "cmd:perm(wall) or perm(Wizards)"
locks = "cmd:perm(wall) or perm(Admin)"
help_category = "Admin"
def func(self):
@ -556,5 +556,5 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
self.caller.msg("Usage: @wall <message>")
return
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
self.msg("Announcing to all connected players ...")
self.msg("Announcing to all connected accounts ...")
SESSIONS.announce_all(message)

View file

@ -237,7 +237,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
"""
key = "@batchcommands"
aliases = ["@batchcommand", "@batchcmd"]
locks = "cmd:perm(batchcommands) or superuser()"
locks = "cmd:perm(batchcommands) or perm(Developer)"
help_category = "Building"
def func(self):

View file

@ -117,7 +117,7 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
key = "@alias"
aliases = "@setobjalias"
locks = "cmd:perm(setobjalias) or perm(Builders)"
locks = "cmd:perm(setobjalias) or perm(Builder)"
help_category = "Building"
def func(self):
@ -196,7 +196,7 @@ class CmdCopy(ObjManipCommand):
"""
key = "@copy"
locks = "cmd:perm(copy) or perm(Builders)"
locks = "cmd:perm(copy) or perm(Builder)"
help_category = "Building"
def func(self):
@ -276,7 +276,7 @@ class CmdCpAttr(ObjManipCommand):
If you don't supply a source object, yourself is used.
"""
key = "@cpattr"
locks = "cmd:perm(cpattr) or perm(Builders)"
locks = "cmd:perm(cpattr) or perm(Builder)"
help_category = "Building"
def check_from_attr(self, obj, attr, clear=False):
@ -418,7 +418,7 @@ class CmdMvAttr(ObjManipCommand):
object. If you don't supply a source object, yourself is used.
"""
key = "@mvattr"
locks = "cmd:perm(mvattr) or perm(Builders)"
locks = "cmd:perm(mvattr) or perm(Builder)"
help_category = "Building"
def func(self):
@ -466,12 +466,12 @@ class CmdCreate(ObjManipCommand):
"""
key = "@create"
locks = "cmd:perm(create) or perm(Builders)"
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(Wizards);delete:id({id}) or perm(Wizards)"
new_obj_lockstring = "control:id({id}) or perm(Admin);delete:id({id}) or perm(Admin)"
def func(self):
"""
@ -535,10 +535,10 @@ def _desc_quit(caller):
class CmdDesc(COMMAND_DEFAULT_CLASS):
"""
describe an object
describe an object or the current room.
Usage:
@desc [<obj> =] <description>
@setdesc [<obj> =] <description>
Switches:
edit - Open up a line editor for more advanced editing.
@ -546,9 +546,9 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
Sets the "desc" attribute on an object. If an object is not given,
describe the current room.
"""
key = "@desc"
key = "@setdesc"
aliases = "@describe"
locks = "cmd:perm(desc) or perm(Builders)"
locks = "cmd:perm(desc) or perm(Builder)"
help_category = "Building"
def edit_handler(self):
@ -607,7 +607,7 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
switches:
override - The @destroy command will usually avoid accidentally
destroying player objects. This switch overrides this safety.
destroying account objects. This switch overrides this safety.
examples:
@destroy house, roof, door, 44-78
@destroy 5-10, flower, 45
@ -618,7 +618,7 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
key = "@destroy"
aliases = ["@delete", "@del"]
locks = "cmd:perm(destroy) or perm(Builders)"
locks = "cmd:perm(destroy) or perm(Builder)"
help_category = "Building"
def func(self):
@ -640,8 +640,8 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
objname = obj.name
if not (obj.access(caller, "control") or obj.access(caller, 'delete')):
return "\nYou don't have permission to delete %s." % objname
if obj.player and not 'override' in self.switches:
return "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname
if obj.account and not 'override' in self.switches:
return "\nObject %s is controlled by an active account. Use /override to delete anyway." % objname
if obj.dbid == int(settings.DEFAULT_HOME.lstrip("#")):
return "\nYou are trying to delete |c%s|n, which is set as DEFAULT_HOME. " \
"Re-point settings.DEFAULT_HOME to another " \
@ -703,14 +703,14 @@ class CmdDig(ObjManipCommand):
would be 'north;no;n'.
"""
key = "@dig"
locks = "cmd:perm(dig) or perm(Builders)"
locks = "cmd:perm(dig) or perm(Builder)"
help_category = "Building"
# lockstring of newly created rooms, for easy overloading.
# Will be formatted with the {id} of the creating object.
new_room_lockstring = "control:id({id}) or perm(Wizards); " \
"delete:id({id}) or perm(Wizards); " \
"edit:id({id}) or perm(Wizards)"
new_room_lockstring = "control:id({id}) or perm(Admin); " \
"delete:id({id}) or perm(Admin); " \
"edit:id({id}) or perm(Admin)"
def func(self):
"Do the digging. Inherits variables from ObjManipCommand.parse()"
@ -847,7 +847,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
key = "@tunnel"
aliases = ["@tun"]
locks = "cmd: perm(tunnel) or perm(Builders)"
locks = "cmd: perm(tunnel) or perm(Builder)"
help_category = "Building"
# store the direction, full name and its opposite
@ -920,7 +920,7 @@ class CmdLink(COMMAND_DEFAULT_CLASS):
"""
key = "@link"
locks = "cmd:perm(link) or perm(Builders)"
locks = "cmd:perm(link) or perm(Builder)"
help_category = "Building"
def func(self):
@ -998,7 +998,7 @@ class CmdUnLink(CmdLink):
# this is just a child of CmdLink
key = "@unlink"
locks = "cmd:perm(unlink) or perm(Builders)"
locks = "cmd:perm(unlink) or perm(Builder)"
help_key = "Building"
def func(self):
@ -1035,9 +1035,8 @@ class CmdSetHome(CmdLink):
If no location is given, just view the object's home location.
"""
key = "@home"
aliases = "@sethome"
locks = "cmd:perm(@home) or perm(Builders)"
key = "@sethome"
locks = "cmd:perm(@home) or perm(Builder)"
help_category = "Building"
def func(self):
@ -1084,7 +1083,7 @@ class CmdListCmdSets(COMMAND_DEFAULT_CLASS):
"""
key = "@cmdsets"
aliases = "@listcmsets"
locks = "cmd:perm(listcmdsets) or perm(Builders)"
locks = "cmd:perm(listcmdsets) or perm(Builder)"
help_category = "Building"
def func(self):
@ -1109,13 +1108,13 @@ class CmdName(ObjManipCommand):
@name obj = name;alias1;alias2
Rename an object to something new. Use *obj to
rename a player.
rename an account.
"""
key = "@name"
aliases = ["@rename"]
locks = "cmd:perm(rename) or perm(Builders)"
locks = "cmd:perm(rename) or perm(Builder)"
help_category = "Building"
def func(self):
@ -1130,22 +1129,22 @@ class CmdName(ObjManipCommand):
if self.lhs_objs:
objname = self.lhs_objs[0]['name']
if objname.startswith("*"):
# player mode
obj = caller.player.search(objname.lstrip("*"))
# account mode
obj = caller.account.search(objname.lstrip("*"))
if obj:
if self.rhs_objs[0]['aliases']:
caller.msg("Players can't have aliases.")
caller.msg("Accounts can't have aliases.")
return
newname = self.rhs
if not newname:
caller.msg("No name defined!")
return
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
caller.msg("You don't have right to edit this player %s." % obj)
caller.msg("You don't have right to edit this account %s." % obj)
return
obj.username = newname
obj.save()
caller.msg("Player's name changed to '%s'." % newname)
caller.msg("Account's name changed to '%s'." % newname)
return
# object search, also with *
obj = caller.search(objname)
@ -1191,7 +1190,7 @@ class CmdOpen(ObjManipCommand):
"""
key = "@open"
locks = "cmd:perm(open) or perm(Builders)"
locks = "cmd:perm(open) or perm(Builder)"
help_category = "Building"
# a custom member method to chug out exits and do checks
@ -1327,7 +1326,7 @@ def _convert_from_string(cmd, strobj):
be converted to a string and a warning will be given.
We need to convert like this since all data being sent over the
telnet connection by the Player is text - but we will want to
telnet connection by the Account is text - but we will want to
store it as the "real" python type so we can do convenient
comparisons later (e.g. obj.db.value = 2, if value is stored as a
string this will always fail).
@ -1382,13 +1381,13 @@ def _convert_from_string(cmd, strobj):
class CmdSetAttribute(ObjManipCommand):
"""
set attribute on an object or player
set attribute on an object or account
Usage:
@set <obj>/<attr> = <value>
@set <obj>/<attr> =
@set <obj>/<attr>
@set *<player>/attr = <value>
@set *<account>/attr = <value>
Switch:
edit: Open the line editor (string values only)
@ -1413,14 +1412,14 @@ class CmdSetAttribute(ObjManipCommand):
"""
key = "@set"
locks = "cmd:perm(set) or perm(Builders)"
locks = "cmd:perm(set) or perm(Builder)"
help_category = "Building"
def check_obj(self, obj):
"""
This may be overridden by subclasses in case restrictions need to be
placed on whether certain objects can have attributes set by certain
players.
accounts.
This function is expected to display its own error message.
@ -1506,7 +1505,7 @@ class CmdSetAttribute(ObjManipCommand):
attrs = self.lhs_objattr[0]['attrs']
if objname.startswith('*'):
obj = caller.search_player(objname.lstrip('*'))
obj = caller.search_account(objname.lstrip('*'))
else:
obj = caller.search(objname)
if not obj:
@ -1595,7 +1594,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
key = "@typeclass"
aliases = ["@type", "@parent", "@swap", "@update"]
locks = "cmd:perm(typeclass) or perm(Builders)"
locks = "cmd:perm(typeclass) or perm(Builder)"
help_category = "Building"
def func(self):
@ -1685,7 +1684,7 @@ class CmdWipe(ObjManipCommand):
matching the given attribute-wildcard search string.
"""
key = "@wipe"
locks = "cmd:perm(wipe) or perm(Builders)"
locks = "cmd:perm(wipe) or perm(Builder)"
help_category = "Building"
def func(self):
@ -1743,18 +1742,18 @@ class CmdLock(ObjManipCommand):
Separator expressions need not be capitalized.
For example:
'get: id(25) or perm(Wizards)'
'get: id(25) or perm(Admin)'
The 'get' access_type is checked by the get command and will
an object locked with this string will only be possible to
pick up by Wizards or by object with id 25.
pick up by Admins or by object with id=25.
You can add several access_types after oneanother by separating
them by ';', i.e:
'get:id(25);delete:perm(Builders)'
'get:id(25);delete:perm(Builder)'
"""
key = "@lock"
aliases = ["@locks", "lock", "locks"]
locks = "cmd: perm(locks) or perm(Builders)"
aliases = ["@locks"]
locks = "cmd: perm(locks) or perm(Builder)"
help_category = "Building"
def func(self):
@ -1832,26 +1831,26 @@ class CmdExamine(ObjManipCommand):
Usage:
examine [<object>[/attrname]]
examine [*<player>[/attrname]]
examine [*<account>[/attrname]]
Switch:
player - examine a Player (same as adding *)
account - examine an Account (same as adding *)
object - examine an Object (useful when OOC)
The examine command shows detailed game info about an
object and optionally a specific attribute on it.
If object is not specified, the current location is examined.
Append a * before the search string to examine a player.
Append a * before the search string to examine an account.
"""
key = "@examine"
aliases = ["@ex","ex", "exam", "examine"]
locks = "cmd:perm(examine) or perm(Builders)"
aliases = ["@ex","exam"]
locks = "cmd:perm(examine) or perm(Builder)"
help_category = "Building"
arg_regex = r"(/\w+?(\s|$))|\s|$"
player_mode = False
account_mode = False
def list_attribute(self, crop, attr, value):
"""
@ -1911,15 +1910,15 @@ class CmdExamine(ObjManipCommand):
for sess in obj.sessions.all()))
if hasattr(obj, "email") and obj.email:
string += "\n|wEmail|n: |c%s|n" % obj.email
if hasattr(obj, "has_player") and obj.has_player:
string += "\n|wPlayer|n: |c%s|n" % obj.player.name
perms = obj.player.permissions.all()
if obj.player.is_superuser:
if hasattr(obj, "has_account") and obj.has_account:
string += "\n|wAccount|n: |c%s|n" % obj.account.name
perms = obj.account.permissions.all()
if obj.account.is_superuser:
perms = ["<Superuser>"]
elif not perms:
perms = ["<None>"]
string += "\n|wPlayer Perms|n: %s" % (", ".join(perms))
if obj.player.attributes.has("_quell"):
string += "\n|wAccount Perms|n: %s" % (", ".join(perms))
if obj.account.attributes.has("_quell"):
string += " |r(quelled)|n"
string += "\n|wTypeclass|n: %s (%s)" % (obj.typename,
obj.typeclass_path)
@ -1962,16 +1961,16 @@ class CmdExamine(ObjManipCommand):
# this gets all components of the currently merged set
all_cmdsets = [(cmdset.key, cmdset) for cmdset in avail_cmdset.merged_from]
# we always at least try to add player- and session sets since these are ignored
# we always at least try to add account- and session sets since these are ignored
# if we merge on the object level.
if hasattr(obj, "player") and obj.player:
all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.player.cmdset.all()])
if hasattr(obj, "account") and obj.account:
all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.cmdset.all()])
if obj.sessions.count():
# if there are more sessions than one on objects it's because of multisession mode 3.
# we only show the first session's cmdset here (it is -in principle- possible that
# different sessions have different cmdsets but for admins who want such madness
# it is better that they overload with their own CmdExamine to handle it).
all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.player.sessions.all()[0].cmdset.all()])
all_cmdsets.extend([(cmdset.key, cmdset) for cmdset in obj.account.sessions.all()[0].cmdset.all()])
else:
try:
# we have to protect this since many objects don't have sessions.
@ -2012,7 +2011,7 @@ class CmdExamine(ObjManipCommand):
for content in obj.contents:
if content.destination:
exits.append(content)
elif content.player:
elif content.account:
pobjs.append(content)
else:
things.append(content)
@ -2052,7 +2051,7 @@ class CmdExamine(ObjManipCommand):
self.msg(caller.at_look(obj))
return
# using callback for printing result whenever function returns.
get_and_merge_cmdsets(obj, self.session, self.player, obj, "object", self.raw_string).addCallback(get_cmdset_callback)
get_and_merge_cmdsets(obj, self.session, self.account, obj, "object", self.raw_string).addCallback(get_cmdset_callback)
else:
self.msg("You need to supply a target to examine.")
return
@ -2064,13 +2063,13 @@ class CmdExamine(ObjManipCommand):
obj_name = objdef['name']
obj_attrs = objdef['attrs']
self.player_mode = utils.inherits_from(caller, "evennia.players.players.DefaultPlayer") or \
"player" in self.switches or obj_name.startswith('*')
if self.player_mode:
self.account_mode = utils.inherits_from(caller, "evennia.accounts.accounts.DefaultAccount") or \
"account" in self.switches or obj_name.startswith('*')
if self.account_mode:
try:
obj = caller.search_player(obj_name.lstrip('*'))
obj = caller.search_account(obj_name.lstrip('*'))
except AttributeError:
# this means we are calling examine from a player object
# this means we are calling examine from an account object
obj = caller.search(obj_name.lstrip('*'), search_object = 'object' in self.switches)
else:
obj = caller.search(obj_name)
@ -2090,12 +2089,12 @@ class CmdExamine(ObjManipCommand):
else:
if obj.sessions.count():
mergemode = "session"
elif self.player_mode:
mergemode = "player"
elif self.account_mode:
mergemode = "account"
else:
mergemode = "object"
# using callback to print results whenever function returns.
get_and_merge_cmdsets(obj, self.session, self.player, obj, mergemode, self.raw_string).addCallback(get_cmdset_callback)
get_and_merge_cmdsets(obj, self.session, self.account, obj, mergemode, self.raw_string).addCallback(get_cmdset_callback)
class CmdFind(COMMAND_DEFAULT_CLASS):
@ -2103,7 +2102,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
search the database for objects
Usage:
@find[/switches] <name or dbref or *player> [= dbrefmin[-dbrefmax]]
@find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]
Switches:
room - only look for rooms (location=None)
@ -2112,15 +2111,15 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
exact- only exact matches are returned.
Searches the database for an object of a particular name or exact #dbref.
Use *playername to search for a player. The switches allows for
Use *accountname to search for an account. The switches allows for
limiting object matches to certain game entities. Dbrefmin and dbrefmax
limits matches to within the given dbrefs range, or above/below if only
one is given.
"""
key = "@find"
aliases = "find, @search, search, @locate, locate"
locks = "cmd:perm(find) or perm(Builders)"
aliases = "@search, @locate"
locks = "cmd:perm(find) or perm(Builder)"
help_category = "Building"
def func(self):
@ -2149,22 +2148,22 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
high = max(low, high)
is_dbref = utils.dbref(searchstring)
is_player = searchstring.startswith("*")
is_account = searchstring.startswith("*")
restrictions = ""
if self.switches:
restrictions = ", %s" % (",".join(self.switches))
if is_dbref or is_player:
if is_dbref or is_account:
if is_dbref:
# a dbref search
result = caller.search(searchstring, global_search=True, quiet=True)
string = "|wExact dbref match|n(#%i-#%i%s):" % (low, high, restrictions)
else:
# a player search
# an account search
searchstring = searchstring.lstrip("*")
result = caller.search_player(searchstring, quiet=True)
result = caller.search_account(searchstring, quiet=True)
string = "|wMatch|n(#%i-#%i%s):" % (low, high, restrictions)
if "room" in switches:
@ -2182,7 +2181,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
result=result[0]
string += "\n|g %s - %s|n" % (result.get_display_name(caller), result.path)
else:
# Not a player/dbref search but a wider search; build a queryset.
# Not an account/dbref search but a wider search; build a queryset.
# Searchs for key and aliases
if "exact" in switches:
keyquery = Q(db_key__iexact=searchstring, id__gte=low, id__lte=high)
@ -2251,7 +2250,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
is teleported to the target location. """
key = "@tel"
aliases = "@teleport"
locks = "cmd:perm(teleport) or perm(Builders)"
locks = "cmd:perm(teleport) or perm(Builder)"
help_category = "Building"
def func(self):
@ -2275,10 +2274,10 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
if not obj_to_teleport:
caller.msg("Did not find object to teleport.")
return
if obj_to_teleport.has_player:
if obj_to_teleport.has_account:
caller.msg("Cannot teleport a puppeted object "
"(%s, puppeted by %s) to a None-location." % (
obj_to_teleport.key, obj_to_teleport.player))
obj_to_teleport.key, obj_to_teleport.account))
return
caller.msg("Teleported %s -> None-location." % obj_to_teleport)
if obj_to_teleport.location and not tel_quietly:
@ -2352,7 +2351,7 @@ class CmdScript(COMMAND_DEFAULT_CLASS):
key = "@script"
aliases = "@addscript"
locks = "cmd:perm(script) or perm(Builders)"
locks = "cmd:perm(script) or perm(Builder)"
help_category = "Building"
def func(self):
@ -2453,7 +2452,7 @@ class CmdTag(COMMAND_DEFAULT_CLASS):
key = "@tag"
aliases = ["@tags"]
locks = "cmd:perm(tag) or perm(Builders)"
locks = "cmd:perm(tag) or perm(Builder)"
help_category = "Building"
arg_regex = r"(/\w+?(\s|$))|\s|$"
@ -2593,8 +2592,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
"""
key = "@spawn"
aliases = ["spawn"]
locks = "cmd:perm(spawn) or perm(Builders)"
locks = "cmd:perm(spawn) or perm(Builder)"
help_category = "Building"
def func(self):
@ -2635,8 +2633,8 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
return
elif isinstance(prototype, dict):
# we got the prototype on the command line. We must make sure to not allow
# the 'exec' key unless we are immortals or higher.
if "exec" in prototype and not self.caller.check_permstring("Immortals"):
# the 'exec' key unless we are developers or higher.
if "exec" in prototype and not self.caller.check_permstring("Developer"):
self.caller.msg("Spawn aborted: You don't have access to use the 'exec' prototype key.")
return
else:

View file

@ -1,8 +1,8 @@
"""
This is the cmdset for Player (OOC) commands. These are
stored on the Player object and should thus be able to handle getting
a Player object as caller rather than a Character.
This is the cmdset for Account (OOC) commands. These are
stored on the Account object and should thus be able to handle getting
an Account object as caller rather than a Character.
Note - in order for session-rerouting (in MULTISESSION_MODE=2) to
function, all commands in this cmdset should use the self.msg()
@ -11,33 +11,33 @@ command method rather than caller.msg().
from evennia.commands.cmdset import CmdSet
from evennia.commands.default import help, comms, admin, system
from evennia.commands.default import building, player
from evennia.commands.default import building, account
class PlayerCmdSet(CmdSet):
class AccountCmdSet(CmdSet):
"""
Implements the player command set.
Implements the account command set.
"""
key = "DefaultPlayer"
key = "DefaultAccount"
priority = -10
def at_cmdset_creation(self):
"Populates the cmdset"
# Player-specific commands
self.add(player.CmdOOCLook())
self.add(player.CmdIC())
self.add(player.CmdOOC())
self.add(player.CmdCharCreate())
self.add(player.CmdCharDelete())
#self.add(player.CmdSessions())
self.add(player.CmdWho())
self.add(player.CmdOption())
self.add(player.CmdQuit())
self.add(player.CmdPassword())
self.add(player.CmdColorTest())
self.add(player.CmdQuell())
# Account-specific commands
self.add(account.CmdOOCLook())
self.add(account.CmdIC())
self.add(account.CmdOOC())
self.add(account.CmdCharCreate())
self.add(account.CmdCharDelete())
#self.add(account.CmdSessions())
self.add(account.CmdWho())
self.add(account.CmdOption())
self.add(account.CmdQuit())
self.add(account.CmdPassword())
self.add(account.CmdColorTest())
self.add(account.CmdQuell())
# testing
self.add(building.CmdExamine())
@ -52,7 +52,7 @@ class PlayerCmdSet(CmdSet):
self.add(system.CmdPy())
# Admin commands
self.add(admin.CmdDelPlayer())
self.add(admin.CmdDelAccount())
self.add(admin.CmdNewPassword())
# Comm commands

View file

@ -1,8 +1,8 @@
"""
This module ties together all the commands default Character objects have
available (i.e. IC commands). Note that some commands, such as
communication-commands are instead put on the player level, in the
Player cmdset. Player commands remain available also to Characters.
communication-commands are instead put on the account level, in the
Account cmdset. Account commands remain available also to Characters.
"""
from evennia.commands.cmdset import CmdSet
from evennia.commands.default import general, help, admin, system
@ -25,7 +25,7 @@ class CharacterCmdSet(CmdSet):
self.add(general.CmdInventory())
self.add(general.CmdPose())
self.add(general.CmdNick())
self.add(general.CmdDesc())
self.add(general.CmdSetDesc())
self.add(general.CmdGet())
self.add(general.CmdDrop())
self.add(general.CmdGive())
@ -41,7 +41,7 @@ class CharacterCmdSet(CmdSet):
self.add(system.CmdPy())
self.add(system.CmdScripts())
self.add(system.CmdObjects())
self.add(system.CmdPlayers())
self.add(system.CmdAccounts())
self.add(system.CmdService())
self.add(system.CmdAbout())
self.add(system.CmdTime())

View file

@ -2,7 +2,7 @@
This module stores session-level commands.
"""
from evennia.commands.cmdset import CmdSet
from evennia.commands.default import player
from evennia.commands.default import account
class SessionCmdSet(CmdSet):
"""
@ -13,4 +13,4 @@ class SessionCmdSet(CmdSet):
def at_cmdset_creation(self):
"Populate the cmdset"
self.add(player.CmdSessions())
self.add(account.CmdSessions())

View file

@ -2,16 +2,16 @@
Comsystem command module.
Comm commands are OOC commands and intended to be made available to
the Player at all times (they go into the PlayerCmdSet). So we
make sure to homogenize self.caller to always be the player object
the Account at all times (they go into the AccountCmdSet). So we
make sure to homogenize self.caller to always be the account object
for easy handling.
"""
from past.builtins import cmp
from django.conf import settings
from evennia.comms.models import ChannelDB, Msg
from evennia.players.models import PlayerDB
from evennia.players import bots
from evennia.accounts.models import AccountDB
from evennia.accounts import bots
from evennia.comms.channelhandler import CHANNELHANDLER
from evennia.locks.lockhandler import LockException
from evennia.utils import create, utils, evtable
@ -69,14 +69,14 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
locks = "cmd:not pperm(channel_banned)"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implement the command"""
caller = self.caller
args = self.args
player = caller
account = caller
if not args:
self.msg("Usage: addcom [alias =] channelname.")
@ -96,21 +96,21 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
return
# check permissions
if not channel.access(player, 'listen'):
if not channel.access(account, 'listen'):
self.msg("%s: You are not allowed to listen to this channel." % channel.key)
return
string = ""
if not channel.has_connection(player):
if not channel.has_connection(account):
# we want to connect as well.
if not channel.connect(player):
# if this would have returned True, the player is connected
if not channel.connect(account):
# if this would have returned True, the account is connected
self.msg("%s: You are not allowed to join this channel." % channel.key)
return
else:
string += "You now listen to the channel %s. " % channel.key
else:
if channel.unmute(player):
if channel.unmute(account):
string += "You unmute channel %s." % channel.key
else:
string += "You are already connected to channel %s." % channel.key
@ -145,13 +145,13 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
locks = "cmd:not perm(channel_banned)"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implementing the command. """
caller = self.caller
player = caller
account = caller
if not self.args:
self.msg("Usage: delcom <alias or channel>")
@ -161,7 +161,7 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
channel = find_channel(caller, ostring, silent=True, noaliases=True)
if channel:
# we have given a channel name - unsubscribe
if not channel.has_connection(player):
if not channel.has_connection(account):
self.msg("You are not listening to that channel.")
return
chkey = channel.key.lower()
@ -171,7 +171,7 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
for nick in [nick for nick in make_iter(caller.nicks.get(category="channel", return_obj=True))
if nick and nick.pk and nick.value[3].lower() == chkey]:
nick.delete()
disconnect = channel.disconnect(player)
disconnect = channel.disconnect(account)
if disconnect:
wipednicks = " Eventual aliases were removed." if delnicks else ""
self.msg("You stop listening to channel '%s'.%s" % (channel.key, wipednicks))
@ -209,7 +209,7 @@ class CmdAllCom(COMMAND_DEFAULT_CLASS):
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Runs the function"""
@ -268,12 +268,12 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
Use addcom/delcom to join and leave channels
"""
key = "@channels"
aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
aliases = ["@clist", "comlist", "chanlist", "channellist", "all channels"]
help_category = "Comms"
locks = "cmd: not pperm(channel_banned)"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implement function"""
@ -345,7 +345,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
locks = "cmd: not pperm(channel_banned)"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Destroy objects cleanly."""
@ -372,15 +372,15 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
class CmdCBoot(COMMAND_DEFAULT_CLASS):
"""
kick a player from a channel you control
kick an account from a channel you control
Usage:
@cboot[/quiet] <channel> = <player> [:reason]
@cboot[/quiet] <channel> = <account> [:reason]
Switches:
quiet - don't notify the channel
Kicks a player or object from a channel you control.
Kicks an account or object from a channel you control.
"""
@ -389,13 +389,13 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""implement the function"""
if not self.args or not self.rhs:
string = "Usage: @cboot[/quiet] <channel> = <player> [:reason]"
string = "Usage: @cboot[/quiet] <channel> = <account> [:reason]"
self.msg(string)
return
@ -404,12 +404,12 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
return
reason = ""
if ":" in self.rhs:
playername, reason = self.rhs.rsplit(":", 1)
searchstring = playername.lstrip('*')
accountname, reason = self.rhs.rsplit(":", 1)
searchstring = accountname.lstrip('*')
else:
searchstring = self.rhs.lstrip('*')
player = self.caller.search(searchstring, player=True)
if not player:
account = self.caller.search(searchstring, account=True)
if not account:
return
if reason:
reason = " (reason: %s)" % reason
@ -417,20 +417,20 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
string = "You don't control this channel."
self.msg(string)
return
if player not in channel.db_subscriptions.all():
string = "Player %s is not connected to channel %s." % (player.key, channel.key)
if account not in channel.db_subscriptions.all():
string = "Account %s is not connected to channel %s." % (account.key, channel.key)
self.msg(string)
return
if "quiet" not in self.switches:
string = "%s boots %s from channel.%s" % (self.caller, player.key, reason)
string = "%s boots %s from channel.%s" % (self.caller, account.key, reason)
channel.msg(string)
# find all player's nicks linked to this channel and delete them
# find all account's nicks linked to this channel and delete them
for nick in [nick for nick in
player.character.nicks.get(category="channel") or []
account.character.nicks.get(category="channel") or []
if nick.value[3].lower() == channel.key]:
nick.delete()
# disconnect player
channel.disconnect(player)
# disconnect account
channel.disconnect(account)
CHANNELHANDLER.update()
@ -453,11 +453,11 @@ class CmdCemit(COMMAND_DEFAULT_CLASS):
key = "@cemit"
aliases = ["@cmsg"]
locks = "cmd: not pperm(channel_banned) and pperm(Players)"
locks = "cmd: not pperm(channel_banned) and pperm(Account)"
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implement function"""
@ -496,7 +496,7 @@ class CmdCWho(COMMAND_DEFAULT_CLASS):
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""implement function"""
@ -530,11 +530,11 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS):
key = "@ccreate"
aliases = "channelcreate"
locks = "cmd:not pperm(channel_banned) and pperm(Players)"
locks = "cmd:not pperm(channel_banned) and pperm(Account)"
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implement the command"""
@ -587,7 +587,7 @@ class CmdClock(COMMAND_DEFAULT_CLASS):
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""run the function"""
@ -639,7 +639,7 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implement command"""
@ -666,10 +666,10 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
class CmdPage(COMMAND_DEFAULT_CLASS):
"""
send a private message to another player
send a private message to another account
Usage:
page[/switches] [<player>,<player>,... = <message>]
page[/switches] [<account>,<account>,... = <message>]
tell ''
page <number>
@ -687,12 +687,12 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
help_category = "Comms"
# this is used by the COMMAND_DEFAULT_CLASS parent
player_caller = True
account_caller = True
def func(self):
"""Implement function using the Msg methods"""
# Since player_caller is set above, this will be a Player.
# Since account_caller is set above, this will be an Account.
caller = self.caller
# get the messages we've sent (not to channels)
@ -718,7 +718,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
try:
number = int(self.args)
except ValueError:
self.msg("Usage: tell [<player> = msg]")
self.msg("Usage: tell [<account> = msg]")
return
if len(pages) > number:
@ -767,7 +767,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
self.msg("Noone found to page.")
return
header = "|wPlayer|n |c%s|n |wpages:|n" % caller.key
header = "|wAccount|n |c%s|n |wpages:|n" % caller.key
message = self.rhs
# if message begins with a :, we assume it is a 'page-pose'
@ -778,7 +778,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
create.create_message(caller, message,
receivers=recobjs)
# tell the players they got a message.
# tell the accounts they got a message.
received = []
rstrings = []
for pobj in recobjs:
@ -805,7 +805,7 @@ def _list_bots():
bots (str): A table of bots or an error message.
"""
ircbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")]
ircbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")]
if ircbots:
from evennia.utils.evtable import EvTable
table = EvTable("|w#dbref|n", "|wbotname|n", "|wev-channel|n",
@ -836,7 +836,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
Example:
@irc2chan myircchan = irc.dalnet.net 6667 #mychannel evennia-bot
@irc2chan public = irc.freenode.net 6667 #evgaming #evbot:players.mybot.MyBot
@irc2chan public = irc.freenode.net 6667 #evgaming #evbot:accounts.mybot.MyBot
This creates an IRC bot that connects to a given IRC network and
channel. If a custom typeclass path is given, this will be used
@ -850,7 +850,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
"""
key = "@irc2chan"
locks = "cmd:serversetting(IRC_ENABLED) and pperm(Immortals)"
locks = "cmd:serversetting(IRC_ENABLED) and pperm(Developer)"
help_category = "Comms"
def func(self):
@ -868,11 +868,11 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
botname = "ircbot-%s" % self.lhs
matches = PlayerDB.objects.filter(db_is_bot=True, username=botname)
matches = AccountDB.objects.filter(db_is_bot=True, username=botname)
dbref = utils.dbref(self.lhs)
if not matches and dbref:
# try dbref match
matches = PlayerDB.objects.filter(db_is_bot=True, id=dbref)
matches = AccountDB.objects.filter(db_is_bot=True, id=dbref)
if matches:
matches[0].delete()
self.msg("IRC connection destroyed.")
@ -906,16 +906,16 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
irc_ssl = "ssl" in self.switches
# create a new bot
bot = PlayerDB.objects.filter(username__iexact=botname)
bot = AccountDB.objects.filter(username__iexact=botname)
if bot:
# re-use an existing bot
bot = bot[0]
if not bot.is_bot:
self.msg("Player '%s' already exists and is not a bot." % botname)
self.msg("Account '%s' already exists and is not a bot." % botname)
return
else:
try:
bot = create.create_player(botname, None, None, typeclass=botclass)
bot = create.create_account(botname, None, None, typeclass=botclass)
except Exception as err:
self.msg("|rError, could not create the bot:|n '%s'." % err)
return
@ -943,7 +943,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
"""
key = "@ircstatus"
locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builders))"
locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))"
help_category = "Comms"
def func(self):
@ -963,7 +963,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
return
matches = None
if utils.dbref(botname):
matches = PlayerDB.objects.filter(db_is_bot=True, id=utils.dbref(botname))
matches = AccountDB.objects.filter(db_is_bot=True, id=utils.dbref(botname))
if not matches:
self.msg("No matching IRC-bot found. Use @ircstatus without arguments to list active bots.")
return
@ -981,7 +981,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
# an asynchronous call.
self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port))
ircbot.get_nicklist(self.caller)
elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Immortals)"):
elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Developer)"):
# reboot the client
self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext)
ircbot.reconnect()
@ -1016,7 +1016,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
"""
key = "@rss2chan"
locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)"
locks = "cmd:serversetting(RSS_ENABLED) and pperm(Developer)"
help_category = "Comms"
def func(self):
@ -1038,7 +1038,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
if 'list' in self.switches:
# show all connections
rssbots = [bot for bot in PlayerDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")]
rssbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")]
if rssbots:
from evennia.utils.evtable import EvTable
table = EvTable("|wdbid|n", "|wupdate rate|n", "|wev-channel",
@ -1052,10 +1052,10 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
botname = "rssbot-%s" % self.lhs
matches = PlayerDB.objects.filter(db_is_bot=True, db_key=botname)
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
if not matches:
# try dbref match
matches = PlayerDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#"))
matches = AccountDB.objects.filter(db_is_bot=True, id=self.args.lstrip("#"))
if matches:
matches[0].delete()
self.msg("RSS connection destroyed.")
@ -1072,14 +1072,14 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
botname = "rssbot-%s" % url
# create a new bot
bot = PlayerDB.objects.filter(username__iexact=botname)
bot = AccountDB.objects.filter(username__iexact=botname)
if bot:
# re-use existing bot
bot = bot[0]
if not bot.is_bot:
self.msg("Player '%s' already exists and is not a bot." % botname)
self.msg("Account '%s' already exists and is not a bot." % botname)
return
else:
bot = create.create_player(botname, None, None, typeclass=bots.RSSBot)
bot = create.create_account(botname, None, None, typeclass=bots.RSSBot)
bot.start(ev_channel=channel, rss_url=url, rss_rate=10)
self.msg("RSS reporter created. Fetching RSS.")

View file

@ -9,7 +9,7 @@ COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
# limit symbol import for API
__all__ = ("CmdHome", "CmdLook", "CmdNick",
"CmdInventory", "CmdGet", "CmdDrop", "CmdGive",
"CmdInventory", "CmdSetDesc", "CmdGet", "CmdDrop", "CmdGive",
"CmdSay", "CmdWhisper", "CmdPose", "CmdAccess")
@ -24,7 +24,7 @@ class CmdHome(COMMAND_DEFAULT_CLASS):
"""
key = "home"
locks = "cmd:perm(home) or perm(Builders)"
locks = "cmd:perm(home) or perm(Builder)"
arg_regex = r"$"
def func(self):
@ -47,7 +47,7 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
Usage:
look
look <obj>
look *<player>
look *<account>
Observes your location or objects in your vicinity.
"""
@ -86,7 +86,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
Switches:
inputline - replace on the inputline (default)
object - replace on object-lookup
player - replace on player-lookup
account - replace on account-lookup
delete - remove nick by name or by index given by /list
clearall - clear all nicks
list - show all defined aliases (also "nicks" works)
@ -113,7 +113,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
"""
key = "nick"
aliases = ["nickname", "nicks", "@nick", "@nicks", "alias"]
aliases = ["nickname", "nicks", "alias"]
locks = "cmd:all()"
def func(self):
@ -121,7 +121,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
caller = self.caller
switches = self.switches
nicktypes = [switch for switch in switches if switch in ("object", "player", "inputline")] or ["inputline"]
nicktypes = [switch for switch in switches if switch in ("object", "account", "inputline")] or ["inputline"]
nicklist = utils.make_iter(caller.nicks.get(return_obj=True) or [])
@ -358,18 +358,18 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
to_give.at_give(caller, target)
class CmdDesc(COMMAND_DEFAULT_CLASS):
class CmdSetDesc(COMMAND_DEFAULT_CLASS):
"""
describe yourself
Usage:
desc <description>
setdesc <description>
Add a description to yourself. This
will be visible to people when they
look at you.
"""
key = "desc"
key = "setdesc"
locks = "cmd:all()"
arg_regex = r"\s|$"
@ -417,7 +417,7 @@ class CmdSay(COMMAND_DEFAULT_CLASS):
return
# Call the at_after_say hook on the character
caller.at_after_say(speech)
caller.at_say(speech)
class CmdWhisper(COMMAND_DEFAULT_CLASS):
"""
@ -439,7 +439,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS):
caller = self.caller
if not self.lhs or not self.rhs:
caller.msg("Usage: whisper <player> = <message>")
caller.msg("Usage: whisper <account> = <message>")
return
receiver = caller.search(self.lhs)
@ -453,14 +453,14 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS):
speech = self.rhs
# Call a hook to change the speech before whispering
speech = caller.at_before_whisper(receiver, speech)
speech = caller.at_before_say(speech, whisper=True, receiver=receiver)
# If the speech is empty, abort the command
if not speech:
return
# Call the at_after_whisper hook for feedback
caller.at_after_whisper(receiver, speech)
caller.at_say(speech, receiver=receiver, whisper=True)
class CmdPose(COMMAND_DEFAULT_CLASS):
@ -529,15 +529,15 @@ class CmdAccess(COMMAND_DEFAULT_CLASS):
hierarchy_full = settings.PERMISSION_HIERARCHY
string = "\n|wPermission Hierarchy|n (climbing):\n %s" % ", ".join(hierarchy_full)
if self.caller.player.is_superuser:
if self.caller.account.is_superuser:
cperms = "<Superuser>"
pperms = "<Superuser>"
else:
cperms = ", ".join(caller.permissions.all())
pperms = ", ".join(caller.player.permissions.all())
pperms = ", ".join(caller.account.permissions.all())
string += "\n|wYour access|n:"
string += "\nCharacter |c%s|n: %s" % (caller.key, cperms)
if hasattr(caller, 'player'):
string += "\nPlayer |c%s|n: %s" % (caller.player.key, pperms)
if hasattr(caller, 'account'):
string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms)
caller.msg(string)

View file

@ -66,7 +66,7 @@ class CmdHelp(Command):
if self.session.protocol_key in ("websocket", "ajax/comet"):
try:
options = self.player.db._saved_webclient_options
options = self.account.db._saved_webclient_options
if options and options["helppopup"]:
usemore = False
except KeyError:
@ -134,12 +134,12 @@ class CmdHelp(Command):
Helper method. If this return True, the given cmd
auto-help will be viewable in the help listing.
Override this to easily select what is shown to
the player. Note that only commands available
the account. Note that only commands available
in the caller's merged cmdset are available.
Args:
cmd (Command): Command class from the merged cmdset
caller (Character, Player or Session): The current caller
caller (Character, Account or Session): The current caller
executing the help command.
"""
@ -300,9 +300,8 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
is to let everyone read the help file.
"""
key = "@help"
aliases = "@sethelp"
locks = "cmd:perm(PlayerHelpers)"
key = "@sethelp"
locks = "cmd:perm(Helper)"
help_category = "Building"
def func(self):

View file

@ -1,13 +1,13 @@
"""
The command template for the default MUX-style command set. There
is also an Player/OOC version that makes sure caller is a Player object.
is also an Account/OOC version that makes sure caller is an Account object.
"""
from evennia.utils import utils
from evennia.commands.command import Command
# limit symbol import for API
__all__ = ("MuxCommand", "MuxPlayerCommand")
__all__ = ("MuxCommand", "MuxAccountCommand")
class MuxCommand(Command):
@ -128,17 +128,17 @@ class MuxCommand(Command):
self.rhs = rhs
self.rhslist = rhslist
# if the class has the player_caller property set on itself, we make
# sure that self.caller is always the player if possible. We also create
# if the class has the account_caller property set on itself, we make
# sure that self.caller is always the account if possible. We also create
# a special property "character" for the puppeted object, if any. This
# is convenient for commands defined on the Player only.
if hasattr(self, "player_caller") and self.player_caller:
# is convenient for commands defined on the Account only.
if hasattr(self, "account_caller") and self.account_caller:
if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
# caller is an Object/Character
self.character = self.caller
self.caller = self.caller.player
elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"):
# caller was already a Player
self.caller = self.caller.account
elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
# caller was already an Account
self.character = self.caller.get_puppet(self.session)
else:
self.character = None
@ -177,32 +177,32 @@ class MuxCommand(Command):
self.caller.msg(string)
class MuxPlayerCommand(MuxCommand):
class MuxAccountCommand(MuxCommand):
"""
This is an on-Player version of the MuxCommand. Since these commands sit
on Players rather than on Characters/Objects, we need to check
This is an on-Account version of the MuxCommand. Since these commands sit
on Accounts rather than on Characters/Objects, we need to check
this in the parser.
Player commands are available also when puppeting a Character, it's
Account commands are available also when puppeting a Character, it's
just that they are applied with a lower priority and are always
available, also when disconnected from a character (i.e. "ooc").
This class makes sure that caller is always a Player object, while
This class makes sure that caller is always an Account object, while
creating a new property "character" that is set only if a
character is actually attached to this Player and Session.
character is actually attached to this Account and Session.
"""
def parse(self):
"""
We run the parent parser as usual, then fix the result
"""
super(MuxPlayerCommand, self).parse()
super(MuxAccountCommand, self).parse()
if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
# caller is an Object/Character
self.character = self.caller
self.caller = self.caller.player
elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"):
# caller was already a Player
self.caller = self.caller.account
elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
# caller was already an Account
self.character = self.caller.get_puppet(self.session)
else:
self.character = None

View file

@ -17,7 +17,7 @@ from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.scripts.models import ScriptDB
from evennia.objects.models import ObjectDB
from evennia.players.models import PlayerDB
from evennia.accounts.models import AccountDB
from evennia.utils import logger, utils, gametime, create
from evennia.utils.eveditor import EvEditor
from evennia.utils.evtable import EvTable
@ -47,7 +47,7 @@ class CmdReload(COMMAND_DEFAULT_CLASS):
@reset to purge) and at_reload() hooks will be called.
"""
key = "@reload"
locks = "cmd:perm(reload) or perm(Immortals)"
locks = "cmd:perm(reload) or perm(Developer)"
help_category = "System"
def func(self):
@ -83,7 +83,7 @@ class CmdReset(COMMAND_DEFAULT_CLASS):
"""
key = "@reset"
aliases = ['@reboot']
locks = "cmd:perm(reload) or perm(Immortals)"
locks = "cmd:perm(reload) or perm(Developer)"
help_category = "System"
def func(self):
@ -105,7 +105,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
Gracefully shut down both Server and Portal.
"""
key = "@shutdown"
locks = "cmd:perm(shutdown) or perm(Immortals)"
locks = "cmd:perm(shutdown) or perm(Developer)"
help_category = "System"
def func(self):
@ -244,7 +244,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
"""
key = "@py"
aliases = ["!"]
locks = "cmd:perm(py) or perm(Immortals)"
locks = "cmd:perm(py) or perm(Developer)"
help_category = "System"
def func(self):
@ -327,7 +327,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
"""
key = "@scripts"
aliases = ["@globalscript", "@listscripts"]
locks = "cmd:perm(listscripts) or perm(Wizards)"
locks = "cmd:perm(listscripts) or perm(Admin)"
help_category = "System"
def func(self):
@ -409,7 +409,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
"""
key = "@objects"
aliases = ["@listobjects", "@listobjs", '@stats', '@db']
locks = "cmd:perm(listobjects) or perm(Builders)"
locks = "cmd:perm(listobjects) or perm(Builder)"
help_category = "System"
def func(self):
@ -455,24 +455,24 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
caller.msg(string)
class CmdPlayers(COMMAND_DEFAULT_CLASS):
class CmdAccounts(COMMAND_DEFAULT_CLASS):
"""
list all registered players
list all registered accounts
Usage:
@players [nr]
@accounts [nr]
Lists statistics about the Players registered with the game.
It will list the <nr> amount of latest registered players
Lists statistics about the Accounts registered with the game.
It will list the <nr> amount of latest registered accounts
If not given, <nr> defaults to 10.
"""
key = "@players"
aliases = ["@listplayers"]
locks = "cmd:perm(listplayers) or perm(Wizards)"
key = "@accounts"
aliases = ["@listaccounts"]
locks = "cmd:perm(listaccounts) or perm(Admin)"
help_category = "System"
def func(self):
"""List the players"""
"""List the accounts"""
caller = self.caller
if self.args and self.args.isdigit():
@ -480,21 +480,21 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS):
else:
nlim = 10
nplayers = PlayerDB.objects.count()
naccounts = AccountDB.objects.count()
# typeclass table
dbtotals = PlayerDB.objects.object_totals()
dbtotals = AccountDB.objects.object_totals()
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l")
for path, count in dbtotals.items():
typetable.add_row(path, count, "%.2f" % ((float(count) / nplayers) * 100))
typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100))
# last N table
plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):]
plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim):]
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l")
for ply in plyrs:
latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path)
string = "\n|wPlayer typeclass distribution:|n\n%s" % typetable
string += "\n|wLast %s Players created:|n\n%s" % (min(nplayers, nlim), latesttable)
string = "\n|wAccount typeclass distribution:|n\n%s" % typetable
string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable)
caller.msg(string)
@ -520,7 +520,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
key = "@service"
aliases = ["@services"]
locks = "cmd:perm(service) or perm(Immortals)"
locks = "cmd:perm(service) or perm(Developer)"
help_category = "System"
def func(self):
@ -644,7 +644,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
"""
key = "@time"
aliases = "@uptime"
locks = "cmd:perm(time) or perm(Players)"
locks = "cmd:perm(time) or perm(Account)"
help_category = "System"
def func(self):
@ -702,7 +702,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
"""
key = "@server"
aliases = ["@serverload", "@serverprocess"]
locks = "cmd:perm(list) or perm(Immortals)"
locks = "cmd:perm(list) or perm(Developer)"
help_category = "System"
def func(self):
@ -823,7 +823,7 @@ class CmdTickers(COMMAND_DEFAULT_CLASS):
"""
key = "@tickers"
help_category = "System"
locks = "cmd:perm(tickers) or perm(Builders)"
locks = "cmd:perm(tickers) or perm(Builder)"
def func(self):
from evennia import TICKER_HANDLER

View file

@ -19,7 +19,7 @@ from mock import Mock
from evennia.commands.default.cmdset_character import CharacterCmdSet
from evennia.utils.test_resources import EvenniaTest
from evennia.commands.default import help, general, system, admin, player, building, batchprocess, comms
from evennia.commands.default import help, general, system, admin, account, building, batchprocess, comms
from evennia.commands.command import Command, InterruptCommand
from evennia.utils import ansi, utils
from evennia.server.sessionhandler import SESSIONS
@ -57,11 +57,13 @@ class CommandTest(EvenniaTest):
caller = caller if caller else self.char1
receiver = receiver if receiver else caller
cmdobj.caller = caller
cmdobj.cmdstring = cmdstring if cmdstring else cmdobj.key
cmdobj.cmdname = cmdstring if cmdstring else cmdobj.key
cmdobj.raw_cmdname = cmdobj.cmdname
cmdobj.cmdstring = cmdobj.cmdname # deprecated
cmdobj.args = args
cmdobj.cmdset = cmdset
cmdobj.session = SESSIONS.session_from_sessid(1)
cmdobj.player = self.player
cmdobj.account = self.account
cmdobj.raw_string = cmdobj.key + " " + args
cmdobj.obj = obj or (caller if caller else self.char1)
# test
@ -76,7 +78,7 @@ class CommandTest(EvenniaTest):
except InterruptCommand:
pass
finally:
# clean out prettytable sugar. We only operate on text-type
# clean out evtable sugar. We only operate on text-type
stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True))
for name, args, kwargs in receiver.msg.mock_calls]
# Get the first element of a tuple if msg received a tuple instead of a string
@ -117,10 +119,10 @@ class TestGeneral(CommandTest):
def test_nick(self):
self.call(general.CmdNick(), "testalias = testaliasedstring1", "Nick 'testalias' mapped to 'testaliasedstring1'.")
self.call(general.CmdNick(), "/player testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.")
self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.")
self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Nick 'testalias' mapped to 'testaliasedstring3'.")
self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias"))
self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="player"))
self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="account"))
self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object"))
def test_get_and_drop(self):
@ -170,54 +172,54 @@ class TestAdmin(CommandTest):
self.call(admin.CmdEmit(), "Char2 = Test", "Emitted to Char2:\nTest")
def test_perm(self):
self.call(admin.CmdPerm(), "Obj = Builders", "Permission 'Builders' given to Obj (the Object/Character).")
self.call(admin.CmdPerm(), "Char2 = Builders", "Permission 'Builders' given to Char2 (the Object/Character).")
self.call(admin.CmdPerm(), "Obj = Builder", "Permission 'Builder' given to Obj (the Object/Character).")
self.call(admin.CmdPerm(), "Char2 = Builder", "Permission 'Builder' given to Char2 (the Object/Character).")
def test_wall(self):
self.call(admin.CmdWall(), "Test", "Announcing to all connected players ...")
self.call(admin.CmdWall(), "Test", "Announcing to all connected accounts ...")
def test_ban(self):
self.call(admin.CmdBan(), "Char", "NameBan char was added.")
class TestPlayer(CommandTest):
class TestAccount(CommandTest):
def test_ooc_look(self):
if settings.MULTISESSION_MODE < 2:
self.call(player.CmdOOCLook(), "", "You are outofcharacter (OOC).", caller=self.player)
self.call(account.CmdOOCLook(), "", "You are outofcharacter (OOC).", caller=self.account)
if settings.MULTISESSION_MODE == 2:
self.call(player.CmdOOCLook(), "", "Account TestPlayer (you are OutofCharacter)", caller=self.player)
self.call(account.CmdOOCLook(), "", "Account TestAccount (you are OutofCharacter)", caller=self.account)
def test_ooc(self):
self.call(player.CmdOOC(), "", "You go OOC.", caller=self.player)
self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account)
def test_ic(self):
self.player.unpuppet_object(self.session)
self.call(player.CmdIC(), "Char", "You become Char.", caller=self.player, receiver=self.char1)
self.account.unpuppet_object(self.session)
self.call(account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1)
def test_password(self):
self.call(player.CmdPassword(), "testpassword = testpassword", "Password changed.", caller=self.player)
self.call(account.CmdPassword(), "testpassword = testpassword", "Password changed.", caller=self.account)
def test_option(self):
self.call(player.CmdOption(), "", "Client settings", caller=self.player)
self.call(account.CmdOption(), "", "Client settings", caller=self.account)
def test_who(self):
self.call(player.CmdWho(), "", "Players:", caller=self.player)
self.call(account.CmdWho(), "", "Accounts:", caller=self.account)
def test_quit(self):
self.call(player.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.player)
self.call(account.CmdQuit(), "", "Quitting. Hope to see you again, soon.", caller=self.account)
def test_sessions(self):
self.call(player.CmdSessions(), "", "Your current session(s):", caller=self.player)
self.call(account.CmdSessions(), "", "Your current session(s):", caller=self.account)
def test_color_test(self):
self.call(player.CmdColorTest(), "ansi", "ANSI colors:", caller=self.player)
self.call(account.CmdColorTest(), "ansi", "ANSI colors:", caller=self.account)
def test_char_create(self):
self.call(player.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.player)
self.call(account.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.account)
def test_quell(self):
self.call(player.CmdQuell(), "", "Quelling to current puppet's permissions (immortals).", caller=self.player)
self.call(account.CmdQuell(), "", "Quelling to current puppet's permissions (developer).", caller=self.account)
class TestBuilding(CommandTest):
@ -272,7 +274,7 @@ class TestBuilding(CommandTest):
"Obj changed typeclass from evennia.objects.objects.DefaultObject to evennia.objects.objects.DefaultExit.")
def test_lock(self):
self.call(building.CmdLock(), "Obj = test:perm(Immortals)", "Added lock 'test:perm(Immortals)' to Obj.")
self.call(building.CmdLock(), "Obj = test:perm(Developer)", "Added lock 'test:perm(Developer)' to Obj.")
def test_find(self):
self.call(building.CmdFind(), "Room2", "One Match")
@ -288,39 +290,39 @@ class TestComms(CommandTest):
def setUp(self):
super(CommandTest, self).setUp()
self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.player)
self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.account)
def test_toggle_com(self):
self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.player)
self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.player)
self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.account)
self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.account)
def test_channels(self):
self.call(comms.CmdChannels(), "" ,"Available channels (use comlist,addcom and delcom to manage", receiver=self.player)
self.call(comms.CmdChannels(), "" ,"Available channels (use comlist,addcom and delcom to manage", receiver=self.account)
def test_all_com(self):
self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.player)
self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.account)
def test_clock(self):
self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.player)
self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.account)
def test_cdesc(self):
self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.player)
self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.account)
def test_cemit(self):
self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.player)
self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.account)
def test_cwho(self):
self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestPlayer", receiver=self.player)
self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestAccount", receiver=self.account)
def test_page(self):
self.call(comms.CmdPage(), "TestPlayer2 = Test", "TestPlayer2 is offline. They will see your message if they list their pages later.|You paged TestPlayer2 with: 'Test'.", receiver=self.player)
self.call(comms.CmdPage(), "TestAccount2 = Test", "TestAccount2 is offline. They will see your message if they list their pages later.|You paged TestAccount2 with: 'Test'.", receiver=self.account)
def test_cboot(self):
# No one else connected to boot
self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] <channel> = <player> [:reason]", receiver=self.player)
self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] <channel> = <account> [:reason]", receiver=self.account)
def test_cdestroy(self):
self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestPlayer: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.player)
self.call(comms.CmdCdestroy(), "testchan" ,"[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.account)
class TestBatchProcess(CommandTest):

View file

@ -7,7 +7,7 @@ from collections import defaultdict
from random import getrandbits
from django.conf import settings
from django.contrib.auth import authenticate
from evennia.players.models import PlayerDB
from evennia.accounts.models import AccountDB
from evennia.objects.models import ObjectDB
from evennia.server.models import ServerConfig
from evennia.comms.models import ChannelDB
@ -25,7 +25,7 @@ MULTISESSION_MODE = settings.MULTISESSION_MODE
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
# Helper function to throttle failed connection attempts.
# This can easily be used to limit player creation too,
# This can easily be used to limit account creation too,
# (just supply a different storage dictionary), but this
# would also block dummyrunner, so it's not added as default.
@ -77,17 +77,17 @@ def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS)
return False
def create_guest_player(session):
def create_guest_account(session):
"""
Creates a guest player/character for this session, if one is available.
Creates a guest account/character for this session, if one is available.
Args:
session (Session): the session which will use the guest player/character.
session (Session): the session which will use the guest account/character.
Returns:
GUEST_ENABLED (boolean), player (Player):
GUEST_ENABLED (boolean), account (Account):
the boolean is whether guest accounts are enabled at all.
the Player which was created from an available guest name.
the Account which was created from an available guest name.
"""
# check if guests are enabled.
if not settings.GUEST_ENABLED:
@ -105,25 +105,25 @@ def create_guest_player(session):
try:
# Find an available guest name.
playername = None
accountname = None
for name in settings.GUEST_LIST:
if not PlayerDB.objects.filter(username__iexact=playername).count():
playername = name
if not AccountDB.objects.filter(username__iexact=accountname).count():
accountname = name
break
if not playername:
if not accountname:
session.msg("All guest accounts are in use. Please try again later.")
return True, None
else:
# build a new player with the found guest playername
# build a new account with the found guest accountname
password = "%016x" % getrandbits(64)
home = ObjectDB.objects.get_id(settings.GUEST_HOME)
permissions = settings.PERMISSION_GUEST_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
ptypeclass = settings.BASE_GUEST_TYPECLASS
new_player = _create_player(session, playername, password, permissions, ptypeclass)
if new_player:
_create_character(session, new_player, typeclass, home, permissions)
return True, new_player
new_account = _create_account(session, accountname, password, permissions, ptypeclass)
if new_account:
_create_character(session, new_account, typeclass, home, permissions)
return True, new_account
except Exception:
# We are in the middle between logged in and -not, so we have
@ -134,17 +134,17 @@ def create_guest_player(session):
raise
def create_normal_player(session, name, password):
def create_normal_account(session, name, password):
"""
Creates a player with the given name and password.
Creates an account with the given name and password.
Args:
session (Session): the session which is requesting to create a player.
name (str): the name that the player wants to use for login.
password (str): the password desired by this player, for login.
session (Session): the session which is requesting to create an account.
name (str): the name that the account wants to use for login.
password (str): the password desired by this account, for login.
Returns:
player (Player): the player which was created from the name and password.
account (Account): the account which was created from the name and password.
"""
# check for too many login errors too quick.
if _throttle(session, maxlim=5, timeout=5*60):
@ -153,22 +153,22 @@ def create_normal_player(session, name, password):
return None
# Match account name and check password
player = authenticate(username=name, password=password)
account = authenticate(username=name, password=password)
if not player:
# No playername or password match
if not account:
# No accountname or password match
session.msg("Incorrect login information given.")
# this just updates the throttle
_throttle(session)
# calls player hook for a failed login if possible.
player = PlayerDB.objects.get_player_from_name(name)
if player:
player.at_failed_login(session)
# calls account hook for a failed login if possible.
account = AccountDB.objects.get_account_from_name(name)
if account:
account.at_failed_login(session)
return None
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == player.name.lower() for tup in bans)
if bans and (any(tup[0] == account.name.lower() for tup in bans)
or
any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name!
@ -178,7 +178,7 @@ def create_normal_player(session, name, password):
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return None
return player
return account
class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
@ -186,8 +186,8 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
connect to the game
Usage (at login screen):
connect playername password
connect "player name" "pass word"
connect accountname password
connect "account name" "pass word"
Use the create command to first create an account before logging in.
@ -204,7 +204,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
have a unique position in that their func() receives
a session object instead of a source_object like all
other types of logged-in commands (this is because
there is no object yet before the player has logged in)
there is no object yet before the account has logged in)
"""
session = self.caller
@ -222,9 +222,9 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
parts = parts[0].split(None, 1)
# Guest login
if len(parts) == 1 and parts[0].lower() == "guest":
enabled, new_player = create_guest_player(session)
if new_player:
session.sessionhandler.login(session, new_player)
enabled, new_account = create_guest_account(session)
if new_account:
session.sessionhandler.login(session, new_account)
if enabled:
return
@ -233,20 +233,20 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
return
name, password = parts
player = create_normal_player(session, name, password)
if player:
session.sessionhandler.login(session, player)
account = create_normal_account(session, name, password)
if account:
session.sessionhandler.login(session, account)
class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
"""
create a new player account
create a new account account
Usage (at login screen):
create <playername> <password>
create "player name" "pass word"
create <accountname> <password>
create "account name" "pass word"
This creates a new player account.
This creates a new account account.
If you have spaces in your name, enclose it in double quotes.
"""
@ -271,25 +271,25 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
"\nIf <name> or <password> contains spaces, enclose it in double quotes."
session.msg(string)
return
playername, password = parts
accountname, password = parts
# sanity checks
if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30):
# this echoes the restrictions made by django's auth
# module (except not allowing spaces, for convenience of
# logging in).
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
session.msg(string)
return
# strip excessive spaces in playername
playername = re.sub(r"\s+", " ", playername).strip()
if PlayerDB.objects.filter(username__iexact=playername):
# player already exists (we also ignore capitalization here)
session.msg("Sorry, there is already a player with the name '%s'." % playername)
# strip excessive spaces in accountname
accountname = re.sub(r"\s+", " ", accountname).strip()
if AccountDB.objects.filter(username__iexact=accountname):
# account already exists (we also ignore capitalization here)
session.msg("Sorry, there is already an account with the name '%s'." % accountname)
return
# Reserve playernames found in GUEST_LIST
if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
string = "\n\r That name is reserved. Please choose another Playername."
# Reserve accountnames found in GUEST_LIST
if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST):
string = "\n\r That name is reserved. Please choose another Accountname."
session.msg(string)
return
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
@ -301,7 +301,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == playername.lower() for tup in bans)
if bans and (any(tup[0] == accountname.lower() for tup in bans)
or
any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name!
@ -311,22 +311,22 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return
# everything's ok. Create the new player account.
# everything's ok. Create the new account account.
try:
permissions = settings.PERMISSION_PLAYER_DEFAULT
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
new_player = _create_player(session, playername, password, permissions)
if new_player:
new_account = _create_account(session, accountname, password, permissions)
if new_account:
if MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
_create_character(session, new_player, typeclass, default_home, permissions)
_create_character(session, new_account, typeclass, default_home, permissions)
# tell the caller everything went well.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
if " " in accountname:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
else:
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, playername))
session.msg(string % (accountname, accountname))
except Exception:
# We are in the middle between logged in and -not, so we have
@ -344,7 +344,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
quit
We maintain a different version of the quit command
here for unconnected players for the sake of simplicity. The logged in
here for unconnected accounts for the sake of simplicity. The logged in
version is a bit more complicated.
"""
key = "quit"
@ -516,50 +516,50 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
self.session.sessionhandler.session_portal_sync(self.session)
def _create_player(session, playername, password, permissions, typeclass=None, email=None):
def _create_account(session, accountname, password, permissions, typeclass=None, email=None):
"""
Helper function, creates a player of the specified typeclass.
Helper function, creates an account of the specified typeclass.
"""
try:
new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass)
new_account = create.create_account(accountname, email, password, permissions=permissions, typeclass=typeclass)
except Exception as e:
session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e)
session.msg("There was an error creating the Account:\n%s\n If this problem persists, contact an admin." % e)
logger.log_trace()
return False
# This needs to be set so the engine knows this player is
# This needs to be set so the engine knows this account is
# logging in for the first time. (so it knows to call the right
# hooks during login later)
new_player.db.FIRST_LOGIN = True
new_account.db.FIRST_LOGIN = True
# join the new player to the public channel
# join the new account to the public channel
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
if not pchannel or not pchannel.connect(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
if not pchannel or not pchannel.connect(new_account):
string = "New account '%s' could not connect to public channel!" % new_account.key
logger.log_err(string)
return new_player
return new_account
def _create_character(session, new_player, typeclass, home, permissions):
def _create_character(session, new_account, typeclass, home, permissions):
"""
Helper function, creates a character based on a player's name.
Helper function, creates a character based on an account's name.
This is meant for Guest and MULTISESSION_MODE < 2 situations.
"""
try:
new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions)
new_character = create.create_object(typeclass, key=new_account.key, home=home, permissions=permissions)
# set playable character list
new_player.db._playable_characters.append(new_character)
new_account.db._playable_characters.append(new_character)
# allow only the character itself and the player to puppet this character (and Immortals).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
(new_character.id, new_player.id))
# allow only the character itself and the account to puppet this character (and Developers).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
(new_character.id, new_account.id))
# If no description is set, set a default description
if not new_character.db.desc:
new_character.db.desc = "This is a Player."
new_character.db.desc = "This is an Account."
# We need to set this to have @ic auto-connect to this character
new_player.db._last_puppet = new_character
new_account.db._last_puppet = new_character
except Exception as e:
session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e)
logger.log_trace()

View file

@ -266,15 +266,15 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
deferred.addCallback(_callback)
return deferred
def test_from_player(self):
from evennia.commands.default.cmdset_player import PlayerCmdSet
def test_from_account(self):
from evennia.commands.default.cmdset_account import AccountCmdSet
a = self.cmdset_a
a.no_channels = True
self.set_cmdsets(self.player, a)
deferred = cmdhandler.get_and_merge_cmdsets(self.player, None, self.player, None, "player", "")
self.set_cmdsets(self.account, a)
deferred = cmdhandler.get_and_merge_cmdsets(self.account, None, self.account, None, "account", "")
# get_and_merge_cmdsets converts to lower-case internally.
def _callback(cmdset):
pcmdset = PlayerCmdSet()
pcmdset = AccountCmdSet()
pcmdset.at_cmdset_creation()
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"]
self.assertTrue(all(cmd.key in pcmds for cmd in cmdset.commands))
@ -305,18 +305,18 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
def test_autocmdsets(self):
import evennia
from evennia.commands.default.cmdset_player import PlayerCmdSet
from evennia.commands.default.cmdset_account import AccountCmdSet
from evennia.comms.channelhandler import CHANNEL_HANDLER
testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()")
CHANNEL_HANDLER.add(testchannel)
CHANNEL_HANDLER.update()
self.assertTrue(testchannel.connect(self.player))
self.assertTrue(testchannel.has_connection(self.player))
self.assertTrue(testchannel.connect(self.account))
self.assertTrue(testchannel.has_connection(self.account))
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
self.set_cmdsets(self.player, a, b, c, d)
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.player, self.char1, "session", "")
self.set_cmdsets(self.account, a, b, c, d)
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.account, self.char1, "session", "")
def _callback(cmdset):
pcmdset = PlayerCmdSet()
pcmdset = AccountCmdSet()
pcmdset.at_cmdset_creation()
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"]
self.assertTrue(all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands))

View file

@ -112,7 +112,7 @@ class ChannelCommand(command.Command):
self.msg(string % channelkey)
return
if msg == "on":
caller = caller if not hasattr(caller, 'player') else caller.player
caller = caller if not hasattr(caller, 'account') else caller.account
unmuted = channel.unmute(caller)
if unmuted:
self.msg("You start listening to %s." % channel)
@ -120,7 +120,7 @@ class ChannelCommand(command.Command):
self.msg("You were already listening to %s." % channel)
return
if msg == "off":
caller = caller if not hasattr(caller, 'player') else caller.player
caller = caller if not hasattr(caller, 'account') else caller.account
muted = channel.mute(caller)
if muted:
self.msg("You stop listening to %s." % channel)
@ -134,7 +134,7 @@ class ChannelCommand(command.Command):
if "[-]" in line else line for line in lines))
tail_log_file(log_file, self.history_start, 20, callback=send_msg)
else:
caller = caller if not hasattr(caller, 'player') else caller.player
caller = caller if not hasattr(caller, 'account') else caller.account
if caller in channel.mutelist:
self.msg("You currently have %s muted." % channel)
return
@ -145,7 +145,7 @@ class ChannelCommand(command.Command):
Let users know that this command is for communicating on a channel.
Args:
caller (TypedObject): A Character or Player who has entered an ambiguous command.
caller (TypedObject): A Character or Account who has entered an ambiguous command.
Returns:
A string with identifying information to disambiguate the object, conventionally with a preceding space.

View file

@ -62,26 +62,26 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
def has_connection(self, subscriber):
"""
Checks so this player is actually listening
Checks so this account is actually listening
to this channel.
Args:
subscriber (Player or Object): Entity to check.
subscriber (Account or Object): Entity to check.
Returns:
has_sub (bool): Whether the subscriber is subscribing to
this channel or not.
Notes:
This will first try Player subscribers and only try Object
if the Player fails.
This will first try Account subscribers and only try Object
if the Account fails.
"""
has_sub = self.subscriptions.has(subscriber)
if not has_sub and hasattr(subscriber, "player"):
if not has_sub and hasattr(subscriber, "account"):
# it's common to send an Object when we
# by default only allow Players to subscribe.
has_sub = self.subscriptions.has(subscriber.player)
# by default only allow Accounts to subscribe.
has_sub = self.subscriptions.has(subscriber.account)
return has_sub
@property
@ -94,16 +94,22 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist]
if subs:
# display listening subscribers in bold
string = ", ".join([player.key if player not in listening else "|w%s|n" % player.key for player in subs])
string = ", ".join([account.key if account not in listening else "|w%s|n" % account.key for account in subs])
else:
string = "<None>"
return string
def mute(self, subscriber):
def mute(self, subscriber, **kwargs):
"""
Adds an entity to the list of muted subscribers.
A muted subscriber will no longer see channel messages,
but may use channel commands.
Args:
subscriber (Object or Account): Subscriber to mute.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
mutelist = self.mutelist
if subscriber not in mutelist:
@ -112,11 +118,16 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
return True
return False
def unmute(self, subscriber):
def unmute(self, subscriber, **kwargs):
"""
Removes an entity to the list of muted subscribers.
A muted subscriber will no longer see channel messages,
Removes an entity to the list of muted subscribers. A muted subscriber will no longer see channel messages,
but may use channel commands.
Args:
subscriber (Object or Account): The subscriber to unmute.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
mutelist = self.mutelist
if subscriber in mutelist:
@ -125,13 +136,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
return True
return False
def connect(self, subscriber):
def connect(self, subscriber, **kwargs):
"""
Connect the user to this channel. This checks access.
Args:
subscriber (Player or Object): the entity to subscribe
subscriber (Account or Object): the entity to subscribe
to this channel.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
success (bool): Whether or not the addition was
@ -153,13 +166,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
self.post_join_channel(subscriber)
return True
def disconnect(self, subscriber):
def disconnect(self, subscriber, **kwargs):
"""
Disconnect entity from this channel.
Args:
subscriber (Player of Object): the
subscriber (Account of Object): the
entity to disconnect.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
success (bool): Whether or not the removal was
@ -178,7 +193,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
self.post_leave_channel(subscriber)
return True
def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False):
def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False, **kwargs):
"""
Determines if another object has permission to access.
@ -188,6 +203,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
default (bool, optional): What to return if no lock of access_type was found
no_superuser_bypass (bool, optional): Turns off superuser
lock bypass. Be careful with this one.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
return (bool): Result of lock check.
@ -208,7 +225,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
CHANNELHANDLER.update()
def message_transform(self, msgobj, emit=False, prefix=True,
sender_strings=None, external=False):
sender_strings=None, external=False, **kwargs):
"""
Generates the formatted string sent to listeners on a channel.
@ -219,6 +236,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
prefix (bool, optional): Prefix `msg` with a text given by `self.channel_prefix`.
sender_strings (list, optional): Used by bots etc, one string per external sender.
external (bool, optional): If this is an external sender or not.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
if sender_strings or external:
@ -230,7 +249,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
msgobj.message = body
return msgobj
def distribute_message(self, msgobj, online=False):
def distribute_message(self, msgobj, online=False, **kwargs):
"""
Method for grabbing all listeners that a message should be
sent to on this channel, and sending them a message.
@ -238,12 +257,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Args:
msgobj (Msg or TempMsg): Message to distribute.
online (bool): Only send to receivers who are actually online
(not currently used):
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
This is also where logging happens, if enabled.
"""
# get all players or objects connected to this channel and send to them
# get all accounts or objects connected to this channel and send to them
if online:
subs = self.subscriptions.online()
else:
@ -254,7 +276,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
continue
try:
# note our addition of the from_channel keyword here. This could be checked
# by a custom player.msg() to treat channel-receives differently.
# by a custom account.msg() to treat channel-receives differently.
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id})
except AttributeError as e:
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
@ -266,7 +288,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
def msg(self, msgobj, header=None, senders=None, sender_strings=None,
keep_log=None, online=False, emit=False, external=False):
"""
Send the given message to all players connected to channel. Note that
Send the given message to all accounts connected to channel. Note that
no permission-checking is done here; it is assumed to have been
done before calling this method. The optional keywords are not used if
persistent is False.
@ -278,10 +300,10 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
(if persistent=False) or it will be used together with `header`
and `senders` keywords to create a Msg instance on the fly.
header (str, optional): A header for building the message.
senders (Object, Player or list, optional): Optional if persistent=False, used
senders (Object, Account or list, optional): Optional if persistent=False, used
to build senders for the message.
sender_strings (list, optional): Name strings of senders. Used for external
connections where the sender is not a player or object.
connections where the sender is not an account or object.
When this is defined, external will be assumed.
keep_log (bool or None, optional): This allows to temporarily change the logging status of
this channel message. If `None`, the Channel's `keep_log` Attribute will
@ -289,8 +311,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
message only (note that for unlogged channels, a `True` value here will
create a new log file only for this message).
online (bool, optional) - If this is set true, only messages people who are
online. Otherwise, messages all players connected. This can
make things faster, but may not trigger listeners on players
online. Otherwise, messages all accounts connected. This can
make things faster, but may not trigger listeners on accounts
that are offline.
emit (bool, optional) - Signals to the message formatter that this message is
not to be directly associated with a name.
@ -334,7 +356,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
# hooks
def channel_prefix(self, msg=None, emit=False):
def channel_prefix(self, msg=None, emit=False, **kwargs):
"""
Hook method. How the channel should prefix itself for users.
@ -343,6 +365,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
msg (str, optional): Prefix text
emit (bool, optional): Switches to emit mode, which usually
means to not prefix the channel's info.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
prefix (str): The created channel prefix.
@ -350,12 +374,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
"""
return '' if emit else '[%s] ' % self.key
def format_senders(self, senders=None):
def format_senders(self, senders=None, **kwargs):
"""
Hook method. Function used to format a list of sender names.
Args:
senders (list): Sender object names.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
formatted_list (str): The list of names formatted appropriately.
@ -363,14 +389,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Notes:
This function exists separately so that external sources
can use it to format source names in the same manner as
normal object/player names.
normal object/account names.
"""
if not senders:
return ''
return ', '.join(senders)
def pose_transform(self, msgobj, sender_string):
def pose_transform(self, msgobj, sender_string, **kwargs):
"""
Hook method. Detects if the sender is posing, and modifies the
message accordingly.
@ -378,6 +404,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Args:
msgobj (Msg or TempMsg): The message to analyze for a pose.
sender_string (str): The name of the sender/poser.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
string (str): A message that combines the `sender_string`
@ -400,17 +428,19 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
else:
return '%s: %s' % (sender_string, message)
def format_external(self, msgobj, senders, emit=False):
def format_external(self, msgobj, senders, emit=False, **kwargs):
"""
Hook method. Used for formatting external messages. This is
needed as a separate operation because the senders of external
messages may not be in-game objects/players, and so cannot
messages may not be in-game objects/accounts, and so cannot
have things like custom user preferences.
Args:
msgobj (Msg or TempMsg): The message to send.
senders (list): Strings, one per sender.
emit (bool, optional): A sender-agnostic message or not.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
transformed (str): A formatted string.
@ -421,13 +451,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
senders = ', '.join(senders)
return self.pose_transform(msgobj, senders)
def format_message(self, msgobj, emit=False):
def format_message(self, msgobj, emit=False, **kwargs):
"""
Hook method. Formats a message body for display.
Args:
msgobj (Msg or TempMsg): The message object to send.
emit (bool, optional): The message is agnostic of senders.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
transformed (str): The formatted message.
@ -445,13 +477,15 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
senders = ', '.join(senders)
return self.pose_transform(msgobj, senders)
def pre_join_channel(self, joiner):
def pre_join_channel(self, joiner, **kwargs):
"""
Hook method. Runs right before a channel is joined. If this
returns a false value, channel joining is aborted.
Args:
joiner (object): The joining object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
should_join (bool): If `False`, channel joining is aborted.
@ -459,23 +493,27 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
"""
return True
def post_join_channel(self, joiner):
def post_join_channel(self, joiner, **kwargs):
"""
Hook method. Runs right after an object or player joins a channel.
Hook method. Runs right after an object or account joins a channel.
Args:
joiner (object): The joining object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def pre_leave_channel(self, leaver):
def pre_leave_channel(self, leaver, **kwargs):
"""
Hook method. Runs right before a user leaves a channel. If this returns a false
value, leaving the channel will be aborted.
Args:
leaver (object): The leaving object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
should_leave (bool): If `False`, channel parting is aborted.
@ -483,17 +521,19 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
"""
return True
def post_leave_channel(self, leaver):
def post_leave_channel(self, leaver, **kwargs):
"""
Hook method. Runs right after an object or player leaves a channel.
Hook method. Runs right after an object or account leaves a channel.
Args:
leaver (object): The leaving object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def pre_send_message(self, msg):
def pre_send_message(self, msg, **kwargs):
"""
Hook method. Runs before a message is sent to the channel and
should return the message object, after any transformations.
@ -501,6 +541,8 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Args:
msg (Msg or TempMsg): Message to send.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
result (Msg, TempMsg or bool): If False, abort send.
@ -508,12 +550,14 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
"""
return msg
def post_send_message(self, msg):
def post_send_message(self, msg, **kwargs):
"""
Hook method. Run after a message is sent to the channel.
Args:
msg (Msg or TempMsg): Message sent.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass

View file

@ -5,14 +5,12 @@ Comm system components.
"""
from __future__ import print_function
from django.db import models
from django.db.models import Q
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager,
returns_typeclass_list, returns_typeclass)
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager)
from evennia.utils import logger
_GA = object.__getattribute__
_PlayerDB = None
_AccountDB = None
_ObjectDB = None
_ChannelDB = None
_SESSIONS = None
@ -59,7 +57,7 @@ def dbref(inp, reqhash=True):
def identify_object(inp):
"""
Helper function. Identifies if an object is a player or an object;
Helper function. Identifies if an object is an account or an object;
return its database model
Args:
@ -67,14 +65,14 @@ def identify_object(inp):
Returns:
identified (tuple): This is a tuple with (`inp`, identifier)
where `identifier` is one of "player", "object", "channel",
where `identifier` is one of "account", "object", "channel",
"string", "dbref" or None.
"""
if hasattr(inp, "__dbclass__"):
clsname = inp.__dbclass__.__name__
if clsname == "PlayerDB":
return inp, "player"
if clsname == "AccountDB":
return inp, "account"
elif clsname == "ObjectDB":
return inp ,"object"
elif clsname == "ChannelDB":
@ -87,14 +85,14 @@ def identify_object(inp):
return inp, None
def to_object(inp, objtype='player'):
def to_object(inp, objtype='account'):
"""
Locates the object related to the given playername or channel key.
Locates the object related to the given accountname or channel key.
If input was already the correct object, return it.
Args:
inp (any): The input object/string
objtype (str): Either 'player' or 'channel'.
objtype (str): Either 'account' or 'channel'.
Returns:
obj (object): The correct object related to `inp`.
@ -103,17 +101,17 @@ def to_object(inp, objtype='player'):
obj, typ = identify_object(inp)
if typ == objtype:
return obj
if objtype == 'player':
if objtype == 'account':
if typ == 'object':
return obj.player
return obj.account
if typ == 'string':
return _PlayerDB.objects.get(user_username__iexact=obj)
return _AccountDB.objects.get(user_username__iexact=obj)
if typ == 'dbref':
return _PlayerDB.objects.get(id=obj)
return _AccountDB.objects.get(id=obj)
logger.log_err("%s %s %s %s %s", objtype, inp, obj, typ, type(inp))
raise CommError()
elif objtype == 'object':
if typ == 'player':
if typ == 'account':
return obj.obj
if typ == 'string':
return _ObjectDB.objects.get(db_key__iexact=obj)
@ -131,6 +129,7 @@ def to_object(inp, objtype='player'):
# an unknown
return None
#
# Msg manager
#
@ -159,7 +158,7 @@ class MsgManager(TypedObjectManager):
Returns:
identified (tuple): This is a tuple with (`inp`, identifier)
where `identifier` is one of "player", "object", "channel",
where `identifier` is one of "account", "object", "channel",
"string", "dbref" or None.
"""
@ -184,10 +183,10 @@ class MsgManager(TypedObjectManager):
def get_messages_by_sender(self, sender, exclude_channel_messages=False):
"""
Get all messages sent by one entity - this could be either a
player or an object
account or an object
Args:
sender (Player or Object): The sender of the message.
sender (Account or Object): The sender of the message.
exclude_channel_messages (bool, optional): Only return messages
not aimed at a channel (that is, private tells for example)
@ -201,9 +200,9 @@ class MsgManager(TypedObjectManager):
obj, typ = identify_object(sender)
if exclude_channel_messages:
# explicitly exclude channel recipients
if typ == 'player':
return list(self.filter(db_sender_players=obj,
db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj))
if typ == 'account':
return list(self.filter(db_sender_accounts=obj,
db_receivers_channels__isnull=True).exclude(db_hide_from_accounts=obj))
elif typ == 'object':
return list(self.filter(db_sender_objects=obj,
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
@ -211,8 +210,8 @@ class MsgManager(TypedObjectManager):
raise CommError
else:
# get everything, channel or not
if typ == 'player':
return list(self.filter(db_sender_players=obj).exclude(db_hide_from_players=obj))
if typ == 'account':
return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj))
elif typ == 'object':
return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj))
else:
@ -223,7 +222,7 @@ class MsgManager(TypedObjectManager):
Get all messages sent to one given recipient.
Args:
recipient (Object, Player or Channel): The recipient of the messages to search for.
recipient (Object, Account or Channel): The recipient of the messages to search for.
Returns:
messages (list): Matching messages.
@ -233,8 +232,8 @@ class MsgManager(TypedObjectManager):
"""
obj, typ = identify_object(recipient)
if typ == 'player':
return list(self.filter(db_receivers_players=obj).exclude(db_hide_from_players=obj))
if typ == 'account':
return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj))
elif typ == 'object':
return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj))
elif typ == 'channel':
@ -261,9 +260,9 @@ class MsgManager(TypedObjectManager):
one of the arguments must be given to do a search.
Args:
sender (Object or Player, optional): Get messages sent by a particular player or object
receiver (Object, Player or Channel, optional): Get messages
received by a certain player,object or channel
sender (Object or Account, optional): Get messages sent by a particular account or object
receiver (Object, Account or Channel, optional): Get messages
received by a certain account,object or channel
freetext (str): Search for a text string in a message. NOTE:
This can potentially be slow, so make sure to supply one of
the other arguments to limit the search.
@ -288,16 +287,16 @@ class MsgManager(TypedObjectManager):
# filter by sender
sender, styp = identify_object(sender)
if styp == 'player':
sender_restrict = Q(db_sender_players=sender) & ~Q(db_hide_from_players=sender)
if styp == 'account':
sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender)
elif styp == 'object':
sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender)
else:
sender_restrict = Q()
# filter by receiver
receiver, rtyp = identify_object(receiver)
if rtyp == 'player':
receiver_restrict = Q(db_receivers_players=receiver) & ~Q(db_hide_from_players=receiver)
if rtyp == 'account':
receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q(db_hide_from_accounts=receiver)
elif rtyp == 'object':
receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver)
elif rtyp == 'channel':
@ -314,6 +313,7 @@ class MsgManager(TypedObjectManager):
# back-compatibility alias
message_search = search_message
#
# Channel manager
#
@ -332,7 +332,6 @@ class ChannelDBManager(TypedObjectManager):
subscribed to the Channel.
"""
@returns_typeclass_list
def get_all_channels(self):
"""
Get all channels.
@ -343,7 +342,6 @@ class ChannelDBManager(TypedObjectManager):
"""
return self.all()
@returns_typeclass
def get_channel(self, channelkey):
"""
Return the channel object if given its key.
@ -366,26 +364,24 @@ class ChannelDBManager(TypedObjectManager):
return channels[0]
return None
@returns_typeclass_list
def get_subscriptions(self, subscriber):
"""
Return all channels a given entity is subscribed to.
Args:
subscriber (Object or Player): The one subscribing.
subscriber (Object or Account): The one subscribing.
Returns:
subscriptions (list): Channel subscribed to.
"""
clsname = subscriber.__dbclass__.__name__
if clsname == "PlayerDB":
return subscriber.subscription_set.all()
if clsname == "AccountDB":
return subscriber.account_subscription_set.all()
if clsname == "ObjectDB":
return subscriber.object_subscription_set.all()
return []
@returns_typeclass_list
def search_channel(self, ostring, exact=True):
"""
Search the channel database for a particular channel.
@ -397,7 +393,8 @@ class ChannelDBManager(TypedObjectManager):
"""
channels = []
if not ostring: return channels
if not ostring:
return channels
try:
# try an id match first
dbref = int(ostring.strip('#'))
@ -414,16 +411,14 @@ class ChannelDBManager(TypedObjectManager):
if not channels:
# still no match. Search by alias.
channels = [channel for channel in self.all()
if ostring.lower() in [a.lower
for a in channel.aliases.all()]]
if ostring.lower() in [a.lower for a in channel.aliases.all()]]
return channels
# back-compatibility alias
channel_search = search_channel
class ChannelManager(ChannelDBManager, TypeclassManager):
"""
Wrapper to group the typeclass manager to a consistent name.
"""
pass

View file

@ -17,8 +17,8 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='msg',
name='db_hide_from_players',
field=models.ManyToManyField(related_name=b'hide_from_players_set', null=True, to=settings.AUTH_USER_MODEL),
name='db_hide_from_accounts',
field=models.ManyToManyField(related_name=b'hide_from_accounts_set', null=True, to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
migrations.AddField(
@ -35,8 +35,8 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='msg',
name='db_receivers_players',
field=models.ManyToManyField(help_text=b'player receivers', related_name=b'receiver_player_set', null=True, to=settings.AUTH_USER_MODEL),
name='db_receivers_accounts',
field=models.ManyToManyField(help_text=b'account receivers', related_name=b'receiver_account_set', null=True, to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
migrations.AddField(
@ -47,8 +47,8 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='msg',
name='db_sender_players',
field=models.ManyToManyField(related_name=b'sender_player_set', null=True, verbose_name=b'sender(player)', to=settings.AUTH_USER_MODEL, db_index=True),
name='db_sender_accounts',
field=models.ManyToManyField(related_name=b'sender_account_set', null=True, verbose_name=b'sender(account)', to=settings.AUTH_USER_MODEL, db_index=True),
preserve_default=True,
),
migrations.AddField(

View file

@ -25,8 +25,8 @@ class Migration(migrations.Migration):
),
migrations.AlterField(
model_name='msg',
name='db_hide_from_players',
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_players_set', to=settings.AUTH_USER_MODEL),
name='db_hide_from_accounts',
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_accounts_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='msg',
@ -40,8 +40,8 @@ class Migration(migrations.Migration):
),
migrations.AlterField(
model_name='msg',
name='db_receivers_players',
field=models.ManyToManyField(blank=True, help_text=b'player receivers', null=True, related_name='receiver_player_set', to=settings.AUTH_USER_MODEL),
name='db_receivers_accounts',
field=models.ManyToManyField(blank=True, help_text=b'account receivers', null=True, related_name='receiver_account_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='msg',
@ -55,8 +55,8 @@ class Migration(migrations.Migration):
),
migrations.AlterField(
model_name='msg',
name='db_sender_players',
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_player_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(player)'),
name='db_sender_accounts',
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(account)'),
),
migrations.AlterField(
model_name='msg',

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-02-17 20:39
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0007_auto_20150403_2339'),
('comms', '0010_auto_20161206_1912'),
]
operations = [
migrations.AddField(
model_name='msg',
name='db_receivers_scripts',
field=models.ManyToManyField(blank=True, help_text=b'script_receivers', null=True, related_name='receiver_script_set', to='scripts.ScriptDB'),
),
migrations.AddField(
model_name='msg',
name='db_sender_scripts',
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'),
),
]

View file

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-06 17:31
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comms', '0010_auto_20161206_1912'),
]
operations = [
migrations.AlterField(
model_name='channeldb',
name='db_attributes',
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
),
migrations.AlterField(
model_name='channeldb',
name='db_object_subscriptions',
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'subscriptions'),
),
migrations.AlterField(
model_name='channeldb',
name='db_subscriptions',
field=models.ManyToManyField(blank=True, db_index=True, related_name='subscription_set', to=settings.AUTH_USER_MODEL, verbose_name=b'subscriptions'),
),
migrations.AlterField(
model_name='channeldb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
),
migrations.AlterField(
model_name='msg',
name='db_hide_from_channels',
field=models.ManyToManyField(blank=True, related_name='hide_from_channels_set', to='comms.ChannelDB'),
),
migrations.AlterField(
model_name='msg',
name='db_hide_from_objects',
field=models.ManyToManyField(blank=True, related_name='hide_from_objects_set', to='objects.ObjectDB'),
),
migrations.AlterField(
model_name='msg',
name='db_hide_from_accounts',
field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_channels',
field=models.ManyToManyField(blank=True, help_text=b'channel recievers', related_name='channel_set', to='comms.ChannelDB'),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_objects',
field=models.ManyToManyField(blank=True, help_text=b'object receivers', related_name='receiver_object_set', to='objects.ObjectDB'),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_accounts',
field=models.ManyToManyField(blank=True, help_text=b'account receivers', related_name='receiver_account_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='msg',
name='db_sender_objects',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name=b'sender(object)'),
),
migrations.AlterField(
model_name='msg',
name='db_sender_accounts',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name=b'sender(account)'),
),
migrations.AlterField(
model_name='msg',
name='db_tags',
field=models.ManyToManyField(blank=True, help_text=b'tags on this message. Tags are simple string markers to identify, group and alias messages.', to='typeclasses.Tag'),
),
]

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-17 20:17
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('comms', '0011_auto_20170606_1731'),
('comms', '0011_auto_20170217_2039'),
]
operations = [
]

View file

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:26
from __future__ import unicode_literals
from django.db import migrations, models, connection
from django.db import OperationalError
def _table_exists(db_cursor, tablename):
"Returns bool if table exists or not"
sql_check_exists = "SELECT * from %s;" % tablename
try:
db_cursor.execute(sql_check_exists)
return True
except OperationalError:
return False
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_copy_player_to_account'),
('comms', '0012_merge_20170617_2017'),
]
db_cursor = connection.cursor()
operations = [
migrations.AddField(
model_name='channeldb',
name='db_account_subscriptions',
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name=b'account subscriptions'),
),
migrations.AlterField(
model_name='channeldb',
name='db_object_subscriptions',
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'object subscriptions'),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_scripts',
field=models.ManyToManyField(blank=True, help_text=b'script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'),
),
migrations.AlterField(
model_name='msg',
name='db_sender_scripts',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'),
),
migrations.AlterField(
model_name='channeldb',
name='db_object_subscriptions',
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name=b'object subscriptions'),
),
migrations.AlterField(
model_name='msg',
name='db_receivers_scripts',
field=models.ManyToManyField(blank=True, help_text=b'script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'),
),
migrations.AlterField(
model_name='msg',
name='db_sender_scripts',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name=b'sender(script)'),
),
]
if _table_exists(db_cursor, 'comms_msg_db_hide_from_players'):
# OBS - this is run BEFORE migrations are run!
# not a migration of an existing database
operations += [
migrations.AddField(
model_name='channeldb',
name='db_account_subscriptions',
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name=b'account subscriptions'),
),
migrations.AddField(
model_name='msg',
name='db_hide_from_accounts',
field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to='accounts.AccountDB'),
),
migrations.AddField(
model_name='msg',
name='db_receivers_accounts',
field=models.ManyToManyField(blank=True, help_text=b'account receivers', related_name='receiver_account_set', to='accounts.AccountDB'),
),
migrations.AddField(
model_name='msg',
name='db_sender_accounts',
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to='accounts.AccountDB', verbose_name=b'sender(account)'),
),
]

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:36
from __future__ import unicode_literals
from django.db import migrations
def forwards(apps, schema_editor):
try:
apps.get_model('accounts', 'AccountDB')
except LookupError:
return
AccountDB = apps.get_model('accounts', 'AccountDB')
Msg = apps.get_model('comms', 'Msg')
for msg in Msg.objects.all():
for account in msg.db_sender_accounts.all():
account = AccountDB.objects.get(id=account.id)
msg.db_sender_accounts.add(account)
for account in msg.db_receivers_accounts.all():
account = AccountDB.objects.get(id=account.id)
msg.db_receivers_accounts.add(account)
for account in msg.db_hide_from_accounts.all():
account = AccountDB.objects.get(id=account.id)
msg.db_hide_from_accounts.add(account)
ChannelDB = apps.get_model('comms', 'ChannelDB')
for channel in ChannelDB.objects.all():
for account in channel.db_subscriptions.all():
account = AccountDB.objects.get(id=account.id)
channel.db_account_subscriptions.add(account)
class Migration(migrations.Migration):
dependencies = [
('comms', '0013_auto_20170705_1726'),
]
operations = [
migrations.RunPython(forwards)
]

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-06 20:41
from __future__ import unicode_literals
from django.db import migrations, OperationalError, connection
def _table_exists(db_cursor, tablename):
"Returns bool if table exists or not"
sql_check_exists = "SELECT * from %s;" % tablename
try:
db_cursor.execute(sql_check_exists)
return True
except OperationalError:
return False
class Migration(migrations.Migration):
dependencies = [
('comms', '0014_auto_20170705_1736'),
]
db_cursor = connection.cursor()
if not _table_exists(db_cursor, "channels.channeldb_db_receivers_players"):
# OBS - this is run BEFORE migrations are run!
operations = []
else:
operations = [
migrations.RemoveField(
model_name='channeldb',
name='db_subscriptions', # this is now db_account_subscriptions
),
migrations.RemoveField(
model_name='msg',
name='db_receivers_players',
),
migrations.RemoveField(
model_name='msg',
name='db_sender_players',
),
]

View file

@ -13,7 +13,7 @@ For non-persistent (and slightly faster) use one can also use the
TempMsg, which mimics the Msg API but without actually saving to the
database.
Channels are central objects that act as targets for Msgs. Players can
Channels are central objects that act as targets for Msgs. Accounts can
connect to channels by use of a ChannelConnect object (this object is
necessary to easily be able to delete connections on the fly).
"""
@ -48,16 +48,18 @@ _CHANNELHANDLER = None
class Msg(SharedMemoryModel):
"""
A single message. This model describes all ooc messages
sent in-game, both to channels and between players.
sent in-game, both to channels and between accounts.
The Msg class defines the following database fields (all
accessed via specific handler methods):
- db_sender_players: Player senders
- db_sender_accounts: Account senders
- db_sender_objects: Object senders
- db_sender_scripts: Script senders
- db_sender_external: External senders (defined as string names)
- db_receivers_players: Receiving players
- db_receivers_accounts: Receiving accounts
- db_receivers_objects: Receiving objects
- db_receivers_scripts: Receiveing scripts
- db_receivers_channels: Receiving channels
- db_header: Header text
- db_message: The actual message text
@ -75,25 +77,31 @@ class Msg(SharedMemoryModel):
# These databse fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
# Sender is either a player, an object or an external sender, like
# Sender is either an account, an object or an external sender, like
# an IRC channel; normally there is only one, but if co-modification of
# a message is allowed, there may be more than one "author"
db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set',
null=True, blank=True, verbose_name='sender(player)', db_index=True)
db_sender_accounts = models.ManyToManyField("accounts.AccountDB", related_name='sender_account_set',
blank=True, verbose_name='sender(account)', db_index=True)
db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set',
null=True, blank=True, verbose_name='sender(object)', db_index=True)
blank=True, verbose_name='sender(object)', db_index=True)
db_sender_scripts = models.ManyToManyField("scripts.ScriptDB", related_name='sender_script_set',
blank=True, verbose_name='sender(script)', db_index=True)
db_sender_external = models.CharField('external sender', max_length=255, null=True, blank=True, db_index=True,
help_text="identifier for external sender, for example a sender over an "
"IRC connection (i.e. someone who doesn't have an exixtence in-game).")
# The destination objects of this message. Stored as a
# comma-separated string of object dbrefs. Can be defined along
# with channels below.
db_receivers_players = models.ManyToManyField('players.PlayerDB', related_name='receiver_player_set',
null=True, blank=True, help_text="player receivers")
db_receivers_accounts = models.ManyToManyField('accounts.AccountDB', related_name='receiver_account_set',
blank=True, help_text="account receivers")
db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set',
null=True, blank=True, help_text="object receivers")
blank=True, help_text="object receivers")
db_receivers_scripts = models.ManyToManyField('scripts.ScriptDB', related_name='receiver_script_set',
blank=True, help_text="script_receivers")
db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set',
null=True, blank=True, help_text="channel recievers")
blank=True, help_text="channel recievers")
# header could be used for meta-info about the message if your system needs
# it, or as a separate store for the mail subject line maybe.
@ -106,12 +114,14 @@ class Msg(SharedMemoryModel):
db_lock_storage = models.TextField('locks', blank=True,
help_text='access locks on this message.')
# these can be used to filter/hide a given message from supplied objects/players/channels
db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', null=True, blank=True)
db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', null=True, blank=True)
db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', null=True, blank=True)
# these can be used to filter/hide a given message from supplied objects/accounts/channels
db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True)
db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True)
db_tags = models.ManyToManyField(Tag, null=True, blank=True,
db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', blank=True)
db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', blank=True)
db_tags = models.ManyToManyField(Tag, blank=True,
help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.')
# Database manager
@ -146,8 +156,9 @@ class Msg(SharedMemoryModel):
#@property
def __senders_get(self):
"Getter. Allows for value = self.sender"
return list(self.db_sender_players.all()) + \
return list(self.db_sender_accounts.all()) + \
list(self.db_sender_objects.all()) + \
list(self.db_sender_scripts.all()) + \
self.extra_senders
#@sender.setter
@ -166,14 +177,17 @@ class Msg(SharedMemoryModel):
clsname = sender.__dbclass__.__name__
if clsname == "ObjectDB":
self.db_sender_objects.add(sender)
elif clsname == "PlayerDB":
self.db_sender_players.add(sender)
elif clsname == "AccountDB":
self.db_sender_accounts.add(sender)
elif clsname == "ScriptDB":
self.db_sender_scripts.add(sender)
#@sender.deleter
def __senders_del(self):
"Deleter. Clears all senders"
self.db_sender_players.clear()
self.db_sender_accounts.clear()
self.db_sender_objects.clear()
self.db_sender_scripts.clear()
self.db_sender_external = ""
self.extra_senders = []
self.save()
@ -184,7 +198,7 @@ class Msg(SharedMemoryModel):
Remove a single sender or a list of senders.
Args:
senders (Player, Object, str or list): Senders to remove.
senders (Account, Object, str or list): Senders to remove.
"""
for sender in make_iter(senders):
@ -198,17 +212,22 @@ class Msg(SharedMemoryModel):
clsname = sender.__dbclass__.__name__
if clsname == "ObjectDB":
self.db_sender_objects.remove(sender)
elif clsname == "PlayerDB":
self.db_sender_players.remove(sender)
elif clsname == "AccountDB":
self.db_sender_accounts.remove(sender)
elif clsname == "ScriptDB":
self.db_sender_accounts.remove(sender)
# receivers property
#@property
def __receivers_get(self):
"""
Getter. Allows for value = self.receivers.
Returns three lists of receivers: players, objects and channels.
Returns four lists of receivers: accounts, objects, scripts and channels.
"""
return list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all())
return list(self.db_receivers_accounts.all()) + \
list(self.db_receivers_objects.all()) + \
list(self.db_receivers_scripts.all()) + \
list(self.db_receivers_channels.all())
#@receivers.setter
def __receivers_set(self, receivers):
@ -224,14 +243,21 @@ class Msg(SharedMemoryModel):
clsname = receiver.__dbclass__.__name__
if clsname == "ObjectDB":
self.db_receivers_objects.add(receiver)
elif clsname == "PlayerDB":
self.db_receivers_players.add(receiver)
elif clsname == "AccountDB":
self.db_receivers_accounts.add(receiver)
elif clsname == "ScriptDB":
self.db_receivers_scripts.add(receiver)
elif clsname == "ChannelDB":
self.db_receivers_channels.add(receiver)
#@receivers.deleter
def __receivers_del(self):
"Deleter. Clears all receivers"
self.db_receivers_players.clear()
self.db_receivers_accounts.clear()
self.db_receivers_objects.clear()
self.db_receivers_scripts.clear()
self.db_receivers_channels.clear()
self.save()
receivers = property(__receivers_get, __receivers_set, __receivers_del)
@ -240,7 +266,7 @@ class Msg(SharedMemoryModel):
Remove a single receiver or a list of receivers.
Args:
receivers (Player, Object, Channel or list): Receiver to remove.
receivers (Account, Object, Script, Channel or list): Receiver to remove.
"""
for receiver in make_iter(receivers):
@ -251,8 +277,12 @@ class Msg(SharedMemoryModel):
clsname = receiver.__dbclass__.__name__
if clsname == "ObjectDB":
self.db_receivers_objects.remove(receiver)
elif clsname == "PlayerDB":
self.db_receivers_players.remove(receiver)
elif clsname == "AccountDB":
self.db_receivers_accounts.remove(receiver)
elif clsname == "ScriptDB":
self.db_receivers_scripts.remove(receiver)
elif clsname == "ChannelDB":
self.db_receivers_channels.remove(receiver)
# channels property
#@property
@ -279,9 +309,9 @@ class Msg(SharedMemoryModel):
def __hide_from_get(self):
"""
Getter. Allows for value = self.hide_from.
Returns 3 lists of players, objects and channels
Returns 3 lists of accounts, objects and channels
"""
return self.db_hide_from_players.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all()
return self.db_hide_from_accounts.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all()
#@hide_from_sender.setter
def __hide_from_set(self, hiders):
@ -292,8 +322,8 @@ class Msg(SharedMemoryModel):
if not hasattr(hider, "__dbclass__"):
raise ValueError("This is a not a typeclassed object!")
clsname = hider.__dbclass__.__name__
if clsname == "PlayerDB":
self.db_hide_from_players.add(hider.__dbclass__)
if clsname == "AccountDB":
self.db_hide_from_accounts.add(hider.__dbclass__)
elif clsname == "ObjectDB":
self.db_hide_from_objects.add(hider.__dbclass__)
elif clsname == "ChannelDB":
@ -302,7 +332,7 @@ class Msg(SharedMemoryModel):
#@hide_from_sender.deleter
def __hide_from_del(self):
"Deleter. Allows for del self.hide_from_senders"
self.db_hide_from_players.clear()
self.db_hide_from_accounts.clear()
self.db_hide_from_objects.clear()
self.db_hide_from_channels.clear()
self.save()
@ -323,7 +353,7 @@ class Msg(SharedMemoryModel):
Checks lock access.
Args:
accessing_obj (Object or Player): The object trying to gain access.
accessing_obj (Object or Account): The object trying to gain access.
access_type (str, optional): The type of lock access to check.
default (bool): Fallback to use if `access_type` lock is not defined.
@ -353,13 +383,13 @@ class TempMsg(object):
Args:
senders (any or list, optional): Senders of the message.
receivers (Player, Object, Channel or list, optional): Receivers of this message.
receivers (Account, Object, Channel or list, optional): Receivers of this message.
channels (Channel or list, optional): Channels to send to.
message (str, optional): Message to send.
header (str, optional): Header of message.
type (str, optional): Message class, if any.
lockstring (str, optional): Lock for the message.
hide_from (Player, Object, Channel or list, optional): Entities to hide this message from.
hide_from (Account, Object, Channel or list, optional): Entities to hide this message from.
"""
self.senders = senders and make_iter(senders) or []
@ -389,7 +419,7 @@ class TempMsg(object):
Remove a sender or a list of senders.
Args:
sender (Object, Player, str or list): Senders to remove.
sender (Object, Account, str or list): Senders to remove.
"""
for o in make_iter(sender):
@ -403,7 +433,7 @@ class TempMsg(object):
Remove a receiver or a list of receivers
Args:
receiver (Object, Player, Channel, str or list): Receivers to remove.
receiver (Object, Account, Channel, str or list): Receivers to remove.
"""
for o in make_iter(receiver):
@ -417,7 +447,7 @@ class TempMsg(object):
Checks lock access.
Args:
accessing_obj (Object or Player): The object trying to gain access.
accessing_obj (Object or Account): The object trying to gain access.
access_type (str, optional): The type of lock access to check.
default (bool): Fallback to use if `access_type` lock is not defined.
@ -439,7 +469,7 @@ class SubscriptionHandler(object):
"""
This handler manages subscriptions to the
channel and hides away which type of entity is
subscribing (Player or Object)
subscribing (Account or Object)
"""
def __init__(self, obj):
"""
@ -453,7 +483,7 @@ class SubscriptionHandler(object):
self._cache = None
def _recache(self):
self._cache = {player : True for player in self.obj.db_subscriptions.all()}
self._cache = {account : True for account in self.obj.db_account_subscriptions.all()}
self._cache.update({obj : True for obj in self.obj.db_object_subscriptions.all()})
def has(self, entity):
@ -461,12 +491,12 @@ class SubscriptionHandler(object):
Check if the given entity subscribe to this channel
Args:
entity (str, Player or Object): The entity to return. If
entity (str, Account or Object): The entity to return. If
a string, it assumed to be the key or the #dbref
of the entity.
Returns:
subscriber (Player, Object or None): The given
subscriber (Account, Object or None): The given
subscriber.
"""
@ -479,7 +509,7 @@ class SubscriptionHandler(object):
Subscribe an entity to this channel.
Args:
entity (Player, Object or list): The entity or
entity (Account, Object or list): The entity or
list of entities to subscribe to this channel.
Note:
@ -497,8 +527,8 @@ class SubscriptionHandler(object):
# chooses the right type
if clsname == "ObjectDB":
self.obj.db_object_subscriptions.add(subscriber)
elif clsname == "PlayerDB":
self.obj.db_subscriptions.add(subscriber)
elif clsname == "AccountDB":
self.obj.db_account_subscriptions.add(subscriber)
_CHANNELHANDLER.cached_cmdsets.pop(subscriber, None)
self._recache()
@ -507,7 +537,7 @@ class SubscriptionHandler(object):
Remove a subscriber from the channel.
Args:
entity (Player, Object or list): The entity or
entity (Account, Object or list): The entity or
entities to un-subscribe from the channel.
"""
@ -518,8 +548,8 @@ class SubscriptionHandler(object):
if subscriber:
clsname = subscriber.__dbclass__.__name__
# chooses the right type
if clsname == "PlayerDB":
self.obj.db_subscriptions.remove(entity)
if clsname == "AccountDB":
self.obj.db_account_subscriptions.remove(entity)
elif clsname == "ObjectDB":
self.obj.db_object_subscriptions.remove(entity)
_CHANNELHANDLER.cached_cmdsets.pop(subscriber, None)
@ -531,7 +561,7 @@ class SubscriptionHandler(object):
Returns:
subscribers (list): The subscribers. This
may be a mix of Players and Objects!
may be a mix of Accounts and Objects!
"""
if self._cache is None:
@ -540,17 +570,17 @@ class SubscriptionHandler(object):
def online(self):
"""
Get all online players from our cache
Get all online accounts from our cache
Returns:
subscribers (list): Subscribers who are online or
are puppeted by an online player.
are puppeted by an online account.
"""
subs = []
for obj in self.all():
if hasattr(obj, 'player'):
if not obj.player:
if hasattr(obj, 'account'):
if not obj.account:
continue
obj = obj.player
obj = obj.account
if not obj.is_connected:
continue
subs.append(obj)
@ -561,7 +591,7 @@ class SubscriptionHandler(object):
Remove all subscribers from channel.
"""
self.obj.db_subscriptions.clear()
self.obj.db_account_subscriptions.clear()
self.obj.db_object_subscriptions.clear()
self._cache = None
@ -574,16 +604,15 @@ class ChannelDB(TypedObject):
The Channel class defines the following database fields
beyond the ones inherited from TypedObject:
- db_subscriptions: The Player subscriptions (this is the most
usual case, named this way for legacy.
- db_account_subscriptions: The Account subscriptions.
- db_object_subscriptions: The Object subscriptions.
"""
db_subscriptions = models.ManyToManyField("players.PlayerDB",
related_name="subscription_set", null=True, blank=True, verbose_name='subscriptions', db_index=True)
db_account_subscriptions = models.ManyToManyField("accounts.AccountDB",
related_name="account_subscription_set", blank=True, verbose_name='account subscriptions', db_index=True)
db_object_subscriptions = models.ManyToManyField("objects.ObjectDB",
related_name="object_subscription_set", null=True, blank=True, verbose_name='subscriptions', db_index=True)
related_name="object_subscription_set", blank=True, verbose_name='object subscriptions', db_index=True)
# Database manager
objects = managers.ChannelDBManager()

View file

@ -21,6 +21,7 @@ things you want from here into your game folder and change them there.
Meant as a starting point for a more fleshed-out system.
* Clothing (BattleJenkins 2017) - A layered clothing system with
slots for different types of garments auto-showing in description.
* Color-markups (Griatch, 2017) - Alternative in-game color markups.
* Custom gametime (Griatch, vlgeoff 2017) - Implements Evennia's
gametime module but for custom game world-specific calendars.
* Dice (Griatch 2012) - A fully featured dice rolling system.

View file

@ -411,7 +411,7 @@ class CmdTradeBase(Command):
if ':' in self.args:
self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)]
self.str_caller = 'You say, "' + self.emote + '"\n [%s]'
if self.caller.has_player:
if self.caller.has_account:
self.str_other = '|c%s|n says, "' % self.caller.key + self.emote + '"\n [%s]'
else:
self.str_other = '%s says, "' % self.caller.key + self.emote + '"\n [%s]'
@ -766,7 +766,7 @@ class CmdTrade(Command):
if ':' in self.args:
self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)]
selfemote = 'You say, "%s"\n ' % emote
if self.caller.has_player:
if self.caller.has_account:
theiremote = '|c%s|n says, "%s"\n ' % (self.caller.key, emote)
else:
theiremote = '%s says, "%s"\n ' % (self.caller.key, emote)

View file

@ -7,8 +7,8 @@ necessary anymore - the ooclook and @charcreate commands in that mode
replaces this module with better functionality. This remains here for
inspiration.
This is a simple character creation commandset for the Player level.
It shows some more info and gives the Player the option to create a
This is a simple character creation commandset for the Account level.
It shows some more info and gives the Account the option to create a
character without any more customizations than their name (further
options are unique for each game anyway).
@ -19,7 +19,7 @@ cmdset.
Installation:
Import this module to `mygame/commands/default_cmdsets.py` and
add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class
add `chargen.OOCCMdSetCharGen` to the `AccountCmdSet` class
(it says where to add it). Reload.
"""
@ -39,7 +39,7 @@ class CmdOOCLook(default_cmds.CmdLook):
look
look <character>
This is an OOC version of the look command. Since a Player doesn't
This is an OOC version of the look command. Since an Account doesn't
have an in-game existence, there is no concept of location or
"self".
@ -56,24 +56,24 @@ class CmdOOCLook(default_cmds.CmdLook):
"""
Implements the ooc look command
We use an attribute _character_dbrefs on the player in order
We use an attribute _character_dbrefs on the account in order
to figure out which characters are "theirs". A drawback of this
is that only the CmdCharacterCreate command adds this attribute,
and thus e.g. player #1 will not be listed (although it will work).
and thus e.g. account #1 will not be listed (although it will work).
Existence in this list does not depend on puppeting rights though,
that is checked by the @ic command directly.
"""
# making sure caller is really a player
# making sure caller is really an account
self.character = None
if utils.inherits_from(self.caller, "evennia.objects.objects.Object"):
# An object of some type is calling. Convert to player.
# An object of some type is calling. Convert to account.
self.character = self.caller
if hasattr(self.caller, "player"):
self.caller = self.caller.player
if hasattr(self.caller, "account"):
self.caller = self.caller.account
if not self.character:
# ooc mode, we are players
# ooc mode, we are accounts
avail_chars = self.caller.db._character_dbrefs
if self.args:
@ -139,13 +139,13 @@ class CmdOOCCharacterCreate(Command):
attribute on ourselves to remember it.
"""
# making sure caller is really a player
# making sure caller is really an account
self.character = None
if utils.inherits_from(self.caller, "evennia.objects.objects.Object"):
# An object of some type is calling. Convert to player.
# An object of some type is calling. Convert to account.
self.character = self.caller
if hasattr(self.caller, "player"):
self.caller = self.caller.player
if hasattr(self.caller, "account"):
self.caller = self.caller.account
if not self.args:
self.caller.msg("Usage: create <character name>")
@ -161,8 +161,8 @@ class CmdOOCCharacterCreate(Command):
if not new_character:
self.caller.msg("|rThe Character couldn't be created. This is a bug. Please contact an admin.")
return
# make sure to lock the character to only be puppeted by this player
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
# make sure to lock the character to only be puppeted by this account
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
(new_character.id, self.caller.id))
# save dbref
@ -175,7 +175,7 @@ class CmdOOCCharacterCreate(Command):
self.caller.msg("|gThe character |c%s|g was successfully created!" % charname)
class OOCCmdSetCharGen(default_cmds.PlayerCmdSet):
class OOCCmdSetCharGen(default_cmds.AccountCmdSet):
"""
Extends the default OOC cmdset.
"""

View file

@ -0,0 +1,238 @@
"""
Color markups
Contribution, Griatch 2017
Additional color markup styles for Evennia (extending or replacing the default |r, |234 etc).
Installation:
Import the desired style variables from this module into mygame/server/conf/settings.py and add them
to these settings variables. Each are specified as a list, and multiple such lists can be added to
each variable to support multiple formats. Note that list order affects which regexes are applied
first. You must restart both Portal and Server for color tags to update.
Assign to the following settings variables:
COLOR_ANSI_EXTRA_MAP - a mapping between regexes and ANSI colors
COLOR_XTERM256_EXTRA_FG - regex for defining XTERM256 foreground colors
COLOR_XTERM256_EXTRA_BG - regex for defining XTERM256 background colors
COLOR_XTERM256_EXTRA_GFG - regex for defining XTERM256 grayscale foreground colors
COLOR_XTERM256_EXTRA_GBG - regex for defining XTERM256 grayscale background colors
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = ANSI does not support bright backgrounds; we fake
this by mapping ANSI markup to matching bright XTERM256 backgrounds
COLOR_NO_DEFAULT - Set True/False. If False (default), extend the default markup, otherwise
replace it completely.
To add the {- "curly-bracket" style, add the following to your settings file, then reboot both
Server and Portal:
from evennia.contrib import color_markups
COLOR_ANSI_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP
COLOR_XTERM256_EXTRA_FG = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG
COLOR_XTERM256_EXTRA_BG = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG
COLOR_XTERM256_EXTRA_GFG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG
COLOR_XTERM256_EXTRA_GBG = color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG
COLOR_ANSI_BRIGHT_BG_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BG_EXTRA_MAP
To add the %c- "mux/mush" style, add the following to your settings file, then reboot both Server
and Portal:
from evennia.contrib import color_markups
COLOR_ANSI_EXTRA_MAP = color_markups.MUX_COLOR_ANSI_EXTRA_MAP
COLOR_XTERM256_EXTRA_FG = color_markups.MUX_COLOR_XTERM256_EXTRA_FG
COLOR_XTERM256_EXTRA_BG = color_markups.MUX_COLOR_XTERM256_EXTRA_BG
COLOR_XTERM256_EXTRA_GFG = color_markups.MUX_COLOR_XTERM256_EXTRA_GFG
COLOR_XTERM256_EXTRA_GBG = color_markups.MUX_COLOR_XTERM256_EXTRA_GBG
COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP = color_markups.CURLY_COLOR_ANSI_BRIGHT_BGS_EXTRA_MAP
"""
# ANSI constants (copied from evennia.utils.ansi to avoid import)
_ANSI_BEEP = "\07"
_ANSI_ESCAPE = "\033"
_ANSI_NORMAL = "\033[0m"
_ANSI_UNDERLINE = "\033[4m"
_ANSI_HILITE = "\033[1m"
_ANSI_UNHILITE = "\033[22m"
_ANSI_BLINK = "\033[5m"
_ANSI_INVERSE = "\033[7m"
_ANSI_INV_HILITE = "\033[1;7m"
_ANSI_INV_BLINK = "\033[7;5m"
_ANSI_BLINK_HILITE = "\033[1;5m"
_ANSI_INV_BLINK_HILITE = "\033[1;5;7m"
# Foreground colors
_ANSI_BLACK = "\033[30m"
_ANSI_RED = "\033[31m"
_ANSI_GREEN = "\033[32m"
_ANSI_YELLOW = "\033[33m"
_ANSI_BLUE = "\033[34m"
_ANSI_MAGENTA = "\033[35m"
_ANSI_CYAN = "\033[36m"
_ANSI_WHITE = "\033[37m"
# Background colors
_ANSI_BACK_BLACK = "\033[40m"
_ANSI_BACK_RED = "\033[41m"
_ANSI_BACK_GREEN = "\033[42m"
_ANSI_BACK_YELLOW = "\033[43m"
_ANSI_BACK_BLUE = "\033[44m"
_ANSI_BACK_MAGENTA = "\033[45m"
_ANSI_BACK_CYAN = "\033[46m"
_ANSI_BACK_WHITE = "\033[47m"
# Formatting Characters
_ANSI_RETURN = "\r\n"
_ANSI_TAB = "\t"
_ANSI_SPACE = " "
#############################################################
#
# {- style MUD markup (old Evennia default). This is
# basically identical to the default |-style except using
# a curly bracket instead. This was removed because {}
# are used in Python string formatting.
#
# {r, {R - bright/dark red foreground
# {[r, {[R - bright/dark red background
# {500, {[500 - XTERM256 red foreground/background
# {=w, {[=w - XTERM256 greyscale foreground/background
#
#############################################################
CURLY_COLOR_ANSI_EXTRA_MAP = [
(r'{n', _ANSI_NORMAL), # reset
(r'{/', _ANSI_RETURN), # line break
(r'{-', _ANSI_TAB), # tab
(r'{_', _ANSI_SPACE), # space
(r'{*', _ANSI_INVERSE), # invert
(r'{^', _ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'{u', _ANSI_UNDERLINE), # underline
(r'{r', _ANSI_HILITE + _ANSI_RED),
(r'{g', _ANSI_HILITE + _ANSI_GREEN),
(r'{y', _ANSI_HILITE + _ANSI_YELLOW),
(r'{b', _ANSI_HILITE + _ANSI_BLUE),
(r'{m', _ANSI_HILITE + _ANSI_MAGENTA),
(r'{c', _ANSI_HILITE + _ANSI_CYAN),
(r'{w', _ANSI_HILITE + _ANSI_WHITE), # pure white
(r'{x', _ANSI_HILITE + _ANSI_BLACK), # dark grey
(r'{R', _ANSI_HILITE + _ANSI_RED),
(r'{G', _ANSI_HILITE + _ANSI_GREEN),
(r'{Y', _ANSI_HILITE + _ANSI_YELLOW),
(r'{B', _ANSI_HILITE + _ANSI_BLUE),
(r'{M', _ANSI_HILITE + _ANSI_MAGENTA),
(r'{C', _ANSI_HILITE + _ANSI_CYAN),
(r'{W', _ANSI_HILITE + _ANSI_WHITE), # light grey
(r'{X', _ANSI_HILITE + _ANSI_BLACK), # pure black
# hilight-able colors
(r'{h', _ANSI_HILITE),
(r'{H', _ANSI_UNHILITE),
(r'{!R', _ANSI_RED),
(r'{!G', _ANSI_GREEN),
(r'{!Y', _ANSI_YELLOW),
(r'{!B', _ANSI_BLUE),
(r'{!M', _ANSI_MAGENTA),
(r'{!C', _ANSI_CYAN),
(r'{!W', _ANSI_WHITE), # light grey
(r'{!X', _ANSI_BLACK), # pure black
# normal ANSI backgrounds
(r'{[R', _ANSI_BACK_RED),
(r'{[G', _ANSI_BACK_GREEN),
(r'{[Y', _ANSI_BACK_YELLOW),
(r'{[B', _ANSI_BACK_BLUE),
(r'{[M', _ANSI_BACK_MAGENTA),
(r'{[C', _ANSI_BACK_CYAN),
(r'{[W', _ANSI_BACK_WHITE), # light grey background
(r'{[X', _ANSI_BACK_BLACK), # pure black background
]
CURLY_COLOR_XTERM256_EXTRA_FG = [r'\{([0-5])([0-5])([0-5])'] # |123 - foreground colour
CURLY_COLOR_XTERM256_EXTRA_BG = [r'\{\[([0-5])([0-5])([0-5])'] # |[123 - background colour
CURLY_COLOR_XTERM256_EXTRA_GFG = [r'\{=([a-z])'] # |=a - greyscale foreground
CURLY_COLOR_XTERM256_EXTRA_GBG = [r'\{\[=([a-z])'] # |[=a - greyscale background
CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
(r'{[r', r'{[500'),
(r'{[g', r'{[050'),
(r'{[y', r'{[550'),
(r'{[b', r'{[005'),
(r'{[m', r'{[505'),
(r'{[c', r'{[055'),
(r'{[w', r'{[555'), # white background
(r'{[x', r'{[222'), # dark grey background
]
#############################################################
#
# %c - MUX/MUSH style markup. This was Evennia's first
# color markup style. It was phased out due to % being used
# in Python formatting operations.
#
# %ch%cr, %cr - bright/dark red foreground
# %ch%cR, %cR- bright/dark red background
# %c500, %c[500 - XTERM256 red foreground/background
# %c=w, %c[=w - XTERM256 greyscale foreground/background
#
#############################################################
MUX_COLOR_ANSI_EXTRA_MAP = [
(r'%cn', _ANSI_NORMAL), # reset
(r'%ch', _ANSI_HILITE), # highlight
(r'%r', _ANSI_RETURN), # line break
(r'%R', _ANSI_RETURN), #
(r'%t', _ANSI_TAB), # tab
(r'%T', _ANSI_TAB), #
(r'%b', _ANSI_SPACE), # space
(r'%B', _ANSI_SPACE),
(r'%cf', _ANSI_BLINK), # annoying and not supported by all clients
(r'%ci', _ANSI_INVERSE), # invert
(r'%cr', _ANSI_RED),
(r'%cg', _ANSI_GREEN),
(r'%cy', _ANSI_YELLOW),
(r'%cb', _ANSI_BLUE),
(r'%cm', _ANSI_MAGENTA),
(r'%cc', _ANSI_CYAN),
(r'%cw', _ANSI_WHITE),
(r'%cx', _ANSI_BLACK),
(r'%cR', _ANSI_BACK_RED),
(r'%cG', _ANSI_BACK_GREEN),
(r'%cY', _ANSI_BACK_YELLOW),
(r'%cB', _ANSI_BACK_BLUE),
(r'%cM', _ANSI_BACK_MAGENTA),
(r'%cC', _ANSI_BACK_CYAN),
(r'%cW', _ANSI_BACK_WHITE),
(r'%cX', _ANSI_BACK_BLACK)
]
MUX_COLOR_XTERM256_EXTRA_FG = [r'%c([0-5])([0-5])([0-5])'] # %c123 - foreground colour
MUX_COLOR_XTERM256_EXTRA_BG = [r'%c\[([0-5])([0-5])([0-5])'] # %c[123 - background colour
MUX_COLOR_XTERM256_EXTRA_GFG = [r'%c=([a-z])'] # %c=a - greyscale foreground
MUX_COLOR_XTERM256_EXTRA_GBG = [r'%c\[=([a-z])'] # %c[=a - greyscale background
MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
(r'%ch%cR', r'%c[500'),
(r'%ch%cG', r'%c[050'),
(r'%ch%cY', r'%c[550'),
(r'%ch%cB', r'%c[005'),
(r'%ch%cM', r'%c[505'),
(r'%ch%cC', r'%c[055'),
(r'%ch%cW', r'%c[555'), # white background
(r'%ch%cX', r'%c[222'), # dark grey background
]

View file

@ -13,7 +13,7 @@ from twisted.web.http_headers import Headers
from twisted.web.iweb import IBodyProducer
from zope.interface import implements
from evennia.players.models import PlayerDB
from evennia.accounts.models import AccountDB
from evennia.server.sessionhandler import SESSIONS
from evennia.utils import get_evennia_version, logger
@ -97,8 +97,8 @@ class EvenniaGameIndexClient(object):
'web_client_url': egi_config.get('web_client_url') or '',
# Game stats
'connected_player_count': SESSIONS.player_count(),
'total_player_count': PlayerDB.objects.num_total_players() or 0,
'connected_account_count': SESSIONS.account_count(),
'total_account_count': AccountDB.objects.num_total_accounts() or 0,
# System info
'evennia_version': get_evennia_version(),

View file

@ -31,13 +31,12 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
"""
import re
from django.conf import settings
from evennia.players.models import PlayerDB
from evennia.accounts.models import AccountDB
from evennia.objects.models import ObjectDB
from evennia.server.models import ServerConfig
from evennia.comms.models import ChannelDB
from evennia.commands.cmdset import CmdSet
from evennia.utils import create, logger, utils, ansi
from evennia.utils import logger, utils, ansi
from evennia.commands.default.muxcommand import MuxCommand
from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate
@ -78,7 +77,7 @@ class CmdUnconnectedConnect(MuxCommand):
have a unique position in that their `func()` receives
a session object instead of a `source_object` like all
other types of logged-in commands (this is because
there is no object yet before the player has logged in)
there is no object yet before the account has logged in)
"""
session = self.caller
@ -91,23 +90,22 @@ class CmdUnconnectedConnect(MuxCommand):
password = arglist[1]
# Match an email address to an account.
player = PlayerDB.objects.get_player_from_email(email)
# No playername match
if not player:
account = AccountDB.objects.get_account_from_email(email)
# No accountname match
if not account:
string = "The email '%s' does not match any accounts." % email
string += "\n\r\n\rIf you are new you should first create a new account "
string += "using the 'create' command."
session.msg(string)
return
# We have at least one result, so we can check the password.
if not player.check_password(password):
if not account[0].check_password(password):
session.msg("Incorrect password.")
return
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == player.name for tup in bans)
or
if bans and (any(tup[0] == account.name for tup in bans) or
any(tup[2].match(session.address[0]) for tup in bans if tup[2])):
# this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here."
@ -117,7 +115,7 @@ class CmdUnconnectedConnect(MuxCommand):
return
# actually do the login. This will call all hooks.
session.sessionhandler.login(session, player)
session.sessionhandler.login(session, account)
class CmdUnconnectedCreate(MuxCommand):
@ -125,9 +123,9 @@ class CmdUnconnectedCreate(MuxCommand):
Create a new account.
Usage (at login screen):
create \"playername\" <email> <password>
create \"accountname\" <email> <password>
This creates a new player account.
This creates a new account account.
"""
key = "create"
@ -136,36 +134,36 @@ class CmdUnconnectedCreate(MuxCommand):
def parse(self):
"""
The parser must handle the multiple-word player
The parser must handle the multiple-word account
name enclosed in quotes:
connect "Long name with many words" my@myserv.com mypassw
"""
super(CmdUnconnectedCreate, self).parse()
self.playerinfo = []
self.accountinfo = []
if len(self.arglist) < 3:
return
if len(self.arglist) > 3:
# this means we have a multi_word playername. pop from the back.
# this means we have a multi_word accountname. pop from the back.
password = self.arglist.pop()
email = self.arglist.pop()
# what remains is the playername.
playername = " ".join(self.arglist)
# what remains is the accountname.
accountname = " ".join(self.arglist)
else:
playername, email, password = self.arglist
accountname, email, password = self.arglist
playername = playername.replace('"', '') # remove "
playername = playername.replace("'", "")
self.playerinfo = (playername, email, password)
accountname = accountname.replace('"', '') # remove "
accountname = accountname.replace("'", "")
self.accountinfo = (accountname, email, password)
def func(self):
"""Do checks and create account"""
session = self.caller
try:
playername, email, password = self.playerinfo
accountname, email, password = self.accountinfo
except ValueError:
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
string = "\n\r Usage (without <>): create \"<accountname>\" <email> <password>"
session.msg(string)
return
if not email or not password:
@ -176,26 +174,26 @@ class CmdUnconnectedCreate(MuxCommand):
session.msg("'%s' is not a valid e-mail address." % email)
return
# sanity checks
if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30):
# this echoes the restrictions made by django's auth
# module (except not allowing spaces, for convenience of
# logging in).
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
session.msg(string)
return
# strip excessive spaces in playername
playername = re.sub(r"\s+", " ", playername).strip()
if PlayerDB.objects.filter(username__iexact=playername):
# player already exists (we also ignore capitalization here)
session.msg("Sorry, there is already a player with the name '%s'." % playername)
# strip excessive spaces in accountname
accountname = re.sub(r"\s+", " ", accountname).strip()
if AccountDB.objects.filter(username__iexact=accountname):
# account already exists (we also ignore capitalization here)
session.msg("Sorry, there is already an account with the name '%s'." % accountname)
return
if PlayerDB.objects.get_player_from_email(email):
# email already set on a player
session.msg("Sorry, there is already a player with that email address.")
if AccountDB.objects.get_account_from_email(email):
# email already set on an account
session.msg("Sorry, there is already an account with that email address.")
return
# Reserve playernames found in GUEST_LIST
if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
string = "\n\r That name is reserved. Please choose another Playername."
# Reserve accountnames found in GUEST_LIST
if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST):
string = "\n\r That name is reserved. Please choose another Accountname."
session.msg(string)
return
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
@ -207,8 +205,7 @@ class CmdUnconnectedCreate(MuxCommand):
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == playername.lower() for tup in bans)
or
if bans and (any(tup[0] == accountname.lower() for tup in bans) or
any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here." \
@ -219,20 +216,20 @@ class CmdUnconnectedCreate(MuxCommand):
# everything's ok. Create the new player account.
try:
permissions = settings.PERMISSION_PLAYER_DEFAULT
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email)
if new_player:
new_account = default_unloggedin._create_account(session, accountname, password, permissions, email=email)
if new_account:
if MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions)
default_unloggedin._create_character(session, new_account, typeclass, default_home, permissions)
# tell the caller everything went well.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
if " " in accountname:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
else:
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, email))
session.msg(string % (accountname, email))
except Exception:
# We are in the middle between logged in and -not, so we have
@ -246,7 +243,7 @@ class CmdUnconnectedCreate(MuxCommand):
class CmdUnconnectedQuit(MuxCommand):
"""
We maintain a different version of the `quit` command
here for unconnected players for the sake of simplicity. The logged in
here for unconnected accounts for the sake of simplicity. The logged in
version is a bit more complicated.
"""
key = "quit"

View file

@ -277,7 +277,7 @@ class CmdExtendedLook(default_cmds.CmdLook):
look
look <obj>
look <room detail>
look *<player>
look *<account>
Observes your location, details at your location or objects in your vicinity.
"""
@ -315,7 +315,7 @@ class CmdExtendedLook(default_cmds.CmdLook):
return
if not hasattr(looking_at_obj, 'return_appearance'):
# this is likely due to us having a player instead
# this is likely due to us having an account instead
looking_at_obj = looking_at_obj.character
if not looking_at_obj.access(caller, "view"):
caller.msg("Could not find '%s'." % args)
@ -377,7 +377,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
"""Define extended command"""
caller = self.caller
location = caller.location
if self.cmdstring == '@detail':
if self.cmdname == 'detail':
# switch to detailing mode. This operates only on current location
if not location:
caller.msg("No location to detail!")

View file

@ -88,7 +88,7 @@ class CallbackHandler(object):
Args:
callback_name (str): the name of the callback to add.
code (str): the Python code associated with this callback.
author (Character or Player, optional): the author of the callback.
author (Character or Account, optional): the author of the callback.
valid (bool, optional): should the callback be connected?
parameters (str, optional): optional parameters.
@ -109,7 +109,7 @@ class CallbackHandler(object):
callback_name (str): the name of the callback to edit.
number (int): the callback number to be changed.
code (str): the Python code associated with this callback.
author (Character or Player, optional): the author of the callback.
author (Character or Account, optional): the author of the callback.
valid (bool, optional): should the callback be connected?
Returns:

View file

@ -17,8 +17,8 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
# Permissions
WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None)
WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION",
"immortals")
VALIDATING = getattr(settings, "callbackS_VALIDATING", "immortals")
"developer")
VALIDATING = getattr(settings, "callbackS_VALIDATING", "developer")
# Split help text
BASIC_HELP = "Add, edit or delete callbacks."
@ -94,7 +94,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
on user permission.
Args:
caller (Object or Player): the caller asking for help on the command.
caller (Object or Account): the caller asking for help on the command.
cmdset (CmdSet): the command set (if you need additional commands).
Returns:
@ -360,8 +360,8 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
callback = dict(callback)
self.caller.db._callback = callback
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
quitfunc=_ev_quit, key="Callback {} of {}".format(
callback_name, obj), persistent=True, codefunc=_ev_save)
quitfunc=_ev_quit, key="Callback {} of {}".format(
callback_name, obj), persistent=True, codefunc=_ev_save)
def del_callback(self):
"""Delete a callback."""

View file

@ -200,7 +200,7 @@ class EventHandler(DefaultScript):
obj (Object): the Evennia typeclassed object to be extended.
callback_name (str): the name of the callback to add.
code (str): the Python code associated with this callback.
author (Character or Player, optional): the author of the callback.
author (Character or Account, optional): the author of the callback.
valid (bool, optional): should the callback be connected?
parameters (str, optional): optional parameters.
@ -254,7 +254,7 @@ class EventHandler(DefaultScript):
callback_name (str): the name of the callback to edit.
number (int): the callback number to be changed.
code (str): the Python code associated with this callback.
author (Character or Player, optional): the author of the callback.
author (Character or Account, optional): the author of the callback.
valid (bool, optional): should the callback be connected?
Raises:

View file

@ -81,7 +81,7 @@ This event is called when another character arrives in the location
where the current character is. For instance, a puppeted character
arrives in the shop of a shopkeeper (assuming the shopkeeper is
a character). As its name suggests, this event can be very useful
to have NPC greeting one another, or players, who come to visit.
to have NPC greeting one another, or accounts, who come to visit.
Variables you can use in this event:
character: the character connected to this event.
@ -100,9 +100,9 @@ Variables you can use in this event:
"""
CHARACTER_PUPPETED = """
When the character has been puppeted by a player.
This event is called when a player has just puppeted this character.
This can commonly happen when a player connects onto this character,
When the character has been puppeted by an account.
This event is called when an account has just puppeted this character.
This can commonly happen when an account connects onto this character,
or when puppeting to a NPC or free character.
Variables you can use in this event:
@ -151,8 +151,8 @@ Variables you can use in this event:
CHARACTER_UNPUPPETED = """
When the character is about to be un-puppeted.
This event is called when a player is about to un-puppet the
character, which can happen if the player is disconnecting or
This event is called when an account is about to un-puppet the
character, which can happen if the account is disconnecting or
changing puppets.
Variables you can use in this event:
@ -244,8 +244,8 @@ class EventCharacter(DefaultCharacter):
"""
if not source_location and self.location.has_player:
# This was created from nowhere and added to a player's
if not source_location and self.location.has_account:
# This was created from nowhere and added to an account's
# inventory; it's probably the result of a create command.
string = "You now have %s in your possession." % self.get_display_name(self.location)
self.location.msg(string)
@ -357,11 +357,11 @@ class EventCharacter(DefaultCharacter):
def at_post_puppet(self):
"""
Called just after puppeting has been completed and all
Player<->Object links have been established.
Account<->Object links have been established.
Note:
You can use `self.player` and `self.sessions.get()` to get
player and sessions at this point; the last entry in the
You can use `self.account` and `self.sessions.get()` to get
account and sessions at this point; the last entry in the
list from `self.sessions.get()` is the latest Session
puppeting this Object.
@ -378,11 +378,11 @@ class EventCharacter(DefaultCharacter):
def at_pre_unpuppet(self):
"""
Called just before beginning to un-connect a puppeting from
this Player.
this Account.
Note:
You can use `self.player` and `self.sessions.get()` to get
player and sessions at this point; the last entry in the
You can use `self.account` and `self.sessions.get()` to get
account and sessions at this point; the last entry in the
list from `self.sessions.get()` is the latest Session
puppeting this Object.
@ -683,7 +683,7 @@ Variables you can use in this event:
ROOM_PUPPETED_IN = """
After the character has been puppeted in this room.
This event is called after a character has been puppeted in this
room. This can happen when a player, having connected, begins
room. This can happen when an account, having connected, begins
to puppet a character. The character's location at this point,
if it's a room, will see this event fire.
@ -733,7 +733,7 @@ Variables you can use in this event:
ROOM_UNPUPPETED_IN = """
Before the character is un-puppeted in this room.
This event is called before a character is un-puppeted in this
room. This can happen when a player, puppeting a character, is
room. This can happen when an account, puppeting a character, is
disconnecting. The character's location at this point, if it's a
room, will see this event fire.

View file

@ -7,12 +7,12 @@ A simple Brandymail style @mail system that uses the Msg class from Evennia Core
Installation:
import CmdMail from this module (from evennia.contrib.mail import CmdMail),
and add into the default Player or Character command set (self.add(CmdMail)).
and add into the default Account or Character command set (self.add(CmdMail)).
"""
import re
from evennia import ObjectDB, PlayerDB
from evennia import ObjectDB, AccountDB
from evennia import default_cmds
from evennia.utils import create, evtable, make_iter
from evennia.comms.models import Msg
@ -27,17 +27,17 @@ class CmdMail(default_cmds.MuxCommand):
Commands that allow either IC or OOC communications
Usage:
@mail - Displays all the mail a player has in their mailbox
@mail - Displays all the mail an account has in their mailbox
@mail <#> - Displays a specific message
@mail <players>=<subject>/<message>
- Sends a message to the comma separated list of players.
@mail <accounts>=<subject>/<message>
- Sends a message to the comma separated list of accounts.
@mail/delete <#> - Deletes a specific message
@mail/forward <player list>=<#>[/<Message>]
- Forwards an existing message to the specified list of players,
@mail/forward <account list>=<#>[/<Message>]
- Forwards an existing message to the specified list of accounts,
original message is delivered with optional Message prepended.
@mail/reply <#>=<message>
@ -65,7 +65,7 @@ class CmdMail(default_cmds.MuxCommand):
Search a list of targets of the same type as caller.
Args:
caller (Object or Player): The type of object to search.
caller (Object or Account): The type of object to search.
namelist (list): List of strings for objects to search for.
Returns:
@ -73,10 +73,10 @@ class CmdMail(default_cmds.MuxCommand):
"""
nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist))
if hasattr(self.caller, "player") and self.caller.player:
if hasattr(self.caller, "account") and self.caller.account:
matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex))
else:
matches = list(PlayerDB.objects.filter(username__iregex=nameregex))
matches = list(AccountDB.objects.filter(username__iregex=nameregex))
return matches
def get_all_mail(self):
@ -89,10 +89,10 @@ class CmdMail(default_cmds.MuxCommand):
# mail_messages = Msg.objects.get_by_tag(category="mail")
# messages = []
try:
player = self.caller.player
account = self.caller.account
except AttributeError:
player = self.caller
messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player)
account = self.caller
messages = Msg.objects.get_by_tag(category="mail").filter(db_receivers_accounts=account)
return messages
def send_mail(self, recipients, subject, message, caller):
@ -100,10 +100,10 @@ class CmdMail(default_cmds.MuxCommand):
Function for sending new mail. Also useful for sending notifications from objects or systems.
Args:
recipients (list): list of Player or character objects to receive the newly created mails.
recipients (list): list of Account or character objects to receive the newly created mails.
subject (str): The header or subject of the message to be delivered.
message (str): The body of the message being sent.
caller (obj): The object (or Player or Character) that is sending the message.
caller (obj): The object (or Account or Character) that is sending the message.
"""
for recipient in recipients:
recipient.msg("You have received a new @mail from %s" % caller)
@ -114,7 +114,7 @@ class CmdMail(default_cmds.MuxCommand):
caller.msg("You sent your message.")
return
else:
caller.msg("No valid players found. Cannot send message.")
caller.msg("No valid accounts found. Cannot send message.")
return
def func(self):
@ -142,7 +142,7 @@ class CmdMail(default_cmds.MuxCommand):
elif "forward" in self.switches:
try:
if not self.rhs:
self.caller.msg("Cannot forward a message without a player list. Please try again.")
self.caller.msg("Cannot forward a message without an account list. Please try again.")
return
elif not self.lhs:
self.caller.msg("You must define a message to forward.")
@ -176,7 +176,7 @@ class CmdMail(default_cmds.MuxCommand):
except IndexError:
self.caller.msg("Message does not exixt.")
except ValueError:
self.caller.msg("Usage: @mail/forward <player list>=<#>[/<Message>]")
self.caller.msg("Usage: @mail/forward <account list>=<#>[/<Message>]")
elif "reply" in self.switches:
try:
if not self.rhs:

View file

@ -32,7 +32,7 @@ called and so on until the map is completed. Building instructions are passed
the following arguments:
x - The rooms position on the maps x axis
y - The rooms position on the maps y axis
caller - The player calling the command
caller - The account calling the command
iteration - The current iterations number (0, 1 or 2)
room_dict - A dictionary containing room references returned by build
functions where tuple coordinates are the keys (x, y).
@ -119,7 +119,7 @@ def example1_build_forest(x, y, **kwargs):
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
room.db.desc = "Basic forest room."
# Send a message to the player
# Send a message to the account
kwargs["caller"].msg(room.key + " " + room.dbref)
# This is generally mandatory.
@ -143,7 +143,7 @@ def example1_build_mountains(x, y, **kwargs):
rock = create_object(key="Rock", location=room)
rock.db.desc = "An ordinary rock."
# Send a message to the player
# Send a message to the account
kwargs["caller"].msg(room.key + " " + room.dbref)
# This is generally mandatory.
@ -167,7 +167,7 @@ def example1_build_temple(x, y, **kwargs):
"keeping the sound level only just below thunderous. "
"This is a rare spot of mirth on this dread moor.")
# Send a message to the player
# Send a message to the account
kwargs["caller"].msg(room.key + " " + room.dbref)
# This is generally mandatory.

View file

@ -18,8 +18,8 @@ CMDSET_UNLOGGEDIN = "contrib.menu_login.UnloggedinCmdSet"
When you'll reload the server, new sessions will connect to the new
login system, where they will be able to:
* Enter their username, assuming they have an existing player.
* Enter 'NEW' to create a new player.
* Enter their username, assuming they have an existing account.
* Enter 'NEW' to create a new account.
The top-level functions in this file are menu nodes (as described in
evennia.utils.evmenu.py). Each one of these functions is responsible
@ -61,7 +61,7 @@ def start(caller):
a session has been created OR if an error occurs further
down the menu tree. From there, users can either enter a
username (if this username exists) or type NEW (capitalized
or not) to create a new player.
or not) to create a new account.
"""
text = random_string_from_module(CONNECTION_SCREEN_MODULE)
@ -79,7 +79,7 @@ def start(caller):
def username(caller, string_input):
"""Check that the username leads to an existing player.
"""Check that the username leads to an existing account.
Check that the specified username exists. If the username doesn't
exist, display an error message and ask the user to try again. If
@ -88,8 +88,8 @@ def username(caller, string_input):
"""
string_input = string_input.strip()
player = managers.players.get_player_from_name(string_input)
if player is None:
account = managers.accounts.get_account_from_name(string_input)
if account is None:
text = dedent("""
|rThe username '{}' doesn't exist. Have you created it?|n
Try another name or leave empty to go back.
@ -100,8 +100,8 @@ def username(caller, string_input):
{"key": "_default",
"goto": "username"})
else:
caller.ndb._menutree.player = player
text = "Enter the password for the {} account.".format(player.name)
caller.ndb._menutree.account = account
text = "Enter the password for the {} account.".format(account.name)
# Disables echo for the password
caller.msg("", options={"echo": False})
options = (
@ -115,7 +115,7 @@ def username(caller, string_input):
def ask_password(caller, string_input):
"""Ask the user to enter the password to this player.
"""Ask the user to enter the password to this account.
This is assuming the user exists (see 'create_username' and
'create_password'). This node "loops" if needed: if the
@ -129,14 +129,14 @@ def ask_password(caller, string_input):
# Check the password and login is correct; also check for bans
player = menutree.player
account = menutree.account
password_attempts = menutree.password_attempts \
if hasattr(menutree, "password_attempts") else 0
bans = ServerConfig.objects.conf("server_bans")
banned = bans and (any(tup[0] == player.name.lower() for tup in bans) or
banned = bans and (any(tup[0] == account.name.lower() for tup in bans) or
any(tup[2].match(caller.address) for tup in bans if tup[2]))
if not player.check_password(string_input):
if not account.check_password(string_input):
# Didn't enter a correct password
password_attempts += 1
if password_attempts > 2:
@ -173,7 +173,7 @@ def ask_password(caller, string_input):
text = ""
options = {}
caller.msg("", options={"echo": True})
caller.sessionhandler.login(caller, player)
caller.sessionhandler.login(caller, account)
return text, options
@ -201,10 +201,10 @@ def create_username(caller, string_input):
"""
menutree = caller.ndb._menutree
string_input = string_input.strip()
player = managers.players.get_player_from_name(string_input)
account = managers.accounts.get_account_from_name(string_input)
# If a player with that name exists, a new one will not be created
if player:
# If an account with that name exists, a new one will not be created
if account:
text = dedent("""
|rThe account {} already exists.|n
Enter another username or leave blank to go back.
@ -229,7 +229,7 @@ def create_username(caller, string_input):
"goto": "create_username"})
else:
# a valid username - continue getting the password
menutree.playername = string_input
menutree.accountname = string_input
# Disables echo for entering password
caller.msg("", options={"echo": False})
# Redirects to the creation of a password
@ -259,7 +259,7 @@ def create_password(caller, string_input):
"goto": "create_password"})
password = string_input.strip()
playername = menutree.playername
accountname = menutree.accountname
if len(password) < LEN_PASSWD:
# The password is too short
@ -273,15 +273,15 @@ def create_password(caller, string_input):
from evennia.commands.default import unloggedin
# We make use of the helper functions from the default set here.
try:
permissions = settings.PERMISSION_PLAYER_DEFAULT
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
new_player = unloggedin._create_player(caller, playername,
new_account = unloggedin._create_account(caller, accountname,
password, permissions)
if new_player:
if new_account:
if settings.MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(
settings.DEFAULT_HOME)
unloggedin._create_character(caller, new_player,
unloggedin._create_character(caller, new_account,
typeclass, default_home, permissions)
except Exception:
# We are in the middle between logged in and -not, so we have
@ -297,7 +297,7 @@ def create_password(caller, string_input):
text = ""
caller.msg("|gWelcome, your new account has been created!|n")
caller.msg("", options={"echo": True})
caller.sessionhandler.login(caller, new_player)
caller.sessionhandler.login(caller, new_account)
return text, options
@ -335,7 +335,7 @@ class UnloggedinCmdSet(CmdSet):
class CmdUnloggedinLook(Command):
"""
An unloggedin version of the look command. This is called by the server
when the player first connects. It sets up the menu before handing off
when the account first connects. It sets up the menu before handing off
to the menu's own look command.
"""
key = syscmdkeys.CMD_LOGINSTART

View file

@ -1203,14 +1203,14 @@ class ContribRPObject(DefaultObject):
nofound_string (str): optional custom string for not-found error message.
multimatch_string (str): optional custom string for multimatch error header.
use_dbref (bool or None): If None, only turn off use_dbref if we are of a lower
permission than Builders. Otherwise, honor the True/False value.
permission than Builder. Otherwise, honor the True/False value.
Returns:
match (Object, None or list): will return an Object/None if `quiet=False`,
otherwise it will return a list of 0, 1 or more matches.
Notes:
To find Players, use eg. `evennia.player_search`. If
To find Accounts, use eg. `evennia.account_search`. If
`quiet=False`, error messages will be handled by
`settings.SEARCH_AT_RESULT` and echoed automatically (on
error, return will be `None`). If `quiet=True`, the error
@ -1228,7 +1228,7 @@ class ContribRPObject(DefaultObject):
if use_nicks:
# do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "account"), include_account=True)
if(global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())):
@ -1256,7 +1256,7 @@ class ContribRPObject(DefaultObject):
candidates.append(self)
# the sdesc-related substitution
is_builder = self.locks.check_lockstring(self, "perm(Builders)")
is_builder = self.locks.check_lockstring(self, "perm(Builder)")
use_dbref = is_builder if use_dbref is None else use_dbref
search_obj = lambda string: ObjectDB.objects.object_search(string,
attribute_name=attribute_name,
@ -1296,7 +1296,7 @@ class ContribRPObject(DefaultObject):
Displays the name of the object in a viewer-aware manner.
Args:
looker (TypedObject): The object or player that is looking
looker (TypedObject): The object or account that is looking
at/getting inforamtion for this object.
Kwargs:
@ -1342,7 +1342,7 @@ class ContribRPObject(DefaultObject):
key = con.get_display_name(looker, pose=True)
if con.destination:
exits.append(key)
elif con.has_player:
elif con.has_account:
users.append(key)
else:
things.append(key)
@ -1383,7 +1383,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
Displays the name of the object in a viewer-aware manner.
Args:
looker (TypedObject): The object or player that is looking
looker (TypedObject): The object or account that is looking
at/getting inforamtion for this object.
Kwargs:

View file

@ -4,7 +4,9 @@ Testing suite for contrib folder
"""
import sys
import datetime
from django.test import override_settings
from evennia.commands.default.tests import CommandTest
from evennia.utils.test_resources import EvenniaTest
from mock import Mock, patch
@ -219,13 +221,12 @@ class TestExtendedRoom(CommandTest):
self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.")
def test_cmdextendeddesc(self):
self.call(extended_room.CmdExtendedDesc(), "", "Details on Room", cmdstring="@detail")
self.call(extended_room.CmdExtendedDesc(), "", "Details on Room", cmdstring="detail")
self.call(extended_room.CmdExtendedDesc(), "thingie = newdetail with spaces",
"Set Detail thingie to 'newdetail with spaces'.", cmdstring="@detail")
self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' on Room:\n", cmdstring="@detail")
self.call(extended_room.CmdExtendedDesc(), "/del thingie",
"Detail thingie deleted, if it existed.", cmdstring="@detail")
self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' not found.", cmdstring="@detail")
"Set Detail thingie to 'newdetail with spaces'.", cmdstring="detail")
self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' on Room:\n", cmdstring="detail")
self.call(extended_room.CmdExtendedDesc(), "/del thingie", "Detail thingie deleted, if it existed.", cmdstring="detail")
self.call(extended_room.CmdExtendedDesc(), "thingie", "Detail 'thingie' not found.", cmdstring="detail")
self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:")
def test_cmdgametime(self):
@ -396,13 +397,13 @@ class TestWilderness(EvenniaTest):
# Pretend that both char1 and char2 are connected...
self.char1.sessions.add(1)
self.char2.sessions.add(1)
self.assertTrue(self.char1.has_player)
self.assertTrue(self.char2.has_player)
self.assertTrue(self.char1.has_account)
self.assertTrue(self.char2.has_account)
wilderness.create_wilderness()
w = self.get_wilderness_script()
# We should have no unused room after moving the first player in.
# We should have no unused room after moving the first account in.
self.assertEquals(len(w.db.unused_rooms), 0)
w.move_obj(self.char1, (0, 0))
self.assertEquals(len(w.db.unused_rooms), 0)
@ -443,15 +444,15 @@ from evennia.contrib import chargen
class TestChargen(CommandTest):
def test_ooclook(self):
self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.player)
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.", caller=self.player)
self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.account)
self.call(chargen.CmdOOCLook(), "", "You, TestAccount, are an OOC ghost without form.", caller=self.account)
def test_charcreate(self):
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.player)
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.player)
self.assertTrue(self.player.db._character_dbrefs)
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player)
self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player)
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.account)
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.account)
self.assertTrue(self.account.db._character_dbrefs)
self.call(chargen.CmdOOCLook(), "", "You, TestAccount, are an OOC ghost without form.",caller=self.account)
self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.account)
# Testing clothing contrib
from evennia.contrib import clothing
@ -601,9 +602,9 @@ class TestEmailLogin(CommandTest):
def test_connect(self):
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test", "The email 'mytest@test.com' does not match any accounts.")
self.call(email_login.CmdUnconnectedCreate(), '"mytest" mytest@test.com test11111', "A new account 'mytest' was created. Welcome!")
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.player.sessions.get()[0])
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.account.sessions.get()[0])
def test_quit(self):
self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.player.sessions.get()[0])
self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.account.sessions.get()[0])
def test_unconnectedlook(self):
self.call(email_login.CmdUnconnectedLook(), "", "==========")
def test_unconnectedhelp(self):
@ -629,19 +630,19 @@ from evennia.contrib import mail
class TestMail(CommandTest):
def test_mail(self):
self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.player)
self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.player)
self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.account)
self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.account)
self.call(mail.CmdMail(), "Char=Message 1", "You have received a new @mail from Char|You sent your message.", caller=self.char1)
self.call(mail.CmdMail(), "Char=Message 2", "You sent your message.", caller=self.char2)
self.call(mail.CmdMail(), "TestPlayer2=Message 2",
"You have received a new @mail from TestPlayer2(player 2)|You sent your message.", caller=self.player2)
self.call(mail.CmdMail(), "TestPlayer=Message 1", "You sent your message.", caller=self.player2)
self.call(mail.CmdMail(), "TestPlayer=Message 2", "You sent your message.", caller=self.player2)
self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.player)
self.call(mail.CmdMail(), "2", "From: TestPlayer2", caller=self.player)
self.call(mail.CmdMail(), "/forward TestPlayer2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.player)
self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.player)
self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.player)
self.call(mail.CmdMail(), "TestAccount2=Message 2",
"You have received a new @mail from TestAccount2(account 2)|You sent your message.", caller=self.account2)
self.call(mail.CmdMail(), "TestAccount=Message 1", "You sent your message.", caller=self.account2)
self.call(mail.CmdMail(), "TestAccount=Message 2", "You sent your message.", caller=self.account2)
self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.account)
self.call(mail.CmdMail(), "2", "From: TestAccount2", caller=self.account)
self.call(mail.CmdMail(), "/forward TestAccount2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.account)
self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.account)
self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.account)
# test map builder contrib
@ -985,3 +986,63 @@ class TestUnixCommand(CommandTest):
lines = ret.splitlines()
self.assertTrue(any(l.startswith("usage:") for l in lines))
self.assertTrue(any(l.startswith("dummy: error:") for l in lines))
import re
from evennia.contrib import color_markups
class TestColorMarkup(EvenniaTest):
"""
Note: Normally this would be tested by importing the ansi parser and run
the mappings through it. This is not possible since the ansi module creates
its mapping at the module/class level; since the ansi module is used by so
many other modules it appears that trying to overload
settings to test it causes issues with unrelated tests.
"""
def test_curly_markup(self):
ansi_map = color_markups.CURLY_COLOR_ANSI_EXTRA_MAP
self.assertIsNotNone(re.match(re.escape(ansi_map[7][0]), '{r'))
self.assertIsNotNone(re.match(re.escape(ansi_map[-1][0]), '{[X'))
xterm_fg = color_markups.CURLY_COLOR_XTERM256_EXTRA_FG
self.assertIsNotNone(re.match(xterm_fg[0], '{001'))
self.assertIsNotNone(re.match(xterm_fg[0], '{123'))
self.assertIsNotNone(re.match(xterm_fg[0], '{455'))
xterm_bg = color_markups.CURLY_COLOR_XTERM256_EXTRA_BG
self.assertIsNotNone(re.match(xterm_bg[0], '{[001'))
self.assertIsNotNone(re.match(xterm_bg[0], '{[123'))
self.assertIsNotNone(re.match(xterm_bg[0], '{[455'))
xterm_gfg = color_markups.CURLY_COLOR_XTERM256_EXTRA_GFG
self.assertIsNotNone(re.match(xterm_gfg[0], '{=h'))
self.assertIsNotNone(re.match(xterm_gfg[0], '{=e'))
self.assertIsNotNone(re.match(xterm_gfg[0], '{=w'))
xterm_gbg = color_markups.CURLY_COLOR_XTERM256_EXTRA_GBG
self.assertIsNotNone(re.match(xterm_gbg[0], '{[=a'))
self.assertIsNotNone(re.match(xterm_gbg[0], '{[=k'))
self.assertIsNotNone(re.match(xterm_gbg[0], '{[=z'))
bright_map = color_markups.CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP
self.assertEqual(bright_map[0][1], '{[500')
self.assertEqual(bright_map[-1][1], '{[222')
def test_mux_markup(self):
ansi_map = color_markups.MUX_COLOR_ANSI_EXTRA_MAP
self.assertIsNotNone(re.match(re.escape(ansi_map[10][0]), '%cr'))
self.assertIsNotNone(re.match(re.escape(ansi_map[-1][0]), '%cX'))
xterm_fg = color_markups.MUX_COLOR_XTERM256_EXTRA_FG
self.assertIsNotNone(re.match(xterm_fg[0], '%c001'))
self.assertIsNotNone(re.match(xterm_fg[0], '%c123'))
self.assertIsNotNone(re.match(xterm_fg[0], '%c455'))
xterm_bg = color_markups.MUX_COLOR_XTERM256_EXTRA_BG
self.assertIsNotNone(re.match(xterm_bg[0], '%c[001'))
self.assertIsNotNone(re.match(xterm_bg[0], '%c[123'))
self.assertIsNotNone(re.match(xterm_bg[0], '%c[455'))
xterm_gfg = color_markups.MUX_COLOR_XTERM256_EXTRA_GFG
self.assertIsNotNone(re.match(xterm_gfg[0], '%c=h'))
self.assertIsNotNone(re.match(xterm_gfg[0], '%c=e'))
self.assertIsNotNone(re.match(xterm_gfg[0], '%c=w'))
xterm_gbg = color_markups.MUX_COLOR_XTERM256_EXTRA_GBG
self.assertIsNotNone(re.match(xterm_gbg[0], '%c[=a'))
self.assertIsNotNone(re.match(xterm_gbg[0], '%c[=k'))
self.assertIsNotNone(re.match(xterm_gbg[0], '%c[=z'))
bright_map = color_markups.MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP
self.assertEqual(bright_map[0][1], '%c[500')
self.assertEqual(bright_map[-1][1], '%c[222')

View file

@ -297,17 +297,17 @@ class LidOpenCmdSet(CmdSet):
class BlindCmdSet(CmdSet):
"""
This is the cmdset added to the *player* when
This is the cmdset added to the *account* when
the button is pushed.
"""
key = "BlindCmdSet"
# we want it to completely replace all normal commands
# until the timed script removes it again.
mergetype = "Replace"
# we want to stop the player from walking around
# we want to stop the account from walking around
# in this blinded state, so we hide all exits too.
# (channel commands will still work).
no_exits = True # keep player in the same room
no_exits = True # keep account in the same room
no_objs = True # don't allow object commands
def at_cmdset_creation(self):

View file

@ -100,17 +100,17 @@ class BlindedState(DefaultScript):
"""
This is a timed state.
This adds a (very limited) cmdset TO THE PLAYER, during a certain time,
This adds a (very limited) cmdset TO THE ACCOUNT, during a certain time,
after which the script will close and all functions are
restored. It's up to the function starting the script to actually
set it on the right player object.
set it on the right account object.
"""
def at_script_creation(self):
"""
We set up the script here.
"""
self.key = "temporary_blinder"
self.desc = "Temporarily blinds the player for a little while."
self.desc = "Temporarily blinds the account for a little while."
self.interval = 20 # seconds
self.start_delay = True # we don't want it to stop until after 20s.
self.repeats = 1 # this will go away after interval seconds.
@ -123,7 +123,7 @@ class BlindedState(DefaultScript):
Note that the RedButtonBlind cmdset is defined to completly
replace the other cmdsets on the stack while it is active
(this means that while blinded, only operations in this cmdset
will be possible for the player to perform). It is however
will be possible for the account to perform). It is however
not persistent, so should there be a bug in it, we just need
to restart the server to clear out of it during development.
"""
@ -228,7 +228,7 @@ class DeactivateButtonEvent(DefaultScript):
This deactivates the button for a short while (it won't blink, won't
close its lid etc). It is meant to be called when the button is pushed
and run as long as the blinded effect lasts. We cannot put these methods
in the AddBlindedCmdSet script since that script is defined on the *player*
in the AddBlindedCmdSet script since that script is defined on the *account*
whereas this one must be defined on the *button*.
"""
def at_script_creation(self):
@ -250,7 +250,7 @@ class DeactivateButtonEvent(DefaultScript):
"""
# closing the lid will also add the ClosedState script
self.obj.close_lid()
# lock the lid so other players can't access it until the
# lock the lid so other accounts can't access it until the
# first one's effect has worn off.
self.obj.db.lid_locked = True
# breaking the lamp also sets a correct desc

View file

@ -206,7 +206,7 @@ class Mob(tut_objects.TutorialObject):
"""
targets = [obj for obj in location.contents_get(exclude=self)
if obj.has_player and not obj.is_superuser]
if obj.has_account and not obj.is_superuser]
return targets[0] if targets else None
def set_alive(self, *args, **kwargs):
@ -290,9 +290,9 @@ class Mob(tut_objects.TutorialObject):
"""
Called repeatedly during patrolling mode. In this mode, the
mob scans its surroundings and randomly chooses a viable exit.
One should lock exits with the traverse:has_player() lock in
One should lock exits with the traverse:has_account() lock in
order to block the mob from moving outside its area while
allowing player-controlled characters to move normally.
allowing account-controlled characters to move normally.
"""
if random.random() < 0.01 and self.db.irregular_msgs:
self.location.msg_contents(random.choice(self.db.irregular_msgs))

View file

@ -582,7 +582,7 @@ class CrumblingWall(TutorialObject, DefaultExit):
The CrumblingWall can be examined in various ways, but only if a
lit light source is in the room. The traversal itself is blocked
by a traverse: lock on the exit that only allows passage if a
certain attribute is set on the trying player.
certain attribute is set on the trying account.
Important attribute
destination - this property must be set to make this a valid exit
@ -701,7 +701,7 @@ class CrumblingWall(TutorialObject, DefaultExit):
self.reset()
def at_failed_traverse(self, traverser):
"""This is called if the player fails to pass the Exit."""
"""This is called if the account fails to pass the Exit."""
traverser.msg("No matter how you try, you cannot force yourself through %s." % self.key)
def reset(self):
@ -868,7 +868,7 @@ class Weapon(TutorialObject):
When reset, the weapon is simply deleted, unless it has a place
to return to.
"""
if self.location.has_player and self.home == self.location:
if self.location.has_account and self.home == self.location:
self.location.msg_contents("%s suddenly and magically fades into nothingness, as if it was never there ..."
% self.key)
self.delete()
@ -1032,7 +1032,7 @@ class WeaponRack(TutorialObject):
Attributes to set on this object:
available_weapons: list of prototype-keys from
WEAPON_PROTOTYPES, the weapons available in this rack.
no_more_weapons_msg - error message to return to players
no_more_weapons_msg - error message to return to accounts
who already got one weapon from the rack and tries to
grab another one.

View file

@ -98,7 +98,7 @@ class CmdTutorialSetDetail(default_cmds.MuxCommand):
multiple aliases to the detail all at once.
"""
key = "@detail"
locks = "cmd:perm(Builders)"
locks = "cmd:perm(Builder)"
help_category = "TutorialWorld"
def func(self):
@ -126,7 +126,7 @@ class CmdTutorialLook(default_cmds.CmdLook):
Usage:
look <obj>
look <room detail>
look *<player>
look *<account>
Observes your location, details at your location or objects
in your vicinity.
@ -182,7 +182,7 @@ class CmdTutorialLook(default_cmds.CmdLook):
return
if not hasattr(looking_at_obj, 'return_appearance'):
# this is likely due to us having a player instead
# this is likely due to us having an account instead
looking_at_obj = looking_at_obj.character
if not looking_at_obj.access(caller, "view"):
caller.msg("Could not find '%s'." % args)
@ -232,7 +232,7 @@ class TutorialRoom(DefaultRoom):
source_location (Object): the previous location of new_arrival.
"""
if new_arrival.has_player and not new_arrival.is_superuser:
if new_arrival.has_account and not new_arrival.is_superuser:
# this is a character
for obj in self.contents_get(exclude=new_arrival):
if hasattr(obj, "at_new_arrival"):
@ -362,7 +362,7 @@ class IntroRoom(TutorialRoom):
super(IntroRoom, self).at_object_creation()
self.db.tutorial_info = "The first room of the tutorial. " \
"This assigns the health Attribute to "\
"the player."
"the account."
def at_object_receive(self, character, source_location):
"""
@ -372,7 +372,7 @@ class IntroRoom(TutorialRoom):
# setup character for the tutorial
health = self.db.char_health or 20
if character.has_player:
if character.has_account:
character.db.health = health
character.db.health_max = health
@ -388,7 +388,7 @@ class IntroRoom(TutorialRoom):
# Defines a special west-eastward "bridge"-room, a large room that takes
# several steps to cross. It is complete with custom commands and a
# chance of falling off the bridge. This room has no regular exits,
# instead the exitings are handled by custom commands set on the player
# instead the exitings are handled by custom commands set on the account
# upon first entering the room.
#
# Since one can enter the bridge room from both ends, it is
@ -537,7 +537,7 @@ class CmdLookBridge(Command):
BRIDGE_POS_MESSAGES[bridge_position],
random.choice(BRIDGE_MOODS))
chars = [obj for obj in self.obj.contents_get(exclude=caller) if obj.has_player]
chars = [obj for obj in self.obj.contents_get(exclude=caller) if obj.has_account]
if chars:
# we create the You see: message manually here
message += "\n You see: %s" % ", ".join("|c%s|n" % char.key for char in chars)
@ -606,7 +606,7 @@ class BridgeRoom(WeatherRoom):
The bridge room implements an unsafe bridge. It also enters the player into
a state where they get new commands so as to try to cross the bridge.
We want this to result in the player getting a special set of
We want this to result in the account getting a special set of
commands related to crossing the bridge. The result is that it
will take several steps to cross it, despite it being represented
by only a single room.
@ -659,7 +659,7 @@ class BridgeRoom(WeatherRoom):
This hook is called by the engine whenever the player is moved
into this room.
"""
if character.has_player:
if character.has_account:
# we only run this if the entered object is indeed a player object.
# check so our east/west exits are correctly defined.
wexit = search_object(self.db.west_exit)
@ -682,7 +682,7 @@ class BridgeRoom(WeatherRoom):
"""
This is triggered when the player leaves the bridge room.
"""
if character.has_player:
if character.has_account:
# clean up the position attribute
del character.db.tutorial_bridge_position
@ -876,7 +876,7 @@ class DarkRoom(TutorialRoom):
self.locks.add("view:all()")
self.cmdset.remove(DarkCmdSet)
self.db.is_lit = True
for char in (obj for obj in self.contents if obj.has_player):
for char in (obj for obj in self.contents if obj.has_account):
# this won't do anything if it is already removed
char.msg("The room is lit up.")
else:
@ -884,7 +884,7 @@ class DarkRoom(TutorialRoom):
self.db.is_lit = False
self.locks.add("view:false()")
self.cmdset.add(DarkCmdSet, permanent=True)
for char in (obj for obj in self.contents if obj.has_player):
for char in (obj for obj in self.contents if obj.has_account):
if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.")
else:
@ -895,7 +895,7 @@ class DarkRoom(TutorialRoom):
"""
Called when an object enters the room.
"""
if obj.has_player:
if obj.has_account:
# a puppeted object, that is, a Character
self._heal(obj)
# in case the new guy carries light with them
@ -960,7 +960,7 @@ class TeleportRoom(TutorialRoom):
This hook is called by the engine whenever the player is moved into
this room.
"""
if not character.has_player:
if not character.has_account:
# only act on player characters.
return
# determine if the puzzle is a success or not
@ -1020,7 +1020,7 @@ class OutroRoom(TutorialRoom):
"""
Do cleanup.
"""
if character.has_player:
if character.has_account:
del character.db.health_max
del character.db.health
del character.db.last_climbed

View file

@ -341,7 +341,7 @@ class WildernessScript(DefaultScript):
old_room.wilderness.at_after_object_leave(obj)
else:
for item in old_room.contents:
if item.has_player:
if item.has_account:
# There is still a player in the old room.
# Let's create a new room and not touch that old
# room.
@ -419,7 +419,7 @@ class WildernessScript(DefaultScript):
return
for item in room.contents:
if item.has_player:
if item.has_account:
# There is still a character in that room. We can't get rid of
# it just yet
break
@ -457,7 +457,7 @@ class WildernessScript(DefaultScript):
class WildernessRoom(DefaultRoom):
"""
This is a single room inside the wilderness. This room provides a "view"
into the wilderness map. When a player moves around, instead of going to
into the wilderness map. When an account moves around, instead of going to
another room as with traditional rooms, they stay in the same room but the
room itself changes to display another area of the wilderness.
"""
@ -588,7 +588,7 @@ class WildernessRoom(DefaultRoom):
Displays the name of the object in a viewer-aware manner.
Args:
looker (TypedObject): The object or player that is looking
looker (TypedObject): The object or account that is looking
at/getting inforamtion for this object.
Returns:
@ -603,7 +603,7 @@ class WildernessRoom(DefaultRoom):
searching, and is expected to produce something useful for
builders.
"""
if self.locks.check_lockstring(looker, "perm(Builders)"):
if self.locks.check_lockstring(looker, "perm(Builder)"):
name = "{}(#{})".format(self.location_name, self.id)
else:
name = self.location_name

View file

@ -1,7 +1,7 @@
"""
Commands
Commands describe the input the player can do to the game.
Commands describe the input the account can do to the game.
"""
@ -169,17 +169,17 @@ class Command(BaseCommand):
# self.rhs = rhs
# self.rhslist = rhslist
#
# # if the class has the player_caller property set on itself, we make
# # sure that self.caller is always the player if possible. We also create
# # if the class has the account_caller property set on itself, we make
# # sure that self.caller is always the account if possible. We also create
# # a special property "character" for the puppeted object, if any. This
# # is convenient for commands defined on the Player only.
# if hasattr(self, "player_caller") and self.player_caller:
# # is convenient for commands defined on the Account only.
# if hasattr(self, "account_caller") and self.account_caller:
# if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"):
# # caller is an Object/Character
# self.character = self.caller
# self.caller = self.caller.player
# elif utils.inherits_from(self.caller, "evennia.players.players.DefaultPlayer"):
# # caller was already a Player
# self.caller = self.caller.account
# elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"):
# # caller was already an Account
# self.character = self.caller.get_puppet(self.session)
# else:
# self.character = None

View file

@ -20,7 +20,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
"""
The `CharacterCmdSet` contains general in-game commands like `look`,
`get`, etc available on in-game Character objects. It is merged with
the `PlayerCmdSet` when a Player puppets a Character.
the `AccountCmdSet` when an Account puppets a Character.
"""
key = "DefaultCharacter"
@ -34,20 +34,20 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
#
class PlayerCmdSet(default_cmds.PlayerCmdSet):
class AccountCmdSet(default_cmds.AccountCmdSet):
"""
This is the cmdset available to the Player at all times. It is
combined with the `CharacterCmdSet` when the Player puppets a
This is the cmdset available to the Account at all times. It is
combined with the `CharacterCmdSet` when the Account puppets a
Character. It holds game-account-specific commands, channel
commands, etc.
"""
key = "DefaultPlayer"
key = "DefaultAccount"
def at_cmdset_creation(self):
"""
Populates the cmdset
"""
super(PlayerCmdSet, self).at_cmdset_creation()
super(AccountCmdSet, self).at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#

View file

@ -5,7 +5,7 @@ The serversession is the Server-side in-memory representation of a
user connecting to the game. Evennia manages one Session per
connection to the game. So a user logged into the game with multiple
clients (if Evennia is configured to allow that) will have multiple
sessions tied to one Player object. All communication between Evennia
sessions tied to one Account object. All communication between Evennia
and the real-world user goes through the Session(s) associated with that user.
It should be noted that modifying the Session object is not usually
@ -28,8 +28,8 @@ class ServerSession(BaseServerSession):
This class represents a player's session and is a template for
individual protocols to communicate with Evennia.
Each player gets one or more sessions assigned to them whenever they connect
to the game server. All communication between game and player goes
Each account gets one or more sessions assigned to them whenever they connect
to the game server. All communication between game and account goes
through their session(s).
"""
pass

View file

@ -1,15 +1,15 @@
"""
Player
Account
The Player represents the game "account" and each login has only one
Player object. A Player is what chats on default channels but has no
other in-game-world existence. Rather the Player puppets Objects (such
The Account represents the game "account" and each login has only one
Account object. An Account is what chats on default channels but has no
other in-game-world existence. Rather the Account puppets Objects (such
as Characters) in order to actually participate in the game world.
Guest
Guest players are simple low-level accounts that are created/deleted
Guest accounts are simple low-level accounts that are created/deleted
on the fly and allows users to test the game without the commitment
of a full registration. Guest accounts are deactivated by default; to
activate them, add the following line to your settings file:
@ -22,11 +22,11 @@ several more options for customizing the Guest account system.
"""
from evennia import DefaultPlayer, DefaultGuest
from evennia import DefaultAccount, DefaultGuest
class Player(DefaultPlayer):
class Account(DefaultAccount):
"""
This class describes the actual OOC player (i.e. the user connecting
This class describes the actual OOC account (i.e. the user connecting
to the MUD). It does NOT have visual appearance in the game world (that
is handled by the character which is connected to this). Comm channels
are attended/joined using this object.
@ -35,12 +35,12 @@ class Player(DefaultPlayer):
should generally not hold any character-related info (that's best handled
on the character level).
Can be set using BASE_PLAYER_TYPECLASS.
Can be set using BASE_ACCOUNT_TYPECLASS.
* available properties
key (string) - name of player
key (string) - name of account
name (string)- wrapper for user.username
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
dbref (int, read-only) - unique #id-number. Also "id" can be used.
@ -48,8 +48,8 @@ class Player(DefaultPlayer):
permissions (list of strings) - list of permission strings
user (User, read-only) - django User authorization object
obj (Object) - game object controlled by player. 'character' can also be used.
sessions (list of Sessions) - sessions connected to this player
obj (Object) - game object controlled by account. 'character' can also be used.
sessions (list of Sessions) - sessions connected to this account
is_superuser (bool, read-only) - if the connected user is a superuser
* Handlers
@ -66,7 +66,7 @@ class Player(DefaultPlayer):
msg(text=None, **kwargs)
swap_character(new_character, delete_old_character=False)
execute_cmd(raw_string, session=None)
search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False)
search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, account=False)
is_typeclass(typeclass, exact=False)
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
access(accessing_obj, access_type='read', default=False)
@ -75,7 +75,7 @@ class Player(DefaultPlayer):
* Hook methods (when re-implementation, remember methods need to have self as first arg)
basetype_setup()
at_player_creation()
at_account_creation()
- note that the following hooks are also found on Objects and are
usually handled on the character level:
@ -96,7 +96,7 @@ class Player(DefaultPlayer):
class Guest(DefaultGuest):
"""
This class is used for guest logins. Unlike Players, Guests and their
This class is used for guest logins. Unlike Accounts, Guests and their
characters are deleted after disconnection.
"""
pass

View file

@ -2,7 +2,7 @@
Channel
The channel class represents the out-of-character chat-room usable by
Players in-game. It is mostly overloaded to change its appearance, but
Accounts in-game. It is mostly overloaded to change its appearance, but
channels can be used to implement many different forms of message
distribution systems.
@ -18,9 +18,9 @@ class Channel(DefaultChannel):
"""
Working methods:
at_channel_creation() - called once, when the channel is created
has_connection(player) - check if the given player listens to this channel
connect(player) - connect player to this channel
disconnect(player) - disconnect player from channel
has_connection(account) - check if the given account listens to this channel
connect(account) - connect account to this channel
disconnect(account) - disconnect account from channel
access(access_obj, access_type='listen', default=False) - check the
access on this channel (default access_type is listen)
delete() - delete this channel
@ -33,8 +33,8 @@ class Channel(DefaultChannel):
tempmsg(msg, header=None, senders=None) - wrapper for sending non-persistent
messages.
distribute_message(msg, online=False) - send a message to all
connected players on channel, optionally sending only
to players that are currently online (optimized for very large sends)
connected accounts on channel, optionally sending only
to accounts that are currently online (optimized for very large sends)
Useful hooks:
channel_prefix(msg, emit=False) - how the channel should be

View file

@ -1,7 +1,7 @@
"""
Characters
Characters are (by default) Objects setup to be puppeted by Players.
Characters are (by default) Objects setup to be puppeted by Accounts.
They are what you "see" in game. The Character class in this module
is setup to be the "default" character type created by the default
creation commands.
@ -19,14 +19,14 @@ class Character(DefaultCharacter):
and its commands only be called by itself, not anyone else.
(to change things, use at_object_creation() instead).
at_after_move(source_location) - Launches the "look" command after every move.
at_post_unpuppet(player) - when Player disconnects from the Character, we
at_post_unpuppet(account) - when Account disconnects from the Character, we
store the current location in the pre_logout_location Attribute and
move it to a None-location so the "unpuppeted" character
object does not need to stay on grid. Echoes "Player has disconnected"
object does not need to stay on grid. Echoes "Account has disconnected"
to the room.
at_pre_puppet - Just before Player re-connects, retrieves the character's
at_pre_puppet - Just before Account re-connects, retrieves the character's
pre_logout_location Attribute and move it back on the grid.
at_post_puppet - Echoes "PlayerName has entered the game" to the room.
at_post_puppet - Echoes "AccountName has entered the game" to the room.
"""
pass

View file

@ -40,16 +40,16 @@ class Object(DefaultObject):
date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings
player (Player) - controlling player (if any, only set together with
account (Account) - controlling account (if any, only set together with
sessid below)
sessid (int, read-only) - session id (if any, only set together with
player above). Use `sessions` handler to get the
account above). Use `sessions` handler to get the
Sessions directly.
location (Object) - current location. Is None if this is a room
home (Object) - safety start-location
sessions (list of Sessions, read-only) - returns all sessions connected
to this object
has_player (bool, read-only)- will only return *connected* players
has_account (bool, read-only)- will only return *connected* accounts
contents (list of Objects, read-only) - returns all objects inside this
object (including exits)
exits (list of Objects, read-only) - returns all exits from this
@ -73,7 +73,7 @@ class Object(DefaultObject):
* Helper methods (see src.objects.objects.py for full headers)
search(ostring, global_search=False, attribute_name=None,
use_nicks=False, location=None, ignore_errors=False, player=False)
use_nicks=False, location=None, ignore_errors=False, account=False)
execute_cmd(raw_string)
msg(text=None, **kwargs)
msg_contents(message, exclude=None, from_obj=None, **kwargs)
@ -105,14 +105,14 @@ class Object(DefaultObject):
requests a cmdset from this object. The kwargs are
not normally used unless the cmdset is created
dynamically (see e.g. Exits).
at_pre_puppet(player)- (player-controlled objects only) called just
at_pre_puppet(account)- (account-controlled objects only) called just
before puppeting
at_post_puppet() - (player-controlled objects only) called just
after completing connection player<->object
at_pre_unpuppet() - (player-controlled objects only) called just
at_post_puppet() - (account-controlled objects only) called just
after completing connection account<->object
at_pre_unpuppet() - (account-controlled objects only) called just
before un-puppeting
at_post_unpuppet(player) - (player-controlled objects only) called just
after disconnecting player<->object link
at_post_unpuppet(account) - (account-controlled objects only) called just
after disconnecting account<->object link
at_server_reload() - called before server is reloaded
at_server_shutdown() - called just before server is fully shut down

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-06 17:31
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('help', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='helpentry',
name='db_tags',
field=models.ManyToManyField(blank=True, help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
),
]

View file

@ -58,7 +58,7 @@ class HelpEntry(SharedMemoryModel):
# lock string storage
db_lock_storage = models.TextField('locks', blank=True, help_text='normally view:all().')
# tags are primarily used for permissions
db_tags = models.ManyToManyField(Tag, null=True,
db_tags = models.ManyToManyField(Tag, blank=True,
help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.')
# (deprecated, only here to allow MUX helpfile load (don't use otherwise)).
# TODO: remove this when not needed anymore.

View file

@ -28,38 +28,38 @@ MUX Name: Affects: Effect:
DefaultLock: Exits: controls who may traverse the exit to
its destination.
Evennia: "traverse:<lockfunc()>"
Rooms: controls whether the player sees the
Rooms: controls whether the account sees the
SUCC or FAIL message for the room
following the room description when
looking at the room.
Evennia: Custom typeclass
Players/Things: controls who may GET the object.
Accounts/Things: controls who may GET the object.
Evennia: "get:<lockfunc()"
EnterLock: Players/Things: controls who may ENTER the object
EnterLock: Accounts/Things: controls who may ENTER the object
Evennia:
GetFromLock: All but Exits: controls who may gets things from a
given location.
Evennia:
GiveLock: Players/Things: controls who may give the object.
GiveLock: Accounts/Things: controls who may give the object.
Evennia:
LeaveLock: Players/Things: controls who may LEAVE the object.
LeaveLock: Accounts/Things: controls who may LEAVE the object.
Evennia:
LinkLock: All but Exits: controls who may link to the location
if the location is LINK_OK (for linking
exits or setting drop-tos) or ABODE (for
setting homes)
Evennia:
MailLock: Players: controls who may @mail the player.
MailLock: Accounts: controls who may @mail the account.
Evennia:
OpenLock: All but Exits: controls who may open an exit.
Evennia:
PageLock: Players: controls who may page the player.
PageLock: Accounts: controls who may page the account.
Evennia: "send:<lockfunc()>"
ParentLock: All: controls who may make @parent links to
the object.
Evennia: Typeclasses and
"puppet:<lockstring()>"
ReceiveLock: Players/Things: controls who may give things to the
ReceiveLock: Accounts/Things: controls who may give things to the
object.
Evennia:
SpeechLock: All but Exits: controls who may speak in that location
@ -95,11 +95,11 @@ from evennia.utils import utils
_PERMISSION_HIERARCHY = [pe.lower() for pe in settings.PERMISSION_HIERARCHY]
def _to_player(accessing_obj):
"Helper function. Makes sure an accessing object is a player object"
def _to_account(accessing_obj):
"Helper function. Makes sure an accessing object is an account object"
if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject"):
# an object. Convert to player.
accessing_obj = accessing_obj.player
# an object. Convert to account.
accessing_obj = accessing_obj.account
return accessing_obj
@ -149,11 +149,11 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs):
If the given permission is part of settings.PERMISSION_HIERARCHY,
permission is also granted to all ranks higher up in the hierarchy.
If accessing_object is an Object controlled by a Player, the
permissions of the Player is used unless the Attribute _quell
If accessing_object is an Object controlled by an Account, the
permissions of the Account is used unless the Attribute _quell
is set to True on the Object. In this case however, the
LOWEST hieararcy-permission of the Player/Object-pair will be used
(this is order to avoid Players potentially escalating their own permissions
LOWEST hieararcy-permission of the Account/Object-pair will be used
(this is order to avoid Accounts potentially escalating their own permissions
by use of a higher-level Object)
"""
@ -166,29 +166,30 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs):
except (AttributeError, IndexError):
return False
if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject") and accessing_obj.player:
player = accessing_obj.player
perms_player = [p.lower() for p in player.permissions.all()]
is_quell = player.attributes.get("_quell")
if utils.inherits_from(accessing_obj, "evennia.objects.objects.DefaultObject") and accessing_obj.account:
account = accessing_obj.account
# we strip eventual plural forms, so Builders == Builder
perms_account = [p.lower().rstrip("s") for p in account.permissions.all()]
is_quell = account.attributes.get("_quell")
if permission in _PERMISSION_HIERARCHY:
# check hierarchy without allowing escalation obj->player
# check hierarchy without allowing escalation obj->account
hpos_target = _PERMISSION_HIERARCHY.index(permission)
hpos_player = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_player]
hpos_player = hpos_player and hpos_player[-1] or -1
hpos_account = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_account]
hpos_account = hpos_account and hpos_account[-1] or -1
if is_quell:
hpos_object = [hpos for hpos, hperm in enumerate(_PERMISSION_HIERARCHY) if hperm in perms_object]
hpos_object = hpos_object and hpos_object[-1] or -1
if gtmode:
return hpos_target < min(hpos_player, hpos_object)
return hpos_target < min(hpos_account, hpos_object)
else:
return hpos_target <= min(hpos_player, hpos_object)
return hpos_target <= min(hpos_account, hpos_object)
elif gtmode:
return hpos_target < hpos_player
return hpos_target < hpos_account
else:
return hpos_target <= hpos_player
elif not is_quell and permission in perms_player:
# if we get here, check player perms first, otherwise
return hpos_target <= hpos_account
elif not is_quell and permission in perms_account:
# if we get here, check account perms first, otherwise
# continue as normal
return True
@ -216,7 +217,7 @@ def perm_above(accessing_obj, accessed_obj, *args, **kwargs):
def pperm(accessing_obj, accessed_obj, *args, **kwargs):
"""
The basic permission-checker only for Player objects. Ignores case.
The basic permission-checker only for Account objects. Ignores case.
Usage:
pperm(<permission>)
@ -226,17 +227,17 @@ def pperm(accessing_obj, accessed_obj, *args, **kwargs):
is part of _PERMISSION_HIERARCHY, permission is also granted
to all ranks higher up in the hierarchy.
"""
return perm(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
return perm(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
def pperm_above(accessing_obj, accessed_obj, *args, **kwargs):
"""
Only allow Player objects with a permission *higher* in the permission
Only allow Account objects with a permission *higher* in the permission
hierarchy than the one given. If there is no such higher rank,
it's assumed we refer to superuser. If no hierarchy is defined,
this function has no meaning and returns False.
"""
return perm_above(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
return perm_above(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
def dbref(accessing_obj, accessed_obj, *args, **kwargs):
@ -262,9 +263,9 @@ def dbref(accessing_obj, accessed_obj, *args, **kwargs):
def pdbref(accessing_obj, accessed_obj, *args, **kwargs):
"""
Same as dbref, but making sure accessing_obj is a player.
Same as dbref, but making sure accessing_obj is an account.
"""
return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
return dbref(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
def id(accessing_obj, accessed_obj, *args, **kwargs):
@ -273,8 +274,8 @@ def id(accessing_obj, accessed_obj, *args, **kwargs):
def pid(accessing_obj, accessed_obj, *args, **kwargs):
"Alias to dbref, for Players"
return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
"Alias to dbref, for Accounts"
return dbref(_to_account(accessing_obj), accessed_obj, *args, **kwargs)
# this is more efficient than multiple if ... elif statments
@ -565,15 +566,15 @@ def superuser(*args, **kwargs):
"""
return False
def has_player(accessing_obj, accessed_obj, *args, **kwargs):
def has_account(accessing_obj, accessed_obj, *args, **kwargs):
"""
Only returns true if accessing_obj has_player is true, that is,
this is a player-controlled object. It fails on actual players!
Only returns true if accessing_obj has_account is true, that is,
this is an account-controlled object. It fails on actual accounts!
This is a useful lock for traverse-locking Exits to restrain NPC
mobiles from moving outside their areas.
"""
return hasattr(accessing_obj, "has_player") and accessing_obj.has_player
return hasattr(accessing_obj, "has_account") and accessing_obj.has_account
def serversetting(accessing_obj, accessed_obj, *args, **kwargs):
"""

View file

@ -56,21 +56,21 @@ Example:
We want to limit who may edit a particular object (let's call this access_type
for 'edit', it depends on what the command is looking for). We want this to
only work for those with the Permission 'Builders'. So we use our lock
only work for those with the Permission 'Builder'. So we use our lock
function above and define it like this:
'edit:perm(Builders)'
'edit:perm(Builder)'
Here, the lock-function perm() will be called with the string
'Builders' (accessing_obj and accessed_obj are added automatically,
'Builder' (accessing_obj and accessed_obj are added automatically,
you only need to add the args/kwargs, if any).
If we wanted to make sure the accessing object was BOTH a Builders and a
If we wanted to make sure the accessing object was BOTH a Builder and a
GoodGuy, we could use AND:
'edit:perm(Builders) AND perm(GoodGuy)'
'edit:perm(Builder) AND perm(GoodGuy)'
To allow EITHER Builders and GoodGuys, we replace AND with OR. perm() is just
To allow EITHER Builder and GoodGuys, we replace AND with OR. perm() is just
one example, the lock function can do anything and compare any properties of
the calling object to decide if the lock is passed or not.
@ -79,7 +79,7 @@ the calling object to decide if the lock is passed or not.
To make these work, add the string to the lockhandler of the object you want
to apply the lock to:
obj.lockhandler.add('edit:perm(Builders)')
obj.lockhandler.add('edit:perm(Builder)')
From then on, a command that wants to check for 'edit' access on this
object would do something like this:
@ -271,10 +271,10 @@ class LockHandler(object):
def cache_lock_bypass(self, obj):
"""
We cache superuser bypass checks here for efficiency. This
needs to be re-run when a player is assigned to a character.
needs to be re-run when an account is assigned to a character.
We need to grant access to superusers. We need to check both
directly on the object (players), through obj.player and using
the get_player() method (this sits on serversessions, in some
directly on the object (accounts), through obj.account and using
the get_account() method (this sits on serversessions, in some
rare cases where a check is done before the login process has
yet been fully finalized)
@ -450,8 +450,8 @@ class LockHandler(object):
except AttributeError:
# happens before session is initiated.
if not no_superuser_bypass and ((hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser)
or (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser)
or (hasattr(accessing_obj, 'get_player') and (not accessing_obj.get_player() or accessing_obj.get_player().is_superuser))):
or (hasattr(accessing_obj, 'account') and hasattr(accessing_obj.account, 'is_superuser') and accessing_obj.account.is_superuser)
or (hasattr(accessing_obj, 'get_account') and (not accessing_obj.get_account() or accessing_obj.get_account().is_superuser))):
return True
# no superuser or bypass -> normal lock operation
@ -511,8 +511,8 @@ class LockHandler(object):
return True
except AttributeError:
if no_superuser_bypass and ((hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser)
or (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser)
or (hasattr(accessing_obj, 'get_player') and (not accessing_obj.get_player() or accessing_obj.get_player().is_superuser))):
or (hasattr(accessing_obj, 'account') and hasattr(accessing_obj.account, 'is_superuser') and accessing_obj.account.is_superuser)
or (hasattr(accessing_obj, 'get_account') and (not accessing_obj.get_account() or accessing_obj.get_account().is_superuser))):
return True
if not ":" in lockstring:
lockstring = "%s:%s" % ("_dummy", lockstring)
@ -541,13 +541,13 @@ def _test():
obj1 = TestObj()
obj2 = TestObj()
#obj1.lock_storage = "owner:dbref(#4);edit:dbref(#5) or perm(Wizards);examine:perm(Builders);delete:perm(Wizards);get:all()"
#obj1.lock_storage = "owner:dbref(#4);edit:dbref(#5) or perm(Admin);examine:perm(Builder);delete:perm(Admin);get:all()"
#obj1.lock_storage = "cmd:all();admin:id(1);listen:all();send:all()"
obj1.lock_storage = "listen:perm(Immortals)"
obj1.lock_storage = "listen:perm(Developer)"
pdb.set_trace()
obj1.locks = LockHandler(obj1)
obj2.permissions.add("Immortals")
obj2.permissions.add("Developer")
obj2.id = 4
#obj1.locks.add("edit:attr(test)")

View file

@ -25,8 +25,8 @@ from evennia.locks import lockfuncs
class TestLockCheck(EvenniaTest):
def testrun(self):
dbref = self.obj2.dbref
self.obj1.locks.add("owner:dbref(%s);edit:dbref(%s) or perm(Wizards);examine:perm(Builders) and id(%s);delete:perm(Wizards);get:all()" % (dbref, dbref, dbref))
self.obj2.permissions.add('Wizards')
self.obj1.locks.add("owner:dbref(%s);edit:dbref(%s) or perm(Admin);examine:perm(Builder) and id(%s);delete:perm(Admin);get:all()" % (dbref, dbref, dbref))
self.obj2.permissions.add('Admin')
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'owner'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'edit'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'examine'))
@ -39,11 +39,11 @@ class TestLockCheck(EvenniaTest):
class TestLockfuncs(EvenniaTest):
def testrun(self):
self.obj2.permissions.add('Wizards')
self.obj2.permissions.add('Admin')
self.assertEquals(True, lockfuncs.true(self.obj2, self.obj1))
self.assertEquals(False, lockfuncs.false(self.obj2, self.obj1))
self.assertEquals(True, lockfuncs.perm(self.obj2, self.obj1, 'Wizards'))
self.assertEquals(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builders'))
self.assertEquals(True, lockfuncs.perm(self.obj2, self.obj1, 'Admin'))
self.assertEquals(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builder'))
dbref = self.obj2.dbref
self.assertEquals(True, lockfuncs.dbref(self.obj2, self.obj1, '%s' % dbref))
self.obj2.db.testattr = 45

View file

@ -81,9 +81,9 @@ class ObjectDBAdmin(admin.ModelAdmin):
"""
inlines = [ObjectTagInline, ObjectAttributeInline]
list_display = ('id', 'db_key', 'db_player', 'db_typeclass_path')
list_display = ('id', 'db_key', 'db_account', 'db_typeclass_path')
list_display_links = ('id', 'db_key')
ordering = ['db_player', 'db_typeclass_path', 'id']
ordering = ['db_account', 'db_typeclass_path', 'id']
search_fields = ['^db_key', 'db_typeclass_path']
raw_id_fields = ('db_destination', 'db_location', 'db_home')

View file

@ -7,7 +7,6 @@ from django.db.models import Q
from django.conf import settings
from django.db.models.fields import exceptions
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
from evennia.typeclasses.managers import returns_typeclass, returns_typeclass_list
from evennia.utils.utils import to_unicode, is_iter, make_iter, string_partial_matching
from builtins import int
@ -36,7 +35,7 @@ class ObjectDBManager(TypedObjectManager):
get_dbref_range
object_totals
typeclass_search
get_object_with_player
get_object_with_account
get_objs_with_key_and_typeclass
get_objs_with_attr
get_objs_with_attr_match
@ -54,19 +53,18 @@ class ObjectDBManager(TypedObjectManager):
# ObjectManager Get methods
#
# player related
# account related
@returns_typeclass
def get_object_with_player(self, ostring, exact=True, candidates=None):
def get_object_with_account(self, ostring, exact=True, candidates=None):
"""
Search for an object based on its player's name or dbref.
Search for an object based on its account's name or dbref.
Args:
ostring (str or int): Search criterion or dbref. Searching
for a player is sometimes initiated by appending an `*` to
for an account is sometimes initiated by appending an `*` to
the beginning of the search criterion (e.g. in
local_and_global_search). This is stripped here.
exact (bool, optional): Require an exact player match.
exact (bool, optional): Require an exact account match.
candidates (list, optional): Only search among this list of possible
object candidates.
@ -83,9 +81,9 @@ class ObjectDBManager(TypedObjectManager):
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
if exact:
return self.filter(cand_restriction & Q(db_player__username__iexact=ostring))
return self.filter(cand_restriction & Q(db_account__username__iexact=ostring))
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)
ply_cands = self.filter(cand_restriction & Q(accountdb__username__istartswith=ostring)
).values_list("db_key", flat=True)
if candidates:
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True)
@ -93,7 +91,6 @@ class ObjectDBManager(TypedObjectManager):
else:
return string_partial_matching(ply_cands, ostring, ret_index=False)
@returns_typeclass_list
def get_objs_with_key_and_typeclass(self, oname, otypeclass_path, candidates=None):
"""
Returns objects based on simultaneous key and typeclass match.
@ -112,7 +109,6 @@ class ObjectDBManager(TypedObjectManager):
# attr/property related
@returns_typeclass_list
def get_objs_with_attr(self, attribute_name, candidates=None):
"""
Get objects based on having a certain Attribute defined.
@ -130,7 +126,6 @@ class ObjectDBManager(TypedObjectManager):
if obj]) or Q()
return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
@returns_typeclass_list
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
"""
Get all objects having the given attrname set to the given value.
@ -169,7 +164,6 @@ class ObjectDBManager(TypedObjectManager):
db_value=attribute_value)]
return chain(*results)
@returns_typeclass_list
def get_objs_with_db_property(self, property_name, candidates=None):
"""
Get all objects having a given db field property.
@ -191,7 +185,6 @@ class ObjectDBManager(TypedObjectManager):
except exceptions.FieldError:
return []
@returns_typeclass_list
def get_objs_with_db_property_value(self, property_name, property_value, candidates=None, typeclasses=None):
"""
Get objects with a specific field name and value.
@ -222,7 +215,6 @@ class ObjectDBManager(TypedObjectManager):
(property_name, type(property_value)))
return []
@returns_typeclass_list
def get_contents(self, location, excludeobj=None):
"""
Get all objects that has a location set to this one.
@ -238,7 +230,6 @@ class ObjectDBManager(TypedObjectManager):
exclude_restriction = Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q()
return self.filter(db_location=location).exclude(exclude_restriction)
@returns_typeclass_list
def get_objs_with_key_or_alias(self, ostring, exact=True,
candidates=None, typeclasses=None):
"""
@ -303,7 +294,6 @@ class ObjectDBManager(TypedObjectManager):
# main search methods and helper functions
@returns_typeclass_list
def search_object(self, searchdata,
attribute_name=None,
typeclass=None,
@ -431,6 +421,7 @@ class ObjectDBManager(TypedObjectManager):
return matches
# alias for backwards compatibility
object_search = search_object
search = search_object
#
# ObjectManager Copy method
@ -512,7 +503,7 @@ class ObjectDBManager(TypedObjectManager):
def clear_all_sessids(self):
"""
Clear the db_sessid field of all objects having also the
db_player field set.
db_account field set.
"""
self.filter(db_sessid__isnull=False).update(db_sessid=None)

View file

@ -8,7 +8,7 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('typeclasses', '0001_initial'),
('typeclasses', '0002_auto_20150109_0913'),
]
operations = [
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
('db_sessid', models.CommaSeparatedIntegerField(help_text=b'csv list of session ids of connected Player, if any.', max_length=32, null=True, verbose_name=b'session id')),
('db_sessid', models.CommaSeparatedIntegerField(help_text=b'csv list of session ids of connected Account, if any.', max_length=32, null=True, verbose_name=b'session id')),
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class.', max_length=255, null=True, verbose_name=b'cmdset', blank=True)),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_destination', models.ForeignKey(related_name=b'destinations_set', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='objects.ObjectDB', help_text=b'a destination, used only by exit objects.', null=True, verbose_name=b'destination')),

View file

@ -17,8 +17,8 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='objectdb',
name='db_player',
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'player', to=settings.AUTH_USER_MODEL, help_text=b'a Player connected to this object, if any.', null=True),
name='db_account',
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'account', to=settings.AUTH_USER_MODEL, help_text=b'an Account connected to this object, if any.', null=True),
preserve_default=True,
),
migrations.AddField(

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-06 17:31
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import re
class Migration(migrations.Migration):
dependencies = [
('objects', '0005_auto_20150403_2339'),
]
operations = [
migrations.AlterField(
model_name='objectdb',
name='db_attributes',
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
),
migrations.AlterField(
model_name='objectdb',
name='db_sessid',
field=models.CharField(help_text=b'csv list of session ids of connected Account, if any.', max_length=32, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:\\,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name=b'session id'),
),
migrations.AlterField(
model_name='objectdb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
),
]

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:27
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_copy_player_to_account'),
('objects', '0006_auto_20170606_1731'),
]
operations = [
migrations.AddField(
model_name='objectdb',
name='db_account',
field=models.ForeignKey(help_text=b'an Account connected to this object, if any.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.AccountDB', verbose_name=b'account'),
),
]

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:36
from __future__ import unicode_literals
from django.db import migrations
def forwards(apps, schema_editor):
try:
apps.get_model('accounts', 'AccountDB')
except LookupError:
return
AccountDB = apps.get_model('accounts', 'AccountDB')
ObjectDB = apps.get_model('objects', 'ObjectDB')
for object in ObjectDB.objects.all():
account = object.db_account
if account:
account = AccountDB.objects.get(id=account.id)
object.db_account = account
object.save(update_fields=['db_account'])
class Migration(migrations.Migration):
dependencies = [
('objects', '0007_objectdb_db_account'),
]
operations = [
migrations.RunPython(forwards)
]

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-06 20:41
from __future__ import unicode_literals
from django.db import migrations, connection
from django.db import OperationalError
def _table_exists(db_cursor, tablename):
"Returns bool if table exists or not"
sql_check_exists = "SELECT * from %s;" % tablename
try:
db_cursor.execute(sql_check_exists)
return True
except OperationalError:
return False
class Migration(migrations.Migration):
dependencies = [
('objects', '0008_auto_20170705_1736'),
]
db_cursor = connection.cursor()
if not _table_exists(db_cursor, "objectdb_db_player"):
# OBS - this is run BEFORE migrations are run!
operations = []
else:
operations = [
migrations.RemoveField(
model_name='objectdb',
name='db_player',
),
]

View file

@ -18,6 +18,7 @@ from builtins import object
from django.conf import settings
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import validate_comma_separated_integer_list
from evennia.typeclasses.models import TypedObject
from evennia.objects.manager import ObjectDBManager
@ -140,16 +141,16 @@ class ObjectDB(TypedObject):
The ObjectDB adds the following properties:
- player - optional connected player (always together with sessid)
- sessid - optional connection session id (always together with player)
- account - optional connected account (always together with sessid)
- sessid - optional connection session id (always together with account)
- location - in-game location of object
- home - safety location for object (handler)
- scripts - scripts assigned to object (handler from typeclass)
- cmdset - active cmdset on object (handler from typeclass)
- aliases - aliases for this object (property)
- nicks - nicknames for *other* things in Evennia (handler)
- sessions - sessions connected to this object (see also player)
- has_player - bool if an active player is currently connected
- sessions - sessions connected to this object (see also account)
- has_account - bool if an active account is currently connected
- contents - other objects having this object as location
- exits - exits from this object
@ -168,12 +169,14 @@ class ObjectDB(TypedObject):
# self.key instead). The wrappers are created at the metaclass level and
# will automatically save and cache the data more efficiently.
# If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", null=True, verbose_name='player', on_delete=models.SET_NULL,
help_text='a Player connected to this object, if any.')
# the session id associated with this player, if any
db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id",
help_text="csv list of session ids of connected Player, if any.")
# If this is a character object, the account is connected here.
db_account = models.ForeignKey("accounts.AccountDB", null=True, verbose_name='account', on_delete=models.SET_NULL,
help_text='an Account connected to this object, if any.')
# the session id associated with this account, if any
db_sessid = models.CharField(null=True, max_length=32, validators=[validate_comma_separated_integer_list],
verbose_name="session id",
help_text="csv list of session ids of connected Account, if any.")
# The location in the game world. Since this one is likely
# to change often, we set this with the 'location' property
# to transparently handle Typeclassing.

View file

@ -1,6 +1,6 @@
"""
This module defines the basic `DefaultObject` and its children
`DefaultCharacter`, `DefaultPlayer`, `DefaultRoom` and `DefaultExit`.
`DefaultCharacter`, `DefaultAccount`, `DefaultRoom` and `DefaultExit`.
These are the (default) starting points for all in-game visible
entities.
@ -21,7 +21,8 @@ from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.commands import cmdhandler
from evennia.utils import logger
from evennia.utils.utils import (variable_from_module, lazy_property,
make_iter, to_unicode, calledby, is_iter)
make_iter, to_unicode, is_iter)
from django.utils.translation import ugettext as _
_MULTISESSION_MODE = settings.MULTISESSION_MODE
@ -32,8 +33,6 @@ _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.',
# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
_SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
from django.utils.translation import ugettext as _
class ObjectSessionHandler(object):
"""
@ -206,9 +205,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return ObjectSessionHandler(self)
@property
def has_player(self):
def has_account(self):
"""
Convenience property for checking if an active player is
Convenience property for checking if an active account is
currently connected to this object.
"""
@ -217,11 +216,11 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
@property
def is_superuser(self):
"""
Check if user has a player, and if so, if it is a superuser.
Check if user has an account, and if so, if it is a superuser.
"""
return self.db_player and self.db_player.is_superuser \
and not self.db_player.attributes.get("_quell")
return self.db_account and self.db_account.is_superuser \
and not self.db_account.attributes.get("_quell")
def contents_get(self, exclude=None):
"""
@ -260,7 +259,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Displays the name of the object in a viewer-aware manner.
Args:
looker (TypedObject): The object or player that is looking
looker (TypedObject): The object or account that is looking
at/getting inforamtion for this object.
Returns:
@ -276,7 +275,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
builders.
"""
if self.locks.check_lockstring(looker, "perm(Builders)"):
if self.locks.check_lockstring(looker, "perm(Builder)"):
return "{}(#{})".format(self.name, self.id)
return self.name
@ -350,7 +349,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
otherwise it will return a list of 0, 1 or more matches.
Notes:
To find Players, use eg. `evennia.player_search`. If
To find Accounts, use eg. `evennia.account_search`. If
`quiet=False`, error messages will be handled by
`settings.SEARCH_AT_RESULT` and echoed automatically (on
error, return will be `None`). If `quiet=True`, the error
@ -368,7 +367,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
if use_nicks:
# do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "account"), include_account=True)
if (global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())):
@ -406,19 +405,19 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string)
def search_player(self, searchdata, quiet=False):
def search_account(self, searchdata, quiet=False):
"""
Simple shortcut wrapper to search for players, not characters.
Simple shortcut wrapper to search for accounts, not characters.
Args:
searchdata (str): Search criterion - the key or dbref of the player
searchdata (str): Search criterion - the key or dbref of the account
to search for. If this is "here" or "me", search
for the player connected to this object.
for the account connected to this object.
quiet (bool): Returns the results as a list rather than
echo eventual standard error messages. Default `False`.
Returns:
result (Player, None or list): Just what is returned depends on
result (Account, None or list): Just what is returned depends on
the `quiet` setting:
- `quiet=True`: No match or multumatch auto-echoes errors
to self.msg, then returns `None`. The esults are passed
@ -427,15 +426,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
unique match, this will be returned.
- `quiet=True`: No automatic error messaging is done, and
what is returned is always a list with 0, 1 or more
matching Players.
matching Accounts.
"""
if isinstance(searchdata, basestring):
# searchdata is a string; wrap some common self-references
if searchdata.lower() in ("me", "self",):
return [self.player] if quiet else self.player
return [self.account] if quiet else self.account
results = self.player.__class__.objects.player_search(searchdata)
results = self.account.__class__.objects.account_search(searchdata)
if quiet:
return results
@ -446,7 +445,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Do something as this object. This is never called normally,
it's only used when wanting specifically to let an object be
the caller of a command. It makes use of nicks of eventual
connected players as well.
connected accounts as well.
Args:
raw_string (string): Raw command input
@ -474,7 +473,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# nick replacement - we require full-word matching.
# do text encoding conversion
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs)
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
@ -755,7 +754,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
def clear_contents(self):
"""
Moves all objects (players/things) to their home location or
Moves all objects (accounts/things) to their home location or
to default home.
"""
# Gather up everything that thinks this is its location.
@ -786,13 +785,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
logger.log_err(string % (obj.name, obj.dbid))
return
if obj.has_player:
if obj.has_account:
if home:
string = "Your current location has ceased to exist,"
string += " moving you to %s(#%d)."
obj.msg(_(string) % (home.name, home.dbid))
else:
# Famous last words: The player should never see this.
# Famous last words: The account should never see this.
string = "This place should not exist ... contact an admin."
obj.msg(_(string))
obj.move_to(home)
@ -820,8 +819,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
key = self.key
num = 1
for _ in (obj for obj in self.location.contents
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
for inum in (obj for obj in self.location.contents
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
num += 1
return "%s%03i" % (key, num)
new_key = new_key or find_clone_key()
@ -858,16 +857,16 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.delete_iter += 1
# See if we need to kick the player off.
# See if we need to kick the account off.
for session in self.sessions.all():
session.msg(_("Your character %s has been destroyed.") % self.key)
# no need to disconnect, Player just jumps to OOC mode.
# no need to disconnect, Account just jumps to OOC mode.
# sever the connection (important!)
if self.player:
if self.account:
for session in self.sessions.all():
self.player.unpuppet_object(session)
self.player = None
self.account.unpuppet_object(session)
self.account = None
for script in _ScriptDB.objects.get_all_scripts_on_obj(self):
script.stop()
@ -948,21 +947,20 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.save(update_fields=updates)
if cdict.get("permissions"):
self.permissions.add(cdict["permissions"])
self.permissions.batch_add(*cdict["permissions"])
if cdict.get("locks"):
self.locks.add(cdict["locks"])
if cdict.get("aliases"):
self.aliases.add(cdict["aliases"])
self.aliases.batch_add(*cdict["aliases"])
if cdict.get("location"):
cdict["location"].at_object_receive(self, None)
self.at_after_move(None)
if cdict.get("tags"):
# this should be a list of tags
self.tags.add(cdict["tags"])
self.tags.batch_add(*cdict["tags"])
if cdict.get("attributes"):
# this should be a dict of attrname:value
keys, values = list(cdict["attributes"]), listvalues(cdict["attributes"])
self.attributes.batch_add(keys, values)
self.attributes.batch_add(*cdict["attributes"])
if cdict.get("nattributes"):
# this should be a dict of nattrname:value
for key, value in cdict["nattributes"].items():
@ -989,15 +987,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# controller, for example)
self.locks.add(";".join([
"control:perm(Immortals)", # edit locks/permissions, delete
"examine:perm(Builders)", # examine properties
"view:all()", # look at object (visibility)
"edit:perm(Wizards)", # edit properties/attributes
"delete:perm(Wizards)", # delete object
"get:all()", # pick up object
"call:true()", # allow to call commands on this object
"tell:perm(Wizards)", # allow emits to this object
"puppet:pperm(Immortals)"])) # lock down puppeting only to staff by default
"control:perm(Developer)", # edit locks/permissions, delete
"examine:perm(Builder)", # examine properties
"view:all()", # look at object (visibility)
"edit:perm(Admin)", # edit properties/attributes
"delete:perm(Admin)", # delete object
"get:all()", # pick up object
"call:true()", # allow to call commands on this object
"tell:perm(Admin)", # allow emits to this object
"puppet:pperm(Developer)"])) # lock down puppeting only to staff by default
def basetype_posthook_setup(self):
"""
@ -1048,61 +1046,72 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
have no cmdsets.
Kwargs:
caller (Session, Object or Player): The caller requesting
caller (Session, Object or Account): The caller requesting
this cmdset.
"""
pass
def at_pre_puppet(self, player, session=None):
def at_pre_puppet(self, account, session=None, **kwargs):
"""
Called just before a Player connects to this object to puppet
Called just before an Account connects to this object to puppet
it.
Args:
player (Player): This is the connecting player.
account (Account): This is the connecting account.
session (Session): Session controlling the connection.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_post_puppet(self):
def at_post_puppet(self, **kwargs):
"""
Called just after puppeting has been completed and all
Player<->Object links have been established.
Account<->Object links have been established.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Note:
You can use `self.player` and `self.sessions.get()` to get
player and sessions at this point; the last entry in the
You can use `self.account` and `self.sessions.get()` to get
account and sessions at this point; the last entry in the
list from `self.sessions.get()` is the latest Session
puppeting this Object.
"""
self.player.db._last_puppet = self
self.account.db._last_puppet = self
def at_pre_unpuppet(self):
def at_pre_unpuppet(self, **kwargs):
"""
Called just before beginning to un-connect a puppeting from
this Player.
this Account.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Note:
You can use `self.player` and `self.sessions.get()` to get
player and sessions at this point; the last entry in the
You can use `self.account` and `self.sessions.get()` to get
account and sessions at this point; the last entry in the
list from `self.sessions.get()` is the latest Session
puppeting this Object.
"""
pass
def at_post_unpuppet(self, player, session=None):
def at_post_unpuppet(self, account, session=None, **kwargs):
"""
Called just after the Player successfully disconnected from
Called just after the Account successfully disconnected from
this object, severing all connections.
Args:
player (Player): The player object that just disconnected
account (Account): The account object that just disconnected
from this object.
session (Session): Session id controlling the connection that
just disconnected.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
@ -1134,7 +1143,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
result (bool): The outcome of the access call.
accessing_obj (Object or Player): The entity trying to gain access.
accessing_obj (Object or Account): The entity trying to gain access.
access_type (str): The type of access that was requested.
Kwargs:
@ -1146,13 +1155,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# hooks called when moving the object
def at_before_move(self, destination):
def at_before_move(self, destination, **kwargs):
"""
Called just before starting to move this object to
destination.
Args:
destination (Object): The object we are moving to
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
shouldmove (bool): If we should move or not.
@ -1165,7 +1176,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# return has_perm(self, destination, "can_move")
return True
def announce_move_from(self, destination, msg=None, mapping=None):
def announce_move_from(self, destination, msg=None, mapping=None, **kwargs):
"""
Called if the move is to be announced. This is
called while we are still standing in the old
@ -1175,6 +1186,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
destination (Object): The place we are going to.
msg (str, optional): a replacement message.
mapping (dict, optional): additional mapping objects.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
You can override this method and call its parent with a
message to simply change the default message. In the string,
@ -1206,7 +1219,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
location.msg_contents(string, exclude=(self, ), mapping=mapping)
def announce_move_to(self, source_location, msg=None, mapping=None):
def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs):
"""
Called after the move if the move was not quiet. At this point
we are standing in the new location.
@ -1215,19 +1228,22 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
source_location (Object): The place we came from
msg (str, optional): the replacement message if location.
mapping (dict, optional): additional mapping objects.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
Notes:
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
if not source_location and self.location.has_player:
# This was created from nowhere and added to a player's
if not source_location and self.location.has_account:
# This was created from nowhere and added to an account's
# inventory; it's probably the result of a create command.
string = "You now have %s in your possession." % self.get_display_name(self.location)
self.location.msg(string)
@ -1259,7 +1275,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
destination.msg_contents(string, exclude=(self, ), mapping=mapping)
def at_after_move(self, source_location):
def at_after_move(self, source_location, **kwargs):
"""
Called after move has completed, regardless of quiet mode or
not. Allows changes to the object due to the location it is
@ -1267,22 +1283,26 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
source_location (Object): Wwhere we came from. This may be `None`.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_object_leave(self, moved_obj, target_location):
def at_object_leave(self, moved_obj, target_location, **kwargs):
"""
Called just before an object leaves from inside this object
Args:
moved_obj (Object): The object leaving
target_location (Object): Where `moved_obj` is going.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_object_receive(self, moved_obj, source_location):
def at_object_receive(self, moved_obj, source_location, **kwargs):
"""
Called after an object has been moved into this object.
@ -1290,11 +1310,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
moved_obj (Object): The object moved into this one
source_location (Object): Where `moved_object` came from.
Note that this could be `None`.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_traverse(self, traversing_object, target_location):
def at_traverse(self, traversing_object, target_location, **kwargs):
"""
This hook is responsible for handling the actual traversal,
normally by calling
@ -1307,11 +1329,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
traversing_object (Object): Object traversing us.
target_location (Object): Where target is going.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_after_traverse(self, traversing_object, source_location):
def at_after_traverse(self, traversing_object, source_location, **kwargs):
"""
Called just after an object successfully used this object to
traverse to another object (i.e. this object is a type of
@ -1320,19 +1344,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
traversing_object (Object): The object traversing us.
source_location (Object): Where `traversing_object` came from.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
The target location should normally be available as `self.destination`.
"""
pass
def at_failed_traverse(self, traversing_object):
def at_failed_traverse(self, traversing_object, **kwargs):
"""
This is called if an object fails to traverse this object for
some reason.
Args:
traversing_object (Object): The object that failed traversing us.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
Using the default exits, this hook will not be called if an
@ -1393,13 +1421,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# hooks called by the default cmdset.
def return_appearance(self, looker):
def return_appearance(self, looker, **kwargs):
"""
This formats a description. It is the hook a 'look' command
should call.
Args:
looker (Object): Object doing the looking.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
if not looker:
return ""
@ -1411,7 +1441,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
key = con.get_display_name(looker)
if con.destination:
exits.append(key)
elif con.has_player:
elif con.has_account:
users.append("|c%s|n" % key)
else:
things.append(key)
@ -1426,7 +1456,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
string += "\n|wYou see:|n " + ", ".join(users + things)
return string
def at_look(self, target):
def at_look(self, target, **kwargs):
"""
Called when this object performs a look. It allows to
customize just what this means. It will not itself
@ -1436,6 +1466,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
target (Object): The target being looked at. This is
commonly an object or the current location. It will
be checked for the "view" type access.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
lookstring (str): A ready-processed look string
@ -1456,22 +1488,27 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return description
def at_desc(self, looker=None):
def at_desc(self, looker=None, **kwargs):
"""
This is called whenever someone looks at this object.
looker (Object): The object requesting the description.
Args:
looker (Object, optional): The object requesting the description.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
pass
def at_get(self, getter):
def at_get(self, getter, **kwargs):
"""
Called by the default `get` command when this object has been
picked up.
Args:
getter (Object): The object getting this object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
This hook cannot stop the pickup from happening. Use
@ -1480,7 +1517,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
pass
def at_give(self, giver, getter):
def at_give(self, giver, getter, **kwargs):
"""
Called by the default `give` command when this object has been
given.
@ -1488,6 +1525,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
giver (Object): The object giving this object.
getter (Object): The object getting this object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
This hook cannot stop the give from happening. Use
@ -1496,13 +1535,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
pass
def at_drop(self, dropper):
def at_drop(self, dropper, **kwargs):
"""
Called by the default `drop` command when this object has been
dropped.
Args:
dropper (Object): The object which just dropped this object.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
This hook cannot stop the drop from happening. Use
@ -1511,149 +1552,103 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
pass
def at_before_say(self, speech):
def at_before_say(self, message, **kwargs):
"""
Before the object says something.
This hook is called by the 'say' command on the object itself
(probably a character). It is called before the actual say,
and can be used to control the content of the text to be said,
prevent saying altogether or perform some alternative checks.
This hook should return the modified speech. If this return
value is empty (like "" or None), the command is aborted.
This hook is by default used by the 'say' and 'whisper'
commands as used by this command it is called before the text
is said/whispered and can be used to customize the outgoing
text from the object. Returning `None` aborts the command.
Args:
speech (str): the text to be said by self.
message (str): The suggested say/whisper text spoken by self.
Kwargs:
whisper (bool): If True, this is a whisper rather than
a say. This is sent by the whisper command by default.
Other verbal commands could use this hook in similar
ways.
receiver (Object): If set, this is a target for the say/whisper.
Returns:
speech (str): the text to be said (can be modified).
message (str): The (possibly modified) text to be spoken.
"""
return speech
return message
def at_after_say(self, speech, msg_self=None, msg_location=None,
mapping=None):
def at_say(self, message, msg_self=None, msg_location=None,
receiver=None, msg_receiver=None, mapping=None, **kwargs):
"""
Display the actual say of self.
Display the actual say (or whisper) of self.
This hook should display the actual say of the object in its
This hook should display the actual say/whisper of the object in its
location. It should both alert the object (self) and its
location that some text is spoken. The overriding of messages or
`mapping` allows for simple customization of the hook without
re-writing it completely.
Args:
speech (str): the text to be said by self.
msg_self (str, optional): the replacement message to say to self.
msg_location (str, optional): the replacement message to say
to the location.
message (str): The text to be conveyed by self.
msg_self (str, optional): The message to echo to self.
msg_location (str, optional): The message to echo to self's location.
receiver (Object, optional): An eventual receiver of the message
(by default only used by whispers).
msg_receiver(str, optional): Specific message for receiver only.
mapping (dict, optional): Additional mapping in messages.
Kwargs:
whisper (bool): If this is a whisper rather than a say. Kwargs
can be used by other verbal commands in a similar way.
Both `msg_self` and `msg_location` should contain references
to other objects between braces, the way `locaiton.msg_contents`
would allow. For instance:
msg_self = 'You say: "{speech}"'
msg_location = '{object} says: "{speech}"'
Notes:
The following mappings can be used in both messages:
object: the object speaking.
location: the location where object is.
speech: the text spoken by self.
Messages can contain {} markers, which must
If used, `msg_self`, `msg_receiver` and `msg_location` should contain
references to other objects between braces, the way `location.msg_contents`
would allow. For instance:
msg_self = 'You say: "{speech}"'
msg_location = '{object} says: "{speech}"'
msg_receiver = '{object} whispers: "{speech}"'
You can use additional mappings if you want to add other
information in your messages.
The following mappings can be used in both messages:
object: the object speaking.
location: the location where object is.
speech: the text spoken by self.
You can use additional mappings if you want to add other
information in your messages.
"""
if self.location is None:
self.msg("You can't utter a sound in the void.")
return
if kwargs.get("whisper", False):
# whisper mode
msg_self = msg_self or 'You whisper to {receiver}, "{speech}"|n'
msg_receiver = msg_receiver or '{object} whispers: "{speech}"|n'
msg_location = None
else:
msg_self = msg_self or 'You say, "{speech}"|n'
msg_receiver = None
msg_location = msg_location or '{object} says, "{speech}"|n'
msg_self = msg_self or 'You say, "{speech}"|n'
msg_location = msg_location or '{object} says, "{speech}"|n'
mapping = mapping or {}
mapping.update({
"object": self,
"location": self.location,
"speech": speech,
})
self_mapping = {k: v.get_display_name(self) if hasattr(
v, "get_display_name") else str(v) for k, v in mapping.items()}
self.msg(msg_self.format(**self_mapping))
self.location.msg_contents(msg_location, exclude=(self, ),
mapping=mapping)
"speech": message,
"receiver": receiver
})
def at_before_whisper(self, receiver, speech):
"""
Before the object whispers something to receiver.
if msg_self:
self_mapping = {k: v.get_display_name(self) if hasattr(
v, "get_display_name") else str(v) for k, v in mapping.items()}
self.msg(msg_self.format(**self_mapping))
This hook is called by the 'whisper' command on the object itself
(probably a character). It is called before the actual whisper,
and can be used to control the content of the text to be whispered,
prevent whispering altogether or perform some alternative checks.
This hook should return the modified speech. If this return
value is empty (like "" or None), the command is aborted.
Args:
receiver (Object): the object to whisper to.
speech (str): the text to be whispered by self.
Returns:
speech (str): the text to be whispered (can be modified).
"""
return speech
def at_after_whisper(self, receiver, speech, msg_self=None,
msg_receiver=None, mapping=None):
"""
Display the actual whisper of self.
This hook should display the actual whisper of the object to
receiver. It should both alert the object (self) and the
receiver. You can also notify the location if you want to,
to indicate to others that a message was whispered but you
can't hear it. The overriding of messages or
`mapping` allows for simple customization of the hook without
re-writing it completely.
Args:
receiver (Objecvt): the object to whisper to.
speech (str): the text to be said by self.
msg_self (str, optional): the replacement message to say to self.
msg_receiver (str, optional): the replacement message to say
to receiver.
mapping (dict, optional): Additional mapping in messages.
Both `msg_self` and `msg_receiver` should contain references
to other objects between braces, the way `locaiton.msg_contents`
would allow. For instance:
msg_self = 'You whisper to {receiver}, "{speech}"|n'
msg_receiver = '{object} whispers: "{speech}"|n'
The following mappings can be used in both messages:
object: the object whispering.
receiver: the object whispered to.
speech: the text spoken by self.
You can use additional mappings if you want to add other
information in your messages.
"""
msg_self = msg_self or 'You whisper to {receiver}, "{speech}"|n'
msg_receiver = msg_receiver or '{object} whispers: "{speech}"|n'
mapping = mapping or {}
mapping.update({
"object": self,
"receiver": receiver,
"speech": speech,
})
self_mapping = {k: v.get_display_name(self) if hasattr(
v, "get_display_name") else str(v) for k, v in mapping.items()}
receiver_mapping = {k: v.get_display_name(receiver) if hasattr(
v, "get_display_name") else str(v) for k, v in mapping.items()}
self.msg(msg_self.format(**self_mapping))
receiver.msg(msg_receiver.format(**receiver_mapping))
if receiver and msg_receiver:
receiver_mapping = {k: v.get_display_name(receiver) if hasattr(
v, "get_display_name") else str(v) for k, v in mapping.items()}
receiver.msg(msg_receiver.format(**receiver_mapping))
if self.location and msg_location:
self.location.msg_contents(msg_location, exclude=(self, ),
mapping=mapping)
#
@ -1663,7 +1658,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
class DefaultCharacter(DefaultObject):
"""
This implements an Object puppeted by a Session - that is,
a character avatar controlled by a player.
a character avatar controlled by an account.
"""
@ -1683,7 +1678,7 @@ class DefaultCharacter(DefaultObject):
# add the default cmdset
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
def at_after_move(self, source_location):
def at_after_move(self, source_location, **kwargs):
"""
We make sure to look around after a move.
@ -1691,11 +1686,11 @@ class DefaultCharacter(DefaultObject):
if self.location.access(self, "view"):
self.msg(self.at_look(self.location))
def at_pre_puppet(self, player, session=None):
def at_pre_puppet(self, account, session=None, **kwargs):
"""
Return the character from storage in None location in `at_post_unpuppet`.
Args:
player (Player): This is the connecting player.
account (Account): This is the connecting account.
session (Session): Session controlling the connection.
"""
if self.location is None: # Make sure character's location is never None before being puppeted.
@ -1705,16 +1700,19 @@ class DefaultCharacter(DefaultObject):
if self.location: # If the character is verified to be somewhere,
self.db.prelogout_location = self.location # save location again to be sure.
else:
player.msg("|r%s has no location and no home is set.|n" % self, session=session) # Note to set home.
account.msg("|r%s has no location and no home is set.|n" % self, session=session) # Note to set home.
def at_post_puppet(self):
def at_post_puppet(self, **kwargs):
"""
Called just after puppeting has been completed and all
Player<->Object links have been established.
Account<->Object links have been established.
Args:
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Note:
You can use `self.player` and `self.sessions.get()` to get
player and sessions at this point; the last entry in the
You can use `self.account` and `self.sessions.get()` to get
account and sessions at this point; the last entry in the
list from `self.sessions.get()` is the latest Session
puppeting this Object.
@ -1726,17 +1724,19 @@ class DefaultCharacter(DefaultObject):
obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj)
self.location.for_contents(message, exclude=[self], from_obj=self)
def at_post_unpuppet(self, player, session=None):
def at_post_unpuppet(self, account, session=None, **kwargs):
"""
We stove away the character when the player goes ooc/logs off,
We stove away the character when the account goes ooc/logs off,
otherwise the character object will remain in the room also
after the player logged off ("headless", so to say).
after the account logged off ("headless", so to say).
Args:
player (Player): The player object that just disconnected
account (Account): The account object that just disconnected
from this object.
session (Session): Session controlling the connection that
just disconnected.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
if not self.sessions.count():
# only remove this char from grid if no sessions control it anymore.
@ -1826,6 +1826,8 @@ class ExitCommand(command.Command):
Args:
caller (Object): The object (usually a character) that entered an ambiguous command.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Returns:
A string with identifying information to disambiguate the command, conventionally with a preceding space.
@ -1862,7 +1864,7 @@ class DefaultExit(DefaultObject):
Helper function for creating an exit command set + command.
The command of this cmdset has the same name as the Exit
object and allows the exit to react when the player enter the
object and allows the exit to react when the account enter the
exit's name, triggering the movement between rooms.
Args:
@ -1936,7 +1938,7 @@ class DefaultExit(DefaultObject):
"""
self.cmdset.remove_default()
def at_traverse(self, traversing_object, target_location):
def at_traverse(self, traversing_object, target_location, **kwargs):
"""
This implements the actual traversal. The traverse lock has
already been checked (in the Exit command) at this point.
@ -1944,6 +1946,8 @@ class DefaultExit(DefaultObject):
Args:
traversing_object (Object): Object traversing us.
target_location (Object): Where target is going.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
"""
source_location = traversing_object.location
@ -1957,12 +1961,14 @@ class DefaultExit(DefaultObject):
# No shorthand error message. Call hook.
self.at_failed_traverse(traversing_object)
def at_failed_traverse(self, traversing_object):
def at_failed_traverse(self, traversing_object, **kwargs):
"""
Overloads the default hook to implement a simple default error message.
Args:
traversing_object (Object): The object that failed traversing us.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
Notes:
Using the default exits, this hook will not be called if an

View file

@ -1,6 +0,0 @@
"""
This sub-package defines the out-of-character entities known as
Players. These are equivalent to 'accounts' and can puppet one or
more Objects depending on settings. A Player has no in-game existence.
"""

View file

@ -1,6 +1,6 @@
"""
This sub-package holds the Scripts system. Scripts are database
entities that can store data both in connection to Objects and Players
entities that can store data both in connection to Objects and Accounts
or globally. They may also have a timer-component to execute various
timed effects.

View file

@ -4,7 +4,6 @@ The custom manager for Scripts.
from django.db.models import Q
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
from evennia.typeclasses.managers import returns_typeclass_list
from evennia.utils.utils import make_iter
__all__ = ("ScriptManager",)
_GA = object.__getattribute__
@ -35,7 +34,6 @@ class ScriptDBManager(TypedObjectManager):
copy_script
"""
@returns_typeclass_list
def get_all_scripts_on_obj(self, obj, key=None):
"""
Find all Scripts related to a particular object.
@ -51,24 +49,23 @@ class ScriptDBManager(TypedObjectManager):
"""
if not obj:
return []
player = _GA(_GA(obj, "__dbclass__"), "__name__") == "PlayerDB"
account = _GA(_GA(obj, "__dbclass__"), "__name__") == "AccountDB"
if key:
dbref = self.dbref(key)
if dbref or dbref == 0:
if player:
return self.filter(db_player=obj, id=dbref)
if account:
return self.filter(db_account=obj, id=dbref)
else:
return self.filter(db_obj=obj, id=dbref)
elif player:
return self.filter(db_player=obj, db_key=key)
elif account:
return self.filter(db_account=obj, db_key=key)
else:
return self.filter(db_obj=obj, db_key=key)
elif player:
return self.filter(db_player=obj)
elif account:
return self.filter(db_account=obj)
else:
return self.filter(db_obj=obj)
@returns_typeclass_list
def get_all_scripts(self, key=None):
"""
Get all scripts in the database.
@ -200,7 +197,7 @@ class ScriptDBManager(TypedObjectManager):
elif obj:
scripts = self.get_all_scripts_on_obj(obj, key=key)
else:
scripts = self.get_all_scripts(key=key) #self.model.get_all_cached_instances()
scripts = self.get_all_scripts(key=key)
if not scripts:
# no scripts available to validate
@ -216,7 +213,6 @@ class ScriptDBManager(TypedObjectManager):
VALIDATE_ITERATION -= 1
return nr_started, nr_stopped
@returns_typeclass_list
def search_script(self, ostring, obj=None, only_timed=False):
"""
Search for a particular script.
@ -236,8 +232,8 @@ class ScriptDBManager(TypedObjectManager):
if dbref or dbref == 0:
# this is a dbref, try to find the script directly
dbref_match = self.dbref_search(dbref)
if dbref_match and not ((obj and obj != dbref_match.obj)
or (only_timed and dbref_match.interval)):
if dbref_match and not ((obj and obj != dbref_match.obj) or
(only_timed and dbref_match.interval)):
return [dbref_match]
# not a dbref; normal search
@ -273,5 +269,6 @@ class ScriptDBManager(TypedObjectManager):
locks=new_locks, autostart=True)
return new_script
class ScriptManager(ScriptDBManager, TypeclassManager):
pass

View file

@ -30,7 +30,7 @@ class Migration(migrations.Migration):
('db_is_active', models.BooleanField(default=False, verbose_name=b'script active')),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_obj', models.ForeignKey(blank=True, to='objects.ObjectDB', help_text=b'the object to store this script on, if not a global script.', null=True, verbose_name=b'scripted object')),
('db_player', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, help_text=b'the player to store this script on (should not be set if obj is set)', null=True, verbose_name=b'scripted player')),
('db_account', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, help_text=b'the account to store this script on (should not be set if obj is set)', null=True, verbose_name=b'scripted account')),
('db_tags', models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True)),
],
options={

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-06 17:31
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scripts', '0007_auto_20150403_2339'),
]
operations = [
migrations.AlterField(
model_name='scriptdb',
name='db_attributes',
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'),
),
migrations.AlterField(
model_name='scriptdb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'),
),
]

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:27
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_copy_player_to_account'),
('scripts', '0008_auto_20170606_1731'),
]
operations = [
migrations.AddField(
model_name='scriptdb',
name='db_account',
field=models.ForeignKey(blank=True, help_text=b'the account to store this script on (should not be set if db_obj is set)', null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.AccountDB', verbose_name=b'scripted account'),
),
]

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-07-05 17:36
from __future__ import unicode_literals
from django.db import migrations
def forwards(apps, schema_editor):
try:
apps.get_model('accounts', 'AccountDB')
except LookupError:
return
AccountDB = apps.get_model('accounts', 'AccountDB')
ScriptDB = apps.get_model('scripts', 'ScriptDB')
for script in ScriptDB.objects.all():
account = script.db_account
if account:
account = AccountDB.objects.get(id=account.id)
script.db_account = account
script.save(update_fields=['db_account'])
class Migration(migrations.Migration):
dependencies = [
('scripts', '0009_scriptdb_db_account'),
]
operations = [
migrations.RunPython(forwards)
]

Some files were not shown because too many files have changed in this diff Show more