mirror of
https://github.com/evennia/evennia.git
synced 2026-03-28 18:47:16 +01:00
Adopt the new names for the event system
This commit is contained in:
parent
17b546c041
commit
96b90dde1e
9 changed files with 1433 additions and 1740 deletions
|
|
@ -1,332 +0,0 @@
|
|||
# Evennia's event system, user documentation
|
||||
|
||||
Evennia's event system allows to add dynamic features in your world without editing the source code. These features are placed on individual objects, and can offer opportunities to customize a few objects without customizing all of them. Usages can range from:
|
||||
|
||||
- Adding dialogues to some characters (a NPC greeting player-characters).
|
||||
- Adding some custom actions at specific in-game moments (a shop-keeper going home at 8 PM and coming back to the shop in the morning).
|
||||
- Build complex quests (a set of actions with conditions required to obtain some reward or advantage).
|
||||
- Deny a command from executing based on some conditions (prevent a character from going in some room without completing some quest).
|
||||
- Have some objects react in specific ways when some action occurs (a character enters the room, a character says something).
|
||||
|
||||
In short, the event system allows what other engines would implement through soft code or "scripting". The event system in Evennia doesn't rely on a homemade language, however, but on Python, and therefore allows almost everything possible through modifications to the source code. It's not necessary to know Evennia to use the event system, although knowing some basis of Evennia (the system of typeclasses and attributes, for instance) will not hurt.
|
||||
|
||||
## Some basic examples
|
||||
|
||||
Before beginning to use this system, it might be worth understanding its possibilities and basic features. The event system allows to create events that can be fired at specific moments. For instance, checking beforehand if a character has some characteristics before allowing him/her to walk through an exit. You will find some examples here (of course, this is only a list of examples, you could do so much more through this system):
|
||||
|
||||
Edit the event 'can_traverse' of a specific exit:
|
||||
if character.db.health < 30:
|
||||
character.msg("You are obviously too weak to do that.")
|
||||
deny()
|
||||
else: # That's really opional here, but why not?
|
||||
character.msg("Alrigh, you can go.")
|
||||
|
||||
The `deny()` function denies characters from moving and so, after the message has been sent, the action is cancelled (he/she doesn't move). The `else:` statement and instructions are, as in standard Python, optional here.
|
||||
|
||||
Edit the event 'eat' of a specific object:
|
||||
if character.db.race != "goblin":
|
||||
character.msg("This is a nice-tasting apple, as juicy as you'd like.")
|
||||
else:
|
||||
character.msg("You bite into the apple... and spit it out! Do people really eat that?!")
|
||||
character.db.health -= 10
|
||||
|
||||
This time, we have an event that behaves differently when a character eats an apple... and is a goblin, or something else. Notice that the race system will need to be in your game, the event system just provides ways to access your regular Evennia objects and attributes.
|
||||
|
||||
Edit the event 'time' of a specific NPC with the parameter '19:45':
|
||||
character.execute_cmd("say Well, it's time to go home, folks!")
|
||||
exit = character.location.search("up")
|
||||
|
||||
exit.db.lock = False
|
||||
exit.db.closed = False
|
||||
move(character, "up")
|
||||
exit.db.closed = True
|
||||
exit.db.lock = True
|
||||
|
||||
For this example, at 19:45 sharp (game time), the NPC leaves. It can be useful for a shop-keeper to just go in his/her room to sleep, and comeback in the morning.
|
||||
|
||||
You will find more examples in this documentation, along with clear indications on how to use this feature in context.
|
||||
|
||||
## Basic usage
|
||||
|
||||
The event system relies, to a great extent, on its `@event` command. By default, immortals will be the only ones to have access to this command, for obvious security reasons.
|
||||
|
||||
### The `@event` command
|
||||
|
||||
The event system can be used on most Evennia objects, mostly typeclassed objects (rooms, exits, characters, objects, and the ones you want to add to your game, players don't use this system however). The first argument of the `@event` command is the name of the object you want to edit.
|
||||
|
||||
#### Examining events
|
||||
|
||||
Let's say we are in a room with two exist, north and south. You could see what events are currently linked with the `north` exit by entering:
|
||||
|
||||
@event north
|
||||
|
||||
The object to display or edit is searched in the room, by default, which makes editing rather easy. However, you can also provide its DBREF (a number) after a `#` sign, like this:
|
||||
|
||||
@event #1
|
||||
|
||||
(In most settings, this will show the events linked with the character 1, the superuser.)
|
||||
|
||||
This command will display a table, containing:
|
||||
|
||||
- The name of each event in the first column.
|
||||
- The number of events of this name, and the number of total lines of these events in the second column.
|
||||
- A short help to tell you when the event is triggered in the third column.
|
||||
|
||||
Notice that several events can be linked at the same location. For instance, you can have several events in an exit's "can_traverse" event: each event will be called in the order and each can prevent the character from going elsewhere.
|
||||
|
||||
You can see the list of events of each name by using the same command, specifying the name of the event after an equal sign:
|
||||
|
||||
@event south = can_traverse
|
||||
|
||||
If you have more than one event of this name, they will be shown in a table with numbers starting from 1. You can examine a specific event by providing the number after the event's name:
|
||||
|
||||
@event south = can_traverse 1
|
||||
|
||||
This command will allow you to examine the event more closely, including seeing its associated code.
|
||||
|
||||
#### Creating a new event
|
||||
|
||||
The `/add` switch should be used to add an event. It takes two arguments beyond the object's name/DBREF:
|
||||
|
||||
1. After an = sign, the event to be edited (if not supplied, will display the list of possible events).
|
||||
2. The parameters (optional).
|
||||
|
||||
We'll see events with parameters later. For now, let's create an event 'can_traverse' connected to the exit 'north' in this room:
|
||||
|
||||
@event/add north = can_traverse
|
||||
|
||||
This will create a new event connected to this exit. It will be fired before a character traverses this exit. It is possible to prevent the character from moving at this point.
|
||||
|
||||
This command should open a line-editor. This editor is described in greater details in another section. For now, you can write instructions as normal:
|
||||
|
||||
if character.id == 1:
|
||||
character.msg("You're the superuser, 'course I'll let you pass.")
|
||||
else:
|
||||
character.msg("Hold on, what do you think you're doing?")
|
||||
deny()
|
||||
|
||||
You can now enter `:wq` to leave the editor by saving the event.
|
||||
|
||||
Then try to walk through this exit. Do it with another character if possible, too, to see the difference.
|
||||
|
||||
#### Editing an event
|
||||
|
||||
You can use the `/edit` switch to the `@event` command to edit an event. You should provide, after the name of the object to edit and the equal sign:
|
||||
|
||||
1. The name of the event (as seen above).
|
||||
2. A number, if several events are connected at this location.
|
||||
|
||||
You can type `@event/edit <object> = <event_name>` to see the events that are linked at this location. If there is only one event, it will be opened in the editor; if more are defined, you will be asked for a number to provide (for instance, `@event/edit north = can_traverse 2`).
|
||||
|
||||
#### Removing an event
|
||||
|
||||
The command `@event` also provides a `/del` switch to remove an event. It takes the same arguments as the `/edit` switch:
|
||||
|
||||
1. The name of the object.
|
||||
2. The name of the event after an = sign.
|
||||
3. Optionally a number if more than one event are located there.
|
||||
|
||||
When removed, events are logged, so an administrator can retrieve its content, assuming the `/del` was an error and the administrator has access to log files.
|
||||
|
||||
### The event editor
|
||||
|
||||
When adding or editing an event, the event editor should open. It is basically the same as [EvEditor](https://github.com/evennia/evennia/wiki/EvEditor), which so ressemble VI, but it adds a couple of options to handle indentation.
|
||||
|
||||
Python is a programming language that needs correct indentation. It is not an aesthetic concern, but a requirement to differentiate between blocks. The event editor will try to guess the right level of indentation to make your life easier, but it will not be perfect.
|
||||
|
||||
- If you enter an instruction beginning by `if`, `elif`, or `else`, the editor will automatically increase the level of indentation of the next line.
|
||||
- If the instruction is an `elif` or `else`, the editor will look for the opening block of `if` and match indentation.
|
||||
- Blocks `while`, `for`, `try`, `except`, 'finally' obey the same rules.
|
||||
|
||||
There are still some cases when you must tell the editor to reduce or increase indentation. The usual use cases are:
|
||||
|
||||
1. When you close a condition or loop, the editor will not be able to tell.
|
||||
2. When you want to keep the instruction on several lines, the editor will not bother with indentation.
|
||||
|
||||
In both cases, you should use the `:>` command (increase indentation by one level) and `:<` (decrease indentation by one level). Indentation is always shown when you add a new line in your event.
|
||||
|
||||
In all the cases shown above, you don't need to enter your indentation manually. Just change the indentation whenever needed, don't bother to write spaces or tabulations at the beginning of your line. For instance, you could enter the following lines in your client:
|
||||
|
||||
```
|
||||
if character.id == 1:
|
||||
character.msg("You're the big boss.")
|
||||
else:
|
||||
character.msg("I don't know who you are.")
|
||||
:<
|
||||
character.msg("This is not inside of the condition.")
|
||||
```
|
||||
|
||||
This will produce the following code:
|
||||
|
||||
```
|
||||
if character.id == 1:
|
||||
character.msg("You're the big boss.")
|
||||
else:
|
||||
character.msg("I don't know who you are.")
|
||||
|
||||
character.msg("This is not inside of the condition.")
|
||||
```
|
||||
|
||||
You can also disable the automatic-indentation mode. Just enter the command `:=`. In this mode, you will have to manually type in the spaces or tabulations, the editor will not indent anything without you asking to do it. This mode can be useful if you copy/paste some code and want to keep the original indentation.
|
||||
|
||||
## Using events
|
||||
|
||||
The following sub-sections describe how to use events for various tasks, from the most simple to the most complex.
|
||||
|
||||
### Standard Python code in events
|
||||
|
||||
This might sound superfluous, considering the previous explanations, but remember you can use standard Python code in your events. Everything that you could do in the source code itself, like changing attributes or aliases, creating or removing objects, can be done through this system. What you will see in the following sub-sections doesn't rely on a new syntax of Python: they add functions and some features, at the best. Events aren't written in softcode, and their syntax might, at first glance, be a bit unfriendly to a user without any programming skills. However, you will probably grasp the basic concept very quickly, and will be able to move beyond simple events in good time. Don't overlook examples, in this documentation, or in your game.
|
||||
|
||||
### The helper functions
|
||||
|
||||
In order to make development a little easier, the event system provides helper functions to be used in events themselves. You don't have to use them, they are just shortcuts.
|
||||
|
||||
The `deny()` function is such a helper. It allows to interrupt the event and the action that called it. In the `can_*` events, it can be used to prevent the action from happening. For instance, in `can_traverse` on exits, it can prevent the user from moving in that direction. One could have a `can_eat` event set on food that would prevent this character from eating this food. Or a `can_say` event in a room that would prevent the character from saying something here.
|
||||
|
||||
Behind the scene, the `deny()` function raises an exception that is being intercepted by the handler of events. Calling this function in events that cannot be stopped may result in errors.
|
||||
|
||||
You could easily add other helper functions. This will greatly depend on the objects you have defined in your game, and how often specific features have to be used by event users.
|
||||
|
||||
### Variables in events
|
||||
|
||||
Most events have variables. Variables are just Python variables. As you've seen in the previous example, when we manipulate characters or character actions, we often have a `character` variable that holds the character doing the action. The list of variables can change between events, and is always available in the help of the event. When you edit or add a new event, you'll see the help: read it carefully until you're familiar with this event, since it will give you useful information beyond the list of variables.
|
||||
|
||||
Sometimes, variables in events can also be set to contain new directions. One simple example is the exits' "msg_leave" event, that is called when a character leaves a room through this exit. This event is executed and you can set a custom message when a character walks through this exit, which can sometimes be useful:
|
||||
|
||||
@event/add down = msg_leave
|
||||
message = "{character} falls into a hole in the ground!"
|
||||
|
||||
Then, if the character Wilfred takes this story, others in the room will see:
|
||||
|
||||
Wildred falls into a hole in the ground!
|
||||
|
||||
### Events with parameters
|
||||
|
||||
Some events are called without parameter. For instance, when a character traverses through an exit, the exit's "traverse" event is called with no argument. In some cases, you can create events that are triggered under only some conditions. A typical example is the room's "say" event. This event is triggered when somebody says something in the room. The event can be configured to fire only when some words are used in the sentence.
|
||||
|
||||
For instance, let's say we want to create a cool voice-operated elevator. You enter into the elevator and say the floor number... and the elevator moves in the right direction. In this case, we could create an event with the parameter "one":
|
||||
|
||||
@event/add here = say one
|
||||
|
||||
This event will only fire when the user says "one" in this room.
|
||||
|
||||
But what if we want to have an event that would fire if the user says 1 or one? We can provide several parameters, separated by a comma.
|
||||
|
||||
@event/add here = say 1, one
|
||||
|
||||
Or, still more keywords:
|
||||
|
||||
@event/add here = say 1, one, ground
|
||||
|
||||
This time, the user could say "ground" or "one" in the room, and it would fire the event.
|
||||
|
||||
Not all events can take parameters, and these who do have a different ways of handling them. There isn't a single meaning to parameters that could apply to all events. Refer to the event documentation for details.
|
||||
|
||||
### Time-related events
|
||||
|
||||
Events are usually linked to commands. As we saw before, however, this is not always the case. Events can be triggered by other actions and, as we'll see later, could even be called from inside other events!
|
||||
|
||||
There is a specific event, on all objects, that can trigger at a specific time. It's an event with a mandatory argument, which is the time you expect this event to fire.
|
||||
|
||||
For instance, let's add an event on this room that should trigger every day, at precisely 12:00 PM (the time is given as game time, not real time):
|
||||
|
||||
```
|
||||
@event here = time 12:00
|
||||
# This will be called every MUD day at 12:00 PM
|
||||
room.msg_contents("It's noon, time to have lunch!")
|
||||
```
|
||||
|
||||
Now, at noon every MUD day, this event will fire. You can use this event on every kind of typeclassed object, to have a specific action done every MUD day at the same time.
|
||||
|
||||
Time-related events can be much more complex than this. They can trigger every in-game hour or more often (it might not be a good idea to have events trigger that often on a lot of objects). You can have events that run every in-game week or month or year. It will greatly vary depending on the type of calendar used in your game. The number of time units is described in the game configuration.
|
||||
|
||||
With a standard calendar, for instance, you have the following units: minutes, hours, days, months and years. You will specify them as numbers separated by either a colon (:), a space ( ), or a dash (-). Pick whatever feels more appropriate (usually, we separate hours and minutes with a colon, the other units with a dash).
|
||||
|
||||
Some examples of syntax:
|
||||
|
||||
- `18:30`: every day at 6:30 PM.
|
||||
- `01 12:00`: every month, the first day, at 12 PM.
|
||||
- `06-15 09:58`: every year, on the 15th of June (month comes before day), at 9:58 AM.
|
||||
- `2025-01-01 00:00`: January 1st, 2025 at midnight (obviously, this will trigger only once).
|
||||
|
||||
Notice that we specify units in the reverse order (year, month, day, hour and minute) and separate them with logical separators. The smallest unit that is not defined is going to set how often the event should fire. That's why, if you use `12:00`, the smallest unit that is not defined is "day": the event will fire every day at the specific time.
|
||||
|
||||
> You can use chained events (see below) in conjunction with time-related events to create more random or frequent actions in events.
|
||||
|
||||
### Chained events
|
||||
|
||||
Events can call other events, either now or a bit later. It is potentially very powerful.
|
||||
|
||||
To use chained events, just use the `call` helper function. It takes 2-3 arguments:
|
||||
|
||||
- The object containing the event.
|
||||
- The name of the event to call.
|
||||
- Optionally, the number of seconds to wait before calling this event.
|
||||
|
||||
All objects have events that are not triggered by commands or game-related operations. They are called "chain_X", like "chain_1", "chain_2", "chain_3" and so on. You can give them more specific names, as long as it begins by "chain_", like "chain_flood_room".
|
||||
|
||||
Rather than a long explanation, let's look at an example: a subway that will go from one place to the next at regular times. Creating exits (opening its doors), waiting a bit, closing them, rolling around and stopping at a different station. That's quite a complex set of events, as it is, but let's only look at the part that opens and closes the doors:
|
||||
|
||||
```
|
||||
@event here = time 10:00
|
||||
# At 10:00 AM, the subway arrives in the room of ID 22.
|
||||
# Notice that exit #23 and #24 are respectively the exit leading
|
||||
# on the platform and back in the subway.
|
||||
station = get(id=22)
|
||||
# Open the door
|
||||
to_exit = get(id=23)
|
||||
to_exit.name = "platform"
|
||||
to_exit.aliases = ["p"]
|
||||
to_exit.location = room
|
||||
to_exit.destination = station
|
||||
# Create the return exit
|
||||
back_exit = get(id=24)
|
||||
back_exit.name = "subway"
|
||||
back_exit.location = station
|
||||
back_exit.destination = room
|
||||
# Display some messages
|
||||
room.msg_contents("The doors open and wind gushes in the subway")
|
||||
station.msg_contents("The doors of the subway open with a dull clank.")
|
||||
# Set the doors to close in 20 seconds
|
||||
call(room, "chain_1", 20)
|
||||
```
|
||||
|
||||
This event will:
|
||||
|
||||
1. Be called at 10:00 AM (specify 22:00 to say 10:00 PM).
|
||||
2. Set an exit between the subway and the station. Notice that the exits already exist (you will have to create them), but they don't need to have specific location and destination.
|
||||
3. Display a message both in the subway and on the platform.
|
||||
4. Call the event "chain_1" to execute in 20 seconds.
|
||||
|
||||
And now, what should we have in "chain_1"?
|
||||
|
||||
```
|
||||
@event here = chain_1
|
||||
# Close the doors
|
||||
to_exit.location = None
|
||||
to_exit.destination = None
|
||||
back_exit.location = None
|
||||
back_exit.destination = None
|
||||
room.msg_content("After a short warning signal, the doors close and the subway begins moving.")
|
||||
station.msg_content("After a short warning signal, the doors close and the subway begins moving.")
|
||||
```
|
||||
|
||||
Behind the scene, the `call` function freezes all variables ("room", "station", "to_exit, "back_exit" in our example), so you don't need to define them afterward.
|
||||
|
||||
A word of caution on events that call chained events: it isn't impossible for an event to call itself at some recursion level. If `chain_1` calls `chain_2` that calls `chain_3` that calls `chain_`, particularly if there's no pause between them, you might run into an infinite loop.
|
||||
|
||||
Be also careful when it comes to handling characters or objects that may very well move during your pause between event calls. When you use `call()`, the MUD doesn't pause and commands can be entered by players, fortunately. It also means that, a character could start an event that pauses for awhile, but be gone when the chained event is called. You need to check that, even lock the character into place while you are pausing (some actions should require locking) or at least, checking that the character is still in the room, for it might create illogical situations if you don't.
|
||||
|
||||
## Errors in events
|
||||
|
||||
There are a lot of ways to make mistakes while writing events. Once you begin, you might encounter syntax errors very often, but leave them behind as you gain in confidence. However, there are still so many ways to trigger errors: passing the wrong arguments to a helper function is only one of many possible examples.
|
||||
|
||||
When an event encounters an error, it stops abruptly and sends the error on a special channel, named "everror", on which you can connect or disconnect should the amount of information be overwhelming. These error messages will contain:
|
||||
|
||||
- The name and ID of the object that encountered the error.
|
||||
- The name and number of the event that crashed.
|
||||
- The line number (and code) that caused the error.
|
||||
- The short error messages (it might not be that short at times).
|
||||
|
||||
The error will also be logged, so an administrator can still access it more completely, seeing the full traceback, which can help to understand the error sometimes.
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Module containing the commands of the event system.
|
||||
Module containing the commands of the callback system.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
|
@ -10,77 +10,77 @@ from evennia.utils.ansi import raw
|
|||
from evennia.utils.eveditor import EvEditor
|
||||
from evennia.utils.evtable import EvTable
|
||||
from evennia.utils.utils import class_from_module, time_format
|
||||
from evennia.contrib.events.custom import get_event_handler
|
||||
from evennia.contrib.events.utils import get_event_handler
|
||||
|
||||
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",
|
||||
WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None)
|
||||
WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION",
|
||||
"immortals")
|
||||
VALIDATING = getattr(settings, "EVENTS_VALIDATING", "immortals")
|
||||
VALIDATING = getattr(settings, "callbackS_VALIDATING", "immortals")
|
||||
|
||||
# Split help text
|
||||
BASIC_HELP = "Add, edit or delete events."
|
||||
BASIC_HELP = "Add, edit or delete callbacks."
|
||||
|
||||
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]",
|
||||
"@event/tasks [object name [= <event name>]]",
|
||||
"@call <object name> [= <callback name>]",
|
||||
"@call/add <object name> = <callback name> [parameters]",
|
||||
"@call/edit <object name> = <callback name> [callback number]",
|
||||
"@call/del <object name> = <callback name> [callback number]",
|
||||
"@call/tasks [object name [= <callback name>]]",
|
||||
]
|
||||
|
||||
BASIC_SWITCHES = [
|
||||
"add - add and edit a new event",
|
||||
"edit - edit an existing event",
|
||||
"del - delete an existing event",
|
||||
"add - add and edit a new callback",
|
||||
"edit - edit an existing callback",
|
||||
"del - delete an existing callback",
|
||||
"tasks - show the list of differed tasks",
|
||||
]
|
||||
|
||||
VALIDATOR_USAGES = [
|
||||
"@event/accept [object name = <event name> [event number]]",
|
||||
"@call/accept [object name = <callback name> [callback number]]",
|
||||
]
|
||||
|
||||
VALIDATOR_SWITCHES = [
|
||||
"accept - show events to be validated or accept one",
|
||||
"accept - show callbacks to be validated or accept one",
|
||||
]
|
||||
|
||||
BASIC_TEXT = """
|
||||
This command is used to manipulate events. An event can be linked to
|
||||
This command is used to manipulate callbacks. A callback 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 a number after the event name to see details on one event:
|
||||
@event here = say 2
|
||||
You can also add, edit or remove events using the add, edit or del switches.
|
||||
Additionally, you can see the list of differed tasks created by events
|
||||
(chained events to be called) using the /tasks switch.
|
||||
switches to see what callbacks are active on an object:
|
||||
@call self
|
||||
You can also specify a callback name if you want the list of callbacks
|
||||
associated with this object of this name:
|
||||
@call north = can_traverse
|
||||
You can also add a number after the callback name to see details on one callback:
|
||||
@call here = say 2
|
||||
You can also add, edit or remove callbacks using the add, edit or del switches.
|
||||
Additionally, you can see the list of differed tasks created by callbacks
|
||||
(chained callbacks to be called) using the /tasks switch.
|
||||
"""
|
||||
|
||||
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
|
||||
You can also use this command to validate callbacks. Depending on your game
|
||||
setting, some users might be allowed to add new callbacks, but these callbacks
|
||||
will not be fired until you accept them. To see the callbacks needing
|
||||
validation, enter the /accept switch without argument:
|
||||
@event/accept
|
||||
A table will show you the events that are not validated yet, who created
|
||||
them and when. You can then accept a specific event:
|
||||
@event here = enter 1
|
||||
Use the /del switch to remove events that should not be connected.
|
||||
@call/accept
|
||||
A table will show you the callbacks that are not validated yet, who created
|
||||
them and when. You can then accept a specific callback:
|
||||
@call here = enter 1
|
||||
Use the /del switch to remove callbacks that should not be connected.
|
||||
"""
|
||||
|
||||
class CmdEvent(COMMAND_DEFAULT_CLASS):
|
||||
class CmdCallback(COMMAND_DEFAULT_CLASS):
|
||||
|
||||
"""
|
||||
Command to edit events.
|
||||
Command to edit callbacks.
|
||||
"""
|
||||
|
||||
key = "@event"
|
||||
aliases = ["@events", "@ev"]
|
||||
key = "@call"
|
||||
aliases = ["@callback", "@callbacks", "@calls"]
|
||||
locks = "cmd:perm({})".format(VALIDATING)
|
||||
if WITH_VALIDATION:
|
||||
locks += " or perm({})".format(WITH_VALIDATION)
|
||||
|
|
@ -101,7 +101,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
|
|||
docstring (str): the help text to provide the caller for this command.
|
||||
|
||||
"""
|
||||
lock = "perm({}) or perm(events_validating)".format(VALIDATING)
|
||||
lock = "perm({}) or perm(callbacks_validating)".format(VALIDATING)
|
||||
validator = caller.locks.check_lockstring(caller, lock)
|
||||
text = "\n" + BASIC_HELP + "\n\nUsages:\n "
|
||||
|
||||
|
|
@ -132,12 +132,12 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
|
|||
WITHOUT_VALIDATION)
|
||||
autovalid = caller.locks.check_lockstring(caller, lock)
|
||||
|
||||
# First and foremost, get the event handler and set other variables
|
||||
# First and foremost, get the callback 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.callback_name, sep, self.parameters = rhs.partition(" ")
|
||||
self.callback_name = self.callback_name.lower()
|
||||
self.is_validator = validator
|
||||
self.autovalid = autovalid
|
||||
if self.handler is None:
|
||||
|
|
@ -158,75 +158,73 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
|
||||
if switch == "":
|
||||
self.list_events()
|
||||
self.list_callbacks()
|
||||
elif switch == "add":
|
||||
self.add_event()
|
||||
self.add_callback()
|
||||
elif switch == "edit":
|
||||
self.edit_event()
|
||||
self.edit_callback()
|
||||
elif switch == "del":
|
||||
self.del_event()
|
||||
self.del_callback()
|
||||
elif switch == "accept" and validator:
|
||||
self.accept_event()
|
||||
self.accept_callback()
|
||||
elif switch in ["tasks", "task"]:
|
||||
self.list_tasks()
|
||||
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."""
|
||||
def list_callbacks(self):
|
||||
"""Display the list of callbacks connected to the object."""
|
||||
obj = self.obj
|
||||
event_name = self.event_name
|
||||
callback_name = self.callback_name
|
||||
parameters = self.parameters
|
||||
events = self.handler.get_events(obj)
|
||||
types = self.handler.get_event_types(obj)
|
||||
callbacks = self.handler.get_callbacks(obj)
|
||||
types = self.handler.get_events(obj)
|
||||
|
||||
if event_name:
|
||||
# Check that the event name can be found in this object
|
||||
created = events.get(event_name)
|
||||
if callback_name:
|
||||
# Check that the callback name can be found in this object
|
||||
created = callbacks.get(callback_name)
|
||||
if created is None:
|
||||
self.msg("No event {} has been set on {}.".format(event_name,
|
||||
self.msg("No callback {} has been set on {}.".format(callback_name,
|
||||
obj))
|
||||
return
|
||||
|
||||
if parameters:
|
||||
# Check that the parameter points to an existing event
|
||||
# Check that the parameter points to an existing callback
|
||||
try:
|
||||
number = int(parameters) - 1
|
||||
assert number >= 0
|
||||
event = events[event_name][number]
|
||||
callback = callbacks[callback_name][number]
|
||||
except (ValueError, AssertionError, IndexError):
|
||||
self.msg("The event {} {} cannot be found in {}.".format(
|
||||
event_name, parameters, obj))
|
||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||
callback_name, parameters, obj))
|
||||
return
|
||||
|
||||
# Display the events' details
|
||||
author = event.get("author")
|
||||
# Display the callback's details
|
||||
author = callback.get("author")
|
||||
author = author.key if author else "|gUnknown|n"
|
||||
updated_by = event.get("updated_by")
|
||||
updated_by = callback.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"
|
||||
msg = "Event {} {} of {}:".format(event_name, parameters, obj)
|
||||
created_on = callback.get("created_on")
|
||||
created_on = created_on.strftime("%Y-%m-%d %H:%M:%S") if created_on else "|gUnknown|n"
|
||||
updated_on = callback.get("updated_on")
|
||||
updated_on = updated_on.strftime("%Y-%m-%d %H:%M:%S") if updated_on else "|gUnknown|n"
|
||||
msg = "Callback {} {} of {}:".format(callback_name, parameters, obj)
|
||||
msg += "\nCreated by {} on {}.".format(author, created_on)
|
||||
msg += "\nUpdated by {} on {}".format(updated_by, updated_on)
|
||||
|
||||
if self.is_validator:
|
||||
if event.get("valid"):
|
||||
msg += "\nThis event is |rconnected|n and active."
|
||||
if callback.get("valid"):
|
||||
msg += "\nThis callback is |rconnected|n and active."
|
||||
else:
|
||||
msg += "\nThis event |rhasn't been|n accepted yet."
|
||||
msg += "\nThis callback |rhasn't been|n accepted yet."
|
||||
|
||||
msg += "\nEvent code:\n"
|
||||
msg += raw(event["code"])
|
||||
msg += "\nCallback code:\n"
|
||||
msg += raw(callback["code"])
|
||||
self.msg(msg)
|
||||
return
|
||||
|
||||
# No parameter has been specified, display the table of events
|
||||
# No parameter has been specified, display the table of callbacks
|
||||
cols = ["Number", "Author", "Updated", "Param"]
|
||||
if self.is_validator:
|
||||
cols.append("Valid")
|
||||
|
|
@ -234,12 +232,12 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
|
|||
table = EvTable(*cols, width=78)
|
||||
table.reformat_column(0, align="r")
|
||||
now = datetime.now()
|
||||
for i, event in enumerate(created):
|
||||
author = event.get("author")
|
||||
for i, callback in enumerate(created):
|
||||
author = callback.get("author")
|
||||
author = author.key if author else "|gUnknown|n"
|
||||
updated_on = event.get("updated_on")
|
||||
updated_on = callback.get("updated_on")
|
||||
if updated_on is None:
|
||||
updated_on = event.get("created_on")
|
||||
updated_on = callback.get("created_on")
|
||||
|
||||
if updated_on:
|
||||
updated_on = "{} ago".format(time_format(
|
||||
|
|
@ -247,211 +245,211 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
|
|||
4).capitalize())
|
||||
else:
|
||||
updated_on = "|gUnknown|n"
|
||||
parameters = event.get("parameters", "")
|
||||
parameters = callback.get("parameters", "")
|
||||
|
||||
row = [str(i + 1), author, updated_on, parameters]
|
||||
if self.is_validator:
|
||||
row.append("Yes" if event.get("valid") else "No")
|
||||
row.append("Yes" if callback.get("valid") else "No")
|
||||
table.add_row(*row)
|
||||
|
||||
self.msg(unicode(table))
|
||||
else:
|
||||
names = list(set(list(types.keys()) + list(events.keys())))
|
||||
table = EvTable("Event name", "Number", "Description",
|
||||
names = list(set(list(types.keys()) + list(callbacks.keys())))
|
||||
table = EvTable("Callback 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, []))
|
||||
number = len(callbacks.get(name, []))
|
||||
lines = sum(len(e["code"].splitlines()) for e in callbacks.get(name, []))
|
||||
no = "{} ({})".format(number, lines)
|
||||
description = types.get(name, (None, "Chained event."))[1]
|
||||
description = description.splitlines()[0]
|
||||
description = types.get(name, (None, "Chained callback."))[1]
|
||||
description = description.strip("\n").splitlines()[0]
|
||||
table.add_row(name, no, description)
|
||||
|
||||
self.msg(unicode(table))
|
||||
|
||||
def add_event(self):
|
||||
"""Add an event."""
|
||||
def add_callback(self):
|
||||
"""Add a callback."""
|
||||
obj = self.obj
|
||||
event_name = self.event_name
|
||||
types = self.handler.get_event_types(obj)
|
||||
callback_name = self.callback_name
|
||||
types = self.handler.get_events(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)))
|
||||
# Check that the callback exists
|
||||
if not callback_name.startswith("chain_") and not callback_name in types:
|
||||
self.msg("The callback name {} can't be found in {} of " \
|
||||
"typeclass {}.".format(callback_name, obj, type(obj)))
|
||||
return
|
||||
|
||||
definition = types.get(event_name, (None, "Chain event"))
|
||||
definition = types.get(callback_name, (None, "Chained callback"))
|
||||
description = definition[1]
|
||||
self.msg(raw(description))
|
||||
self.msg(raw(description.strip("\n")))
|
||||
|
||||
# Open the editor
|
||||
event = self.handler.add_event(obj, event_name, "",
|
||||
callback = self.handler.add_callback(obj, callback_name, "",
|
||||
self.caller, False, parameters=self.parameters)
|
||||
|
||||
# Lock this event right away
|
||||
self.handler.db.locked.append((obj, event_name, event["number"]))
|
||||
# Lock this callback right away
|
||||
self.handler.db.locked.append((obj, callback_name, callback["number"]))
|
||||
|
||||
# Open the editor for this event
|
||||
self.caller.db._event = event
|
||||
# Open the editor for this callback
|
||||
self.caller.db._callback = callback
|
||||
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
|
||||
quitfunc=_ev_quit, key="Event {} of {}".format(
|
||||
event_name, obj), persistent=True, codefunc=_ev_save)
|
||||
quitfunc=_ev_quit, key="Callback {} of {}".format(
|
||||
callback_name, obj), persistent=True, codefunc=_ev_save)
|
||||
|
||||
def edit_event(self):
|
||||
"""Edit an event."""
|
||||
def edit_callback(self):
|
||||
"""Edit a callback."""
|
||||
obj = self.obj
|
||||
event_name = self.event_name
|
||||
callback_name = self.callback_name
|
||||
parameters = self.parameters
|
||||
events = self.handler.get_events(obj)
|
||||
types = self.handler.get_event_types(obj)
|
||||
callbacks = self.handler.get_callbacks(obj)
|
||||
types = self.handler.get_events(obj)
|
||||
|
||||
# If no event name is specified, display the list of events
|
||||
if not event_name:
|
||||
self.list_events()
|
||||
# If no callback name is specified, display the list of callbacks
|
||||
if not callback_name:
|
||||
self.list_callbacks()
|
||||
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))
|
||||
# Check that the callback exists
|
||||
if not callback_name in callbacks:
|
||||
self.msg("The callback name {} can't be found in {}.".format(
|
||||
callback_name, obj))
|
||||
return
|
||||
|
||||
# If there's only one event, just edit it
|
||||
if len(events[event_name]) == 1:
|
||||
# If there's only one callback, just edit it
|
||||
if len(callbacks[callback_name]) == 1:
|
||||
number = 0
|
||||
event = events[event_name][0]
|
||||
callback = callbacks[callback_name][0]
|
||||
else:
|
||||
if not parameters:
|
||||
self.msg("Which event do you wish to edit? Specify a number.")
|
||||
self.list_events()
|
||||
self.msg("Which callback do you wish to edit? Specify a number.")
|
||||
self.list_callbacks()
|
||||
return
|
||||
|
||||
# Check that the parameter points to an existing event
|
||||
# Check that the parameter points to an existing callback
|
||||
try:
|
||||
number = int(parameters) - 1
|
||||
assert number >= 0
|
||||
event = events[event_name][number]
|
||||
callback = callbacks[callback_name][number]
|
||||
except (ValueError, AssertionError, IndexError):
|
||||
self.msg("The event {} {} cannot be found in {}.".format(
|
||||
event_name, parameters, obj))
|
||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||
callback_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.")
|
||||
if not self.autovalid and callback["author"] is not self.caller:
|
||||
self.msg("You cannot edit this callback created by someone else.")
|
||||
return
|
||||
|
||||
# If the event is locked (edited by someone else)
|
||||
if (obj, event_name, number) in self.handler.db.locked:
|
||||
self.msg("This event is locked, you cannot edit it.")
|
||||
# If the callback is locked (edited by someone else)
|
||||
if (obj, callback_name, number) in self.handler.db.locked:
|
||||
self.msg("This callback is locked, you cannot edit it.")
|
||||
return
|
||||
self.handler.db.locked.append((obj, event_name, number))
|
||||
|
||||
# Check the definition of the event
|
||||
definition = types.get(event_name, (None, "Chained event"))
|
||||
self.handler.db.locked.append((obj, callback_name, number))
|
||||
|
||||
# Check the definition of the callback
|
||||
definition = types.get(callback_name, (None, "Chained callback"))
|
||||
description = definition[1]
|
||||
self.msg(raw(description))
|
||||
self.msg(raw(description.strip("\n")))
|
||||
|
||||
# Open the editor
|
||||
event = dict(event)
|
||||
event["obj"] = obj
|
||||
event["name"] = event_name
|
||||
event["number"] = number
|
||||
self.caller.db._event = event
|
||||
callback = dict(callback)
|
||||
callback["obj"] = obj
|
||||
callback["name"] = callback_name
|
||||
callback["number"] = number
|
||||
self.caller.db._callback = callback
|
||||
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
|
||||
quitfunc=_ev_quit, key="Event {} of {}".format(
|
||||
event_name, obj), persistent=True, codefunc=_ev_save)
|
||||
quitfunc=_ev_quit, key="Callback {} of {}".format(
|
||||
callback_name, obj), persistent=True, codefunc=_ev_save)
|
||||
|
||||
def del_event(self):
|
||||
"""Delete an event."""
|
||||
def del_callback(self):
|
||||
"""Delete a callback."""
|
||||
obj = self.obj
|
||||
event_name = self.event_name
|
||||
callback_name = self.callback_name
|
||||
parameters = self.parameters
|
||||
events = self.handler.get_events(obj)
|
||||
types = self.handler.get_event_types(obj)
|
||||
callbacks = self.handler.get_callbacks(obj)
|
||||
types = self.handler.get_events(obj)
|
||||
|
||||
# If no event name is specified, display the list of events
|
||||
if not event_name:
|
||||
self.list_events()
|
||||
# If no callback name is specified, display the list of callbacks
|
||||
if not callback_name:
|
||||
self.list_callbacks()
|
||||
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))
|
||||
# Check that the callback exists
|
||||
if not callback_name in callbacks:
|
||||
self.msg("The callback name {} can't be found in {}.".format(
|
||||
callback_name, obj))
|
||||
return
|
||||
|
||||
# If there's only one event, just delete it
|
||||
if len(events[event_name]) == 1:
|
||||
# If there's only one callback, just delete it
|
||||
if len(callbacks[callback_name]) == 1:
|
||||
number = 0
|
||||
event = events[event_name][0]
|
||||
callback = callbacks[callback_name][0]
|
||||
else:
|
||||
if not parameters:
|
||||
self.msg("Which event do you wish to delete? Specify " \
|
||||
self.msg("Which callback do you wish to delete? Specify " \
|
||||
"a number.")
|
||||
self.list_events()
|
||||
self.list_callbacks()
|
||||
return
|
||||
|
||||
# Check that the parameter points to an existing event
|
||||
# Check that the parameter points to an existing callback
|
||||
try:
|
||||
number = int(parameters) - 1
|
||||
assert number >= 0
|
||||
event = events[event_name][number]
|
||||
callback = callbacks[callback_name][number]
|
||||
except (ValueError, AssertionError, IndexError):
|
||||
self.msg("The event {} {} cannot be found in {}.".format(
|
||||
event_name, parameters, obj))
|
||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||
callback_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.")
|
||||
if not self.autovalid and callback["author"] is not self.caller:
|
||||
self.msg("You cannot delete this callback created by someone else.")
|
||||
return
|
||||
|
||||
# If the event is locked (edited by someone else)
|
||||
if (obj, event_name, number) in self.handler.db.locked:
|
||||
self.msg("This event is locked, you cannot delete it.")
|
||||
# If the callback is locked (edited by someone else)
|
||||
if (obj, callback_name, number) in self.handler.db.locked:
|
||||
self.msg("This callback is locked, you cannot delete it.")
|
||||
return
|
||||
|
||||
# Delete the event
|
||||
self.handler.del_event(obj, event_name, number)
|
||||
self.msg("The event {}[{}] of {} was deleted.".format(
|
||||
event_name, number + 1, obj))
|
||||
# Delete the callback
|
||||
self.handler.del_callback(obj, callback_name, number)
|
||||
self.msg("The callback {}[{}] of {} was deleted.".format(
|
||||
callback_name, number + 1, obj))
|
||||
|
||||
def accept_event(self):
|
||||
"""Accept an event."""
|
||||
def accept_callback(self):
|
||||
"""Accept a callback."""
|
||||
obj = self.obj
|
||||
event_name = self.event_name
|
||||
callback_name = self.callback_name
|
||||
parameters = self.parameters
|
||||
|
||||
# If no object, display the list of events to be checked
|
||||
# If no object, display the list of callbacks 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:
|
||||
callbacks = self.handler.get_callbacks(obj).get(name)
|
||||
if callbacks is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
event = events[number]
|
||||
callback = callbacks[number]
|
||||
except IndexError:
|
||||
continue
|
||||
|
||||
type_name = obj.typeclass_path.split(".")[-1]
|
||||
by = event.get("updated_by")
|
||||
by = callback.get("updated_by")
|
||||
by = by.key if by else "|gUnknown|n"
|
||||
updated_on = event.get("updated_on")
|
||||
updated_on = callback.get("updated_on")
|
||||
if updated_on is None:
|
||||
updated_on = event.get("created_on")
|
||||
updated_on = callback.get("created_on")
|
||||
|
||||
if updated_on:
|
||||
updated_on = "{} ago".format(time_format(
|
||||
|
|
@ -465,100 +463,100 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
|
||||
# An object was specified
|
||||
events = self.handler.get_events(obj)
|
||||
types = self.handler.get_event_types(obj)
|
||||
callbacks = self.handler.get_callbacks(obj)
|
||||
types = self.handler.get_events(obj)
|
||||
|
||||
# If no event name is specified, display the list of events
|
||||
if not event_name:
|
||||
self.list_events()
|
||||
# If no callback name is specified, display the list of callbacks
|
||||
if not callback_name:
|
||||
self.list_callbacks()
|
||||
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))
|
||||
# Check that the callback exists
|
||||
if not callback_name in callbacks:
|
||||
self.msg("The callback name {} can't be found in {}.".format(
|
||||
callback_name, obj))
|
||||
return
|
||||
|
||||
if not parameters:
|
||||
self.msg("Which event do you wish to accept? Specify a number.")
|
||||
self.list_events()
|
||||
self.msg("Which callback do you wish to accept? Specify a number.")
|
||||
self.list_callbacks()
|
||||
return
|
||||
|
||||
# Check that the parameter points to an existing event
|
||||
# Check that the parameter points to an existing callback
|
||||
try:
|
||||
number = int(parameters) - 1
|
||||
assert number >= 0
|
||||
event = events[event_name][number]
|
||||
callback = callbacks[callback_name][number]
|
||||
except (ValueError, AssertionError, IndexError):
|
||||
self.msg("The event {} {} cannot be found in {}.".format(
|
||||
event_name, parameters, obj))
|
||||
self.msg("The callback {} {} cannot be found in {}.".format(
|
||||
callback_name, parameters, obj))
|
||||
return
|
||||
|
||||
# Accept the event
|
||||
if event["valid"]:
|
||||
self.msg("This event has already been accepted.")
|
||||
# Accept the callback
|
||||
if callback["valid"]:
|
||||
self.msg("This callback has already been accepted.")
|
||||
else:
|
||||
self.handler.accept_event(obj, event_name, number)
|
||||
self.msg("The event {} {} of {} has been accepted.".format(
|
||||
event_name, parameters, obj))
|
||||
self.handler.accept_callback(obj, callback_name, number)
|
||||
self.msg("The callback {} {} of {} has been accepted.".format(
|
||||
callback_name, parameters, obj))
|
||||
|
||||
def list_tasks(self):
|
||||
"""List the active tasks."""
|
||||
obj = self.obj
|
||||
event_name = self.event_name
|
||||
callback_name = self.callback_name
|
||||
handler = self.handler
|
||||
tasks = [(k, v[0], v[1], v[2]) for k, v in handler.db.tasks.items()]
|
||||
if obj:
|
||||
tasks = [task for task in tasks if task[2] is obj]
|
||||
if event_name:
|
||||
tasks = [task for task in tasks if task[3] == event_name]
|
||||
if callback_name:
|
||||
tasks = [task for task in tasks if task[3] == callback_name]
|
||||
|
||||
tasks.sort()
|
||||
table = EvTable("ID", "Object", "Event", "In", width=78)
|
||||
table = EvTable("ID", "Object", "Callback", "In", width=78)
|
||||
table.reformat_column(0, align="r")
|
||||
now = datetime.now()
|
||||
for task_id, future, obj, event_name in tasks:
|
||||
for task_id, future, obj, callback_name in tasks:
|
||||
key = obj.get_display_name(self.caller)
|
||||
delta = time_format((future - now).total_seconds(), 1)
|
||||
table.add_row(task_id, key, event_name, delta)
|
||||
table.add_row(task_id, key, callback_name, delta)
|
||||
|
||||
self.msg(unicode(table))
|
||||
|
||||
# Private functions to handle editing
|
||||
def _ev_load(caller):
|
||||
return caller.db._event and caller.db._event.get("code", "") or ""
|
||||
return caller.db._callback and caller.db._callback.get("code", "") or ""
|
||||
|
||||
def _ev_save(caller, buf):
|
||||
"""Save and add the event."""
|
||||
"""Save and add the callback."""
|
||||
lock = "perm({}) or perm(events_without_validation)".format(
|
||||
WITHOUT_VALIDATION)
|
||||
autovalid = caller.locks.check_lockstring(caller, lock)
|
||||
event = caller.db._event
|
||||
callback = caller.db._callback
|
||||
handler = get_event_handler()
|
||||
if not handler or not event or not all(key in event for key in \
|
||||
if not handler or not callback or not all(key in callback for key in \
|
||||
("obj", "name", "number", "valid")):
|
||||
caller.msg("Couldn't save this event.")
|
||||
caller.msg("Couldn't save this callback.")
|
||||
return False
|
||||
|
||||
if (event["obj"], event["name"], event["number"]) in handler.db.locked:
|
||||
handler.db.locked.remove((event["obj"], event["name"],
|
||||
event["number"]))
|
||||
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
|
||||
handler.db.locked.remove((callback["obj"], callback["name"],
|
||||
callback["number"]))
|
||||
|
||||
handler.edit_event(event["obj"], event["name"], event["number"], buf,
|
||||
handler.edit_callback(callback["obj"], callback["name"], callback["number"], buf,
|
||||
caller, valid=autovalid)
|
||||
return True
|
||||
|
||||
def _ev_quit(caller):
|
||||
event = caller.db._event
|
||||
callback = caller.db._callback
|
||||
handler = get_event_handler()
|
||||
if not handler or not event or not all(key in event for key in \
|
||||
if not handler or not callback or not all(key in callback for key in \
|
||||
("obj", "name", "number", "valid")):
|
||||
caller.msg("Couldn't save this event.")
|
||||
caller.msg("Couldn't save this callback.")
|
||||
return False
|
||||
|
||||
if (event["obj"], event["name"], event["number"]) in handler.db.locked:
|
||||
handler.db.locked.remove((event["obj"], event["name"],
|
||||
event["number"]))
|
||||
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
|
||||
handler.db.locked.remove((callback["obj"], callback["name"],
|
||||
callback["number"]))
|
||||
|
||||
del caller.db._event
|
||||
del caller.db._callback
|
||||
caller.msg("Exited the code editor.")
|
||||
|
|
|
|||
|
|
@ -1,265 +0,0 @@
|
|||
"""
|
||||
Functions to extend the event system.
|
||||
|
||||
These funcitons are not helpers (helpers are in a separate module)
|
||||
and are designed to be used more by developers to add event types.
|
||||
|
||||
"""
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
from django.conf import settings
|
||||
from evennia import logger
|
||||
from evennia import ScriptDB
|
||||
from evennia.utils.create import create_script
|
||||
from evennia.utils.gametime import real_seconds_until as standard_rsu
|
||||
from evennia.contrib.custom_gametime import UNITS
|
||||
from evennia.contrib.custom_gametime import gametime_to_realtime
|
||||
from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu
|
||||
|
||||
hooks = []
|
||||
event_types = []
|
||||
|
||||
def get_event_handler():
|
||||
"""Return the event handler or None."""
|
||||
try:
|
||||
script = ScriptDB.objects.get(db_key="event_handler")
|
||||
except ScriptDB.DoesNotExist:
|
||||
logger.log_trace("Can't get the event handler.")
|
||||
script = None
|
||||
|
||||
return script
|
||||
|
||||
def create_event_type(typeclass, event_name, variables, help_text,
|
||||
custom_add=None, custom_call=None):
|
||||
"""
|
||||
Create a new event type for a specific typeclass.
|
||||
|
||||
Args:
|
||||
typeclass (type): the class defining tye typeclass to be used.
|
||||
event_name (str): the name of the event to be added.
|
||||
variables (list of str): a list of variable names.
|
||||
help_text (str): a help text of the event.
|
||||
custom_add (function, optional): a callback to call when adding
|
||||
the new event.
|
||||
custom_call (function, optional): a callback to call when
|
||||
preparing to call the event.
|
||||
|
||||
Events obey the inheritance hierarchy: if you set an event on
|
||||
DefaultRoom, for instance, and if your Room typeclass inherits
|
||||
from DefaultRoom (the default), the event will be available to
|
||||
all rooms. Objects of the typeclass set in argument will be
|
||||
able to set one or more events of that name.
|
||||
|
||||
If the event type already exists in the typeclass, replace it.
|
||||
|
||||
"""
|
||||
typeclass_name = typeclass.__module__ + "." + typeclass.__name__
|
||||
event_types.append((typeclass_name, event_name, variables, help_text,
|
||||
custom_add, custom_call))
|
||||
|
||||
def invalidate_event_type(typeclass, event_name):
|
||||
"""
|
||||
Invalidate a descending event type defined above in the hierarchy.
|
||||
|
||||
Event types follow the hierarchy of inheritance. Events defined
|
||||
in DefaultObjects would be accessible in DefaultRooms, for instance.
|
||||
This can ensure that the event is limited and doesn't apply to
|
||||
children with instances.
|
||||
|
||||
Args:
|
||||
typeclass (type): the class describing the typeclass.
|
||||
event_name (str): the name of the event to invalidate.
|
||||
|
||||
Example:
|
||||
create_event_type(DefaultObject, "get", ["object"], "Someone gets.")
|
||||
invalidate_event_type(DefaultRoom, "get")
|
||||
# room objects won't have the 'get' event
|
||||
|
||||
"""
|
||||
typeclass_name = typeclass.__module__ + "." + typeclass.__name__
|
||||
event_types.append((typeclass_name, event_name, None, "", None, None))
|
||||
|
||||
def connect_event_types():
|
||||
"""
|
||||
Connect the event types when the script runs.
|
||||
|
||||
This method should be called automatically by the event handler
|
||||
(the script). It might be useful, however, to call it after adding
|
||||
new event types in typeclasses.
|
||||
|
||||
"""
|
||||
try:
|
||||
script = ScriptDB.objects.get(db_key="event_handler")
|
||||
except ScriptDB.DoesNotExist:
|
||||
logger.log_trace("Can't connect event types, the event handler " \
|
||||
"cannot be found.")
|
||||
return
|
||||
|
||||
if script.ndb.event_types is None:
|
||||
return
|
||||
|
||||
t_event_types = list(event_types)
|
||||
while t_event_types:
|
||||
typeclass_name, event_name, variables, help_text, \
|
||||
custom_add, custom_call = t_event_types[0]
|
||||
|
||||
# Get the event types for this typeclass
|
||||
if typeclass_name not in script.ndb.event_types:
|
||||
script.ndb.event_types[typeclass_name] = {}
|
||||
types = script.ndb.event_types[typeclass_name]
|
||||
|
||||
# Add or replace the event
|
||||
help_text = dedent(help_text.strip("\n"))
|
||||
types[event_name] = (variables, help_text, custom_add, custom_call)
|
||||
del t_event_types[0]
|
||||
|
||||
# Custom callbacks for specific event types
|
||||
def get_next_wait(format):
|
||||
"""
|
||||
Get the length of time in seconds before format.
|
||||
|
||||
Args:
|
||||
format (str): a time format matching the set calendar.
|
||||
|
||||
The time format could be something like "2018-01-08 12:00". The
|
||||
number of units set in the calendar affects the way seconds are
|
||||
calculated.
|
||||
|
||||
Returns:
|
||||
until (int or float): the number of seconds until the event.
|
||||
usual (int or float): the usual number of seconds between events.
|
||||
format (str): a string format representing the time.
|
||||
|
||||
"""
|
||||
calendar = getattr(settings, "EVENTS_CALENDAR", None)
|
||||
if calendar is None:
|
||||
logger.log_err("A time-related event has been set whereas " \
|
||||
"the gametime calendar has not been set in the settings.")
|
||||
return
|
||||
elif calendar == "standard":
|
||||
rsu = standard_rsu
|
||||
units = ["min", "hour", "day", "month", "year"]
|
||||
elif calendar == "custom":
|
||||
rsu = custom_rsu
|
||||
back = dict([(value, name) for name, value in UNITS.items()])
|
||||
sorted_units = sorted(back.items())
|
||||
del sorted_units[0]
|
||||
units = [n for v, n in sorted_units]
|
||||
|
||||
params = {}
|
||||
for delimiter in ("-", ":"):
|
||||
format = format.replace(delimiter, " ")
|
||||
|
||||
pieces = list(reversed(format.split()))
|
||||
details = []
|
||||
i = 0
|
||||
for uname in units:
|
||||
try:
|
||||
piece = pieces[i]
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
if not piece.isdigit():
|
||||
logger.log_trace("The time specified '{}' in {} isn't " \
|
||||
"a valid number".format(piece, format))
|
||||
return
|
||||
|
||||
# Convert the piece to int
|
||||
piece = int(piece)
|
||||
params[uname] = piece
|
||||
details.append("{}={}".format(uname, piece))
|
||||
if i < len(units):
|
||||
next_unit = units[i + 1]
|
||||
else:
|
||||
next_unit = None
|
||||
i += 1
|
||||
|
||||
params["sec"] = 0
|
||||
details = " ".join(details)
|
||||
until = rsu(**params)
|
||||
usual = -1
|
||||
if next_unit:
|
||||
kwargs = {next_unit: 1}
|
||||
usual = gametime_to_realtime(**kwargs)
|
||||
return until, usual, details
|
||||
|
||||
def create_time_event(obj, event_name, number, parameters):
|
||||
"""
|
||||
Create a time-related event.
|
||||
|
||||
Args:
|
||||
obj (Object): the object on which stands the event.
|
||||
event_name (str): the event's name.
|
||||
number (int): the number of the event.
|
||||
parameters (str): the parameter of the event.
|
||||
|
||||
"""
|
||||
seconds, usual, key = get_next_wait(parameters)
|
||||
script = create_script("evennia.contrib.events.scripts.TimeEventScript", interval=seconds, obj=obj)
|
||||
script.key = key
|
||||
script.desc = "event on {}".format(key)
|
||||
script.db.time_format = parameters
|
||||
script.db.number = number
|
||||
script.ndb.usual = usual
|
||||
|
||||
def keyword_event(events, parameters):
|
||||
"""
|
||||
Custom call for events with keywords (like push, or pull, or turn...).
|
||||
|
||||
This function should be imported and added as a custom_call
|
||||
parameter to add the event type when the event supports keywords
|
||||
as parameters. Keywords in parameters are one or more words
|
||||
separated by a comma. For instance, a 'push 1, one' event can
|
||||
be set to trigger when the player 'push 1' or 'push one'.
|
||||
|
||||
Args:
|
||||
events (list of dict): the list of events to be called.
|
||||
parameters (str): the actual parameters entered to trigger the event.
|
||||
|
||||
Returns:
|
||||
A list containing the event dictionaries to be called.
|
||||
|
||||
"""
|
||||
key = parameters.strip().lower()
|
||||
to_call = []
|
||||
for event in events:
|
||||
keys = event["parameters"]
|
||||
if not keys or key in [p.strip().lower() for p in keys.split(",")]:
|
||||
to_call.append(event)
|
||||
|
||||
return to_call
|
||||
|
||||
def phrase_event(events, parameters):
|
||||
"""
|
||||
Custom call for events with keywords in sentences (like say or whisper).
|
||||
|
||||
This function should be imported and added as a custom_call
|
||||
parameter to add the event type when the event supports keywords
|
||||
in phrase as parameters. Keywords in parameters are one or more
|
||||
words separated by a comma. For instance, a 'say yes, okay' event
|
||||
can be set to trigger when the player says something containing
|
||||
either "yes" or "okay" (maybe 'say I don't like it, but okay').
|
||||
|
||||
Args:
|
||||
events (list of dict): the list of events to be called.
|
||||
parameters (str): the actual parameters entered to trigger the event.
|
||||
|
||||
Returns:
|
||||
A list containing the event dictionaries to be called.
|
||||
|
||||
"""
|
||||
phrase = parameters.strip().lower()
|
||||
# Remove punctuation marks
|
||||
punctuations = ',.";?!'
|
||||
for p in punctuations:
|
||||
phrase = phrase.replace(p, " ")
|
||||
words = phrase.split()
|
||||
words = [w.strip("' ") for w in words if w.strip("' ")]
|
||||
to_call = []
|
||||
for event in events:
|
||||
keys = event["parameters"]
|
||||
if not keys or any(key.strip().lower() in words for key in keys.split(",")):
|
||||
to_call.append(event)
|
||||
|
||||
return to_call
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Module defining basic helpers for the event system.
|
||||
Module defining basic eventfuncs for the event system.
|
||||
|
||||
Hlpers are just Python functions that can be used inside of events. They
|
||||
Eventfuncs are just Python functions that can be used inside of calllbacks.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -4,15 +4,15 @@ Module containing the EventHandler for individual objects.
|
|||
|
||||
from collections import namedtuple
|
||||
|
||||
class EventsHandler(object):
|
||||
class CallbackHandler(object):
|
||||
|
||||
"""
|
||||
The event handler for a specific object.
|
||||
|
||||
The script that contains all events will be reached through this
|
||||
handler. This handler is therefore a shortcut to be used by
|
||||
developers. This handler (accessible through `obj.events`) is a
|
||||
shortcut to manipulating events within this object, getting,
|
||||
developers. This handler (accessible through `obj.callbacks`) is a
|
||||
shortcut to manipulating callbacks within this object, getting,
|
||||
adding, editing, deleting and calling them.
|
||||
|
||||
"""
|
||||
|
|
@ -24,41 +24,45 @@ class EventsHandler(object):
|
|||
|
||||
def all(self):
|
||||
"""
|
||||
Return all events linked to this object.
|
||||
Return all callbacks linked to this object.
|
||||
|
||||
Returns:
|
||||
All events in a dictionary event_name: event}. The event
|
||||
is returned as a namedtuple to simply manipulation.
|
||||
All callbacks in a dictionary callback_name: callback}. The callback
|
||||
is returned as a namedtuple to simplify manipulation.
|
||||
|
||||
"""
|
||||
events = {}
|
||||
callbacks = {}
|
||||
handler = type(self).script
|
||||
if handler:
|
||||
dicts = handler.get_events(self.obj)
|
||||
for event_name, in_list in dicts.items():
|
||||
dicts = handler.get_callbacks(self.obj)
|
||||
for callback_name, in_list in dicts.items():
|
||||
new_list = []
|
||||
for event in in_list:
|
||||
event = self.format_event(event)
|
||||
new_list.append(event)
|
||||
for callback in in_list:
|
||||
callback = self.format_callback(callback)
|
||||
new_list.append(callback)
|
||||
|
||||
if new_list:
|
||||
events[event_name] = new_list
|
||||
callbacks[callback_name] = new_list
|
||||
|
||||
return events
|
||||
return callbacks
|
||||
|
||||
def get(self, event_name):
|
||||
def get(self, callback_name):
|
||||
"""
|
||||
Return the events associated with this name.
|
||||
Return the callbacks associated with this name.
|
||||
|
||||
Args:
|
||||
event_name (str): the name of the event.
|
||||
callback_name (str): the name of the callback.
|
||||
|
||||
This method returns a list of Event objects (namedtuple
|
||||
representations). If the event name cannot be found in the
|
||||
object's events, return an empty list.
|
||||
Returns:
|
||||
A list of callbacks associated with this object and of this name.
|
||||
|
||||
Note:
|
||||
This method returns a list of callback objects (namedtuple
|
||||
representations). If the callback name cannot be found in the
|
||||
object's callbacks, return an empty list.
|
||||
|
||||
"""
|
||||
return self.all().get(event_name, [])
|
||||
return self.all().get(callback_name, [])
|
||||
|
||||
def get_variable(self, variable_name):
|
||||
"""
|
||||
|
|
@ -77,124 +81,124 @@ class EventsHandler(object):
|
|||
|
||||
return None
|
||||
|
||||
def add(self, event_name, code, author=None, valid=False, parameters=""):
|
||||
def add(self, callback_name, code, author=None, valid=False, parameters=""):
|
||||
"""
|
||||
Add a new event for this object.
|
||||
Add a new callback for this object.
|
||||
|
||||
Args:
|
||||
event_name (str): the name of the event to add.
|
||||
code (str): the Python code associated with this event.
|
||||
author (Character or Player, optional): the author of the event.
|
||||
valid (bool, optional): should the event be connected?
|
||||
callback_name (str): the name of the callback to add.
|
||||
code (str): the Python code associated with this callback.
|
||||
author (Character or Player, optional): the author of the callback.
|
||||
valid (bool, optional): should the callback be connected?
|
||||
parameters (str, optional): optional parameters.
|
||||
|
||||
Returns:
|
||||
The event definition that was added or None.
|
||||
The callback definition that was added or None.
|
||||
|
||||
"""
|
||||
handler = type(self).script
|
||||
if handler:
|
||||
return self.format_event(handler.add_event(self.obj, event_name, code,
|
||||
return self.format_callback(handler.add_callback(self.obj, callback_name, code,
|
||||
author=author, valid=valid, parameters=parameters))
|
||||
|
||||
def edit(self, event_name, number, code, author=None, valid=False):
|
||||
def edit(self, callback_name, number, code, author=None, valid=False):
|
||||
"""
|
||||
Edit an existing event bound to this object.
|
||||
Edit an existing callback bound to this object.
|
||||
|
||||
Args:
|
||||
event_name (str): the name of the event to edit.
|
||||
number (int): the event number to be changed.
|
||||
code (str): the Python code associated with this event.
|
||||
author (Character or Player, optional): the author of the event.
|
||||
valid (bool, optional): should the event be connected?
|
||||
callback_name (str): the name of the callback to edit.
|
||||
number (int): the callback number to be changed.
|
||||
code (str): the Python code associated with this callback.
|
||||
author (Character or Player, optional): the author of the callback.
|
||||
valid (bool, optional): should the callback be connected?
|
||||
|
||||
Returns:
|
||||
The event definition that was edited or None.
|
||||
The callback definition that was edited or None.
|
||||
|
||||
Raises:
|
||||
RuntimeError if the event is locked.
|
||||
RuntimeError if the callback is locked.
|
||||
|
||||
"""
|
||||
handler = type(self).script
|
||||
if handler:
|
||||
return self.format_event(handler.edit_event(self.obj, event_name,
|
||||
return self.format_callback(handler.edit_callback(self.obj, callback_name,
|
||||
number, code, author=author, valid=valid))
|
||||
|
||||
def remove(self, event_name, number):
|
||||
def remove(self, callback_name, number):
|
||||
"""
|
||||
Delete the specified event bound to this object.
|
||||
Delete the specified callback bound to this object.
|
||||
|
||||
Args:
|
||||
event_name (str): the name of the event to delete.
|
||||
number (int): the number of the event to delete.
|
||||
callback_name (str): the name of the callback to delete.
|
||||
number (int): the number of the callback to delete.
|
||||
|
||||
Raises:
|
||||
RuntimeError if the event is locked.
|
||||
RuntimeError if the callback is locked.
|
||||
|
||||
"""
|
||||
handler = type(self).script
|
||||
if handler:
|
||||
handler.del_event(self.obj, event_name, number)
|
||||
handler.del_callback(self.obj, callback_name, number)
|
||||
|
||||
def call(self, event_name, *args, **kwargs):
|
||||
def call(self, callback_name, *args, **kwargs):
|
||||
"""
|
||||
Call the specified event(s) bound to this object.
|
||||
Call the specified callback(s) bound to this object.
|
||||
|
||||
Args:
|
||||
event_name (str): the event name to call.
|
||||
*args: additional variables for this event.
|
||||
callback_name (str): the callback name to call.
|
||||
*args: additional variables for this callback.
|
||||
|
||||
Kwargs:
|
||||
number (int, optional): call just a specific event.
|
||||
parameters (str, optional): call an event with parameters.
|
||||
number (int, optional): call just a specific callback.
|
||||
parameters (str, optional): call a callback with parameters.
|
||||
locals (dict, optional): a locals replacement.
|
||||
|
||||
Returns:
|
||||
True to report the event was called without interruption,
|
||||
False otherwise. If the EventHandler isn't found, return
|
||||
True to report the callback was called without interruption,
|
||||
False otherwise. If the callbackHandler isn't found, return
|
||||
None.
|
||||
|
||||
"""
|
||||
handler = type(self).script
|
||||
if handler:
|
||||
return handler.call_event(self.obj, event_name, *args, **kwargs)
|
||||
return handler.call(self.obj, callback_name, *args, **kwargs)
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def format_event(event):
|
||||
def format_callback(callback):
|
||||
"""
|
||||
Return the Event namedtuple to represent the specified event.
|
||||
Return the callback namedtuple to represent the specified callback.
|
||||
|
||||
Args:
|
||||
event (dict): the event definition.
|
||||
callback (dict): the callback definition.
|
||||
|
||||
The event given in argument should be a dictionary containing
|
||||
the expected fields for an event (code, author, valid...).
|
||||
The callback given in argument should be a dictionary containing
|
||||
the expected fields for a callback (code, author, valid...).
|
||||
|
||||
"""
|
||||
if "obj" not in event:
|
||||
event["obj"] = None
|
||||
if "name" not in event:
|
||||
event["name"] = "unknown"
|
||||
if "number" not in event:
|
||||
event["number"] = -1
|
||||
if "code" not in event:
|
||||
event["code"] = ""
|
||||
if "author" not in event:
|
||||
event["author"] = None
|
||||
if "valid" not in event:
|
||||
event["valid"] = False
|
||||
if "parameters" not in event:
|
||||
event["parameters"] = ""
|
||||
if "created_on" not in event:
|
||||
event["created_on"] = None
|
||||
if "updated_by" not in event:
|
||||
event["updated_by"] = None
|
||||
if "updated_on" not in event:
|
||||
event["updated_on"] = None
|
||||
if "obj" not in callback:
|
||||
callback["obj"] = None
|
||||
if "name" not in callback:
|
||||
callback["name"] = "unknown"
|
||||
if "number" not in callback:
|
||||
callback["number"] = -1
|
||||
if "code" not in callback:
|
||||
callback["code"] = ""
|
||||
if "author" not in callback:
|
||||
callback["author"] = None
|
||||
if "valid" not in callback:
|
||||
callback["valid"] = False
|
||||
if "parameters" not in callback:
|
||||
callback["parameters"] = ""
|
||||
if "created_on" not in callback:
|
||||
callback["created_on"] = None
|
||||
if "updated_by" not in callback:
|
||||
callback["updated_by"] = None
|
||||
if "updated_on" not in callback:
|
||||
callback["updated_on"] = None
|
||||
|
||||
return Event(**event)
|
||||
return Callback(**callback)
|
||||
|
||||
Event = namedtuple("Event", ("obj", "name", "number", "code", "author",
|
||||
Callback = namedtuple("Callback", ("obj", "name", "number", "code", "author",
|
||||
"valid", "parameters", "created_on", "updated_by", "updated_on"))
|
||||
|
|
|
|||
|
|
@ -14,10 +14,9 @@ from evennia import logger
|
|||
from evennia.utils.create import create_channel
|
||||
from evennia.utils.dbserialize import dbserialize
|
||||
from evennia.utils.utils import all_from_module, delay
|
||||
from evennia.contrib.events.custom import connect_event_types, get_next_wait
|
||||
from evennia.contrib.events.exceptions import InterruptEvent
|
||||
from evennia.contrib.events.handler import EventsHandler as Handler
|
||||
from evennia.contrib.events import typeclasses
|
||||
from evennia.contrib.events.handler import CallbackHandler
|
||||
from evennia.contrib.events.utils import get_next_wait, EVENTS
|
||||
|
||||
# Constants
|
||||
RE_LINE_ERROR = re.compile(r'^ File "\<string\>", line (\d+)')
|
||||
|
|
@ -28,7 +27,7 @@ class EventHandler(DefaultScript):
|
|||
The event handler that contains all events in a global script.
|
||||
|
||||
This script shouldn't be created more than once. It contains
|
||||
event types (in a non-persistent attribute) and events (in a
|
||||
event (in a non-persistent attribute) and callbacks (in a
|
||||
persistent attribute). The script method would help adding,
|
||||
editing and deleting these events.
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ class EventHandler(DefaultScript):
|
|||
self.persistent = True
|
||||
|
||||
# Permanent data to be stored
|
||||
self.db.events = {}
|
||||
self.db.callbacks = {}
|
||||
self.db.to_valid = []
|
||||
self.db.locked = []
|
||||
|
||||
|
|
@ -56,21 +55,22 @@ class EventHandler(DefaultScript):
|
|||
(including when it's reloaded). This hook performs the following
|
||||
tasks:
|
||||
|
||||
- Refresh and re-connect event types.
|
||||
- Create temporarily stored events.
|
||||
- Generate locals (individual events' namespace).
|
||||
- Load event helpers, including user-defined ones.
|
||||
- Load eventfuncs, including user-defined ones.
|
||||
- Re-schedule tasks that aren't set to fire anymore.
|
||||
- Effectively connect the handler to the main script.
|
||||
|
||||
"""
|
||||
self.ndb.event_types = {}
|
||||
connect_event_types()
|
||||
self.ndb.events = {}
|
||||
for typeclass, name, variables, help_text, custom_call, custom_add in EVENTS:
|
||||
self.add_event(typeclass, name, variables, help_text, custom_call, custom_add)
|
||||
|
||||
# Generate locals
|
||||
self.ndb.current_locals = {}
|
||||
self.ndb.fresh_locals = {}
|
||||
addresses = ["evennia.contrib.events.helpers"]
|
||||
addresses.extend(getattr(settings, "EVENTS_HELPERS_LOCATIONS", []))
|
||||
addresses = ["evennia.contrib.events.eventfuncs"]
|
||||
addresses.extend(getattr(settings, "EVENTFUNCS_LOCATIONS", []))
|
||||
for address in addresses:
|
||||
self.ndb.fresh_locals.update(all_from_module(address))
|
||||
|
||||
|
|
@ -84,9 +84,10 @@ class EventHandler(DefaultScript):
|
|||
|
||||
delay(seconds, complete_task, task_id)
|
||||
|
||||
# Place the script in the EventsHandler
|
||||
Handler.script = self
|
||||
DefaultObject.events = typeclasses.EventObject.events
|
||||
# Place the script in the CallbackHandler
|
||||
from evennia.contrib.events import typeclasses
|
||||
CallbackHandler.script = self
|
||||
DefaultObject.callbacks = typeclasses.EventObject.callbacks
|
||||
|
||||
# Create the channel if non-existent
|
||||
try:
|
||||
|
|
@ -97,73 +98,42 @@ class EventHandler(DefaultScript):
|
|||
|
||||
def get_events(self, obj):
|
||||
"""
|
||||
Return a dictionary of the object's events.
|
||||
|
||||
Args:
|
||||
obj (Object): the connected objects.
|
||||
|
||||
Returns:
|
||||
A dictionary of the object's events.
|
||||
|
||||
Note:
|
||||
This method can be useful to override in some contexts,
|
||||
when several objects would share events.
|
||||
|
||||
"""
|
||||
obj_events = self.db.events.get(obj, {})
|
||||
events = {}
|
||||
for event_name, event_list in obj_events.items():
|
||||
new_list = []
|
||||
for i, event in enumerate(event_list):
|
||||
event = dict(event)
|
||||
event["obj"] = obj
|
||||
event["name"] = event_name
|
||||
event["number"] = i
|
||||
new_list.append(event)
|
||||
|
||||
if new_list:
|
||||
events[event_name] = new_list
|
||||
|
||||
return events
|
||||
|
||||
def get_event_types(self, obj):
|
||||
"""
|
||||
Return a dictionary of event types on this object.
|
||||
Return a dictionary of events on this object.
|
||||
|
||||
Args:
|
||||
obj (Object): the connected object.
|
||||
|
||||
Returns:
|
||||
A dictionary of the object's event types.
|
||||
A dictionary of the object's events.
|
||||
|
||||
Note:
|
||||
Event types would define what the object can have as
|
||||
events. Note, however, that chained events will not
|
||||
appear in event types and are handled separately.
|
||||
Events would define what the object can have as
|
||||
callbacks. Note, however, that chained callbacks will not
|
||||
appear in events and are handled separately.
|
||||
|
||||
"""
|
||||
types = {}
|
||||
event_types = self.ndb.event_types
|
||||
events = {}
|
||||
all_events = self.ndb.events
|
||||
classes = Queue()
|
||||
classes.put(type(obj))
|
||||
invalid = []
|
||||
while not classes.empty():
|
||||
typeclass = classes.get()
|
||||
typeclass_name = typeclass.__module__ + "." + typeclass.__name__
|
||||
for key, etype in event_types.get(typeclass_name, {}).items():
|
||||
for key, etype in all_events.get(typeclass_name, {}).items():
|
||||
if key in invalid:
|
||||
continue
|
||||
if etype[0] is None: # Invalidate
|
||||
invalid.append(key)
|
||||
continue
|
||||
if key not in types:
|
||||
types[key] = etype
|
||||
if key not in events:
|
||||
events[key] = etype
|
||||
|
||||
# Look for the parent classes
|
||||
for parent in typeclass.__bases__:
|
||||
classes.put(parent)
|
||||
|
||||
return types
|
||||
return events
|
||||
|
||||
def get_variable(self, variable_name):
|
||||
"""
|
||||
|
|
@ -190,34 +160,66 @@ class EventHandler(DefaultScript):
|
|||
"""
|
||||
return self.ndb.current_locals.get(variable_name)
|
||||
|
||||
def add_event(self, obj, event_name, code, author=None, valid=False,
|
||||
def get_callbacks(self, obj):
|
||||
"""
|
||||
Return a dictionary of the object's callbacks.
|
||||
|
||||
Args:
|
||||
obj (Object): the connected objects.
|
||||
|
||||
Returns:
|
||||
A dictionary of the object's callbacks.
|
||||
|
||||
Note:
|
||||
This method can be useful to override in some contexts,
|
||||
when several objects would share callbacks.
|
||||
|
||||
"""
|
||||
obj_callbacks = self.db.callbacks.get(obj, {})
|
||||
callbacks = {}
|
||||
for callback_name, callback_list in obj_callbacks.items():
|
||||
new_list = []
|
||||
for i, callback in enumerate(callback_list):
|
||||
callback = dict(callback)
|
||||
callback["obj"] = obj
|
||||
callback["name"] = callback_name
|
||||
callback["number"] = i
|
||||
new_list.append(callback)
|
||||
|
||||
if new_list:
|
||||
callbacks[callback_name] = new_list
|
||||
|
||||
return callbacks
|
||||
|
||||
def add_callback(self, obj, callback_name, code, author=None, valid=False,
|
||||
parameters=""):
|
||||
"""
|
||||
Add the specified event.
|
||||
Add the specified callback.
|
||||
|
||||
Args:
|
||||
obj (Object): the Evennia typeclassed object to be extended.
|
||||
event_name (str): the name of the event to add.
|
||||
code (str): the Python code associated with this event.
|
||||
author (Character or Player, optional): the author of the event.
|
||||
valid (bool, optional): should the event be connected?
|
||||
callback_name (str): the name of the callback to add.
|
||||
code (str): the Python code associated with this callback.
|
||||
author (Character or Player, optional): the author of the callback.
|
||||
valid (bool, optional): should the callback be connected?
|
||||
parameters (str, optional): optional parameters.
|
||||
|
||||
This method doesn't check that the event type exists.
|
||||
Note:
|
||||
This method doesn't check that the callback type exists.
|
||||
|
||||
"""
|
||||
obj_events = self.db.events.get(obj, {})
|
||||
if not obj_events:
|
||||
self.db.events[obj] = {}
|
||||
obj_events = self.db.events[obj]
|
||||
obj_callbacks = self.db.callbacks.get(obj, {})
|
||||
if not obj_callbacks:
|
||||
self.db.callbacks[obj] = {}
|
||||
obj_callbacks = self.db.callbacks[obj]
|
||||
|
||||
events = obj_events.get(event_name, [])
|
||||
if not events:
|
||||
obj_events[event_name] = []
|
||||
events = obj_events[event_name]
|
||||
callbacks = obj_callbacks.get(callback_name, [])
|
||||
if not callbacks:
|
||||
obj_callbacks[callback_name] = []
|
||||
callbacks = obj_callbacks[callback_name]
|
||||
|
||||
# Add the event in the list
|
||||
events.append({
|
||||
# Add the callback in the list
|
||||
callbacks.append({
|
||||
"created_on": datetime.now(),
|
||||
"author": author,
|
||||
"valid": valid,
|
||||
|
|
@ -227,56 +229,57 @@ class EventHandler(DefaultScript):
|
|||
|
||||
# If not valid, set it in 'to_valid'
|
||||
if not valid:
|
||||
self.db.to_valid.append((obj, event_name, len(events) - 1))
|
||||
self.db.to_valid.append((obj, callback_name, len(callbacks) - 1))
|
||||
|
||||
# Call the custom_add if needed
|
||||
custom_add = self.get_event_types(obj).get(
|
||||
event_name, [None, None, None])[2]
|
||||
custom_add = self.get_events(obj).get(
|
||||
callback_name, [None, None, None, None])[3]
|
||||
if custom_add:
|
||||
custom_add(obj, event_name, len(events) - 1, parameters)
|
||||
custom_add(obj, callback_name, len(callbacks) - 1, parameters)
|
||||
|
||||
# Build the definition to return (a dictionary)
|
||||
definition = dict(events[-1])
|
||||
definition = dict(callbacks[-1])
|
||||
definition["obj"] = obj
|
||||
definition["name"] = event_name
|
||||
definition["number"] = len(events) - 1
|
||||
definition["name"] = callback_name
|
||||
definition["number"] = len(callbacks) - 1
|
||||
return definition
|
||||
|
||||
def edit_event(self, obj, event_name, number, code, author=None,
|
||||
def edit_callback(self, obj, callback_name, number, code, author=None,
|
||||
valid=False):
|
||||
"""
|
||||
Edit the specified event.
|
||||
Edit the specified callback.
|
||||
|
||||
Args:
|
||||
obj (Object): the Evennia typeclassed object to be edited.
|
||||
event_name (str): the name of the event to edit.
|
||||
number (int): the event number to be changed.
|
||||
code (str): the Python code associated with this event.
|
||||
author (Character or Player, optional): the author of the event.
|
||||
valid (bool, optional): should the event be connected?
|
||||
callback_name (str): the name of the callback to edit.
|
||||
number (int): the callback number to be changed.
|
||||
code (str): the Python code associated with this callback.
|
||||
author (Character or Player, optional): the author of the callback.
|
||||
valid (bool, optional): should the callback be connected?
|
||||
|
||||
Raises:
|
||||
RuntimeError if the event is locked.
|
||||
RuntimeError if the callback is locked.
|
||||
|
||||
This method doesn't check that the event type exists.
|
||||
Note:
|
||||
This method doesn't check that the callback type exists.
|
||||
|
||||
"""
|
||||
obj_events = self.db.events.get(obj, {})
|
||||
if not obj_events:
|
||||
self.db.events[obj] = {}
|
||||
obj_events = self.db.events[obj]
|
||||
obj_callbacks = self.db.callbacks.get(obj, {})
|
||||
if not obj_callbacks:
|
||||
self.db.callbacks[obj] = {}
|
||||
obj_callbacks = self.db.callbacks[obj]
|
||||
|
||||
events = obj_events.get(event_name, [])
|
||||
if not events:
|
||||
obj_events[event_name] = []
|
||||
events = obj_events[event_name]
|
||||
callbacks = obj_callbacks.get(callback_name, [])
|
||||
if not callbacks:
|
||||
obj_callbacks[callback_name] = []
|
||||
callbacks = obj_callbacks[callback_name]
|
||||
|
||||
# If locked, don't edit it
|
||||
if (obj, event_name, number) in self.db.locked:
|
||||
raise RuntimeError("this event is locked.")
|
||||
if (obj, callback_name, number) in self.db.locked:
|
||||
raise RuntimeError("this callback is locked.")
|
||||
|
||||
# Edit the event
|
||||
events[number].update({
|
||||
# Edit the callback
|
||||
callbacks[number].update({
|
||||
"updated_on": datetime.now(),
|
||||
"updated_by": author,
|
||||
"valid": valid,
|
||||
|
|
@ -284,118 +287,118 @@ class EventHandler(DefaultScript):
|
|||
})
|
||||
|
||||
# If not valid, set it in 'to_valid'
|
||||
if not valid and (obj, event_name, number) not in self.db.to_valid:
|
||||
self.db.to_valid.append((obj, event_name, number))
|
||||
elif valid and (obj, event_name, number) in self.db.to_valid:
|
||||
self.db.to_valid.remove((obj, event_name, number))
|
||||
if not valid and (obj, callback_name, number) not in self.db.to_valid:
|
||||
self.db.to_valid.append((obj, callback_name, number))
|
||||
elif valid and (obj, callback_name, number) in self.db.to_valid:
|
||||
self.db.to_valid.remove((obj, callback_name, number))
|
||||
|
||||
# Build the definition to return (a dictionary)
|
||||
definition = dict(events[number])
|
||||
definition = dict(callbacks[number])
|
||||
definition["obj"] = obj
|
||||
definition["name"] = event_name
|
||||
definition["name"] = callback_name
|
||||
definition["number"] = number
|
||||
return definition
|
||||
|
||||
def del_event(self, obj, event_name, number):
|
||||
def del_callback(self, obj, callback_name, number):
|
||||
"""
|
||||
Delete the specified event.
|
||||
Delete the specified callback.
|
||||
|
||||
Args:
|
||||
obj (Object): the typeclassed object containing the event.
|
||||
event_name (str): the name of the event to delete.
|
||||
number (int): the number of the event to delete.
|
||||
obj (Object): the typeclassed object containing the callback.
|
||||
callback_name (str): the name of the callback to delete.
|
||||
number (int): the number of the callback to delete.
|
||||
|
||||
Raises:
|
||||
RuntimeError if the event is locked.
|
||||
RuntimeError if the callback is locked.
|
||||
|
||||
"""
|
||||
obj_events = self.db.events.get(obj, {})
|
||||
events = obj_events.get(event_name, [])
|
||||
obj_callbacks = self.db.callbacks.get(obj, {})
|
||||
callbacks = obj_callbacks.get(callback_name, [])
|
||||
|
||||
# If locked, don't edit it
|
||||
if (obj, event_name, number) in self.db.locked:
|
||||
raise RuntimeError("this event is locked.")
|
||||
if (obj, callback_name, number) in self.db.locked:
|
||||
raise RuntimeError("this callback is locked.")
|
||||
|
||||
# Delete the event itself
|
||||
# Delete the callback itself
|
||||
try:
|
||||
code = events[number]["code"]
|
||||
code = callbacks[number]["code"]
|
||||
except IndexError:
|
||||
return
|
||||
else:
|
||||
logger.log_info("Deleting event {} {} of {}:\n{}".format(
|
||||
event_name, number, obj, code))
|
||||
del events[number]
|
||||
logger.log_info("Deleting callback {} {} of {}:\n{}".format(
|
||||
callback_name, number, obj, code))
|
||||
del callbacks[number]
|
||||
|
||||
# Change IDs of events to be validated
|
||||
# Change IDs of callbacks to be validated
|
||||
i = 0
|
||||
while i < len(self.db.to_valid):
|
||||
t_obj, t_event_name, t_number = self.db.to_valid[i]
|
||||
if obj is t_obj and event_name == t_event_name:
|
||||
t_obj, t_callback_name, t_number = self.db.to_valid[i]
|
||||
if obj is t_obj and callback_name == t_callback_name:
|
||||
if t_number == number:
|
||||
# Strictly equal, delete the event
|
||||
# Strictly equal, delete the callback
|
||||
del self.db.to_valid[i]
|
||||
i -= 1
|
||||
elif t_number > number:
|
||||
# Change the ID for this event
|
||||
self.db.to_valid.insert(i, (t_obj, t_event_name,
|
||||
# Change the ID for this callback
|
||||
self.db.to_valid.insert(i, (t_obj, t_callback_name,
|
||||
t_number - 1))
|
||||
del self.db.to_valid[i + 1]
|
||||
i += 1
|
||||
|
||||
# Update locked event
|
||||
# Update locked callback
|
||||
for i, line in enumerate(self.db.locked):
|
||||
t_obj, t_event_name, t_number = line
|
||||
if obj is t_obj and event_name == t_event_name:
|
||||
t_obj, t_callback_name, t_number = line
|
||||
if obj is t_obj and callback_name == t_callback_name:
|
||||
if number < t_number:
|
||||
self.db.locked[i] = (t_obj, t_event_name, t_number - 1)
|
||||
self.db.locked[i] = (t_obj, t_callback_name, t_number - 1)
|
||||
|
||||
# Delete time-related events associated with this object
|
||||
# Delete time-related callbacks associated with this object
|
||||
for script in list(obj.scripts.all()):
|
||||
if isinstance(script, TimeEventScript):
|
||||
if script.obj is obj and script.db.event_name == event_name:
|
||||
if isinstance(script, TimecallbackScript):
|
||||
if script.obj is obj and script.db.callback_name == callback_name:
|
||||
if script.db.number == number:
|
||||
script.stop()
|
||||
elif script.db.number > number:
|
||||
script.db.number -= 1
|
||||
|
||||
def accept_event(self, obj, event_name, number):
|
||||
def accept_callback(self, obj, callback_name, number):
|
||||
"""
|
||||
Valid an event.
|
||||
Valid a callback.
|
||||
|
||||
Args:
|
||||
obj (Object): the object containing the event.
|
||||
event_name (str): the name of the event.
|
||||
number (int): the number of the event.
|
||||
obj (Object): the object containing the callback.
|
||||
callback_name (str): the name of the callback.
|
||||
number (int): the number of the callback.
|
||||
|
||||
"""
|
||||
obj_events = self.db.events.get(obj, {})
|
||||
events = obj_events.get(event_name, [])
|
||||
obj_callbacks = self.db.callbacks.get(obj, {})
|
||||
callbacks = obj_callbacks.get(callback_name, [])
|
||||
|
||||
# Accept and connect the event
|
||||
events[number].update({"valid": True})
|
||||
if (obj, event_name, number) in self.db.to_valid:
|
||||
self.db.to_valid.remove((obj, event_name, number))
|
||||
# Accept and connect the callback
|
||||
callbacks[number].update({"valid": True})
|
||||
if (obj, callback_name, number) in self.db.to_valid:
|
||||
self.db.to_valid.remove((obj, callback_name, number))
|
||||
|
||||
def call_event(self, obj, event_name, *args, **kwargs):
|
||||
def call(self, obj, callback_name, *args, **kwargs):
|
||||
"""
|
||||
Call the event.
|
||||
Call the connected callbacks.
|
||||
|
||||
Args:
|
||||
obj (Object): the Evennia typeclassed object.
|
||||
event_name (str): the event name to call.
|
||||
*args: additional variables for this event.
|
||||
callback_name (str): the callback name to call.
|
||||
*args: additional variables for this callback.
|
||||
|
||||
Kwargs:
|
||||
number (int, optional): call just a specific event.
|
||||
parameters (str, optional): call an event with parameters.
|
||||
number (int, optional): call just a specific callback.
|
||||
parameters (str, optional): call a callback with parameters.
|
||||
locals (dict, optional): a locals replacement.
|
||||
|
||||
Returns:
|
||||
True to report the event was called without interruption,
|
||||
True to report the callback was called without interruption,
|
||||
False otherwise.
|
||||
|
||||
"""
|
||||
# First, look for the event type corresponding to this name
|
||||
# First, look for the callback type corresponding to this name
|
||||
number = kwargs.get("number")
|
||||
parameters = kwargs.get("parameters")
|
||||
locals = kwargs.get("locals")
|
||||
|
|
@ -404,54 +407,54 @@ class EventHandler(DefaultScript):
|
|||
allowed = ("number", "parameters", "locals")
|
||||
if any(k for k in kwargs if k not in allowed):
|
||||
raise TypeError("Unknown keyword arguments were specified " \
|
||||
"to call events: {}".format(kwargs))
|
||||
"to call callbacks: {}".format(kwargs))
|
||||
|
||||
event_type = self.get_event_types(obj).get(event_name)
|
||||
if locals is None and not event_type:
|
||||
logger.log_err("The event {} for the object {} (typeclass " \
|
||||
"{}) can't be found".format(event_name, obj, type(obj)))
|
||||
event = self.get_events(obj).get(callback_name)
|
||||
if locals is None and not event:
|
||||
logger.log_err("The callback {} for the object {} (typeclass " \
|
||||
"{}) can't be found".format(callback_name, obj, type(obj)))
|
||||
return False
|
||||
|
||||
# Prepare the locals if necessary
|
||||
if locals is None:
|
||||
locals = self.ndb.fresh_locals.copy()
|
||||
for i, variable in enumerate(event_type[0]):
|
||||
for i, variable in enumerate(event[0]):
|
||||
try:
|
||||
locals[variable] = args[i]
|
||||
except IndexError:
|
||||
logger.log_trace("event {} of {} ({}): need variable " \
|
||||
"{} in position {}".format(event_name, obj,
|
||||
logger.log_trace("callback {} of {} ({}): need variable " \
|
||||
"{} in position {}".format(callback_name, obj,
|
||||
type(obj), variable, i))
|
||||
return False
|
||||
else:
|
||||
locals = {key: value for key, value in locals.items()}
|
||||
|
||||
events = self.get_events(obj).get(event_name, [])
|
||||
if event_type:
|
||||
custom_call = event_type[3]
|
||||
callbacks = self.get_callbacks(obj).get(callback_name, [])
|
||||
if event:
|
||||
custom_call = event[2]
|
||||
if custom_call:
|
||||
events = custom_call(events, parameters)
|
||||
callbacks = custom_call(callbacks, parameters)
|
||||
|
||||
# Now execute all the valid events linked at this address
|
||||
# Now execute all the valid callbacks linked at this address
|
||||
self.ndb.current_locals = locals
|
||||
for i, event in enumerate(events):
|
||||
if not event["valid"]:
|
||||
for i, callback in enumerate(callbacks):
|
||||
if not callback["valid"]:
|
||||
continue
|
||||
|
||||
if number is not None and event["number"] != number:
|
||||
if number is not None and callback["number"] != number:
|
||||
continue
|
||||
|
||||
try:
|
||||
exec(event["code"], locals, locals)
|
||||
exec(callback["code"], locals, locals)
|
||||
except InterruptEvent:
|
||||
return False
|
||||
except Exception:
|
||||
etype, evalue, tb = sys.exc_info()
|
||||
trace = traceback.format_exception(etype, evalue, tb)
|
||||
number = event["number"]
|
||||
number = callback["number"]
|
||||
oid = obj.id
|
||||
logger.log_err("An error occurred during the event {} of " \
|
||||
"{} (#{}), number {}\n{}".format(event_name, obj,
|
||||
logger.log_err("An error occurred during the callback {} of " \
|
||||
"{} (#{}), number {}\n{}".format(callback_name, obj,
|
||||
oid, number + 1, "\n".join(trace)))
|
||||
|
||||
# Inform the 'everror' channel
|
||||
|
|
@ -465,33 +468,54 @@ class EventHandler(DefaultScript):
|
|||
|
||||
# Try to extract the line
|
||||
try:
|
||||
line = event["code"].splitlines()[lineno - 1]
|
||||
line = callback["code"].splitlines()[lineno - 1]
|
||||
except IndexError:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
self.ndb.channel.msg("Error in {} of {} (#{})[{}], line {}:" \
|
||||
" {}\n {}".format(event_name, obj,
|
||||
" {}\n {}".format(callback_name, obj,
|
||||
oid, number + 1, lineno, line, repr(evalue)))
|
||||
|
||||
return True
|
||||
|
||||
def set_task(self, seconds, obj, event_name):
|
||||
def add_event(self, typeclass, name, variables, help_text, custom_call, custom_add):
|
||||
"""
|
||||
Add a new event for a defined typeclass.
|
||||
|
||||
Args:
|
||||
typeclass (str): the path leading to the typeclass.
|
||||
name (str): the name of the event to add.
|
||||
variables (list of str): list of variable names for this event.
|
||||
help_text (str): the long help text of the event.
|
||||
custom_call (callable or None): the function to be called
|
||||
when the event fires.
|
||||
custom_add (callable or None): the function to be called when
|
||||
a callback is added.
|
||||
|
||||
"""
|
||||
if typeclass not in self.ndb.events:
|
||||
self.ndb.events[typeclass] = {}
|
||||
|
||||
events = self.ndb.events[typeclass]
|
||||
if name not in events:
|
||||
events[name] = (variables, help_text, custom_call, custom_add)
|
||||
|
||||
def set_task(self, seconds, obj, callback_name):
|
||||
"""
|
||||
Set and schedule a task to run.
|
||||
|
||||
This method allows to schedule a "persistent" task.
|
||||
'utils.delay' is called, but a copy of the task is kept in
|
||||
the event handler, and when the script restarts (after reload),
|
||||
the differed delay is called again.
|
||||
|
||||
Args:
|
||||
seconds (int, float): the delay in seconds from now.
|
||||
obj (Object): the typecalssed object connected to the event.
|
||||
event_name (str): the event's name.
|
||||
callback_name (str): the callback's name.
|
||||
|
||||
Note:
|
||||
Notes:
|
||||
This method allows to schedule a "persistent" task.
|
||||
'utils.delay' is called, but a copy of the task is kept in
|
||||
the event handler, and when the script restarts (after reload),
|
||||
the differed delay is called again.
|
||||
The dictionary of locals is frozen and will be available
|
||||
again when the task runs. This feature, however, is limited
|
||||
by the database: all data cannot be saved. Lambda functions,
|
||||
|
|
@ -514,7 +538,7 @@ class EventHandler(DefaultScript):
|
|||
else:
|
||||
locals[key] = value
|
||||
|
||||
self.db.tasks[task_id] = (now + delta, obj, event_name, locals)
|
||||
self.db.tasks[task_id] = (now + delta, obj, callback_name, locals)
|
||||
delay(seconds, complete_task, task_id)
|
||||
|
||||
|
||||
|
|
@ -572,11 +596,12 @@ def complete_task(task_id):
|
|||
"""
|
||||
Mark the task in the event handler as complete.
|
||||
|
||||
This function should be called automatically for individual tasks.
|
||||
|
||||
Args:
|
||||
task_id (int): the task ID.
|
||||
|
||||
Note:
|
||||
This function should be called automatically for individual tasks.
|
||||
|
||||
"""
|
||||
try:
|
||||
script = ScriptDB.objects.get(db_key="event_handler")
|
||||
|
|
@ -589,5 +614,5 @@ def complete_task(task_id):
|
|||
"found".format(task_id))
|
||||
return
|
||||
|
||||
delta, obj, event_name, locals = script.db.tasks.pop(task_id)
|
||||
script.call_event(obj, event_name, locals=locals)
|
||||
delta, obj, callback_name, locals = script.db.tasks.pop(task_id)
|
||||
script.call(obj, callback_name, locals=locals)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ from evennia.objects.objects import ExitCommand
|
|||
from evennia.utils import ansi, utils
|
||||
from evennia.utils.create import create_object, create_script
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.contrib.events.commands import CmdEvent
|
||||
from evennia.contrib.events.handler import EventsHandler
|
||||
from evennia.contrib.events.commands import CmdCallback
|
||||
from evennia.contrib.events.handler import CallbackHandler
|
||||
|
||||
# Force settings
|
||||
settings.EVENTS_CALENDAR = "standard"
|
||||
|
|
@ -40,125 +40,101 @@ class TestEventHandler(EvenniaTest):
|
|||
def tearDown(self):
|
||||
"""Stop the event handler."""
|
||||
self.handler.stop()
|
||||
EventsHandler.script = None
|
||||
CallbackHandler.script = None
|
||||
super(TestEventHandler, self).tearDown()
|
||||
|
||||
def test_start(self):
|
||||
"""Simply make sure the handler runs with proper initial values."""
|
||||
self.assertEqual(self.handler.db.events, {})
|
||||
self.assertEqual(self.handler.db.callbacks, {})
|
||||
self.assertEqual(self.handler.db.to_valid, [])
|
||||
self.assertEqual(self.handler.db.locked, [])
|
||||
self.assertEqual(self.handler.db.tasks, {})
|
||||
self.assertEqual(self.handler.db.task_id, 0)
|
||||
self.assertIsNotNone(self.handler.ndb.event_types)
|
||||
|
||||
def test_add(self):
|
||||
"""Add a single event on room1."""
|
||||
author = self.char1
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
"character.db.strength = 50", author=author, valid=True)
|
||||
event = self.handler.get_events(self.room1).get("dummy")
|
||||
event = event[0]
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event["obj"], self.room1)
|
||||
self.assertEqual(event["name"], "dummy")
|
||||
self.assertEqual(event["number"], 0)
|
||||
self.assertEqual(event["author"], author)
|
||||
self.assertEqual(event["valid"], True)
|
||||
|
||||
# Since this event is valid, it shouldn't appear in 'to_valid'
|
||||
self.assertNotIn((self.room1, "dummy", 0), self.handler.db.to_valid)
|
||||
|
||||
# Run this dummy event
|
||||
self.char1.db.strength = 10
|
||||
locals = {"character": self.char1}
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.room1, "dummy", locals=locals))
|
||||
self.assertEqual(self.char1.db.strength, 50)
|
||||
self.assertIsNotNone(self.handler.ndb.events)
|
||||
|
||||
def test_add_validation(self):
|
||||
"""Add an event while needing validation."""
|
||||
"""Add a callback while needing validation."""
|
||||
author = self.char1
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 40", author=author, valid=False)
|
||||
event = self.handler.get_events(self.room1).get("dummy")
|
||||
event = event[0]
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event["author"], author)
|
||||
self.assertEqual(event["valid"], False)
|
||||
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
||||
callback = callback[0]
|
||||
self.assertIsNotNone(callback)
|
||||
self.assertEqual(callback["author"], author)
|
||||
self.assertEqual(callback["valid"], False)
|
||||
|
||||
# Since this event is not valid, it should appear in 'to_valid'
|
||||
# Since this callback is not valid, it should appear in 'to_valid'
|
||||
self.assertIn((self.room1, "dummy", 0), self.handler.db.to_valid)
|
||||
|
||||
# Run this dummy event (shouldn't do anything)
|
||||
# Run this dummy callback (shouldn't do anything)
|
||||
self.char1.db.strength = 10
|
||||
locals = {"character": self.char1}
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals=locals))
|
||||
self.assertEqual(self.char1.db.strength, 10)
|
||||
|
||||
def test_edit(self):
|
||||
"""Test editing an event."""
|
||||
"""Test editing a callback."""
|
||||
author = self.char1
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 60", author=author, valid=True)
|
||||
|
||||
# Edit it right away
|
||||
self.handler.edit_event(self.room1, "dummy", 0,
|
||||
self.handler.edit_callback(self.room1, "dummy", 0,
|
||||
"character.db.strength = 65", author=self.char2, valid=True)
|
||||
|
||||
# Check that the event was written
|
||||
event = self.handler.get_events(self.room1).get("dummy")
|
||||
event = event[0]
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event["author"], author)
|
||||
self.assertEqual(event["valid"], True)
|
||||
self.assertEqual(event["updated_by"], self.char2)
|
||||
# Check that the callback was written
|
||||
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
||||
callback = callback[0]
|
||||
self.assertIsNotNone(callback)
|
||||
self.assertEqual(callback["author"], author)
|
||||
self.assertEqual(callback["valid"], True)
|
||||
self.assertEqual(callback["updated_by"], self.char2)
|
||||
|
||||
# Run this dummy event
|
||||
# Run this dummy callback
|
||||
self.char1.db.strength = 10
|
||||
locals = {"character": self.char1}
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals=locals))
|
||||
self.assertEqual(self.char1.db.strength, 65)
|
||||
|
||||
def test_edit_validation(self):
|
||||
"""Edit an event when validation isn't automatic."""
|
||||
"""Edit a callback when validation isn't automatic."""
|
||||
author = self.char1
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 70", author=author, valid=True)
|
||||
|
||||
# Edit it right away
|
||||
self.handler.edit_event(self.room1, "dummy", 0,
|
||||
self.handler.edit_callback(self.room1, "dummy", 0,
|
||||
"character.db.strength = 80", author=self.char2, valid=False)
|
||||
|
||||
# Run this dummy event (shouldn't do anything)
|
||||
# Run this dummy callback (shouldn't do anything)
|
||||
self.char1.db.strength = 10
|
||||
locals = {"character": self.char1}
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals=locals))
|
||||
self.assertEqual(self.char1.db.strength, 10)
|
||||
|
||||
def test_del(self):
|
||||
"""Try to delete an event."""
|
||||
# Add 3 events
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
"""Try to delete a callback."""
|
||||
# Add 3 callbacks
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 5", author=self.char1, valid=True)
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 8", author=self.char2, valid=False)
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 9", author=self.char1, valid=True)
|
||||
|
||||
# Note that the second event isn't valid
|
||||
# Note that the second callback isn't valid
|
||||
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
||||
|
||||
# Lock the third event
|
||||
# Lock the third callback
|
||||
self.handler.db.locked.append((self.room1, "dummy", 2))
|
||||
|
||||
# Delete the first event
|
||||
self.handler.del_event(self.room1, "dummy", 0)
|
||||
# Delete the first callback
|
||||
self.handler.del_callback(self.room1, "dummy", 0)
|
||||
|
||||
# The event #1 that was to valid should be #0 now
|
||||
# The callback #1 that was to valid should be #0 now
|
||||
self.assertIn((self.room1, "dummy", 0), self.handler.db.to_valid)
|
||||
self.assertNotIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
||||
|
||||
|
|
@ -166,104 +142,104 @@ class TestEventHandler(EvenniaTest):
|
|||
self.assertIn((self.room1, "dummy", 1), self.handler.db.locked)
|
||||
self.assertNotIn((self.room1, "dummy", 2), self.handler.db.locked)
|
||||
|
||||
# Now delete the first (not valid) event
|
||||
self.handler.del_event(self.room1, "dummy", 0)
|
||||
# Now delete the first (not valid) callback
|
||||
self.handler.del_callback(self.room1, "dummy", 0)
|
||||
self.assertEqual(self.handler.db.to_valid, [])
|
||||
self.assertIn((self.room1, "dummy", 0), self.handler.db.locked)
|
||||
self.assertNotIn((self.room1, "dummy", 1), self.handler.db.locked)
|
||||
|
||||
# Call the remaining event
|
||||
# Call the remaining callback
|
||||
self.char1.db.strength = 10
|
||||
locals = {"character": self.char1}
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals=locals))
|
||||
self.assertEqual(self.char1.db.strength, 9)
|
||||
|
||||
def test_accept(self):
|
||||
"""Accept an event."""
|
||||
# Add 2 events
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
"""Accept an callback."""
|
||||
# Add 2 callbacks
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 5", author=self.char1, valid=True)
|
||||
self.handler.add_event(self.room1, "dummy",
|
||||
self.handler.add_callback(self.room1, "dummy",
|
||||
"character.db.strength = 8", author=self.char2, valid=False)
|
||||
|
||||
# Note that the second event isn't valid
|
||||
# Note that the second callback isn't valid
|
||||
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
||||
|
||||
# Accept the second event
|
||||
self.handler.accept_event(self.room1, "dummy", 1)
|
||||
event = self.handler.get_events(self.room1).get("dummy")
|
||||
event = event[1]
|
||||
self.assertIsNotNone(event)
|
||||
self.assertEqual(event["valid"], True)
|
||||
# Accept the second callback
|
||||
self.handler.accept_callback(self.room1, "dummy", 1)
|
||||
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
||||
callback = callback[1]
|
||||
self.assertIsNotNone(callback)
|
||||
self.assertEqual(callback["valid"], True)
|
||||
|
||||
# Call the dummy event
|
||||
# Call the dummy callback
|
||||
self.char1.db.strength = 10
|
||||
locals = {"character": self.char1}
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals=locals))
|
||||
self.assertEqual(self.char1.db.strength, 8)
|
||||
|
||||
def test_call(self):
|
||||
"""Test to call amore complex event."""
|
||||
"""Test to call amore complex callback."""
|
||||
self.char1.key = "one"
|
||||
self.char2.key = "two"
|
||||
|
||||
# Add an event
|
||||
# Add an callback
|
||||
code = dedent("""
|
||||
if character.key == "one":
|
||||
character.db.health = 50
|
||||
else:
|
||||
character.db.health = 0
|
||||
""".strip("\n"))
|
||||
self.handler.add_event(self.room1, "dummy", code,
|
||||
self.handler.add_callback(self.room1, "dummy", code,
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Call the dummy event
|
||||
self.assertTrue(self.handler.call_event(
|
||||
# Call the dummy callback
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals={"character": self.char1}))
|
||||
self.assertEqual(self.char1.db.health, 50)
|
||||
self.assertTrue(self.handler.call_event(
|
||||
self.assertTrue(self.handler.call(
|
||||
self.room1, "dummy", locals={"character": self.char2}))
|
||||
self.assertEqual(self.char2.db.health, 0)
|
||||
|
||||
def test_handler(self):
|
||||
"""Test the object handler."""
|
||||
self.assertIsNotNone(self.char1.events)
|
||||
self.assertIsNotNone(self.char1.callbacks)
|
||||
|
||||
# Add an event
|
||||
event = self.room1.events.add("dummy", "pass", author=self.char1,
|
||||
# Add an callback
|
||||
callback = self.room1.callbacks.add("dummy", "pass", author=self.char1,
|
||||
valid=True)
|
||||
self.assertEqual(event.obj, self.room1)
|
||||
self.assertEqual(event.name, "dummy")
|
||||
self.assertEqual(event.code, "pass")
|
||||
self.assertEqual(event.author, self.char1)
|
||||
self.assertEqual(event.valid, True)
|
||||
self.assertIn([event], self.room1.events.all().values())
|
||||
self.assertEqual(callback.obj, self.room1)
|
||||
self.assertEqual(callback.name, "dummy")
|
||||
self.assertEqual(callback.code, "pass")
|
||||
self.assertEqual(callback.author, self.char1)
|
||||
self.assertEqual(callback.valid, True)
|
||||
self.assertIn([callback], self.room1.callbacks.all().values())
|
||||
|
||||
# Edit this very event
|
||||
new = self.room1.events.edit("dummy", 0, "character.db.say = True",
|
||||
# Edit this very callback
|
||||
new = self.room1.callbacks.edit("dummy", 0, "character.db.say = True",
|
||||
author=self.char1, valid=True)
|
||||
self.assertIn([new], self.room1.events.all().values())
|
||||
self.assertNotIn([event], self.room1.events.all().values())
|
||||
self.assertIn([new], self.room1.callbacks.all().values())
|
||||
self.assertNotIn([callback], self.room1.callbacks.all().values())
|
||||
|
||||
# Try to call this event
|
||||
self.assertTrue(self.room1.events.call("dummy",
|
||||
# Try to call this callback
|
||||
self.assertTrue(self.room1.callbacks.call("dummy",
|
||||
locals={"character": self.char2}))
|
||||
self.assertTrue(self.char2.db.say)
|
||||
|
||||
# Delete the event
|
||||
self.room1.events.remove("dummy", 0)
|
||||
self.assertEqual(self.room1.events.all(), {})
|
||||
# Delete the callback
|
||||
self.room1.callbacks.remove("dummy", 0)
|
||||
self.assertEqual(self.room1.callbacks.all(), {})
|
||||
|
||||
|
||||
class TestCmdEvent(CommandTest):
|
||||
class TestCmdCallback(CommandTest):
|
||||
|
||||
"""Test the @event command."""
|
||||
"""Test the @callback command."""
|
||||
|
||||
def setUp(self):
|
||||
"""Create the event handler."""
|
||||
super(TestCmdEvent, self).setUp()
|
||||
"""Create the callback handler."""
|
||||
super(TestCmdCallback, self).setUp()
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.events.scripts.EventHandler")
|
||||
|
||||
|
|
@ -275,32 +251,32 @@ class TestCmdEvent(CommandTest):
|
|||
self.exit.swap_typeclass("evennia.contrib.events.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop the event handler."""
|
||||
"""Stop the callback handler."""
|
||||
self.handler.stop()
|
||||
for script in ScriptDB.objects.filter(
|
||||
db_typeclass_path="evennia.contrib.events.scripts.TimeEventScript"):
|
||||
script.stop()
|
||||
|
||||
EventsHandler.script = None
|
||||
super(TestCmdEvent, self).tearDown()
|
||||
CallbackHandler.script = None
|
||||
super(TestCmdCallback, self).tearDown()
|
||||
|
||||
def test_list(self):
|
||||
"""Test listing events with different rights."""
|
||||
table = self.call(CmdEvent(), "out")
|
||||
"""Test listing callbacks with different rights."""
|
||||
table = self.call(CmdCallback(), "out")
|
||||
lines = table.splitlines()[3:-1]
|
||||
self.assertNotEqual(lines, [])
|
||||
|
||||
# Check that the second column only contains 0 (0) (no event yet)
|
||||
# Check that the second column only contains 0 (0) (no callback yet)
|
||||
for line in lines:
|
||||
cols = line.split("|")
|
||||
self.assertIn(cols[2].strip(), ("0 (0)", ""))
|
||||
|
||||
# Add some event
|
||||
self.handler.add_event(self.exit, "traverse", "pass",
|
||||
# Add some callback
|
||||
self.handler.add_callback(self.exit, "traverse", "pass",
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Try to obtain more details on a specific event on exit
|
||||
table = self.call(CmdEvent(), "out = traverse")
|
||||
# Try to obtain more details on a specific callback on exit
|
||||
table = self.call(CmdCallback(), "out = traverse")
|
||||
lines = table.splitlines()[3:-1]
|
||||
self.assertEqual(len(lines), 1)
|
||||
line = lines[0]
|
||||
|
|
@ -311,7 +287,7 @@ class TestCmdEvent(CommandTest):
|
|||
|
||||
# Run the same command with char2
|
||||
# char2 shouldn't see the last column (Valid)
|
||||
table = self.call(CmdEvent(), "out = traverse", caller=self.char2)
|
||||
table = self.call(CmdCallback(), "out = traverse", caller=self.char2)
|
||||
lines = table.splitlines()[3:-1]
|
||||
self.assertEqual(len(lines), 1)
|
||||
line = lines[0]
|
||||
|
|
@ -319,18 +295,18 @@ class TestCmdEvent(CommandTest):
|
|||
self.assertEqual(cols[1].strip(), "1")
|
||||
self.assertNotIn(cols[-1].strip(), ("Yes", "No"))
|
||||
|
||||
# In any case, display the event
|
||||
# The last line should be "pass" (the event code)
|
||||
details = self.call(CmdEvent(), "out = traverse 1")
|
||||
# In any case, display the callback
|
||||
# The last line should be "pass" (the callback code)
|
||||
details = self.call(CmdCallback(), "out = traverse 1")
|
||||
self.assertEqual(details.splitlines()[-1], "pass")
|
||||
|
||||
def test_add(self):
|
||||
"""Test to add an event."""
|
||||
self.call(CmdEvent(), "/add out = traverse")
|
||||
"""Test to add an callback."""
|
||||
self.call(CmdCallback(), "/add out = traverse")
|
||||
editor = self.char1.ndb._eveditor
|
||||
self.assertIsNotNone(editor)
|
||||
|
||||
# Edit the event
|
||||
# Edit the callback
|
||||
editor.update_buffer(dedent("""
|
||||
if character.key == "one":
|
||||
character.msg("You can pass.")
|
||||
|
|
@ -340,89 +316,89 @@ class TestCmdEvent(CommandTest):
|
|||
""".strip("\n")))
|
||||
editor.save_buffer()
|
||||
editor.quit()
|
||||
event = self.exit.events.get("traverse")[0]
|
||||
self.assertEqual(event.author, self.char1)
|
||||
self.assertEqual(event.valid, True)
|
||||
self.assertTrue(len(event.code) > 0)
|
||||
callback = self.exit.callbacks.get("traverse")[0]
|
||||
self.assertEqual(callback.author, self.char1)
|
||||
self.assertEqual(callback.valid, True)
|
||||
self.assertTrue(len(callback.code) > 0)
|
||||
|
||||
# We're going to try the same thing but with char2
|
||||
# char2 being a player for our test, the event won't be validated.
|
||||
er = self.call(CmdEvent(), "/add out = traverse", caller=self.char2)
|
||||
# char2 being a player for our test, the callback won't be validated.
|
||||
self.call(CmdCallback(), "/add out = traverse", caller=self.char2)
|
||||
editor = self.char2.ndb._eveditor
|
||||
self.assertIsNotNone(editor)
|
||||
|
||||
# Edit the event
|
||||
# Edit the callback
|
||||
editor.update_buffer(dedent("""
|
||||
character.msg("No way.")
|
||||
""".strip("\n")))
|
||||
editor.save_buffer()
|
||||
editor.quit()
|
||||
event = self.exit.events.get("traverse")[1]
|
||||
self.assertEqual(event.author, self.char2)
|
||||
self.assertEqual(event.valid, False)
|
||||
self.assertTrue(len(event.code) > 0)
|
||||
callback = self.exit.callbacks.get("traverse")[1]
|
||||
self.assertEqual(callback.author, self.char2)
|
||||
self.assertEqual(callback.valid, False)
|
||||
self.assertTrue(len(callback.code) > 0)
|
||||
|
||||
def test_del(self):
|
||||
"""Add and remove an event."""
|
||||
self.handler.add_event(self.exit, "traverse", "pass",
|
||||
"""Add and remove an callback."""
|
||||
self.handler.add_callback(self.exit, "traverse", "pass",
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Try to delete the event
|
||||
# char2 shouldn't be allowed to do so (that's not HIS event)
|
||||
self.call(CmdEvent(), "/del out = traverse 1", caller=self.char2)
|
||||
self.assertTrue(len(self.handler.get_events(self.exit).get(
|
||||
# Try to delete the callback
|
||||
# char2 shouldn't be allowed to do so (that's not HIS callback)
|
||||
self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2)
|
||||
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
||||
"traverse", [])) == 1)
|
||||
|
||||
# Now, char1 should be allowed to delete it
|
||||
self.call(CmdEvent(), "/del out = traverse 1")
|
||||
self.assertTrue(len(self.handler.get_events(self.exit).get(
|
||||
self.call(CmdCallback(), "/del out = traverse 1")
|
||||
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
||||
"traverse", [])) == 0)
|
||||
|
||||
def test_lock(self):
|
||||
"""Test the lock of multiple editing."""
|
||||
self.call(CmdEvent(), "/add here = time 8:00", caller=self.char2)
|
||||
self.call(CmdCallback(), "/add here = time 8:00", caller=self.char2)
|
||||
self.assertIsNotNone(self.char2.ndb._eveditor)
|
||||
|
||||
# Now ask char1 to edit
|
||||
line = self.call(CmdEvent(), "/edit here = time 1")
|
||||
line = self.call(CmdCallback(), "/edit here = time 1")
|
||||
self.assertIsNone(self.char1.ndb._eveditor)
|
||||
|
||||
# Try to delete this event while char2 is editing it
|
||||
line = self.call(CmdEvent(), "/del here = time 1")
|
||||
# Try to delete this callback while char2 is editing it
|
||||
line = self.call(CmdCallback(), "/del here = time 1")
|
||||
|
||||
def test_accept(self):
|
||||
"""Accept an event."""
|
||||
self.call(CmdEvent(), "/add here = time 8:00", caller=self.char2)
|
||||
"""Accept an callback."""
|
||||
self.call(CmdCallback(), "/add here = time 8:00", caller=self.char2)
|
||||
editor = self.char2.ndb._eveditor
|
||||
self.assertIsNotNone(editor)
|
||||
|
||||
# Edit the event
|
||||
# Edit the callback
|
||||
editor.update_buffer(dedent("""
|
||||
room.msg_contents("It's 8 PM, everybody up!")
|
||||
""".strip("\n")))
|
||||
editor.save_buffer()
|
||||
editor.quit()
|
||||
event = self.room1.events.get("time")[0]
|
||||
self.assertEqual(event.valid, False)
|
||||
callback = self.room1.callbacks.get("time")[0]
|
||||
self.assertEqual(callback.valid, False)
|
||||
|
||||
# chars shouldn't be allowed to the event
|
||||
self.call(CmdEvent(), "/accept here = time 1", caller=self.char2)
|
||||
event = self.room1.events.get("time")[0]
|
||||
self.assertEqual(event.valid, False)
|
||||
# chars shouldn't be allowed to the callback
|
||||
self.call(CmdCallback(), "/accept here = time 1", caller=self.char2)
|
||||
callback = self.room1.callbacks.get("time")[0]
|
||||
self.assertEqual(callback.valid, False)
|
||||
|
||||
# char1 will accept the event
|
||||
self.call(CmdEvent(), "/accept here = time 1")
|
||||
event = self.room1.events.get("time")[0]
|
||||
self.assertEqual(event.valid, True)
|
||||
# char1 will accept the callback
|
||||
self.call(CmdCallback(), "/accept here = time 1")
|
||||
callback = self.room1.callbacks.get("time")[0]
|
||||
self.assertEqual(callback.valid, True)
|
||||
|
||||
|
||||
class TestDefaultEvents(CommandTest):
|
||||
class TestDefaultCallbacks(CommandTest):
|
||||
|
||||
"""Test the default events."""
|
||||
"""Test the default callbacks."""
|
||||
|
||||
def setUp(self):
|
||||
"""Create the event handler."""
|
||||
super(TestDefaultEvents, self).setUp()
|
||||
"""Create the callback handler."""
|
||||
super(TestDefaultCallbacks, self).setUp()
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.events.scripts.EventHandler")
|
||||
|
||||
|
|
@ -434,13 +410,13 @@ class TestDefaultEvents(CommandTest):
|
|||
self.exit.swap_typeclass("evennia.contrib.events.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop the event handler."""
|
||||
"""Stop the callback handler."""
|
||||
self.handler.stop()
|
||||
EventsHandler.script = None
|
||||
super(TestDefaultEvents, self).tearDown()
|
||||
CallbackHandler.script = None
|
||||
super(TestDefaultCallbacks, self).tearDown()
|
||||
|
||||
def test_exit(self):
|
||||
"""Test the events of an exit."""
|
||||
"""Test the callbacks of an exit."""
|
||||
self.char1.key = "char1"
|
||||
code = dedent("""
|
||||
if character.key == "char1":
|
||||
|
|
@ -452,8 +428,8 @@ class TestDefaultEvents(CommandTest):
|
|||
# Enforce self.exit.destination since swapping typeclass lose it
|
||||
self.exit.destination = self.room2
|
||||
|
||||
# Try the can_traverse event
|
||||
self.handler.add_event(self.exit, "can_traverse", code,
|
||||
# Try the can_traverse callback
|
||||
self.handler.add_callback(self.exit, "can_traverse", code,
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Have char1 move through the exit
|
||||
|
|
@ -465,15 +441,15 @@ class TestDefaultEvents(CommandTest):
|
|||
caller=self.char2)
|
||||
self.assertIs(self.char2.location, self.room1)
|
||||
|
||||
# Try the traverse event
|
||||
self.handler.del_event(self.exit, "can_traverse", 0)
|
||||
self.handler.add_event(self.exit, "traverse", "character.msg('Fine!')",
|
||||
# Try the traverse callback
|
||||
self.handler.del_callback(self.exit, "can_traverse", 0)
|
||||
self.handler.add_callback(self.exit, "traverse", "character.msg('Fine!')",
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Have char2 move through the exit
|
||||
self.call(ExitCommand(), "", obj=self.exit, caller=self.char2)
|
||||
self.assertIs(self.char2.location, self.room2)
|
||||
self.handler.del_event(self.exit, "traverse", 0)
|
||||
self.handler.del_callback(self.exit, "traverse", 0)
|
||||
|
||||
# Move char1 and char2 back
|
||||
self.char1.location = self.room1
|
||||
|
|
@ -481,7 +457,7 @@ class TestDefaultEvents(CommandTest):
|
|||
|
||||
# Test msg_arrive and msg_leave
|
||||
code = 'message = "{character} goes out."'
|
||||
self.handler.add_event(self.exit, "msg_leave", code,
|
||||
self.handler.add_callback(self.exit, "msg_leave", code,
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Have char1 move through the exit
|
||||
|
|
@ -502,7 +478,7 @@ class TestDefaultEvents(CommandTest):
|
|||
back = create_object("evennia.objects.objects.DefaultExit",
|
||||
key="in", location=self.room2, destination=self.room1)
|
||||
code = 'message = "{character} goes in."'
|
||||
self.handler.add_event(self.exit, "msg_arrive", code,
|
||||
self.handler.add_callback(self.exit, "msg_arrive", code,
|
||||
author=self.char1, valid=True)
|
||||
|
||||
# Have char1 move through the exit
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
235
evennia/contrib/events/utils.py
Normal file
235
evennia/contrib/events/utils.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
"""
|
||||
Functions to extend the event system.
|
||||
|
||||
These functions are to be used by developers to customize events and callbacks.
|
||||
|
||||
"""
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
from django.conf import settings
|
||||
from evennia import logger
|
||||
from evennia import ScriptDB
|
||||
from evennia.utils.create import create_script
|
||||
from evennia.utils.gametime import real_seconds_until as standard_rsu
|
||||
from evennia.utils.utils import class_from_module
|
||||
from evennia.contrib.custom_gametime import UNITS
|
||||
from evennia.contrib.custom_gametime import gametime_to_realtime
|
||||
from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu
|
||||
|
||||
# Temporary storage for events waiting for the script to be started
|
||||
EVENTS = []
|
||||
|
||||
def get_event_handler():
|
||||
"""Return the event handler or None."""
|
||||
try:
|
||||
script = ScriptDB.objects.get(db_key="event_handler")
|
||||
except ScriptDB.DoesNotExist:
|
||||
logger.log_trace("Can't get the event handler.")
|
||||
script = None
|
||||
|
||||
return script
|
||||
|
||||
def register_events(path_or_typeclass):
|
||||
"""
|
||||
Register the events in this typeclass.
|
||||
|
||||
Args:
|
||||
path_or_typeclass (str or type): the Python path leading to the
|
||||
class containing events, or the class itself.
|
||||
|
||||
Returns:
|
||||
The typeclass itself.
|
||||
|
||||
Notes:
|
||||
This function will read events from the `_events` class variable
|
||||
defined in the typeclass given in parameters. It will add
|
||||
the events, either to the script if it exists, or to some
|
||||
temporary storage, waiting for the script to be initialized.
|
||||
|
||||
"""
|
||||
if isinstance(path_or_typeclass, basestring):
|
||||
typeclass = class_from_module(path_or_typeclass)
|
||||
else:
|
||||
typeclass = path_or_typeclass
|
||||
|
||||
typeclass_name = typeclass.__module__ + "." + typeclass.__name__
|
||||
try:
|
||||
storage = ScriptDB.objects.get(db_key="event_handler")
|
||||
assert storage.is_active
|
||||
except (ScriptDB.DoesNotExist, AssertionError):
|
||||
storage = EVENTS
|
||||
|
||||
# If the script is started, add the event directly.
|
||||
# Otherwise, add it to the temporary storage.
|
||||
for name, tup in getattr(typeclass, "_events", {}).items():
|
||||
if len(tup) == 4:
|
||||
variables, help_text, custom_call, custom_add = tup
|
||||
elif len(tup) == 3:
|
||||
variables, help_text, custom_call = tup
|
||||
custom_add = None
|
||||
elif len(tup) == 2:
|
||||
variables, help_text = tup
|
||||
custom_call = None
|
||||
custom_add = None
|
||||
else:
|
||||
variables = help_text = custom_call = custom_add = None
|
||||
|
||||
if isinstance(storage, list):
|
||||
storage.append((typeclass_name, name, variables, help_text, custom_call, custom_add))
|
||||
else:
|
||||
storage.add_event(typeclass_name, name, variables, help_text, custom_call, custom_add)
|
||||
|
||||
return typeclass
|
||||
|
||||
# Custom callbacks for specific event types
|
||||
def get_next_wait(format):
|
||||
"""
|
||||
Get the length of time in seconds before format.
|
||||
|
||||
Args:
|
||||
format (str): a time format matching the set calendar.
|
||||
|
||||
The time format could be something like "2018-01-08 12:00". The
|
||||
number of units set in the calendar affects the way seconds are
|
||||
calculated.
|
||||
|
||||
Returns:
|
||||
until (int or float): the number of seconds until the event.
|
||||
usual (int or float): the usual number of seconds between events.
|
||||
format (str): a string format representing the time.
|
||||
|
||||
"""
|
||||
calendar = getattr(settings, "EVENTS_CALENDAR", None)
|
||||
if calendar is None:
|
||||
logger.log_err("A time-related event has been set whereas " \
|
||||
"the gametime calendar has not been set in the settings.")
|
||||
return
|
||||
elif calendar == "standard":
|
||||
rsu = standard_rsu
|
||||
units = ["min", "hour", "day", "month", "year"]
|
||||
elif calendar == "custom":
|
||||
rsu = custom_rsu
|
||||
back = dict([(value, name) for name, value in UNITS.items()])
|
||||
sorted_units = sorted(back.items())
|
||||
del sorted_units[0]
|
||||
units = [n for v, n in sorted_units]
|
||||
|
||||
params = {}
|
||||
for delimiter in ("-", ":"):
|
||||
format = format.replace(delimiter, " ")
|
||||
|
||||
pieces = list(reversed(format.split()))
|
||||
details = []
|
||||
i = 0
|
||||
for uname in units:
|
||||
try:
|
||||
piece = pieces[i]
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
if not piece.isdigit():
|
||||
logger.log_trace("The time specified '{}' in {} isn't " \
|
||||
"a valid number".format(piece, format))
|
||||
return
|
||||
|
||||
# Convert the piece to int
|
||||
piece = int(piece)
|
||||
params[uname] = piece
|
||||
details.append("{}={}".format(uname, piece))
|
||||
if i < len(units):
|
||||
next_unit = units[i + 1]
|
||||
else:
|
||||
next_unit = None
|
||||
i += 1
|
||||
|
||||
params["sec"] = 0
|
||||
details = " ".join(details)
|
||||
until = rsu(**params)
|
||||
usual = -1
|
||||
if next_unit:
|
||||
kwargs = {next_unit: 1}
|
||||
usual = gametime_to_realtime(**kwargs)
|
||||
return until, usual, details
|
||||
|
||||
def time_event(obj, event_name, number, parameters):
|
||||
"""
|
||||
Create a time-related event.
|
||||
|
||||
Args:
|
||||
obj (Object): the object on which stands the event.
|
||||
event_name (str): the event's name.
|
||||
number (int): the number of the event.
|
||||
parameters (str): the parameter of the event.
|
||||
|
||||
"""
|
||||
seconds, usual, key = get_next_wait(parameters)
|
||||
script = create_script("evennia.contrib.events.scripts.TimeEventScript", interval=seconds, obj=obj)
|
||||
script.key = key
|
||||
script.desc = "event on {}".format(key)
|
||||
script.db.time_format = parameters
|
||||
script.db.number = number
|
||||
script.ndb.usual = usual
|
||||
|
||||
def keyword_event(callbacks, parameters):
|
||||
"""
|
||||
Custom call for events with keywords (like push, or pull, or turn...).
|
||||
|
||||
Args:
|
||||
callbacks (list of dict): the list of callbacks to be called.
|
||||
parameters (str): the actual parameters entered to trigger the callback.
|
||||
|
||||
Returns:
|
||||
A list containing the callback dictionaries to be called.
|
||||
|
||||
Notes:
|
||||
This function should be imported and added as a custom_call
|
||||
parameter to add the event when the event supports keywords
|
||||
as parameters. Keywords in parameters are one or more words
|
||||
separated by a comma. For instance, a 'push 1, one' callback can
|
||||
be set to trigger when the player 'push 1' or 'push one'.
|
||||
|
||||
"""
|
||||
key = parameters.strip().lower()
|
||||
to_call = []
|
||||
for callback in callbacks:
|
||||
keys = callback["parameters"]
|
||||
if not keys or key in [p.strip().lower() for p in keys.split(",")]:
|
||||
to_call.append(callback)
|
||||
|
||||
return to_call
|
||||
|
||||
def phrase_event(callbacks, parameters):
|
||||
"""
|
||||
Custom call for events with keywords in sentences (like say or whisper).
|
||||
|
||||
Args:
|
||||
callbacks (list of dict): the list of callbacks to be called.
|
||||
parameters (str): the actual parameters entered to trigger the callback.
|
||||
|
||||
Returns:
|
||||
A list containing the callback dictionaries to be called.
|
||||
|
||||
Notes:
|
||||
This function should be imported and added as a custom_call
|
||||
parameter to add the event when the event supports keywords
|
||||
in phrases as parameters. Keywords in parameters are one or more
|
||||
words separated by a comma. For instance, a 'say yes, okay' callback
|
||||
can be set to trigger when the player says something containing
|
||||
either "yes" or "okay" (maybe 'say I don't like it, but okay').
|
||||
|
||||
"""
|
||||
phrase = parameters.strip().lower()
|
||||
# Remove punctuation marks
|
||||
punctuations = ',.";?!'
|
||||
for p in punctuations:
|
||||
phrase = phrase.replace(p, " ")
|
||||
words = phrase.split()
|
||||
words = [w.strip("' ") for w in words if w.strip("' ")]
|
||||
to_call = []
|
||||
for callback in callbacks:
|
||||
keys = callback["parameters"]
|
||||
if not keys or any(key.strip().lower() in words for key in keys.split(",")):
|
||||
to_call.append(callback)
|
||||
|
||||
return to_call
|
||||
Loading…
Add table
Add a link
Reference in a new issue