Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch

This commit is contained in:
Griatch 2010-08-29 18:46:58 +00:00
parent df29defbcd
commit f83c2bddf8
222 changed files with 22304 additions and 14371 deletions

View file

@ -1,19 +0,0 @@
"""
Player command alias management stuff.
"""
from src.config.models import CommandAlias
CMD_ALIAS_LIST = {}
def load_cmd_aliases():
"""
Load up our command aliases.
"""
alias_list = CommandAlias.objects.all()
# Reset the list.
CMD_ALIAS_LIST.clear()
for alias in alias_list:
CMD_ALIAS_LIST[alias.user_input] = alias.equiv_command
print ' Command Aliases Loaded: %i' % (len(CMD_ALIAS_LIST),)

284
src/cache/cache.py vendored
View file

@ -1,284 +0,0 @@
"""
The cache module implements a volatile and
semi-volatile storage
object mechanism for Evennia.
Volatile Cache:
Data stored using the Cache is stored in
memory (so requires no database access). The
drawback is that it will be lost upon a
reboot. It is however @reload-safe unless
explicitly flushed with @reload/cache (the cache
is not flushed with @reload/all)
Access I/O of the cache is normally done through
the object model, using e.g.
source_object.cache.variable = data
and
data = source_object.cache.variable
Semi-persistent Cache:
This form of cache works like the volatile cache but the
data will survive a reboot since the state is backed up
to the database at regular intervals (it is thus a save-point
scheme). How often the backup is done can be set in preferences.
Access I/O:
source_object.pcache = data
and
data = source_object.pcache
Whereas you can also access the cache(s) using
set_cache/get_cache and set_pcache/get_pcache
directly, you must continue to use these methods
on a particular piece of data once you start using them
(i.e. you won't be able to use dot-notation to retrieve
a piece of data saved explicitly using set_cache())
"""
from src.cache.models import PersistentCache
from src import logger
class Cache(object):
"""
Each Cache object is intended to store the volatile properties
of one in-game database object or one user-defined application.
By default, the object allows to safely reference variables on
itself also if it does not exist (so test = cache.var will
set test to None if cache has no attribute var instead of raising
a traceback). This allows for stable and transparent operation
during most circumstances.
Due to how the objects are stored in database (using pickle), the
object has a __safedot switch to deactivate the safe mode
of variables mentioned above; this is necessary in order to have
pickle work correctly (it does not like redefining __getattr__)
and should not be used for anything else.
Observe that this object in itself is not persistent, the only
thing determining if it is persistent is which of the global
variables (CACHE or PCACHE) it is saved in (and that there
exists an event to save the cache at regular intervals, use
@ps to check that this is the case).
"""
__safedot = True
def __getattr__(self, key):
"""
This implements a safe dot notation (i.e. it will not
raise an exception if a variable does not exist)
"""
if self.__safedot:
return self.__dict__.get(key, None)
else:
super(Cache, self).__getattr__(key)
def show(self):
"""
Return nice display of data.
"""
return ", ".join(key for key in sorted(self.__dict__.keys())
if key != '_Cache__safedot')
def store(self, key, value):
"""
Store data directly, without going through the dot notation.
"""
if key != '__safedot':
self.__dict__[key] = value
def retrieve(self, key):
"""
Retrieve data directly, without going through dot notation.
Note that this intentionally raises a KeyError if key is not
found. This is mainly used by get_cache to determine if a
new cache object should be created.
"""
return self.__dict__[key]
def pickle_yes(self):
"""
Since pickle cannot handle a custom getattr, we
need to deactivate it before pickling.
"""
self.__safedot = False
for data in (data for data in self.__dict__.values()
if type(data)==type(self)):
data.pickle_yes()
def pickle_no(self):
"""
Convert back from pickle mode to normal safe dot notation.
"""
self.__safedot = True
for data in (data for data in self.__dict__.values()
if type(data)==type(self)):
data.pickle_no()
def has_key(self, key):
"""
Decide if cache has a particular piece of data.
"""
return key in self.__dict__
def to_dict(self):
"""
Return all data stored in cache in
the form of a dictionary.
"""
return self.__dict__
def del_key(self, key):
"""
Clear cache data.
"""
if key in self.__dict__:
del self.__dict__[key]
# Cache access functions - these only deal with the default global
# cache and pcache.
# Volatile cache
def set_cache(cache_key, value):
"""
Set a value in the volatile cache (oftenmost this is done
through properties instead).
"""
CACHE.store(cache_key, value)
def get_cache(cache_key):
"""
Retrieve a cache object from the storage. This is primarily
used by the objects.models.Object.cache property.
cache_key - identifies the cache storage area (e.g. an object dbref)
reference - this bool describes if the function is called as part of
a obj.cache.cache_key.data contstruct.
"""
try:
return CACHE.retrieve(cache_key)
except:
CACHE.store(cache_key, Cache())
return CACHE.retrieve(cache_key)
def flush_cache(cache_key=None):
"""
Clears a particular cache_key from memory. If
no key is given, entire cache is flushed.
"""
global CACHE
if cache_key == None:
CACHE = Cache()
else:
CACHE.del_key(cache_key)
# Persistent cache
def set_pcache(cache_key, value):
"""
Set a value in the volatile cache (oftenmost this is done
through properties instead).
"""
PCACHE.store(cache_key, value)
def get_pcache(pcache_key):
"""
Retrieve a pcache object from the storage. This is primarily
used by the objects.models.Object.cache property.
cache_key - identifies the cache storage area (e.g. an object dbref)
"""
try:
return PCACHE.retrieve(pcache_key)
except KeyError:
PCACHE.store(pcache_key, Cache())
return PCACHE.retrieve(pcache_key)
def flush_pcache(pcache_key=None):
"""
Clears a particular cache_key from memory. If
no key is given, entire cache is flushed.
"""
global PCACHE
if pcache_key == None:
PCACHE = Cache()
elif pcache_key in PCACHE.__dict__:
PCACHE.del_key(pcache_key)
def show():
"""
Show objects stored in caches
"""
return CACHE.show(), PCACHE.show()
# Admin-level commands for initializing and saving/loading pcaches.
def init_pcache(cache_name=None):
"""
Creates the global pcache object in database.
(this is normally only called by initial_setup.py)
"""
from src.cache.managers.cache import GLOBAL_PCACHE_NAME
pcache = PersistentCache()
if cache_name:
pcache.cache_name = cache_name
else:
pcache.cache_name = GLOBAL_PCACHE_NAME
#initial save of the the empty pcache object to database
pcache.save()
#create empty storage object in cache
pcache.save_cache(Cache())
def save_pcache(cache_name=""):
"""
Force-save persistent cache right away.
"""
try:
if cache_name:
pcache = PersistentCache.objects.get(cache_name=cache_name)
else:
pcache = PersistentCache.objects.get_default_pcache()
except:
logger.log_errmsg("Save error: %s Pcache not initialized." % cache_name)
return
pcache.save_cache(PCACHE)
def load_pcache(cache_name=""):
"""
Load pcache from database storage. This is also called during
startup and fills the pcache with persistent cache data.
"""
global PCACHE
try:
if cache_name:
pcache = PersistentCache.objects.get(cache_name=cache_name)
return pcache
else:
pcache = PersistentCache.objects.get_default_pcache()
except:
logger.log_errmsg("Could not load %s: Pcache not found." % cache_name)
return
if pcache :
print " Loading persistent cache from disk."
unpacked = pcache.load_cache()
if unpacked:
PCACHE = unpacked
# Volatile Cache. This is a non-persistent cache. It will be lost upon
# a reboot. This can be referenced directly, but most
# transparently it's accessed through the object model.
CACHE = Cache()
# Persistent Cache. The system will make sure to save the contents of this
# cache at regular intervals, recovering it after a server
# reboot. It is accessed directly or through the object model.
PCACHE = Cache()

View file

@ -1,20 +0,0 @@
"""
Custom manager for Cache objects
"""
from django.db import models
# This is the (arbitrary, but consistent) name used by the
# global interval-saved (persistent) cache (this is
# used by initial_setup)
GLOBAL_PCACHE_NAME = "_global_persistent_cache"
class CacheManager(models.Manager):
"""
Custom cache manager.
"""
def get_default_pcache(self):
"""
Find and return the global pcache object.
"""
return self.get(cache_name=GLOBAL_PCACHE_NAME)

56
src/cache/models.py vendored
View file

@ -1,56 +0,0 @@
"""
This implements a database storage cache for storing global
cache data persistently.
It is intended to be used with an event timer for updating
semi-regularly (otherwise, object attributes are better to use
if full persistency is needed).
"""
from django.db import models
from django.conf import settings
from src.cache.managers.cache import CacheManager
# 091120 - there is a bug in cPickle for importing the
# custom cache objects; only normal pickle works. /Griatch
import pickle
#try:
# import cPickle as pickle
#except ImportError:
# import pickle
class PersistentCache(models.Model):
"""
Implements a simple pickled database object, without
using the in-game object attribute model.
"""
cache_name = models.CharField(max_length=255)
cache_data = models.TextField(blank=True)
objects = CacheManager()
class Meta:
permissions = settings.PERM_CACHE
def load_cache(self):
"""
Recovers cache from database storage.
"""
cache_data = str(self.cache_data)
#print "loading cache: %s" % cache_data
if cache_data:
cache_data = pickle.loads(cache_data)
cache_data.pickle_no()
return cache_data
else:
return None
def save_cache(self, cache_obj):
"""
Stores a cache as a pickle.
"""
#print "saving ... '%s': %s" % (cache_obj,cache_obj.show())
cache_obj.pickle_yes()
self.cache_data = pickle.dumps(cache_obj)
cache_obj.pickle_no()
self.save()

1
src/cache/views.py vendored
View file

@ -1 +0,0 @@
# Create your views here.

View file

@ -1,14 +0,0 @@
from django.contrib import admin
from src.channels.models import CommChannel, CommChannelMessage, CommChannelMembership
class CommChannelAdmin(admin.ModelAdmin):
list_display = ('name', 'ansi_name', 'owner', 'description', 'is_joined_by_default')
admin.site.register(CommChannel, CommChannelAdmin)
class CommChannelMembershipAdmin(admin.ModelAdmin):
list_display = ('channel', 'listener', 'user_alias', 'is_listening')
admin.site.register(CommChannelMembership, CommChannelMembershipAdmin)
class CommChannelMessageAdmin(admin.ModelAdmin):
list_display = ('channel', 'date_sent', 'message')
admin.site.register(CommChannelMessage, CommChannelMessageAdmin)

View file

@ -1,126 +0,0 @@
"""
Models for the help system.
"""
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User, Group
from src.objects.models import Object
from src.ansi import parse_ansi
class CommChannel(models.Model):
"""
The CommChannel class represents a comsys channel in the vein of MUX/MUSH.
"""
name = models.CharField(max_length=255)
ansi_name = models.CharField(max_length=255)
owner = models.ForeignKey(Object, related_name="channel_owner_set")
description = models.CharField(max_length=80, blank=True, null=True)
is_joined_by_default = models.BooleanField(default=False)
req_grp = models.ManyToManyField(Group, blank=True, null=True)
def __str__(self):
return "%s" % (self.name,)
class Meta:
ordering = ['-name']
permissions = settings.PERM_CHANNELS
def get_name(self):
"""
Returns a channel's name.
"""
return self.name
def get_header(self):
"""
Returns the channel's header text, or what is shown before each channel
message.
"""
return parse_ansi(self.ansi_name)
def get_owner(self):
"""
Returns a channels' owner.
"""
return self.owner
def set_name(self, new_name):
"""
Rename a channel
"""
self.name = parse_ansi(new_name, strip_ansi=True)
self.header = "[%s]" % (parse_ansi(new_name),)
self.save()
def set_header(self, new_header):
"""
Sets a channel's header text.
"""
self.header = parse_ansi(new_header)
self.save()
def set_owner(self, new_owner):
"""
Sets a channel's owner.
"""
self.owner = new_owner
self.save()
def set_description(self, new_description):
"""
Sets a channel's description.
"""
self.description = new_description
self.save()
def controlled_by(self, pobject):
"""
Use this to see if another object controls the channel. This is means
that the specified object either owns the channel or has special
permissions to control it.
pobject: (Object) Player object to check for control.
"""
if pobject.is_superuser():
return True
if self.owner and self.owner.id == pobject.id:
# If said object owns the target, then give it the green.
return True
# They've failed to meet any of the above conditions.
return False
def get_default_chan_alias(self):
"""
Returns a default channel alias for the channel if none is provided.
"""
return self.name[:3].lower()
class CommChannelMembership(models.Model):
"""
Used to track which channels an Object is listening to.
"""
channel = models.ForeignKey(CommChannel, related_name="membership_set")
listener = models.ForeignKey(Object, related_name="channel_membership_set")
user_alias = models.CharField(max_length=10)
comtitle = models.CharField(max_length=25, blank=True)
is_listening = models.BooleanField(default=True)
def __str__(self):
return "%s: %s" % (self.channel.name, self.listener.name)
class CommChannelMessage(models.Model):
"""
A single logged channel message.
"""
channel = models.ForeignKey(CommChannel, related_name="msg_channel")
message = models.TextField()
date_sent = models.DateTimeField(editable=False, auto_now_add=True)
class Meta:
ordering = ['-date_sent']
def __str__(self):
return "%s: %s" % (self.channel.name, self.message)

View file

@ -1,564 +0,0 @@
"""
This is the command processing module. It is instanced once in the main
server module and the handle() function is hit every time a player sends
something.
"""
#import time
from traceback import format_exc
from django.conf import settings
#from django.contrib.contenttypes.models import ContentType
from objects.models import Object
import defines_global
import cmdtable
import statetable
import logger
import comsys
import alias_mgr
COMMAND_MAXLEN = settings.COMMAND_MAXLEN
class UnknownCommand(Exception):
"""
Throw this when a user enters an an invalid command.
"""
pass
class CommandNotInState(Exception):
"""
Throw this when a user tries a global command that exists, but
don't happen to be defined in the current game state.
err_string: The error string returned to the user.
"""
def __init__(self,err_string):
self.err_string = err_string
class ExitCommandHandler(Exception):
"""
Thrown when something happens and it's time to exit the command handler.
"""
pass
class Command(object):
# The source object that the command originated from.
source_object = None
# The session that the command originated from (optional)
session = None
# The entire raw, un-parsed command.
raw_input = None
# Just the root command. IE: if input is "look dog", this is just "look".
command_string = None
# A list of switches in the form of strings.
command_switches = []
# The un-parsed argument provided. IE: if input is "look dog", this is "dog".
command_argument = None
# list of tuples for possible multi-space commands and their arguments
command_alternatives = None
# A reference to the command function looked up in a command table.
command_function = None
# An optional dictionary that is passed through the command table as extra_vars.
extra_vars = None
def parse_command_switches(self):
"""
Splits any switches out of a command_string into the command_switches
list, and yanks the switches out of the original command_string.
"""
splitted_command = self.command_string.split('/')
self.command_switches = splitted_command[1:]
self.command_string = splitted_command[0]
def parse_command(self):
"""
Breaks the command up into the main command string, a list of switches,
and a string containing the argument provided with the command. More
specific processing is left up to the individual command functions.
The command can come in two forms:
command/switches arg
command_with_spaces arg
The first form is the normal one, used for administration and other commands
that benefit from the use of switches and options. The drawback is that it
can only consist of one single word (no spaces).
The second form, which does not accept switches, allows for longer command
names (e.g. 'press button' instead of pressbutton) and is mainly useful for
object-based commands for roleplay, puzzles etc.
"""
if not self.raw_input:
return
# add a space after the raw input; this cause split() to always
# create a list with at least two entries.
raw = "%s " % self.raw_input
cmd_words = raw.split(' ')
try:
if '/' in cmd_words[0]:
# if we have switches we directly go for the first command form.
command_string, command_argument = \
(inp.strip() for inp in raw.split(' ', 1))
if command_argument:
self.command_argument = command_argument
if command_string:
# we have a valid command, store and parse switches.
self.command_string = command_string
self.parse_command_switches()
else:
# no switches - we need to save a list of all possible command
# names up to the max-length allowed.
command_maxlen = min(COMMAND_MAXLEN, len(cmd_words))
command_alternatives = []
for spacecount in reversed(range(command_maxlen)):
# store all space-separated possible command names
# as tuples (commandname, args). They are stored with
# the longest possible name first.
try:
command_alternatives.append( (" ".join([w.strip()
for w in cmd_words[:spacecount+1]]).strip(),
" ".join(cmd_words[spacecount+1:]).strip()) )
except IndexError:
continue
if command_alternatives:
# store alternatives. Store the one-word command
# as the default command name.
one_word_command = command_alternatives.pop()
self.command_string = one_word_command[0]
self.command_argument = one_word_command[1]
self.command_alternatives = command_alternatives
except IndexError:
# this SHOULD only happen if raw_input is malformed
# (like containing only control characters).
pass
def __init__(self, source_object, raw_input, session=None):
"""
Instantiates the Command object and does some preliminary parsing.
"""
# If we get a unicode string with un-recognizable characters, replace
# them instead of throwing errors.
self.raw_input = raw_input
if not isinstance(raw_input, unicode):
self.raw_input = unicode(raw_input, errors='replace')
self.source_object = source_object
self.session = session
# The work starts here.
self.parse_command()
def arg_has_target(self):
"""
Returns true if the argument looks to be target-style. IE:
page blah=hi
kick ball=north
"""
return "=" in self.command_argument
def get_arg_targets(self, delim=','):
"""
Returns a list of targets from the argument. These happen before
the '=' sign and may be separated by a delimiter.
"""
# Make sure we even have a target (= sign).
if not self.arg_has_target():
return None
target = self.command_argument.split('=', 1)[0]
return [targ.strip() for targ in target.split(delim)]
def get_arg_target_value(self):
"""
In a case of something like: page bob=Hello there, the target is "bob",
while the value is "Hello there". This function returns the portion
of the command that takes place after the first equal sign.
"""
# Make sure we even have a target (= sign).
if not self.arg_has_target():
return None
return self.command_argument.split('=', 1)[1]
def match_idle(command):
"""
Matches against the 'idle' command. It doesn't actually do anything, but it
lets the users get around badly configured NAT timeouts that would cause
them to drop if they don't send or receive something from the connection
for a while.
"""
if command.session and command.command_string != 'idle' \
and command.command_string != None:
# Anything other than an 'idle' command or a blank return
# updates the public-facing idle time for the session.
command.session.count_command(silently=False)
elif command.session:
# User is hitting IDLE command. Don't update their publicly
# facing idle time, drop out of command handler immediately.
command.session.count_command(silently=True)
raise ExitCommandHandler
def match_alias(command):
"""
Checks to see if the entered command matches an alias. If so, replaces
the command_string with the correct command.
We do a dictionary lookup. If the key (the player's command_string) doesn't
exist on the dict, just keep the command_string the same. If the key exists,
its value replaces the command_string. For example, sa -> say.
"""
# See if there's an entry in the global alias table.
command.command_string = alias_mgr.CMD_ALIAS_LIST.get(
command.command_string,
command.command_string)
# Run aliasing on alternative command names (for commands with
# spaces in them)
if command.command_alternatives:
command_alternatives = []
for command_alternative in command.command_alternatives:
# create correct command_alternative tuples for storage
command_alternatives.append( (alias_mgr.CMD_ALIAS_LIST.get(
command_alternative[0],
command_alternative[0]),
command_alternative[1]) )
command.command_alternatives = command_alternatives
def get_aliased_message():
"""
Convenience sub-function to combine the lopped off command string
and arguments for posing, saying, and nospace posing aliases.
"""
if not command.command_argument:
return command.command_string[1:]
else:
return "%s %s" % (command.command_string[1:],
command.command_argument)
# Match against the single-character aliases of MUX/MUSH-dom.
first_char = command.command_string[0]
# Shortened say alias.
if first_char == '"':
command.command_argument = get_aliased_message()
command.command_string = "say"
# Shortened pose alias.
elif first_char == ':':
command.command_argument = get_aliased_message()
command.command_string = "pose"
# Pose without space alias.
elif first_char == ';':
command.command_argument = get_aliased_message()
command.command_string = "pose"
command.command_switches.insert(0, "nospace")
def match_channel(command):
"""
Match against a comsys channel or comsys command. If the player is talking
over a channel, replace command_string with @cemit. If they're entering
a channel manipulation command, perform the operation and kill the things
immediately with a True value sent back to the command handler.
This only works with PLAYER objects at this point in time.
"""
if command.session and comsys.plr_has_channel(command.session,
command.command_string, alias_search=True, return_muted=True):
calias = command.command_string
cname = comsys.plr_cname_from_alias(command.session, calias)
if command.command_argument == "who":
comsys.msg_cwho(command.source_object, cname)
raise ExitCommandHandler
elif command.command_argument == "on":
comsys.plr_chan_on(command.session, calias)
raise ExitCommandHandler
elif command.command_argument == "off":
comsys.plr_chan_off(command.session, calias)
raise ExitCommandHandler
elif command.command_argument == "last":
comsys.msg_chan_hist(command.source_object, cname)
raise ExitCommandHandler
if not command.command_argument:
command.source_object.emit_to("What do you want to say?")
raise ExitCommandHandler
second_arg = "%s=%s" % (cname, command.command_argument)
command.command_string = "@cemit"
command.command_switches = ["sendername", "quiet"]
command.command_argument = second_arg
return True
def match_exits(command,test=False):
"""
See if we can find an input match to exits.
command - the command we are testing for.
if a match, move obj and exit
test - just return Truee if it is an exit command,
do not move the object there.
"""
# If we're not logged in, don't check exits.
source_object = command.source_object
location = source_object.get_location()
if location == None:
logger.log_errmsg("cmdhandler.match_exits(): Object '%s' has no location." %
source_object)
return
# get all exits at location
exits = location.get_contents(filter_type=defines_global.OTYPE_EXIT)
# /not sure why this was done this way when one can import Object.
# Object = ContentType.objects.get(app_label="objects",
# model="object").model_class()
exit_matches = None
if command.command_alternatives:
# we have command alternatives (due to spaces in command definition).
# if so we replace the command_string appropriately.
for cmd_alternative in command.command_alternatives:
# the alternatives are ordered longest -> shortest.
exit_matches = Object.objects.list_search_object_namestr(exits,
cmd_alternative[0],
match_type="exact")
if exit_matches:
command.command_string = cmd_alternative[0]
command.command_argument = cmd_alternative[1]
break
if not exit_matches:
exit_matches = Object.objects.list_search_object_namestr(exits,
command.command_string,
match_type="exact")
if exit_matches:
if test:
return True
# Only interested in the first match.
targ_exit = exit_matches[0]
# An exit's home is its destination. If the exit has a None home value,
# it's not traversible.
if targ_exit.get_home():
# SCRIPT: See if the player can traverse the exit
if not targ_exit.scriptlink.default_lock(source_object):
lock_msg = targ_exit.get_attribute_value("lock_msg")
if lock_msg:
source_object.emit_to(lock_msg)
else:
source_object.emit_to("You can't traverse that exit.")
else:
source_object.move_to(targ_exit.get_home())
else:
source_object.emit_to("That exit leads nowhere.")
# We found a match, kill the command handler.
raise ExitCommandHandler
def command_table_lookup(command, command_table, eval_perms=True,
test=False, neighbor=None):
"""
Performs a command table lookup on the specified command table. Also
evaluates the permissions tuple.
The test flag only checks without manipulating the command
neighbor (object) If this is supplied, we are looking at a object table and
must check for locks.
In the case of one-word commands with switches, this is a
quick look-up. For non-switch commands the command might
however consist of several words separated by spaces up to
a certain max number of words. We don't know beforehand if one
of these match an entry in this particular command table. We search
them in order longest to shortest before deferring to the normal,
one-word assumption.
"""
cmdtuple = None
if command.command_alternatives:
#print "alternatives:",command.command_alternatives
#print command_table.ctable
# we have command alternatives (due to spaces in command definition)
for cmd_alternative in command.command_alternatives:
# the alternatives are ordered longest -> shortest.
cmdtuple = command_table.get_command_tuple(cmd_alternative[0])
if cmdtuple:
# we have a match, so this is the 'right' command to use
# with this particular command table.
command.command_string = cmd_alternative[0]
command.command_argument = cmd_alternative[1]
break
if not cmdtuple:
# None of the alternatives match, go with the default one-word name
cmdtuple = command_table.get_command_tuple(command.command_string)
if cmdtuple:
# if we get here we have found a command match in the table
if test:
# Check if this is just a test.
return True
# Check uselocks
if neighbor and not neighbor.scriptlink.use_lock(command.source_object):
# send an locked error message only if lock_desc is defined
lock_msg = neighbor.get_attribute_value("use_lock_msg")
if lock_msg:
command.source_object.emit_to(lock_msg)
raise ExitCommandHandler
return False
# If there is a permissions element to the entry, check perms.
if eval_perms and cmdtuple[1]:
if not command.source_object.has_perm_list(cmdtuple[1]):
command.source_object.emit_to(defines_global.NOPERMS_MSG)
raise ExitCommandHandler
# If flow reaches this point, user has perms and command is ready.
command.command_function = cmdtuple[0]
command.extra_vars = cmdtuple[2]
return True
def match_neighbor_ctables(command,test=False):
"""
Looks through the command tables of neighboring objects for command
matches.
test mode just checks if the command is a match, without manipulating
any commands.
"""
source_object = command.source_object
location = source_object.get_location()
if location:
# get all objects, including the current room
neighbors = location.get_contents() + [location] + source_object.get_contents()
for neighbor in neighbors:
#print "neighbor:", neighbor
obj_cmdtable = neighbor.get_cmdtable()
if obj_cmdtable and command_table_lookup(command, obj_cmdtable,
test=test,
neighbor=neighbor):
# If there was a command match, set the scripted_obj attribute
# for the script parent to pick up.
if test:
return True
command.scripted_obj = neighbor
return True
# No matches
return False
def handle(command, ignore_state=False):
"""
Use the spliced (list) uinput variable to retrieve the correct
command, or return an invalid command error.
We're basically grabbing the player's command by tacking
their input on to 'cmd_' and looking it up in the GenCommands
class.
ignore_state : ignore eventual statetable lookups completely.
"""
try:
# TODO: Protect against non-standard characters.
if not command.command_string:
# Nothing sent in of value, ignore it.
raise ExitCommandHandler
# No state by default.
state = None
if command.session and not command.session.logged_in:
# Not logged in, look through the unlogged-in command table.
command_table_lookup(command, cmdtable.GLOBAL_UNCON_CMD_TABLE,
eval_perms=False)
else:
# User is logged in.
# Match against the 'idle' command.
match_idle(command)
# See if this is an aliased command.
match_alias(command)
state = command.source_object.get_state()
state_cmd_table = statetable.GLOBAL_STATE_TABLE.get_cmd_table(state)
if state and state_cmd_table and not ignore_state:
# Caller is in a special state.
state_allow_exits, state_allow_obj_cmds = \
statetable.GLOBAL_STATE_TABLE.get_exec_rights(state)
state_lookup = True
if match_channel(command):
command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE)
state_lookup = False
# See if the user is trying to traverse an exit.
if state_allow_exits:
match_exits(command)
# check if this is a command defined on a nearby object.
if state_allow_obj_cmds and match_neighbor_ctables(command):
state_lookup = False
#if nothing has happened to change our mind, search the state table.
if state_lookup:
command_table_lookup(command, state_cmd_table)
else:
# Not in a state. Normal operation.
state = None # make sure, in case the object had a malformed statename.
# Check if the user is using a channel command.
match_channel(command)
# See if the user is trying to traverse an exit.
match_exits(command)
# check if this is a command defined on a nearby object
if not match_neighbor_ctables(command):
command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE)
"""
By this point, we assume that the user has entered a command and not
something like a channel or exit. Make sure that the command's
function reference is value and try to run it.
"""
if callable(command.command_function):
try:
# Move to the command function, passing the command object.
command.command_function(command)
except:
"""
This is a crude way of trapping command-related exceptions
and showing them to the user and server log. Once the
codebase stabilizes, we will probably want something more
useful or give them the option to hide exception values.
"""
if command.source_object:
command.source_object.emit_to("Untrapped error, please file a bug report:\n%s" %
(format_exc(),))
logger.log_errmsg("Untrapped error, evoker %s: %s" %
(command.source_object, format_exc()))
# Prevent things from falling through to UnknownCommand.
raise ExitCommandHandler
else:
# If we reach this point, we haven't matched anything.
if state:
# if we are in a state, it could be that the command exists, but
# it is temporarily not available. If so, we want a different error message.
if match_exits(command,test=True):
raise CommandNotInState("Movement is not possible right now.")
if match_neighbor_ctables(command,test=True):
raise CommandNotInState("You can not do that at the moment.")
if command_table_lookup(command,cmdtable.GLOBAL_CMD_TABLE,test=True):
raise CommandNotInState("This command is not available right now.")
raise UnknownCommand
except ExitCommandHandler:
# When this is thrown, just get out and do nothing. It doesn't mean
# something bad has happened.
pass
except CommandNotInState, e:
# The command exists, but not in the current state
if command.source_object != None:
# The logged-in error message
command.source_object.emit_to(e.err_string)
elif command.session != None:
# States are not available before login, so this should never
# be reached. But better safe than sorry.
command.session.msg("%s %s" % (e.err_string," (Type \"help\" for help.)"))
else:
pass
except UnknownCommand:
# Default fall-through. No valid command match.
if command.source_object != None:
# A typical logged in or object-based error message.
command.source_object.emit_to("Huh? (Type \"help\" for help.)")
elif command.session != None:
# This is hit when invalid commands are sent at the login screen
# primarily. Also protect against bad things in odd cases.
command.session.msg("Huh? (Type \"help\" for help.)")
else:
# We should never get to this point, but if we do, don't freak out.
pass

View file

@ -1,89 +0,0 @@
"""
Command Table Module
Each command entry consists of a key and a tuple containing a reference to the
command's function, and a tuple of the permissions to match against. The user
only need have one of the permissions in the permissions tuple to gain
access to the command. Obviously, super users don't have to worry about this
stuff. If the command is open to all (or you want to implement your own
privilege checking in the command function), use None in place of the
permissions tuple.
Commands are located under evennia/src/commands. server.py imports these
based on the value of settings.COMMAND_MODULES and
settings.CUSTOM_COMMAND_MODULES. Each module imports cmdtable.py and runs
add_command on the command table each command belongs to.
"""
from django.conf import settings
from src.helpsys import helpsystem
class CommandTable(object):
"""
Stores commands and performs lookups.
"""
ctable = None
def __init__(self):
# This ensures there are no leftovers when the class is instantiated.
self.ctable = {}
def add_command(self, command_string, function, priv_tuple=None,
extra_vals=None, help_category="", priv_help_tuple=None,
auto_help_override=None):
"""
Adds a command to the command table.
command_string: (string) Command string (IE: WHO, QUIT, look).
function: (reference) The command's function.
priv_tuple: (tuple) String tuple of permissions required for command.
extra_vals: (dict) Dictionary to add to the Command object.
Auto-help system: (this is only used if settings.HELP_AUTO_ENABLED is active)
help_category (str): An overall help category where auto-help will place
the help entry. If not given, 'General' is assumed.
priv_help_tuple (tuple) String tuple of permissions required to view this
help entry. If nothing is given, priv_tuple is used.
auto_help_override (bool/None): Override the value in settings.AUTO_HELP_ENABLED with the
value given. Use None to not override.
This can be useful when developing a new routine and
has made manual changes to help entries of other
commands in the database (and so do not want to use global
auto-help).
Note: the auto_help system also supports limited markup. You can divide your __doc__
with markers of any combinations of the forms
[[Title]]
[[Title, category]]
[[Title, (priv_tuple)]]
[[Title, category, (priv_tuple)]],
If such markers are found, the system will automatically create
separate help topics for each topic. Your main help entry will
default to the name of your command.
"""
self.ctable[command_string] = (function, priv_tuple, extra_vals)
if auto_help_override == None:
auto_help_override = settings.HELP_AUTO_ENABLED
if auto_help_override:
#add automatic help text from the command's doc string
topicstr = command_string
entrytext = function.__doc__
if not help_category:
help_category = "General"
if not priv_help_tuple:
priv_help_tuple = priv_tuple
helpsystem.edithelp.add_help_auto(topicstr, help_category,
entrytext, priv_help_tuple)
def get_command_tuple(self, func_name):
"""
Returns a reference to the command's tuple. If there are no matches,
returns false.
"""
return self.ctable.get(func_name, False)
# Global command table, for authenticated users.
GLOBAL_CMD_TABLE = CommandTable()
# Global unconnected command table, for unauthenticated users.
GLOBAL_UNCON_CMD_TABLE = CommandTable()

View file

@ -1,543 +0,0 @@
"""
Batch processor
The batch processor accepts 'batchcommand files' e.g 'batch.ev', containing a
sequence of valid evennia commands in a simple format. The engine
runs each command in sequence, as if they had been run at the terminal prompt.
This way entire game worlds can be created and planned offline; it is
especially useful in order to create long room descriptions where a
real offline text editor is often much better than any online text editor
or prompt.
Example of batch.ev file:
----------------------------
# batch file
# all lines starting with # are comments; they also indicate
# that a command definition is over.
@create box
# this comment ends the @create command.
@set box=desc: A large box.
Inside are some scattered piles of clothing.
It seems the bottom of the box is a bit loose.
# Again, this comment indicates the @set command is over. Note how
# the description could be freely added. Excess whitespace on a line
# is ignored. An empty line in the command definition is parsed as a \n
# (so two empty lines becomes a new paragraph).
@teleport #221
# (Assuming #221 is a warehouse or something.)
# (remember, this comment ends the @teleport command! Don'f forget it)
@drop box
# Done, the box is in the warehouse! (this last comment is not necessary to
# close the @drop command since it's the end of the file)
-------------------------
An example batch file is found in game/gamesrc/commands/examples.
"""
import os
import re
from django.conf import settings
from src import logger
from src import defines_global
from src.cmdtable import GLOBAL_CMD_TABLE
from src.statetable import GLOBAL_STATE_TABLE
#global defines for storage
STATENAME="_interactive batch processor"
cwhite = r"%cn%ch%cw"
cred = r"%cn%ch%cr"
cgreen = r"%cn%ci%cg"
cyellow = r"%cn%ch%cy"
cnorm = r"%cn"
def read_batchbuild_file(filename):
"""
This reads the contents of batchfile.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
filename = os.path.abspath("%s/%s" % (settings.BATCH_IMPORT_PATH, filename))
try:
f = open(filename)
except IOError:
logger.log_errmsg("file %s not found." % filename)
return None
lines = f.readlines()
f.close()
return lines
def parse_batchbuild_file(filename):
"""
This parses the lines of a batchfile according to the following
rules:
1) # at the beginning of a line marks the end of the command before it.
It is also a comment and any number of # can exist on subsequent
lines (but not inside comments).
2) Commands are placed alone at the beginning of a line and their
arguments are considered to be everything following (on any
number of lines) until the next comment line beginning with #.
3) Newlines are ignored in command definitions
4) A completely empty line in a command line definition is condered
a newline (so two empty lines is a paragraph).
5) Excess spaces and indents inside arguments are stripped.
"""
#read the indata, if possible.
lines = read_batchbuild_file(filename)
if not lines:
logger.log_errmsg("File %s not found." % filename)
return
#helper function
def identify_line(line):
"""
Identifies the line type (comment, commanddef or empty)
"""
try:
if line.strip()[0] == '#':
return "comment"
else:
return "commanddef"
except IndexError:
return "empty"
commands = []
curr_cmd = ""
#purge all superfluous whitespace and newlines from lines
reg1 = re.compile(r"\s+")
lines = [reg1.sub(" ",l) for l in lines]
#parse all command definitions into a list.
for line in lines:
typ = identify_line(line)
if typ == "commanddef":
curr_cmd += line
elif typ == "empty" and curr_cmd:
curr_cmd += "\r\n"
else: #comment
if curr_cmd:
commands.append(curr_cmd.strip())
curr_cmd = ""
if curr_cmd: commands.append(curr_cmd.strip())
#second round to clean up now merged line edges etc.
reg2 = re.compile(r"[ \t\f\v]+")
commands = [reg2.sub(" ",c) for c in commands]
#remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands]
return commands
def batch_process(source_object, commands):
"""
Process a file straight off.
"""
for i, command in enumerate(commands):
cmdname = command[:command.find(" ")]
source_object.emit_to("%s== %s%02i/%02i: %s %s%s" % (cgreen,cwhite,i+1,
len(commands),
cmdname,
cgreen,"="*(50-len(cmdname))))
source_object.execute_cmd(command)
#main access function @batchprocess
def cmd_batchprocess(command):
"""
@batchprocess - build from batch file
Usage:
@batchprocess[/interactive] <filename with full path>
Runs batches of commands from a batchfile. This is a
superuser command, intended for large-scale offline world
development.
Interactive mode allows the user more control over the
processing of the file.
"""
source_object = command.source_object
#check permissions; this is a superuser only command.
if not source_object.is_superuser():
source_object.emit_to(defines_global.NOPERMS_MSG)
return
args = command.command_argument
if not args:
source_object.emit_to("Usage: @batchprocess[/interactive] <path/to/file>")
return
filename = args.strip()
#parse indata file
commands = parse_batchbuild_file(filename)
if not commands:
string = "'%s' not found.\nYou have to supply the real path "
string += "of the file relative to \nyour batch-file directory (%s)."
source_object.emit_to(string % (filename, settings.BATCH_IMPORT_PATH))
return
switches = command.command_switches
if switches and switches[0] in ['inter','interactive']:
# Allow more control over how batch file is executed
if source_object.has_flag("ADMIN_NOSTATE"):
source_object.unset_flag("ADMIN_NOSTATE")
string = cred + "\nOBS: Flag ADMIN_NOSTATE unset in order to "
string += "run Interactive mode. Don't forget to re-set "
string += "it (if you need it) after you're done."
source_object.emit_to(string)
# Set interactive state directly
source_object.cache.state = STATENAME
# Store work data in cache
source_object.cache.batch_cmdstack = commands
source_object.cache.batch_stackptr = 0
source_object.cache.batch_filename = filename
source_object.emit_to("\nBatch processor - Interactive mode for %s ..." % filename)
show_curr(source_object)
else:
set_admin_nostate = False
if not source_object.has_flag("ADMIN_NOSTATE"):
source_object.set_flag("ADMIN_NOSTATE")
set_admin_nostate = True
source_object.emit_to("Running Batch processor - Automatic mode for %s ..." % filename)
source_object.clear_state()
batch_process(source_object, commands)
source_object.emit_to("%s== Batchfile '%s' applied." % (cgreen,filename))
if set_admin_nostate:
source_object.unset_flag("ADMIN_NOSTATE")
GLOBAL_CMD_TABLE.add_command("@batchprocess", cmd_batchprocess,
priv_tuple=("genperms.process_control",), help_category="Building")
# The Interactive batch processor state
def show_curr(source_object,showall=False):
"Show the current command."
ptr = source_object.cache.batch_stackptr
commands = source_object.cache.batch_cmdstack
if ptr >= len(commands):
s = "\n You have reached the end of the batch file."
s += "\n Use qq to exit or bb to go back."
source_object.emit_to(s)
source_object.cache.batch_stackptr = len(commands)-1
show_curr(source_object)
return
command = commands[ptr]
cmdname = command[:command.find(" ")]
s = "%s== %s%02i/%02i: %s %s===== %s %s%s" % (cgreen,cwhite,
ptr+1,len(commands),
cmdname,cgreen,
"(hh for help)",
"="*(35-len(cmdname)),
cnorm)
if showall:
s += "\n%s" % command
source_object.emit_to(s)
def process_commands(source_object, steps=0):
"process one or more commands "
ptr = source_object.cache.batch_stackptr
commands = source_object.cache.batch_cmdstack
if steps:
try:
cmds = commands[ptr:ptr+steps]
except IndexError:
cmds = commands[ptr:]
for cmd in cmds:
#this so it is kept in case of traceback
source_object.cache.batch_stackptr = ptr + 1
#show_curr(source_object)
source_object.execute_cmd(cmd)
else:
#show_curr(source_object)
source_object.execute_cmd(commands[ptr])
def reload_stack(source_object):
"reload the stack"
commands = parse_batchbuild_file(source_object.cache.batch_filename)
if commands:
ptr = source_object.cache.batch_stackptr
else:
source_object.emit_to("Commands in file could not be reloaded. Was it moved?")
def move_in_stack(source_object, step=1):
"store data in stack"
N = len(source_object.cache.batch_cmdstack)
currpos = source_object.cache.batch_stackptr
source_object.cache.batch_stackptr = max(0,min(N-1,currpos+step))
def exit_state(source_object):
"Quit the state"
source_object.cache.batch_cmdstack = None
source_object.cache.batch_stackptr = None
source_object.cache.batch_filename = None
# since clear_state() is protected against exiting the interactive mode
# (to avoid accidental drop-outs by rooms clearing a player's state),
# we have to clear the state directly here.
source_object.cache.state = None
def cmd_state_ll(command):
"""
ll
Look at the full source for the current
command definition.
"""
show_curr(command.source_object,showall=True)
def cmd_state_pp(command):
"""
pp
Process the currently shown command definition.
"""
process_commands(command.source_object)
def cmd_state_rr(command):
"""
rr
Reload the batch file, keeping the current
position in it.
"""
reload_stack(command.source_object)
command.source_object.emit_to("\nFile reloaded. Staying on same command.\n")
show_curr(command.source_object)
def cmd_state_rrr(command):
"""
rrr
Reload the batch file, starting over
from the beginning.
"""
reload_stack(command.source_object)
command.source_object.cache.batch_stackptr = 0
command.source_object.emit_to("\nFile reloaded. Restarting from top.\n")
show_curr(command.source_object)
def cmd_state_nn(command):
"""
nn
Go to next command. No commands are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
move_in_stack(source_object, step)
show_curr(source_object)
def cmd_state_nl(command):
"""
nl
Go to next command, viewing its full source.
No commands are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
move_in_stack(source_object, step)
show_curr(source_object, showall=True)
def cmd_state_bb(command):
"""
bb
Backwards to previous command. No commands
are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = -int(command.command_argument)
else:
step = -1
move_in_stack(source_object, step)
show_curr(source_object)
def cmd_state_bl(command):
"""
bl
Backwards to previous command, viewing its full
source. No commands are executed.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = -int(command.command_argument)
else:
step = -1
move_in_stack(source_object, step)
show_curr(source_object, showall=True)
def cmd_state_ss(command):
"""
ss [steps]
Process current command, then step to the next
one. If steps is given,
process this many commands.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
process_commands(source_object,step)
show_curr(source_object)
def cmd_state_sl(command):
"""
sl [steps]
Process current command, then step to the next
one, viewing its full source. If steps is given,
process this many commands.
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
step = int(command.command_argument)
else:
step = 1
process_commands(source_object,step)
show_curr(source_object, showall=True)
def cmd_state_cc(command):
"""
cc
Continue to process all remaining
commands.
"""
source_object = command.source_object
N = len(source_object.cache.batch_cmdstack)
ptr = source_object.cache.batch_stackptr
step = N - ptr
process_commands(source_object,step)
exit_state(source_object)
source_object.emit_to("Finished processing batch file.")
def cmd_state_jj(command):
"""
j <command number>
Jump to specific command number
"""
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
no = int(command.command_argument)-1
else:
source_object.emit_to("You must give a number index.")
return
ptr = source_object.cache.batch_stackptr
step = no - ptr
move_in_stack(source_object, step)
show_curr(source_object)
def cmd_state_jl(command):
"""
jl <command number>
Jump to specific command number and view its full source.
"""
global STACKPTRS
source_object = command.source_object
arg = command.command_argument
if arg and arg.isdigit():
no = int(command.command_argument)-1
else:
source_object.emit_to("You must give a number index.")
return
ptr = source_object.cache.batch_stackptr
step = no - ptr
move_in_stack(source_object, step)
show_curr(source_object, showall=True)
def cmd_state_qq(command):
"""
qq
Quit the batchprocessor.
"""
exit_state(command.source_object)
command.source_object.emit_to("Aborted interactive batch mode.")
def cmd_state_hh(command):
"Help command"
s = """
Interactive batch processing commands:
nn [steps] - next command (no processing)
nl [steps] - next & look
bb [steps] - back to previous command (no processing)
bl [steps] - back & look
jj <N> - jump to command nr N (no processing)
jl <N> - jump & look
pp - process currently shown command (no step)
ss [steps] - process & step
sl [steps] - process & step & look
ll - look at full definition of current command
rr - reload batch file (stay on current)
rrr - reload batch file (start from first)
hh - this help list
cc - continue processing to end, then quit.
qq - quit (abort all remaining commands)
"""
command.source_object.emit_to(s)
#create the state; we want it as open as possible so we can do everything
# in our batch processing.
GLOBAL_STATE_TABLE.add_state(STATENAME,global_cmds='all',
allow_exits=True,allow_obj_cmds=True,exit_command=True)
#add state commands
GLOBAL_STATE_TABLE.add_command(STATENAME,"nn",cmd_state_nn)
GLOBAL_STATE_TABLE.add_command(STATENAME,"nl",cmd_state_nl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"bb",cmd_state_bb)
GLOBAL_STATE_TABLE.add_command(STATENAME,"bl",cmd_state_bl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"jj",cmd_state_jj)
GLOBAL_STATE_TABLE.add_command(STATENAME,"jl",cmd_state_jl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"pp",cmd_state_pp)
GLOBAL_STATE_TABLE.add_command(STATENAME,"ss",cmd_state_ss)
GLOBAL_STATE_TABLE.add_command(STATENAME,"sl",cmd_state_sl)
GLOBAL_STATE_TABLE.add_command(STATENAME,"cc",cmd_state_cc)
GLOBAL_STATE_TABLE.add_command(STATENAME,"ll",cmd_state_ll)
GLOBAL_STATE_TABLE.add_command(STATENAME,"rr",cmd_state_rr)
GLOBAL_STATE_TABLE.add_command(STATENAME,"rrr",cmd_state_rrr)
GLOBAL_STATE_TABLE.add_command(STATENAME,"hh",cmd_state_hh)
GLOBAL_STATE_TABLE.add_command(STATENAME,"qq",cmd_state_qq)

364
src/commands/cmdhandler.py Normal file
View file

@ -0,0 +1,364 @@
"""
Command handler
This module contains the infrastructure for accepting commands on the
command line. The process is as follows:
1) The calling object (caller) inputs a string and triggers the command parsing system.
2) The system checks the state of the caller - loggedin or not
3) Depending on the login/not state, it collects cmdsets from different sources:
not logged in - uses the single cmdset in settings.CMDSET_UNLOGGEDIN
normal - gathers command sets from many different sources (shown in dropping priority):
channels - all available channel names are auto-created into a cmdset, to allow
for giving the channel name and have the following immediately
sent to the channel. The sending is performed by the CMD_CHANNEL
system command.
exits - exits from a room are dynamically made into a cmdset for matching,
allowing the player to give just the name and thus traverse the exit.
If a match, the traversing is handled by the CMD_EXIT system command.
object cmdsets - all objects at caller's location are scanned for non-empty
cmdsets.
caller - the caller is searched for its currently active cmdset.
4) All the gathered cmdsets (if more than one) are merged into one using the cmdset priority rules.
5) If no cmdsets where found, we raise NoCmdSet exception. This should not happen, at least the
caller should have a default cmdset available at all times. --> Finished
6) The raw input string is parsed using the parser defined by settings.CMDPARSER. It returns
a special match object since a command may consist of many space-separated words and we
thus have to match them all.
7) If no command was supplied, we search the merged cmdset for system command CMD_NOINPUT
and branches to execute that. --> Finished
8) We match the the match object against the merged cmdset and the eventual priorities given it
by the parser. The result is a list of command matches tied to their respective match object.
9) If we found no matches, branch to system command CMD_NOMATCH --> Finished
10) If we were unable to weed out multiple matches, branch CMD_MULTIMATCH --> Finished
11) If we have a single match, we now check user permissions.
not permissions: branch to system command CMD_NOPERM --> Finished
12) We analyze the matched command to determine if it is a channel-type command, that is
a command auto-created to represent a valid comm channel. If so, we see if CMD_CHANNEL is
custom-defined in the merged cmdset, or we launch the auto-created command
direclty --> Finished
13 We next check if this is an exit-type command, that is, a command auto-created to represent
an exit from this room. If so we check for custom CMD_EXIT in cmdset or launch
the auto-generated command directly --> Finished
14) At this point we have found a normal command. We assign useful variables to it, that
will be available to the command coder at run-time.
When launching the command (normal, or system command both), two hook functions are called
in sequence, cmd.parse() followed by cmd.func(). It's up to the implementation as to how to
use this to most advantage.
"""
from traceback import format_exc
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
#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
# There are a few system-hardcoded command names. These
# allow for custom behaviour when the command handler hits
# special situations -- it then calls a normal Command
# that you can customize!
CMD_NOINPUT = "__noinput_command"
CMD_NOMATCH = "__nomatch_command"
CMD_MULTIMATCH = "__multimatch_command"
CMD_NOPERM = "__noperm_command"
CMD_CHANNEL = "__send_to_channel"
CMD_EXIT = "__move_to_exit"
class NoCmdSets(Exception):
"No cmdsets found. Critical error."
pass
class ExecSystemCommand(Exception):
"Run a system command"
def __init__(self, syscmd, sysarg):
self.args = (syscmd, sysarg) # needed by exception error handling
self.syscmd = syscmd
self.sysarg = sysarg
def get_and_merge_cmdsets(caller):
"""
Gather all relevant cmdsets and merge them. Note
that this is only relevant for logged-in callers.
"""
# The calling object's cmdset
try:
caller_cmdset = caller.cmdset.current
except AttributeError:
caller_cmdset = None
# All surrounding cmdsets
channel_cmdset = None
exit_cmdset = None
local_objects_cmdsets = [None]
#print "cmdset flags:", caller_cmdset.no_channels, caller_cmdset.no_exits, caller_cmdset.no_objs
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
if location and not caller_cmdset.no_objs:
# Gather all cmdsets stored on objects in the room
local_objlist = location.contents
local_objects_cmdsets = [obj.cmdset.current
for obj in local_objlist
if obj.cmdset.outside_access
and obj.cmdset.allow_outside_access(caller)]
# Merge all command sets into one
# (the order matters, the higher-prio cmdsets are merged last)
cmdset = caller_cmdset
for obj_cmdset in local_objects_cmdsets:
try:
cmdset = obj_cmdset + cmdset
except TypeError:
pass
try:
cmdset = exit_cmdset + cmdset
except TypeError:
pass
try:
cmdset = channel_cmdset + cmdset
except TypeError:
pass
return cmdset
def match_command(cmd_candidates, cmdset, logged_caller=None):
"""
Try to match the command against one of the
cmd_candidates.
logged_caller - a logged-in object, if any.
"""
# Searching possible command matches in the given cmdset
matches = []
prev_found_cmds = [] # to avoid aliases clashing with themselves
for cmd_candidate in cmd_candidates:
cmdmatches = list(set([cmd for cmd in cmdset
if cmd == cmd_candidate.cmdname and
cmd not in prev_found_cmds]))
matches.extend([(cmd_candidate, cmd) for cmd in cmdmatches])
prev_found_cmds.extend(cmdmatches)
if not matches or len(matches) == 1:
return matches
# Do our damndest to resolve multiple matches
# First try candidate priority to separate them
top_ranked = []
top_priority = None
for match in matches:
if top_priority == None \
or match[0].priority >= top_priority:
top_priority = match[0].priority
top_ranked.append(match)
matches = top_ranked
if not matches or len(matches) == 1:
return matches
# still multiplies. Check if player supplied
# an obj name on the command line. We know they
# all have at least the same cmdname and obj_key
# at this point.
if logged_caller:
try:
local_objlist = logged_caller.location.contents
match = matches[0]
top_ranked = [obj for obj in local_objlist
if match[0].obj_key == obj.name
and any(cmd == match[0].cmdname
for cmd in obj.cmdset.current)]
if top_ranked:
matches = \
[(match[0],
obj.cmdset.current.get(match[0].cmdname))
for obj in top_ranked]
except Exception:
logger.log_trace()
# regardless what we have at this point, we have to be content
return matches
# Main command-handler function
def cmdhandler(caller, raw_string, unloggedin=False):
"""
This is the main function to handle any string sent to the engine.
"""
try: # catch bugs in cmdhandler itself
try: # catch special-type commands
if unloggedin:
# not logged in, so it's just one cmdset we are interested in
cmdset = import_cmdset(settings.CMDSET_UNLOGGEDIN, caller)
else:
# We are logged in, collect all relevant cmdsets and merge
cmdset = get_and_merge_cmdsets(caller)
#print cmdset
if not cmdset:
# this is bad and shouldn't happen.
raise NoCmdSets
raw_string = raw_string.strip()
if not raw_string:
# Empty input. Test for system command instead.
syscmd = cmdset.get(CMD_NOINPUT)
sysarg = ""
raise ExecSystemCommand(syscmd, sysarg)
# Parse the input string into command candidates
cmd_candidates = CMDPARSER(raw_string)
#string ="Command candidates"
#for cand in cmd_candidates:
# string += "\n %s || %s" % (cand.cmdname, cand.args)
#caller.msg(string)
# Try to produce a unique match between the merged
# cmdset and the candidates.
if unloggedin:
matches = match_command(cmd_candidates, cmdset)
else:
matches = match_command(cmd_candidates, cmdset, caller)
#print "matches: ", matches
# Deal with matches
if not matches:
# No commands match our entered command
syscmd = cmdset.get(CMD_NOMATCH)
if syscmd:
sysarg = raw_string
else:
sysarg = "Huh? (Type \"help\" for help)"
raise ExecSystemCommand(syscmd, sysarg)
if len(matches) > 1:
# We have a multiple-match
syscmd = cmdset.get(CMD_MULTIMATCH)
matchstring = ", ".join([match[0].cmdname
for match in matches])
if syscmd:
sysarg = matchstring
else:
sysarg = "There were multiple matches:\n %s"
sysarg = sysarg % matchstring
raise ExecSystemCommand(syscmd, sysarg)
# At this point, we have a unique command match.
cmd_candidate, cmd = matches[0]
# Check so we have permission to use this command.
if not cmd.has_perm(caller):
cmd = cmdset.get(CMD_NOPERM)
if cmd:
sysarg = raw_string
else:
sysarg = "Huh? (type 'help' for help)"
raise ExecSystemCommand(cmd, sysarg)
# Check if this is a Channel match.
if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = cmdset.get(CMD_CHANNEL)
if syscmd:
# replace system command with custom version
cmd = syscmd
sysarg = "%s:%s" % (cmd_candidate.cmdname,
cmd_candidate.args)
raise ExecSystemCommand(cmd, sysarg)
# Check if this is an Exit match.
if hasattr(cmd, 'is_exit') and cmd.is_exit:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = cmdset.get(CMD_EXIT)
if syscmd:
# replace system command with custom version
cmd = syscmd
sysarg = raw_string
raise ExecSystemCommand(cmd, sysarg)
# A normal command.
# Assign useful variables to the instance
cmd.caller = caller
cmd.cmdstring = cmd_candidate.cmdname
cmd.args = cmd_candidate.args
cmd.cmdset = cmdset
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
# cmd.obj are automatically made available.
# we make sure to validate its scripts.
cmd.obj.scripts.validate()
# Parse and execute
cmd.parse()
cmd.func()
# Done!
except ExecSystemCommand, exc:
# Not a normal command: run a system command, if available,
# or fall back to a return string.
syscmd = exc.syscmd
sysarg = exc.sysarg
if syscmd:
syscmd.caller = caller
syscmd.cmdstring = syscmd.key
syscmd.args = sysarg
syscmd.cmdset = cmdset
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'):
# cmd.obj is automatically made available.
# we make sure to validate its scripts.
cmd.obj.scripts.validate()
# parse and run the command
syscmd.parse()
syscmd.func()
elif sysarg:
caller.msg(exc.sysarg)
except NoCmdSets:
# Critical error.
string = "No command sets found! This is a sign of a critical bug.\n"
string += "The error was logged.\n"
string += "If logging out/in doesn't solve the problem, try to "
string += "contact the server admin through some other means "
string += "for assistance."
caller.msg(string)
logger.log_errmsg("No cmdsets found: %s" % caller)
except Exception:
# We should not end up here. If we do, it's a programming bug.
string = "%s\nAbove traceback is from an untrapped error."
string += " Please file a bug report."
logger.log_trace(string)
caller.msg(string % format_exc())
except Exception:
# This catches exceptions in cmdhandler exceptions themselves
string = "%s\nAbove traceback is from a Command handler bug."
string += " Please contact an admin."
logger.log_trace(string)
caller.msg(string % format_exc())
#----------------------------------------------------- end cmdhandler

174
src/commands/cmdparser.py Normal file
View file

@ -0,0 +1,174 @@
"""
The default command parser. Use your own by assigning
settings.ALTERNATE_PARSER to a Python path to a module containing the
replacing cmdparser function. The replacement parser must
return a CommandCandidates object.
"""
import re
from django.conf import settings
# This defines how many space-separated words may at most be in a command.
COMMAND_MAXLEN = settings.COMMAND_MAXLEN
# These chars (and space) end a command name and may
# thus never be part of a command name. Exception is
# if the char is the very first character - the char
# is then treated as the name of the command.
SPECIAL_CHARS = ["/", "\\", "'", '"', ":", ";", "\-", '#', '=', '!']
# Pre-compiling the regular expression is more effective
REGEX = re.compile(r"""["%s"]""" % ("".join(SPECIAL_CHARS)))
class CommandCandidate(object):
"""
This is a convenient container for one possible
combination of command names that may appear if we allow
many-word commands.
"""
def __init__(self, cmdname, args=0, priority=0, obj_key=None):
"initiate"
self.cmdname = cmdname
self.args = args
self.priority = priority
self.obj_key = obj_key
def __str__(self):
return "<cmdname:'%s',args:'%s'>" % (self.cmdname, self.args)
#
# The command parser
#
def cmdparser(raw_string):
"""
This function parses the raw string into three parts: command
name(s), keywords(if any) and arguments(if any). It returns a
CommandCandidates object. It should be general enough for most
game implementations, but you can also overwrite it should you
wish to implement some completely different way of handling and
ranking commands. Arguments and keywords are parsed/dealt with by
each individual command's parse() command.
The cmdparser understand the following command combinations (where
[] marks optional parts and <char> is one of the SPECIAL_CHARs
defined globally.):
[<char>]cmdname[ cmdname2 cmdname3 ...][<char>] [the rest]
A command may contain spaces, but never any of of the <char>s. A
command can maximum have CMD_MAXLEN words, or the number of words
up to the first <char>, whichever is smallest. An exception is if
<char> is the very first character in the string - the <char> is
then assumed to be the actual command name (a common use for this
is for e.g ':' to be a shortcut for 'emote').
All words not part of the command name is considered a part of the
command's argument. Note that <char>s ending a command are never
removed but are included as the first character in the
argument. This makes it easier for individual commands to identify
things like switches. Example: '@create/drop ball' finds the
command name to trivially be '@create' since '/' ends it. As the
command's arguments are sent '/drop ball'. In this MUX-inspired
example, '/' denotes a keyword (or switch) and it is now easy for
the receiving command to parse /drop as a keyword just by looking
at the first character.
Allowing multiple command names means we have to take care of all
possible meanings and the result will be a CommandCandidates
object with up to COMMAND_MAXLEN names stored in it. So if
COMMAND_MAXLEN was, say, 4, we would have to search all commands
matching one of 'hit', 'hit orc', 'hit orc with' and 'hit orc with
sword' - each which are potentially valid commands. Assuming a
longer written name means being more specific, a longer command
name takes precedence over a short one.
There is one optional form:
<objname>'s [<char>]cmdname[ cmdname2 cmdname3 ...][<char>] [the rest]
This is to be used for object command sets with the 'duplicate' flag
set. It allows the player to define a particular object by name.
This object name(without the 's) will be stored as obj_key in the
CommandCandidates object and one version of the command name will be added
that lack this first part. If a command exists that has the same
name (including the 's), that command will be used
instead. Observe that the player setting <objname> will not override
normal commandset priorities - it's only used if there is no other
way to differentiate between commands (e.g. two objects in the
room both having the exact same command names and priorities).
"""
def produce_candidates(nr_candidates, wordlist):
"Helper function"
candidates = []
cmdwords_list = []
#print "wordlist:",wordlist
for n_words in range(nr_candidates):
cmdwords_list.append(wordlist.pop(0))
cmdwords = " ".join([word.strip().lower()
for word in cmdwords_list])
args = ""
for word in wordlist:
if not args or (word and (REGEX.search(word[0]))):
#print "nospace: %s '%s'" % (args, word)
args += word
else:
#print "space: %s '%s'" % (args, word)
args += " %s" % word
#print "'%s' | '%s'" % (cmdwords, args)
candidates.append(CommandCandidate(cmdwords, args, priority=n_words))
return candidates
raw_string = raw_string.strip()
#TODO: check for non-standard characters.
candidates = []
regex_result = REGEX.search(raw_string)
if not regex_result == None:
# there are characters from SPECIAL_CHARS in the string.
# since they cannot be part of a longer command, these
# will cut short the command, no matter how long we
# allow commands to be.
end_index = regex_result.start()
end_char = raw_string[end_index]
if end_index == 0:
# There is one exception: if the input begins with
# a special char, we let that be the command name.
cmdwords = end_char
if len(raw_string) > 1:
args = raw_string[1:]
else:
args = ""
candidates.append(CommandCandidate(cmdwords, args))
return candidates
else:
# the special char occurred somewhere inside the string
if end_char == "'" and \
len(raw_string) > end_index+1 and \
raw_string[end_index+1:end_index+3] == "s ":
# The command is of the form "<word>'s ". The
# player might have made an attempt at identifying the
# object of which's cmdtable we should prefer (e.g.
# > red door's button).
obj_key = raw_string[:end_index]
alt_string = raw_string[end_index+2:]
alt_candidates = cmdparser(alt_string)
for candidate in alt_candidates:
candidate.obj_key = obj_key
candidates.extend(alt_candidates)
# now we let the parser continue as normal, in case
# the 's -business was not meant to be an obj ref at all.
# We only run the command finder up until the end char
nr_candidates = len(raw_string[:end_index].split(None))
if nr_candidates <= COMMAND_MAXLEN:
wordlist = raw_string[:end_index].split(" ")
wordlist.extend(raw_string[end_index:].split(" "))
#print "%i, wordlist: %s" % (nr_candidates, wordlist)
candidates.extend(produce_candidates(nr_candidates, wordlist))
return candidates
# if there were no special characters, or that character
# was not found within the allowed number of words, we run normally
nr_candidates = min(COMMAND_MAXLEN,
len(raw_string.split(None)))
wordlist = raw_string.split(" ")
candidates.extend(produce_candidates(nr_candidates, wordlist))
return candidates

338
src/commands/cmdset.py Normal file
View file

@ -0,0 +1,338 @@
"""
A cmdset holds a set of commands available to the object or to other
objects near it. All the commands a player can give (look, @create etc)
are stored as the default cmdset on the player object and managed using the
CmdHandler object (see cmdhandler.py).
The power of having command sets in CmdSets like this is that CmdSets
can be merged together according to individual rules to create a new
on-the-fly CmdSet that is some combination of the
previous ones. Their function are borrowed to a large parts from mathematical
Set theory, it should not be much of a problem to understand.
See CmdHandler for practical examples on how to apply cmdsets
together to create interesting in-game effects.
"""
import copy
class CmdSetMeta(type):
"""
This metaclass makes some minor on-the-fly convenience fixes to
the cmdset class.
"""
def __init__(mcs, *args, **kwargs):
"""
Fixes some things in the cmdclass
"""
# by default we key the cmdset the same as the
# name of its class.
mcs.key = mcs.__name__
if not type(mcs.key_mergetypes) == dict:
mcs.key_mergetypes = {}
super(CmdSetMeta, mcs).__init__(*args, **kwargs)
# Some priority-sensitive merge operations for cmdsets
def union(cmdset_a, cmdset_b, duplicates=False):
"C = A U B. CmdSet A is assumed to have higher priority"
cmdset_c = cmdset_a.copy_this()
# we make copies, not refs by use of [:]
cmdset_c.commands = cmdset_a.commands[:]
if duplicates and cmdset_a.priority == cmdset_b.priority:
cmdset_c.commands.extend(cmdset_b.commands)
else:
cmdset_c.commands.extend([cmd for cmd in cmdset_b
if not cmd in cmdset_a])
return cmdset_c
def intersect(cmdset_a, cmdset_b, duplicates=False):
"C = A (intersect) B. A is assumed higher priority"
cmdset_c = cmdset_a.copy_this()
if duplicates and cmdset_a.priority == cmdset_b.priority:
for cmd in [cmd for cmd in cmdset_a if cmd in cmdset_b]:
cmdset_c.add(cmd)
cmdset_c.add(cmdset_b.get(cmd))
else:
cmdset_c.commands = [cmd for cmd in cmdset_a if cmd in cmdset_b]
return cmdset_c
def replace(cmdset_a, cmdset_b, cmdset_c):
"C = A + B where the result is A."
cmdset_c = cmdset_a.copy_this()
cmdset_c.commands = cmdset_a.commands[:]
return cmdset_c
def remove(cmdset_a, cmdset_b, cmdset_c):
"C = A + B, where B is filtered by A"
cmdset_c = cmdset_a.copy_this()
cmdset_c.commands = [cmd for cmd in cmdset_b if not cmd in cmdset_a]
return cmdset_c
def instantiate(cmd):
"""
checks so that object is an instantiated command
and not, say a cmdclass. If it is, instantiate it.
Other types, like strings, are passed through.
"""
if callable(cmd):
# this is a valid check since Command *instances*
# don't implement __call__, so this will catch
# Command *classes* and instantiate them.
return cmd()
return cmd
class CmdSet(object):
"""
This class describes a unique cmdset that understands priorities. CmdSets
can be merged and made to perform various set operations on each other.
CmdSets have priorities that affect which of their ingoing commands gets used.
In the examples, cmdset A always have higher priority than cmdset B.
key - the name of the cmdset. This can be used on its own for game operations
mergetype (partly from Set theory):
Union - The two command sets are merged so that as many
commands as possible of each cmdset ends up in the
merged cmdset. Same-name commands are merged by
priority. This is the most common default.
Ex: A1,A3 + B1,B2,B4,B5 = A1,B2,A3,B4,B5
Intersect - Only commands found in *both* cmdsets
(i.e. which have same names) end up in the merged
cmdset, with the higher-priority cmdset replacing the
lower one. Ex: A1,A3 + B1,B2,B4,B5 = A1
Replace - The commands of this cmdset completely replaces
the lower-priority cmdset's commands, regardless
of if same-name commands exist.
Ex: A1,A3 + B1,B2,B4,B5 = A1,A3
Remove - This removes the relevant commands from the
lower-priority cmdset completely. They are not
replaced with anything, so this in effects uses the
high-priority cmdset as a filter to affect the
low-priority cmdset.
Ex: A1,A3 + B1,B2,B4,B5 = B2,B4,B5
Note: Commands longer than 2 characters and starting
with double underscrores, like '__noinput_command'
are considered 'system commands' and are
excempt from all merge operations - they are
ALWAYS included across mergers and only affected
if same-named system commands replace them.
priority- All cmdsets are always merged in pairs of two so that
the higher set's mergetype is applied to the
lower-priority cmdset. Evennia uses priorities from 0-10
where 10 are used for high-priority things like comsys
channel names and 9 for exit names in order to give
these priority when the given command matches.
duplicates - determines what happens when two sets of equal
priority merge. Default has the first of them in the
merger (i.e. A above) automatically taking
precedence. But if allow_duplicates is true, the
result will be a merger with more than one of each
name match. This will usually lead to the player
receiving a multiple-match error higher up the road,
but can be good for things like cmdsets on non-player
objects in a room, to allow the system to warn that
more than one 'ball' in the room has the same 'kick'
command defined on it, so it may offer a chance to
select which ball to kick ... Allowing duplicates
only makes sense for Union and Intersect, the setting
is ignored for the other mergetypes.
key_mergetype (dict) - allows the cmdset to define a unique
mergetype for particular cmdsets. Format is
{CmdSetkeystring:mergetype}. Priorities still apply.
Example: {'Myevilcmdset','Replace'} which would make
sure for this set to always use 'Replace' on
Myevilcmdset no matter what overall mergetype this set
has.
no_objs - don't include any commands from nearby objects
when searching for suitable commands
no_exits - ignore the names of exits when matching against
commands
no_channels - ignore the name of channels when matching against
commands (WARNING- this is dangerous since the
player can then not even ask staff for help if
something goes wrong)
"""
__metaclass__ = CmdSetMeta
key = "Unnamed CmdSet"
mergetype = "Union"
priority = 0
duplicates = False
key_mergetypes = {}
no_exits = False
no_objs = False
no_channels = False
def __init__(self, cmdsetobj=None, key=None):
"""
Creates a new CmdSet instance.
cmdsetobj - this is the database object to which this particular
instance of cmdset is related. It is often a player but may also be a
regular object.
"""
if key:
self.key = key
self.commands = []
self.actual_mergetype = self.mergetype
self.cmdsetobj = cmdsetobj
# initialize system
self.at_cmdset_creation()
def at_cmdset_creation(self):
"""
Hook method - this should be overloaded in the inheriting
class, and should take care of populating the cmdset
by use of self.add().
"""
pass
def add(self, cmd):
"""
Add a command to this cmdset.
Note that if cmd already has
"""
cmd = instantiate(cmd)
if cmd:
if not hasattr(cmd, 'obj'):
cmd.obj = self.cmdsetobj
self.commands.append(cmd)
self.commands = list(set(self.commands))
def remove(self, cmd):
"""
Remove a command instance from the cmdset.
cmd can be either a cmd instance or a key string.
"""
cmd = instantiate(cmd)
self.commands = [oldcmd for oldcmd in self.commands
if oldcmd != cmd]
def get(self, cmd):
"""
Return the command in this cmdset that matches the
given command. cmd may be either a command instance or
a key string.
"""
cmd = instantiate(cmd)
if cmd:
for thiscmd in self.commands:
if thiscmd == cmd:
return thiscmd
def get_system_cmds(self):
"""
Return system commands in the cmdset, defined as
commands starting with double underscore __.
These are excempt from merge operations.
"""
return [cmd for cmd in self.commands
if len(cmd.key) > 2 and cmd.key[:2] == '__']
def copy_this(self):
"""
Returns a new cmdset with the same settings as this one
(no commands are copied over)
"""
cmdset = CmdSet()
cmdset.key = self.key
cmdset.cmdsetobj = self.cmdsetobj
cmdset.no_exits = self.no_exits
cmdset.no_objs = self.no_objs
cmdset.no_channels = self.no_channels
cmdset.mergetype = self.mergetype
cmdset.priority = self.priority
cmdset.duplicates = self.duplicates
cmdset.key_mergetypes = copy.deepcopy(self.key_mergetypes)
return cmdset
def __str__(self):
"""
Show all commands in cmdset when printing it.
"""
return ", ".join([str(cmd) for cmd in self.commands])
def __iter__(self):
"""
Allows for things like 'for cmd in cmdset':
"""
return iter(self.commands)
def __contains__(self, othercmd):
"""
Returns True if this cmdset contains the given command (as defined
by command name and aliases). This allows for things like 'if cmd in cmdset'
"""
return any(cmd == othercmd for cmd in self.commands)
def __add__(self, cmdset_b):
"""
Merge this cmdset (A) with another cmdset (B) using the + operator,
C = A + B
Here, we (by convention) say that 'A is merged onto B to form
C'. The actual merge operation used in the 'addition' depends
on which priorities A and B have. The one of the two with the
highest priority will apply and give its properties to C. In
the case of a tie, A takes priority and replaces the
same-named commands in B unless A has the 'duplicate' variable
set (which means both sets' commands are kept).
"""
# It's okay to merge with None
if not cmdset_b:
return self
# preserve system __commands
sys_commands = self.get_system_cmds() + cmdset_b.get_system_cmds()
if self.priority >= cmdset_b.priority:
# A higher or equal priority than B
mergetype = self.key_mergetypes.get(cmdset_b.key,
self.mergetype)
if mergetype == "Intersect":
cmdset_c = intersect(self, cmdset_b, cmdset_b.duplicates)
elif mergetype == "Replace":
cmdset_c = replace(self, cmdset_b, cmdset_b.duplicates)
elif mergetype == "Remove":
cmdset_c = remove(self, cmdset_b, cmdset_b.duplicates)
else: # Union
cmdset_c = union(self, cmdset_b, cmdset_b.duplicates)
else:
# B higher priority than A
mergetype = cmdset_b.key_mergetypes.get(self.key,
cmdset_b.mergetype)
if mergetype == "Intersect":
cmdset_c = intersect(cmdset_b, self, self.duplicates)
elif mergetype == "Replace":
cmdset_c = replace(cmdset_b, self, self.duplicates)
elif mergetype == "Remove":
cmdset_c = remove(self, cmdset_b, self.duplicates)
else: # Union
cmdset_c = union(cmdset_b, self, self.duplicates)
# we store actual_mergetype since key_mergetypes
# might be different from the main mergetype.
# This is used for diagnosis.
cmdset_c.actual_mergetype = mergetype
# return the system commands to the cmdset
for cmd in sys_commands:
cmdset_c.add(cmd)
return cmdset_c

View file

@ -0,0 +1,376 @@
"""
CmdSethandler
The Cmdhandler tracks an object's 'Current CmdSet', which is the
current merged sum of all CmdSets added to it.
A CmdSet constitues a set of commands. The CmdSet works as a special
intelligent container that, when added to other CmdSet make sure that
same-name commands are treated correctly (usually so there are no
doublets). This temporary but up-to-date merger of CmdSet is jointly
called the Current Cmset. It is this Current CmdSet that the
commandhandler looks through whenever a player enters a command (it
also adds CmdSets from objects in the room in real-time). All player
objects have a 'default cmdset' containing all the normal in-game mud
commands (look etc).
So what is all this cmdset complexity good for?
In its simplest form, a CmdSet has no commands, only a key name. In
this case the cmdset's use is up to each individual game - it can be
used by an AI module for example (mobs in cmdset 'roam' move from room
to room, in cmdset 'attack' they enter combat with players).
Defining commands in cmdsets offer some further powerful game-design
consequences however. Here are some examples:
As mentioned above, all players always have at least the Default
CmdSet. This contains the set of all normal-use commands in-game,
stuff like look and @desc etc. Now assume our players end up in a dark
room. You don't want the player to be able to do much in that dark
room unless they light a candle. You could handle this by changing all
your normal commands to check if the player is in a dark room. This
rapidly goes unwieldly and error prone. Instead you just define a
cmdset with only those commands you want to be available in the 'dark'
cmdset - maybe a modified look command and a 'light candle' command -
and have this completely replace the default cmdset.
Another example: Say you want your players to be able to go
fishing. You could implement this as a 'fish' command that fails
whenever the player has no fishing rod. Easy enough. But what if you
want to make fishing more complex - maybe you want four-five different
commands for throwing your line, reeling in, etc? Most players won't
(we assume) have fishing gear, and having all those detailed commands
is cluttering up the command list. And what if you want to use the
'throw' command also for throwing rocks etc instead of 'using it up'
for a minor thing like fishing?
So instead you put all those detailed fishing commands into their own
CommandSet called 'Fishing'. Whenever the player gives the command
'fish' (presumably the code checks there is also water nearby), only
THEN this CommandSet is added to the Cmdhandler of the player. The
'throw' command (which normally throws rocks) is replaced by the
custom 'fishing variant' of throw. What has happened is that the
Fishing CommandSet was merged on top of the Default ones, and due to
how we defined it, its command overrules the default ones.
When we are tired of fishing, we give the 'go home' command (or
whatever) and the Cmdhandler simply removes the fishing CommandSet
so that we are back at defaults (and can throw rocks again).
Since any number of ComandSets can be piled on top of each other, you
can then implement separate sets for different situations. For
example, you can have a 'On a boat' set, onto which you then tack on
the 'Fishing' set. Fishing from a boat? No problem!
"""
import traceback
from src.utils import logger
from src.utils import create
from src.commands.cmdset import CmdSet
from src.scripts.scripts import AddCmdSet
CACHED_CMDSETS = {}
def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
"""
This helper function is used by the cmdsethandler to load a cmdset
instance from a python module, given a python_path. It's usually accessed
through the cmdsethandler's add() and add_default() methods.
python_path - This is the full path to the cmdset object.
cmsetobj - the database object/typeclass on which this cmdset is to be assigned
(this can be also channels and exits, as well as players but there will
always be such an object)
emit_to_obj - if given, error is emitted to this object (in addition to logging)
no_logging - don't log/send error messages. This can be useful if import_cmdset is just
used to check if this is a valid python path or not.
function returns None if an error was encountered or path not found.
"""
try:
try:
wanted_cache_key = python_path
cmdsetclass = CACHED_CMDSETS.get(wanted_cache_key, None)
errstring = ""
if not cmdsetclass:
#print "cmdset %s not in cache. Reloading." % wanted_cache_key
# Not in cache. Reload from disk.
modulepath, classname = python_path.rsplit('.', 1)
module = __import__(modulepath, fromlist=[True])
cmdsetclass = module.__dict__[classname]
CACHED_CMDSETS[wanted_cache_key] = cmdsetclass
#print "cmdset %s found." % wanted_cache_key
#instantiate the cmdset (and catch its errors)
if callable(cmdsetclass):
cmdsetclass = cmdsetclass(cmdsetobj)
return cmdsetclass
except ImportError:
errstring = "Error loading cmdset: Couldn't import module '%s'."
errstring = errstring % modulepath
raise
except KeyError:
errstring = "Error in loading cmdset: No cmdset class '%s' in %s."
errstring = errstring % (modulepath, classname)
raise
except Exception:
errstring = "\n%s\nCompile/Run error when loading cmdset '%s'."
errstring = errstring % (traceback.format_exc(), python_path)
raise
except Exception:
if errstring and not no_logging:
logger.log_trace()
if emit_to_obj:
emit_to_obj.msg(errstring)
raise
def get_cached_cmdsets():
"""
Get the currently live cache from outside this module.
Calling a routine avoids update-problems when importing
the variable from an external module.
"""
return CACHED_CMDSETS
# classes
class CmdSetHandler(object):
"""
The CmdSetHandler is always stored on an object, supplied as the argument.
The 'current' cmdset is the merged set currently active for this object.
This is the set the game engine will retrieve when determining which
commands are available to the object. The cmdset_stack holds a history of all CmdSets
to allow the handler to remove/add cmdsets at will. Doing so will re-calculate
the 'current' cmdset.
"""
def __init__(self, obj, outside_access=True):
"""
This method is called whenever an object is recreated.
obj - this is a reference to the game object this handler
belongs to.
outside_access - if false, the cmdparser will only retrieve
this cmdset when it is its obj itself that is calling for it.
(this is is good to use for player objects, since they
should not have access to the cmdsets of other player
objects).
"""
self.obj = obj
# if false, only the object itself may use this handler
# (this should be set especially by character objects)
self.outside_access = outside_access
# the id of the "merged" current cmdset for easy access.
self.key = None
# this holds the "merged" current command set
self.current = None
# this holds a history of CommandSets
self.cmdset_stack = [CmdSet(cmdsetobj=self.obj, key="Empty")]
# this tracks which mergetypes are actually in play in the stack
self.mergetype_stack = ["Union"]
self.update()
def __str__(self):
"Display current commands"
string = ""
if len(self.cmdset_stack) > 1:
# We have more than one cmdset in stack; list them all
num = 0
#print self.cmdset_stack, self.mergetype_stack
for snum, cmdset in enumerate(self.cmdset_stack):
num = snum
mergetype = self.mergetype_stack[snum]
if mergetype != cmdset.mergetype:
mergetype = "%s^" % (mergetype)
string += "\n %i: <%s (%s, prio %i)>: %s" % \
(snum, cmdset.key, mergetype,
cmdset.priority, cmdset)
string += "\n (combining %i cmdsets):" % (num+1)
else:
string += "\n "
# Display the currently active cmdset
mergetype = self.mergetype_stack[-1]
if mergetype != self.current.mergetype:
merged_on = self.cmdset_stack[-2].key
mergetype = "custom %s on %s" % (mergetype, merged_on)
string += " <%s (%s)> %s" % (self.current.key,
mergetype, self.current)
return string.strip()
def allow_outside_access(self, source_object):
"""
This is what the main commandhandler is using to check if
it should include this handler when searching for matching
commands. It should return True for most of the time except
for player-object handlers, which are only available to the
player herself. Handle individual permission checks with
the command.permissions mechanic instead.
"""
return self.outside_access or self.obj == source_object
def update(self):
"""
Re-adds all sets in the handler to have an updated
current set.
"""
updated = None
self.mergetype_stack = []
for cmdset in self.cmdset_stack:
try:
# for cmdset's '+' operator, order matters.
updated = cmdset + updated
except TypeError:
continue
self.mergetype_stack.append(updated.actual_mergetype)
self.current = updated
def import_cmdset(self, cmdset_path, emit_to_obj=None):
"""
load a cmdset from a module.
cmdset_path - the python path to an cmdset object.
emit_to_obj - object to send error messages to
"""
if not emit_to_obj:
emit_to_obj = self.obj
return import_cmdset(cmdset_path, self.obj, emit_to_obj)
def add(self, cmdset, emit_to_obj=None, permanent=False):
"""
Add a cmdset to the handler, on top of the old ones.
Default is to not make this permanent (i.e. no script
will be added to add the cmdset every server start/login).
cmdset - can be a cmdset object or the python path to
such an object.
emit_to_obj - an object to receive error messages.
permanent - create a script to automatically add the cmdset
every time the server starts/the object logins.
Note: An interesting feature of this method is if you were to
send it an *already instantiated cmdset* (i.e. not a class),
the current cmdsethandler's obj attribute will then *not* be
transferred over to this already instantiated set (this is
because it might be used elsewhere and can cause strange effects).
This means you could in principle have the handler
launch command sets tied to a *different* object than the
handler. Not sure when this would be useful, but it's a 'quirk'
that has to be documented.
"""
if callable(cmdset):
cmdset = cmdset(self.obj)
elif isinstance(cmdset, basestring):
# this is (maybe) a python path. Try to import from cache.
cmdset = self.import_cmdset(cmdset, emit_to_obj)
if cmdset:
self.cmdset_stack.append(cmdset)
self.update()
if permanent:
# create a script to automatically add this cmdset at
# startup. We don't start it here since the cmdset was
# already added above.
try:
cmdset = "%s.%s" % (cmdset.__module__, cmdset.__name__)
except Exception:
logger.log_trace()
return
script = create.create_script(AddCmdSet)
script.db.cmdset = cmdset
script.db.add_default = False
self.obj.scripts.add(script, autostart=False)
def add_default(self, cmdset, emit_to_obj=None, permanent=False):
"""
Add a new default cmdset. If an old default existed,
it is replaced. If permanent is set, a script will be created to
add the cmdset to the object.
cmdset - can be a cmdset object or the python path to
an instance of such an object.
emit_to_obj - an object to receive error messages.
permanent - create a script that assigns this script every
startup/login.
See also the notes for self.add(), which applies here too.
"""
if callable(cmdset):
cmdset = cmdset(self.obj)
elif isinstance(cmdset, basestring):
# this is (maybe) a python path. Try to import from cache.
cmdset = self.import_cmdset(cmdset, emit_to_obj)
if cmdset:
self.cmdset_stack[0] = cmdset
self.mergetype_stack[0] = cmdset.mergetype
self.update()
#print "add_default:", permanent
if permanent:
# create a script to automatically add this cmdset at
# startup. We don't start it here since the cmdset was
# already added above.
try:
cmdset = "%s.%s" % (cmdset.__module__, cmdset.__class__.__name__)
except Exception:
#print traceback.format_exc()
logger.log_trace()
return
#print "cmdset to add:", cmdset
script = create.create_script(AddCmdSet)
script.db.cmdset = cmdset
script.db.add_default = True
self.obj.scripts.add(script, key="add_default_cmdset", autostart=False)
def delete(self, key_or_class=None):
"""
Remove a cmdset from the handler. If a key is supplied,
it attempts to remove this. If no key is given,
the last cmdset in the stack is removed. Whenever
the cmdset_stack changes, the cmdset is updated.
The default cmdset (first entry in stack) is never
removed - set it to an empty set with add_default instead.
key_or_class - a specific cmdset key or a cmdset class (in
the latter case, *all* cmdsets of this class
will be removed from handler!)
"""
if len(self.cmdset_stack) < 2:
# don't allow deleting default cmdsets here.
return
if not key_or_class:
# remove the last one in the stack (except the default position)
self.cmdset_stack.pop()
else:
# argument key is given, is it a key or a class?
default_cmdset = self.cmdset_stack[0]
if callable(key_or_class) and hasattr(key_or_class, '__name__'):
# this is a callable with __name__ - we assume it's a class
self.cmdset_stack = [cmdset for cmdset in self.cmdset_stack[1:]
if cmdset.__class__.__name__ != key_or_class.__name__]
else:
# try it as a string
self.cmdset_stack = [cmdset for cmdset in self.cmdset_stack[1:]
if cmdset.key != key_or_class]
self.cmdset_stack.insert(0, default_cmdset)
# re-sync the cmdsethandler.
self.update()
def all(self):
"""
Returns the list of cmdsets. Mostly useful to check if stack if empty or not.
"""
return self.cmdset_stack
def clear(self):
"""
Removes all extra Command sets from the handler, leaving only the
default one.
"""
self.cmdset_stack = [self.cmdset_stack[0]]
self.mergetype_stack = [self.cmdset_stack[0].mergetype]
self.update()

149
src/commands/command.py Normal file
View file

@ -0,0 +1,149 @@
"""
The base Command class.
All commands in Evennia inherit from the 'Command' class in this module.
"""
from src.permissions import permissions
from src.utils.utils import is_iter
class CommandMeta(type):
"""
This metaclass makes some minor on-the-fly convenience fixes to the command
class in case the admin forgets to put things in lowercase etc.
"""
def __init__(mcs, *args, **kwargs):
"""
Simply make sure all data are stored as lowercase and
do checking on all properties that should be in list form.
"""
mcs.key = mcs.key.lower()
if mcs.aliases and not is_iter(mcs.aliases):
mcs.aliases = mcs.aliases.split(',')
mcs.aliases = [str(alias).strip().lower() for alias in mcs.aliases]
if mcs.permissions and not is_iter(mcs.permissions) :
mcs.permissions = mcs.permissions.split(',')
mcs.permissions = [str(perm).strip().lower() for perm in mcs.permissions]
mcs.help_category = mcs.help_category.lower()
super(CommandMeta, mcs).__init__(*args, **kwargs)
# The Command class is the basic unit of an Evennia command; when
# defining new commands, the admin subclass this class and
# define their own parser method to handle the input. The
# advantage of this is inheritage; commands that have similar
# structure can parse the input string the same way, minimizing
# parsing errors.
class Command(object):
"""
Base command
Usage:
command [args]
This is the base command class. Inherit from this
to create new commands.
The cmdhandler makes the following variables available to the
command methods (so you can always assume them to be there):
self.caller - the game object calling the command
self.cmdstring - the command name used to trigger this command (allows
you to know which alias was used, for example)
cmd.args - everything supplied to the command following the cmdstring
(this is usually what is parsed in self.parse())
cmd.cmdset - the cmdset from which this command was matched (useful only
seldomly, notably for help-type commands, to create dynamic
help entries and lists)
cmd.obj - the object on which this command is defined. If a default command,
this is usually the same as caller.
(Note that this initial string is also used by the system to create the help
entry for the command, so it's a good idea to format it similar to this one)
"""
# Tie our metaclass, for some convenience cleanup
__metaclass__ = CommandMeta
# the main way to call this command (e.g. 'look')
key = "command"
# alternative ways to call the command (e.g. 'l', 'glance', 'examine')
aliases = []
# a list of permission strings or comma-separated string limiting
# access to this command.
permissions = []
# used by the help system to group commands in lists.
help_category = "default"
# There is also the property 'obj'. This gets set by the system
# on the fly to tie this particular command to a certain in-game entity.
# self.obj should NOT be defined here since it will not be overwritten
# if it already exists.
def __str__(self):
"Print the command"
return self.key
def __eq__(self, cmd):
"""
Compare two command instances to each other by matching their
key and aliases.
input can be either a cmd object or the name of a command.
"""
if type(cmd) != self:
return self.match(cmd)
return self.match(cmd.key)
def __contains__(self, query):
"""
This implements searches like 'if query in cmd'. It's a fuzzy matching
used by the help system, returning True if query can be found
as a substring of the commands key or its aliases.
input can be either a command object or a command name.
"""
if type(query) == type(Command()):
query = query.key
return (query in self.key) or \
(sum([query in alias for alias in self.aliases]) > 0)
def match(self, cmdname):
"""
This is called by the system when searching the available commands,
in order to determine if this is the one we wanted. cmdname was
previously extracted from the raw string by the system.
cmdname is always lowercase when reaching this point.
"""
return (cmdname == self.key) or (cmdname in self.aliases)
def has_perm(self, srcobj):
"""
This hook is called by the cmdhandler to determine if srcobj
is allowed to execute this command. It should return a boolean
value and is not normally something that need to be changed since
it's using the Evennia permission system directly.
"""
return permissions.has_perm(srcobj, self, 'cmd')
# Common Command hooks
def parse(self):
"""
Once the cmdhandler has identified this as the command we
want, this function is run. If many of your commands have
a similar syntax (for example 'cmd arg1 = arg2') you should simply
define this once and just let other commands of the same form
inherit from this. See the docstring of this module for
which object properties are available to use
(notably self.args).
"""
pass
def func(self):
"""
This is the actual executing part of the command.
It is called directly after self.parse(). See the docstring
of this module for which object properties are available
(beyond those set in self.parse())
"""
string = "Command '%s' was executed with arg string '%s'."
self.caller.msg(string % (self.key, self.args))

View file

@ -1,623 +0,0 @@
"""
Comsys command module.
"""
from src import comsys
from src.channels.models import CommChannelMembership, CommChannel
from src import defines_global
from src.objects.models import Object
from src.cmdtable import GLOBAL_CMD_TABLE
def cmd_addcom(command):
"""
addcom - join a channel with alias
Usage:
addcom [alias=] <channel>
Allows adding an alias for a channel to make is easier and
faster to use. Subsequent calls of this command can
be used to add multiple aliases.
"""
source_object = command.source_object
command_argument = command.command_argument
if not command_argument:
source_object.emit_to("Usage: addcom [alias=]channelname.")
return
if '=' in command_argument:
chan_alias, chan_name = command.command_argument.split('=', 1)
chan_alias, chan_name = chan_alias.strip(), chan_name.strip()
else:
chan_name = command_argument.strip()
chan_alias = chan_name
membership = source_object.channel_membership_set.filter(channel__name__iexact=chan_name)
try:
chan = CommChannel.objects.get(name__iexact=chan_name)
s = ""
#if we happened to have this alias already defined on another channel, make
#sure to tell us.
aliasmatch = [cmatch.channel.name for cmatch in
source_object.channel_membership_set.filter(user_alias=chan_alias)
if cmatch.channel.name != chan_name]
if aliasmatch:
s = "The alias '%s' is already in use (for channel '%s')." % (chan_alias, aliasmatch[0])
source_object.emit_to(s)
return
if membership:
#we are already members of this channel. Set a different alias.
# Note: To this without requiring a the user to logout then login again,
# we need to delete, then rejoin the channel. Is this due to the lazy
# loading? /Griatch
prev_alias = membership[0].user_alias
if chan_alias == prev_alias:
s += "Alias unchanged."
else:
comsys.plr_del_channel(source_object, prev_alias)
comsys.plr_add_channel(source_object, chan_alias, chan)
s += "Channel '%s' alias changed from '%s' to '%s'." % (chan_name,prev_alias,
chan_alias)
else:
# This adds a CommChannelMembership object and a matching dict entry
# on the session's cdict.
comsys.plr_add_channel(source_object, chan_alias, chan)
# Let the player know everything went well.
s += "You join %s, with an alias of %s." % \
(chan.get_name(), chan_alias)
# Announce the user's joining.
join_msg = "%s has joined the channel." % \
(source_object.get_name(show_dbref=False),)
comsys.send_cmessage(chan, join_msg)
source_object.emit_to(s)
except CommChannel.DoesNotExist:
# Failed to match iexact on channel's 'name' attribute.
source_object.emit_to("Could not find channel %s." % chan_name)
GLOBAL_CMD_TABLE.add_command("addcom", cmd_addcom, help_category="Comms")
def cmd_delcom(command):
"""
delcom - remove a channel alias
Usage:
delcom <alias>
Removes the specified alias to a channel. If this is the last alias,
the user is effectively removed from the channel.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Usage: delcom <alias>")
return
try:
membership = source_object.channel_membership_set.get(user_alias__iexact=command.command_argument)
except CommChannelMembership.DoesNotExist:
source_object.emit_to("You are not on that channel.")
return
chan_name = membership.channel.get_name()
source_object.emit_to("You have left %s." % chan_name)
comsys.plr_del_channel(source_object, command.command_argument)
# Announce the user's leaving.
leave_msg = "%s has left the channel." % \
(source_object.get_name(show_dbref=False),)
comsys.send_cmessage(chan_name, leave_msg)
GLOBAL_CMD_TABLE.add_command("delcom", cmd_delcom,help_category="Comms")
def cmd_comlist(command):
"""
comlist - list channel memberships
Usage:
comlist
Lists the channels a user is subscribed to.
"""
source_object = command.source_object
session = command.session
s = "Your subscibed channels (use @clist for full chan list)\n"
s += "** Alias Channel Status\n"
channels = source_object.channel_membership_set.all()
if not channels:
s += " (No subscriptions) "
for membership in channels:
chan = membership.channel
if membership.is_listening:
chan_on = "On"
else:
chan_on = "Off"
s += " %s%s %-15.14s%-22.15s%s\n" % ('-','-',membership.user_alias,
chan.get_name(), chan_on)
s = s[:-1]
source_object.emit_to(s)
GLOBAL_CMD_TABLE.add_command("comlist", cmd_comlist,help_category="Comms")
def cmd_allcom(command):
"""
allcom - operate on all channels
Usage:
allcom [on | off | who | clear]
Allows the user to universally turn off or on all channels they are on,
as well as perform a 'who' for all channels they are on. Clear deletes
all channels.
Without argument, works like comlist.
"""
source_object = command.source_object
arg = command.command_argument
if not arg:
cmd_comlist(command)
source_object.emit_to("(allcom arguments: 'on', 'off', 'who' and 'clear'.)")
return
arg = arg.strip()
if arg == 'clear':
cmd_clearcom(command)
return
#get names and alias of all subscribed channels
chandict = comsys.plr_get_cdict(command.session)
aliaslist = chandict.keys()
aliaslist.sort()
if arg == "on":
for alias in aliaslist:
comsys.plr_chan_on(command.session, alias)
elif arg == "off":
for alias in aliaslist:
comsys.plr_chan_off(command.session, alias)
elif arg == "who":
s = ""
if not aliaslist:
s += " (No channels) "
for alias in aliaslist:
s += "-- %s (alias: %s)\n" % (chandict[alias][0],alias)
sess_list = comsys.get_cwho_list(chandict[alias][0])
objlist = [sess.get_pobject() for sess in sess_list]
plist = [p.get_name(show_dbref=source_object.sees_dbrefs())
for p in filter(lambda o: o.is_player(), objlist)]
olist = [o.get_name(show_dbref=source_object.sees_dbrefs())
for o in filter(lambda o: not o.is_player(), objlist)]
plist.sort()
olist.sort()
if plist:
s += " Players:\n "
for pname in plist:
s += "%s, " % pname
s = s[:-2] + "\n"
if olist:
s += " Objects:\n "
for oname in olist:
s += "%s, " % oname
s = s[:-2] + "\n"
s = s[:-1]
source_object.emit_to(s)
GLOBAL_CMD_TABLE.add_command("allcom", cmd_allcom, help_category="Comms")
def cmd_clearcom(command):
"""
clearcom - removes all channels
Usage:
clearcom
Effectively runs delcom on all channels the user is on. It will remove
their aliases, remove them from the channel, and clear any titles they
have set.
"""
source_object = command.source_object
#get aall subscribed channel memberships
memberships = source_object.channel_membership_set.all()
if not memberships:
s = "No channels to delete. "
else:
s = "Deleting all channels in your subscriptions ...\n"
for membership in memberships:
chan_name = membership.channel.get_name()
s += "You have left %s.\n" % chan_name
comsys.plr_del_channel(source_object, membership.user_alias)
comsys.send_cmessage(chan_name, "%s has left the channel." % source_object.get_name(show_dbref=False))
s = s[:-1]
source_object.emit_to(s)
GLOBAL_CMD_TABLE.add_command("clearcom", cmd_clearcom)
def cmd_clist(command):
"""
@clist
Usage:
@clist
Lists all available channels in the game.
[[clist]]
This is the same as @clist - it shows all
available channels in game.
"""
session = command.session
source_object = command.source_object
s = "All channels (use comlist or allcom to see your subscriptions)\n"
s += "** Channel Owner Description\n"
channels = comsys.get_all_channels()
if not channels:
s += "(No channels) "
for chan in channels:
s += " %s%s %-15.14s%-22.15s%s\n" % \
('-',
'-',
chan.get_name(),
chan.get_owner().get_name(show_dbref=False),
chan.description)
s = s[:-1]
#s += "** End of Channel List **"
source_object.emit_to(s)
GLOBAL_CMD_TABLE.add_command("@clist", cmd_clist, help_category="Comms")
GLOBAL_CMD_TABLE.add_command("clist", cmd_clist, help_category="Comms")
def cmd_cdestroy(command):
"""
@cdestroy
Usage:
@cdestroy <channel>
Destroys a channel that you control.
"""
source_object = command.source_object
cname = command.command_argument
if not cname:
source_object.emit_to("Usage: @cdestroy <channelname>")
return
name_matches = comsys.cname_search(cname, exact=True)
if not name_matches:
source_object.emit_to("Could not find channel %s." % (cname,))
else:
is_controlled_by_plr = name_matches[0].controlled_by(source_object)
if is_controlled_by_plr or source_object.has_perm("channels.channel_admin"):
source_object.emit_to("Channel %s destroyed." % (name_matches[0],))
name_matches.delete()
else:
source_object.emit_to("Permission denied.")
return
GLOBAL_CMD_TABLE.add_command("@cdestroy", cmd_cdestroy, help_category="Comms")
def cmd_cset(command):
"""
@cset
Sets various flags on a channel.
"""
# TODO: Implement cmd_cset
pass
def cmd_ccharge(command):
"""
@ccharge
Sets the cost to transmit over a channel. Default is free.
"""
# TODO: Implement cmd_ccharge
pass
def cmd_cboot(command):
"""
@cboot
Usage:
@cboot[/quiet] <channel> = <player or object>
Kicks a player or object from a channel you control.
"""
source_object = command.source_object
args = command.command_argument
switches = command.command_switches
if not args or not "=" in args:
source_object.emit_to("Usage: @cboot[/quiet] <channel> = <object>")
return
cname, objname = args.split("=",1)
cname, objname = cname.strip(), objname.strip()
if not cname or not objname:
source_object.emit_to("You must supply both channel and object.")
return
try:
channel = CommChannel.objects.get(name__iexact=cname)
except CommChannel.DoesNotExist:
source_object.emit_to("Could not find channel %s." % cname)
return
#do we have power over this channel?
if not channel.controlled_by(source_object) or source_object.has_perm("channels.channel_admin"):
source_object.emit_to("You don't have that power in channel '%s'." % cname)
return
#mux specification requires an * before player objects.
player_boot = False
if objname[0] == '*':
player_boot = True
objname = objname[1:]
bootobj = Object.objects.player_name_search(objname)
if not bootobj:
source_object.emit_to("Object '%s' not found." % objname)
return
if bootobj.is_player() and not player_boot:
source_object.emit_to("To boot players you need to start their name with an '*'. ")
return
#check so that this object really is on the channel in the first place
membership = bootobj.channel_membership_set.filter(channel__name__iexact=cname)
if not membership:
source_object.emit_to("'%s' is not on channel '%s'." % (objname,cname))
return
#announce to channel
if not 'quiet' in switches:
comsys.send_cmessage(cname, "%s boots %s from channel." % \
(source_object.get_name(show_dbref=False), objname))
#all is set, boot the object by removing all its aliases from the channel.
for mship in membership:
comsys.plr_del_channel(bootobj, mship.user_alias)
GLOBAL_CMD_TABLE.add_command("@cboot", cmd_cboot, help_category="Comms")
def cmd_cemit(command):
"""
@cemit - send a message to channel
Usage:
@cemit <channel>=<message>
@cemit/noheader <channel>=<message>
@cemit/sendername <channel>=<message>
Allows the user to send a message over a channel as long as
they own or control it. It does not show the user's name unless they
provide the /sendername switch.
[[channel_commands]]
Useful channel commands
(see their help pages for detailed help and options)
- Listing channels
clist - show all channels available to you
comlist - show channels you listen to
- Joining/parting channels
addcom - add your alias for a channel
delcom - remove alias for channel
(leave channel if no more aliases)
allcom - view, on/off or remove all your channels
clearcom - removes all channels
- Other
who - list who's online
<chanalias> off - silence channel temporarily
<chanalias> on - turn silenced channel back on
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("@cemit[/switches] <channel> = <message>")
return
eq_args = command.command_argument.split('=', 1)
if len(eq_args) != 2:
source_object.emit_to("You must provide a channel name and a message to emit.")
return
cname = eq_args[0].strip()
cmessage = eq_args[1].strip()
final_cmessage = cmessage
if len(cname) == 0:
source_object.emit_to("You must provide a channel name to emit to.")
return
if len(cmessage) == 0:
source_object.emit_to("You must provide a message to emit.")
return
name_matches = comsys.cname_search(cname, exact=True)
if name_matches:
cname_parsed = name_matches[0].get_name()
else:
source_object.emit_to("Could not find channel %s." % (cname,))
return
# If this is False, don't show the channel header before
# the message. For example: [Public] Woohoo!
show_channel_header = True
if "noheader" in command.command_switches:
if not source_object.has_perm("objects.emit_commchannel"):
source_object.emit_to(defines_global.NOPERMS_MSG)
return
final_cmessage = cmessage
show_channel_header = False
else:
if "sendername" in command.command_switches:
if not comsys.plr_has_channel(command.session, cname_parsed,
return_muted=False):
source_object.emit_to("You must be on %s to do that." % (cname_parsed,))
return
final_cmessage = "%s: %s" % (source_object.get_name(show_dbref=False),
cmessage)
else:
if not source_object.has_perm("objects.emit_commchannel"):
source_object.emit_to(defines_global.NOPERMS_MSG)
return
final_cmessage = cmessage
if not "quiet" in command.command_switches:
source_object.emit_to("Sent - %s" % (name_matches[0],))
comsys.send_cmessage(cname_parsed, final_cmessage,
show_header=show_channel_header)
#pipe to external channels (IRC, IMC) eventually mapped to this channel
comsys.send_cexternal(cname_parsed, cmessage, caller=source_object)
GLOBAL_CMD_TABLE.add_command("@cemit", cmd_cemit,priv_tuple=("channels.emit_commchannel",),
help_category="Comms")
def cmd_cwho(command):
"""
@cwho
Usage:
@cwho channel[/all]
Displays the name, status and object type for a given channel.
Adding /all after the channel name will list disconnected players
as well.
"""
session = command.session
source_object = command.source_object
if not command.command_argument:
cmd_clist(command)
source_object.emit_to("Usage: @cwho <channel>[/all]")
return
channel_name = command.command_argument
if channel_name.strip() == '':
source_object.emit_to("You must specify a channel name.")
return
name_matches = comsys.cname_search(channel_name, exact=True)
if name_matches:
# Check to make sure the user has permission to use @cwho.
is_channel_admin = source_object.has_perm("objects.channel_admin")
is_controlled_by_plr = name_matches[0].controlled_by(source_object)
if is_controlled_by_plr or is_channel_admin:
comsys.msg_cwho(source_object, channel_name)
else:
source_object.emit_to("Permission denied.")
return
else:
source_object.emit_to("No channel with that name was found.")
return
GLOBAL_CMD_TABLE.add_command("@cwho", cmd_cwho, help_category="Comms")
def cmd_ccreate(command):
"""
@ccreate
Usage:
@ccreate <new channel>
Creates a new channel owned by you.
"""
source_object = command.source_object
cname = command.command_argument
if not cname:
source_object.emit_to("Usage @ccreate <channelname>")
return
if not source_object.has_perm("objects.channel_admin"):
source_object.emit_to("Permission denied.")
return
name_matches = comsys.cname_search(cname, exact=True)
if name_matches:
source_object.emit_to("A channel with that name already exists.")
else:
# Create and set the object up.
new_chan = comsys.create_channel(cname, source_object)
source_object.emit_to("Channel %s created." % (new_chan.get_name(),))
GLOBAL_CMD_TABLE.add_command("@ccreate", cmd_ccreate, help_category="Comms")
def cmd_cchown(command):
"""
@cchown
Usage:
@cchown <channel> = <player>
Changes the owner of a channel.
"""
source_object = command.source_object
args = command.command_argument
if not args or "=" not in args:
source_object.emit_to("Usage: @cchown <channel> = <player>")
return
cname, pname = args.split("=",1)
cname, pname = cname.strip(), pname.strip()
#locate channel
try:
channel = CommChannel.objects.get(name__iexact=cname)
except CommChannel.DoesNotExist:
source_object.emit_to("Channel '%s' not found." % cname)
return
#check so we have ownership to give away.
if not channel.controlled_by(source_object) and not source_object.has_perm("channels.channel_admin"):
source_object.emit_to("You don't control this channel.")
return
#find the new owner
new_owner = Object.objects.player_name_search(pname)
if not new_owner:
source_object.emit_to("New owner '%s' not found." % pname)
return
old_owner = channel.get_owner()
old_pname = old_owner.get_name(show_dbref=False)
if old_owner == new_owner:
source_object.emit_to("Owner unchanged.")
return
#all is set, change owner
channel.set_owner(new_owner)
source_object.emit_to("Owner of %s changed from %s to %s." % (cname, old_pname, pname))
new_owner.emit_to("%s transfered ownership of channel '%s' to you." % (old_pname, cname))
GLOBAL_CMD_TABLE.add_command("@cchown", cmd_cchown, help_category="Comms")
def cmd_cdesc(command):
"""
@cdesc - set channel description
Usage:
@cdesc <channel> = <description>
Changes the description of the channel as shown in
channel lists.
"""
source_object = command.source_object
args = command.command_argument
if not args or "=" not in args:
source_object.emit_to("Usage: @cdesc <channel> = <description>")
return
cname, text = args.split("=",1)
cname, text = cname.strip(), text.strip()
#locate channel
try:
channel = CommChannel.objects.get(name__iexact=cname)
except CommChannel.DoesNotExist:
source_object.emit_to("Channel '%s' not found." % cname)
return
#check permissions
if not channel.controlled_by(source_object) \
and not source_object.has_perm("channels.channel_admin"):
source_object.emit_to("You don't control this channel.")
return
# set the description
channel.set_description(text)
source_object.emit_to("Description of channel '%s' set to '%s'." % (cname, text))
GLOBAL_CMD_TABLE.add_command("@cdesc", cmd_cdesc, help_category="Comms")

View file

@ -1,807 +0,0 @@
"""
Generic command module. Pretty much every command should go here for
now.
"""
import time
from django.contrib.auth.models import User
from src.objects.models import Object
from src.config.models import ConfigValue
from src.helpsys.models import HelpEntry
from src.ansi import ANSITable
from src import session_mgr
from src.util import functions_general
from src.helpsys import helpsystem
from src.cmdtable import GLOBAL_CMD_TABLE
def cmd_password(command):
"""
@password - set your password
Usage:
@password <old password> = <new password>
Changes your password. Make sure to pick a safe one.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Usage: @password <oldpass> = <newpass>")
return
if not source_object.is_player():
source_object.emit_to("This is only applicable for players.")
return
eq_args = command.command_argument.split('=', 1)
if len(eq_args) != 2:
source_object.emit_to("Incorrect number of arguments.")
return
oldpass = eq_args[0]
newpass = eq_args[1]
if len(oldpass) == 0:
source_object.emit_to("You must provide your old password.")
elif len(newpass) == 0:
source_object.emit_to("You must provide your new password.")
else:
uaccount = source_object.get_user_account()
if not uaccount.check_password(oldpass):
source_object.emit_to("The specified old password isn't correct.")
elif len(newpass) < 3:
source_object.emit_to("Passwords must be at least three characters long.")
return
else:
uaccount.set_password(newpass)
uaccount.save()
source_object.emit_to("Password changed.")
GLOBAL_CMD_TABLE.add_command("@password", cmd_password, help_category="System")
def cmd_emit(command):
"""
@emit
Usage:
@emit[/switches] [<obj>, <obj>, ... =] <message>
Switches:
room : limit emits to rooms only
contents : send to the contents of objects
Emits a message to the selected objects or to
your immediate surroundings. If the object is a room,
send to its contents. @pemit and @remit are
restricted aliases to this main command.
"""
source_object = command.source_object
args = command.command_argument
switches = command.command_switches
if not args:
source_object.emit_to("Usage: @emit/switches [<obj>, <obj>, ... =] <message>")
return
if '=' in args:
args, message = [arg.strip() for arg in args.split('=',1)]
targets = [arg.strip() for arg in args.split(',')]
else:
targets = [source_object.get_location().dbref()]
message = args.strip()
# we now have a text to send and a list of target names.
# perform a global search for actual objects
tobjects = []
for target in targets:
if target in ['here']:
results = [source_object.get_location()]
elif target in ['me','my']:
results = [source_object]
else:
results = Object.objects.global_object_name_search(target)
if not results:
source_object.emit_to("No matches found for '%s'." % target)
return
if len(results) > 1:
string = "There are multiple matches. Please use #dbref to be more specific."
for result in results:
string += "\n %s" % results.get_name(show_dbref=True)
source_object.emit_to(string)
return
tobjects.append(results[0])
if not tobjects:
return
# sort the objects into categories
players = [obj for obj in tobjects if obj.is_player()]
rooms = [obj for obj in tobjects if obj.is_room()]
exits = [obj for obj in tobjects if obj.is_exit()]
things = [obj for obj in tobjects if obj.is_thing()]
# send differently depending on flags
if "room" in switches or "rooms" in switches:
# send to rooms only
norooms = players + exits + things
if norooms:
source_object.emit_to("These are not rooms: %s" %
", ".join([r.get_name() for r in norooms]))
return
for room in rooms:
room.emit_to_contents(message, exclude=source_object)
elif "contents" in switches:
# send to contents of objects
allobj = players + rooms + exits + things
for obj in allobj:
if not source_object.controls_other(obj):
source_object.emit_to("Cannot emit to %s (you don's control it)" % obj.get_name())
continue
obj.emit_to_contents(message)
else:
# assume reasonable defaults depending on object type
for obj in players:
obj.emit_to(message)
for obj in rooms:
obj.emit_to_contents(message)
for obj in exits: #send to destination
obj.get_home().emit_to_contents(message)
for obj in things:
if not source_object.controls_other(obj):
source_object.emit_to("Cannot emit to %s (you don's control it)" % obj.get_name())
continue
obj. emit_to_contents(message)
allobj = players + rooms + exits + things
string = ", ".join([obj.get_name() for obj in allobj])
source_object.emit_to("Emitted message to: %s." % string)
GLOBAL_CMD_TABLE.add_command("@emit", cmd_emit,
priv_tuple=("genperms.announce",),help_category="Comms")
def cmd_remit(command):
"""
@remit - emit to a room
Usage:
@remit <room> [<room2>,<room3>,...] = <message>
Emits message to the contents of the room.
"""
if not command.command_argument:
command.source_object.emit_to("Usage: @remit <room>[,<room2>,<room3>,...] = <message>")
return
command.command_switches = ["room"]
cmd_emit(command)
GLOBAL_CMD_TABLE.add_command("@remit", cmd_remit,
priv_tuple=("genperms.announce",),help_category="Comms")
def cmd_pemit(command):
"""
@pemit - emit to an object or player
Usage:
@pemit[/switch] <obj> [,<obj2>, <obj3>, ...] = <message>
Switches:
contents : emit to the contents of each object (only if you own it)
Emits message to objects or the contents of objects.
"""
if not command.command_argument:
command.source_object.emit_to("Usage: @pemit <obj>[,<obj2>,<obj3>,...] = <message>")
return
command.command_switches = [switch for switch in command.command_switches
if switch == 'contents']
cmd_emit(command)
GLOBAL_CMD_TABLE.add_command("@pemit", cmd_pemit,
priv_tuple=("genperms.announce",),help_category="Comms")
def cmd_wall(command):
"""
@wall
Usage:
@wall <message>
Announces a message to all connected players.
"""
wallstring = command.command_argument
if not wallstring:
command.source_object.emit_to("Announce what?")
return
message = "%s shouts \"%s\"" % (
command.source_object.get_name(show_dbref=False), wallstring)
session_mgr.announce_all(message)
GLOBAL_CMD_TABLE.add_command("@wall", cmd_wall,
priv_tuple=("genperms.announce",),help_category="Comms")
def cmd_idle(command):
"""
idle
Usage:
idle
Returns and does nothing. You can use this to send idle
messages to the game, in order to avoid getting timed out.
"""
pass
GLOBAL_CMD_TABLE.add_command("idle", cmd_idle, help_category="System")
def cmd_inventory(command):
"""
inventory
Usage:
inventory
inv
Shows a player's inventory.
"""
source_object = command.source_object
source_object.emit_to("You are carrying:")
for item in source_object.get_contents():
source_object.emit_to(" %s" % (item.get_name(),))
money = int(source_object.get_attribute_value("MONEY", default=0))
if money == 1:
money_name = ConfigValue.objects.get_configvalue("MONEY_NAME_SINGULAR")
else:
money_name = ConfigValue.objects.get_configvalue("MONEY_NAME_PLURAL")
source_object.emit_to("You have %d %s." % (money, money_name))
GLOBAL_CMD_TABLE.add_command("inventory", cmd_inventory)
def cmd_look(command):
"""
look
Usage:
look
look <obj>
Observers your location or objects in your vicinity.
"""
source_object = command.source_object
# If an argument is provided with the command, search for the object.
# else look at the current room.
if command.command_argument:
target_obj = source_object.search_for_object(command.command_argument)
# Use search_for_object to handle duplicate/nonexistant results.
if not target_obj:
return
else:
target_obj = source_object.get_location()
# SCRIPT: Get the item's appearance from the scriptlink.
source_object.emit_to(target_obj.scriptlink.return_appearance(pobject=source_object))
# SCRIPT: Call the object's script's at_desc() method.
target_obj.scriptlink.at_desc(pobject=source_object)
GLOBAL_CMD_TABLE.add_command("look", cmd_look)
def cmd_get(command):
"""
get
Usage:
get <obj>
Picks up an object from your location and puts it in
your inventory.
"""
source_object = command.source_object
obj_is_staff = source_object.is_staff()
if not command.command_argument:
source_object.emit_to("Get what?")
return
else:
target_obj = source_object.search_for_object(command.command_argument,
search_contents=False)
# Use search_for_object to handle duplicate/nonexistant results.
if not target_obj:
return
if source_object == target_obj:
source_object.emit_to("You can't get yourself.")
return
if not obj_is_staff and (target_obj.is_player() or target_obj.is_exit()):
source_object.emit_to("You can't get that.")
return
if target_obj.is_room() or target_obj.is_garbage() or target_obj.is_going():
source_object.emit_to("You can't get that.")
return
if not target_obj.scriptlink.default_lock(source_object):
lock_msg = target_obj.get_attribute_value("lock_msg")
if lock_msg:
source_object.emit_to(lock_msg)
else:
source_object.emit_to("You can't get that.")
return
target_obj.move_to(source_object, quiet=True)
source_object.emit_to("You pick up %s." % (target_obj.get_name(show_dbref=False),))
source_object.get_location().emit_to_contents("%s picks up %s." %
(source_object.get_name(show_dbref=False),
target_obj.get_name(show_dbref=False)),
exclude=source_object)
# SCRIPT: Call the object's script's a_get() method.
target_obj.scriptlink.at_get(source_object)
GLOBAL_CMD_TABLE.add_command("get", cmd_get)
def cmd_drop(command):
"""
drop
Usage:
drop <obj>
Has you drop an object from your inventory into the
location you are currently in.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Drop what?")
return
else:
target_obj = source_object.search_for_object(command.command_argument,
search_location=False)
# Use search_for_object to handle duplicate/nonexistant results.
if not target_obj:
return
if not source_object == target_obj.get_location():
source_object.emit_to("You don't appear to be carrying that.")
return
target_obj.move_to(source_object.get_location(), quiet=True)
source_object.emit_to("You drop %s." % (target_obj.get_name(show_dbref=False),))
source_object.get_location().emit_to_contents("%s drops %s." %
(source_object.get_name(show_dbref=False),
target_obj.get_name(show_dbref=False)),
exclude=source_object)
# SCRIPT: Call the object script's a_drop() method.
target_obj.scriptlink.at_drop(source_object)
GLOBAL_CMD_TABLE.add_command("drop", cmd_drop),
def cmd_quit(command):
"""
quit
Usage:
quit
Gracefully disconnect from the game.
"""
if command.session:
session = command.session
session.msg("Quitting. Hope to see you soon again.")
session.handle_close()
GLOBAL_CMD_TABLE.add_command("quit", cmd_quit, help_category="System")
def cmd_who(command):
"""
who
Usage:
who
Shows who is currently online.
"""
session_list = session_mgr.get_session_list()
source_object = command.source_object
# In the case of the DOING command, don't show session data regardless.
if command.extra_vars and \
command.extra_vars.get("show_session_data", None) == False:
show_session_data = False
else:
show_session_data = source_object.has_perm("genperms.see_session_data")
# Only those with the see_session_data or superuser status can see
# session details.
if show_session_data:
retval = "Player Name On For Idle Room Cmds Host\n\r"
else:
retval = "Player Name On For Idle\n\r"
for player in session_list:
if not player.logged_in:
continue
delta_cmd = time.time() - player.cmd_last_visible
delta_conn = time.time() - player.conn_time
plr_pobject = player.get_pobject()
if show_session_data:
retval += '%-31s%9s %4s%-3s#%-6d%5d%3s%-25s\r\n' % \
(plr_pobject.get_name(show_dbref=True, show_flags=False)[:25], \
# On-time
functions_general.time_format(delta_conn,0), \
# Idle time
functions_general.time_format(delta_cmd,1), \
# Flags
'', \
# Location
plr_pobject.get_location().id, \
player.cmd_total, \
# More flags?
'', \
player.address[0])
else:
retval += '%-31s%9s %4s%-3s\r\n' % \
(plr_pobject.get_name(show_dbref=False)[:25], \
# On-time
functions_general.time_format(delta_conn,0), \
# Idle time
functions_general.time_format(delta_cmd,1), \
# Flags
'')
retval += '%d Players logged in.' % (len(session_list),)
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("doing", cmd_who,
extra_vals={"show_session_data": False}, help_category="System")
GLOBAL_CMD_TABLE.add_command("who", cmd_who,help_category="System")
def cmd_say(command):
"""
say
Usage:
say <message>
Talk to those in your current location.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Say what?")
return
speech = command.command_argument
# Feedback for the object doing the talking.
source_object.emit_to("You say, '%s%s'" % (speech,
ANSITable.ansi['normal']))
# Build the string to emit to neighbors.
emit_string = "%s says, '%s'" % (source_object.get_name(show_dbref=False),
speech)
source_object.get_location().emit_to_contents(emit_string,
exclude=source_object)
GLOBAL_CMD_TABLE.add_command("say", cmd_say)
def cmd_fsay(command):
"""
@fsay - make an object say something
Usage:
@fsay <obj> = <text to say>
Make an object talk to its current location.
"""
source_object = command.source_object
args = command.command_argument
if not args or not "=" in args:
source_object.emit_to("Usage: @fsay <obj> = <text to say>")
return
target, speech = [arg.strip() for arg in args.split("=",1)]
# find object
if target in ['here']:
results = [source_object.get_location()]
elif target in ['me','my']:
results = [source_object]
else:
results = Object.objects.global_object_name_search(target)
if not results:
source_object.emit_to("No matches found for '%s'." % target)
return
if len(results) > 1:
string = "There are multiple matches. Please use #dbref to be more specific."
for result in results:
string += "\n %s" % results.get_name(show_dbref=True)
source_object.emit_to(string)
return
target = results[0]
# permission check
if not source_object.controls_other(target):
source_object.emit_to("Cannot pose %s (you don's control it)" % target.get_name())
return
# Feedback for the object doing the talking.
source_object.emit_to("%s says, '%s%s'" % (target.get_name(show_dbref=False),
speech,
ANSITable.ansi['normal']))
# Build the string to emit to neighbors.
emit_string = "%s says, '%s'" % (target.get_name(show_dbref=False),
speech)
target.get_location().emit_to_contents(emit_string,
exclude=source_object)
GLOBAL_CMD_TABLE.add_command("@fsay", cmd_fsay)
def cmd_pose(command):
"""
pose - strike a pose
Usage:
pose[/switches] <pose text>
Switches:
/nospace : put no space between your name
and the start of the pose.
Example:
pose is standing by the wall, smiling.
-> others will see:
Tom is standing by the wall, smiling.
Describe an action being taken. The pose text will
automatically begin with your name.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Do what?")
return
pose_string = command.command_argument
if "nospace" in command.command_switches:
# Output without a space between the player name and the emote.
sent_msg = "%s%s" % (source_object.get_name(show_dbref=False),
pose_string)
else:
# No switches, default.
sent_msg = "%s %s" % (source_object.get_name(show_dbref=False),
pose_string)
source_object.get_location().emit_to_contents(sent_msg)
GLOBAL_CMD_TABLE.add_command("pose", cmd_pose)
def cmd_fpose(command):
"""
@fpose - force an object to pose
Usage:
@fpose[/switches] <obj> = <pose text>
Switches:
nospace : put no text between the object's name
and the start of the pose.
Describe an action being taken as performed by obj.
The pose text will automatically begin with the name
of the object.
"""
source_object = command.source_object
args = command.command_argument
if not args or not "=" in args:
source_object.emit_to("Usage: @fpose <obj> = <pose text>")
return
target, pose_string = [arg.strip() for arg in args.split("=",1)]
# find object
if target in ['here']:
results = [source_object.get_location()]
elif target in ['me','my']:
results = [source_object]
else:
results = Object.objects.global_object_name_search(target)
if not results:
source_object.emit_to("No matches found for '%s'." % target)
return
if len(results) > 1:
string = "There are multiple matches. Please use #dbref to be more specific."
for result in results:
string += "\n %s" % results.get_name(show_dbref=True)
source_object.emit_to(string)
return
target = results[0]
# permission check
if not source_object.controls_other(target):
source_object.emit_to("Cannot pose %s (you don's control it)" % target.get_name())
return
if "nospace" in command.command_switches:
# Output without a space between the player name and the emote.
sent_msg = "%s%s" % (target.get_name(show_dbref=False),
pose_string)
else:
# No switches, default.
sent_msg = "%s %s" % (target.get_name(show_dbref=False),
pose_string)
source_object.get_location().emit_to_contents(sent_msg)
GLOBAL_CMD_TABLE.add_command("@fpose", cmd_fpose)
def cmd_group(command):
"""
@group - show your groups
Usage:
@group
This command shows you which user permission groups
you are a member of, if any.
"""
source_object = command.source_object
user = User.objects.get(username=source_object.get_name(show_dbref=False, no_ansi=True))
string = ""
if source_object.is_superuser():
string += "\n This is a SUPERUSER account! Group membership does not matter."
if not user.is_active:
string += "\n ACCOUNT NOT ACTIVE."
for group in user.groups.all():
string += "\n -- %s" % group
for perm in group.permissions.all():
string += "\n --- %s" % perm.name
if not string:
string = "You are not a member of any groups." % source_object.get_name(show_dbref=False)
else:
string = "\nYour (%s's) group memberships: %s" % (source_object.get_name(show_dbref=False), string)
source_object.emit_to(string)
GLOBAL_CMD_TABLE.add_command("@group", cmd_group)
GLOBAL_CMD_TABLE.add_command("@groups", cmd_group, help_category="System")
def cmd_help(command):
"""
help - view help database
Usage:
help[/switches] <topic>
Switch:
apropos - show a list of all topics loosely matching the search criterion
(you can also use the commands 'apropos' or 'suggest' for this).
Examples: help index
help topic
help 345
help/apropos del
Shows the available help on <topic>. Use without <topic> to get the help
index. If more than one topic match your query, you will get a
list of topics to choose between. You can also supply a help entry number
directly if you know it.
"""
source_object = command.source_object
topicstr = command.command_argument
switches = command.command_switches
if not command.command_argument:
#display topic index if just help command is given
topicstr = "index"
if len(topicstr) < 2 and not topicstr.isdigit():
#check valid query
source_object.emit_to("Your search query must be at least two letters long.")
return
# speciel help index names. These entries are dynamically
# created upon request.
if topicstr in ['topic','topics']:
# the full index, affected by permissions
text = helpsystem.viewhelp.index_full(source_object)
text = " \nHELP TOPICS (By Category):\n\r%s" % text
source_object.emit_to(text)
return
elif 'index' in topicstr:
# view the category index
text = helpsystem.viewhelp.index_categories()
text = " \nHELP CATEGORIES (try 'help <category>' or 'help topics'):\n\r\n\r%s" % text
source_object.emit_to(text)
return
if switches and 'apropos' in switches:
# run a loose apropos match
topics = HelpEntry.objects.find_apropos(source_object, topicstr)
if topics:
if len(topics) > 50:
string = "Topics containing string '%s' (first 50):\n" % topicstr
topics = topics[:49]
else:
string = "Topics containing string '%s':\n" % topicstr
string += ", ".join(topic.get_topicname() for topic in topics)
else:
string = "No matches found for %s." % topicstr
source_object.emit_to(string)
return
# not a special help index entry. Do a search for the help entry.
topics = HelpEntry.objects.find_topicmatch(source_object, topicstr)
# display help entry or handle no/multiple matches
string = ""
if not topics:
# no matches.
# try to see if it is matching the name of a category. If so,
# show the topics for this category.
text = helpsystem.viewhelp.index_category(source_object, topicstr)
if text:
# We have category matches, display the index and exit.
string = "\n%s%s%s\n\r\n\r%s" % ("---", " Help topics in category %s: " % \
topicstr.capitalize(), "-"* (30-len(topicstr)), text)
source_object.emit_to(string)
return
# at this point we just give a not-found error and give suggestions.
topics = HelpEntry.objects.find_topicsuggestions(source_object,
topicstr)
if topics:
if len(topics) > 3:
topics = topics[:3]
string += "\n\rMatching similarly named topics (use name or number to refine search):"
for entry in topics:
string += "\n %i.%s" % (entry.id, entry.topicname)
else:
string += "No matching topics found, please refine your search."
elif len(topics) > 1:
# multiple matches found
string += "More than one match found:"
for result in topics:
string += " %3d. %s" % (result.id, result.get_topicname())
else:
# a single match found
topic = topics[0]
header = "--- Help entry for '%s' (%s category) " % (topic.get_topicname(),
topic.get_category())
header = "%s%s" % (header, "-" * (80-len(header)))
string += "\n\r%s\n\r\n\r%s" % (header, topic.get_entrytext_ingame())
# add the 'See also:' footer
topics = HelpEntry.objects.find_topicsuggestions(source_object,
topic.get_topicname())
if topics:
if len(topics) > 5:
topics = topics[:5]
topics = [str(topic.topicname) for topic in topics ]
string += "\n\r\n\r" + " " * helpsystem.viewhelp.indent + \
"See also: " + ", ".join(topics)
source_object.emit_to(string)
GLOBAL_CMD_TABLE.add_command("help", cmd_help)
def cmd_apropos(command):
"""
apropos - show rough help matches
Usage:
apropos <text>
or
suggest <text>
This presents a list of topics very loosely matching your
search text. Use this command when you are searching for
help on a certain concept but don't know any exact
command names. You can also use the normal help command
with the /apropos switch to get the same functionality.
"""
arg = command.command_argument
command.source_object.execute_cmd("help/apropos %s" % arg)
GLOBAL_CMD_TABLE.add_command("apropos", cmd_apropos)
GLOBAL_CMD_TABLE.add_command("suggest", cmd_apropos)

View file

@ -1,211 +0,0 @@
"""
IMC2 user and administrative commands.
"""
from django.conf import settings
from src import comsys
from src.cmdtable import GLOBAL_CMD_TABLE
from src.ansi import parse_ansi
from src.imc2.imc_ansi import IMCANSIParser
from src.imc2 import connection as imc2_conn
from src.imc2.packets import *
from src.imc2.models import IMC2ChannelMapping
from src.imc2.trackers import IMC2_MUDLIST, IMC2_CHANLIST
from src.channels.models import CommChannel
def cmd_imcwhois(command):
"""
imcwhois
Usage:
imcwhois
IMC2 command. Shows a player's inventory.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Get what?")
return
else:
source_object.emit_to("Sending IMC whois request. If you receive no response, no matches were found.")
packet = IMC2PacketWhois(source_object, command.command_argument)
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
GLOBAL_CMD_TABLE.add_command("imcwhois", cmd_imcwhois, help_category="Comms")
def cmd_imcansi(command):
"""
imcansi
Usage:
imcansi <string>
Test IMC ANSI conversion.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("You must provide a string to convert.")
return
else:
retval = parse_ansi(command.command_argument, parser=IMCANSIParser())
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("imcansi", cmd_imcansi, help_category="Comms")
def cmd_imcicerefresh(command):
"""
imcicerefresh
Usage:
imcicerefresh
IMC2: Semds an ice-refresh packet.
"""
source_object = command.source_object
packet = IMC2PacketIceRefresh()
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
source_object.emit_to("Sent")
GLOBAL_CMD_TABLE.add_command("imcicerefresh", cmd_imcicerefresh, help_category="Comms")
def cmd_imcchanlist(command):
"""
imcchanlist
Usage:
imcchanlist
Shows the list of cached channels from the IMC2 Channel list.
"""
source_object = command.source_object
retval = 'Channels on %s\n\r' % imc2_conn.IMC2_PROTOCOL_INSTANCE.network_name
retval += ' Full Name Name Owner Perm Policy\n\r'
retval += ' --------- ---- ----- ---- ------\n\r'
for channel in IMC2_CHANLIST.get_channel_list():
retval += ' %-18s %-10s %-15s %-7s %s\n\r' % (channel.name,
channel.localname,
channel.owner,
channel.level,
channel.policy)
retval += '%s channels found.' % len(IMC2_CHANLIST.chan_list)
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("imcchanlist", cmd_imcchanlist, help_category="Comms")
def cmd_imclist(command):
"""
imclist
Usage:
imclist
Shows the list of cached games from the IMC2 Mud list.
"""
source_object = command.source_object
retval = 'Active MUDs on %s\n\r' % imc2_conn.IMC2_PROTOCOL_INSTANCE.network_name
for mudinfo in IMC2_MUDLIST.get_mud_list():
mudline = ' %-20s %s' % (mudinfo.name, mudinfo.versionid)
retval += '%s\n\r' % mudline[:78]
retval += '%s active MUDs found.' % len(IMC2_MUDLIST.mud_list)
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("imclist", cmd_imclist, help_category="Comms")
def cmd_imcstatus(command):
"""
imcstatus
Usage:
imcstatus
Shows some status information for your IMC2 connection.
"""
source_object = command.source_object
# This manages our game's plugged in services.
collection = command.session.server.service_collection
# Retrieve the IMC2 service.
service = collection.getServiceNamed('IMC2')
if service.running == 1:
status_string = 'Running'
else:
status_string = 'Inactive'
# Build the output to emit to the player.
retval = '-' * 50
retval += '\n\r'
retval += 'IMC Status\n\r'
retval += ' * MUD Name: %s\n\r' % (settings.IMC2_MUDNAME)
retval += ' * Status: %s\n\r' % (status_string)
retval += ' * Debugging Mode: %s\n\r' % (settings.IMC2_DEBUG)
retval += ' * IMC Network Address: %s\n\r' % (settings.IMC2_SERVER_ADDRESS)
retval += ' * IMC Network Port: %s\n\r' % (settings.IMC2_SERVER_PORT)
retval += '-' * 50
source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("imcstatus", cmd_imcstatus,
priv_tuple=('imc2.admin_imc_channels',), help_category="Comms")
def cmd_IMC2chan(command):
"""
@imc2chan
Usage:
@imc2chan <IMCServer> : <IMCchannel> <channel>
Links an IMC channel to an existing evennia
channel. You can link as many existing
evennia channels as you like to the
IMC channel this way. Running the command with an
existing mapping will re-map the channels.
Use 'imcchanlist' to get a list of IMC channels and
servers. Note that both are case sensitive.
"""
source_object = command.source_object
if not settings.IMC2_ENABLED:
s = """IMC is not enabled. You need to activate it in game/settings.py."""
source_object.emit_to(s)
return
args = command.command_argument
if not args or len(args.split()) != 2 :
source_object.emit_to("Usage: @imc2chan IMCServer:IMCchannel channel")
return
#identify the server-channel pair
imcdata, channel = args.split()
if not ":" in imcdata:
source_object.emit_to("You need to supply an IMC Server:Channel pair.")
return
imclist = IMC2_CHANLIST.get_channel_list()
imc_channels = filter(lambda c: c.name == imcdata, imclist)
if not imc_channels:
source_object.emit_to("IMC server and channel '%s' not found." % imcdata)
return
else:
imc_server_name, imc_channel_name = imcdata.split(":")
#find evennia channel
try:
chanobj = comsys.get_cobj_from_name(channel)
except CommChannel.DoesNotExist:
source_object.emit_to("Local channel '%s' not found (use real name, not alias)." % channel)
return
#create the mapping.
outstring = ""
mapping = IMC2ChannelMapping.objects.filter(channel__name=channel)
if mapping:
mapping = mapping[0]
outstring = "Replacing %s. New " % mapping
else:
mapping = IMC2ChannelMapping()
mapping.imc2_server_name = imc_server_name
mapping.imc2_channel_name = imc_channel_name
mapping.channel = chanobj
mapping.save()
outstring += "Mapping set: %s." % mapping
source_object.emit_to(outstring)
GLOBAL_CMD_TABLE.add_command("@imc2chan",cmd_IMC2chan,
priv_tuple=("imc2.admin_imc_channels",), help_category="Comms")

View file

@ -1,223 +0,0 @@
"""
Commands that are generally staff-oriented that show information regarding
the server instance.
"""
import os
import time
from src.util import functions_general
if not functions_general.host_os_is('nt'):
# Don't import the resource module if the host OS is Windows.
import resource
import django
from django.conf import settings
from src.objects.models import Object
from src import scheduler
from src import defines_global
from src import flags
from src.cmdtable import GLOBAL_CMD_TABLE
from src.cache import cache
from src import gametime
def cmd_version(command):
"""
@version - game version
Usage:
@version
Display the game version info
"""
retval = "-"*50 +"\n\r"
retval += " Evennia %s\n\r" % (defines_global.EVENNIA_VERSION,)
retval += " Django %s\n\r" % (django.get_version())
retval += "-"*50
command.source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("@version", cmd_version, help_category="Admin"),
def cmd_time(command):
"""
@time
Usage:
@time
Server local time.
"""
gtime = gametime.time()
gtime_h = functions_general.time_format(gtime, style=2)
ictime = gtime * settings.TIME_FACTOR
ictime_h = functions_general.time_format(ictime, style=2)
uptime = time.time() - command.session.server.start_time
uptime_h = functions_general.time_format(uptime, style=2)
synctime = gametime.time_last_sync()
synctime_h = functions_general.time_format(synctime, style=2)
ltime = time.strftime('%a %b %d %H:%M:%S %Y (%Z)', time.localtime())
string = " Real-world times:"
string += "\n -- Main time counter: %s (%i s)." % (gtime_h, gtime)
string += "\n -- Time since last reboot: %s (%i s). " % (uptime_h, uptime)
string += "\n -- Time since cache was last saved: %s (%i s)." % (synctime_h,
synctime)
string += "\n -- Current server time: %s" % ltime
string += "\n In-game time (time factor %s):" % settings.TIME_FACTOR
string += "\n -- Time passed: %s" % ictime_h
command.source_object.emit_to(string)
GLOBAL_CMD_TABLE.add_command("@time", cmd_time, priv_tuple=("genperms.game_info",),
help_category="Admin")
def cmd_uptime(command):
"""
@uptime
Usage:
@uptime
Server uptime and stats.
"""
source_object = command.source_object
server = command.session.server
start_delta = time.time() - server.start_time
string = " Server time info:"
string += "\n -- Current server time : %s" % \
(time.strftime('%a %b %d %H:%M %Y (%Z)', time.localtime(),))
string += "\n -- Server start time : %s" % \
(time.strftime('%a %b %d %H:%M %Y',
time.localtime(server.start_time),))
string += "\n -- Server uptime : %s" % \
(functions_general.time_format(start_delta, style=2))
if not functions_general.host_os_is('nt'):
# os.getloadavg() is not available on Windows.
loadavg = os.getloadavg()
string += "\n -- Server load (1 min) : %.2f" % loadavg[0]
source_object.emit_to(string)
GLOBAL_CMD_TABLE.add_command("@uptime",
cmd_uptime,
priv_tuple=("genperms.game_info",),
help_category="Admin")
def cmd_list(command):
"""
@list - list info
Usage:
@list commands | flags | process
Shows game related information depending
on which argument is given.
"""
server = command.session.server
source_object = command.source_object
msg_invalid = "Usage @list commands|flags|process"
if not command.command_argument:
source_object.emit_to(msg_invalid)
elif command.command_argument == "commands":
clist = GLOBAL_CMD_TABLE.ctable.keys()
clist.sort()
source_object.emit_to('Commands: '+ ' '.join(clist))
elif command.command_argument == "process":
if not functions_general.host_os_is('nt'):
loadvg = os.getloadavg()
psize = resource.getpagesize()
rusage = resource.getrusage(resource.RUSAGE_SELF)
source_object.emit_to("Process ID: %10d %10d bytes per page" %
(os.getpid(), psize))
source_object.emit_to("Time used: %10d user %10d sys" %
(rusage[0],rusage[1]))
source_object.emit_to("Integral mem:%10d shared %10d private%10d stack" %
(rusage[3], rusage[4], rusage[5]))
source_object.emit_to("Max res mem: %10d pages %10d bytes" %
(rusage[2],rusage[2] * psize))
source_object.emit_to("Page faults: %10d hard %10d soft %10d swapouts" %
(rusage[7], rusage[6], rusage[8]))
source_object.emit_to("Disk I/O: %10d reads %10d writes" %
(rusage[9], rusage[10]))
source_object.emit_to("Network I/O: %10d in %10d out" %
(rusage[12], rusage[11]))
source_object.emit_to("Context swi: %10d vol %10d forced %10d sigs" %
(rusage[14], rusage[15], rusage[13]))
else:
source_object.emit_to("Feature not available on Windows.")
return
elif command.command_argument == "flags":
source_object.emit_to("Flags: "+" ".join(flags.SERVER_FLAGS))
else:
source_object.emit_to(msg_invalid)
GLOBAL_CMD_TABLE.add_command("@list", cmd_list,priv_tuple=("genperms.game_info",), help_category="Admin")
def cmd_ps(command):
"""
@ps - list processes
Usage
@ps
Shows the process/event table.
"""
source_object = command.source_object
source_object.emit_to("Processes Scheduled:\n-- PID [time/interval] [repeats] description --")
for event in scheduler.SCHEDULE:
repeats = "[inf] "
if event.repeats != None:
repeats = "[%i] " % event.repeats
source_object.emit_to(" %i [%d/%d] %s%s" % (
event.pid,
event.get_nextfire(),
event.interval,
repeats,
event.description))
source_object.emit_to("Totals: %d interval events" % (len(scheduler.SCHEDULE),))
GLOBAL_CMD_TABLE.add_command("@ps", cmd_ps,
priv_tuple=("genperms.process_control",), help_category="Admin")
def cmd_stats(command):
"""
@stats - show object stats
Usage:
@stats
Example:
@stats
->
4012 objects = 144 rooms, 212 exits, 613 things, 1878 players. (1165 garbage)
Shows stats about the database.
"""
stats_dict = Object.objects.object_totals()
command.source_object.emit_to(
"%d objects = %d rooms, %d exits, %d things, %d players. (%d garbage)" %
(stats_dict["objects"],
stats_dict["rooms"],
stats_dict["exits"],
stats_dict["things"],
stats_dict["players"],
stats_dict["garbage"]))
GLOBAL_CMD_TABLE.add_command("@stats", cmd_stats, priv_tuple=("genperms.game_info",), help_category="Admin"),
def cmd_showcache(command):
"""
@showcache - show stats about the cache system
Usage:
@showcache
Study the current contents and size of the cache.
"""
source_object = command.source_object
str_cache, str_pcache = cache.show()
ncache = len(str_cache.split(','))
npcache = len(str_pcache.split(','))
string = ""
if str_cache:
string += "\nVolatile cache (%i):\n %s" % (ncache, str_cache)
if str_pcache:
string += "\nPersistent cache (%i):\n %s" % (npcache, str_pcache)
source_object.emit_to(string)
GLOBAL_CMD_TABLE.add_command("@showcache", cmd_showcache, priv_tuple=("genperms.game_info",), help_category="Admin"),

View file

@ -1,130 +0,0 @@
"""
IRC-related commands
"""
from twisted.application import internet
from django.conf import settings
from src.irc.connection import IRC_CHANNELS
from src.irc.models import IRCChannelMapping
from src import comsys
from src.cmdtable import GLOBAL_CMD_TABLE
from src.channels.models import CommChannel
def cmd_IRC2chan(command):
"""
@irc2chan - link irc to ingame channel
Usage:
@irc2chan <#IRCchannel> <local channel>
Links an IRC channel (including #) to an existing
evennia channel. You can link as many existing
evennia channels as you like to the
IRC channel this way. Running the command with an
existing mapping will re-map the channels.
"""
source_object = command.source_object
if not settings.IRC_ENABLED:
s = """IRC is not enabled. You need to activate it in game/settings.py."""
source_object.emit_to(s)
return
args = command.command_argument
if not args or len(args.split()) != 2 :
source_object.emit_to("Usage: @irc2chan IRCchannel channel")
return
irc_channel, channel = args.split()
if irc_channel not in [o.factory.channel for o in IRC_CHANNELS]:
source_object.emit_to("IRC channel '%s' not found." % irc_channel)
return
try:
chanobj = comsys.get_cobj_from_name(channel)
except CommChannel.DoesNotExist:
source_object.emit_to("Local channel '%s' not found (use real name, not alias)." % channel)
return
#create the mapping.
outstring = ""
mapping = IRCChannelMapping.objects.filter(channel__name=channel)
if mapping:
mapping = mapping[0]
outstring = "Replacing %s. New " % mapping
else:
mapping = IRCChannelMapping()
mapping.irc_server_name = settings.IRC_NETWORK
mapping.irc_channel_name = irc_channel
mapping.channel = chanobj
mapping.save()
outstring += "Mapping set: %s." % mapping
source_object.emit_to(outstring)
GLOBAL_CMD_TABLE.add_command("@irc2chan",cmd_IRC2chan,
priv_tuple=("irc.admin_irc_channels",),
help_category="Comms")
def cmd_IRCjoin(command):
"""
@ircjoin - join a new irc channel
Usage:
@ircjoin <#IRCchannel>
Attempts to connect a bot to a new IRC channel (don't forget that
IRC channels begin with a #).
The bot uses the connection details defined in the main settings.
Observe that channels added using this command does not survive a reboot.
"""
source_object = command.source_object
arg = command.command_argument
if not arg:
source_object.emit_to("Usage: @ircjoin #irc_channel")
return
channel = arg.strip()
if channel[0] != "#": channel = "#%s" % channel
if not settings.IRC_ENABLED:
source_object.emit_to("IRC services are not active. You need to turn them on in preferences.")
return
#direct creation of bot (do not add to services)
from src.irc.connection import connect_to_IRC
connect_to_IRC(settings.IRC_NETWORK,
settings.IRC_PORT,
channel, settings.IRC_NICKNAME)
# ---below should be checked so as to add subequent IRC bots to Services.
# it adds just fine, but the bot does not connect. /Griatch
# from src.irc.connection import IRC_BotFactory
# from src.server import mud_service
# irc = internet.TCPClient(settings.IRC_NETWORK,
# settings.IRC_PORT,
# IRC_BotFactory(channel,
# settings.IRC_NETWORK,
# settings.IRC_NICKNAME))
# irc.setName("%s:%s" % ("IRC",channel))
# irc.setServiceParent(mud_service.service_collection)
GLOBAL_CMD_TABLE.add_command("@ircjoin",cmd_IRCjoin,
priv_tuple=("irc.admin_irc_channels",),
help_category="Comms")
def cmd_IRCchanlist(command):
"""
ircchanlist
Usage:
ircchanlist
Lists all externally available IRC channels.
"""
source_object = command.source_object
s = "Available IRC channels:"
for c in IRC_CHANNELS:
s += "\n %s \t(nick '%s') on %s" % (c.factory.channel,
c.factory.nickname,
c.factory.network,)
source_object.emit_to(s)
GLOBAL_CMD_TABLE.add_command("ircchanlist", cmd_IRCchanlist,
help_category="Comms")

File diff suppressed because it is too large Load diff

View file

@ -1,136 +0,0 @@
"""
Paging command and support functions.
"""
from src.objects.models import Object
from src.cmdtable import GLOBAL_CMD_TABLE
def get_last_paged_objects(source_object):
"""
Returns a list of objects of the user's last paged list, or None if invalid
or non-existant.
"""
last_paged_dbrefs = source_object.get_attribute_value("LASTPAGED", None)
if last_paged_dbrefs:
last_paged_objects = list()
try:
last_paged_dbref_list = [
x.strip() for x in last_paged_dbrefs.split(',')
]
for dbref in last_paged_dbref_list:
if not Object.objects.is_dbref(dbref):
raise ValueError
last_paged_object = Object.objects.dbref_search(dbref)
if last_paged_object is not None:
last_paged_objects.append(last_paged_object)
return last_paged_objects
except ValueError:
# Remove the invalid LASTPAGED attribute
source_object.clear_attribute("LASTPAGED")
return None
def cmd_page(command):
"""
page - send private message
Usage:
page [<user> = <message>]
Send a message to target user (if online). If no
argument is given, you will instead see who was the last
person you paged to.
"""
source_object = command.source_object
# Get the last paged person(s)
last_paged_objects = get_last_paged_objects(source_object)
# If they don't give a target, or any data to send to the target
# then tell them who they last paged if they paged someone, if not
# tell them they haven't paged anyone.
if not command.command_argument:
if last_paged_objects:
source_object.emit_to("You last paged: %s." % (
', '.join([x.name for x in last_paged_objects])))
return
else:
# No valid LASTPAGE values
source_object.emit_to("You have not paged anyone.")
return
# Stores a list of targets
targets = []
# Build a list of targets
# If there are no targets, then set the targets to the last person they
# paged.
cmd_targets = command.get_arg_targets()
if cmd_targets is None:
targets = last_paged_objects
# No valid last paged players found, error out.
if not targets:
source_object.emit_to("Page who?")
return
else:
# For each of the targets listed, grab their objects and append
# it to the targets list if valid.
for target in cmd_targets:
matched_object = Object.objects.player_name_search(target)
if matched_object:
# Found a good object, store it
targets.append(matched_object)
else:
# Search returned None
source_object.emit_to("Player '%s' can not be found." % (
target))
# Depending on the argument provided, either send the entire thing as
# a message or break off the point after the equal sign.
if command.arg_has_target():
# User specified targets, get the stuff after the equal sign.
message = command.get_arg_target_value()
else:
# No targets specified with equal sign, use lastpaged and the user's
# arguments as the message to send.
message = command.command_argument
sender_name = source_object.get_name(show_dbref=False)
# Build our messages
target_message = "%s pages: %s"
sender_message = "You paged %s with '%s'."
# Handle paged emotes
if message.startswith(':'):
message = message[1:]
target_message = "From afar, %s %s"
sender_message = "Long distance to %s: %s %s"
# Handle paged emotes without spaces
elif message.startswith(';'):
message = message[1:]
target_message = "From afar, %s%s"
sender_message = "Long distance to %s: %s%s"
# We build a list of target_names for the sender_message later
target_names = []
for target in targets:
# Check to make sure they're connected, or a player
if target.is_connected_plr():
target.emit_to(target_message % (sender_name, message))
target_names.append(target.get_name(show_dbref=False))
else:
source_object.emit_to("Player %s does not exist or is not online." % (
target.get_name(show_dbref=False)))
# Now send a confirmation to the person doing the paging.
if len(target_names) > 0:
target_names_string = ', '.join(target_names)
try:
source_object.emit_to(sender_message % (target_names_string,
sender_name, message))
except TypeError:
source_object.emit_to(sender_message % (target_names_string,
message))
# Now set the LASTPAGED attribute
source_object.set_attribute("LASTPAGED", ','.join(
["#%d" % (x.id) for x in targets]))
GLOBAL_CMD_TABLE.add_command("page", cmd_page, priv_tuple=('channels.page',), help_category="Comms")

View file

@ -1,97 +0,0 @@
"""
Contains commands for managing script parents.
"""
from src import scripthandler
from src import defines_global
from src.cmdtable import GLOBAL_CMD_TABLE
def cmd_scriptcache(command):
"""
@scriptcache
Usage
@scriptcache
Shows the contents of the script cache.
"""
cache_dict = scripthandler.CACHED_SCRIPTS
retval = "Currently Cached Script Parents\n"
retval += "-" * 78
for script in cache_dict.keys():
retval += "\n " + script
retval += "\n" + "-" * 78 + "\n"
retval += "%d cached parents" % len(cache_dict)
command.source_object.emit_to(retval)
GLOBAL_CMD_TABLE.add_command("@scriptcache", cmd_scriptcache,
priv_tuple=("genperms.builder",), help_category="Admin")
def cmd_parent(command):
"""
@parent - set script parent
Usage:
@parent <object> = <parent>
Example:
@parent button = examples.red_button
Sets an object's script parent. The parent must be identified
by its location using dot-notation pointing to the script
parent module.
"""
source_object = command.source_object
if not command.command_argument:
source_object.emit_to("Usage: @parent <object> [=<parent]")
return
eq_args = command.command_argument.split('=', 1)
target_name = eq_args[0]
target_obj = source_object.search_for_object(target_name)
if not target_obj:
return
if len(eq_args) > 1:
#if we gave a command of the form @parent obj=something we want to
#somehow affect the parent.
parent_name = eq_args[1]
#check permissions
if not source_object.controls_other(target_obj):
source_object.emit_to(defines_global.NOCONTROL_MSG)
return
# Clear parent if command was @parent obj= or obj=none
if not parent_name or parent_name.lower() == "none":
target_obj.set_script_parent(None)
target_obj.scriptlink.at_object_creation()
new_parent = target_obj.scriptlink()
source_object.emit_to("%s reverted to its default parent (%s)." %
(target_obj, new_parent))
return
# If we reach this point, attempt to change parent.
former_parent = target_obj.get_scriptlink()
if target_obj.set_script_parent(parent_name):
#new script path added; initialize the parent
target_obj.scriptlink.at_object_creation()
s = "%s's parent is now %s (instead of %s).\n\r"
s += "Note that the new parent type could have overwritten "
s += "same-named attributes on the existing object."
source_object.emit_to(s)
else:
source_object.emit_to("'%s' is not a valid parent path." % parent_name)
else:
# We haven't provided a target; list the current parent
current_parent = target_obj.get_scriptlink()
source_object.emit_to("Current parent of %s is %s." %
(target_obj,current_parent))
GLOBAL_CMD_TABLE.add_command("@parent", cmd_parent,
priv_tuple=("genperms.builder",), help_category="Building" )

View file

@ -1,879 +0,0 @@
"""
This file contains commands that require special permissions to use. These
are generally @-prefixed commands, but there are exceptions.
"""
from django.contrib.auth.models import Permission, Group, User
from django.conf import settings
from src.objects.models import Object
from src import session_mgr
from src import comsys
from src.scripthandler import rebuild_cache
from src.cmdtable import GLOBAL_CMD_TABLE
from src.helpsys.models import HelpEntry
from src.helpsys import helpsystem
from src.config.models import CommandAlias
from src.config import edit_aliases
from src import cache
from src import scheduler
def cmd_reload(command):
"""
@reload - reload game subsystems
Usage:
@reload/switches
Switches:
aliases - alias definitions
commands - the command modules
scripts, parents - the script parent modules
all - reload all of the above
cache - flush the volatile cache (warning, this
might cause unexpected results if your
script parents use the cache a lot)
reset - flush cache then reload all
Reloads all the identified subsystems. Flushing the cache is
generally not needed outside the main game development.
"""
source_object = command.source_object
switches = command.command_switches
if not switches or switches[0] not in ['all','aliases','alias',
'commands','command',
'scripts','parents',
'cache','reset']:
source_object.emit_to("Usage: @reload/<aliases|scripts|commands|all>")
return
switch = switches[0]
sname = source_object.get_name(show_dbref=False)
if switch in ["reset", "cache"]:
# Clear the volatile cache
cache.flush()
comsys.cemit_mudinfo("%s flushed the non-persistent cache." % sname)
if switch in ["reset","all","aliases","alias"]:
# Reload Aliases
command.session.server.reload_aliases(source_object=source_object)
comsys.cemit_mudinfo("%s reloaded Aliases." % sname)
if switch in ["reset","all","scripts","parents"]:
# Reload Script parents
rebuild_cache()
comsys.cemit_mudinfo("%s reloaded Script parents." % sname)
if switch in ["reset","all","commands","command"]:
# Reload command objects.
comsys.cemit_mudinfo("%s is reloading Command modules ..." % sname)
command.session.server.reload(source_object=command.source_object)
comsys.cemit_mudinfo("... all Command modules were reloaded.")
GLOBAL_CMD_TABLE.add_command("@reload", cmd_reload,
priv_tuple=("genperms.process_control",), help_category="Admin")
GLOBAL_CMD_TABLE.add_command("@restart", cmd_reload,
priv_tuple=("genperms.process_control",), help_category="Admin")
def cmd_boot(command):
"""
@boot
Usage
@boot[/switches] <player obj> [: reason]
Switches:
quiet - Silently boot without informing player
port - boot by port number instead of name or dbref
Boot a player object from the server. If a reason is
supplied it will be echoed to the user unless /quiet is set.
"""
source_object = command.source_object
switch_quiet = False
switch_port = False
if "quiet" in command.command_switches:
# Don't tell the player they've been disconnected, silently boot them.
switch_quiet = True
if "port" in command.command_switches:
# Boot by port number instead of name or dbref.
switch_port = True
if not command.command_argument:
source_object.emit_to("Usage: @boot[/switches] <player> [:reason]")
return
else:
arg = command.command_argument
reason = ""
if ':' in arg:
arg, reason = [a.strip() for a in arg.split(':',1)]
boot_list = []
if switch_port:
# Boot a particular port.
sessions = session_mgr.get_session_list(True)
for sess in sessions:
# Find the session with the matching port number.
if sess.getClientAddress()[1] == int(arg):
boot_list.append(sess)
# Match found, kill the loop and continue with booting.
break
else:
# Grab the objects that match
objs = Object.objects.local_and_global_search(source_object, arg)
if not objs:
source_object.emit_to("No name or dbref match found for booting.")
return
if not objs[0].is_player():
source_object.emit_to("You can only boot players.")
return
if not source_object.controls_other(objs[0]):
if objs[0].is_superuser():
source_object.emit_to("You cannot boot a Wizard.")
return
else:
source_object.emit_to("You do not have permission to boot that player.")
return
if objs[0].is_connected_plr():
matches = session_mgr.sessions_from_object(objs[0])
for match in matches:
boot_list.append(match)
else:
source_object.emit_to("That player is not connected.")
return
if not boot_list:
source_object.emit_to("No matches found.")
return
# Carry out the booting of the sessions in the boot list.
for boot in boot_list:
if not switch_quiet:
msg = "You have been disconnected by %s." % (source_object.name)
if reason:
msg += "\n Reason given:\n '%s'" % reason
boot.msg(msg)
boot.disconnectClient()
session_mgr.remove_session(boot)
return
GLOBAL_CMD_TABLE.add_command("@boot", cmd_boot,
priv_tuple=("genperms.manage_players",),
help_category="Admin")
def cmd_delplayer(command):
"""
delplayer - delete player from server
Usage:
@delplayer <name> [: reason]
Completely deletes a user from the server database,
making their nick and e-mail again available.
"""
source_object = command.source_object
arg = command.command_argument
if not arg:
source_object.emit_to("Usage: @delplayer <player name or #id>")
return
reason = ""
if ':' in arg:
arg, reason = [a.strip() for a in arg.split(':',1)]
objs = Object.objects.local_and_global_search(source_object, arg)
if not objs:
source_object.emit_to("No player object matches found for '%s'." % arg)
return
pobj = objs[0]
if not source_object.controls_other(pobj):
if pobj.is_superuser():
source_object.emit_to("You cannot delete a Superuser.")
return
else:
source_object.emit_to("You do not have permission to delete that player.")
return
# boot the player then delete
source_object.emit_to("Booting and informing player if currently online ...")
name = pobj.get_name()
msg = "\nYour account '%s' is being *permanently* deleted.\n" % name
if reason:
msg += " Reason given:\n '%s'" % reason
pobj.emit_to(msg)
source_object.execute_cmd("@boot %s" % arg)
pobj.delete()
source_object.emit_to("Player %s was successfully deleted." % name)
GLOBAL_CMD_TABLE.add_command("@delplayer", cmd_delplayer,
priv_tuple=("genperms.manage_players",),
help_category="Admin")
def cmd_newpassword(command):
"""
@newpassword
Usage:
@newpassword <user obj> = <new password>
Set a player's password.
"""
source_object = command.source_object
eq_args = command.command_argument.split('=', 1)
searchstring = eq_args[0]
newpass = eq_args[1]
if not command.command_argument or len(searchstring) == 0:
source_object.emit_to("What player's password do you want to change?")
return
if len(newpass) == 0:
source_object.emit_to("You must supply a new password.")
return
target_obj = source_object.search_for_object(searchstring)
# Use search_for_object to handle duplicate/nonexistant results.
if not target_obj:
return
if not target_obj.is_player():
source_object.emit_to("You can only change passwords on players.")
elif not source_object.controls_other(target_obj):
source_object.emit_to("You do not control %s." % (target_obj.get_name(),))
else:
uaccount = target_obj.get_user_account()
if len(newpass) == 0:
uaccount.set_password()
else:
uaccount.set_password(newpass)
uaccount.save()
source_object.emit_to("%s - PASSWORD set." % (target_obj.get_name(),))
target_obj.emit_to("%s has changed your password." %
(source_object.get_name(show_dbref=False),))
GLOBAL_CMD_TABLE.add_command("@newpassword", cmd_newpassword,
priv_tuple=("genperms.manage_players",),
help_category="Admin")
def cmd_home(command):
"""
home
Usage:
home
Teleport the player to their home.
"""
pobject = command.source_object
if pobject.home == None:
pobject.emit_to("You have no home set, @link yourself to somewhere.")
else:
pobject.emit_to("There's no place like home...")
pobject.move_to(pobject.get_home())
GLOBAL_CMD_TABLE.add_command("home", cmd_home,
priv_tuple=("genperms.tel_anywhere",))
def cmd_service(command):
"""
@service - manage services
Usage:
@service[/switch] <service>
Switches:
start - activates a service
stop - stops a service
list - shows all available services
Service management system. Allows for the listing,
starting, and stopping of services.
"""
source_object = command.source_object
switches = command.command_switches
if not switches or switches[0] not in ["list","start","stop"]:
source_object.emit_to("Usage: @servive/<start|stop|list> [service]")
return
switch = switches[0].strip()
sname = source_object.get_name(show_dbref=False)
if switch == "list":
#Just display the list of installed services and their status, then exit.
s = "-" * 40
s += "\nService Listing"
for service in command.session.server.service_collection.services:
# running is either 1 or 0, 1 meaning the service is running.
if service.running == 1:
status = 'Running'
else:
status = 'Inactive'
s += '\n * %s (%s)' % (service.name, status)
s += "\n" + "-" * 40
source_object.emit_to(s)
return
if switch in ["stop", "start"]:
# This stuff is common to both start and stop switches.
collection = command.session.server.service_collection
try:
service = collection.getServiceNamed(command.command_argument)
except:
source_object.emit_to('Invalid service name. This command is case-sensitive. See @service/list.')
return
if switch == "stop":
"""
Stopping a service gracefully closes it and disconnects any connections
(if applicable).
"""
if service.running == 0:
source_object.emit_to('That service is not currently running.')
return
# We don't want killing main Evennia TCPServer services here. If
# wanting to kill a listening port, one needs to do it through
# settings.py and a restart.
if service.name[:7] == 'Evennia':
s = "You can not stop Evennia TCPServer services this way."
s += "\nTo e.g. remove a listening port, change settings file and restart."
source_object.emit_to(s)
return
comsys.cemit_mudinfo("%s is *Stopping* the service '%s'." % (sname, service.name))
service.stopService()
return
if switch == "start":
"""
Starts a service.
"""
if service.running == 1:
source_object.emit_to('That service is already running.')
return
comsys.cemit_mudinfo("%s is *Starting* the service '%s'." % (sname,service.name))
service.startService()
return
GLOBAL_CMD_TABLE.add_command("@service", cmd_service,
priv_tuple=("genperms.process_control",),
help_category="Admin")
def cmd_shutdown(command):
"""
@shutdown
Usage:
@shutdown
Shut the game server down gracefully.
"""
command.source_object.emit_to('Shutting down...')
print 'Server shutdown by %s' % (command.source_object.get_name(show_dbref=False),)
command.session.server.shutdown()
GLOBAL_CMD_TABLE.add_command("@shutdown", cmd_shutdown,
priv_tuple=("genperms.process_control",),
help_category="Admin")
# permission administration
# Django automatically creates a host of permissions that we don't want to
# mess with, but which are not very useful from inside the game. While these
# permissions are ok to use, we only show the permissions that we have defined
# in our settings file in order to give better control.
APPS_NOSHOW = ("news","admin","auth","config","contentypes",
"flatpages","news","sessions","sites")
SETTINGS_PERM_NAMES = []
for apps in settings.PERM_ALL_DEFAULTS + settings.PERM_ALL_CUSTOM:
for permtuples in apps:
SETTINGS_PERM_NAMES.append(permtuples[1])
def cmd_setperm(command):
"""
@setperm - set permissions
Usage:
@setperm[/switch] [<user>] = [<permission>]
Switches:
add : add a permission from <user>
del : delete a permission from <user>
list : list all permissions, or those set on <user>
This command sets/clears individual permission bits on a user.
Use /list without any arguments to see all available permissions or those
defined on the <user> argument.
"""
source_object = command.source_object
args = command.command_argument
switches = command.command_switches
if not args:
if "list" not in switches:
source_object.emit_to("Usage: @setperm[/switch] [user] = [permission]")
return
else:
#just print all available permissions
s = "\n---Permission name %s ---Description" % (24 * " ")
permlist = [perm for perm in Permission.objects.all() if perm.content_type.app_label not in APPS_NOSHOW and
perm.name in SETTINGS_PERM_NAMES]
for p in permlist:
app = p.content_type.app_label
if app not in APPS_NOSHOW:
s += "\n%s.%s%s\t%s" % (app, p.codename, (35 - len(app) - len(p.codename)) * " ", p.name)
source_object.emit_to(s)
return
#we have command arguments.
arglist = args.split('=',1)
obj_name = arglist[0].strip()
if not obj_name:
source_object.emit_to("Usage: @setperm[/switch] [user] [= permission]")
return
obj = source_object.search_for_object(obj_name)
if not obj:
return
user = obj.get_user_account()
if not user:
return
if len(arglist) == 1:
#if we didn't have any =, we list the permissions set on <object>.
s = ""
if obj.is_superuser():
s += "\n This is a SUPERUSER account! All permissions are automatically set."
if not user.is_active:
s += "\n ACCOUNT NOT ACTIVE."
if obj.is_staff():
s += "\n Member of staff (can enter Admin interface)"
aperms = user.get_all_permissions()
gperms = user.get_group_permissions()
uperms = [perm for perm in aperms if perm not in gperms]
if gperms:
s += "\n Group-inherited Permissions:"
for p in gperms:
s += "\n --- %s" % p
if uperms:
s += "\n Individually granted Permisssions:"
for p in uperms:
s += "\n ---- %s" % p
if not s:
s = "User %s has no permissions." % obj.get_name()
else:
s = "\nPermissions for user %s: %s" % (obj.get_name(),s)
source_object.emit_to(s)
else:
# we supplied an argument on the form obj = perm
perm_string = arglist[1].strip()
try:
app_label, codename = perm_string.split(".",1)
except ValueError:
source_object.emit_to("Permission should be on the form 'application.permission' .")
return
try:
permission = Permission.objects.filter(content_type__app_label=app_label).get(codename=codename)
except Permission.DoesNotExist:
source_object.emit_to("Permission type '%s' is not a valid permission.\nUse @chperm/list for help with valid permission strings." % perm_string)
return
if not switches:
source_object.emit_to("You must supply a switch /add or /del.")
return
if "add" in switches:
#add the permission to this user
if user.is_superuser:
source_object.emit_to("As a superuser you always have all permissions.")
return
if user.has_perm(perm_string):
source_object.emit_to("User already has this permission.")
return
user.user_permissions.add(permission)
user.save();obj.save()
source_object.emit_to("%s gained the permission '%s'." % (obj.get_name(), permission.name))
obj.emit_to("%s gave you the permission '%s'." % (source_object.get_name(show_dbref=False,no_ansi=True),
permission.name))
if "del" in switches:
#delete the permission from this user
if user.is_superuser:
source_object.emit_to("As a superuser you always have all permissions.")
return
if not user.has_perm(perm_string):
source_object.emit_to("User is already lacking this permission.")
return
user.user_permissions.remove(permission)
user.save();obj.save()
source_object.emit_to("%s lost the permission '%s'." % (obj.get_name(), permission.name))
obj.emit_to("%s removed your permission '%s'." % (source_object.get_name(show_dbref=False,no_ansi=True),
permission.name))
GLOBAL_CMD_TABLE.add_command("@setperm", cmd_setperm,
priv_tuple=("auth.change_permission",
"genperms.admin_perm"),
help_category="Admin")
def cmd_setgroup(command):
"""
@setgroup - manage group memberships
Usage:
@setgroup[/switch] [<user>] [= <group>]
Switches:
add - add user to a group
del - remove user from a group
list - list all groups a user is part of, or list all available groups if no user is given
Changes and views the group membership of a user.
"""
source_object = command.source_object
args = command.command_argument
switches = command.command_switches
if not args:
if "list" not in switches:
source_object.emit_to("Usage: @setgroup[/switch] [user] [= permission]")
return
else:
#just print all available permissions
s = "\n---Group name (and grouped permissions):"
for g in Group.objects.all():
s += "\n %s" % g.name
for p in g.permissions.all():
app = p.content_type.app_label
if app not in APPS_NOSHOW:
s += "\n --- %s.%s%s\t%s" % (app, p.codename,
(35 - len(app) - len(p.codename)) * " ", p.name)
source_object.emit_to(s)
return
#we have command arguments.
arglist = args.split('=',1)
obj_name = arglist[0].strip()
if not obj_name:
source_object.emit_to("Usage: @setgroup[/switch] [user] = [permission]")
return
obj = source_object.search_for_object(obj_name)
if not obj:
return
if not obj.is_player():
source_object.emit_to("Only players may be members of permission groups.")
return
user = obj.get_user_account()
if not user:
return
if len(arglist) == 1:
#if we didn't have any =, we list the groups this user is member of
s = ""
if obj.is_superuser():
s += "\n This is a SUPERUSER account! Group membership does not matter."
if not user.is_active:
s += "\n ACCOUNT NOT ACTIVE."
for g in user.groups.all():
s += "\n --- %s" % g
for p in g.permissions.all():
app = p.content_type.app_label
s += "\n -- %s.%s%s\t%s" % (app, p.codename, (35 - len(app) - len(p.codename)) * " ", p.name)
if not s:
s = "User %s is not a member of any groups." % obj.get_name()
else:
s = "\nGroup memberships for user %s: %s" % (obj.get_name(),s)
source_object.emit_to(s)
else:
# we supplied an argument on the form obj = group
group_string = arglist[1].strip()
try:
group = Group.objects.get(name=group_string)
except Group.DoesNotExist:
source_object.emit_to("Group '%s' is not a valid group. Remember that the name is case-sensitive.\nUse @chperm/list for help with valid group names." % group_string)
return
if not switches:
source_object.emit_to("You must supply a switch /add or /del.")
return
if "add" in switches:
#add the user to this group
if user.is_superuser:
source_object.emit_to("As a superuser, group access does not matter.")
return
if user.groups.filter(name=group_string):
source_object.emit_to("User is already a member of this group.")
return
user.groups.add(group)
user.save(); obj.save()
source_object.emit_to("%s added to group '%s'." % (obj.get_name(), group.name))
obj.emit_to("%s added you to the group '%s'." % (source_object.get_name(show_dbref=False,no_ansi=True),
group.name))
if "del" in switches:
#delete the permission from this user
if user.is_superuser:
source_object.emit_to("As a superuser, group access does not matter.")
return
if not user.groups.filter(name=group_string):
source_object.emit_to("User was not in this group to begin with.")
return
user.groups.remove(group)
user.save(); obj.save()
source_object.emit_to("%s was removed from group '%s'." % (obj.get_name(), group.name))
obj.emit_to("%s removed you from group '%s'." % (source_object.get_name(show_dbref=False,no_ansi=True),
group.name))
GLOBAL_CMD_TABLE.add_command("@setgroup", cmd_setgroup,
priv_tuple=("auth.change_group",
"genperms.admin_group"),
help_category="Admin")
def cmd_sethelp(command):
"""
@sethelp - edit the help database
Usage:
@sethelp[/switches] <topic>[,category][(permissions)][:<text>]
Switches:
add - add or replace a new topic with text.
append - add text to the end of topic.
delete - remove help topic.
force - (used with add) create help topic also if the topic
already exists.
newl - (used with append) add a newline between the old
text and the appended text.
Examples:
@sethelp/add throw : This throws something at ...
@sethelp/add throw, General (genperms.throwing) : This throws ...
@sethelp/add throw : 1st help entry
[[@sethelp_markup]]
@sethelp Help markup
The <text> entry in @sethelp supports markup to automatically divide the help text into
several sub-entries. The beginning of each new entry is marked in the form
[ [Title, category, (privtuple)] ] (with no spaces between the square brackets)
In the markup header, Title is mandatory, the other parts are optional. A new
help entry named Title will be created for each occurence. It is recommended
that the help entries should begin similarly since the system will then identify
them and better handle a list of recommended topics.
"""
source_object = command.source_object
arg = command.command_argument
switches = command.command_switches
if not arg or not switches:
source_object.emit_to("Usage: @sethelp/[add|del|append] <topic>[,category][:<text>]")
return
topicstr = ""
category = ""
text = ""
permtuple = ()
# analyze the argument
arg = arg.split(':', 1)
if len(arg) < 2:
# no : detected; this means we are deleting something.
topicstr = arg[0].strip()
else:
text = arg[1].strip()
# we have 4 possibilities:
# topicstr
# topicstr, category
# topicstr (perm1,perm2,...)
# topicstr, category, (perm1,perm2,...)
arg = arg[0].split('(',1)
if len(arg) > 1:
# we have a perm tuple
arg, permtuple = arg
try:
permtuple = permtuple.strip()[:-1] # cut last ')'
except IndexError:
source_object.emit_to("Malformed permission tuple. %s" % permtuple)
return
permtuple = tuple(permtuple.split(','))
else:
# no perm tuple
arg = arg[0]
arg = arg.split(',', 1)
if len(arg) > 1:
# we have a category
category = arg[1].strip()
topicstr = arg[0].strip()
if 'add' in switches:
# add a new help entry.
if not topicstr or not text:
source_object.emit_to("Usage: @sethelp/add <topic>[,category]:<text>")
return
force_create = ('for' in switches) or ('force' in switches)
topics = helpsystem.edithelp.add_help_manual(source_object, topicstr,
category, text,
permissions=permtuple,
force=force_create)
if not topics:
return
if len(topics) == 1:
string = "The topic already exists. Use /force to overwrite it."
elif len(topics)>1:
string = "The following results are similar to '%s'."
string += " Make sure you are not misspelling, then "
string += "use the /force flag to create a new entry."
string += "\n ".join(topics)
source_object.emit_to(string)
elif 'append' in switches or 'app' in switches:
# add text to the end of a help topic
if not topicstr or not text:
source_object.emit_to("Usage: @sethelp/append <topic>:<text>")
return
# find the topic to append to
topics = HelpEntry.objects.find_topicmatch(source_object, topicstr)
if not topics:
source_object.emit_to("Help topic '%s' not found." % topicstr)
elif len(topics) > 1:
string = "Multiple matches to this topic. Refine your search."
string += "\n ".join(topics)
else:
# we have exactly one match. Extract all info from it,
# append the text and feed it back into the system.
newtext = topics[0].get_entrytext_ingame()
category = topics[0].category
perm_tuple = topics[0].canview
if perm_tuple:
perm_tuple = tuple(perm for perm in perm_tuple.split(','))
newl = "\n"
if 'newl' in switches or 'newline' in switches:
newl = "\n\n"
newtext += "%s%s" % (newl, text)
topics = helpsystem.edithelp.add_help_manual(source_object,
topicstr,
category,
newtext,
perm_tuple,
force=True)
elif 'del' in switches or 'delete' in switches:
#delete a help entry
topics = helpsystem.edithelp.del_help_manual(source_object, topicstr)
if not topics:
return
else:
string = "Multiple matches for '%s'. Please specify:" % topicstr
string += "\n ".join(topics)
GLOBAL_CMD_TABLE.add_command("@sethelp", cmd_sethelp,
priv_tuple=("helpsys.add_help",
"helpsys.del_help",
"helpsys.admin_heelp"),
help_category="Admin")
def cmd_setcmdalias(command):
"""
@setcmdalias - define shortcuts for commands
Usage:
@setcmdalias[/switch] alias [= command]
Switches:
list - view all command aliases
add - add alias
del - remove and existing alias
This defins a new alias for a common command,
for example like letting 'l' work as
well as 'look'. When you change an alias you must
use @reload/aliases before the alias-change gets
recognized.
"""
source_object = command.source_object
args = command.command_argument
switches = command.command_switches
if "list" in switches:
# show all aliases
string = "Command aliases defined:"
aliases = CommandAlias.objects.all()
if not aliases:
string = "No command aliases defined."
for alias in aliases:
string += "\n %s -> %s" % (alias.user_input, alias.equiv_command)
source_object.emit_to(string)
return
if not args:
source_object.emit_to("Usage: @setcmdalias[/list/add/del] alias [= command]")
return
equiv_command = ""
user_input = ""
# analyze args
if '=' in args:
user_input, equiv_command = [arg.strip() for arg in args.split("=",1)]
else:
user_input = args.strip()
if 'add' in switches:
# add alias
edit_aliases.add_alias(user_input, equiv_command)
source_object.emit_to("Alias %s -> %s added. Now do '@reload/aliases'." % (user_input, equiv_command))
return
elif 'del' in switches:
# delete alias
edit_aliases.del_alias(user_input)
source_object.emit_to("Removed alias %s (if it existed). Now do '@reload/aliases'." % user_input)
else:
source_object.emit_to("Usage: @setcmdalias[/switch] [command = ] alias")
GLOBAL_CMD_TABLE.add_command("@setcmdalias", cmd_setcmdalias,
priv_tuple=("genperms.process_control",),
help_category="Admin")
def cmd_delevent(command):
"""
@delevent - remove events manually
Usage:
@delevent[/switch] <pid>
Switch:
force - by default, certain default low-pid system events are protected
from accidental deletion. This switch overrides this
protection.
Removes an event with the given pid (process ID) from the event scheduler.
To see all active events and their pids, use the @ps command.
"""
source_object = command.source_object
switches = command.command_switches
if not command.command_argument:
source_object.emit_to("Usage: @delevent <pid>")
return
try:
pid = int(command.command_argument)
except ValueError:
source_object.emit_to("You must supply a valid pid number.")
return
if pid < 3 and "force" not in switches:
# low-pid protection
string = "Warning:\n"
string += " Pid %i is a low-pid system event. It is usually not\n" % pid
string +=" something you want to delete since crucial\n"
string += " engine functions depend on it. If you were not\n"
string += " mistaken and know what you are doing, give this\n"
string += " command again with the /force switch to override\n"
string += " this protection."
source_object.emit_to(string)
return
event = scheduler.get_event(pid)
if event:
desc = event.description
scheduler.del_event(pid)
source_object.emit_to("Event %i - '%s' removed." % (pid, desc))
else:
source_object.emit_to("No event found with a pid of %i. Use @ps to list process IDs." % pid)
GLOBAL_CMD_TABLE.add_command("@delevent", cmd_delevent,
priv_tuple=("genperms.process_control",),
help_category="Admin")

View file

@ -1,241 +0,0 @@
"""
Implementation of the @search command that resembles MUX2.
"""
from django.db.models import Q
from src.objects.models import Object
from src import defines_global
from src.cmdtable import GLOBAL_CMD_TABLE
def _parse_restriction_split(source_object, restriction_split, search_low_dbnum,
search_high_dbnum):
"""
Parses a split restriction string and sets some needed variables.
Returns a tuple in the form of: (low dbnum, high dbnum)
"""
restriction_size = len(restriction_split)
if restriction_size >= 2:
try:
search_low_dbnum = int(restriction_split[1].strip())
except ValueError:
source_object.emit_to("Invalid value for low dbref limit.")
return False
if restriction_size >= 3:
try:
search_high_dbnum = int(restriction_split[2].strip())
except ValueError:
source_object.emit_to("Invalid value for high dbref limit.")
return False
return search_low_dbnum, search_high_dbnum
def display_results(source_object, search_query):
"""
Display the results to the searcher.
"""
# Lists to hold results by type. There may be a better way to do this
thing_list = []
room_list = []
exit_list = []
player_list = []
for obj in search_query:
if obj.is_thing():
thing_list.append(obj)
elif obj.is_room():
room_list.append(obj)
elif obj.is_exit():
exit_list.append(obj)
elif obj.is_player():
player_list.append(obj)
# Render each section for different object types
if thing_list:
source_object.emit_to("\n\rTHINGS:")
for thing in thing_list:
source_object.emit_to(thing.get_name(show_dbref=True, show_flags=True))
if exit_list:
source_object.emit_to("\n\rEXITS:")
for exit in exit_list:
source_object.emit_to(exit.get_name(show_dbref=True, show_flags=True))
if room_list:
source_object.emit_to("\n\rROOMS:")
for room in room_list:
source_object.emit_to(room.get_name(show_dbref=True, show_flags=True))
if player_list:
source_object.emit_to("\n\rPLAYER:")
for player in player_list:
source_object.emit_to(player.get_name(show_dbref=True, show_flags=True))
# Show the total counts by type
source_object.emit_to("\n\rFound: Rooms...%d Exits...%d Things...%d Players...%d" % (
len(room_list),
len(exit_list),
len(thing_list),
len(player_list)))
def build_query(source_object, search_query, search_player, search_type,
search_restriction, search_low_dbnum, search_high_dbnum):
"""
Builds and returns a QuerySet object, or None if an error occurs.
"""
# Look up an Object matching the player search query
if search_player:
# Replace the string variable with an Object reference
search_player = source_object.search_for_object(search_player)
# Use standard_objsearch to handle duplicate/nonexistant results
if not search_player:
return None
# Searching by player, chain filter
search_query = search_query.filter(owner=search_player)
# Check to ensure valid search types
if search_type == "type":
if search_restriction == "room":
search_query = search_query.filter(type=defines_global.OTYPE_ROOM)
elif search_restriction == "thing":
search_query = search_query.filter(type=defines_global.OTYPE_THING)
elif search_restriction == "exit":
search_query = search_query.filter(type=defines_global.OTYPE_EXIT)
elif search_restriction == "player":
search_query = search_query.filter(type=defines_global.OTYPE_PLAYER)
else:
source_object.emit_to("Invalid class. See 'help SEARCH CLASSES'.")
return None
elif search_type == "parent":
search_query = search_query.filter(script_parent__iexact=search_restriction)
elif search_type == "object" or search_type == "thing":
search_query = search_query.filter(name__icontains=search_restriction,
type=defines_global.OTYPE_THING)
elif search_type == "rooms":
search_query = search_query.filter(name__icontains=search_restriction,
type=defines_global.OTYPE_ROOM)
elif search_type == "exits":
search_query = search_query.filter(name__icontains=search_restriction,
type=defines_global.OTYPE_EXIT)
elif search_type == "players":
search_query = search_query.filter(name__icontains=search_restriction,
type=defines_global.OTYPE_PLAYER)
elif search_type == "zone":
zone_obj = source_object.search_for_object(search_restriction)
# Use search_for_object to handle duplicate/nonexistant results.
if not zone_obj:
return None
search_query = search_query.filter(zone=zone_obj)
elif search_type == "power":
# TODO: Need this once we have powers implemented.
source_object.emit_to("To be implemented...")
return None
elif search_type == "flags":
flag_list = search_restriction.split()
#source_object.emit_to("restriction: %s" % flag_list)
for flag in flag_list:
search_query = search_query.filter(Q(flags__icontains=flag) | Q(nosave_flags__icontains=flag))
if search_low_dbnum:
search_query = search_query.filter(id__gte=search_low_dbnum)
if search_high_dbnum:
search_query = search_query.filter(id__lte=search_high_dbnum)
return search_query
def cmd_search(command):
"""
search
Usage:
search <name>
Searches for owned objects as per MUX2.
"""
source_object = command.source_object
search_player = None
search_type = None
search_restriction = None
search_low_dbnum = None
search_high_dbnum = None
if not command.command_argument:
search_player = "#" + str(source_object.id)
else:
first_check_split = command.command_argument.split(' ', 1)
if '=' in first_check_split[0]:
# @search class=restriction...
eq_split = command.command_argument.split('=', 1)
search_type = eq_split[0]
restriction_split = eq_split[1].split(',')
search_restriction = restriction_split[0].strip()
#source_object.emit_to("@search class=restriction")
#source_object.emit_to("eq_split: %s" % eq_split)
#source_object.emit_to("restriction_split: %s" % restriction_split)
try:
search_low_dbnum, search_high_dbnum = _parse_restriction_split(source_object,
restriction_split,
search_low_dbnum,
search_high_dbnum)
except TypeError:
return
else:
# @search player
if len(first_check_split) == 1:
#source_object.emit_to("@search player")
#source_object.emit_to(first_check_split)
search_player = first_check_split[0]
else:
#source_object.emit_to("@search player class=restriction")
#source_object.emit_to(first_check_split)
search_player = first_check_split[0]
eq_split = first_check_split[1].split('=', 1)
search_type = eq_split[0]
#source_object.emit_to("eq_split: %s" % eq_split)
restriction_split = eq_split[1].split(',')
search_restriction = restriction_split[0]
#source_object.emit_to("restriction_split: %s" % restriction_split)
try:
search_low_dbnum, search_high_dbnum = _parse_restriction_split(source_object,
restriction_split,
search_low_dbnum,
search_high_dbnum)
except TypeError:
return
search_query = Object.objects.all()
#source_object.emit_to("search_player: %s" % search_player)
#source_object.emit_to("search_type: %s" % search_type)
#source_object.emit_to("search_restriction: %s" % search_restriction)
#source_object.emit_to("search_lowdb: %s" % search_low_dbnum)
#source_object.emit_to("search_highdb: %s" % search_high_dbnum)
# Clean up these variables for comparisons.
try:
search_type = search_type.strip().lower()
except AttributeError:
pass
try:
search_restriction = search_restriction.strip().lower()
except AttributeError:
pass
# Build the search query.
search_query = build_query(source_object, search_query, search_player, search_type,
search_restriction, search_low_dbnum,
search_high_dbnum)
# Something bad happened in query construction, die here.
if search_query is None:
return
display_results(source_object, search_query)
GLOBAL_CMD_TABLE.add_command("@search", cmd_search,
priv_tuple=("objects.info",),
help_category="Building")

View file

@ -1,127 +0,0 @@
"""
Commands that are available from the connect screen.
"""
import traceback
from django.contrib.auth.models import User
from src.objects.models import Object
from src import defines_global
from src.util import functions_general
from src.cmdtable import GLOBAL_UNCON_CMD_TABLE
from src.logger import log_errmsg
def cmd_connect(command):
"""
This is the connect command at the connection screen. Fairly simple,
uses the Django database API and User model to make it extremely simple.
"""
session = command.session
# Argument check.
# Fail gracefully if no argument is provided
if not command.command_argument:
session.msg("No arguments provided.\n\r Usage (without <>): connect <email> <password>")
return
arg_list = command.command_argument.split()
if not functions_general.cmd_check_num_args(session, arg_list, 2):
session.msg("Not enough arguments provided.\n\r Usage (without <>): connect <email> <password>")
return
uemail = arg_list[0]
password = arg_list[1]
# Match an email address to an account.
email_matches = Object.objects.get_user_from_email(uemail)
# No username match
if email_matches.count() == 0:
session.msg("The email '%s' does not match any accounts.\n\rIf you are new you should create a new account." % uemail)
return
# We have at least one result, so we can check the password.
user = email_matches[0]
if not user.check_password(password):
session.msg("Incorrect password.")
else:
uname = user.username
session.login(user)
GLOBAL_UNCON_CMD_TABLE.add_command("connect", cmd_connect, auto_help_override=False)
def cmd_create(command):
"""
Handle the creation of new accounts.
"""
session = command.session
# Argument check.
# Fail gracefully if no argument is provided
if not command.command_argument:
session.msg("No arguments provided\n\r Usage (without <>): create \"<username>\" <email> <password>")
return
arg_list = command.command_argument.split()
if not functions_general.cmd_check_num_args(session, arg_list, 2):
session.msg("Too few arguments provided\n\r Usage (without <>): create \"<username>\" <email> <password>")
return
quote_split = command.command_argument.split("\"")
if len(quote_split) < 2:
session.msg("You must enclose your username in quotation marks.")
return
uname = quote_split[1]
lastarg_split = quote_split[2].split()
if len(lastarg_split) != 2:
session.msg("You must specify an email address, followed by a password.")
return
email = lastarg_split[0].strip()
password = lastarg_split[1].strip()
#check so the email is at least on the form xxxx@xxx.xxx
addr = email.split('@')
if len(addr) != 2 or not len(addr[1].split('.')) > 1 or not addr[1].split('.')[-1]:
session.msg("'%s' is not a valid e-mail address." % email)
return
# Search for a user object with the specified username.
account = User.objects.filter(username=uname)
# Match an email address to an account.
email_matches = Object.objects.get_user_from_email(email)
# Look for any objects with an 'Alias' attribute that matches
# the requested username
alias_matches = Object.objects.filter(attribute__attr_name__exact="ALIAS",
attribute__attr_value__iexact=uname).filter(
type=defines_global.OTYPE_PLAYER)
if not account.count() == 0 or not alias_matches.count() == 0:
session.msg("Sorry, there is already a player with that name.")
elif not email_matches.count() == 0:
session.msg("Sorry, there is already a player with that email address.")
elif len(password) < 3:
session.msg("Your password must be at least 3 characters or longer.\n\rFor best security, make it at least 8 characters long, avoid making it a real word and mix numbers into it.")
else:
try:
Object.objects.create_user(command, uname, email, password)
except:
# we have to handle traceback ourself at this point, if we don't errors will givo no feedback.
session.msg("This is a bug. Please e-mail an admin if the problem persists.\n%s" % traceback.format_exc())
log_errmsg(traceback.format_exc())
raise
GLOBAL_UNCON_CMD_TABLE.add_command("create", cmd_create, auto_help_override=False)
def cmd_quit(command):
"""
We're going to maintain a different version of the quit command
here for unconnected users for the sake of simplicity. The logged in
version will be a bit more complicated.
"""
session = command.session
session.msg("Good bye! Disconnecting ...")
session.handle_close()
GLOBAL_UNCON_CMD_TABLE.add_command("quit", cmd_quit, auto_help_override=False)

40
src/comms/admin.py Normal file
View file

@ -0,0 +1,40 @@
#
# This sets up how models are displayed
# in the web admin interface.
#
from django.contrib import admin
from src.comms.models import Channel, Msg, ChannelConnection
class MsgAdmin(admin.ModelAdmin):
list_display = ('id', 'db_date_sent', 'db_sender', 'db_receivers', 'db_channels', 'db_message')
list_display_links = ("id",)
ordering = ["db_date_sent", 'db_sender', 'db_receivers', 'db_channels']
readonly_fields = ['db_permissions', 'db_message', 'db_sender', 'db_receivers', 'db_channels']
search_fields = ['id', '^db_date_sent', '^db_message']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(Msg, MsgAdmin)
class ChannelAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_desc', 'db_aliases', 'db_keep_log', 'db_permissions')
list_display_links = ("id", 'db_key')
ordering = ["db_key"]
readonly_fields = ['db_permissions']
search_fields = ['id', 'db_key', 'db_aliases']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(Channel, ChannelAdmin)
class ChannelConnectionAdmin(admin.ModelAdmin):
list_display = ('db_channel', 'db_player')
list_display_links = ("db_player", 'db_channel')
ordering = ["db_channel"]
search_fields = ['db_channel', 'db_player']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(ChannelConnection, ChannelConnectionAdmin)

138
src/comms/channelhandler.py Normal file
View file

@ -0,0 +1,138 @@
"""
The channel handler handles the stored set of channels
and how they are represented against the cmdhandler.
If there is a channel named 'newbie', we want to be able
to just write
> newbie Hello!
For this to work, 'newbie', the name of the channel, must
be identified by the cmdhandler as a command name. The
channelhandler stores all channels as custom 'commands'
that the cmdhandler can import and look through.
Warning - channel names take precedence over command names,
so make sure to not pick clashing channel names.
Unless deleting a channel you normally don't need to bother about
the channelhandler at all - the create_channel method handles the update.
To delete a channel cleanly, delete the channel object, then call
update() on the channelhandler. Or use Channel.objects.delete() which
does this for you.
"""
from src.comms.models import Channel, Msg
from src.commands import cmdset, command
from src.permissions.permissions import has_perm
class ChannelCommand(command.Command):
"""
Channel
Usage:
<channel name or alias> <message>
This is a channel. You can send to it by entering
its name or alias, followed by the text you want to send.
"""
# this flag is what identifies this cmd as a channel cmd
# and branches off to the system send-to-channel command
# (which is customizable by admin)
key = "general"
help_category = "Channels"
permissions = "cmd:use_channels"
is_channel = True
obj = None
def parse(self):
"""
Simple parser
"""
channelname, msg = self.args.split(":", 1)
self.args = (channelname.strip(), msg.strip())
def func(self):
"""
Create a new message and send it to channel, using
the already formatted input.
"""
channelkey, msg = self.args
caller = self.caller
if not msg:
caller.msg("Say what?")
return
channel = Channel.objects.get_channel(channelkey)
if not channel:
caller.msg("Channel '%s' not found." % channelkey)
return
if not channel.has_connection(caller):
string = "You are not connected to channel '%s'."
caller.msg(string % channelkey)
return
if not has_perm(caller, channel, 'chan_send'):
string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey)
return
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)
msgobj.save()
msgobj.channels = channel
# send new message object to channel
channel.msg(msgobj)
class ChannelHandler(object):
"""
Handles the set of commands related to channels.
"""
def __init__(self):
self.cached_channel_cmds = []
def __str__(self):
return ", ".join(str(cmd) for cmd in self.cached_channel_cmds)
def reset(self):
"""
Reset the cache storage.
"""
self.cached_channel_cmds = []
def add_channel(self, channel):
"""
Add an individual channel to the handler. This should be
called whenever a new channel is created. To
remove a channel, simply delete the channel object
and run self.update on the handler.
"""
# map the channel to a searchable command
cmd = ChannelCommand()
cmd.key = channel.key.strip().lower()
cmd.obj = channel
if channel.aliases:
cmd.aliases = channel.aliases
self.cached_channel_cmds.append(cmd)
def update(self):
"Updates the handler completely."
self.cached_channel_cmds = []
for channel in Channel.objects.all():
self.add_channel(channel)
def get_cmdset(self, source_object):
"""
Retrieve cmdset for channels this source_object has
access to send to.
"""
# create a temporary cmdset holding all channels
chan_cmdset = cmdset.CmdSet()
chan_cmdset.key = '_channelset'
chan_cmdset.priority = 10
for cmd in [cmd for cmd in self.cached_channel_cmds
if has_perm(source_object, cmd, 'chan_send')]:
chan_cmdset.add(cmd)
return chan_cmdset
CHANNELHANDLER = ChannelHandler()

322
src/comms/managers.py Normal file
View file

@ -0,0 +1,322 @@
"""
These managers handles the
"""
from django.db import models
from src.players.models import PlayerDB
from django.contrib.contenttypes.models import ContentType
from src.utils.utils import is_iter
class CommError(Exception):
"Raise by comm system, to allow feedback to player when caught."
pass
# helper function
def to_object(inp, objtype='player'):
"""
Locates the object related to the given
playername or channel key. If input was already
the correct object, return it.
inp - the input object/string
objtype - 'player' or 'channel'
"""
if objtype == 'player':
if type(inp) == PlayerDB:
return inp
if hasattr(inp, 'player'):
return inp.player
else:
umatch = PlayerDB.objects.filter(user__username__iexact=inp)
if umatch:
return umatch[0]
else:
# have to import this way to avoid circular imports
from src.comms.models import Channel
#= ContentType.objects.get(app_label="comms",
# model="channel").model_class()
if type(inp) == Channel:
return inp
cmatch = Channel.objects.filter(db_key__iexact=inp)
if cmatch:
return cmatch[0]
return None
#
# Msg manager
#
class MsgManager(models.Manager):
"""
Handle msg database
"""
def get_message_by_id(self, idnum):
"Retrieve message by its id."
try:
idnum = int(idnum)
return self.get(id=id)
except:
return None
def get_messages_by_sender(self, sender):
"""
Get all messages sent by one player
"""
sender = to_object(sender)
if not sender:
return None
return [msg for msg in sender.sender_set.all()
if sender not in msg.hide_from_senders.all()]
def get_messages_by_receiver(self, receiver):
"""
Get all messages sent to one player
"""
receiver = to_object(receiver)
if not receiver:
return None
return [msg for msg in receiver.receiver_set.all()
if receiver not in msg.hide_from_receivers.all()]
def get_messages_by_channel(self, channel):
"""
Get all messages sent to one channel
"""
channel = to_object(channel, objtype='channel')
if not channel:
return None
return [msg for msg in channel.channel_set.all()
if channel not in msg.hide_from_channels.all()]
#TODO add search limited by send_times
def text_search(self, searchstring, filterdict=None):
"""
Returns all messages that contain the matching
search string. To avoid too many results, and also
since this can be a very computing-
heavy operation, it's recommended to be filtered
by at least channel or sender/receiver.
searchstring - string to search for
filterdict -
{'channels':[list],
'senders':[list],
'receivers':[list]}
lists can contain either the name/keys of the
objects or the actual objects to filter by.
"""
if filterdict:
# obtain valid objects for all filters
channels = [chan for chan in
[to_object(chan, objtype='channel')
for chan in filterdict.get('channels',[])]
if chan]
senders = [sender for sender in
[to_object(sender)
for sender in filterdict.get('senders',[])]
if sender]
receivers = [receiver for receiver in
[to_object(receiver)
for receiver in filterdict.get('receivers',[])]
if receiver]
# filter the messages lazily using the filter objects
msgs = []
for sender in senders:
msgs = list(sender.message_set.filter(
db_message__icontains=searchstring))
for receiver in receivers:
rec_msgs = receiver.message_set.filter(
db_message__icontains=searchstring)
if msgs:
msgs = [msg for msg in rec_msgs if msg in msgs]
else:
msgs = rec_msgs
for channel in channels:
chan_msgs = list(channel.message_set.filter(
db_message__icontains=searchstring))
if msgs:
msgs = [msg for msg in chan_msgs if msg in msgs]
else:
msgs = chan_msgs
return list(set(msgs))
return list(self.all().filter(db_message__icontains=searchstring))
def message_search(self, sender=None, receiver=None, channel=None, freetext=None, dbref=None):
"""
Search the message database for particular messages. At least one
of the arguments must be given to do a search.
sender - get messages sent by a particular player
receiver - get messages received by a certain player or players
channel - get messages sent to a particular channel or channels
freetext - Search for a text string in a message.
NOTE: This can potentially be slow, so make sure to supply
one of the other arguments to limit the search.
dbref - (int) the exact database id of the message. This will override
all other search crieteria since it's unique and
always gives a list with only one match.
"""
if dbref:
return self.filter(id=dbref)
if freetext:
if sender:
sender = [sender]
if receiver and not is_iter(receiver):
receiver = [receiver]
if channel and not is_iter(channel):
channel = [channel]
filterdict = {"senders":sender,
"receivers":receiver,
"channels":channel}
return self.textsearch(freetext, filterdict)
msgs = []
if sender:
msgs = self.get_messages_by_sender(sender)
if receiver:
rec_msgs = self.get_messages_by_receiver(receiver)
if msgs:
msgs = [msg for msg in rec_msgs if msg in msgs]
else:
msgs = rec_msgs
if channel:
chan_msgs = self.get_messaqge_by_channel(channel)
if msgs:
msgs = [msg for msg in chan_msgs if msg in msgs]
else:
msgs = chan_msgs
return msgs
#
# Channel manager
#
class ChannelManager(models.Manager):
"""
Handle channel database
"""
def get_all_channels(self):
"""
Returns all channels in game.
"""
return self.all()
def get_channel(self, channelkey):
"""
Return the channel object if given its key.
Also searches its aliases.
"""
# first check the channel key
channels = self.filter(db_key__iexact=channelkey)
if not channels:
# also check aliases
channels = [channel for channel in self.all()
if channelkey in channel.aliases]
if channels:
return channels[0]
return None
def del_channel(self, channelkey):
"""
Delete channel matching channelkey.
Also cleans up channelhandler.
"""
channels = self.filter(db_key__iexact=channelkey)
if not channels:
# no aliases allowed for deletion.
return False
for channel in channels:
channel.delete()
from src.comms.channelhandler import CHANNELHANDLER
CHANNELHANDLER.update()
return None
def has_connection(self, player, channel):
"Check so the player is really listening to this channel."
ChannelConnection = ContentType.objects.get(app_label="comms",
model="channelconnection").model_class()
return ChannelConnection.objects.has_connection(player, channel)
def get_all_connections(self, channel):
"""
Return the connections of all players listening
to this channel
"""
# import here to avoid circular imports
from src.comms.models import ChannelConnection
#= ContentType.objects.get(app_label="comms",
# model="channelconnection").model_class()
return ChannelConnection.objects.get_all_connections(channel)
def channel_search(self, ostring):
"""
Search the channel database for a particular channel.
ostring - the key or database id of the channel.
"""
channels = []
try:
# try an id match first
dbref = int(ostring.strip('#'))
channels = self.filter(id=dbref)
except Exception:
pass
if not channels:
# no id match. Search on the key.
channels = self.filter(db_key=ostring)
return channels
#
# ChannelConnection manager
#
class ChannelConnectionManager(models.Manager):
"""
This handles all connections between a player and
a channel.
"""
def get_all_player_connections(self, player):
"Get all connections that the given player has."
player = to_object(player)
return self.filter(db_player=player)
def has_connection(self, player, channel):
"Checks so a connection exists player<->channel"
player = to_object(player)
channel = to_object(channel, objtype="channel")
if player and channel:
return self.filter(db_player=player).filter(db_channel=channel).count() > 0
return False
def get_all_connections(self, channel):
"""
Get all connections for a channel
"""
channel = to_object(channel, objtype='channel')
return self.filter(db_channel=channel)
def create_connection(self, player, channel):
"""
Connect a player to a channel. player and channel
can be actual objects or keystrings.
"""
player = to_object(player)
channel = to_object(channel, objtype='channel')
if not player or not channel:
raise CommError("NOTFOUND")
new_connection = self.model(db_player=player, db_channel=channel)
new_connection.save()
return new_connection
def break_connection(self, player, channel):
"Remove link between player and channel"
player = to_object(player)
channel = to_object(channel, objtype='channel')
if not player or not channel:
raise CommError("NOTFOUND")
conns = self.filter(db_player=player).filter(db_channel=channel)
for conn in conns:
conn.delete()

590
src/comms/models.py Normal file
View file

@ -0,0 +1,590 @@
"""
Models for the comsystem.
The comsystem's main component is the Message, which
carries the actual information between two parties.
Msgs are stored in the database and usually not
deleted.
A Msg always have one sender (a user), but can have
any number targets, both users and channels.
Channels are central objects that act as targets for
Msgs. Players can connect to channels by use of a
ChannelConnect object (this object is necessary to easily
be able to delete connections on the fly).
"""
from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.players.models import PlayerDB
from src.comms import managers
from src.server import sessionhandler
from src.permissions.permissions import has_perm
from src.utils.utils import is_iter
from src.utils.utils import dbref as is_dbref
#------------------------------------------------------------
#
# Utils
#
#------------------------------------------------------------
def obj_to_id(inp):
"""
Converts input object to an id string.
"""
dbref = is_dbref(inp)
if dbref:
return str(dbref)
if hasattr(inp, 'id'):
return str(inp.id)
if hasattr(inp, 'dbobj') and hasattr(inp.dbobj, 'id'):
return str(inp.dbobj.id)
return str(inp)
def id_to_obj(dbref, db_model='PlayerDB'):
"""
loads from dbref to object. Uses the db_model to search
for the id.
"""
if db_model == 'PlayerDB':
from src.player.objects import PlayerDB as db_model
else:
db_model = Channel
try:
dbref = int(dbref.strip())
return db_model.objects.get(id=dbref)
except Exception:
return None
#------------------------------------------------------------
#
# Msg
#
#------------------------------------------------------------
class Msg(SharedMemoryModel):
"""
A single message. This model describes all ooc messages
sent in-game, both to channels and between players.
The Msg class defines the following properties:
sender - sender of message
receivers - list of target objects for message
channels - list of channels message was sent to
message - the text being sent
date_sent - time message was sent
hide_from_sender - bool if message should be hidden from sender
hide_from_receivers - list of receiver objects to hide message from
hide_from_channels - list of channels objects to hide message from
permissions - perm strings
"""
#
# Msg database model setup
#
#
# These databse fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
# There must always be one sender of the message.
db_sender = models.ForeignKey(PlayerDB, related_name='sender_set')
# The destination objects of this message. Stored as a
# comma-separated string of object dbrefs. Can be defined along
# with channels below.
db_receivers = models.CharField(max_length=255, null=True, blank=True)
# The channels this message was sent to. Stored as a
# comma-separated string of channel dbrefs. A message can both
# have channel targets and destination objects.
db_channels = models.CharField(max_length=255, null=True, blank=True)
# The actual message and a timestamp. The message field
# should itself handle eventual headers etc.
db_message = models.TextField()
db_date_sent = models.DateTimeField(editable=False, auto_now_add=True)
# These are settable by senders/receivers/channels respectively.
# Stored as a comma-separated string of dbrefs. Can be used by the
# game to mask out messages from being visible in the archive (no
# messages are actually deleted)
#db_hide_from_sender = models.BooleanField(default=False)
#db_hide_from_receivers = models.CharField(max_length=255, null=True, blank=True)
#db_hide_from_channels = models.CharField(max_length=255, null=True, blank=True)
# permission strings, separated by commas
db_permissions = models.CharField(max_length=255, blank=True)
# Database manager
objects = managers.MsgManager()
class Meta:
"Define Django meta options"
verbose_name = "Message"
verbose_name_plural = "Messages"
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# sender property (wraps db_sender)
#@property
def sender_get(self):
"Getter. Allows for value = self.sender"
return self.db_sender
#@sender.setter
def sender_set(self, value):
"Setter. Allows for self.sender = value"
self.db_sender = value
self.save()
#@sender.deleter
def sender_del(self):
"Deleter. Allows for del self.sender"
raise Exception("You cannot delete the sender of a message!")
sender = property(sender_get, sender_set, sender_del)
# receivers property
#@property
def receivers_get(self):
"Getter. Allows for value = self.receivers. Returns a list of receivers."
if self.db_receivers:
return [id_to_obj(dbref) for dbref in self.db_receivers.split(',')]
return []
#@receivers.setter
def receivers_set(self, value):
"Setter. Allows for self.receivers = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([obj_to_id(val) for val in value])
self.db_receivers = obj_to_id(value)
self.save()
#@receivers.deleter
def receivers_del(self):
"Deleter. Allows for del self.receivers"
self.db_receivers = ""
self.save()
receivers = property(receivers_get, receivers_set, receivers_del)
# channels property
#@property
def channels_get(self):
"Getter. Allows for value = self.channels. Returns a list of channels."
if self.db_channels:
return [id_to_obj(dbref, 'Channel') for dbref in self.db_channels.split(',')]
return []
#@channels.setter
def channels_set(self, value):
"Setter. Allows for self.channels = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([obj_to_id(val) for val in value])
self.db_channels = obj_to_id(value)
self.save()
#@channels.deleter
def channels_del(self):
"Deleter. Allows for del self.channels"
self.db_channels = ""
self.save()
channels = property(channels_get, channels_set, channels_del)
# message property (wraps db_message)
#@property
def message_get(self):
"Getter. Allows for value = self.message"
return self.db_message
#@message.setter
def message_set(self, value):
"Setter. Allows for self.message = value"
self.db_message = value
self.save()
#@message.deleter
def message_del(self):
"Deleter. Allows for del self.message"
self.db_message = ""
self.save()
message = property(message_get, message_set, message_del)
# date_sent property (wraps db_date_sent)
#@property
def date_sent_get(self):
"Getter. Allows for value = self.date_sent"
return self.db_date_sent
#@date_sent.setter
def date_sent_set(self, value):
"Setter. Allows for self.date_sent = value"
self.db_date_sent = value
self.save()
#@date_sent.deleter
def date_sent_del(self):
"Deleter. Allows for del self.date_sent"
raise Exception("You cannot delete the date_sent property!")
date_sent = property(date_sent_get, date_sent_set, date_sent_del)
# hide_from_sender property
#@property
def hide_from_sender_get(self):
"Getter. Allows for value = self.hide_from_sender."
return self.db_hide_from_sender
#@hide_from_sender.setter
def hide_from_sender_set(self, value):
"Setter. Allows for self.hide_from_senders = value."
self.db_hide_from_sender = value
self.save()
#@hide_from_sender.deleter
def hide_from_sender_del(self):
"Deleter. Allows for del self.hide_from_senders"
self.db_hide_from_sender = False
self.save()
hide_from_sender = property(hide_from_sender_get, hide_from_sender_set, hide_from_sender_del)
# hide_from_receivers property
#@property
def hide_from_receivers_get(self):
"Getter. Allows for value = self.hide_from_receivers. Returns a list of hide_from_receivers."
if self.db_hide_from_receivers:
return [id_to_obj(dbref) for dbref in self.db_hide_from_receivers.split(',')]
return []
#@hide_from_receivers.setter
def hide_from_receivers_set(self, value):
"Setter. Allows for self.hide_from_receivers = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([obj_to_id(val) for val in value])
self.db_hide_from_receivers = obj_to_id(value)
self.save()
#@hide_from_receivers.deleter
def hide_from_receivers_del(self):
"Deleter. Allows for del self.hide_from_receivers"
self.db_hide_from_receivers = ""
self.save()
hide_from_receivers = property(hide_from_receivers_get, hide_from_receivers_set, hide_from_receivers_del)
# hide_from_channels property
#@property
def hide_from_channels_get(self):
"Getter. Allows for value = self.hide_from_channels. Returns a list of hide_from_channels."
if self.db_hide_from_channels:
return [id_to_obj(dbref) for dbref in self.db_hide_from_channels.split(',')]
return []
#@hide_from_channels.setter
def hide_from_channels_set(self, value):
"Setter. Allows for self.hide_from_channels = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([obj_to_id(val) for val in value])
self.db_hide_from_channels = obj_to_id(value)
self.save()
#@hide_from_channels.deleter
def hide_from_channels_del(self):
"Deleter. Allows for del self.hide_from_channels"
self.db_hide_from_channels = ""
self.save()
hide_from_channels = property(hide_from_channels_get, hide_from_channels_set, hide_from_channels_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
self.db_permissions = ""
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
#
# Msg class method
#
def __str__(self):
"Print text"
if self.channels:
return "%s -> %s: %s" % (self.sender.key,
", ".join([chan.key for chan in self.channels]),
self.message)
else:
return "%s -> %s: %s" % (self.sender.key,
", ".join([rec.key for rec in self.receivers]),
self.message)
#------------------------------------------------------------
#
# Channel
#
#------------------------------------------------------------
class Channel(SharedMemoryModel):
"""
This is the basis of a comm channel, only implementing
the very basics of distributing messages.
The Channel class defines the following properties:
key - main name for channel
desc - optional description of channel
aliases - alternative names for the channel
keep_log - bool if the channel should remember messages
permissions - perm strings
"""
#
# Channel database model setup
#
#
# These databse fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
# unique identifier for this channel
db_key = models.CharField(max_length=255, unique=True)
# optional description of channel
db_desc = models.CharField(max_length=80, blank=True, null=True)
# aliases for the channel. These are searched by cmdhandler
# as well to determine if a command is the name of a channel.
# Several aliases are separated by commas.
db_aliases = models.CharField(max_length=255)
# Whether this channel should remember its past messages
db_keep_log = models.BooleanField(default=True)
# Permission strings, separated by commas
db_permissions = models.CharField(max_length=255, blank=True)
# Database manager
objects = managers.ChannelManager()
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
"Getter. Allows for value = self.key"
return self.db_key
#@key.setter
def key_set(self, value):
"Setter. Allows for self.key = value"
self.db_key = value
self.save()
#@key.deleter
def key_del(self):
"Deleter. Allows for del self.key"
raise Exception("You cannot delete the channel key!")
key = property(key_get, key_set, key_del)
# desc property (wraps db_desc)
#@property
def desc_get(self):
"Getter. Allows for value = self.desc"
return self.db_desc
#@desc.setter
def desc_set(self, value):
"Setter. Allows for self.desc = value"
self.db_desc = value
self.save()
#@desc.deleter
def desc_del(self):
"Deleter. Allows for del self.desc"
self.db_desc = ""
self.save()
desc = property(desc_get, desc_set, desc_del)
# aliases property
#@property
def aliases_get(self):
"Getter. Allows for value = self.aliases. Returns a list of aliases."
if self.db_aliases:
return [perm.strip() for perm in self.db_aliases.split(',')]
return []
#@aliases.setter
def aliases_set(self, value):
"Setter. Allows for self.aliases = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_aliases = value
self.save()
#@aliases_del.deleter
def aliases_del(self):
"Deleter. Allows for del self.aliases"
self.db_aliases = ""
self.save()
aliases = property(aliases_get, aliases_set, aliases_del)
# keep_log property (wraps db_keep_log)
#@property
def keep_log_get(self):
"Getter. Allows for value = self.keep_log"
return self.db_keep_log
#@keep_log.setter
def keep_log_set(self, value):
"Setter. Allows for self.keep_log = value"
self.db_keep_log = value
self.save()
#@keep_log.deleter
def keep_log_del(self):
"Deleter. Allows for del self.keep_log"
self.db_keep_log = False
self.save()
keep_log = property(keep_log_get, keep_log_set, keep_log_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
self.db_permissions = ""
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
class Meta:
"Define Django meta options"
verbose_name = "Channel"
verbose_name_plural = "Channels"
#
# Channel class methods
#
def __str__(self):
return "Channel '%s' (%s)" % (self.key, self.desc)
def has_connection(self, player):
"""
Checks so this player is actually listening
to this channel.
"""
return Channel.objects.has_connection(player, self)
def msg(self, msgobj, from_obj=None):
"""
Send the given message to all players connected to channel. Note that
no permission-checking is done here; it is assumed to have been
done before calling this method.
msgobj - a Msg instance. May be a message string.
from_obj - if msgobj is not an Msg-instance, this is used to create
a message on the fly. The advantage of this is that such
messages are logged.
"""
if not type(msgobj) == Msg:
# the given msgobj is not an Msg instance. If it is a string and from_obj
# was given, we create the message on the fly instead.
if from_obj and isinstance(msgobj, basestring):
msgobj = Msg(db_sender=from_obj, db_message=msgobj)
msgobj.save()
msgobj.channels = [self]
msg = msgobj.message
else:
# this just sends a message, without any sender
# (and without storing it in a persistent Msg object)
msg = str(msgobj)
else:
msg = msgobj.message
# get all players connected to this channel
conns = Channel.objects.get_all_connections(self)
# send message to all connected players
for conn in conns:
for session in \
sessionhandler.find_sessions_from_username(conn.player.name):
session.msg(msg)
return True
def connect_to(self, player):
"Connect the user to this channel"
if not has_perm(player, self, 'chan_listen'):
return False
conn = ChannelConnection.objects.create_connection(player, self)
if conn:
return True
return False
def disconnect_from(self, player):
"Disconnect user from this channel."
ChannelConnection.objects.break_connection(player, self)
def delete(self):
"Clean out all connections to this channel and delete it."
for connection in Channel.objects.get_all_connections(self):
connection.delete()
super(Channel, self).delete()
class ChannelConnection(SharedMemoryModel):
"""
This connects a user object to a particular comm channel.
The advantage of making it like this is that one can easily
break the connection just by deleting this object.
"""
# Player connected to a channel
db_player = models.ForeignKey(PlayerDB)
# Channel the player is connected to
db_channel = models.ForeignKey(Channel)
# Database manager
objects = managers.ChannelConnectionManager()
# player property (wraps db_player)
#@property
def player_get(self):
"Getter. Allows for value = self.player"
return self.db_player
#@player.setter
def player_set(self, value):
"Setter. Allows for self.player = value"
self.db_player = value
self.save()
#@player.deleter
def player_del(self):
"Deleter. Allows for del self.player. Deletes connection."
self.delete()
player = property(player_get, player_set, player_del)
# channel property (wraps db_channel)
#@property
def channel_get(self):
"Getter. Allows for value = self.channel"
return self.db_channel
#@channel.setter
def channel_set(self, value):
"Setter. Allows for self.channel = value"
self.db_channel = value
self.save()
#@channel.deleter
def channel_del(self):
"Deleter. Allows for del self.channel. Deletes connection."
self.delete()
channel = property(channel_get, channel_set, channel_del)
def __str__(self):
return "Connection Player '%s' <-> %s" % (self.player, self.channel)
class Meta:
"Define Django meta options"
verbose_name = "Channel<->Player link"
verbose_name_plural = "Channel<->Player links"

View file

@ -1,398 +0,0 @@
"""
Comsys functions.
"""
import time
import datetime
from django.conf import settings
from django.utils import simplejson
from src.channels.models import CommChannel, CommChannelMessage, CommChannelMembership
from src import session_mgr
from src import ansi
from src import logger
from src.imc2.packets import IMC2PacketIceMsgBroadcasted
from src.imc2.models import IMC2ChannelMapping
from src.irc.models import IRCChannelMapping
import src.ansi
def plr_get_cdict(session):
"""
Returns the users's channel subscription dictionary. Use this instead of
directly referring to the session object.
session: (SessionProtocol) Reference to a player session.
"""
return session.channels_subscribed
def plr_listening_channel(session, cstr, alias_search=False):
"""
Returns a player's listening status for a channel.
session: (SessionProtocol) Reference to a player session.
cstr: (str) The channel name or alias (depending on alias_search).
alias_search: (bool) If true, search by alias. Else search by full name.
"""
return plr_has_channel(session, cstr, alias_search=alias_search,
return_muted=False)
def plr_cname_from_alias(session, calias):
"""
Returns a channel name given a channel alias.
session: (SessionProtocol) Reference to a player session.
calias: (str) The channel alias.
"""
return plr_get_cdict(session).get(calias, None)[0]
def plr_chan_off(session, calias):
"""
Turn a channel off for a player.
session: (SessionProtocol) Reference to a player session.
calias: (str) The channel alias.
"""
if not plr_listening_channel(session, calias, alias_search=True):
session.msg("You're already not listening to that channel.")
return
else:
cname = plr_cname_from_alias(session, calias)
cobj = get_cobj_from_name(cname)
plr_set_channel_listening(session, calias, False)
session.msg("You have left %s." % (cname,))
send_cmessage(cname, "%s has left the channel." % (
session.get_pobject().get_name(show_dbref=False),))
def plr_chan_on(session, calias):
"""
Turn a channel on for a player.
session: (SessionProtocol) Reference to a player session.
calias: (str) The channel alias.
"""
plr_listening = plr_listening_channel(session, calias, alias_search=True)
if plr_listening:
session.msg("You're already listening to that channel.")
return
else:
cname = plr_cname_from_alias(session, calias)
cobj = get_cobj_from_name(cname)
send_cmessage(cname, "%s has joined the channel." % (
session.get_pobject().get_name(show_dbref=False),))
plr_set_channel_listening(session, calias, True)
session.msg("You have joined %s." % (cname,))
def plr_has_channel(session, cname, alias_search=False, return_muted=False):
"""
Is this session subscribed to the named channel?
session: (SessionProtocol) Reference to a player session.
cname: (str) The channel name or alias (depending on alias_search)
alias_search: (bool) If False, search by full name. Else search by alias.
return_muted: (bool) Take the user's enabling/disabling of the channel
into consideration?
"""
has_channel = False
if alias_search:
# Search by aliases only.
cdat = plr_get_cdict(session).get(cname, False)
# No key match, fail immediately.
if not cdat:
return False
# If channel status is taken into consideration, see if the user
# has the channel muted or not.
if return_muted:
return True
else:
return cdat[1]
else:
# Search by complete channel name.
chan_list = plr_get_cdict(session).values()
for chan in chan_list:
# Check for a name match
if cname.lower() == chan[0].lower():
has_channel = True
# If channel status is taken into consideration, see if the user
# has the channel muted or not.
if return_muted is False and not chan[1]:
has_channel = False
break
return has_channel
def plr_set_channel_listening(session, alias, listening):
"""
Enables or disables listening on a particular channel based on the
user's channel alias.
session: (SessionProtocol) A reference to the player session.
alias: (str) The channel alias.
listening: (bool) A True or False value to determine listening status.
"""
membership = session.get_pobject().channel_membership_set.get(user_alias=alias)
membership.is_listening = listening
membership.save()
plr_get_cdict(session).get(alias)[1] = listening
def plr_add_channel(source_object, alias, channel):
"""
Adds a player to a channel via a CommChannelMembership and sets the cached
cdict value.
source_object: (Object) Reference to the object that will be listening.
alias: (str) The channel alias (also the key in the user's cdict)
channel: (CommChannel) The channel object to add.
listening: (bool) A True or False value to determine listening status.
"""
membership = CommChannelMembership(channel=channel, listener=source_object,
user_alias=alias)
membership.save()
sessions = session_mgr.sessions_from_object(source_object)
for session in sessions:
plr_get_cdict(session)[alias] = [channel.get_name(), True]
def plr_del_channel(source_object, alias):
"""
Remove a channel from a session's channel list.
source_object: (Object) Reference to the object that will be listening.
alias: (str) The channel alias (also the key in the user's cdict)
"""
membership = source_object.channel_membership_set.get(user_alias=alias)
membership.delete()
sessions = session_mgr.sessions_from_object(source_object)
for session in sessions:
del plr_get_cdict(session)[alias]
def msg_chan_hist(target_obj, channel_name):
"""
Sends a listing of subscribers to a channel given a channel name.
target_obj: (Object) The object to send the history listing to.
channel_name: (str) The channel's full name.
"""
cobj = get_cobj_from_name(channel_name)
hist_list = CommChannelMessage.objects.filter(channel=cobj).order_by('date_sent')
# Negative indexing is not currently supported with QuerySet objects.
# Figure out what the first CommChannelMessage is to return and grab the
# next 20.
first_msg = hist_list.count() - 20
# Prevent a negative index from being called on.
if first_msg < 0:
first_msg = 0
# Slice and dice, display last 20 messages.
for entry in hist_list[first_msg:]:
delta_days = datetime.datetime.now() - entry.date_sent
days_elapsed = delta_days.days
if days_elapsed > 0:
# Message happened more than a day ago, show the date.
time_str = entry.date_sent.strftime("%m.%d / %H:%M")
else:
# Recent message (within last 24 hours), show hour:minute.
time_str = entry.date_sent.strftime("%H:%M")
target_obj.emit_to("[%s] %s" % (time_str, entry.message))
def msg_cwho(target_obj, channel_name):
"""
Sends a listing of subscribers to a channel given a channel name.
target_obj: (Object) Send the cwho listing to this object.
channel_name: (str) The channel's full name.
"""
target_obj.emit_to("--- Users Listening to %s ---" % (channel_name,))
for plr_sess in get_cwho_list(channel_name):
target_obj.emit_to(plr_sess.get_pobject().get_name(show_dbref=target_obj.sees_dbrefs()))
target_obj.emit_to("--- End Channel Listeners ---")
def get_cwho_list(channel_name, return_muted=False):
"""
Get all users on a channel.
channel_name: (string) The name of the channel.
return_muted: (bool) Return those who have the channel muted if True.
"""
sess_list = session_mgr.get_session_list()
return [sess for sess in sess_list if plr_has_channel(sess, channel_name, return_muted)]
def load_object_channels(pobject):
"""
Parse JSON dict of a user's channel list from their CHANLIST attribute.
"""
membership_list = pobject.channel_membership_set.all()
for membership in membership_list:
sessions = session_mgr.sessions_from_object(pobject)
for session in sessions:
session.channels_subscribed[membership.user_alias] = [membership.channel.name,
membership.is_listening]
def send_cmessage(channel, message, show_header=True, from_external=None):
"""
Sends a message to all players on the specified channel.
channel: (string or CommChannel) Name of channel or a CommChannel object.
message: (string) Message to send.
show_header: (bool) If False, don't prefix message with the channel header.
from_external: (string/None)
Can be None, 'IRC' or 'IMC2'. The sending functions of the
respective protocol sets this flag, otherwise it should
be None; it allows for piping messages between protocols
without accidentally also echoing it back to where it came from.
"""
if isinstance(channel, unicode) or isinstance(channel, str):
# If they've passed a string as the channel argument, look up the
# correct channel object.
try:
channel_obj = get_cobj_from_name(channel)
except:
logger.log_errmsg("send_cmessage(): Can't find channel: %s" % channel)
return
else:
# Else, assume that it's a channel object and skip re-querying for
# the channel.
channel_obj = channel
final_message = message
if show_header == True:
final_message = "%s %s" % (channel_obj.ansi_name, message)
for user in get_cwho_list(channel_obj.name, return_muted=False):
user.msg(final_message)
chan_message = CommChannelMessage()
chan_message.channel = channel_obj
chan_message.message = final_message
chan_message.save()
#pipe to external protocols
if from_external:
send_cexternal(channel_obj.name, message,
from_external=from_external)
def get_all_channels():
"""
Returns all channel objects.
"""
return CommChannel.objects.all()
def get_cobj_from_name(cname):
"""
Returns the channel's object when given a name.
"""
return CommChannel.objects.get(name=cname)
def create_channel(name, owner, description=None):
"""
Create a new channel.
name: (string) Name of the new channel
owner: (Object) Object that owns the channel
"""
new_chan = CommChannel()
new_chan.name = ansi.parse_ansi(name, strip_ansi=True)
new_chan.ansi_name = "[%s]" % (ansi.parse_ansi(name),)
new_chan.set_owner(owner)
new_chan.description = description
new_chan.save()
return new_chan
def cname_search(search_text, exact=False):
"""
Searches for a particular channel name. Returns a QuerySet with the
results.
exact: (bool) Do an exact (case-insensitive) name match if true.
"""
if exact:
return CommChannel.objects.filter(name__iexact=search_text)
else:
return CommChannel.objects.filter(name__istartswith=search_text)
def cemit_mudinfo(message):
"Send to mud info channel This is "
send_cmessage(settings.COMMCHAN_MUD_INFO,
'Info: %s' % message)
def send_cexternal(cname, cmessage, caller=None, from_external=None):
"""
This allows external protocols like IRC and IMC to send to a channel
while also echoing to each other. This used by channel-emit functions
to transparently distribute channel sends to external protocols.
cname - name of evennia channel sent to
cmessage - message sent (should be pre-formatted already)
caller - a player object sending the message (not present for IRC<->IMC comms)
from_external - which protocol sent the emit.
Currently supports 'IRC' and 'IMC2' or None
(this avoids emits echoing back to themselves). If
None, it is assumed the message comes from within Evennia
and all mapped external channels will be notified.
"""
if settings.IMC2_ENABLED and from_external != "IMC2":
#map a non-IMC emit to the IMC network
# Look for IMC2 channel maps. If one is found, send an ice-msg-b
# packet to the network.
#logger.msg_infomsg("caller found: %s (%s). Msg: %s" % (caller,from_external,cmessage))
imccaller = caller
imccmessage = cmessage
if not caller:
if from_external in ['IRC']:
#try to guess the caller's name from the message
#always on form name@#channel: msg
imccaller, imccmessage = cmessage.split("@",1)
#throw away IRC channel name; IMC users need not know it
imccmessage = (imccmessage.split(":",1)[1]).strip()
#logger.msg_infomsg("caller: %s cmessage: %s" % (imccaller, imccmessage))
else:
imccaller = "*"
try:
from src.imc2.connection import IMC2_PROTOCOL_INSTANCE
map = IMC2ChannelMapping.objects.get(channel__name=cname)
packet = IMC2PacketIceMsgBroadcasted(map.imc2_server_name,
map.imc2_channel_name,
imccaller,
imccmessage)
IMC2_PROTOCOL_INSTANCE.send_packet(packet)
except IMC2ChannelMapping.DoesNotExist:
# No map found, do nothing.
pass
if settings.IRC_ENABLED and from_external != "IRC":
# Map a non-IRC emit to IRC channels
# Look for IRC channel maps. If found, echo cmessage to the
# IRC channel.
if caller:
#message comes from a local channel
caller = caller.get_name(show_dbref=False)
cmessage = "[%s] %s: %s" % (cname, caller, cmessage)
else:
#message from IMC2; name is part of cmessage
cmessage = "[%s] %s" % (cname, cmessage)
try:
#this fails with a DoesNotExist if the channel is not mapped.
from src.irc.connection import IRC_CHANNELS
mapping = IRCChannelMapping.objects.filter(channel__name=cname)
#strip the message of ansi characters.
cmessage = ansi.clean_ansi(cmessage)
for mapp in mapping:
mapped_irc = filter(lambda c: c.factory.channel==mapp.irc_channel_name,
IRC_CHANNELS)
for chan in mapped_irc:
chan.send_msg(cmessage.encode("utf-8"))
except IRCChannelMapping.DoesNotExist:
#no mappings. Ignore
pass

View file

@ -1,15 +1,27 @@
from src.config.models import CommandAlias, ConfigValue, ConnectScreen
#
# This sets up how models are displayed
# in the web admin interface.
#
from src.config.models import ConfigValue, ConnectScreen
from django.contrib import admin
class CommandAliasAdmin(admin.ModelAdmin):
list_display = ('user_input', 'equiv_command')
admin.site.register(CommandAlias, CommandAliasAdmin)
class ConfigValueAdmin(admin.ModelAdmin):
list_display = ('conf_key', 'conf_value')
list_display = ('id', 'db_key')
list_display_links = ("id", 'db_key')
ordering = ['id', 'db_key']
search_fields = ['db_key']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(ConfigValue, ConfigValueAdmin)
class ConnectScreenAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'is_active')
list_display_links = ('id', 'name')
admin.site.register(ConnectScreen, ConnectScreenAdmin)
list_display = ('id', 'db_key', 'db_text', 'db_is_active')
list_display_links = ('id', 'db_key')
ordering = ['id', 'db_key']
search_fields = ['^db_key']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(ConnectScreen, ConnectScreenAdmin)

View file

@ -1,27 +0,0 @@
#
# Support command for editing command aliases
#
from src.config.models import CommandAlias
def add_alias(user_input, equiv_command):
"""
Adds a new alias or replace an old one.
"""
aquery = CommandAlias.objects.filter(user_input=user_input)
if aquery:
# overwrite existing alias
alias = aquery[0]
alias.user_input = user_input
alias.equiv_command = equiv_command
alias.save()
else:
# create new alias
CommandAlias(user_input=user_input, equiv_command=equiv_command).save()
def del_alias(alias):
"""
Delete an alias from the database
"""
aquery = CommandAlias.objects.filter(user_input=alias)
if aquery:
aquery[0].delete()

91
src/config/manager.py Normal file
View file

@ -0,0 +1,91 @@
"""
Custom manager for ConfigValue objects.
"""
from django.db import models
from django.contrib.contenttypes.models import ContentType
class ConfigValueManager(models.Manager):
"""
This gives some access methods to search and edit
the configvalue database.
"""
def set_configvalue(self, db_key, db_value):
"Set new/overwrite old config value"
db_objs = self.filter(db_key=db_key)
if db_objs:
# Overwrite old config value
db_obj = db_objs[0]
db_obj.db_value = db_value
db_obj.save()
else:
# No old conf found. Create a new one.
ConfigValue = ContentType.objects.get(app_label="config",
model="configvalue").model_class()
new_conf = ConfigValue()
new_conf.db_key = db_key
new_conf.db_value = db_value
new_conf.save()
def get_configvalue(self, config_key):
"""
Retrieve a configuration value.
config_key - the name of the configuration option
"""
try:
return self.get(db_key__iexact=config_key).db_value
except self.model.DoesNotExist:
return None
# a simple wrapper for consistent naming in utils.search
def config_search(self, ostring):
"""
Retrieve a configuration value.
ostring - a (unique) configuration key
"""
return self.get_configvalue(ostring)
def conf(self, db_key=None, db_value=None, delete=False):
"""
Wrapper to access the Config database.
This will act as a get/setter, lister or deleter
depending on how many arguments are supplied.
Due to its design, you cannot set conf to a value of
None using this method, use objects.set_configvalue
instead.
"""
if not db_key:
return self.all()
elif delete == True:
conf = self.get_configvalue(db_key)
if conf:
conf.delete()
elif db_value != None:
self.set_configvalue(db_key, db_value)
else:
return self.get_configvalue(db_key)
class ConnectScreenManager(models.Manager):
"""
This handles the random initial login screen system.
"""
def get_random_connect_screen(self):
"""
Returns a random active connect screen.
"""
try:
return self.filter(db_is_active=True).order_by('?')[0]
except IndexError:
ConnectScreen = ContentType.objects.get(app_label="config",
model="connectscreen").model_class()
text = "This is a placeholder connect screen. "
text += "Remind your admin to edit it."
new_screen = ConnectScreen(db_key='Default',
db_text=text,
db_is_active=True)
new_screen.save()
return new_screen

View file

@ -1,8 +0,0 @@
"""
Custom manager for CommandAlias objects.
"""
from django.db import models
class CommandAliasManager(models.Manager):
pass

View file

@ -1,34 +0,0 @@
"""
Custom manager for ConfigValue objects.
"""
from django.db import models
from src import logger
class ConfigValueManager(models.Manager):
def get_configvalue(self, configname):
"""
Retrieve a configuration value.
"""
try:
return self.get(conf_key__iexact=configname).conf_value
except self.model.DoesNotExist:
if configname not in ["game_firstrun"]:
logger.log_errmsg("Unable to get config value for %s (does not exist).\n" % (
configname))
raise
def set_configvalue(self, configname, newvalue):
"""
Sets a configuration value with the specified name.
Returns the new value for the directive.
"""
try:
conf = self.get(conf_key=configname)
conf.conf_value = newvalue
conf.save()
# We'll do this instead of conf.conf_value, might save a DB query.
return newvalue
except self.model.DoesNotExist:
logger.log_errmsg("Unable to set config value for %s (does not exist):\n%s" % (
configname))
raise

View file

@ -1,18 +0,0 @@
"""
Custom manager for ConnectScreen objects.
"""
from django.db import models
class ConnectScreenManager(models.Manager):
def get_random_connect_screen(self):
"""
Returns a random active connect screen.
"""
try:
return self.filter(is_active=True).order_by('?')[0]
except IndexError:
new_screen = ConnectScreen(name='Default',
text='This is a placeholder connect screen. Remind your admin to edit it through the Admin interface.',
is_active=True)
new_screen.save()
return new_screen

View file

@ -1,41 +1,191 @@
from django.db import models
from src.config.managers.commandalias import CommandAliasManager
from src.config.managers.configvalue import ConfigValueManager
from src.config.managers.connectscreen import ConnectScreenManager
"""
Configuration model - storing global flags on the fly, as
opposed to what is set once and for all
in the settings file.
class CommandAlias(models.Model):
"""
Command aliases. If the player enters the value equal to user_input, the
command denoted by equiv_command is used instead.
"""
user_input = models.CharField(max_length=50)
equiv_command = models.CharField(max_length=50)
objects = CommandAliasManager()
class Meta:
verbose_name_plural = "Command aliases"
ordering = ['user_input']
ConnectScreen model - cycling connect screens
"""
from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.config.manager import ConfigValueManager
from src.config.manager import ConnectScreenManager
#------------------------------------------------------------
#
# ConfigValue
#
#------------------------------------------------------------
class ConfigValue(models.Model):
class ConfigValue(SharedMemoryModel):
"""
Experimental new config model.
On-the fly storage of global settings.
Properties defined on ConfigValue:
key - main identifier
value - value stored in key
"""
conf_key = models.CharField(max_length=100)
conf_value = models.TextField()
#
# ConfigValue database model setup
#
#
# These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
# main name of the database entry
db_key = models.CharField(max_length=100)
# config value
db_value = models.TextField()
# Database manager
objects = ConfigValueManager()
def __unicode__(self):
return "%s" % self.conf_key
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
class ConnectScreen(models.Model):
# key property (wraps db_key)
#@property
def key_get(self):
"Getter. Allows for value = self.key"
return self.db_key
#@key.setter
def key_set(self, value):
"Setter. Allows for self.key = value"
self.db_key = value
self.save()
#@key.deleter
def key_del(self):
"Deleter. Allows for del self.key. Deletes entry."
self.delete()
key = property(key_get, key_set, key_del)
# value property (wraps db_value)
#@property
def value_get(self):
"Getter. Allows for value = self.value"
return self.db_value
#@value.setter
def value_set(self, value):
"Setter. Allows for self.value = value"
self.db_value = value
self.save()
#@value.deleter
def value_del(self):
"Deleter. Allows for del self.value. Deletes entry."
self.delete()
value = property(value_get, value_set, value_del)
class Meta:
"Define Django meta options"
verbose_name = "Server Config value"
verbose_name_plural = "Server Config values"
#
# ConfigValue other methods
#
def __unicode__(self):
return "%s" % self.key
#------------------------------------------------------------
## ConnectScreen
#
#------------------------------------------------------------
class ConnectScreen(SharedMemoryModel):
"""
Stores connect screens. The admins may have only one or multiple, which
will cycle randomly.
Properties on ConnectScreen:
key - optional identifier
text - the text to show
is_active - if the screen is in rotation
"""
name = models.CharField(max_length=255, help_text="An optional name for this screen (for organizational purposes).", blank=True)
text = models.TextField(help_text="The text for the connect screen. Color codes and substitutions are evaluated.")
is_active = models.BooleanField(default=1, help_text="Only active connect screens are placed in the rotation")
#
# ConnectScreen database model setup
#
# These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
#
# optional identifier
db_key = models.CharField(max_length=255, blank=True)
# connect screen text (ansi may be used)
db_text = models.TextField()
# if this screen should be used in rotation
db_is_active = models.BooleanField(default=True)
# Database manager
objects = ConnectScreenManager()
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
"Getter. Allows for value = self.key"
return self.db_key
#@key.setter
def key_set(self, value):
"Setter. Allows for self.key = value"
self.db_key = value
self.save()
#@key.deleter
def key_del(self):
"Deleter. Allows for del self.key. Deletes entry."
self.delete()
key = property(key_get, key_set, key_del)
# text property (wraps db_text)
#@property
def text_get(self):
"Getter. Allows for value = self.text"
return self.db_text
#@text.setter
def text_set(self, value):
"Setter. Allows for self.text = value"
self.db_text = value
self.save()
#@text.deleter
def text_del(self):
"Deleter. Allows for del self.text."
raise Exception("You can't delete the text of the connect screen!")
text = property(text_get, text_set, text_del)
# is_active property (wraps db_is_active)
#@property
def is_active_get(self):
"Getter. Allows for value = self.is_active"
return self.db_is_active
#@is_active.setter
def is_active_set(self, value):
"Setter. Allows for self.is_active = value"
self.db_is_active = value
self.save()
#@is_active.deleter
def is_active_del(self):
"Deleter. Allows for del self.is_active."
self.db_is_active = False
self.save()
is_active = property(is_active_get, is_active_set, is_active_del)
class Meta:
"Define Django meta options"
verbose_name = "Connect Screen"
verbose_name_plural = "Connect Screens"

View file

@ -1 +0,0 @@
# Create your views here.

View file

@ -1,461 +0,0 @@
"""
Master configuration file for Evennia.
NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE! All settings changes should
be done by copy-pasting the variable and its value to your game directory's
settings.py file.
"""
import os
# A list of ports to listen on. Can be one or many.
GAMEPORTS = [4000]
# While DEBUG is False, show a regular server error page on the web stuff,
# email the traceback to the people in the ADMINS tuple below. By default (True),
# show a detailed traceback for the web browser to display.
DEBUG = True
# While true, show "pretty" error messages for template syntax errors.
TEMPLATE_DEBUG = DEBUG
# Emails are sent to these people if the above DEBUG value is False. If you'd
# rather nobody recieve emails, leave this commented out or empty.
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True.
MANAGERS = ADMINS
# The path that contains this settings.py file (no trailing slash).
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Path to the game directory (containing the database file if using sqlite).
GAME_DIR = os.path.join(BASE_PATH, 'game')
# Logging paths
LOG_DIR = os.path.join(GAME_DIR, 'logs')
DEFAULT_LOG_FILE = os.path.join(LOG_DIR, 'evennia.log')
# Path to the src directory containing the bulk of the codebase's code.
SRC_DIR = os.path.join(BASE_PATH, 'src')
# Absolute path to the directory that holds media (no trailing slash).
# Example: "/home/media/media.lawrence.com"
MEDIA_ROOT = os.path.join(GAME_DIR, 'web', 'media')
# Import style path to the directory holding script parents. Must be in the import path.
SCRIPT_IMPORT_PATH = 'game.gamesrc.parents'
# Default parent associated with non-player objects. This starts from where
# the SCRIPT_IMPORT_PATH left off.
SCRIPT_DEFAULT_OBJECT = 'base.basicobject'
# Default parent associated with player objects. This starts from where
# the SCRIPT_IMPORT_PATH left off.
SCRIPT_DEFAULT_PLAYER = 'base.basicplayer'
# Real path to a directory to be searched for batch scripts for the
# batch processor. Specify relative evennia's 'game' directory.
BATCH_IMPORT_PATH = 'gamesrc/world'
# 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3', and 'oracle'.
DATABASE_ENGINE = 'sqlite3'
# Database name, or path to DB file if using sqlite3.
DATABASE_NAME = os.path.join(GAME_DIR, 'evennia.db3')
# Unused for sqlite3
DATABASE_USER = ''
# Unused for sqlite3
DATABASE_PASSWORD = ''
# Empty string defaults to localhost. Not used with sqlite3.
DATABASE_HOST = ''
# Empty string defaults to localhost. Not used with sqlite3.
DATABASE_PORT = ''
# How often the persistent cache will save to disk (in seconds).
CACHE_BACKUP_INTERVAL = 600
# How many words a single command name may have (e.g. 'push button' instead of 'pushbutton')
# (commands with switches can always only have one word in the name, e.g. @sethelp/add)
COMMAND_MAXLEN = 3
## Time units - this defines a useful base for how fast time will run in the game.
# You don't actually have to use this, but it affects the routines in src.gametime.py
# and allows for a convenient measure to determine the current in-game time.
# The time factor dictates if the game world runs faster (timefactor>1) or
# slower (timefactor<1) than the real world.
TIME_FACTOR = 2.0
# The tick is the smallest unit of time in the game. Smallest value is 1.
TIME_TICK = 1.0
# These measures might or might not make sense to the game world.
TIME_MIN_PER_HOUR = 60
TIME_HOUR_PER_DAY = 24
TIME_DAY_PER_WEEK = 7
TIME_WEEK_PER_MONTH = 4
TIME_MONTH_PER_YEAR = 12
## Command aliases
# These are convenient aliases set up when the game is started
# for the very first time. You can add/delete aliases in-game using
# the @setcmdalias command.
COMMAND_ALIASES = {"@desc":"@describe",
"@dest":"@destroy", "@nuke":"@destroy",
"@tel":"@teleport",
"i":"inventory", "inv":"inventory",
"l":"look",
"ex":"examine",
"sa":"say",
"emote":"pose",
"p":"page"}
## Permissions
## The variables in this section are used by each evennia subsystem to tell which permissions to define.
## These variables are called by the respective subsystem ('application' in django lingo) of Evennia. The final
## look of the permission will be 'app.permission', e.g. 'irc.admin.irc_channels'.
## Note that beyond what is listed here, django automatically creates 3 add/change/delete permissions
## for each model defined in each appliction. These are however not used by any default commands in
## game and are filtered out in e.g. @adminperm/list in order to give the admin more overview.
## Note that all variables here must be proper nested tuples of tuples. ( (),(), )
# irc permissions
PERM_IRC = (
('admin_irc_channels', 'May administer IRC channels.'),)
# imc2 permissions
PERM_IMC2 = (
('admin_imc_channels', 'May administer IMC channels.'),)
# general channel permissions
PERM_CHANNELS = (
('emit_commchannel', 'May @cemit over channels.'),
('channel_admin', 'May administer comm channels.'),
('page','May page other users.'),)
# help system access permissions
PERM_HELPSYS = (
("admin_help","May admin the help system"),
("staff_help", "May see staff help topics."),
("add_help", "May add or append to help entries"),
("del_help", "May delete help entries"),)
# handling cache
PERM_CACHE = (
("admin_cache","May admin the cache system"),)
# object manipulation/information permissions
PERM_OBJECTS = (
("teleport","May teleport an object to any location."),
("wipe","May wipe all attributes on an object."),
("modify_attributes","May modify/delete/add attributes to an object."),
("info","May search for and examine objects."),
("create", "May create, copy and destroy objects."),
("dig", "May dig new rooms, open new exits and link them."),
("admin_ownership", "May change ownership of any object."),
("see_dbref","May see an object's dbref."),)
# misc general and admin permissions
PERM_GENPERMS = (
("announce", "May make announcements to everyone."),
("admin_perm", "Can modify individual permissions."),
("admin_group", "Can manage membership in groups."),
("process_control", "May shutdown/restart/reload the game."),
("manage_players", "Can change passwords, siteban, etc."),
("game_info", "Can review game metadata."),
("admin_nostate", "Do not enter states (should be set only temporarily)."),)
## These permissions are not yet used in the default engine.
## ("boot", "May use @boot to kick players"),
## ("chown_all", "Can @chown anything to anyone."),
## ("free_money", "Has infinite money"),
## ("long_fingers", "May get/look/examine etc. from a distance"),
## ("steal", "May give negative money"),
## ("set_hide", "May set themself invisible"),
## ("tel_anywhere", "May @teleport anywhere"),
## ("tel_anyone", "May @teleport anything"),
## ("see_session_data", "May see detailed player session data"),)
# Gathering of all permission tuple groups. This is used by e.g. @adminperm to only show these permissions.
PERM_ALL_DEFAULTS = (PERM_IRC, PERM_IMC2, PERM_CHANNELS, PERM_HELPSYS, PERM_OBJECTS, PERM_GENPERMS)
# If you defined your own tuple groups, add them below.
PERM_ALL_CUSTOM = ()
## Permission Groups
## Permission groups clump the permissions into larger chunks for quick assigning to
## a user (e.g. a builder). Each permission is written on the form app.permission,
## e.g. 'helpsys.view_staff_help'. Each group can contain an arbitrary number of
## permissions. A user is added to a group with the default @admingroup command.
## Superusers are automatically members of all groups.
# A dict defining the groups, on the form {group_name:(perm1,perm2,...),...}
PERM_GROUPS = \
{"Immortals":('irc.admin_irc_channels', 'imc2.admin_imc_channels', 'channels.emit_commchannel',
'channels.channel_admin', 'channels.page', 'helpsys.admin_help',
'helpsys.staff_help', 'helpsys.add_help',
'helpsys.del_help', 'objects.teleport', 'objects.wipe', 'objects.modify_attributes',
'objects.info','objects.create','objects.dig','objects.see_dbref',
'objects.admin_ownership', 'genperms.announce', 'genperms.admin_perm',
'genperms.admin_group', 'genperms.process_control', 'genperms.manage_players',
'genperms.game_info'),
"Wizards": ('irc.admin_irc_channels', 'imc2.admin_imc_channels', 'channels.emit_commchannel',
'channels.channel_admin', 'channels.page', 'helpsys.admin_help',
'helpsys.staff_help', 'helpsys.add_help',
'helpsys.del_help', 'objects.teleport', 'objects.wipe', 'objects.see_dbref',
'objects.modify_attributes', 'objects.info', 'objects.create', 'objects.dig',
'objects.admin_ownership', 'genperms.announce', 'genperms.game_info'),
"Builders":('channels.emit_commchannel', 'channels.page', 'helpsys.staff_help',
'helpsys.add_help', 'helpsys.del_help',
'objects.teleport', 'objects.wipe', 'objects.see_dbref',
'objects.modify_attributes', 'objects.info',
'objects.create', 'objects.dig', 'genperms.game_info'),
"Player Helpers":('channels.emit_commchannel', 'channels.page', 'helpsys.staff_help',
'helpsys.add_help', 'helpsys.del_help'),
"Players":('channels.emit_commchannel', 'channels.page')
}
# By defining a default player group, all players may start with some permissions pre-set.
PERM_DEFAULT_PLAYER_GROUP = "Players"
## Help system
## Evennia allows automatic help-updating of commands by use of the auto-help system
## which use the command's docstrings for documentation, automatically updating it
## as commands are reloaded. Auto-help is a powerful way to keep your help database
## up-to-date, but it will also overwrite manual changes made
## to the help database using other means (@set_help, admin interface etc), so
## for a production environment you might want to turn auto-help off. You can
## later activate auto-help on a per-command basis (e.g. when developing a new command)
## using the auto_help_override argument to add_command().
# activate the auto-help system
HELP_AUTO_ENABLED = True
# Add a dynamically calculated 'See also' footer to help entries
HELP_SHOW_RELATED = True
## Channels
## Your names of various default comm channels for emitting debug- or informative messages.
COMMCHAN_MUD_INFO = 'MUDInfo'
COMMCHAN_MUD_CONNECTIONS = 'MUDConnections'
COMMCHAN_IMC2_INFO = 'MUDInfo'
COMMCHAN_IRC_INFO = 'MUDInfo'
## IMC Configuration
## IMC (Inter-MUD communication) allows for an evennia chat channel that connects
## to people on other MUDs also using the IMC. Your evennia server do *not* have
## to be open to the public to use IMC; it works as a stand-alone chat client.
##
## Copy and paste this section to your game/settings.py file and change the
## values to fit your needs.
##
## Evennia's IMC2 client was developed against MudByte's network. You must
## register your MUD on the network before you can use it, go to
## http://www.mudbytes.net/imc2-intermud-join-network.
##
## Choose 'Other unsupported IMC2 version' from the choices and
## and enter your information there. You have to enter the same
## 'short mud name', 'client password' and 'server password' as you
## define in this file.
## The Evennia discussion channel is on server02.mudbytes.net:9000.
# Change to True if you want IMC active at all.
IMC2_ENABLED = False
# The hostname/ip address of your IMC2 server of choice.
IMC2_SERVER_ADDRESS = 'server02.mudbytes.net'
#IMC2_SERVER_ADDRESS = None
# The port to connect to on your IMC2 server.
IMC2_SERVER_PORT = 9000
#IMC2_SERVER_PORT = None
# This is your game's IMC2 name on the network (e.g. "MyMUD").
IMC2_MUDNAME = None
# Your IMC2 client-side password. Used to authenticate with your network.
IMC2_CLIENT_PW = None
# Your IMC2 server-side password. Used to verify your network's identity.
IMC2_SERVER_PW = None
# Emit additional debugging info to log.
IMC2_DEBUG = False
# This isn't something you should generally change.
IMC2_PROTOCOL_VERSION = '2'
## IRC config. This allows your evennia channels to connect to an external IRC
## channel. Evennia will connect under a nickname that then echoes what is
## said on the channel to IRC and vice versa.
## Obs - make sure the IRC network allows bots.
#Activate the IRC bot.
IRC_ENABLED = False
#Which IRC network (e.g. irc.freenode.net)
IRC_NETWORK = "irc.freenode.net"
#Which IRC port to connect to (e.g. 6667)
IRC_PORT = 6667
#Which channel on the network to connect to (including the #)
IRC_CHANNEL = ""
#Under what nickname should Evennia connect to the channel
IRC_NICKNAME = ""
# Local time zone for this installation. All choices can be found here:
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
TIME_ZONE = 'America/New_York'
# Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
LANGUAGE_CODE = 'en-us'
# It's safe to dis-regard this, as it's a Django feature we only half use as a
# dependency, not actually what it's primarily meant for.
SITE_ID = 1
# The age for sessions.
# Default: 1209600 (2 weeks, in seconds)
SESSION_COOKIE_AGE = 1209600
# Session cookie domain
# Default: None
# SESSION_COOKIE_DOMAIN = None
# The name of the cookie to use for sessions.
# Default: 'sessionid'
SESSION_COOKIE_NAME = 'sessionid'
# Should the session expire when the browser closes?
# Default: False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = False
# If you'd like to serve media files via Django (strongly not recommended!),
# set SERVE_MEDIA to True. This is appropriate on a developing site, or if
# you're running Django's built-in test server. Normally you want a webserver
# that is optimized for serving static content to handle media files (apache,
# lighttpd).
SERVE_MEDIA = True
# The master urlconf file that contains all of the sub-branches to the
# applications.
ROOT_URLCONF = 'game.web.urls'
# Where users are redirected after logging in via contribu.auth.login.
LOGIN_REDIRECT_URL = '/'
# Where to redirect users when using the @login_required decorator.
LOGIN_URL = '/accounts/login'
# Where to redirect users who wish to logout.
LOGOUT_URL = '/accounts/login'
# URL that handles the media served from MEDIA_ROOT.
# Example: "http://media.lawrence.com"
MEDIA_URL = '/media/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/amedia/'
# Make this unique, and don't share it with anybody.
# NOTE: If you change this after creating any accounts, your users won't be
# able to login, as the SECRET_KEY is used to salt passwords.
SECRET_KEY = 'changeme!(*#&*($&*(#*(&SDFKJJKLS*(@#KJAS'
# The name of the currently selected web template. This corresponds to the
# directory names shown in the webtemplates directory.
ACTIVE_TEMPLATE = 'prosimii'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(GAME_DIR, "web", "html", ACTIVE_TEMPLATE),
)
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
)
# MiddleWare are semi-transparent extensions to Django's functionality.
# see http://www.djangoproject.com/documentation/middleware/ for a more detailed
# explanation.
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.doc.XViewMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
)
# Context processors define context variables, generally for the template
# system to use.
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.i18n',
'django.core.context_processors.request',
'django.core.context_processors.auth',
'django.core.context_processors.media',
'django.core.context_processors.debug',
'game.web.apps.website.webcontext.general_context',
)
# Global and Evennia-specific apps. This ties everything together so we can
# refer to app models and perform DB syncs.
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.sites',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'django.contrib.admindocs',
'django.contrib.flatpages',
'src.config',
'src.objects',
'src.channels',
'src.imc2',
'src.irc',
'src.helpsys',
'src.genperms',
'src.cache',
'game.web.apps.news',
'game.web.apps.website',
)
"""
A tuple of strings representing all of the Evennia (IE: non-custom) command
modules that are used at the login screen in the UNLOGGED command table. Do
not modify this directly, add your custom command modules to
CUSTOM_UNLOGGED_COMMAND_MODULES.
"""
UNLOGGED_COMMAND_MODULES = (
'src.commands.unloggedin',
)
"""
Add your custom command modules under game/gamesrc/commands and to this list.
These will be loaded after the Evennia codebase modules, meaning that any
duplicate command names will be overridden by your version.
"""
CUSTOM_UNLOGGED_COMMAND_MODULES = ()
"""
A tuple of strings representing all of the Evennia (IE: non-custom)
command modules. Do not modify this directly, add your custom command
modules to CUSTOM_COMMAND_MODULES.
"""
COMMAND_MODULES = (
'src.commands.comsys',
'src.commands.general',
'src.commands.info',
'src.commands.objmanip',
'src.commands.paging',
'src.commands.parents',
'src.commands.privileged',
'src.commands.search',
'src.commands.imc2',
'src.commands.irc',
'src.commands.batchprocess'
)
"""
Add your custom command modules under game/gamesrc/commands and to this list.
These will be loaded after the Evennia codebase modules, meaning that any
duplicate command names will be overridden by your version.
"""
CUSTOM_COMMAND_MODULES = ()
# If django_extensions is present, import it and install it. Otherwise fail
# silently.
try:
import django_extensions
INSTALLED_APPS = INSTALLED_APPS + ('django_extensions',)
except ImportError:
pass

View file

@ -1,49 +0,0 @@
import os
# Object type keys, DO NOT CHANGE!
OTYPE_NOTHING = 0
OTYPE_PLAYER = 1
OTYPE_ROOM = 2
OTYPE_THING = 3
OTYPE_EXIT = 4
OTYPE_GOING = 5
OTYPE_GARBAGE = 6
# Do not mess with the default types (0-5). This is passed to the Object
# model's 'choices' argument.
OBJECT_TYPES = (
(OTYPE_NOTHING, 'NOTHING'),
(OTYPE_PLAYER, 'PLAYER'),
(OTYPE_ROOM, 'ROOM'),
(OTYPE_THING, 'THING'),
(OTYPE_EXIT, 'EXIT'),
(OTYPE_GOING, 'GOING'),
(OTYPE_GARBAGE, 'GARBAGE'),
)
# These attribute names can't be modified by players.
NOSET_ATTRIBS = ["MONEY", "ALIAS", "LASTPAGED", "__CHANLIST", "LAST",
"__PARENT", "LASTSITE", "LOCKS"]
# These attributes don't show up on objects when examined.
HIDDEN_ATTRIBS = ["__CHANLIST", "__PARENT", "LOCKS"]
# Server version number.
REVISION = os.popen('svnversion .', 'r').readline().strip()
if not REVISION:
REVISION = "Unknown"
# Clip out the SVN keyword information
EVENNIA_VERSION = 'Alpha ' + REVISION
# The message to show when the user lacks permissions for something.
NOPERMS_MSG = "You do not have the necessary permissions to do that."
# Message seen when object doesn't control the other object.
NOCONTROL_MSG = "You don't have authority over that object."
# Default descs when creating new objects
DESC_PLAYER = "An average person."
DESC_ROOM = "There is nothing special about this place."
DESC_THING = "You see nothing special."
DESC_EXIT = "This is an exit out of here."

View file

@ -1,196 +0,0 @@
"""
Holds the global events scheduled in scheduler.py.
Events are sub-classed from IntervalEvent (which is not to be used directly).
Create your sub-class, call src.scheduler.add_event(YourEventClass()) to add
it to the global scheduler.
Use @ps to view the event list.
The events set with the member variable persistent equal to True will be
stored in persistent cache and will survive server downtime.
"""
import time
import copy
from twisted.internet import task
from django.conf import settings
import session_mgr
from src import scheduler
from src import defines_global
from src.objects.models import Object
from src.cache import cache
from src import logger
from src import gametime
class IntervalEvent(object):
"""
Represents an event that is triggered periodically. Sub-class this and
fill in the stub function.
self.repeats decides if this event will fire indefinitely or only a
certain number of times.
"""
def __init__(self, description="IntervalEvent"):
"""
Executed when the class is instantiated.
"""
# This is a globally unique ID of this event. If None, a new one will
# be allocated when the event is added to the scheduler.
self.pid = None
# This is set to prevent a Nonetype exception on @ps before the
# event is fired for the first time.
self.time_last_executed = time.time()
# This used to describe the event in @ps listings.
self.description = description
# An interval (in seconds) for execution.
self.interval = None
# How many times to repeat this event.
# None : indefinitely,
# positive integer : number of times
self.repeats = None
# A reference to the task.LoopingCall object.
self.looped_task = None
# If true, the event definition will survive a reboot.
self.persistent = False
def __getstate__(self):
"""
Used by pickle.
"""
edict = copy.copy(self.__dict__)
edict["looped_task"] = None
edict["pid"] = None
return edict
def __unicode__(self):
"""
String representation of the event.
"""
return self.description
def __eq__(self, event2):
"""
Handles comparison operations.
"""
return self.pid == event2.pid
def __hash__(self):
"""
Used for dictionary key comparisons.
"""
return self.pid
def start_event_loop(self):
"""
Called to start up the event loop when the event is added to the
scheduler.
"""
# Set the call-back function for the task to trigger_event, but pass
# a reference to the event function.
self.looped_task = task.LoopingCall(self.fire_event)
# Start the task up with the specified interval.
self.looped_task.start(self.interval, now=False)
def stop_event_loop(self):
"""
Called to stop the event loop when the event is removed from the
scheduler.
"""
self.looped_task.stop()
def event_function(self):
"""
### Over-ride this in your sub-class. ###
"""
pass
def get_nextfire(self):
"""
Returns a value in seconds when the event is going to fire off next.
"""
return max(0, (self.time_last_executed + self.interval) - time.time())
def set_lastfired(self):
"""
Sets the timestamp (int) that the event was last fired.
"""
self.time_last_executed = time.time()
def fire_event(self):
"""
Set the last ran stamp and fire off the event.
Stop repeating if number of repeats have been achieved.
"""
self.set_lastfired()
self.event_function()
if self.repeats != None:
self.repeats -= 1
if self.repeats <= 0 and self.pid != None:
scheduler.del_event(self.pid)
# Some default server events
class IEvt_Check_Sessions(IntervalEvent):
"""
Event: Check all of the connected sessions.
"""
def __init__(self):
super(IEvt_Check_Sessions, self).__init__()
#self.name = 'IEvt_Check_Sessions'
self.interval = 60
self.description = "Session consistency checks."
self.persistent = True
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
session_mgr.check_all_sessions()
class IEvt_Destroy_Objects(IntervalEvent):
"""
Event: Clean out all objects marked for destruction.
"""
def __init__(self):
super(IEvt_Destroy_Objects, self).__init__()
#self.name = 'IEvt_Destroy_Objects'
self.interval = 1800
self.description = "Clean out objects marked for destruction."
self.persistent = True
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
going_objects = Object.objects.filter(type__exact=defines_global.OTYPE_GOING)
for obj in going_objects:
obj.delete()
class IEvt_Sync_PCache(IntervalEvent):
"""
Event: Sync the persistent cache to with the database.
This is an important event since it also makes sure to
update the time stamp.
"""
def __init__(self):
super(IEvt_Sync_PCache, self).__init__()
#self.name = 'IEvt_Sync_PCache'
self.interval = settings.CACHE_BACKUP_INTERVAL
self.description = "Backup pcache to disk."
self.persistent = True
def event_function(self):
"""
This is the function that is fired every self.interval seconds.
"""
#infostring = "Syncing time, events and persistent cache to disk."
#logger.log_infomsg(infostring)
# save the current time
gametime.time_save()
# update the event database to pcache
ecache = [event for event in scheduler.SCHEDULE
if event.persistent]
cache.set_pcache("_persistent_event_cache", ecache)
# save pcache to disk.
cache.save_pcache()

View file

@ -1,11 +0,0 @@
"""
This module contains exceptions used throughout the server
"""
class GenericException(Exception):
"""
The custom exception class from which all other exceptions are derived.
"""
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)

View file

@ -1,30 +0,0 @@
"""
Everything related to flags and flag management.
"""
import defines_global
# This is a list of flags that the server actually uses. Anything not in this
# list is a custom flag.
SERVER_FLAGS = ["CONNECTED", "DARK", "FLOATING", "GAGGED", "HAVEN", "OPAQUE",
"SAFE", "SLAVE", "SUSPECT", "TRANSPARENT"]
# These flags are not saved.
NOSAVE_FLAGS = ["CONNECTED"]
# These flags can't be modified by players.
NOSET_FLAGS = ["CONNECTED"]
def is_unsavable_flag(flagname):
"""
Returns TRUE if the flag is an unsavable flag.
"""
return flagname.upper() in NOSAVE_FLAGS
def is_modifiable_flag(flagname):
"""
Check to see if a particular flag is modifiable.
"""
if flagname.upper() not in NOSET_FLAGS:
return True
else:
return False

View file

@ -1,133 +0,0 @@
"""
The gametime module handles the global passage of time in the mud.
It also supplies some useful methods to convert between
in-mud time and real-worl time.
"""
from django.conf import settings
import time as time_module
from src.cache import cache
# Speed-up factor of the in-game time compared
# to real time.
TIMEFACTOR = settings.TIME_FACTOR
# Common real-life time measures, in seconds.
# You should normally not change these.
REAL_TICK = settings.TIME_TICK #This is the smallest time unit (minimum 1s)
REAL_MIN = 60.0 # seconds per minute in real world
# Game-time units, in real-life seconds. These are supplied as
# a convenient measure for determining the current in-game time,
# e.g. when defining events. The words month, week and year can
# of course be translated into any suitable measures.
TICK = REAL_TICK / TIMEFACTOR
MIN = REAL_MIN / TIMEFACTOR
HOUR = MIN * settings.TIME_MIN_PER_HOUR
DAY = HOUR * settings.TIME_HOUR_PER_DAY
WEEK = DAY * settings.TIME_DAY_PER_WEEK
MONTH = WEEK * settings.TIME_WEEK_PER_MONTH
YEAR = MONTH * settings.TIME_MONTH_PER_YEAR
# Access routines
def time(currtime=None):
"""
Find the current in-game time (in seconds) since the start of the mud.
The value returned from this function can be used to track the 'true'
in-game time since only the time the game has actually been active will
be adding up (ignoring downtimes).
Obs: depending on how often the persistent cache is saved to disk
(this is defined in the config file), there might be some discrepancy
here after a server crash, notably that some time will be 'lost' (i.e.
the time since last backup). If this is a concern, consider saving
the cache more often.
currtime : An externally calculated current time to compare with.
This is used by Evennia to make sure to sync the game
time to a new real-world timestamp
"""
# saved real world timestamp (seconds since 1970 or so)
time0 = cache.get_pcache("_game_time0")
# saved game time at real-world time time0
time1 = cache.get_pcache("_game_time")
if currtime:
return time1 + (currtime - time0)
else:
return time1 + (time_module.time() - time0)
def gametime_to_realtime(secs=0, mins=0, hrs=0, days=0,
weeks=0, months=0, yrs=0):
"""
This method helps to figure out the real-world time it will take until a in-game time
has passed. E.g. if an event should take place a month later in-game, you will be able
to find the number of real-world seconds this corresponds to (hint: Interval events deal
with real life seconds).
Example:
gametime_to_realtime(days=2) -> number of seconds in real life from now after which
2 in-game days will have passed.
"""
stot = secs/TIMEFACTOR + mins*MIN + hrs*HOUR + \
days*DAY + weeks*WEEK + months*MONTH + yrs*YEAR
return stot
def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0,
weeks=0, months=0, yrs=0):
"""
This method calculates how large an in-game time a real-world time interval would
correspond to. This is usually a lot less interesting than the other way around.
Example:
realtime_to_gametime(days=2) -> number of game-world seconds
corresponding to 2 real days.
"""
stot = TIMEFACTOR * (secs + mins*60 + hrs*3600 + days*86400 + \
weeks*604800 + months*2419200 + yrs*29030400)
return stot
# Time administration routines
def time_init():
"""
Called by Evennia's initial startup; this should normally not be called from
a running game, it resets the global in-game time!
"""
time0 = time_module.time()
time1 = 0
cache.set_pcache("_game_time0", time0)
cache.set_pcache("_game_time", time1)
cache.save_pcache()
def time_save():
"""
Force a save of the current time to persistent cache.
Shutting down the server from within the mud will
automatically call this routine.
"""
time0 = time_module.time()
time1 = time(time0)
cache.set_pcache("_game_time0", time0)
cache.set_pcache("_game_time", time1)
cache.save_pcache()
def time_last_sync():
"""
Calculates the time since the system was last synced to disk. This e.g. used
to adjust event counters for offline time, resulting in a maximum error being
the time between backups.
"""
# Real-world timestamp for last backup
time0 = cache.get_pcache("_game_time0")
# The correction factor is the time
# since last backup + downtime.
return time_module.time() - time0

View file

@ -1,10 +0,0 @@
from django.db import models
from django.conf import settings
class GenericPerm(models.Model):
"""
This is merely a container class for some generic permissions that don't
fit under a particular module.
"""
class Meta:
permissions = settings.PERM_GENPERMS

View file

@ -1 +0,0 @@
# Create your views here.

12
src/help/admin.py Normal file
View file

@ -0,0 +1,12 @@
from django.contrib import admin
from src.help.models import HelpEntry
class HelpEntryAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_help_category', 'db_permissions')
list_display_links = ('id', 'db_key')
search_fields = ['^db_key', 'db_entrytext']
ordering = ['db_help_category', 'db_key']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(HelpEntry, HelpEntryAdmin)

86
src/help/manager.py Normal file
View file

@ -0,0 +1,86 @@
"""
Custom manager for HelpEntry objects.
"""
from django.db import models
from src.utils import logger
class HelpEntryManager(models.Manager):
"""
This implements different ways to search for help entries.
"""
def find_topicmatch(self, topicstr, exact=False):
"""
Searches for matching topics based on player's input.
"""
if topicstr.isdigit():
return self.filter(id=topicstr)
topics = self.filter(db_key__iexact=topicstr)
if not topics and not exact:
topics = self.filter(db_key__istartswith=topicstr)
if not topics:
topics = self.filter(db_key__icontains=topicstr)
return topics
def find_apropos(self, topicstr):
"""
Do a very loose search, returning all help entries containing
the search criterion in their titles.
"""
return self.filter(db_key__icontains=topicstr)
def find_topicsuggestions(self, topicstr):
"""
Do a fuzzy match, preferably within the category of the
current topic.
"""
topics = self.find_apropos(topicstr)
# we need to clean away the given help entry.
return [topic for topic in topics
if topic.key.lower() != topicstr.lower()]
def find_topics_with_category(self, help_category):
"""
Search topics having a particular category
"""
topics = self.filter(db_help_category__iexact=help_category)
return topics
def get_all_topics(self):
"""
Return all topics.
"""
return self.all()
def get_all_categories(self, pobject):
"""
Return all defined category names with at least one
topic in them.
"""
return list(set(topic.help_category for topic in self.all()))
def all_to_category(self, default_category):
"""
Shifts all help entries in database to default_category.
This action cannot be reverted. It is used primarily by
the engine when importing a default help database, making
sure this ends up in one easily separated category.
"""
topics = self.all()
for topic in topics:
topic.help_category = default_category
topic.save()
string = "Help database moved to category %s" % default_category
logger.log_infomsg(string)
def search_help(self, ostring, help_category=None):
"""
Retrieve a search entry object.
ostring - the help topic to look for
category - limit the search to a particular help topic
"""
ostring = ostring.strip().lower()
help_entries = self.filter(db_topicstr=ostring)
if help_category:
help_entries.filter(db_help_category=help_category)
return help_entries

158
src/help/models.py Normal file
View file

@ -0,0 +1,158 @@
"""
Models for the help system.
The database-tied help system is only half of Evennia's help
functionality, the other one being the auto-generated command help
that is created on the fly from each command's __doc__ string. The
persistent database system defined here is intended for all other
forms of help that do not concern commands, like information about the
game world, policy info, rules and similar.
"""
from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.help.manager import HelpEntryManager
from src.utils import ansi
from src.utils.utils import is_iter
#------------------------------------------------------------
#
# HelpEntry
#
#------------------------------------------------------------
class HelpEntry(SharedMemoryModel):
"""
A generic help entry.
An HelpEntry object has the following properties defined:
key - main name of entry
help_category - which category entry belongs to (defaults to General)
entrytext - the actual help text
permissions - perm strings
"""
#
# HelpEntry Database Model setup
#
#
# These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
# title of the help
db_key = models.CharField(max_length=255, unique=True)
# help category
db_help_category = models.CharField(max_length=255, default="General")
# the actual help entry text, in any formatting.
db_entrytext = models.TextField(blank=True)
# a string of permissionstrings, separated by commas.
db_permissions = models.CharField(max_length=255, blank=True)
# (deprecated, only here to allow MUX helpfile load (don't use otherwise)).
# TODO: remove this when not needed anymore.
db_staff_only = models.BooleanField(default=False)
# Database manager
objects = HelpEntryManager()
class Meta:
"Define Django meta options"
verbose_name = "Help Entry"
verbose_name_plural = "Help Entries"
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
"Getter. Allows for value = self.key"
return self.db_key
#@key.setter
def key_set(self, value):
"Setter. Allows for self.key = value"
self.db_key = value
self.save()
#@key.deleter
def key_del(self):
"Deleter. Allows for del self.key. Deletes entry."
self.delete()
key = property(key_get, key_set, key_del)
# help_category property (wraps db_help_category)
#@property
def help_category_get(self):
"Getter. Allows for value = self.help_category"
return self.db_help_category
#@help_category.setter
def help_category_set(self, value):
"Setter. Allows for self.help_category = value"
self.db_help_category = value
self.save()
#@help_category.deleter
def help_category_del(self):
"Deleter. Allows for del self.help_category"
self.db_help_category = "General"
self.save()
help_category = property(help_category_get, help_category_set, help_category_del)
# entrytext property (wraps db_entrytext)
#@property
def entrytext_get(self):
"Getter. Allows for value = self.entrytext"
return self.db_entrytext
#@entrytext.setter
def entrytext_set(self, value):
"Setter. Allows for self.entrytext = value"
self.db_entrytext = value
self.save()
#@entrytext.deleter
def entrytext_del(self):
"Deleter. Allows for del self.entrytext"
self.db_entrytext = ""
self.save()
entrytext = property(entrytext_get, entrytext_set, entrytext_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions."
return [perm.strip() for perm in self.db_permissions.split(',')]
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
self.db_permissions = ""
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
#
#
# HelpEntry main class methods
#
#
def __str__(self):
return self.key
def __unicode__(self):
return u'%s' % self.key
def get_entrytext_ingame(self):
"""
Gets the entry text for in-game viewing.
"""
return ansi.parse_ansi(self.entrytext)

File diff suppressed because one or more lines are too long

View file

@ -1,9 +0,0 @@
from django.contrib import admin
from src.helpsys.models import HelpEntry
class HelpEntryAdmin(admin.ModelAdmin):
list_display = ('id', 'topicname', 'staff_only')
list_display_links = ('id', 'topicname')
list_filter = ('staff_only',)
search_fields = ['topicname', 'entrytext']
admin.site.register(HelpEntry, HelpEntryAdmin)

View file

@ -1 +0,0 @@
[{"pk": 1, "model": "helpsys.helpentry", "fields": {"entrytext": " This is the TinyMUX online help facility.\r\n\r\n Notes on help descriptions:\r\n [text] - Text enclosed in []'s is optional. The []'s are never typed\r\n in as part of the command.\r\n <parameter> - Information parameter for a command. The <>'s are\r\n never typed in as part of the command.\r\n\r\n - Syntax of help command:\r\n help [<command>]\r\n\r\n - To get a list of TinyMUX topics:\r\n help topics\r\n\r\n - To get a list of Comsystem commands:\r\n help comsys\r\n\r\n - To get a list of TinyMUX Commands:\r\n help commands (or @list commands)\r\n\r\n Some of the configuration shown in the help.txt might not be the same as\r\n the configuration of this MUX. If you notice any errors, contact an admin.\r\n", "topicname": "Help Index", "staff_only": false}}]

View file

@ -1,415 +0,0 @@
"""
Support functions for the help system.
Allows adding help to the data base from inside the mud as
well as creating auto-docs of commands based on their doc strings.
The system supports help-markup for multiple help entries as well
as a dynamically updating help index.
"""
import textwrap
from django.conf import settings
from src.helpsys.models import HelpEntry
from src import logger
from src import defines_global
class EditHelp(object):
"""
This sets up an object able to perform normal editing
operations on the help database.
"""
def __init__(self, indent=4, width=70):
"""
We check if auto-help is active or not and
set some formatting options.
"""
self.indent = indent # indentation of help text
self.width = width # width of help text
def format_help_text(self, help_text):
"""
This formats the help entry text for proper left-side indentation.
The first line is adjusted to the proper indentation and the
subsequent lines are then adjusted proportionally to the first;
so indentation relative this first line remains intact.
"""
lines = help_text.expandtabs().splitlines()
# strip empty lines above and below the text
while True:
if lines and not lines[0].strip():
lines.pop(0)
else:
break
while True:
if lines and not lines[-1].strip():
lines.pop()
else:
break
if not lines:
return ""
# produce a list of the indentations of each line initially
indentlist = [len(line) - len(line.lstrip()) for line in lines]
# use the first line to set the shift
lineshift = indentlist[0] - self.indent
# shift everything to the left
indentlist = [max(self.indent, indent-lineshift) for indent in indentlist]
trimmed = []
for il, line in enumerate(lines):
indentstr = " " * indentlist[il]
trimmed.append("%s%s" % (indentstr, line.strip()))
return "\n".join(trimmed)
def parse_markup_header(self, subtopic_header):
"""
The possible markup headers for splitting the help into sections are:
[[TopicTitle]]
[[TopicTitle,category]]
[[TopicTitle(perm1,perm2)]]
[[TopicTitle,category(perm1,perm2)]]
"""
subtitle = ""
subcategory = ""
subpermissions = ()
#identifying the header parts. The header can max have three parts:
# topicname, category (perm1,perm2,...)
try:
# find the permission tuple
lindex = subtopic_header.index('(')
rindex = subtopic_header.index(')')
if lindex < rindex:
permtuple = subtopic_header[lindex+1:rindex]
subpermissions = tuple([p.strip()
for p in permtuple.split(',')])
subtopic_header = subtopic_header[:lindex]
except ValueError:
# no permission tuple found
pass
# see if we have a name, category pair.
try:
subtitle, subcategory = subtopic_header.split(',')
subtitle, subcategory = subtitle.strip(), subcategory.strip()
except ValueError:
subtitle = subtopic_header.strip()
# we are done, return a tuple with the results
return ( subtitle, subcategory, subpermissions )
def format_help_entry(self, helptopic, category, helptext, permissions=None):
"""
helptopic (string) - name of the full help entry
helptext (string) - the help entry (may contain sections)
permissions (tuple) - tuple with permission/group names
defined for the entire help entry.
(markup permissions override those)
Handles help markup in order to split help into subsections.
These markup markers will be assumed to start a new line, regardless
of where they are located in the help entry. If no permission string
tuple and/or category is given, the overall permission/category of
the entire help entry is used.
"""
# sanitize input
topics = []
if not helptext:
return topics
if '[[' not in helptext:
formatted_text = self.format_help_text(helptext)
topics.append((helptopic, category,
formatted_text, permissions))
return topics
subtopics = helptext.split('[[')
if subtopics[0]:
# the very first entry (before any markup) is the normal
# help entry for the helptopic at hand.
formatted_text = self.format_help_text(subtopics[0])
topics.append((helptopic, category, formatted_text, permissions))
for subtopic in subtopics[1:]:
# handle all extra topics designated with markup
try:
subtopic_header, subtopic_text = subtopic.split(']]', 1)
except ValueError:
# if we have no ending, the entry is malformed and
# we ignore this entry (better than overwriting
# something in the database).
logger.log_errmsg("Malformed help markup in %s: '%s'\n (missing end ']]' )" % \
(helptopic, subtopic))
continue
# parse and format the help entry parts
subtopic_header = self.parse_markup_header(subtopic_header)
if not subtopic_header[0]:
# we require a topic title.
logger.log_errmsg("Malformed help markup in '%s': Missing title." % subtopic_header)
return
# parse the header and use defaults
subtopic_name = subtopic_header[0]
subtopic_category = subtopic_header[1]
subtopic_text = self.format_help_text(subtopic_text)
subtopic_permissions = subtopic_header[2]
if not subtopic_category:
# no category set; inherit from main topic
subtopic_category = category
if not subtopic_permissions:
# no permissions set; inherit from main topic
subtopic_permissions = permissions
# We have a finished topic, add it to the list as a topic tuple.
topics.append((subtopic_name, subtopic_category,
subtopic_text, subtopic_permissions))
return topics
def create_help(self, newtopic):
"""
Add a help entry to the database, replace an old one if it exists.
topic (tuple) - this is a formatted tuple of data as prepared
by format_help_entry, on the form (title, category, text, (perm_tuple))
"""
#sanity checks;
topicname = newtopic[0]
category = newtopic[1]
entrytext = newtopic[2]
permissions = newtopic[3]
if not (topicname or entrytext):
# don't create anything if there we
# are missing vital parts
return
if not category:
# this will force the default
category = "General"
if permissions:
# the permissions tuple might be mangled;
# make sure we build a string properly.
if type(permissions) != type(tuple()):
permissions = "%s" % permissions
else:
permissions = ", ".join(permissions)
else:
permissions = ""
# check if the help topic already exist.
oldtopic = HelpEntry.objects.filter(topicname__iexact=newtopic[0])
if oldtopic:
#replace an old help file
topic = oldtopic[0]
topic.category = category
topic.entrytext = entrytext
topic.canview = permissions
topic.save()
else:
#we have a new topic - create a new help object
new_entry = HelpEntry(topicname=topicname,
category=category,
entrytext=entrytext,
canview=permissions)
new_entry.save()
def add_help_auto(self, topicstr, category, entrytext, permissions=()):
"""
This is used by the auto_help system to add help one or more
help entries to the system.
"""
# sanity checks
if permissions and type(permissions) != type(tuple()):
string = "Auto-Help: malformed perm_tuple %s: %s -> %s (fixed)" % \
(topicstr,permissions, (permissions,))
logger.log_errmsg(string)
permissions = (permissions,)
# identify markup and do nice formatting as well as eventual
# related entries to the help entries.
#logger.log_infomsg("auto-help in: %s %s %s %s" % (topicstr, category, entrytext, permissions))
topics = self.format_help_entry(topicstr, category,
entrytext, permissions)
#logger.log_infomsg("auto-help: %s -> %s" % (topicstr,topics))
# create the help entries:
if topics:
for topic in topics:
self.create_help(topic)
def add_help_manual(self, pobject, topicstr, category,
entrytext, permissions=(), force=False):
"""
This is used when a player wants to add a help entry to the database
manually (most often from inside the game)
force - this is given by the player and forces an overwrite also if the
entry already exists or there are multiple similar matches to
the entry.
"""
# permission check:
if not (pobject.is_superuser() or pobject.has_perm("helpsys.add_help")):
pobject.emit_to(defines_global.NOPERMS_MSG)
return None
# do a more fuzzy search to warn in case in case we are misspelling.
topic = HelpEntry.objects.find_topicmatch(pobject, topicstr)
if topic and not force:
return topic
self.add_help_auto(topicstr, category, entrytext, permissions)
pobject.emit_to("Added/appended help topic '%s'." % topicstr)
def del_help_auto(self, topicstr):
"""
Delete a help entry from the data base. Automatic version.
"""
topic = HelpEntry.objects.filter(topicname__iexact=topicstr)
if topic:
topic[0].delete()
def del_help_manual(self, pobject, topicstr):
"""
Deletes an entry from the database. Interactive version.
Note that it makes no sense to delete auto-added help entries this way since
they will be re-added on the next @reload. This is mostly useful for cleaning
the database from doublet or orphaned entries, or when auto-help is turned off.
"""
# find topic with permission checks
if not (pobject.is_superuser() or pobject.has_perm("helpsys.del_help")):
pobject.emit_to(defines_global.NOPERMS_MSG)
return None
topic = HelpEntry.objects.find_topicmatch(pobject, topicstr)
if not topic or len(topic) > 1:
return topic
# we have an exact match. Delete topic.
topic[0].delete()
pobject.emit_to("Help entry '%s' deleted." % topicstr)
def homogenize_database(self, category):
"""
This sets the entire help database to one category.
It can be used to mark an initially loaded help database
in a particular category, for later filtering.
In evennia dev version, this is done with MUX help database.
"""
entries = HelpEntry.objects.all()
for entry in entries:
entry.category = category
entry.save()
logger.log_infomsg("Help database homogenized to category %s" % category)
def autoclean_database(self, topiclist):
"""
This syncs the entire help database against a reference topic
list, deleting non-used or duplicate help entries that can be
the result of auto-help misspellings etc.
"""
pass
class ViewHelp(object):
"""
This class contains ways to view the
help database in a dynamical fashion.
"""
def __init__(self, indent=4, width=78, category_cols=4, entry_cols=6):
"""
indent (int) - number of spaces to indent tables with
width (int) - width of index tables
category_cols (int) - number of collumns per row for
category tables
entry_cols (int) - number of collumns per row for help entries
"""
self.width = width
self.indent = indent
self.category_cols = category_cols
self.entry_cols = entry_cols
self.show_related = settings.HELP_SHOW_RELATED
def make_table(self, items, cols):
"""
This takes a list of string items and displays them in collumn order,
(sorted horizontally-first), ie
A A A A
A B B B
B B C C
C C
cols is the number of collumns to format.
"""
items.sort()
if not items or not cols:
return []
length = len(items)
# split the list into sublists of length cols
rows = [items[i:i+cols] for i in xrange(0, length, cols)]
# build the table
string = ""
for row in rows:
string += self.indent * " " + ", ".join(row) + "\n"
return string
def index_full(self, pobject):
"""
This lists all available topics in the help index,
ordered after category.
The MUX category is not shown, it is for development
reference only.
"""
entries = HelpEntry.objects.all()
categories = [e.category for e in entries if e.category != 'MUX']
categories = list(set(categories)) # make list unique
categories.sort()
table = ""
for category in categories:
topics = [e.topicname.lower() for e in entries.filter(category__iexact=category)
if e.can_view(pobject)]
# pretty-printing the list
header = "--- Topics in category %s:" % category
nl = self.width - len(header)
if not topics:
text = self.indent*" " + "[There are no topics relevant to you in this category.]\n\r"
else:
text = self.make_table(topics, self.entry_cols)
table += "\r\n%s%s\n\r\n\r%s" % (header, "-"*nl, text)
return table
def index_categories(self):
"""
This lists all categories defined in the help index.
"""
entries = HelpEntry.objects.all()
categories = [e.category for e in entries]
categories = list(set(categories)) # make list unique
return self.make_table(categories, self.category_cols)
def index_category(self, pobject, category):
"""
List the help entries within a certain category
"""
entries = HelpEntry.objects.find_topics_with_category(pobject, category)
if not entries:
return []
# filter out those we can actually view
helptopics = [e.topicname.lower() for e in entries if e.can_view(pobject)]
if not helptopics:
# we don't have permission to view anything in this category
return " [There are no topics relevant to you in this category.]\n\r"
return self.make_table(helptopics, self.entry_cols)
def suggest_help(self, pobject, topic):
"""
This goes through the help database, searching for relatively
close matches to this topic. If those are found, they are
added as a nice footer to the end of the topic entry.
"""
if not self.show_related:
return None
topicname = topic.topicname
return HelpEntry.objects.find_topicsuggestions(pobject, topicname)
# Object instances
edithelp = EditHelp(indent=3,
width=80)
viewhelp = ViewHelp(indent=3,
width=80,
category_cols=4,
entry_cols=4)

View file

@ -1,47 +0,0 @@
"""
Custom manager for HelpEntry objects.
"""
from django.db import models
class HelpEntryManager(models.Manager):
"""
This implements different ways to search for help entries.
"""
def find_topicmatch(self, pobject, topicstr, exact=False):
"""
Searches for matching topics based on player's input.
"""
if topicstr.isdigit():
return self.filter(id=topicstr)
t_query = self.filter(topicname__iexact=topicstr)
if not t_query and not exact:
t_query = self.filter(topicname__istartswith=topicstr)
# check permissions
t_query = [topic for topic in t_query if topic.can_view(pobject)]
return t_query
def find_apropos(self, pobject, topicstr):
"""
Do a very loose search, returning all help entries containing
the search criterion in their titles.
"""
topics = self.filter(topicname__icontains=topicstr)
return [topic for topic in topics if topic.can_view(pobject)]
def find_topicsuggestions(self, pobject, topicstr):
"""
Do a fuzzy match, preferably within the category of the
current topic.
"""
topics = self.find_apropos(pobject, topicstr)
# we need to clean away the given help entry.
return [topic for topic in topics
if topic.topicname.lower() != topicstr.lower()]
def find_topics_with_category(self, pobject, category):
"""
Search topics having a particular category
"""
t_query = self.filter(category__iexact=category)
return [topic for topic in t_query if topic.can_view(pobject)]

View file

@ -1,63 +0,0 @@
"""
Models for the help system.
"""
from django.db import models
from django.conf import settings
from src import ansi
from src.helpsys.managers import HelpEntryManager
class HelpEntry(models.Model):
"""
A generic help entry.
"""
topicname = models.CharField(max_length=255, unique=True)
category = models.CharField(max_length=255, default="General")
canview = models.CharField(max_length=255, blank=True)
entrytext = models.TextField(blank=True)
#deprecated, only here to allow MUX helpfile load.
staff_only = models.BooleanField(default=False)
objects = HelpEntryManager()
class Meta:
"""
Permissions here defines access to modifying help
entries etc, not which entries can be viewed (that
is controlled by the canview field).
"""
verbose_name_plural = "Help entries"
ordering = ['topicname']
permissions = settings.PERM_HELPSYS
def __str__(self):
return self.topicname
def get_topicname(self):
"""
Returns the topic's name.
"""
return self.topicname
def get_category(self):
"""
Returns the category of this help entry.
"""
return self.category
def can_view(self, pobject):
"""
Check if the pobject has the necessary permission/group
to view this help entry.
"""
perm = self.canview.split(',')
if not perm or (len(perm) == 1 and not perm[0]) or \
pobject.has_perm("helpsys.admin_help"):
return True
return pobject.has_perm_list(perm)
def get_entrytext_ingame(self):
"""
Gets the entry text for in-game viewing.
"""
return ansi.parse_ansi(self.entrytext)

View file

@ -1,15 +0,0 @@
from django.core.management.base import NoArgsCommand
from django.core.management.color import no_style
from django.core import management
class Command(NoArgsCommand):
"""
Updates the database's copy of the help files from the fixtures located
under evennia/game/docs/help_files.json.
"""
option_list = NoArgsCommand.option_list
help = "Updates (over-writes) your game's help files from the docs dir."
def handle_noargs(self, **options):
self.style = no_style()
management.call_command('loaddata', 'docs/help_files.json', verbosity=1)
print "Help files updated."

View file

@ -1 +0,0 @@
# Create your views here.

View file

@ -8,13 +8,14 @@ from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor, task
from twisted.conch.telnet import StatefulTelnetProtocol
from django.conf import settings
from src import logger
from src import session_mgr
from src.utils import logger
from src.server import sessionhandler
from src.imc2.packets import *
from src.imc2.trackers import *
from src.imc2 import reply_listener
from src.imc2.models import IMC2ChannelMapping
from src import comsys
#from src import comsys
# The active instance of IMC2Protocol. Set at server startup.
IMC2_PROTOCOL_INSTANCE = None
@ -174,7 +175,7 @@ class IMC2Protocol(StatefulTelnetProtocol):
elif packet.packet_type == 'ice-destroy':
IMC2_CHANLIST.remove_channel_from_packet(packet)
elif packet.packet_type == 'tell':
sessions = session_mgr.find_sessions_from_username(packet.target)
sessions = sessionhandler.find_sessions_from_username(packet.target)
for session in sessions:
session.msg("%s@%s IMC tells: %s" %
(packet.sender,

View file

@ -3,9 +3,11 @@ This module contains all IMC2 events that are triggered periodically.
Most of these are used to maintain the existing connection and keep various
lists/caches up to date.
"""
# TODO: This is deprecated!
from time import time
from src import events
from src import scheduler
#from src import events
#from src import scheduler
from src.imc2 import connection as imc2_conn
from src.imc2.packets import *
from src.imc2.trackers import IMC2_MUDLIST

View file

@ -1,5 +1,10 @@
from src import ansi
from src.ansi import BaseParser, ANSITable
"""
ANSI parser - this adds colour to text according to
special markup strings.
"""
from src.utils import ansi
from ansi import BaseParser, ANSITable
class IMCANSIParser(BaseParser):
"""
@ -57,4 +62,4 @@ def parse_ansi(*args, **kwargs):
"""
Shortcut to use the IMC2 ANSI parser.
"""
return ansi.parse_ansi(parser=IMCANSIParser(), *args, **kwargs)
return ansi.parse_ansi(parser=IMCANSIParser(), *args, **kwargs)

View file

@ -1,13 +1,13 @@
from django.db import models
from django.conf import settings
from src.channels.models import CommChannel
from src.comms.models import Channel
class IMC2ChannelMapping(models.Model):
"""
Each IMC2ChannelMapping object determines which in-game channel incoming
IMC2 messages are routed to.
"""
channel = models.ForeignKey(CommChannel)
channel = models.ForeignKey(Channel)
imc2_server_name = models.CharField(max_length=78)
imc2_channel_name = models.CharField(max_length=78)
is_enabled = models.BooleanField(default=True)
@ -15,7 +15,7 @@ class IMC2ChannelMapping(models.Model):
class Meta:
verbose_name = "IMC2 Channel mapping"
verbose_name_plural = "IMC2 Channel mappings"
permissions = settings.PERM_IMC2
#permissions = settings.PERM_IMC2
def __str__(self):
return "%s <-> %s" % (self.channel, self.imc2_channel_name)

View file

@ -1,6 +1,7 @@
"""
This module handles some of the -reply packets like whois-reply.
"""
#TODO: This is deprecated!
from src.objects.models import Object
from src.imc2 import imc_ansi
@ -13,4 +14,4 @@ def handle_whois_reply(packet):
response_text))
except Object.DoesNotExist:
# No match found for whois sender. Ignore it.
pass
pass

View file

@ -1,190 +0,0 @@
"""
This module handles initial database propagation, which is only run the first
time the game starts. It will create some default channels, objects, and
other things.
Everything starts at handle_setup()
"""
import time
from django.contrib.auth.models import User, Group, Permission
from django.core import management
from django.conf import settings
from src.objects.models import Object
from src.config.models import ConfigValue, CommandAlias, ConnectScreen
from src import comsys, defines_global, logger
from src.helpsys import helpsystem
from src import session_mgr
from src import scheduler
from src import events
from src.cache import cache
from src import gametime
# Main module methods
def get_god_user():
"""
Returns the initially created 'god' User object.
"""
return User.objects.get(id=1)
def get_god_obj():
"""
Returns the initially created 'god' user's PLAYER object.
"""
return Object.objects.get(id=1)
def create_objects():
"""
Creates the #1 player and Limbo room.
"""
# Set the initial User's account object's username on the #1 object.
god_user = get_god_user()
god_user.is_superuser = True
god_user.is_staff = True
# Create the matching PLAYER object in the object DB.
god_user_obj = Object(id=1, type=defines_global.OTYPE_PLAYER)
god_user_obj.set_name(god_user.username)
god_user_obj.set_attribute('desc', 'You are Player #1.')
god_user_obj.scriptlink.at_player_creation()
god_user_obj.save()
# Limbo is the initial starting room.
limbo_obj = Object(id=2, type=defines_global.OTYPE_ROOM)
limbo_obj.set_owner(god_user_obj)
limbo_obj.set_name('%ch%ccLimbo%cn')
limbo_obj.set_attribute('desc',"Welcome to your new %chEvennia%cn-based game. From here you are ready to begin development. If you should need help or would like to participate in community discussions, visit http://evennia.com.")
limbo_obj.scriptlink.at_object_creation()
limbo_obj.save()
# Now that Limbo exists, set the user up in Limbo.
god_user_obj.location = limbo_obj
god_user_obj.set_home(limbo_obj)
def create_groups():
"""
Creates the default permissions groups and assign permissions to each as defined in settings.
"""
for group_name, perm_tuple in settings.PERM_GROUPS.items():
newgroup = Group()
newgroup.name = group_name
newgroup.save()
for perm_string in perm_tuple:
#assign permissions to the group
app_label, codename = perm_string.split(".",1)
try:
permission = Permission.objects.filter(content_type__app_label=app_label).get(codename=codename)
except Permission.DoesNotExist:
logger.log_errmsg("Initial_setup: Permission %s is not defined." % perm_string)
continue
newgroup.permissions.add(permission)
def create_channels():
"""
Creates some sensible default channels.
"""
god_user_obj = get_god_obj()
chan_pub = comsys.create_channel("Public", god_user_obj,
description="Public Discussion")
chan_pub.is_joined_by_default = True
chan_pub.save()
chan_info = comsys.create_channel(settings.COMMCHAN_MUD_INFO, god_user_obj,
description="Informative messages")
chan_conn = comsys.create_channel(settings.COMMCHAN_MUD_CONNECTIONS, god_user_obj,
description="Connection log")
#add god user to default channels.
comsys.plr_add_channel(god_user_obj, "pub", chan_pub)
comsys.plr_add_channel(god_user_obj, "info", chan_info)
comsys.plr_add_channel(god_user_obj, "conn", chan_conn)
def create_config_values():
"""
Creates the initial config values.
"""
ConfigValue(conf_key="default_home", conf_value="2").save()
ConfigValue(conf_key="idle_timeout", conf_value="3600").save()
ConfigValue(conf_key="money_name_singular", conf_value="Credit").save()
ConfigValue(conf_key="money_name_plural", conf_value="Credits").save()
ConfigValue(conf_key="player_dbnum_start", conf_value="2").save()
ConfigValue(conf_key="site_name", conf_value="Evennia Test Site").save()
# We don't want to do initial setup tasks every startup, only the first.
ConfigValue(conf_key="game_firstrun", conf_value="0").save()
def create_connect_screens():
"""
Creates the default connect screen(s).
"""
ConnectScreen(name="Default",
text="%ch%cb==================================================================%cn\r\n Welcome to %chEvennia%cn! Please type one of the following to begin:\r\n\r\n If you have an existing account, connect to it by typing:\r\n %chconnect <email> <password>%cn\r\n If you need to create an account, type (without the <>'s):\r\n %chcreate \"<username>\" <email> <password>%cn\r\n%ch%cb==================================================================%cn\r\n",
is_active=True).save()
def create_aliases():
"""
Populates the standard aliases. by reading the COMMAND_ALIASES dict
from the settings file.
"""
command_aliases = settings.COMMAND_ALIASES
for user_input, equiv_command in command_aliases.items():
CommandAlias(user_input=user_input, equiv_command=equiv_command).save()
def import_help_files():
"""
Imports the help files.
"""
management.call_command('loaddata', 'docs/help_files.json', verbosity=0)
def categorize_initial_helpdb():
"""
This makes sure that the initially loaded
database is separated into its own
help category.
"""
default_category = "MUX"
print " Moving initial imported help db to help category '%s'." % default_category
helpsystem.edithelp.homogenize_database(default_category)
def create_pcache():
"""
Create the global persistent cache object.
"""
from src.cache import cache
# create the main persistent cache
cache.init_pcache()
def create_system_events():
"""
Set up the default system events of the server
"""
# create instances of events and add to scheduler (which survives a reboot)
print " Defining system events ..."
scheduler.add_event(events.IEvt_Check_Sessions())
scheduler.add_event(events.IEvt_Destroy_Objects())
scheduler.add_event(events.IEvt_Sync_PCache())
# Make sure that these events are saved to pcache right away.
ecache = [event for event in scheduler.SCHEDULE if event.persistent]
cache.set_pcache("_persistent_event_cache", ecache)
cache.save_pcache()
def start_game_time():
"""
This creates a persistent time stamp (in s since an arbitrary start)
upon first server start and is saved and updated regularly in persistent cache.
"""
gametime.time_init()
def handle_setup():
"""
Main logic for the module.
"""
create_config_values()
create_aliases()
create_connect_screens()
create_objects()
create_groups()
create_channels()
import_help_files()
categorize_initial_helpdb()
create_pcache()
create_system_events()
start_game_time()

View file

@ -3,14 +3,15 @@ This connects to an IRC network/channel and launches an 'bot' onto it.
The bot then pipes what is being said between the IRC channel and one or
more Evennia channels.
"""
# TODO: This is deprecated!
from twisted.words.protocols import irc
from twisted.internet import protocol
from twisted.internet import reactor
from django.conf import settings
from src.irc.models import IRCChannelMapping
from src import comsys
from src import logger
#from src import comsys
from src.utils import logger
#store all irc channels
IRC_CHANNELS = []

View file

@ -1,13 +1,13 @@
from django.db import models
from django.conf import settings
from src.channels.models import CommChannel
from src.comms.models import Channel
class IRCChannelMapping(models.Model):
"""
Each IRCChannelMapping object determines which in-game channel incoming
IRC messages are routed to.
"""
channel = models.ForeignKey(CommChannel)
channel = models.ForeignKey(Channel)
irc_server_name = models.CharField(max_length=78)
irc_channel_name = models.CharField(max_length=78)
is_enabled = models.BooleanField(default=True)
@ -15,7 +15,7 @@ class IRCChannelMapping(models.Model):
class Meta:
verbose_name = "IRC Channel mapping"
verbose_name_plural = "IRC Channel mappings"
permissions = settings.PERM_IRC
#permissions = settings.PERM_IRC
def __str__(self):
return "%s <-> %s (%s)" % (self.channel, self.irc_channel_name,

View file

@ -1,330 +0,0 @@
"""
This module handles all in-game locks.
A lock object contains a set of criteria (keys). When queried, the
lock tries the tested object/player against these criteria and returns
a True/False result.
"""
from src.objects.models import Object
class Key(object):
"""
This implements a lock key.
Normally the Key is of OR type; if an object matches any criterion in the key,
the entire key is considered a match. With the 'exact' criterion, all criteria
contained in the key (except the list of object dbrefs) must also exist in the
object.
With the invert_result flag the key is inversed, that is, only objects which do not
match the criteria (exact or not) will be considered to have access.
Supplying no criteria will make the lock impassable (invert_result flag results in an alvays open lock)
"""
def __init__(self, criteria=[], extra=None, invert_result=False, exact=False):
"""
Defines the basic permission laws
permlist (list of strings) - permission definitions
grouplist (list of strings) - group names
objlist (list of obj or dbrefs) - match individual objects to the lock
invert_result (bool) - invert the lock
exact (bool) - objects must match all criteria. Default is OR operation.
"""
self.criteria = criteria
self.extra = extra
#set the boolean operators
self.invert_result = not invert_result
self.exact = exact
# if we have no criteria, this is an impassable lock
# (or always open if invert_result given).
self.impassable = not(criteria)
def __str__(self):
string = " "
if not self.criteria:
string += " (Impassable)"
string += ", ".join(self.criteria)
return string
def _result(self, result):
"Return result depending on exact criterion."
if self.exact:
result = result == len(self.criteria)
if result:
return self.invert_result
else:
return not self.invert_result
def check(self, obj):
"""
Compare the object to see if the key matches.
"""
if self.invert_result:
return not self.impassable
return self.impassable
class ObjKey(Key):
"""
This implements a Key matching against object id
"""
def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
if obj.dbref() in self.criteria:
return self.invert_result
else:
return not self.invert_result
class GroupKey(Key):
"""
This key matches against group membership
"""
def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
user = obj.get_user_account()
if not user:
return False
return self._result(len([g for g in user.groups.all()
if str(g) in self.criteria]))
class PermKey(Key):
"""
This key matches against permissions
"""
def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
user = obj.get_user_account()
if not user:
return False
return self._result(len([p for p in self.criteria
if obj.has_perm(p)]))
class FlagKey(Key):
"""
This key use a set of object flags to define access.
Only if the trying object has the correct flags will
it pass the lock.
self.criterion holds the flag names
"""
def __str__(self):
string = " "
if not self.criteria:
string += " <Impassable>"
for crit in self.criteria:
string += " obj.%s," % str(crit).upper()
return string[:-1].strip()
def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
return self._result(len([f for f in self.criteria
if obj.has_flag(f)]))
class AttrKey(Key):
"""
This key use a list of arbitrary attributes to define access.
self.criteria contains a list of tuples [(attrname, value),...].
The attribute with the given name must match the given value in
order to pass the lock.
"""
def __str__(self):
string = " "
if not self.criteria:
string += " <Impassable>"
for crit in self.criteria:
string += " obj.%s=%s," % (crit[0],crit[1])
return string[:-1].strip()
def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
return self._result(len([tup for tup in self.criteria
if len(tup)>1 and
obj.get_attribute_value(tup[0]) == tup[1]]))
class FuncKey(Key):
"""
This Key stores a set of function names and return values. The matching
of those return values depend on the function (defined on the locked object) to
return a matching value to the one stored in the key
The relevant data is stored in the key in this format:
self.criteria = list of (funcname, return_value) tuples, where funcname
is a function to be called on the locked object.
This function func(obj) takes the calling object
as argument and only
if its return value matches the one set in the tuple
will the lock be passed. Note that the return value
can, in the case of locks set with @lock, only be
a string, so in the comparison we do a string
conversion of the return values.
self.index contains the locked object's dbref.
"""
def __str__(self):
string = ""
if not self.criteria:
string += " <Impassable>"
for crit in self.criteria:
string += " lockobj.%s(obj) => %s" % (crit[0],crit[1])
return string.strip()
def check(self, obj):
"Checks object against the stored locks."
if self.impassable:
return self.invert_result
# we need the locked object since the lock-function is defined on it.
lock_obj = Object.objects.dbref_search(self.extra)
if not lock_obj:
return self.invert_result
# build tuples of functions and their return values
ftuple_list = [(getattr(lock_obj.scriptlink, tup[0], None),
tup[1]) for tup in self.criteria
if len(tup) > 1]
# loop through the comparisons. Convert to strings before
# doing the comparison.
return self._result(len([ftup for ftup in ftuple_list
if callable(ftup[0]) and
str(ftup[0](obj)) == str(ftup[1])]))
class Locks(object):
"""
The Locks object defines an overall grouping of Locks based after type.
The Locks object is stored in the reserved attribute LOCKS on the locked object.
Each Locks instance stores a set of keys for each Lock type, normally
created using the @lock command in-game. The engine queries Locks.check()
with an object as argument in order to determine if the object has access.
Below is a list of Lock-types copied from MUX. Currently Evennia only use
3 lock types: Default, Use and Enter; it's not clear if any more are really
needed.
Name: Affects: Effect:
-----------------------------------------------------------------------
DefaultLock: Exits: controls who may traverse the exit to
its destination.
Rooms: controls whether the player sees the SUCC
or FAIL message for the room following the
room description when looking at the room.
Players/Things: controls who may GET the object.
EnterLock: Players/Things: controls who may ENTER the object if the
object is ENTER_OK. Also, the enter lock
of an object being used as a Zone Master
Object determines control of that zone.
GetFromLock: All but Exits: controls who may gets things from a given
location.
GiveLock: Players/Things: controls who may give the object.
LeaveLock: Players/Things: controls who may LEAVE the object.
LinkLock: All but Exits: controls who may link to the location if the
location is LINK_OK (for linking exits or
setting drop-tos) or ABODE (for setting
homes)
MailLock: Players: controls who may @mail the player.
OpenLock: All but Exits: controls who may open an exit.
PageLock: Players: controls who may page the player.
ParentLock: All: controls who may make @parent links to the
object.
ReceiveLock: Players/Things: controls who may give things to the object.
SpeechLock: All but Exits: controls who may speak in that location
(only checked if AUDITORIUM flag is set
on that location)
TeloutLock: All but Exits: controls who may teleport out of the
location.
TportLock: Rooms/Things: controls who may teleport there if the
location is JUMP_OK.
UseLock: All but Exits: controls who may USE the object, GIVE the
object money and have the PAY attributes
run, have their messages heard and possibly
acted on by LISTEN and AxHEAR, and invoke
$-commands stored on the object.
DropLock: All but rooms: controls who may drop that object.
UserLock: All: Not used by MUX, is intended to be used
in MUX programming where a user-defined
lock is needed.
VisibleLock: All: Controls object visibility when the object
is not dark and the looker passes the lock.
In DARK locations, the object must also be
set LIGHT and the viewer must pass the
VisibleLock.
"""
def __init__(self):
"""
The Lock logic is strictly OR. If you want to make access restricted,
make it so in the respective Key.
"""
self.locks = {}
def __str__(self):
string = ""
for lock in self.locks.keys():
string += " %s" % lock
return string.strip()
def add_type(self, ltype, keys=[]):
"""
type (string) : the type pf lock, like DefaultLock, UseLock etc.
keylist = list of Key objects defining who have access.
"""
if type(keys) != type(list()):
keys = [keys]
self.locks[ltype] = keys
def del_type(self, ltype):
"""
Clears a lock.
"""
if self.has_type(ltype):
del self.locks[ltype]
def has_type(self, ltype):
"Checks if LockType ltype exists in the lock."
return self.locks.has_key(ltype)
def show(self):
"""
Displays a fancier view of the stored locks and their keys.
"""
if not self.locks:
return "No locks."
string = " "
for lock, keys in self.locks.items():
string += "\n %s\n " % lock
for key in keys:
string += " %s," % key
string = string[:-1]
return string
def check(self, ltype, obj):
"""
This is called by the engine. It checks if this lock is of the right type,
and if so if there is access. If the type does not exist, there is no
lock for it and thus we return True.
"""
if not self.has_type(ltype):
return True
result = False
for key in self.locks[ltype]:
try:
result = result or key.check(obj)
except KeyError:
pass
if not result and obj.is_superuser():
obj.emit_to("Lock '%s' - Superuser override." % ltype)
return True
return result

View file

@ -1,32 +0,0 @@
"""
Logging facilities
This file should have an absolute minimum in imports. If you'd like to layer
additional functionality on top of some of the methods below, wrap them in
a higher layer module.
"""
from twisted.python import log
def log_errmsg(errormsg):
"""
Prints/logs an error message to the server log.
errormsg: (string) The message to be logged.
"""
log.err('ERROR: %s' % (errormsg,))
def log_warnmsg(warnmsg):
"""
Prints/logs any warnings that aren't critical but should be noted.
warnmsg: (string) The message to be logged.
"""
log.msg('WARNING: %s' % (warnmsg,))
def log_infomsg(infomsg):
"""
Prints any generic debugging/informative info that should appear in the log.
debugmsg: (string) The message to be logged.
"""
log.msg('%s' % (infomsg,))

0
src/objects/__init__.py Executable file → Normal file
View file

View file

@ -1,14 +1,29 @@
from src.objects.models import Attribute, Object
#
# This sets up how models are displayed
# in the web admin interface.
#
from src.objects.models import ObjAttribute, ObjectDB
from django.contrib import admin
class AttributeAdmin(admin.ModelAdmin):
list_display = ('attr_object', 'attr_name', 'attr_value',)
search_fields = ['attr_name']
admin.site.register(Attribute, AttributeAdmin)
class ObjAttributeAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_value', 'db_mode', 'db_obj', 'db_permissions')
list_display_links = ("id", 'db_key')
ordering = ["db_obj", 'db_key']
readonly_fields = ['db_permissions']
search_fields = ['id', 'db_key', 'db_obj']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(ObjAttribute, ObjAttributeAdmin)
class ObjectAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'type', 'date_created')
list_filter = ('type',)
search_fields = ['name']
class ObjectDBAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_typeclass_path', 'db_location', 'db_player')
list_display_links = ('id', 'db_key')
ordering = ['id', 'db_typeclass_path']
readonly_fields = ['db_permissions']
search_fields = ['^db_key', 'db_typeclass_path']
save_as = True
save_on_top = True
admin.site.register(Object, ObjectAdmin)
list_select_related = True
admin.site.register(ObjectDB, ObjectDBAdmin)

View file

@ -1,11 +0,0 @@
"""
Exceptions for the object application.
"""
from src.exceptions_generic import GenericException
class ObjectNotExist(GenericException):
"""
Raised when an object is queried for but does not exist.
"""
def __str__(self):
return repr("No such object: %s" % self.value)

View file

@ -0,0 +1,88 @@
"""
This handler creates cmdsets on the fly, by searching
an object's location for valid exit objects.
"""
from src.commands import cmdset, command
from src.permissions.permissions import has_perm
class ExitCommand(command.Command):
"Simple identifier command"
is_exit = True
destination = None
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if has_perm(self.caller, self.obj, 'traverse'):
self.caller.move_to(self.destination)
else:
self.caller.msg("You cannot enter.")
class ExitHandler(object):
"""
The exithandler auto-creates 'commands' to represent exits in the
room. It is called by cmdhandler when building its index of all
viable commands. This allows for exits to be processed along with
all other inputs the player gives to the game. The handler tries
to intelligently cache exit objects to cut down on processing.
"""
def __init__(self):
"Setup cache storage"
self.cached_exit_cmds = {}
def reset(self, exitcmd=None):
"""
Reset cache storage. If obj is given, only remove
that object from cache.
"""
if exitcmd:
# delete only a certain object from cache
try:
del self.cached_exit_cmds[exitcmd.id]
except KeyError:
pass
return
# reset entire cache
self.cached_exit_cmds = {}
def get_cmdset(self, srcobj):
"""
Search srcobjs location for valid exits, and
return objects as stored in command set
"""
# create a quick "throw away" cmdset
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
try:
location = srcobj.location
except Exception:
location = None
if not location:
# there can be no exits if there's no location
return exit_cmdset
# use exits to create searchable "commands" for the cmdhandler
for exi in (exi for exi in location.contents
if exi.has_attribute('_destination')):
if exi.id in self.cached_exit_cmds:
# retrieve from cache
exit_cmdset.add(self.cached_exit_cmds[exi.id])
else:
# not in cache, create a new exit command
cmd = ExitCommand()
cmd.key = exi.name.strip().lower()
cmd.obj = exi
if exi.aliases:
cmd.aliases = exi.aliases
cmd.destination = exi.attr('_destination')
exit_cmdset.add(cmd)
self.cached_exit_cmds[exi.id] = cmd
return exit_cmdset
# The actual handler - call this to get exits
EXITHANDLER = ExitHandler()

388
src/objects/manager.py Normal file
View file

@ -0,0 +1,388 @@
"""
Custom manager for Objects.
"""
from django.conf import settings
from django.contrib.auth.models import User
from src.typeclasses.managers import TypedObjectManager
from src.typeclasses.managers import returns_typeclass_list
from src.utils import create
# Try to use a custom way to parse id-tagged multimatches.
try:
IDPARSER = __import__(
settings.ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER).object_multimatch_parser
except Exception:
from src.objects.object_search_funcs import object_multimatch_parser as IDPARSER
#
# Helper function for the ObjectManger's search methods
#
def match_list(searchlist, ostring, exact_match=True,
attribute_name=None):
"""
Helper function.
does name/attribute matching through a list of objects.
"""
ostring = ostring.lower()
if attribute_name:
#search an arbitrary attribute name for a value match.
if exact_match:
return [prospect for prospect in searchlist
if (hasattr(prospect, attribute_name) and
ostring == str(getattr(prospect, attribute_name)).lower()) \
or (ostring == str(prospect.get_attribute(attribute_name)).lower())]
else:
return [prospect for prospect in searchlist
if (hasattr(prospect, attribute_name) and
ostring in str(getattr(prospect, attribute_name)).lower()) \
or (ostring in (str(p).lower() for p in prospect.get_attribute(attribute_name)))]
else:
#search the default "key" attribute
if exact_match:
return [prospect for prospect in searchlist
if ostring == str(prospect.key).lower()]
else:
return [prospect for prospect in searchlist
if ostring in str(prospect.key).lower()]
class ObjectManager(TypedObjectManager):
"""
This is the main ObjectManager for all in-game objects. It
implements search functions specialized for objects of this
type, such as searches based on user, contents or location.
See src.dbobjects.TypedObjectManager for more general
search methods.
"""
#
# ObjectManager Get methods
#
@returns_typeclass_list
def get_object_with_user(self, user):
"""
Matches objects with obj.player.user matching the argument.
Both an user object and a user id may be supplied.
"""
try:
uid = int(user)
except TypeError:
uid = user.id
return self.filter(db_player__user__id=uid)
# This returns typeclass since get_object_with_user and get_dbref does.
def player_name_search(self, search_string):
"""
Search for an object based on its player's name or dbref.
This search
is sometimes initiated by appending a * to the beginning of
the search criterion (e.g. in local_and_global_search).
search_string: (string) The name or dbref to search for.
"""
search_string = str(search_string).lstrip('*')
dbref = self.dbref(search_string)
if dbref:
# this is a valid dbref. Try to match it.
dbref_match = self.dbref_search(dbref)
if dbref_match:
return dbref_match
# not a dbref. Search by name.
player_matches = User.objects.filter(username__iexact=search_string)
if player_matches:
uid = player_matches[0].id
return self.get_object_with_user(uid)
return None
@returns_typeclass_list
def get_objs_with_attr_match(self, attribute_name, attribute_value):
"""
Returns all objects having the valid
attrname set to the given value. Note that no conversion is made
to attribute_value, and so it can accept also non-strings.
"""
return [prospect for prospect in self.all()
if attribute_value
and attribute_value == prospect.get_attribute(attribute_name)]
@returns_typeclass_list
def get_objs_with_attr(self, attribute_name):
"""
Returns all objects having the given attribute_name defined at all.
"""
return [prospect for prospect in self.all()
if prospect.get_attribute(attribute_name)]
@returns_typeclass_list
def get_contents(self, location, excludeobj=None):
"""
Get all objects that has a location
set to this one.
"""
oquery = self.filter(db_location__id=location.id)
if excludeobj:
oquery = oquery.exclude(db_key=excludeobj)
return oquery
@returns_typeclass_list
def alias_list_search(self, ostring, objlist):
"""
Search a list of objects by trying to match their aliases.
"""
matches = []
for obj in (obj for obj in objlist
if hasattr(obj, 'aliases') and
ostring in obj.aliases):
matches.append(obj)
return matches
@returns_typeclass_list
def separable_search(self, ostring, searchlist=None,
attribute_name=None, exact_match=False):
"""
Searches for a object hit for ostring.
This version handles search criteria of the type N-keyword, this is used
to differentiate several objects of the exact same name, e.g. 1-box, 2-box etc.
ostring: (string) The string to match against.
searchlist: (List of Objects) The objects to perform name comparisons on.
if not given, will search the database normally.
attribute_name: (string) attribute name to search, if None, object key is used.
exact_match: (bool) 'exact' or 'fuzzy' matching.
Note that the fuzzy matching gives precedence to exact matches; so if your
search query matches an object in the list exactly, it will be the only result.
This means that if the list contains [box,box11,box12], the search string 'box'
will only match the first entry since it is exact. The search 'box1' will however
match both box11 and box12 since neither is an exact match.
This method always returns a list, also for a single result.
"""
def run_dbref_search(ostring):
"dbref matching only"
dbref = self.dbref(ostring)
if searchlist:
results = [prospect for prospect in searchlist
if prospect.id == dbref]
else:
results = self.filter(id=dbref)
return results
def run_full_search(ostring, searchlist, exact_match=False):
"full matching"
if searchlist:
results = match_list(searchlist, ostring,
exact_match, attribute_name)
elif attribute_name:
results = match_list(self.all(), ostring,
exact_match, attribute_name)
elif exact_match:
results = self.filter(db_key__iexact=ostring)
else:
results = self.filter(db_key__icontains=ostring)
return results
# Easiest case - dbref matching (always exact)
if self.dbref(ostring):
results = run_dbref_search(ostring)
if results:
return results
# Full search - this may return multiple matches.
results = run_full_search(ostring, searchlist, exact_match)
# Deal with results of full search
match_number = None
if not results:
# if we have no match, check if we are dealing
# with a "N-keyword" query, if so, strip it out.
match_number, ostring = IDPARSER(ostring)
if match_number != None and ostring:
# Run the search again, without the match number
results = run_full_search(ostring, searchlist, exact_match)
elif not exact_match:
# we have results, but are using fuzzy matching; run
# second sweep in results to catch eventual exact matches
# (these are given precedence, so a search for 'ball' in
# ['ball', 'ball2'] will correctly return the first ball
# only).
exact_results = run_full_search(ostring, results, True)
if exact_results:
results = exact_results
if len(results) > 1 and match_number != None:
# We have multiple matches, but a N-type match number
# is available to separate them.
try:
results = [results[match_number]]
except IndexError:
pass
# this is always a list.
return results
@returns_typeclass_list
def object_search(self, character, ostring,
global_search=False,
attribute_name=None):
"""
Search as an object and return results.
character: (Object) The object performing the search.
ostring: (string) The string to compare names against.
Can be a dbref. If name is appended by *, a player is searched for.
global_search: Search all objects, not just the current location/inventory
attribute_name: (string) Which attribute to search in each object.
If None, the default 'name' attribute is used.
"""
ostring = str(ostring).strip()
if not ostring or not character:
return None
dbref = self.dbref(ostring)
if dbref:
# this is a valid dbref. If it matches, we return directly.
dbref_match = self.dbref_search(dbref)
if dbref_match:
return [dbref_match]
location = character.location
# If the search string is one of the following, return immediately with
# the appropriate result.
if location and ostring == 'here':
return [location]
if character and ostring in ['me', 'self']:
return [character]
if character and ostring in ['*me', '*self']:
return [character.player]
if ostring.startswith("*"):
# Player search - search player base
player_string = ostring.lstrip("*")
player_match = self.player_name_search(player_string)
if player_match is not None:
return [player_match]
if global_search or not location:
# search all objects
return self.separable_search(ostring, None,
attribute_name)
# None of the above cases yielded a return, so we fall through to
# location/contents searches.
matches = []
local_objs = []
local_objs.extend(character.contents)
local_objs.extend(location.contents)
local_objs.append(location) #easy to forget!
if local_objs:
# normal key/attribute search (typedobject_search is
# found in class parent)
matches = self.separable_search(ostring, local_objs,
attribute_name, exact_match=False)
if not matches:
# no match, try an alias search
matches = self.alias_list_search(ostring, local_objs)
return matches
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_name=None,
new_location=None, new_home=None, aliases=None):
"""
Create and return a new object as a copy of the source object. All will
be identical to the original except for the dbref and the 'user' field
which will be set to None.
original_object (obj) - the object to make a copy from
new_name (str) - name the copy differently from the original.
new_location (obj) - if None, we create the new object in the same place as the old one.
"""
# get all the object's stats
name = original_object.key
if new_name:
name = new_name
typeclass_path = original_object.typeclass_path
# create new object
from src import create
new_object = create.create_object(name, typeclass_path, new_location,
new_home, user=None, aliases=None)
if not new_object:
return None
for attr in original_object.attr():
# copy over all attributes from old to new.
new_object.attr(attr.attr_name, attr.value)
return new_object
#
# ObjectManager User control
#
def user_swap_object(self, uid, new_obj_id, delete_old_obj=False):
"""
This moves the user from one database object to another.
The new object must already exist.
delete_old_obj (bool) - Delete the user's old dbobject.
This is different from ObjectDB.swap_type() since it actually
swaps the database object the user is connected to, rather
than change any typeclass on the same dbobject. This means
that the old object (typeclass and all) can remain unchanged
in-game except it is now not tied to any user.
Note that the new object will be unchanged, the only
difference is that its 'user' property is set to the
user. No other initializations are done here, such as
setting the default cmdset - this has to be done
separately when calling this method.
This method raises Exceptions instead of logging feedback
since this is a method which might be very useful to embed in
your own game implementation.
Also note that this method don't check any permissions beyond
making sure no other user is connected to the object before
swapping.
"""
# get the objects.
try:
user = User.get(uid)
new_obj = self.get(new_obj_id)
except:
raise Exception("OBJ_FIND_ERROR")
# check so the new object is not already controlled.
if new_obj.user:
if new_obj.user == user:
raise Exception("SELF_CONTROL_ERROR")
else:
raise Exception("CONTROL_ERROR")
# set user to new object.
new_obj.user = user
new_obj.save()
# get old object, sets its user to None and/or delete it
for old_obj in self.get_object_with_user(uid):
if delete_old_obj:
old_obj.delete()
else:
old_obj.user = None
old_obj.save()

View file

@ -1,18 +0,0 @@
"""
Custom manager for Attribute objects.
"""
from django.db import models
from src import defines_global
class AttributeManager(models.Manager):
def is_modifiable_attrib(self, attribname):
"""
Check to see if a particular attribute is modifiable.
attribname: (string) An attribute name to check.
"""
if attribname.upper() not in defines_global.NOSET_ATTRIBS:
return True
else:
return False

View file

@ -1,598 +0,0 @@
"""
Custom manager for Object objects.
"""
from datetime import datetime, timedelta
from django.db import models
from django.db import connection
from django.contrib.auth.models import User
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
from src.config.models import ConfigValue
from src.objects.exceptions import ObjectNotExist
from src.objects.util import object as util_object
from src import defines_global
from src import logger
class ObjectManager(models.Manager):
#
# ObjectManager Get methods
#
def num_total_players(self):
"""
Returns the total number of registered players.
"""
return User.objects.count()
def get_connected_players(self):
"""
Returns the a QuerySet containing the currently connected players.
"""
return self.filter(nosave_flags__contains="CONNECTED")
def get_recently_created_users(self, days=7):
"""
Returns a QuerySet containing the player User accounts that have been
connected within the last <days> days.
"""
end_date = datetime.now()
tdelta = timedelta(days)
start_date = end_date - tdelta
return User.objects.filter(date_joined__range=(start_date, end_date))
def get_recently_connected_users(self, days=7):
"""
Returns a QuerySet containing the player User accounts that have been
connected within the last <days> days.
"""
end_date = datetime.now()
tdelta = timedelta(days)
start_date = end_date - tdelta
return User.objects.filter(last_login__range=(start_date, end_date)).order_by('-last_login')
def get_user_from_email(self, uemail):
"""
Returns a player's User object when given an email address.
"""
return User.objects.filter(email__iexact=uemail)
def get_object_from_dbref(self, dbref):
"""
Returns an object when given a dbref.
"""
if type(dbref) == type(str()):
if len(dbref)>1 and dbref[0]=="#":
dbref = dbref[1:]
dbref = "%s" % dbref
try:
return self.get(id=dbref)
except self.model.DoesNotExist:
raise ObjectNotExist(dbref)
def object_totals(self):
"""
Returns a dictionary with database object totals.
"""
dbtotals = {
"objects": self.count(),
"things": self.filter(type=defines_global.OTYPE_THING).count(),
"exits": self.filter(type=defines_global.OTYPE_EXIT).count(),
"rooms": self.filter(type=defines_global.OTYPE_ROOM).count(),
"garbage": self.filter(type=defines_global.OTYPE_GARBAGE).count(),
"players": self.filter(type=defines_global.OTYPE_PLAYER).count(),
}
return dbtotals
def get_nextfree_dbnum(self):
"""
Figure out what our next free database reference number is.
If we need to recycle a GARBAGE object, return the object to recycle
Otherwise, return the first free dbref.
"""
# First we'll see if there's an object of type 6 (GARBAGE) that we
# can recycle.
nextfree = self.filter(type__exact=defines_global.OTYPE_GARBAGE)
if nextfree:
# We've got at least one garbage object to recycle.
return nextfree[0]
else:
# No garbage to recycle, find the highest dbnum and increment it
# for our next free.
return int(self.order_by('-id')[0].id + 1)
def is_dbref(self, dbstring, require_pound=True):
"""
Is the input a well-formed dbref number?
"""
return util_object.is_dbref(dbstring, require_pound=require_pound)
#
# ObjectManager Search methods
#
def dbref_search(self, dbref_string, limit_types=False):
"""
Searches for a given dbref.
dbref_number: (string) The dbref to search for. With # sign.
limit_types: (list of int) A list of Object type numbers to filter by.
"""
if not util_object.is_dbref(dbref_string):
return None
dbref_string = dbref_string[1:]
dbref_matches = self.filter(id=dbref_string).exclude(
type=defines_global.OTYPE_GARBAGE)
# Check for limiters
if limit_types is not False:
for limiter in limit_types:
dbref_matches.filter(type=limiter)
try:
return dbref_matches[0]
except IndexError:
return None
def global_object_name_search(self, ostring, exact_match=True, limit_types=[]):
"""
Searches through all objects for a name match.
limit_types is a list of types as defined in defines_global.
"""
if self.is_dbref(ostring):
o_query = self.dbref_search(ostring, limit_types=limit_types)
if o_query:
return [o_query]
return None
# get rough match
o_query = self.filter(name__icontains=ostring)
o_query = o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
if not o_query:
# use list-search to catch N-style queries. Note
# that we want to keep the original ostring since
# search_object_namestr does its own N-string treatment
# on this.
dum, test_ostring = self._parse_match_number(ostring)
o_query = self.filter(name__icontains=test_ostring)
o_query = o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
match_type = "fuzzy"
if exact_match:
match_type = "exact"
return self.list_search_object_namestr(o_query, ostring,
limit_types=limit_types,
match_type=match_type)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
def global_object_script_parent_search(self, script_parent):
"""
Searches through all objects returning those which has a certain script parent.
"""
o_query = self.filter(script_parent__exact=script_parent)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
def local_object_script_parent_search(self, script_parent, location):
o_query = self.filter(script_parent__exact=script_parent)
if o_query:
o_query = o_query.filter(location__iexact=location)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
def list_search_object_namestr(self, searchlist, ostring, dbref_only=False,
limit_types=False, match_type="fuzzy",
attribute_name=None):
"""
Iterates through a list of objects and returns a list of
name matches.
This version handles search criteria of the type N-keyword, this is used
to differentiate several objects of the exact same name, e.g. 1-box, 2-box etc.
searchlist: (List of Objects) The objects to perform name comparisons on.
ostring: (string) The string to match against.
dbref_only: (bool) Only compare dbrefs.
limit_types: (list of int) A list of Object type numbers to filter by.
match_type: (string) 'exact' or 'fuzzy' matching.
attribute_name: (string) attribute name to search, if None, 'name' is used.
Note that the fuzzy matching gives precedence to exact matches; so if your
search query matches an object in the list exactly, it will be the only result.
This means that if the list contains [box,box11,box12], the search string 'box'
will only match the first entry since it is exact. The search 'box1' will however
match both box11 and box12 since neither is an exact match.
Uses two helper functions, _list_search_helper1/2.
"""
if dbref_only:
#search by dbref - these must always be unique.
if limit_types:
return [prospect for prospect in searchlist
if prospect.dbref_match(ostring)
and prospect.type in limit_types]
else:
return [prospect for prospect in searchlist
if prospect.dbref_match(ostring)]
#search by name - this may return multiple matches.
results = self._match_name_attribute(searchlist,ostring,dbref_only,
limit_types, match_type,
attribute_name=attribute_name)
match_number = None
if not results:
#if we have no match, check if we are dealing
#with a "N-keyword" query - if so, strip it and run again.
match_number, ostring = self._parse_match_number(ostring)
if match_number != None and ostring:
results = self._match_name_attribute(searchlist,ostring,dbref_only,
limit_types, match_type,
attribute_name=attribute_name)
if match_type == "fuzzy":
#fuzzy matching; run second sweep to catch exact matches
if attribute_name:
exact_results = [prospect for prospect in results
if ostring == prospect.get_attribute_value(attribute_name)]
else:
exact_results = [prospect for prospect in results
if prospect.name_match(ostring, match_type="exact")]
if exact_results:
results = exact_results
if len(results) > 1 and match_number != None:
#select a particular match using the "keyword-N" markup.
try:
results = [results[match_number]]
except IndexError:
pass
return results
def _match_name_attribute(self, searchlist, ostring, dbref_only,
limit_types, match_type,
attribute_name=None):
"""
Helper function for list_search_object_namestr -
does name/attribute matching through a list of objects.
"""
if attribute_name:
#search an arbitrary attribute name.
if limit_types:
if match_type == "exact":
return [prospect for prospect in searchlist
if prospect.type in limit_types and
ostring == prospect.get_attribute_value(attribute_name)]
else:
return [prospect for prospect in searchlist
if prospect.type in limit_types and
ostring in str(prospect.get_attribute_value(attribute_name))]
else:
if match_type == "exact":
return [prospect for prospect in searchlist
if ostring == str(prospect.get_attribute_value(attribute_name))]
else:
return [prospect for prospect in searchlist
if ostring in str(prospect.get_attribute_value(attribute_name))]
else:
#search the default "name" attribute
if limit_types:
return [prospect for prospect in searchlist
if prospect.type in limit_types and
prospect.name_match(ostring, match_type=match_type)]
else:
return [prospect for prospect in searchlist
if prospect.name_match(ostring, match_type=match_type)]
def _parse_match_number(self, ostring):
"""
Helper function for list_search_object_namestr -
strips eventual N-keyword endings from a search criterion
"""
if not '-' in ostring:
return False, ostring
try:
il = ostring.find('-')
number = int(ostring[:il])-1
return number, ostring[il+1:]
except ValueError:
#not a number; this is not an identifier.
return None, ostring
except IndexError:
return None, ostring
def player_alias_search(self, searcher, ostring):
"""
Search players by alias. Returns a list of objects whose "ALIAS"
attribute exactly (not case-sensitive) matches ostring.
searcher: (Object) The object doing the searching.
ostring: (string) The alias string to search for.
"""
if ostring.lower().strip() == "me":
return searcher
Attribute = ContentType.objects.get(app_label="objects",
model="attribute").model_class()
results = Attribute.objects.select_related().filter(attr_name__exact="ALIAS").filter(attr_value__iexact=ostring)
return [prospect.get_object() for prospect in results if prospect.get_object().is_player()]
def player_name_search(self, search_string):
"""
Combines an alias and global search for a player's name. If there are
no alias matches, do a global search limiting by type PLAYER.
search_string: (string) The name string to search for.
"""
# Handle the case where someone might have started the search_string
# with a *
if search_string.startswith('*') is True:
search_string = search_string[1:]
# Use Q objects to build complex OR query to look at either
# the player name or ALIAS attribute
player_filter = Q(name__iexact=search_string)
alias_filter = Q(attribute__attr_name__exact="ALIAS") & \
Q(attribute__attr_value__iexact=search_string)
player_matches = self.filter(
player_filter | alias_filter).filter(
type=defines_global.OTYPE_PLAYER).distinct()
try:
return player_matches[0]
except IndexError:
return None
def local_and_global_search(self, searcher, ostring, search_contents=True,
search_location=True, dbref_only=False,
limit_types=False, attribute_name=None):
"""
Searches an object's location then globally for a dbref or name match.
searcher: (Object) The object performing the search.
ostring: (string) The string to compare names against.
search_contents: (bool) While true, check the contents of the searcher.
search_location: (bool) While true, check the searcher's surroundings.
dbref_only: (bool) Only compare dbrefs.
limit_types: (list of int) A list of Object type numbers to filter by.
attribute_name: (string) Which attribute to search in each object.
If None, the default 'name' attribute is used.
"""
search_query = str(ostring).strip()
# This is a global dbref search. Not applicable if we're only searching
# searcher's contents/locations, dbref comparisons for location/contents
# searches are handled by list_search_object_namestr() below.
if util_object.is_dbref(ostring):
dbref_match = self.dbref_search(search_query, limit_types)
if dbref_match is not None:
return [dbref_match]
# If the search string is one of the following, return immediately with
# the appropriate result.
if searcher.get_location().dbref_match(ostring) or ostring == 'here':
return [searcher.get_location()]
elif ostring == 'me' and searcher:
return [searcher]
if search_query and search_query[0] == "*":
# Player search- gotta search by name or alias
search_target = search_query[1:]
player_match = self.player_name_search(search_target)
if player_match is not None:
return [player_match]
# Handle our location/contents searches. list_search_object_namestr() does
# name and dbref comparisons against search_query.
local_objs = []
if search_contents:
local_objs.extend(searcher.get_contents())
if search_location:
local_objs.extend(searcher.get_location().get_contents())
return self.list_search_object_namestr(local_objs, search_query,
limit_types=limit_types,
attribute_name=attribute_name)
#
# ObjectManager Create methods
#
def create_object(self, name, otype, location, owner, home=None, script_parent=None):
"""
Create a new object
type: Integer representing the object's type.
name: The name of the new object.
location: Reference to another object for the new object to reside in.
owner: The creator of the object.
home: Reference to another object to home to. If not specified,
set to location.
script_parent: The parent of this object (ignored if otype is OTYPE_PLAYER)
"""
#get_nextfree_dbnum() returns either an integer or an old object to recycle.
next_dbref = self.get_nextfree_dbnum()
if type(next_dbref) == type(int()):
#create new object with a fresh dbref
Object = ContentType.objects.get(app_label="objects",
model="object").model_class()
new_object = Object()
new_object.id = next_dbref
else:
#recycle an old object's id instead
new_object = next_dbref
new_object.purge_object()
new_object.type = otype
new_object.set_name(name)
# Set the script_parent.
# If the script_parent string is not valid, the defaults will be used.
# To see if it worked or not from outside this method, easiest is to use the
# obj.get_script_parent() function to find out what was actually set.
new_object.set_script_parent(script_parent)
# If this is a player, we don't want him owned by anyone.
# The get_owner() function will return that the player owns
# himself.
if otype == defines_global.OTYPE_PLAYER:
new_object.owner = None
new_object.zone = None
else:
if owner == None:
# if owner is None for a non-player object we are probably
# creating an object from a script. In this case we set
# the owner to be the superuser.
owner = self.get_object_from_dbref("#1")
new_object.owner = owner
if new_object.get_owner().get_zone():
new_object.zone = new_object.get_owner().get_zone()
# Set default description, depending on type.
default_desc = None
if otype == defines_global.OTYPE_PLAYER:
default_desc = defines_global.DESC_PLAYER
elif otype == defines_global.OTYPE_ROOM:
default_desc = defines_global.DESC_ROOM
location = None
elif otype == defines_global.OTYPE_EXIT:
default_desc = defines_global.DESC_EXIT
else:
default_desc = defines_global.DESC_THING
new_object.set_attribute("desc", default_desc)
# Run the script parent's creation hook function.
# This is where all customization happens.
new_object.scriptlink.at_object_creation()
# If we have a 'home' key, use that for our home value. Otherwise use
# the location key. All objects must have this
if home:
new_object.home = home
else:
if new_object.is_exit():
new_object.home = None
else:
new_object.home = location
new_object.save()
# Rooms have a NULL location. Move everything else to new location.
if not new_object.is_room():
new_object.move_to(location, quiet=True, force_look=False)
return new_object
def create_user(self, command, uname, email, password):
"""
Handles the creation of new users.
"""
start_room = int(ConfigValue.objects.get_configvalue('player_dbnum_start'))
start_room_obj = self.get_object_from_dbref(start_room)
# The user's entry in the User table must match up to an object
# on the object table. The id's are the same, we need to figure out
# the next free unique ID to use and make sure the two entries are
# the same number.
uid = self.get_nextfree_dbnum()
# If this is an object, we know to recycle it since it's garbage. We'll
# pluck the user ID from it.
if not str(uid).isdigit():
uid = uid.id
logger.log_infomsg('Create_user: Recycling object ID %d.' % uid)
user = User.objects.create_user(uname, email, password)
# It stinks to have to do this but it's the only trivial way now.
user.save()
# We can't use the user model to change the id because of the way keys
# are handled, so we actually need to fall back to raw SQL. Boo hiss.
cursor = connection.cursor()
cursor.execute("UPDATE auth_user SET id=%d WHERE id=%d" % (uid, user.id))
# Update the session to use the newly created User object's ID.
command.session.uid = uid
# Grab the user object again since we've changed it and the old reference
# is no longer valid.
user = User.objects.get(id=uid)
# Create a player object of the same ID in the Objects table.
user_object = self.create_object(uname,
defines_global.OTYPE_PLAYER,
start_room_obj,
None)
# The User and player Object are ready, do anything needed by the
# game to further prepare things.
user_object.scriptlink.at_player_creation()
# Add the user to all of the CommChannel objects that are flagged
# is_joined_by_default.
command.session.add_default_channels()
# Add user to the default permission group, if defined set in preferences.
command.session.add_default_group()
logger.log_infomsg("Registered new user: %s" % user_object.get_name())
# Activate the player's session and set them loose.
command.session.login(user, first_login=True)
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_name=None, new_location=None, reset=False):
"""
Create and return a new object as a copy of the source object. All will
be identical to the original except for the dbref. Does not allow the
copying of Player objects.
original_object (obj) - the object to make a copy from
new_location (obj) - if None, we create the new object in the same place as the old one.
reset (bool) - copy only the default attributes/flags set by the script_parent, ignoring
any changes to the original after it was originally created.
"""
if not original_object or original_object.is_player():
return
# get all the object's stats
if new_name:
name = new_name
else:
name = original_object.get_name(show_dbref=False,no_ansi=True)
otype = original_object.type
if new_location:
location = new_location
else:
location = original_object.get_location()
owner = original_object.get_owner()
home = original_object.get_home()
script_parent = original_object.get_script_parent()
# create new object
new_object = self.create_object(name, otype, location, owner, home,
script_parent=script_parent)
if not new_object:
return
if not reset:
# we make sure that the objects are identical by manually copying over all attributes and
# flags; this way we also get those that might have changed since the original was created.
all_attribs = original_object.get_all_attributes()
for attr in all_attribs:
new_object.set_attribute(attr.get_name(), attr.get_value())
all_flags = original_object.get_flags() #this is a string
for flag in all_flags.split():
new_object.set_flag(flag)
return new_object

1896
src/objects/models.py Executable file → Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
"""
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.
"""
from src.permissions.permissions import has_perm_string
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.emit_to("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 \
has_perm_string(emit_to_obj, 'see_dbref')
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.emit_to(string)
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 '-' 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)

376
src/objects/objects.py Normal file
View file

@ -0,0 +1,376 @@
"""
This is the basis of the typeclass system.
The idea is have the object as a normal class with the
database-connection tied to itself through a property.
The instances of all the different object types are all tied to their
own database object stored in the 'dbobj' property. All attribute
get/set operations are channeled transparently to the database object
as desired. You should normally never have to worry about the database
abstraction, just do everything on the TypeClass object.
That an object is controlled by a player/user is just defined by its
'user' property being set. This means a user may switch which object
they control by simply linking to a new object's user property.
"""
from src.typeclasses.typeclass import TypeClass
from src.commands.cmdsethandler import CmdSetHandler
from src.scripts.scripthandler import ScriptHandler
#from src.permissions.permissions import has_perm
from src.objects.exithandler import EXITHANDLER
#
# Base class to inherit from.
#
class Object(TypeClass):
"""
This is the base class for all in-game objects.
Inherit from this to create different types of
objects in the game.
"""
def __init__(self, dbobj):
"""
Set up the Object-specific handlers. Note that we must
be careful to run the parent's init function too
or typeclasses won't work!
"""
# initialize typeclass system. This sets up self.dbobj.
super(Object, self).__init__(dbobj)
# create the command- and scripthandlers as needed
try:
dummy = object.__getattribute__(dbobj, 'cmdset')
create_cmdset = type(dbobj.cmdset) != CmdSetHandler
except AttributeError:
create_cmdset = True
try:
dummy = object.__getattribute__(dbobj, 'scripts')
create_scripts = type(dbobj.scripts) != ScriptHandler
except AttributeError:
create_scripts = True
if create_cmdset:
dbobj.cmdset = CmdSetHandler(dbobj)
if create_scripts:
dbobj.scripts = ScriptHandler(dbobj)
def __eq__(self, other):
"""
This has be located at this level, having it in the
parent doesn't work.
"""
result = other and other.id == self.id
try:
uresult = other and (other.user.id == self.user.id)
except AttributeError:
uresult = False
return result or uresult
# hooks called by the game engine
def at_object_creation(self):
"""
Called once, when this object is first
created.
"""
pass
def at_first_login(self):
"""
Only called once, the very first
time the user logs in.
"""
pass
def at_pre_login(self):
"""
Called every time the user logs in,
before they are actually logged in.
"""
pass
def at_post_login(self):
"""
Called at the end of the login
process, just before letting
them loose.
"""
pass
def at_disconnect(self):
"""
Called just before user
is disconnected.
"""
pass
# hooks called when moving the object
def at_before_move(self, destination):
"""
Called just before starting to move
this object to destination.
destination - the object we are moving to
If this method returns False/None, the move
is cancelled before it is even started.
"""
#return has_perm(self, destination, "can_move")
return True
def announce_move_from(self, destination):
"""
Called if the move is to be announced. This is
called while we are still standing in the old
location.
destination - the place we are going to.
"""
if not self.location:
return
name = self.name
loc_name = ""
loc_name = self.location.name
dest_name = destination.name
string = "%s is leaving %s, heading for %s."
self.location.emit_to_contents(string % (name, loc_name, dest_name), exclude=self)
def announce_move_to(self, source_location):
"""
Called after the move if the move was not quiet. At this
point we are standing in the new location.
source_location - the place we came from
"""
name = self.name
if not source_location and self.location.has_player:
# This was created from nowhere and added to a player's
# inventory; it's probably the result of a create command.
string = "You now have %s in your possession." % name
self.location.emit_to(string)
return
src_name = "nowhere"
loc_name = self.location.name
if source_location:
src_name = source_location.name
string = "%s arrives to %s from %s."
self.location.emit_to_contents(string % (name, loc_name, src_name), exclude=self)
def at_after_move(self, source_location):
"""
Called after move has completed, regardless of quiet mode or not.
Allows changes to the object due to the location it is now in.
source_location - where we came from
"""
pass
def at_object_leave(self, moved_obj, target_location):
"""
Called just before an object leaves from inside this object
moved_obj - the object leaving
target_location - where the object is going.
"""
pass
def at_object_receive(self, moved_obj, source_location):
"""
Called after an object has been moved into this object.
moved_obj - the object moved into this one
source_location - where moved_object came from.
"""
pass
# hooks called by the default cmdset.
def return_appearance(self, pobject):
"""
This is a convenient hook for a 'look'
command to call.
"""
if not pobject:
return
string = "{c%s{n" % self.name
desc = self.attr("desc")
if desc:
string += ":\n %s" % desc
exits = []
users = []
things = []
for content in self.contents:
if content == pobject:
continue
name = content.name
if content.attr('_destination'):
exits.append(name)
elif content.has_player:
users.append(name)
else:
things.append(name)
if exits:
string += "\n{wExits:{n " + ", ".join(exits)
if users or things:
string += "\n{wYou see: {n"
if users:
string += "{c" + ", ".join(users) + "{n "
if things:
string += ", ".join(things)
return string
def at_msg_receive(self, msg, from_obj=None):
"""
This hook is called whenever someone
sends a message to this object.
Note that from_obj may be None if the sender did
not include itself as an argument to the obj.msg()
call - so you have to check for this. .
Consider this a pre-processing method before
msg is passed on to the user sesssion. If this
method returns False, the msg will not be
passed on.
msg = the message received
from_obj = the one sending the message
"""
return True
def at_msg_send(self, msg, to_obj):
"""
This is a hook that is called when /this/ object
sends a message to another object with obj.msg()
while also specifying that it is the one sending.
Note that this method is executed on the object
passed along with the msg() function (i.e. using
obj.msg(msg, caller) will then launch caller.at_msg())
and if no object was passed, it will never be called.
"""
pass
def at_desc(self, looker=None):
"""
This is called whenever someone looks
at this object. Looker is the looking
object.
"""
pass
def at_object_delete(self):
"""
Called just before the database object is
permanently delete()d from the database. If
this method returns False, deletion is aborted.
"""
return True
def at_get(self, getter):
"""
Called when this object has been picked up. Obs-
this method cannot stop the pickup - use permissions
for that!
getter - the object getting this object.
"""
pass
def at_drop(self, dropper):
"""
Called when this object has been dropped.
dropper - the object which just dropped this object.
"""
pass
def at_say(self, speaker, message):
"""
Called on this object if an object inside this object speaks.
The string returned from this method is the final form
of the speech. Obs - you don't have to add things like
'you say: ' or similar, that is handled by the say command.
speaker - the object speaking
message - the words spoken.
"""
return message
#
# Base Player object
#
class Character(Object):
"""
This is just like the Object except it implements its own
version of the at_object_creation to set up the script
that adds the default cmdset to the object.
"""
def at_object_creation(self):
"""
All this does (for now) is to add the default cmdset. Since
the script is permanently stored to this object (the permanent
keyword creates a script to do this), we should never need to
do this again for as long as this object exists.
"""
from settings import CMDSET_DEFAULT
self.cmdset.add_default(CMDSET_DEFAULT, permanent=True)
# this makes sure other objects are not accessing our
# command sets as they would any other object's sets.
self.cmdset.outside_access = False
def at_after_move(self, source_location):
"Default is to look around after a move."
self.execute_cmd('look')
#
# Base Room object
#
class Room(Object):
"""
This is the base room object. It's basically
like any Object except its location is None.
"""
def at_object_creation(self):
"""
Simple setup, shown as an example
(since default is None anyway)
"""
self.location = None
class Exit(Object):
"""
This is the base exit object - it connects a location
to another. What separates it from other objects
is that it has the '_destination' attribute defined.
Note that _destination is the only identifier to
separate an exit from normal objects, so if _destination
is removed, it will be treated like any other object. This
also means that any object can be made an exit by setting
the attribute _destination to a valid location
('Quack like a duck...' and so forth).
"""
def at_object_creation(self):
"""
Another example just for show; the _destination attribute
is usually set at creation time, not as part of the class
definition (unless you want an entire class of exits
all leadning to the same hard-coded place ...)
"""
self.attr("_destination", "None")
def at_object_delete(self):
"""
We have to make sure to clean the exithandler cache
when deleting the exit, or a new exit could be created
out of sync with the cache. You should do this also if
overloading this function in a child class.
"""
EXITHANDLER.reset(self.dbobj)
return True

View file

@ -1,29 +0,0 @@
"""
Utility functions for the Object class. These functions should not import
any models or modify the database.
"""
def is_dbref(dbstring, require_pound=True):
"""
Is the input a well-formed dbref number?
"""
# Strip the leading # sign if it's there.
if dbstring.startswith("#"):
dbstring = dbstring[1:]
else:
if require_pound:
# The pound sign was required and it didn't have it, fail out.
return False
try:
# If this fails, it's probably not valid.
number = int(dbstring)
except ValueError:
return False
except TypeError:
return False
# Numbers less than 1 are not valid dbrefs.
if number < 1:
return False
else:
return True

View file

@ -1 +0,0 @@
# Create your views here.

18
src/permissions/admin.py Normal file
View file

@ -0,0 +1,18 @@
#
# This sets up how models are displayed
# in the web admin interface.
#
from src.permissions.models import PermissionGroup
from django.contrib import admin
class PermissionGroupAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_group_permissions')
list_display_links = ('id', "db_key")
ordering = ['db_key', 'db_group_permissions']
readonly_fields = ['db_key', 'db_group_permissions', 'db_permissions']
search_fields = ['db_key']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(PermissionGroup, PermissionGroupAdmin)

View file

@ -0,0 +1,100 @@
"""
Setup the permission hierarchy and groups. This is
read once during server startup. Further groups and
permissions have to be added manually.
To set up your own permission scheme, have
PERMISSION_SETUP_MODULE in game/settings point to
a module of your own. This module must define two global
dictionaries PERMS and GROUPS.
PERMS contains all permissions defined at server start
on the form {key:desc, key:desc, ...}
GROUPS gathers permissions (which must have been
previously created as keys in PERMS) into clusters
on the form {groupname: [key, key, ...], ...}
"""
# Defining all permissions.
PERMS = [
'emit',
'wall',
'teleport',
'setobjalias',
'wipe',
'set',
'cpattr',
'mvattr',
'find',
'create',
'copy',
'open',
'link',
'unlink',
'dig',
'desc',
'destroy',
'examine',
'typeclass',
'debug',
'batchcommands',
'batchcodes',
'ccreate',
'cdesc',
'tell',
'time',
'list',
'ps',
'stats',
'reload',
'py',
'listscripts',
'listcmdsets',
'listobjects',
'boot',
'delplayer',
'newpassword',
'home',
'service',
'shutdown',
'perm',
'sethelp',
]
# Permission Groups
# Permission groups clump the previously defined permissions into
# larger chunks. {groupname: [permissionkey,... ]}
GROUPS = {
"Immortals": PERMS,
"Wizards": [perm for perm in PERMS
if perm not in ['shutdown',
'py',
'reload',
'service',
'perm',
'batchcommands',
'batchcodes']],
"Builders": [perm for perm in PERMS
if perm not in ['shutdown',
'py',
'reload',
'service',
'perm',
'batchcommands',
'batchcodes',
'wall',
'boot',
'delplayer',
'newpassword']],
"PlayerHelpers": ['tell',
'sethelp', 'ccreate', 'use_channels'],
"Players": ['tell', 'ccreate', 'use_channels']
}

View file

@ -0,0 +1,188 @@
"""
This module provides a set of permission lock functions for use
with Evennia's permissions system.
To call these locks, make sure this module is included in the
settings tuple PERMISSION_FUNC_MODULES then define a permission
string of the form 'myfunction(myargs)' and store it in the
'permissions' field or variable on your object/command/channel/whatever.
As part of the permission check, such permission strings will be
evaluated to call myfunction(checking_obj, checked_obj, *yourargs) in
this module. A boolean value is expected back.
Note that checking_obj and checked_obj can be any object type
with a permissions variable/field, so be careful to not expect
a certain object type.
MUX locks
Below is a list nicked from the MUX docs on the locks available
in MUX. These are not all necessarily relevant to an Evennia game
but to show they are all possible with Evennia, each entry is a
suggestion on how one could implement similar functionality in Evennia.
Name: Affects: Effect:
-------------------------------------------------------------------------
DefaultLock: Exits: controls who may traverse the exit to
its destination.
Evennia: specialized permission key
'traverse' checked in move method
Rooms: controls whether the player sees the SUCC
or FAIL message for the room following the
room description when looking at the room.
Evennia: This is better done by implementing
a clever room class ...
Players/Things: controls who may GET the object.
Evennia: specialized permission key 'get'
defined on object, checked by get command
EnterLock: Players/Things: controls who may ENTER the object
Evennia: specialized permission key 'enter'
defined on object, checked by move command
GetFromLock: All but Exits: controls who may gets things from a given
location.
Evennia: Probably done best with a lock function
that searches the database for permitted users
GiveLock: Players/Things: controls who may give the object.
Evennia: specialized permission key 'give'
checked by the give command
LeaveLock: Players/Things: controls who may LEAVE the object.
Evennia: specialized permission key 'leave'
checked by move command
LinkLock: All but Exits: controls who may link to the location if the
location is LINK_OK (for linking exits or
setting drop-tos) or ABODE (for setting
homes)
Evennia: specialized permission key 'link'
set on obj and checked by link command
MailLock: Players: controls who may @mail the player.
Evennia: Lock function that pulls the
config from the player to see if the
calling player is on the blacklist/whitelist
OpenLock: All but Exits: controls who may open an exit.
Evennia: specialized permission key 'open'
set on exit, checked by open command
PageLock: Players: controls who may page the player.
Evennia: see Maillock
ParentLock: All: controls who may make @parent links to the
object.
Evennia: This is handled with typeclasses
and typeclass switching instead.
ReceiveLock: Players/Things: controls who may give things to the object.
Evennia: See GiveLock
SpeechLock: All but Exits: controls who may speak in that location
Evennia: Lock function checking if there
is some special restrictions on the room
(game dependent)
TeloutLock: All but Exits: controls who may teleport out of the
location.
Evennia: See LeaveLock
TportLock: Rooms/Things: controls who may teleport there
Evennia: See EnterLock
UseLock: All but Exits: controls who may USE the object, GIVE the
object money and have the PAY attributes
run, have their messages heard and possibly
acted on by LISTEN and AxHEAR, and invoke
$-commands stored on the object.
Evennia: Implemented per game
DropLock: All but rooms: controls who may drop that object.
Evennia: specialized permission key 'drop'
set on room, checked by drop command.
VisibleLock: All: Controls object visibility when the object
is not dark and the looker passes the lock.
In DARK locations, the object must also be
set LIGHT and the viewer must pass the
VisibleLock.
Evennia: Better done with Scripts implementing
a dark state/cmdset. For a single object,
use a specialized permission key 'visible'
set on object and checked by look command.
"""
from src.permissions.permissions import get_types, has_perm, has_perm_string
from src.utils import search
def noperm(checking_obj, checked_obj, *args):
"""
Usage:
noperm(mypermstring)
noperm(perm1, perm2, perm3, ...)
A negative permission; this will return False only if
the checking object *has any* of the given permission(s), True
otherwise. The searched permission cannot itself be a
function-permission (i.e. you cannot wrap functions in
functions).
"""
if not args:
# this is an always-false permission
return False
return not has_perm_string(checking_obj, args)
def is_superuser(checking_obj, checked_obj, *args):
"""
Usage:
is_superuser()
Determines if the checking object is superuser.
"""
if hasattr(checking_obj, 'is_superuser'):
return checking_obj.is_superuser
return False
def has_id(checking_obj, checked_obj, *args):
"""
Usage:
has_id(3)
This lock type checks if the checking object
has a particular dbref. Note that this only
works for checking objects that are stored
in the database (e.g. not for commands)
"""
if not args:
return False
try:
dbref = int(args[0].strip())
except ValueError:
return False
if hasattr(checking_obj, 'id'):
return dbref == checking_obj.id
return False
def has_attr(checking_obj, checked_obj, *args):
"""
Usage:
has_attr(attrname)
has_attr(attrname, value)
Searches attributes *and* properties stored on the checking
object. The first form works like a flag - if the attribute/property
exists on the object, it returns True. The second form also requires
that the value of the attribute/property matches. Note that all
retrieved values will be converted to strings before doing the comparison.
"""
# deal with arguments
if not args:
return False
attrname = args[0].strip()
value = None
if len(args) > 1:
value = args[1].strip()
# first, look for normal properties on the object trying to gain access
if hasattr(checking_obj, attrname):
if value:
return str(getattr(checking_obj, attrname)) == value
return True
# check attributes, if they exist
#print "lockfunc default: %s (%s)" % (checking_obj, attrname)
if hasattr(checking_obj, 'has_attribute') \
and checking_obj.has_attribute(attrname):
if value:
return hasattr(checking_obj, 'attr') \
and checking_obj.attr(attrname) == value
return True
return False

View file

@ -0,0 +1,25 @@
"""
A simple manager for Permission groups
"""
from django.db import models
class PermissionGroupManager(models.Manager):
"Adds a search method to the default manager"
def search_permgroup(self, ostring):
"""
Find a permission group
ostring = permission group name (case sensitive)
or database dbref
"""
groups = []
try:
dbref = int(ostring.strip('#'))
groups = self.filter(id=dbref)
except Exception:
pass
if not groups:
groups = self.filter(db_key=ostring)
return groups

150
src/permissions/models.py Normal file
View file

@ -0,0 +1,150 @@
"""
The PermissionGroup model clumps permissions together into
manageable chunks.
"""
from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.permissions.manager import PermissionGroupManager
from src.utils.utils import is_iter
#------------------------------------------------------------
#
# PermissionGroup
#
#------------------------------------------------------------
class PermissionGroup(SharedMemoryModel):
"""
This groups permissions into a clump.
The following properties are defined:
key - main ident for group
desc - optional description of group
group_permissions - the perms stored in group
permissions - the group's own permissions
"""
#
# PermissionGroup database model setup
#
# These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
#
# identifier for the group
db_key = models.CharField(max_length=80, unique=True)
# description of the group's permission contents
db_desc = models.CharField(max_length=255, null=True, blank=True)
# the permissions stored in this group; comma separated string
# (NOT to be confused with the group object's own permissions!)
db_group_permissions = models.TextField(blank=True)
# OBS - this is the groups OWN permissions, for accessing/changing
# the group object itself (comma-separated string)!
db_permissions = models.CharField(max_length=256, blank=True)
# Database manager
objects = PermissionGroupManager()
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
"Getter. Allows for value = self.key"
return self.db_key
#@key.setter
def key_set(self, value):
"Setter. Allows for self.key = value"
self.db_key = value
self.save()
#@key.deleter
def key_del(self):
"Deleter. Allows for del self.key"
self.db_key = None
self.save()
key = property(key_get, key_set, key_del)
# desc property (wraps db_desc)
#@property
def desc_get(self):
"Getter. Allows for value = self.desc"
return self.db_desc
#@desc.setter
def desc_set(self, value):
"Setter. Allows for self.desc = value"
self.db_desc = value
self.save()
#@desc.deleter
def desc_del(self):
"Deleter. Allows for del self.desc"
self.db_desc = None
self.save()
desc = property(desc_get, desc_set, desc_del)
# group_permissions property
#@property
def group_permissions_get(self):
"Getter. Allows for value = self.name. Returns a list of group_permissions."
if self.db_group_permissions:
return [perm.strip() for perm in self.db_group_permissions.split(',')]
return []
#@group_permissions.setter
def group_permissions_set(self, value):
"Setter. Allows for self.name = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_group_permissions = value
self.save()
#@group_permissions.deleter
def group_permissions_del(self):
"Deleter. Allows for del self.name"
self.db_group_permissions = ""
self.save()
group_permissions = property(group_permissions_get, group_permissions_set, group_permissions_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.name. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.name = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.name"
self.db_permissions = ""
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
class Meta:
"Define Django meta options"
verbose_name = "Permission Group"
verbose_name_plural = "Permission Groups"
#
# PermissionGroup help method
#
#
def contains(self, permission):
"""
Checks if the given permission string is defined in the
permission group.
"""
return permission in self.group_permissions

View file

@ -0,0 +1,644 @@
"""
Permissions
Evennia's permission system can be as coarse or fine-grained as desired.
**Note: Django's in-built permission checking is still used for web-related access
to the admin site etc. This uses the normal django permission
strings of the form 'appname.permstring' and automatically adds three of them
for each model - for src/object this would be for example
'object.create', 'object.admin' and 'object.edit'. Checking and permissions
are done completely separate from the present mud permission system, see
Django manuals. **
Almost all engine elements (Commands, Players, Objects, Scripts,
Channels) have a field or variable called 'permissions' - this holds a
list or a comma-separated string of 'permission strings'.
Whenever a permission is to be checked there is always an
'Accessing object' and an 'Accessed object'. The 'permissions' field
on the two objects are matched against each other. A permission field
can contain one of two types of strings - 'keys' and 'locks' and the
accessing object's keys are matched against the accessing object's locks
depending on which type of access is required. The check function
has_perm (in this module) is used to handle the check.
access_obj.permissions (keys) ---> | |
lock_type ---> | has_perm() | ---> False/True
accessed_obj.permissions (locks) ---> | |
If there are no locks (of the right type) available on accessed_obj, access is
granted by default. You can however give an argument to has_perm() to reverse
this policy to one where default is no access until explicitly granted.
- Keys
Keys are simple strings that can contain any alphanumerical symbol and
be of any length. They are case-sensitive. Only whitespace, colons(:) and
commas (,) are not allowed in keys.
examples:
myperm
look_perm
Builders
obj.create (this is what a general Django key looks like)
- Locks
A lock always consists of two parts separated by a colon (:). A lock must nowhere
contain commas (,) since these are used as separators between different locks and keys
during storage.
-type header- -lock body-
type1 type2 FLAG:string1 string2 string3()
type header
The first part is the 'type' of lock, i.e. what kind of functionality
this lock regulates. The function has_perm() takes this as an argument and uses it to sort
out which locks are checked (if any).
A type name is not case sensitive but may not contain any whitespace (or colons). Use
whitespaces to expand the number of types this lock represents.
The last word in the type header may be a special flag that regulates how the second
part of the lock is handled, especially if the second part is a space-separated list
of permissions:
- OR or ANY (or none, default) - ANY one match in list means that the entire lock is passed.
- AND or ALL - ALL permissions in list must be matched by accessing obj for lock to pass
- NOT - inverses the lock. A matched lock means False and vice versa.
lock body
The second part, after the comma, is the function body of the lock. The function body
can look in several ways:
- it could be a permission string, to be matched directly with a key
- it could be a function call to a function defined in a safe module (this returns True/False)
- it could be a space-separated list of any combination of the above.
- it could be one of the non-passable keywords FALSE, LOCK or IMPASSABLE (can be put in a
list too, but the list will then always return False, so they are usually
used on their own).
examples:
look:look_perm
this lock is of type 'look' and simply checks if the accessing_obj has
they key look_perm or not.
add edit:Builders
this lock regulates two lock types, 'add' and 'edit'. In both cases,
having the key 'Builders' will pass the lock.
add edit:Builders Admin
this is like above, but it is passed if the accessing object has either
the key Builders or the key Admin (or both)
system NOT: Untrusted
this lock of type 'system' is passed if the accessing object does NOT have
the key 'Untrusted', since the NOT flag inverses the lock result.
delete:FALSE
this lock, of type 'delete' can never be passed since the keyword FALSE
is always False (same with LOCK and IMPASSABLE).
open:
this lock has an empty lock body and is an always passable lock. Unless you
change how has_perm() behaves you can usually just completely skip defining
this lock to get the same effect.
enter:has_key()
this 'enter'-type lock might for example be placed on a door. The lock body
is a function has_key() defined in a module set in the settings. See below
for more info on lock functions. It is up the admin to create
these lock functions ahead of time as needed for the game.
unlock:has_pickpocket_lvl(4,difficulty=7)
function locks can even take arguments, in this case it calls a function
has_pick() with info on how hard the lock is.
delete AND: Builders can_delete has_credits()
Since the keyword AND is set (ALL would also work), the
accessing object must have both the 'Builders', 'can_delete'
*and* the function lock 'has_credits()' in order to pass the lock.
- More on functional locks
A function lock goes into the second part of a lock definition (after the colon).
Syntax:
funcname(arg3, arg4, ...)
arg1 == always accessing_obj
arg2 == always accessed_obj
A function lock is called just like any Python function and should be defined in
the same way. The system searches for the first matching function in one of the
modules defined by settings.PERMISSION_FUNC_MODULES and executes it.
Accessing_object and Accessed_object are always passed, followed by the arguments
and keywords supplied in the permission. The function should return a boolean and
must make sure to handle any possible type of objects being passed in
the first two arguments (such as Command objects, Scripts or whatever). There is a
convenience function available in this module (get_types) for doing just that.
examples:
'is_stronger_than(40)'
'is_suitably_dressed()'
'has_lockpick_higher_than(24, difficulty=4)'
'has_attr_value('mood', 'happy')
- Permissions string
As mentioned, keys and locks are stored together on an object in a string
called 'permissions'. You are most likely to come into contact with this
when defining new Commands.
This is a comma-separated string (which is why commas
are not allowed in keys nor in locks). Keys and Locks can appear in any
order in the permission string, they are easily separated by the colon
appearing only in locks.
example:
'call ALL:can_edit Builders'
this might be a Command permission and has only one lock on it (a command usually have no need
for keys of its own) but two requirements to be able to call it: The accessing object
(probably a player) must have both the can_edit and Builders keys (one is not enough
since the ALL flag is set).
'Builders, edit_channels, control:has_id(45)'
this permission string could sit on a Character object. It has two keys and one lock.
The keys tell us Character is a Builder and also has the edit_channels permission.
The lock is checked when someone tries to gain 'control' over this object (whatever
this means in your game). This lock calls the function has_id() with the argument 45.
We can't see here what has_id() actually does, but a likely implementation would
be to return True only if the accessing object actually has an id of 45.
- Reserved Server lock types
The Evennia server (i.e. everything in /src) tries to 'outsource' as many permission checks
as possible to what the admin can customize in /game. All the admin needs to do is import
has_perm() from this module to use the system. As seen above, the actual syntax of
locks and keys above gives the admin much freedom in designing their own system.
That said, there are a few actions that cannot be outsourced due to technical reasons.
In these situations the server must hard-code its permission checks. What this means
is that it always calls has_perm() with a specific lock-type keyword that you cannot change. For
example it always checks if a command may be accessed by matching the calling player's
keys against command-locks of type 'call'. Below you will find all hard-coded lock types
the server checks and when it does it.
locktype entity situation
call Command when a player tries to call a
command. Determines if the command is available
at all. Also determines if command will be
visible in help lists etc.
chan_send Channels
chan_listen "
traverse Exits
"""
from django.conf import settings
from src.permissions.models import PermissionGroup
from src.utils import logger
from src.utils.utils import is_iter
IMPASSABLE = ['FALSE', 'LOCK', 'IMPASSABLE']
ORFLAGS = ['OR', 'ANY']
ANDFLAGS = ['AND', 'ALL']
NOTFLAGS = ['NOT']
LOCK_FUNC_PATHS = settings.PERMISSION_FUNC_MODULES
CACHED_FUNC_MODULES = []
PARENTS = {
"command":"src.commands.command.Command",
"msg":"src.comms.models.Msg",
"channel":"src.comms.models.Channel",
"help":"src.help.models.HelpEntry",
"typeclass":"src.typeclasses.typeclass.TypeClass",
"script":"src.scripts.models.ScriptDB",
"object":"src.objects.models.ObjectDB",
"player":"src.players.models.Player"}
#
# Utility functions for handling permissions
#
def perm_to_list(permissions):
"""
Process a permstring and return a list
permissions - can be a list of permissions, a permission string
or a comma-separated string of permissions.
"""
if not permissions:
return []
if not is_iter(permissions):
# convert input to a list
permissions = str(permissions)
if ',' in permissions:
permissions = permissions.split(',')
else:
permissions = [permissions]
p_nested = []
for nested_perm in (p for p in permissions if ',' in p):
# handling eventual weird nested comma-separated
# permissions inside lists
p_nested.extend(nested_perm.split(','))
permissions.extend(p_nested)
# merge units with unmatched parenthesis (so e.g. '(a,b,c,d)' are not
# split by comma separation (this allows for functional locks with
# multiple arguments, like 'has_attr(attrname, attrvalue)'). This
# also removes all whitespace in the parentheses to avoid confusion later.
lparents = 0
rparents = 0
in_block = False
perm_list = []
for perm in permissions:
lparents += perm.count('(')
rparents += perm.count(')')
if lparents > rparents:
# unmatched left-parenthesis - trigger block preservation
if not in_block:
# beginning of block
in_block = True
perm_list.append(perm)
else:
# in block
block = perm_list.pop()
perm_list.append(",".join([block.strip(), perm.strip()]))
elif in_block:
# parentheses match again - end the block
in_block = False
block = perm_list.pop()
perm_list.append(",".join([block.strip(), perm.strip()]))
else:
# no parenthesis/block
perm_list.append(perm.strip())
return perm_list
def set_perm(obj, new_perms, replace=True):
"""
Set a permission string on an object.
The permissions given by default replace the old one.
If 'replace' is unset, the new one will be appended to
the old ones.
obj - object to receive permission. must have field/variable
named 'permissions' for this to work.
new_perms - a permission string, a list of permission strings
or a comma-separated string of permissions
replace - clear and replace old permission completely
Note - this is also how you add an entity to a group
"""
try:
# get the old permissions if possible
obj_perms = perm_to_list(obj.permissions)
except AttributeError:
# this object cannot get a permission
return False
new_perms = perm_to_list(new_perms)
if replace:
# replace permissions completely
obj_perms = new_perms
else:
# extend the old permissions with the new ones
for newperm in (perm for perm in new_perms if perm not in obj_perms):
obj_perms.append(newperm)
# set on object as comma-separated list.
obj.permissions = ",".join(str(perm).strip() for perm in obj_perms)
try:
# E.g. Commands are not db-connected and cannot save,
# so we ignore errors here.
obj.save()
except Exception:
pass
return True
def add_perm(obj, add_perms):
"""
Convenience function to add a permission to an entity.
"""
return set_perm(obj, add_perms, replace=False)
def del_perm(obj, del_perms):
"""
Remove permission from object (if possible)
"""
try:
obj_perms = perm_to_list(obj.permissions)
except AttributeError:
return False
del_perms = perm_to_list(del_perms)
obj_perms = [perm for perm in obj_perms if perm not in del_perms]
obj.permissions = ",".join(str(perm) for perm in obj_perms)
try:
obj.save()
except Exception:
pass
return True
def get_types(accessing_obj, accessed_obj):
"""
A convenience function for determining what type the objects are.
This is intended for easy importing into the modules
defined in LOCK_FUNC_PATHS.
"""
def has_parent(basepath, obj):
"Checks if basepath is somewhere is objs parent tree."
return any(cls for cls in obj.__class__.mro()
if basepath == "%s.%s" % (cls.__module__, cls.__name__))
checking_type = None
checked_type = None
try:
checking_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessing_obj)][0]
if checking_type == 'typeclass':
checking_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessing_obj.db)][0]
except IndexError:
logger.log_trace("LockFunc: %s is not of a valid type."
% accessing_obj)
raise
try:
checked_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessed_obj)][0]
if checking_type == 'typeclass':
checked_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessed_obj.db)][0]
except IndexError:
logger.log_trace("LockFunc: %s is not of a valid type."
% accessed_obj)
raise
return (checking_type, checked_type)
#
# helper functions for has_perm()
#
def append_group_permissions(keylist):
"""
Step through keylist and discover if
one the keys match a permission group name
(case sensitive). In that case, go into
that group and add its permissions to
the keylist.
"""
groups = []
for match_key in keylist:
try:
groups.append(PermissionGroup.objects.get(db_key=match_key))
except Exception:
pass
for group in groups:
keylist.extend(perm_to_list(group.group_permissions))
return list(set(keylist)) # set makes elements of keylist unique
def try_impassable(lockdefs):
"""
Test if this list of lockdefs is impassable.
"""
return any(True for lockdef in lockdefs if lockdef in IMPASSABLE)
def try_key_lock(iflag, keylist, lockdefs):
"""
Test a direct-comparison match between keys and lockstrings.
Returns the number of matches found.
"""
if iflag in ANDFLAGS:
return len([True for key in keylist if key in lockdefs])
elif iflag in NOTFLAGS:
return not any(True for key in keylist if key in lockdefs)
else:
return any(True for key in keylist if key in lockdefs)
def try_functional_lock(lflag, lockdefs, accessing_obj, accessed_obj):
"""
Functional lock
lockdefs is a list of permission strings (called by check_lock)
"""
global CACHED_FUNC_MODULES
if not CACHED_FUNC_MODULES:
# Cache the imported func module(s) once and for all
# so we don't have to re-import them for every call.
# We allow for LOCK_FUNC_PATHS to be a tuple of
# paths as well.
CACHED_FUNC_MODULES = []
try:
module_paths = list(LOCK_FUNC_PATHS)
except Exception:
module_paths = [LOCK_FUNC_PATHS]
for path in module_paths:
try:
CACHED_FUNC_MODULES.append(__import__(path, fromlist=[True]))
except ImportError:
logger.log_trace("lock func: import error for '%s'" % path)
continue
# try to execute functions, if they exist
#print "locklist:", locklist
passed_locks = 0
for lockstring in lockdefs:
if not lockstring \
or not ('(' in lockstring and ')' in lockstring) \
or not (lockstring.find('(') < lockstring.find(')')):
# must be a valid function() call
continue
funcname, args = (str(part).strip() for part in lockstring.split('(', 1))
args = args.rstrip(')').split(',')
kwargs = [kwarg for kwarg in args if '=' in kwarg]
args = tuple([arg for arg in args if arg not in kwargs])
kwargs = dict([(key.strip(), value.strip()) for key, value in [kwarg.split('=', 1) for kwarg in kwargs]])
#print "%s '%s'" % (funcname, args)
for module in CACHED_FUNC_MODULES:
# step through all safe modules, executing the first one that matches
lockfunc = module.__dict__.get(funcname, None)
if callable(lockfunc):
try:
# try the lockfunction.
if lockfunc(accessing_obj, accessed_obj, *args, **kwargs):
if lflag in ANDFLAGS:
passed_locks += 1
elif lflag in NOTFLAGS:
return False
else:
return True
except Exception:
continue
return passed_locks
#------------------------------------------------------------
# has_perm & has_perm_string : main access functions
#------------------------------------------------------------
def has_perm(accessing_obj, accessed_obj, lock_type, default_deny=False):
"""
The main access method of the permission system. Note that
this will not perform checks against django's in-built permission
system, that is assumed to be done in the calling function
after this method returns False.
accessing_obj - the object trying to gain access
accessed_obj - the object being checked for access
lock_type - Only locks of this type 'lock_type:permissionstring'
will be considered for a match.
default_deny - Normally, if no suitable locks are found on the object, access
is granted by default. This switch changes security policy to
instead lock down the object unless access is explicitly given.
"""
# Get the list of locks of the accessed_object
try:
locklist = [lock for lock in perm_to_list(accessed_obj.permissions) if ':' in lock]
except AttributeError:
# This is not a valid permissable object at all
logger.log_trace()
return False
# filter the locks to find only those of the correct lock_type. This creates
# a list with elements being tuples (flag, [lockdef, lockdef, ...])
lock_type = lock_type.lower()
locklist = [(typelist[-1].strip(), [lo.strip() for lo in lock.split(None)])
for typelist, lock in [(ltype.split(None), lock)
for ltype, lock in [lock.split(':', 1)
for lock in locklist]]
if typelist and lock_type in typelist]
if not locklist or not any(locklist):
# No locks; use default security policy
return not default_deny
# we have locks of the right type. Set default flag OR on all that
# don't explictly specify a flag (AND, OR, NOT). These flags determine
# how the permstrings in the lock definition should relate to one another.
locktemp = []
for ltype, lock in locklist:
if ltype not in ANDFLAGS and ltype not in NOTFLAGS:
ltype = 'OR'
locktemp.append((ltype, lock))
locklist = locktemp
# Get the list of keys of the accessing_object
keylist = []
#print "testing %s" % accessing_obj.__class__
if hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser:
# superusers always have access.
return True
# try to add permissions from connected player
if hasattr(accessing_obj, 'has_player') and accessing_obj.has_player:
# accessing_obj has a valid, connected player. We start with
# those permissions.
player = accessing_obj.player
if player.is_superuser:
# superuser always has access
return True
try:
keylist.extend([perm for perm in perm_to_list(player.permissions)
if not ':' in perm])
except Exception:
pass
# next we get the permissions directly from accessing_obj.
try:
keylist.extend([perm for perm in perm_to_list(accessing_obj.permissions)
if not ':' in perm])
except Exception:
# not a valid permissable object
return False
# expand also with group permissions, if any
keylist = append_group_permissions(keylist)
#print "keylist: %s" % keylist
# Test permissions against the locks
for lflag, lockdefs in locklist:
# impassable locks normally shuts down the entire operation right away.
if try_impassable(lockdefs):
return lflag in NOTFLAGS
# test direct key-lock comparison and functional locks
if lflag in ANDFLAGS:
# with the AND flag, we have to match all lockdefs to pass the lock
passed_locks = try_key_lock(lflag, keylist, lockdefs)
passed_locks += try_functional_lock(lflag, lockdefs, accessing_obj, accessed_obj)
if passed_locks == len(lockdefs):
return True
else:
# normal operation; any match passes the lock
if try_key_lock(lflag, keylist, lockdefs):
return True
if try_functional_lock(lflag, lockdefs, accessing_obj, accessed_obj):
return True
# If we fall through to here, we don't have access
return False
def has_perm_string(accessing_obj, lock_list, default_deny=False):
"""
This tests the given accessing object against the
given string rather than a particular accessing object.
OBS: Be careful if supplying function locks to this method since
there is no meaningful accessed_obj present (the one fed to
the function is just a dummy).
accessing_obj - the object being checked for permissions
lock_list - a list or a comma-separated string of lock definitions.
default_deny - Normally, if no suitable locks are found on the object, access
is granted by default. This switch changes security policy to
instead lock down the object unless access is explicitly given.
"""
# prepare the permissions we want, so it's properly stripped etc.
class Dummy(object):
"Dummy object"
def __init__(self, permissions):
self.permissions = permissions
if not is_iter(lock_list):
lock_list = [perm for perm in lock_list.split(',')]
lockstring = ",".join(["dummy:%s" % perm.strip() for perm in lock_list if perm.strip()])
# prepare a dummy object with the permissions
accessed_obj = Dummy(lockstring)
# call has_perm normally with the dummy object.
return has_perm(accessing_obj, accessed_obj, 'dummy', default_deny)

29
src/players/admin.py Normal file
View file

@ -0,0 +1,29 @@
#
# This sets up how models are displayed
# in the web admin interface.
#
from src.players.models import PlayerDB, PlayerAttribute
from django.contrib import admin
class PlayerAttributeAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_value', 'db_mode', 'db_obj', 'db_permissions')
list_display_links = ("id", 'db_key')
ordering = ["db_obj", 'db_key']
readonly_fields = ['db_permissions']
search_fields = ['id', 'db_key', 'db_obj']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(PlayerAttribute, PlayerAttributeAdmin)
class PlayerDBAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'db_obj', 'db_typeclass_path')
list_display_links = ('id', 'user')
ordering = ['id', 'user']
readonly_fields = ['db_permissions']
search_fields = ['^db_key', 'db_typeclass_path']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(PlayerDB, PlayerDBAdmin)

147
src/players/manager.py Normal file
View file

@ -0,0 +1,147 @@
"""
The managers for the custom Player object and permissions.
"""
import datetime
from django.db import models
from django.contrib.auth.models import User
from src.typeclasses.managers import returns_typeclass_list, returns_typeclass
#
# Player Manager
#
def returns_player_list(method):
"""
decorator that makes sure that a method
returns a Player object instead of a User
one (if you really want the User object, not
the player, use the player's 'user' property)
"""
def func(self, *args, **kwargs):
"This *always* returns a list."
match = method(self, *args, **kwargs)
if not match:
return []
try:
match = list(match)
except TypeError:
match = [match]
players = []
for user in match:
try:
players.append(user.get_profile())
except Exception:
players.append(user)
return players
return func
def returns_player(method):
"""
Decorator: Always returns a single result or None.
"""
def func(self, *args, **kwargs):
"decorator"
rfunc = returns_player_list(method)
match = rfunc(self, *args, **kwargs)
if match:
return match[0]
else:
return None
return func
class PlayerManager(models.Manager):
"""
Custom manager for the player profile model. We use this
to wrap users in general in evennia, and supply some useful
search/statistics methods.
"""
def num_total_players(self):
"""
Returns the total number of registered users/players.
"""
return self.count()
@returns_typeclass_list
def get_connected_players(self):
"""
Returns a list of player objects with currently connected users/players.
"""
return [player for player in self.all() if player.sessions]
@returns_typeclass_list
@returns_player_list
def get_recently_created_players(self, days=7):
"""
Returns a QuerySet containing the player User accounts that have been
connected within the last <days> days.
"""
end_date = datetime.datetime.now()
tdelta = datetime.timedelta(days)
start_date = end_date - tdelta
return User.objects.filter(date_joined__range=(start_date, end_date))
@returns_typeclass_list
@returns_player_list
def get_recently_connected_players(self, days=7):
"""
Returns a QuerySet containing the player User accounts that have been
connected within the last <days> days.
"""
end_date = datetime.datetime.now()
tdelta = datetime.timedelta(days)
start_date = end_date - tdelta
return User.objects.filter(last_login__range=(
start_date, end_date)).order_by('-last_login')
@returns_typeclass
@returns_player
def get_player_from_email(self, uemail):
"""
Returns a player object when given an email address.
"""
return User.objects.filter(email__iexact=uemail)
@returns_typeclass
def get_player_from_name(self, uname):
"Get player object based on name"
players = self.filter(user__username=uname)
if players:
return players[0]
return None
# @returns_typeclass_list
# def get_players_with_perm(self, permstring):
# """
# Returns all players having access according to the given
# permission string.
# """
# return [player for player in self.all()
# if player.has_perm(permstring)]
# @returns_typeclass_list
# def get_players_with_group(self, groupstring):
# """
# Returns all players belonging to the given group.
# """
# return [player.user for player in self.all()
# if player.has_group(groupstring)]
@returns_typeclass_list
def player_search(self, ostring):
"""
Searches for a particular player by name or
database id.
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

278
src/players/models.py Normal file
View file

@ -0,0 +1,278 @@
"""
Player
The Player class is a simple extension of the django User model using
the 'profile' system of django. A profile is a model that tack new
fields to the User model without actually editing the User model
(which would mean hacking into django internals which we want to avoid
for future compatability reasons). The profile, which we call
'Player', is accessed with user.get_profile() by the property 'player'
defined on ObjectDB objects. Since we can customize it, we will try to
abstract as many operations as possible to work on Player rather than
on User.
We use the Player to store a more mud-friendly style of permission
system as well as to allow the admin more flexibility by storing
attributes on the Player. Within the game we should normally use the
Player manager's methods to create users, since that automatically
adds the profile extension.
The default Django permission system is geared towards web-use, and is
defined on a per-application basis permissions. In django terms,
'src/objects' is one application, 'src/scripts' another, indeed all
folders in /src with a model.py inside them is an application. Django
permissions thus have the form
e.g. 'applicationlabel.permissionstring' and django automatically
defines a set of these for editing each application from its automatic
admin interface. These are still available should you want them.
For most in-game mud-use however, like commands and other things, it
does not make sense to tie permissions to the applications in src/ -
To the user these should all just be considered part of the game
engine. So instead we define our own separate permission system here,
borrowing heavily from the django original, but allowing the
permission string to look however we want, making them unrelated to
the applications.
To make the Player model more flexible for your own game, it can also
persistently store attributes of its own. This is ideal for extra
account info and OOC account configuration variables etc.
"""
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 src.server import sessionhandler
from src.players import manager
from src.typeclasses.models import Attribute, TypedObject
from src.permissions import permissions
from src.utils.ansi import parse_ansi
from src.utils import logger
#------------------------------------------------------------
#
# PlayerAttribute
#
#------------------------------------------------------------
class PlayerAttribute(Attribute):
"""
PlayerAttributes work the same way as Attributes on game objects,
but are intended to store OOC information specific to each user
and game (example would be configurations etc).
"""
db_obj = models.ForeignKey("PlayerDB")
class Meta:
"Define Django meta options"
verbose_name = "Player Attribute"
verbose_name_plural = "Player Attributes"
#------------------------------------------------------------
#
# PlayerDB
#
#------------------------------------------------------------
class PlayerDB(TypedObject):
"""
This is a special model using Django's 'profile' functionality
and extends the default Django User model. It is defined as such
by use of the variable AUTH_PROFILE_MODULE in the settings.
One accesses the fields/methods. We try use this model as much
as possible rather than User, since we can customize this to
our liking.
The TypedObject supplies the following (inherited) properties:
key - main name
name - alias for key
typeclass_path - the path to the decorating typeclass
typeclass - auto-linked typeclass
date_created - time stamp of object creation
permissions - perm strings
dbref - #id of object
db - persistent attribute storage
ndb - non-persistent attribute storage
The PlayerDB adds the following properties:
user - Connected User object. django field, needs to be save():d.
obj - game object controlled by player
character - alias for obj
name - alias for user.username
sessions - sessions connected to this player
is_superuser - bool if this player is a superuser
"""
#
# PlayerDB Database model setup
#
# inherited fields (from TypedObject):
# db_key, db_typeclass_path, db_date_created, db_permissions
# this is the one-to-one link between the customized Player object and
# this profile model. It is required by django.
user = models.ForeignKey(User, unique=True)
# 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 manager
objects = manager.PlayerManager()
class Meta:
app_label = 'players'
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# obj property (wraps db_obj)
#@property
def obj_get(self):
"Getter. Allows for value = self.obj"
return self.db_obj
#@obj.setter
def obj_set(self, value):
"Setter. Allows for self.obj = value"
from src.typeclasses.typeclass import TypeClass
if isinstance(value, TypeClass):
value = value.dbobj
try:
self.db_obj = value
self.save()
except Exception:
logger.log_trace()
raise Exception("Cannot assign %s as a player object!" % value)
#@obj.deleter
def obj_del(self):
"Deleter. Allows for del self.obj"
self.db_obj = None
self.save()
obj = property(obj_get, obj_set, obj_del)
# whereas the name 'obj' is consistent with the rest of the code,
# 'character' is a more intuitive property name, so we
# define this too, as an alias to player.obj.
#@property
def character_get(self):
"Getter. Allows for value = self.character"
return self.obj
#@character.setter
def character_set(self, value):
"Setter. Allows for self.character = value"
self.obj = value
#@character.deleter
def character_del(self):
"Deleter. Allows for del self.character"
del self.obj
character = property(character_get, character_set, character_del)
class Meta:
"Define Django meta options"
verbose_name = "Player"
verbose_name_plural = "Players"
#
# PlayerDB main class properties and methods
#
def __str__(self):
return smart_str("%s(player %i)" % (self.name, self.id))
def __unicode__(self):
return u"%s(player#%i)" % (self.name, self.id)
# this is used by all typedobjects as a fallback
try:
default_typeclass_path = settings.BASE_PLAYER_TYPECLASS
except Exception:
default_typeclass_path = "src.players.player.Player"
# this is required to properly handle attributes
attribute_model_path = "src.players.models"
attribute_model_name = "PlayerAttribute"
# name property (wraps self.user.username)
#@property
def name_get(self):
"Getter. Allows for value = self.name"
return self.user.username
#@name.setter
def name_set(self, value):
"Setter. Allows for player.name = newname"
self.user.username = value
self.user.save() # this might be stopped by Django?
#@name.deleter
def name_del(self):
"Deleter. Allows for del self.name"
raise Exception("Player name cannot be deleted!")
name = property(name_get, name_set, name_del)
key = property(name_get, name_set, name_del)
# sessions property (wraps sessionhandler)
#@property
def sessions_get(self):
"Getter. Retrieve sessions related to this player/user"
return sessionhandler.find_sessions_from_username(self.name)
#@sessions.setter
def sessions_set(self, value):
"Setter. Protects the sessions property from adding things"
raise Exception("Cannot set sessions manually!")
#@sessions.deleter
def sessions_del(self):
"Deleter. Protects the sessions property from deletion"
raise Exception("Cannot delete sessions manually!")
sessions = property(sessions_get, sessions_set, sessions_del)
#@property
def is_superuser_get(self):
"Superusers have all permissions."
return self.user.is_superuser
is_superuser = property(is_superuser_get)
def set_perm(self, perm):
"Shortcuts to set permissions, replacing old ones"
return permissions.set_perm(self, perm)
def add_perm(self, perm):
"Add permissions to the old ones"
return permissions.add_perm(self, perm)
def del_perm(self, perm):
"Delete permission from old ones"
return permissions.del_perm(self, perm)
#
# PlayerDB class access methods
#
def msg(self, message, from_obj=None):
"""
This duplicates the same-named method on the Character.
It forwards messages to the character or uses
the session messaging directly.
"""
if self.character:
self.character.msg(message, from_obj)
else:
if from_obj:
try:
from_obj.at_msg_send(message, self)
except Exception:
pass
if self.at_msg_receive(message, from_obj):
for session in self.sessions:
session.msg(parse_ansi(message))
def emit_to(self, message, from_obj=None):
"""
Deprecated. Use msg instead.
"""
self.msg(message, from_obj)

79
src/players/player.py Normal file
View file

@ -0,0 +1,79 @@
"""
Typeclass for Player objects
Note that this object is primarily intended to
store OOC information, not game info! This
object represents the actual user (not their
character) and has NO actual precence in the
game world (this is handled by the associated
character object, so you should customize that
instead for most things).
"""
from src.typeclasses.typeclass import TypeClass
class Player(TypeClass):
"""
Base typeclass for all Players.
"""
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
# character ones instead, unless you
# are implementing a multi-character game
# and have some things that should be done
# regardless of which character is currently
# connected to this player.
def at_first_login(self):
"""
Only called once, the very first
time the user logs in.
"""
pass
def at_pre_login(self):
"""
Called every time the user logs in,
before they are actually logged in.
"""
pass
def at_post_login(self):
"""
Called at the end of the login
process, just before letting
them loose.
"""
pass
def at_disconnect(self):
"""
Called just before user
is disconnected.
"""
pass
def at_message_receive(self, message, from_obj=None):
"""
Called when any text is emitted to this
object. If it returns False, no text
will be sent automatically.
"""
return True
def at_message_send(self, message, to_object):
"""
Called whenever this object tries to send text
to another object. Only called if the object supplied
itself as a sender in the msg() call.
"""
pass

View file

@ -1,97 +0,0 @@
"""
This file contains the event scheduler system.
ADDING AN EVENT:
* Create an event sub-class from the IntervalEvent class in events.py.
* Call src.scheduler.add_event() with your IntervalEvent subclass as the arg.
* Make sure that the module where your add_event() call resides is either
imported, or that add_event() is called by a command or some kind of action.
* Profit.
"""
from src.cache import cache
CACHE_NAME = "_persistent_event_cache"
# dict of IntervalEvent sub-classed objects, keyed by their
# process id:s.
SCHEDULE = []
def next_free_pid():
"""
Find the next free pid
"""
pids = [event.pid for event in SCHEDULE]
if not pids:
return 0
maxpid = max(pids)
freepids = [pid for pid in xrange(maxpid+1) if pid not in pids]
if freepids:
return min(freepids)
return maxpid + 1
def add_event(event):
"""
Adds an event instance to the scheduled event list. Call this any time you
need to add a custom event to the global scheduler.
Args:
* event: (IntervalEvent) The event to add to the scheduler.
Returns:
* pid : (int) The process ID assigned to this event, for future reference.
"""
# Make sure not to add multiple instances of the same event.
matches = [i for i, stored_event in enumerate(SCHEDULE)
if event == stored_event]
if matches:
#print "replacing existing event pid=%i: %s" % (event.pid, event.name)
# Before replacing an event, stop its old incarnation.
del_event(matches[0])
SCHEDULE[matches[0]] = event
else:
# Add a new event with a fresh pid.
event.pid = next_free_pid()
#print "adding new event with fresh pid=%i: %s" % (event.pid,event.name)
SCHEDULE.append(event)
event.start_event_loop()
if event.persistent:
# We have to sync to disk, otherwise we might end up
# in situations (such as after a crash) where an object exists,
# but the event tied to it does not.
ecache = [event for event in SCHEDULE if event.persistent]
cache.set_pcache("_persistent_event_cache", ecache)
cache.save_pcache()
return event.pid
def get_event(pid):
"""
Return an event with the given pid, if it exists,
otherwise return None.
"""
pid = int(pid)
imatches = [i for i, stored_event in enumerate(SCHEDULE)
if stored_event.pid == pid]
if imatches:
return SCHEDULE[imatches[0]]
def del_event(pid):
"""
Remove an event from scheduler. There should never be more than one
event with a certain pid, this cleans up in case there are any multiples.
"""
pid = int(pid)
imatches = [i for i, stored_event in enumerate(SCHEDULE)
if stored_event.pid == pid]
for imatch in imatches:
event = SCHEDULE[imatch]
event.stop_event_loop()
del SCHEDULE[imatch]
if event.persistent:
# We have to sync to disk, otherwise we might end
# up in situations (such as after a crash) where an
# object has been removed, but the event tied to it remains.
ecache = [event for event in SCHEDULE
if event.persistent]
cache.set_pcache("_persistent_event_cache", ecache)
cache.save_pcache()

View file

@ -1,281 +0,0 @@
"""
This is the base object type/interface that all parents are derived from by
default. Each object type sub-classes this class and over-rides methods as
needed.
NOTE: This file should NOT be directly modified. Sub-class the BasicObject
class in game/gamesrc/parents/base/basicobject.py and change the
SCRIPT_DEFAULT_OBJECT variable in settings.py to point to the new class.
"""
from src.cmdtable import CommandTable
from src.ansi import ANSITable
class EvenniaBasicObject(object):
def __init__(self, scripted_obj, *args, **kwargs):
"""
Get our ducks in a row. You should generally never override this. Note
that this will not be called on object creation in a manner typical to
most Python objects. This is only called when the script parent is
cached or recalled on an object. This means that this function is not
called until someone does something to warrant calling get_scriptlink().
This happens very often, so nothing too intense should be done here.
If you're wanting to do something on object/player creation, override
at_object_creation() (in basicobject.py) or at_player_creation()
(in basicplayer.py).
scripted_obj: (Object) A reference to the object being scripted (the child).
"""
self.scripted_obj = scripted_obj
# below is now handled by self.scripted_obj.add_command() in
# self.at_object_creation().
#self.command_table = CommandTable()
def at_object_creation(self):
"""
This is triggered after a new object is created and ready to go. If
you'd like to set attributes, flags, or do anything when the object
is created, do it here and not in __init__().
"""
pass
def at_object_destruction(self, pobject=None):
"""
This is triggered when an object is about to be destroyed via
@destroy ONLY. If an object is deleted via delete(), it is assumed
that this method is to be skipped.
values:
* pobject: (Object) The object requesting the action.
"""
# Un-comment the line below for an example
#print "SCRIPT TEST: %s looked at %s." % (pobject, self.scripted_obj)
pass
def at_desc(self, pobject=None):
"""
Perform this action when someone uses the LOOK command on the object.
values:
* pobject: (Object) The object requesting the action.
"""
# Un-comment the line below for an example
#print "SCRIPT TEST: %s looked at %s." % (pobject, self.scripted_obj)
pass
def at_get(self, pobject):
"""
Perform this action when someone uses the GET command on the object.
values:
* pobject: (Object) The object requesting the action.
"""
# Un-comment the line below for an example
#print "SCRIPT TEST: %s got %s." % (pobject, self.scripted_obj)
pass
def at_before_move(self, target_location):
"""
This hook is called just before the object is moved.
arg:
target_location (obj): the place where this object is to be moved
returns:
if this function returns anything but None, the move is cancelled.
"""
pass
def announce_move_from(self, target_location):
"""
Called when announcing to leave a destination.
target_location - the place we are going to
"""
obj = self.scripted_obj
loc = obj.get_location()
if loc:
loc.emit_to_contents("%s has left." % obj.get_name(), exclude=obj)
if loc.is_player():
loc.emit_to("%s has left your inventory." % (obj.get_name()))
def announce_move_to(self, source_location):
"""
Called when announcing one's arrival at a destination.
source_location - the place we are coming from
"""
obj = self.scripted_obj
loc = obj.get_location()
if loc:
loc.emit_to_contents("%s has arrived." % obj.get_name(), exclude=obj)
if loc.is_player():
loc.emit_to("%s is now in your inventory." % obj.get_name())
def at_after_move(self, old_loc=None):
"""
This hook is called just after the object was successfully moved.
No return values.
"""
pass
def at_drop(self, pobject):
"""
Perform this action when someone uses the DROP command on the object.
values:
* pobject: (Object) The object requesting the action.
"""
# Un-comment the line below for an example
#print "SCRIPT TEST: %s dropped %s." % (pobject, self.scripted_obj)
pass
def at_obj_receive(self, object=None, old_loc=None):
"""
Called whenever an object is added to the contents of this object.
"""
pass
def return_appearance(self, pobject=None):
"""
Returns a string representation of an object's appearance when LOOKed at.
values:
* pobject: (Object) The object requesting the action.
"""
# This is the object being looked at.
target_obj = self.scripted_obj
# See if the envoker sees dbref numbers.
lock_msg = ""
if pobject:
#check visibility lock
if not target_obj.scriptlink.visible_lock(pobject):
temp = target_obj.get_attribute_value("visible_lock_msg")
if temp:
return temp
return "I don't see that here."
show_dbrefs = pobject.sees_dbrefs()
#check for the defaultlock, this shows a lock message after the normal desc, if one is defined.
if target_obj.is_room() and \
not target_obj.scriptlink.default_lock(pobject):
temp = target_obj.get_attribute_value("lock_msg")
if temp:
lock_msg = "\n%s" % temp
else:
show_dbrefs = False
description = target_obj.get_attribute_value('desc')
if description is not None:
retval = "%s%s\r\n%s%s%s" % ("%ch",
target_obj.get_name(show_dbref=show_dbrefs),
target_obj.get_attribute_value('desc'), lock_msg,
"%cn")
else:
retval = "%s%s%s" % ("%ch",
target_obj.get_name(show_dbref=show_dbrefs),
"%cn")
# Storage for the different object types.
con_players = []
con_things = []
con_exits = []
for obj in target_obj.get_contents():
# check visible lock.
if pobject and not obj.scriptlink.visible_lock(pobject):
continue
if obj.is_player():
if (obj != pobject and obj.is_connected_plr()) or pobject == None:
con_players.append(obj)
elif obj.is_exit():
con_exits.append(obj)
else:
con_things.append(obj)
if not con_players == []:
retval += "\n\r%sPlayers:%s" % (ANSITable.ansi["hilite"],
ANSITable.ansi["normal"])
for player in con_players:
retval +='\n\r%s' % (player.get_name(show_dbref=show_dbrefs),)
if not con_things == []:
retval += "\n\r%sYou see:%s" % (ANSITable.ansi["hilite"],
ANSITable.ansi["normal"])
for thing in con_things:
retval += '\n\r%s' % (thing.get_name(show_dbref=show_dbrefs),)
if not con_exits == []:
retval += "\n\r%sExits:%s" % (ANSITable.ansi["hilite"],
ANSITable.ansi["normal"])
for exit in con_exits:
retval += '\n\r%s' %(exit.get_name(show_dbref=show_dbrefs),)
return retval
def default_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for picking up
objects or traversing exits.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("DefaultLock", pobject)
else:
return True
def use_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for seeing whether
a player can use an object or any of its commands.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("UseLock", pobject)
else:
return True
def enter_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for seeing whether
a player can enter another object.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("EnterLock", pobject)
else:
return True
def visible_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for picking up
objects or traversing exits.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("VisibleLock", pobject)
else:
return True
def lock_func(self, obj):
"""
This is a custom function called by locks with the FuncKey key. Its
return value should match that specified in the lock (so no true/false
lock result is actually determined in here). Default desired return
value is True. Also remember that the comparison in FuncKey is made
using the string representation of the return value, since @lock can
only define string lock criteria.
"""
return False

View file

@ -1,187 +0,0 @@
"""
This is the basic Evennia-standard player parent.
NOTE: This file should NOT be directly modified. Sub-class the BasicPlayer
class in game/gamesrc/parents/base/basicplayer.py and change the
SCRIPT_DEFAULT_PLAYER variable in settings.py to point to the new class.
"""
import time
from src import comsys
from src.config.models import ConfigValue
class EvenniaBasicPlayer(object):
def at_player_creation(self):
"""
This is triggered after a new User and accompanying Object is created.
By the time this is triggered, the player is ready to go but not
logged in. Note that this is different from at_object_creation(), which
is executed before at_player_creation(). This function is only
triggered when the User account _and_ the Object are ready.
"""
pass
def at_pre_login(self, session):
"""
Everything done here takes place before the player is actually
'logged in', in a sense that they're not ready to send logged in
commands or receive communication.
"""
pobject = self.scripted_obj
# Load the player's channels from their JSON __CHANLIST attribute.
comsys.load_object_channels(pobject)
pobject.set_attribute("Last", "%s" % (time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()),))
pobject.set_attribute("Lastsite", "%s" % (session.address[0],))
pobject.set_flag("CONNECTED", True)
def at_first_login(self, session):
"""
This hook is called only *once*, when the player is created and logs
in for first time. It is called after the user has logged in, but
before at_post_login() is called.
"""
pobject = self.scripted_obj
pobject.emit_to("Welcome to %s, %s.\n\r" % (
ConfigValue.objects.get_configvalue('site_name'),
pobject.get_name(show_dbref=False)))
def at_post_login(self, session):
"""
The user is now logged in. This is what happens right after the moment
they are 'connected'.
"""
pobject = self.scripted_obj
pobject.emit_to("You are now logged in as %s." % (pobject.name,))
pobject.get_location().emit_to_contents("%s has connected." %
(pobject.get_name(show_dbref=False),), exclude=pobject)
pobject.execute_cmd("look")
def at_before_move(self, target_location):
"""
This hook is called just before the player is moved.
Input:
target_location (obj): The location the player is about to move to.
Return value:
If this function returns anything but None (no return value),
the move is aborted. This allows for character-based move
restrictions (not only exit locks).
"""
pass
def announce_move_from(self, target_location):
"""
Called when announcing to leave a destination.
target_location - the place we are about to move to
"""
obj = self.scripted_obj
loc = obj.get_location()
if loc:
loc.emit_to_contents("%s has left." % obj.get_name(), exclude=obj)
if loc.is_player():
loc.emit_to("%s has left your inventory." % (obj.get_name()))
def announce_move_to(self, source_location):
"""
Called when announcing one's arrival at a destination.
source_location - the place we are coming from
"""
obj = self.scripted_obj
loc = obj.get_location()
if loc:
loc.emit_to_contents("%s has arrived." % obj.get_name(),exclude=obj)
if loc.is_player():
loc.emit_to("%s is now in your inventory." % obj.get_name())
def at_after_move(self):
"""
This hook is called just after the player has been successfully moved.
"""
pass
def at_obj_receive(self, object=None):
"""
Called whenever an object is added to the inventory of the player.
"""
pass
def at_disconnect(self):
"""
This is called just before the session disconnects, for whatever reason.
"""
pobject = self.scripted_obj
location = pobject.get_location()
if location != None:
location.emit_to_contents("%s has disconnected." % (pobject.get_name(show_dbref=False),), exclude=pobject)
def default_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for picking up
objects or traversing exits.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("DefaultLock", pobject)
else:
return True
def use_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for seeing whether
a player can use an object or any of its commands.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("UseLock", pobject)
else:
return True
def enter_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for seeing whether
a player can enter another object.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("EnterLock", pobject)
else:
return True
def visible_lock(self, pobject):
"""
This method returns a True or False boolean value to determine whether
the actor passes the lock. This is generally used for picking up
objects or traversing exits.
values:
* pobject: (Object) The object requesting the action.
"""
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("VisibleLock", pobject)
else:
return True
def lock_func(self, obj):
"""
This is a custom function called by locks with the FuncKey key. Its
return value should match that specified in the lock (so no true/false
lock result is actually determined in here). Default desired return
value is True. Also remember that the comparison in FuncKey is made
using the string representation of the return value, since @lock can
only define string lock criteria.
"""
return False

View file

@ -1,70 +0,0 @@
"""
This module is responsible for managing scripts and their connection to the
Object class model. It is important to keep this as independent from the
codebase as possible in order to allow for drop-in replacements. All
interaction with actual script methods should happen via calls to Objects.
"""
import os
from traceback import format_exc
from twisted.python.rebuild import rebuild
from django.conf import settings
from src import logger
# A dictionary with keys equivalent to the script's name and values that
# contain references to the associated module for each key.
CACHED_SCRIPTS = {}
def rebuild_cache():
"""
Rebuild all cached scripts.
"""
cache_dict = CACHED_SCRIPTS.items()
for key, val in cache_dict:
rebuild(val)
def scriptlink(source_obj, scriptname):
"""
Each Object will refer to this function when trying to execute a function
contained within a scripted module. For the sake of ease of management,
modules are cached and compiled as they are requested and stored in
the CACHED_SCRIPTS dictionary.
Returns a reference to an instance of the script's class as per it's
class_factory() method.
source_obj: (Object) A reference to the object being scripted.
scriptname: (str) Name of the module to load (minus 'scripts').
"""
# The module is already cached, just return it rather than re-load.
retval = CACHED_SCRIPTS.get(scriptname, False)
if retval:
return retval.class_factory(source_obj)
"""
NOTE: Only go past here when the script isn't already cached.
"""
# Split the script name up by periods to give us the directory we need
# to change to. I really wish we didn't have to do this, but there's some
# strange issue with __import__ and more than two directories worth of
# nesting.
full_script = "%s.%s" % (settings.SCRIPT_IMPORT_PATH, scriptname)
script_name = full_script.split('.')[-1]
try:
# Change the working directory to the location of the script and import.
logger.log_infomsg("SCRIPT: Caching and importing %s." % (scriptname))
modreference = __import__(full_script, fromlist=[script_name])
# Store the module reference for later fast retrieval.
CACHED_SCRIPTS[scriptname] = modreference
except ImportError:
logger.log_infomsg('Error importing %s: %s' % (scriptname, format_exc()))
os.chdir(settings.BASE_PATH)
return
except OSError:
logger.log_infomsg('Invalid module path: %s' % (format_exc()))
os.chdir(settings.BASE_PATH)
return
# The new script module has been cached, return the reference.
return modreference.class_factory(source_obj)

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