mirror of
https://github.com/evennia/evennia.git
synced 2026-04-03 06:27:17 +02:00
After lots of discussions, default commands where moved from game/gamesrc/commands/default to src/commands/default in order to make it clearer which parts are updated as part of evennia and which can be tweaked at heart's content. New templates where left in gamesrc/commands that should hopefully make it clearer how to extend the command system. Also game/web was moved to src/web - we'll likely extend this from game/gamesrc/web in the future. If you already did extensions you should just have to edit your import paths and make use of the new cmdset template supplied.
The unit testing was for commands was split out from src/objects/tests.py into the new src/commands/default/test.py in order to keep the testing modules thematically grouped with the things they are testing.
This commit is contained in:
parent
a3917073ff
commit
72d40285b8
61 changed files with 381 additions and 184 deletions
|
|
@ -204,13 +204,22 @@ class CmdSet(object):
|
|||
"""
|
||||
Add a command to this cmdset.
|
||||
|
||||
Note that if cmd already has
|
||||
Note that if cmd already exists in set,
|
||||
it will replace the old one (no priority checking etc
|
||||
at this point; this is often used to overload
|
||||
default commands).
|
||||
"""
|
||||
cmd = instantiate(cmd)
|
||||
|
||||
if cmd:
|
||||
if not hasattr(cmd, 'obj'):
|
||||
cmd.obj = self.cmdsetobj
|
||||
self.commands.append(cmd)
|
||||
try:
|
||||
ic = self.commands.index(cmd)
|
||||
self.commands[ic] = cmd # replace
|
||||
except ValueError:
|
||||
self.commands.append(cmd)
|
||||
# extra run to make sure to avoid doublets
|
||||
self.commands = list(set(self.commands))
|
||||
|
||||
def remove(self, cmd):
|
||||
|
|
|
|||
|
|
@ -145,5 +145,18 @@ class Command(object):
|
|||
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))
|
||||
# a simple test command to show the available properties
|
||||
string = "-" * 50
|
||||
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
|
||||
string += "-" * 50
|
||||
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
|
||||
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
|
||||
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
|
||||
string += "help category (self.help_category): {w%s{n\n" % self.help_category
|
||||
string += "object calling (self.caller): {w%s{n\n" % self.caller
|
||||
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
|
||||
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
|
||||
# show cmdset.key instead of cmdset to shorten output
|
||||
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
|
||||
|
||||
self.caller.msg(string)
|
||||
|
|
|
|||
0
src/commands/default/__init__.py
Normal file
0
src/commands/default/__init__.py
Normal file
482
src/commands/default/admin.py
Normal file
482
src/commands/default/admin.py
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
"""
|
||||
|
||||
Admin commands
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from src.players.models import PlayerDB
|
||||
from src.server import sessionhandler
|
||||
from src.permissions.permissions import has_perm, has_perm_string
|
||||
from src.permissions.models import PermissionGroup
|
||||
from src.utils import utils
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
class CmdBoot(MuxCommand):
|
||||
"""
|
||||
@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.
|
||||
"""
|
||||
|
||||
key = "@boot"
|
||||
permissions = "cmd:boot"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implementing the function"
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
||||
if not args:
|
||||
caller.msg("Usage: @boot[/switches] <player> [:reason]")
|
||||
return
|
||||
|
||||
if ':' in args:
|
||||
args, reason = [a.strip() for a in args.split(':', 1)]
|
||||
boot_list = []
|
||||
reason = ""
|
||||
|
||||
if 'port' in self.switches:
|
||||
# Boot a particular port.
|
||||
sessions = sessionhandler.get_session_list(True)
|
||||
for sess in sessions:
|
||||
# Find the session with the matching port number.
|
||||
if sess.getClientAddress()[1] == int(args):
|
||||
boot_list.append(sess)
|
||||
break
|
||||
else:
|
||||
# Boot by player object
|
||||
pobj = caller.search("*%s" % args, global_search=True)
|
||||
if not pobj:
|
||||
return
|
||||
pobj = pobj
|
||||
if pobj.has_player:
|
||||
if not has_perm(caller, pobj, 'can_boot'):
|
||||
string = "You don't have the permission to boot %s."
|
||||
pobj.msg(string)
|
||||
return
|
||||
# we have a bootable object with a connected user
|
||||
matches = sessionhandler.sessions_from_object(pobj)
|
||||
for match in matches:
|
||||
boot_list.append(match)
|
||||
else:
|
||||
caller.msg("That object has no connected player.")
|
||||
return
|
||||
|
||||
if not boot_list:
|
||||
caller.msg("No matches found.")
|
||||
return
|
||||
|
||||
# Carry out the booting of the sessions in the boot list.
|
||||
|
||||
feedback = None
|
||||
if not 'quiet' in self.switches:
|
||||
feedback = "You have been disconnected by %s.\n" % caller.name
|
||||
if reason:
|
||||
feedback += "\nReason given: %s" % reason
|
||||
|
||||
for session in boot_list:
|
||||
name = session.name
|
||||
if feedback:
|
||||
session.msg(feedback)
|
||||
session.disconnectClient()
|
||||
sessionhandler.remove_session(session)
|
||||
caller.msg("You booted %s." % name)
|
||||
|
||||
|
||||
class CmdDelPlayer(MuxCommand):
|
||||
"""
|
||||
delplayer - delete player from server
|
||||
|
||||
Usage:
|
||||
@delplayer[/switch] <name> [: reason]
|
||||
|
||||
Switch:
|
||||
delobj - also delete the player's currently
|
||||
assigned in-game object.
|
||||
|
||||
Completely deletes a user from the server database,
|
||||
making their nick and e-mail again available.
|
||||
"""
|
||||
|
||||
key = "@delplayer"
|
||||
permissions = "cmd:delplayer"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implements the command."
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
||||
if not args:
|
||||
caller.msg("Usage: @delplayer[/delobj] <player/user name or #id>")
|
||||
return
|
||||
|
||||
reason = ""
|
||||
if ':' in args:
|
||||
args, reason = [arg.strip() for arg in args.split(':', 1)]
|
||||
|
||||
# We use player_search since we want to be sure to find also players
|
||||
# that lack characters.
|
||||
players = PlayerDB.objects.filter(db_key=args)
|
||||
if not players:
|
||||
try:
|
||||
players = PlayerDB.objects.filter(id=args)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not players:
|
||||
# try to find a user instead of a Player
|
||||
try:
|
||||
user = User.objects.get(id=args)
|
||||
except Exception:
|
||||
try:
|
||||
user = User.objects.get(username__iexact=args)
|
||||
except Exception:
|
||||
string = "No Player nor User found matching '%s'." % args
|
||||
caller.msg(string)
|
||||
return
|
||||
try:
|
||||
player = user.get_profile()
|
||||
except Exception:
|
||||
player = None
|
||||
|
||||
if not has_perm_string(caller, 'manage_players'):
|
||||
string = "You don't have the permissions to delete this player."
|
||||
caller.msg(string)
|
||||
return
|
||||
string = ""
|
||||
name = user.username
|
||||
user.delete()
|
||||
if player:
|
||||
name = player.name
|
||||
player.delete()
|
||||
string = "Player %s was deleted." % name
|
||||
else:
|
||||
string += "The User %s was deleted, but had no Player associated with it." % name
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
elif len(players) > 1:
|
||||
string = "There where multiple matches:"
|
||||
for player in players:
|
||||
string += "\n %s %s" % (player.id, player.key)
|
||||
return
|
||||
|
||||
else:
|
||||
# one single match
|
||||
|
||||
player = players[0]
|
||||
user = player.user
|
||||
character = player.character
|
||||
|
||||
if not has_perm(caller, player, 'manage_players'):
|
||||
string = "You don't have the permissions to delete that player."
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
uname = user.username
|
||||
# boot the player then delete
|
||||
if character and character.has_player:
|
||||
caller.msg("Booting and informing player ...")
|
||||
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
|
||||
if reason:
|
||||
string += " Reason given:\n '%s'" % reason
|
||||
character.msg(string)
|
||||
caller.execute_cmd("@boot %s" % uname)
|
||||
|
||||
player.delete()
|
||||
user.delete()
|
||||
caller.msg("Player %s was successfully deleted." % uname)
|
||||
|
||||
|
||||
class CmdEmit(MuxCommand):
|
||||
"""
|
||||
@emit
|
||||
|
||||
Usage:
|
||||
@emit[/switches] [<obj>, <obj>, ... =] <message>
|
||||
@remit [<obj>, <obj>, ... =] <message>
|
||||
@pemit [<obj>, <obj>, ... =] <message>
|
||||
|
||||
Switches:
|
||||
room : limit emits to rooms only
|
||||
players : limit emits to players only
|
||||
contents : send to the contents of matched objects too
|
||||
|
||||
Emits a message to the selected objects or to
|
||||
your immediate surroundings. If the object is a room,
|
||||
send to its contents. @remit and @pemit are just
|
||||
limited forms of @emit, for sending to rooms and
|
||||
to players respectively.
|
||||
"""
|
||||
key = "@emit"
|
||||
aliases = ["@pemit", "@remit"]
|
||||
permissions = "cmd:emit"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
||||
if not args:
|
||||
string = "Usage: "
|
||||
string += "\n@emit[/switches] [<obj>, <obj>, ... =] <message>"
|
||||
string += "\n@remit [<obj>, <obj>, ... =] <message>"
|
||||
string += "\n@pemit [<obj>, <obj>, ... =] <message>"
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
rooms_only = 'rooms' in self.switches
|
||||
players_only = 'players' in self.switches
|
||||
send_to_contents = 'contents' in self.switches
|
||||
|
||||
# we check which command was used to force the switches
|
||||
if self.cmdstring == '@remit':
|
||||
rooms_only = True
|
||||
elif self.cmdstring == '@pemit':
|
||||
players_only = True
|
||||
|
||||
if not self.rhs:
|
||||
message = self.args
|
||||
objnames = [caller.location.key]
|
||||
else:
|
||||
message = self.rhs
|
||||
objnames = self.lhslist
|
||||
|
||||
# send to all objects
|
||||
for objname in objnames:
|
||||
obj = caller.search(objname, global_search=True)
|
||||
if not obj:
|
||||
return
|
||||
if rooms_only and not obj.location == None:
|
||||
caller.msg("%s is not a room. Ignored." % objname)
|
||||
continue
|
||||
if players_only and not obj.has_player:
|
||||
caller.msg("%s has no active player. Ignored." % objname)
|
||||
continue
|
||||
if has_perm(caller, obj, 'send_to'):
|
||||
obj.msg(message)
|
||||
if send_to_contents:
|
||||
for content in obj.contents:
|
||||
content.msg(message)
|
||||
caller.msg("Emitted to %s and its contents." % objname)
|
||||
else:
|
||||
caller.msg("Emitted to %s." % objname)
|
||||
else:
|
||||
caller.msg("You are not allowed to send to %s." % objname)
|
||||
|
||||
|
||||
|
||||
class CmdNewPassword(MuxCommand):
|
||||
"""
|
||||
@setpassword
|
||||
|
||||
Usage:
|
||||
@userpassword <user obj> = <new password>
|
||||
|
||||
Set a player's password.
|
||||
"""
|
||||
|
||||
key = "@userpassword"
|
||||
permissions = "cmd:newpassword"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement the function."
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.rhs:
|
||||
caller.msg("Usage: @userpassword <user obj> = <new password>")
|
||||
return
|
||||
|
||||
# the player search also matches 'me' etc.
|
||||
character = caller.search("*%s" % self.lhs, global_search=True)
|
||||
if not character:
|
||||
return
|
||||
player = character.player
|
||||
player.user.set_password(self.rhs)
|
||||
player.user.save()
|
||||
caller.msg("%s - new password set to '%s'." % (player.name, self.rhs))
|
||||
if character != caller:
|
||||
player.msg("%s has changed your password to '%s'." % (caller.name, self.rhs))
|
||||
|
||||
|
||||
class CmdPerm(MuxCommand):
|
||||
"""
|
||||
@perm - set permissions
|
||||
|
||||
Usage:
|
||||
@perm[/switch] [<object>] = [<permission>]
|
||||
@perm[/switch] [*<player>] = [<permission>]
|
||||
|
||||
Switches:
|
||||
del : delete the given permission from <object>.
|
||||
list : list all permissions, or those set on <object>
|
||||
|
||||
Use * before the search string to add permissions to a player.
|
||||
This command sets/clears individual permission strings on an object.
|
||||
Use /list without any arguments to see all available permissions
|
||||
or those defined on the <object>/<player> argument.
|
||||
"""
|
||||
key = "@perm"
|
||||
aliases = "@setperm"
|
||||
permissions = "cmd:perm"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement function"
|
||||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
lhs, rhs = self.lhs, self.rhs
|
||||
|
||||
if not self.args:
|
||||
|
||||
if "list" not in switches:
|
||||
string = "Usage: @setperm[/switch] [object = permission]\n"
|
||||
string += " @setperm[/switch] [*player = permission]"
|
||||
caller.msg(string)
|
||||
return
|
||||
else:
|
||||
#just print all available permissions
|
||||
string = "\nAll defined permission groups and keys (i.e. not locks):"
|
||||
pgroups = list(PermissionGroup.objects.all())
|
||||
pgroups.sort(lambda x, y: cmp(x.key, y.key)) # sort by group key
|
||||
|
||||
for pgroup in pgroups:
|
||||
string += "\n\n - {w%s{n (%s):" % (pgroup.key, pgroup.desc)
|
||||
string += "\n%s" % \
|
||||
utils.fill(", ".join(sorted(pgroup.group_permissions)))
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
# locate the object/player
|
||||
obj = caller.search(lhs, global_search=True)
|
||||
if not obj:
|
||||
return
|
||||
|
||||
pstring = ""
|
||||
if utils.inherits_from(obj, settings.BASE_PLAYER_TYPECLASS):
|
||||
pstring = " Player "
|
||||
|
||||
if not rhs:
|
||||
string = "Permission string on %s{w%s{n: " % (pstring, obj.key)
|
||||
if not obj.permissions:
|
||||
string += "<None>"
|
||||
else:
|
||||
string += ", ".join(obj.permissions)
|
||||
if pstring and obj.is_superuser:
|
||||
string += "\n(... But this player is a SUPERUSER! "
|
||||
string += "All access checked are passed automatically.)"
|
||||
elif obj.player and obj.player.is_superuser:
|
||||
string += "\n(... But this object's player is a SUPERUSER! "
|
||||
string += "All access checked are passed automatically.)"
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
# we supplied an argument on the form obj = perm
|
||||
|
||||
cstring = ""
|
||||
tstring = ""
|
||||
if 'del' in switches:
|
||||
# delete the given permission(s) from object.
|
||||
for perm in self.rhslist:
|
||||
try:
|
||||
index = obj.permissions.index(perm)
|
||||
except ValueError:
|
||||
cstring += "\nPermission '%s' was not defined on %s%s." % (perm, pstring, lhs)
|
||||
continue
|
||||
permissions = obj.permissions
|
||||
del permissions[index]
|
||||
obj.permissions = permissions
|
||||
cstring += "\nPermission '%s' was removed from %s%s." % (perm, pstring, obj.name)
|
||||
tstring += "\n%s revokes the permission '%s' from you." % (caller.name, perm)
|
||||
else:
|
||||
# As an extra check, we warn the user if they customize the
|
||||
# permission string (which is okay, and is used by the lock system)
|
||||
permissions = obj.permissions
|
||||
for perm in self.rhslist:
|
||||
|
||||
if perm in permissions:
|
||||
cstring += "\nPermission '%s' is already defined on %s%s." % (rhs, pstring, obj.name)
|
||||
else:
|
||||
permissions.append(perm)
|
||||
obj.permissions = permissions
|
||||
cstring += "\nPermission '%s' given to %s%s." % (rhs, pstring, obj.name)
|
||||
tstring += "\n%s granted you the permission '%s'." % (caller.name, rhs)
|
||||
caller.msg(cstring.strip())
|
||||
if tstring:
|
||||
obj.msg(tstring.strip())
|
||||
|
||||
|
||||
class CmdPuppet(MuxCommand):
|
||||
"""
|
||||
Switch control to an object
|
||||
|
||||
Usage:
|
||||
@puppet <character object>
|
||||
|
||||
This will attempt to "become" a different character. Note that this command does not check so that
|
||||
the target object has the appropriate cmdset. You cannot puppet a character that is already "taken".
|
||||
"""
|
||||
|
||||
key = "@puppet"
|
||||
permissions = "cmd:puppet"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Simple puppet method (does not check permissions)
|
||||
"""
|
||||
caller = self.caller
|
||||
if not self.args:
|
||||
caller.msg("Usage: @puppet <character>")
|
||||
return
|
||||
|
||||
player = caller.player
|
||||
new_character = caller.search(self.args)
|
||||
if not new_character:
|
||||
return
|
||||
if not utils.inherits_from(new_character, settings.BASE_CHARACTER_TYPECLASS):
|
||||
caller.msg("%s is not a Character." % self.args)
|
||||
return
|
||||
if player.swap_character(new_character):
|
||||
new_character.msg("You now control %s." % new_character.name)
|
||||
else:
|
||||
caller.msg("You cannot control %s." % new_character.name)
|
||||
|
||||
class CmdWall(MuxCommand):
|
||||
"""
|
||||
@wall
|
||||
|
||||
Usage:
|
||||
@wall <message>
|
||||
|
||||
Announces a message to all connected players.
|
||||
"""
|
||||
key = "@wall"
|
||||
permissions = "cmd:wall"
|
||||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implements command"
|
||||
if not self.args:
|
||||
self.caller.msg("Usage: @wall <message>")
|
||||
return
|
||||
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
|
||||
sessionhandler.announce_all(message)
|
||||
740
src/commands/default/batchprocess.py
Normal file
740
src/commands/default/batchprocess.py
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
"""
|
||||
Batch processors
|
||||
|
||||
These commands implements the 'batch-command' and 'batch-code'
|
||||
processors, using the functionality in src.utils.batchprocessors.
|
||||
They allow for offline world-building.
|
||||
|
||||
Batch-command is the simpler system. This reads a file (*.ev)
|
||||
containing a list of in-game commands and executes them in sequence as
|
||||
if they had been entered in the game (including permission checks
|
||||
etc).
|
||||
|
||||
Example batch-command file: game/gamesrc/commands/examples/example_batch_cmd.ev
|
||||
|
||||
Batch-code is a full-fledged python code interpreter that reads blocks
|
||||
of python code (*.py) and executes them in sequence. This allows for
|
||||
much more power than Batch-command, but requires knowing Python and
|
||||
the Evennia API. It is also a severe security risk and should
|
||||
therefore always be limited to superusers only.
|
||||
|
||||
Example batch-code file: game/gamesrc/commands/examples/example_batch_code.py
|
||||
|
||||
"""
|
||||
from traceback import format_exc
|
||||
from django.conf import settings
|
||||
from src.utils.batchprocessors import BATCHCMD, BATCHCODE
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
HEADER_WIDTH = 70
|
||||
UTF8_ERROR = \
|
||||
"""
|
||||
{rDecode error in '%s'.{n
|
||||
|
||||
This file contains non-ascii character(s). This is common if you
|
||||
wrote some input in a language that has more letters and special
|
||||
symbols than English; such as accents or umlauts. This is usually
|
||||
fine and fully supported! But for Evennia to know how to decode such
|
||||
characters in a universal way, the batchfile must be saved with the
|
||||
international 'UTF-8' encoding. This file is not.
|
||||
|
||||
Please re-save the batchfile with the UTF-8 encoding (refer to the
|
||||
documentation of your text editor on how to do this, or switch to a
|
||||
better featured one) and try again.
|
||||
|
||||
The (first) error was found with a character on line %s in the file.
|
||||
"""
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Helper functions
|
||||
#------------------------------------------------------------
|
||||
|
||||
def format_header(caller, entry):
|
||||
"""
|
||||
Formats a header
|
||||
"""
|
||||
width = HEADER_WIDTH - 10
|
||||
entry = entry.strip()
|
||||
header = entry[:min(width, min(len(entry), entry.find('\n')))]
|
||||
if len(entry) > width:
|
||||
header = "%s[...]" % header
|
||||
ptr = caller.ndb.batch_stackptr + 1
|
||||
stacklen = len(caller.ndb.batch_stack)
|
||||
header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
|
||||
# add extra space to the side for padding.
|
||||
header = "%s%s" % (header, " "*(width-len(header)))
|
||||
header = header.replace('\n', '\\n')
|
||||
|
||||
return header
|
||||
|
||||
def format_code(entry):
|
||||
"""
|
||||
Formats the viewing of code and errors
|
||||
"""
|
||||
code = ""
|
||||
for line in entry.split('\n'):
|
||||
code += "\n{G>>>{n %s" % line
|
||||
return code.strip()
|
||||
|
||||
def batch_cmd_exec(caller):
|
||||
"""
|
||||
Helper function for executing a single batch-command entry
|
||||
"""
|
||||
ptr = caller.ndb.batch_stackptr
|
||||
stack = caller.ndb.batch_stack
|
||||
command = stack[ptr]
|
||||
caller.msg(format_header(caller, command))
|
||||
try:
|
||||
caller.execute_cmd(command)
|
||||
except Exception:
|
||||
caller.msg(format_code(format_exc()))
|
||||
return False
|
||||
return True
|
||||
|
||||
def batch_code_exec(caller):
|
||||
"""
|
||||
Helper function for executing a single batch-code entry
|
||||
"""
|
||||
ptr = caller.ndb.batch_stackptr
|
||||
stack = caller.ndb.batch_stack
|
||||
debug = caller.ndb.batch_debug
|
||||
codedict = stack[ptr]
|
||||
|
||||
caller.msg(format_header(caller, codedict['code']))
|
||||
err = BATCHCODE.code_exec(codedict,
|
||||
extra_environ={"caller":caller}, debug=debug)
|
||||
if err:
|
||||
caller.msg(format_code(err))
|
||||
return False
|
||||
return True
|
||||
|
||||
def step_pointer(caller, step=1):
|
||||
"""
|
||||
Step in stack, returning the item located.
|
||||
|
||||
stackptr - current position in stack
|
||||
stack - the stack of units
|
||||
step - how many steps to move from stackptr
|
||||
"""
|
||||
ptr = caller.ndb.batch_stackptr
|
||||
stack = caller.ndb.batch_stack
|
||||
nstack = len(stack)
|
||||
if ptr + step <= 0:
|
||||
caller.msg("{RBeginning of batch file.")
|
||||
if ptr + step >= nstack:
|
||||
caller.msg("{REnd of batch file.")
|
||||
caller.ndb.batch_stackptr = max(0, min(nstack-1, ptr + step))
|
||||
|
||||
def show_curr(caller, showall=False):
|
||||
"""
|
||||
Show the current position in stack
|
||||
"""
|
||||
stackptr = caller.ndb.batch_stackptr
|
||||
stack = caller.ndb.batch_stack
|
||||
|
||||
if stackptr >= len(stack):
|
||||
caller.ndb.batch_stackptr = len(stack) - 1
|
||||
show_curr(caller, showall)
|
||||
return
|
||||
|
||||
entry = stack[stackptr]
|
||||
|
||||
if type(entry) == dict:
|
||||
# this is a batch-code entry
|
||||
string = format_header(caller, entry['code'])
|
||||
codeall = entry['code'].strip()
|
||||
else:
|
||||
# this is a batch-cmd entry
|
||||
string = format_header(caller, entry)
|
||||
codeall = entry.strip()
|
||||
string += "{G(hh for help)"
|
||||
if showall:
|
||||
for line in codeall.split('\n'):
|
||||
string += "\n{n>>> %s" % line
|
||||
caller.msg(string)
|
||||
|
||||
def purge_processor(caller):
|
||||
"""
|
||||
This purges all effects running
|
||||
on the caller.
|
||||
"""
|
||||
try:
|
||||
del caller.ndb.batch_stack
|
||||
del caller.ndb.batch_stackptr
|
||||
del caller.ndb.batch_pythonpath
|
||||
del caller.ndb.batch_batchmode
|
||||
except:
|
||||
pass
|
||||
# clear everything but the default cmdset.
|
||||
caller.cmdset.delete(BatchSafeCmdSet)
|
||||
caller.cmdset.clear()
|
||||
caller.scripts.validate() # this will purge interactive mode
|
||||
|
||||
#------------------------------------------------------------
|
||||
# main access commands
|
||||
#------------------------------------------------------------
|
||||
|
||||
class CmdBatchCommands(MuxCommand):
|
||||
"""
|
||||
Build from batch-command file
|
||||
|
||||
Usage:
|
||||
@batchcommands[/interactive] <python path to file>
|
||||
|
||||
Switch:
|
||||
interactive - this mode will offer more control when
|
||||
executing the batch file, like stepping,
|
||||
skipping, reloading etc.
|
||||
|
||||
Runs batches of commands from a batch-cmd text file (*.ev).
|
||||
|
||||
"""
|
||||
key = "@batchcommands"
|
||||
aliases = ["@batchcommand", "@batchcmd"]
|
||||
permissions = "cmd:batchcommands"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"Starts the processor."
|
||||
|
||||
caller = self.caller
|
||||
|
||||
args = self.args
|
||||
if not args:
|
||||
caller.msg("Usage: @batchcommands[/interactive] <path.to.file>")
|
||||
return
|
||||
python_path = self.args
|
||||
|
||||
#parse indata file
|
||||
|
||||
try:
|
||||
commands = BATCHCMD.parse_file(python_path)
|
||||
except UnicodeDecodeError, err:
|
||||
lnum = err.linenum
|
||||
caller.msg(UTF8_ERROR % (python_path, lnum))
|
||||
return
|
||||
|
||||
if not commands:
|
||||
string = "'%s' not found.\nYou have to supply the python path "
|
||||
string += "of the file relative to \nyour batch-file directory (%s)."
|
||||
caller.msg(string % (python_path, settings.BASE_BATCHPROCESS_PATH))
|
||||
return
|
||||
switches = self.switches
|
||||
|
||||
# Store work data in cache
|
||||
caller.ndb.batch_stack = commands
|
||||
caller.ndb.batch_stackptr = 0
|
||||
caller.ndb.batch_pythonpath = python_path
|
||||
caller.ndb.batch_batchmode = "batch_commands"
|
||||
caller.cmdset.add(BatchSafeCmdSet)
|
||||
|
||||
if 'inter' in switches or 'interactive' in switches:
|
||||
# Allow more control over how batch file is executed
|
||||
|
||||
# Set interactive state directly
|
||||
caller.cmdset.add(BatchInteractiveCmdSet)
|
||||
|
||||
caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path)
|
||||
show_curr(caller)
|
||||
else:
|
||||
caller.msg("Running Batch-command processor - Automatic mode for %s ..." % python_path)
|
||||
# add the 'safety' cmdset in case the batch processing adds cmdsets to us
|
||||
for inum in range(len(commands)):
|
||||
# loop through the batch file
|
||||
if not batch_cmd_exec(caller):
|
||||
return
|
||||
step_pointer(caller, 1)
|
||||
# clean out the safety cmdset and clean out all other temporary attrs.
|
||||
caller.cmdset.delete(BatchSafeCmdSet)
|
||||
del caller.ndb.batch_stack
|
||||
del caller.ndb.batch_stackptr
|
||||
del caller.ndb.batch_pythonpath
|
||||
del caller.ndb.batch_batchmode
|
||||
string = " Batchfile '%s' applied." % python_path
|
||||
caller.msg("{G%s" % string)
|
||||
purge_processor(caller)
|
||||
|
||||
class CmdBatchCode(MuxCommand):
|
||||
"""
|
||||
Build from batch-code file
|
||||
|
||||
Usage:
|
||||
@batchcode[/interactive] <python path to file>
|
||||
|
||||
Switch:
|
||||
interactive - this mode will offer more control when
|
||||
executing the batch file, like stepping,
|
||||
skipping, reloading etc.
|
||||
debug - auto-delete all objects that has been marked as
|
||||
deletable in the script file (see example files for
|
||||
syntax). This is useful so as to to not leave multiple
|
||||
object copies behind when testing out the script.
|
||||
|
||||
Runs batches of commands from a batch-code text file (*.py).
|
||||
|
||||
"""
|
||||
key = "@batchcode"
|
||||
aliases = ["@batchcodes"]
|
||||
permissions = "cmd:batchcodes"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"Starts the processor."
|
||||
|
||||
caller = self.caller
|
||||
|
||||
args = self.args
|
||||
if not args:
|
||||
caller.msg("Usage: @batchcode[/interactive/debug] <path.to.file>")
|
||||
return
|
||||
python_path = self.args
|
||||
|
||||
#parse indata file
|
||||
try:
|
||||
codes = BATCHCODE.parse_file(python_path)
|
||||
except UnicodeDecodeError, err:
|
||||
lnum = err.linenum
|
||||
caller.msg(UTF8_ERROR % (python_path, lnum))
|
||||
return
|
||||
|
||||
if not codes:
|
||||
string = "'%s' not found.\nYou have to supply the python path "
|
||||
string += "of the file relative to \nyour batch-file directory (%s)."
|
||||
caller.msg(string % (python_path, settings.BASE_BATCHPROCESS_PATH))
|
||||
return
|
||||
switches = self.switches
|
||||
|
||||
debug = False
|
||||
if 'debug' in switches:
|
||||
debug = True
|
||||
|
||||
# Store work data in cache
|
||||
caller.ndb.batch_stack = codes
|
||||
caller.ndb.batch_stackptr = 0
|
||||
caller.ndb.batch_pythonpath = python_path
|
||||
caller.ndb.batch_batchmode = "batch_code"
|
||||
caller.ndb.batch_debug = debug
|
||||
caller.cmdset.add(BatchSafeCmdSet)
|
||||
|
||||
if 'inter' in switches or 'interactive'in switches:
|
||||
# Allow more control over how batch file is executed
|
||||
|
||||
# Set interactive state directly
|
||||
caller.cmdset.add(BatchInteractiveCmdSet)
|
||||
|
||||
caller.msg("\nBatch-code processor - Interactive mode for %s ..." % python_path)
|
||||
show_curr(caller)
|
||||
else:
|
||||
caller.msg("Running Batch-code processor - Automatic mode for %s ..." % python_path)
|
||||
# add the 'safety' cmdset in case the batch processing adds cmdsets to us
|
||||
for inum in range(len(codes)):
|
||||
# loop through the batch file
|
||||
if not batch_code_exec(caller):
|
||||
return
|
||||
step_pointer(caller, 1)
|
||||
# clean out the safety cmdset and clean out all other temporary attrs.
|
||||
caller.cmdset.delete(BatchSafeCmdSet)
|
||||
del caller.ndb.batch_stack
|
||||
del caller.ndb.batch_stackptr
|
||||
del caller.ndb.batch_pythonpath
|
||||
del caller.ndb.batch_batchmode
|
||||
string = " Batchfile '%s' applied." % python_path
|
||||
caller.msg("{G%s" % string)
|
||||
purge_processor(caller)
|
||||
|
||||
#------------------------------------------------------------
|
||||
# State-commands for the interactive batch processor modes
|
||||
# (these are the same for both processors)
|
||||
#------------------------------------------------------------
|
||||
|
||||
class CmdStateAbort(MuxCommand):
|
||||
"""
|
||||
@abort
|
||||
|
||||
This is a safety feature. It force-ejects us out of the processor and to
|
||||
the default cmdset, regardless of what current cmdset the processor might
|
||||
have put us in (e.g. when testing buggy scripts etc).
|
||||
"""
|
||||
key = "@abort"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
"Exit back to default."
|
||||
purge_processor(self.caller)
|
||||
self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
|
||||
|
||||
class CmdStateLL(MuxCommand):
|
||||
"""
|
||||
ll
|
||||
|
||||
Look at the full source for the current
|
||||
command definition.
|
||||
"""
|
||||
key = "ll"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
show_curr(self.caller, showall=True)
|
||||
|
||||
class CmdStatePP(MuxCommand):
|
||||
"""
|
||||
pp
|
||||
|
||||
Process the currently shown command definition.
|
||||
"""
|
||||
key = "pp"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
This checks which type of processor we are running.
|
||||
"""
|
||||
caller = self.caller
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
batch_cmd_exec(caller)
|
||||
|
||||
|
||||
class CmdStateRR(MuxCommand):
|
||||
"""
|
||||
rr
|
||||
|
||||
Reload the batch file, keeping the current
|
||||
position in it.
|
||||
"""
|
||||
key = "rr"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
BATCHCODE.read_file(caller.ndb.batch_pythonpath)
|
||||
else:
|
||||
BATCHCMD.read_file(caller.ndb.batch_pythonpath)
|
||||
caller.msg(format_code("File reloaded. Staying on same command."))
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateRRR(MuxCommand):
|
||||
"""
|
||||
rrr
|
||||
|
||||
Reload the batch file, starting over
|
||||
from the beginning.
|
||||
"""
|
||||
key = "rrr"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
BATCHCODE.read_file(caller.ndb.batch_pythonpath)
|
||||
else:
|
||||
BATCHCMD.read_file(caller.ndb.batch_pythonpath)
|
||||
caller.ndb.batch_stackptr = 0
|
||||
caller.msg(format_code("File reloaded. Restarting from top."))
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateNN(MuxCommand):
|
||||
"""
|
||||
nn
|
||||
|
||||
Go to next command. No commands are executed.
|
||||
"""
|
||||
key = "nn"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
step = int(self.args)
|
||||
else:
|
||||
step = 1
|
||||
step_pointer(caller, step)
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateNL(MuxCommand):
|
||||
"""
|
||||
nl
|
||||
|
||||
Go to next command, viewing its full source.
|
||||
No commands are executed.
|
||||
"""
|
||||
key = "nl"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
step = int(self.args)
|
||||
else:
|
||||
step = 1
|
||||
step_pointer(caller, step)
|
||||
show_curr(caller, showall=True)
|
||||
|
||||
class CmdStateBB(MuxCommand):
|
||||
"""
|
||||
bb
|
||||
|
||||
Backwards to previous command. No commands
|
||||
are executed.
|
||||
"""
|
||||
key = "bb"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
step = -int(self.args)
|
||||
else:
|
||||
step = -1
|
||||
step_pointer(caller, step)
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateBL(MuxCommand):
|
||||
"""
|
||||
bl
|
||||
|
||||
Backwards to previous command, viewing its full
|
||||
source. No commands are executed.
|
||||
"""
|
||||
key = "bl"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
step = -int(self.args)
|
||||
else:
|
||||
step = -1
|
||||
step_pointer(caller, step)
|
||||
show_curr(caller, showall=True)
|
||||
|
||||
class CmdStateSS(MuxCommand):
|
||||
"""
|
||||
ss [steps]
|
||||
|
||||
Process current command, then step to the next
|
||||
one. If steps is given,
|
||||
process this many commands.
|
||||
"""
|
||||
key = "ss"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
step = int(self.args)
|
||||
else:
|
||||
step = 1
|
||||
|
||||
for istep in range(step):
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
batch_cmd_exec(caller)
|
||||
step_pointer(caller, 1)
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateSL(MuxCommand):
|
||||
"""
|
||||
sl [steps]
|
||||
|
||||
Process current command, then step to the next
|
||||
one, viewing its full source. If steps is given,
|
||||
process this many commands.
|
||||
"""
|
||||
key = "sl"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
step = int(self.args)
|
||||
else:
|
||||
step = 1
|
||||
|
||||
for istep in range(step):
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
batch_cmd_exec(caller)
|
||||
step_pointer(caller, 1)
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateCC(MuxCommand):
|
||||
"""
|
||||
cc
|
||||
|
||||
Continue to process all remaining
|
||||
commands.
|
||||
"""
|
||||
key = "cc"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
nstack = len(caller.ndb.batch_stack)
|
||||
ptr = caller.ndb.batch_stackptr
|
||||
step = nstack - ptr
|
||||
|
||||
for istep in range(step):
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
batch_cmd_exec(caller)
|
||||
step_pointer(caller, 1)
|
||||
show_curr(caller)
|
||||
|
||||
del caller.ndb.batch_stack
|
||||
del caller.ndb.batch_stackptr
|
||||
del caller.ndb.batch_pythonpath
|
||||
del caller.ndb.batch_batchmode
|
||||
caller.msg(format_code("Finished processing batch file."))
|
||||
|
||||
class CmdStateJJ(MuxCommand):
|
||||
"""
|
||||
j <command number>
|
||||
|
||||
Jump to specific command number
|
||||
"""
|
||||
key = "j"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
number = int(self.args)-1
|
||||
else:
|
||||
caller.msg(format_code("You must give a number index."))
|
||||
return
|
||||
ptr = caller.ndb.batch_stackptr
|
||||
step = number - ptr
|
||||
step_pointer(caller, step)
|
||||
show_curr(caller)
|
||||
|
||||
class CmdStateJL(MuxCommand):
|
||||
"""
|
||||
jl <command number>
|
||||
|
||||
Jump to specific command number and view its full source.
|
||||
"""
|
||||
key = "jl"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
arg = self.args
|
||||
if arg and arg.isdigit():
|
||||
number = int(self.args)-1
|
||||
else:
|
||||
caller.msg(format_code("You must give a number index."))
|
||||
return
|
||||
ptr = caller.ndb.batch_stackptr
|
||||
step = number - ptr
|
||||
step_pointer(caller, step)
|
||||
show_curr(caller, showall=True)
|
||||
|
||||
class CmdStateQQ(MuxCommand):
|
||||
"""
|
||||
qq
|
||||
|
||||
Quit the batchprocessor.
|
||||
"""
|
||||
key = "qq"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
purge_processor(self.caller)
|
||||
self.caller.msg("Aborted interactive batch mode.")
|
||||
|
||||
class CmdStateHH(MuxCommand):
|
||||
"Help command"
|
||||
|
||||
key = "hh"
|
||||
help_category = "BatchProcess"
|
||||
|
||||
def func(self):
|
||||
string = """
|
||||
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)
|
||||
|
||||
@abort - this is a safety command that always is available
|
||||
regardless of what cmdsets gets added to us during
|
||||
batch-command processing. It immediately shuts down
|
||||
the processor and returns us to the default cmdset.
|
||||
"""
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Defining the cmdsets for the interactive batchprocessor
|
||||
# mode (same for both processors)
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class BatchSafeCmdSet(CmdSet):
|
||||
"""
|
||||
The base cmdset for the batch processor.
|
||||
This sets a 'safe' @abort command that will
|
||||
always be available to get out of everything.
|
||||
"""
|
||||
key = "Batch_default"
|
||||
priority = 104 # override other cmdsets.
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"Init the cmdset"
|
||||
self.add(CmdStateAbort())
|
||||
|
||||
class BatchInteractiveCmdSet(CmdSet):
|
||||
"""
|
||||
The cmdset for the interactive batch processor mode.
|
||||
"""
|
||||
key = "Batch_interactive"
|
||||
priority = 104
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"init the cmdset"
|
||||
self.add(CmdStateAbort())
|
||||
self.add(CmdStateLL())
|
||||
self.add(CmdStatePP())
|
||||
self.add(CmdStateRR())
|
||||
self.add(CmdStateRRR())
|
||||
self.add(CmdStateNN())
|
||||
self.add(CmdStateNL())
|
||||
self.add(CmdStateBB())
|
||||
self.add(CmdStateBL())
|
||||
self.add(CmdStateSS())
|
||||
self.add(CmdStateSL())
|
||||
self.add(CmdStateCC())
|
||||
self.add(CmdStateJJ())
|
||||
self.add(CmdStateJL())
|
||||
self.add(CmdStateQQ())
|
||||
self.add(CmdStateHH())
|
||||
1522
src/commands/default/building.py
Normal file
1522
src/commands/default/building.py
Normal file
File diff suppressed because it is too large
Load diff
98
src/commands/default/cmdset_default.py
Normal file
98
src/commands/default/cmdset_default.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
This module ties together all the commands of the default command set.
|
||||
"""
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.default import general, help, admin, system
|
||||
from src.commands.default import utils, comms, building
|
||||
from src.commands.default import batchprocess
|
||||
|
||||
class DefaultCmdSet(CmdSet):
|
||||
"""
|
||||
Implements the default command set.
|
||||
"""
|
||||
key = "DefaultMUX"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"Populates the cmdset"
|
||||
|
||||
# The general commands
|
||||
self.add(general.CmdLook())
|
||||
self.add(general.CmdHome())
|
||||
self.add(general.CmdPassword())
|
||||
self.add(general.CmdInventory())
|
||||
self.add(general.CmdQuit())
|
||||
self.add(general.CmdPose())
|
||||
self.add(general.CmdNick())
|
||||
self.add(general.CmdGet())
|
||||
self.add(general.CmdDrop())
|
||||
self.add(general.CmdWho())
|
||||
self.add(general.CmdSay())
|
||||
self.add(general.CmdGroup())
|
||||
self.add(general.CmdEncoding())
|
||||
|
||||
# The help system
|
||||
self.add(help.CmdHelp())
|
||||
self.add(help.CmdSetHelp())
|
||||
|
||||
# System commands
|
||||
self.add(system.CmdReload())
|
||||
self.add(system.CmdPy())
|
||||
self.add(system.CmdListScripts())
|
||||
self.add(system.CmdListObjects())
|
||||
self.add(system.CmdService())
|
||||
self.add(system.CmdShutdown())
|
||||
self.add(system.CmdVersion())
|
||||
self.add(system.CmdTime())
|
||||
self.add(system.CmdList())
|
||||
self.add(system.CmdPs())
|
||||
self.add(system.CmdStats())
|
||||
|
||||
# Admin commands
|
||||
self.add(admin.CmdBoot())
|
||||
self.add(admin.CmdDelPlayer())
|
||||
self.add(admin.CmdEmit())
|
||||
self.add(admin.CmdNewPassword())
|
||||
self.add(admin.CmdPerm())
|
||||
self.add(admin.CmdPuppet())
|
||||
self.add(admin.CmdWall())
|
||||
|
||||
# Building and world manipulation
|
||||
self.add(building.CmdTeleport())
|
||||
self.add(building.CmdSetObjAlias())
|
||||
self.add(building.CmdListCmdSets())
|
||||
self.add(building.CmdDebug())
|
||||
self.add(building.CmdWipe())
|
||||
self.add(building.CmdSetAttribute())
|
||||
self.add(building.CmdName())
|
||||
self.add(building.CmdDesc())
|
||||
#self.add(building.CmdCpAttr()) #TODO - need testing/debugging
|
||||
#self.add(building.CmdMvAttr()) #TODO - need testing/debugging
|
||||
self.add(building.CmdFind())
|
||||
self.add(building.CmdCopy()) #TODO - need testing/debugging
|
||||
self.add(building.CmdOpen())
|
||||
self.add(building.CmdLink())
|
||||
self.add(building.CmdUnLink())
|
||||
self.add(building.CmdCreate())
|
||||
self.add(building.CmdDig())
|
||||
self.add(building.CmdDestroy())
|
||||
self.add(building.CmdExamine())
|
||||
self.add(building.CmdTypeclass())
|
||||
|
||||
# Comm commands
|
||||
self.add(comms.CmdAddCom())
|
||||
self.add(comms.CmdDelCom())
|
||||
self.add(comms.CmdComlist())
|
||||
self.add(comms.CmdClist())
|
||||
self.add(comms.CmdCdestroy())
|
||||
self.add(comms.CmdChannelCreate())
|
||||
self.add(comms.CmdCdesc())
|
||||
self.add(comms.CmdPage())
|
||||
|
||||
# Batchprocessor commands
|
||||
self.add(batchprocess.CmdBatchCommands())
|
||||
self.add(batchprocess.CmdBatchCode())
|
||||
|
||||
# Testing/Utility commands
|
||||
self.add(utils.CmdTest())
|
||||
self.add(utils.CmdTestPerms())
|
||||
self.add(utils.TestCom())
|
||||
21
src/commands/default/cmdset_unloggedin.py
Normal file
21
src/commands/default/cmdset_unloggedin.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
This module describes the unlogged state of the default game.
|
||||
The setting STATE_UNLOGGED should be set to the python path
|
||||
of the state instance in this module.
|
||||
"""
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.default import unloggedin
|
||||
|
||||
class UnloggedinCmdSet(CmdSet):
|
||||
"""
|
||||
Sets up the unlogged cmdset.
|
||||
"""
|
||||
key = "Unloggedin"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"Populate the cmdset"
|
||||
self.add(unloggedin.CmdConnect())
|
||||
self.add(unloggedin.CmdCreate())
|
||||
self.add(unloggedin.CmdQuit())
|
||||
self.add(unloggedin.CmdUnconnectedLook())
|
||||
self.add(unloggedin.CmdUnconnectedHelp())
|
||||
827
src/commands/default/comms.py
Normal file
827
src/commands/default/comms.py
Normal file
|
|
@ -0,0 +1,827 @@
|
|||
"""
|
||||
Comsys command module.
|
||||
"""
|
||||
|
||||
from src.comms.models import Channel, Msg, ChannelConnection
|
||||
from src.utils import create, utils
|
||||
from src.permissions.permissions import has_perm
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
def find_channel(caller, channelname):
|
||||
"""
|
||||
Helper function for searching for a single channel with
|
||||
some error handling.
|
||||
"""
|
||||
channels = Channel.objects.channel_search(channelname)
|
||||
if not channels:
|
||||
caller.msg("Channel '%s' not found." % channelname)
|
||||
return None
|
||||
elif len(channels) > 1:
|
||||
matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels])
|
||||
caller.msg("Multiple channels match (be more specific): \n%s" % matches)
|
||||
return None
|
||||
return channels[0]
|
||||
|
||||
class CmdAddCom(MuxCommand):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
key = "addcom"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
player = caller.player
|
||||
|
||||
if not args:
|
||||
caller.msg("Usage: addcom [alias =] channelname.")
|
||||
return
|
||||
|
||||
if self.rhs:
|
||||
# rhs holds the channelname
|
||||
channelname = self.rhs
|
||||
alias = self.lhs
|
||||
else:
|
||||
channelname = self.args
|
||||
alias = None
|
||||
|
||||
channel = find_channel(caller, channelname)
|
||||
if not channel:
|
||||
# we use the custom search method to handle errors.
|
||||
return
|
||||
|
||||
# check permissions
|
||||
if not has_perm(player, channel, 'chan_listen'):
|
||||
caller.msg("You are not allowed to listen to this channel.")
|
||||
return
|
||||
|
||||
string = ""
|
||||
if not channel.has_connection(player):
|
||||
# we want to connect as well.
|
||||
if not channel.connect_to(player):
|
||||
# if this would have returned True, the player is connected
|
||||
caller.msg("You are not allowed to join this channel.")
|
||||
return
|
||||
else:
|
||||
string += "You now listen to the channel %s. " % channel.key
|
||||
|
||||
if alias:
|
||||
# create a nick and add it to the caller.
|
||||
nicks = caller.nicks
|
||||
nicks[alias.strip()] = channel.key
|
||||
caller.nicks = nicks # nicks auto-save to database.
|
||||
string += "You can now refer to the channel %s with the alias '%s'."
|
||||
caller.msg(string % (channel.key, alias))
|
||||
else:
|
||||
string += "No alias added."
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdDelCom(MuxCommand):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
key = "delcom"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Implementing the command. "
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.args:
|
||||
caller.msg("Usage: delcom <alias>")
|
||||
return
|
||||
|
||||
#find all the nicks defining this channel
|
||||
searchnick = self.args.lower()
|
||||
nicks = caller.nicks
|
||||
channicks = [nick for nick in nicks.keys()
|
||||
if nick == searchnick]
|
||||
if not channicks:
|
||||
caller.msg("You don't have any such alias defined.")
|
||||
return
|
||||
#if there are possible nick matches, look if they match a channel.
|
||||
channel = None
|
||||
for nick in channicks:
|
||||
channel = find_channel(caller, nicks[nick])
|
||||
if channel:
|
||||
break
|
||||
if not channel:
|
||||
caller.msg("No channel with alias '%s' found." % searchnick)
|
||||
return
|
||||
player = caller.player
|
||||
|
||||
if not channel.has_connection(player):
|
||||
caller.msg("You are not on that channel.")
|
||||
else:
|
||||
if len(channicks) > 1:
|
||||
del nicks[searchnick]
|
||||
caller.msg("Your alias '%s' for channel %s was cleared." % (searchnick,
|
||||
channel.key))
|
||||
else:
|
||||
del nicks[searchnick]
|
||||
channel.disconnect_from(player)
|
||||
caller.msg("You stop listening to channel '%s'." % channel.key)
|
||||
# have to save nicks back too
|
||||
caller.nicks = nicks
|
||||
|
||||
class CmdComlist(MuxCommand):
|
||||
"""
|
||||
comlist - list channel memberships
|
||||
|
||||
Usage:
|
||||
comlist
|
||||
|
||||
Lists the channels a user is subscribed to.
|
||||
"""
|
||||
|
||||
key = "comlist"
|
||||
aliases = ["channels"]
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
|
||||
|
||||
caller = self.caller
|
||||
player = caller.player
|
||||
|
||||
connections = ChannelConnection.objects.get_all_player_connections(player)
|
||||
|
||||
if not connections:
|
||||
caller.msg("You don't listen to any channels.")
|
||||
return
|
||||
|
||||
# get aliases:
|
||||
nicks = caller.nicks
|
||||
channicks = {}
|
||||
for connection in connections:
|
||||
channame = connection.channel.key.lower()
|
||||
channicks[channame] = ", ".join([nick for nick in nicks
|
||||
if nicks[nick].lower() == channame])
|
||||
|
||||
string = "Your subscribed channels (use @clist for full chan list)\n"
|
||||
string += "** Alias Channel Status\n"
|
||||
|
||||
for connection in connections:
|
||||
string += " %s%s %-15.14s%-22.15s\n" % ('-', '-',
|
||||
channicks[connection.channel.key.lower()],
|
||||
connection.channel.key)
|
||||
string = string[:-1]
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
# 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.
|
||||
# """
|
||||
|
||||
# caller = self.caller
|
||||
# arg = self.args
|
||||
# if not arg:
|
||||
# cmd_comlist(command)
|
||||
# caller.msg("(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(self.session)
|
||||
# aliaslist = chandict.keys()
|
||||
# aliaslist.sort()
|
||||
# if arg == "on":
|
||||
# for alias in aliaslist:
|
||||
# comsys.plr_chan_on(self.session, alias)
|
||||
# elif arg == "off":
|
||||
# for alias in aliaslist:
|
||||
# comsys.plr_chan_off(self.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=caller.sees_dbrefs())
|
||||
# for p in filter(lambda o: o.is_player(), objlist)]
|
||||
# olist = [o.get_name(show_dbref=caller.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]
|
||||
# caller.msg(s)
|
||||
# GLOBAL_CMD_TABLE.add_self("allcom", cmd_allcom, help_category="Comms")
|
||||
|
||||
## def cmd_clearcom(self):
|
||||
## """
|
||||
## 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.
|
||||
## """
|
||||
## caller = self.caller
|
||||
## #get aall subscribed channel memberships
|
||||
## memberships = caller.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(caller, membership.user_alias)
|
||||
## comsys.send_cmessage(chan_name, "%s has left the channel." % caller.get_name(show_dbref=False))
|
||||
## s = s[:-1]
|
||||
## caller.msg(s)
|
||||
## GLOBAL_CMD_TABLE.add_self("clearcom", cmd_clearcom)
|
||||
|
||||
|
||||
class CmdClist(MuxCommand):
|
||||
"""
|
||||
@clist
|
||||
|
||||
Usage:
|
||||
@clist
|
||||
list channels
|
||||
all channels
|
||||
|
||||
Lists all available channels in the game.
|
||||
"""
|
||||
key = "@clist"
|
||||
aliases = ["channellist", "all channels"]
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Implement function"
|
||||
|
||||
caller = self.caller
|
||||
|
||||
string = "All channels (use comlist to see your subscriptions)\n"
|
||||
|
||||
string += "** Channel Perms Description\n"
|
||||
channels = Channel.objects.get_all_channels()
|
||||
if not channels:
|
||||
string += "(No channels) "
|
||||
for chan in channels:
|
||||
if has_perm(caller, chan, 'can_listen'):
|
||||
string += " %s%s %-15.14s%-22.15s%s\n" % \
|
||||
('-',
|
||||
'-',
|
||||
chan.key,
|
||||
chan.permissions,
|
||||
#chan.get_owner().get_name(show_dbref=False),
|
||||
chan.desc)
|
||||
string = string[:-1]
|
||||
#s += "** End of Channel List **"
|
||||
caller.msg(string)
|
||||
|
||||
class CmdCdestroy(MuxCommand):
|
||||
"""
|
||||
@cdestroy
|
||||
|
||||
Usage:
|
||||
@cdestroy <channel>
|
||||
|
||||
Destroys a channel that you control.
|
||||
"""
|
||||
|
||||
key = "@cdestroy"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Destroy objects cleanly."
|
||||
caller = self.caller
|
||||
|
||||
if not self.args:
|
||||
caller.msg("Usage: @cdestroy <channelname>")
|
||||
return
|
||||
channel = find_channel(caller, self.args)
|
||||
if not channel:
|
||||
caller.msg("Could not find channel %s." % self.args)
|
||||
return
|
||||
if not has_perm(caller, channel, 'chan_admin', default_deny=True):
|
||||
caller.msg("You are not allowed to do that.")
|
||||
return
|
||||
|
||||
message = "Channel %s is being destroyed. Make sure to change your aliases." % channel.key
|
||||
msgobj = create.create_message(caller, message, channel)
|
||||
channel.msg(msgobj)
|
||||
channel.delete()
|
||||
caller.msg("Channel %s was destroyed." % channel)
|
||||
|
||||
|
||||
## def cmd_cset(self):
|
||||
## """
|
||||
## @cset
|
||||
|
||||
## Sets various flags on a channel.
|
||||
## """
|
||||
## # TODO: Implement cmd_cset
|
||||
## pass
|
||||
|
||||
## def cmd_ccharge(self):
|
||||
## """
|
||||
## @ccharge
|
||||
|
||||
## Sets the cost to transmit over a channel. Default is free.
|
||||
## """
|
||||
## # TODO: Implement cmd_ccharge
|
||||
## pass
|
||||
|
||||
## def cmd_cboot(self):
|
||||
## """
|
||||
## @cboot
|
||||
|
||||
## Usage:
|
||||
## @cboot[/quiet] <channel> = <player or object>
|
||||
|
||||
## Kicks a player or object from a channel you control.
|
||||
## """
|
||||
## caller = self.caller
|
||||
## args = self.args
|
||||
## switches = self.self_switches
|
||||
|
||||
## if not args or not "=" in args:
|
||||
## caller.msg("Usage: @cboot[/quiet] <channel> = <object>")
|
||||
## return
|
||||
## cname, objname = args.split("=",1)
|
||||
## cname, objname = cname.strip(), objname.strip()
|
||||
## if not cname or not objname:
|
||||
## caller.msg("You must supply both channel and object.")
|
||||
## return
|
||||
## try:
|
||||
## channel = CommChannel.objects.get(name__iexact=cname)
|
||||
## except CommChannel.DoesNotExist:
|
||||
## caller.msg("Could not find channel %s." % cname)
|
||||
## return
|
||||
|
||||
## #do we have power over this channel?
|
||||
## if not channel.controlled_by(caller) or caller.has_perm("channels.channel_admin"):
|
||||
## caller.msg("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:
|
||||
## caller.msg("Object '%s' not found." % objname)
|
||||
## return
|
||||
## if bootobj.is_player() and not player_boot:
|
||||
## caller.msg("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:
|
||||
## caller.msg("'%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." % \
|
||||
## (caller.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_self("@cboot", cmd_cboot, help_category="Comms")
|
||||
|
||||
|
||||
## def cmd_cemit(self):
|
||||
## """
|
||||
## @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_selfs]]
|
||||
|
||||
## Useful channel selfs
|
||||
## (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
|
||||
## """
|
||||
## caller = self.caller
|
||||
|
||||
## if not self.args:
|
||||
## caller.msg("@cemit[/switches] <channel> = <message>")
|
||||
## return
|
||||
|
||||
## eq_args = self.args.split('=', 1)
|
||||
|
||||
## if len(eq_args) != 2:
|
||||
## caller.msg("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:
|
||||
## caller.msg("You must provide a channel name to emit to.")
|
||||
## return
|
||||
## if len(cmessage) == 0:
|
||||
## caller.msg("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:
|
||||
## caller.msg("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 self.self_switches:
|
||||
## if not caller.has_perm("objects.emit_commchannel"):
|
||||
## caller.msg(defines_global.NOPERMS_MSG)
|
||||
## return
|
||||
## final_cmessage = cmessage
|
||||
## show_channel_header = False
|
||||
## else:
|
||||
## if "sendername" in self.self_switches:
|
||||
## if not comsys.plr_has_channel(self.session, cname_parsed,
|
||||
## return_muted=False):
|
||||
## caller.msg("You must be on %s to do that." % (cname_parsed,))
|
||||
## return
|
||||
## final_cmessage = "%s: %s" % (caller.get_name(show_dbref=False),
|
||||
## cmessage)
|
||||
## else:
|
||||
## if not caller.has_perm("objects.emit_commchannel"):
|
||||
## caller.msg(defines_global.NOPERMS_MSG)
|
||||
## return
|
||||
## final_cmessage = cmessage
|
||||
|
||||
## if not "quiet" in self.self_switches:
|
||||
## caller.msg("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=caller)
|
||||
|
||||
## GLOBAL_CMD_TABLE.add_self("@cemit", cmd_cemit,priv_tuple=("channels.emit_commchannel",),
|
||||
## help_category="Comms")
|
||||
|
||||
## def cmd_cwho(self):
|
||||
## """
|
||||
## @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 = self.session
|
||||
## caller = self.caller
|
||||
|
||||
## if not self.args:
|
||||
## cmd_clist(self)
|
||||
## caller.msg("Usage: @cwho <channel>[/all]")
|
||||
## return
|
||||
|
||||
## channel_name = self.args
|
||||
|
||||
## if channel_name.strip() == '':
|
||||
## caller.msg("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 = caller.has_perm("objects.channel_admin")
|
||||
## is_controlled_by_plr = name_matches[0].controlled_by(caller)
|
||||
|
||||
## if is_controlled_by_plr or is_channel_admin:
|
||||
## comsys.msg_cwho(caller, channel_name)
|
||||
## else:
|
||||
## caller.msg("Permission denied.")
|
||||
## return
|
||||
## else:
|
||||
## caller.msg("No channel with that name was found.")
|
||||
## return
|
||||
## GLOBAL_CMD_TABLE.add_self("@cwho", cmd_cwho, help_category="Comms")
|
||||
|
||||
class CmdChannelCreate(MuxCommand):
|
||||
"""
|
||||
@ccreate
|
||||
channelcreate
|
||||
Usage:
|
||||
@ccreate <new channel>[;alias;alias...] = description
|
||||
|
||||
Creates a new channel owned by you.
|
||||
"""
|
||||
|
||||
key = "@ccreate"
|
||||
aliases = "channelcreate"
|
||||
permissions = "cmd:ccreate"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.args:
|
||||
caller.msg("Usage @ccreate <channelname>[;alias;alias..] = description")
|
||||
return
|
||||
|
||||
description = ""
|
||||
|
||||
if self.rhs:
|
||||
description = self.rhs
|
||||
lhs = self.lhs
|
||||
channame = lhs
|
||||
aliases = None
|
||||
if ';' in lhs:
|
||||
channame, aliases = [part.strip().lower()
|
||||
for part in lhs.split(';', 1) if part.strip()]
|
||||
aliases = [alias.strip().lower()
|
||||
for alias in aliases.split(';') if alias.strip()]
|
||||
channel = Channel.objects.channel_search(channame)
|
||||
if channel:
|
||||
caller.msg("A channel with that name already exists.")
|
||||
return
|
||||
# Create and set the channel up
|
||||
permissions = "chan_send:%s,chan_listen:%s,chan_admin:has_id(%s)" % \
|
||||
("Players","Players",caller.id)
|
||||
new_chan = create.create_channel(channame, aliases, description, permissions)
|
||||
new_chan.connect_to(caller)
|
||||
caller.msg("Created channel %s and connected to it." % new_chan.key)
|
||||
|
||||
|
||||
## def cmd_cchown(self):
|
||||
## """
|
||||
## @cchown
|
||||
|
||||
## Usage:
|
||||
## @cchown <channel> = <player>
|
||||
|
||||
## Changes the owner of a channel.
|
||||
## """
|
||||
## caller = self.caller
|
||||
## args = self.args
|
||||
## if not args or "=" not in args:
|
||||
## caller.msg("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:
|
||||
## caller.msg("Channel '%s' not found." % cname)
|
||||
## return
|
||||
## #check so we have ownership to give away.
|
||||
## if not channel.controlled_by(caller) and not caller.has_perm("channels.channel_admin"):
|
||||
## caller.msg("You don't control this channel.")
|
||||
## return
|
||||
## #find the new owner
|
||||
## new_owner = Object.objects.player_name_search(pname)
|
||||
## if not new_owner:
|
||||
## caller.msg("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:
|
||||
## caller.msg("Owner unchanged.")
|
||||
## return
|
||||
## #all is set, change owner
|
||||
## channel.set_owner(new_owner)
|
||||
## caller.msg("Owner of %s changed from %s to %s." % (cname, old_pname, pname))
|
||||
## new_owner.msg("%s transfered ownership of channel '%s' to you." % (old_pname, cname))
|
||||
## GLOBAL_CMD_TABLE.add_self("@cchown", cmd_cchown, help_category="Comms")
|
||||
|
||||
|
||||
class CmdCdesc(MuxCommand):
|
||||
"""
|
||||
@cdesc - set channel description
|
||||
|
||||
Usage:
|
||||
@cdesc <channel> = <description>
|
||||
|
||||
Changes the description of the channel as shown in
|
||||
channel lists.
|
||||
"""
|
||||
|
||||
key = "@cdesc"
|
||||
permissions = "cmd:cdesc"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
"Implement command"
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.rhs:
|
||||
caller.msg("Usage: @cdesc <channel> = <description>")
|
||||
return
|
||||
channel = find_channel(caller, self.lhs)
|
||||
if not channel:
|
||||
caller.msg("Channel '%s' not found." % self.lhs)
|
||||
return
|
||||
#check permissions
|
||||
if not has_perm(caller, channel, 'channel_admin'):
|
||||
caller.msg("You cant admin this channel.")
|
||||
return
|
||||
# set the description
|
||||
channel.desc = self.rhs
|
||||
channel.save()
|
||||
caller.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs))
|
||||
|
||||
class CmdPage(MuxCommand):
|
||||
"""
|
||||
page - send private message
|
||||
|
||||
Usage:
|
||||
page[/switches] [<player>,<player>,... = <message>]
|
||||
tell ''
|
||||
page/list <number>
|
||||
|
||||
Switch:
|
||||
list - show your last <number> of tells/pages.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
key = "page"
|
||||
aliases = ['tell']
|
||||
permissions = "cmd:tell"
|
||||
help_category = "Comms"
|
||||
|
||||
def func(self):
|
||||
|
||||
"Implement function using the Msg methods"
|
||||
|
||||
caller = self.caller
|
||||
player = caller.player
|
||||
|
||||
# get the messages we've sent
|
||||
messages_we_sent = list(Msg.objects.get_messages_by_sender(player))
|
||||
pages_we_sent = [msg for msg in messages_we_sent
|
||||
if msg.receivers]
|
||||
# get last messages we've got
|
||||
pages_we_got = list(Msg.objects.get_messages_by_receiver(player))
|
||||
|
||||
if 'list' in self.switches:
|
||||
pages = pages_we_sent + pages_we_got
|
||||
pages.sort(lambda x,y: cmp(x.date_sent, y.date_sent))
|
||||
|
||||
number = 10
|
||||
if self.args:
|
||||
try:
|
||||
number = int(self.args)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if len(pages) > number:
|
||||
lastpages = pages[-number:]
|
||||
else:
|
||||
lastpages = pages
|
||||
|
||||
lastpages = "\n ".join(["{w%s{n {c%s{n to {c%s{n: %s" % (utils.datetime_format(page.date_sent),
|
||||
page.sender.name,
|
||||
"{n,{c ".join([obj.name for obj in page.receivers]),
|
||||
page.message)
|
||||
for page in lastpages])
|
||||
caller.msg("Your latest pages:\n %s" % lastpages )
|
||||
return
|
||||
|
||||
if not self.args or not self.rhs:
|
||||
if pages_we_sent:
|
||||
string = "You last paged {c%s{n." % (", ".join([obj.name
|
||||
for obj in pages_we_sent[-1].receivers]))
|
||||
caller.msg(string)
|
||||
return
|
||||
else:
|
||||
string = "You haven't paged anyone yet."
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
# We are sending. Build a list of targets
|
||||
|
||||
if not self.lhs:
|
||||
# If there are no targets, then set the targets
|
||||
# to the last person they paged.
|
||||
if pages_we_sent:
|
||||
receivers = pages_we_sent[-1].receivers
|
||||
else:
|
||||
caller.msg("Who do you want to page?")
|
||||
return
|
||||
else:
|
||||
receivers = self.lhslist
|
||||
|
||||
recobjs = []
|
||||
for receiver in set(receivers):
|
||||
if isinstance(receiver, basestring):
|
||||
pobj = caller.search("*%s" % (receiver.lstrip('*')), global_search=True)
|
||||
if not pobj:
|
||||
return
|
||||
elif hasattr(receiver, 'character'):
|
||||
pobj = receiver.character
|
||||
else:
|
||||
caller.msg("Who do you want to page?")
|
||||
return
|
||||
recobjs.append(pobj)
|
||||
if not recobjs:
|
||||
caller.msg("No players matching your target were found.")
|
||||
return
|
||||
|
||||
header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key
|
||||
message = self.rhs
|
||||
|
||||
# if message begins with a :, we assume it is a 'page-pose'
|
||||
if message.startswith(":"):
|
||||
message = "%s %s" % (caller.key, message.strip(':').strip())
|
||||
|
||||
# create the persistent message object
|
||||
msg = create.create_message(player, message,
|
||||
receivers=recobjs)
|
||||
|
||||
# tell the players they got a message.
|
||||
received = []
|
||||
for pobj in recobjs:
|
||||
pobj.msg("%s %s" % (header, message))
|
||||
if hasattr(pobj, 'has_player') and not pobj.has_player:
|
||||
received.append("{C%s{n" % pobj.name)
|
||||
caller.msg("%s is offline. They will see your message if they list their pages later." % received[-1])
|
||||
else:
|
||||
received.append("{c%s{n" % pobj.name)
|
||||
received = ", ".join(received)
|
||||
caller.msg("You paged %s with: '%s'." % (received, message))
|
||||
686
src/commands/default/general.py
Normal file
686
src/commands/default/general.py
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
"""
|
||||
Generic command module. Pretty much every command should go here for
|
||||
now.
|
||||
"""
|
||||
import time
|
||||
from django.conf import settings
|
||||
from src.server import sessionhandler
|
||||
from src.permissions.models import PermissionGroup
|
||||
from src.permissions.permissions import has_perm, has_perm_string
|
||||
from src.objects.models import HANDLE_SEARCH_ERRORS
|
||||
from src.utils import utils
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
class CmdHome(MuxCommand):
|
||||
"""
|
||||
home
|
||||
|
||||
Usage:
|
||||
home
|
||||
|
||||
Teleports the player to their home.
|
||||
"""
|
||||
|
||||
key = "home"
|
||||
permissions = "cmd:home"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
caller = self.caller
|
||||
home = caller.home
|
||||
if not home:
|
||||
caller.msg("You have no home set.")
|
||||
else:
|
||||
caller.move_to(home)
|
||||
caller.msg("There's no place like home ...")
|
||||
|
||||
class CmdLook(MuxCommand):
|
||||
"""
|
||||
look
|
||||
|
||||
Usage:
|
||||
look
|
||||
look <obj>
|
||||
|
||||
Observes your location or objects in your vicinity.
|
||||
"""
|
||||
key = "look"
|
||||
aliases = ["l"]
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Handle the looking.
|
||||
"""
|
||||
caller = self.caller
|
||||
args = self.args # caller.msg(inp)
|
||||
|
||||
if args:
|
||||
# Use search to handle duplicate/nonexistant results.
|
||||
looking_at_obj = caller.search(args)
|
||||
if not looking_at_obj:
|
||||
return
|
||||
else:
|
||||
looking_at_obj = caller.location
|
||||
if not looking_at_obj:
|
||||
caller.msg("Location: None")
|
||||
return
|
||||
# get object's appearance
|
||||
caller.msg(looking_at_obj.return_appearance(caller))
|
||||
# the object's at_desc() method.
|
||||
looking_at_obj.at_desc(looker=caller)
|
||||
|
||||
class CmdPassword(MuxCommand):
|
||||
"""
|
||||
@password - set your password
|
||||
|
||||
Usage:
|
||||
@password <old password> = <new password>
|
||||
|
||||
Changes your password. Make sure to pick a safe one.
|
||||
"""
|
||||
key = "@password"
|
||||
|
||||
def func(self):
|
||||
"hook function."
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.rhs:
|
||||
caller.msg("Usage: @password <oldpass> = <newpass>")
|
||||
return
|
||||
oldpass = self.lhslist[0] # this is already stripped by parse()
|
||||
newpass = self.rhslist[0] # ''
|
||||
try:
|
||||
uaccount = caller.player.user
|
||||
except AttributeError:
|
||||
caller.msg("This is only applicable for players.")
|
||||
return
|
||||
if not uaccount.check_password(oldpass):
|
||||
caller.msg("The specified old password isn't correct.")
|
||||
elif len(newpass) < 3:
|
||||
caller.msg("Passwords must be at least three characters long.")
|
||||
else:
|
||||
uaccount.set_password(newpass)
|
||||
uaccount.save()
|
||||
caller.msg("Password changed.")
|
||||
|
||||
class CmdNick(MuxCommand):
|
||||
"""
|
||||
Define a personal alias/nick
|
||||
|
||||
Usage:
|
||||
alias[/switches] <alias> = [<string>]
|
||||
nick ''
|
||||
|
||||
Switches: obj - alias an object
|
||||
player - alias a player
|
||||
clearall - clear all your aliases
|
||||
list - show all defined aliases
|
||||
|
||||
If no switch is given, a command/channel alias is created, used
|
||||
to replace strings before sending the command.
|
||||
|
||||
Creates a personal nick for some in-game object or
|
||||
string. When you enter that string, it will be replaced
|
||||
with the alternate string. The switches dictate in what
|
||||
situations the nick is checked and substituted. If string
|
||||
is None, the alias (if it exists) will be cleared.
|
||||
Obs - no objects are actually changed with this command,
|
||||
if you want to change the inherent aliases of an object,
|
||||
use the @alias command instead.
|
||||
"""
|
||||
key = "nickname"
|
||||
aliases = ["nick, @nick, alias"]
|
||||
|
||||
def func(self):
|
||||
"Create the nickname"
|
||||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
|
||||
if 'list' in switches:
|
||||
string = "{wAliases:{n \n"
|
||||
string = string + "\n\r".join(["%s = %s" % (alias, replace)
|
||||
for alias, replace
|
||||
in caller.nicks.items()])
|
||||
caller.msg(string)
|
||||
return
|
||||
if 'clearall' in switches:
|
||||
del caller.nicks
|
||||
caller.msg("Cleared all aliases.")
|
||||
return
|
||||
|
||||
if not self.args or not self.lhs:
|
||||
caller.msg("Usage: alias[/switches] string = [alias]")
|
||||
return
|
||||
|
||||
alias = self.lhs
|
||||
rstring = self.rhs
|
||||
err = None
|
||||
if rstring == alias:
|
||||
err = "No point in setting alias same as the string to replace..."
|
||||
caller.msg(err)
|
||||
return
|
||||
elif 'obj' in switches:
|
||||
# object alias, for adressing objects
|
||||
# (including user-controlled ones)
|
||||
err = caller.set_nick("_obj:%s" % alias, rstring)
|
||||
atype = "Object"
|
||||
elif 'player' in switches:
|
||||
# player alias, used for messaging
|
||||
err = caller.set_nick("_player:%s" % alias, rstring)
|
||||
atype = "Player "
|
||||
else:
|
||||
# a command/channel alias - these are replaced if
|
||||
# they begin a command string.
|
||||
caller.msg(rstring)
|
||||
caller.msg("going in: %s %s" % (alias, rstring))
|
||||
err = caller.set_nick(alias, rstring)
|
||||
atype = "Command/channel "
|
||||
if err:
|
||||
if rstring:
|
||||
err = "%salias %s changed from '%s' to '%s'." % (atype, alias, err, rstring)
|
||||
else:
|
||||
err = "Cleared %salias '%s'(='%s')." % (atype, alias, err)
|
||||
else:
|
||||
err = "Set %salias '%s' = '%s'" % (atype, alias, rstring)
|
||||
caller.msg(err.capitalize())
|
||||
|
||||
|
||||
class CmdInventory(MuxCommand):
|
||||
"""
|
||||
inventory
|
||||
|
||||
Usage:
|
||||
inventory
|
||||
inv
|
||||
|
||||
Shows a player's inventory.
|
||||
"""
|
||||
key = "inventory"
|
||||
aliases = ["inv", "i"]
|
||||
|
||||
def func(self):
|
||||
"hook function"
|
||||
string = "You are carrying:"
|
||||
for item in self.caller.contents:
|
||||
string += "\n %s" % item.name
|
||||
self.caller.msg(string)
|
||||
|
||||
## money = int(caller.MONEY)
|
||||
## if money == 1:
|
||||
## money_name = ConfigValue.objects.get_configvalue("MONEY_NAME_SINGULAR")
|
||||
## else:
|
||||
## money_name = ConfigValue.objects.get_configvalue("MONEY_NAME_PLURAL")
|
||||
##caller.msg("You have %d %s." % (money, money_name))
|
||||
|
||||
|
||||
class CmdGet(MuxCommand):
|
||||
"""
|
||||
get
|
||||
|
||||
Usage:
|
||||
get <obj>
|
||||
|
||||
Picks up an object from your location and puts it in
|
||||
your inventory.
|
||||
"""
|
||||
key = "get"
|
||||
aliases = "grab"
|
||||
|
||||
def func(self):
|
||||
"implements the command."
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.args:
|
||||
caller.msg("Get what?")
|
||||
return
|
||||
obj = caller.search(self.args)
|
||||
if not obj:
|
||||
return
|
||||
if caller == obj:
|
||||
caller.msg("You can't get yourself.")
|
||||
return
|
||||
if obj.player or obj.db._destination:
|
||||
# don't allow picking up player objects, nor exits.
|
||||
caller.msg("You can't get that.")
|
||||
return
|
||||
if not has_perm(caller, obj, 'get'):
|
||||
if obj.db.get_err_msg:
|
||||
caller.msg(obj.db.get_err_msg)
|
||||
else:
|
||||
caller.msg("You can't get that.")
|
||||
return
|
||||
|
||||
obj.move_to(caller, quiet=True)
|
||||
caller.msg("You pick up %s." % obj.name)
|
||||
caller.location.msg_contents("%s picks up %s." %
|
||||
(caller.name,
|
||||
obj.name),
|
||||
exclude=caller)
|
||||
# calling hook method
|
||||
obj.at_get(caller)
|
||||
|
||||
|
||||
class CmdDrop(MuxCommand):
|
||||
"""
|
||||
drop
|
||||
|
||||
Usage:
|
||||
drop <obj>
|
||||
|
||||
Lets you drop an object from your inventory into the
|
||||
location you are currently in.
|
||||
"""
|
||||
|
||||
key = "drop"
|
||||
|
||||
def func(self):
|
||||
"Implement command"
|
||||
|
||||
caller = self.caller
|
||||
if not self.args:
|
||||
caller.msg("Drop what?")
|
||||
return
|
||||
|
||||
results = caller.search(self.args, ignore_errors=True)
|
||||
# we process the results ourselves since we want to sift out only
|
||||
# those in our inventory.
|
||||
results = [obj for obj in results if obj in caller.contents]
|
||||
# now we send it into the handler.
|
||||
obj = HANDLE_SEARCH_ERRORS(caller, self.args, results, False)
|
||||
if not obj:
|
||||
return
|
||||
|
||||
obj.move_to(caller.location, quiet=True)
|
||||
caller.msg("You drop %s." % (obj.name,))
|
||||
caller.location.msg_contents("%s drops %s." %
|
||||
(caller.name, obj.name),
|
||||
exclude=caller)
|
||||
# Call the object script's at_drop() method.
|
||||
obj.at_drop(caller)
|
||||
|
||||
|
||||
class CmdQuit(MuxCommand):
|
||||
"""
|
||||
quit
|
||||
|
||||
Usage:
|
||||
@quit
|
||||
|
||||
Gracefully disconnect from the game.
|
||||
"""
|
||||
key = "@quit"
|
||||
|
||||
def func(self):
|
||||
"hook function"
|
||||
sessions = self.caller.sessions
|
||||
for session in sessions:
|
||||
session.msg("Quitting. Hope to see you soon again.")
|
||||
session.handle_close()
|
||||
|
||||
class CmdWho(MuxCommand):
|
||||
"""
|
||||
who
|
||||
|
||||
Usage:
|
||||
who
|
||||
doing
|
||||
|
||||
Shows who is currently online. Doing is an
|
||||
alias that limits info also for those with
|
||||
all permissions.
|
||||
"""
|
||||
|
||||
key = "who"
|
||||
aliases = "doing"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Get all connected players by polling session.
|
||||
"""
|
||||
|
||||
caller = self.caller
|
||||
session_list = sessionhandler.get_sessions()
|
||||
|
||||
if self.cmdstring == "doing":
|
||||
show_session_data = False
|
||||
else:
|
||||
show_session_data = has_perm_string(caller, "Immortals,Wizards")
|
||||
|
||||
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 session in session_list:
|
||||
if not session.logged_in:
|
||||
continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
delta_conn = time.time() - session.conn_time
|
||||
plr_pobject = session.get_character()
|
||||
|
||||
if show_session_data:
|
||||
retval += '%-31s%9s %4s%-3s#%-6d%5d%3s%-25s\r\n' % \
|
||||
(plr_pobject.name[:25], \
|
||||
# On-time
|
||||
utils.time_format(delta_conn,0), \
|
||||
# Idle time
|
||||
utils.time_format(delta_cmd,1), \
|
||||
# Flags
|
||||
'', \
|
||||
# Location
|
||||
plr_pobject.location.id, \
|
||||
session.cmd_total, \
|
||||
# More flags?
|
||||
'', \
|
||||
session.address[0])
|
||||
else:
|
||||
retval += '%-31s%9s %4s%-3s\r\n' % \
|
||||
(plr_pobject.name[:25], \
|
||||
# On-time
|
||||
utils.time_format(delta_conn,0), \
|
||||
# Idle time
|
||||
utils.time_format(delta_cmd,1), \
|
||||
# Flags
|
||||
'')
|
||||
retval += '%d Players logged in.' % (len(session_list),)
|
||||
|
||||
caller.msg(retval)
|
||||
|
||||
class CmdSay(MuxCommand):
|
||||
"""
|
||||
say
|
||||
|
||||
Usage:
|
||||
say <message>
|
||||
|
||||
Talk to those in your current location.
|
||||
"""
|
||||
|
||||
key = "say"
|
||||
aliases = ['"']
|
||||
|
||||
def func(self):
|
||||
"Run the say command"
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.args:
|
||||
caller.msg("Say what?")
|
||||
return
|
||||
|
||||
speech = self.args
|
||||
|
||||
# calling the speech hook on the location
|
||||
speech = caller.location.at_say(caller, speech)
|
||||
|
||||
# Feedback for the object doing the talking.
|
||||
caller.msg('You say, "%s{n"' % speech)
|
||||
|
||||
# Build the string to emit to neighbors.
|
||||
emit_string = '{c%s{n says, "%s{n"' % (caller.name,
|
||||
speech)
|
||||
caller.location.msg_contents(emit_string,
|
||||
exclude=caller)
|
||||
|
||||
## def cmd_fsay(command):
|
||||
## """
|
||||
## @fsay - make an object say something
|
||||
|
||||
## Usage:
|
||||
## @fsay <obj> = <text to say>
|
||||
|
||||
## Make an object talk to its current location.
|
||||
## """
|
||||
## caller = command.caller
|
||||
## args = command.command_argument
|
||||
|
||||
## if not args or not "=" in args:
|
||||
## caller.msg("Usage: @fsay <obj> = <text to say>")
|
||||
## return
|
||||
## target, speech = [arg.strip() for arg in args.split("=",1)]
|
||||
|
||||
## # find object
|
||||
## if target in ['here']:
|
||||
## results = [caller.location]
|
||||
## elif target in ['me','my']:
|
||||
## results = [caller]
|
||||
## else:
|
||||
## results = Object.objects.global_object_name_search(target)
|
||||
## if not results:
|
||||
## caller.msg("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.name
|
||||
## caller.msg(string)
|
||||
## return
|
||||
## target = results[0]
|
||||
|
||||
## # permission check
|
||||
## if not caller.controls_other(target):
|
||||
## caller.msg("Cannot pose %s (you don's control it)" % target.name)
|
||||
## return
|
||||
|
||||
## # Feedback for the object doing the talking.
|
||||
## caller.msg("%s says, '%s%s'" % (target.name,
|
||||
## speech,
|
||||
## ANSITable.ansi['normal']))
|
||||
|
||||
## # Build the string to emit to neighbors.
|
||||
## emit_string = "%s says, '%s'" % (target.name,
|
||||
## speech)
|
||||
## target.location.msg_contents(emit_string,
|
||||
## exclude=caller)
|
||||
## GLOBAL_CMD_TABLE.add_command("@fsay", cmd_fsay)
|
||||
|
||||
class CmdPose(MuxCommand):
|
||||
"""
|
||||
pose - strike a pose
|
||||
|
||||
Usage:
|
||||
pose <pose text>
|
||||
pose's <pose text>
|
||||
|
||||
Example:
|
||||
pose is standing by the wall, smiling.
|
||||
-> others will see:
|
||||
Tom is standing by the wall, smiling.
|
||||
|
||||
Describe an script being taken. The pose text will
|
||||
automatically begin with your name.
|
||||
"""
|
||||
key = "pose"
|
||||
aliases = [":", "emote"]
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Custom parse the cases where the emote
|
||||
starts with some special letter, such
|
||||
as 's, at which we don't want to separate
|
||||
the caller's name and the emote with a
|
||||
space.
|
||||
"""
|
||||
args = self.args
|
||||
if args and not args[0] in ["'", ",", ":"]:
|
||||
args = " %s" % args
|
||||
self.args = args
|
||||
|
||||
def func(self):
|
||||
"Hook function"
|
||||
if not self.args:
|
||||
msg = "Do what?"
|
||||
else:
|
||||
msg = "%s%s" % (self.caller.name, self.args)
|
||||
self.caller.location.msg_contents(msg)
|
||||
|
||||
## 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.
|
||||
## """
|
||||
## caller = command.caller
|
||||
## args = command.command_argument
|
||||
|
||||
## if not args or not "=" in args:
|
||||
## caller.msg("Usage: @fpose <obj> = <pose text>")
|
||||
## return
|
||||
## target, pose_string = [arg.strip() for arg in args.split("=",1)]
|
||||
## # find object
|
||||
## if target in ['here']:
|
||||
## results = [caller.location]
|
||||
## elif target in ['me','my']:
|
||||
## results = [caller]
|
||||
## else:
|
||||
## results = Object.objects.global_object_name_search(target)
|
||||
## if not results:
|
||||
## caller.msg("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.name
|
||||
## caller.msg(string)
|
||||
## return
|
||||
## target = results[0]
|
||||
|
||||
## # permission check
|
||||
## if not caller.controls_other(target):
|
||||
## caller.msg("Cannot pose %s (you don's control it)" % target.name)
|
||||
## return
|
||||
|
||||
## if "nospace" in command.command_switches:
|
||||
## # Output without a space between the player name and the emote.
|
||||
## sent_msg = "%s%s" % (target.name,
|
||||
## pose_string)
|
||||
## else:
|
||||
## # No switches, default.
|
||||
## sent_msg = "%s %s" % (target.name,
|
||||
## pose_string)
|
||||
|
||||
## caller.location.msg_contents(sent_msg)
|
||||
## GLOBAL_CMD_TABLE.add_command("@fpose", cmd_fpose)
|
||||
|
||||
|
||||
class CmdEncoding(MuxCommand):
|
||||
"""
|
||||
encoding - set a custom text encoding
|
||||
|
||||
Usage:
|
||||
@encoding/switches [<encoding>]
|
||||
|
||||
Switches:
|
||||
clear - clear your custom encoding
|
||||
|
||||
|
||||
This sets the text encoding for communicating with Evennia. This is mostly an issue only if
|
||||
you want to use non-ASCII characters (i.e. letters/symbols not found in English). If you see
|
||||
that your characters look strange (or you get encoding errors), you should use this command
|
||||
to set the server encoding to be the same used in your client program.
|
||||
|
||||
Common encodings are utf-8 (default), latin-1, ISO-8859-1 etc.
|
||||
|
||||
If you don't submit an encoding, the current encoding will be displayed instead.
|
||||
"""
|
||||
|
||||
key = "@encoding"
|
||||
aliases = "@encode"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Sets the encoding.
|
||||
"""
|
||||
caller = self.caller
|
||||
if 'clear' in self.switches:
|
||||
# remove customization
|
||||
old_encoding = caller.player.db.encoding
|
||||
if old_encoding:
|
||||
string = "Your custom text encoding ('%s') was cleared." % old_encoding
|
||||
else:
|
||||
string = "No custom encoding was set."
|
||||
del caller.player.db.encoding
|
||||
elif not self.args:
|
||||
# just list the encodings supported
|
||||
encodings = []
|
||||
encoding = caller.player.db.encoding
|
||||
string = "Supported encodings "
|
||||
if encoding:
|
||||
encodings.append(encoding)
|
||||
string += "(the first one you can change with {w@encoding <encoding>{n)"
|
||||
encodings.extend(settings.ENCODINGS)
|
||||
string += ":\n " + ", ".join(encodings)
|
||||
else:
|
||||
# change encoding
|
||||
old_encoding = caller.player.db.encoding
|
||||
encoding = self.args
|
||||
caller.player.db.encoding = encoding
|
||||
string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding)
|
||||
caller.msg(string)
|
||||
|
||||
class CmdGroup(MuxCommand):
|
||||
"""
|
||||
group - show your groups
|
||||
|
||||
Usage:
|
||||
group
|
||||
|
||||
This command shows you which user permission groups
|
||||
you are a member of, if any.
|
||||
"""
|
||||
key = "access"
|
||||
aliases = "groups"
|
||||
|
||||
def func(self):
|
||||
"Load the permission groups"
|
||||
|
||||
caller = self.caller
|
||||
|
||||
string = ""
|
||||
if caller.player and caller.player.is_superuser:
|
||||
string += "\n This is a SUPERUSER account! Group membership does not matter."
|
||||
else:
|
||||
# get permissions and determine if they are groups
|
||||
perms = list(set(caller.permissions + caller.player.permissions))
|
||||
|
||||
for group in [group for group in PermissionGroup.objects.all()
|
||||
if group.key in perms]:
|
||||
string += "\n {w%s{n\n%s" % (group.key, ", ".join(group.group_permissions))
|
||||
if string:
|
||||
string = "\nGroup memberships for you (Player %s + Character %s): %s" % (caller.player.name,
|
||||
caller.name, string)
|
||||
else:
|
||||
string = "\nYou are not not a member of any groups."
|
||||
caller.msg(string)
|
||||
|
||||
## 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.caller.execute_cmd("help/apropos %s" % arg)
|
||||
## GLOBAL_CMD_TABLE.add_command("apropos", cmd_apropos)
|
||||
## GLOBAL_CMD_TABLE.add_command("suggest", cmd_apropos)
|
||||
297
src/commands/default/help.py
Normal file
297
src/commands/default/help.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
"""
|
||||
The help command. The basic idea is that help texts for commands
|
||||
are best written by those that write the commands - the admins. So
|
||||
command-help is all auto-loaded and searched from the current command
|
||||
set. The normal, database-tied help system is used for collaborative
|
||||
creation of other help topics such as RP help or game-world aides.
|
||||
"""
|
||||
|
||||
from src.utils.utils import fill, dedent
|
||||
from src.commands.command import Command
|
||||
from src.help.models import HelpEntry
|
||||
from src.permissions.permissions import has_perm
|
||||
from src.utils import create
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
LIST_ARGS = ["list", "all"]
|
||||
|
||||
def format_help_entry(title, help_text, aliases=None,
|
||||
suggested=None):
|
||||
"""
|
||||
This visually formats the help entry.
|
||||
"""
|
||||
string = "-"*70 + "\n"
|
||||
if title:
|
||||
string += "Help topic for {w%s{n" % (title.capitalize())
|
||||
if aliases:
|
||||
string += " (aliases: %s)" % (", ".join(aliases))
|
||||
if help_text:
|
||||
string += "\n%s" % dedent(help_text.rstrip())
|
||||
if suggested:
|
||||
string += "\nSuggested:\n"
|
||||
string += fill(", ".join(suggested))
|
||||
string.strip()
|
||||
string += "\n" + "-"*70
|
||||
return string
|
||||
|
||||
def format_help_list(hdict_cmds, hdict_db):
|
||||
"""
|
||||
Output a category-ordered list.
|
||||
"""
|
||||
string = ""
|
||||
if hdict_cmds and hdict_cmds.values():
|
||||
string += "\n\r" + "-"*70 + "\n\r {gCommand help entries{n\n" + "-"*70
|
||||
for category in sorted(hdict_cmds.keys()):
|
||||
string += "\n {w%s{n:\n" % \
|
||||
(str(category).capitalize())
|
||||
string += fill(", ".join(sorted(hdict_cmds[category])))
|
||||
if hdict_db and hdict_db.values():
|
||||
string += "\n\r\n\r" + "-"*70 + "\n\r {gOther help entries{n\n" + '-'*70
|
||||
for category in sorted(hdict_db.keys()):
|
||||
string += "\n\r {w%s{n:\n" % (str(category).capitalize())
|
||||
string += fill(", ".join(sorted([str(topic) for topic in hdict_db[category]])))
|
||||
return string
|
||||
|
||||
class CmdHelp(Command):
|
||||
"""
|
||||
The main help command
|
||||
|
||||
Usage:
|
||||
help <topic or command>
|
||||
help list
|
||||
help all
|
||||
|
||||
This will search for help on commands and other
|
||||
topics related to the game.
|
||||
"""
|
||||
key = "help"
|
||||
# this is a special cmdhandler flag that makes the cmdhandler also pack
|
||||
# the current cmdset with the call to self.func().
|
||||
return_cmdset = True
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
inp is a string containing the command or topic match.
|
||||
"""
|
||||
self.args = self.args.strip().lower()
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Run the dynamic help entry creator.
|
||||
"""
|
||||
query, cmdset = self.args, self.cmdset
|
||||
caller = self.caller
|
||||
|
||||
if not query:
|
||||
query = "all"
|
||||
|
||||
# removing doublets in cmdset, caused by cmdhandler
|
||||
# having to allow doublet commands to manage exits etc.
|
||||
cmdset.make_unique(caller)
|
||||
|
||||
# Listing help entries
|
||||
|
||||
if query in LIST_ARGS:
|
||||
# we want to list all available help entries
|
||||
hdict_cmd = {}
|
||||
for cmd in (cmd for cmd in cmdset if has_perm(caller, cmd, 'cmd')
|
||||
if not cmd.key.startswith('__')
|
||||
and not (hasattr(cmd, 'is_exit') and cmd.is_exit)):
|
||||
if hdict_cmd.has_key(cmd.help_category):
|
||||
hdict_cmd[cmd.help_category].append(cmd.key)
|
||||
else:
|
||||
hdict_cmd[cmd.help_category] = [cmd.key]
|
||||
hdict_db = {}
|
||||
for topic in (topic for topic in HelpEntry.objects.get_all_topics()
|
||||
if has_perm(caller, topic, 'view')):
|
||||
if hdict_db.has_key(topic.help_category):
|
||||
hdict_db[topic.help_category].append(topic.key)
|
||||
else:
|
||||
hdict_db[topic.help_category] = [topic.key]
|
||||
help_entry = format_help_list(hdict_cmd, hdict_db)
|
||||
caller.msg(help_entry)
|
||||
return
|
||||
|
||||
# Look for a particular help entry
|
||||
|
||||
# Cmd auto-help dynamic entries
|
||||
cmdmatches = [cmd for cmd in cmdset
|
||||
if query in cmd and has_perm(caller, cmd, 'cmd')]
|
||||
if len(cmdmatches) > 1:
|
||||
# multiple matches. Try to limit it down to exact match
|
||||
exactmatches = [cmd for cmd in cmdmatches if cmd == query]
|
||||
if exactmatches:
|
||||
cmdmatches = exactmatches
|
||||
|
||||
# Help-database static entries
|
||||
dbmatches = \
|
||||
[topic for topic in
|
||||
HelpEntry.objects.find_topicmatch(query, exact=False)
|
||||
if has_perm(caller, topic, 'view')]
|
||||
if len(dbmatches) > 1:
|
||||
exactmatches = \
|
||||
[topic for topic in
|
||||
HelpEntry.objects.find_topicmatch(query, exact=True)
|
||||
if has_perm(caller, topic, 'view')]
|
||||
if exactmatches:
|
||||
dbmatches = exactmatches
|
||||
|
||||
# Handle result
|
||||
if (not cmdmatches) and (not dbmatches):
|
||||
# no normal match. Check if this is a category match instead
|
||||
categ_cmdmatches = [cmd.key for cmd in cmdset
|
||||
if query == cmd.help_category and has_perm(caller, cmd, 'cmd')]
|
||||
categ_dbmatches = \
|
||||
[topic.key for topic in
|
||||
HelpEntry.objects.find_topics_with_category(query)
|
||||
if has_perm(caller, topic, 'view')]
|
||||
cmddict = None
|
||||
dbdict = None
|
||||
if categ_cmdmatches:
|
||||
cmddict = {query:categ_cmdmatches}
|
||||
if categ_dbmatches:
|
||||
dbdict = {query:categ_dbmatches}
|
||||
if cmddict or dbdict:
|
||||
help_entry = format_help_list(cmddict, dbdict)
|
||||
else:
|
||||
help_entry = "No help entry found for '%s'" % query
|
||||
|
||||
elif len(cmdmatches) == 1:
|
||||
# we matched against a command name or alias. Show its help entry.
|
||||
suggested = []
|
||||
if dbmatches:
|
||||
suggested = [entry.key for entry in dbmatches]
|
||||
cmd = cmdmatches[0]
|
||||
help_entry = format_help_entry(cmd.key, cmd.__doc__,
|
||||
aliases=cmd.aliases,
|
||||
suggested=suggested)
|
||||
elif len(dbmatches) == 1:
|
||||
# matched against a database entry
|
||||
entry = dbmatches[0]
|
||||
help_entry = format_help_entry(entry.key, entry.entrytext)
|
||||
else:
|
||||
# multiple matches of either type
|
||||
cmdalts = [cmd.key for cmd in cmdmatches]
|
||||
dbalts = [entry.key for entry in dbmatches]
|
||||
helptext = "Multiple help entries match your search ..."
|
||||
help_entry = format_help_entry("", helptext, None, cmdalts + dbalts)
|
||||
|
||||
# send result to user
|
||||
caller.msg(help_entry)
|
||||
|
||||
class CmdSetHelp(MuxCommand):
|
||||
"""
|
||||
@help - edit the help database
|
||||
|
||||
Usage:
|
||||
@help[/switches] <topic>[,category[,permission,permission,...]] = <text>
|
||||
|
||||
Switches:
|
||||
add - add or replace a new topic with text.
|
||||
append - add text to the end of topic with a newline between.
|
||||
merge - As append, but don't add a newline between the old
|
||||
text and the appended text.
|
||||
delete - remove help topic.
|
||||
force - (used with add) create help topic also if the topic
|
||||
already exists.
|
||||
|
||||
Examples:
|
||||
@sethelp/add throw = This throws something at ...
|
||||
@sethelp/append pickpocketing,Thievery,is_thief, is_staff) = This steals ...
|
||||
@sethelp/append pickpocketing, ,is_thief, is_staff) = This steals ...
|
||||
|
||||
"""
|
||||
key = "@help"
|
||||
aliases = "@sethelp"
|
||||
permissions = "cmd:sethelp"
|
||||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"Implement the function"
|
||||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
lhslist = self.lhslist
|
||||
rhs = self.rhs
|
||||
|
||||
if not self.rhs:
|
||||
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,permission,..] = <text>]")
|
||||
return
|
||||
|
||||
topicstr = ""
|
||||
category = ""
|
||||
permissions = ""
|
||||
try:
|
||||
topicstr = lhslist[0]
|
||||
category = lhslist[1]
|
||||
permissions = ",".join(lhslist[2:])
|
||||
except Exception:
|
||||
pass
|
||||
if not topicstr:
|
||||
caller.msg("You have to define a topic!")
|
||||
return
|
||||
string = ""
|
||||
print topicstr, category, permissions
|
||||
|
||||
if switches and switches[0] in ('append', 'app','merge'):
|
||||
# add text to the end of a help topic
|
||||
# find the topic to append to
|
||||
old_entry = None
|
||||
try:
|
||||
old_entry = HelpEntry.objects.get(key=topicstr)
|
||||
except Exception:
|
||||
pass
|
||||
if not old_entry:
|
||||
string = "Could not find topic '%s'. You must give an exact name." % topicstr
|
||||
else:
|
||||
entrytext = old_entry.entrytext
|
||||
if switches[0] == 'merge':
|
||||
old_entry.entrytext = "%s %s" % (entrytext, self.rhs)
|
||||
string = "Added the new text right after the old one (merge)."
|
||||
else:
|
||||
old_entry.entrytext = "%s\n\n%s" % (entrytext, self.rhs)
|
||||
string = "Added the new text as a new paragraph after the old one (append)"
|
||||
old_entry.save()
|
||||
|
||||
elif switches and switches[0] in ('delete','del'):
|
||||
#delete a help entry
|
||||
old_entry = None
|
||||
try:
|
||||
old_entry = HelpEntry.objects.get(key=topicstr)
|
||||
except Exception:
|
||||
pass
|
||||
if not old_entry:
|
||||
string = "Could not find topic. You must give an exact name."
|
||||
else:
|
||||
old_entry.delete()
|
||||
string = "Deleted the help entry '%s'." % topicstr
|
||||
|
||||
else:
|
||||
# add a new help entry.
|
||||
force_create = ('for' in switches) or ('force' in switches)
|
||||
old_entry = None
|
||||
try:
|
||||
old_entry = HelpEntry.objects.get(key=topicstr)
|
||||
except Exception:
|
||||
pass
|
||||
if old_entry:
|
||||
if force_create:
|
||||
old_entry.key = topicstr
|
||||
old_entry.entrytext = self.rhs
|
||||
old_entry.help_category = category
|
||||
old_entry.permissions = permissions
|
||||
old_entry.save()
|
||||
string = "Overwrote the old topic '%s' with a new one." % topicstr
|
||||
else:
|
||||
string = "Topic '%s' already exists. Use /force to overwrite it." % topicstr
|
||||
else:
|
||||
# no old entry. Create a new one.
|
||||
new_entry = create.create_help_entry(topicstr,
|
||||
rhs, category, permissions)
|
||||
if new_entry:
|
||||
string = "Topic '%s' was successfully created." % topicstr
|
||||
else:
|
||||
string = "Error when creating topic '%s'! Maybe it already exists?" % topicstr
|
||||
|
||||
# give feedback
|
||||
caller.msg(string)
|
||||
147
src/commands/default/muxcommand.py
Normal file
147
src/commands/default/muxcommand.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
The command template for the default MUX-style command set
|
||||
"""
|
||||
|
||||
from src.utils import utils
|
||||
from src.commands.command import Command
|
||||
|
||||
class MuxCommand(Command):
|
||||
"""
|
||||
This sets up the basis for a MUX command. The idea
|
||||
is that most other Mux-related commands should just
|
||||
inherit from this and don't have to implement much
|
||||
parsing of their own unless they do something particularly
|
||||
advanced.
|
||||
|
||||
Note that the class's __doc__ string (this text) is
|
||||
used by Evennia to create the automatic help entry for
|
||||
the command, so make sure to document consistently here.
|
||||
"""
|
||||
def has_perm(self, srcobj):
|
||||
"""
|
||||
This is called by the cmdhandler to determine
|
||||
if srcobj is allowed to execute this command.
|
||||
We just show it here for completeness - we
|
||||
are satisfied using the default check in Command.
|
||||
"""
|
||||
return super(MuxCommand, self).has_perm(srcobj)
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
This method is called by the cmdhandler once the command name
|
||||
has been identified. It creates a new set of member variables
|
||||
that can be later accessed from self.func() (see below)
|
||||
|
||||
The following variables are available for our use when entering this
|
||||
method (from the command definition, and assigned on the fly by the
|
||||
cmdhandler):
|
||||
self.key - the name of this command ('look')
|
||||
self.aliases - the aliases of this cmd ('l')
|
||||
self.permissions - permission string for this command
|
||||
self.help_category - overall category of command
|
||||
|
||||
self.caller - the object calling this command
|
||||
self.cmdstring - the actual command name used to call this
|
||||
(this allows you to know which alias was used,
|
||||
for example)
|
||||
self.args - the raw input; everything following self.cmdstring.
|
||||
self.cmdset - the cmdset from which this command was picked. Not
|
||||
often used (useful for commands like 'help' or to
|
||||
list all available commands etc)
|
||||
self.obj - the object on which this command was defined. It is often
|
||||
the same as self.caller.
|
||||
|
||||
A MUX command has the following possible syntax:
|
||||
|
||||
name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]]
|
||||
|
||||
The 'name[ with several words]' part is already dealt with by the
|
||||
cmdhandler at this point, and stored in self.cmdname (we don't use
|
||||
it here). The rest of the command is stored in self.args, which can start
|
||||
with the switch indicator /.
|
||||
|
||||
This parser breaks self.args into its constituents and stores them in the
|
||||
following variables:
|
||||
self.switches = [list of /switches (without the /)]
|
||||
self.raw = This is the raw argument input, including switches
|
||||
self.args = This is re-defined to be everything *except* the switches
|
||||
self.lhs = Everything to the left of = (lhs:'left-hand side'). If
|
||||
no = is found, this is identical to self.args.
|
||||
self.rhs: Everything to the right of = (rhs:'right-hand side').
|
||||
If no '=' is found, this is None.
|
||||
self.lhslist - [self.lhs split into a list by comma]
|
||||
self.rhslist - [list of self.rhs split into a list by comma]
|
||||
self.arglist = [list of space-separated args (stripped, including '=' if it exists)]
|
||||
|
||||
All args and list members are stripped of excess whitespace around the
|
||||
strings, but case is preserved.
|
||||
"""
|
||||
raw = self.args
|
||||
args = raw.strip()
|
||||
|
||||
# split out switches
|
||||
switches = []
|
||||
if args and len(args) >1 and args[0] == "/":
|
||||
# we have a switch, or a set of switches. These end with a space.
|
||||
#print "'%s'" % args
|
||||
switches = args[1:].split(None, 1)
|
||||
if len(switches) > 1:
|
||||
switches, args = switches
|
||||
switches = switches.split('/')
|
||||
else:
|
||||
args = ""
|
||||
switches = switches[0].split('/')
|
||||
arglist = [arg.strip() for arg in args.split(None)]
|
||||
|
||||
# check for arg1, arg2, ... = argA, argB, ... constructs
|
||||
lhs, rhs = args, None
|
||||
lhslist, rhslist = [arg.strip() for arg in args.split(',')], []
|
||||
if args and '=' in args:
|
||||
lhs, rhs = [arg.strip() for arg in args.split('=', 1)]
|
||||
lhslist = [arg.strip() for arg in lhs.split(',')]
|
||||
rhslist = [arg.strip() for arg in rhs.split(',')]
|
||||
|
||||
# save to object properties:
|
||||
self.raw = raw
|
||||
self.switches = switches
|
||||
self.args = args.strip()
|
||||
self.arglist = arglist
|
||||
self.lhs = lhs
|
||||
self.lhslist = lhslist
|
||||
self.rhs = rhs
|
||||
self.rhslist = rhslist
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
This is the hook function that actually does all the work. It is called
|
||||
by the cmdhandler right after self.parser() finishes, and so has access
|
||||
to all the variables defined therein.
|
||||
"""
|
||||
# a simple test command to show the available properties
|
||||
string = "-" * 50
|
||||
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
|
||||
string += "-" * 50
|
||||
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
|
||||
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
|
||||
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
|
||||
string += "help category (self.help_category): {w%s{n\n" % self.help_category
|
||||
string += "object calling (self.caller): {w%s{n\n" % self.caller
|
||||
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
|
||||
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
|
||||
# show cmdset.key instead of cmdset to shorten output
|
||||
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
|
||||
|
||||
|
||||
string += "\n" + "-" * 50
|
||||
string += "\nVariables from MuxCommand baseclass\n"
|
||||
string += "-" * 50
|
||||
string += "\nraw argument (self.raw): {w%s{n \n" % self.raw
|
||||
string += "cmd args (self.args): {w%s{n\n" % self.args
|
||||
string += "cmd switches (self.switches): {w%s{n\n" % self.switches
|
||||
string += "space-separated arg list (self.arglist): {w%s{n\n" % self.arglist
|
||||
string += "lhs, left-hand side of '=' (self.lhs): {w%s{n\n" % self.lhs
|
||||
string += "lhs, comma separated (self.lhslist): {w%s{n\n" % self.lhslist
|
||||
string += "rhs, right-hand side of '=' (self.rhs): {w%s{n\n" % self.rhs
|
||||
string += "rhs, comma separated (self.rhslist): {w%s{n\n" % self.rhslist
|
||||
string += "-" * 50
|
||||
self.caller.msg(string)
|
||||
220
src/commands/default/syscommands.py
Normal file
220
src/commands/default/syscommands.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
"""
|
||||
System commands
|
||||
|
||||
These are the default commands called by the system commandhandler
|
||||
when various exceptions occur. If one of these commands are not
|
||||
implemented and part of the current cmdset, the engine falls back
|
||||
to a default solution instead.
|
||||
|
||||
Some system commands are shown in this module
|
||||
as a REFERENCE only (they are not all added to Evennia's
|
||||
default cmdset since they don't currently do anything differently from the
|
||||
default backup systems hard-wired in the engine).
|
||||
|
||||
Overloading these commands in a cmdset can be used to create
|
||||
interesting effects. An example is using the NoMatch system command
|
||||
to implement a line-editor where you don't have to start each
|
||||
line with a command (if there is no match to a known command,
|
||||
the line is just added to the editor buffer).
|
||||
"""
|
||||
|
||||
from src.comms.models import Channel
|
||||
from src.utils import create
|
||||
from src.permissions.permissions import has_perm
|
||||
|
||||
# The command keys the engine is calling
|
||||
# (the actual names all start with __)
|
||||
from src.commands.cmdhandler import CMD_NOINPUT
|
||||
from src.commands.cmdhandler import CMD_NOMATCH
|
||||
from src.commands.cmdhandler import CMD_MULTIMATCH
|
||||
from src.commands.cmdhandler import CMD_NOPERM
|
||||
from src.commands.cmdhandler import CMD_CHANNEL
|
||||
from src.commands.cmdhandler import CMD_EXIT
|
||||
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
# Command called when there is no input at line
|
||||
# (i.e. an lone return key)
|
||||
|
||||
class SystemNoInput(MuxCommand):
|
||||
"""
|
||||
This is called when there is no input given
|
||||
"""
|
||||
key = CMD_NOINPUT
|
||||
|
||||
def func(self):
|
||||
"Do nothing."
|
||||
pass
|
||||
|
||||
#
|
||||
# Command called when there was no match to the
|
||||
# command name
|
||||
#
|
||||
|
||||
class SystemNoMatch(MuxCommand):
|
||||
"""
|
||||
No command was found matching the given input.
|
||||
"""
|
||||
key = CMD_NOMATCH
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
This is given the failed raw string as input.
|
||||
"""
|
||||
self.caller.msg("Huh?")
|
||||
|
||||
#
|
||||
# Command called when there were mulitple matches to the command.
|
||||
#
|
||||
class SystemMultimatch(MuxCommand):
|
||||
"""
|
||||
Multiple command matches.
|
||||
|
||||
The cmdhandler adds a special attribute 'matches' to this
|
||||
system command.
|
||||
|
||||
matches = [(candidate, cmd) , (candidate, cmd), ...],
|
||||
|
||||
where candidate is an instance of src.commands.cmdparser.CommandCandidate
|
||||
and cmd is an an instantiated Command object matching the candidate.
|
||||
"""
|
||||
key = CMD_MULTIMATCH
|
||||
|
||||
def format_multimatches(self, caller, matches):
|
||||
"""
|
||||
Format multiple command matches to a useful error.
|
||||
|
||||
This is copied directly from the default method in
|
||||
src.commands.cmdhandler.
|
||||
|
||||
"""
|
||||
string = "There where multiple matches:"
|
||||
for num, match in enumerate(matches):
|
||||
# each match is a tuple (candidate, cmd)
|
||||
candidate, cmd = match
|
||||
|
||||
is_channel = hasattr(cmd, "is_channel") and cmd.is_channel
|
||||
if is_channel:
|
||||
is_channel = " (channel)"
|
||||
else:
|
||||
is_channel = ""
|
||||
is_exit = hasattr(cmd, "is_exit") and cmd.is_exit
|
||||
if is_exit and cmd.destination:
|
||||
is_exit = " (exit to %s)" % cmd.destination
|
||||
else:
|
||||
is_exit = ""
|
||||
|
||||
id1 = ""
|
||||
id2 = ""
|
||||
if not (is_channel or is_exit) and (hasattr(cmd, 'obj') and cmd.obj != caller):
|
||||
# the command is defined on some other object
|
||||
id1 = "%s-" % cmd.obj.name
|
||||
id2 = " (%s-%s)" % (num + 1, candidate.cmdname)
|
||||
else:
|
||||
id1 = "%s-" % (num + 1)
|
||||
id2 = ""
|
||||
string += "\n %s%s%s%s%s" % (id1, candidate.cmdname, id2, is_channel, is_exit)
|
||||
return string
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
argument to cmd is a comma-separated string of
|
||||
all the clashing matches.
|
||||
"""
|
||||
string = self.format_multimatches(self.caller, self.matches)
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
class SystemNoPerm(MuxCommand):
|
||||
"""
|
||||
This is called when the user does not have the
|
||||
correct permissions to use a particular command.
|
||||
"""
|
||||
key = CMD_NOPERM
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
This receives the original raw
|
||||
input string (the one whose command failed to validate)
|
||||
as argument.
|
||||
"""
|
||||
self.caller.msg("You are not allowed to do that.")
|
||||
|
||||
|
||||
# Command called when the comman given at the command line
|
||||
# was identified as a channel name, like there existing a
|
||||
# channel named 'ooc' and the user wrote
|
||||
# > ooc Hello!
|
||||
|
||||
class SystemSendToChannel(MuxCommand):
|
||||
"""
|
||||
This is a special command that the cmdhandler calls
|
||||
when it detects that the command given matches
|
||||
an existing Channel object key (or alias).
|
||||
"""
|
||||
|
||||
key = CMD_CHANNEL
|
||||
permissions = "cmd:use_channels"
|
||||
|
||||
def parse(self):
|
||||
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.
|
||||
"""
|
||||
caller = self.caller
|
||||
channelkey, msg = self.args
|
||||
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)
|
||||
msgobj = create.create_message(caller, msg, channels=[channel])
|
||||
channel.msg(msgobj)
|
||||
|
||||
#
|
||||
# Command called when the system recognizes the command given
|
||||
# as matching an exit from the room. E.g. if there is an exit called 'door'
|
||||
# and the user gives the command
|
||||
# > door
|
||||
# the exit 'door' should be traversed to its destination.
|
||||
|
||||
class SystemUseExit(MuxCommand):
|
||||
"""
|
||||
Handles what happens when user gives a valid exit
|
||||
as a command. It receives the raw string as input.
|
||||
"""
|
||||
key = CMD_EXIT
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Handle traversing an exit
|
||||
"""
|
||||
caller = self.caller
|
||||
if not self.args:
|
||||
return
|
||||
exit_name = self.args
|
||||
exi = caller.search(exit_name)
|
||||
if not exi:
|
||||
return
|
||||
destination = exi.attr('_destination')
|
||||
if not destination:
|
||||
return
|
||||
if has_perm(caller, exit, 'traverse'):
|
||||
caller.move_to(destination)
|
||||
else:
|
||||
caller.msg("You cannot enter")
|
||||
686
src/commands/default/system.py
Normal file
686
src/commands/default/system.py
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
"""
|
||||
|
||||
System commands
|
||||
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import os, datetime
|
||||
import django, twisted
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from src.server import sessionhandler
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.objects.models import ObjectDB
|
||||
from src.config.models import ConfigValue
|
||||
from src.utils import reloads, create, logger, utils, gametime
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
class CmdReload(MuxCommand):
|
||||
"""
|
||||
Reload the system
|
||||
|
||||
Usage:
|
||||
@reload
|
||||
|
||||
This reloads the system modules and
|
||||
re-validates all scripts.
|
||||
"""
|
||||
key = "@reload"
|
||||
permissions = "cmd:reload"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Reload the system.
|
||||
"""
|
||||
caller = self.caller
|
||||
reloads.reload_modules()
|
||||
|
||||
max_attempts = 4
|
||||
for attempt in range(max_attempts):
|
||||
# if reload modules take a long time,
|
||||
# we might end up in a situation where
|
||||
# the subsequent commands fail since they
|
||||
# can't find the reloads module (due to it
|
||||
# not yet fully loaded). So we retry a few
|
||||
# times before giving up.
|
||||
try:
|
||||
reloads.reload_scripts()
|
||||
reloads.reload_commands()
|
||||
break
|
||||
except AttributeError:
|
||||
if attempt < max_attempts-1:
|
||||
caller.msg(" Waiting for modules(s) to finish (%s) ..." % attempt)
|
||||
else:
|
||||
string = " ... The module(s) took too long to reload, "
|
||||
string += "\n so the remaining reloads where skipped."
|
||||
string += "\n Re-run @reload again when modules have fully "
|
||||
string += "\n re-initialized."
|
||||
caller.msg(string)
|
||||
|
||||
class CmdPy(MuxCommand):
|
||||
"""
|
||||
Execute a snippet of python code
|
||||
|
||||
Usage:
|
||||
@py <cmd>
|
||||
|
||||
In this limited python environment.
|
||||
|
||||
available_vars: 'self','me' : caller
|
||||
'here' : caller.location
|
||||
'obj' : dummy obj instance
|
||||
'script': dummy script instance
|
||||
'config': dummy conf instance
|
||||
'ObjectDB' : ObjectDB class
|
||||
'ScriptDB' : ScriptDB class
|
||||
'ConfigValue' ConfigValue class
|
||||
only two
|
||||
variables are defined: 'self'/'me' which refers to one's
|
||||
own object, and 'here' which refers to the current
|
||||
location.
|
||||
"""
|
||||
key = "@py"
|
||||
aliases = ["!"]
|
||||
permissions = "cmd:py"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"hook function"
|
||||
|
||||
caller = self.caller
|
||||
pycode = self.args
|
||||
|
||||
if not pycode:
|
||||
string = "Usage: @py <code>"
|
||||
caller.msg(string)
|
||||
return
|
||||
# create temporary test objects for playing with
|
||||
script = create.create_script("src.scripts.scripts.DoNothing",
|
||||
'testscript')
|
||||
obj = create.create_object("src.objects.objects.Object",
|
||||
'testobject')
|
||||
conf = ConfigValue() # used to access conf values
|
||||
available_vars = {'self':caller,
|
||||
'me':caller,
|
||||
'here':caller.location,
|
||||
'obj':obj,
|
||||
'script':script,
|
||||
'config':conf,
|
||||
'ObjectDB':ObjectDB,
|
||||
'ScriptDB':ScriptDB,
|
||||
'ConfigValue':ConfigValue}
|
||||
|
||||
caller.msg(">>> %s" % pycode)
|
||||
try:
|
||||
ret = eval(pycode, {}, available_vars)
|
||||
ret = "<<< %s" % str(ret)
|
||||
except Exception:
|
||||
try:
|
||||
exec(pycode, {}, available_vars)
|
||||
ret = "<<< Done."
|
||||
except Exception:
|
||||
errlist = traceback.format_exc().split('\n')
|
||||
if len(errlist) > 4:
|
||||
errlist = errlist[4:]
|
||||
ret = "\n".join("<<< %s" % line for line in errlist if line)
|
||||
caller.msg(ret)
|
||||
obj.delete()
|
||||
script.delete()
|
||||
|
||||
class CmdListScripts(MuxCommand):
|
||||
"""
|
||||
Operate on scripts.
|
||||
|
||||
Usage:
|
||||
@scripts[/switches] [<obj or scriptid>]
|
||||
|
||||
Switches:
|
||||
stop - stops an existing script
|
||||
validate - run a validation on the script(s)
|
||||
|
||||
If no switches are given, this command just views all active
|
||||
scripts. The argument can be either an object, at which point it
|
||||
will be searched for all scripts defined on it, or an script name
|
||||
or dbref. For using the /stop switch, a unique script dbref is
|
||||
required since whole classes of scripts often have the same name.
|
||||
"""
|
||||
key = "@scripts"
|
||||
aliases = "@listscripts"
|
||||
permissions = "cmd:listscripts"
|
||||
help_category = "System"
|
||||
|
||||
def format_script_list(self, scripts):
|
||||
"Takes a list of scripts and formats the output."
|
||||
if not scripts:
|
||||
return "<No scripts>"
|
||||
|
||||
table = [["id"], ["obj"], ["key"],["intval"],["next"],["rept"], ["db"],["typeclass"],["desc"]]
|
||||
for script in scripts:
|
||||
|
||||
table[0].append(script.id)
|
||||
if not hasattr(script, 'obj') or not script.obj:
|
||||
table[1].append("<Global>")
|
||||
else:
|
||||
table[1].append(script.obj.key)
|
||||
table[2].append(script.key)
|
||||
if not hasattr(script, 'interval') or script.interval < 0:
|
||||
table[3].append("--")
|
||||
else:
|
||||
table[3].append("%ss" % script.interval)
|
||||
next = script.time_until_next_repeat()
|
||||
if not next:
|
||||
table[4].append("--")
|
||||
else:
|
||||
table[4].append("%ss" % next)
|
||||
|
||||
if not hasattr(script, 'repeats') or not script.repeats:
|
||||
table[5].append("--")
|
||||
else:
|
||||
table[5].append("%ss" % script.repeats)
|
||||
if script.persistent:
|
||||
table[6].append("*")
|
||||
else:
|
||||
table[6].append("-")
|
||||
typeclass_path = script.typeclass_path.rsplit('.', 1)
|
||||
table[7].append("%s" % typeclass_path[-1])
|
||||
table[8].append(script.desc)
|
||||
|
||||
ftable = utils.format_table(table)
|
||||
string = ""
|
||||
for irow, row in enumerate(ftable):
|
||||
if irow == 0:
|
||||
srow = "\n" + "".join(row)
|
||||
srow = "{w%s{n" % srow.rstrip()
|
||||
else:
|
||||
srow = "\n" + "{w%s{n" % row[0] + "".join(row[1:])
|
||||
string += srow.rstrip()
|
||||
return string.strip()
|
||||
|
||||
def func(self):
|
||||
"implement method"
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
||||
string = ""
|
||||
if args:
|
||||
|
||||
# test first if this is a script match
|
||||
scripts = ScriptDB.objects.get_all_scripts(key=args)
|
||||
if not scripts:
|
||||
# try to find an object instead.
|
||||
objects = ObjectDB.objects.object_search(caller,
|
||||
args,
|
||||
global_search=True)
|
||||
if objects:
|
||||
scripts = []
|
||||
for obj in objects:
|
||||
# get all scripts on the object(s)
|
||||
scripts.extend(ScriptDB.objects.get_all_scripts_on_obj(obj))
|
||||
else:
|
||||
# we want all scripts.
|
||||
scripts = ScriptDB.objects.get_all_scripts()
|
||||
|
||||
if not scripts:
|
||||
string = "No scripts found with a key '%s', or on an object named '%s'." % (args, args)
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
if self.switches and self.switches[0] in ('stop', 'del', 'delete'):
|
||||
# we want to delete something
|
||||
if not scripts:
|
||||
string = "No scripts/objects matching '%s'. " % args
|
||||
string += "Be more specific."
|
||||
elif len(scripts) == 1:
|
||||
# we have a unique match!
|
||||
string = "Stopping script '%s'." % scripts[0].key
|
||||
scripts[0].stop()
|
||||
ScriptDB.objects.validate() #just to be sure all is synced
|
||||
else:
|
||||
# multiple matches.
|
||||
string = "Multiple script matches. Please refine your search:\n"
|
||||
string += self.format_script_list(scripts)
|
||||
elif self.switches and self.switches[0] in ("validate", "valid", "val"):
|
||||
# run validation on all found scripts
|
||||
nr_started, nr_stopped = ScriptDB.objects.validate(scripts=scripts)
|
||||
string = "Validated %s scripts. " % ScriptDB.objects.all().count()
|
||||
string += "Started %s and stopped %s scripts." % (nr_started, nr_stopped)
|
||||
else:
|
||||
# No stopping or validation. We just want to view things.
|
||||
string = self.format_script_list(scripts)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
|
||||
class CmdListObjects(MuxCommand):
|
||||
"""
|
||||
Give a summary of object types in database
|
||||
|
||||
Usage:
|
||||
@objects [<nr>]
|
||||
|
||||
Gives statictics on objects in database as well as
|
||||
a list of <nr> latest objects in database. If not
|
||||
given, <nr> defaults to 10.
|
||||
"""
|
||||
key = "@objects"
|
||||
aliases = ["@listobjects", "@listobjs"]
|
||||
permissions = "cmd:listobjects"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if self.args and self.args.isdigit():
|
||||
nlim = int(self.args)
|
||||
else:
|
||||
nlim = 10
|
||||
dbtotals = ObjectDB.objects.object_totals()
|
||||
#print dbtotals
|
||||
string = "\n{wDatase Object totals:{n"
|
||||
table = [["Count"], ["Typeclass"]]
|
||||
for path, count in dbtotals.items():
|
||||
table[0].append(count)
|
||||
table[1].append(path)
|
||||
ftable = utils.format_table(table, 3)
|
||||
for irow, row in enumerate(ftable):
|
||||
srow = "\n" + "".join(row)
|
||||
srow = srow.rstrip()
|
||||
if irow == 0:
|
||||
srow = "{w%s{n" % srow
|
||||
string += srow
|
||||
|
||||
string += "\n\n{wLast %s Objects created:{n" % nlim
|
||||
objs = list(ObjectDB.objects.all())[-nlim:]
|
||||
|
||||
table = [["Created"], ["dbref"], ["name"], ["typeclass"]]
|
||||
for i, obj in enumerate(objs):
|
||||
table[0].append(utils.datetime_format(obj.date_created))
|
||||
table[1].append(obj.dbref)
|
||||
table[2].append(obj.key)
|
||||
table[3].append(str(obj.typeclass))
|
||||
ftable = utils.format_table(table, 5)
|
||||
for irow, row in enumerate(ftable):
|
||||
srow = "\n" + "".join(row)
|
||||
srow = srow.rstrip()
|
||||
if irow == 0:
|
||||
srow = "{w%s{n" % srow
|
||||
string += srow
|
||||
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
class CmdService(MuxCommand):
|
||||
"""
|
||||
@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.
|
||||
"""
|
||||
|
||||
key = "@service"
|
||||
permissions = "cmd:service"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Implement command"
|
||||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
|
||||
if not switches or \
|
||||
switches[0] not in ["list","start","stop"]:
|
||||
caller.msg("Usage: @service/<start|stop|list> [service]")
|
||||
return
|
||||
switch = switches[0]
|
||||
|
||||
# get all services
|
||||
sessions = caller.sessions
|
||||
if not sessions:
|
||||
return
|
||||
service_collection = sessions[0].server.service_collection
|
||||
|
||||
if switch == "list":
|
||||
# Just display the list of installed services and their
|
||||
# status, then exit.
|
||||
string = "-" * 40
|
||||
string += "\nService Listing"
|
||||
|
||||
for service in service_collection.services:
|
||||
if service.running:
|
||||
status = 'Running'
|
||||
else:
|
||||
status = 'Inactive'
|
||||
string += '\n * %s (%s)' % (service.name, status)
|
||||
string += "\n" + "-" * 40
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
# Get the service to start / stop
|
||||
|
||||
try:
|
||||
service = service_collection.getServiceNamed(self.args)
|
||||
except Exception:
|
||||
string = 'Invalid service name. This command is case-sensitive. '
|
||||
string += 'See @service/list.'
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
if switch == "stop":
|
||||
# Stopping a service gracefully closes it and disconnects
|
||||
# any connections (if applicable).
|
||||
|
||||
if not service.running:
|
||||
caller.msg('That service is not currently running.')
|
||||
return
|
||||
# We don't want to kill the 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':
|
||||
string = "You can not stop Evennia TCPServer services this way."
|
||||
string += "\nTo e.g. remove a listening port, change settings file and restart."
|
||||
caller.msg(string)
|
||||
return
|
||||
#comsys.cemit_mudinfo("%s is *Stopping* the service '%s'." % (sname, service.name)) #TODO!
|
||||
service.stopService()
|
||||
return
|
||||
|
||||
if switch == "start":
|
||||
#Starts a service.
|
||||
if service.running:
|
||||
caller.msg('That service is already running.')
|
||||
return
|
||||
#comsys.cemit_mudinfo("%s is *Starting* the service '%s'." % (sname,service.name)) #TODO!
|
||||
service.startService()
|
||||
|
||||
class CmdShutdown(MuxCommand):
|
||||
|
||||
"""
|
||||
@shutdown
|
||||
|
||||
Usage:
|
||||
@shutdown [announcement]
|
||||
|
||||
Shut the game server down gracefully.
|
||||
"""
|
||||
key = "@shutdown"
|
||||
permissions = "cmd:shutdown"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Define function"
|
||||
try:
|
||||
session = self.caller.sessions[0]
|
||||
except Exception:
|
||||
return
|
||||
self.caller.msg('Shutting down server ...')
|
||||
announcement = "\nServer is being SHUT DOWN!\n"
|
||||
if self.args:
|
||||
announcement += "%s\n" % self.args
|
||||
|
||||
sessionhandler.announce_all(announcement)
|
||||
logger.log_infomsg('Server shutdown by %s.' % self.caller.name)
|
||||
|
||||
# access server through session so we don't call server directly
|
||||
# (importing it directly would restart it...)
|
||||
session.server.shutdown()
|
||||
|
||||
class CmdVersion(MuxCommand):
|
||||
"""
|
||||
@version - game version
|
||||
|
||||
Usage:
|
||||
@version
|
||||
|
||||
Display the game version info.
|
||||
"""
|
||||
|
||||
key = "@version"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show the version"
|
||||
version = utils.get_evennia_version()
|
||||
string = "-"*50 +"\n\r"
|
||||
string += " {cEvennia{n %s\n\r" % version
|
||||
string += " (Django %s, " % (django.get_version())
|
||||
string += " Twisted %s)\n\r" % (twisted.version.short())
|
||||
string += "-"*50
|
||||
self.caller.msg(string)
|
||||
|
||||
class CmdTime(MuxCommand):
|
||||
"""
|
||||
@time
|
||||
|
||||
Usage:
|
||||
@time
|
||||
|
||||
Server local time.
|
||||
"""
|
||||
key = "@time"
|
||||
aliases = "@uptime"
|
||||
permissions = "cmd:time"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show times."
|
||||
|
||||
string1 = "\nCurrent server uptime: \t"
|
||||
string1 += "{w%s{n" % (utils.time_format(gametime.uptime(format=False), 2))
|
||||
|
||||
string2 = "\nTotal server running time: \t"
|
||||
string2 += "{w%s{n" % (utils.time_format(gametime.runtime(format=False), 2))
|
||||
|
||||
string3 = "\nTotal in-game time (realtime x %g):\t" % (gametime.TIMEFACTOR)
|
||||
string3 += "{w%s{n" % (utils.time_format(gametime.gametime(format=False), 2))
|
||||
|
||||
string4 = "\nServer time stamp: \t"
|
||||
string4 += "{w%s{n" % (str(datetime.datetime.now()))
|
||||
string5 = ""
|
||||
if not utils.host_os_is('nt'):
|
||||
# os.getloadavg() is not available on Windows.
|
||||
loadavg = os.getloadavg()
|
||||
string5 += "\nServer load (per minute): \t"
|
||||
string5 += "{w%g%%{n" % (100 * loadavg[0])
|
||||
string = "%s%s%s%s%s" % (string1, string2, string3, string4, string5)
|
||||
self.caller.msg(string)
|
||||
|
||||
class CmdList(MuxCommand):
|
||||
"""
|
||||
@list - list info
|
||||
|
||||
Usage:
|
||||
@list <option>
|
||||
|
||||
Options:
|
||||
process - list processes
|
||||
objects - list objects
|
||||
scripts - list scripts
|
||||
perms - list permission keys and groups
|
||||
|
||||
Shows game related information depending
|
||||
on which argument is given.
|
||||
"""
|
||||
key = "@list"
|
||||
permissions = "cmd:list"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show list."
|
||||
|
||||
caller = self.caller
|
||||
if not self.args:
|
||||
caller.msg("Usage: @list process|objects|scripts|perms")
|
||||
return
|
||||
|
||||
string = ""
|
||||
if self.arglist[0] in ["proc","process"]:
|
||||
|
||||
# display active processes
|
||||
|
||||
if utils.host_os_is('nt'):
|
||||
string = "Feature not available on Windows."
|
||||
else:
|
||||
import resource
|
||||
loadavg = os.getloadavg()
|
||||
psize = resource.getpagesize()
|
||||
rusage = resource.getrusage(resource.RUSAGE_SELF)
|
||||
table = [["Server load (1 min):",
|
||||
"Process ID:",
|
||||
"Bytes per page:",
|
||||
"Time used:",
|
||||
"Integral memory:",
|
||||
"Max res memory:",
|
||||
"Page faults:",
|
||||
"Disk I/O:",
|
||||
"Network I/O",
|
||||
"Context switching:"
|
||||
],
|
||||
["%g%%" % (100 * loadavg[0]),
|
||||
"%10d" % os.getpid(),
|
||||
"%10d " % psize,
|
||||
"%10d" % rusage[0],
|
||||
"%10d shared" % rusage[3],
|
||||
"%10d pages" % rusage[2],
|
||||
"%10d hard" % rusage[7],
|
||||
"%10d reads" % rusage[9],
|
||||
"%10d in" % rusage[12],
|
||||
"%10d vol" % rusage[14]
|
||||
],
|
||||
["", "", "",
|
||||
"(user: %g)" % rusage[1],
|
||||
"%10d private" % rusage[4],
|
||||
"%10d bytes" % (rusage[2] * psize),
|
||||
"%10d soft" % rusage[6],
|
||||
"%10d writes" % rusage[10],
|
||||
"%10d out" % rusage[11],
|
||||
"%10d forced" % rusage[15]
|
||||
],
|
||||
["", "", "", "",
|
||||
"%10d stack" % rusage[5],
|
||||
"",
|
||||
"%10d swapouts" % rusage[8],
|
||||
"", "",
|
||||
"%10d sigs" % rusage[13]
|
||||
]
|
||||
]
|
||||
stable = []
|
||||
for col in table:
|
||||
stable.append([str(val).strip() for val in col])
|
||||
ftable = utils.format_table(stable, 5)
|
||||
string = ""
|
||||
for row in ftable:
|
||||
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
|
||||
|
||||
# string = "\n Server load (1 min) : %.2f " % loadavg[0]
|
||||
# string += "\n Process ID: %10d" % os.getpid()
|
||||
# string += "\n Bytes per page: %10d" % psize
|
||||
# string += "\n Time used: %10d, user: %g" % (rusage[0], rusage[1])
|
||||
# string += "\n Integral mem: %10d shared, %10d, private, %10d stack " % \
|
||||
# (rusage[3], rusage[4], rusage[5])
|
||||
# string += "\n Max res mem: %10d pages %10d bytes" % \
|
||||
# (rusage[2],rusage[2] * psize)
|
||||
# string += "\n Page faults: %10d hard %10d soft %10d swapouts " % \
|
||||
# (rusage[7], rusage[6], rusage[8])
|
||||
# string += "\n Disk I/O: %10d reads %10d writes " % \
|
||||
# (rusage[9], rusage[10])
|
||||
# string += "\n Network I/O: %10d in %10d out " % \
|
||||
# (rusage[12], rusage[11])
|
||||
# string += "\n Context swi: %10d vol %10d forced %10d sigs " % \
|
||||
# (rusage[14], rusage[15], rusage[13])
|
||||
|
||||
elif self.arglist[0] in ["obj", "objects"]:
|
||||
caller.execute_cmd("@objects")
|
||||
elif self.arglist[0] in ["scr", "scripts"]:
|
||||
caller.execute_cmd("@scripts")
|
||||
elif self.arglist[0] in ["perm", "perms","permissions"]:
|
||||
caller.execute_cmd("@perm/list")
|
||||
else:
|
||||
string = "'%s' is not a valid option." % self.arglist[0]
|
||||
# send info
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
#TODO - expand @ps as we add irc/imc2 support.
|
||||
class CmdPs(MuxCommand):
|
||||
"""
|
||||
@ps - list processes
|
||||
Usage
|
||||
@ps
|
||||
|
||||
Shows the process/event table.
|
||||
"""
|
||||
key = "@ps"
|
||||
permissions = "cmd:ps"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"run the function."
|
||||
|
||||
string = "Processes Scheduled:\n-- PID [time/interval] [repeats] description --"
|
||||
all_scripts = ScriptDB.objects.get_all_scripts()
|
||||
repeat_scripts = [script for script in all_scripts if script.interval]
|
||||
nrepeat_scripts = [script for script in all_scripts if script not in repeat_scripts]
|
||||
|
||||
string = "\nNon-timed scripts:"
|
||||
for script in nrepeat_scripts:
|
||||
string += "\n %i %s %s" % (script.id, script.key, script.desc)
|
||||
|
||||
string += "\n\nTimed scripts:"
|
||||
for script in repeat_scripts:
|
||||
repeats = "[inf] "
|
||||
if script.repeats:
|
||||
repeats = "[%i] " % script.repeats
|
||||
string += "\n %i %s [%d/%d] %s%s" % (script.id, script.key,
|
||||
script.time_until_next_repeat(),
|
||||
script.interval,
|
||||
repeats,
|
||||
script.desc)
|
||||
string += "\nTotals: %d interval scripts" % len(all_scripts)
|
||||
self.caller.msg(string)
|
||||
|
||||
class CmdStats(MuxCommand):
|
||||
"""
|
||||
@stats - show object stats
|
||||
|
||||
Usage:
|
||||
@stats
|
||||
|
||||
Shows stats about the database.
|
||||
"""
|
||||
|
||||
key = "@stats"
|
||||
aliases = "@db"
|
||||
permissions = "cmd:stats"
|
||||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show all stats"
|
||||
|
||||
# get counts for all typeclasses
|
||||
stats_dict = ObjectDB.objects.object_totals()
|
||||
# get all objects
|
||||
stats_allobj = ObjectDB.objects.all().count()
|
||||
# get all rooms
|
||||
stats_room = ObjectDB.objects.filter(db_location=None).count()
|
||||
# get all players
|
||||
stats_users = User.objects.all().count()
|
||||
|
||||
string = "\n{wNumber of users:{n %i" % stats_users
|
||||
string += "\n{wTotal number of objects:{n %i" % stats_allobj
|
||||
string += "\n{wNumber of rooms (location==None):{n %i" % stats_room
|
||||
string += "\n (Use @objects for detailed info)"
|
||||
self.caller.msg(string)
|
||||
|
||||
126
src/commands/default/tests.py
Normal file
126
src/commands/default/tests.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
** OBS - this is not a normal command module! **
|
||||
** You cannot import anythin in this module as a command! **
|
||||
|
||||
This is part of the Evennia unittest framework, for testing the
|
||||
stability and integrity of the codebase during updates. This module
|
||||
test the default command set. It is instantiated by the
|
||||
src/objects/tests.py module, which in turn is run by as part of the
|
||||
main test suite started with
|
||||
> python game/manage.py test.
|
||||
|
||||
"""
|
||||
|
||||
import re, time
|
||||
try:
|
||||
# this is a special optimized Django version, only available in current Django devel
|
||||
from django.utils.unittest import TestCase
|
||||
except ImportError:
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from src.utils import create
|
||||
from src.server import session, sessionhandler
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Command testing
|
||||
# ------------------------------------------------------------
|
||||
|
||||
# print all feedback from test commands (can become very verbose!)
|
||||
VERBOSE = False
|
||||
|
||||
class FakeSession(session.SessionProtocol):
|
||||
"""
|
||||
A fake session that
|
||||
implements dummy versions of the real thing; this is needed to
|
||||
mimic a logged-in player.
|
||||
"""
|
||||
def connectionMade(self):
|
||||
self.prep_session()
|
||||
sessionhandler.add_session(self)
|
||||
def prep_session(self):
|
||||
self.server, self.address = None, "0.0.0.0"
|
||||
self.name, self.uid = None, None
|
||||
self.logged_in = False
|
||||
self.encoding = "utf-8"
|
||||
self.cmd_last, self.cmd_last_visible, self.cmd_conn_time = time.time(), time.time(), time.time()
|
||||
self.cmd_total = 0
|
||||
def disconnectClient(self):
|
||||
pass
|
||||
def lineReceived(self, raw_string):
|
||||
pass
|
||||
def msg(self, message, markup=True):
|
||||
if VERBOSE:
|
||||
print message
|
||||
|
||||
class CommandTest(TestCase):
|
||||
"""
|
||||
Sets up the basics of testing the default commands and the generic things
|
||||
that should always be present in a command.
|
||||
|
||||
Inherit new tests from this.
|
||||
"""
|
||||
def setUp(self):
|
||||
"sets up the testing environment"
|
||||
self.room1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1")
|
||||
self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2")
|
||||
|
||||
# create a faux player/character for testing.
|
||||
self.char1 = create.create_player("TestingPlayer", "testplayer@test.com", "testpassword", location=self.room1)
|
||||
self.char1.player.user.is_superuser = True
|
||||
sess = FakeSession()
|
||||
sess.connectionMade()
|
||||
sess.login(self.char1.player)
|
||||
|
||||
self.char2 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="char2", location=self.room1)
|
||||
self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=self.room1)
|
||||
self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1)
|
||||
self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1)
|
||||
self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2)
|
||||
|
||||
def get_cmd(self, cmd_class, argument_string=""):
|
||||
"""
|
||||
Obtain a cmd instance from a class and an input string
|
||||
Note: This does not make use of the cmdhandler functionality.
|
||||
"""
|
||||
cmd = cmd_class()
|
||||
cmd.caller = self.char1
|
||||
cmd.cmdstring = cmd_class.key
|
||||
cmd.args = argument_string
|
||||
cmd.cmdset = None
|
||||
cmd.obj = self.char1
|
||||
return cmd
|
||||
|
||||
def execute_cmd(self, raw_string):
|
||||
"""
|
||||
Creates the command through faking a normal command call;
|
||||
This also mangles the input in various ways to test if the command
|
||||
will be fooled.
|
||||
"""
|
||||
test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it
|
||||
test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call
|
||||
test3 = "%s %s" % (raw_string, raw_string) # multiple calls
|
||||
self.char1.execute_cmd(test1)
|
||||
self.char1.execute_cmd(test2)
|
||||
self.char1.execute_cmd(test3)
|
||||
self.char1.execute_cmd(raw_string)
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Default set Command testing
|
||||
#------------------------------------------------------------
|
||||
|
||||
class TestHome(CommandTest):
|
||||
def test_call(self):
|
||||
self.char1.home = self.room2
|
||||
self.execute_cmd("home")
|
||||
self.assertEqual(self.char1.location, self.room2)
|
||||
class TestLook(CommandTest):
|
||||
def test_call(self):
|
||||
self.execute_cmd("look here")
|
||||
class TestPassword(CommandTest):
|
||||
def test_call(self):
|
||||
self.execute_cmd("@password testpassword = newpassword")
|
||||
class TestNick(CommandTest):
|
||||
def test_call(self):
|
||||
self.execute_cmd("nickname testalias = testaliasedstring")
|
||||
self.assertEquals("testaliasedstring", self.char1.nicks.get("testalias", None))
|
||||
0
src/commands/default/unimplemented/__init__.py
Normal file
0
src/commands/default/unimplemented/__init__.py
Normal file
211
src/commands/default/unimplemented/imc2.py
Normal file
211
src/commands/default/unimplemented/imc2.py
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
"""
|
||||
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")
|
||||
|
||||
130
src/commands/default/unimplemented/irc.py
Normal file
130
src/commands/default/unimplemented/irc.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
"""
|
||||
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")
|
||||
263
src/commands/default/unimplemented/objmanip.py
Normal file
263
src/commands/default/unimplemented/objmanip.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
"""
|
||||
These commands typically are to do with building or modifying Objects.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from src.permissions.permissions import has_perm, has_perm_string
|
||||
from src.objects.models import ObjectDB, ObjAttribute
|
||||
from game.gamesrc.commands.default.muxcommand import MuxCommand
|
||||
from src.utils import create, utils
|
||||
|
||||
|
||||
##
|
||||
## def cmd_chown(command):
|
||||
## """
|
||||
## @chown - change ownerships
|
||||
|
||||
## Usage:
|
||||
## @chown <Object> = <NewOwner>
|
||||
|
||||
## Changes the ownership of an object. The new owner specified must be a
|
||||
## player object.
|
||||
## """
|
||||
## caller = command.caller
|
||||
|
||||
## if not command.command_argument:
|
||||
## caller.msg("Usage: @chown <object> = <newowner>")
|
||||
## return
|
||||
|
||||
## eq_args = command.command_argument.split('=', 1)
|
||||
## target_name = eq_args[0]
|
||||
## owner_name = eq_args[1]
|
||||
|
||||
## if len(target_name) == 0:
|
||||
## caller.msg("Change the ownership of what?")
|
||||
## return
|
||||
|
||||
## if len(eq_args) > 1:
|
||||
## target_obj = caller.search_for_object(target_name)
|
||||
## # Use search_for_object to handle duplicate/nonexistant results.
|
||||
## if not target_obj:
|
||||
## return
|
||||
|
||||
## if not caller.controls_other(target_obj) and not caller.has_perm("objects.admin_ownership"):
|
||||
## caller.msg(defines_global.NOCONTROL_MSG)
|
||||
## return
|
||||
|
||||
## owner_obj = caller.search_for_object(owner_name)
|
||||
## # Use search_for_object to handle duplicate/nonexistant results.
|
||||
## if not owner_obj:
|
||||
## return
|
||||
## if not owner_obj.is_player():
|
||||
## caller.msg("Only players may own objects.")
|
||||
## return
|
||||
## if target_obj.is_player():
|
||||
## caller.msg("You may not change the ownership of player objects.")
|
||||
## return
|
||||
|
||||
## target_obj.set_owner(owner_obj)
|
||||
## caller.msg("%s now owns %s." % (owner_obj, target_obj))
|
||||
## else:
|
||||
## # We haven't provided a target.
|
||||
## caller.msg("Who should be the new owner of the object?")
|
||||
## return
|
||||
## GLOBAL_CMD_TABLE.add_command("@chown", cmd_chown, priv_tuple=("objects.modify_attributes",
|
||||
## "objects.admin_ownership"),
|
||||
## help_category="Building" )
|
||||
|
||||
|
||||
|
||||
#NOT VALID IN NEW SYSTEM!
|
||||
## def cmd_lock(command):
|
||||
## """
|
||||
## @lock - limit use of objects
|
||||
|
||||
## Usage:
|
||||
## @lock[/switch] <obj> [:type] [= <key>[,key2,key3,...]]
|
||||
|
||||
## Switches:
|
||||
## add - add a lock (default) from object
|
||||
## del - remove a lock from object
|
||||
## list - view all locks on object (default)
|
||||
## type:
|
||||
## DefaultLock - the default lock type (default)
|
||||
## UseLock - prevents usage of objects' commands
|
||||
## EnterLock - blocking objects from entering the object
|
||||
|
||||
## Locks an object for everyone except those matching the keys.
|
||||
## The keys can be of the following types (and searched in this order):
|
||||
## - a user #dbref (#2, #45 etc)
|
||||
## - a Group name (Builder, Immortal etc, case sensitive)
|
||||
## - a Permission string (genperms.get, etc)
|
||||
## - a Function():return_value pair. (ex: alliance():Red). The
|
||||
## function() is called on the locked object (if it exists) and
|
||||
## if its return value matches the Key is passed. If no
|
||||
## return_value is given, matches against True.
|
||||
## - an Attribute:return_value pair (ex: key:yellow_key). The
|
||||
## Attribute is the name of an attribute defined on the locked
|
||||
## object. If this attribute has a value matching return_value,
|
||||
## the lock is passed. If no return_value is given,
|
||||
## attributes will be searched, requiring a True
|
||||
## value.
|
||||
|
||||
## If no keys at all are given, the object is locked for everyone.
|
||||
## When the lock blocks a user, you may customize which error is given by
|
||||
## storing error messages in an attribute. For DefaultLocks, UseLocks and
|
||||
## EnterLocks, these attributes are called lock_msg, use_lock_msg and
|
||||
## enter_lock_msg respectively.
|
||||
|
||||
## [[lock_types]]
|
||||
|
||||
## Lock types:
|
||||
|
||||
## Name: Affects: Effect:
|
||||
## -----------------------------------------------------------------------
|
||||
## DefaultLock: Exits: controls who may traverse the exit to
|
||||
## its destination.
|
||||
## Rooms: controls whether the player sees a failure
|
||||
## message after the room description when
|
||||
## looking at the room.
|
||||
## Players/Things: controls who may 'get' the object.
|
||||
|
||||
## UseLock: All but Exits: controls who may use commands defined on
|
||||
## the locked object.
|
||||
|
||||
## EnterLock: Players/Things: controls who may enter/teleport into
|
||||
## the object.
|
||||
## VisibleLock: Players/Things: controls if the object is visible to
|
||||
## someone using the look command.
|
||||
|
||||
## Fail messages echoed to the player are stored in the attributes 'lock_msg',
|
||||
## 'use_lock_msg', 'enter_lock_msg' and 'visible_lock_msg' on the locked object
|
||||
## in question. If no such message is stored, a default will be used (or none at
|
||||
## all in some cases).
|
||||
## """
|
||||
|
||||
## caller = command.caller
|
||||
## arg = command.command_argument
|
||||
## switches = command.command_switches
|
||||
|
||||
## if not arg:
|
||||
## caller.msg("Usage: @lock[/switch] <obj> [:type] [= <key>[,key2,key3,...]]")
|
||||
## return
|
||||
## keys = ""
|
||||
## #deal with all possible arguments.
|
||||
## try:
|
||||
## lside, keys = arg.split("=",1)
|
||||
## except ValueError:
|
||||
## lside = arg
|
||||
## lside, keys = lside.strip(), keys.strip()
|
||||
## try:
|
||||
## obj_name, ltype = lside.split(":",1)
|
||||
## except:
|
||||
## obj_name = lside
|
||||
## ltype = "DefaultLock"
|
||||
## obj_name, ltype = obj_name.strip(), ltype.strip()
|
||||
|
||||
## if ltype not in ["DefaultLock","UseLock","EnterLock","VisibleLock"]:
|
||||
## caller.msg("Lock type '%s' not recognized." % ltype)
|
||||
## return
|
||||
|
||||
## obj = caller.search_for_object(obj_name)
|
||||
## if not obj:
|
||||
## return
|
||||
|
||||
## obj_locks = obj.LOCKS
|
||||
|
||||
## if "list" in switches:
|
||||
## if not obj_locks:
|
||||
## s = "There are no locks on %s." % obj.name
|
||||
## else:
|
||||
## s = "Locks on %s:" % obj.name
|
||||
## s += obj_locks.show()
|
||||
## caller.msg(s)
|
||||
## return
|
||||
|
||||
## # we are trying to change things. Check permissions.
|
||||
## if not caller.controls_other(obj):
|
||||
## caller.msg(defines_global.NOCONTROL_MSG)
|
||||
## return
|
||||
|
||||
## if "del" in switches:
|
||||
## # clear a lock
|
||||
## if obj_locks:
|
||||
## if not obj_locks.has_type(ltype):
|
||||
## caller.msg("No %s set on this object." % ltype)
|
||||
## else:
|
||||
## obj_locks.del_type(ltype)
|
||||
## obj.LOCKS = obj_locks
|
||||
## caller.msg("Cleared lock %s on %s." % (ltype, obj.name))
|
||||
## else:
|
||||
## caller.msg("No %s set on this object." % ltype)
|
||||
## return
|
||||
## else:
|
||||
## #try to add a lock
|
||||
## if not obj_locks:
|
||||
## obj_locks = locks.Locks()
|
||||
## if not keys:
|
||||
## #add an impassable lock
|
||||
## obj_locks.add_type(ltype, locks.Key())
|
||||
## caller.msg("Added impassable '%s' lock to %s." % (ltype, obj.name))
|
||||
## else:
|
||||
## keys = [k.strip() for k in keys.split(",")]
|
||||
## obj_keys, group_keys, perm_keys = [], [], []
|
||||
## func_keys, attr_keys = [], []
|
||||
## allgroups = [g.name for g in Group.objects.all()]
|
||||
## allperms = ["%s.%s" % (p.content_type.app_label, p.codename)
|
||||
## for p in Permission.objects.all()]
|
||||
## for key in keys:
|
||||
## #differentiate different type of keys
|
||||
## if Object.objects.is_dbref(key):
|
||||
## # this is an object key, like #2, #6 etc
|
||||
## obj_keys.append(key)
|
||||
## elif key in allgroups:
|
||||
## # a group key
|
||||
## group_keys.append(key)
|
||||
## elif key in allperms:
|
||||
## # a permission string
|
||||
## perm_keys.append(key)
|
||||
## elif '()' in key:
|
||||
## # a function()[:returnvalue] tuple.
|
||||
## # Check if we also request a return value
|
||||
## funcname, rvalue = [k.strip() for k in key.split('()',1)]
|
||||
## if not funcname:
|
||||
## funcname = "lock_func"
|
||||
## rvalue = rvalue.lstrip(':')
|
||||
## if not rvalue:
|
||||
## rvalue = True
|
||||
## # pack for later adding.
|
||||
## func_keys.append((funcname, rvalue))
|
||||
## elif ':' in key:
|
||||
## # an attribute[:returnvalue] tuple.
|
||||
## attr_name, rvalue = [k.strip() for k in key.split(':',1)]
|
||||
## # pack for later adding
|
||||
## attr_keys.append((attr_name, rvalue))
|
||||
## else:
|
||||
## caller.msg("Key '%s' is not recognized as a valid dbref, group or permission." % key)
|
||||
## return
|
||||
## # Create actual key objects from the respective lists
|
||||
## keys = []
|
||||
## if obj_keys:
|
||||
## keys.append(locks.ObjKey(obj_keys))
|
||||
## if group_keys:
|
||||
## keys.append(locks.GroupKey(group_keys))
|
||||
## if perm_keys:
|
||||
## keys.append(locks.PermKey(perm_keys))
|
||||
## if func_keys:
|
||||
## keys.append(locks.FuncKey(func_keys, obj.dbref))
|
||||
## if attr_keys:
|
||||
## keys.append(locks.AttrKey(attr_keys))
|
||||
|
||||
## #store the keys in the lock
|
||||
## obj_locks.add_type(ltype, keys)
|
||||
## kstring = " "
|
||||
## for key in keys:
|
||||
## kstring += " %s," % key
|
||||
## kstring = kstring[:-1]
|
||||
## caller.msg("Added lock '%s' to %s with keys%s." % (ltype, obj.name, kstring))
|
||||
## obj.LOCKS = obj_locks
|
||||
## GLOBAL_CMD_TABLE.add_command("@lock", cmd_lock, priv_tuple=("objects.create",), help_category="Building")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
235
src/commands/default/unimplemented/search.py
Normal file
235
src/commands/default/unimplemented/search.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
"""
|
||||
Implementation of the @search command that resembles MUX2.
|
||||
"""
|
||||
from django.db.models import Q
|
||||
#from src.objects.models import Object
|
||||
from src.utils import OBJECT as 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.msg("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.msg("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 = []
|
||||
# this bits gotta get totally redone
|
||||
for obj in search_query:
|
||||
thing_list.append(obj)
|
||||
|
||||
# Render each section for different object types
|
||||
if thing_list:
|
||||
source_object.msg("\n\rTHINGS:")
|
||||
for thing in thing_list:
|
||||
source_object.msg(thing.name)
|
||||
|
||||
if exit_list:
|
||||
source_object.msg("\n\rEXITS:")
|
||||
for exit in exit_list:
|
||||
source_object.msg(exit.name)
|
||||
|
||||
if room_list:
|
||||
source_object.msg("\n\rROOMS:")
|
||||
for room in room_list:
|
||||
source_object.msg(room.name)
|
||||
|
||||
if player_list:
|
||||
source_object.msg("\n\rPLAYER:")
|
||||
for player in player_list:
|
||||
source_object.msg(player.name)
|
||||
|
||||
# Show the total counts by type
|
||||
source_object.msg("\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.msg("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.msg("To be implemented...")
|
||||
return None
|
||||
elif search_type == "flags":
|
||||
flag_list = search_restriction.split()
|
||||
#source_object.msg("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.msg("@search class=restriction")
|
||||
#source_object.msg("eq_split: %s" % eq_split)
|
||||
#source_object.msg("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.msg("@search player")
|
||||
#source_object.msg(first_check_split)
|
||||
search_player = first_check_split[0]
|
||||
else:
|
||||
#source_object.msg("@search player class=restriction")
|
||||
#source_object.msg(first_check_split)
|
||||
search_player = first_check_split[0]
|
||||
eq_split = first_check_split[1].split('=', 1)
|
||||
search_type = eq_split[0]
|
||||
#source_object.msg("eq_split: %s" % eq_split)
|
||||
restriction_split = eq_split[1].split(',')
|
||||
search_restriction = restriction_split[0]
|
||||
#source_object.msg("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.msg("search_player: %s" % search_player)
|
||||
#source_object.msg("search_type: %s" % search_type)
|
||||
#source_object.msg("search_restriction: %s" % search_restriction)
|
||||
#source_object.msg("search_lowdb: %s" % search_low_dbnum)
|
||||
#source_object.msg("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")
|
||||
297
src/commands/default/unloggedin.py
Normal file
297
src/commands/default/unloggedin.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
"""
|
||||
Commands that are available from the connect screen.
|
||||
"""
|
||||
import traceback
|
||||
#from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from src.players.models import PlayerDB
|
||||
from src.objects.models import ObjectDB
|
||||
from src.config.models import ConfigValue
|
||||
from src.comms.models import Channel
|
||||
from src.utils import create, logger, utils
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
class CmdConnect(MuxCommand):
|
||||
"""
|
||||
Connect to the game.
|
||||
|
||||
Usage (at login screen):
|
||||
connect <email> <password>
|
||||
|
||||
Use the create command to first create an account before logging in.
|
||||
"""
|
||||
key = "connect"
|
||||
aliases = ["conn", "con", "co"]
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Uses the Django admin api. Note that unlogged-in commands
|
||||
have a unique position in that their func() receives
|
||||
a session object instead of a source_object like all
|
||||
other types of logged-in commands (this is because
|
||||
there is no object yet before the player has logged in)
|
||||
"""
|
||||
|
||||
session = self.caller
|
||||
arglist = self.arglist
|
||||
|
||||
if not arglist or len(arglist) < 2:
|
||||
session.msg("\n\r Usage (without <>): connect <email> <password>")
|
||||
return
|
||||
email = arglist[0]
|
||||
password = arglist[1]
|
||||
|
||||
# Match an email address to an account.
|
||||
player = PlayerDB.objects.get_player_from_email(email)
|
||||
# No playername match
|
||||
if not player:
|
||||
string = "The email '%s' does not match any accounts." % email
|
||||
string += "\n\r\n\rIf you are new you should first create a new account "
|
||||
string += "using the 'create' command."
|
||||
session.msg(string)
|
||||
return
|
||||
# We have at least one result, so we can check the password.
|
||||
if not player.user.check_password(password):
|
||||
session.msg("Incorrect password.")
|
||||
return
|
||||
|
||||
# We are logging in, get/setup the player object controlled by player
|
||||
|
||||
character = player.character
|
||||
if not character:
|
||||
# Create a new character object to tie the player to. This should
|
||||
# usually not be needed unless the old character object was manually
|
||||
# deleted.
|
||||
default_home_id = ConfigValue.objects.conf(db_key="default_home")
|
||||
default_home = ObjectDB.objects.get_id(default_home_id)
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
character = create.create_object(typeclass=typeclass,
|
||||
key=player.name,
|
||||
location=default_home,
|
||||
home=default_home,
|
||||
player=player)
|
||||
|
||||
character.db.FIRST_LOGIN = "True"
|
||||
|
||||
# Getting ready to log the player in.
|
||||
|
||||
# Check if this is the first time the
|
||||
# *player* connects
|
||||
if player.db.FIRST_LOGIN:
|
||||
player.at_first_login()
|
||||
del player.db.FIRST_LOGIN
|
||||
|
||||
# check if this is the first time the *character*
|
||||
# character (needs not be the first time the player
|
||||
# does so, e.g. if the player has several characters)
|
||||
if character.db.FIRST_LOGIN:
|
||||
character.at_first_login()
|
||||
del character.db.FIRST_LOGIN
|
||||
|
||||
# actually do the login, calling
|
||||
# customization hooks before and after.
|
||||
player.at_pre_login()
|
||||
character.at_pre_login()
|
||||
|
||||
session.login(player)
|
||||
|
||||
player.at_post_login()
|
||||
character.at_post_login()
|
||||
# run look
|
||||
#print "character:", character, character.scripts.all(), character.cmdset.current
|
||||
character.execute_cmd('look')
|
||||
|
||||
class CmdCreate(MuxCommand):
|
||||
"""
|
||||
Create a new account.
|
||||
|
||||
Usage (at login screen):
|
||||
create \"playername\" <email> <password>
|
||||
|
||||
This creates a new player account.
|
||||
|
||||
"""
|
||||
key = "create"
|
||||
aliases = ["cre", "cr"]
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
The parser must handle the multiple-word player
|
||||
name enclosed in quotes:
|
||||
connect "Long name with many words" my@myserv.com mypassw
|
||||
"""
|
||||
super(CmdCreate, self).parse()
|
||||
|
||||
self.playerinfo = []
|
||||
if len(self.arglist) < 3:
|
||||
return
|
||||
if len(self.arglist) > 3:
|
||||
# this means we have a multi_word playername. pop from the back.
|
||||
password = self.arglist.pop()
|
||||
email = self.arglist.pop()
|
||||
# what remains is the playername.
|
||||
playername = " ".join(self.arglist)
|
||||
else:
|
||||
playername, email, password = self.arglist
|
||||
|
||||
playername = playername.replace('"', '') # remove "
|
||||
playername = playername.replace("'", "")
|
||||
self.playerinfo = (playername, email, password)
|
||||
|
||||
def func(self):
|
||||
"Do checks and create account"
|
||||
|
||||
session = self.caller
|
||||
|
||||
try:
|
||||
playername, email, password = self.playerinfo
|
||||
except ValueError:
|
||||
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
|
||||
session.msg(string)
|
||||
return
|
||||
if not playername:
|
||||
# entered an empty string
|
||||
session.msg("\n\r You have to supply a longer playername, surrounded by quotes.")
|
||||
return
|
||||
if not email or not password:
|
||||
session.msg("\n\r You have to supply an e-mail address followed by a password." )
|
||||
return
|
||||
|
||||
if not utils.validate_email_address(email):
|
||||
# check so the email at least looks ok.
|
||||
session.msg("'%s' is not a valid e-mail address." % email)
|
||||
return
|
||||
|
||||
# Run sanity and security checks
|
||||
|
||||
if PlayerDB.objects.get_player_from_name(playername) or User.objects.filter(username=playername):
|
||||
# player already exists
|
||||
session.msg("Sorry, there is already a player with the name '%s'." % playername)
|
||||
elif PlayerDB.objects.get_player_from_email(email):
|
||||
# email already set on a player
|
||||
session.msg("Sorry, there is already a player with that email address.")
|
||||
elif len(password) < 3:
|
||||
# too short password
|
||||
string = "Your password must be at least 3 characters or longer."
|
||||
string += "\n\rFor best security, make it at least 8 characters long, "
|
||||
string += "avoid making it a real word and mix numbers into it."
|
||||
session.msg(string)
|
||||
else:
|
||||
# everything's ok. Create the new player account
|
||||
try:
|
||||
default_home_id = ConfigValue.objects.conf(db_key="default_home")
|
||||
default_home = ObjectDB.objects.get_id(default_home_id)
|
||||
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
|
||||
new_character = create.create_player(playername, email, password,
|
||||
permissions=permissions,
|
||||
location=default_home,
|
||||
typeclass=typeclass,
|
||||
home=default_home)
|
||||
|
||||
# set a default description
|
||||
new_character.db.desc = "This is a Player."
|
||||
|
||||
new_character.db.FIRST_LOGIN = True
|
||||
new_player = new_character.player
|
||||
new_player.db.FIRST_LOGIN = True
|
||||
|
||||
# join the new player to the public channel
|
||||
pchanneldef = settings.CHANNEL_PUBLIC
|
||||
if pchanneldef:
|
||||
pchannel = Channel.objects.get_channel(pchanneldef[0])
|
||||
if not pchannel.connect_to(new_player):
|
||||
string = "New player '%s' could not connect to public channel!" % new_player.key
|
||||
logger.log_errmsg(string)
|
||||
|
||||
string = "A new account '%s' was created with the email address %s. Welcome!"
|
||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||
session.msg(string % (playername, email, email))
|
||||
except Exception:
|
||||
# we have to handle traceback ourselves at this point, if
|
||||
# we don't, errors will give no feedback.
|
||||
string = "%s\nThis is a bug. Please e-mail an admin if the problem persists."
|
||||
session.msg(string % (traceback.format_exc()))
|
||||
logger.log_errmsg(traceback.format_exc())
|
||||
|
||||
class CmdQuit(MuxCommand):
|
||||
"""
|
||||
We maintain a different version of the quit command
|
||||
here for unconnected players for the sake of simplicity. The logged in
|
||||
version is a bit more complicated.
|
||||
"""
|
||||
key = "quit"
|
||||
aliases = ["q", "qu"]
|
||||
|
||||
def func(self):
|
||||
"Simply close the connection."
|
||||
session = self.caller
|
||||
session.msg("Good bye! Disconnecting ...")
|
||||
session.handle_close()
|
||||
|
||||
class CmdUnconnectedLook(MuxCommand):
|
||||
"""
|
||||
This is an unconnected version of the look command for simplicity.
|
||||
All it does is re-show the connect screen.
|
||||
"""
|
||||
key = "look"
|
||||
aliases = "l"
|
||||
|
||||
def func(self):
|
||||
"Show the connect screen."
|
||||
try:
|
||||
self.caller.game_connect_screen()
|
||||
except Exception:
|
||||
self.caller.msg("Connect screen not found. Enter 'help' for aid.")
|
||||
|
||||
class CmdUnconnectedHelp(MuxCommand):
|
||||
"""
|
||||
This is an unconnected version of the help command,
|
||||
for simplicity. It shows a pane or info.
|
||||
"""
|
||||
key = "help"
|
||||
aliases = ["h", "?"]
|
||||
|
||||
def func(self):
|
||||
"Shows help"
|
||||
|
||||
string = \
|
||||
"""Welcome to Evennia!
|
||||
|
||||
Commands available at this point:
|
||||
create - create a new account
|
||||
connect - login with an existing account
|
||||
look - re-show the connect screen
|
||||
help - this help
|
||||
quit - leave
|
||||
|
||||
To login to the system, you need to do one of the following:
|
||||
|
||||
1) If you have no previous account, you need to use the 'create'
|
||||
command followed by your desired character name (in quotes), your
|
||||
e-mail address and finally a password of your choice. Like
|
||||
this:
|
||||
|
||||
> create "Anna the Barbarian" anna@myemail.com tuK3221mP
|
||||
|
||||
It's always a good idea (not only here, but everywhere on the net)
|
||||
to not use a regular word for your password. Make it longer than
|
||||
3 characters (ideally 6 or more) and mix numbers and capitalization
|
||||
into it. Now proceed to 2).
|
||||
|
||||
2) If you have an account already, either because you just created
|
||||
one in 1) above, or you are returning, use the 'connect' command
|
||||
followed by the e-mail and password you previously set.
|
||||
Example:
|
||||
|
||||
> connect anna@myemail.com tuK3221mP
|
||||
|
||||
This should log you in. Run 'help' again once you're logged in
|
||||
to get more aid. Hope you enjoy your stay!
|
||||
|
||||
You can use the 'look' command if you want to see the connect screen again.
|
||||
"""
|
||||
self.caller.msg(string)
|
||||
227
src/commands/default/utils.py
Normal file
227
src/commands/default/utils.py
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
"""
|
||||
This defines some test commands for use while testing the MUD and its components.
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import IntegrityError
|
||||
from src.comms.models import Msg
|
||||
from src.permissions import permissions
|
||||
from src.utils import create, debug, utils
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
|
||||
from src.commands import cmdsethandler
|
||||
|
||||
|
||||
# Test permissions
|
||||
|
||||
class CmdTest(MuxCommand):
|
||||
"""
|
||||
test the command system
|
||||
|
||||
Usage:
|
||||
@test <any argument or switch>
|
||||
|
||||
This command will echo back all argument or switches
|
||||
given to it, showcasing the muxcommand style.
|
||||
"""
|
||||
|
||||
key = "@test"
|
||||
aliases = ["@te", "@test all"]
|
||||
help_category = "Testing"
|
||||
permissions = "cmd:Immortals" #Wizards
|
||||
|
||||
# the muxcommand class itself handles the display
|
||||
# so we just defer to it by not adding any function.
|
||||
|
||||
def func(self):
|
||||
|
||||
def test():
|
||||
li = []
|
||||
for l in range(10000):
|
||||
li.append(l)
|
||||
self.caller.msg(li[-1])
|
||||
return "This is the return text"
|
||||
#print 1/0
|
||||
def succ(f):
|
||||
self.caller.msg("This is called after successful completion. Return value: %s" % f)
|
||||
def err(e):
|
||||
self.caller.msg("An error was encountered... %s" % e)
|
||||
|
||||
#self.caller.msg("printed before call to sync run ...")
|
||||
#test()
|
||||
#self.caller.msg("after after call to sync run...")
|
||||
|
||||
self.caller.msg("printed before call to async run ...")
|
||||
utils.run_async(test, at_return=succ, at_err=err)
|
||||
self.caller.msg("printed after call to async run ...")
|
||||
|
||||
#cmdsetname = "game.gamesrc.commands.default.cmdset_default.DefaultCmdSet"
|
||||
#self.caller.msg(cmdsethandler.CACHED_CMDSETS)
|
||||
#cmdsethandler.import_cmdset(cmdsetname, self, self)
|
||||
#self.caller.msg("Imported %s" % cmdsetname)
|
||||
#self.caller.msg(cmdsethandler.CACHED_CMDSETS)
|
||||
|
||||
|
||||
|
||||
|
||||
class CmdTestPerms(MuxCommand):
|
||||
"""
|
||||
Test command - test permissions
|
||||
|
||||
Usage:
|
||||
@testperm [[lockstring] [=permstring]]
|
||||
|
||||
With no arguments, runs a sequence of tests for the
|
||||
permission system using the calling player's permissions.
|
||||
|
||||
If <lockstring> is given, match caller's permissions
|
||||
against these locks. If also <permstring> is given,
|
||||
match this against the given locks instead.
|
||||
|
||||
"""
|
||||
key = "@testperm"
|
||||
permissions = "cmd:Immortals Wizards"
|
||||
help_category = "Testing"
|
||||
def func(self):
|
||||
"""
|
||||
Run tests
|
||||
"""
|
||||
caller = self.caller
|
||||
|
||||
if caller.user.is_superuser:
|
||||
caller.msg("You are a superuser. Permission tests are pointless.")
|
||||
return
|
||||
# create a test object
|
||||
obj = create.create_object(None, "accessed_object") # this will use default typeclass
|
||||
obj_id = obj.id
|
||||
caller.msg("obj_attr: %s" % obj.attr("testattr"))
|
||||
|
||||
# perms = ["has_permission", "has permission", "skey:has_permission",
|
||||
# "has_id(%s)" % obj_id, "has_attr(testattr)",
|
||||
# "has_attr(testattr, testattr_value)"]
|
||||
|
||||
# test setting permissions
|
||||
uprofile = caller.user.get_profile()
|
||||
# do testing
|
||||
caller.msg("----------------")
|
||||
|
||||
permissions.set_perm(obj, "has_permission")
|
||||
permissions.add_perm(obj, "skey:has_permission")
|
||||
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
|
||||
caller.msg("normal permtest: %s" % permissions.has_perm(uprofile, obj))
|
||||
caller.msg("skey permtest: %s" % permissions.has_perm(uprofile, obj, 'skey'))
|
||||
|
||||
permissions.set_perm(uprofile, "has_permission")
|
||||
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
|
||||
caller.msg("normal permtest: %s" % permissions.has_perm(uprofile, obj))
|
||||
caller.msg("skey permtest: %s" % permissions.has_perm(uprofile, obj, 'skey'))
|
||||
|
||||
# function tests
|
||||
permissions.set_perm(obj, "has_id(%s)" % (uprofile.id))
|
||||
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
|
||||
caller.msg("functest: %s" % permissions.has_perm(uprofile, obj))
|
||||
|
||||
uprofile.attr("testattr", "testattr_value")
|
||||
permissions.set_perm(obj, "has_attr(testattr, testattr_value)")
|
||||
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
|
||||
caller.msg("functest: %s" % permissions.has_perm(uprofile, obj))
|
||||
|
||||
# cleanup of test permissions
|
||||
permissions.del_perm(uprofile, "has_permission")
|
||||
caller.msg(" cleanup: keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
|
||||
obj.delete()
|
||||
uprofile.attr("testattr", delete=True)
|
||||
|
||||
|
||||
# # Add/remove states (removed; not valid.)
|
||||
|
||||
# EXAMPLE_STATE="game.gamesrc.commands.examples.example.EXAMPLESTATE"
|
||||
|
||||
# class CmdTestState(MuxCommand):
|
||||
# """
|
||||
# Test command - add a state.
|
||||
|
||||
# Usage:
|
||||
# @teststate[/switch] [<python path to state instance>]
|
||||
# Switches:
|
||||
# add - add a state
|
||||
# clear - remove all added states.
|
||||
# list - view current state stack
|
||||
# reload - reload current state stack
|
||||
|
||||
# If no python path is given, an example state will be added.
|
||||
# You will know it worked if you can use the commands '@testcommand'
|
||||
# and 'smile'.
|
||||
# """
|
||||
|
||||
# key = "@teststate"
|
||||
# alias = "@testingstate"
|
||||
# permissions = "cmd:Immortals Wizards"
|
||||
|
||||
# def func(self):
|
||||
# """
|
||||
# inp is the dict returned from MuxCommand's parser.
|
||||
# """
|
||||
# caller = self.caller
|
||||
# switches = self.switches
|
||||
|
||||
# if not switches or switches[0] not in ["add", "clear", "list", "reload"]:
|
||||
# string = "Usage: @teststate[/add|clear|list|reload] [<python path>]"
|
||||
# caller.msg(string)
|
||||
# elif "clear" in switches:
|
||||
# caller.cmdset.clear()
|
||||
# caller.msg("All cmdset cleared.")
|
||||
# return
|
||||
# elif "list" in switches:
|
||||
# string = "%s" % caller.cmdset
|
||||
# caller.msg(string)
|
||||
# elif "reload" in switches:
|
||||
# caller.cmdset.load()
|
||||
# caller.msg("Cmdset reloaded.")
|
||||
# else: #add
|
||||
# arg = inp["raw"]
|
||||
# if not arg:
|
||||
# arg = EXAMPLE_STATE
|
||||
# caller.cmdset.add(arg)
|
||||
# string = "Added state '%s'." % caller.cmdset.state.key
|
||||
# caller.msg(string)
|
||||
|
||||
class TestCom(MuxCommand):
|
||||
"""
|
||||
Test the command system
|
||||
|
||||
Usage:
|
||||
@testcom/create/list [channel]
|
||||
"""
|
||||
key = "@testcom"
|
||||
permissions = "cmd:Immortals Wizards"
|
||||
help_category = "Testing"
|
||||
def func(self):
|
||||
"Run the test program"
|
||||
caller = self.caller
|
||||
|
||||
if 'create' in self.switches:
|
||||
if self.args:
|
||||
chankey = self.args
|
||||
try:
|
||||
channel = create.create_channel(chankey)
|
||||
except IntegrityError:
|
||||
caller.msg("Channel '%s' already exists." % chankey)
|
||||
return
|
||||
channel.connect_to(caller)
|
||||
caller.msg("Created new channel %s" % chankey)
|
||||
msgobj = create.create_message(caller.player,
|
||||
"First post to new channel!")
|
||||
channel.msg(msgobj)
|
||||
|
||||
return
|
||||
elif 'list' in self.switches:
|
||||
msgresults = Msg.objects.get_messages_by_sender(caller)
|
||||
string = "\n".join(["%s %s: %s" % (msg.date_sent,
|
||||
[str(chan.key) for chan in msg.channels.all()],
|
||||
msg.message)
|
||||
for msg in msgresults])
|
||||
caller.msg(string)
|
||||
return
|
||||
caller.msg("Usage: @testcom/create channel")
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"""
|
||||
Unit testing of the 'objects' Evennia component.
|
||||
|
||||
Runs as part of the Evennia's test suite with 'manage.py test-evennia'.
|
||||
Runs as part of the Evennia's test suite with 'manage.py test"
|
||||
|
||||
Please add new tests to this module as needed.
|
||||
|
||||
|
|
@ -14,20 +14,20 @@ Guidelines:
|
|||
Inside the test methods, special member methods assert*() are used to test the behaviour.
|
||||
"""
|
||||
|
||||
import re, time
|
||||
import sys
|
||||
try:
|
||||
# this is a special optimized Django version, only available in current Django devel
|
||||
from django.utils.unittest import TestCase
|
||||
except ImportError:
|
||||
# if our Django is older we use the normal version
|
||||
# TODO: Switch this to django.test.TestCase when the but has been plugged that gives
|
||||
# traceback when using that module over TransactionTestCase.
|
||||
from django.test import TestCase
|
||||
#from django.test import TransactionTestCase as TestCase
|
||||
try:
|
||||
from django.utils import unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
from src.objects import models, objects
|
||||
from src.utils import create
|
||||
from src.server import session, sessionhandler
|
||||
from src.commands.default import tests as commandtests
|
||||
|
||||
class TestObjAttrs(TestCase):
|
||||
"""
|
||||
|
|
@ -38,8 +38,6 @@ class TestObjAttrs(TestCase):
|
|||
self.attr = models.ObjAttribute()
|
||||
self.obj1 = create.create_object(objects.Object, key="testobj1", location=None)
|
||||
self.obj2 = create.create_object(objects.Object, key="testobj2", location=self.obj1)
|
||||
|
||||
# tests
|
||||
def test_store_str(self):
|
||||
hstring = "sdfv00=97sfjs842 ivfjlQKFos9GF^8dddsöäå-?%"
|
||||
self.obj1.db.testattr = hstring
|
||||
|
|
@ -48,106 +46,13 @@ class TestObjAttrs(TestCase):
|
|||
self.obj1.db.testattr = self.obj2
|
||||
self.assertEqual(self.obj2 ,self.obj1.db.testattr)
|
||||
self.assertEqual(self.obj2.location, self.obj1.db.testattr.location)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Command testing
|
||||
#------------------------------------------------------------
|
||||
|
||||
# print all feedback from test commands (can become very verbose!)
|
||||
VERBOSE = False
|
||||
|
||||
class FakeSession(session.SessionProtocol):
|
||||
"""
|
||||
A fake session that implements dummy versions of the real thing; this is needed to mimic
|
||||
a logged-in player.
|
||||
"""
|
||||
def connectionMade(self):
|
||||
self.prep_session()
|
||||
sessionhandler.add_session(self)
|
||||
def prep_session(self):
|
||||
self.server, self.address = None, "0.0.0.0"
|
||||
self.name, self.uid = None, None
|
||||
self.logged_in = False
|
||||
self.encoding = "utf-8"
|
||||
self.cmd_last, self.cmd_last_visible, self.cmd_conn_time = time.time(), time.time(), time.time()
|
||||
self.cmd_total = 0
|
||||
def disconnectClient(self):
|
||||
pass
|
||||
def lineReceived(self, raw_string):
|
||||
pass
|
||||
def msg(self, message, markup=True):
|
||||
if VERBOSE:
|
||||
print message
|
||||
|
||||
class TestCommand(TestCase):
|
||||
"""
|
||||
Sets up the basics of testing the default commands and the generic things
|
||||
that should always be present in a command.
|
||||
|
||||
Inherit new tests from this.
|
||||
"""
|
||||
def setUp(self):
|
||||
"sets up the testing environment"
|
||||
self.room1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1")
|
||||
self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2")
|
||||
|
||||
# create a faux player/character for testing.
|
||||
self.char1 = create.create_player("TestingPlayer", "testplayer@test.com", "testpassword", location=self.room1)
|
||||
self.char1.player.user.is_superuser = True
|
||||
sess = FakeSession()
|
||||
sess.connectionMade()
|
||||
sess.login(self.char1.player)
|
||||
|
||||
self.char2 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="char2", location=self.room1)
|
||||
self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=self.room1)
|
||||
self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1)
|
||||
self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1)
|
||||
self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2)
|
||||
|
||||
def get_cmd(self, cmd_class, argument_string=""):
|
||||
"""
|
||||
Obtain a cmd instance from a class and an input string
|
||||
Note: This does not make use of the cmdhandler functionality.
|
||||
"""
|
||||
cmd = cmd_class()
|
||||
cmd.caller = self.char1
|
||||
cmd.cmdstring = cmd_class.key
|
||||
cmd.args = argument_string
|
||||
cmd.cmdset = None
|
||||
cmd.obj = self.char1
|
||||
return cmd
|
||||
|
||||
def execute_cmd(self, raw_string):
|
||||
"""
|
||||
Creates the command through faking a normal command call;
|
||||
This also mangles the input in various ways to test if the command
|
||||
will be fooled.
|
||||
"""
|
||||
test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it
|
||||
test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call
|
||||
test3 = "%s %s" % (raw_string, raw_string) # multiple calls
|
||||
self.char1.execute_cmd(test1)
|
||||
self.char1.execute_cmd(test2)
|
||||
self.char1.execute_cmd(test3)
|
||||
self.char1.execute_cmd(raw_string)
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Default set Command testing
|
||||
#------------------------------------------------------------
|
||||
|
||||
class TestHome(TestCommand):
|
||||
def test_call(self):
|
||||
self.char1.home = self.room2
|
||||
self.execute_cmd("home")
|
||||
self.assertEqual(self.char1.location, self.room2)
|
||||
class TestLook(TestCommand):
|
||||
def test_call(self):
|
||||
self.execute_cmd("look here")
|
||||
class TestPassword(TestCommand):
|
||||
def test_call(self):
|
||||
self.execute_cmd("@password testpassword = newpassword")
|
||||
class TestNick(TestCommand):
|
||||
def test_call(self):
|
||||
self.execute_cmd("nickname testalias = testaliasedstring")
|
||||
self.assertEquals("testaliasedstring", self.char1.nicks.get("testalias", None))
|
||||
def suite():
|
||||
"""
|
||||
This function is called automatically by the django test runner.
|
||||
This also runs the command tests defined in src/commands/default/tests.py.
|
||||
"""
|
||||
tsuite = unittest.TestSuite()
|
||||
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
|
||||
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(commandtests))
|
||||
return tsuite
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ ALLOW_MULTISESSION = True
|
|||
SECRET_KEY = 'changeme!(*#&*($&*(#*(&SDFKJJKLS*(@#KJAS'
|
||||
# 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')
|
||||
# Path to the src directory containing the bulk of the codebase's code.
|
||||
SRC_DIR = os.path.join(BASE_PATH, 'src')
|
||||
# Path to the game directory (containing the database file if using sqlite).
|
||||
GAME_DIR = os.path.join(BASE_PATH, 'game')
|
||||
# Place to put log files
|
||||
LOG_DIR = os.path.join(GAME_DIR, 'logs')
|
||||
DEFAULT_LOG_FILE = os.path.join(LOG_DIR, 'evennia.log')
|
||||
|
|
@ -136,9 +136,9 @@ ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER = ""
|
|||
###################################################
|
||||
|
||||
# Command set used before player has logged in
|
||||
CMDSET_UNLOGGEDIN = "game.gamesrc.commands.default.cmdset_unloggedin.UnloggedinCmdSet"
|
||||
CMDSET_UNLOGGEDIN = "src.commands.default.cmdset_unloggedin.UnloggedinCmdSet"
|
||||
# Default set for logged in players (fallback)
|
||||
CMDSET_DEFAULT = "game.gamesrc.commands.default.cmdset_default.DefaultCmdSet"
|
||||
CMDSET_DEFAULT = "src.commands.default.cmdset_default.DefaultCmdSet"
|
||||
|
||||
|
||||
###################################################
|
||||
|
|
@ -326,7 +326,7 @@ ADMINS = () #'Your Name', 'your_email@domain.com'),)
|
|||
MANAGERS = ADMINS
|
||||
# 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')
|
||||
MEDIA_ROOT = os.path.join(SRC_DIR, 'web', 'media')
|
||||
# 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
|
||||
|
|
@ -354,7 +354,7 @@ SERVE_MEDIA = True
|
|||
|
||||
# The master urlconf file that contains all of the sub-branches to the
|
||||
# applications.
|
||||
ROOT_URLCONF = 'game.web.urls'
|
||||
ROOT_URLCONF = 'src.web.urls'
|
||||
# Where users are redirected after logging in via contrib.auth.login.
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
# Where to redirect users when using the @login_required decorator.
|
||||
|
|
@ -373,8 +373,8 @@ ADMIN_MEDIA_PREFIX = '/media/amedia/'
|
|||
ACTIVE_TEMPLATE = 'prosimii'
|
||||
# We setup the location of the website template as well as the admin site.
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(GAME_DIR, "web", "templates", ACTIVE_TEMPLATE),
|
||||
os.path.join(GAME_DIR, "web", "templates"),)
|
||||
os.path.join(SRC_DIR, "web", "templates", ACTIVE_TEMPLATE),
|
||||
os.path.join(SRC_DIR, "web", "templates"),)
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.load_template_source',
|
||||
|
|
@ -397,12 +397,10 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||
'django.core.context_processors.auth',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.debug',
|
||||
'game.web.utils.general_context.general_context',)
|
||||
# Use a custom test runner that just tests Evennia-specific apps.
|
||||
TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner'
|
||||
'src.web.utils.general_context.general_context',)
|
||||
|
||||
###################################################
|
||||
# Evennia components (django apps)
|
||||
# Evennia components
|
||||
###################################################
|
||||
|
||||
# Global and Evennia-specific apps. This ties everything together so we can
|
||||
|
|
@ -424,12 +422,13 @@ INSTALLED_APPS = (
|
|||
'src.help',
|
||||
'src.scripts',
|
||||
'src.permissions',
|
||||
'game.web.news',
|
||||
'game.web.website',)
|
||||
|
||||
'src.web.news',
|
||||
'src.web.website',)
|
||||
# The user profile extends the User object with more functionality;
|
||||
# This should usually not be changed.
|
||||
AUTH_PROFILE_MODULE = "players.PlayerDB"
|
||||
# Use a custom test runner that just tests Evennia-specific apps.
|
||||
TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner'
|
||||
|
||||
###################################################
|
||||
# Django extensions
|
||||
|
|
@ -443,7 +442,7 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
# South handles automatic database scheme migrations when evennia updates
|
||||
try:
|
||||
try:
|
||||
import south
|
||||
INSTALLED_APPS = INSTALLED_APPS + ('south',)
|
||||
except ImportError:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
"""
|
||||
Test runner for Evennia test suite. Run with "game/manage.py test".
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.simple import DjangoTestSuiteRunner
|
||||
|
||||
|
|
@ -14,4 +19,4 @@ class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
|
|||
if not test_labels:
|
||||
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
|
||||
if (applabel.startswith('src.') or applabel.startswith('game.'))]
|
||||
return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs)
|
||||
return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs)
|
||||
|
|
|
|||
0
src/web/__init__.py
Normal file
0
src/web/__init__.py
Normal file
225
src/web/media/css/prosimii-print.css
Normal file
225
src/web/media/css/prosimii-print.css
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/**************************************
|
||||
* TITLE: Prosimii Print Stylesheet *
|
||||
* URI : prosimii/prosimii-print.css *
|
||||
* MODIF: 2003-Apr-30 19:15 +0800 *
|
||||
**************************************/
|
||||
|
||||
|
||||
/* ##### Common Styles ##### */
|
||||
|
||||
body {
|
||||
color: black;
|
||||
background-color: white;
|
||||
font-family: "times new roman", times, roman, serif;
|
||||
font-size: 12pt;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
acronym, .titleTip {
|
||||
font-style: italic;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
acronym:after, .titleTip:after { /* Prints titles after the acronyms/titletips. Doesn't work in MSIE */
|
||||
content: "(" attr(title) ")";
|
||||
font-size: 90%;
|
||||
font-style: normal;
|
||||
padding-left: 1ex;
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a[href]:after { /* Prints the links' URIs after the links' texts. Doesn't work in MSIE */
|
||||
content: "<" attr(href) ">";
|
||||
font-size: 90%;
|
||||
padding-left: 1ex;
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: -0.25em 0 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: square;
|
||||
margin: -0.25em 0 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0 0 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin: 1ex 0 0 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol li {
|
||||
margin: 1ex 0 0 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.doNotPrint {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Header ##### */
|
||||
|
||||
#header {
|
||||
}
|
||||
|
||||
.superHeader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.midHeader {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 200%;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.headerSubTitle {
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 110%;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
padding: 0 0 1ex 0;
|
||||
}
|
||||
|
||||
.headerLinks {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subHeader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Side Menu ##### */
|
||||
|
||||
#side-bar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Main Copy ##### */
|
||||
|
||||
#main-copy {
|
||||
text-align: justify;
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#main-copy h1 {
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 120%;
|
||||
margin: 2ex 0 1ex 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#main-copy h2 {
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
padding: 2ex 0 0.5ex 0;
|
||||
}
|
||||
|
||||
#main-copy h1 + h2 {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#main-copy p {
|
||||
margin: 0 0 2ex 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h2 a:after {
|
||||
content: "" !important;
|
||||
}
|
||||
|
||||
.newsDate {
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.newsDate:before { /* Prints an '[' before the news item's date. Doesn't work in MSIE */
|
||||
content: "[";
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.newsDate:after { /* Prints a ']' after the news item's date. Doesn't work in MSIE */
|
||||
content: "]";
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.newsSummary {
|
||||
display: inline;
|
||||
margin: 0 0 0 1ex !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.more {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.smallCaps {
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.quarter, .oneThird, .half, .twoThirds, .fullWidth {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Footer ##### */
|
||||
|
||||
#footer {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
font-size: 100%;
|
||||
text-align: center;
|
||||
margin: 2em 0 0 0;
|
||||
padding: 1ex 0 0 0;
|
||||
border-top: 1px solid black;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
342
src/web/media/css/prosimii-screen-alt.css
Normal file
342
src/web/media/css/prosimii-screen-alt.css
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
/*************************************************
|
||||
* TITLE: Prosimii Alternative Screen Stylesheet *
|
||||
* URI : prosimii/prosimii-screen-alt.css *
|
||||
* MODIF: 2004-Apr-28 21:56 +0800 *
|
||||
*************************************************/
|
||||
|
||||
|
||||
/* ##### Common Styles ##### */
|
||||
|
||||
body {
|
||||
font-family: verdana, helvetica, arial, sans-serif;
|
||||
font-size: 73%; /* Enables font size scaling in MSIE */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html > body {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
acronym, .titleTip {
|
||||
border-bottom: 1px dotted rgb(61,92,122);
|
||||
cursor: help;
|
||||
margin: 0;
|
||||
padding: 0 0 0.4px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
padding: 0 1px 2px 1px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: rgb(117,144,174);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: 1em 0 1.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: square;
|
||||
margin: 1em 0 1.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 1em 0 0.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
line-height: 1.5em;
|
||||
margin: 1.25ex 0 0 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol li {
|
||||
line-height: 1.5em;
|
||||
margin: 1.25ex 0 0 2em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0 0 1ex 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
line-height: 1.75em;
|
||||
margin: 0 0 1.5em 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.doNotDisplay {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
.smallCaps {
|
||||
font-size: 117%;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Header ##### */
|
||||
|
||||
.superHeader {
|
||||
color: rgb(130,128,154);
|
||||
background-color: rgb(33,50,66);
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0.5ex 10px;
|
||||
}
|
||||
|
||||
.superHeader span {
|
||||
color: rgb(195,196,210);
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.superHeader a {
|
||||
color: rgb(195,196,210);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0 0.25ex 0 0;
|
||||
}
|
||||
|
||||
.superHeader a:hover {
|
||||
color: rgb(193,102,90);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.midHeader {
|
||||
color: white;
|
||||
background-color: rgb(61,92,122);
|
||||
margin: 0;
|
||||
padding: 0.26ex 10px;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
font-size: 300%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.headerSubTitle {
|
||||
font-size: 151%;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
margin: 0 0 1ex 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.headerLinks {
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0 0 2ex 0;
|
||||
position: absolute;
|
||||
right: 1.5em;
|
||||
top: 3.5em;
|
||||
}
|
||||
|
||||
.headerLinks a {
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0 0 0.5ex 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.headerLinks a:hover {
|
||||
color: rgb(195,196,210);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.subHeader {
|
||||
color: white;
|
||||
background-color: rgb(117,144,174);
|
||||
margin: 0;
|
||||
padding: 0.5ex 10px;
|
||||
}
|
||||
|
||||
.subHeader a, .subHeader .highlight {
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
font-size: 110%;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0 0.25ex 0 0;
|
||||
}
|
||||
|
||||
.subHeader a:hover, .subHeader .highlight {
|
||||
color: rgb(255,204,0);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Side Menu ##### */
|
||||
|
||||
#side-bar {
|
||||
color: rgb(204,204,204);
|
||||
background-color: transparent;
|
||||
list-style-type: square;
|
||||
list-style-position: inside;
|
||||
width: 10em;
|
||||
margin: 0;
|
||||
padding: 1ex 0;
|
||||
border: 1px solid rgb(204,204,204);
|
||||
position: absolute;
|
||||
left: 1.5ex;
|
||||
top: 12em;
|
||||
}
|
||||
|
||||
[id="side-bar"] {
|
||||
position: fixed !important; /* Makes the side menu scroll with the page. Doesn't work in MSIE */
|
||||
}
|
||||
|
||||
#side-bar a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#side-bar:hover {
|
||||
color: rgb(117,144,174);
|
||||
background-color: transparent;
|
||||
border-color: rgb(117,144,174);
|
||||
}
|
||||
|
||||
#side-bar li {
|
||||
margin: 0;
|
||||
padding: 0.75ex 0 1ex 1.75ex;
|
||||
}
|
||||
|
||||
#side-bar li:hover {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
#side-bar li a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Main Copy ##### */
|
||||
|
||||
#main-copy {
|
||||
text-align: justify;
|
||||
margin: -0.5ex 1em 1em 12.5em;
|
||||
padding: 0.5em 10px;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#main-copy h1 {
|
||||
color: rgb(117,144,174);
|
||||
background-color: transparent;
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 186%;
|
||||
margin: 0;
|
||||
padding: 1.5ex 0 0 0;
|
||||
}
|
||||
|
||||
#main-copy h2 {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 151%;
|
||||
margin: 0;
|
||||
padding: 1ex 0 0 0;
|
||||
}
|
||||
|
||||
#main-copy p {
|
||||
line-height: 1.75em;
|
||||
margin: 1em 0 1.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.newsHeading {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 145%;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 1ex 0 0 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.newsHeading:hover {
|
||||
color: rgb(117,144,174);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.newsDate {
|
||||
font-style: italic;
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.newsSummary {
|
||||
margin: 1.5ex 0 2.5ex 0.75ex !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.more {
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.more a {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
font-size: 92%;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
padding: 0.25ex 0.75ex;
|
||||
}
|
||||
|
||||
.more a:hover {
|
||||
color: rgb(117,144,174);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Footer ##### */
|
||||
|
||||
#footer {
|
||||
color: rgb(51,51,102);
|
||||
background-color: rgb(239,239,239);
|
||||
font-size: 87%;
|
||||
text-align: center;
|
||||
line-height: 1.25em;
|
||||
margin: 2em 0 0 0;
|
||||
padding: 1ex 10px;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: rgb(0,68,204);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
359
src/web/media/css/prosimii-screen.css
Normal file
359
src/web/media/css/prosimii-screen.css
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/***************************************
|
||||
* TITLE: Prosimii Screen Stylesheet *
|
||||
* URI : prosimii/prosimii-screen.css *
|
||||
* MODIF: 2004-Apr-28 21:43 +0800 *
|
||||
***************************************/
|
||||
|
||||
|
||||
/* ##### Common Styles ##### */
|
||||
|
||||
body {
|
||||
font-family: verdana, helvetica, arial, sans-serif;
|
||||
font-size: 73%; /* Enables font size scaling in MSIE */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html > body {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
acronym, .titleTip {
|
||||
border-bottom: 1px dotted rgb(61,92,122);
|
||||
cursor: help;
|
||||
margin: 0;
|
||||
padding: 0 0 0.4px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
padding: 0 1px 2px 1px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: rgb(117,144,174);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: 1em 0 1.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: square;
|
||||
margin: 1em 0 1.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 1em 0 0.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
line-height: 1.5em;
|
||||
margin: 1.25ex 0 0 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol li {
|
||||
line-height: 1.5em;
|
||||
margin: 1.25ex 0 0 2em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 0 0 1ex 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
line-height: 1.75em;
|
||||
margin: 0 0 1.5em 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.doNotDisplay {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
.smallCaps {
|
||||
font-size: 117%;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Header ##### */
|
||||
|
||||
.superHeader {
|
||||
color: rgb(130,128,154);
|
||||
background-color: rgb(33,50,66);
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0.5ex 10px;
|
||||
}
|
||||
|
||||
.superHeader span {
|
||||
color: rgb(195,196,210);
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.superHeader a {
|
||||
color: rgb(195,196,210);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0 0.25ex 0 0;
|
||||
}
|
||||
|
||||
.superHeader a:hover {
|
||||
color: rgb(193,102,90);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.midHeader {
|
||||
color: white;
|
||||
background-color: rgb(61,92,122);
|
||||
margin: 0;
|
||||
padding: 0.26ex 10px;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
font-size: 300%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.headerSubTitle {
|
||||
font-size: 151%;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
margin: 0 0 1ex 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.headerLinks {
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0 0 2ex 0;
|
||||
position: absolute;
|
||||
right: 1.5em;
|
||||
top: 3.5em;
|
||||
}
|
||||
|
||||
.headerLinks a {
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0 0 0.5ex 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.headerLinks a:hover {
|
||||
color: rgb(195,196,210);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.subHeader {
|
||||
color: white;
|
||||
background-color: rgb(117,144,174);
|
||||
margin: 0;
|
||||
padding: 0.5ex 10px;
|
||||
}
|
||||
|
||||
.subHeader a, .subHeader .highlight {
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
font-size: 110%;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 0 0.25ex 0 0;
|
||||
}
|
||||
|
||||
.subHeader a:hover, .subHeader .highlight {
|
||||
color: rgb(255,204,0);
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* ##### Main Copy ##### */
|
||||
|
||||
#main-copy {
|
||||
margin: 0;
|
||||
padding: 0.5em 10px;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#main-copy h1 {
|
||||
color: rgb(117,144,174);
|
||||
background-color: transparent;
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 200%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#main-copy h2 {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-weight: normal;
|
||||
font-size: 151%;
|
||||
margin: 0;
|
||||
padding: 1ex 0 0 0;
|
||||
}
|
||||
|
||||
#main-copy p {
|
||||
line-height: 1.75em;
|
||||
margin: 1em 0 1.5em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.newsHeading {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
|
||||
font-size: 145%;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
padding: 1ex 0 0 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.newsHeading:hover {
|
||||
color: rgb(117,144,174);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.newsDate {
|
||||
font-style: italic;
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.newsSummary {
|
||||
margin: 1.5ex 0 2.5ex 0.75ex !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.more {
|
||||
text-align: right;
|
||||
margin: 0;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.more a {
|
||||
color: rgb(61,92,122);
|
||||
background-color: transparent;
|
||||
font-size: 92%;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
padding: 0.25ex 0.75ex;
|
||||
}
|
||||
|
||||
.more a:hover {
|
||||
color: rgb(117,144,174);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.rowOfBoxes {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.quarter, .oneThird, .half, .twoThirds, .fullWidth {
|
||||
margin: 1em 0;
|
||||
float: left;
|
||||
border-left: 1px solid rgb(204,204,204);
|
||||
}
|
||||
|
||||
.quarter {
|
||||
width: 21%;
|
||||
padding: 0 1.9%;
|
||||
}
|
||||
|
||||
.oneThird {
|
||||
width: 28%;
|
||||
padding: 0 1.9%;
|
||||
}
|
||||
|
||||
.half {
|
||||
text-align: justify;
|
||||
width: 46%;
|
||||
padding: 0 1.9%;
|
||||
}
|
||||
|
||||
.twoThirds {
|
||||
text-align: justify;
|
||||
width: 63%;
|
||||
padding: 0 1.9%;
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
text-align: justify;
|
||||
width: 96%;
|
||||
padding: 0 1.2em;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.filler { /* use with an empty <p> element to add padding to the end of a text box */
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.noBorderOnLeft {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.dividingBorderAbove {
|
||||
border-top: 1px solid rgb(204,204,204);
|
||||
}
|
||||
|
||||
/* More elegant alternatives to .noBorderOnLeft & .dividingBorderAbove
|
||||
* that don't require the creation of new classes - but which are not
|
||||
* supported by MSIE - are the following:
|
||||
*
|
||||
* .rowOfBoxes > div:first-child {
|
||||
* border-left: none;
|
||||
* }
|
||||
*
|
||||
* .rowOfBoxes + .rowOfBoxes {
|
||||
* border-top: 1px solid rgb(204,204,204);
|
||||
* }
|
||||
*/
|
||||
|
||||
|
||||
/* ##### Footer ##### */
|
||||
|
||||
#footer {
|
||||
color: rgb(51,51,102);
|
||||
background-color: rgb(239,239,239);
|
||||
font-size: 87%;
|
||||
text-align: center;
|
||||
line-height: 1.25em;
|
||||
margin: 2em 0 0 0;
|
||||
padding: 1ex 10px;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: rgb(0,68,204);
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
4
src/web/media/images/LICENCE
Normal file
4
src/web/media/images/LICENCE
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
The evennia logo (the python snaking a cogwheel-globe) was created in 2009
|
||||
by Griatch (www.griatch-art.deviantart.com, www.griatch.com) using open-source software (of course).
|
||||
|
||||
The logo is released with the same licence as Evennia itself (look in evennia/LICENCE).
|
||||
BIN
src/web/media/images/evennia_logo.png
Executable file
BIN
src/web/media/images/evennia_logo.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 672 KiB |
BIN
src/web/media/images/evennia_logo_small.png
Executable file
BIN
src/web/media/images/evennia_logo_small.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
src/web/media/images/favicon.ico
Normal file
BIN
src/web/media/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
0
src/web/news/__init__.py
Executable file
0
src/web/news/__init__.py
Executable file
17
src/web/news/admin.py
Normal file
17
src/web/news/admin.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# This makes the news model visible in the admin web interface
|
||||
# so one can add/edit/delete news items etc.
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from src.web.news.models import NewsTopic, NewsEntry
|
||||
|
||||
class NewsTopicAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'icon')
|
||||
admin.site.register(NewsTopic, NewsTopicAdmin)
|
||||
|
||||
class NewsEntryAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'author', 'topic', 'date_posted')
|
||||
list_filter = ('topic',)
|
||||
search_fields = ['title']
|
||||
admin.site.register(NewsEntry, NewsEntryAdmin)
|
||||
45
src/web/news/models.py
Executable file
45
src/web/news/models.py
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
#
|
||||
# This module implements a simple news entry system
|
||||
# for the evennia website. One needs to use the
|
||||
# admin interface to add/edit/delete entries.
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class NewsTopic(models.Model):
|
||||
"""
|
||||
Represents a news topic.
|
||||
"""
|
||||
name = models.CharField(max_length=75, unique=True)
|
||||
description = models.TextField(blank=True)
|
||||
icon = models.ImageField(upload_to='newstopic_icons',
|
||||
default='newstopic_icons/default.png',
|
||||
blank=True, help_text="Image for the news topic.")
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
return self.name
|
||||
except:
|
||||
return "Invalid"
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
class NewsEntry(models.Model):
|
||||
"""
|
||||
An individual news entry.
|
||||
"""
|
||||
author = models.ForeignKey(User, related_name='author')
|
||||
title = models.CharField(max_length=255)
|
||||
body = models.TextField()
|
||||
topic = models.ForeignKey(NewsTopic, related_name='newstopic')
|
||||
date_posted = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ('-date_posted',)
|
||||
verbose_name_plural = "News entries"
|
||||
|
||||
13
src/web/news/urls.py
Executable file
13
src/web/news/urls.py
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
"""
|
||||
This structures the url tree for the news application.
|
||||
It is imported from the root handler, game.web.urls.py.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('src.web.news.views',
|
||||
(r'^show/(?P<entry_id>\d+)/$', 'show_news'),
|
||||
(r'^archive/$', 'news_archive'),
|
||||
(r'^search/$', 'search_form'),
|
||||
(r'^search/results/$', 'search_results'),
|
||||
)
|
||||
128
src/web/news/views.py
Executable file
128
src/web/news/views.py
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
"""
|
||||
This is a very simple news application, with most of the expected features
|
||||
like news-categories/topics and searchable archives.
|
||||
|
||||
"""
|
||||
|
||||
import django.views.generic.list_detail as gv_list_detail
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth.models import User
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from src.web.news.models import NewsTopic, NewsEntry
|
||||
|
||||
# The sidebar text to be included as a variable on each page. There's got to
|
||||
# be a better, cleaner way to include this on every page.
|
||||
sidebar = """
|
||||
<p class='doNotDisplay doNotPrint'>This page’s menu:</p>
|
||||
<ul id='side-bar'>
|
||||
<li><a href='/news/archive'>News Archive</a></li>
|
||||
<li><a href='/news/search'>Search News</a></li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
class SearchForm(forms.Form):
|
||||
"""
|
||||
Class to represent a news search form under Django's newforms. This is used
|
||||
to validate the input on the search_form view, as well as the search_results
|
||||
view when we're picking the query out of GET. This makes searching safe
|
||||
via the search form or by directly inputing values via GET key pairs.
|
||||
"""
|
||||
search_terms = forms.CharField(max_length=100, min_length=3, required=True)
|
||||
|
||||
def show_news(request, entry_id):
|
||||
"""
|
||||
Show an individual news entry. Display some basic information along with
|
||||
the title and content.
|
||||
"""
|
||||
news_entry = get_object_or_404(NewsEntry, id=entry_id)
|
||||
|
||||
pagevars = {
|
||||
"page_title": "News Entry",
|
||||
"news_entry": news_entry,
|
||||
"sidebar": sidebar
|
||||
}
|
||||
|
||||
context_instance = RequestContext(request)
|
||||
return render_to_response('news/show_entry.html', pagevars, context_instance)
|
||||
|
||||
def news_archive(request):
|
||||
"""
|
||||
Shows an archive of news entries.
|
||||
|
||||
TODO: Expand this a bit to allow filtering by month/year.
|
||||
"""
|
||||
news_entries = NewsEntry.objects.all().order_by('-date_posted')
|
||||
# TODO: Move this to either settings.py or the SQL configuration.
|
||||
entries_per_page = 15
|
||||
|
||||
pagevars = {
|
||||
"page_title": "News Archive",
|
||||
"browse_url": "/news/archive",
|
||||
"sidebar": sidebar
|
||||
}
|
||||
|
||||
return gv_list_detail.object_list(request, news_entries, template_name='news/archive.html', extra_context=pagevars, paginate_by=entries_per_page)
|
||||
|
||||
def search_form(request):
|
||||
"""
|
||||
Render the news search form. Don't handle much validation at all. If the
|
||||
user enters a search term that meets the minimum, send them on their way
|
||||
to the results page.
|
||||
"""
|
||||
if request.method == 'GET':
|
||||
# A GET request was sent to the search page, load the value and
|
||||
# validate it.
|
||||
search_form = SearchForm(request.GET)
|
||||
if search_form.is_valid():
|
||||
# If the input is good, send them to the results page with the
|
||||
# query attached in GET variables.
|
||||
return HttpResponseRedirect('/news/search/results/?search_terms='+ search_form.cleaned_data['search_terms'])
|
||||
else:
|
||||
# Brand new search, nothing has been sent just yet.
|
||||
search_form = SearchForm()
|
||||
|
||||
pagevars = {
|
||||
"page_title": "Search News",
|
||||
"search_form": search_form,
|
||||
"debug": settings.DEBUG,
|
||||
"sidebar": sidebar
|
||||
}
|
||||
|
||||
context_instance = RequestContext(request)
|
||||
return render_to_response('news/search_form.html', pagevars, context_instance)
|
||||
|
||||
def search_results(request):
|
||||
"""
|
||||
Shows an archive of news entries. Use the generic news browsing template.
|
||||
"""
|
||||
# TODO: Move this to either settings.py or the SQL configuration.
|
||||
entries_per_page = 15
|
||||
|
||||
# Load the form values from GET to validate against.
|
||||
search_form = SearchForm(request.GET)
|
||||
# You have to call is_valid() or cleaned_data won't be populated.
|
||||
valid_search = search_form.is_valid()
|
||||
# This is the safe data that we can pass to queries without huge worry of
|
||||
# badStuff(tm).
|
||||
cleaned_get = search_form.cleaned_data
|
||||
|
||||
# Perform searches that match the title and contents.
|
||||
# TODO: Allow the user to specify what to match against and in what
|
||||
# topics/categories.
|
||||
news_entries = NewsEntry.objects.filter(Q(title__contains=cleaned_get['search_terms']) | Q(body__contains=cleaned_get['search_terms']))
|
||||
|
||||
pagevars = {
|
||||
"game_name": settings.SERVERNAME,
|
||||
"page_title": "Search Results",
|
||||
"searchtext": cleaned_get['search_terms'],
|
||||
"browse_url": "/news/search/results",
|
||||
"sidebar": sidebar
|
||||
}
|
||||
|
||||
return gv_list_detail.object_list(request, news_entries, template_name='news/archive.html', extra_context=pagevars, paginate_by=entries_per_page)
|
||||
11
src/web/templates/admin/base_site.html
Normal file
11
src/web/templates/admin/base_site.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "admin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }} | {% trans 'Evennia site admin' %}{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<h1 id="site-name">{% trans 'Evennia database administration' %}
|
||||
<a href="/">(Back)</a> </h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block nav-global %}{% endblock %}
|
||||
242
src/web/templates/admin/index.html
Normal file
242
src/web/templates/admin/index.html
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css" />{% endblock %}
|
||||
|
||||
{% block coltype %}colMS{% endblock %}
|
||||
|
||||
{% block bodyclass %}dashboard{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
|
||||
{% if app_list %}
|
||||
|
||||
{% for app in app_list %}
|
||||
|
||||
{% if app.name in evennia_userapps %}
|
||||
|
||||
<div class="module">
|
||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||
{% for model in app.models %}
|
||||
<tr>
|
||||
{% if model.perms.change %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.add %}
|
||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<h1>In-game entities</h1>
|
||||
|
||||
{% for app in app_list %}
|
||||
|
||||
{% if app.name in evennia_entityapps %}
|
||||
|
||||
<div class="module">
|
||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||
{% for model in app.models %}
|
||||
<tr>
|
||||
{% if model.perms.change %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.add %}
|
||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<h1>Game setups and configs</h1>
|
||||
|
||||
|
||||
{% for app in app_list %}
|
||||
|
||||
{% if app.name in evennia_setupapps %}
|
||||
|
||||
<div class="module">
|
||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||
{% for model in app.models %}
|
||||
<tr>
|
||||
{% if model.perms.change %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.add %}
|
||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<h1>Connection protocols</h1>
|
||||
|
||||
{% for app in app_list %}
|
||||
|
||||
{% if app.name in evennia_connectapps %}
|
||||
|
||||
<div class="module">
|
||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||
{% for model in app.models %}
|
||||
<tr>
|
||||
{% if model.perms.change %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.add %}
|
||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<h1>Website Specific</h1>
|
||||
|
||||
|
||||
{% for app in app_list %}
|
||||
|
||||
{% if app.name in evennia_websiteapps %}
|
||||
|
||||
<div class="module">
|
||||
<table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||
{% for model in app.models %}
|
||||
<tr>
|
||||
{% if model.perms.change %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.add %}
|
||||
<td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.change %}
|
||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% else %}
|
||||
<p>{% trans "You don't have permission to edit anything." %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
<div id="content-related">
|
||||
<div class="module" id="recent-actions-module">
|
||||
<h2>{% trans 'Recent Actions' %}</h2>
|
||||
<h3>{% trans 'My Actions' %}</h3>
|
||||
{% load log %}
|
||||
{% get_admin_log 10 as admin_log for_user user %}
|
||||
{% if not admin_log %}
|
||||
<p>{% trans 'None yet.' %}</p>
|
||||
{% else %}
|
||||
<ul class="actionlist">
|
||||
{% for entry in admin_log %}
|
||||
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
|
||||
{% if entry.is_deletion %}
|
||||
{{ entry.object_repr }}
|
||||
{% else %}
|
||||
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
|
||||
{% endif %}
|
||||
<br/>
|
||||
{% if entry.content_type %}
|
||||
<span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span>
|
||||
{% else %}
|
||||
<span class="mini quiet">{% trans 'Unknown content' %}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
90
src/web/templates/prosimii/base.html
Normal file
90
src/web/templates/prosimii/base.html
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-AU">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
|
||||
<meta name="author" content="haran" />
|
||||
<meta name="generator" content="haran" />
|
||||
|
||||
{% if sidebar %}
|
||||
<link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/prosimii-screen-alt.css" media="screen" title="Prosimii (Sidebar)" />
|
||||
{% else %}
|
||||
<link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/prosimii-screen.css" media="screen" title="Prosimii" />
|
||||
{% endif %}
|
||||
<link rel="stylesheet alternative" type="text/css" href="{{MEDIA_URL}}css/prosimii-print.css" media="screen" title="Print Preview" />
|
||||
<link rel="stylesheet" type="text/css" href="{{MEDIA_URL}}css/prosimii-print.css" media="print" />
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
<title>{{game_name}} - {% if flatpage %}{{flatpage.title}}{% else %}{% block titleblock %}{{page_title}}{% endblock %}{% endif %}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- For non-visual user agents: -->
|
||||
<div id="top"><a href="#main-copy" class="doNotDisplay doNotPrint">Skip to main content.</a></div>
|
||||
|
||||
<!-- ##### Header ##### -->
|
||||
|
||||
<div id="header">
|
||||
<div class="superHeader">
|
||||
<!--span>Sites:</span-->
|
||||
<a href="http://evennia.com" title="The Python-based MUD server">Evennia.com</a>
|
||||
</div>
|
||||
|
||||
<div class="midHeader">
|
||||
<img src="/media/images/evennia_logo_small.png" align='left'/> <h1 class="headerTitle" lang="la">{{game_name}}</h1>
|
||||
<div class="headerSubTitle" title="Slogan">
|
||||
<!-- Insert a slogan here if you want -->
|
||||
{{game_slogan}}
|
||||
</div>
|
||||
|
||||
<br class="doNotDisplay doNotPrint" />
|
||||
|
||||
<div class="headerLinks">
|
||||
<span class="doNotDisplay">Tools:</span>
|
||||
{% if user.is_authenticated %}
|
||||
<a href="/accounts/logout/">Log Out «</a>
|
||||
<span class="doNotDisplay">|</span>
|
||||
Logged in as {{user.username}} «
|
||||
{% else %}
|
||||
<a href="/accounts/login/">Log In «</a>
|
||||
<span class="doNotDisplay">|</span>
|
||||
<a href="/tbi">Register «</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="subHeader">
|
||||
<span class="doNotDisplay">Navigation:</span>
|
||||
<a href="/">Home</a> |
|
||||
<a href="http://code.evennia.com/">About</a> |
|
||||
<a href="http://code.google.com/p/evennia/wiki/Index">Documentation</a> |
|
||||
<a href="/admin/">Admin Interface</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ##### Side Menu ##### -->
|
||||
{% block sidebar %}{% endblock %}
|
||||
<!-- ##### Main Copy ##### -->
|
||||
|
||||
<div id="main-copy">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- ##### Footer ##### -->
|
||||
|
||||
<div id="footer">
|
||||
<span class="doNotPrint">
|
||||
Template design by
|
||||
<a href="http://www.oswd.org/designs/search/designer/id/3013/"
|
||||
title="Other designs by haran">haran</a>.
|
||||
Powered by
|
||||
<a href="http://evennia.com">Evennia.</a>
|
||||
<br \>
|
||||
</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
12
src/web/templates/prosimii/flatpages/default.html
Normal file
12
src/web/templates/prosimii/flatpages/default.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">{{flatpage.title}}</h1>
|
||||
{{flatpage.content}}
|
||||
{% endblock %}
|
||||
82
src/web/templates/prosimii/index.html
Normal file
82
src/web/templates/prosimii/index.html
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="rowOfBoxes">
|
||||
<div class="twoThirds noBorderOnLeft">
|
||||
<h1>Welcome!</h1>
|
||||
<p>Welcome to your new installation of Evennia, your friendly
|
||||
neighborhood next-generation MUD server. You are looking at Evennia's web
|
||||
presence, which can be expanded to a full-fledged site as
|
||||
needed. Through the <a href="/admin">admin interface</a> you can view and edit the
|
||||
database without logging into the game. Also take your time to
|
||||
peruse our extensive online <a href="http://code.google.com/p/evennia/wiki/Index">documentation</a>.
|
||||
<p>
|
||||
Should you have any questions, concerns, bug reports, or
|
||||
if you want to help out, don't hesitate to come join the
|
||||
<a href="http://evennia.com">Evennia community</a> and get
|
||||
your voice heard!
|
||||
</p>
|
||||
<i>(To edit this file, go to game/web/templates/prosimii/index.html.)</i>
|
||||
</div>
|
||||
|
||||
<div class="oneThird">
|
||||
<h1>Latest News</h1>
|
||||
{% for entry in news_entries %}
|
||||
<a href="/news/show/{{entry.id}}" class="newsHeading">{{entry.topic.name}}: {{entry.title}}</a>
|
||||
<p class="newsDate">By {{entry.author.username}} on {{entry.date_posted|time}}</p>
|
||||
<p class="newsSummary">{{entry.body|truncatewords:20}}</p>
|
||||
{% endfor %}
|
||||
|
||||
<div class="more"><a href="/news/archive">More News »</a></div>
|
||||
|
||||
<p class="filler"><!-- Filler para to extend left vertical line --></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rowOfBoxes dividingBorderAbove">
|
||||
|
||||
<div class="quarter noBorderOnLeft">
|
||||
<h1>Players</h1>
|
||||
<p>
|
||||
There are currently <strong>{{num_players_connected}}</strong> connected,
|
||||
and a total of <strong>{{num_players_registered}}</strong> registered. Of these, <strong>{{num_players_registered_recent}}</strong> were created this week, and <strong>{{num_players_connected_recent}}</strong> have connected within the last seven days.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="quarter">
|
||||
<h1>Recently Connected</h1>
|
||||
<ul>
|
||||
{% for player in players_connected_recent %}
|
||||
<li>{{player.user.username}} -- <em>{{player.user.last_login|timesince}} ago</em></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="filler"><!-- Filler para to extend left vertical line --></p>
|
||||
</div>
|
||||
|
||||
<div class="quarter">
|
||||
<h1>Database Stats</h1>
|
||||
<ul>
|
||||
<li>{{num_players_registered}} players</li>
|
||||
<li>{{num_rooms}} rooms ({{num_exits}} exits) </li>
|
||||
<li>{{num_objects}} objects total</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="quarter">
|
||||
<h1>Evennia</h1>
|
||||
<p><a href="http://evennia.com">Evennia</a> is MUD server built in
|
||||
<a href="http://python.org">Python</a>, on top of the
|
||||
<a href="http://twistedmatrix.com">Twisted</a> and
|
||||
<a href="http://djangoproject.com">Django</a> frameworks. This
|
||||
combination of technologies allows for the quick and easy creation
|
||||
of the game of your dreams - as simple or as complex as you like.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
51
src/web/templates/prosimii/news/archive.html
Normal file
51
src/web/templates/prosimii/news/archive.html
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{{sidebar|safe}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">{{page_title}}</h1>
|
||||
|
||||
<strong>Navigation:</strong> <a href="{{browse_url}}/?page=1&search_terms={{searchtext|urlencode}}">First</a> |
|
||||
{% if has_previous %}
|
||||
<a href="{{browse_url}}/?page={{previous}}&search_terms={{searchtext|urlencode}}">Prev</a>
|
||||
{% else %}
|
||||
Prev
|
||||
{% endif %}
|
||||
|
||||
| <em>{{page}}</em> of <em>{{pages}}</em> pages |
|
||||
|
||||
{% if has_next %}
|
||||
<a href="{{browse_url}}/?page={{next}}&search_terms={{searchtext|urlencode}}">Next</a>
|
||||
{% else %}
|
||||
Next
|
||||
{% endif %}
|
||||
| <a href="{{browse_url}}/?page={{pages}}&search_terms={{searchtext|urlencode}}">Last</a>
|
||||
|
||||
{% for entry in object_list %}
|
||||
<a href="/news/show/{{entry.id}}" class="newsHeading">{{entry.topic.name}}: {{entry.title}}</a>
|
||||
<p class="newsDate">By {{entry.author.username}} on {{entry.date_posted|time}}</p>
|
||||
<p class="newsSummary">{{entry.body|truncatewords:80}}</p>
|
||||
{% endfor %}
|
||||
|
||||
<strong>Navigation:</strong> <a href="{{browse_url}}/?page=1&search_terms={{searchtext|urlencode}}">First</a> |
|
||||
{% if has_previous %}
|
||||
<a href="{{browse_url}}/?page={{previous}}&search_terms={{searchtext|urlencode}}">Prev</a>
|
||||
{% else %}
|
||||
Prev
|
||||
{% endif %}
|
||||
|
||||
| <em>{{page}}</em> of <em>{{pages}}</em> pages |
|
||||
|
||||
{% if has_next %}
|
||||
<a href="{{browse_url}}/?page={{next}}&search_terms={{searchtext|urlencode}}">Next</a>
|
||||
{% else %}
|
||||
Next
|
||||
{% endif %}
|
||||
| <a href="{{browse_url}}/?page={{pages}}&search_terms={{searchtext|urlencode}}">Last</a>
|
||||
|
||||
{% endblock %}
|
||||
19
src/web/templates/prosimii/news/search_form.html
Normal file
19
src/web/templates/prosimii/news/search_form.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{{sidebar|safe}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">Search News</h1>
|
||||
<p>Enter a search term or phrase to search by. Matches will be made against
|
||||
news titles and their contents. Searches must be at least three characters
|
||||
long.</p>
|
||||
<form method="GET">
|
||||
{{search_form.search_terms}}
|
||||
<button type="Submit">Search</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
14
src/web/templates/prosimii/news/show_entry.html
Normal file
14
src/web/templates/prosimii/news/show_entry.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{{sidebar|safe}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">{{news_entry.topic.name}}: {{news_entry.title}}</h1>
|
||||
<p class="newsDate">By {{news_entry.author.username}} on {{news_entry.date_posted|time}}</p>
|
||||
<p class="newsSummary">{{news_entry.body}}</p>
|
||||
{% endblock %}
|
||||
10
src/web/templates/prosimii/registration/logged_out.html
Normal file
10
src/web/templates/prosimii/registration/logged_out.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Logged Out
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">Logged Out</h1>
|
||||
<p>You have been logged out.</p>
|
||||
{% endblock %}
|
||||
31
src/web/templates/prosimii/registration/login.html
Normal file
31
src/web/templates/prosimii/registration/login.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block titleblock %}
|
||||
Login
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">Login</h1>
|
||||
{% if user.is_authenticated %}
|
||||
<p>You are already logged in!</p>
|
||||
{% else %}
|
||||
{% if form.has_errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="."{% csrf_token %} >
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="id_username">Username:</label></td>
|
||||
<td>{{ form.username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="id_password">Password:</label></td>
|
||||
<td>{{ form.password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<input type="submit" value="Login" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
18
src/web/templates/prosimii/tbi.html
Normal file
18
src/web/templates/prosimii/tbi.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header_ext %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 id="alt-layout">To Be Implemented...</h1>
|
||||
<p>
|
||||
This feature has yet to be implemented, but rest assured that it will be!
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
Eventually...
|
||||
</p>
|
||||
{% endblock %}
|
||||
50
src/web/urls.py
Executable file
50
src/web/urls.py
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#
|
||||
# File that determines what each URL points to. This uses _Python_ regular
|
||||
# expressions, not Perl's.
|
||||
#
|
||||
# See:
|
||||
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
|
||||
#
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
|
||||
# loop over all settings.INSTALLED_APPS and execute code in
|
||||
# files named admin.py ine each such app (this will add those
|
||||
# models to the admin site)
|
||||
admin.autodiscover()
|
||||
|
||||
# Setup the root url tree from /
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# User Authentication
|
||||
url(r'^accounts/login', 'django.contrib.auth.views.login'),
|
||||
url(r'^accounts/logout', 'django.contrib.auth.views.logout'),
|
||||
|
||||
# Front page
|
||||
url(r'^', include('src.web.website.urls')),
|
||||
# News stuff
|
||||
url(r'^news/', include('src.web.news.urls')),
|
||||
|
||||
# Page place-holder for things that aren't implemented yet.
|
||||
url(r'^tbi/', 'src.web.website.views.to_be_implemented'),
|
||||
|
||||
# Admin interface
|
||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
#url(r'^admin/(.*)', admin.site.root, name='admin'),
|
||||
|
||||
# favicon
|
||||
url(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url':'/media/images/favicon.ico'}),
|
||||
)
|
||||
|
||||
# If you'd like to serve media files via Django (strongly not recommended!),
|
||||
# open up your settings.py file and 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).
|
||||
if settings.SERVE_MEDIA:
|
||||
urlpatterns += patterns('',
|
||||
(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
|
||||
)
|
||||
0
src/web/utils/__init__.py
Normal file
0
src/web/utils/__init__.py
Normal file
16
src/web/utils/apache_wsgi.conf
Normal file
16
src/web/utils/apache_wsgi.conf
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import os, sys
|
||||
|
||||
# Calculate the path based on the location of the WSGI script.
|
||||
web_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
workspace = os.path.dirname(os.path.dirname(web_dir))
|
||||
|
||||
sys.path.insert(0, workspace)
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
import django.core.handlers.wsgi
|
||||
|
||||
_application = django.core.handlers.wsgi.WSGIHandler()
|
||||
# This handles apps mounted in places other than the root mount point
|
||||
def application(environ, start_response):
|
||||
environ['PATH_INFO'] = environ['SCRIPT_NAME'] + environ['PATH_INFO']
|
||||
return _application(environ, start_response)
|
||||
45
src/web/utils/evennia_modpy_apache.conf
Normal file
45
src/web/utils/evennia_modpy_apache.conf
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
# WARNING: mod_python is no longer the recommended way to run Evennia's
|
||||
# web front end. This file is no longer actively maintained and may
|
||||
# no longer work. We suggest using mod_wsgi unless absolutely necessary.
|
||||
|
||||
# Add this vhost file to your Apache sites-enabled directory, typically found
|
||||
# at /etc/apache2/sites-enabled. You'll need to go through and change the
|
||||
# /home/evennia to point to the correct home directory, and the evennia
|
||||
# subdir must be under that home directory.
|
||||
|
||||
# A NOTE ON IMAGES/CSS: These files must be handled separately from the actual
|
||||
# dynamic content which is interpreted by Python. You may host these static
|
||||
# images/css from the same server or a different one. The static media
|
||||
# is served from /home/evennia/evennia/media. The easiest way to serve
|
||||
# this stuff is by either hosting them remotely or symlinking
|
||||
# the media directory into an existing Apache site's directory.
|
||||
|
||||
# A NOTE ON ADMIN IMAGES/CSS: You'll need to create a symlink called
|
||||
# 'amedia' under the media directory if you want to use the admin interface.
|
||||
# This can be found in the Django package in your Python path. For example,
|
||||
# the default is: /usr/lib/python2.4/site-packages/django/contrib/admin/media/
|
||||
# although your location may vary.
|
||||
<VirtualHost *>
|
||||
# Set ServerName and ServerAdmin appropriately
|
||||
ServerName evennia.somewhere.com
|
||||
ServerAdmin someone@somewhere.com
|
||||
DocumentRoot /home/evennia/evennia
|
||||
|
||||
<Directory "/home/evennia/evennia">
|
||||
SetHandler python-program
|
||||
PythonHandler django.core.handlers.modpython
|
||||
PythonPath "['/home/evennia/evennia'] + sys.path"
|
||||
SetEnv DJANGO_SETTINGS_MODULE settings
|
||||
PythonDebug On
|
||||
</Directory>
|
||||
|
||||
Alias /media/ "/home/evennia/evennia/media/"
|
||||
<Directory "/home/evennia/evennia/media/">
|
||||
SetHandler None
|
||||
</Directory>
|
||||
|
||||
<LocationMatch "\.(jpg|gif|png|css)$">
|
||||
SetHandler None
|
||||
</LocationMatch>
|
||||
</VirtualHost>
|
||||
51
src/web/utils/evennia_wsgi_apache.conf
Normal file
51
src/web/utils/evennia_wsgi_apache.conf
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<VirtualHost *>
|
||||
# This is an example mod_wsgi apache2 vhost. There are a number of
|
||||
# things you'll need to change. They are commented below. Make sure
|
||||
# you read and understand the stuff below.
|
||||
|
||||
# Copy this file to your apache2 vhosts directory before editing.
|
||||
# This file is merely a template. The vhost directory is usually
|
||||
# something like /etc/apache2/sites-enabled/.
|
||||
|
||||
# You'll want to replace the following with your domain info.
|
||||
ServerName dev.evennia.com
|
||||
ServerAlias www.dev.evennia.com
|
||||
#
|
||||
## BEGIN EVENNIA WEB CONFIG
|
||||
#
|
||||
# evennia_web is just an internal name for mod_wsgi and may be left
|
||||
# The user and group options are the user and the group that the
|
||||
# python code will be executed under. This user must have permissions
|
||||
# to the evennia source.
|
||||
WSGIDaemonProcess evennia_web user=evennia group=evennia threads=25
|
||||
WSGIProcessGroup evennia_web
|
||||
|
||||
# This needs to be changed to the appropriate path on your django
|
||||
# install. It serves admin site media.
|
||||
# For Python 2.6, this is:
|
||||
# /usr/lib/python2.6/dist-packages/django/contrib/admin/media/
|
||||
Alias /media/amedia/ "/usr/lib/python2.5/site-packages/django/contrib/admin/media/"
|
||||
<Directory "/usr/lib/python2.5/site-packages/django/contrib/admin/media">
|
||||
Order allow,deny
|
||||
Options Indexes
|
||||
Allow from all
|
||||
IndexOptions FancyIndexing
|
||||
</Directory>
|
||||
|
||||
# Media Directory. This needs to be set to your equivalent path.
|
||||
Alias /media/ "/home/evennia/evennia/game/web/media/"
|
||||
<Directory "/home/evennia/evennia/game/web/media">
|
||||
Order allow,deny
|
||||
Options Indexes FollowSymLinks
|
||||
Allow from all
|
||||
IndexOptions FancyIndexing
|
||||
</Directory>
|
||||
|
||||
|
||||
# WSGI Config File. You'll need to update this path as well.
|
||||
WSGIScriptAlias / /home/evennia/evennia/game/web/utils/apache_wsgi.conf
|
||||
|
||||
#
|
||||
## END EVENNIA WEB CONFIG
|
||||
#
|
||||
</VirtualHost>
|
||||
46
src/web/utils/general_context.py
Normal file
46
src/web/utils/general_context.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#
|
||||
# This file defines global variables that will always be
|
||||
# available in a view context without having to repeatedly
|
||||
# include it. For this to work, this file is included in
|
||||
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
|
||||
# tuple.
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from src.utils.utils import get_evennia_version
|
||||
|
||||
# Determine the site name and server version
|
||||
|
||||
try:
|
||||
GAME_NAME = settings.SERVERNAME.strip()
|
||||
except AttributeError:
|
||||
GAME_NAME = "Evennia"
|
||||
SERVER_VERSION = get_evennia_version()
|
||||
|
||||
|
||||
# Setup lists of the most relevant apps so
|
||||
# the adminsite becomes more readable.
|
||||
|
||||
USER_RELATED = ['Auth', 'Players']
|
||||
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
|
||||
GAME_SETUP = ['Permissions', 'Config']
|
||||
CONNECTIONS = ['Irc', 'Imc2']
|
||||
WEBSITE = ['Flatpages', 'News', 'Sites']
|
||||
|
||||
# The main context processor function
|
||||
|
||||
def general_context(request):
|
||||
"""
|
||||
Returns common Evennia-related context stuff, which
|
||||
is automatically added to context of all views.
|
||||
"""
|
||||
return {
|
||||
'game_name': GAME_NAME,
|
||||
'game_slogan': SERVER_VERSION,
|
||||
'evennia_userapps': USER_RELATED,
|
||||
'evennia_entityapps': GAME_ENTITIES,
|
||||
'evennia_setupapps': GAME_SETUP,
|
||||
'evennia_connectapps': CONNECTIONS,
|
||||
'evennia_websiteapps':WEBSITE
|
||||
}
|
||||
0
src/web/website/__init__.py
Normal file
0
src/web/website/__init__.py
Normal file
7
src/web/website/models.py
Normal file
7
src/web/website/models.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Define database entities for the app.
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
10
src/web/website/urls.py
Normal file
10
src/web/website/urls.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
This structures the (simple) structure of the
|
||||
webpage 'application'.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('src.web.website.views',
|
||||
(r'^$', 'page_index'),
|
||||
)
|
||||
63
src/web/website/views.py
Normal file
63
src/web/website/views.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
|
||||
from src.config.models import ConfigValue
|
||||
from src.objects.models import ObjectDB
|
||||
from src.typeclasses.models import TypedObject
|
||||
from src.players.models import PlayerDB
|
||||
from src.web.news.models import NewsEntry
|
||||
|
||||
"""
|
||||
This file contains the generic, assorted views that don't fall under one of
|
||||
the other applications.
|
||||
"""
|
||||
|
||||
def page_index(request):
|
||||
"""
|
||||
Main root page.
|
||||
"""
|
||||
# Some misc. configurable stuff.
|
||||
# TODO: Move this to either SQL or settings.py based configuration.
|
||||
fpage_player_limit = 4
|
||||
fpage_news_entries = 2
|
||||
|
||||
# A QuerySet of recent news entries.
|
||||
news_entries = NewsEntry.objects.all().order_by('-date_posted')[:fpage_news_entries]
|
||||
# A QuerySet of the most recently connected players.
|
||||
recent_users = PlayerDB.objects.get_recently_connected_players()[:fpage_player_limit]
|
||||
|
||||
exits = ObjectDB.objects.get_objs_with_attr('_destination')
|
||||
rooms = [room for room in ObjectDB.objects.filter(db_home=None) if room not in exits]
|
||||
|
||||
pagevars = {
|
||||
"page_title": "Front Page",
|
||||
"news_entries": news_entries,
|
||||
"players_connected_recent": recent_users,
|
||||
"num_players_connected": ConfigValue.objects.conf('nr_sessions'),#len(PlayerDB.objects.get_connected_players()),
|
||||
"num_players_registered": PlayerDB.objects.num_total_players(),
|
||||
"num_players_connected_recent": len(PlayerDB.objects.get_recently_connected_players()),
|
||||
"num_players_registered_recent": len(PlayerDB.objects.get_recently_created_players()),
|
||||
"num_rooms": len(rooms),
|
||||
"num_exits": len(exits),
|
||||
"num_objects" : ObjectDB.objects.all().count()
|
||||
}
|
||||
|
||||
context_instance = RequestContext(request)
|
||||
return render_to_response('index.html', pagevars, context_instance)
|
||||
|
||||
def to_be_implemented(request):
|
||||
"""
|
||||
A notice letting the user know that this particular feature hasn't been
|
||||
implemented yet.
|
||||
"""
|
||||
|
||||
pagevars = {
|
||||
"page_title": "To Be Implemented...",
|
||||
}
|
||||
|
||||
context_instance = RequestContext(request)
|
||||
return render_to_response('tbi.html', pagevars, context_instance)
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue