Changed how the Typeclass system returns errors. Instead of echoing typeclass erros to the MUD-info channel (which is not only not only very spammy for everyone but also very hard to make clean so as to avoid recursion at a stage of typeclass failing), the system instead stores a property on itself called 'typeclass_last_errmsg' that holds eventual errors. This means that the task of reporting errors does not fall on the typeclass system itself but on the calling methods, as it should be. So src.utils.create.create_* functions now takes a new optional keyword "report_to" that holds an object to receive errors. If this keyword is given, the function msg():es that object with the error and returns None as before. If report_to is not set however, the create_* methods now return an Exception containing the error text. All default commands have been changed to accomodate for this behaviour, which allows for much more control over errors.

Also, the default ADMIN_MEDIA static files changed location in Django 1.4. The initial_setup function now accounts for this.
This commit is contained in:
Griatch 2012-04-21 16:15:37 +02:00
parent 63329f5420
commit 8c3b49e704
15 changed files with 838 additions and 785 deletions

View file

@ -16,13 +16,13 @@ from src.commands.default.muxcommand import MuxCommand
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
# limit members for API inclusion # limit members for API inclusion
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer", "CmdEmit", "CmdNewPassword", __all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer", "CmdEmit", "CmdNewPassword",
"CmdPerm", "CmdWall") "CmdPerm", "CmdWall")
class CmdBoot(MuxCommand): class CmdBoot(MuxCommand):
""" """
@boot @boot
Usage Usage
@boot[/switches] <player obj> [: reason] @boot[/switches] <player obj> [: reason]
@ -30,11 +30,11 @@ class CmdBoot(MuxCommand):
Switches: Switches:
quiet - Silently boot without informing player quiet - Silently boot without informing player
port - boot by port number instead of name or dbref port - boot by port number instead of name or dbref
Boot a player object from the server. If a reason is Boot a player object from the server. If a reason is
supplied it will be echoed to the user unless /quiet is set. supplied it will be echoed to the user unless /quiet is set.
""" """
key = "@boot" key = "@boot"
locks = "cmd:perm(boot) or perm(Wizards)" locks = "cmd:perm(boot) or perm(Wizards)"
help_category = "Admin" help_category = "Admin"
@ -43,7 +43,7 @@ class CmdBoot(MuxCommand):
"Implementing the function" "Implementing the function"
caller = self.caller caller = self.caller
args = self.args args = self.args
if not args: if not args:
caller.msg("Usage: @boot[/switches] <player> [:reason]") caller.msg("Usage: @boot[/switches] <player> [:reason]")
return return
@ -67,12 +67,12 @@ class CmdBoot(MuxCommand):
# Boot by player object # Boot by player object
pobj = caller.search("*%s" % args, global_search=True, player=True) pobj = caller.search("*%s" % args, global_search=True, player=True)
if not pobj: if not pobj:
return return
if pobj.character.has_player: if pobj.character.has_player:
if not pobj.access(caller, 'boot'): if not pobj.access(caller, 'boot'):
string = "You don't have the permission to boot %s." string = "You don't have the permission to boot %s."
pobj.msg(string) pobj.msg(string)
return return
# we have a bootable object with a connected user # we have a bootable object with a connected user
matches = SESSIONS.sessions_from_player(pobj) matches = SESSIONS.sessions_from_player(pobj)
for match in matches: for match in matches:
@ -87,7 +87,7 @@ class CmdBoot(MuxCommand):
# Carry out the booting of the sessions in the boot list. # Carry out the booting of the sessions in the boot list.
feedback = None feedback = None
if not 'quiet' in self.switches: if not 'quiet' in self.switches:
feedback = "You have been disconnected by %s.\n" % caller.name feedback = "You have been disconnected by %s.\n" % caller.name
if reason: if reason:
@ -108,16 +108,16 @@ def list_bans(banlist):
Helper function to display a list of active bans. Input argument Helper function to display a list of active bans. Input argument
is the banlist read into the two commands @ban and @undban below. is the banlist read into the two commands @ban and @undban below.
""" """
if not banlist: if not banlist:
return "No active bans were found." return "No active bans were found."
table = [["id"], ["name/ip"], ["date"], ["reason"]] table = [["id"], ["name/ip"], ["date"], ["reason"]]
table[0].extend([str(i+1) for i in range(len(banlist))]) table[0].extend([str(i+1) for i in range(len(banlist))])
for ban in banlist: for ban in banlist:
if ban[0]: if ban[0]:
table[1].append(ban[0]) table[1].append(ban[0])
else: else:
table[1].append(ban[1]) table[1].append(ban[1])
table[2].extend([ban[3] for ban in banlist]) table[2].extend([ban[3] for ban in banlist])
table[3].extend([ban[4] for ban in banlist]) table[3].extend([ban[4] for ban in banlist])
ftable = utils.format_table(table, 4) ftable = utils.format_table(table, 4)
@ -128,8 +128,8 @@ def list_bans(banlist):
srow = "{w%s{n" % srow.rstrip() srow = "{w%s{n" % srow.rstrip()
else: else:
srow = "\n" + "{w%s{n" % row[0] + "".join(row[1:]) srow = "\n" + "{w%s{n" % row[0] + "".join(row[1:])
string += srow.rstrip() string += srow.rstrip()
return string return string
class CmdBan(MuxCommand): class CmdBan(MuxCommand):
""" """
@ -137,34 +137,34 @@ class CmdBan(MuxCommand):
Usage: Usage:
@ban [<name or ip> [: reason]] @ban [<name or ip> [: reason]]
Without any arguments, shows numbered list of active bans. Without any arguments, shows numbered list of active bans.
This command bans a user from accessing the game. Supply an This command bans a user from accessing the game. Supply an
optional reason to be able to later remember why the ban was put in optional reason to be able to later remember why the ban was put in
place place
It is often to It is often to
prefer over deleting a player with @delplayer. If banned by name, prefer over deleting a player with @delplayer. If banned by name,
that player account can no longer be logged into. that player account can no longer be logged into.
IP (Internet Protocol) address banning allows to block all access IP (Internet Protocol) address banning allows to block all access
from a specific address or subnet. Use the asterisk (*) as a from a specific address or subnet. Use the asterisk (*) as a
wildcard. wildcard.
Examples: Examples:
@ban thomas - ban account 'thomas' @ban thomas - ban account 'thomas'
@ban/ip 134.233.2.111 - ban specific ip address @ban/ip 134.233.2.111 - ban specific ip address
@ban/ip 134.233.2.* - ban all in a subnet @ban/ip 134.233.2.* - ban all in a subnet
@ban/ip 134.233.*.* - even wider ban @ban/ip 134.233.*.* - even wider ban
A single IP filter is easy to circumvent by changing the computer A single IP filter is easy to circumvent by changing the computer
(also, some ISPs assign only temporary IPs to their users in the (also, some ISPs assign only temporary IPs to their users in the
first placer. Widening the IP block filter with wildcards might be first placer. Widening the IP block filter with wildcards might be
tempting, but remember that blocking too much may accidentally tempting, but remember that blocking too much may accidentally
also block innocent users connecting from the same country and also block innocent users connecting from the same country and
region. region.
""" """
key = "@ban" key = "@ban"
aliases = ["@bans"] aliases = ["@bans"]
@ -173,25 +173,25 @@ class CmdBan(MuxCommand):
def func(self): def func(self):
""" """
Bans are stored in a serverconf db object as a list of Bans are stored in a serverconf db object as a list of
dictionaries: dictionaries:
[ (name, ip, ipregex, date, reason), [ (name, ip, ipregex, date, reason),
(name, ip, ipregex, date, reason),... ] (name, ip, ipregex, date, reason),... ]
where name and ip are set by the user and are shown in where name and ip are set by the user and are shown in
lists. ipregex is a converted form of ip where the * is lists. ipregex is a converted form of ip where the * is
replaced by an appropriate regex pattern for fast replaced by an appropriate regex pattern for fast
matching. date is the time stamp the ban was instigated and matching. date is the time stamp the ban was instigated and
'reason' is any optional info given to the command. Unset 'reason' is any optional info given to the command. Unset
values in each tuple is set to the empty string. values in each tuple is set to the empty string.
""" """
banlist = ServerConfig.objects.conf('server_bans') banlist = ServerConfig.objects.conf('server_bans')
if not banlist: if not banlist:
banlist = [] banlist = []
if not self.args or (self.switches if not self.args or (self.switches
and not any(switch in ('ip', 'name') for switch in self.switches)): and not any(switch in ('ip', 'name') for switch in self.switches)):
self.caller.msg(list_bans(banlist)) self.caller.msg(list_bans(banlist))
return return
now = time.ctime() now = time.ctime()
reason = "" reason = ""
@ -199,13 +199,13 @@ class CmdBan(MuxCommand):
ban, reason = self.args.rsplit(':',1) ban, reason = self.args.rsplit(':',1)
else: else:
ban = self.args ban = self.args
ban = ban.lower() ban = ban.lower()
ipban = IPREGEX.findall(ban) ipban = IPREGEX.findall(ban)
if not ipban: if not ipban:
# store as name # store as name
typ = "Name" typ = "Name"
bantup = (ban, "", "", now, reason) bantup = (ban, "", "", now, reason)
else: else:
# an ip address. # an ip address.
typ = "IP" typ = "IP"
ban = ipban[0] ban = ipban[0]
@ -215,16 +215,16 @@ class CmdBan(MuxCommand):
print "regex:",ipregex print "regex:",ipregex
ipregex = re.compile(r"%s" % ipregex) ipregex = re.compile(r"%s" % ipregex)
bantup = ("", ban, ipregex, now, reason) bantup = ("", ban, ipregex, now, reason)
# save updated banlist # save updated banlist
banlist.append(bantup) banlist.append(bantup)
ServerConfig.objects.conf('server_bans', banlist) ServerConfig.objects.conf('server_bans', banlist)
self.caller.msg("%s-Ban {w%s{x was added." % (typ, ban)) self.caller.msg("%s-Ban {w%s{x was added." % (typ, ban))
class CmdUnban(MuxCommand): class CmdUnban(MuxCommand):
""" """
remove a ban remove a ban
Usage: Usage:
@unban <banid> @unban <banid>
This will clear a player name/ip ban previously set with the @ban This will clear a player name/ip ban previously set with the @ban
@ -236,26 +236,26 @@ class CmdUnban(MuxCommand):
key = "@unban" key = "@unban"
locks = "cmd:perm(unban) or perm(Immortals)" locks = "cmd:perm(unban) or perm(Immortals)"
help_category="Admin" help_category="Admin"
def func(self): def func(self):
"Implement unbanning" "Implement unbanning"
banlist = ServerConfig.objects.conf('server_bans') banlist = ServerConfig.objects.conf('server_bans')
if not self.args: if not self.args:
self.caller.msg(list_bans(banlist)) self.caller.msg(list_bans(banlist))
return return
try: try:
num = int(self.args) num = int(self.args)
except Exception: except Exception:
self.caller.msg("You must supply a valid ban id to clear.") self.caller.msg("You must supply a valid ban id to clear.")
return return
if not banlist: if not banlist:
self.caller.msg("There are no bans to clear.") self.caller.msg("There are no bans to clear.")
elif not (0 < num < len(banlist) + 1): elif not (0 < num < len(banlist) + 1):
self.caller.msg("Ban id {w%s{x was not found." % self.args) self.caller.msg("Ban id {w%s{x was not found." % self.args)
else: else:
# all is ok, clear ban # all is ok, clear ban
ban = banlist[num-1] ban = banlist[num-1]
@ -263,20 +263,20 @@ class CmdUnban(MuxCommand):
ServerConfig.objects.conf('server_bans', banlist) ServerConfig.objects.conf('server_bans', banlist)
self.caller.msg("Cleared ban %s: %s" % (num, " ".join([s for s in ban[:2]]))) self.caller.msg("Cleared ban %s: %s" % (num, " ".join([s for s in ban[:2]])))
class CmdDelPlayer(MuxCommand): class CmdDelPlayer(MuxCommand):
""" """
delplayer - delete player from server delplayer - delete player from server
Usage: Usage:
@delplayer[/switch] <name> [: reason] @delplayer[/switch] <name> [: reason]
Switch: Switch:
delobj - also delete the player's currently delobj - also delete the player's currently
assigned in-game object. assigned in-game object.
Completely deletes a user from the server database, Completely deletes a user from the server database,
making their nick and e-mail again available. making their nick and e-mail again available.
""" """
key = "@delplayer" key = "@delplayer"
@ -287,10 +287,10 @@ class CmdDelPlayer(MuxCommand):
"Implements the command." "Implements the command."
caller = self.caller caller = self.caller
args = self.args args = self.args
if hasattr(caller, 'player'): if hasattr(caller, 'player'):
caller = caller.player caller = caller.player
if not args: if not args:
caller.msg("Usage: @delplayer[/delobj] <player/user name or #id> [: reason]") caller.msg("Usage: @delplayer[/delobj] <player/user name or #id> [: reason]")
@ -309,26 +309,26 @@ class CmdDelPlayer(MuxCommand):
except ValueError: except ValueError:
pass pass
if not players: if not players:
# try to find a user instead of a Player # try to find a user instead of a Player
try: try:
user = User.objects.get(id=args) user = User.objects.get(id=args)
except Exception: except Exception:
try: try:
user = User.objects.get(username__iexact=args) user = User.objects.get(username__iexact=args)
except Exception: except Exception:
string = "No Player nor User found matching '%s'." % args string = "No Player nor User found matching '%s'." % args
caller.msg(string) caller.msg(string)
return return
try: try:
player = user.get_profile() player = user.get_profile()
except Exception: except Exception:
player = None player = None
if player and not player.access(caller, 'delete'): if player and not player.access(caller, 'delete'):
string = "You don't have the permissions to delete this player." string = "You don't have the permissions to delete this player."
caller.msg(string) caller.msg(string)
return return
string = "" string = ""
name = user.username name = user.username
@ -340,13 +340,13 @@ class CmdDelPlayer(MuxCommand):
else: else:
string += "The User %s was deleted. It had no Player associated with it." % name string += "The User %s was deleted. It had no Player associated with it." % name
caller.msg(string) caller.msg(string)
return return
elif utils.is_iter(players): elif utils.is_iter(players):
string = "There were multiple matches:" string = "There were multiple matches:"
for player in players: for player in players:
string += "\n %s %s" % (player.id, player.key) string += "\n %s %s" % (player.id, player.key)
return return
else: else:
# one single match # one single match
@ -357,10 +357,10 @@ class CmdDelPlayer(MuxCommand):
if not player.access(caller, 'delete'): if not player.access(caller, 'delete'):
string = "You don't have the permissions to delete that player." string = "You don't have the permissions to delete that player."
caller.msg(string) caller.msg(string)
return return
uname = user.username uname = user.username
# boot the player then delete # boot the player then delete
if character and character.has_player: if character and character.has_player:
caller.msg("Booting and informing player ...") caller.msg("Booting and informing player ...")
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
@ -368,30 +368,30 @@ class CmdDelPlayer(MuxCommand):
string += " Reason given:\n '%s'" % reason string += " Reason given:\n '%s'" % reason
character.msg(string) character.msg(string)
caller.execute_cmd("@boot %s" % uname) caller.execute_cmd("@boot %s" % uname)
player.delete() player.delete()
user.delete() user.delete()
caller.msg("Player %s was successfully deleted." % uname) caller.msg("Player %s was successfully deleted." % uname)
class CmdEmit(MuxCommand): class CmdEmit(MuxCommand):
""" """
@emit @emit
Usage: Usage:
@emit[/switches] [<obj>, <obj>, ... =] <message> @emit[/switches] [<obj>, <obj>, ... =] <message>
@remit [<obj>, <obj>, ... =] <message> @remit [<obj>, <obj>, ... =] <message>
@pemit [<obj>, <obj>, ... =] <message> @pemit [<obj>, <obj>, ... =] <message>
Switches: Switches:
room : limit emits to rooms only (default) room : limit emits to rooms only (default)
players : limit emits to players only players : limit emits to players only
contents : send to the contents of matched objects too contents : send to the contents of matched objects too
Emits a message to the selected objects or to Emits a message to the selected objects or to
your immediate surroundings. If the object is a room, your immediate surroundings. If the object is a room,
send to its contents. @remit and @pemit are just send to its contents. @remit and @pemit are just
limited forms of @emit, for sending to rooms and limited forms of @emit, for sending to rooms and
to players respectively. to players respectively.
""" """
key = "@emit" key = "@emit"
@ -401,7 +401,7 @@ class CmdEmit(MuxCommand):
def func(self): def func(self):
"Implement the command" "Implement the command"
caller = self.caller caller = self.caller
args = self.args args = self.args
@ -411,12 +411,12 @@ class CmdEmit(MuxCommand):
string += "\n@remit [<obj>, <obj>, ... =] <message>" string += "\n@remit [<obj>, <obj>, ... =] <message>"
string += "\n@pemit [<obj>, <obj>, ... =] <message>" string += "\n@pemit [<obj>, <obj>, ... =] <message>"
caller.msg(string) caller.msg(string)
return return
rooms_only = 'rooms' in self.switches rooms_only = 'rooms' in self.switches
players_only = 'players' in self.switches players_only = 'players' in self.switches
send_to_contents = 'contents' in self.switches send_to_contents = 'contents' in self.switches
# we check which command was used to force the switches # we check which command was used to force the switches
if self.cmdstring == '@remit': if self.cmdstring == '@remit':
rooms_only = True rooms_only = True
@ -429,12 +429,12 @@ class CmdEmit(MuxCommand):
else: else:
message = self.rhs message = self.rhs
objnames = self.lhslist objnames = self.lhslist
# send to all objects # send to all objects
for objname in objnames: for objname in objnames:
obj = caller.search(objname, global_search=True) obj = caller.search(objname, global_search=True)
if not obj: if not obj:
return return
if rooms_only and not obj.location == None: if rooms_only and not obj.location == None:
caller.msg("%s is not a room. Ignored." % objname) caller.msg("%s is not a room. Ignored." % objname)
continue continue
@ -463,7 +463,7 @@ class CmdNewPassword(MuxCommand):
Set a player's password. Set a player's password.
""" """
key = "@userpassword" key = "@userpassword"
locks = "cmd:perm(newpassword) or perm(Wizards)" locks = "cmd:perm(newpassword) or perm(Wizards)"
help_category = "Admin" help_category = "Admin"
@ -475,12 +475,12 @@ class CmdNewPassword(MuxCommand):
if not self.rhs: if not self.rhs:
caller.msg("Usage: @userpassword <user obj> = <new password>") caller.msg("Usage: @userpassword <user obj> = <new password>")
return return
# the player search also matches 'me' etc. # the player search also matches 'me' etc.
player = caller.search("*%s" % self.lhs, global_search=True, player=True) player = caller.search("*%s" % self.lhs, global_search=True, player=True)
if not player: if not player:
return return
player.user.set_password(self.rhs) player.user.set_password(self.rhs)
player.user.save() player.user.save()
caller.msg("%s - new password set to '%s'." % (player.name, self.rhs)) caller.msg("%s - new password set to '%s'." % (player.name, self.rhs))
@ -495,12 +495,12 @@ class CmdPerm(MuxCommand):
Usage: Usage:
@perm[/switch] <object> [= <permission>[,<permission>,...]] @perm[/switch] <object> [= <permission>[,<permission>,...]]
@perm[/switch] *<player> [= <permission>[,<permission>,...]] @perm[/switch] *<player> [= <permission>[,<permission>,...]]
Switches: Switches:
del : delete the given permission from <object> or <player>. del : delete the given permission from <object> or <player>.
player : set permission on a player (same as adding * to name) player : set permission on a player (same as adding * to name)
This command sets/clears individual permission strings on an object This command sets/clears individual permission strings on an object
or player. If no permission is given, list all permissions on <object>. or player. If no permission is given, list all permissions on <object>.
""" """
key = "@perm" key = "@perm"
@ -515,19 +515,19 @@ class CmdPerm(MuxCommand):
switches = self.switches switches = self.switches
lhs, rhs = self.lhs, self.rhs lhs, rhs = self.lhs, self.rhs
if not self.args: if not self.args:
string = "Usage: @perm[/switch] object [ = permission, permission, ...]" string = "Usage: @perm[/switch] object [ = permission, permission, ...]"
caller.msg(string) caller.msg(string)
return return
playermode = 'player' in self.switches or lhs.startswith('*') playermode = 'player' in self.switches or lhs.startswith('*')
# locate the object # locate the object
obj = caller.search(lhs, global_search=True, player=playermode) obj = caller.search(lhs, global_search=True, player=playermode)
if not obj: if not obj:
return return
if not rhs: if not rhs:
if not obj.access(caller, 'examine'): if not obj.access(caller, 'examine'):
caller.msg("You are not allowed to examine this object.") caller.msg("You are not allowed to examine this object.")
return return
@ -539,15 +539,15 @@ class CmdPerm(MuxCommand):
string += ", ".join(obj.permissions) string += ", ".join(obj.permissions)
if hasattr(obj, 'player') and hasattr(obj.player, 'is_superuser') and obj.player.is_superuser: if hasattr(obj, 'player') and hasattr(obj.player, 'is_superuser') and obj.player.is_superuser:
string += "\n(... but this object is currently controlled by a SUPERUSER! " string += "\n(... but this object is currently controlled by a SUPERUSER! "
string += "All access checks are passed automatically.)" string += "All access checks are passed automatically.)"
caller.msg(string) caller.msg(string)
return return
# we supplied an argument on the form obj = perm # we supplied an argument on the form obj = perm
if not obj.access(caller, 'control'): if not obj.access(caller, 'control'):
caller.msg("You are not allowed to edit this object's permissions.") caller.msg("You are not allowed to edit this object's permissions.")
return return
cstring = "" cstring = ""
tstring = "" tstring = ""
@ -561,24 +561,24 @@ class CmdPerm(MuxCommand):
continue continue
permissions = obj.permissions permissions = obj.permissions
del permissions[index] del permissions[index]
obj.permissions = permissions obj.permissions = permissions
cstring += "\nPermission '%s' was removed from %s." % (perm, obj.name) cstring += "\nPermission '%s' was removed from %s." % (perm, obj.name)
tstring += "\n%s revokes the permission '%s' from you." % (caller.name, perm) tstring += "\n%s revokes the permission '%s' from you." % (caller.name, perm)
else: else:
# add a new permission # add a new permission
permissions = obj.permissions permissions = obj.permissions
caller.permissions caller.permissions
for perm in self.rhslist: for perm in self.rhslist:
# don't allow to set a permission higher in the hierarchy than the one the # don't allow to set a permission higher in the hierarchy than the one the
# caller has (to prevent self-escalation) # caller has (to prevent self-escalation)
if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm): if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm):
caller.msg("You cannot assign a permission higher than the one you have yourself.") caller.msg("You cannot assign a permission higher than the one you have yourself.")
return return
if perm in permissions: if perm in permissions:
cstring += "\nPermission '%s' is already defined on %s." % (rhs, obj.name) cstring += "\nPermission '%s' is already defined on %s." % (rhs, obj.name)
@ -586,7 +586,7 @@ class CmdPerm(MuxCommand):
permissions.append(perm) permissions.append(perm)
obj.permissions = permissions obj.permissions = permissions
cstring += "\nPermission '%s' given to %s." % (rhs, obj.name) cstring += "\nPermission '%s' given to %s." % (rhs, obj.name)
tstring += "\n%s gives you the permission '%s'." % (caller.name, rhs) tstring += "\n%s gives you the permission '%s'." % (caller.name, rhs)
caller.msg(cstring.strip()) caller.msg(cstring.strip())
if tstring: if tstring:
obj.msg(tstring.strip()) obj.msg(tstring.strip())
@ -597,7 +597,7 @@ class CmdWall(MuxCommand):
Usage: Usage:
@wall <message> @wall <message>
Announces a message to all connected players. Announces a message to all connected players.
""" """
key = "@wall" key = "@wall"

View file

@ -1,16 +1,16 @@
""" """
Batch processors Batch processors
These commands implements the 'batch-command' and 'batch-code' These commands implements the 'batch-command' and 'batch-code'
processors, using the functionality in src.utils.batchprocessors. processors, using the functionality in src.utils.batchprocessors.
They allow for offline world-building. They allow for offline world-building.
Batch-command is the simpler system. This reads a file (*.ev) Batch-command is the simpler system. This reads a file (*.ev)
containing a list of in-game commands and executes them in sequence as containing a list of in-game commands and executes them in sequence as
if they had been entered in the game (including permission checks if they had been entered in the game (including permission checks
etc). etc).
Example batch-command file: game/gamesrc/commands/examples/example_batch_cmd.ev Example batch-command file: game/gamesrc/commands/examples/example_batch_cmd.ev
Batch-code is a full-fledged python code interpreter that reads blocks Batch-code is a full-fledged python code interpreter that reads blocks
of python code (*.py) and executes them in sequence. This allows for of python code (*.py) and executes them in sequence. This allows for
@ -18,7 +18,7 @@ much more power than Batch-command, but requires knowing Python and
the Evennia API. It is also a severe security risk and should the Evennia API. It is also a severe security risk and should
therefore always be limited to superusers only. therefore always be limited to superusers only.
Example batch-code file: game/gamesrc/commands/examples/example_batch_code.py Example batch-code file: game/gamesrc/commands/examples/example_batch_code.py
""" """
from traceback import format_exc from traceback import format_exc
@ -42,10 +42,10 @@ UTF8_ERROR = \
fine and fully supported! But for Evennia to know how to decode such 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 characters in a universal way, the batchfile must be saved with the
international 'UTF-8' encoding. This file is not. international 'UTF-8' encoding. This file is not.
Please re-save the batchfile with the UTF-8 encoding (refer to the 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 documentation of your text editor on how to do this, or switch to a
better featured one) and try again. better featured one) and try again.
The (first) error was found with a character on line %s in the file. The (first) error was found with a character on line %s in the file.
""" """
@ -60,16 +60,16 @@ def format_header(caller, entry):
Formats a header Formats a header
""" """
width = HEADER_WIDTH - 10 width = HEADER_WIDTH - 10
entry = entry.strip() entry = entry.strip()
header = utils.crop(entry, width=width) header = utils.crop(entry, width=width)
ptr = caller.ndb.batch_stackptr + 1 ptr = caller.ndb.batch_stackptr + 1
stacklen = len(caller.ndb.batch_stack) stacklen = len(caller.ndb.batch_stack)
header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header) header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
# add extra space to the side for padding. # add extra space to the side for padding.
header = "%s%s" % (header, " "*(width - len(header))) header = "%s%s" % (header, " "*(width - len(header)))
header = header.replace('\n', '\\n') header = header.replace('\n', '\\n')
return header return header
def format_code(entry): def format_code(entry):
""" """
@ -89,7 +89,7 @@ def batch_cmd_exec(caller):
command = stack[ptr] command = stack[ptr]
caller.msg(format_header(caller, command)) caller.msg(format_header(caller, command))
try: try:
caller.execute_cmd(command) caller.execute_cmd(command)
except Exception: except Exception:
caller.msg(format_code(format_exc())) caller.msg(format_code(format_exc()))
return False return False
@ -98,24 +98,24 @@ def batch_cmd_exec(caller):
def batch_code_exec(caller): def batch_code_exec(caller):
""" """
Helper function for executing a single batch-code entry Helper function for executing a single batch-code entry
""" """
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
stack = caller.ndb.batch_stack stack = caller.ndb.batch_stack
debug = caller.ndb.batch_debug debug = caller.ndb.batch_debug
codedict = stack[ptr] codedict = stack[ptr]
caller.msg(format_header(caller, codedict['code'])) caller.msg(format_header(caller, codedict['code']))
err = BATCHCODE.code_exec(codedict, err = BATCHCODE.code_exec(codedict,
extra_environ={"caller":caller}, debug=debug) extra_environ={"caller":caller}, debug=debug)
if err: if err:
caller.msg(format_code(err)) caller.msg(format_code(err))
return False return False
return True return True
def step_pointer(caller, step=1): def step_pointer(caller, step=1):
""" """
Step in stack, returning the item located. Step in stack, returning the item located.
stackptr - current position in stack stackptr - current position in stack
stack - the stack of units stack - the stack of units
step - how many steps to move from stackptr step - how many steps to move from stackptr
@ -139,10 +139,10 @@ def show_curr(caller, showall=False):
if stackptr >= len(stack): if stackptr >= len(stack):
caller.ndb.batch_stackptr = len(stack) - 1 caller.ndb.batch_stackptr = len(stack) - 1
show_curr(caller, showall) show_curr(caller, showall)
return return
entry = stack[stackptr]
entry = stack[stackptr]
if type(entry) == dict: if type(entry) == dict:
# this is a batch-code entry # this is a batch-code entry
string = format_header(caller, entry['code']) string = format_header(caller, entry['code'])
@ -160,7 +160,7 @@ def show_curr(caller, showall=False):
def purge_processor(caller): def purge_processor(caller):
""" """
This purges all effects running This purges all effects running
on the caller. on the caller.
""" """
try: try:
del caller.ndb.batch_stack del caller.ndb.batch_stack
@ -169,17 +169,17 @@ def purge_processor(caller):
del caller.ndb.batch_batchmode del caller.ndb.batch_batchmode
except: except:
pass pass
# clear everything but the default cmdset. # clear everything but the default cmdset.
caller.cmdset.delete(BatchSafeCmdSet) caller.cmdset.delete(BatchSafeCmdSet)
caller.cmdset.clear() caller.cmdset.clear()
caller.scripts.validate() # this will purge interactive mode caller.scripts.validate() # this will purge interactive mode
#------------------------------------------------------------ #------------------------------------------------------------
# main access commands # main access commands
#------------------------------------------------------------ #------------------------------------------------------------
class CmdBatchCommands(MuxCommand): class CmdBatchCommands(MuxCommand):
""" """
Build from batch-command file Build from batch-command file
Usage: Usage:
@ -188,9 +188,9 @@ class CmdBatchCommands(MuxCommand):
Switch: Switch:
interactive - this mode will offer more control when interactive - this mode will offer more control when
executing the batch file, like stepping, executing the batch file, like stepping,
skipping, reloading etc. skipping, reloading etc.
Runs batches of commands from a batch-cmd text file (*.ev). Runs batches of commands from a batch-cmd text file (*.ev).
""" """
key = "@batchcommands" key = "@batchcommands"
@ -200,23 +200,23 @@ class CmdBatchCommands(MuxCommand):
def func(self): def func(self):
"Starts the processor." "Starts the processor."
caller = self.caller caller = self.caller
args = self.args args = self.args
if not args: if not args:
caller.msg("Usage: @batchcommands[/interactive] <path.to.file>") caller.msg("Usage: @batchcommands[/interactive] <path.to.file>")
return return
python_path = self.args python_path = self.args
#parse indata file #parse indata file
try: try:
commands = BATCHCMD.parse_file(python_path) commands = BATCHCMD.parse_file(python_path)
except UnicodeDecodeError, err: except UnicodeDecodeError, err:
lnum = err.linenum lnum = err.linenum
caller.msg(UTF8_ERROR % (python_path, lnum)) caller.msg(UTF8_ERROR % (python_path, lnum))
return return
if not commands: if not commands:
string = "'%s' not found.\nYou have to supply the python path " string = "'%s' not found.\nYou have to supply the python path "
@ -225,37 +225,37 @@ class CmdBatchCommands(MuxCommand):
return return
switches = self.switches switches = self.switches
# Store work data in cache # Store work data in cache
caller.ndb.batch_stack = commands caller.ndb.batch_stack = commands
caller.ndb.batch_stackptr = 0 caller.ndb.batch_stackptr = 0
caller.ndb.batch_pythonpath = python_path caller.ndb.batch_pythonpath = python_path
caller.ndb.batch_batchmode = "batch_commands" caller.ndb.batch_batchmode = "batch_commands"
caller.cmdset.add(BatchSafeCmdSet) caller.cmdset.add(BatchSafeCmdSet)
if 'inter' in switches or 'interactive' in switches: if 'inter' in switches or 'interactive' in switches:
# Allow more control over how batch file is executed # Allow more control over how batch file is executed
# Set interactive state directly # Set interactive state directly
caller.cmdset.add(BatchInteractiveCmdSet) caller.cmdset.add(BatchInteractiveCmdSet)
caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path) caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path)
show_curr(caller) show_curr(caller)
else: else:
caller.msg("Running Batch-command processor - Automatic mode for %s ..." % python_path) 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 # add the 'safety' cmdset in case the batch processing adds cmdsets to us
for inum in range(len(commands)): for inum in range(len(commands)):
# loop through the batch file # loop through the batch file
if not batch_cmd_exec(caller): if not batch_cmd_exec(caller):
return return
step_pointer(caller, 1) step_pointer(caller, 1)
# clean out the safety cmdset and clean out all other temporary attrs. # clean out the safety cmdset and clean out all other temporary attrs.
string = " Batchfile '%s' applied." % python_path string = " Batchfile '%s' applied." % python_path
caller.msg("{G%s" % string) caller.msg("{G%s" % string)
purge_processor(caller) purge_processor(caller)
class CmdBatchCode(MuxCommand): class CmdBatchCode(MuxCommand):
""" """
Build from batch-code file Build from batch-code file
Usage: Usage:
@ -264,29 +264,29 @@ class CmdBatchCode(MuxCommand):
Switch: Switch:
interactive - this mode will offer more control when interactive - this mode will offer more control when
executing the batch file, like stepping, executing the batch file, like stepping,
skipping, reloading etc. skipping, reloading etc.
debug - auto-delete all objects that has been marked as debug - auto-delete all objects that has been marked as
deletable in the script file (see example files for deletable in the script file (see example files for
syntax). This is useful so as to to not leave multiple syntax). This is useful so as to to not leave multiple
object copies behind when testing out the script. object copies behind when testing out the script.
Runs batches of commands from a batch-code text file (*.py). Runs batches of commands from a batch-code text file (*.py).
""" """
key = "@batchcode" key = "@batchcode"
aliases = ["@batchcodes"] aliases = ["@batchcodes"]
locks = "cmd:superuser()" locks = "cmd:superuser()"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
"Starts the processor." "Starts the processor."
caller = self.caller caller = self.caller
args = self.args args = self.args
if not args: if not args:
caller.msg("Usage: @batchcode[/interactive/debug] <path.to.file>") caller.msg("Usage: @batchcode[/interactive/debug] <path.to.file>")
return return
python_path = self.args python_path = self.args
#parse indata file #parse indata file
@ -295,7 +295,7 @@ class CmdBatchCode(MuxCommand):
except UnicodeDecodeError, err: except UnicodeDecodeError, err:
lnum = err.linenum lnum = err.linenum
caller.msg(UTF8_ERROR % (python_path, lnum)) caller.msg(UTF8_ERROR % (python_path, lnum))
return return
if not codes: if not codes:
string = "'%s' not found.\nYou have to supply the python path " string = "'%s' not found.\nYou have to supply the python path "
@ -314,12 +314,12 @@ class CmdBatchCode(MuxCommand):
caller.ndb.batch_pythonpath = python_path caller.ndb.batch_pythonpath = python_path
caller.ndb.batch_batchmode = "batch_code" caller.ndb.batch_batchmode = "batch_code"
caller.ndb.batch_debug = debug caller.ndb.batch_debug = debug
caller.cmdset.add(BatchSafeCmdSet) caller.cmdset.add(BatchSafeCmdSet)
if 'inter' in switches or 'interactive'in switches: if 'inter' in switches or 'interactive'in switches:
# Allow more control over how batch file is executed # Allow more control over how batch file is executed
# Set interactive state directly # Set interactive state directly
caller.cmdset.add(BatchInteractiveCmdSet) caller.cmdset.add(BatchInteractiveCmdSet)
caller.msg("\nBatch-code processor - Interactive mode for %s ..." % python_path) caller.msg("\nBatch-code processor - Interactive mode for %s ..." % python_path)
@ -328,11 +328,11 @@ class CmdBatchCode(MuxCommand):
caller.msg("Running Batch-code processor - Automatic mode for %s ..." % python_path) 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 # add the 'safety' cmdset in case the batch processing adds cmdsets to us
for inum in range(len(codes)): for inum in range(len(codes)):
# loop through the batch file # loop through the batch file
if not batch_code_exec(caller): if not batch_code_exec(caller):
return return
step_pointer(caller, 1) step_pointer(caller, 1)
string = " Batchfile '%s' applied." % python_path string = " Batchfile '%s' applied." % python_path
caller.msg("{G%s" % string) caller.msg("{G%s" % string)
purge_processor(caller) purge_processor(caller)
@ -346,41 +346,41 @@ class CmdStateAbort(MuxCommand):
@abort @abort
This is a safety feature. It force-ejects us out of the processor and to 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 the default cmdset, regardless of what current cmdset the processor might
have put us in (e.g. when testing buggy scripts etc). have put us in (e.g. when testing buggy scripts etc).
""" """
key = "@abort" key = "@abort"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
"Exit back to default." "Exit back to default."
purge_processor(self.caller) purge_processor(self.caller)
self.caller.msg("Exited processor and reset out active cmdset back to the default one.") self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
class CmdStateLL(MuxCommand): class CmdStateLL(MuxCommand):
""" """
ll ll
Look at the full source for the current Look at the full source for the current
command definition. command definition.
""" """
key = "ll" key = "ll"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
show_curr(self.caller, showall=True) show_curr(self.caller, showall=True)
class CmdStatePP(MuxCommand): class CmdStatePP(MuxCommand):
""" """
pp pp
Process the currently shown command definition. Process the currently shown command definition.
""" """
key = "pp" key = "pp"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
""" """
@ -392,17 +392,17 @@ class CmdStatePP(MuxCommand):
else: else:
batch_cmd_exec(caller) batch_cmd_exec(caller)
class CmdStateRR(MuxCommand): class CmdStateRR(MuxCommand):
""" """
rr rr
Reload the batch file, keeping the current Reload the batch file, keeping the current
position in it. position in it.
""" """
key = "rr" key = "rr"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -410,7 +410,7 @@ class CmdStateRR(MuxCommand):
new_data = BATCHCODE.parse_file(caller.ndb.batch_pythonpath) new_data = BATCHCODE.parse_file(caller.ndb.batch_pythonpath)
else: else:
new_data = BATCHCMD.parse_file(caller.ndb.batch_pythonpath) new_data = BATCHCMD.parse_file(caller.ndb.batch_pythonpath)
caller.ndb.batch_stack = new_data caller.ndb.batch_stack = new_data
caller.msg(format_code("File reloaded. Staying on same command.")) caller.msg(format_code("File reloaded. Staying on same command."))
show_curr(caller) show_curr(caller)
@ -423,7 +423,7 @@ class CmdStateRRR(MuxCommand):
""" """
key = "rrr" key = "rrr"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -443,7 +443,7 @@ class CmdStateNN(MuxCommand):
""" """
key = "nn" key = "nn"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -464,7 +464,7 @@ class CmdStateNL(MuxCommand):
""" """
key = "nl" key = "nl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -485,7 +485,7 @@ class CmdStateBB(MuxCommand):
""" """
key = "bb" key = "bb"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -493,7 +493,7 @@ class CmdStateBB(MuxCommand):
if arg and arg.isdigit(): if arg and arg.isdigit():
step = -int(self.args) step = -int(self.args)
else: else:
step = -1 step = -1
step_pointer(caller, step) step_pointer(caller, step)
show_curr(caller) show_curr(caller)
@ -506,7 +506,7 @@ class CmdStateBL(MuxCommand):
""" """
key = "bl" key = "bl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -514,9 +514,9 @@ class CmdStateBL(MuxCommand):
if arg and arg.isdigit(): if arg and arg.isdigit():
step = -int(self.args) step = -int(self.args)
else: else:
step = -1 step = -1
step_pointer(caller, step) step_pointer(caller, step)
show_curr(caller, showall=True) show_curr(caller, showall=True)
class CmdStateSS(MuxCommand): class CmdStateSS(MuxCommand):
""" """
@ -528,7 +528,7 @@ class CmdStateSS(MuxCommand):
""" """
key = "ss" key = "ss"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -536,14 +536,14 @@ class CmdStateSS(MuxCommand):
if arg and arg.isdigit(): if arg and arg.isdigit():
step = int(self.args) step = int(self.args)
else: else:
step = 1 step = 1
for istep in range(step): for istep in range(step):
if caller.ndb.batch_batchmode == "batch_code": if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller) batch_code_exec(caller)
else: else:
batch_cmd_exec(caller) batch_cmd_exec(caller)
step_pointer(caller, 1) step_pointer(caller, 1)
show_curr(caller) show_curr(caller)
class CmdStateSL(MuxCommand): class CmdStateSL(MuxCommand):
@ -552,11 +552,11 @@ class CmdStateSL(MuxCommand):
Process current command, then step to the next Process current command, then step to the next
one, viewing its full source. If steps is given, one, viewing its full source. If steps is given,
process this many commands. process this many commands.
""" """
key = "sl" key = "sl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -564,7 +564,7 @@ class CmdStateSL(MuxCommand):
if arg and arg.isdigit(): if arg and arg.isdigit():
step = int(self.args) step = int(self.args)
else: else:
step = 1 step = 1
for istep in range(step): for istep in range(step):
if caller.ndb.batch_batchmode == "batch_code": if caller.ndb.batch_batchmode == "batch_code":
@ -583,7 +583,7 @@ class CmdStateCC(MuxCommand):
""" """
key = "cc" key = "cc"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -604,7 +604,7 @@ class CmdStateCC(MuxCommand):
del caller.ndb.batch_pythonpath del caller.ndb.batch_pythonpath
del caller.ndb.batch_batchmode del caller.ndb.batch_batchmode
caller.msg(format_code("Finished processing batch file.")) caller.msg(format_code("Finished processing batch file."))
class CmdStateJJ(MuxCommand): class CmdStateJJ(MuxCommand):
""" """
j <command number> j <command number>
@ -613,7 +613,7 @@ class CmdStateJJ(MuxCommand):
""" """
key = "j" key = "j"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -622,9 +622,9 @@ class CmdStateJJ(MuxCommand):
number = int(self.args)-1 number = int(self.args)-1
else: else:
caller.msg(format_code("You must give a number index.")) caller.msg(format_code("You must give a number index."))
return return
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
step = number - ptr step = number - ptr
step_pointer(caller, step) step_pointer(caller, step)
show_curr(caller) show_curr(caller)
@ -636,7 +636,7 @@ class CmdStateJL(MuxCommand):
""" """
key = "jl" key = "jl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -645,43 +645,43 @@ class CmdStateJL(MuxCommand):
number = int(self.args)-1 number = int(self.args)-1
else: else:
caller.msg(format_code("You must give a number index.")) caller.msg(format_code("You must give a number index."))
return return
ptr = caller.ndb.batch_stackptr ptr = caller.ndb.batch_stackptr
step = number - ptr step = number - ptr
step_pointer(caller, step) step_pointer(caller, step)
show_curr(caller, showall=True) show_curr(caller, showall=True)
class CmdStateQQ(MuxCommand): class CmdStateQQ(MuxCommand):
""" """
qq qq
Quit the batchprocessor. Quit the batchprocessor.
""" """
key = "qq" key = "qq"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
purge_processor(self.caller) purge_processor(self.caller)
self.caller.msg("Aborted interactive batch mode.") self.caller.msg("Aborted interactive batch mode.")
class CmdStateHH(MuxCommand): class CmdStateHH(MuxCommand):
"Help command" "Help command"
key = "hh" key = "hh"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
def func(self): def func(self):
string = """ string = """
Interactive batch processing commands: Interactive batch processing commands:
nn [steps] - next command (no processing) nn [steps] - next command (no processing)
nl [steps] - next & look nl [steps] - next & look
bb [steps] - back to previous command (no processing) bb [steps] - back to previous command (no processing)
bl [steps] - back & look bl [steps] - back & look
jj <N> - jump to command nr N (no processing) jj <N> - jump to command nr N (no processing)
jl <N> - jump & look jl <N> - jump & look
pp - process currently shown command (no step) pp - process currently shown command (no step)
ss [steps] - process & step ss [steps] - process & step
sl [steps] - process & step & look sl [steps] - process & step & look
@ -693,9 +693,9 @@ class CmdStateHH(MuxCommand):
cc - continue processing to end, then quit. cc - continue processing to end, then quit.
qq - quit (abort all remaining commands) qq - quit (abort all remaining commands)
@abort - this is a safety command that always is available @abort - this is a safety command that always is available
regardless of what cmdsets gets added to us during regardless of what cmdsets gets added to us during
batch-command processing. It immediately shuts down batch-command processing. It immediately shuts down
the processor and returns us to the default cmdset. the processor and returns us to the default cmdset.
""" """
self.caller.msg(string) self.caller.msg(string)

View file

@ -399,9 +399,8 @@ class CmdCreate(ObjManipCommand):
# object typeclass will automatically be used) # object typeclass will automatically be used)
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id) lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id)
obj = create.create_object(typeclass, name, caller, obj = create.create_object(typeclass, name, caller,
home=caller, aliases=aliases, locks=lockstring) home=caller, aliases=aliases, locks=lockstring, report_to=caller)
if not obj: if not obj:
string = "Error when creating object."
continue continue
if aliases: if aliases:
string = "You create a new %s: %s (aliases: %s)." string = "You create a new %s: %s (aliases: %s)."
@ -416,8 +415,8 @@ class CmdCreate(ObjManipCommand):
if caller.location: if caller.location:
obj.home = caller.location obj.home = caller.location
obj.move_to(caller.location, quiet=True) obj.move_to(caller.location, quiet=True)
if string: if string:
caller.msg(string) caller.msg(string)
class CmdDebug(MuxCommand): class CmdDebug(MuxCommand):
@ -630,7 +629,7 @@ class CmdDig(ObjManipCommand):
lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref) lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref)
new_room = create.create_object(typeclass, room["name"], new_room = create.create_object(typeclass, room["name"],
aliases=room["aliases"]) aliases=room["aliases"], report_to=caller)
new_room.locks.add(lockstring) new_room.locks.add(lockstring)
alias_string = "" alias_string = ""
if new_room.aliases: if new_room.aliases:
@ -659,7 +658,7 @@ class CmdDig(ObjManipCommand):
new_to_exit = create.create_object(typeclass, to_exit["name"], location, new_to_exit = create.create_object(typeclass, to_exit["name"], location,
aliases=to_exit["aliases"], aliases=to_exit["aliases"],
locks=lockstring, destination=new_room) locks=lockstring, destination=new_room, report_to=caller)
alias_string = "" alias_string = ""
if new_to_exit.aliases: if new_to_exit.aliases:
alias_string = " (%s)" % ", ".join(new_to_exit.aliases) alias_string = " (%s)" % ", ".join(new_to_exit.aliases)
@ -684,7 +683,7 @@ class CmdDig(ObjManipCommand):
typeclass = settings.BASE_EXIT_TYPECLASS typeclass = settings.BASE_EXIT_TYPECLASS
new_back_exit = create.create_object(typeclass, back_exit["name"], new_back_exit = create.create_object(typeclass, back_exit["name"],
new_room, aliases=back_exit["aliases"], new_room, aliases=back_exit["aliases"],
locks=lockstring, destination=location) locks=lockstring, destination=location, report_to=caller)
alias_string = "" alias_string = ""
if new_back_exit.aliases: if new_back_exit.aliases:
alias_string = " (%s)" % ", ".join(new_back_exit.aliases) alias_string = " (%s)" % ", ".join(new_back_exit.aliases)
@ -1083,7 +1082,7 @@ class CmdOpen(ObjManipCommand):
typeclass = settings.BASE_EXIT_TYPECLASS typeclass = settings.BASE_EXIT_TYPECLASS
exit_obj = create.create_object(typeclass, key=exit_name, exit_obj = create.create_object(typeclass, key=exit_name,
location=location, location=location,
aliases=exit_aliases) aliases=exit_aliases, report_to=caller)
if exit_obj: if exit_obj:
# storing a destination is what makes it an exit! # storing a destination is what makes it an exit!
exit_obj.destination = destination exit_obj.destination = destination
@ -1283,9 +1282,9 @@ class CmdTypeclass(MuxCommand):
@typeclass - set object typeclass @typeclass - set object typeclass
Usage: Usage:
@typclass[/switch] <object> [= <typeclass path>] @typclass[/switch] <object> [= <typeclass.path>]
@type '' @type ''
@parent '' @parent ''
Switch: Switch:
reset - clean out *all* the attributes on the object - reset - clean out *all* the attributes on the object -
@ -1295,12 +1294,18 @@ class CmdTypeclass(MuxCommand):
Example: Example:
@type button = examples.red_button.RedButton @type button = examples.red_button.RedButton
Sets an object's typeclass. The typeclass must be identified View or set an object's typeclass. If setting, the creation hooks
by its location using python dot-notation pointing to the correct of the new typeclass will be run on the object. If you have
module and class. If no typeclass is given (or a wrong typeclass clashing properties on the old class, use /reset. By default you
is given), the object will be set to the default typeclass. are protected from changing to a typeclass of the same name as the
The location of the typeclass module is searched from one you already have, use /force to override this protection.
the default typeclass directory, as defined in the server settings.
The given typeclass must be identified by its location using
python dot-notation pointing to the correct module and class. If
no typeclass is given (or a wrong typeclass is given). Errors in
the path or new typeclass will lead to the old typeclass being
kept. The location of the typeclass module is searched from the
default typeclass directory, as defined in the server settings.
""" """
@ -1327,7 +1332,7 @@ class CmdTypeclass(MuxCommand):
# we did not supply a new typeclass, view the # we did not supply a new typeclass, view the
# current one instead. # current one instead.
if hasattr(obj, "typeclass"): if hasattr(obj, "typeclass"):
string = "%s's current typeclass is '%s'." % (obj.name, obj.typeclass.typename) string = "%s's current typeclass is '%s' (%s)." % (obj.name, obj.typeclass.typename, obj.typeclass.path)
else: else:
string = "%s is not a typed object." % obj.name string = "%s is not a typed object." % obj.name
caller.msg(string) caller.msg(string)
@ -1344,25 +1349,31 @@ class CmdTypeclass(MuxCommand):
caller.msg("This object cannot have a type at all!") caller.msg("This object cannot have a type at all!")
return return
if obj.is_typeclass(typeclass) and not 'force' in self.switches: is_same = obj.is_typeclass(typeclass)
string = "%s already has the typeclass '%s'." % (obj.name, typeclass) if is_same and not 'force' in self.switches:
string = "%s already has the typeclass '%s'. Use /force to override." % (obj.name, typeclass)
else: else:
reset = "reset" in self.switches reset = "reset" in self.switches
old_typeclass_name = obj.typeclass.typename old_typeclass_path = obj.typeclass.path
ok = obj.swap_typeclass(typeclass, clean_attributes=reset) ok = obj.swap_typeclass(typeclass, clean_attributes=reset)
if ok: if ok:
string = "%s's type is now %s (instead of %s).\n" % (obj.name, if is_same:
obj.typeclass.typename, string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.typeclass.path)
old_typeclass_name)
if reset:
string += "All attributes where reset."
else: else:
string += "Note that the new class type could have overwritten " string = "%s's changed typeclass from %s to %s.\n" % (obj.name,
string += "same-named attributes on the existing object." old_typeclass_path,
obj.typeclass.path)
string += "Creation hooks were run."
if reset:
string += " All old attributes where deleted before the swap."
else:
string += " Note that the typeclassed object could have ended up with a mixture of old"
string += "\nand new attributes. Use /reset to remove old attributes if you don't want this."
else: else:
string = "Could not swap '%s' (%s) to typeclass '%s'." % (obj.name, string = obj.typeclass_last_errmsg
old_typeclass_name, string += "\nCould not swap '%s' (%s) to typeclass '%s'." % (obj.name,
typeclass) old_typeclass_path,
typeclass)
caller.msg(string) caller.msg(string)

View file

@ -2,9 +2,9 @@
Comsystem command module. Comsystem command module.
Comm commands are OOC commands and intended to be made available to Comm commands are OOC commands and intended to be made available to
the Player at all times (they go into the PlayerCmdSet). So we the Player at all times (they go into the PlayerCmdSet). So we
make sure to homogenize self.caller to always be the player object make sure to homogenize self.caller to always be the player object
for easy handling. for easy handling.
""" """
from django.conf import settings from django.conf import settings
@ -12,7 +12,7 @@ from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChan
from src.comms import irc, imc2, rss from src.comms import irc, imc2, rss
from src.comms.channelhandler import CHANNELHANDLER from src.comms.channelhandler import CHANNELHANDLER
from src.utils import create, utils from src.utils import create, utils
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# limit symbol import for API # limit symbol import for API
__all__ = ("CommCommand", "CmdAddCom", "CmdDelCom", "CmdAllCom", __all__ = ("CommCommand", "CmdAddCom", "CmdDelCom", "CmdAllCom",
@ -45,10 +45,10 @@ def find_channel(caller, channelname, silent=False, noaliases=False):
class CommCommand(MuxCommand): class CommCommand(MuxCommand):
""" """
This is a parent for comm-commands. Since This is a parent for comm-commands. Since
These commands are to be available to the These commands are to be available to the
Player, we make sure to homogenize the caller Player, we make sure to homogenize the caller
here, so it's always seen as a player to the here, so it's always seen as a player to the
command body. command body.
""" """
def parse(self): def parse(self):
@ -60,14 +60,14 @@ class CommCommand(MuxCommand):
if utils.inherits_from(self.caller, "src.objects.objects.Object"): if utils.inherits_from(self.caller, "src.objects.objects.Object"):
# an object. Convert it to its player. # an object. Convert it to its player.
self.caller = self.caller.player self.caller = self.caller.player
class CmdAddCom(MuxCommand): class CmdAddCom(MuxCommand):
""" """
addcom - subscribe to a channel with optional alias addcom - subscribe to a channel with optional alias
Usage: Usage:
addcom [alias=] <channel> addcom [alias=] <channel>
Joins a given channel. If alias is given, this will allow you to Joins a given channel. If alias is given, this will allow you to
refer to the channel by this alias rather than the full channel refer to the channel by this alias rather than the full channel
name. Subsequent calls of this command can be used to add multiple name. Subsequent calls of this command can be used to add multiple
@ -81,7 +81,7 @@ class CmdAddCom(MuxCommand):
def func(self): def func(self):
"Implement the command" "Implement the command"
caller = self.caller caller = self.caller
args = self.args args = self.args
player = caller player = caller
@ -92,7 +92,7 @@ class CmdAddCom(MuxCommand):
if self.rhs: if self.rhs:
# rhs holds the channelname # rhs holds the channelname
channelname = self.rhs channelname = self.rhs
alias = self.lhs alias = self.lhs
else: else:
channelname = self.args channelname = self.args
@ -101,12 +101,12 @@ class CmdAddCom(MuxCommand):
channel = find_channel(caller, channelname) channel = find_channel(caller, channelname)
if not channel: if not channel:
# we use the custom search method to handle errors. # we use the custom search method to handle errors.
return return
# check permissions # check permissions
if not channel.access(player, 'listen'): if not channel.access(player, 'listen'):
caller.msg("%s: You are not allowed to listen to this channel." % channel.key) caller.msg("%s: You are not allowed to listen to this channel." % channel.key)
return return
string = "" string = ""
if not channel.has_connection(player): if not channel.has_connection(player):
@ -114,16 +114,16 @@ class CmdAddCom(MuxCommand):
if not channel.connect_to(player): if not channel.connect_to(player):
# if this would have returned True, the player is connected # if this would have returned True, the player is connected
caller.msg("%s: You are not allowed to join this channel." % channel.key) caller.msg("%s: You are not allowed to join this channel." % channel.key)
return return
else: else:
string += "You now listen to the channel %s. " % channel.key string += "You now listen to the channel %s. " % channel.key
else: else:
string += "You are already connected to channel %s." % channel.key string += "You are already connected to channel %s." % channel.key
if alias: if alias:
# create a nick and add it to the caller. # create a nick and add it to the caller.
caller.nicks.add(alias, channel.key, nick_type="channel") caller.nicks.add(alias, channel.key, nick_type="channel")
string += " You can now refer to the channel %s with the alias '%s'." string += " You can now refer to the channel %s with the alias '%s'."
caller.msg(string % (channel.key, alias)) caller.msg(string % (channel.key, alias))
else: else:
string += " No alias added." string += " No alias added."
@ -155,26 +155,26 @@ class CmdDelCom(MuxCommand):
if not self.args: if not self.args:
caller.msg("Usage: delcom <alias or channel>") caller.msg("Usage: delcom <alias or channel>")
return return
ostring = self.args.lower() ostring = self.args.lower()
channel = find_channel(caller, ostring, silent=True, noaliases=True) channel = find_channel(caller, ostring, silent=True, noaliases=True)
if channel: if channel:
# we have given a channel name - unsubscribe # we have given a channel name - unsubscribe
if not channel.has_connection(player): if not channel.has_connection(player):
caller.msg("You are not listening to that channel.") caller.msg("You are not listening to that channel.")
return return
chkey = channel.key.lower() chkey = channel.key.lower()
# find all nicks linked to this channel and delete them # find all nicks linked to this channel and delete them
for nick in [nick for nick in caller.nicks.get(nick_type="channel") for nick in [nick for nick in caller.nicks.get(nick_type="channel")
if nick.db_real.lower() == chkey]: if nick.db_real.lower() == chkey]:
nick.delete() nick.delete()
channel.disconnect_from(player) channel.disconnect_from(player)
caller.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key) caller.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key)
return return
else: else:
# we are removing a channel nick # we are removing a channel nick
channame = caller.nicks.get(ostring, nick_type="channel") channame = caller.nicks.get(ostring, nick_type="channel")
channel = find_channel(caller, channame, silent=True) channel = find_channel(caller, channame, silent=True)
if not channel: if not channel:
caller.msg("No channel with alias '%s' was found." % ostring) caller.msg("No channel with alias '%s' was found." % ostring)
@ -184,13 +184,13 @@ class CmdDelCom(MuxCommand):
caller.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key)) caller.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key))
else: else:
caller.msg("You had no such alias defined for this channel.") caller.msg("You had no such alias defined for this channel.")
class CmdAllCom(MuxCommand): class CmdAllCom(MuxCommand):
""" """
allcom - operate on all channels allcom - operate on all channels
Usage: Usage:
allcom [on | off | who | destroy] allcom [on | off | who | destroy]
Allows the user to universally turn off or on all channels they are on, 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. Destroy deletes as well as perform a 'who' for all channels they are on. Destroy deletes
@ -198,11 +198,11 @@ class CmdAllCom(MuxCommand):
Without argument, works like comlist. Without argument, works like comlist.
""" """
key = "allcom" key = "allcom"
locks = "cmd: not pperm(channel_banned)" locks = "cmd: not pperm(channel_banned)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
"Runs the function" "Runs the function"
@ -224,12 +224,12 @@ class CmdAllCom(MuxCommand):
for channel in channels: for channel in channels:
caller.execute_cmd("delcom %s" % channel.key) caller.execute_cmd("delcom %s" % channel.key)
elif args == "destroy": elif args == "destroy":
# destroy all channels you control # destroy all channels you control
channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'control')] channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'control')]
for channel in channels: for channel in channels:
caller.execute_cmd("@cdestroy %s" % channel.key) caller.execute_cmd("@cdestroy %s" % channel.key)
elif args == "who": elif args == "who":
# run a who, listing the subscribers on visible channels. # run a who, listing the subscribers on visible channels.
string = "\n{CChannel subscriptions{n" string = "\n{CChannel subscriptions{n"
channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'listen')] channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'listen')]
if not channels: if not channels:
@ -243,7 +243,7 @@ class CmdAllCom(MuxCommand):
string += " <None>" string += " <None>"
caller.msg(string.strip()) caller.msg(string.strip())
else: else:
# wrong input # wrong input
caller.msg("Usage: allcom on | off | who | clear") caller.msg("Usage: allcom on | off | who | clear")
class CmdChannels(MuxCommand): class CmdChannels(MuxCommand):
@ -255,7 +255,7 @@ class CmdChannels(MuxCommand):
@clist @clist
comlist comlist
Lists all channels available to you, wether you listen to them or not. Lists all channels available to you, wether you listen to them or not.
Use 'comlist" to only view your current channel subscriptions. Use 'comlist" to only view your current channel subscriptions.
""" """
key = "@channels" key = "@channels"
@ -265,11 +265,11 @@ class CmdChannels(MuxCommand):
def func(self): def func(self):
"Implement function" "Implement function"
caller = self.caller caller = self.caller
# all channels we have available to listen to # all channels we have available to listen to
channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'listen')] channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'listen')]
if not channels: if not channels:
caller.msg("No channels available.") caller.msg("No channels available.")
return return
@ -278,7 +278,7 @@ class CmdChannels(MuxCommand):
if self.cmdstring != "comlist": if self.cmdstring != "comlist":
string = "\nChannels available:" string = "\nChannels available:"
cols = [[" "], ["Channel"], ["Aliases"], ["Perms"], ["Description"]] cols = [[" "], ["Channel"], ["Aliases"], ["Perms"], ["Description"]]
for chan in channels: for chan in channels:
if chan in subs: if chan in subs:
@ -289,10 +289,10 @@ class CmdChannels(MuxCommand):
cols[2].append(",".join(chan.aliases)) cols[2].append(",".join(chan.aliases))
cols[3].append(str(chan.locks)) cols[3].append(str(chan.locks))
cols[4].append(chan.desc) cols[4].append(chan.desc)
# put into table # put into table
for ir, row in enumerate(utils.format_table(cols)): for ir, row in enumerate(utils.format_table(cols)):
if ir == 0: if ir == 0:
string += "\n{w" + "".join(row) + "{n" string += "\n{w" + "".join(row) + "{n"
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
self.caller.msg(string) self.caller.msg(string)
@ -306,13 +306,13 @@ class CmdChannels(MuxCommand):
for chan in subs: for chan in subs:
cols[0].append(" ") cols[0].append(" ")
cols[1].append(chan.key) cols[1].append(chan.key)
cols[2].append(",".join([nick.db_nick for nick in nicks cols[2].append(",".join([nick.db_nick for nick in nicks
if nick.db_real.lower() == chan.key.lower()] + chan.aliases)) if nick.db_real.lower() == chan.key.lower()] + chan.aliases))
cols[3].append(chan.desc) cols[3].append(chan.desc)
# put into table # put into table
for ir, row in enumerate(utils.format_table(cols)): for ir, row in enumerate(utils.format_table(cols)):
if ir == 0: if ir == 0:
string += "\n{w" + "".join(row) + "{n" string += "\n{w" + "".join(row) + "{n"
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
caller.msg(string) caller.msg(string)
@ -341,10 +341,10 @@ class CmdCdestroy(MuxCommand):
channel = find_channel(caller, self.args) channel = find_channel(caller, self.args)
if not channel: if not channel:
caller.msg("Could not find channel %s." % self.args) caller.msg("Could not find channel %s." % self.args)
return return
if not channel.access(caller, 'control'): if not channel.access(caller, 'control'):
caller.msg("You are not allowed to do that.") caller.msg("You are not allowed to do that.")
return return
message = "%s is being destroyed. Make sure to change your aliases." % channel message = "%s is being destroyed. Make sure to change your aliases." % channel
msgobj = create.create_message(caller, message, channel) msgobj = create.create_message(caller, message, channel)
@ -352,7 +352,7 @@ class CmdCdestroy(MuxCommand):
channel.delete() channel.delete()
CHANNELHANDLER.update() CHANNELHANDLER.update()
caller.msg("%s was destroyed." % channel) caller.msg("%s was destroyed." % channel)
class CmdCBoot(MuxCommand): class CmdCBoot(MuxCommand):
""" """
@cboot @cboot
@ -370,24 +370,24 @@ class CmdCBoot(MuxCommand):
key = "@cboot" key = "@cboot"
locks = "cmd: not pperm(channel_banned)" locks = "cmd: not pperm(channel_banned)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
"implement the function" "implement the function"
if not self.args or not self.rhs: if not self.args or not self.rhs:
string = "Usage: @cboot[/quiet] <channel> = <player> [:reason]" string = "Usage: @cboot[/quiet] <channel> = <player> [:reason]"
self.caller.msg(string) self.caller.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = find_channel(self.caller, self.lhs)
if not channel: if not channel:
return return
reason = "" reason = ""
if ":" in self.rhs: if ":" in self.rhs:
playername, reason = self.rhs.rsplit(":", 1) playername, reason = self.rhs.rsplit(":", 1)
searchstring = playername.lstrip('*') searchstring = playername.lstrip('*')
else: else:
searchstring = self.rhs.lstrip('*') searchstring = self.rhs.lstrip('*')
player = self.caller.search(searchstring, player=True) player = self.caller.search(searchstring, player=True)
if not player: if not player:
return return
@ -396,7 +396,7 @@ class CmdCBoot(MuxCommand):
if not channel.access(self.caller, "control"): if not channel.access(self.caller, "control"):
string = "You don't control this channel." string = "You don't control this channel."
self.caller.msg(string) self.caller.msg(string)
return return
if not PlayerChannelConnection.objects.has_connection(player, channel): if not PlayerChannelConnection.objects.has_connection(player, channel):
string = "Player %s is not connected to channel %s." % (player.key, channel.key) string = "Player %s is not connected to channel %s." % (player.key, channel.key)
self.caller.msg(string) self.caller.msg(string)
@ -405,10 +405,10 @@ class CmdCBoot(MuxCommand):
string = "%s boots %s from channel.%s" % (self.caller, player.key, reason) string = "%s boots %s from channel.%s" % (self.caller, player.key, reason)
channel.msg(string) channel.msg(string)
# find all player's nicks linked to this channel and delete them # find all player's nicks linked to this channel and delete them
for nick in [nick for nick in player.character.nicks.get(nick_type="channel") for nick in [nick for nick in player.character.nicks.get(nick_type="channel")
if nick.db_real.lower() == channel.key]: if nick.db_real.lower() == channel.key]:
nick.delete() nick.delete()
# disconnect player # disconnect player
channel.disconnect_from(player) channel.disconnect_from(player)
class CmdCemit(MuxCommand): class CmdCemit(MuxCommand):
@ -426,9 +426,9 @@ class CmdCemit(MuxCommand):
Allows the user to broadcast a message over a channel as long as Allows the user to broadcast a message over a channel as long as
they control it. It does not show the user's name unless they they control it. It does not show the user's name unless they
provide the /sendername switch. provide the /sendername switch.
""" """
key = "@cemit" key = "@cemit"
aliases = ["@cmsg"] aliases = ["@cmsg"]
locks = "cmd: not pperm(channel_banned)" locks = "cmd: not pperm(channel_banned)"
@ -440,18 +440,18 @@ class CmdCemit(MuxCommand):
if not self.args or not self.rhs: if not self.args or not self.rhs:
string = "Usage: @cemit[/switches] <channel> = <message>" string = "Usage: @cemit[/switches] <channel> = <message>"
self.caller.msg(string) self.caller.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = find_channel(self.caller, self.lhs)
if not channel: if not channel:
return return
if not channel.access(self.caller, "control"): if not channel.access(self.caller, "control"):
string = "You don't control this channel." string = "You don't control this channel."
self.caller.msg(string) self.caller.msg(string)
return return
message = self.rhs message = self.rhs
if "sendername" in self.switches: if "sendername" in self.switches:
message = "%s: %s" % (self.caller.key, message) message = "%s: %s" % (self.caller.key, message)
if not "noheader" in self.switches: if not "noheader" in self.switches:
message = "[%s] %s" % (channel.key, message) message = "[%s] %s" % (channel.key, message)
channel.msg(message) channel.msg(message)
if not "quiet" in self.switches: if not "quiet" in self.switches:
@ -461,8 +461,8 @@ class CmdCemit(MuxCommand):
class CmdCWho(MuxCommand): class CmdCWho(MuxCommand):
""" """
@cwho @cwho
Usage: Usage:
@cwho <channel> @cwho <channel>
List who is connected to a given channel you have access to. List who is connected to a given channel you have access to.
@ -473,18 +473,18 @@ class CmdCWho(MuxCommand):
def func(self): def func(self):
"implement function" "implement function"
if not self.args: if not self.args:
string = "Usage: @cwho <channel>" string = "Usage: @cwho <channel>"
self.caller.msg(string) self.caller.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = find_channel(self.caller, self.lhs)
if not channel: if not channel:
return return
if not channel.access(self.caller, "listen"): if not channel.access(self.caller, "listen"):
string = "You can't access this channel." string = "You can't access this channel."
self.caller.msg(string) self.caller.msg(string)
string = "\n{CChannel subscriptions{n" string = "\n{CChannel subscriptions{n"
string += "\n{w%s:{n\n" % channel.key string += "\n{w%s:{n\n" % channel.key
conns = PlayerChannelConnection.objects.get_all_connections(channel) conns = PlayerChannelConnection.objects.get_all_connections(channel)
@ -497,13 +497,13 @@ class CmdCWho(MuxCommand):
class CmdChannelCreate(MuxCommand): class CmdChannelCreate(MuxCommand):
""" """
@ccreate @ccreate
channelcreate channelcreate
Usage: Usage:
@ccreate <new channel>[;alias;alias...] = description @ccreate <new channel>[;alias;alias...] = description
Creates a new channel owned by you. Creates a new channel owned by you.
""" """
key = "@ccreate" key = "@ccreate"
aliases = "channelcreate" aliases = "channelcreate"
locks = "cmd:not pperm(channel_banned)" locks = "cmd:not pperm(channel_banned)"
@ -517,7 +517,7 @@ class CmdChannelCreate(MuxCommand):
if not self.args: if not self.args:
caller.msg("Usage @ccreate <channelname>[;alias;alias..] = description") caller.msg("Usage @ccreate <channelname>[;alias;alias..] = description")
return return
description = "" description = ""
if self.rhs: if self.rhs:
@ -526,25 +526,25 @@ class CmdChannelCreate(MuxCommand):
channame = lhs channame = lhs
aliases = None aliases = None
if ';' in lhs: if ';' in lhs:
channame, aliases = [part.strip().lower() channame, aliases = [part.strip().lower()
for part in lhs.split(';', 1) if part.strip()] for part in lhs.split(';', 1) if part.strip()]
aliases = [alias.strip().lower() aliases = [alias.strip().lower()
for alias in aliases.split(';') if alias.strip()] for alias in aliases.split(';') if alias.strip()]
channel = Channel.objects.channel_search(channame) channel = Channel.objects.channel_search(channame)
if channel: if channel:
caller.msg("A channel with that name already exists.") caller.msg("A channel with that name already exists.")
return return
# Create and set the channel up # Create and set the channel up
lockstring = "send:all();listen:all();control:id(%s)" % caller.id lockstring = "send:all();listen:all();control:id(%s)" % caller.id
new_chan = create.create_channel(channame, aliases, description, locks=lockstring) new_chan = create.create_channel(channame, aliases, description, locks=lockstring)
new_chan.connect_to(caller) new_chan.connect_to(caller)
caller.msg("Created channel %s and connected to it." % new_chan.key) caller.msg("Created channel %s and connected to it." % new_chan.key)
class CmdCset(MuxCommand): class CmdCset(MuxCommand):
""" """
@cset - changes channel access restrictions @cset - changes channel access restrictions
Usage: Usage:
@cset <channel> [= <lockstring>] @cset <channel> [= <lockstring>]
@ -559,26 +559,26 @@ class CmdCset(MuxCommand):
def func(self): def func(self):
"run the function" "run the function"
if not self.args: if not self.args:
string = "Usage: @cset channel [= lockstring]" string = "Usage: @cset channel [= lockstring]"
self.caller.msg(string) self.caller.msg(string)
return return
channel = find_channel(self.caller, self.lhs) channel = find_channel(self.caller, self.lhs)
if not channel: if not channel:
return return
if not self.rhs: if not self.rhs:
# no =, so just view the current locks # no =, so just view the current locks
string = "Current locks on %s:" % channel.key string = "Current locks on %s:" % channel.key
string = "%s\n %s" % (string, channel.locks) string = "%s\n %s" % (string, channel.locks)
self.caller.msg(string) self.caller.msg(string)
return return
# we want to add/change a lock. # we want to add/change a lock.
if not channel.access(self.caller, "control"): if not channel.access(self.caller, "control"):
string = "You don't control this channel." string = "You don't control this channel."
self.caller.msg(string) self.caller.msg(string)
return return
# Try to add the lock # Try to add the lock
channel.locks.add(self.rhs) channel.locks.add(self.rhs)
string = "Lock(s) applied. " string = "Lock(s) applied. "
@ -595,7 +595,7 @@ class CmdCdesc(MuxCommand):
@cdesc <channel> = <description> @cdesc <channel> = <description>
Changes the description of the channel as shown in Changes the description of the channel as shown in
channel lists. channel lists.
""" """
key = "@cdesc" key = "@cdesc"
@ -604,12 +604,12 @@ class CmdCdesc(MuxCommand):
def func(self): def func(self):
"Implement command" "Implement command"
caller = self.caller caller = self.caller
if not self.rhs: if not self.rhs:
caller.msg("Usage: @cdesc <channel> = <description>") caller.msg("Usage: @cdesc <channel> = <description>")
return return
channel = find_channel(caller, self.lhs) channel = find_channel(caller, self.lhs)
if not channel: if not channel:
caller.msg("Channel '%s' not found." % self.lhs) caller.msg("Channel '%s' not found." % self.lhs)
@ -635,7 +635,7 @@ class CmdPage(MuxCommand):
Switch: Switch:
last - shows who you last messaged last - shows who you last messaged
list - show your last <number> of tells/pages (default) list - show your last <number> of tells/pages (default)
Send a message to target user (if online). If no Send a message to target user (if online). If no
argument is given, you will get a list of your latest messages. argument is given, you will get a list of your latest messages.
""" """
@ -644,24 +644,24 @@ class CmdPage(MuxCommand):
aliases = ['tell'] aliases = ['tell']
locks = "cmd:not pperm(page_banned)" locks = "cmd:not pperm(page_banned)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
"Implement function using the Msg methods" "Implement function using the Msg methods"
caller = self.caller caller = self.caller
player = caller player = caller
# get the messages we've sent # get the messages we've sent
messages_we_sent = list(Msg.objects.get_messages_by_sender(player)) messages_we_sent = list(Msg.objects.get_messages_by_sender(player))
pages_we_sent = [msg for msg in messages_we_sent pages_we_sent = [msg for msg in messages_we_sent
if msg.receivers] if msg.receivers]
# get last messages we've got # get last messages we've got
pages_we_got = list(Msg.objects.get_messages_by_receiver(player)) pages_we_got = list(Msg.objects.get_messages_by_receiver(player))
if 'last' in self.switches: if 'last' in self.switches:
if pages_we_sent: if pages_we_sent:
string = "You last paged {c%s{n." % (", ".join([obj.name string = "You last paged {c%s{n." % (", ".join([obj.name
for obj in pages_we_sent[-1].receivers])) for obj in pages_we_sent[-1].receivers]))
caller.msg(string) caller.msg(string)
return return
@ -680,15 +680,15 @@ class CmdPage(MuxCommand):
number = int(self.args) number = int(self.args)
except ValueError: except ValueError:
caller.msg("Usage: tell [<player> = msg]") caller.msg("Usage: tell [<player> = msg]")
return return
if len(pages) > number: if len(pages) > number:
lastpages = pages[-number:] lastpages = pages[-number:]
else: else:
lastpages = pages lastpages = pages
lastpages = "\n ".join(["{w%s{n {c%s{n to {c%s{n: %s" % (utils.datetime_format(page.date_sent), lastpages = "\n ".join(["{w%s{n {c%s{n to {c%s{n: %s" % (utils.datetime_format(page.date_sent),
page.sender.name, page.sender.name,
"{n,{c ".join([obj.name for obj in page.receivers]), "{n,{c ".join([obj.name for obj in page.receivers]),
page.message) page.message)
for page in lastpages]) for page in lastpages])
@ -704,16 +704,16 @@ class CmdPage(MuxCommand):
# We are sending. Build a list of targets # We are sending. Build a list of targets
if not self.lhs: if not self.lhs:
# If there are no targets, then set the targets # If there are no targets, then set the targets
# to the last person they paged. # to the last person they paged.
if pages_we_sent: if pages_we_sent:
receivers = pages_we_sent[-1].receivers receivers = pages_we_sent[-1].receivers
else: else:
caller.msg("Who do you want to page?") caller.msg("Who do you want to page?")
return return
else: else:
receivers = self.lhslist receivers = self.lhslist
recobjs = [] recobjs = []
for receiver in set(receivers): for receiver in set(receivers):
if isinstance(receiver, basestring): if isinstance(receiver, basestring):
@ -724,22 +724,22 @@ class CmdPage(MuxCommand):
pobj = receiver.character pobj = receiver.character
else: else:
caller.msg("Who do you want to page?") caller.msg("Who do you want to page?")
return return
recobjs.append(pobj) recobjs.append(pobj)
if not recobjs: if not recobjs:
caller.msg("No players matching your target were found.") caller.msg("No players matching your target were found.")
return return
header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key
message = self.rhs message = self.rhs
# if message begins with a :, we assume it is a 'page-pose' # if message begins with a :, we assume it is a 'page-pose'
if message.startswith(":"): if message.startswith(":"):
message = "%s %s" % (caller.key, message.strip(':').strip()) message = "%s %s" % (caller.key, message.strip(':').strip())
# create the persistent message object # create the persistent message object
msg = create.create_message(player, message, msg = create.create_message(player, message,
receivers=recobjs) receivers=recobjs)
# tell the players they got a message. # tell the players they got a message.
received = [] received = []
@ -747,8 +747,8 @@ class CmdPage(MuxCommand):
for pobj in recobjs: for pobj in recobjs:
if not pobj.access(caller, 'msg'): if not pobj.access(caller, 'msg'):
rstrings.append("You are not allowed to page %s." % pobj) rstrings.append("You are not allowed to page %s." % pobj)
continue continue
pobj.msg("%s %s" % (header, message)) pobj.msg("%s %s" % (header, message))
if hasattr(pobj, 'has_player') and not pobj.has_player: if hasattr(pobj, 'has_player') and not pobj.has_player:
received.append("{C%s{n" % pobj.name) received.append("{C%s{n" % pobj.name)
rstrings.append("%s is offline. They will see your message if they list their pages later." % received[-1]) rstrings.append("%s is offline. They will see your message if they list their pages later." % received[-1])
@ -768,19 +768,19 @@ class CmdIRC2Chan(MuxCommand):
Switches: Switches:
/disconnect - this will delete the bot and remove the irc connection to the channel. /disconnect - this will delete the bot and remove the irc connection to the channel.
/remove - " /remove - "
/list - show all irc<->evennia mappings /list - show all irc<->evennia mappings
Example: Example:
@irc2chan myircchan = irc.dalnet.net 6667 myevennia-channel evennia-bot @irc2chan myircchan = irc.dalnet.net 6667 myevennia-channel evennia-bot
This creates an IRC bot that connects to a given IRC network and channel. It will This creates an IRC bot that connects to a given IRC network and channel. It will
relay everything said in the evennia channel to the IRC channel and vice versa. The relay everything said in the evennia channel to the IRC channel and vice versa. The
bot will automatically connect at server start, so this comman need only be given once. bot will automatically connect at server start, so this comman need only be given once.
The /disconnect switch will permanently delete the bot. To only temporarily deactivate it, The /disconnect switch will permanently delete the bot. To only temporarily deactivate it,
use the @services command instead. use the @services command instead.
""" """
key = "@irc2chan" key = "@irc2chan"
locks = "cmd:serversetting(IRC_ENABLED) and pperm(Immortals)" locks = "cmd:serversetting(IRC_ENABLED) and pperm(Immortals)"
help_category = "Comms" help_category = "Comms"
@ -811,21 +811,21 @@ class CmdIRC2Chan(MuxCommand):
self.caller.msg(string) self.caller.msg(string)
else: else:
self.caller.msg("No connections found.") self.caller.msg("No connections found.")
return return
if not self.args or not self.rhs: if not self.args or not self.rhs:
string = "Usage: @irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>" string = "Usage: @irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>"
self.caller.msg(string) self.caller.msg(string)
return return
channel = self.lhs channel = self.lhs
self.rhs = self.rhs.replace('#', ' ') # to avoid Python comment issues self.rhs = self.rhs.replace('#', ' ') # to avoid Python comment issues
try: try:
irc_network, irc_port, irc_channel, irc_botname = [part.strip() for part in self.rhs.split(None, 3)] irc_network, irc_port, irc_channel, irc_botname = [part.strip() for part in self.rhs.split(None, 3)]
irc_channel = "#%s" % irc_channel irc_channel = "#%s" % irc_channel
except Exception: except Exception:
string = "IRC bot definition '%s' is not valid." % self.rhs string = "IRC bot definition '%s' is not valid." % self.rhs
self.caller.msg(string) self.caller.msg(string)
return return
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
chanmatch = find_channel(self.caller, channel, silent=True) chanmatch = find_channel(self.caller, channel, silent=True)
@ -837,7 +837,7 @@ class CmdIRC2Chan(MuxCommand):
self.caller.msg("IRC connection/bot could not be removed, does it exist?") self.caller.msg("IRC connection/bot could not be removed, does it exist?")
else: else:
self.caller.msg("IRC connection destroyed.") self.caller.msg("IRC connection destroyed.")
return return
channel = find_channel(self.caller, channel) channel = find_channel(self.caller, channel)
if not channel: if not channel:
@ -845,7 +845,7 @@ class CmdIRC2Chan(MuxCommand):
ok = irc.create_connection(channel, irc_network, irc_port, irc_channel, irc_botname) ok = irc.create_connection(channel, irc_network, irc_port, irc_channel, irc_botname)
if not ok: if not ok:
self.caller.msg("This IRC connection already exists.") self.caller.msg("This IRC connection already exists.")
return return
self.caller.msg("Connection created. Starting IRC bot.") self.caller.msg("Connection created. Starting IRC bot.")
class CmdIMC2Chan(MuxCommand): class CmdIMC2Chan(MuxCommand):
@ -857,12 +857,12 @@ class CmdIMC2Chan(MuxCommand):
Switches: Switches:
/disconnect - this clear the imc2 connection to the channel. /disconnect - this clear the imc2 connection to the channel.
/remove - " /remove - "
/list - show all imc2<->evennia mappings /list - show all imc2<->evennia mappings
Example: Example:
@imc2chan myimcchan = ievennia @imc2chan myimcchan = ievennia
Connect an existing evennia channel to a channel on an IMC2 Connect an existing evennia channel to a channel on an IMC2
network. The network contact information is defined in settings and network. The network contact information is defined in settings and
should already be accessed at this point. Use @imcchanlist to see should already be accessed at this point. Use @imcchanlist to see
@ -901,27 +901,27 @@ class CmdIMC2Chan(MuxCommand):
self.caller.msg(string) self.caller.msg(string)
else: else:
self.caller.msg("No connections found.") self.caller.msg("No connections found.")
return return
if not self.args or not self.rhs: if not self.args or not self.rhs:
string = "Usage: @imc2chan[/switches] <evennia_channel> = <imc2_channel>" string = "Usage: @imc2chan[/switches] <evennia_channel> = <imc2_channel>"
self.caller.msg(string) self.caller.msg(string)
return return
channel = self.lhs channel = self.lhs
imc2_channel = self.rhs imc2_channel = self.rhs
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches:
# we don't search for channels before this since we want to clear the link # we don't search for channels before this since we want to clear the link
# also if the channel no longer exists. # also if the channel no longer exists.
ok = imc2.delete_connection(channel, imc2_channel) ok = imc2.delete_connection(channel, imc2_channel)
if not ok: if not ok:
self.caller.msg("IMC2 connection could not be removed, does it exist?") self.caller.msg("IMC2 connection could not be removed, does it exist?")
else: else:
self.caller.msg("IMC2 connection destroyed.") self.caller.msg("IMC2 connection destroyed.")
return return
# actually get the channel object # actually get the channel object
channel = find_channel(self.caller, channel) channel = find_channel(self.caller, channel)
if not channel: if not channel:
return return
@ -929,7 +929,7 @@ class CmdIMC2Chan(MuxCommand):
ok = imc2.create_connection(channel, imc2_channel) ok = imc2.create_connection(channel, imc2_channel)
if not ok: if not ok:
self.caller.msg("The connection %s <-> %s already exists." % (channel.key, imc2_channel)) self.caller.msg("The connection %s <-> %s already exists." % (channel.key, imc2_channel))
return return
self.caller.msg("Created connection channel %s <-> IMC channel %s." % (channel.key, imc2_channel)) self.caller.msg("Created connection channel %s <-> IMC channel %s." % (channel.key, imc2_channel))
@ -940,18 +940,18 @@ class CmdIMCInfo(MuxCommand):
Usage: Usage:
@imcinfo[/switches] @imcinfo[/switches]
@imcchanlist - list imc2 channels @imcchanlist - list imc2 channels
@imclist - list connected muds @imclist - list connected muds
@imcwhois <playername> - whois info about a remote player @imcwhois <playername> - whois info about a remote player
Switches for @imcinfo: Switches for @imcinfo:
channels - as @imcchanlist (default) channels - as @imcchanlist (default)
games or muds - as @imclist games or muds - as @imclist
whois - as @imcwhois (requires an additional argument) whois - as @imcwhois (requires an additional argument)
update - force an update of all lists update - force an update of all lists
Shows lists of games or channels on the IMC2 network. Shows lists of games or channels on the IMC2 network.
""" """
key = "@imcinfo" key = "@imcinfo"
aliases = ["@imcchanlist", "@imclist", "@imcwhois"] aliases = ["@imcchanlist", "@imclist", "@imcwhois"]
locks = "cmd: serversetting(IMC2_ENABLED) and pperm(Wizards)" locks = "cmd: serversetting(IMC2_ENABLED) and pperm(Wizards)"
@ -966,18 +966,18 @@ class CmdIMCInfo(MuxCommand):
return return
if "update" in self.switches: if "update" in self.switches:
# update the lists # update the lists
import time import time
from src.comms.imc2lib import imc2_packets as pck from src.comms.imc2lib import imc2_packets as pck
from src.comms.imc2 import IMC2_MUDLIST, IMC2_CHANLIST, IMC2_CLIENT from src.comms.imc2 import IMC2_MUDLIST, IMC2_CHANLIST, IMC2_CLIENT
# update connected muds # update connected muds
IMC2_CLIENT.send_packet(pck.IMC2PacketKeepAliveRequest()) IMC2_CLIENT.send_packet(pck.IMC2PacketKeepAliveRequest())
# prune inactive muds # prune inactive muds
for name, mudinfo in IMC2_MUDLIST.mud_list.items(): for name, mudinfo in IMC2_MUDLIST.mud_list.items():
if time.time() - mudinfo.last_updated > 3599: if time.time() - mudinfo.last_updated > 3599:
del IMC2_MUDLIST.mud_list[name] del IMC2_MUDLIST.mud_list[name]
# update channel list # update channel list
IMC2_CLIENT.send_packet(pck.IMC2PacketIceRefresh()) IMC2_CLIENT.send_packet(pck.IMC2PacketIceRefresh())
self.caller.msg("IMC2 lists were re-synced.") self.caller.msg("IMC2 lists were re-synced.")
elif "games" in self.switches or "muds" in self.switches or self.cmdstring == "@imclist": elif "games" in self.switches or "muds" in self.switches or self.cmdstring == "@imclist":
@ -999,24 +999,24 @@ class CmdIMCInfo(MuxCommand):
cols[3].append(mud.port) cols[3].append(mud.port)
ftable = utils.format_table(cols) ftable = utils.format_table(cols)
for ir, row in enumerate(ftable): for ir, row in enumerate(ftable):
if ir == 0: if ir == 0:
string += "\n{w" + "".join(row) + "{n" string += "\n{w" + "".join(row) + "{n"
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
string += "\n %i Muds found." % nmuds string += "\n %i Muds found." % nmuds
self.caller.msg(string) self.caller.msg(string)
elif "whois" in self.switches or self.cmdstring == "@imcwhois": elif "whois" in self.switches or self.cmdstring == "@imcwhois":
# find out about a player # find out about a player
if not self.args: if not self.args:
self.caller.msg("Usage: @imcwhois <playername>") self.caller.msg("Usage: @imcwhois <playername>")
return return
from src.comms.imc2 import IMC2_CLIENT from src.comms.imc2 import IMC2_CLIENT
self.caller.msg("Sending IMC whois request. If you receive no response, no matches were found.") self.caller.msg("Sending IMC whois request. If you receive no response, no matches were found.")
IMC2_CLIENT.msg_imc2(None, from_obj=self.caller, packet_type="imcwhois", data={"target":self.args}) IMC2_CLIENT.msg_imc2(None, from_obj=self.caller, packet_type="imcwhois", data={"target":self.args})
elif not self.switches or "channels" in self.switches or self.cmdstring == "@imcchanlist": elif not self.switches or "channels" in self.switches or self.cmdstring == "@imcchanlist":
# show channels # show channels
from src.comms.imc2 import IMC2_CHANLIST, IMC2_CLIENT from src.comms.imc2 import IMC2_CHANLIST, IMC2_CLIENT
channels = IMC2_CHANLIST.get_channel_list() channels = IMC2_CHANLIST.get_channel_list()
@ -1028,12 +1028,12 @@ class CmdIMCInfo(MuxCommand):
nchans += 1 nchans += 1
cols[0].append(channel.name) cols[0].append(channel.name)
cols[1].append(channel.localname) cols[1].append(channel.localname)
cols[2].append(channel.owner) cols[2].append(channel.owner)
cols[3].append(channel.level) cols[3].append(channel.level)
cols[4].append(channel.policy) cols[4].append(channel.policy)
ftable = utils.format_table(cols) ftable = utils.format_table(cols)
for ir, row in enumerate(ftable): for ir, row in enumerate(ftable):
if ir == 0: if ir == 0:
string += "\n{w" + "".join(row) + "{n" string += "\n{w" + "".join(row) + "{n"
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
@ -1045,19 +1045,19 @@ class CmdIMCInfo(MuxCommand):
string = "Usage: imcinfo|imcchanlist|imclist" string = "Usage: imcinfo|imcchanlist|imclist"
self.caller.msg(string) self.caller.msg(string)
# unclear if this is working ... # unclear if this is working ...
class CmdIMCTell(MuxCommand): class CmdIMCTell(MuxCommand):
""" """
imctell - send a page to a remote IMC player imctell - send a page to a remote IMC player
Usage: Usage:
imctell User@MUD = <msg> imctell User@MUD = <msg>
imcpage " imcpage "
Sends a page to a user on a remote MUD, connected Sends a page to a user on a remote MUD, connected
over IMC2. over IMC2.
""" """
key = "imctell" key = "imctell"
aliases = ["imcpage", "imc2tell", "imc2page"] aliases = ["imcpage", "imc2tell", "imc2page"]
locks = "cmd: serversetting(IMC2_ENABLED)" locks = "cmd: serversetting(IMC2_ENABLED)"
@ -1072,11 +1072,11 @@ class CmdIMCTell(MuxCommand):
return return
from src.comms.imc2 import IMC2_CLIENT from src.comms.imc2 import IMC2_CLIENT
if not self.args or not '@' in self.lhs or not self.rhs: if not self.args or not '@' in self.lhs or not self.rhs:
string = "Usage: imctell User@Mud = <msg>" string = "Usage: imctell User@Mud = <msg>"
self.caller.msg(string) self.caller.msg(string)
return return
target, destination = self.lhs.split("@", 1) target, destination = self.lhs.split("@", 1)
message = self.rhs.strip() message = self.rhs.strip()
data = {"target":target, "destination":destination} data = {"target":target, "destination":destination}
@ -1087,7 +1087,7 @@ class CmdIMCTell(MuxCommand):
self.caller.msg("You paged {c%s@%s{n (over IMC): '%s'." % (target, destination, message)) self.caller.msg("You paged {c%s@%s{n (over IMC): '%s'." % (target, destination, message))
# RSS connection # RSS connection
class CmdRSS2Chan(MuxCommand): class CmdRSS2Chan(MuxCommand):
""" """
@rss2chan - link evennia channel to an RSS feed @rss2chan - link evennia channel to an RSS feed
@ -1097,20 +1097,20 @@ class CmdRSS2Chan(MuxCommand):
Switches: Switches:
/disconnect - this will stop the feed and remove the connection to the channel. /disconnect - this will stop the feed and remove the connection to the channel.
/remove - " /remove - "
/list - show all rss->evennia mappings /list - show all rss->evennia mappings
Example: Example:
@rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic @rss2chan rsschan = http://code.google.com/feeds/p/evennia/updates/basic
This creates an RSS reader that connects to a given RSS feed url. Updates will be This creates an RSS reader that connects to a given RSS feed url. Updates will be
echoed as a title and news link to the given channel. The rate of updating is set echoed as a title and news link to the given channel. The rate of updating is set
with the RSS_UPDATE_INTERVAL variable in settings (default is every 10 minutes). with the RSS_UPDATE_INTERVAL variable in settings (default is every 10 minutes).
When disconnecting you need to supply both the channel and url again so as to identify When disconnecting you need to supply both the channel and url again so as to identify
the connection uniquely. the connection uniquely.
""" """
key = "@rss2chan" key = "@rss2chan"
locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)" locks = "cmd:serversetting(RSS_ENABLED) and pperm(Immortals)"
help_category = "Comms" help_category = "Comms"
@ -1125,7 +1125,7 @@ class CmdRSS2Chan(MuxCommand):
if 'list' in self.switches: if 'list' in self.switches:
# show all connections # show all connections
connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='rss_') connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='rss_')
if connections: if connections:
cols = [["Evennia-channel"], ["RSS-url"]] cols = [["Evennia-channel"], ["RSS-url"]]
for conn in connections: for conn in connections:
@ -1141,12 +1141,12 @@ class CmdRSS2Chan(MuxCommand):
self.caller.msg(string) self.caller.msg(string)
else: else:
self.caller.msg("No connections found.") self.caller.msg("No connections found.")
return return
if not self.args or not self.rhs: if not self.args or not self.rhs:
string = "Usage: @rss2chan[/switches] <evennia_channel> = <rss url>" string = "Usage: @rss2chan[/switches] <evennia_channel> = <rss url>"
self.caller.msg(string) self.caller.msg(string)
return return
channel = self.lhs channel = self.lhs
url = self.rhs url = self.rhs
@ -1160,8 +1160,8 @@ class CmdRSS2Chan(MuxCommand):
self.caller.msg("RSS connection/reader could not be removed, does it exist?") self.caller.msg("RSS connection/reader could not be removed, does it exist?")
else: else:
self.caller.msg("RSS connection destroyed.") self.caller.msg("RSS connection destroyed.")
return return
channel = find_channel(self.caller, channel) channel = find_channel(self.caller, channel)
if not channel: if not channel:
return return
@ -1171,5 +1171,5 @@ class CmdRSS2Chan(MuxCommand):
ok = rss.create_connection(channel, url, interval) ok = rss.create_connection(channel, url, interval)
if not ok: if not ok:
self.caller.msg("This RSS connection already exists.") self.caller.msg("This RSS connection already exists.")
return return
self.caller.msg("Connection created. Starting RSS reader.") self.caller.msg("Connection created. Starting RSS reader.")

View file

@ -23,17 +23,17 @@ class CmdHome(MuxCommand):
home home
Usage: Usage:
home home
Teleports the player to their home. Teleports the player to their home.
""" """
key = "home" key = "home"
locks = "cmd:perm(home) or perm(Builders)" locks = "cmd:perm(home) or perm(Builders)"
def func(self): def func(self):
"Implement the command" "Implement the command"
caller = self.caller caller = self.caller
home = caller.home home = caller.home
if not home: if not home:
caller.msg("You have no home set.") caller.msg("You have no home set.")
@ -47,7 +47,7 @@ class CmdLook(MuxCommand):
Usage: Usage:
look look
look <obj> look <obj>
look *<player> look *<player>
Observes your location or objects in your vicinity. Observes your location or objects in your vicinity.
@ -59,27 +59,27 @@ class CmdLook(MuxCommand):
def func(self): def func(self):
""" """
Handle the looking. Handle the looking.
""" """
caller = self.caller caller = self.caller
args = self.args args = self.args
if args: if args:
# Use search to handle duplicate/nonexistant results. # Use search to handle duplicate/nonexistant results.
looking_at_obj = caller.search(args, use_nicks=True) looking_at_obj = caller.search(args, use_nicks=True)
if not looking_at_obj: if not looking_at_obj:
return return
else: else:
looking_at_obj = caller.location looking_at_obj = caller.location
if not looking_at_obj: if not looking_at_obj:
caller.msg("You have no location to look at!") caller.msg("You have no location to look at!")
return return
if not hasattr(looking_at_obj, 'return_appearance'): if not hasattr(looking_at_obj, 'return_appearance'):
# this is likely due to us having a player instead # this is likely due to us having a player instead
looking_at_obj = looking_at_obj.character looking_at_obj = looking_at_obj.character
if not looking_at_obj.access(caller, "view"): if not looking_at_obj.access(caller, "view"):
caller.msg("Could not find '%s'." % args) caller.msg("Could not find '%s'." % args)
return return
# get object's appearance # get object's appearance
caller.msg(looking_at_obj.return_appearance(caller)) caller.msg(looking_at_obj.return_appearance(caller))
# the object's at_desc() method. # the object's at_desc() method.
@ -93,16 +93,16 @@ class CmdPassword(MuxCommand):
@password <old password> = <new password> @password <old password> = <new password>
Changes your password. Make sure to pick a safe one. Changes your password. Make sure to pick a safe one.
""" """
key = "@password" key = "@password"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"hook function." "hook function."
caller = self.caller caller = self.caller
if hasattr(caller, "player"): if hasattr(caller, "player"):
caller = caller.player caller = caller.player
if not self.rhs: if not self.rhs:
caller.msg("Usage: @password <oldpass> = <newpass>") caller.msg("Usage: @password <oldpass> = <newpass>")
@ -131,16 +131,16 @@ class CmdNick(MuxCommand):
nick[/switches] <nickname> = [<string>] nick[/switches] <nickname> = [<string>]
alias '' alias ''
Switches: Switches:
object - alias an object object - alias an object
player - alias a player player - alias a player
clearall - clear all your aliases clearall - clear all your aliases
list - show all defined aliases list - show all defined aliases
If no switch is given, a command alias is created, used If no switch is given, a command alias is created, used
to replace strings before sending the command. Give an empty to replace strings before sending the command. Give an empty
right-hand side to clear the nick right-hand side to clear the nick
Creates a personal nick for some in-game object or Creates a personal nick for some in-game object or
string. When you enter that string, it will be replaced string. When you enter that string, it will be replaced
with the alternate string. The switches dictate in what with the alternate string. The switches dictate in what
@ -148,15 +148,15 @@ class CmdNick(MuxCommand):
is None, the alias (if it exists) will be cleared. is None, the alias (if it exists) will be cleared.
Obs - no objects are actually changed with this command, Obs - no objects are actually changed with this command,
if you want to change the inherent aliases of an object, if you want to change the inherent aliases of an object,
use the @alias command instead. use the @alias command instead.
""" """
key = "nick" key = "nick"
aliases = ["nickname", "nicks", "@nick", "alias"] aliases = ["nickname", "nicks", "@nick", "alias"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Create the nickname" "Create the nickname"
caller = self.caller caller = self.caller
switches = self.switches switches = self.switches
@ -178,20 +178,20 @@ class CmdNick(MuxCommand):
if 'clearall' in switches: if 'clearall' in switches:
nicks.delete() nicks.delete()
caller.msg("Cleared all aliases.") caller.msg("Cleared all aliases.")
return return
if not self.args or not self.lhs: if not self.args or not self.lhs:
caller.msg("Usage: nick[/switches] nickname = [realname]") caller.msg("Usage: nick[/switches] nickname = [realname]")
return return
nick = self.lhs nick = self.lhs
real = self.rhs real = self.rhs
if real == nick: if real == nick:
caller.msg("No point in setting nick same as the string to replace...") caller.msg("No point in setting nick same as the string to replace...")
return return
# check so we have a suitable nick type # check so we have a suitable nick type
if not any(True for switch in switches if switch in ("object", "player", "inputline")): if not any(True for switch in switches if switch in ("object", "player", "inputline")):
switches = ["inputline"] switches = ["inputline"]
string = "" string = ""
for switch in switches: for switch in switches:
oldnick = Nick.objects.filter(db_obj=caller.dbobj, db_nick__iexact=nick, db_type__iexact=switch) oldnick = Nick.objects.filter(db_obj=caller.dbobj, db_nick__iexact=nick, db_type__iexact=switch)
@ -204,14 +204,14 @@ class CmdNick(MuxCommand):
else: else:
string += "\nNo nick '%s' found, so it could not be removed." % nick string += "\nNo nick '%s' found, so it could not be removed." % nick
else: else:
# creating new nick # creating new nick
if oldnick: if oldnick:
string += "\nNick %s changed from '%s' to '%s'." % (nick, oldnick[0].db_real, real) string += "\nNick %s changed from '%s' to '%s'." % (nick, oldnick[0].db_real, real)
else: else:
string += "\nNick set: '%s' = '%s'." % (nick, real) string += "\nNick set: '%s' = '%s'." % (nick, real)
caller.nicks.add(nick, real, nick_type=switch) caller.nicks.add(nick, real, nick_type=switch)
caller.msg(string) caller.msg(string)
class CmdInventory(MuxCommand): class CmdInventory(MuxCommand):
""" """
inventory inventory
@ -219,9 +219,9 @@ class CmdInventory(MuxCommand):
Usage: Usage:
inventory inventory
inv inv
Shows a player's inventory. Shows a player's inventory.
""" """
key = "inventory" key = "inventory"
aliases = ["inv", "i"] aliases = ["inv", "i"]
locks = "cmd:all()" locks = "cmd:all()"
@ -235,7 +235,7 @@ class CmdInventory(MuxCommand):
# format item list into nice collumns # format item list into nice collumns
cols = [[],[]] cols = [[],[]]
for item in items: for item in items:
cols[0].append(item.name) cols[0].append(item.name)
desc = item.db.desc desc = item.db.desc
if not desc: if not desc:
desc = "" desc = ""
@ -247,19 +247,19 @@ class CmdInventory(MuxCommand):
string += "\n " + "{C%s{n - %s" % (row[0], row[1]) string += "\n " + "{C%s{n - %s" % (row[0], row[1])
self.caller.msg(string) self.caller.msg(string)
class CmdGet(MuxCommand): class CmdGet(MuxCommand):
""" """
get get
Usage: Usage:
get <obj> get <obj>
Picks up an object from your location and puts it in Picks up an object from your location and puts it in
your inventory. your inventory.
""" """
key = "get" key = "get"
aliases = "grab" aliases = "grab"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"implements the command." "implements the command."
@ -287,13 +287,13 @@ class CmdGet(MuxCommand):
obj.move_to(caller, quiet=True) obj.move_to(caller, quiet=True)
caller.msg("You pick up %s." % obj.name) caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents("%s picks up %s." % caller.location.msg_contents("%s picks up %s." %
(caller.name, (caller.name,
obj.name), obj.name),
exclude=caller) exclude=caller)
# calling hook method # calling hook method
obj.at_get(caller) obj.at_get(caller)
class CmdDrop(MuxCommand): class CmdDrop(MuxCommand):
""" """
@ -301,14 +301,14 @@ class CmdDrop(MuxCommand):
Usage: Usage:
drop <obj> drop <obj>
Lets you drop an object from your inventory into the Lets you drop an object from your inventory into the
location you are currently in. location you are currently in.
""" """
key = "drop" key = "drop"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Implement command" "Implement command"
@ -318,17 +318,17 @@ class CmdDrop(MuxCommand):
return return
results = caller.search(self.args, ignore_errors=True) results = caller.search(self.args, ignore_errors=True)
# we process the results ourselves since we want to sift out only # we process the results ourselves since we want to sift out only
# those in our inventory. # those in our inventory.
results = [obj for obj in results if obj in caller.contents] results = [obj for obj in results if obj in caller.contents]
# now we send it into the handler. # now we send it into the handler.
obj = AT_SEARCH_RESULT(caller, self.args, results, False) obj = AT_SEARCH_RESULT(caller, self.args, results, False)
if not obj: if not obj:
return return
obj.move_to(caller.location, quiet=True) obj.move_to(caller.location, quiet=True)
caller.msg("You drop %s." % (obj.name,)) caller.msg("You drop %s." % (obj.name,))
caller.location.msg_contents("%s drops %s." % caller.location.msg_contents("%s drops %s." %
(caller.name, obj.name), (caller.name, obj.name),
exclude=caller) exclude=caller)
# Call the object script's at_drop() method. # Call the object script's at_drop() method.
@ -340,33 +340,33 @@ class CmdQuit(MuxCommand):
quit quit
Usage: Usage:
@quit @quit
Gracefully disconnect from the game. Gracefully disconnect from the game.
""" """
key = "@quit" key = "@quit"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"hook function" "hook function"
for session in self.caller.sessions: for session in self.caller.sessions:
session.msg("{RQuitting{n. Hope to see you soon again.") session.msg("{RQuitting{n. Hope to see you soon again.")
session.session_disconnect() session.session_disconnect()
class CmdWho(MuxCommand): class CmdWho(MuxCommand):
""" """
who who
Usage: Usage:
who who
doing doing
Shows who is currently online. Doing is an alias that limits info Shows who is currently online. Doing is an alias that limits info
also for those with all permissions. also for those with all permissions.
""" """
key = "who" key = "who"
aliases = "doing" aliases = "doing"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
@ -386,7 +386,7 @@ class CmdWho(MuxCommand):
table = [["Player Name"], ["On for"], ["Idle"], ["Room"], ["Cmds"], ["Host"]] table = [["Player Name"], ["On for"], ["Idle"], ["Room"], ["Cmds"], ["Host"]]
else: else:
table = [["Player Name"], ["On for"], ["Idle"]] table = [["Player Name"], ["On for"], ["Idle"]]
for session in session_list: for session in session_list:
if not session.logged_in: if not session.logged_in:
continue continue
@ -396,12 +396,12 @@ class CmdWho(MuxCommand):
plr_pobject = session.get_character() plr_pobject = session.get_character()
if not plr_pobject: if not plr_pobject:
plr_pobject = session.get_player() plr_pobject = session.get_player()
show_session_data = False show_session_data = False
table = [["Player Name"], ["On for"], ["Idle"]] table = [["Player Name"], ["On for"], ["Idle"]]
if show_session_data: if show_session_data:
table[0].append(plr_pobject.name[:25]) table[0].append(plr_pobject.name[:25])
table[1].append(utils.time_format(delta_conn, 0)) table[1].append(utils.time_format(delta_conn, 0))
table[2].append(utils.time_format(delta_cmd, 1)) table[2].append(utils.time_format(delta_cmd, 1))
table[3].append(plr_pobject.location.id) table[3].append(plr_pobject.location.id)
table[4].append(session.cmd_total) table[4].append(session.cmd_total)
table[5].append(session.address[0]) table[5].append(session.address[0])
@ -421,7 +421,7 @@ class CmdWho(MuxCommand):
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
nplayers = (SESSIONS.player_count()) nplayers = (SESSIONS.player_count())
if nplayers == 1: if nplayers == 1:
string += '\nOne player logged in.' string += '\nOne player logged in.'
else: else:
string += '\n%d players logged in.' % nplayers string += '\n%d players logged in.' % nplayers
@ -434,14 +434,14 @@ class CmdSay(MuxCommand):
Usage: Usage:
say <message> say <message>
Talk to those in your current location. Talk to those in your current location.
""" """
key = "say" key = "say"
aliases = ['"', "'"] aliases = ['"', "'"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Run the say command" "Run the say command"
@ -458,13 +458,13 @@ class CmdSay(MuxCommand):
# Feedback for the object doing the talking. # Feedback for the object doing the talking.
caller.msg('You say, "%s{n"' % speech) caller.msg('You say, "%s{n"' % speech)
# Build the string to emit to neighbors. # Build the string to emit to neighbors.
emit_string = '{c%s{n says, "%s{n"' % (caller.name, emit_string = '{c%s{n says, "%s{n"' % (caller.name,
speech) speech)
caller.location.msg_contents(emit_string, caller.location.msg_contents(emit_string,
exclude=caller) exclude=caller)
class CmdPose(MuxCommand): class CmdPose(MuxCommand):
""" """
@ -477,13 +477,13 @@ class CmdPose(MuxCommand):
Example: Example:
pose is standing by the wall, smiling. pose is standing by the wall, smiling.
-> others will see: -> others will see:
Tom is standing by the wall, smiling. Tom is standing by the wall, smiling.
Describe an script being taken. The pose text will Describe an script being taken. The pose text will
automatically begin with your name. automatically begin with your name.
""" """
key = "pose" key = "pose"
aliases = [":", "emote"] aliases = [":", "emote"]
locks = "cmd:all()" locks = "cmd:all()"
def parse(self): def parse(self):
@ -491,7 +491,7 @@ class CmdPose(MuxCommand):
Custom parse the cases where the emote Custom parse the cases where the emote
starts with some special letter, such starts with some special letter, such
as 's, at which we don't want to separate as 's, at which we don't want to separate
the caller's name and the emote with a the caller's name and the emote with a
space. space.
""" """
args = self.args args = self.args
@ -500,33 +500,33 @@ class CmdPose(MuxCommand):
self.args = args self.args = args
def func(self): def func(self):
"Hook function" "Hook function"
if not self.args: if not self.args:
msg = "Do what?" msg = "Do what?"
self.caller.msg(msg) self.caller.msg(msg)
else: else:
msg = "%s%s" % (self.caller.name, self.args) msg = "%s%s" % (self.caller.name, self.args)
self.caller.location.msg_contents(msg) self.caller.location.msg_contents(msg)
class CmdEncoding(MuxCommand): class CmdEncoding(MuxCommand):
""" """
encoding - set a custom text encoding encoding - set a custom text encoding
Usage: Usage:
@encoding/switches [<encoding>] @encoding/switches [<encoding>]
Switches: Switches:
clear - clear your custom encoding clear - clear your custom encoding
This sets the text encoding for communicating with Evennia. This is mostly an issue only if 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 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 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. 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. 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. If you don't submit an encoding, the current encoding will be displayed instead.
""" """
key = "@encoding" key = "@encoding"
@ -539,7 +539,7 @@ class CmdEncoding(MuxCommand):
""" """
caller = self.caller caller = self.caller
if hasattr(caller, 'player'): if hasattr(caller, 'player'):
caller = caller.player caller = caller.player
if 'clear' in self.switches: if 'clear' in self.switches:
# remove customization # remove customization
@ -551,22 +551,22 @@ class CmdEncoding(MuxCommand):
del caller.db.encoding del caller.db.encoding
elif not self.args: elif not self.args:
# just list the encodings supported # just list the encodings supported
pencoding = caller.db.encoding pencoding = caller.db.encoding
string = "" string = ""
if pencoding: if pencoding:
string += "Default encoding: {g%s{n (change with {w@encoding <encoding>{n)" % pencoding string += "Default encoding: {g%s{n (change with {w@encoding <encoding>{n)" % pencoding
encodings = settings.ENCODINGS encodings = settings.ENCODINGS
if encodings: if encodings:
string += "\nServer's alternative encodings (tested in this order):\n {g%s{n" % ", ".join(encodings) string += "\nServer's alternative encodings (tested in this order):\n {g%s{n" % ", ".join(encodings)
if not string: if not string:
string = "No encodings found." string = "No encodings found."
else: else:
# change encoding # change encoding
old_encoding = caller.db.encoding old_encoding = caller.db.encoding
encoding = self.args encoding = self.args
caller.db.encoding = encoding caller.db.encoding = encoding
string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding) string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding)
caller.msg(string.strip()) caller.msg(string.strip())
class CmdAccess(MuxCommand): class CmdAccess(MuxCommand):
""" """
@ -575,7 +575,7 @@ class CmdAccess(MuxCommand):
Usage: Usage:
access access
This command shows you the permission hierarchy and This command shows you the permission hierarchy and
which permission groups you are a member of. which permission groups you are a member of.
""" """
key = "access" key = "access"
@ -587,8 +587,8 @@ class CmdAccess(MuxCommand):
caller = self.caller caller = self.caller
hierarchy_full = settings.PERMISSION_HIERARCHY hierarchy_full = settings.PERMISSION_HIERARCHY
string = "\n{wPermission Hierarchy{n (climbing):\n %s" % ", ".join(hierarchy_full) string = "\n{wPermission Hierarchy{n (climbing):\n %s" % ", ".join(hierarchy_full)
hierarchy = [p.lower() for p in hierarchy_full] #hierarchy = [p.lower() for p in hierarchy_full]
if self.caller.player.is_superuser: if self.caller.player.is_superuser:
cperms = "<Superuser>" cperms = "<Superuser>"
@ -603,7 +603,7 @@ class CmdAccess(MuxCommand):
string += "\nPlayer {c%s{n: %s" % (caller.player.key, pperms) string += "\nPlayer {c%s{n: %s" % (caller.player.key, pperms)
caller.msg(string) caller.msg(string)
# OOC commands # OOC commands
class CmdOOCLook(CmdLook): class CmdOOCLook(CmdLook):
""" """
@ -614,8 +614,8 @@ class CmdOOCLook(CmdLook):
This is an OOC version of the look command. Since a This is an OOC version of the look command. Since a
Player doesn't have an in-game existence, there is no Player doesn't have an in-game existence, there is no
concept of location or "self". If we are controlling concept of location or "self". If we are controlling
a character, pass control over to normal look. a character, pass control over to normal look.
""" """
@ -631,7 +631,7 @@ class CmdOOCLook(CmdLook):
if utils.inherits_from(self.caller, "src.objects.objects.Object"): if utils.inherits_from(self.caller, "src.objects.objects.Object"):
# An object of some type is calling. Convert to player. # An object of some type is calling. Convert to player.
#print self.caller, self.caller.__class__ #print self.caller, self.caller.__class__
self.character = self.caller self.character = self.caller
if hasattr(self.caller, "player"): if hasattr(self.caller, "player"):
self.caller = self.caller.player self.caller = self.caller.player
@ -642,20 +642,20 @@ class CmdOOCLook(CmdLook):
else: else:
self.caller = self.character # we have to put this back for normal look to work. self.caller = self.character # we have to put this back for normal look to work.
super(CmdOOCLook, self).func() super(CmdOOCLook, self).func()
class CmdIC(MuxCommand): class CmdIC(MuxCommand):
""" """
Switch control to an object Switch control to an object
Usage: Usage:
@ic <character> @ic <character>
Go in-character (IC) as a given Character. Go in-character (IC) as a given Character.
This will attempt to "become" a different object assuming you have This will attempt to "become" a different object assuming you have
the right to do so. You cannot become an object that is already the right to do so. You cannot become an object that is already
controlled by another player. In principle <character> can be controlled by another player. In principle <character> can be
any in-game object as long as you have access right to puppet it. any in-game object as long as you have access right to puppet it.
""" """
key = "@ic" key = "@ic"
@ -671,7 +671,7 @@ class CmdIC(MuxCommand):
if utils.inherits_from(caller, "src.objects.objects.Object"): if utils.inherits_from(caller, "src.objects.objects.Object"):
caller = caller.player caller = caller.player
new_character = None new_character = None
if not self.args: if not self.args:
new_character = caller.db.last_puppet new_character = caller.db.last_puppet
if not new_character: if not new_character:
@ -682,19 +682,19 @@ class CmdIC(MuxCommand):
new_character = caller.search(self.args, global_search=True) new_character = caller.search(self.args, global_search=True)
if not new_character: if not new_character:
# the search method handles error messages etc. # the search method handles error messages etc.
return return
if new_character.player: if new_character.player:
if new_character.player == caller: if new_character.player == caller:
caller.msg("{RYou already are {c%s{n." % new_character.name) caller.msg("{RYou already are {c%s{n." % new_character.name)
else: else:
caller.msg("{c%s{r is already acted by another player.{n" % new_character.name) caller.msg("{c%s{r is already acted by another player.{n" % new_character.name)
return return
if not new_character.access(caller, "puppet"): if not new_character.access(caller, "puppet"):
caller.msg("{rYou may not become %s.{n" % new_character.name) caller.msg("{rYou may not become %s.{n" % new_character.name)
return return
old_char = None old_char = None
if caller.character: if caller.character:
# save the old character. We only assign this to last_puppet if swap is successful. # save the old character. We only assign this to last_puppet if swap is successful.
old_char = caller.character old_char = caller.character
if caller.swap_character(new_character): if caller.swap_character(new_character):
new_character.msg("\n{gYou become {c%s{n.\n" % new_character.name) new_character.msg("\n{gYou become {c%s{n.\n" % new_character.name)
@ -704,7 +704,7 @@ class CmdIC(MuxCommand):
loc = new_character.db.prelogout_location loc = new_character.db.prelogout_location
if not loc: # still no location; use home if not loc: # still no location; use home
loc = new_character.home loc = new_character.home
new_character.location = loc new_character.location = loc
if new_character.location: if new_character.location:
new_character.location.msg_contents("%s has entered the game." % new_character.key, exclude=[new_character]) new_character.location.msg_contents("%s has entered the game." % new_character.key, exclude=[new_character])
new_character.location.at_object_receive(new_character, new_character.location) new_character.location.at_object_receive(new_character, new_character.location)
@ -715,10 +715,10 @@ class CmdIC(MuxCommand):
class CmdOOC(MuxCommand): class CmdOOC(MuxCommand):
""" """
@ooc - go ooc @ooc - go ooc
Usage: Usage:
@ooc @ooc
Go out-of-character (OOC). Go out-of-character (OOC).
This will leave your current character and put you in a incorporeal OOC state. This will leave your current character and put you in a incorporeal OOC state.
@ -730,8 +730,8 @@ class CmdOOC(MuxCommand):
help_category = "General" help_category = "General"
def func(self): def func(self):
"Implement function" "Implement function"
caller = self.caller caller = self.caller
if utils.inherits_from(caller, "src.objects.objects.Object"): if utils.inherits_from(caller, "src.objects.objects.Object"):
@ -740,18 +740,18 @@ class CmdOOC(MuxCommand):
if not caller.character: if not caller.character:
string = "You are already OOC." string = "You are already OOC."
caller.msg(string) caller.msg(string)
return return
caller.db.last_puppet = caller.character caller.db.last_puppet = caller.character
# save location as if we were disconnecting from the game entirely. # save location as if we were disconnecting from the game entirely.
if caller.character.location: if caller.character.location:
caller.character.location.msg_contents("%s has left the game." % caller.character.key, exclude=[caller.character]) caller.character.location.msg_contents("%s has left the game." % caller.character.key, exclude=[caller.character])
caller.character.db.prelogout_location = caller.character.location caller.character.db.prelogout_location = caller.character.location
caller.character.location = None caller.character.location = None
# disconnect # disconnect
caller.character.player = None caller.character.player = None
caller.character = None caller.character = None
caller.msg("\n{GYou go OOC.{n\n") caller.msg("\n{GYou go OOC.{n\n")
caller.execute_cmd("look") caller.execute_cmd("look")

View file

@ -5,11 +5,11 @@ command-help is all auto-loaded and searched from the current command
set. The normal, database-tied help system is used for collaborative set. The normal, database-tied help system is used for collaborative
creation of other help topics such as RP help or game-world aides. creation of other help topics such as RP help or game-world aides.
""" """
from src.utils.utils import fill, dedent from src.utils.utils import fill, dedent
from src.commands.command import Command from src.commands.command import Command
from src.help.models import HelpEntry from src.help.models import HelpEntry
from src.utils import create from src.utils import create
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# limit symbol import for API # limit symbol import for API
@ -18,43 +18,43 @@ __all__ = ("CmdHelp", "CmdSetHelp")
LIST_ARGS = ("list", "all") LIST_ARGS = ("list", "all")
SEP = "{C" + "-"*78 + "{n" SEP = "{C" + "-"*78 + "{n"
def format_help_entry(title, help_text, aliases=None, suggested=None): def format_help_entry(title, help_text, aliases=None, suggested=None):
""" """
This visually formats the help entry. This visually formats the help entry.
""" """
string = SEP + "\n" string = SEP + "\n"
if title: if title:
string += "{CHelp topic for {w%s{n" % (title.capitalize()) string += "{CHelp topic for {w%s{n" % (title.capitalize())
if aliases: if aliases:
string += " {C(aliases: {w%s{n{C){n" % (", ".join(aliases)) string += " {C(aliases: {w%s{n{C){n" % (", ".join(aliases))
if help_text: if help_text:
string += "\n%s" % dedent(help_text.rstrip()) string += "\n%s" % dedent(help_text.rstrip())
if suggested: if suggested:
string += "\n\n{CSuggested:{n " string += "\n\n{CSuggested:{n "
string += "{w%s{n" % fill(", ".join(suggested)) string += "{w%s{n" % fill(", ".join(suggested))
string.strip() string.strip()
string += "\n" + SEP string += "\n" + SEP
return string return string
def format_help_list(hdict_cmds, hdict_db): def format_help_list(hdict_cmds, hdict_db):
""" """
Output a category-ordered list. The input are the Output a category-ordered list. The input are the
pre-loaded help files for commands and database-helpfiles pre-loaded help files for commands and database-helpfiles
resectively. resectively.
""" """
string = "" string = ""
if hdict_cmds and hdict_cmds.values(): if hdict_cmds and hdict_cmds.values():
string += "\n" + SEP + "\n {CCommand help entries{n\n" + SEP string += "\n" + SEP + "\n {CCommand help entries{n\n" + SEP
for category in sorted(hdict_cmds.keys()): for category in sorted(hdict_cmds.keys()):
string += "\n {w%s{n:\n" % (str(category).capitalize()) string += "\n {w%s{n:\n" % (str(category).capitalize())
string += "{G" + fill(", ".join(sorted(hdict_cmds[category]))) + "{n" string += "{G" + fill(", ".join(sorted(hdict_cmds[category]))) + "{n"
if hdict_db and hdict_db.values(): if hdict_db and hdict_db.values():
string += "\n\n" + SEP + "\n\r {COther help entries{n\n" + SEP string += "\n\n" + SEP + "\n\r {COther help entries{n\n" + SEP
for category in sorted(hdict_db.keys()): for category in sorted(hdict_db.keys()):
string += "\n\r {w%s{n:\n" % (str(category).capitalize()) string += "\n\r {w%s{n:\n" % (str(category).capitalize())
string += "{G" + fill(", ".join(sorted([str(topic) for topic in hdict_db[category]]))) + "{n" string += "{G" + fill(", ".join(sorted([str(topic) for topic in hdict_db[category]]))) + "{n"
return string return string
class CmdHelp(Command): class CmdHelp(Command):
""" """
@ -66,15 +66,15 @@ class CmdHelp(Command):
help all help all
This will search for help on commands and other This will search for help on commands and other
topics related to the game. topics related to the game.
""" """
key = "help" key = "help"
locks = "cmd:all()" locks = "cmd:all()"
# this is a special cmdhandler flag that makes the cmdhandler also pack # this is a special cmdhandler flag that makes the cmdhandler also pack
# the current cmdset with the call to self.func(). # the current cmdset with the call to self.func().
return_cmdset = True return_cmdset = True
def parse(self): def parse(self):
""" """
input is a string containing the command or topic to match. input is a string containing the command or topic to match.
@ -85,7 +85,7 @@ class CmdHelp(Command):
def func(self): def func(self):
""" """
Run the dynamic help entry creator. Run the dynamic help entry creator.
""" """
query, cmdset = self.args, self.cmdset query, cmdset = self.args, self.cmdset
caller = self.caller caller = self.caller
@ -95,37 +95,37 @@ class CmdHelp(Command):
# removing doublets in cmdset, caused by cmdhandler # removing doublets in cmdset, caused by cmdhandler
# having to allow doublet commands to manage exits etc. # having to allow doublet commands to manage exits etc.
cmdset.make_unique(caller) cmdset.make_unique(caller)
# Listing all help entries # Listing all help entries
if query in LIST_ARGS: if query in LIST_ARGS:
# we want to list all available help entries, grouped by category. # we want to list all available help entries, grouped by category.
hdict_cmd = {} hdict_cmd = {}
for cmd in (cmd for cmd in cmdset if cmd.auto_help and not cmd.is_exit for cmd in (cmd for cmd in cmdset if cmd.auto_help and not cmd.is_exit
and not cmd.key.startswith('__') and cmd.access(caller)): and not cmd.key.startswith('__') and cmd.access(caller)):
try: try:
hdict_cmd[cmd.help_category].append(cmd.key) hdict_cmd[cmd.help_category].append(cmd.key)
except KeyError: except KeyError:
hdict_cmd[cmd.help_category] = [cmd.key] hdict_cmd[cmd.help_category] = [cmd.key]
hdict_db = {} hdict_db = {}
for topic in (topic for topic in HelpEntry.objects.get_all_topics() for topic in (topic for topic in HelpEntry.objects.get_all_topics()
if topic.access(caller, 'view', default=True)): if topic.access(caller, 'view', default=True)):
try: try:
hdict_db[topic.help_category].append(topic.key) hdict_db[topic.help_category].append(topic.key)
except KeyError: except KeyError:
hdict_db[topic.help_category] = [topic.key] hdict_db[topic.help_category] = [topic.key]
help_entry = format_help_list(hdict_cmd, hdict_db) help_entry = format_help_list(hdict_cmd, hdict_db)
caller.msg(help_entry) caller.msg(help_entry)
return return
# Look for a particular help entry # Look for a particular help entry
# Cmd auto-help dynamic entries # Cmd auto-help dynamic entries
cmdmatches = [cmd for cmd in cmdset if query in cmd and cmd.auto_help and cmd.access(caller)] cmdmatches = [cmd for cmd in cmdset if query in cmd and cmd.auto_help and cmd.access(caller)]
if len(cmdmatches) > 1: if len(cmdmatches) > 1:
# multiple matches. Try to limit it down to exact match # multiple matches. Try to limit it down to exact match
cmdmatches = [cmd for cmd in cmdmatches if cmd == query] or cmdmatches cmdmatches = [cmd for cmd in cmdmatches if cmd == query] or cmdmatches
# Help-database static entries # Help-database static entries
dbmatches = [topic for topic in dbmatches = [topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=False) HelpEntry.objects.find_topicmatch(query, exact=False)
@ -135,7 +135,7 @@ class CmdHelp(Command):
dbmatches = [topic for topic in HelpEntry.objects.find_topicmatch(query, exact=True) dbmatches = [topic for topic in HelpEntry.objects.find_topicmatch(query, exact=True)
if topic.access(caller, 'view', default=True)] or dbmatches if topic.access(caller, 'view', default=True)] or dbmatches
# Handle result # Handle result
if (not cmdmatches) and (not dbmatches): if (not cmdmatches) and (not dbmatches):
# no normal match. Check if this is a category match instead # 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 cmd.access(caller)] categ_cmdmatches = [cmd.key for cmd in cmdset if query == cmd.help_category and cmd.access(caller)]
@ -143,13 +143,13 @@ class CmdHelp(Command):
if topic.access(caller, 'view', default=True)] if topic.access(caller, 'view', default=True)]
cmddict = None cmddict = None
dbdict = None dbdict = None
if categ_cmdmatches: if categ_cmdmatches:
cmddict = {query:categ_cmdmatches} cmddict = {query:categ_cmdmatches}
if categ_dbmatches: if categ_dbmatches:
dbdict = {query:categ_dbmatches} dbdict = {query:categ_dbmatches}
if cmddict or dbdict: if cmddict or dbdict:
help_entry = format_help_list(cmddict, dbdict) help_entry = format_help_list(cmddict, dbdict)
else: else:
help_entry = "No help entry found for '%s'" % self.original_args help_entry = "No help entry found for '%s'" % self.original_args
@ -187,33 +187,33 @@ class CmdSetHelp(MuxCommand):
add - add or replace a new topic with text. add - add or replace a new topic with text.
append - add text to the end of topic with a newline between. append - add text to the end of topic with a newline between.
merge - As append, but don't add a newline between the old merge - As append, but don't add a newline between the old
text and the appended text. text and the appended text.
delete - remove help topic. delete - remove help topic.
force - (used with add) create help topic also if the topic force - (used with add) create help topic also if the topic
already exists. already exists.
Examples: Examples:
@sethelp/add throw = This throws something at ... @sethelp/add throw = This throws something at ...
@sethelp/append pickpocketing,Thievery,is_thief, is_staff) = This steals ... @sethelp/append pickpocketing,Thievery,is_thief, is_staff) = This steals ...
@sethelp/append pickpocketing, ,is_thief, is_staff) = This steals ... @sethelp/append pickpocketing, ,is_thief, is_staff) = This steals ...
""" """
key = "@help" key = "@help"
aliases = "@sethelp" aliases = "@sethelp"
locks = "cmd:perm(PlayerHelpers)" locks = "cmd:perm(PlayerHelpers)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
"Implement the function" "Implement the function"
caller = self.caller caller = self.caller
switches = self.switches switches = self.switches
lhslist = self.lhslist lhslist = self.lhslist
rhs = self.rhs rhs = self.rhs
if not self.args: if not self.args:
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,locks,..] = <text>]") caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,locks,..] = <text>]")
return return
topicstr = "" topicstr = ""
category = "" category = ""
@ -226,12 +226,12 @@ class CmdSetHelp(MuxCommand):
pass pass
if not topicstr: if not topicstr:
caller.msg("You have to define a topic!") caller.msg("You have to define a topic!")
return return
string = "" string = ""
#print topicstr, category, lockstring #print topicstr, category, lockstring
if switches and switches[0] in ('append', 'app','merge'): if switches and switches[0] in ('append', 'app','merge'):
# add text to the end of a help topic # add text to the end of a help topic
# find the topic to append to # find the topic to append to
old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr) old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
if not old_entry: if not old_entry:
@ -242,14 +242,14 @@ class CmdSetHelp(MuxCommand):
if switches[0] == 'merge': if switches[0] == 'merge':
old_entry.entrytext = "%s %s" % (entrytext, self.rhs) old_entry.entrytext = "%s %s" % (entrytext, self.rhs)
string = "Added the new text right after the old one (merge)." string = "Added the new text right after the old one (merge)."
else: else:
old_entry.entrytext = "%s\n\n%s" % (entrytext, self.rhs) old_entry.entrytext = "%s\n\n%s" % (entrytext, self.rhs)
string = "Added the new text as a new paragraph after the old one (append)" string = "Added the new text as a new paragraph after the old one (append)"
old_entry.save() old_entry.save()
elif switches and switches[0] in ('delete','del'): elif switches and switches[0] in ('delete','del'):
#delete a help entry #delete a help entry
old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr) old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
if not old_entry: if not old_entry:
string = "Could not find topic '%s'." % topicstr string = "Could not find topic '%s'." % topicstr
else: else:
@ -257,14 +257,14 @@ class CmdSetHelp(MuxCommand):
string = "Deleted the help entry '%s'." % topicstr string = "Deleted the help entry '%s'." % topicstr
else: else:
# add a new help entry. # add a new help entry.
force_create = ('for' in switches) or ('force' in switches) force_create = ('for' in switches) or ('force' in switches)
old_entry = None old_entry = None
try: try:
old_entry = HelpEntry.objects.get(key=topicstr) old_entry = HelpEntry.objects.get(key=topicstr)
except Exception: except Exception:
pass pass
if old_entry: if old_entry:
if force_create: if force_create:
old_entry.key = topicstr old_entry.key = topicstr
old_entry.entrytext = self.rhs old_entry.entrytext = self.rhs
@ -277,13 +277,13 @@ class CmdSetHelp(MuxCommand):
string = "Topic '%s' already exists. Use /force to overwrite it." % topicstr string = "Topic '%s' already exists. Use /force to overwrite it." % topicstr
else: else:
# no old entry. Create a new one. # no old entry. Create a new one.
new_entry = create.create_help_entry(topicstr, new_entry = create.create_help_entry(topicstr,
rhs, category, lockstring) rhs, category, lockstring)
if new_entry: if new_entry:
string = "Topic '%s' was successfully created." % topicstr string = "Topic '%s' was successfully created." % topicstr
else: else:
string = "Error when creating topic '%s'! Maybe it already exists?" % topicstr string = "Error when creating topic '%s'! Maybe it already exists?" % topicstr
# give feedback # give feedback
caller.msg(string) caller.msg(string)

View file

@ -4,18 +4,18 @@ System commands
These are the default commands called by the system commandhandler These are the default commands called by the system commandhandler
when various exceptions occur. If one of these commands are not when various exceptions occur. If one of these commands are not
implemented and part of the current cmdset, the engine falls back implemented and part of the current cmdset, the engine falls back
to a default solution instead. to a default solution instead.
Some system commands are shown in this module Some system commands are shown in this module
as a REFERENCE only (they are not all added to Evennia's 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 cmdset since they don't currently do anything differently from the
default backup systems hard-wired in the engine). default backup systems hard-wired in the engine).
Overloading these commands in a cmdset can be used to create Overloading these commands in a cmdset can be used to create
interesting effects. An example is using the NoMatch system command interesting effects. An example is using the NoMatch system command
to implement a line-editor where you don't have to start each 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, line with a command (if there is no match to a known command,
the line is just added to the editor buffer). the line is just added to the editor buffer).
""" """
from src.comms.models import Channel from src.comms.models import Channel
@ -27,7 +27,7 @@ from src.commands.cmdhandler import CMD_NOINPUT
from src.commands.cmdhandler import CMD_NOMATCH from src.commands.cmdhandler import CMD_NOMATCH
from src.commands.cmdhandler import CMD_MULTIMATCH from src.commands.cmdhandler import CMD_MULTIMATCH
from src.commands.cmdhandler import CMD_CHANNEL from src.commands.cmdhandler import CMD_CHANNEL
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# Command called when there is no input at line # Command called when there is no input at line
@ -59,7 +59,7 @@ class SystemNoMatch(MuxCommand):
def func(self): def func(self):
""" """
This is given the failed raw string as input. This is given the failed raw string as input.
""" """
self.caller.msg("Huh?") self.caller.msg("Huh?")
# #
@ -69,13 +69,13 @@ class SystemMultimatch(MuxCommand):
""" """
Multiple command matches. Multiple command matches.
The cmdhandler adds a special attribute 'matches' to this The cmdhandler adds a special attribute 'matches' to this
system command. system command.
matches = [(candidate, cmd) , (candidate, cmd), ...], matches = [(candidate, cmd) , (candidate, cmd), ...],
where candidate is an instance of src.commands.cmdparser.CommandCandidate where candidate is an instance of src.commands.cmdparser.CommandCandidate
and cmd is an an instantiated Command object matching the candidate. and cmd is an an instantiated Command object matching the candidate.
""" """
key = CMD_MULTIMATCH key = CMD_MULTIMATCH
locks = "cmd:all()" locks = "cmd:all()"
@ -84,21 +84,21 @@ class SystemMultimatch(MuxCommand):
""" """
Format multiple command matches to a useful error. Format multiple command matches to a useful error.
This is copied directly from the default method in This is copied directly from the default method in
src.commands.cmdhandler. src.commands.cmdhandler.
""" """
string = "There where multiple matches:" string = "There where multiple matches:"
for num, match in enumerate(matches): for num, match in enumerate(matches):
# each match is a tuple (candidate, cmd) # each match is a tuple (candidate, cmd)
candidate, cmd = match candidate, cmd = match
is_channel = hasattr(cmd, "is_channel") and cmd.is_channel is_channel = hasattr(cmd, "is_channel") and cmd.is_channel
if is_channel: if is_channel:
is_channel = " (channel)" is_channel = " (channel)"
else: else:
is_channel = "" is_channel = ""
is_exit = hasattr(cmd, "is_exit") and cmd.is_exit is_exit = hasattr(cmd, "is_exit") and cmd.is_exit
if is_exit and cmd.destination: if is_exit and cmd.destination:
is_exit = " (exit to %s)" % cmd.destination is_exit = " (exit to %s)" % cmd.destination
else: else:
@ -115,7 +115,7 @@ class SystemMultimatch(MuxCommand):
id2 = "" id2 = ""
string += "\n %s%s%s%s%s" % (id1, candidate.cmdname, id2, is_channel, is_exit) string += "\n %s%s%s%s%s" % (id1, candidate.cmdname, id2, is_channel, is_exit)
return string return string
def func(self): def func(self):
""" """
argument to cmd is a comma-separated string of argument to cmd is a comma-separated string of
@ -126,37 +126,37 @@ class SystemMultimatch(MuxCommand):
# Command called when the command given at the command line # Command called when the command given at the command line
# was identified as a channel name, like there existing a # was identified as a channel name, like there existing a
# channel named 'ooc' and the user wrote # channel named 'ooc' and the user wrote
# > ooc Hello! # > ooc Hello!
class SystemSendToChannel(MuxCommand): class SystemSendToChannel(MuxCommand):
""" """
This is a special command that the cmdhandler calls This is a special command that the cmdhandler calls
when it detects that the command given matches when it detects that the command given matches
an existing Channel object key (or alias). an existing Channel object key (or alias).
""" """
key = CMD_CHANNEL key = CMD_CHANNEL
locks = "cmd:all()" locks = "cmd:all()"
def parse(self): def parse(self):
channelname, msg = self.args.split(':', 1) channelname, msg = self.args.split(':', 1)
self.args = channelname.strip(), msg.strip() self.args = channelname.strip(), msg.strip()
def func(self): def func(self):
""" """
Create a new message and send it to channel, using Create a new message and send it to channel, using
the already formatted input. the already formatted input.
""" """
caller = self.caller caller = self.caller
channelkey, msg = self.args channelkey, msg = self.args
if not msg: if not msg:
caller.msg("Say what?") caller.msg("Say what?")
return return
channel = Channel.objects.get_channel(channelkey) channel = Channel.objects.get_channel(channelkey)
if not channel: if not channel:
caller.msg("Channel '%s' not found." % channelkey) caller.msg("Channel '%s' not found." % channelkey)
return return
if not channel.has_connection(caller): if not channel.has_connection(caller):
string = "You are not connected to channel '%s'." string = "You are not connected to channel '%s'."
caller.msg(string % channelkey) caller.msg(string % channelkey)
@ -165,6 +165,6 @@ class SystemSendToChannel(MuxCommand):
string = "You are not permitted to send to channel '%s'." string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey) caller.msg(string % channelkey)
return return
msg = "[%s] %s: %s" % (channel.key, caller.name, msg) msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
msgobj = create.create_message(caller, msg, channels=[channel]) msgobj = create.create_message(caller, msg, channels=[channel])
channel.msg(msgobj) channel.msg(msgobj)

View file

@ -8,14 +8,12 @@ import traceback
import os, datetime, time import os, datetime, time
import django, twisted import django, twisted
from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
from src.objects.models import ObjectDB from src.objects.models import ObjectDB
from src.players.models import PlayerDB from src.players.models import PlayerDB
from src.server.models import ServerConfig from src.utils import logger, utils, gametime
from src.utils import create, logger, utils, gametime
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# limit symbol import for API # limit symbol import for API
@ -40,7 +38,7 @@ class CmdReload(MuxCommand):
def func(self): def func(self):
""" """
Reload the system. Reload the system.
""" """
SESSIONS.announce_all(" Server restarting ...") SESSIONS.announce_all(" Server restarting ...")
SESSIONS.server.shutdown(mode='reload') SESSIONS.server.shutdown(mode='reload')
@ -64,7 +62,7 @@ class CmdReset(MuxCommand):
def func(self): def func(self):
""" """
Reload the system. Reload the system.
""" """
SESSIONS.announce_all(" Server restarting ...") SESSIONS.announce_all(" Server restarting ...")
SESSIONS.server.shutdown(mode='reset') SESSIONS.server.shutdown(mode='reset')
@ -101,7 +99,7 @@ class CmdShutdown(MuxCommand):
class CmdPy(MuxCommand): class CmdPy(MuxCommand):
""" """
Execute a snippet of python code Execute a snippet of python code
Usage: Usage:
@py <cmd> @py <cmd>
@ -110,10 +108,10 @@ class CmdPy(MuxCommand):
available for convenience in order to offer access to the system available for convenience in order to offer access to the system
(you can import more at execution time). (you can import more at execution time).
Available variables in @py environment: Available variables in @py environment:
self, me : caller self, me : caller
here : caller.location here : caller.location
ev : the evennia API ev : the evennia API
inherits_from(obj, parent) : check object inheritance inherits_from(obj, parent) : check object inheritance
{rNote: In the wrong hands this command is a severe security risk. {rNote: In the wrong hands this command is a severe security risk.
@ -160,7 +158,7 @@ class CmdPy(MuxCommand):
caller.msg(ret) caller.msg(ret)
# helper function. Kept outside so it can be imported and run # helper function. Kept outside so it can be imported and run
# by other commands. # by other commands.
def format_script_list(scripts): def format_script_list(scripts):
"Takes a list of scripts and formats the output." "Takes a list of scripts and formats the output."
@ -216,7 +214,7 @@ class CmdScripts(MuxCommand):
Usage: Usage:
@scripts[/switches] [<obj or scriptid>] @scripts[/switches] [<obj or scriptid>]
Switches: Switches:
stop - stops an existing script stop - stops an existing script
kill - kills a script - without running its cleanup hooks kill - kills a script - without running its cleanup hooks
@ -232,7 +230,7 @@ class CmdScripts(MuxCommand):
aliases = "@listscripts" aliases = "@listscripts"
locks = "cmd:perm(listscripts) or perm(Wizards)" locks = "cmd:perm(listscripts) or perm(Wizards)"
help_category = "System" help_category = "System"
def func(self): def func(self):
"implement method" "implement method"
@ -247,7 +245,7 @@ class CmdScripts(MuxCommand):
if not scripts: if not scripts:
# try to find an object instead. # try to find an object instead.
objects = ObjectDB.objects.object_search(args, caller=caller, global_search=True) objects = ObjectDB.objects.object_search(args, caller=caller, global_search=True)
if objects: if objects:
scripts = [] scripts = []
for obj in objects: for obj in objects:
# get all scripts on the object(s) # get all scripts on the object(s)
@ -267,7 +265,7 @@ class CmdScripts(MuxCommand):
string = "No scripts/objects matching '%s'. " % args string = "No scripts/objects matching '%s'. " % args
string += "Be more specific." string += "Be more specific."
elif len(scripts) == 1: elif len(scripts) == 1:
# we have a unique match! # we have a unique match!
if 'kill' in self.switches: if 'kill' in self.switches:
string = "Killing script '%s'" % scripts[0].key string = "Killing script '%s'" % scripts[0].key
scripts[0].stop(kill=True) scripts[0].stop(kill=True)
@ -300,8 +298,8 @@ class CmdObjects(MuxCommand):
Usage: Usage:
@objects [<nr>] @objects [<nr>]
Gives statictics on objects in database as well as Gives statictics on objects in database as well as
a list of <nr> latest objects in database. If not a list of <nr> latest objects in database. If not
given, <nr> defaults to 10. given, <nr> defaults to 10.
""" """
key = "@objects" key = "@objects"
@ -379,7 +377,7 @@ class CmdService(MuxCommand):
list - shows all available services (default) list - shows all available services (default)
start - activates a service start - activates a service
stop - stops a service stop - stops a service
Service management system. Allows for the listing, Service management system. Allows for the listing,
starting, and stopping of services. If no switches starting, and stopping of services. If no switches
are given, services will be listed. are given, services will be listed.
@ -487,8 +485,8 @@ class CmdTime(MuxCommand):
@time @time
Usage: Usage:
@time @time
Server local time. Server local time.
""" """
key = "@time" key = "@time"
@ -498,12 +496,12 @@ class CmdTime(MuxCommand):
def func(self): def func(self):
"Show times." "Show times."
table = [["Current server uptime:", table = [["Current server uptime:",
"Total server running time:", "Total server running time:",
"Total in-game time (realtime x %g):" % (gametime.TIMEFACTOR), "Total in-game time (realtime x %g):" % (gametime.TIMEFACTOR),
"Server time stamp:" "Server time stamp:"
], ],
[utils.time_format(time.time() - SESSIONS.server.start_time, 3), [utils.time_format(time.time() - SESSIONS.server.start_time, 3),
utils.time_format(gametime.runtime(format=False), 2), utils.time_format(gametime.runtime(format=False), 2),
utils.time_format(gametime.gametime(format=False), 2), utils.time_format(gametime.gametime(format=False), 2),
@ -523,13 +521,13 @@ class CmdTime(MuxCommand):
self.caller.msg(string) self.caller.msg(string)
class CmdServerLoad(MuxCommand): class CmdServerLoad(MuxCommand):
""" """
server load statistics server load statistics
Usage: Usage:
@serverload @serverload
Show server load statistics in a table. Show server load statistics in a table.
""" """
key = "@serverload" key = "@serverload"
locks = "cmd:perm(list) or perm(Immortals)" locks = "cmd:perm(list) or perm(Immortals)"
@ -607,9 +605,9 @@ class CmdServerLoad(MuxCommand):
# class CmdPs(MuxCommand): # class CmdPs(MuxCommand):
# """ # """
# list processes # list processes
# Usage # Usage
# @ps # @ps
# Shows the process/event table. # Shows the process/event table.
# """ # """

View file

@ -182,13 +182,14 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m
typeclass = settings.BASE_CHARACTER_TYPECLASS typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT permissions = settings.PERMISSION_PLAYER_DEFAULT
new_character = create.create_player(playername, email, password, try:
permissions=permissions, new_character = create.create_player(playername, email, password,
character_typeclass=typeclass, permissions=permissions,
character_location=default_home, character_typeclass=typeclass,
character_home=default_home) character_location=default_home,
if not new_character: character_home=default_home)
session.msg("There was an error creating the default Character/Player. This error was logged. Contact an admin.") except Exception, e:
session.msg("There was an error creating the default Character/Player:\n%s\n If this problem persists, contact an admin.")
return return
new_player = new_character.player new_player = new_character.player

View file

@ -323,7 +323,7 @@ class Object(TypeClass):
""" """
self.dbobj.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default) return self.dbobj.swap_typeclass(new_typeclass, clean_attributes=clean_attributes, no_default=no_default)
def access(self, accessing_obj, access_type='read', default=False): def access(self, accessing_obj, access_type='read', default=False):
""" """

View file

@ -58,9 +58,6 @@ def create_objects():
create_character=True, create_character=True,
character_typeclass=character_typeclass) character_typeclass=character_typeclass)
if not god_character:
raise Exception(_("#1 could not be created. Check the Player/Character typeclass for bugs."))
god_character.id = 1 god_character.id = 1
god_character.db.desc = _('This is User #1.') god_character.db.desc = _('This is User #1.')
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()") god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()")
@ -164,7 +161,11 @@ def create_admin_media_links():
on system. on system.
""" """
import django, os import django, os
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
if django.get_version() < 1.4:
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
else:
dpath = os.path.join(django.__path__[0], 'contrib', 'admin', 'static', 'admin')
apath = os.path.join(settings.ADMIN_MEDIA_ROOT) apath = os.path.join(settings.ADMIN_MEDIA_ROOT)
if os.path.isdir(apath): if os.path.isdir(apath):
print _(" ADMIN_MEDIA_ROOT already exists. Ignored.") print _(" ADMIN_MEDIA_ROOT already exists. Ignored.")
@ -177,8 +178,8 @@ def create_admin_media_links():
try: try:
os.symlink(dpath, apath) os.symlink(dpath, apath)
print _(" Admin-media symlinked to ADMIN_MEDIA_ROOT.") print _(" Admin-media symlinked to ADMIN_MEDIA_ROOT.")
except OSError: except OSError, e:
print _(" There was an error symlinking Admin-media to ADMIN_MEDIA_ROOT. If you see issues, link manually.") print _(" There was an error symlinking Admin-media to ADMIN_MEDIA_ROOT:\n %s\n -> \n %s\n (%s)\n If you see issues, link manually." % (dpath, apath, e))
else: else:
print _(" Admin-media files should be copied manually to ADMIN_MEDIA_ROOT.") print _(" Admin-media files should be copied manually to ADMIN_MEDIA_ROOT.")
@ -194,7 +195,7 @@ def at_initial_setup():
return return
try: try:
mod = __import__(modname, fromlist=[None]) mod = __import__(modname, fromlist=[None])
except ImportError, ValueError: except (ImportError, ValueError):
return return
print _(" Running at_initial_setup() hook.") print _(" Running at_initial_setup() hook.")
if mod.__dict__.get("at_initial_setup", None): if mod.__dict__.get("at_initial_setup", None):

View file

@ -12,6 +12,7 @@ etc.
""" """
import re import re
from src.utils.utils import make_iter from src.utils.utils import make_iter
from src.utils import logger
# variables # variables
MSDP = chr(69) MSDP = chr(69)
@ -24,7 +25,7 @@ MSDP_ARRAY_CLOSE = chr(6)
regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)) # return 2-tuple regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)) # return 2-tuple
regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # return 2-tuple (may be nested) regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # return 2-tuple (may be nested)
regex_varval = re.compile(r"%s(.*?)%s(.*?)[%s]" % (MSDP_VAR, MSDP_VAL, ENDING)) # return 2-tuple regex_varval = re.compile(r"%s(.*?)%s(.*?)" % (MSDP_VAR, MSDP_VAL)) # return 2-tuple
class Msdp(object): class Msdp(object):
""" """
@ -111,8 +112,8 @@ class Msdp(object):
tables[table[0]] = dict(regex_varval(table[1])) tables[table[0]] = dict(regex_varval(table[1]))
for array in regex_array.findall(data): for array in regex_array.findall(data):
arrays[array[0]] = dict(regex_varval(array[1])) arrays[array[0]] = dict(regex_varval(array[1]))
variables = dict(regex._varval(regex_array.sub("", regex_table.sub("", data)))) variables = dict(regex_varval(regex_array.sub("", regex_table.sub("", data))))
print variables
# MSDP Commands # MSDP Commands
@ -125,7 +126,7 @@ class Msdp(object):
The List command allows for retrieving various info about the server/client The List command allows for retrieving various info about the server/client
""" """
if arg == 'COMMANDS': if arg == 'COMMANDS':
return self.func_to_msdp(arg, MSDP_COMMANDS.keys()) return self.func_to_msdp(arg, self.MSDP_COMMANDS.keys())
elif arg == 'LISTS': elif arg == 'LISTS':
return self.func_to_msdp(arg, ("COMMANDS", "LISTS", return self.func_to_msdp(arg, ("COMMANDS", "LISTS",
"CONFIGURABLE_VARIABLES", "CONFIGURABLE_VARIABLES",
@ -133,11 +134,11 @@ class Msdp(object):
elif arg == 'CONFIGURABLE_VARIABLES': elif arg == 'CONFIGURABLE_VARIABLES':
return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID")) return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
elif arg == 'REPORTABLE_VARIABLES': elif arg == 'REPORTABLE_VARIABLES':
return self.func_to_msdp(arg, MSDP_REPORTABLE.keys()) return self.func_to_msdp(arg, self.MSDP_REPORTABLE.keys())
elif arg == 'REPORTED_VARIABLES': elif arg == 'REPORTED_VARIABLES':
return self.func_to_msdp(arg, MSDP_REPORTED.keys()) return self.func_to_msdp(arg, self.MSDP_REPORTED.keys())
elif arg == 'SENDABLE_VARIABLES': elif arg == 'SENDABLE_VARIABLES':
return self.func_to_msdp(arg, MSDP_SEND.keys()) return self.func_to_msdp(arg, self.MSDP_SEND.keys())
else: else:
return self.func_to_msdp("LIST", arg) return self.func_to_msdp("LIST", arg)
@ -147,7 +148,7 @@ class Msdp(object):
reportable variable to the client. reportable variable to the client.
""" """
try: try:
MSDP_REPORTABLE[arg](report=True) self.MSDP_REPORTABLE[arg](report=True)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
@ -156,16 +157,16 @@ class Msdp(object):
Unreport a previously reported variable Unreport a previously reported variable
""" """
try: try:
MSDP_REPORTABLE[arg](eport=False) self.MSDP_REPORTABLE[arg](eport=False)
except Exception: except Exception:
logger.log_trace() self.logger.log_trace()
def msdp_cmd_reset(self, arg): def msdp_cmd_reset(self, arg):
""" """
The reset command resets a variable to its initial state. The reset command resets a variable to its initial state.
""" """
try: try:
MSDP_REPORTABLE[arg](reset=True) self.MSDP_REPORTABLE[arg](reset=True)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
@ -177,21 +178,22 @@ class Msdp(object):
arg - this is a list of variables the client wants. arg - this is a list of variables the client wants.
""" """
ret = [] ret = []
for var in makeiter(arg): for var in make_iter(arg):
try: try:
ret.append(MSDP_REPORTABLE[arg](send=True)) ret.append(self.MSDP_REPORTABLE[arg](send=True))
except Exception: except Exception:
logger.log_trace() logger.log_trace()
return ret return ret
MSDP_COMMANDS = { MSDP_COMMANDS = {
"LIST": self.msdp_list, "LIST": "msdp_list",
"REPORT":"mspd_report", "REPORT":"mspd_report",
"RESET":"mspd_reset", "RESET":"mspd_reset",
"SEND":"mspd_send", "SEND":"mspd_send",
"UNREPORT":"mspd_unreport" "UNREPORT":"mspd_unreport"
} }
# MSDP_MAP is a standard suggestions for making it easy to create generic guis. # MSDP_MAP is a standard suggestions for making it easy to create generic guis.
# this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It # this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It
# is up to these commands to return data on proper form. # is up to these commands to return data on proper form.

View file

@ -244,6 +244,7 @@ class ServerSession(Session):
else: else:
logger.log_errmsg("oob_data_in error: funcname '%s' not found in OOB_FUNC_MODULE." % funcname) logger.log_errmsg("oob_data_in error: funcname '%s' not found in OOB_FUNC_MODULE." % funcname)
if outdata: if outdata:
# we have a result, send it back
self.oob_data_out(outdata) self.oob_data_out(outdata)

View file

@ -951,6 +951,7 @@ class TypedObject(SharedMemoryModel):
# If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling # If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling
# method to use e.g. self.is_typeclass() to detect that the result is not the one asked for. # method to use e.g. self.is_typeclass() to detect that the result is not the one asked for.
_GA(self, "_display_errmsg")(errstring) _GA(self, "_display_errmsg")(errstring)
_SA(self, "typeclass_lasterrmsg", errstring)
return _GA(self, "_get_default_typeclass")(cache=False, silent=False, save=False) return _GA(self, "_get_default_typeclass")(cache=False, silent=False, save=False)
#@typeclass.deleter #@typeclass.deleter
@ -961,6 +962,11 @@ class TypedObject(SharedMemoryModel):
# typeclass property # typeclass property
typeclass = property(__typeclass_get, fdel=__typeclass_del) typeclass = property(__typeclass_get, fdel=__typeclass_del)
# the last error string will be stored here for accessing methods to access.
# It is set by _display_errmsg, which will print to log if error happens
# during server startup.
typeclass_last_errmsg = ""
def _path_import(self, path): def _path_import(self, path):
""" """
Import a class from a python path of the Import a class from a python path of the
@ -973,17 +979,19 @@ class TypedObject(SharedMemoryModel):
return None return None
try: try:
modpath, class_name = path.rsplit('.', 1) modpath, class_name = path.rsplit('.', 1)
module = __import__(modpath, fromlist=[class_name]) module = __import__(modpath, fromlist=["none"])
return module.__dict__[class_name] return module.__dict__[class_name]
except ImportError: except ImportError:
trc = sys.exc_traceback trc = sys.exc_traceback
if not trc.tb_next: if not trc.tb_next:
# we separate between not finding the module, and finding a buggy one. # we separate between not finding the module, and finding a buggy one.
errstring += "(Tried path '%s')." % path errstring = "Typeclass not found trying path '%s'." % path
else: else:
# a bug in the module is reported normally. # a bug in the module is reported normally.
trc = traceback.format_exc() trc = traceback.format_exc()
errstring += "\n%sError importing '%s'." % (trc, path) errstring = "\n%sError importing '%s'." % (trc, path)
except (ValueError, TypeError):
errstring = "Malformed typeclass path '%s'." % path
except KeyError: except KeyError:
errstring = "No class '%s' was found in module '%s'." errstring = "No class '%s' was found in module '%s'."
errstring = errstring % (class_name, modpath) errstring = errstring % (class_name, modpath)
@ -997,26 +1005,32 @@ class TypedObject(SharedMemoryModel):
""" """
Helper function to display error. Helper function to display error.
""" """
infochan = None if ServerConfig.objects.conf("server_starting_mode"):
cmessage = message print message.strip()
try: else:
from src.comms.models import Channel _SA(self, "typeclass_last_errmsg", message.strip())
infochan = settings.CHANNEL_MUDINFO return
infochan = Channel.objects.get_channel(infochan[0])
if infochan: #infochan = None
cname = infochan.key #cmessage = message
cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n') if line]) #try:
cmessage = cmessage.strip() # from src.comms.models import Channel
infochan.msg(cmessage) # infochan = settings.CHANNEL_MUDINFO
else: # infochan = Channel.objects.get_channel(infochan[0])
# no mudinfo channel is found. Log instead. # if infochan:
cmessage = "\n".join(["[NO MUDINFO CHANNEL]: %s" % line for line in message.split('\n')]) # cname = infochan.key
logger.log_errmsg(cmessage) # cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n') if line])
except Exception: # cmessage = cmessage.strip()
if ServerConfig.objects.conf("server_starting_mode"): # infochan.msg(cmessage)
print cmessage # else:
else: # # no mudinfo channel is found. Log instead.
logger.log_trace(cmessage) # cmessage = "\n".join(["[NO MUDINFO CHANNEL]: %s" % line for line in message.split('\n')])
# logger.log_errmsg(cmessage)
#except Exception:
# if ServerConfig.objects.conf("server_starting_mode"):
# print cmessage
# else:
# logger.log_trace(cmessage)
def _get_default_typeclass(self, cache=False, silent=False, save=False): def _get_default_typeclass(self, cache=False, silent=False, save=False):
""" """
@ -1064,7 +1078,9 @@ class TypedObject(SharedMemoryModel):
""" """
Returns true if this object has this type Returns true if this object has this type
OR has a typeclass which is an subclass of OR has a typeclass which is an subclass of
the given typeclass. the given typeclass. This operates on the actually
loaded typeclass (this is important since a failing
typeclass may instead have its default currently loaded)
typeclass - can be a class object or the typeclass - can be a class object or the
python path to such an object to match against. python path to such an object to match against.
@ -1079,7 +1095,7 @@ class TypedObject(SharedMemoryModel):
pass pass
typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in _GA(self, "_typeclass_paths")] typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in _GA(self, "_typeclass_paths")]
if exact: if exact:
current_path = _GA(self, "_cached_db_typeclass_path") current_path = _GA(self.typeclass, "path") #"_GA(self, "_cached_db_typeclass_path")
return typeclass and any((current_path == typec for typec in typeclasses)) return typeclass and any((current_path == typec for typec in typeclasses))
else: else:
# check parent chain # check parent chain
@ -1158,10 +1174,9 @@ class TypedObject(SharedMemoryModel):
self.nattr(nattr, delete=True) self.nattr(nattr, delete=True)
else: else:
#print "deleting attrs ..." #print "deleting attrs ..."
self.get_all_attributes()
for attr in self.get_all_attributes(): for attr in self.get_all_attributes():
attr.delete() attr.delete()
for nattr in self.ndb.all(): for nattr in self.ndb.all:
del nattr del nattr
# run hooks for this new typeclass # run hooks for this new typeclass
@ -1328,8 +1343,9 @@ class TypedObject(SharedMemoryModel):
_GA(self, 'obj').set_attribute(attrname, value) _GA(self, 'obj').set_attribute(attrname, value)
def __delattr__(self, attrname): def __delattr__(self, attrname):
_GA(self, 'obj').del_attribute(attrname) _GA(self, 'obj').del_attribute(attrname)
def all(self): def get_all(self):
return _GA(self, 'obj').get_all_attributes() return _GA(self, 'obj').get_all_attributes()
all = property(get_all)
self._db_holder = DbHolder(self) self._db_holder = DbHolder(self)
return self._db_holder return self._db_holder
#@db.setter #@db.setter
@ -1386,9 +1402,10 @@ class TypedObject(SharedMemoryModel):
except AttributeError: except AttributeError:
class NdbHolder(object): class NdbHolder(object):
"Holder for storing non-persistent attributes." "Holder for storing non-persistent attributes."
def all(self): def get_all(self):
return [val for val in self.__dict__.keys() return [val for val in self.__dict__.keys()
if not val.startswith['_']] if not val.startswith('_')]
all = property(get_all)
def __getattribute__(self, key): def __getattribute__(self, key):
# return None if no matching attribute was found. # return None if no matching attribute was found.
try: try:

View file

@ -13,12 +13,12 @@ shorter "create.object()".
The respective object managers hold more methods for manipulating and The respective object managers hold more methods for manipulating and
searching objects already existing in the database. searching objects already existing in the database.
Models covered: Models covered:
Objects Objects
Scripts Scripts
Help Help
Message Message
Channel Channel
Players Players
""" """
@ -26,8 +26,7 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import IntegrityError from django.db import IntegrityError
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.utils import logger, utils, idmapper from src.utils import utils, logger
from src.utils.utils import is_iter, has_parent, inherits_from
# limit symbol import from API # limit symbol import from API
__all__ = ("create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_player") __all__ = ("create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_player")
@ -35,23 +34,28 @@ __all__ = ("create_object", "create_script", "create_help_entry", "create_messag
GA = object.__getattribute__ GA = object.__getattribute__
# #
# Game Object creation # Game Object creation
# #
def create_object(typeclass, key=None, location=None, def create_object(typeclass, key=None, location=None,
home=None, player=None, permissions=None, locks=None, home=None, player=None, permissions=None, locks=None,
aliases=None, destination=None): aliases=None, destination=None, report_to=None):
""" """
Create a new in-game object. Any game object is a combination Create a new in-game object. Any game object is a combination
of a database object that stores data persistently to of a database object that stores data persistently to
the database, and a typeclass, which on-the-fly 'decorates' the database, and a typeclass, which on-the-fly 'decorates'
the database object into whataver different type of object the database object into whataver different type of object
it is supposed to be in the game. it is supposed to be in the game.
See src.objects.managers for methods to manipulate existing objects See src.objects.managers for methods to manipulate existing objects
in the database. src.objects.objects holds the base typeclasses in the database. src.objects.objects holds the base typeclasses
and src.objects.models hold the database model. and src.objects.models hold the database model.
report_to is an optional object for reporting errors to in string form.
If report_to is not set, errors will be raised as en Exception
containing the error message. If set, this method will return
None upon errors.
""" """
# deferred import to avoid loops # deferred import to avoid loops
from src.objects.objects import Object from src.objects.objects import Object
@ -64,32 +68,36 @@ def create_object(typeclass, key=None, location=None,
typeclass = typeclass.typeclass.path typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Object) or utils.inherits_from(typeclass, Object): elif isinstance(typeclass, Object) or utils.inherits_from(typeclass, Object):
# this is already an object typeclass, extract its path # this is already an object typeclass, extract its path
typeclass = typeclass.path typeclass = typeclass.path
# create new database object # create new database object
new_db_object = ObjectDB() new_db_object = ObjectDB()
# assign the typeclass # assign the typeclass
typeclass = utils.to_unicode(typeclass) typeclass = utils.to_unicode(typeclass)
new_db_object.typeclass_path = typeclass new_db_object.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This # the name/key is often set later in the typeclass. This
# is set here as a failsafe. # is set here as a failsafe.
if key: if key:
new_db_object.key = key new_db_object.key = key
else: else:
new_db_object.key = "#%i" % new_db_object.id new_db_object.key = "#%i" % new_db_object.id
# this will either load the typeclass or the default one # this will either load the typeclass or the default one
new_object = new_db_object.typeclass new_object = new_db_object.typeclass
if not GA(new_object, "is_typeclass")(typeclass, exact=True):
if not GA(new_db_object, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default # this will fail if we gave a typeclass as input and it still gave us a default
SharedMemoryModel.delete(new_db_object) SharedMemoryModel.delete(new_db_object)
return None if report_to:
GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_object.key, typeclass,
GA(new_db_object, "typeclass_last_errmsg")))
return None
else:
raise Exception(GA(new_db_object, "typeclass_last_errmsg"))
# from now on we can use the typeclass object # from now on we can use the typeclass object
# as if it was the database object. # as if it was the database object.
if player: if player:
@ -97,14 +105,14 @@ def create_object(typeclass, key=None, location=None,
new_object.player = player new_object.player = player
player.obj = new_object player.obj = new_object
new_object.destination = destination new_object.destination = destination
# call the hook method. This is where all at_creation # call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom # customization happens as the typeclass stores custom
# things on its database object. # things on its database object.
new_object.basetype_setup() # setup the basics of Exits, Characters etc. new_object.basetype_setup() # setup the basics of Exits, Characters etc.
new_object.at_object_creation() new_object.at_object_creation()
# custom-given perms/locks overwrite hooks # custom-given perms/locks overwrite hooks
if permissions: if permissions:
new_object.permissions = permissions new_object.permissions = permissions
@ -119,15 +127,15 @@ def create_object(typeclass, key=None, location=None,
else: else:
new_object.home = settings.CHARACTER_DEFAULT_HOME new_object.home = settings.CHARACTER_DEFAULT_HOME
if location: if location:
new_object.move_to(location, quiet=True) new_object.move_to(location, quiet=True)
else: else:
# rooms would have location=None. # rooms would have location=None.
new_object.location = None new_object.location = None
# post-hook setup (mainly used by Exits) # post-hook setup (mainly used by Exits)
new_object.basetype_posthook_setup() new_object.basetype_posthook_setup()
new_object.save() new_object.save()
return new_object return new_object
@ -136,12 +144,12 @@ def create_object(typeclass, key=None, location=None,
object = create_object object = create_object
# #
# Script creation # Script creation
# #
def create_script(typeclass, key=None, obj=None, locks=None, def create_script(typeclass, key=None, obj=None, locks=None,
interval=None, start_delay=None, repeats=None, interval=None, start_delay=None, repeats=None,
persistent=None, autostart=True): persistent=None, autostart=True, report_to=None):
""" """
Create a new script. All scripts are a combination Create a new script. All scripts are a combination
of a database object that communicates with the of a database object that communicates with the
@ -149,42 +157,47 @@ def create_script(typeclass, key=None, obj=None, locks=None,
database object into being different types of scripts. database object into being different types of scripts.
It's behaviour is similar to the game objects except It's behaviour is similar to the game objects except
scripts has a time component and are more limited in scripts has a time component and are more limited in
scope. scope.
Argument 'typeclass' can be either an actual Argument 'typeclass' can be either an actual
typeclass object or a python path to such an object. typeclass object or a python path to such an object.
Only set key here if you want a unique name for this Only set key here if you want a unique name for this
particular script (set it in config to give particular script (set it in config to give
same key to all scripts of the same type). Set obj same key to all scripts of the same type). Set obj
to tie this script to a particular object. to tie this script to a particular object.
See src.scripts.manager for methods to manipulate existing See src.scripts.manager for methods to manipulate existing
scripts in the database. scripts in the database.
report_to is an obtional object to receive error messages.
If report_to is not set, an Exception with the
error will be raised. If set, this method will
return None upon errors.
""" """
# deferred import to avoid loops. # deferred import to avoid loops.
from src.scripts.scripts import Script from src.scripts.scripts import Script
#print "in create_script", typeclass #print "in create_script", typeclass
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
if not typeclass: if not typeclass:
typeclass = settings.BASE_SCRIPT_TYPECLASS typeclass = settings.BASE_SCRIPT_TYPECLASS
elif isinstance(typeclass, ScriptDB): elif isinstance(typeclass, ScriptDB):
# this is already an scriptdb instance, extract its typeclass # this is already an scriptdb instance, extract its typeclass
typeclass = new_db_object.typeclass.path typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Script) or utils.inherits_from(typeclass, Script): elif isinstance(typeclass, Script) or utils.inherits_from(typeclass, Script):
# this is already an object typeclass, extract its path # this is already an object typeclass, extract its path
typeclass = typeclass.path typeclass = typeclass.path
# create new database script # create new database script
new_db_script = ScriptDB() new_db_script = ScriptDB()
# assign the typeclass # assign the typeclass
typeclass = utils.to_unicode(typeclass) typeclass = utils.to_unicode(typeclass)
new_db_script.typeclass_path = typeclass new_db_script.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This # the name/key is often set later in the typeclass. This
# is set here as a failsafe. # is set here as a failsafe.
if key: if key:
new_db_script.key = key new_db_script.key = key
else: else:
@ -195,15 +208,19 @@ def create_script(typeclass, key=None, obj=None, locks=None,
if not GA(new_db_script, "is_typeclass")(typeclass, exact=True): if not GA(new_db_script, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default # this will fail if we gave a typeclass as input and it still gave us a default
print "failure:", new_db_script, typeclass
SharedMemoryModel.delete(new_db_script) SharedMemoryModel.delete(new_db_script)
return None if report_to:
GA(report_to, "msg")("Error creating %s (%s): %s" % (new_db_script.key, typeclass,
GA(new_db_script, "typeclass_last_errmsg")))
return None
else:
raise Exception(GA(new_db_script, "typeclass_last_errmsg"))
if obj: if obj:
try: try:
new_script.obj = obj new_script.obj = obj
except ValueError: except ValueError:
new_script.obj = obj.dbobj new_script.obj = obj.dbobj
# call the hook method. This is where all at_creation # call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom # customization happens as the typeclass stores custom
@ -212,10 +229,10 @@ def create_script(typeclass, key=None, obj=None, locks=None,
# custom-given variables override the hook # custom-given variables override the hook
if key: if key:
new_script.key = key new_script.key = key
if locks: if locks:
new_script.locks.add(locks) new_script.locks.add(locks)
if interval != None: if interval != None:
new_script.interval = interval new_script.interval = interval
if start_delay != None: if start_delay != None:
new_script.start_delay = start_delay new_script.start_delay = start_delay
@ -223,13 +240,13 @@ def create_script(typeclass, key=None, obj=None, locks=None,
new_script.repeats = repeats new_script.repeats = repeats
if persistent != None: if persistent != None:
new_script.persistent = persistent new_script.persistent = persistent
# a new created script should usually be started. # a new created script should usually be started.
if autostart: if autostart:
new_script.start() new_script.start()
new_db_script.save() new_db_script.save()
return new_script return new_script
#alias #alias
script = create_script script = create_script
@ -243,7 +260,7 @@ def create_help_entry(key, entrytext, category="General", locks=None):
help entries are dynamic and directly taken from the __doc__ entries help entries are dynamic and directly taken from the __doc__ entries
of the command. The database-stored help entries are intended for more of the command. The database-stored help entries are intended for more
general help on the game, more extensive info, in-game setting information general help on the game, more extensive info, in-game setting information
and so on. and so on.
""" """
from src.help.models import HelpEntry from src.help.models import HelpEntry
@ -255,19 +272,19 @@ def create_help_entry(key, entrytext, category="General", locks=None):
if locks: if locks:
new_help.locks.add(locks) new_help.locks.add(locks)
new_help.save() new_help.save()
return new_help return new_help
except IntegrityError: except IntegrityError:
string = "Could not add help entry: key '%s' already exists." % key string = "Could not add help entry: key '%s' already exists." % key
logger.log_errmsg(string) logger.log_errmsg(string)
return None return None
except Exception: except Exception:
logger.log_trace() logger.log_trace()
return None return None
# alias # alias
help_entry = create_help_entry help_entry = create_help_entry
# #
# Comm system methods # Comm system methods
# #
def create_message(senderobj, message, channels=None, def create_message(senderobj, message, channels=None,
@ -279,7 +296,7 @@ def create_message(senderobj, message, channels=None,
senderobj - the player sending the message. This must be the actual object. senderobj - the player sending the message. This must be the actual object.
message - text with the message. Eventual headers, titles etc message - text with the message. Eventual headers, titles etc
should all be included in this text string. Formatting should all be included in this text string. Formatting
will be retained. will be retained.
channels - a channel or a list of channels to send to. The channels channels - a channel or a list of channels to send to. The channels
may be actual channel objects or their unique key strings. may be actual channel objects or their unique key strings.
receivers - a player to send to, or a list of them. May be Player objects receivers - a player to send to, or a list of them. May be Player objects
@ -289,7 +306,7 @@ def create_message(senderobj, message, channels=None,
The Comm system is created very open-ended, so it's fully possible The Comm system is created very open-ended, so it's fully possible
to let a message both go to several channels and to several receivers to let a message both go to several channels and to several receivers
at the same time, it's up to the command definitions to limit this as at the same time, it's up to the command definitions to limit this as
desired. desired.
""" """
from src.comms.models import Msg from src.comms.models import Msg
from src.players.models import PlayerDB from src.players.models import PlayerDB
@ -304,10 +321,10 @@ def create_message(senderobj, message, channels=None,
elif hasattr(obj, 'db_player'): elif hasattr(obj, 'db_player'):
return obj.db_player return obj.db_player
else: else:
return None return None
if not message: if not message:
# we don't allow empty messages. # we don't allow empty messages.
return return
new_message = Msg() new_message = Msg()
@ -315,19 +332,19 @@ def create_message(senderobj, message, channels=None,
new_message.message = message new_message.message = message
new_message.save() new_message.save()
if channels: if channels:
if not is_iter(channels): if not utils.is_iter(channels):
channels = [channels] channels = [channels]
new_message.channels = [channel for channel in new_message.channels = [channel for channel in
[to_object(channel, objtype='channel') [to_object(channel, objtype='channel')
for channel in channels] if channel] for channel in channels] if channel]
if receivers: if receivers:
#print "Found receiver:", receivers #print "Found receiver:", receivers
if not is_iter(receivers): if not utils.is_iter(receivers):
receivers = [receivers] receivers = [receivers]
#print "to_player: %s" % to_player(receivers[0]) #print "to_player: %s" % to_player(receivers[0])
new_message.receivers = [to_player(receiver) for receiver in new_message.receivers = [to_player(receiver) for receiver in
[to_object(receiver) for receiver in receivers] [to_object(receiver) for receiver in receivers]
if receiver] if receiver]
if locks: if locks:
new_message.locks.add(locks) new_message.locks.add(locks)
new_message.save() new_message.save()
@ -343,20 +360,20 @@ def create_channel(key, aliases=None, desc=None,
specifying the receivers explicitly. Instead players may specifying the receivers explicitly. Instead players may
'connect' to the channel and follow the flow of messages. By 'connect' to the channel and follow the flow of messages. By
default the channel allows access to all old messages, but default the channel allows access to all old messages, but
this can be turned off with the keep_log switch. this can be turned off with the keep_log switch.
key - this must be unique. key - this must be unique.
aliases - list of alternative (likely shorter) keynames. aliases - list of alternative (likely shorter) keynames.
locks - lock string definitions locks - lock string definitions
""" """
from src.comms.models import Channel from src.comms.models import Channel
from src.comms import channelhandler from src.comms import channelhandler
try: try:
new_channel = Channel() new_channel = Channel()
new_channel.key = key new_channel.key = key
if aliases: if aliases:
if not is_iter(aliases): if not utils.is_iter(aliases):
aliases = [aliases] aliases = [aliases]
new_channel.aliases = ",".join([alias for alias in aliases]) new_channel.aliases = ",".join([alias for alias in aliases])
new_channel.desc = desc new_channel.desc = desc
@ -369,33 +386,33 @@ def create_channel(key, aliases=None, desc=None,
new_channel.locks.add(locks) new_channel.locks.add(locks)
new_channel.save() new_channel.save()
channelhandler.CHANNELHANDLER.add_channel(new_channel) channelhandler.CHANNELHANDLER.add_channel(new_channel)
return new_channel return new_channel
channel = create_channel
channel = create_channel
# #
# Player creation methods # Player creation methods
# #
def create_player(name, email, password, def create_player(name, email, password,
user=None, user=None,
typeclass=None, typeclass=None,
is_superuser=False, is_superuser=False,
locks=None, permissions=None, locks=None, permissions=None,
create_character=True, character_typeclass=None, create_character=True, character_typeclass=None,
character_location=None, character_home=None, character_location=None, character_home=None,
player_dbobj=None): player_dbobj=None, report_to=None):
""" """
This creates a new player, handling the creation of the User This creates a new player, handling the creation of the User
object and its associated Player object. object and its associated Player object.
If player_dbobj is given, this player object is used instead of If player_dbobj is given, this player object is used instead of
creating a new one. This is called by the admin interface since it creating a new one. This is called by the admin interface since it
needs to create the player object in order to relate it automatically needs to create the player object in order to relate it automatically
to the user. to the user.
If create_character is If create_character is
True, a game player object with the same name as the User/Player will True, a game player object with the same name as the User/Player will
also be created. Its typeclass and base properties can also be given. also be created. Its typeclass and base properties can also be given.
@ -403,15 +420,15 @@ def create_player(name, email, password,
Returns the new game character, or the Player obj if no Returns the new game character, or the Player obj if no
character is created. For more info about the typeclass argument, character is created. For more info about the typeclass argument,
see create_objects() above. see create_objects() above.
Note: if user is supplied, it will NOT be modified (args name, email, Note: if user is supplied, it will NOT be modified (args name, email,
passw and is_superuser will be ignored). Change those properties passw and is_superuser will be ignored). Change those properties
directly on the User instead. directly on the User instead.
If no permissions are given (None), the default permission group If no permissions are given (None), the default permission group
as defined in settings.PERMISSION_PLAYER_DEFAULT will be as defined in settings.PERMISSION_PLAYER_DEFAULT will be
assigned. If permissions are given, no automatic assignment will assigned. If permissions are given, no automatic assignment will
occur. occur.
Concerning is_superuser: Concerning is_superuser:
A superuser should have access to everything A superuser should have access to everything
@ -420,18 +437,18 @@ def create_player(name, email, password,
django's own creation, not this one). django's own creation, not this one).
Usually only the server admin should need to be superuser, all Usually only the server admin should need to be superuser, all
other access levels can be handled with more fine-grained other access levels can be handled with more fine-grained
permissions or groups. permissions or groups.
Since superuser overrules all permissions, we don't Since superuser overrules all permissions, we don't
set any in this case. set any in this case.
""" """
# The system should already have checked so the name/email # The system should already have checked so the name/email
# isn't already registered, and that the password is ok before # isn't already registered, and that the password is ok before
# getting here. # getting here.
from src.players.models import PlayerDB from src.players.models import PlayerDB
from src.players.player import Player from src.players.player import Player
if not email: if not email:
email = "dummy@dummy.com" email = "dummy@dummy.com"
if user: if user:
@ -441,7 +458,7 @@ def create_player(name, email, password,
if is_superuser: if is_superuser:
new_user = User.objects.create_superuser(name, email, password) new_user = User.objects.create_superuser(name, email, password)
else: else:
new_user = User.objects.create_user(name, email, password) new_user = User.objects.create_user(name, email, password)
try: try:
if not typeclass: if not typeclass:
typeclass = settings.BASE_PLAYER_TYPECLASS typeclass = settings.BASE_PLAYER_TYPECLASS
@ -450,7 +467,7 @@ def create_player(name, email, password,
typeclass = typeclass.typeclass.path typeclass = typeclass.typeclass.path
elif isinstance(typeclass, Player) or utils.inherits_from(typeclass, Player): elif isinstance(typeclass, Player) or utils.inherits_from(typeclass, Player):
# this is already an object typeclass, extract its path # this is already an object typeclass, extract its path
typeclass = typeclass.path typeclass = typeclass.path
if player_dbobj: if player_dbobj:
new_db_player = player_dbobj new_db_player = player_dbobj
@ -458,7 +475,7 @@ def create_player(name, email, password,
new_db_player = PlayerDB(db_key=name, user=new_user) new_db_player = PlayerDB(db_key=name, user=new_user)
new_db_player.save() new_db_player.save()
# assign the typeclass # assign the typeclass
typeclass = utils.to_unicode(typeclass) typeclass = utils.to_unicode(typeclass)
new_db_player.typeclass_path = typeclass new_db_player.typeclass_path = typeclass
@ -468,13 +485,18 @@ def create_player(name, email, password,
if not GA(new_db_player, "is_typeclass")(typeclass, exact=True): if not GA(new_db_player, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default # this will fail if we gave a typeclass as input and it still gave us a default
SharedMemoryModel.delete(new_db_player) SharedMemoryModel.delete(new_db_player)
return None if report_to:
GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_player.key, typeclass,
GA(new_db_player, "typeclass_last_errmsg")))
return None
else:
raise Exception(GA(new_db_player, "typeclass_last_errmsg"))
new_player.basetype_setup() # setup the basic locks and cmdset new_player.basetype_setup() # setup the basic locks and cmdset
# call hook method (may override default permissions) # call hook method (may override default permissions)
new_player.at_player_creation() new_player.at_player_creation()
# custom given arguments potentially overrides the hook # custom given arguments potentially overrides the hook
if permissions: if permissions:
new_player.permissions = permissions new_player.permissions = permissions
elif not new_player.permissions: elif not new_player.permissions:
@ -483,19 +505,19 @@ def create_player(name, email, password,
if locks: if locks:
new_player.locks.add(locks) new_player.locks.add(locks)
# create *in-game* 'player' object # create *in-game* 'player' object
if create_character: if create_character:
if not character_typeclass: if not character_typeclass:
character_typeclass = settings.BASE_CHARACTER_TYPECLASS character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# creating the object automatically links the player # creating the object automatically links the player
# and object together by player.obj <-> obj.player # and object together by player.obj <-> obj.player
new_character = create_object(character_typeclass, key=name, new_character = create_object(character_typeclass, key=name,
location=None, home=character_location, location=None, home=character_location,
permissions=permissions, permissions=permissions,
player=new_player) player=new_player, report_to=report_to)
return new_character return new_character
return new_player return new_player
except Exception,e: except Exception:
# a failure in creating the character # a failure in creating the character
if not user: if not user:
# in there was a failure we clean up everything we can # in there was a failure we clean up everything we can
@ -507,12 +529,12 @@ def create_player(name, email, password,
try: try:
new_player.delete() new_player.delete()
except Exception: except Exception:
pass pass
try: try:
del new_character del new_character
except Exception: except Exception:
pass pass
raise raise
# alias # alias
player = create_player player = create_player