mission_script_lua_event_fields.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. // Event views. Every view serves the field groups the table lists for its kind; a group binds its
  2. // fields in table order, so a field the table does not declare is never reachable.
  3. #include <array>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <span>
  7. #include <string_view>
  8. #include "mission_script_lua_dispatch.h"
  9. #include "mission_script_lua_event_internal.h"
  10. #include "mission_script_lua_internal.h"
  11. #include "mission_trigger_observation.h"
  12. namespace sunrise::server::activity::mission::lua_vm::detail {
  13. namespace {
  14. using G = api::FieldGroupId;
  15. using EventBinding = Binding<host::Event>;
  16. using EventBindings = std::span<const EventBinding>;
  17. /** Pushes one integer field of the event. */
  18. template <auto Field> void integer_field(lua_State* state, const host::Event& event) {
  19. lua_pushinteger(state, static_cast<lua_Integer>(event.*Field));
  20. }
  21. /** Pushes one 64-bit field of the event as a decimal string. */
  22. template <auto Field> void decimal_field(lua_State* state, const host::Event& event) {
  23. push_u64_string(state, event.*Field);
  24. }
  25. /** Pushes one real field of the event. */
  26. template <auto Field> void number_field(lua_State* state, const host::Event& event) {
  27. lua_pushnumber(state, event.*Field);
  28. }
  29. /** Pushes one flag of the event. */
  30. template <auto Field> void boolean_field(lua_State* state, const host::Event& event) {
  31. lua_pushboolean(state, event.*Field ? 1 : 0);
  32. }
  33. /** Pushes the slot the event's identity resolves to, or nil outside this activity. */
  34. void push_event_slot(lua_State* state, const host::Event& event) {
  35. Impl* const impl = impl_from_state(state);
  36. SlotDefinition definition{};
  37. const host::SenseObservationKey identity{.registryKey = event.firstRegistryKey,
  38. .objectTag = event.slotObjectTag,
  39. .senseSchema = event.slotSenseSchema,
  40. .slotIndex = event.firstSlotIndex,
  41. .slotType = event.firstSlotType};
  42. if (impl == nullptr || impl->definitions.resolveSenseSlot == nullptr
  43. || !impl->definitions.resolveSenseSlot(impl->definitions.context, identity, definition)) {
  44. lua_pushnil(state);
  45. } else {
  46. push_handle(state, kSlotMetatable, SlotHandle{definition.localRow});
  47. }
  48. }
  49. /** Pushes one slot identity field only when the update named an object. */
  50. template <void (*Push)(lua_State*, const host::Event&)>
  51. void first_object_field(lua_State* state, const host::Event& event) {
  52. if (event.hasFirstObject) {
  53. Push(state, event);
  54. } else {
  55. lua_pushnil(state);
  56. }
  57. }
  58. /** Pushes one flag only when its presence flag is set. */
  59. template <auto Field, auto Present>
  60. void present_boolean(lua_State* state, const host::Event& event) {
  61. if (event.*Present) {
  62. lua_pushboolean(state, event.*Field ? 1 : 0);
  63. } else {
  64. lua_pushnil(state);
  65. }
  66. }
  67. /** Pushes one integer field only when its presence flag is set. */
  68. template <auto Field, auto Present>
  69. void present_integer(lua_State* state, const host::Event& event) {
  70. if (event.*Present) {
  71. lua_pushinteger(state, static_cast<lua_Integer>(event.*Field));
  72. } else {
  73. lua_pushnil(state);
  74. }
  75. }
  76. /** Pushes one catalog view of the event's client message, or nil without a catalog row. */
  77. template <bool Row> void client_message_row(lua_State* state, const host::Event& event) {
  78. ActivityMessageDefinition definition{};
  79. if (!resolve_message_id(state, event.clientMessageType, definition)) {
  80. lua_pushnil(state);
  81. } else if (Row) {
  82. push_message(state, definition.localRow);
  83. } else {
  84. lua_pushlstring(state, definition.name.data(), definition.name.size());
  85. }
  86. }
  87. /** @return The direct Lua spelling of one native mission action, or empty for a generic one. */
  88. [[nodiscard]] std::string_view effect_action_name(std::uint8_t action) noexcept {
  89. switch (static_cast<ActionKind>(action)) {
  90. case ActionKind::placeSquad:
  91. return "squad.place";
  92. case ActionKind::activateAuthoredScene:
  93. return "scene.activate";
  94. case ActionKind::stopAuthoredScene:
  95. return "scene.stop";
  96. case ActionKind::signalAuthoredScene:
  97. return "scene.send_event";
  98. case ActionKind::setObjectActive:
  99. return "slot.set_object_active";
  100. case ActionKind::setDeviceChannel:
  101. return "slot.set_channel";
  102. case ActionKind::runActorProgram:
  103. return "slot.run_atoms";
  104. case ActionKind::retireActor:
  105. return "slot.retire_actor";
  106. case ActionKind::setInteractableObject:
  107. return "slot.set_interactable_object";
  108. case ActionKind::setGhostLink:
  109. return "slot.set_ghost_link";
  110. case ActionKind::watchDamage:
  111. return "slot.watch_damage";
  112. case ActionKind::assignCombatObjective:
  113. return "slot.assign_combat_objective";
  114. case ActionKind::setLifetime:
  115. return "lifetime.set";
  116. case ActionKind::restartCheckpoint:
  117. return "mission.restart_checkpoint";
  118. case ActionKind::fireTrigger:
  119. return "slot.fire_trigger";
  120. case ActionKind::playSequence:
  121. case ActionKind::playActorSequence:
  122. return "slot.play_sequence";
  123. case ActionKind::setCinematicActive:
  124. return "slot.set_cinematic_active";
  125. case ActionKind::playPerformance:
  126. return "slot.play_performance";
  127. case ActionKind::resetObjectives:
  128. return "slot.reset_objectives";
  129. case ActionKind::advanceTask:
  130. return "slot.advance_task";
  131. case ActionKind::playDialogueCue:
  132. return "slot.play_dialogue_cue";
  133. case ActionKind::holdSpawn:
  134. return "mission.hold_spawn";
  135. case ActionKind::selectMissionState:
  136. return "mission.select_state";
  137. case ActionKind::applySlotAuth:
  138. case ActionKind::bindCombatantToSquad:
  139. case ActionKind::actorCommand:
  140. return {};
  141. }
  142. return {};
  143. }
  144. /** Maps one terminal effect outcome to its Lua spelling. */
  145. [[nodiscard]] std::string_view effect_outcome_name(host::EffectOutcome outcome) noexcept {
  146. using Outcome = host::EffectOutcome;
  147. switch (outcome) {
  148. case Outcome::transportStaged:
  149. return "transport_staged";
  150. case Outcome::refused:
  151. return "refused";
  152. case Outcome::expired:
  153. return "expired";
  154. case Outcome::canceled:
  155. return "canceled";
  156. }
  157. return {};
  158. }
  159. // The table entry of device_state:applied_request.
  160. constexpr const api::MemberSpec& kAppliedRequest =
  161. api::find_member(api::group_members(G::device), "applied_request");
  162. /**
  163. * Returns the exact request first satisfied by this channel's accepted report.
  164. * @param state Lua call holding the event and named channel argument.
  165. * @return One RequestKey, or nil when this report satisfies no request on the channel.
  166. */
  167. [[nodiscard]] int device_applied_request(lua_State* state) {
  168. const auto& event = check_event(state, 1);
  169. refuse_unknown_arguments(state, kAppliedRequest);
  170. const auto channel = checked_argument<DeviceChannelHandle>(
  171. state,
  172. required_key(kAppliedRequest, "channel", api::ValueKind::enumValue),
  173. kDeviceChannelMetatable);
  174. const auto request = event.deviceAppliedRequests[channel.channel];
  175. if (request != 0) {
  176. push_request_key(state, request);
  177. } else {
  178. lua_pushnil(state);
  179. }
  180. return 1;
  181. }
  182. /** Pushes one device channel value, or nil when this report did not carry it. */
  183. template <std::size_t Channel> void device_value(lua_State* state, const host::Event& event) {
  184. if ((event.deviceValueMask & (1U << Channel)) != 0) {
  185. lua_pushnumber(state, event.deviceValues[Channel]);
  186. } else {
  187. lua_pushnil(state);
  188. }
  189. }
  190. /** Pushes one device channel sequence, or nil when this report did not carry it. */
  191. template <std::size_t Channel> void device_sequence(lua_State* state, const host::Event& event) {
  192. if ((event.deviceSequenceMask & (1U << Channel)) != 0) {
  193. lua_pushinteger(state, event.deviceSequences[Channel]);
  194. } else {
  195. lua_pushnil(state);
  196. }
  197. }
  198. // Device fields follow the native position, power and lock channel order.
  199. constexpr std::size_t kPosition = api::device_channel_value("position");
  200. constexpr std::size_t kPower = api::device_channel_value("power");
  201. constexpr std::size_t kLock = api::device_channel_value("lock");
  202. // --- Group bindings, in table order ------------------------------------------------------------
  203. using E = host::Event;
  204. // Fields every event view carries.
  205. constexpr EventBinding kCommon[]{
  206. {"kind",
  207. nullptr,
  208. [](lua_State* state, const E& event) {
  209. lua_pushinteger(state, static_cast<lua_Integer>(event.kind));
  210. }},
  211. {"sequence", nullptr, &decimal_field<&E::sequence>},
  212. {"source_generation", nullptr, &decimal_field<&E::sourceGeneration>},
  213. {"attempt_generation", nullptr, &decimal_field<&E::attemptGeneration>},
  214. };
  215. constexpr EventBinding kMissionSequence[]{
  216. {"mission_sequence", nullptr, &decimal_field<&E::missionSequence>}};
  217. constexpr EventBinding kStateRevision[]{
  218. {"state_revision", nullptr, &decimal_field<&E::stateRevision>}};
  219. constexpr EventBinding kSlotIdentity[]{
  220. {"registry_key", nullptr, &integer_field<&E::firstRegistryKey>},
  221. {"object_tag", nullptr, &integer_field<&E::slotObjectTag>},
  222. {"slot_index", nullptr, &integer_field<&E::firstSlotIndex>},
  223. {"slot_type", nullptr, &integer_field<&E::firstSlotType>},
  224. {"slot", nullptr, &push_event_slot},
  225. };
  226. // The update names one slot only: the first decoded ClientRef of the packet.
  227. constexpr EventBinding kFirstObject[]{
  228. {"registry_key", nullptr, &first_object_field<&integer_field<&E::firstRegistryKey>>},
  229. {"object_tag", nullptr, &first_object_field<&integer_field<&E::slotObjectTag>>},
  230. {"slot_index", nullptr, &first_object_field<&integer_field<&E::firstSlotIndex>>},
  231. {"slot_type", nullptr, &first_object_field<&integer_field<&E::firstSlotType>>},
  232. {"slot", nullptr, &first_object_field<&push_event_slot>},
  233. };
  234. constexpr EventBinding kClientInput[]{
  235. {"client_message_sequence", nullptr, &decimal_field<&E::clientMessageSequence>},
  236. {"payload_bytes", nullptr, &integer_field<&E::payloadBytes>},
  237. };
  238. constexpr EventBinding kPeerHeard[]{
  239. {"peer_heard_mask", nullptr, &integer_field<&E::peerHeardMask>}};
  240. constexpr EventBinding kSenseDecode[]{
  241. {"objects_decoded", nullptr, &integer_field<&E::objectsDecoded>},
  242. {"groups_decoded", nullptr, &integer_field<&E::groupsDecoded>},
  243. };
  244. constexpr EventBinding kClientState[]{
  245. {"activity_state_revision", nullptr, &decimal_field<&E::activityStateRevision>},
  246. {"membership_revision", nullptr, &integer_field<&E::membershipRevision>},
  247. {"region_index", nullptr, &present_integer<&E::regionIndex, &E::clientStateHasRegion>},
  248. {"current_region_index",
  249. nullptr,
  250. &present_integer<&E::currentRegionIndex, &E::clientStateHasCurrentRegion>},
  251. {"held_region_index",
  252. nullptr,
  253. [](lua_State* state, const E& event) {
  254. if (event.heldRegionIndex >= 0) {
  255. lua_pushinteger(state, event.heldRegionIndex);
  256. } else {
  257. lua_pushnil(state);
  258. }
  259. }},
  260. {"region_slice_set_hash",
  261. nullptr,
  262. &present_integer<&E::regionSliceSetHash, &E::clientStateHasRegion>},
  263. {"spawn_state", nullptr, &present_integer<&E::spawnState, &E::clientStateHasSpawn>},
  264. {"teleport_state", nullptr, &present_integer<&E::teleportState, &E::clientStateHasTeleport>},
  265. {"teleport_slice_set_index",
  266. nullptr,
  267. &present_integer<&E::teleportSliceSetIndex, &E::clientStateHasTeleport>},
  268. {"teleport_slice_set_hash",
  269. nullptr,
  270. &present_integer<&E::teleportSliceSetHash, &E::clientStateHasTeleport>},
  271. {"entered",
  272. nullptr,
  273. [](lua_State* state, const E& event) {
  274. if (event.clientEntered) {
  275. lua_pushboolean(state, 1);
  276. } else {
  277. lua_pushnil(state);
  278. }
  279. }},
  280. };
  281. constexpr EventBinding kIncidentBody[]{
  282. {"incident_target", nullptr, &integer_field<&E::incidentTarget>},
  283. {"incident_extra_targets", nullptr, &integer_field<&E::incidentExtraTargets>},
  284. {"incident_selector_bytes", nullptr, &integer_field<&E::incidentSelectorBytes>},
  285. {"incident_payload_bytes", nullptr, &integer_field<&E::incidentPayloadBytes>},
  286. };
  287. constexpr EventBinding kClientMessage[]{
  288. {"message_type", nullptr, &integer_field<&E::clientMessageType>},
  289. {"message_status",
  290. nullptr,
  291. [](lua_State* state, const E& event) {
  292. lua_pushstring(state, lua_client_message_status_name(event.clientMessageStatus));
  293. }},
  294. {"message_name", nullptr, &client_message_row<false>},
  295. {"message", nullptr, &client_message_row<true>},
  296. };
  297. constexpr EventBinding kScriptableRevision[]{
  298. {"scriptable_revision", nullptr, &decimal_field<&E::scriptableRevision>}};
  299. constexpr EventBinding kIncidentRevision[]{
  300. {"incident_revision", nullptr, &decimal_field<&E::incidentRevision>}};
  301. constexpr EventBinding kEffect[]{
  302. {"request_key",
  303. nullptr,
  304. [](lua_State* state, const E& event) { push_request_key(state, event.effectRequestKey); }},
  305. {"effect",
  306. nullptr,
  307. [](lua_State* state, const E& event) {
  308. push_optional_name(state, effect_action_name(event.effectAction));
  309. }},
  310. {"outcome",
  311. nullptr,
  312. [](lua_State* state, const E& event) {
  313. push_optional_name(state, effect_outcome_name(event.effectOutcome));
  314. }},
  315. {"outcome_code", nullptr, &integer_field<&E::effectOutcome>},
  316. };
  317. constexpr EventBinding kRegion[]{
  318. {"region_index", nullptr, &integer_field<&E::regionIndex>},
  319. {"previous_region_index",
  320. nullptr,
  321. [](lua_State* state, const E& event) {
  322. if (event.previousRegionIndex >= 0) {
  323. lua_pushinteger(state, event.previousRegionIndex);
  324. } else {
  325. lua_pushnil(state);
  326. }
  327. }},
  328. };
  329. constexpr EventBinding kPlayerTrigger[]{
  330. {"volume_registry_key", nullptr, &integer_field<&E::playerTriggerRegistryKey>},
  331. {"volume_slot_type", nullptr, &integer_field<&E::playerTriggerSlotType>},
  332. {"volume_slot_index", nullptr, &integer_field<&E::playerTriggerSlotIndex>},
  333. {"resolved_object_id", nullptr, &integer_field<&E::playerTriggerResolvedObjectId>},
  334. };
  335. constexpr EventBinding kCinematic[]{
  336. {"runtime_object_id", nullptr, &decimal_field<&E::cinematicRuntimeObjectId>},
  337. {"event_value", nullptr, &number_field<&E::cinematicEventValue>},
  338. };
  339. constexpr EventBinding kTrigger[]{
  340. {"member_count", nullptr, &integer_field<&E::triggerCount>},
  341. {"value", nullptr, &integer_field<&E::triggerValue>},
  342. {"all_inside", nullptr, &boolean_field<&E::triggerAll>},
  343. };
  344. constexpr EventBinding kSquadAlive[]{
  345. {"alive_count", nullptr, &integer_field<&E::squadAliveCount>},
  346. {"previous_alive_count", nullptr, &integer_field<&E::squadPreviousAliveCount>},
  347. };
  348. constexpr EventBinding kSquadState[]{
  349. {"task_cost", &event_task_cost},
  350. {"task_group", &event_task_group},
  351. {"slot_counts",
  352. nullptr,
  353. [](lua_State* state, const E& event) {
  354. const std::size_t count = event.squadSlotCountLength;
  355. lua_createtable(state, static_cast<int>(count), 0);
  356. for (std::size_t index = 0; index < count; ++index) {
  357. lua_pushinteger(state, event.squadSlotCounts[index]);
  358. lua_rawseti(state, -2, static_cast<lua_Integer>(index + 1));
  359. }
  360. }},
  361. {"initial_observation", nullptr, &boolean_field<&E::initialObservation>},
  362. {"registration_reset", nullptr, &boolean_field<&E::squadRegistrationReset>},
  363. {"population_available", nullptr, &boolean_field<&E::squadPopulationAvailable>},
  364. {"objective_revision",
  365. nullptr,
  366. [](lua_State* state, const E& event) {
  367. lua_pushinteger(state, event.squadObjectiveRegistryKey != 0 ? 1 : 0);
  368. }},
  369. {"task_costs",
  370. nullptr,
  371. [](lua_State* state, const E& event) {
  372. lua_createtable(state, static_cast<int>(host::kSquadObjectiveGroupCount), 0);
  373. for (unsigned group = 0; group < host::kSquadObjectiveGroupCount; ++group) {
  374. if ((event.squadObjectiveCostMask & (1U << group)) != 0) {
  375. lua_pushnumber(state, event.squadObjectiveCosts[group]);
  376. lua_rawseti(state, -2, static_cast<lua_Integer>(group) + 1);
  377. }
  378. }
  379. }},
  380. };
  381. constexpr EventBinding kEntitySpawned[]{
  382. {"member_slot", nullptr, &integer_field<&E::squadSlotOrdinal>},
  383. {"count", nullptr, &integer_field<&E::squadSlotValue>},
  384. {"previous_count", nullptr, &integer_field<&E::squadPreviousSlotValue>},
  385. };
  386. constexpr EventBinding kSceneFinished[]{
  387. {"activation_token", nullptr, &integer_field<&E::sceneActivationToken>}};
  388. constexpr EventBinding kSceneOutput[]{
  389. {"key", nullptr, &integer_field<&E::sceneOutputKey>},
  390. {"activation_token", nullptr, &integer_field<&E::sceneActivationToken>},
  391. };
  392. constexpr EventBinding kObjectiveProgress[]{
  393. {"objective", nullptr, &integer_field<&E::objectiveOrdinal>},
  394. {"task", nullptr, &integer_field<&E::objectiveTaskOrdinal>},
  395. {"task_count", nullptr, &integer_field<&E::objectiveTaskCount>},
  396. {"previous_task_count", nullptr, &integer_field<&E::objectivePreviousTaskCount>},
  397. };
  398. constexpr EventBinding kPhase[]{
  399. {"phase", nullptr, &integer_field<&E::missionPhase>},
  400. {"previous_phase", nullptr, &integer_field<&E::previousMissionPhase>},
  401. };
  402. // The timer name is a bounded buffer with its own length.
  403. constexpr EventBinding kTimer[]{
  404. {"timer_name",
  405. nullptr,
  406. [](lua_State* state, const E& event) {
  407. lua_pushlstring(state, event.timerName.bytes.data(), event.timerName.length);
  408. }},
  409. {"timer_deadline_tick", nullptr, &decimal_field<&E::timerDeadlineTick>},
  410. {"timer_sequence", nullptr, &decimal_field<&E::timerSequence>},
  411. };
  412. constexpr EventBinding kEntitySlots[]{
  413. {"requested_count", nullptr, &integer_field<&E::requestedEntitySlots>}};
  414. constexpr EventBinding kSessionPeer[]{
  415. {"session_id", nullptr, &decimal_field<&E::peerSessionId>},
  416. {"session_generation", nullptr, &decimal_field<&E::peerSessionGeneration>},
  417. {"member_key", nullptr, &decimal_field<&E::peerMemberKey>},
  418. };
  419. // Only the join edge writes union 1's state arm.
  420. constexpr EventBinding kJoinedRevision[]{
  421. {"joined_revision", nullptr, &decimal_field<&E::stateRevision>}};
  422. constexpr EventBinding kActorPath[]{
  423. {"generation", nullptr, &integer_field<&E::actorGeneration>},
  424. {"revision", nullptr, &integer_field<&E::actorPathRevision>},
  425. {"path_state", nullptr, &integer_field<&E::actorPathState>},
  426. {"delivery_revision",
  427. nullptr,
  428. &present_integer<&E::actorDeliveryRevision, &E::actorDeliveryKnown>},
  429. {"delivery_state", nullptr, &present_integer<&E::actorDeliveryState, &E::actorDeliveryKnown>},
  430. {"suppressed", nullptr, &boolean_field<&E::actorSuppressed>},
  431. };
  432. constexpr EventBinding kDevice[]{
  433. {"position", nullptr, &device_value<kPosition>},
  434. {"power", nullptr, &device_value<kPower>},
  435. {"lock", nullptr, &device_value<kLock>},
  436. {"position_sequence", nullptr, &device_sequence<kPosition>},
  437. {"power_sequence", nullptr, &device_sequence<kPower>},
  438. {"lock_sequence", nullptr, &device_sequence<kLock>},
  439. {"first_report", nullptr, &boolean_field<&E::deviceFirstReport>},
  440. {"reset", nullptr, &boolean_field<&E::deviceReset>},
  441. {"applied_request", &device_applied_request},
  442. };
  443. constexpr EventBinding kDamage[]{
  444. {"health", nullptr, &number_field<&E::damageHealth>},
  445. {"shield", nullptr, &number_field<&E::damageShield>},
  446. {"revision", nullptr, &integer_field<&E::damageRevision>},
  447. };
  448. // Sense ordinal 1 backs interaction_open; its meaning is assumed.
  449. constexpr EventBinding kObject[]{
  450. {"generation", nullptr, &integer_field<&E::objectGeneration>},
  451. {"present", nullptr, &boolean_field<&E::objectPresent>},
  452. {"alive", nullptr, &boolean_field<&E::objectPresent>},
  453. {"interaction_open", nullptr, &boolean_field<&E::objectAlive>},
  454. {"owner_known", nullptr, &boolean_field<&E::objectOwnerKnown>},
  455. {"has_owner", nullptr, &boolean_field<&E::objectHasOwner>},
  456. {"owner_key",
  457. nullptr,
  458. [](lua_State* state, const E& event) {
  459. if (event.objectOwnerKey != 0) {
  460. push_u64_string(state, event.objectOwnerKey);
  461. } else {
  462. lua_pushnil(state);
  463. }
  464. }},
  465. {"initial_observation", nullptr, &boolean_field<&E::initialObservation>},
  466. {"entry_index", nullptr, &integer_field<&E::objectEntryIndex>},
  467. {"spawn_mask_low",
  468. nullptr,
  469. [](lua_State* state, const E& event) { lua_pushinteger(state, event.objectSpawnMask[0]); }},
  470. {"spawn_mask_high",
  471. nullptr,
  472. [](lua_State* state, const E& event) { lua_pushinteger(state, event.objectSpawnMask[1]); }},
  473. };
  474. constexpr EventBinding kGhostLink[]{
  475. {"generation", nullptr, &integer_field<&E::ghostGeneration>},
  476. {"progress", nullptr, &number_field<&E::ghostProgress>},
  477. {"active", nullptr, &boolean_field<&E::ghostActive>},
  478. };
  479. // A provocation carries the exact squad reference, not a native Sense body.
  480. constexpr EventBinding kSquadProvoked[]{
  481. {"registry_key", nullptr, &integer_field<&E::firstRegistryKey>},
  482. {"slot_type", nullptr, &integer_field<&E::firstSlotType>},
  483. {"slot_index", nullptr, &integer_field<&E::firstSlotIndex>},
  484. };
  485. constexpr EventBinding kFireteam[]{
  486. {"alive_count", nullptr, &integer_field<&E::fireteamAlive>},
  487. {"dead_count", nullptr, &integer_field<&E::fireteamDead>},
  488. {"unknown_count", nullptr, &integer_field<&E::fireteamUnknown>},
  489. };
  490. // Copied from the exact Sense observation, never from an actor token.
  491. constexpr EventBinding kSenseGeneration[]{
  492. {"spawn_generation",
  493. nullptr,
  494. &present_integer<&E::squadSpawnGeneration, &E::squadHasSpawnGeneration>},
  495. {"sense_generation",
  496. nullptr,
  497. &present_integer<&E::senseGenerationPlusOne, &E::hasSenseGeneration>},
  498. };
  499. constexpr EventBinding kSquadRemoval[]{
  500. {"removal_flag",
  501. nullptr,
  502. [](lua_State* state, const E& event) {
  503. lua_pushboolean(state, event.squadHasRemovalFlag && event.squadRemovalFlag ? 1 : 0);
  504. }},
  505. };
  506. // An invalidation carries no level, so its level fields read nil.
  507. constexpr EventBinding kTriggerLevel[]{
  508. {"available", nullptr, &boolean_field<&E::triggerAvailable>},
  509. {"continuity",
  510. nullptr,
  511. [](lua_State* state, const E& event) {
  512. lua_pushstring(state,
  513. trigger_observation::name(
  514. static_cast<trigger_observation::Continuity>(event.triggerContinuity)));
  515. }},
  516. {"occupied", nullptr, &present_boolean<&E::triggerOccupied, &E::triggerAvailable>},
  517. {"member_count", nullptr, &present_integer<&E::triggerCount, &E::triggerAvailable>},
  518. {"value", nullptr, &present_integer<&E::triggerValue, &E::triggerAvailable>},
  519. {"all_inside", nullptr, &present_boolean<&E::triggerAll, &E::triggerAvailable>},
  520. };
  521. /** Every group's bindings, indexed by FieldGroupId. */
  522. constexpr std::array<EventBindings, static_cast<std::size_t>(G::count)> kGroupBindings{{
  523. kCommon,
  524. kMissionSequence,
  525. kStateRevision,
  526. kSlotIdentity,
  527. kFirstObject,
  528. kClientInput,
  529. kPeerHeard,
  530. kSenseDecode,
  531. kClientState,
  532. kIncidentBody,
  533. kClientMessage,
  534. kScriptableRevision,
  535. kIncidentRevision,
  536. kEffect,
  537. kRegion,
  538. kPlayerTrigger,
  539. kCinematic,
  540. kTrigger,
  541. kSquadAlive,
  542. kSquadState,
  543. kEntitySpawned,
  544. kSceneFinished,
  545. kSceneOutput,
  546. kObjectiveProgress,
  547. kPhase,
  548. kTimer,
  549. kEntitySlots,
  550. kSessionPeer,
  551. kJoinedRevision,
  552. kActorPath,
  553. kDevice,
  554. kDamage,
  555. kObject,
  556. kGhostLink,
  557. kSquadProvoked,
  558. kFireteam,
  559. kSenseGeneration,
  560. kSquadRemoval,
  561. kTriggerLevel,
  562. }};
  563. static_assert([]() consteval {
  564. for (std::size_t index = 0; index < kGroupBindings.size(); ++index) {
  565. if (!bound_to<host::Event>(api::kFieldGroups[index].members, kGroupBindings[index])) {
  566. return false;
  567. }
  568. }
  569. return true;
  570. }());
  571. static_assert(std::size(api::kEvents) == host::kEventKindCount);
  572. /** Reads one member of any event view through the field groups its kind declares. */
  573. [[nodiscard]] int event_index(lua_State* state) {
  574. const host::Event& event = check_event(state, 1);
  575. const std::string_view key = lua_string_view(state, 2);
  576. const auto kind = static_cast<std::size_t>(event.kind);
  577. if (kind < std::size(api::kEvents)) {
  578. for (const api::FieldGroup* group : api::kEvents[kind].groups) {
  579. const auto id = static_cast<std::size_t>(group - api::kFieldGroups.data());
  580. if (serve<host::Event>(state, kGroupBindings[id], key, event)) {
  581. return 1;
  582. }
  583. }
  584. }
  585. lua_pushnil(state);
  586. return 1;
  587. }
  588. } // namespace
  589. /** Installs the event view metatable; every kind shares it. */
  590. void register_event_metatables(lua_State* state) {
  591. register_metatable(state, kEventMetatable, &event_index);
  592. }
  593. } // namespace sunrise::server::activity::mission::lua_vm::detail