Run Migrate. Implemented a full separation between Player and Character - Players (OOC entities) can now also hold cmdsets and execute commands. This means that "disconnecting" from a Character becomes possible, putting the Player in an "OOC" state outside the game. This overall makes the game much more stable since there used to be issues if the character was destroyed. Having an OOC set also avoids the previous problem of @puppeting into an object that didn't have any cmdset of its own - you couldn't get back out! A new default OOC-Cmdset handles commands available to a player while OOC. Commands in this set are applied with a low priority, allowing "IC" mode to give precedence if desired.

This change meant several changes to the lock and permission functionality, since it becomes important if permissions are assigned on the Player or on their Character (lock functions pperm() and pid() etc check on Player rather than Character). This has the boon of allowing Admins to switch and play/test the game as a "Low access" character as they like.

Plenty of bug fixes and adjustments. Migrations should make sure to move over all data properly.
This commit is contained in:
Griatch 2011-04-23 11:54:08 +00:00
parent ce2a8e9ffe
commit 28fe2ad3f4
37 changed files with 1622 additions and 555 deletions

View file

@ -19,7 +19,7 @@ new cmdset class.
"""
from src.commands.cmdset import CmdSet
from src.commands.default import cmdset_default, cmdset_unloggedin
from src.commands.default import cmdset_default, cmdset_unloggedin, cmdset_ooc
from game.gamesrc.commands.basecommand import Command
@ -40,7 +40,7 @@ class DefaultCmdSet(cmdset_default.DefaultCmdSet):
Populates the cmdset
"""
super(DefaultCmdSet, self).at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#
@ -70,6 +70,24 @@ class UnloggedinCmdSet(cmdset_unloggedin.UnloggedinCmdSet):
#
class OOCCmdSet(cmdset_ooc.OOCCmdSet):
"""
This is set is available to the player when they have no
character connected to them (i.e. they are out-of-character, ooc).
"""
key = "OOC"
def at_cmdset_creation(self):
"""
Populates the cmdset
"""
super(OOCCmdSet, self).at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#
class BaseCmdSet(CmdSet):
"""
Implements an empty, example cmdset.

View file

@ -44,12 +44,6 @@ class Object(BaseObject):
Hooks (these are class methods, so their arguments should also start with self):
basetype_setup() - only called once, when object is first created, before
at_object_creation(). Sets up semi-engine-specific details such as
the default lock policy, what defines a Room, Exit etc.
Usually not modified unless you want to overload the default
security restriction policies.
at_object_creation() - only called once, when object is first created.
Almost all object customizations go here.
at_first_login() - only called once, the very first time user logs in.

View file

@ -54,14 +54,11 @@ from django.conf import settings
from src.comms.channelhandler import CHANNELHANDLER
from src.commands.cmdsethandler import import_cmdset
from src.objects.exithandler import EXITHANDLER
from src.utils import logger
from src.utils import logger, utils
#This switches the command parser to a user-defined one.
# You have to restart the server for this to take effect.
try:
CMDPARSER = __import__(settings.ALTERNATE_PARSER, fromlist=[True]).cmdparser
except Exception:
from src.commands.cmdparser import cmdparser as CMDPARSER
COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1))
# There are a few system-hardcoded command names. These
# allow for custom behaviour when the command handler hits
@ -101,13 +98,21 @@ def get_and_merge_cmdsets(caller):
exit_cmdset = None
local_objects_cmdsets = [None]
# Player object's commandsets
try:
player_cmdset = caller.player.cmdset.current
except AttributeError:
player_cmdset = None
if not caller_cmdset.no_channels:
# Make cmdsets out of all valid channels
channel_cmdset = CHANNELHANDLER.get_cmdset(caller)
if not caller_cmdset.no_exits:
# Make cmdsets out of all valid exits in the room
exit_cmdset = EXITHANDLER.get_cmdset(caller)
location = caller.location
location = None
if hasattr(caller, "location"):
location = caller.location
if location and not caller_cmdset.no_objs:
# Gather all cmdsets stored on objects in the room and
# also in the caller's inventory
@ -138,6 +143,12 @@ def get_and_merge_cmdsets(caller):
cmdset = channel_cmdset + cmdset
except TypeError:
pass
# finally merge on the player cmdset. This should have a low priority
try:
cmdset = player_cmdset + cmdset
except TypeError:
pass
return cmdset
def match_command(cmd_candidates, cmdset, logged_caller=None):
@ -295,7 +306,7 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
raise ExecSystemCommand(syscmd, sysarg)
# Parse the input string into command candidates
cmd_candidates = CMDPARSER(raw_string)
cmd_candidates = COMMAND_PARSER(raw_string)
#string ="Command candidates"
#for cand in cmd_candidates:

View file

@ -174,3 +174,128 @@ def cmdparser(raw_string):
wordlist = raw_string.split(" ")
candidates.extend(produce_candidates(nr_candidates, wordlist))
return candidates
#------------------------------------------------------------
# Search parsers and support methods
#------------------------------------------------------------
#
# Default functions for formatting and processing searches.
#
# This is in its own module due to them being possible to
# replace from the settings file by setting the variables
#
# SEARCH_AT_RESULTERROR_HANDLER
# SEARCH_MULTIMATCH_PARSER
#
# The the replacing modules must have the same inputs and outputs as
# those in this module.
#
def at_search_result(msg_obj, ostring, results, global_search=False):
"""
Called by search methods after a result of any type has been found.
Takes a search result (a list) and
formats eventual errors.
msg_obj - object to receive feedback.
ostring - original search string
results - list of found matches (0, 1 or more)
global_search - if this was a global_search or not
(if it is, there might be an idea of supplying
dbrefs instead of only numbers)
Multiple matches are returned to the searching object
as
1-object
2-object
3-object
etc
"""
string = ""
if not results:
# no results.
string = "Could not find '%s'." % ostring
results = None
elif len(results) > 1:
# we have more than one match. We will display a
# list of the form 1-objname, 2-objname etc.
# check if the msg_object may se dbrefs
show_dbref = global_search
string += "More than one match for '%s'" % ostring
string += " (please narrow target):"
for num, result in enumerate(results):
invtext = ""
dbreftext = ""
if hasattr(result, "location") and result.location == msg_obj:
invtext = " (carried)"
if show_dbref:
dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext)
results = None
else:
# we have exactly one match.
results = results[0]
if string:
msg_obj.msg(string.strip())
return results
def at_multimatch_input(ostring):
"""
Parse number-identifiers.
This parser will be called by the engine when a user supplies
a search term. The search term must be analyzed to determine
if the user wants to differentiate between multiple matches
(usually found during a previous search).
This method should separate out any identifiers from the search
string used to differentiate between same-named objects. The
result should be a tuple (index, search_string) where the index
gives which match among multiple matches should be used (1 being
the lowest number, rather than 0 as in Python).
This parser version will identify search strings on the following
forms
2-object
This will be parsed to (2, "object") and, if applicable, will tell
the engine to pick the second from a list of same-named matches of
objects called "object".
Ex for use in a game session:
> look
You see: ball, ball, ball and ball.
> get ball
There where multiple matches for ball:
1-ball
2-ball
3-ball
4-ball
> get 3-ball
You get the ball.
"""
if not isinstance(ostring, basestring):
return (None, ostring)
if not '-' in ostring:
return (None, ostring)
try:
index = ostring.find('-')
number = int(ostring[:index])-1
return (number, ostring[index+1:])
except ValueError:
#not a number; this is not an identifier.
return (None, ostring)
except IndexError:
return (None, ostring)

View file

@ -15,6 +15,9 @@ together to create interesting in-game effects.
"""
import copy
from src.utils.utils import inherits_from, is_iter
RECURSIVE_PROTECTION = False
class CmdSetMeta(type):
"""
@ -204,16 +207,40 @@ class CmdSet(object):
def add(self, cmd):
"""
Add a command to this cmdset.
Add a command, a list of commands or a cmdset to this cmdset.
Note that if cmd already exists in set,
it will replace the old one (no priority checking etc
at this point; this is often used to overload
default commands).
"""
cmd = instantiate(cmd)
if cmd:
If cmd is another cmdset class or -instance, the commands
of that command set is added to this one, as if they were part
of the original cmdset definition. No merging or priority checks
are made, rather later added commands will simply replace
existing ones to make a unique set.
"""
if inherits_from(cmd, "src.commands.cmdset.CmdSet"):
# this is a command set so merge all commands in that set
# to this one. We are not protecting against recursive
# loops (adding a cmdset that itself adds this cmdset here
# since such an error will be very visible and lead to a
# traceback at startup anyway.
try:
cmd = instantiate(cmd)
except RuntimeError, e:
string = "Adding cmdset %s to %s lead to an infinite loop. When adding a cmdset to another, "
string += "make sure they are not themself cyclically added to the new cmdset somewhere in the chain."
raise RuntimeError(string % (cmd, self.__class__))
cmds = cmd.commands
elif not is_iter(cmd):
cmds = [instantiate(cmd)]
else:
cmds = instantiate(cmd)
for cmd in cmds:
# add all commands
if not hasattr(cmd, 'obj'):
cmd.obj = self.cmdsetobj
try:

View file

@ -179,7 +179,7 @@ class CmdSetHandler(object):
string += "\n"
merged = True
# Display the currently active cmdset
# Display the currently active cmdset, limited by self.obj's permissions
mergetype = self.mergetype_stack[-1]
if mergetype != self.current.mergetype:
merged_on = self.cmdset_stack[-2].key
@ -187,7 +187,8 @@ class CmdSetHandler(object):
if merged:
string += " <Merged (%s)>: %s" % (mergetype, self.current)
else:
string += " <%s (%s)>: %s" % (self.current.key, mergetype, self.current)
string += " <%s (%s)>: %s" % (self.current.key, mergetype,
", ".join(cmd.key for cmd in self.current if cmd.access(self.obj, "cmd")))
return string.strip()
def update(self, init_mode=False):

View file

@ -58,7 +58,7 @@ class CmdBoot(MuxCommand):
break
else:
# Boot by player object
pobj = caller.search("*%s" % args, global_search=True)
pobj = caller.search("*%s" % args, global_search=True, player=True)
if not pobj:
return
if pobj.character.has_player:
@ -118,6 +118,9 @@ class CmdDelPlayer(MuxCommand):
caller = self.caller
args = self.args
if hasattr(caller, 'player'):
caller = caller.player
if not args:
caller.msg("Usage: @delplayer[/delobj] <player/user name or #id> [: reason]")
return
@ -128,7 +131,7 @@ class CmdDelPlayer(MuxCommand):
# We use player_search since we want to be sure to find also players
# that lack characters.
players = caller.search("*%s" % args)
players = caller.search("*%s" % args, player=True)
if not players:
try:
players = PlayerDB.objects.filter(id=args)
@ -304,7 +307,7 @@ class CmdNewPassword(MuxCommand):
return
# the player search also matches 'me' etc.
player = caller.search("*%s" % self.lhs, global_search=True)
player = caller.search("*%s" % self.lhs, global_search=True, player=True)
if not player:
return
player.user.set_password(self.rhs)
@ -320,12 +323,14 @@ class CmdPerm(MuxCommand):
Usage:
@perm[/switch] <object> [= <permission>[,<permission>,...]]
@perm[/switch] *<player> [= <permission>[,<permission>,...]]
Switches:
del : delete the given permission from <object>.
This command sets/clears individual permission strings on an object.
If no permission is given, list all permissions on <object>
del : delete the given permission from <object> or <player>.
player : set permission on a player (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>.
"""
key = "@perm"
aliases = "@setperm"
@ -340,17 +345,18 @@ class CmdPerm(MuxCommand):
lhs, rhs = self.lhs, self.rhs
if not self.args:
string = "Usage: @perm[/switch] object [ = permission, permission, ...]\n"
string = "Usage: @perm[/switch] object [ = permission, permission, ...]"
caller.msg(string)
return
# locate the object
obj = caller.search(lhs, global_search=True)
playermode = 'player' in self.switches or lhs.startswith('*')
# locate the object
obj = caller.search(lhs, global_search=True, player=playermode)
if not obj:
return
if not rhs:
if not obj.access(caller, 'examine'):
caller.msg("You are not allowed to examine this object.")
return
@ -396,12 +402,10 @@ class CmdPerm(MuxCommand):
for perm in self.rhslist:
perm = perm.lower()
# don't allow to set a permission higher in the hierarchy than the one the
# caller has (to prevent self-escalation)
if perm in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm):
if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm):
caller.msg("You cannot assign a permission higher than the one you have yourself.")
return
@ -416,45 +420,6 @@ class CmdPerm(MuxCommand):
if tstring:
obj.msg(tstring.strip())
class CmdPuppet(MuxCommand):
"""
Switch control to an object
Usage:
@puppet <character object>
This will attempt to "become" a different character. Note that this command does not check so that
the target object has the appropriate cmdset. You cannot puppet a character that is already "taken".
"""
key = "@puppet"
locks = "cmd:perm(puppet) or perm(Builders)"
help_category = "Admin"
def func(self):
"""
Simple puppet method (does not check permissions)
"""
caller = self.caller
if not self.args:
caller.msg("Usage: @puppet <character>")
return
player = caller.player
new_character = caller.search(self.args)
if not new_character:
return
if not utils.inherits_from(new_character, settings.BASE_CHARACTER_TYPECLASS):
caller.msg("%s is not a Character." % self.args)
return
if new_character.player:
caller.msg("This character is already under the control of a player.")
if player.swap_character(new_character):
new_character.msg("You now control %s." % new_character.name)
else:
caller.msg("You cannot control %s." % new_character.name)
class CmdWall(MuxCommand):
"""
@wall

View file

@ -6,6 +6,7 @@ Building and world design commands
from django.conf import settings
from src.objects.models import ObjectDB, ObjAttribute
from src.players.models import PlayerAttribute
from src.utils import create, utils, debug
from src.commands.default.muxcommand import MuxCommand
@ -122,7 +123,7 @@ class CmdSetObjAlias(MuxCommand):
old_aliases = obj.aliases
if old_aliases:
caller.msg("Cleared aliases from %s: %s" % (obj.key, ", ".join(old_aliases)))
del obj.dbobj.aliases # TODO: del does not understand indirect typeclass reference!
del obj.dbobj.aliases
else:
caller.msg("No aliases to clear.")
return
@ -1389,16 +1390,25 @@ class CmdExamine(ObjManipCommand):
Usage:
examine [<object>[/attrname]]
examine [*<player>[/attrname]]
Switch:
player - examine a Player (same as adding *)
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.
"""
key = "@examine"
aliases = ["@ex","ex", "exam", "examine"]
locks = "cmd:perm(examine) or perm(Builders)"
help_category = "Building"
player_mode = False
def format_attributes(self, obj, attrname=None, crop=True):
"""
Helper function that returns info about attributes and/or
@ -1411,7 +1421,10 @@ class CmdExamine(ObjManipCommand):
except Exception:
ndb_attr = None
else:
db_attr = [(attr.key, attr.value) for attr in ObjAttribute.objects.filter(db_obj=obj)]
if player_mode:
db_attr = [(attr.key, attr.value) for attr in PlayerAttribute.objects.filter(db_obj=obj)]
else:
db_attr = [(attr.key, attr.value) for attr in ObjAttribute.objects.filter(db_obj=obj)]
try:
ndb_attr = [(aname, avalue) for aname, avalue in obj.ndb.__dict__.items()]
except Exception:
@ -1439,24 +1452,25 @@ class CmdExamine(ObjManipCommand):
returns a string.
"""
if obj.has_player:
if hasattr(obj, "has_player") and obj.has_player:
string = "\n{wName/key{n: {c%s{n (%s)" % (obj.name, obj.dbref)
else:
string = "\n{wName/key{n: {C%s{n (%s)" % (obj.name, obj.dbref)
if obj.aliases:
if hasattr(obj, "aliases") and obj.aliases:
string += "\n{wAliases{n: %s" % (", ".join(obj.aliases))
if obj.has_player:
if hasattr(obj, "has_player") and obj.has_player:
string += "\n{wPlayer{n: {c%s{n" % obj.player.name
perms = obj.player.permissions
if obj.player.is_superuser:
perms = ["<Superuser>"]
elif not perms:
perms = ["<None>"]
string += "\n{wPlayer Perms{n: %s" % (", ".join(perms))
string += "\n{wPlayer Perms{n: %s" % (", ".join(perms))
string += "\n{wTypeclass{n: %s (%s)" % (obj.typeclass, obj.typeclass_path)
string += "\n{wLocation{n: %s" % obj.location
if obj.destination:
if hasattr(obj, "location"):
string += "\n{wLocation{n: %s" % obj.location
if hasattr(obj, "destination") and obj.destination:
string += "\n{wDestination{n: %s" % obj.destination
perms = obj.permissions
if perms:
@ -1467,8 +1481,8 @@ class CmdExamine(ObjManipCommand):
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "Empty"):
cmdsetstr = "\n".join([utils.fill(cmdset, indent=2) for cmdset in str(obj.cmdset).split("\n")])
string += "\n{wCurrent Cmdset (before permission checks){n:\n %s" % cmdsetstr
if obj.scripts.all():
string += "\n{wCurrent Cmdset (including permission checks){n:\n %s" % cmdsetstr
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
string += "\n{wScripts{n:\n %s" % obj.scripts
# add the attributes
string += self.format_attributes(obj)
@ -1476,21 +1490,21 @@ class CmdExamine(ObjManipCommand):
exits = []
pobjs = []
things = []
for content in obj.contents:
if content.destination:
# an exit
exits.append(content)
elif content.player:
pobjs.append(content)
else:
things.append(content)
if exits:
string += "\n{wExits{n: " + ", ".join([exit.name for exit in exits])
if pobjs:
string += "\n{wCharacters{n: " + ", ".join(["{c%s{n" % pobj.name for pobj in pobjs])
if things:
string += "\n{wContents{n: " + ", ".join([cont.name for cont in obj.contents
if cont not in exits and cont not in pobjs])
if hasattr(obj, "contents"):
for content in obj.contents:
if content.destination:
exits.append(content)
elif content.player:
pobjs.append(content)
else:
things.append(content)
if exits:
string += "\n{wExits{n: " + ", ".join([exit.name for exit in exits])
if pobjs:
string += "\n{wCharacters{n: " + ", ".join(["{c%s{n" % pobj.name for pobj in pobjs])
if things:
string += "\n{wContents{n: " + ", ".join([cont.name for cont in obj.contents
if cont not in exits and cont not in pobjs])
#output info
return "-"*78 + '\n' + string.strip() + "\n" + '-'*78
@ -1506,36 +1520,35 @@ class CmdExamine(ObjManipCommand):
caller.execute_cmd('look %s' % obj.name)
return
string = self.format_output(obj)
else:
# we have given a specific target object
string = ""
for objdef in self.lhs_objattr:
obj_name = objdef['name']
obj_attrs = objdef['attrs']
obj = caller.search(obj_name)
if not obj:
continue
if not obj.access(caller, 'examine'):
#If we don't have special info access, just look at the object instead.
caller.execute_cmd('look %s' % obj_name)
continue
if obj_attrs:
for attrname in obj_attrs:
# we are only interested in specific attributes
string += self.format_attributes(obj, attrname, crop=False)
else:
string += self.format_output(obj)
string = string.strip()
# Send it all
if string:
caller.msg(string.strip())
return
# we have given a specific target object
string = ""
for objdef in self.lhs_objattr:
obj_name = objdef['name']
obj_attrs = objdef['attrs']
global player_mode
player_mode = "player" in self.switches or obj_name.startswith('*')
obj = caller.search(obj_name, player=player_mode)
if not obj:
continue
if not obj.access(caller, 'examine'):
#If we don't have special info access, just look at the object instead.
caller.execute_cmd('look %s' % obj_name)
continue
if obj_attrs:
for attrname in obj_attrs:
# we are only interested in specific attributes
string += self.format_attributes(obj, attrname, crop=False)
else:
string += self.format_output(obj)
caller.msg(string.strip())
class CmdFind(MuxCommand):

View file

@ -18,17 +18,14 @@ class DefaultCmdSet(CmdSet):
# The general commands
self.add(general.CmdLook())
self.add(general.CmdHome())
self.add(general.CmdPassword())
self.add(general.CmdWho())
self.add(general.CmdInventory())
self.add(general.CmdQuit())
self.add(general.CmdPose())
self.add(general.CmdNick())
self.add(general.CmdGet())
self.add(general.CmdDrop())
self.add(general.CmdWho())
self.add(general.CmdSay())
self.add(general.CmdAccess())
self.add(general.CmdEncoding())
# The help system
self.add(help.CmdHelp())
@ -52,7 +49,6 @@ class DefaultCmdSet(CmdSet):
self.add(admin.CmdEmit())
self.add(admin.CmdNewPassword())
self.add(admin.CmdPerm())
self.add(admin.CmdPuppet())
self.add(admin.CmdWall())
# Building and world manipulation
@ -79,24 +75,6 @@ class DefaultCmdSet(CmdSet):
self.add(building.CmdLock())
self.add(building.CmdScript())
self.add(building.CmdHome())
# Comm commands
self.add(comms.CmdAddCom())
self.add(comms.CmdDelCom())
self.add(comms.CmdAllCom())
self.add(comms.CmdChannels())
self.add(comms.CmdCdestroy())
self.add(comms.CmdChannelCreate())
self.add(comms.CmdCset())
self.add(comms.CmdCBoot())
self.add(comms.CmdCemit())
self.add(comms.CmdCWho())
self.add(comms.CmdCdesc())
self.add(comms.CmdPage())
self.add(comms.CmdIRC2Chan())
self.add(comms.CmdIMC2Chan())
self.add(comms.CmdIMCInfo())
self.add(comms.CmdIMCTell())
# Batchprocessor commands
self.add(batchprocess.CmdBatchCommands())

View file

@ -0,0 +1,55 @@
"""
This is the cmdset for OutOfCharacter (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.
"""
from src.commands.cmdset import CmdSet
from src.commands.default import help, comms, general, admin
class OOCCmdSet(CmdSet):
"""
Implements the player command set.
"""
key = "DefaultOOC"
priority = -5
def at_cmdset_creation(self):
"Populates the cmdset"
# general commands
self.add(general.CmdOOCLook())
self.add(general.CmdIC())
self.add(general.CmdOOC())
self.add(general.CmdEncoding())
self.add(general.CmdQuit())
self.add(general.CmdPassword())
# help command
self.add(help.CmdHelp())
# admin commands
self.add(admin.CmdBoot())
self.add(admin.CmdDelPlayer())
self.add(admin.CmdNewPassword())
# Comm commands
self.add(comms.CmdAddCom())
self.add(comms.CmdDelCom())
self.add(comms.CmdAllCom())
self.add(comms.CmdChannels())
self.add(comms.CmdCdestroy())
self.add(comms.CmdChannelCreate())
self.add(comms.CmdCset())
self.add(comms.CmdCBoot())
self.add(comms.CmdCemit())
self.add(comms.CmdCWho())
self.add(comms.CmdCdesc())
self.add(comms.CmdPage())
self.add(comms.CmdIRC2Chan())
self.add(comms.CmdIMC2Chan())
self.add(comms.CmdIMCInfo())
self.add(comms.CmdIMCTell())

View file

@ -1,5 +1,11 @@
"""
Comsys command module.
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
for easy handling.
"""
from django.conf import settings
from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChannelConnection
@ -29,6 +35,25 @@ def find_channel(caller, channelname, silent=False, noaliases=False):
caller.msg("Multiple channels match (be more specific): \n%s" % matches)
return None
return channels[0]
class CommCommand(MuxCommand):
"""
This is a parent for comm-commands. Since
These commands are to be available to the
Player, we make sure to homogenize the caller
here, so it's always seen as a player to the
command body.
"""
def parse(self):
"overload parts of parse"
# run parent
super(CommCommand, self).parse()
# fix obj->player
if utils.inherits_from(self.caller, "src.objects.objects.Object"):
# an object. Convert it to its player.
self.caller = self.caller.player
class CmdAddCom(MuxCommand):
"""
@ -46,14 +71,14 @@ class CmdAddCom(MuxCommand):
key = "addcom"
aliases = ["aliaschan","chanalias"]
help_category = "Comms"
locks = "cmd:not perm(channel_banned)"
locks = "cmd:not pperm(channel_banned)"
def func(self):
"Implement the command"
caller = self.caller
args = self.args
player = caller.player
player = caller
if not args:
caller.msg("Usage: addcom [alias =] channelname.")
@ -120,7 +145,7 @@ class CmdDelCom(MuxCommand):
"Implementing the command. "
caller = self.caller
player = caller.player
player = caller
if not self.args:
caller.msg("Usage: delcom <alias or channel>")
@ -169,7 +194,7 @@ class CmdAllCom(MuxCommand):
"""
key = "allcom"
locks = "cmd: not perm(channel_banned)"
locks = "cmd: not pperm(channel_banned)"
help_category = "Comms"
def func(self):
@ -189,7 +214,7 @@ class CmdAllCom(MuxCommand):
caller.execute_cmd("addcom %s" % channel.key)
elif args == "off":
#get names all subscribed channels and disconnect from them all
channels = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller.player)]
channels = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller)]
for channel in channels:
caller.execute_cmd("delcom %s" % channel.key)
elif args == "destroy":
@ -230,7 +255,7 @@ class CmdChannels(MuxCommand):
key = "@channels"
aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
help_category = "Comms"
locks = "cmd: not perm(channel_banned)"
locks = "cmd: not pperm(channel_banned)"
def func(self):
"Implement function"
@ -243,7 +268,7 @@ class CmdChannels(MuxCommand):
caller.msg("No channels available.")
return
# all channel we are already subscribed to
subs = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller.player)]
subs = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller)]
if self.cmdstring != "comlist":
@ -298,7 +323,7 @@ class CmdCdestroy(MuxCommand):
key = "@cdestroy"
help_category = "Comms"
locks = "cmd: not perm(channel_banned)"
locks = "cmd: not pperm(channel_banned)"
def func(self):
"Destroy objects cleanly."
@ -337,7 +362,7 @@ class CmdCBoot(MuxCommand):
"""
key = "@cboot"
locks = "cmd: not perm(channel_banned)"
locks = "cmd: not pperm(channel_banned)"
help_category = "Comms"
def func(self):
@ -352,12 +377,12 @@ class CmdCBoot(MuxCommand):
if not channel:
return
reason = ""
player = None
if ":" in self.rhs:
playername, reason = self.rhs.rsplit(":", 1)
player = self.caller.search("*%s" % playername.lstrip('*'))
if not player:
player = self.caller.search("*%s" % self.rhs.lstrip('*'))
searchstring = playername.lstrip('*')
else:
searchstring = self.rhs.lstrip('*')
player = self.caller.search(searchstring, player=True)
if not player:
return
if reason:
@ -400,7 +425,7 @@ class CmdCemit(MuxCommand):
key = "@cemit"
aliases = ["@cmsg"]
locks = "cmd: not perm(channel_banned)"
locks = "cmd: not pperm(channel_banned)"
help_category = "Comms"
def func(self):
@ -437,7 +462,7 @@ class CmdCWho(MuxCommand):
List who is connected to a given channel you have access to.
"""
key = "@cwho"
locks = "cmd: not perm(channel_banned)"
locks = "cmd: not pperm(channel_banned)"
help_category = "Comms"
def func(self):
@ -475,7 +500,7 @@ class CmdChannelCreate(MuxCommand):
key = "@ccreate"
aliases = "channelcreate"
locks = "cmd:not perm(channel_banned)"
locks = "cmd:not pperm(channel_banned)"
help_category = "Comms"
def func(self):
@ -522,7 +547,7 @@ class CmdCset(MuxCommand):
"""
key = "@cset"
locks = "cmd:not perm(channel_banned)"
locks = "cmd:not pperm(channel_banned)"
aliases = ["@cclock"]
help_category = "Comms"
@ -568,7 +593,7 @@ class CmdCdesc(MuxCommand):
"""
key = "@cdesc"
locks = "cmd:not perm(channel_banned)"
locks = "cmd:not pperm(channel_banned)"
help_category = "Comms"
def func(self):
@ -611,7 +636,7 @@ class CmdPage(MuxCommand):
key = "page"
aliases = ['tell']
locks = "cmd:not perm(page_banned)"
locks = "cmd:not pperm(page_banned)"
help_category = "Comms"
def func(self):
@ -619,7 +644,7 @@ class CmdPage(MuxCommand):
"Implement function using the Msg methods"
caller = self.caller
player = caller.player
player = caller
# get the messages we've sent
messages_we_sent = list(Msg.objects.get_messages_by_sender(player))
@ -751,7 +776,7 @@ class CmdIRC2Chan(MuxCommand):
"""
key = "@irc2chan"
locks = "cmd:serversetting(IRC_ENABLED) and perm(Immortals)"
locks = "cmd:serversetting(IRC_ENABLED) and pperm(Immortals)"
help_category = "Comms"
def func(self):
@ -840,7 +865,7 @@ class CmdIMC2Chan(MuxCommand):
"""
key = "@imc2chan"
locks = "cmd:serversetting(IMC2_ENABLED) and perm(Immortals)"
locks = "cmd:serversetting(IMC2_ENABLED) and pperm(Immortals)"
help_category = "Comms"
def func(self):
@ -923,7 +948,7 @@ class CmdIMCInfo(MuxCommand):
key = "@imcinfo"
aliases = ["@imcchanlist", "@imclist", "@imcwhois"]
locks = "cmd: serversetting(IMC2_ENABLED) and perm(Wizards)"
locks = "cmd: serversetting(IMC2_ENABLED) and pperm(Wizards)"
help_category = "Comms"
def func(self):
@ -982,7 +1007,7 @@ class CmdIMCInfo(MuxCommand):
return
from src.comms.imc2 import IMC2_CLIENT
self.caller.msg("Sending IMC whois request. If you receive no response, no matches were found.")
IMC2_CLIENT.msg_imc2(None, from_obj=self.caller.player, packet_type="imcwhois", data={"target":self.args})
IMC2_CLIENT.msg_imc2(None, from_obj=self.caller, packet_type="imcwhois", data={"target":self.args})
elif not self.switches or "channels" in self.switches or self.cmdstring == "@imcchanlist":
# show channels
@ -1051,6 +1076,6 @@ class CmdIMCTell(MuxCommand):
data = {"target":target, "destination":destination}
# send to imc2
IMC2_CLIENT.msg_imc2(message, from_obj=self.caller.player, packet_type="imctell", data=data)
IMC2_CLIENT.msg_imc2(message, from_obj=self.caller, packet_type="imctell", data=data)
self.caller.msg("You paged {c%s@%s{n (over IMC): '%s'." % (target, destination, message))

View file

@ -5,11 +5,13 @@ now.
import time
from django.conf import settings
from src.server.sessionhandler import SESSIONS
from src.objects.models import HANDLE_SEARCH_ERRORS
from src.utils import utils
from src.objects.models import Nick
from src.objects.models import ObjectNick as Nick
from src.commands.default.muxcommand import MuxCommand
AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS
class CmdHome(MuxCommand):
"""
home
@ -45,7 +47,7 @@ class CmdLook(MuxCommand):
Observes your location or objects in your vicinity.
"""
key = "look"
aliases = ["l"]
aliases = ["l", "ls"]
locks = "cmd:all()"
def func(self):
@ -53,18 +55,19 @@ class CmdLook(MuxCommand):
Handle the looking.
"""
caller = self.caller
args = self.args # caller.msg(inp)
args = self.args
if args:
# Use search to handle duplicate/nonexistant results.
looking_at_obj = caller.search(args, use_nicks=True)
if not looking_at_obj:
return
return
else:
looking_at_obj = caller.location
if not looking_at_obj:
caller.msg("Location: None")
caller.msg("You have no location to look at!")
return
if not hasattr(looking_at_obj, 'return_appearance'):
# this is likely due to us having a player instead
looking_at_obj = looking_at_obj.character
@ -89,6 +92,8 @@ class CmdPassword(MuxCommand):
"hook function."
caller = self.caller
if hasattr(caller, "player"):
caller = caller.player
if not self.rhs:
caller.msg("Usage: @password <oldpass> = <newpass>")
@ -96,7 +101,7 @@ class CmdPassword(MuxCommand):
oldpass = self.lhslist[0] # this is already stripped by parse()
newpass = self.rhslist[0] # ''
try:
uaccount = caller.player.user
uaccount = caller.user
except AttributeError:
caller.msg("This is only applicable for players.")
return
@ -309,7 +314,7 @@ class CmdDrop(MuxCommand):
# those in our inventory.
results = [obj for obj in results if obj in caller.contents]
# now we send it into the handler.
obj = HANDLE_SEARCH_ERRORS(caller, self.args, results, False)
obj = AT_SEARCH_RESULT(caller, self.args, results, False)
if not obj:
return
@ -348,13 +353,13 @@ class CmdWho(MuxCommand):
who
doing
Shows who is currently online. Doing is an
alias that limits info also for those with
all permissions.
Shows who is currently online. Doing is an alias that limits info
also for those with all permissions.
"""
key = "who"
aliases = "doing"
locks = "cmd:all()"
def func(self):
"""
@ -518,17 +523,20 @@ class CmdEncoding(MuxCommand):
Sets the encoding.
"""
caller = self.caller
if hasattr(caller, 'player'):
caller = caller.player
if 'clear' in self.switches:
# remove customization
old_encoding = caller.player.db.encoding
old_encoding = caller.db.encoding
if old_encoding:
string = "Your custom text encoding ('%s') was cleared." % old_encoding
else:
string = "No custom encoding was set."
del caller.player.db.encoding
del caller.db.encoding
elif not self.args:
# just list the encodings supported
pencoding = caller.player.db.encoding
pencoding = caller.db.encoding
string = ""
if pencoding:
string += "Default encoding: {g%s{n (change with {w@encoding <encoding>{n)" % pencoding
@ -539,9 +547,9 @@ class CmdEncoding(MuxCommand):
string = "No encodings found."
else:
# change encoding
old_encoding = caller.player.db.encoding
old_encoding = caller.db.encoding
encoding = self.args
caller.player.db.encoding = encoding
caller.db.encoding = encoding
string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding)
caller.msg(string.strip())
@ -579,3 +587,141 @@ class CmdAccess(MuxCommand):
if hasattr(caller, 'player'):
string += "\nPlayer {c%s{n: %s" % (caller.player.key, pperms)
caller.msg(string)
# OOC commands
class CmdOOCLook(CmdLook):
"""
ooc look
Usage:
look
This is an OOC version of the look command. Since a
Player 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.
"""
key = "look"
aliases = ["l", "ls"]
locks = "cmd:all()"
help_cateogory = "General"
def func(self):
"implement the command"
self.character = None
if utils.inherits_from(self.caller, "src.objects.objects.Object"):
# An object of some type is calling. Convert to player.
self.character = self.caller
self.caller = self.caller.player
if not self.character:
string = "You are out-of-character (OOC). "
string += "Use {w@ic{n to get back to the game, {whelp{n for more info."
self.caller.msg(string)
else:
self.caller = self.character # we have to put this back for normal look to work.
super(CmdLook, self).func()
class CmdIC(MuxCommand):
"""
Switch control to an object
Usage:
@ic <character>
Go in-character (IC) as a given Character.
This will attempt to "become" a different object assuming you have
the right to do so. 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 have access right to puppet it.
"""
key = "@ic"
locks = "cmd:all()" # must be all() or different puppeted objects won't be able to access it.
aliases = "@puppet"
help_category = "General"
def func(self):
"""
Simple puppet method
"""
caller = self.caller
if utils.inherits_from(caller, "src.objects.objects.Object"):
caller = caller.player
new_character = None
if not self.args:
new_character = caller.db.last_puppet
if not new_character:
caller.msg("Usage: @ic <character>")
return
if not new_character:
# search for a matching character
new_character = caller.search(self.args)
if not new_character:
# the search method handles error messages etc.
return
if new_character.player:
if new_character.player == caller:
caller.msg("{RYou already are {c%s{n." % new_character.name)
else:
caller.msg("{c%s{r is already acted by another player.{n" % new_character.name)
return
if not new_character.access(caller, "puppet"):
caller.msg("{rYou may not become %s.{n" % new_character.name)
return
old_char = None
if caller.character:
# save the old character. We only assign this to last_puppet if swap is successful.
old_char = caller.character
if caller.swap_character(new_character):
new_character.msg("\n{gYou become {c%s{n.\n" % new_character.name)
caller.db.last_puppet = old_char
new_character.execute_cmd("look")
else:
caller.msg("{rYou cannot become {C%s{n." % new_character.name)
class CmdOOC(MuxCommand):
"""
@ooc - go ooc
Usage:
@ooc
Go out-of-character (OOC).
This will leave your current character and put you in a incorporeal OOC state.
"""
key = "@ooc"
locks = "cmd:all()" # this must be all(), or different puppeted objects won't be able to access it.
aliases = "@unpuppet"
help_category = "General"
def func(self):
"Implement function"
caller = self.caller
if utils.inherits_from(caller, "src.objects.objects.Object"):
caller = self.caller.player
if not caller.character:
string = "You are already OOC."
caller.msg(string)
return
caller.db.last_puppet = caller.character
# disconnect
caller.character.player = None
caller.character = None
caller.msg("\n{GYou go OOC.{n\n")
caller.execute_cmd("look")

View file

@ -441,5 +441,12 @@ class TestCwho(CommandTest):
self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel")
self.execute_cmd("@cwho testchan1b", "Channel subscriptions")
# OOC commands
#class TestOOC_and_IC(CommandTest): # can't be tested it seems, causes errors in other commands (?)
# def test_call(self):
# self.execute_cmd("@ooc", "\nYou go OOC.")
# self.execute_cmd("@ic", "\nYou become TestChar")
# Unloggedin commands
# these cannot be tested from here.

View file

@ -62,49 +62,81 @@ class CmdConnect(MuxCommand):
# We are logging in, get/setup the player object controlled by player
character = player.character
if not character:
# Create a new character object to tie the player to. This should
# usually not be needed unless the old character object was manually
# deleted.
default_home_id = ServerConfig.objects.conf("default_home")
default_home = ObjectDB.objects.get_id(default_home_id)
typeclass = settings.BASE_CHARACTER_TYPECLASS
character = create.create_object(typeclass=typeclass,
key=player.name,
location=default_home,
home=default_home,
player=player)
character.db.FIRST_LOGIN = "True"
# Getting ready to log the player in.
# Check if this is the first time the
# *player* connects
if player.db.FIRST_LOGIN:
player.at_first_login()
del player.db.FIRST_LOGIN
# check if this is the first time the *character*
# character (needs not be the first time the player
# does so, e.g. if the player has several characters)
if character.db.FIRST_LOGIN:
character.at_first_login()
del character.db.FIRST_LOGIN
# actually do the login, calling
# customization hooks before and after.
player.at_pre_login()
character.at_pre_login()
character = player.character
if character:
# this player has a character. Check if it's the
# first time *this character* logs in
if character.db.FIRST_LOGIN:
character.at_first_login()
del character.db.FIRST_LOGIN
# run character login hook
character.at_pre_login()
# actually do the login
session.session_login(player)
# post-login hooks
player.at_post_login()
if character:
character.at_post_login()
character.execute_cmd('look')
else:
player.execute_cmd('look')
player.at_post_login()
character.at_post_login()
# run look
#print "character:", character, character.scripts.all(), character.cmdset.current
character.execute_cmd('look')
#
# character = player.character
# if not character:
# # Create a new character object to tie the player to. This should
# # usually not be needed unless the old character object was manually
# # deleted.
# default_home_id = ServerConfig.objects.conf("default_home")
# default_home = ObjectDB.objects.get_id(default_home_id)
# typeclass = settings.BASE_CHARACTER_TYPECLASS
# character = create.create_object(typeclass=typeclass,
# key=player.name,
# location=default_home,
# home=default_home,
# player=player)
# character.db.FIRST_LOGIN = "True"
# # Getting ready to log the player in.
# # Check if this is the first time the
# # *player* connects
# if player.db.FIRST_LOGIN:
# player.at_first_login()
# del player.db.FIRST_LOGIN
# # check if this is the first time the *character*
# # character (needs not be the first time the player
# # does so, e.g. if the player has several characters)
# if character.db.FIRST_LOGIN:
# character.at_first_login()
# del character.db.FIRST_LOGIN
# # actually do the login, calling
# # customization hooks before and after.
# player.at_pre_login()
# character.at_pre_login()
# session.session_login(player)
# player.at_post_login()
# character.at_post_login()
# # run look
# #print "character:", character, character.scripts.all(), character.cmdset.current
# character.execute_cmd('look')
class CmdCreate(MuxCommand):
"""
@ -169,61 +201,69 @@ class CmdCreate(MuxCommand):
return
# Run sanity and security checks
if PlayerDB.objects.get_player_from_name(playername) or User.objects.filter(username=playername):
# player already exists
session.msg("Sorry, there is already a player with the name '%s'." % playername)
elif PlayerDB.objects.get_player_from_email(email):
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.")
elif len(password) < 3:
return
if len(password) < 3:
# too short password
string = "Your password must be at least 3 characters or longer."
string += "\n\rFor best security, make it at least 8 characters long, "
string += "avoid making it a real word and mix numbers into it."
session.msg(string)
else:
# everything's ok. Create the new player account
try:
default_home_id = ServerConfig.objects.conf("default_home")
default_home = ObjectDB.objects.get_id(default_home_id)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT
return
new_character = create.create_player(playername, email, password,
permissions=permissions,
location=default_home,
typeclass=typeclass,
home=default_home)
# character safety features
new_character.locks.delete("get")
new_character.locks.add("get:perm(Wizards)")
# everything's ok. Create the new player account.
try:
default_home_id = ServerConfig.objects.conf("default_home")
default_home = ObjectDB.objects.get_id(default_home_id)
# set a default description
new_character.db.desc = "This is a Player."
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT
new_character.db.FIRST_LOGIN = True
new_player = new_character.player
new_player.db.FIRST_LOGIN = True
# join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC
if pchanneldef:
pchannel = Channel.objects.get_channel(pchanneldef[0])
if not pchannel.connect_to(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_errmsg(string)
new_character = create.create_player(playername, email, password,
permissions=permissions,
location=default_home,
typeclass=typeclass,
home=default_home)
new_player = new_character.player
# character safety features
new_character.locks.delete("get")
new_character.locks.add("get:perm(Wizards)")
# allow the character itself and the player to puppet this character.
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
(new_character.id, new_player.id))
string = "A new account '%s' was created with the email address %s. Welcome!"
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, email, email))
except Exception:
# we have to handle traceback ourselves at this point, if
# we don't, errors will give no feedback.
string = "%s\nThis is a bug. Please e-mail an admin if the problem persists."
session.msg(string % (traceback.format_exc()))
logger.log_errmsg(traceback.format_exc())
# set a default description
new_character.db.desc = "This is a Player."
new_character.db.FIRST_LOGIN = True
new_player = new_character.player
new_player.db.FIRST_LOGIN = True
# join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC
if pchanneldef:
pchannel = Channel.objects.get_channel(pchanneldef[0])
if not pchannel.connect_to(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_errmsg(string)
string = "A new account '%s' was created with the email address %s. Welcome!"
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, email, email))
except Exception:
# We are in the middle between logged in and -not, so we have to handle tracebacks
# ourselves at this point. If we don't, we won't see any errors at all.
string = "%s\nThis is a bug. Please e-mail an admin if the problem persists."
session.msg(string % (traceback.format_exc()))
logger.log_errmsg(traceback.format_exc())
class CmdQuit(MuxCommand):
"""

View file

@ -80,11 +80,16 @@ class ChannelCommand(command.Command):
msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
# we can't use the utils.create function to make the Msg,
# since that creates an import recursive loop.
msgobj = Msg(db_sender=caller.player, db_message=msg)
try:
sender = caller.player
except AttributeError:
# this could happen if a player is calling directly.
sender = caller.dbobj
msgobj = Msg(db_sender=sender, db_message=msg)
msgobj.save()
msgobj.channels = channel
# send new message object to channel
channel.msg(msgobj, from_obj=caller.player)
channel.msg(msgobj, from_obj=sender)
class ChannelHandler(object):
"""

View file

@ -106,6 +106,16 @@ from src.utils import utils
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
def _to_player(accessing_obj):
"Helper function. Makes sure an accessing object is a player object"
if utils.inherits_from(accessing_obj, "src.objects.objects.Object"):
# an object. Convert to player.
accessing_obj = accessing_obj.player
return accessing_obj
# lock functions
def true(*args, **kwargs):
"Always returns True."
return True
@ -155,6 +165,29 @@ def perm_above(accessing_obj, accessed_obj, *args, **kwargs):
return any(True for hpos, hperm in enumerate(PERMISSION_HIERARCHY)
if hperm in [p.lower() for p in accessing_obj.permissions] and hpos > ppos)
def pperm(accessing_obj, accessed_obj, *args, **kwargs):
"""
The basic permission-checker for Player objects. Ignores case.
Usage:
pperm(<permission>)
where <permission> is the permission accessing_obj must
have in order to pass the lock. If the given permission
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)
def pperm_above(accessing_obj, accessed_obj, *args, **kwargs):
"""
Only allow Player 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)
def dbref(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
@ -175,10 +208,21 @@ def dbref(accessing_obj, accessed_obj, *args, **kwargs):
return dbref == accessing_obj.id
return False
def pdbref(accessing_obj, accessed_obj, *args, **kwargs):
"""
Same as dbref, but making sure accessing_obj is a player.
"""
return dbref(_to_player(accessing_obj), accessed_obj, *args, **kwargs)
def id(accessing_obj, accessed_obj, *args, **kwargs):
"Alias to dbref"
return dbref(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)
def attr(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:

View file

@ -223,7 +223,8 @@ class LockHandler(object):
wlist.append("Lock: access type '%s' changed from '%s' to '%s' " % \
(access_type, locks[access_type][2], raw_lockstring))
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
if wlist:
if wlist and self.log_obj:
# not an error, so only report if log_obj is available.
self._log_error("\n".join(wlist))
if elist:
raise LockException("\n".join(elist))
@ -343,10 +344,11 @@ class LockHandler(object):
to None if the lock functions called don't access it). atype can also be
put to a dummy value since no lock selection is made.
"""
if (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'user')
and hasattr(accessing_obj.player.user, 'is_superuser')
and accessing_obj.player.user.is_superuser):
return True # always grant access to the superuser.
if ((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 (accessing_obj.get_player()==None or accessing_obj.get_player().is_superuser))):
return True
locks = self. _parse_lockstring(lockstring)
for access_type in locks:
evalstring, func_tup, raw_string = locks[access_type]

View file

@ -9,11 +9,8 @@ from src.typeclasses.managers import returns_typeclass, returns_typeclass_list
from src.utils import utils
# Try to use a custom way to parse id-tagged multimatches.
IDPARSER_PATH = getattr(settings, 'ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER', 'src.objects.object_search_funcs')
if not IDPARSER_PATH:
# can happen if variable is set to "" in settings
IDPARSER_PATH = 'src.objects.object_search_funcs'
exec("from %s import object_multimatch_parser as IDPARSER" % IDPARSER_PATH)
AT_MULTIMATCH_INPUT = utils.mod_import(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
class ObjectManager(TypedObjectManager):
"""
@ -172,7 +169,10 @@ class ObjectManager(TypedObjectManager):
global_search=False,
attribute_name=None, location=None):
"""
Search as an object and return results.
Search as an object and return results. The result is always an Object.
If * is appended (player search, a Character controlled by this Player
is looked for. The Character is returned, not the Player. Use player_search
to find Player objects.
character: (Object) The object performing the search.
ostring: (string) The string to compare names against.
@ -187,7 +187,7 @@ class ObjectManager(TypedObjectManager):
if not ostring or not character:
return None
if not location:
if not location and hasattr(character, "location"):
location = character.location
# Easiest case - dbref matching (always exact)
@ -204,16 +204,17 @@ class ObjectManager(TypedObjectManager):
if character and ostring in ['me', 'self']:
return [character]
if character and ostring in ['*me', '*self']:
return [character.player]
return [character]
# Test if we are looking for a player object
# Test if we are looking for an object controlled by a
# specific player
if utils.to_unicode(ostring).startswith("*"):
# Player search - try to find obj by its player's name
player_match = self.get_object_with_player(ostring)
if player_match is not None:
return [player_match.player]
return [player_match]
# Search for keys, aliases or other attributes
search_locations = [None] # this means a global search
@ -246,7 +247,7 @@ class ObjectManager(TypedObjectManager):
matches = local_and_global_search(ostring, exact=True)
if not matches:
# if we have no match, check if we are dealing with an "N-keyword" query - if so, strip it.
match_number, ostring = IDPARSER(ostring)
match_number, ostring = AT_MULTIMATCH_INPUT(ostring)
if match_number != None and ostring:
# Run search again, without match number:
matches = local_and_global_search(ostring, exact=True)

View file

@ -0,0 +1,124 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models, utils
class Migration(SchemaMigration):
def forwards(self, orm):
try:
# if we migrate, we just rename the table. This will move over all values too.
db.rename_table("objects_nick", "objects_objectnick")
except utils.DatabaseError:
# this happens if we start from scratch. In that case the old
# database table doesn't exist, so we just create the new one.
# Adding model 'ObjectNick'
db.create_table('objects_objectnick', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('db_nick', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('db_real', self.gf('django.db.models.fields.TextField')()),
('db_type', self.gf('django.db.models.fields.CharField')(default='inputline', max_length=16, null=True, blank=True)),
('db_obj', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['objects.ObjectDB'])),
))
db.send_create_signal('objects', ['ObjectNick'])
# Adding unique constraint on 'ObjectNick', fields ['db_nick', 'db_type', 'db_obj']
db.create_unique('objects_objectnick', ['db_nick', 'db_type', 'db_obj_id'])
def backwards(self, orm):
raise RuntimeError("This migration cannot be reversed.")
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.alias': {
'Meta': {'object_name': 'Alias'},
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'objects.objattribute': {
'Meta': {'object_name': 'ObjAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'objects.objectnick': {
'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'ObjectNick'},
'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}),
'db_real': ('django.db.models.fields.TextField', [], {}),
'db_type': ('django.db.models.fields.CharField', [], {'default': "'inputline'", 'max_length': '16', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['objects']

View file

@ -0,0 +1,121 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models, utils
class Migration(DataMigration):
def forwards(self, orm):
"Write your forwards methods here."
# we need to add a default lock string to all objects, then a separate set to Characters.
lockstring1 = 'control:id(1);get:all();edit:perm(Wizards);examine:perm(Builders);call:true();puppet:id(#4) or perm(Immortals) or pperm(Immortals);delete:id(1) or perm(Wizards)'
lockstring2 = 'control:id(#3) or perm(Immortals);get:perm(Wizards);edit:perm(Wizards);examine:perm(Builders);call:false();puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals);delete:perm(Wizards)'
try:
for obj in orm.ObjectDB.objects.all().exclude(db_player__isnull=False):
obj.db_lock_storage = lockstring1
obj.save()
for obj in orm.ObjectDB.objects.filter(db_player__isnull=False):
obj.db_lock_storage = lockstring2 % (obj.id, obj.db_player.id)
obj.save()
except utils.DatabaseError:
# running from scatch. In this case we just ignore this.
pass
def backwards(self, orm):
"Write your backwards methods here."
raise RuntimeError("You cannot reverse this migration.")
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.alias': {
'Meta': {'object_name': 'Alias'},
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'objects.objattribute': {
'Meta': {'object_name': 'ObjAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'objects.objectnick': {
'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'ObjectNick'},
'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']"}),
'db_real': ('django.db.models.fields.TextField', [], {}),
'db_type': ('django.db.models.fields.CharField', [], {'default': "'inputline'", 'max_length': '16', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['objects']

View file

@ -16,25 +16,24 @@ transparently through the decorating TypeClass.
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses.models import Attribute, TypedObject
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
from src.typeclasses.typeclass import TypeClass
from src.objects.manager import ObjectManager
from src.players.models import PlayerDB
from src.server.models import ServerConfig
from src.commands.cmdsethandler import CmdSetHandler
from src.commands import cmdhandler
from src.scripts.scripthandler import ScriptHandler
from src.utils import logger
from src.utils.utils import is_iter, to_unicode
from src.utils.utils import is_iter, to_unicode, to_str, mod_import
#PlayerDB = ContentType.objects.get(app_label="players", model="playerdb").model_class()
FULL_PERSISTENCE = settings.FULL_PERSISTENCE
try:
HANDLE_SEARCH_ERRORS = __import__(
settings.ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER).handle_search_errors, fromlist=[None]
except Exception:
from src.objects.object_search_funcs \
import handle_search_errors as HANDLE_SEARCH_ERRORS
AT_SEARCH_RESULT = mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
#------------------------------------------------------------
#
@ -80,80 +79,33 @@ class Alias(SharedMemoryModel):
#------------------------------------------------------------
#
# Nick
# Object Nicks
#
#------------------------------------------------------------
class Nick(SharedMemoryModel):
class ObjectNick(TypeNick):
"""
This model holds whichever alternate names this object
has for OTHER objects, but also for arbitrary strings,
channels, players etc. Setting a nick does not affect
the nicknamed object at all (as opposed to Aliases above),
and only this object will be able to refer to the nicknamed
object by the given nick.
The default nick types used by Evennia are:
inputline (default) - match against all input
player - match against player searches
obj - match against object searches
channel - used to store own names for channels
"""
db_nick = models.CharField(max_length=255, db_index=True) # the nick
db_real = models.TextField() # the aliased string
db_type = models.CharField(default="inputline", max_length=16, null=True, blank=True) # the type of nick
db_obj = models.ForeignKey("ObjectDB")
class Meta:
"Define Django meta options"
verbose_name = "Nickname"
verbose_name_plural = "Nicknames"
verbose_name = "Nickname for Objects"
verbose_name_plural = "Nicknames Objects"
unique_together = ("db_nick", "db_type", "db_obj")
class NickHandler(object):
class ObjectNickHandler(TypeNickHandler):
"""
Handles nick access and setting. Accessed through ObjectDB.nicks
"""
NickClass = ObjectNick
def __init__(self, obj):
"Setup"
self.obj = obj
def add(self, nick, realname, nick_type="inputline"):
"We want to assign a new nick"
if not nick or not nick.strip():
return
nick = nick.strip()
real = realname.strip()
query = Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
if query.count():
old_nick = query[0]
old_nick.db_real = real
old_nick.save()
else:
new_nick = Nick(db_nick=nick, db_real=real, db_type=nick_type, db_obj=self.obj)
new_nick.save()
def delete(self, nick, nick_type="inputline"):
"Removes a nick"
nick = nick.strip()
query = Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
if query.count():
# remove the found nick(s)
query.delete()
def get(self, nick=None, nick_type="inputline"):
if nick:
query = Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
query = query.values_list("db_real", flat=True)
if query.count():
return query[0]
else:
return nick
else:
return Nick.objects.filter(db_obj=self.obj)
def has(self, nick, nick_type="inputline"):
"Returns true/false if this nick is defined or not"
return Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type).count()
#------------------------------------------------------------
#
@ -239,7 +191,7 @@ class ObjectDB(TypedObject):
self.cmdset.update(init_mode=True)
self.scripts = ScriptHandler(self)
self.scripts.validate(init_mode=True)
self.nicks = NickHandler(self)
self.nicks = ObjectNickHandler(self)
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
@ -312,7 +264,7 @@ class ObjectDB(TypedObject):
loc = location
elif ObjectDB.objects.dbref(location):
# location is a dbref; search
loc = ObjectDB.objects.dbref_search(location)
loc = ObjectDB.objects.dbref_search(ocation)
if loc and hasattr(loc,'dbobj'):
loc = loc.dbobj
else:
@ -524,14 +476,11 @@ class ObjectDB(TypedObject):
global_search=False,
attribute_name=None,
use_nicks=False, location=None,
ignore_errors=False):
ignore_errors=False, player=False):
"""
Perform a standard object search in the database, handling
multiple results and lack thereof gracefully.
if local_only AND search_self are both false, a global
search is done instead.
ostring: (str) The string to match object names against.
Obs - To find a player, append * to the
start of ostring.
@ -544,7 +493,16 @@ class ObjectDB(TypedObject):
ignore_errors : Don't display any error messages even
if there are none/multiple matches -
just return the result as a list.
player : Don't search for an Object but a Player.
This will also find players that don't
currently have a character.
Use *<string> to search for objects controlled by a specific
player. Note that the object controlled by the player will be
returned, not the player object itself. This also means that
this will not find Players without a character. Use the keyword
player=True to find player objects.
Note - for multiple matches, the engine accepts a number
linked to the key in order to separate the matches from
each other without showing the dbref explicitly. Default
@ -554,22 +512,30 @@ class ObjectDB(TypedObject):
etc.
"""
if use_nicks:
if ostring.startswith('*'):
if ostring.startswith('*') or player:
# player nick replace
ostring = "*%s" % self.nicks.get(ostring.lstrip('*'), nick_type="player")
ostring = self.nicks.get(ostring.lstrip('*'), nick_type="player")
if not player:
ostring = "*%s" % ostring
else:
# object nick replace
ostring = self.nicks.get(ostring, nick_type="object")
results = ObjectDB.objects.object_search(self, ostring,
global_search=global_search,
attribute_name=attribute_name,
location=location)
if player:
if ostring in ("me", "self", "*me", "*self"):
results = [self.player]
else:
results = PlayerDB.objects.player_search(ostring.lstrip('*'))
else:
results = ObjectDB.objects.object_search(self, ostring,
global_search=global_search,
attribute_name=attribute_name,
location=location)
if ignore_errors:
return results
return HANDLE_SEARCH_ERRORS(self, ostring, results, global_search)
# this import is cache after the first call.
return AT_SEARCH_RESULT(self, ostring, results, global_search)
#
# Execution/action methods
@ -588,7 +554,7 @@ class ObjectDB(TypedObject):
raw_list = raw_string.split(None)
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
for nick in Nick.objects.filter(db_obj=self, db_type__in=("inputline","channel")):
for nick in ObjectNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")):
if nick.db_nick in raw_list:
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break
@ -605,7 +571,7 @@ class ObjectDB(TypedObject):
"""
# This is an important function that must always work.
# we use a different __getattribute__ to avoid recursive loops.
if object.__getattribute__(self, 'player'):
object.__getattribute__(self, 'player').msg(message, from_obj, data)
@ -796,9 +762,15 @@ class ObjectDB(TypedObject):
return False
# See if we need to kick the player off.
for session in self.sessions:
session.msg("Your character %s has been destroyed. Goodbye." % self.name)
session.session_disconnect()
session.msg("Your character %s has been destroyed." % self.name)
#session.session_disconnect()
# sever the connection (important!)
if object.__getattribute__(self, 'player') and self.player:
self.player.character = None
self.player = None
# if self.player:
# self.player.user.is_active = False
@ -811,6 +783,3 @@ class ObjectDB(TypedObject):
# Perform the deletion of the object
super(ObjectDB, self).delete()
return True
# Deferred import to avoid circular import errors.
from src.commands import cmdhandler

View file

@ -1,102 +0,0 @@
"""
Default functions for formatting and processing object searches.
This is in its own module due to them being possible to
replace from the settings file by use of setting the variables
ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER
ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER
Both the replacing functions must have the same name and same input/output
as the ones in this module.
"""
def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
"""
Takes a search result (a list) and
formats eventual errors.
emit_to_obj - object to receive feedback.
ostring - original search string
results - list of object matches, if any
global_search - if this was a global_search or not
(if it is, there might be an idea of supplying
dbrefs instead of only numbers)
"""
if not results:
emit_to_obj.msg("Could not find '%s'." % ostring)
return None
if len(results) > 1:
# we have more than one match. We will display a
# list of the form 1-objname, 2-objname etc.
# check if the emit_to_object may se dbrefs
show_dbref = global_search and \
emit_to_obj.check_permstring('Builders')
string = "More than one match for '%s'" % ostring
string += " (please narrow target):"
for num, result in enumerate(results):
invtext = ""
dbreftext = ""
if result.location == emit_to_obj:
invtext = " (carried)"
if show_dbref:
dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext)
emit_to_obj.msg(string.strip())
return None
else:
return results[0]
def object_multimatch_parser(ostring):
"""
Parse number-identifiers.
Sometimes it can happen that there are several objects in the room
all with exactly the same key/identifier. Showing dbrefs to
separate them is not suitable for all types of games since it's
unique to that object (and e.g. in rp-games the object might not
want to be identified like that). Instead Evennia allows for
dbref-free matching by letting the user number which of the
objects in a multi-match they want.
Ex for use in game session:
> look
You see: ball, ball, ball and ball.
> get ball
There where multiple matches for ball:
1-ball
2-ball
3-ball
4-ball
> get 3-ball
You get the ball.
The actual feedback upon multiple matches has to be
handled by the searching command. The syntax shown above is the
default.
For replacing, the method must be named the same and
take the searchstring as argument and
return a tuple (int, string) where int is the identifier
matching which of the results (in order) should be used to
pick out the right match from the multimatch). Note
that the engine assumes this number to start with 1 (i.e. not
zero as in normal Python).
"""
if not isinstance(ostring, basestring):
return (None, ostring)
if not '-' in ostring:
return (None, ostring)
try:
index = ostring.find('-')
number = int(ostring[:index])-1
return (number, ostring[index+1:])
except ValueError:
#not a number; this is not an identifier.
return (None, ostring)
except IndexError:
return (None, ostring)

View file

@ -49,6 +49,9 @@ class Object(TypeClass):
"""
This sets up the default properties of an Object,
just before the more general at_object_creation.
Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one).
"""
# the default security setup fallback for a generic
# object. Overload in child for a custom setup. Also creation
@ -63,6 +66,7 @@ class Object(TypeClass):
self.locks.add("delete:perm(Wizards)") # delete object
self.locks.add("get:all()") # pick up object
self.locks.add("call:true()") # allow to call commands on this object
self.locks.add("puppet:id(%s) or perm(Immortals) or pperm(Immortals)" % dbref) # restricts puppeting of this object
def at_object_creation(self):
"""
@ -310,9 +314,11 @@ class Character(Object):
def basetype_setup(self):
"""
Setup character-specific security
Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one).
"""
super(Character, self).basetype_setup()
self.locks.add("puppet:id(%s) or perm(Immortals)" % self.dbobj.dbref) # who may become this object's player
self.locks.add("get:false()") # noone can pick up the character
self.locks.add("call:false()") # no commands can be called on character
@ -348,9 +354,13 @@ class Room(Object):
"""
Simple setup, shown as an example
(since default is None anyway)
Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one).
"""
super(Room, self).basetype_setup()
self.locks.add("puppet:false()") # would be weird to puppet a room ...
self.locks.add("get:false()")
super(Room, self).basetype_setup()
@ -371,9 +381,13 @@ class Exit(Object):
def basetype_setup(self):
"""
Setup exit-security
Don't change this, instead edit at_object_creation() to
overload the defaults (it is called after this one).
"""
# the lock is open to all by default
super(Exit, self).basetype_setup()
self.locks.add("puppet:false()") # would be weird to puppet an exit ...
self.locks.add("traverse:all()") # who can pass through exit
self.locks.add("get:false()") # noone can pick up the exit

View file

@ -146,17 +146,13 @@ class PlayerManager(TypedObjectManager):
ostring = a string or database id.
"""
players = []
try:
# try dbref match
dbref = int(ostring.strip('#'))
players = self.filter(id=dbref)
except Exception:
pass
if not players:
players = self.filter(user__username=ostring)
return players
ostring = ostring.lstrip("*")
dbref = self.dbref(ostring)
if dbref:
matches = self.filter(id=dbref)
if matches:
return matches
return self.filter(user__username__iexact=ostring)
def swap_character(self, player, new_character, delete_old_character=False):
"""

View file

@ -0,0 +1,95 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'PlayerDB.db_cmdset_storage'
db.add_column('players_playerdb', 'db_cmdset_storage', self.gf('django.db.models.fields.TextField')(null=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'PlayerDB.db_cmdset_storage'
db.delete_column('players_playerdb', 'db_cmdset_storage')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerattribute': {
'Meta': {'object_name': 'PlayerAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['players']

View file

@ -0,0 +1,116 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'PlayerNick'
db.create_table('players_playernick', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('db_nick', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('db_real', self.gf('django.db.models.fields.TextField')()),
('db_type', self.gf('django.db.models.fields.CharField')(default='inputline', max_length=16, null=True, blank=True)),
('db_obj', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['players.PlayerDB'])),
))
db.send_create_signal('players', ['PlayerNick'])
# Adding unique constraint on 'PlayerNick', fields ['db_nick', 'db_type', 'db_obj']
db.create_unique('players_playernick', ['db_nick', 'db_type', 'db_obj_id'])
def backwards(self, orm):
# Removing unique constraint on 'PlayerNick', fields ['db_nick', 'db_type', 'db_obj']
db.delete_unique('players_playernick', ['db_nick', 'db_type', 'db_obj_id'])
# Deleting model 'PlayerNick'
db.delete_table('players_playernick')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerattribute': {
'Meta': {'object_name': 'PlayerAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'players.playernick': {
'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'PlayerNick'},
'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_real': ('django.db.models.fields.TextField', [], {}),
'db_type': ('django.db.models.fields.CharField', [], {'default': "'inputline'", 'max_length': '16', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['players']

View file

@ -44,10 +44,16 @@ from django.conf import settings
from django.db import models
from django.contrib.auth.models import User
from django.utils.encoding import smart_str
from django.contrib.contenttypes.models import ContentType
from src.server.sessionhandler import SESSIONS
from src.players import manager
from src.typeclasses.models import Attribute, TypedObject
from src.utils import logger
from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler
from src.utils import logger, utils
from src.commands.cmdsethandler import CmdSetHandler
from src.commands import cmdhandler
AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
#------------------------------------------------------------
#
@ -68,6 +74,36 @@ class PlayerAttribute(Attribute):
verbose_name = "Player Attribute"
verbose_name_plural = "Player Attributes"
#------------------------------------------------------------
#
# Player Nicks
#
#------------------------------------------------------------
class PlayerNick(TypeNick):
"""
The default nick types used by Evennia are:
inputline (default) - match against all input
player - match against player searches
obj - match against object searches
channel - used to store own names for channels
"""
db_obj = models.ForeignKey("PlayerDB")
class Meta:
"Define Django meta options"
verbose_name = "Nickname for Players"
verbose_name_plural = "Nicknames Players"
unique_together = ("db_nick", "db_type", "db_obj")
class PlayerNickHandler(TypeNickHandler):
"""
Handles nick access and setting. Accessed through ObjectDB.nicks
"""
NickClass = PlayerNick
#------------------------------------------------------------
#
# PlayerDB
@ -116,6 +152,9 @@ class PlayerDB(TypedObject):
# the in-game object connected to this player (if any).
# Use the property 'obj' to access.
db_obj = models.ForeignKey("objects.ObjectDB", null=True)
# database storage of persistant cmdsets.
db_cmdset_storage = models.TextField(null=True)
# Database manager
objects = manager.PlayerManager()
@ -123,6 +162,14 @@ class PlayerDB(TypedObject):
class Meta:
app_label = 'players'
def __init__(self, *args, **kwargs):
"Parent must be initiated first"
TypedObject.__init__(self, *args, **kwargs)
# handlers
self.cmdset = CmdSetHandler(self)
self.cmdset.update(init_mode=True)
self.nicks = PlayerNickHandler(self)
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
@ -172,6 +219,26 @@ class PlayerDB(TypedObject):
self.db_obj = None
self.save()
character = property(character_get, character_set, character_del)
# cmdset_storage property
#@property
def cmdset_storage_get(self):
"Getter. Allows for value = self.name. Returns a list of cmdset_storage."
if self.db_cmdset_storage:
return [path.strip() for path in self.db_cmdset_storage.split(',')]
return []
#@cmdset_storage.setter
def cmdset_storage_set(self, value):
"Setter. Allows for self.name = value. Stores as a comma-separated string."
if utils.is_iter(value):
value = ",".join([str(val).strip() for val in value])
self.db_cmdset_storage = value
self.save()
#@cmdset_storage.deleter
def cmdset_storage_del(self):
"Deleter. Allows for del self.name"
self.db_cmdset_storage = ""
self.save()
cmdset_storage = property(cmdset_storage_get, cmdset_storage_set, cmdset_storage_del)
class Meta:
"Define Django meta options"
@ -245,15 +312,23 @@ class PlayerDB(TypedObject):
Evennia -> User
This is the main route for sending data back to the user from the server.
"""
if from_obj:
try:
from_obj.at_msg_send(outgoing_string, to_obj=self, data=data)
except Exception:
pass
if object.__getattribute__(self, "character"):
if self.character.at_msg_receive(outgoing_string, from_obj=from_obj, data=data):
for session in object.__getattribute__(self, 'sessions'):
session.msg(outgoing_string, data)
if (object.__getattribute__(self, "character")
and not self.character.at_msg_receive(outgoing_string, from_obj=from_obj, data=data)):
# the at_msg_receive() hook may block receiving of certain messages
return
outgoing_string = utils.to_str(outgoing_string, force_string=True)
for session in object.__getattribute__(self, 'sessions'):
session.msg(outgoing_string, data)
def swap_character(self, new_character, delete_old_character=False):
"""
@ -261,3 +336,49 @@ class PlayerDB(TypedObject):
"""
return self.__class__.objects.swap_character(self, new_character, delete_old_character=delete_old_character)
#
# Execution/action methods
#
def execute_cmd(self, raw_string):
"""
Do something as this playe. This command transparently
lets its typeclass execute the command.
raw_string - raw command input coming from the command line.
"""
# nick replacement - we require full-word matching.
raw_string = utils.to_unicode(raw_string)
raw_list = raw_string.split(None)
raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]]
for nick in PlayerNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")):
if nick.db_nick in raw_list:
raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1)
break
cmdhandler.cmdhandler(self.typeclass(self), raw_string)
def search(self, ostring, global_search=False, attribute_name=None, use_nicks=False,
location=None, ignore_errors=False, player=False):
"""
A shell method mimicking the ObjectDB equivalent, for easy inclusion from
commands regardless of if the command is run by a Player or an Object.
"""
if self.character:
# run the normal search
return self.character.search(ostring, global_search=global_search, attribute_name=attribute_name,
use_nicks=use_nicks, location=location,
ignore_errors=ignore_errors, player=player)
if player:
# seach for players
matches = self.__class__.objects.player_search(ostring)
else:
# more limited player-only search. Still returns an Object.
ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class()
matches = ObjectDB.objects.object_search(self, ostring, global_search=global_search)
# deal with results
matches = AT_SEARCH_RESULT(self, ostring, matches, global_search=global_search)
return matches

View file

@ -12,19 +12,20 @@ instead for most things).
"""
from src.typeclasses.typeclass import TypeClass
from settings import CMDSET_OOC
class Player(TypeClass):
"""
Base typeclass for all Players.
"""
def at_player_creation(self):
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 is called once, the very first time
the player is created (i.e. first time they
register with the game). It's a good place
to store attributes all players should have,
like configuration values etc.
"""
# the text encoding to use.
self.db.encoding = "utf-8"
@ -35,6 +36,21 @@ class Player(TypeClass):
self.locks.add("boot:perm(Wizards)")
self.locks.add("msg:all()")
# The ooc player cmdset
self.cmdset.add_default(CMDSET_OOC, permanent=True)
self.cmdset.outside_access = False
def at_player_creation(self):
"""
This is called once, the very first time
the player is created (i.e. first time they
register with the game). It's a good place
to store attributes all players should have,
like configuration values etc.
"""
pass
# Note that the hooks below also exist
# in the character object's typeclass. You
# can often ignore these and rely on the

View file

@ -1,10 +1,12 @@
"""
This module implements the main Evennia server process, the core of
the game engine. Only import this once!
the game engine. Don't import this module! If you need to access the
server processes from code, instead import sessionhandler.SESSIONS
and use its 'server' property.
This module should be started with the 'twistd' executable since it
sets up all the networking features. (this is done by
game/evennia.py).
sets up all the networking features. (this is done by automatically
by game/evennia.py).
"""
import time

View file

@ -145,13 +145,12 @@ class SessionBase(object):
and have to be done right after this function!
"""
if self.logged_in:
character = self.get_character()
if character:
uaccount = character.player.user
uaccount.last_login = datetime.now()
uaccount.save()
self.at_disconnect()
self.logged_in = False
player = self.get_player()
uaccount = player.user
uaccount.last_login = datetime.now()
uaccount.save()
self.at_disconnect()
self.logged_in = False
SESSIONS.remove_session(self)
def session_validate(self):
@ -227,16 +226,18 @@ class SessionBase(object):
return
# all other inputs, including empty inputs
character = self.get_character()
character = self.get_character()
if character:
#print "loggedin _execute_cmd: '%s' __ %s" % (command_string, character)
# normal operation.
character.execute_cmd(command_string)
else:
#print "unloggedin _execute_cmd: '%s' __ %s" % (command_string, character)
# we are not logged in yet; call cmdhandler directly
cmdhandler.cmdhandler(self, command_string, unloggedin=True)
if self.logged_in:
# there is no character, but we are logged in. Use player instead.
self.get_player().execute_cmd(command_string)
else:
# we are not logged in. Use special unlogged-in call.
cmdhandler.cmdhandler(self, command_string, unloggedin=True)
self.update_session_counters()
def get_data_obj(self, **kwargs):

View file

@ -136,21 +136,18 @@ DATABASE_PORT = ''
###################################################
# An alternate command parser module to use
# (if not set, uses 'src.commands.cmdparser')
ALTERNATE_PARSER = ""
COMMAND_PARSER = "src.commands.cmdparser.cmdparser"
# How many space-separated words a command name may have
# and still be identified as one single command
# (e.g. 'push button' instead of 'pushbutton')
COMMAND_MAXLEN = 3
# The handler that outputs errors when searching
# objects using object.search(). (If not set, uses
# src.objects.object_search_funcs.handle_search_errors)
ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER = ""
# objects using object.search().
SEARCH_AT_RESULT = "src.commands.cmdparser.at_search_result"
# The parser used in order to separate multiple
# object matches (so you can separate between same-named
# objects without using dbrefs). (If not set, uses
# src.objects.object_search_funcs.object_multimatch_parser).
ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER = ""
# objects without using dbrefs).
SEARCH_AT_MULTIMATCH_INPUT = "src.commands.cmdparser.at_multimatch_input"
# The module holding text strings for the connection screen.
# This module should contain one or more variables
# with strings defining the look of the screen.
@ -162,8 +159,10 @@ CONNECTION_SCREEN_MODULE = "game.gamesrc.world.connection_screens"
# Command set used before player has logged in
CMDSET_UNLOGGEDIN = "game.gamesrc.commands.basecmdset.UnloggedinCmdSet"
# Default set for logged in players (fallback)
# Default set for logged in player with characters (fallback)
CMDSET_DEFAULT = "game.gamesrc.commands.basecmdset.DefaultCmdSet"
# Command set for players without a character (ooc)
CMDSET_OOC = "game.gamesrc.commands.basecmdset.OOCCmdSet"
###################################################
# Default Object typeclasses

View file

@ -294,6 +294,87 @@ class Attribute(SharedMemoryModel):
return self.locks.check(accessing_obj, access_type=access_type, default=default)
#------------------------------------------------------------
#
# Nicks
#
#------------------------------------------------------------
class TypeNick(SharedMemoryModel):
"""
This model holds whichever alternate names this object
has for OTHER objects, but also for arbitrary strings,
channels, players etc. Setting a nick does not affect
the nicknamed object at all (as opposed to Aliases above),
and only this object will be able to refer to the nicknamed
object by the given nick.
The default nick types used by Evennia are:
inputline (default) - match against all input
player - match against player searches
obj - match against object searches
channel - used to store own names for channels
"""
db_nick = models.CharField(max_length=255, db_index=True) # the nick
db_real = models.TextField() # the aliased string
db_type = models.CharField(default="inputline", max_length=16, null=True, blank=True) # the type of nick
db_obj = None #models.ForeignKey("ObjectDB")
class Meta:
"Define Django meta options"
abstract = True
verbose_name = "Nickname"
verbose_name_plural = "Nicknames"
unique_together = ("db_nick", "db_type", "db_obj")
class TypeNickHandler(object):
"""
Handles nick access and setting. Accessed through ObjectDB.nicks
"""
NickClass = TypeNick
def __init__(self, obj):
"Setup"
self.obj = obj
def add(self, nick, realname, nick_type="inputline"):
"We want to assign a new nick"
if not nick or not nick.strip():
return
nick = nick.strip()
real = realname.strip()
query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
if query.count():
old_nick = query[0]
old_nick.db_real = real
old_nick.save()
else:
new_nick = self.NickClass(db_nick=nick, db_real=real, db_type=nick_type, db_obj=self.obj)
new_nick.save()
def delete(self, nick, nick_type="inputline"):
"Removes a nick"
nick = nick.strip()
query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
if query.count():
# remove the found nick(s)
query.delete()
def get(self, nick=None, nick_type="inputline"):
if nick:
query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
query = query.values_list("db_real", flat=True)
if query.count():
return query[0]
else:
return nick
else:
return self.NickClass.objects.filter(db_obj=self.obj)
def has(self, nick, nick_type="inputline"):
"Returns true/false if this nick is defined or not"
return self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type).count()
#------------------------------------------------------------
#
# Typed Objects
@ -992,9 +1073,12 @@ class TypedObject(SharedMemoryModel):
def check_permstring(self, permstring):
"""
This explicitly checks for we hold particular permission without involving
This explicitly checks if we hold particular permission without involving
any locks.
"""
if self.player and self.player.is_superuser:
return True
if not permstring:
return False
perm = permstring.lower()

View file

@ -390,6 +390,7 @@ def create_player(name, email, password,
new_player = PlayerDB(db_key=name, user=new_user)
new_player.save()
new_player.basetype_setup() # setup the basic locks and cmdset
# call hook method (may override default permissions)
new_player.at_player_creation()

View file

@ -173,8 +173,8 @@ def reset_loop():
[m.locks.reset() for m in Msg.objects.all()]
[c.locks.reset() for c in Channel.objects.all()]
[s.locks.reset() for s in ScriptDB.objects.all()]
[p.locks.reset() for p in PlayerDB.objects.all()]
[(o.typeclass(o), o.cmdset.reset(), o.locks.reset()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass(p), p.cmdset.reset(), p.locks.reset()) for p in PlayerDB.get_all_cached_instances()]
t2 = time.time()
cemit_info(" ... Loop finished in %g seconds." % (t2-t1))

View file

@ -27,11 +27,15 @@ Example: To reach the search method 'get_object_with_user'
# Import the manager methods to be wrapped
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
from src.scripts.models import ScriptDB
from src.comms.models import Msg, Channel
from src.help.models import HelpEntry
from django.contrib.contenttypes.models import ContentType
# import objects this way to avoid circular import problems
ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class()
PlayerDB = ContentType.objects.get(app_label="players", model="playerdb").model_class()
ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class()
Msg = ContentType.objects.get(app_label="comms", model="msg").model_class()
Channel = ContentType.objects.get(app_label="comms", model="channel").model_class()
HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class()
#
# Search objects as a character

View file

@ -20,3 +20,5 @@ class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
if (applabel.startswith('src.') or applabel.startswith('game.'))]
return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs)

View file

@ -235,14 +235,28 @@ def dbref(dbref):
except Exception:
return None
return dbref
return None
def to_unicode(obj, encoding='utf-8'):
def to_unicode(obj, encoding='utf-8', force_string=False):
"""
This decodes a suitable object to
the unicode format. Note that one
needs to encode it back to utf-8
before writing to disk or printing.
This decodes a suitable object to the unicode format. Note that
one needs to encode it back to utf-8 before writing to disk or
printing. Note that non-string objects are let through without
conversion - this is important for e.g. Attributes. Use
force_string to enforce conversion of objects to string. .
"""
if force_string and not isinstance(obj, basestring):
# some sort of other object. Try to
# convert it to a string representation.
if hasattr(obj, '__str__'):
obj = obj.__str__()
elif hasattr(obj, '__unicode__'):
obj = obj.__unicode__()
else:
# last resort
obj = str(obj)
if isinstance(obj, basestring) and not isinstance(obj, unicode):
try:
obj = unicode(obj, encoding)
@ -257,11 +271,26 @@ def to_unicode(obj, encoding='utf-8'):
raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding))
return obj
def to_str(obj, encoding='utf-8'):
def to_str(obj, encoding='utf-8', force_string=False):
"""
This encodes a unicode string back to byte-representation,
for printing, writing to disk etc.
for printing, writing to disk etc. Note that non-string
objects are let through without modification - this is
required e.g. for Attributes. Use force_string to force
conversion of objects to strings.
"""
if force_string and not isinstance(obj, basestring):
# some sort of other object. Try to
# convert it to a string representation.
if hasattr(obj, '__str__'):
obj = obj.__str__()
elif hasattr(obj, '__unicode__'):
obj = obj.__unicode__()
else:
# last resort
obj = str(obj)
if isinstance(obj, basestring) and isinstance(obj, unicode):
try:
obj = obj.encode(encoding)
@ -321,7 +350,7 @@ def inherits_from(obj, parent):
Takes an object and tries to determine if it inherits at any distance
from parent. What differs this function from e.g. isinstance()
is that obj may be both an instance and a class, and parent
may be an instance, a class, or the python path to a class (counting
< may be an instance, a class, or the python path to a class (counting
from the evennia root directory).
"""
@ -480,10 +509,11 @@ def has_parent(basepath, obj):
# instance. Not sure if one should defend against this.
return False
def mod_import(mod_path):
def mod_import(mod_path, propname=None):
"""
Takes filename of a module, converts it to a python path
and imports it.
and imports it. If property is given, return the named
property from this module instead of the module itself.
"""
def log_trace(errmsg=None):
@ -494,6 +524,7 @@ def mod_import(mod_path):
"""
from traceback import format_exc
from twisted.python import log
print errmsg
tracestring = format_exc()
if tracestring:
@ -507,23 +538,39 @@ def mod_import(mod_path):
for line in errmsg.splitlines():
log.msg('[EE] %s' % line)
if not os.path.isabs(mod_path):
mod_path = os.path.abspath(mod_path)
path, filename = mod_path.rsplit(os.path.sep, 1)
modname = filename.rstrip('.py')
# first try to import as a python path
try:
mod = __import__(mod_path, fromlist=["None"])
except ImportError:
# try absolute path import instead
try:
result = imp.find_module(modname, [path])
except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return
try:
mod = imp.load_module(modname, *result)
except ImportError:
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
mod = None
# we have to close the file handle manually
result[0].close()
if not os.path.isabs(mod_path):
mod_path = os.path.abspath(mod_path)
path, filename = mod_path.rsplit(os.path.sep, 1)
modname = filename.rstrip('.py')
try:
result = imp.find_module(modname, [path])
except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
return
try:
mod = imp.load_module(modname, *result)
except ImportError:
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
mod = None
# we have to close the file handle manually
result[0].close()
if mod and propname:
# we have a module, extract the sought property from it.
try:
mod_prop = mod.__dict__[to_str(propname)]
except KeyError:
log_trace("Could not import property '%s' from module %s." % (propname, mod_path))
return None
return mod_prop
return mod
def string_from_module(modpath, variable=None):