evennia/evennia/contrib/events/commands.py
2017-05-06 14:42:57 +02:00

514 lines
18 KiB
Python

"""
Module containing the commands of the event system.
"""
from datetime import datetime
from django.conf import settings
from evennia import Command
from evennia.contrib.events.custom import get_event_handler
from evennia.utils.eveditor import EvEditor
from evennia.utils.evtable import EvTable
from evennia.utils.utils import class_from_module, time_format
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
# Permissions
WITH_VALIDATION = getattr(settings, "EVENTS_WITH_VALIDATION", None)
WITHOUT_VALIDATION = getattr(settings, "EVENTS_WITHOUT_VALIDATION",
"immortals")
VALIDATING = getattr(settings, "EVENTS_VALIDATING", "immortals")
# Split help file
BASIC_HELP = "Add, edit or delete events."
BASIC_USAGES = [
"@event object name [= event name]",
"@event/add object name = event name [parameters]",
"@event/edit object name = event name [event number]",
"@event/del object name = event name [event number]",
]
BASIC_SWITCHES = [
"add - add and edit a new event",
"edit - edit an existing event",
"del - delete an existing event",
]
VALIDATOR_USAGES = [
"@event/accept [object name = event name [event number]]",
]
VALIDATOR_SWITCHES = [
"accept - show events to be validated or accept one",
]
BASIC_TEXT = """
This command is used to manipulate events. An event can be linked to
an object, to fire at a specific moment. You can use the command without
switches to see what event are active on an object:
@event self
You can also specify an event name if you want the list of events associated
with this object of this name:
@event north = can_traverse
You can also add, edit or remove events using the add, edit or del switches.
"""
VALIDATOR_TEXT = """
You can also use this command to validate events. Depending on your game
setting, some users might be allowed to add new events, but these events
will not be fired until you accept them. To see the events needing
validation, enter the /accept switch without argument:
@event/accept
A table will show you the events that are not validated yet, who created
it and when. You can then accept a specific event:
@event here = enter
Or, if more than one events are connected here, specify the number:
@event here = enter 3
Use the /del switch to remove events that should not be connected.
"""
class CmdEvent(COMMAND_DEFAULT_CLASS):
"""Command to edit events."""
key = "@event"
locks = "cmd:perm({})".format(VALIDATING)
aliases = ["@events", "@ev"]
if WITH_VALIDATION:
locks += " or perm({})".format(WITH_VALIDATION)
help_category = "Building"
def get_help(self, caller, cmdset):
"""
Return the help message for this command and this caller.
The help text of this specific command will vary depending
on user permission.
Args:
caller (Object or Player): the caller asking for help on the command.
cmdset (CmdSet): the command set (if you need additional commands).
Returns:
docstring (str): the help text to provide the caller for this command.
"""
lock = "perm({}) or perm(events_validating)".format(VALIDATING)
validator = caller.locks.check_lockstring(caller, lock)
text = "\n" + BASIC_HELP + "\n\nUsages:\n "
# Usages
text += "\n ".join(BASIC_USAGES)
if validator:
text += "\n " + "\n ".join(VALIDATOR_USAGES)
# Switches
text += "\n\nSwitches:\n "
text += "\n ".join(BASIC_SWITCHES)
if validator:
text += "\n " + "\n".join(VALIDATOR_SWITCHES)
# Text
text += "\n" + BASIC_TEXT
if validator:
text += "\n" + VALIDATOR_TEXT
return text
def func(self):
"""Command body."""
caller = self.caller
lock = "perm({}) or perm(events_validating)".format(VALIDATING)
validator = caller.locks.check_lockstring(caller, lock)
lock = "perm({}) or perm(events_without_validation)".format(
WITHOUT_VALIDATION)
autovalid = caller.locks.check_lockstring(caller, lock)
# First and foremost, get the event handler and set other variables
self.handler = get_event_handler()
self.obj = None
rhs = self.rhs or ""
self.event_name, sep, self.parameters = rhs.partition(" ")
self.event_name = self.event_name.lower()
self.is_validator = validator
self.autovalid = autovalid
if self.handler is None:
caller.msg("The event handler is not running, can't " \
"access the event system.")
return
# Before the equal sign is always an object name
if self.args.strip():
self.obj = caller.search(self.lhs)
if not self.obj:
return
# Switches are mutually exclusive
switch = self.switches and self.switches[0] or ""
if switch == "":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.list_events()
elif switch == "add":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.add_event()
elif switch == "edit":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.edit_event()
elif switch == "del":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.del_event()
elif switch == "accept" and validator:
self.accept_event()
else:
caller.msg("Mutually exclusive or invalid switches were " \
"used, cannot proceed.")
def list_events(self):
"""Display the list of events connected to the object."""
obj = self.obj
event_name = self.event_name
parameters = self.parameters
events = self.handler.get_events(obj)
types = self.handler.get_event_types(obj)
if event_name:
# Check that the event name can be found in this object
created = events.get(event_name)
if created is None:
self.msg("No event {} has been set on {}.".format(event_name, obj))
return
if parameters:
# Check that the parameter points to an existing event
try:
parameters = int(parameters) - 1
assert parameters >= 0
event = events[event_name][parameters]
except (AssertionError, ValueError):
self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj))
return
# Display the events' details
author = event.get("author")
author = author.key if author else "|gUnknown|n"
updated_by = event.get("updated_by")
updated_by = updated_by.key if updated_by else "|gUnknown|n"
created_on = event.get("created_on")
created_on = created_on.strftime("%Y-%m-%d %H:%M:%S") \
if created_on else "|gUnknown|n"
updated_on = event.get("updated_on")
updated_on = updated_on.strftime("%Y-%m-%d %H:%M:%S") \
if updated_on else "|gUnknown|n"
number = parameters + 1
msg = "Event {} {} of {}:".format(event_name, number, obj)
msg += "\nCreated by {} at {}.".format(author, created_on)
msg += "\nUpdated by {} at {}".format(updated_by, updated_on)
if self.is_validator:
if event.get("valid"):
msg += "\nThis event is |rconnected|n and active."
else:
msg += "\nThis event |rhasn't been|n accepted yet."
msg += "\nEvent code:\n"
msg += "\n".join([l for l in event["code"].splitlines()])
self.msg(msg)
return
# No parameter has been specified, display the table of events
cols = ["Number", "Author", "Updated", "Param"]
if self.is_validator:
cols.append("Valid")
table = EvTable(*cols, width=78)
table.reformat_column(0, align="r")
now = datetime.now()
for i, event in enumerate(created):
author = event.get("author")
author = author.key if author else "|gUnknown|n"
updated_on = event.get("updated_on")
if updated_on is None:
updated_on = event.get("created_on")
if updated_on:
updated_on = time_format(
(now - updated_on).total_seconds(), 1)
else:
updated_on = "|gUnknown|n"
parameters = event.get("parameters", "")
row = [str(i + 1), author, updated_on, parameters]
if self.is_validator:
row.append("Yes" if event.get("valid") else "No")
table.add_row(*row)
self.msg(table)
else:
names = list(set(list(types.keys()) + list(events.keys())))
table = EvTable("Event name", "Number", "Description",
valign="t", width=78)
table.reformat_column(0, width=20)
table.reformat_column(1, width=10, align="r")
table.reformat_column(2, width=48)
for name in sorted(names):
number = len(events.get(name, []))
lines = sum(len(e["code"].splitlines()) for e in \
events.get(name, []))
no = "{} ({})".format(number, lines)
description = types.get(name, (None, "Chained event."))[1]
description = description.splitlines()[0]
table.add_row(name, no, description)
self.msg(table)
def add_event(self):
"""Add an event."""
obj = self.obj
event_name = self.event_name
types = self.handler.get_event_types(obj)
# Check that the event exists
if not event_name.startswith("chain_") and not event_name in types:
self.msg("The event name {} can't be found in {} of " \
"typeclass {}.".format(event_name, obj, type(obj)))
return
definition = types.get(event_name, (None, "Chain event"))
description = definition[1]
self.msg(description)
# Open the editor
event = self.handler.add_event(obj, event_name, "",
self.caller, False, parameters=self.parameters)
self.caller.db._event = event
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
quitfunc=_ev_quit, key="Event {} of {}".format(
event_name, obj), persistent=True, codefunc=_ev_save)
def edit_event(self):
"""Edit an event."""
obj = self.obj
event_name = self.event_name
parameters = self.parameters
events = self.handler.get_events(obj)
types = self.handler.get_event_types(obj)
# If no event name is specified, display the list of events
if not event_name:
self.list_events()
return
# Check that the event exists
if not event_name in events:
self.msg("The event name {} can't be found in {}.".format(
event_name, obj))
return
# If there's only one event, just edit it
if len(events[event_name]) == 1:
parameters = 0
event = events[event_name][0]
else:
if not parameters:
self.msg("Which event do you wish to edit? Specify a number.")
self.list_events()
return
# Check that the parameter points to an existing event
try:
parameters = int(parameters) - 1
assert parameters >= 0
event = events[event_name][parameters]
except (AssertionError, ValueError):
self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj))
return
# If caller can't edit without validation, forbid editing
# others' works
if not self.autovalid and event["author"] is not self.caller:
self.msg("You cannot edit this event created by someone else.")
return
# Check the definition of the event
definition = types.get(event_name, (None, "Chained event"))
description = definition[1]
self.msg(description)
# Open the editor
event = dict(event)
event["obj"] = obj
event["name"] = event_name
event["number"] = parameters
self.caller.db._event = event
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
quitfunc=_ev_quit, key="Event {} of {}".format(
event_name, obj), persistent=True, codefunc=_ev_save)
def del_event(self):
"""Delete an event."""
obj = self.obj
event_name = self.event_name
parameters = self.parameters
events = self.handler.get_events(obj)
types = self.handler.get_event_types(obj)
# If no event name is specified, display the list of events
if not event_name:
self.list_events()
return
# Check that the event exists
if not event_name in events:
self.msg("The event name {} can't be found in {}.".format(
event_name, obj))
return
# If there's only one event, just delete it
if len(events[event_name]) == 1:
parameters = 0
event = events[event_name][0]
else:
if not parameters:
self.msg("Which event do you wish to delete? Specify " \
"a number.")
self.list_events()
return
# Check that the parameter points to an existing event
try:
parameters = int(parameters) - 1
assert parameters >= 0
event = events[event_name][parameters]
except (AssertionError, ValueError):
self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj))
return
# If caller can't edit without validation, forbid deleting
# others' works
if not self.autovalid and event["author"] is not self.caller:
self.msg("You cannot delete this event created by someone else.")
return
# Delete the event
self.handler.del_event(obj, event_name, parameters)
self.msg("The event {} {} of {} was deleted.".format(
obj, event_name, parameters + 1))
def accept_event(self):
"""Accept an event."""
obj = self.obj
event_name = self.event_name
parameters = self.parameters
# If no object, display the list of events to be checked
if obj is None:
table = EvTable("ID", "Type", "Object", "Name", "Updated by",
"On", width=78)
table.reformat_column(0, align="r")
now = datetime.now()
for obj, name, number in self.handler.db.to_valid:
events = self.handler.db.events.get(obj, {}).get(name)
if events is None:
continue
try:
event = events[number]
except IndexError:
continue
type_name = obj.typeclass_path.split(".")[-1]
by = event.get("updated_by")
by = by.key if by else "|gUnknown|n"
updated_on = event.get("updated_on")
if updated_on is None:
updated_on = event.get("created_on")
if updated_on:
updated_on = time_format(
(now - updated_on).total_seconds(), 1)
else:
updated_on = "|gUnknown|n"
table.add_row(obj.id, type_name, obj, name, by, updated_on)
self.msg(table)
return
# An object was specified
events = self.handler.get_events(obj)
types = self.handler.get_event_types(obj)
# If no event name is specified, display the list of events
if not event_name:
self.list_events()
return
# Check that the event exists
if not event_name in events:
self.msg("The event name {} can't be found in {}.".format(
event_name, obj))
return
if not parameters:
self.msg("Which event do you wish to accept? Specify a number.")
self.list_events()
return
# Check that the parameter points to an existing event
try:
parameters = int(parameters) - 1
assert parameters >= 0
event = events[event_name][parameters]
except (AssertionError, ValueError):
self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj))
return
# Accept the event
if event["valid"]:
self.msg("This event has already been accepted.")
else:
self.handler.accept_event(obj, event_name, parameters)
self.msg("The event {} {} of {} has been accepted.".format(
event_name, parameters, obj))
# Private functions to handle editing
def _ev_load(caller):
return caller.db._event and caller.db._event.get("code", "") or ""
def _ev_save(caller, buf):
"""Save and add the event."""
lock = "perm({}) or perm(events_without_validation)".format(
WITHOUT_VALIDATION)
autovalid = caller.locks.check_lockstring(caller, lock)
event = caller.db._event
handler = get_event_handler()
if not handler or not event or not all(key in event for key in \
("obj", "name", "number", "valid")):
caller.msg("Couldn't save this event.")
return False
handler.edit_event(event["obj"], event["name"], event["number"], buf,
caller, valid=autovalid)
return True
def _ev_quit(caller):
del caller.db._event
caller.msg("Exited the code editor.")