2008-06-15 17:21:02 +00:00
|
|
|
"""
|
|
|
|
|
This is the command processing module. It is instanced once in the main
|
|
|
|
|
server module and the handle() function is hit every time a player sends
|
|
|
|
|
something.
|
|
|
|
|
"""
|
2009-10-18 19:29:18 +00:00
|
|
|
#import time
|
2009-01-24 20:30:46 +00:00
|
|
|
from traceback import format_exc
|
2009-10-18 19:29:18 +00:00
|
|
|
from django.conf import settings
|
2009-01-24 20:30:46 +00:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2007-05-22 15:11:56 +00:00
|
|
|
import defines_global
|
2007-05-11 15:23:27 +00:00
|
|
|
import cmdtable
|
2009-05-01 15:34:43 +00:00
|
|
|
import statetable
|
2008-06-15 20:31:25 +00:00
|
|
|
import logger
|
|
|
|
|
import comsys
|
2009-01-24 20:30:46 +00:00
|
|
|
import alias_mgr
|
2006-12-03 00:40:19 +00:00
|
|
|
|
2009-10-18 19:29:18 +00:00
|
|
|
COMMAND_MAXLEN = settings.COMMAND_MAXLEN
|
|
|
|
|
|
2006-11-28 21:51:36 +00:00
|
|
|
class UnknownCommand(Exception):
|
2008-06-17 00:38:59 +00:00
|
|
|
"""
|
|
|
|
|
Throw this when a user enters an an invalid command.
|
|
|
|
|
"""
|
2008-12-14 20:21:02 +00:00
|
|
|
pass
|
2009-08-16 01:18:58 +00:00
|
|
|
|
|
|
|
|
class CommandNotInState(Exception):
|
|
|
|
|
"""
|
|
|
|
|
Throw this when a user tries a global command that exists, but
|
|
|
|
|
don't happen to be defined in the current game state.
|
|
|
|
|
err_string: The error string returned to the user.
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self,err_string):
|
|
|
|
|
self.err_string = err_string
|
|
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
class ExitCommandHandler(Exception):
|
|
|
|
|
"""
|
|
|
|
|
Thrown when something happens and it's time to exit the command handler.
|
|
|
|
|
"""
|
|
|
|
|
pass
|
2008-12-14 20:21:02 +00:00
|
|
|
|
|
|
|
|
class Command(object):
|
2009-01-24 20:30:46 +00:00
|
|
|
# The source object that the command originated from.
|
|
|
|
|
source_object = None
|
|
|
|
|
# The session that the command originated from (optional)
|
2008-12-14 20:21:02 +00:00
|
|
|
session = None
|
|
|
|
|
# The entire raw, un-parsed command.
|
|
|
|
|
raw_input = None
|
|
|
|
|
# Just the root command. IE: if input is "look dog", this is just "look".
|
|
|
|
|
command_string = None
|
|
|
|
|
# A list of switches in the form of strings.
|
|
|
|
|
command_switches = []
|
|
|
|
|
# The un-parsed argument provided. IE: if input is "look dog", this is "dog".
|
|
|
|
|
command_argument = None
|
2009-10-18 19:29:18 +00:00
|
|
|
# list of tuples for possible multi-space commands and their arguments
|
|
|
|
|
command_alternatives = None
|
2008-12-15 01:10:26 +00:00
|
|
|
# A reference to the command function looked up in a command table.
|
|
|
|
|
command_function = None
|
2009-01-24 03:17:43 +00:00
|
|
|
# An optional dictionary that is passed through the command table as extra_vars.
|
|
|
|
|
extra_vars = None
|
2008-12-14 20:21:02 +00:00
|
|
|
|
|
|
|
|
def parse_command_switches(self):
|
|
|
|
|
"""
|
|
|
|
|
Splits any switches out of a command_string into the command_switches
|
|
|
|
|
list, and yanks the switches out of the original command_string.
|
|
|
|
|
"""
|
|
|
|
|
splitted_command = self.command_string.split('/')
|
|
|
|
|
self.command_switches = splitted_command[1:]
|
|
|
|
|
self.command_string = splitted_command[0]
|
|
|
|
|
|
|
|
|
|
def parse_command(self):
|
|
|
|
|
"""
|
|
|
|
|
Breaks the command up into the main command string, a list of switches,
|
|
|
|
|
and a string containing the argument provided with the command. More
|
|
|
|
|
specific processing is left up to the individual command functions.
|
2009-10-18 19:29:18 +00:00
|
|
|
|
|
|
|
|
The command can come in two forms:
|
|
|
|
|
command/switches arg
|
|
|
|
|
command_with_spaces arg
|
|
|
|
|
|
|
|
|
|
The first form is the normal one, used for administration and other commands
|
|
|
|
|
that benefit from the use of switches and options. The drawback is that it
|
|
|
|
|
can only consist of one single word (no spaces).
|
|
|
|
|
The second form, which does not accept switches, allows for longer command
|
|
|
|
|
names (e.g. 'press button' instead of pressbutton) and is mainly useful for
|
|
|
|
|
object-based commands for roleplay, puzzles etc.
|
2008-12-14 20:21:02 +00:00
|
|
|
"""
|
2009-10-18 19:29:18 +00:00
|
|
|
if not self.raw_input:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# add a space after the raw input; this cause split() to always
|
|
|
|
|
# create a list with at least two entries.
|
|
|
|
|
raw = "%s " % self.raw_input
|
|
|
|
|
cmd_words = raw.split()
|
2008-12-14 20:21:02 +00:00
|
|
|
try:
|
2009-10-18 19:29:18 +00:00
|
|
|
if '/' in cmd_words[0]:
|
|
|
|
|
# if we have switches we directly go for the first command form.
|
|
|
|
|
command_string, command_argument = \
|
|
|
|
|
(inp.strip() for inp in raw.split(' ', 1))
|
|
|
|
|
if command_argument:
|
|
|
|
|
self.command_argument = command_argument
|
|
|
|
|
if command_string:
|
|
|
|
|
# we have a valid command, store and parse switches.
|
|
|
|
|
self.command_string = command_string
|
|
|
|
|
self.parse_command_switches()
|
|
|
|
|
else:
|
|
|
|
|
# no switches - we need to save a list of all possible command
|
|
|
|
|
# names up to the max-length allowed.
|
|
|
|
|
command_maxlen = min(COMMAND_MAXLEN, len(cmd_words))
|
|
|
|
|
command_alternatives = []
|
|
|
|
|
for spacecount in reversed(range(command_maxlen)):
|
|
|
|
|
# store all space-separated possible command names
|
|
|
|
|
# as tuples (commandname, args). They are stored with
|
|
|
|
|
# the longest possible name first.
|
|
|
|
|
try:
|
|
|
|
|
command_alternatives.append( (" ".join(cmd_words[:spacecount+1]),
|
|
|
|
|
" ".join(cmd_words[spacecount+1:])) )
|
|
|
|
|
except IndexError:
|
|
|
|
|
continue
|
|
|
|
|
if command_alternatives:
|
|
|
|
|
# store alternatives. Store the one-word command
|
|
|
|
|
# as the default command name.
|
|
|
|
|
one_word_command = command_alternatives.pop()
|
|
|
|
|
self.command_string = one_word_command[0]
|
|
|
|
|
self.command_argument = one_word_command[1]
|
|
|
|
|
self.command_alternatives = command_alternatives
|
|
|
|
|
except IndexError:
|
|
|
|
|
# this SHOULD only happen if raw_input is malformed
|
|
|
|
|
# (like containing only control characters).
|
|
|
|
|
pass
|
|
|
|
|
|
2009-01-13 01:42:39 +00:00
|
|
|
|
2009-01-24 20:30:46 +00:00
|
|
|
def __init__(self, source_object, raw_input, session=None):
|
2008-12-15 01:10:26 +00:00
|
|
|
"""
|
|
|
|
|
Instantiates the Command object and does some preliminary parsing.
|
|
|
|
|
"""
|
2008-12-14 20:21:02 +00:00
|
|
|
self.raw_input = raw_input
|
2009-01-24 20:30:46 +00:00
|
|
|
self.source_object = source_object
|
2008-12-14 20:21:02 +00:00
|
|
|
self.session = session
|
2008-12-15 01:10:26 +00:00
|
|
|
# The work starts here.
|
2008-12-14 20:21:02 +00:00
|
|
|
self.parse_command()
|
|
|
|
|
|
|
|
|
|
def arg_has_target(self):
|
|
|
|
|
"""
|
|
|
|
|
Returns true if the argument looks to be target-style. IE:
|
|
|
|
|
page blah=hi
|
|
|
|
|
kick ball=north
|
|
|
|
|
"""
|
|
|
|
|
return "=" in self.command_argument
|
|
|
|
|
|
|
|
|
|
def get_arg_targets(self, delim=','):
|
|
|
|
|
"""
|
|
|
|
|
Returns a list of targets from the argument. These happen before
|
|
|
|
|
the '=' sign and may be separated by a delimiter.
|
|
|
|
|
"""
|
|
|
|
|
# Make sure we even have a target (= sign).
|
|
|
|
|
if not self.arg_has_target():
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
target = self.command_argument.split('=', 1)[0]
|
|
|
|
|
return [targ.strip() for targ in target.split(delim)]
|
|
|
|
|
|
|
|
|
|
def get_arg_target_value(self):
|
|
|
|
|
"""
|
|
|
|
|
In a case of something like: page bob=Hello there, the target is "bob",
|
|
|
|
|
while the value is "Hello there". This function returns the portion
|
|
|
|
|
of the command that takes place after the first equal sign.
|
|
|
|
|
"""
|
|
|
|
|
# Make sure we even have a target (= sign).
|
|
|
|
|
if not self.arg_has_target():
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return self.command_argument.split('=', 1)[1]
|
2006-11-28 21:51:36 +00:00
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
def match_idle(command):
|
|
|
|
|
"""
|
|
|
|
|
Matches against the 'idle' command. It doesn't actually do anything, but it
|
|
|
|
|
lets the users get around badly configured NAT timeouts that would cause
|
|
|
|
|
them to drop if they don't send or receive something from the connection
|
|
|
|
|
for a while.
|
|
|
|
|
"""
|
2009-01-24 20:30:46 +00:00
|
|
|
if command.session and command.command_string != 'idle' \
|
|
|
|
|
and command.command_string != None:
|
|
|
|
|
# Anything other than an 'idle' command or a blank return
|
|
|
|
|
# updates the public-facing idle time for the session.
|
2008-12-15 01:10:26 +00:00
|
|
|
command.session.count_command(silently=False)
|
2009-01-24 20:30:46 +00:00
|
|
|
elif command.session:
|
2008-12-15 01:10:26 +00:00
|
|
|
# User is hitting IDLE command. Don't update their publicly
|
|
|
|
|
# facing idle time, drop out of command handler immediately.
|
|
|
|
|
command.session.count_command(silently=True)
|
|
|
|
|
raise ExitCommandHandler
|
|
|
|
|
|
2009-08-16 01:18:58 +00:00
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
def match_alias(command):
|
|
|
|
|
"""
|
|
|
|
|
Checks to see if the entered command matches an alias. If so, replaces
|
|
|
|
|
the command_string with the correct command.
|
|
|
|
|
|
|
|
|
|
We do a dictionary lookup. If the key (the player's command_string) doesn't
|
|
|
|
|
exist on the dict, just keep the command_string the same. If the key exists,
|
|
|
|
|
its value replaces the command_string. For example, sa -> say.
|
|
|
|
|
"""
|
2009-01-24 21:03:22 +00:00
|
|
|
# See if there's an entry in the global alias table.
|
2009-01-24 20:30:46 +00:00
|
|
|
command.command_string = alias_mgr.CMD_ALIAS_LIST.get(
|
2008-12-15 01:10:26 +00:00
|
|
|
command.command_string,
|
|
|
|
|
command.command_string)
|
2009-10-18 19:29:18 +00:00
|
|
|
# Run aliasing on alternative command names (for commands with
|
|
|
|
|
# spaces in them)
|
|
|
|
|
if command.command_alternatives:
|
|
|
|
|
command_alternatives = []
|
|
|
|
|
for command_alternative in command.command_alternatives:
|
|
|
|
|
command_alternatives.append( (alias_mgr.CMD_ALIAS_LIST.get(
|
|
|
|
|
command_alternative[0],
|
|
|
|
|
command_alternative[0]),
|
|
|
|
|
command_alternative[1]) )
|
|
|
|
|
command.command_alternatives = command_alternatives
|
2008-12-15 01:10:26 +00:00
|
|
|
|
2009-01-24 21:03:22 +00:00
|
|
|
def get_aliased_message():
|
|
|
|
|
"""
|
|
|
|
|
Convenience sub-function to combine the lopped off command string
|
|
|
|
|
and arguments for posing, saying, and nospace posing aliases.
|
|
|
|
|
"""
|
2009-01-25 01:37:43 +00:00
|
|
|
if not command.command_argument:
|
|
|
|
|
return command.command_string[1:]
|
|
|
|
|
else:
|
|
|
|
|
return "%s %s" % (command.command_string[1:],
|
|
|
|
|
command.command_argument)
|
2009-01-24 21:03:22 +00:00
|
|
|
|
2009-01-20 04:18:03 +00:00
|
|
|
# Match against the single-character aliases of MUX/MUSH-dom.
|
|
|
|
|
first_char = command.command_string[0]
|
|
|
|
|
# Shortened say alias.
|
|
|
|
|
if first_char == '"':
|
2009-01-24 21:03:22 +00:00
|
|
|
command.command_argument = get_aliased_message()
|
2009-01-20 04:18:03 +00:00
|
|
|
command.command_string = "say"
|
|
|
|
|
# Shortened pose alias.
|
|
|
|
|
elif first_char == ':':
|
2009-01-24 21:03:22 +00:00
|
|
|
command.command_argument = get_aliased_message()
|
2009-01-20 04:18:03 +00:00
|
|
|
command.command_string = "pose"
|
|
|
|
|
# Pose without space alias.
|
|
|
|
|
elif first_char == ';':
|
2009-01-24 21:03:22 +00:00
|
|
|
command.command_argument = get_aliased_message()
|
2009-01-20 04:18:03 +00:00
|
|
|
command.command_string = "pose"
|
|
|
|
|
command.command_switches.insert(0, "nospace")
|
|
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
def match_channel(command):
|
|
|
|
|
"""
|
|
|
|
|
Match against a comsys channel or comsys command. If the player is talking
|
|
|
|
|
over a channel, replace command_string with @cemit. If they're entering
|
|
|
|
|
a channel manipulation command, perform the operation and kill the things
|
|
|
|
|
immediately with a True value sent back to the command handler.
|
2009-01-24 20:30:46 +00:00
|
|
|
|
|
|
|
|
This only works with PLAYER objects at this point in time.
|
2008-12-15 01:10:26 +00:00
|
|
|
"""
|
2009-01-24 20:30:46 +00:00
|
|
|
if command.session and comsys.plr_has_channel(command.session,
|
|
|
|
|
command.command_string, alias_search=True, return_muted=True):
|
2008-12-15 01:10:26 +00:00
|
|
|
|
|
|
|
|
calias = command.command_string
|
|
|
|
|
cname = comsys.plr_cname_from_alias(command.session, calias)
|
|
|
|
|
|
|
|
|
|
if command.command_argument == "who":
|
2009-01-24 20:30:46 +00:00
|
|
|
comsys.msg_cwho(command.source_object, cname)
|
2008-12-15 01:10:26 +00:00
|
|
|
raise ExitCommandHandler
|
|
|
|
|
elif command.command_argument == "on":
|
|
|
|
|
comsys.plr_chan_on(command.session, calias)
|
|
|
|
|
raise ExitCommandHandler
|
|
|
|
|
elif command.command_argument == "off":
|
|
|
|
|
comsys.plr_chan_off(command.session, calias)
|
|
|
|
|
raise ExitCommandHandler
|
|
|
|
|
elif command.command_argument == "last":
|
2009-01-24 20:30:46 +00:00
|
|
|
comsys.msg_chan_hist(command.source_object, cname)
|
2009-01-15 04:18:23 +00:00
|
|
|
raise ExitCommandHandler
|
2009-08-30 12:28:38 +00:00
|
|
|
if not command.command_argument:
|
|
|
|
|
command.source_object.emit_to("What do you want to say?")
|
|
|
|
|
raise ExitCommandHandler
|
2008-12-15 01:10:26 +00:00
|
|
|
second_arg = "%s=%s" % (cname, command.command_argument)
|
|
|
|
|
command.command_string = "@cemit"
|
|
|
|
|
command.command_switches = ["sendername", "quiet"]
|
2009-01-15 03:22:29 +00:00
|
|
|
command.command_argument = second_arg
|
2009-08-16 01:18:58 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def match_exits(command,test=False):
|
|
|
|
|
"""
|
|
|
|
|
See if we can find an input match to exits.
|
|
|
|
|
command - the command we are testing for.
|
|
|
|
|
if a match, move obj and exit
|
|
|
|
|
test - just return Truee if it is an exit command,
|
|
|
|
|
do not move the object there.
|
|
|
|
|
"""
|
|
|
|
|
# If we're not logged in, don't check exits.
|
|
|
|
|
source_object = command.source_object
|
|
|
|
|
location = source_object.get_location()
|
|
|
|
|
|
|
|
|
|
if location == None:
|
|
|
|
|
logger.log_errmsg("cmdhandler.match_exits(): Object '%s' has no location." %
|
|
|
|
|
source_object)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
exits = location.get_contents(filter_type=defines_global.OTYPE_EXIT)
|
|
|
|
|
Object = ContentType.objects.get(app_label="objects",
|
|
|
|
|
model="object").model_class()
|
|
|
|
|
exit_matches = Object.objects.list_search_object_namestr(exits,
|
|
|
|
|
command.command_string,
|
|
|
|
|
match_type="exact")
|
|
|
|
|
if exit_matches:
|
|
|
|
|
if test:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# Only interested in the first match.
|
|
|
|
|
targ_exit = exit_matches[0]
|
|
|
|
|
# An exit's home is its destination. If the exit has a None home value,
|
|
|
|
|
# it's not traversible.
|
|
|
|
|
if targ_exit.get_home():
|
|
|
|
|
# SCRIPT: See if the player can traverse the exit
|
|
|
|
|
if not targ_exit.scriptlink.default_lock(source_object):
|
Implemented locks.
The main command to use is @lock, which accept three types of locks at the moment, and three types of keys:
Locks: DefaultLock, UseLock, EnterLock
Keys: ObjectIDs, Groups, Permissions
This offers the most useful functionality - stopping people from picking up things, blocking exits and stopping
anyone from using an object.
If the attributes lock_msg, use_lock_msg and enter_lock_msg are defined on the locked object, these will be used
as error messages instead of a standard one (so "the door is locked" instead of "you cannot traverse that exit").
Behind the scenes, there is a new module, src/locks.py that defines Keys and Locks. A Locks object is a collection
of Lock types. This is stored in the LOCKS attribute on objects. Each Lock contains a set of Keys that might be
of mixed type and which the player must match in order to pass the lock.
/Griatch
2009-10-05 20:04:15 +00:00
|
|
|
lock_msg = targ_exit.get_attribute_value("lock_msg")
|
|
|
|
|
if lock_msg:
|
|
|
|
|
source_object.emit_to(lock_msg)
|
|
|
|
|
else:
|
|
|
|
|
source_object.emit_to("You can't traverse that exit.")
|
2009-08-16 01:18:58 +00:00
|
|
|
else:
|
|
|
|
|
source_object.move_to(targ_exit.get_home())
|
|
|
|
|
else:
|
|
|
|
|
source_object.emit_to("That exit leads nowhere.")
|
|
|
|
|
# We found a match, kill the command handler.
|
|
|
|
|
raise ExitCommandHandler
|
|
|
|
|
|
2009-04-25 06:11:42 +00:00
|
|
|
|
2009-10-14 18:15:15 +00:00
|
|
|
def command_table_lookup(command, command_table, eval_perms=True,
|
|
|
|
|
test=False, neighbor=None):
|
2008-12-15 01:10:26 +00:00
|
|
|
"""
|
|
|
|
|
Performs a command table lookup on the specified command table. Also
|
|
|
|
|
evaluates the permissions tuple.
|
2009-08-16 01:18:58 +00:00
|
|
|
The test flag only checks without manipulating the command
|
Implemented locks.
The main command to use is @lock, which accept three types of locks at the moment, and three types of keys:
Locks: DefaultLock, UseLock, EnterLock
Keys: ObjectIDs, Groups, Permissions
This offers the most useful functionality - stopping people from picking up things, blocking exits and stopping
anyone from using an object.
If the attributes lock_msg, use_lock_msg and enter_lock_msg are defined on the locked object, these will be used
as error messages instead of a standard one (so "the door is locked" instead of "you cannot traverse that exit").
Behind the scenes, there is a new module, src/locks.py that defines Keys and Locks. A Locks object is a collection
of Lock types. This is stored in the LOCKS attribute on objects. Each Lock contains a set of Keys that might be
of mixed type and which the player must match in order to pass the lock.
/Griatch
2009-10-05 20:04:15 +00:00
|
|
|
neighbor (object) If this is supplied, we are looking at a object table and
|
2009-10-18 19:29:18 +00:00
|
|
|
must check for locks.
|
|
|
|
|
|
|
|
|
|
In the case of one-word commands with switches, this is a
|
|
|
|
|
quick look-up. For non-switch commands the command might
|
|
|
|
|
however consist of several words separated by spaces up to
|
|
|
|
|
a certain max number of words. We don't know beforehand if one
|
|
|
|
|
of these match an entry in this particular command table. We search
|
|
|
|
|
them in order longest to shortest before deferring to the normal,
|
|
|
|
|
one-word assumption.
|
2008-12-15 01:10:26 +00:00
|
|
|
"""
|
2009-10-18 19:29:18 +00:00
|
|
|
cmdtuple = None
|
|
|
|
|
if command.command_alternatives:
|
|
|
|
|
# we have command alternatives (due to spaces in command definition)
|
|
|
|
|
for cmd_alternative in command.command_alternatives:
|
|
|
|
|
# the alternatives are ordered longest -> shortest.
|
|
|
|
|
cmdtuple = command_table.get_command_tuple(cmd_alternative[0])
|
|
|
|
|
if cmdtuple:
|
|
|
|
|
# we have a match, so this is the 'right' command to use
|
|
|
|
|
# with this particular command table.
|
|
|
|
|
command.command_string = cmd_alternative[0]
|
|
|
|
|
command.command_argument = cmd_alternative[1]
|
|
|
|
|
if not cmdtuple:
|
|
|
|
|
# None of the alternatives match, go with the default one-word name
|
|
|
|
|
cmdtuple = command_table.get_command_tuple(command.command_string)
|
|
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
if cmdtuple:
|
2009-10-18 19:29:18 +00:00
|
|
|
# if we get here we have found a command match in the table
|
2009-08-16 01:18:58 +00:00
|
|
|
if test:
|
2009-10-18 19:29:18 +00:00
|
|
|
# Check if this is just a test.
|
2009-08-16 01:18:58 +00:00
|
|
|
return True
|
Implemented locks.
The main command to use is @lock, which accept three types of locks at the moment, and three types of keys:
Locks: DefaultLock, UseLock, EnterLock
Keys: ObjectIDs, Groups, Permissions
This offers the most useful functionality - stopping people from picking up things, blocking exits and stopping
anyone from using an object.
If the attributes lock_msg, use_lock_msg and enter_lock_msg are defined on the locked object, these will be used
as error messages instead of a standard one (so "the door is locked" instead of "you cannot traverse that exit").
Behind the scenes, there is a new module, src/locks.py that defines Keys and Locks. A Locks object is a collection
of Lock types. This is stored in the LOCKS attribute on objects. Each Lock contains a set of Keys that might be
of mixed type and which the player must match in order to pass the lock.
/Griatch
2009-10-05 20:04:15 +00:00
|
|
|
# Check locks
|
|
|
|
|
if neighbor and not neighbor.scriptlink.use_lock(command.source_object):
|
|
|
|
|
# send an locked error message only if lock_desc is defined
|
|
|
|
|
lock_msg = neighbor.get_attribute_value("use_lock_msg")
|
|
|
|
|
if lock_msg:
|
|
|
|
|
command.source_object.emit_to(lock_msg)
|
|
|
|
|
raise ExitCommandHandler
|
|
|
|
|
return False
|
2008-12-15 01:10:26 +00:00
|
|
|
# If there is a permissions element to the entry, check perms.
|
|
|
|
|
if eval_perms and cmdtuple[1]:
|
2009-01-24 20:30:46 +00:00
|
|
|
if not command.source_object.has_perm_list(cmdtuple[1]):
|
|
|
|
|
command.source_object.emit_to(defines_global.NOPERMS_MSG)
|
Implemented locks.
The main command to use is @lock, which accept three types of locks at the moment, and three types of keys:
Locks: DefaultLock, UseLock, EnterLock
Keys: ObjectIDs, Groups, Permissions
This offers the most useful functionality - stopping people from picking up things, blocking exits and stopping
anyone from using an object.
If the attributes lock_msg, use_lock_msg and enter_lock_msg are defined on the locked object, these will be used
as error messages instead of a standard one (so "the door is locked" instead of "you cannot traverse that exit").
Behind the scenes, there is a new module, src/locks.py that defines Keys and Locks. A Locks object is a collection
of Lock types. This is stored in the LOCKS attribute on objects. Each Lock contains a set of Keys that might be
of mixed type and which the player must match in order to pass the lock.
/Griatch
2009-10-05 20:04:15 +00:00
|
|
|
raise ExitCommandHandler
|
2008-12-15 01:10:26 +00:00
|
|
|
# If flow reaches this point, user has perms and command is ready.
|
|
|
|
|
command.command_function = cmdtuple[0]
|
2009-01-24 03:17:43 +00:00
|
|
|
command.extra_vars = cmdtuple[2]
|
2009-04-25 06:11:42 +00:00
|
|
|
return True
|
2009-08-16 01:18:58 +00:00
|
|
|
|
2009-04-25 06:11:42 +00:00
|
|
|
|
2009-08-16 01:18:58 +00:00
|
|
|
def match_neighbor_ctables(command,test=False):
|
2009-04-25 06:11:42 +00:00
|
|
|
"""
|
|
|
|
|
Looks through the command tables of neighboring objects for command
|
|
|
|
|
matches.
|
2009-08-16 01:18:58 +00:00
|
|
|
test mode just checks if the command is a match, without manipulating
|
2009-10-14 18:15:15 +00:00
|
|
|
any commands.
|
2009-04-25 06:11:42 +00:00
|
|
|
"""
|
|
|
|
|
source_object = command.source_object
|
|
|
|
|
if source_object.location != None:
|
|
|
|
|
neighbors = source_object.location.get_contents()
|
|
|
|
|
for neighbor in neighbors:
|
|
|
|
|
if command_table_lookup(command,
|
Implemented locks.
The main command to use is @lock, which accept three types of locks at the moment, and three types of keys:
Locks: DefaultLock, UseLock, EnterLock
Keys: ObjectIDs, Groups, Permissions
This offers the most useful functionality - stopping people from picking up things, blocking exits and stopping
anyone from using an object.
If the attributes lock_msg, use_lock_msg and enter_lock_msg are defined on the locked object, these will be used
as error messages instead of a standard one (so "the door is locked" instead of "you cannot traverse that exit").
Behind the scenes, there is a new module, src/locks.py that defines Keys and Locks. A Locks object is a collection
of Lock types. This is stored in the LOCKS attribute on objects. Each Lock contains a set of Keys that might be
of mixed type and which the player must match in order to pass the lock.
/Griatch
2009-10-05 20:04:15 +00:00
|
|
|
neighbor.scriptlink.command_table,
|
|
|
|
|
test=test, neighbor=neighbor):
|
|
|
|
|
# test for a use-lock
|
2009-04-25 06:11:42 +00:00
|
|
|
# If there was a command match, set the scripted_obj attribute
|
|
|
|
|
# for the script parent to pick up.
|
2009-08-16 01:18:58 +00:00
|
|
|
if test:
|
|
|
|
|
return True
|
2009-04-25 06:11:42 +00:00
|
|
|
command.scripted_obj = neighbor
|
|
|
|
|
return True
|
2009-08-16 01:18:58 +00:00
|
|
|
else:
|
|
|
|
|
#no matches
|
|
|
|
|
return False
|
2006-12-25 06:04:06 +00:00
|
|
|
|
2009-10-14 18:15:15 +00:00
|
|
|
def handle(command, ignore_state=False):
|
2008-06-17 00:38:59 +00:00
|
|
|
"""
|
|
|
|
|
Use the spliced (list) uinput variable to retrieve the correct
|
|
|
|
|
command, or return an invalid command error.
|
basicobject.py
---------------
- Checks for NULL description on objects- if Null, it doesn't print the extra line any more.
- Made the checks for contents a little less ambiguous
cmdhandler.py
--------------
- Added new method 'parse_command' which takes a command string and tries to break it up based on common command parsing rules. Mostly complete, but could use some work on the edge cases. Check out the docstring on the function- I tried to make it fairly well documented.
- Changed the check for 'non-standard characters' to just return, rather than throw an Exception. Not sure if this causes any issues, but I noticed that when you hit enter without entering a command it would trigger this code. Now it just fails silently.
- The handle function now calls the parse_command function now and stores the results in parsed_input['parsed_command']. This then gets put into cdat['uinput'] at the end of handle() like before. The old data in parsed_input is still there, this is just a new field.
- Added cdat['raw_input'] to pass the full, untouched command string on. This is also stored in parsed_input['parsed_command']['raw_command'] so not sure fi this is necessary any longer, probably not.
cmdtable.py
------------
- Just cleaned it up a bit and straightened out the columns after changing 3-4 space indentation.
apps/objects/models.py
-----------------------
- set_description now sets the description attribute to 'None' (or Null in the db) when given a blank description. This is used for the change mentioned above in basicobject.py
- get_description now returns None if self.description is None
- used defines_global in the comparison methods like is_player
functions_db.py
----------------
- Changed import defines_global as defines_global to just 'import defines_global'- wasn't sure why this was this way, if I broke something (I didn't seem to) let me know.
- renamed player_search to player_name_search. Removed the use of local_and_global_search inside of it. local_and_global_search now calls it when it receives a search_string that starts with *.
- alias_search now only looks at attributes with attr_name == ALIAS. It used to just look at attr_value, which could match anything, it seemed.
- added 'dbref_search'
- local_and_global_search changes:
- Now uses dbref_search & player_search if the string starts with "#" or "*" respectively
- Changed when it uses dbref_search to whenever the search_string is a dbref. It used to check that it was a dbref, and that search_contents & search_location were set, but I *believe* in most MU*'s when you supply a dbref it never fails to find the object.
commands/unloggedin.py
-----------------------
- removed hardcoded object type #'s and started using defines_global instead
- when creating a new account, made sure that no object with an alias matching the player name requested exists. This is behavior from TinyMUSH, and I think most MUSHs follow this, but if not this is easy enough to change back.
commands/general.py
--------------------
- Rewrote cmd_page:
- New Features
- Page by dbref
- Page multiple people
- pose (:) and no space pose (;) pages
- When someone hits page without a target or data, it now will tell the player who they last paged, or say they haven't paged anyone if they don't have a LASTPAGED
- uses parse_command, made it a lot easier to work through the extra functionality added above
- When there are multiple words in a page target, it first tries to find a player that matches the entire string. If that fails, then it goes through each word, assuming each is a separate target, and works out paging them.
commands/objmanip.py
---------------------
- I started to muck with cmd_name & cmd_page, but decided to hold off for now. Largely, if everyone is cool with the idea that names & aliases should be totally unique, then we need to go ahead and re-write these. I'll do that if everyone is cool with it.
2008-06-13 18:15:54 +00:00
|
|
|
|
2008-06-17 00:38:59 +00:00
|
|
|
We're basically grabbing the player's command by tacking
|
|
|
|
|
their input on to 'cmd_' and looking it up in the GenCommands
|
|
|
|
|
class.
|
2009-10-14 18:15:15 +00:00
|
|
|
|
|
|
|
|
ignore_state : ignore eventual statetable lookups completely.
|
2008-06-17 00:38:59 +00:00
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# TODO: Protect against non-standard characters.
|
2008-12-15 01:10:26 +00:00
|
|
|
if not command.command_string:
|
2008-12-14 20:21:02 +00:00
|
|
|
# Nothing sent in of value, ignore it.
|
2008-12-15 01:10:26 +00:00
|
|
|
raise ExitCommandHandler
|
basicobject.py
---------------
- Checks for NULL description on objects- if Null, it doesn't print the extra line any more.
- Made the checks for contents a little less ambiguous
cmdhandler.py
--------------
- Added new method 'parse_command' which takes a command string and tries to break it up based on common command parsing rules. Mostly complete, but could use some work on the edge cases. Check out the docstring on the function- I tried to make it fairly well documented.
- Changed the check for 'non-standard characters' to just return, rather than throw an Exception. Not sure if this causes any issues, but I noticed that when you hit enter without entering a command it would trigger this code. Now it just fails silently.
- The handle function now calls the parse_command function now and stores the results in parsed_input['parsed_command']. This then gets put into cdat['uinput'] at the end of handle() like before. The old data in parsed_input is still there, this is just a new field.
- Added cdat['raw_input'] to pass the full, untouched command string on. This is also stored in parsed_input['parsed_command']['raw_command'] so not sure fi this is necessary any longer, probably not.
cmdtable.py
------------
- Just cleaned it up a bit and straightened out the columns after changing 3-4 space indentation.
apps/objects/models.py
-----------------------
- set_description now sets the description attribute to 'None' (or Null in the db) when given a blank description. This is used for the change mentioned above in basicobject.py
- get_description now returns None if self.description is None
- used defines_global in the comparison methods like is_player
functions_db.py
----------------
- Changed import defines_global as defines_global to just 'import defines_global'- wasn't sure why this was this way, if I broke something (I didn't seem to) let me know.
- renamed player_search to player_name_search. Removed the use of local_and_global_search inside of it. local_and_global_search now calls it when it receives a search_string that starts with *.
- alias_search now only looks at attributes with attr_name == ALIAS. It used to just look at attr_value, which could match anything, it seemed.
- added 'dbref_search'
- local_and_global_search changes:
- Now uses dbref_search & player_search if the string starts with "#" or "*" respectively
- Changed when it uses dbref_search to whenever the search_string is a dbref. It used to check that it was a dbref, and that search_contents & search_location were set, but I *believe* in most MU*'s when you supply a dbref it never fails to find the object.
commands/unloggedin.py
-----------------------
- removed hardcoded object type #'s and started using defines_global instead
- when creating a new account, made sure that no object with an alias matching the player name requested exists. This is behavior from TinyMUSH, and I think most MUSHs follow this, but if not this is easy enough to change back.
commands/general.py
--------------------
- Rewrote cmd_page:
- New Features
- Page by dbref
- Page multiple people
- pose (:) and no space pose (;) pages
- When someone hits page without a target or data, it now will tell the player who they last paged, or say they haven't paged anyone if they don't have a LASTPAGED
- uses parse_command, made it a lot easier to work through the extra functionality added above
- When there are multiple words in a page target, it first tries to find a player that matches the entire string. If that fails, then it goes through each word, assuming each is a separate target, and works out paging them.
commands/objmanip.py
---------------------
- I started to muck with cmd_name & cmd_page, but decided to hold off for now. Largely, if everyone is cool with the idea that names & aliases should be totally unique, then we need to go ahead and re-write these. I'll do that if everyone is cool with it.
2008-06-13 18:15:54 +00:00
|
|
|
|
2009-08-16 01:18:58 +00:00
|
|
|
state = None #no state by default
|
|
|
|
|
|
2009-01-24 20:30:46 +00:00
|
|
|
if command.session and not command.session.logged_in:
|
|
|
|
|
# Not logged in, look through the unlogged-in command table.
|
2009-08-16 01:18:58 +00:00
|
|
|
command_table_lookup(command, cmdtable.GLOBAL_UNCON_CMD_TABLE,eval_perms=False)
|
|
|
|
|
else:
|
|
|
|
|
# User is logged in.
|
|
|
|
|
# Match against the 'idle' command.
|
|
|
|
|
match_idle(command)
|
|
|
|
|
# See if this is an aliased command.
|
|
|
|
|
match_alias(command)
|
2009-05-01 15:34:43 +00:00
|
|
|
|
2009-08-16 01:18:58 +00:00
|
|
|
state = command.source_object.get_state()
|
|
|
|
|
state_cmd_table = statetable.GLOBAL_STATE_TABLE.get_cmd_table(state)
|
2009-05-01 15:34:43 +00:00
|
|
|
|
2009-10-14 18:15:15 +00:00
|
|
|
if state and state_cmd_table and not ignore_state:
|
2009-10-15 09:43:34 +00:00
|
|
|
# Caller is in a special state.
|
2009-05-01 15:34:43 +00:00
|
|
|
|
2009-08-16 01:18:58 +00:00
|
|
|
state_allow_exits, state_allow_obj_cmds = \
|
2009-10-14 18:15:15 +00:00
|
|
|
statetable.GLOBAL_STATE_TABLE.get_exec_rights(state)
|
2009-08-16 01:18:58 +00:00
|
|
|
|
|
|
|
|
state_lookup = True
|
|
|
|
|
if match_channel(command):
|
2009-05-01 15:34:43 +00:00
|
|
|
command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE)
|
2009-08-16 01:18:58 +00:00
|
|
|
state_lookup = False
|
|
|
|
|
# See if the user is trying to traverse an exit.
|
|
|
|
|
if state_allow_exits:
|
|
|
|
|
match_exits(command)
|
|
|
|
|
# check if this is a command defined on a nearby object.
|
|
|
|
|
if state_allow_obj_cmds and match_neighbor_ctables(command):
|
|
|
|
|
state_lookup = False
|
|
|
|
|
#if nothing has happened to change our mind, search the state table.
|
|
|
|
|
if state_lookup:
|
|
|
|
|
command_table_lookup(command, state_cmd_table)
|
2009-05-01 15:34:43 +00:00
|
|
|
else:
|
2009-08-16 01:18:58 +00:00
|
|
|
# Not in a state. Normal operation.
|
|
|
|
|
state = None #make sure, in case the object had a malformed statename.
|
2009-05-01 15:34:43 +00:00
|
|
|
|
2009-08-16 01:18:58 +00:00
|
|
|
# Check if the user is using a channel command.
|
2009-05-01 15:34:43 +00:00
|
|
|
match_channel(command)
|
2009-08-16 01:18:58 +00:00
|
|
|
# See if the user is trying to traverse an exit.
|
2009-05-01 15:34:43 +00:00
|
|
|
match_exits(command)
|
2009-08-16 01:18:58 +00:00
|
|
|
# check if this is a command defined on a nearby object
|
|
|
|
|
if not match_neighbor_ctables(command):
|
|
|
|
|
command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE)
|
|
|
|
|
|
2008-06-17 00:38:59 +00:00
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
"""
|
|
|
|
|
By this point, we assume that the user has entered a command and not
|
|
|
|
|
something like a channel or exit. Make sure that the command's
|
|
|
|
|
function reference is value and try to run it.
|
|
|
|
|
"""
|
|
|
|
|
if callable(command.command_function):
|
2008-06-17 00:38:59 +00:00
|
|
|
try:
|
2008-12-15 01:10:26 +00:00
|
|
|
# Move to the command function, passing the command object.
|
|
|
|
|
command.command_function(command)
|
2008-06-17 00:38:59 +00:00
|
|
|
except:
|
2008-12-15 01:10:26 +00:00
|
|
|
"""
|
|
|
|
|
This is a crude way of trapping command-related exceptions
|
|
|
|
|
and showing them to the user and server log. Once the
|
|
|
|
|
codebase stabilizes, we will probably want something more
|
|
|
|
|
useful or give them the option to hide exception values.
|
|
|
|
|
"""
|
2009-01-24 20:30:46 +00:00
|
|
|
if command.source_object:
|
|
|
|
|
command.source_object.emit_to("Untrapped error, please file a bug report:\n%s" %
|
|
|
|
|
(format_exc(),))
|
|
|
|
|
logger.log_errmsg("Untrapped error, evoker %s: %s" %
|
|
|
|
|
(command.source_object, format_exc()))
|
2008-12-15 01:10:26 +00:00
|
|
|
# Prevent things from falling through to UnknownCommand.
|
|
|
|
|
raise ExitCommandHandler
|
2009-01-15 02:39:11 +00:00
|
|
|
else:
|
|
|
|
|
# If we reach this point, we haven't matched anything.
|
2009-08-16 01:18:58 +00:00
|
|
|
|
|
|
|
|
if state:
|
|
|
|
|
# if we are in a state, it could be that the command exists, but
|
|
|
|
|
# it is temporarily not available. If so, we want a different error message.
|
|
|
|
|
if match_exits(command,test=True):
|
|
|
|
|
raise CommandNotInState("Movement is not possible right now.")
|
|
|
|
|
if match_neighbor_ctables(command,test=True):
|
|
|
|
|
raise CommandNotInState("You can not do that at the moment.")
|
|
|
|
|
if command_table_lookup(command,cmdtable.GLOBAL_CMD_TABLE,test=True):
|
|
|
|
|
raise CommandNotInState("This command is not available right now.")
|
2009-01-15 02:39:11 +00:00
|
|
|
raise UnknownCommand
|
2007-05-11 15:23:27 +00:00
|
|
|
|
2008-12-15 01:10:26 +00:00
|
|
|
except ExitCommandHandler:
|
|
|
|
|
# When this is thrown, just get out and do nothing. It doesn't mean
|
|
|
|
|
# something bad has happened.
|
|
|
|
|
pass
|
2009-08-16 01:18:58 +00:00
|
|
|
except CommandNotInState, e:
|
|
|
|
|
# The command exists, but not in the current state
|
|
|
|
|
if command.source_object != None:
|
|
|
|
|
# The logged-in error message
|
|
|
|
|
command.source_object.emit_to(e.err_string)
|
|
|
|
|
elif command.session != None:
|
|
|
|
|
# States are not available before login, so this should never
|
|
|
|
|
# be reached. But better safe than sorry.
|
|
|
|
|
command.session.msg("%s %s" % (e.err_string," (Type \"help\" for help.)"))
|
|
|
|
|
else:
|
|
|
|
|
pass
|
2008-06-17 00:38:59 +00:00
|
|
|
except UnknownCommand:
|
2008-12-15 01:10:26 +00:00
|
|
|
# Default fall-through. No valid command match.
|
2009-04-06 17:18:43 +00:00
|
|
|
if command.source_object != None:
|
2009-08-16 01:18:58 +00:00
|
|
|
# A typical logged in or object-based error message.
|
2009-04-06 17:18:43 +00:00
|
|
|
command.source_object.emit_to("Huh? (Type \"help\" for help.)")
|
|
|
|
|
elif command.session != None:
|
|
|
|
|
# This is hit when invalid commands are sent at the login screen
|
|
|
|
|
# primarily. Also protect against bad things in odd cases.
|
|
|
|
|
command.session.msg("Huh? (Type \"help\" for help.)")
|
|
|
|
|
else:
|
|
|
|
|
# We should never get to this point, but if we do, don't freak out.
|
|
|
|
|
pass
|