Gameplay · guide
Gameplay events
A verb, a subject and a place, posted by whoever it happened to and filtered by whoever cares — the half of "features meet through tags and events" that tags alone could not carry.
Edit this page on GitHubDocuments
What it is
A GameplayEvent is a verb, a subject, a place, an amount, who caused it, and the subject's tags.
A GameplayEventFilter says which of them somebody wants. A GameplayEventBus is where they
are posted and where the filters wait.
CompositeRequirementContext is here too, because it was built for the same reason: a character's
state is several objects and a rule has to be able to ask all of them.
What it is for
Letting two libraries meet without referencing each other. Doc 28's dependency spine says that where
loot has to drop from a raid encounter, or a quest has to count a kill, they meet *through tags and
events*. Combat posts Event.Kill with the victim's tags and never learns who was listening; a quest
objective is a subscription with a tag query and a scene filter, and it costs nothing when nothing
dies.
Using it
Post from whatever the thing happened to. Subscribe with a filter built from a tag table. Cancel the subscription when whatever wanted it is over.
⚠ An empty verb range matches nothing, never everything. A filter is built from a name a designer
wrote, and an unknown name resolves to an empty range. The other reading turns one typo into a rule
that fires on everything. Wanting every verb is GameplayEventFilter.EveryVerb, which has to be
written down.
⚠ A subscription made during a dispatch does not see the event being dispatched. This is the main path, not an edge case: the last kill of a quest stage completes it, cancels its subscriptions and takes out the next stage's — inside the handler the bus is calling. Letting the new subscription see the current event counts one kill twice.
⚠ Tags on an event is borrowed and must not be kept. It is the subject's own live set, passed by
reference so that filtering a thousand kills allocates nothing.
Examples
Posting a kill:
using Vixen.Gameplay;static class Deaths { public static void Report(GameplayEventBus bus, GameplayTagTable tags, GameplaySubject victim, PlayerId killer) { bus.Post( new GameplayEvent(tags.Resolve("Event.Kill"), Instigator: killer, Tags: victim.Tags) ); }}Waiting for one, in a scene, with a tag query:
using Vixen.Gameplay;static class Watch { public static GameplayEventSubscription Undead(GameplayEventBus bus, GameplayTagTable tags, DefId scene) { var filter = new GameplayEventFilter( tags.RangeOf("Event.Kill"), Scene: scene, Tags: GameplayTagQuery.Resolve(tags, any: ["Creature.Undead"]) ); // A filter that cannot match is accepted rather than refused — reporting a verb the build does // not have belongs to whatever compiled it, which can name the definition. return bus.Subscribe(filter, (in GameplayEvent killed) => Counted(killed.Instigator)); } static void Counted(PlayerId who) { }}Asking four questions of four owners:
using Vixen.Gameplay;static class Gate { public static bool CanBuy(RequirementSet requirements, GameplaySubject character, IRequirementContext progression) { // First answer wins for a value; any context suffices for a tag. var context = new CompositeRequirementContext(progression, character); return requirements.IsMetBy(context); }}See also
- Quests — the first subscriber, and why the bus is in the kernel.
- Gameplay tags — what a verb and a filter are made of.
- Requirements — what the composite context is for.