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

View file

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

View file

@ -16,7 +16,7 @@ also adds the short descriptions and the `sdesc` command).
Installation: Installation:
Edit `mygame/commands/default_cmdsets.py` and add 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` Next, look up the `at_cmdset_create` method of the `CharacterCmdSet`
class and add a line `self.add(CmdMultiDesc())` to the end class and add a line `self.add(CmdMultiDesc())` to the end

View file

@ -858,7 +858,7 @@ class CmdSay(RPCommand): # replaces standard say
return return
# calling the speech hook on the location # 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. # preparing the speech with sdesc/speech parsing.
speech = "/me says, \"{speech}\"".format(speech=speech) speech = "/me says, \"{speech}\"".format(speech=speech)
targets = self.caller.location.contents 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 returns the new clone name on the form keyXX
""" """
key = self.key key = self.key
num = 1 num = sum(1 for obj in self.location.contents
for inum in (obj for obj in self.location.contents if obj.key.startswith(key) and obj.key.lstrip(key).isdigit())
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
num += 1
return "%s%03i" % (key, num) return "%s%03i" % (key, num)
new_key = new_key or find_clone_key() new_key = new_key or find_clone_key()
return ObjectDB.objects.copy_object(self, new_key=new_key) return ObjectDB.objects.copy_object(self, new_key=new_key)
@ -1205,7 +1203,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
mapping.update({ mapping.update({
"object": self, "object": self,
"exit": exits[0] if exits else "somwhere", "exit": exits[0] if exits else "somewhere",
"origin": location or "nowhere", "origin": location or "nowhere",
"destination": destination 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. a say. This is sent by the whisper command by default.
Other verbal commands could use this hook in similar Other verbal commands could use this hook in similar
ways. 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: Returns:
message (str): The (possibly modified) text to be spoken. message (str): The (possibly modified) text to be spoken.
@ -1571,7 +1569,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return message return message
def at_say(self, message, msg_self=None, msg_location=None, 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. Display the actual say (or whisper) of self.
@ -1582,69 +1580,98 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
re-writing it completely. re-writing it completely.
Args: Args:
message (str): The text to be conveyed by self. message (str): The message to convey.
msg_self (str, optional): The message to echo to self. 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. 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). (by default only used by whispers).
msg_receiver(str, optional): Specific message for receiver only. msg_receivers(str): Specific message to pass to the receiver(s). This will parsed
mapping (dict, optional): Additional mapping in messages. with the {receiver} placeholder replaced with the given receiver.
Kwargs: Kwargs:
whisper (bool): If this is a whisper rather than a say. Kwargs whisper (bool): If this is a whisper rather than a say. Kwargs
can be used by other verbal commands in a similar way. can be used by other verbal commands in a similar way.
mapping (dict): Pass an additional mapping to the message.
Notes: Notes:
Messages can contain {} markers, which must
If used, `msg_self`, `msg_receiver` and `msg_location` should contain Messages can contain {} markers. These are substituted against the values
references to other objects between braces, the way `location.msg_contents` passed in the `mapping` argument.
would allow. For instance:
msg_self = 'You say: "{speech}"' msg_self = 'You say: "{speech}"'
msg_location = '{object} says: "{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: Supported markers by default:
object: the object speaking. {self}: text to self-reference with (default 'You')
location: the location where object is. {speech}: the text spoken/whispered by self.
speech: the text spoken by self. {object}: the object speaking.
{receiver}: replaced with a single receiver only for strings meant for a specific
You can use additional mappings if you want to add other receiver (otherwise 'None').
information in your messages. {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): if kwargs.get("whisper", False):
# whisper mode # whisper mode
msg_self = msg_self or 'You whisper to {receiver}, "{speech}"|n' msg_type = 'whisper'
msg_receiver = msg_receiver or '{object} whispers: "{speech}"|n' msg_self = '{self} whisper to {all_receivers}, "{speech}"' if msg_self is True else msg_self
msg_receivers = '{object} whispers: "{speech}"'
msg_location = None msg_location = None
else: else:
msg_self = msg_self or 'You say, "{speech}"|n' msg_self = '{self} say, "{speech}"' if msg_self is True else msg_self
msg_receiver = None msg_receivers = None
msg_location = msg_location or '{object} says, "{speech}"|n' msg_location = msg_location or '{object} says, "{speech}"'
mapping = mapping or {} custom_mapping = kwargs.get('mapping', {})
mapping.update({ receivers = make_iter(receivers) if receivers else None
"object": self, location = self.location
"location": self.location,
"speech": message,
"receiver": receiver
})
if msg_self: if msg_self:
self_mapping = {key: "yourself" if key == "receiver" and val is self self_mapping = {"self": "You",
else val.get_display_name(self) if hasattr(val, "get_display_name") "object": self.get_display_name(self),
else str(val) for key, val in mapping.items()} "location": location.get_display_name(self) if location else None,
self.msg(msg_self.format(**self_mapping)) "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: if receivers and msg_receivers:
receiver_mapping = {key: val.get_display_name(receiver) receiver_mapping = {"self": "You",
if hasattr(val, "get_display_name") "object": None,
else str(val) for key, val in mapping.items()} "location": None,
receiver.msg(msg_receiver.format(**receiver_mapping)) "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: if self.location and msg_location:
self.location.msg_contents(msg_location, exclude=(self, ), location_mapping = {"self": "You",
mapping=mapping) "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 from django.db import migrations, connection
_ENGINE = None
def _table_exists(db_cursor, tablename): def _table_exists(db_cursor, tablename):
"Returns bool if table exists or not" "Returns bool if table exists or not"
@ -11,9 +12,25 @@ def _table_exists(db_cursor, tablename):
def _drop_table(db_cursor, table_name): 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): if _table_exists(db_cursor, table_name):
sql_drop = "DROP TABLE %s;" % table_name if _ENGINE == "django.db.backends.mysql":
db_cursor.execute(sql_drop) 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): 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_db_tags")
_drop_table(db_cursor, "players_playerdb_groups") _drop_table(db_cursor, "players_playerdb_groups")
_drop_table(db_cursor, "players_playerdb_user_permissions") _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): class Migration(migrations.Migration):

View file

@ -674,8 +674,9 @@ class TypedObject(SharedMemoryModel):
Displays the name of the object in a viewer-aware manner. Displays the name of the object in a viewer-aware manner.
Args: Args:
looker (TypedObject): The object or account that is looking looker (TypedObject, optional): The object or account that is looking
at/getting inforamtion for this object. at/getting inforamtion for this object. If not given, some
'safe' minimum level should be returned.
Returns: Returns:
name (str): A string containing the name of the object, 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 from evennia.utils import utils
_ENCODINGS = settings.ENCODINGS _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_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", 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)", _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): def replace_insert(match):
"""Map replace entries""" """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) 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) 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.strip('\r\n') for c in commands]
commands = [c for c in commands if c] 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 # no cached stack; build a new stack and continue
stack = ParseStack() stack = ParseStack()
# process string on stack # process string on stack
ncallable = 0 ncallable = 0
for match in _RE_TOKEN.finditer(string): for match in _RE_TOKEN.finditer(string):
gdict = match.groupdict() gdict = match.groupdict()
if gdict["singlequote"]: if gdict["singlequote"]:
stack.append(gdict["singlequote"]) stack.append(gdict["singlequote"])
elif gdict["doublequote"]: elif gdict["doublequote"]:
stack.append(gdict["doublequote"]) stack.append(gdict["doublequote"])
elif gdict["end"]: elif gdict["end"]:
if ncallable <= 0: if ncallable <= 0:
stack.append(")") stack.append(")")
continue continue
args = [] args = []
while stack: while stack:
operation = stack.pop() operation = stack.pop()
if callable(operation): if callable(operation):
if not strip: if not strip:
stack.append((operation, [arg for arg in reversed(args)])) stack.append((operation, [arg for arg in reversed(args)]))
ncallable -= 1 ncallable -= 1
break 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: else:
args.append(operation) # no callable active - just a string
elif gdict["start"]: stack.append(",")
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: else:
# no callable active - just a string # the rest
stack.append(",") 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: else:
# the rest # cache the stack
stack.append(gdict["rest"]) _PARSING_CACHE[string] = stack
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
# run the stack recursively # run the stack recursively
def _run_stack(item, depth=0): def _run_stack(item, depth=0):