# Schemeful Events (daScript, Quirrel, C++, Net) ## Advantages of Schemeful Events - Events are universally compatible across scripting languages (daScript, Quirrel). - They can be transmitted both locally and over the network. - They can be modified in real-time without restarting the game. - They have a strict, validated structure, making all fields visible in Quirrel and accessible as an instance, e.g., `evt.someField`. - Full runtime information on the event structure (reflection) is available. - C++ API support is provided for handling these events, if required. ## Declaring an Event Each game directory contains an event declaration file, named in the format `events_<game_name>.das` (e.g., `events_cuisine_royale.das`). The event declaration consists of an annotation and a description of the event structure. The annotation specifies whether the event is `unicast` or `broadcast`, and network routing, if needed, which is covered below. **Example:** `events_<game>.das` ``` [event(unicast)] struct CmdCreateMapPoint x: float z: float ``` ```{note} All events in the file `events_<game_name>.das` are loaded before Quirrel, enabling them to be accessed within it. Therefore, events declared for Quirrel should be placed in this file. Although not mandatory for other events, it is recommended for consistency. ``` ## Creating an Event - **daScript:** An event is created like any regular instance structure, e.g., `[[RqUseAbility ability_type=ability_type]]`. - **Quirrel:** Here, strict validation ensures no typographical errors or extraneous fields are included, `RqUseAbility({ability_type="ultimate"})`. ## Subscribing to an Event - **daScript:** Use `on_event=RqUseAbility`, or explicitly set the type of the first argument in the system (e.g., `evt: RqUseAbility...`). - **Quirrel:** Use `local {OnAbilityCanceled} = require("dasevents")... ::ecs.register_es("ability_canceling_es", { [OnAbilityCanceled] = @( evt, eid, comp) ::dlog(evt.ability_type)`. - **C++:** Events can be listened to by subscribing to the event name, e.g., `ECS_ON_EVENT(eastl::integral_constant<ecs::event_type_t, str_hash_fnv1("EventOnSeatOwnersChanged")>)`. ## Sending Events (Server-to-Server, Client-to-Client) - **daScript:** Use the standard `sendEvent`, `broadcastEvent`. - **Quirrel:** Similarly, use `::ecs.g_entity_mgr.sendEvent`, `::ecs.g_entity_mgr.broadcastEvent`. ## Sending Events Over the Network When declaring an event, specify the routing to determine its network path, e.g., `[event(unicast, routing=ROUTING_SERVER_TO_CLIENT)]`, `ROUTING_CLIENT_TO_SERVER`, or `ROUTING_CLIENT_CONTROLLED_ENTITY_TO_SERVER`. - **daScript:** Use `require net ... send_net_event(eid, evt)` or `broadcast_net_event(evt)`. - **Quirrel:** Use `local {CmdBlinkMarker, sendNetEvent, broadcastNetEvent} = require("dasevents") ... sendNetEvent(eid, CmdBlinkMarker()) ... broadcastNetEvent(CmdBlinkMarker(...))`. ## Network Protocol Version All declared network events contribute to the protocol version. If the server and client versions do not match, the client will disconnect from the session. For script events (`[event]`), you can control this behavior by excluding certain events from protocol calculations. In cases of a mismatch, this may result in either an on-screen error or no notification. - `event(... net_liable=strict ...)` – The event participates in protocol versioning; any mismatch triggers a disconnect (default behavior). - `event(... net_liable=logerr ...)` – The event does not affect protocol versioning; a log error is recorded if a mismatch occurs. - `event(... net_liable=ignore ...)` – The event does not affect protocol versioning; a log warning (`logwarn`) is recorded if a mismatch occurs. C++ events follow a similar logic but use the `NET_PROTO_VERSION` constant and the count of network C++ events, without exceptions. ## Event Version An explicit version can be assigned to an event. By default, all events are set to version `0`. When working with `BitStream`, the version is required and will assist in adapting the protocol if the stream content changes significantly. **Example:** `code.das` ``` [event(broadcast, version=1)] struct TestEvent {} ``` ## Sending Containers (Offline and Online) Dynamic arrays/containers can be sent along with events. Currently, the supported types are `ecs::Object`, `ecs::IntList`, `ecs::FloatList`, `ecs::Point3List`, and `ecs::Point4List`. Here's an example of sending such an event from daScript: **Example:** `code.das` ``` [event(broadcast)] struct TestEvent str : string i : int obj : ecs::Object const? ... using() <| $(var obj : Object) obj |> set("foo", 1) broadcastEvent([[TestEvent str="test event", i = 42, obj=ecs_addr(obj)]]) ``` ```{important} 1. All container types in an event are stored as pointers. 2. When sending a container in an event, use the helper function `ecs_addr(container)`. ``` Sending events from Quirrel follows a similar process: **Example:** `code.nut` ```nut let {CompObject} = require("ecs") let {TestEvent, broadcastNetEvent} = require("dasevents") ... let obj = CompObject() obj["foo"] = 1 broadcastNetEvent(TestEvent({str="test event", i=42, obj=obj})) ``` ```{important} - Any `ecs::Object` within an event will automatically include a field called `fromconnid`, which stores the sender's connection ID (on the client side, this is always `0`, indicating the server; on the server side, it holds the actual connection number). - If the container contents undergo substantial changes, it is advisable to specify an event version (e.g., `[event(... version=1)]`). This will ensure that clients or servers with outdated versions will no longer support the event. ``` ## Sending BitStream Similar to containers, a raw data stream (`BitStream`) can also be sent in an event. When sending a `BitStream`, specifying an event version is mandatory. ## Reflection Events possess an exact schema, accessible at runtime and retrievable from any script or C++ code. - **C++:** All event structure information is stored in `ecs::EventsDB`, which provides various methods such as `getEventScheme`, `hasEventScheme`, `getFieldsCount` (for argument count), `getFieldOffset` (for field offset), `getFieldName` (for field name), `findFieldIndex` (for field index), and `getEventFieldValue<T>` (for direct access to parameter values). - **daScript:** All of functions for C++ are also available in the `ecs` module for daScript (e.g., `events_db_getFieldsCount`). For example, the **Events DB** window in *ImGui* uses this API, see `<project_name>/prog/scripts/game/es/imgui/ecs_events_db.das`. - **Quirrel:** A detailed event printout is available when calling `::log(evt)`, which outputs all event fields. An API with reflection support is also provided, as demonstrated below: **Example:** `describe_event.nut` ```nut local function describeEvent(evt) { if (evt == null) { ::dlog("null event") return } local eventType = evt.getType() local eventId = ::ecs.g_entity_mgr.getEventsDB().findEvent(eventType) local hasScheme = ::ecs.g_entity_mgr.getEventsDB().hasEventScheme(eventId) if (!hasScheme) { ::dlog($"event without scheme #{eventType}") return } local fieldsCount = ::ecs.g_entity_mgr.getEventsDB().getFieldsCount(eventId) ::dlog($"Event {eventType} fields count #{fieldsCount}") for (local i = 0; i < fieldsCount; i++) { local name = ::ecs.g_entity_mgr.getEventsDB().getFieldName(eventId, i) local type = ::ecs.g_entity_mgr.getEventsDB().getFieldType(eventId, i) local offset = ::ecs.g_entity_mgr.getEventsDB().getFieldOffset(eventId, i) local value = ::ecs.g_entity_mgr.getEventsDB().getEventFieldValue(evt, eventId, i) ::dlog($"field #{i} {name} <{type}> offset={offset} = '{value}'") } } ``` ## C++ Event (cpp_event) In addition to dynamic events, it is possible to declare C++ events, for which C++ code and SQ bindings will be generated. In Quirrel, handling these events is identical to working with standard events, as is the case in daScript. When declaring a C++ event, the `with_scheme` argument is required. This is necessary because some events cannot be converted into schemeful events due to restrictions (fields must be basic ECS types or compatible containers only). **Example:** `events_<game>.das` ``` [cpp_event(unicast, with_scheme)] struct EventOnPlayerDash from: float3 to: float3 ``` The utility `<game>/scripts/genDasevents.bat` will generate a `.h` and `.cpp` file for this event (currently located at `prog/game/dasEvents.h/cpp`). ## Quirrel Stubs / C++ Code Generation To generate the Quirrel stubs and C++ code automatically, run the batch file `<game>/scripts/genDasevents.bat`. If the batch file does not work, build the daScript compiler manually once by running `jam -sPlatform=win64 -sCheckedContainers=yes` in `<project_name>/prog/aot`. ## Filters You can manage the list of recipients for server-side das-events using filters. This is helpful for targeting specific groups, such as only the player or the player's team. When sending an event, specify the filter as an additional argument. For instance, `send_net_event(eid, [[EnableSpectator]], target_entity_conn(eid))`. The following filters are currently supported: - `broadcast` (default) – Sends to all recipients. - equivalent in C++: `&net::broadcast_rcptf`. - `target_entity_conn` – Sends the event only to the player (the `eid` receiving the event must be the player's hero or player `eid`). - equivalent in C++: `&rcptf::entity_ctrl_conn<SomeNetMsg, rcptf::TARGET_ENTITY>`. - `entity_team` – Sends the event to the player's hero and team. - equivalent in C++: `&rcptf::entity_team<SomeNetMsg, rcptf::TARGET_ENTITY>`. - `possessed_and_spectated` – Sends the event to the player and any spectators watching them. - equivalent in C++: `&rcptf::possessed_and_spectated`. - `possessed_and_spectated_player` – Similar to `possessed_and_spectated` but targets the player instead of the hero. - equivalent in C++: `&rcptf::possessed_and_spectated_player`. In daScript, a filter is a function returning an `array<net::IConnection>`, which is referred to as a "filter" for consistency with C++ terminology. ## Filters in Squirrel In Squirrel, as in daScript, event-sending methods have an optional parameter where you can pass an array of connection IDs (i.e., an array of `int`). Below is an example filter implemented in Squirrel: **Example:** `sq_filter.nut` ```nut local filtered_by_team_query = ecs.SqQuery("filtered_by_team_query", {comps_ro=[["team", ecs.TYPE_INT], ["connid",ecs.TYPE_INT]], comps_rq=["player"], comps_no=["playerIsBot"]}) local function filter_connids_by_team(team){ local connids = [] filtered_by_team_query.perform(function(eid, comp){ connids.append(comp["connid"]) },"and(ne(connid,{0}), eq(team,{1}))".subst(INVALID_CONNECTION_ID, team)) return connids } ``` And here is an example of sending an event using this filter: **Example:** `sq_send_event.nut` ```nut sendNetEvent(eid, RequestNextRespawnEntity({memberEid=eid}), filter_connids_by_team(target_team)) ``` ## Filters in cpp_event When annotating a `cpp_event` with the `filter=` parameter and one of the filters listed above, the code generation process will produce C++ code that includes the specified filter as described above in parentheses. ## Event Delivery Reliability By default, all events are sent with a reliability level of `RELIABLE_ORDERED`. This can be modified using the `reliability` argument. Available reliability levels include: - `UNRELIABLE` - `UNRELIABLE_SEQUENCED` - `RELIABLE_ORDERED` - `RELIABLE_UNORDERED` ## Enums If you need an enumerated type available in both scripts, there's no need to write it in C++ and bind it separately for each language. The `genDasevents.bat` utility now supports generating Squirrel code with enums directly from daScript. **Follow these steps:** 1. Define the enum in daScript where needed (preferably in a separate file for easy parsing during code generation). 2. Explicitly mark the enum with the `[export_enum]` annotation. 3. Add the file path to `genDasevents.bat` with the `--module scripts/file_with_enum.das` argument. 4. Run `genDasevents.bat`. 5. Constructors for all enums will be available in `<game_prog>/sq_globals/dasenums.nut`. ## Utilities - `ecs.dump_events` – This console command prints all events, their schemas, and schema hashes. If there are mismatches between client and server events, this command can be run on both to compare outputs (the log will already contain all necessary information for analysis). - Additionally, there's an in-game window with detailed event information: open the **ImGui menu** (`F2`) ▸ **Window** ▸ **ECS** ▸ **Events db**. <img src="_images/schemeful_events_01.png" alt="Events db" align="center"> <br> ## FAQ ###### I have a C++ network event and want to move its declaration to daScript while keeping the event in C++. (Example: `ECS_REGISTER_NET_EVENT(EventUserMarkDisabled, net::Er::Unicast, net::ROUTING_SERVER_TO_CLIENT, (&rcptf::entity_ctrl_conn<EventUserMarkDisabledNetMsg, rcptf::TARGET_ENTITY>));` Define the event in daScript with the `[cpp_event(unicast, with_scheme, routing=ROUTING_SERVER_TO_CLIENT, filter=direct_connection)]` annotation, then run `genDasevents.bat`. This will generate stubs, and the event will appear or update in the `.h` and `.cpp` files. (`cpp_event + with_scheme` activates code generation). --- ###### I have a C++ network event and want to move it entirely to scripts (no need for it in C++). Follow the same steps as above, but use `[event(unicast, routing=ROUTING_SERVER_TO_CLIENT)]`. Replace all `sendEvent` calls with `send_net_event/sendNetEvent`, passing a filter function call like `target_entity_conn(eid)` as the final argument. Running `genDasevents.bat` will still be necessary to generate the stubs. --- ###### I have a script-based event and need to migrate it to C++. Simply change the event annotation from `event` to `cpp_event`. Then, replace all `send_net_event` calls with standard `sendEvent` calls. Run `genDasevents.bat` to generate the stubs and C++ code. --- ###### I added an event, but I see the following error in Squirrel: `[E] daRg: the index 'CmdHeroSpeech' does not exist`. Make sure the event is registered in the system before Quirrel loads. Each game has an initialization script (e.g., `<game>_init.das`). Load the script containing the event in this initialization script to resolve the error. --- ###### `genDasevents.bat` shows compilation errors and won't run. Rebuild the compiler. ```{seealso} For more information, see [daScript plugin for VSCode](https://marketplace.visualstudio.com/items?itemName=profelis.dascript-plugin). ```