Merge branch 'master' into develop

This commit is contained in:
Griatch 2017-10-01 00:04:03 +02:00
commit a8891b44a6
9 changed files with 189 additions and 135 deletions

View file

@ -417,7 +417,7 @@ class CmdSay(COMMAND_DEFAULT_CLASS):
return
# Call the at_after_say hook on the character
caller.at_say(speech)
caller.at_say(speech, msg_self=True)
class CmdWhisper(COMMAND_DEFAULT_CLASS):
@ -425,10 +425,11 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS):
Speak privately as your character to another
Usage:
whisper <player> = <message>
whisper <character> = <message>
whisper <char1>, <char2> = <message?
Talk privately to those in your current location, without
others being informed.
Talk privately to one or more characters in your current location, without
others in the room being informed.
"""
key = "whisper"
@ -440,24 +441,25 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS):
caller = self.caller
if not self.lhs or not self.rhs:
caller.msg("Usage: whisper <account> = <message>")
caller.msg("Usage: whisper <character> = <message>")
return
receiver = caller.search(self.lhs)
receivers = [recv.strip() for recv in self.lhs.split(",")]
if not receiver:
return
receivers = [caller.search(receiver) for receiver in receivers]
receivers = [recv for recv in receivers if recv]
speech = self.rhs
# Call a hook to change the speech before whispering
speech = caller.at_before_say(speech, whisper=True, receiver=receiver)
# If the speech is empty, abort the command
if not speech:
if not speech or not receivers:
return
# Call the at_after_whisper hook for feedback
caller.at_say(speech, receiver=receiver, whisper=True)
# Call a hook to change the speech before whispering
speech = caller.at_before_say(speech, whisper=True, receivers=receivers)
# no need for self-message if we are whispering to ourselves (for some reason)
msg_self = None if caller in receivers else True
caller.at_say(speech, msg_self=msg_self, receivers=receivers, whisper=True)
class CmdPose(COMMAND_DEFAULT_CLASS):

View file

@ -55,6 +55,7 @@ class CmdMail(default_cmds.MuxCommand):
@mail/delete 6
@mail/forward feend78 Griatch=4/You guys should read this.
@mail/reply 9=Thanks for the info!
"""
key = "@mail"
aliases = ["mail"]
@ -86,6 +87,7 @@ class CmdMail(default_cmds.MuxCommand):
Returns:
messages (list): list of Msg objects.
"""
# mail_messages = Msg.objects.get_by_tag(category="mail")
# messages = []
@ -105,6 +107,7 @@ class CmdMail(default_cmds.MuxCommand):
subject (str): The header or subject of the message to be delivered.
message (str): The body of the message being sent.
caller (obj): The object (or Account or Character) that is sending the message.
"""
for recipient in recipients:
recipient.msg("You have received a new @mail from %s" % caller)
@ -130,7 +133,8 @@ class CmdMail(default_cmds.MuxCommand):
return
else:
all_mail = self.get_all_mail()
mind = int(self.lhs) - 1
mind_max = all_mail.count() - 1
mind = max(0, min(mind_max, int(self.lhs) - 1))
if all_mail[mind]:
all_mail[mind].delete()
self.caller.msg("Message %s deleted" % self.lhs)
@ -150,9 +154,10 @@ class CmdMail(default_cmds.MuxCommand):
return
else:
all_mail = self.get_all_mail()
mind_max = all_mail.count() - 1
if "/" in self.rhs:
message_number, message = self.rhs.split("/")
mind = int(message_number) - 1
message_number, message = self.rhs.split("/", 1)
mind = max(0, min(mind_max, int(message_number) - 1))
if all_mail[mind]:
old_message = all_mail[mind]
@ -164,7 +169,7 @@ class CmdMail(default_cmds.MuxCommand):
else:
raise IndexError
else:
mind = int(self.rhs) - 1
mind = max(0, min(mind_max, int(self.rhs) - 1))
if all_mail[mind]:
old_message = all_mail[mind]
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
@ -188,7 +193,8 @@ class CmdMail(default_cmds.MuxCommand):
return
else:
all_mail = self.get_all_mail()
mind = int(self.lhs) - 1
mind_max = all_mail.count() - 1
mind = max(0, min(mind_max, int(self.lhs) - 1))
if all_mail[mind]:
old_message = all_mail[mind]
self.send_mail(old_message.senders, "RE: " + old_message.header,
@ -211,8 +217,11 @@ class CmdMail(default_cmds.MuxCommand):
body = self.rhs
self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller)
else:
all_mail = self.get_all_mail()
mind_max = all_mail.count() - 1
try:
message = self.get_all_mail()[int(self.lhs) - 1]
mind = max(0, min(mind_max, self.lhs - 1))
message = all_mail[mind]
except (ValueError, IndexError):
self.caller.msg("'%s' is not a valid mail id." % self.lhs)
return

View file

@ -16,7 +16,7 @@ also adds the short descriptions and the `sdesc` command).
Installation:
Edit `mygame/commands/default_cmdsets.py` and add
`from contrib.multidesc import CmdMultiDesc` to the top.
`from evennia.contrib.multidescer import CmdMultiDesc` to the top.
Next, look up the `at_cmdset_create` method of the `CharacterCmdSet`
class and add a line `self.add(CmdMultiDesc())` to the end

View file

@ -858,7 +858,7 @@ class CmdSay(RPCommand): # replaces standard say
return
# calling the speech hook on the location
speech = caller.location.at_say(caller, self.args)
speech = caller.location.at_before_say(caller, self.args)
# preparing the speech with sdesc/speech parsing.
speech = "/me says, \"{speech}\"".format(speech=speech)
targets = self.caller.location.contents

View file

@ -822,10 +822,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
returns the new clone name on the form keyXX
"""
key = self.key
num = 1
for inum in (obj for obj in self.location.contents
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
num += 1
num = sum(1 for obj in self.location.contents
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit())
return "%s%03i" % (key, num)
new_key = new_key or find_clone_key()
return ObjectDB.objects.copy_object(self, new_key=new_key)
@ -1205,7 +1203,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
mapping.update({
"object": self,
"exit": exits[0] if exits else "somwhere",
"exit": exits[0] if exits else "somewhere",
"origin": location or "nowhere",
"destination": destination or "nowhere",
})
@ -1562,7 +1560,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
a say. This is sent by the whisper command by default.
Other verbal commands could use this hook in similar
ways.
receiver (Object): If set, this is a target for the say/whisper.
receivers (Object or iterable): If set, this is the target or targets for the say/whisper.
Returns:
message (str): The (possibly modified) text to be spoken.
@ -1571,7 +1569,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return message
def at_say(self, message, msg_self=None, msg_location=None,
receiver=None, msg_receiver=None, mapping=None, **kwargs):
receivers=None, msg_receivers=None, **kwargs):
"""
Display the actual say (or whisper) of self.
@ -1582,69 +1580,98 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
re-writing it completely.
Args:
message (str): The text to be conveyed by self.
msg_self (str, optional): The message to echo to self.
message (str): The message to convey.
msg_self (bool or str, optional): If boolean True, echo `message` to self. If a string,
return that message. If False or unset, don't echo to self.
msg_location (str, optional): The message to echo to self's location.
receiver (Object, optional): An eventual receiver of the message
receivers (Object or iterable, optional): An eventual receiver or receivers of the message
(by default only used by whispers).
msg_receiver(str, optional): Specific message for receiver only.
mapping (dict, optional): Additional mapping in messages.
msg_receivers(str): Specific message to pass to the receiver(s). This will parsed
with the {receiver} placeholder replaced with the given receiver.
Kwargs:
whisper (bool): If this is a whisper rather than a say. Kwargs
can be used by other verbal commands in a similar way.
mapping (dict): Pass an additional mapping to the message.
Notes:
Messages can contain {} markers, which must
If used, `msg_self`, `msg_receiver` and `msg_location` should contain
references to other objects between braces, the way `location.msg_contents`
would allow. For instance:
Messages can contain {} markers. These are substituted against the values
passed in the `mapping` argument.
msg_self = 'You say: "{speech}"'
msg_location = '{object} says: "{speech}"'
msg_receiver = '{object} whispers: "{speech}"'
msg_receivers = '{object} whispers: "{speech}"'
The following mappings can be used in both messages:
object: the object speaking.
location: the location where object is.
speech: the text spoken by self.
You can use additional mappings if you want to add other
information in your messages.
Supported markers by default:
{self}: text to self-reference with (default 'You')
{speech}: the text spoken/whispered by self.
{object}: the object speaking.
{receiver}: replaced with a single receiver only for strings meant for a specific
receiver (otherwise 'None').
{all_receivers}: comma-separated list of all receivers,
if more than one, otherwise same as receiver
{location}: the location where object is.
"""
msg_type = 'say'
if kwargs.get("whisper", False):
# whisper mode
msg_self = msg_self or 'You whisper to {receiver}, "{speech}"|n'
msg_receiver = msg_receiver or '{object} whispers: "{speech}"|n'
msg_type = 'whisper'
msg_self = '{self} whisper to {all_receivers}, "{speech}"' if msg_self is True else msg_self
msg_receivers = '{object} whispers: "{speech}"'
msg_location = None
else:
msg_self = msg_self or 'You say, "{speech}"|n'
msg_receiver = None
msg_location = msg_location or '{object} says, "{speech}"|n'
msg_self = '{self} say, "{speech}"' if msg_self is True else msg_self
msg_receivers = None
msg_location = msg_location or '{object} says, "{speech}"'
mapping = mapping or {}
mapping.update({
"object": self,
"location": self.location,
"speech": message,
"receiver": receiver
})
custom_mapping = kwargs.get('mapping', {})
receivers = make_iter(receivers) if receivers else None
location = self.location
if msg_self:
self_mapping = {key: "yourself" if key == "receiver" and val is self
else val.get_display_name(self) if hasattr(val, "get_display_name")
else str(val) for key, val in mapping.items()}
self.msg(msg_self.format(**self_mapping))
self_mapping = {"self": "You",
"object": self.get_display_name(self),
"location": location.get_display_name(self) if location else None,
"receiver": None,
"all_receivers": ", ".join(
recv.get_display_name(self)
for recv in receivers) if receivers else None,
"speech": message}
self_mapping.update(custom_mapping)
self.msg(text=(msg_self.format(**self_mapping), {"type": msg_type}))
if receiver and msg_receiver:
receiver_mapping = {key: val.get_display_name(receiver)
if hasattr(val, "get_display_name")
else str(val) for key, val in mapping.items()}
receiver.msg(msg_receiver.format(**receiver_mapping))
if receivers and msg_receivers:
receiver_mapping = {"self": "You",
"object": None,
"location": None,
"receiver": None,
"all_receivers": None,
"speech": message}
for receiver in make_iter(receivers):
individual_mapping = {"object": self.get_display_name(receiver),
"location": location.get_display_name(receiver),
"receiver": receiver.get_display_name(receiver),
"all_receivers": ", ".join(
recv.get_display_name(recv)
for recv in receivers) if receivers else None}
receiver_mapping.update(individual_mapping)
receiver_mapping.update(custom_mapping)
receiver.msg(text=(msg_receivers.format(**receiver_mapping), {"type": msg_type}))
if self.location and msg_location:
self.location.msg_contents(msg_location, exclude=(self, ),
mapping=mapping)
location_mapping = {"self": "You",
"object": self,
"location": location,
"all_receivers": ", ".join(recv for recv in receivers) if receivers else None,
"receiver": None,
"speech": message}
location_mapping.update(custom_mapping)
self.location.msg_contents(text=(msg_location, {"type": msg_type}),
from_obj=self,
exclude=(self, ) if msg_self else None,
mapping=location_mapping)
#

View file

@ -4,6 +4,7 @@ from __future__ import unicode_literals
from django.db import migrations, connection
_ENGINE = None
def _table_exists(db_cursor, tablename):
"Returns bool if table exists or not"
@ -11,9 +12,25 @@ def _table_exists(db_cursor, tablename):
def _drop_table(db_cursor, table_name):
global _ENGINE
if not _ENGINE:
from django.conf import settings
try:
_ENGINE = settings.DATABASES["default"]["ENGINE"]
except KeyError:
_ENGINE = settings.DATABASE_ENGINE
if _table_exists(db_cursor, table_name):
sql_drop = "DROP TABLE %s;" % table_name
db_cursor.execute(sql_drop)
if _ENGINE == "django.db.backends.mysql":
db_cursor.execute("SET FOREIGN_KEY_CHECKS=0;")
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
db_cursor.execute("SET FOREIGN_KEY_CHECKS=1;")
elif _ENGINE == "postgresql_psycopg2":
db_cursor.execute("ALTER TABLE {table} DISABLE TRIGGER ALL;".format(table=table_name))
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
db_cursor.execute("ALTER TABLE {table} ENABLE TRIGGER ALL;".format(table=table_name))
else: # sqlite3, other databases
db_cursor.execute("DROP TABLE {table};".format(table=table_name))
def drop_tables(apps, schema_migrator):
@ -23,6 +40,9 @@ def drop_tables(apps, schema_migrator):
_drop_table(db_cursor, "players_playerdb_db_tags")
_drop_table(db_cursor, "players_playerdb_groups")
_drop_table(db_cursor, "players_playerdb_user_permissions")
_drop_table(db_cursor, "comms_msg_db_sender_players")
_drop_table(db_cursor, "comms_msg_db_receivers_players")
_drop_table(db_cursor, "comms_msg_db_hide_from_players")
class Migration(migrations.Migration):

View file

@ -674,8 +674,9 @@ class TypedObject(SharedMemoryModel):
Displays the name of the object in a viewer-aware manner.
Args:
looker (TypedObject): The object or account that is looking
at/getting inforamtion for this object.
looker (TypedObject, optional): The object or account that is looking
at/getting inforamtion for this object. If not given, some
'safe' minimum level should be returned.
Returns:
name (str): A string containing the name of the object,

View file

@ -180,7 +180,7 @@ from django.conf import settings
from evennia.utils import utils
_ENCODINGS = settings.ENCODINGS
_RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
_RE_INSERT = re.compile(r"^\#INSERT (.*)$", re.MULTILINE)
_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
_RE_CODE_OR_HEADER = re.compile(r"((?:\A|^)#CODE|(?:/A|^)#HEADER|\A)(.*?)$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)",
@ -273,15 +273,10 @@ class BatchCommandProcessor(object):
def replace_insert(match):
"""Map replace entries"""
return "\n#".join(self.parse_file(match.group(1)))
return "\n#\n".join(self.parse_file(match.group(1)))
# insert commands from inserted files
text = _RE_INSERT.sub(replace_insert, text)
# re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
# get all commands
commands = _RE_CMD_SPLIT.split(text)
# re.split(r"^\#.*?$", text, flags=re.MULTILINE)
# remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands]
commands = [c for c in commands if c]

View file

@ -283,64 +283,64 @@ def parse_inlinefunc(string, strip=False, **kwargs):
# no cached stack; build a new stack and continue
stack = ParseStack()
# process string on stack
ncallable = 0
for match in _RE_TOKEN.finditer(string):
gdict = match.groupdict()
if gdict["singlequote"]:
stack.append(gdict["singlequote"])
elif gdict["doublequote"]:
stack.append(gdict["doublequote"])
elif gdict["end"]:
if ncallable <= 0:
stack.append(")")
continue
args = []
while stack:
operation = stack.pop()
if callable(operation):
if not strip:
stack.append((operation, [arg for arg in reversed(args)]))
ncallable -= 1
break
# process string on stack
ncallable = 0
for match in _RE_TOKEN.finditer(string):
gdict = match.groupdict()
if gdict["singlequote"]:
stack.append(gdict["singlequote"])
elif gdict["doublequote"]:
stack.append(gdict["doublequote"])
elif gdict["end"]:
if ncallable <= 0:
stack.append(")")
continue
args = []
while stack:
operation = stack.pop()
if callable(operation):
if not strip:
stack.append((operation, [arg for arg in reversed(args)]))
ncallable -= 1
break
else:
args.append(operation)
elif gdict["start"]:
funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1)
try:
# try to fetch the matching inlinefunc from storage
stack.append(_INLINE_FUNCS[funcname])
except KeyError:
stack.append(_INLINE_FUNCS["nomatch"])
stack.append(funcname)
ncallable += 1
elif gdict["escaped"]:
# escaped tokens
token = gdict["escaped"].lstrip("\\")
stack.append(token)
elif gdict["comma"]:
if ncallable > 0:
# commas outside strings and inside a callable are
# used to mark argument separation - we use None
# in the stack to indicate such a separation.
stack.append(None)
else:
args.append(operation)
elif gdict["start"]:
funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1)
try:
# try to fetch the matching inlinefunc from storage
stack.append(_INLINE_FUNCS[funcname])
except KeyError:
stack.append(_INLINE_FUNCS["nomatch"])
stack.append(funcname)
ncallable += 1
elif gdict["escaped"]:
# escaped tokens
token = gdict["escaped"].lstrip("\\")
stack.append(token)
elif gdict["comma"]:
if ncallable > 0:
# commas outside strings and inside a callable are
# used to mark argument separation - we use None
# in the stack to indicate such a separation.
stack.append(None)
# no callable active - just a string
stack.append(",")
else:
# no callable active - just a string
stack.append(",")
# the rest
stack.append(gdict["rest"])
if ncallable > 0:
# this means not all inlinefuncs were complete
return string
if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < len(stack):
# if stack is larger than limit, throw away parsing
return string + gdict["stackfull"](*args, **kwargs)
else:
# the rest
stack.append(gdict["rest"])
if ncallable > 0:
# this means not all inlinefuncs were complete
return string
if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < len(stack):
# if stack is larger than limit, throw away parsing
return string + gdict["stackfull"](*args, **kwargs)
else:
# cache the stack
_PARSING_CACHE[string] = stack
# cache the stack
_PARSING_CACHE[string] = stack
# run the stack recursively
def _run_stack(item, depth=0):