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:
parent
df29defbcd
commit
f83c2bddf8
222 changed files with 22304 additions and 14371 deletions
|
|
@ -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
284
src/cache/cache.py
vendored
|
|
@ -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()
|
||||
20
src/cache/managers/cache.py
vendored
20
src/cache/managers/cache.py
vendored
|
|
@ -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
56
src/cache/models.py
vendored
|
|
@ -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
1
src/cache/views.py
vendored
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
364
src/commands/cmdhandler.py
Normal 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
174
src/commands/cmdparser.py
Normal 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
338
src/commands/cmdset.py
Normal 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
|
||||
376
src/commands/cmdsethandler.py
Normal file
376
src/commands/cmdsethandler.py
Normal 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
149
src/commands/command.py
Normal 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))
|
||||
|
|
@ -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")
|
||||
|
|
@ -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)
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
@ -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"),
|
||||
|
|
@ -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
|
|
@ -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")
|
||||
|
|
@ -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" )
|
||||
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
40
src/comms/admin.py
Normal 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
138
src/comms/channelhandler.py
Normal 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
322
src/comms/managers.py
Normal 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
590
src/comms/models.py
Normal 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"
|
||||
|
||||
398
src/comsys.py
398
src/comsys.py
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
91
src/config/manager.py
Normal 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
|
||||
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
"""
|
||||
Custom manager for CommandAlias objects.
|
||||
"""
|
||||
from django.db import models
|
||||
|
||||
class CommandAliasManager(models.Manager):
|
||||
pass
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
|
|
@ -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
|
||||
|
|
@ -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."
|
||||
196
src/events.py
196
src/events.py
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
30
src/flags.py
30
src/flags.py
|
|
@ -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
|
||||
133
src/gametime.py
133
src/gametime.py
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
12
src/help/admin.py
Normal file
12
src/help/admin.py
Normal 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
86
src/help/manager.py
Normal 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
158
src/help/models.py
Normal 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)
|
||||
1
src/help/mux_help_db.json
Normal file
1
src/help/mux_help_db.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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)
|
||||
|
|
@ -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}}]
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)]
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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."
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
330
src/locks.py
330
src/locks.py
|
|
@ -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
|
||||
|
|
@ -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
0
src/objects/__init__.py
Executable file → Normal 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
88
src/objects/exithandler.py
Normal file
88
src/objects/exithandler.py
Normal 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
388
src/objects/manager.py
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
1896
src/objects/models.py
Executable file → Normal file
File diff suppressed because it is too large
Load diff
104
src/objects/object_search_funcs.py
Normal file
104
src/objects/object_search_funcs.py
Normal 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
376
src/objects/objects.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
18
src/permissions/admin.py
Normal file
18
src/permissions/admin.py
Normal 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)
|
||||
100
src/permissions/default_permissions.py
Normal file
100
src/permissions/default_permissions.py
Normal 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']
|
||||
}
|
||||
188
src/permissions/lockfunc_default.py
Normal file
188
src/permissions/lockfunc_default.py
Normal 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
|
||||
|
||||
25
src/permissions/manager.py
Normal file
25
src/permissions/manager.py
Normal 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
150
src/permissions/models.py
Normal 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
|
||||
644
src/permissions/permissions.py
Normal file
644
src/permissions/permissions.py
Normal 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
29
src/players/admin.py
Normal 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
147
src/players/manager.py
Normal 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
278
src/players/models.py
Normal 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
79
src/players/player.py
Normal 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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue