mission_script_lua_slot_api.cpp 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. #include "../../../middleware/bap/activity_message/music_section_auth.h"
  2. #include "../../../middleware/bap/activity_message/mission_effect_auth.h"
  3. #include "../../../middleware/bap/activity_message/darkness_zone_auth.h"
  4. #include "../../../middleware/bap/activity_message/squad_objective_auth.h"
  5. #include "../../../middleware/bap/activity_message/combatant_path_auth.h"
  6. #include "../../../middleware/bap/activity_message/combatant_action_auth.h"
  7. #include "../../../middleware/bap/activity_message/combatant_delivery_auth.h"
  8. #include "../../../middleware/bap/activity_message/combatant_retire_auth.h"
  9. #include "../../../middleware/bap/activity_message/ghost_link_auth.h"
  10. #include "../../../middleware/bap/activity_message/interactable_object_auth.h"
  11. #include "../../../middleware/bap/activity_message/scene_events_auth.h"
  12. #include <algorithm>
  13. #include <array>
  14. #include <bit>
  15. #include <charconv>
  16. #include <cmath>
  17. #include <cstddef>
  18. #include <cstdint>
  19. #include <limits>
  20. #include <span>
  21. #include <string_view>
  22. #include "../../../middleware/bap/activity_message/auth_schema_catalog.h"
  23. #include "../../../middleware/bap/activity_message/sensor_auth_update.h"
  24. #include "../../../middleware/encoding/bit_writer.h"
  25. #include "../../../state/activity_sdk/format.h"
  26. #include "../../../state/activity_sdk/runtime.h"
  27. #include "mission_script_lua_internal.h"
  28. namespace sunrise::server::activity::mission::lua_vm::detail {
  29. namespace format = state::activity_sdk::format;
  30. namespace slot_transport = middleware::bap::activity_message::sensor_auth_update;
  31. namespace scriptable_auth = middleware::bap::activity_message::scriptable_auth;
  32. namespace auth_catalog = middleware::bap::activity_message::auth_schema_catalog;
  33. namespace {
  34. /** @return True when one live Slot row is an exact type-23 device. */
  35. [[nodiscard]] bool exact_device_slot(const SlotDefinition& definition) noexcept {
  36. return definition.slotType == format::kDeviceSlotType
  37. && definition.componentClass == format::kDeviceComponentClass
  38. && definition.senseSchema == format::kDeviceSenseSchema
  39. && definition.authSchema == format::kDeviceAuthSchema
  40. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  41. }
  42. /** @return True when one live Slot row is an exact type-4 authored object. */
  43. [[nodiscard]] bool exact_object_slot(const SlotDefinition& definition) noexcept {
  44. return definition.slotType == format::kObjectSlotType
  45. && definition.componentClass == format::kObjectComponentClass
  46. && definition.senseSchema == format::kObjectSenseSchema
  47. && definition.authSchema == format::kObjectAuthSchema
  48. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  49. }
  50. /** @return True when one live Slot row is an exact type-31 configured trigger. */
  51. [[nodiscard]] bool exact_trigger_slot(const SlotDefinition& definition) noexcept {
  52. namespace auth = middleware::bap::activity_message::scriptable_auth;
  53. return definition.slotType == auth::kType31SlotType
  54. && definition.authSchema == auth::kType31Schema
  55. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  56. }
  57. [[nodiscard]] bool exact_sequence_slot(const SlotDefinition& definition) noexcept {
  58. return definition.slotType == format::kSequenceSlotType
  59. && definition.componentClass == format::kSequenceComponentClass
  60. && definition.authSchema == format::kSequenceAuthSchema
  61. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  62. }
  63. [[nodiscard]] bool exact_cinematic_slot(const SlotDefinition& definition) noexcept {
  64. return definition.slotType == format::kCinematicSlotType
  65. && definition.componentClass == format::kCinematicComponentClass
  66. && definition.authSchema == format::kCinematicAuthSchema
  67. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  68. }
  69. /** @return True when one live Slot row is the exact type-3 objective reset control. */
  70. [[nodiscard]] bool exact_objective_reset_slot(const SlotDefinition& definition) noexcept {
  71. namespace auth = middleware::bap::activity_message::scriptable_auth;
  72. return definition.slotType == auth::kType3SlotType
  73. && definition.authSchema == auth::kType3Schema
  74. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  75. }
  76. [[nodiscard]] bool exact_task_slot(const SlotDefinition& definition) noexcept {
  77. return definition.slotType == format::kTaskSlotType
  78. && definition.componentClass == format::kTaskComponentClass
  79. && definition.authSchema == format::kTaskAuthSchema
  80. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  81. }
  82. [[nodiscard]] bool exact_dialogue_slot(const SlotDefinition& definition) noexcept {
  83. return definition.slotType == format::kDialogueSlotType
  84. && definition.componentClass == format::kDialogueComponentClass
  85. && definition.authSchema == format::kDialogueAuthSchema
  86. && (definition.flags & format::kSlotSchemaJoinExact) != 0
  87. && (definition.flags & format::kSlotDialogueCuesExact) != 0;
  88. }
  89. /** @return True when one live Slot row is an exact type-30 occupancy condition. */
  90. [[nodiscard]] bool exact_occupancy_slot(const SlotDefinition& definition) noexcept {
  91. return definition.slotType == format::kOccupancySlotType
  92. && definition.componentClass == format::kOccupancyComponentClass
  93. && definition.senseSchema == format::kOccupancySenseSchema
  94. && definition.authSchema == format::kOccupancyAuthSchema
  95. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  96. }
  97. /** @return True when one live Slot row is the exact type-68 HUD directive state. */
  98. [[nodiscard]] bool exact_directive_slot(const SlotDefinition& definition) noexcept {
  99. return definition.slotType == scriptable_auth::kType68SlotType
  100. && definition.authSchema == scriptable_auth::kType68Schema
  101. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  102. }
  103. /** @return True when one live Slot row is the exact type-70 engagement observer. */
  104. [[nodiscard]] bool exact_engagement_slot(const SlotDefinition& definition) noexcept {
  105. return definition.slotType == scriptable_auth::kType70SlotType
  106. && definition.authSchema == scriptable_auth::kType70Schema
  107. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  108. }
  109. /** @return True when one live Slot row is the exact type-71 public-event sensor. */
  110. [[nodiscard]] bool exact_public_event_slot(const SlotDefinition& definition) noexcept {
  111. return definition.slotType == scriptable_auth::kType71SlotType
  112. && definition.authSchema == scriptable_auth::kType71Schema
  113. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  114. }
  115. /** @return True when one live Slot row is an exact type-42 performance sensor. */
  116. [[nodiscard]] bool exact_performance_slot(const SlotDefinition& definition) noexcept {
  117. return definition.slotType == scriptable_auth::kType42SlotType
  118. && definition.componentClass == scriptable_auth::kType42ComponentClass
  119. && definition.authSchema == scriptable_auth::kType42Schema
  120. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  121. }
  122. /** The occupancy Auth body is a fixed 87 bits: a 55-bit client reference then one int32. */
  123. constexpr std::size_t kOccupancyAuthBitCount = 87;
  124. constexpr std::size_t kOccupancyAuthByteCount = 11;
  125. } // namespace
  126. /** @return True when one live Slot row is an exact type-2 combatant. */
  127. [[nodiscard]] bool exact_combatant_slot(const SlotDefinition& definition) noexcept {
  128. return definition.slotType == scriptable_auth::kType2SlotType
  129. && definition.componentClass == scriptable_auth::kType2ComponentClass
  130. && definition.senseSchema == scriptable_auth::kType2SenseSchema
  131. && definition.authSchema == scriptable_auth::kType2Schema
  132. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  133. }
  134. /** Stages one authored native Auth body on the guarded message-5 route. */
  135. [[nodiscard]] int queue_slot_auth(lua_State* state,
  136. const SlotDefinition& slot,
  137. std::uint32_t schema,
  138. std::size_t bitCount,
  139. std::span<const std::byte> body) {
  140. Impl* const impl = impl_from_state(state);
  141. std::array<std::byte, 32> sdkBuildSha256{};
  142. if (!decode_sdk_build_sha256(std::string_view(impl->identity.sdkBuildId.data()),
  143. sdkBuildSha256)) {
  144. return luaL_error(state, "loaded SDK generation identity is invalid");
  145. }
  146. CallFrame& frame = active_frame(state);
  147. Intent intent{};
  148. intent.kind = IntentKind::applySlotAuth;
  149. intent.sdkBuildSha256 = sdkBuildSha256;
  150. intent.firstRow = slot.nativeRow;
  151. intent.objectTag = slot.objectTag;
  152. intent.registryKey = slot.registryKey;
  153. intent.authSchema = schema;
  154. intent.authBitCount = static_cast<std::uint16_t>(bitCount);
  155. intent.slotIndex = static_cast<std::uint16_t>(slot.slotIndex);
  156. intent.slotType = static_cast<std::uint8_t>(slot.slotType);
  157. intent.authByteCount = static_cast<std::uint16_t>(body.size());
  158. try {
  159. intent.authBody.assign(body.begin(), body.end());
  160. } catch (const std::bad_alloc&) {
  161. return luaL_error(state, "mission Auth body allocation failed");
  162. }
  163. return queue_intent(state, frame, intent);
  164. }
  165. /** Sets the authored object filter and caller value carried by one type-30 condition. */
  166. [[nodiscard]] int slot_set_occupancy_condition(lua_State* state) {
  167. const auto* const handle =
  168. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  169. // Named arguments this call accepts. Any other key is refused.
  170. static constexpr std::array<std::string_view, 2> kDeclared{"filter", "value"};
  171. refuse_unknown_arguments(state, kDeclared);
  172. const SlotHandle reference = checked_argument<SlotHandle>(state, "filter", kSlotMetatable);
  173. const lua_Integer value = checked_integer_argument(state, "value");
  174. SlotDefinition slot{};
  175. SlotDefinition playerSet{};
  176. if (!current_slot(state, *handle, slot) || !current_slot(state, reference, playerSet)) {
  177. return luaL_error(state, "activity slot is stale or invalid");
  178. }
  179. if (!exact_occupancy_slot(slot)) {
  180. return luaL_error(state, "activity slot is not an exact type-30 occupancy condition");
  181. }
  182. // The Auth field is a full-range int32 and lua_Integer is wider, so the lane is still checked.
  183. if (value < (std::numeric_limits<std::int32_t>::min)()
  184. || value > (std::numeric_limits<std::int32_t>::max)()) {
  185. return luaL_error(state, "value must be a 32-bit signed integer");
  186. }
  187. std::array<std::byte, kOccupancyAuthByteCount> body{};
  188. middleware::encoding::bits::Writer writer(body);
  189. const std::uint32_t encodedValue =
  190. std::bit_cast<std::uint32_t>(static_cast<std::int32_t>(value)) + 0x80000000U;
  191. if (!writer.write(playerSet.registryKey, 32)
  192. || !writer.write(static_cast<std::uint32_t>(playerSet.slotType) + 1U, 7)
  193. || !writer.write(static_cast<std::uint32_t>(playerSet.slotIndex) + 32768U, 16)
  194. || !writer.write(encodedValue, 32) || writer.bit_count() != kOccupancyAuthBitCount) {
  195. return luaL_error(state, "occupancy condition native encoder failed");
  196. }
  197. return queue_slot_auth(state, slot, format::kOccupancyAuthSchema, kOccupancyAuthBitCount, body);
  198. }
  199. /** Reads one integer field from a generated directive declaration. */
  200. [[nodiscard]] lua_Integer directive_integer(lua_State* state, int table, const char* field) {
  201. lua_getfield(state, table, field);
  202. const lua_Integer value = luaL_checkinteger(state, -1);
  203. lua_pop(state, 1);
  204. return value;
  205. }
  206. /** Shows one generated directive through the exact type-68 Auth schema. */
  207. [[nodiscard]] int slot_set_directive(lua_State* state) {
  208. const auto* const handle =
  209. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  210. static constexpr std::array<std::string_view, 4> kDeclared{"directive", "state", "navpoint", "audience"};
  211. refuse_unknown_arguments(state, kDeclared);
  212. SlotDefinition slot{};
  213. if (!current_slot(state, *handle, slot)) {
  214. return luaL_error(state, "activity slot is stale or invalid");
  215. }
  216. if (!exact_directive_slot(slot)) {
  217. return luaL_error(state, "activity slot is not an exact type-68 directive state");
  218. }
  219. lua_getfield(state, 2, "directive");
  220. luaL_checktype(state, -1, LUA_TTABLE);
  221. const lua_Integer slotRow = directive_integer(state, -1, "slot_row");
  222. const lua_Integer nameHash = directive_integer(state, -1, "name_hash");
  223. const lua_Integer element = directive_integer(state, -1, "element");
  224. lua_pop(state, 1);
  225. const lua_Integer directiveState = optional_integer_argument(state, "state", 0);
  226. if (slotRow < 0 || slotRow > (std::numeric_limits<std::uint32_t>::max)() || nameHash < 0
  227. || nameHash > (std::numeric_limits<std::uint32_t>::max)() || element < 0
  228. || element > (std::numeric_limits<std::int32_t>::max)() || directiveState < 0
  229. || directiveState > 2) {
  230. return luaL_error(state, "directive declaration is outside its native field width");
  231. }
  232. Impl* const impl = impl_from_state(state);
  233. DirectiveElementDefinition resolved{};
  234. if (impl == nullptr || impl->definitions.resolveDirectiveElement == nullptr
  235. || !impl->definitions.resolveDirectiveElement(impl->definitions.context,
  236. static_cast<std::uint32_t>(slotRow),
  237. static_cast<std::uint32_t>(nameHash),
  238. static_cast<std::int32_t>(element),
  239. resolved)
  240. || resolved.slotRow != slot.nativeRow) {
  241. return luaL_error(state, "directive does not belong to this slot");
  242. }
  243. scriptable_auth::Type68Preset preset{.nameHash = resolved.nameHash,
  244. .elementIndex = resolved.elementIndex,
  245. .state = static_cast<std::int8_t>(directiveState),
  246. .visible = true};
  247. lua_getfield(state, 2, "audience");
  248. if (!lua_isnil(state, -1)) {
  249. const auto* ref = static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
  250. SlotDefinition filter{};
  251. if (!current_slot(state, *ref, filter) || filter.slotType != 70 || filter.slotIndex < 0
  252. || filter.slotIndex > INT16_MAX)
  253. return luaL_error(state, "directive audience requires an authored type-70 engagement sensor");
  254. preset.audience = {filter.registryKey, 70, static_cast<std::int16_t>(filter.slotIndex)};
  255. }
  256. lua_pop(state, 1);
  257. lua_getfield(state, 2, "navpoint");
  258. if (!lua_isnil(state, -1)) {
  259. const auto* targetHandle = static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
  260. SlotDefinition target{};
  261. if (!current_slot(state, *targetHandle, target) || target.slotType != 47
  262. || target.slotIndex < 0 || target.slotIndex > INT16_MAX)
  263. return luaL_error(state, "directive navpoint requires a current authored type-47 slot");
  264. preset.navpoint = {target.registryKey, 47, static_cast<std::int16_t>(target.slotIndex)};
  265. }
  266. lua_pop(state, 1);
  267. std::array<std::byte, scriptable_auth::kType68ByteCount> body{};
  268. std::size_t written = 0;
  269. if (!scriptable_auth::encode_type68(preset, body, written) || written != body.size()) {
  270. return luaL_error(state, "directive native encoder failed");
  271. }
  272. return queue_slot_auth(
  273. state, slot, scriptable_auth::kType68Schema, scriptable_auth::kType68BitCount, body);
  274. }
  275. /** Hides the active directive without naming an authored element. */
  276. [[nodiscard]] int slot_clear_directives(lua_State* state) {
  277. const auto* const handle =
  278. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  279. // Named arguments this call accepts. Any other key is refused.
  280. static constexpr std::array<std::string_view, 0> kDeclared{};
  281. refuse_unknown_arguments(state, kDeclared);
  282. SlotDefinition slot{};
  283. if (!current_slot(state, *handle, slot) || !exact_directive_slot(slot)) {
  284. return luaL_error(state, "activity slot is not an exact type-68 directive state");
  285. }
  286. scriptable_auth::Type68Preset preset{};
  287. preset.visible = false;
  288. std::array<std::byte, scriptable_auth::kType68ByteCount> body{};
  289. std::size_t written = 0;
  290. if (!scriptable_auth::encode_type68(preset, body, written) || written != body.size()) {
  291. return luaL_error(state, "directive native encoder failed");
  292. }
  293. return queue_slot_auth(
  294. state, slot, scriptable_auth::kType68Schema, scriptable_auth::kType68BitCount, body);
  295. }
  296. /** Restores one encounter engagement sensor with script-declared native initial values. */
  297. [[nodiscard]] int slot_set_engagement_state(lua_State* state) {
  298. const auto* const handle =
  299. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  300. SlotDefinition slot{};
  301. if (!current_slot(state, *handle, slot) || !exact_engagement_slot(slot)) {
  302. return luaL_error(state, "activity slot is not an exact type-70 engagement state");
  303. }
  304. // Named arguments this call accepts. Any other key is refused.
  305. static constexpr std::array<std::string_view, 2> kDeclared{"flags", "revision"};
  306. refuse_unknown_arguments(state, kDeclared);
  307. const lua_Integer flags = optional_integer_argument(state, "flags", 1);
  308. const lua_Integer revision = optional_integer_argument(state, "revision", 1);
  309. if (flags < 0 || flags > 0x1F || revision < (std::numeric_limits<std::int16_t>::min)()
  310. || revision > (std::numeric_limits<std::int16_t>::max)()) {
  311. return luaL_error(state, "engagement state is outside its native field width");
  312. }
  313. const scriptable_auth::Type70Preset preset{
  314. .flags = static_cast<std::uint8_t>(flags),
  315. .revision = static_cast<std::int16_t>(revision),
  316. };
  317. std::array<std::byte, scriptable_auth::kType70ByteCount> body{};
  318. std::size_t written = 0;
  319. if (!scriptable_auth::encode_type70(preset, body, written) || written != body.size()) {
  320. return luaL_error(state, "engagement native encoder failed");
  321. }
  322. return queue_slot_auth(
  323. state, slot, scriptable_auth::kType70Schema, scriptable_auth::kType70BitCount, body);
  324. }
  325. /** Enables the native restriction; roster assembly supplies its matching bubble. */
  326. [[nodiscard]] int slot_set_darkness_zone(lua_State* state) {
  327. namespace darkness=middleware::bap::activity_message::darkness_zone;
  328. const auto* handle=static_cast<const SlotHandle*>(luaL_checkudata(state,1,kSlotMetatable));
  329. static constexpr std::array<std::string_view,2> declared{"enabled","wipe_seconds"};
  330. refuse_unknown_arguments(state,declared);
  331. SlotDefinition slot{};
  332. if (!current_slot(state,*handle,slot)||slot.slotType!=darkness::kSlotType
  333. ||slot.componentClass!=darkness::kClass||slot.authSchema!=darkness::kSchema
  334. ||(slot.flags&format::kSlotSchemaJoinExact)==0)
  335. return luaL_error(state,"darkness zone requires the exact hard-wipe globals sensor");
  336. std::array<std::byte,darkness::kBytes> body{};
  337. const auto wipe=optional_integer_argument(state,"wipe_seconds",-1);
  338. if (wipe < -1 || wipe>3
  339. || !darkness::encode(optional_boolean_argument(state,"enabled",false),body,static_cast<int>(wipe)))
  340. return luaL_error(state,"darkness zone encoder failed");
  341. return queue_slot_auth(state,slot,darkness::kSchema,darkness::kBits,body);
  342. }
  343. /** Native typed object filters: players, one object, and volume intersection. */
  344. [[nodiscard]] int slot_set_object_filter(lua_State* state) {
  345. namespace auth = middleware::bap::activity_message::scriptable_auth;
  346. const auto* handle=static_cast<const SlotHandle*>(luaL_checkudata(state,1,kSlotMetatable));
  347. static constexpr std::array<std::string_view,4> fields{"players","target","inside","inside_any"};
  348. refuse_unknown_arguments(state,fields);
  349. SlotDefinition slot{};
  350. if (!current_slot(state,*handle,slot) || slot.slotType!=34 || slot.authSchema!=auth::kType34Schema)
  351. return luaL_error(state,"object filter requires an authored type-34 sensor");
  352. auth::Type34Body body{};
  353. lua_getfield(state, 2, "inside_any");
  354. const bool volumes = !lua_isnil(state, -1);
  355. if (volumes) {
  356. luaL_checktype(state, -1, LUA_TTABLE);
  357. const auto count = lua_rawlen(state, -1);
  358. if (count == 0 || count > 5) return luaL_error(state, "inside_any requires 1..5 authored volumes");
  359. for (std::size_t index = 1; index <= count; ++index) {
  360. lua_rawgeti(state, -1, static_cast<lua_Integer>(index));
  361. const auto* ref = static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
  362. SlotDefinition volume{};
  363. if (!current_slot(state, *ref, volume) || volume.slotType != 60)
  364. return luaL_error(state, "inside_any requires authored type-60 volumes");
  365. body.predicates[body.count++] = auth::Type34ModeFlagSlotRef{0, true,
  366. {volume.registryKey, 60, static_cast<std::int16_t>(volume.slotIndex)}};
  367. lua_pop(state, 1);
  368. }
  369. }
  370. lua_pop(state, 1);
  371. if (optional_boolean_argument(state,"players",false))
  372. body.predicates[body.count++]=auth::Type34ModeOnlyB{static_cast<std::int8_t>(volumes ? 1 : 0)};
  373. for (const auto name : {"target", "inside"}) {
  374. lua_getfield(state,2,name);
  375. if (!lua_isnil(state,-1)) {
  376. const auto* ref=static_cast<const SlotHandle*>(luaL_checkudata(state,-1,kSlotMetatable));
  377. SlotDefinition target{};
  378. if (!current_slot(state,*ref,target) || target.slotType != (std::string_view(name)=="target" ? 4 : 60))
  379. return luaL_error(state,"filter target must be type 4; inside must be type 60");
  380. const auth::Type2LaneClientRef client{target.registryKey,static_cast<std::int8_t>(target.slotType),static_cast<std::int16_t>(target.slotIndex)};
  381. if (std::string_view(name)=="target") body.predicates[body.count++]=auth::Type34ModeSlotRefC{0,client};
  382. else body.predicates[body.count++]=auth::Type34ModeFlagSlotRef{1,false,client};
  383. }
  384. lua_pop(state,1);
  385. }
  386. std::array<std::byte,auth::kType34MaximumByteCount> bytes{};std::size_t written{},bits{};
  387. if (!auth::encode_type34(body,bytes,written,bits)) return luaL_error(state,"object filter encoder failed");
  388. return queue_slot_auth(state,slot,auth::kType34Schema,bits,std::span(bytes).first(written));
  389. }
  390. [[nodiscard]] int slot_set_mission_effect(lua_State* state) {
  391. namespace effect=middleware::bap::activity_message::mission_effect;
  392. namespace auth=middleware::bap::activity_message::scriptable_auth;
  393. const auto* handle=static_cast<const SlotHandle*>(luaL_checkudata(state,1,kSlotMetatable));
  394. static constexpr std::array<std::string_view,3> fields{"filter","enabled","revision"};
  395. refuse_unknown_arguments(state,fields);
  396. SlotDefinition slot{};
  397. if (!current_slot(state,*handle,slot) || slot.slotType!=26 || slot.authSchema!=0x8080954BU)
  398. return luaL_error(state,"mission effect requires an authored type-26 hop-on");
  399. const bool enabled=optional_boolean_argument(state,"enabled",true);
  400. auth::Type2LaneClientRef client{};
  401. if (enabled) {
  402. lua_getfield(state,2,"filter");
  403. const auto* ref=static_cast<const SlotHandle*>(luaL_checkudata(state,-1,kSlotMetatable));
  404. SlotDefinition filter{};
  405. if (!current_slot(state,*ref,filter) || filter.slotType!=34) return luaL_error(state,"mission effect requires a type-34 filter");
  406. client={filter.registryKey,34,static_cast<std::int16_t>(filter.slotIndex)};lua_pop(state,1);
  407. }
  408. const auto revision=optional_integer_argument(state,"revision",1);
  409. if (revision<=0 || revision>INT32_MAX) return luaL_error(state,"effect revision must be positive");
  410. std::array<std::byte,effect::kBytes> body{};std::size_t written{};
  411. if (!effect::encode(client,enabled,static_cast<std::int32_t>(revision),body,written)) return luaL_error(state,"mission effect encoder failed");
  412. return queue_slot_auth(state,slot,0x8080954BU,effect::kBits,body);
  413. }
  414. /** Binds an authored damage monitor to its exact object, with a retry revision. */
  415. [[nodiscard]] int slot_watch_damage(lua_State* state) {
  416. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state,1,kSlotMetatable));
  417. static constexpr std::array<std::string_view,2> fields{"target", "revision"};
  418. refuse_unknown_arguments(state,fields);
  419. SlotDefinition slot{}, target{};
  420. if (!current_slot(state,*handle,slot) || slot.slotType != 20 || slot.authSchema != 0x80809563U)
  421. return luaL_error(state,"damage watch requires an authored type-20 monitor");
  422. lua_getfield(state,2,"target");
  423. const auto* ref = static_cast<const SlotHandle*>(luaL_checkudata(state,-1,kSlotMetatable));
  424. if (!current_slot(state,*ref,target) || !exact_object_slot(target))
  425. return luaL_error(state,"damage target must be an authored type-4 object");
  426. lua_pop(state,1);
  427. const auto revision = optional_integer_argument(state,"revision",1);
  428. if (revision <= 0 || revision > INT32_MAX) return luaL_error(state,"damage revision must be positive");
  429. std::array<std::byte,11> body{}; std::size_t written{};
  430. middleware::encoding::bits::Writer writer(body);
  431. if (!writer.write(target.registryKey,32) || !writer.write(5,7)
  432. || !writer.write(static_cast<std::uint16_t>(target.slotIndex) + 32768U,16)
  433. || !writer.write(static_cast<std::uint32_t>(revision) + 0x80000000U,32)
  434. || !writer.finish(written)) return luaL_error(state,"damage monitor encoder failed");
  435. return queue_slot_auth(state,slot,0x80809563U,87,body);
  436. }
  437. /** Selects one authored section in a native music sensor's 128-bit mask. */
  438. [[nodiscard]] int slot_set_music_section(lua_State* state) {
  439. namespace music = middleware::bap::activity_message::music_section;
  440. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  441. static constexpr std::array<std::string_view, 2> declared{"section", "enabled"};
  442. refuse_unknown_arguments(state, declared);
  443. SlotDefinition slot{};
  444. const auto section = checked_integer_argument(state, "section");
  445. if (!current_slot(state, *handle, slot) || slot.slotType != 11 || slot.componentClass != music::kClass
  446. || slot.authSchema != music::kSchema || !(slot.flags & format::kSlotSchemaJoinExact)
  447. || section < 0 || section >= 128)
  448. return luaL_error(state, "music requires an exact type-11 sensor and section index 0..127");
  449. std::array<std::byte, music::kBytes> body{};
  450. std::size_t written{};
  451. if (!music::encode(static_cast<std::uint8_t>(section), optional_boolean_argument(state, "enabled", true), body, written))
  452. return luaL_error(state, "music section encoder failed");
  453. return queue_slot_auth(state, slot, music::kSchema, music::kBits, body);
  454. }
  455. /** Spawns authored entry zero and subscribes to accepted native player use. */
  456. [[nodiscard]] int slot_set_interactable_object(lua_State* state) {
  457. namespace object = middleware::bap::activity_message::interactable_object;
  458. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state,1,kSlotMetatable));
  459. static constexpr std::array<std::string_view,3> declared{"generation", "track_owner", "active"};
  460. refuse_unknown_arguments(state,declared);
  461. SlotDefinition slot{};
  462. if (!current_slot(state,*handle,slot) || !exact_object_slot(slot))
  463. return luaL_error(state,"interaction requires an exact authored object");
  464. const auto generation = optional_integer_argument(state,"generation",1);
  465. if (generation <= 0 || generation > 0x7FFFFFFF)
  466. return luaL_error(state,"object generation must be a positive int32");
  467. const bool trackOwner = optional_boolean_argument(state,"track_owner",false);
  468. std::array<std::byte,object::kOwnerBytes> body{};
  469. std::size_t written{};
  470. if (!object::encode(static_cast<std::int32_t>(generation),body,written,trackOwner,
  471. optional_boolean_argument(state,"active",true)))
  472. return luaL_error(state,"interactable object encoder failed");
  473. return queue_slot_auth(state,slot,object::kSchema,trackOwner ? object::kOwnerBits : object::kBits,
  474. std::span(body).first(written));
  475. }
  476. /** Enables the authored Ghost interaction without replacing its action hash. */
  477. [[nodiscard]] int slot_set_ghost_link(lua_State* state) {
  478. namespace ghost = middleware::bap::activity_message::ghost_link;
  479. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  480. static constexpr std::array<std::string_view, 2> declared{"generation", "enabled"};
  481. refuse_unknown_arguments(state, declared);
  482. SlotDefinition slot{};
  483. if (!current_slot(state, *handle, slot) || slot.slotType != ghost::kSlotType
  484. || slot.componentClass != ghost::kComponentClass || slot.authSchema != ghost::kAuthSchema
  485. || (slot.flags & format::kSlotSchemaJoinExact) == 0) {
  486. return luaL_error(state, "activity slot is not an exact Ghost-link sensor");
  487. }
  488. const lua_Integer generation = checked_integer_argument(state, "generation");
  489. if (generation <= 0 || generation > 0x7FFFFFFF) {
  490. return luaL_error(state, "Ghost-link generation must be a positive int32");
  491. }
  492. const bool enabled = optional_boolean_argument(state, "enabled", true);
  493. std::array<std::byte, ghost::kByteCount> body{};
  494. std::size_t written = 0;
  495. if (!ghost::encode(static_cast<std::int32_t>(generation), enabled, body, written)) {
  496. return luaL_error(state, "Ghost-link native encoder failed");
  497. }
  498. return queue_slot_auth(state, slot, ghost::kAuthSchema, ghost::kBitCount, body);
  499. }
  500. /** Names the player, the event area and the leave timeout one public-event sensor watches. */
  501. [[nodiscard]] int slot_set_public_event_state(lua_State* state) {
  502. const auto* const handle =
  503. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  504. SlotDefinition slot{};
  505. if (!current_slot(state, *handle, slot) || !exact_public_event_slot(slot)) {
  506. return luaL_error(state, "activity slot is not an exact type-71 public-event sensor");
  507. }
  508. // Named arguments this call accepts. Any other key is refused.
  509. static constexpr std::array<std::string_view, 4> kDeclared{
  510. "state", "player", "area", "leave_seconds"};
  511. refuse_unknown_arguments(state, kDeclared);
  512. const lua_Integer eventState = optional_integer_argument(state, "state", 0);
  513. if (eventState < (std::numeric_limits<std::int32_t>::min)()
  514. || eventState > (std::numeric_limits<std::int32_t>::max)()) {
  515. return luaL_error(state, "state must be a 32-bit signed integer");
  516. }
  517. // Absent, the sensor watches this link's own player. Present, it is a decimal string, the
  518. // form every 64-bit key crosses into Lua in.
  519. std::uint64_t player = impl_from_state(state)->identity.playerKey;
  520. if (push_argument(state, "player") != LUA_TNIL) {
  521. lua_pop(state, 1);
  522. const std::string_view playerText = borrowed_string_argument(state, "player");
  523. const auto parsed =
  524. std::from_chars(playerText.data(), playerText.data() + playerText.size(), player);
  525. if (parsed.ec != std::errc{} || parsed.ptr != playerText.data() + playerText.size()) {
  526. return luaL_error(state, "player must be a decimal player key string");
  527. }
  528. lua_pop(state, 1);
  529. } else {
  530. lua_pop(state, 1);
  531. }
  532. if (player == 0) {
  533. return luaL_error(state, "no player key is known for this activity link");
  534. }
  535. const SlotHandle areaHandle = checked_argument<SlotHandle>(state, "area", kSlotMetatable);
  536. const lua_Number seconds = checked_number_argument(state, "leave_seconds");
  537. SlotDefinition area{};
  538. if (!current_slot(state, areaHandle, area)) {
  539. return luaL_error(state, "area slot is stale or invalid");
  540. }
  541. if (!std::isfinite(seconds) || seconds < 0.0
  542. || seconds > static_cast<lua_Number>((std::numeric_limits<float>::max)())) {
  543. return luaL_error(state, "leave_seconds must be a finite non-negative number");
  544. }
  545. const scriptable_auth::Type71Body body{
  546. .state = static_cast<std::int32_t>(eventState),
  547. .playerIdentity = player,
  548. .areaRegistryKey = area.registryKey,
  549. .areaSlotType = static_cast<std::uint8_t>(area.slotType),
  550. .areaSlotIndex = static_cast<std::uint16_t>(area.slotIndex),
  551. .leaveSeconds = static_cast<float>(seconds),
  552. };
  553. std::array<std::byte, scriptable_auth::kType71ByteCount> bytes{};
  554. std::size_t written = 0;
  555. if (!scriptable_auth::encode_type71(body, bytes, written) || written != bytes.size()) {
  556. return luaL_error(state, "public-event native encoder failed");
  557. }
  558. return queue_slot_auth(
  559. state, slot, scriptable_auth::kType71Schema, scriptable_auth::kType71BitCount, bytes);
  560. }
  561. /** Assigns a squad to a native combat objective and its selected task group. */
  562. [[nodiscard]] int slot_assign_combat_objective(lua_State* state) {
  563. namespace objective = middleware::bap::activity_message::squad_objective;
  564. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  565. static constexpr std::array<std::string_view, 4> declared{"objective", "revision", "task_group", "reserved"};
  566. refuse_unknown_arguments(state, declared);
  567. const auto reference = checked_argument<SlotHandle>(state, "objective", kSlotMetatable);
  568. const auto revision = checked_integer_argument(state, "revision");
  569. const auto group = checked_integer_argument(state, "task_group");
  570. const bool reserved = optional_boolean_argument(state, "reserved", false);
  571. SlotDefinition squad{}, target{};
  572. if (!current_slot(state, *handle, squad) || squad.slotType != 1
  573. || squad.componentClass != format::kSquadComponentClass
  574. || squad.authSchema != objective::kSchema || !(squad.flags & format::kSlotSchemaJoinExact)
  575. || !current_slot(state, reference, target) || !exact_objective_reset_slot(target)
  576. || target.componentClass != 0x80808348 || squad.registryKey != target.registryKey)
  577. return luaL_error(state, "combat objective requires exact squad/objective slots in the same registry");
  578. if (revision <= 0 || revision > 0x7fffffff || group < -1 || group >= 24 || target.slotIndex > 32767)
  579. return luaL_error(state, "combat objective revision, group or index is outside its native range");
  580. std::array<std::byte, objective::kBytes> body{};
  581. if (!objective::encode({target.registryKey, static_cast<std::uint32_t>(revision),
  582. static_cast<std::uint16_t>(target.slotIndex), static_cast<std::int32_t>(group), reserved}, body))
  583. return luaL_error(state, "combat objective encoder failed");
  584. return queue_slot_auth(state, squad, objective::kSchema, objective::kBits, body);
  585. }
  586. /** Creates a named actor and starts one package-authored movement path. */
  587. [[nodiscard]] int slot_play_actor_path(lua_State* state) {
  588. namespace path = middleware::bap::activity_message::combatant_path;
  589. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  590. static constexpr std::array<std::string_view, 3> declared{"generation", "revision", "path"};
  591. refuse_unknown_arguments(state, declared);
  592. const auto generation = checked_integer_argument(state, "generation");
  593. const auto revision = checked_integer_argument(state, "revision");
  594. const auto reference = checked_argument<SlotHandle>(state, "path", kSlotMetatable);
  595. SlotDefinition actor{}, sequence{};
  596. if (!current_slot(state, *handle, actor) || !exact_combatant_slot(actor)
  597. || !current_slot(state, reference, sequence) || sequence.slotType != 58
  598. || sequence.componentClass != 0x80807D9B || actor.objectTag != sequence.objectTag
  599. || actor.registryKey != sequence.registryKey)
  600. return luaL_error(state, "actor path requires an exact member and same-registry type-58 path");
  601. if (generation <= 0 || generation > 0x7fffffff || revision <= 0 || revision > 0x7fffffff
  602. || sequence.slotIndex > 32767)
  603. return luaL_error(state, "actor path generation, revision or index is outside its native range");
  604. std::array<std::byte, path::kBytes> body{};
  605. if (!path::encode({static_cast<std::uint32_t>(generation), static_cast<std::uint32_t>(revision),
  606. sequence.registryKey, static_cast<std::uint16_t>(sequence.slotIndex)}, body))
  607. return luaL_error(state, "actor path encoder failed");
  608. return queue_slot_auth(state, actor, path::kSchema, path::kBits, body);
  609. }
  610. /** Runs an authored native custom action without recreating its actor. */
  611. [[nodiscard]] int slot_play_actor_action(lua_State* state) {
  612. namespace action = middleware::bap::activity_message::combatant_action;
  613. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  614. static constexpr std::array<std::string_view, 4> declared{"generation", "revision", "group", "action"};
  615. refuse_unknown_arguments(state, declared);
  616. const auto generation = checked_integer_argument(state, "generation");
  617. const auto revision = checked_integer_argument(state, "revision");
  618. const auto group = checked_integer_argument(state, "group");
  619. const auto name = checked_integer_argument(state, "action");
  620. SlotDefinition actor{};
  621. if (!current_slot(state, *handle, actor) || !exact_combatant_slot(actor)
  622. || generation <= 0 || generation > 0x7fffffff || revision <= 0 || revision > 0x7fffffff
  623. || group < 0 || group > 0xffffffffLL || name <= 0 || name > 0xffffffffLL)
  624. return luaL_error(state, "actor action requires an exact member and valid native identities");
  625. std::array<std::byte, action::kBytes> body{};
  626. if (!action::encode({static_cast<std::uint32_t>(generation), static_cast<std::uint32_t>(revision),
  627. static_cast<std::uint32_t>(group), static_cast<std::uint32_t>(name)}, body))
  628. return luaL_error(state, "actor action encoder failed");
  629. return queue_slot_auth(state, actor, action::kSchema, action::kBits, body);
  630. }
  631. /** Gives a reserved squad to the named actor's native passenger-delivery component. */
  632. [[nodiscard]] int slot_deliver_squad(lua_State* state) {
  633. namespace delivery = middleware::bap::activity_message::combatant_delivery;
  634. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  635. static constexpr std::array<std::string_view, 3> declared{"generation", "revision", "squad"};
  636. refuse_unknown_arguments(state, declared);
  637. const auto generation = checked_integer_argument(state, "generation");
  638. const auto revision = checked_integer_argument(state, "revision");
  639. const auto reference = checked_argument<SlotHandle>(state, "squad", kSlotMetatable);
  640. SlotDefinition actor{}, squad{};
  641. if (!current_slot(state, *handle, actor) || !exact_combatant_slot(actor)
  642. || !current_slot(state, reference, squad) || squad.slotType != 1
  643. || squad.componentClass != format::kSquadComponentClass
  644. || !(squad.flags & format::kSlotSchemaJoinExact)
  645. || actor.objectTag != squad.objectTag || actor.registryKey != squad.registryKey)
  646. return luaL_error(state, "delivery requires an exact member and same-registry squad");
  647. if (generation <= 0 || generation > 0x7fffffff || revision <= 0 || revision > 0x7fffffff
  648. || squad.slotIndex > 32767)
  649. return luaL_error(state, "delivery generation, revision or index is outside its native range");
  650. std::array<std::byte, delivery::kBytes> body{};
  651. if (!delivery::encode({static_cast<std::uint32_t>(generation), static_cast<std::uint32_t>(revision),
  652. squad.registryKey, static_cast<std::uint16_t>(squad.slotIndex)}, body))
  653. return luaL_error(state, "delivery encoder failed");
  654. return queue_slot_auth(state, actor, delivery::kSchema, delivery::kBits, body);
  655. }
  656. /** Sends one native manifest so multiple reserved squads share the same ship and unload. */
  657. [[nodiscard]] int slot_deliver_squads(lua_State* state) {
  658. namespace delivery = middleware::bap::activity_message::combatant_delivery;
  659. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  660. static constexpr std::array<std::string_view,3> declared{"generation","revision","squads"};
  661. refuse_unknown_arguments(state, declared);
  662. const auto generation = checked_integer_argument(state, "generation");
  663. const auto revision = checked_integer_argument(state, "revision");
  664. SlotDefinition actor{};
  665. if (!current_slot(state,*handle,actor) || !exact_combatant_slot(actor)
  666. || generation <= 0 || generation > 0x7fffffff || revision <= 0 || revision > 0x7fffffff)
  667. return luaL_error(state,"delivery requires an exact actor and positive generation/revision");
  668. lua_getfield(state,2,"squads"); luaL_checktype(state,-1,LUA_TTABLE);
  669. const int list=lua_gettop(state); const auto count=lua_rawlen(state,list);
  670. if (count==0 || count>delivery::kMaxSquads) return luaL_error(state,"delivery needs 1..8 reserved squads");
  671. std::array<delivery::SquadReference,delivery::kMaxSquads> refs{};
  672. for (std::size_t i=0;i<count;++i) {
  673. lua_rawgeti(state,list,static_cast<lua_Integer>(i+1));
  674. const auto* squadHandle=static_cast<const SlotHandle*>(luaL_checkudata(state,-1,kSlotMetatable));
  675. SlotDefinition squad{};
  676. if (!current_slot(state,*squadHandle,squad) || squad.slotType!=1
  677. || squad.componentClass!=format::kSquadComponentClass || !(squad.flags & format::kSlotSchemaJoinExact)
  678. || squad.registryKey!=actor.registryKey || squad.objectTag!=actor.objectTag || squad.slotIndex>32767)
  679. return luaL_error(state,"delivery squads must be exact and in the actor's registry");
  680. refs[i]={squad.registryKey,static_cast<std::uint16_t>(squad.slotIndex)};
  681. lua_pop(state,1);
  682. }
  683. lua_pop(state,1);
  684. std::array<std::byte,delivery::kMaxBytes> body{}; std::size_t written{},bits{};
  685. if (!delivery::encode_many(static_cast<std::uint32_t>(generation),static_cast<std::uint32_t>(revision),
  686. std::span(refs).first(count),body,written,bits)) return luaL_error(state,"invalid delivery manifest");
  687. return queue_slot_auth(state,actor,delivery::kSchema,bits,std::span(body).first(written));
  688. }
  689. /** Disables the named actor after completion, including retained Auth on region reload. */
  690. [[nodiscard]] int slot_retire_actor(lua_State* state) {
  691. namespace retire = middleware::bap::activity_message::combatant_retire;
  692. const auto* handle = static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  693. static constexpr std::array<std::string_view, 1> declared{"generation"};
  694. refuse_unknown_arguments(state, declared);
  695. const auto generation = checked_integer_argument(state, "generation");
  696. SlotDefinition actor{};
  697. if (!current_slot(state, *handle, actor) || !exact_combatant_slot(actor)
  698. || generation <= 0 || generation > 0x7fffffff)
  699. return luaL_error(state, "actor retirement requires an exact member and positive generation");
  700. std::array<std::byte, retire::kBytes> body{};
  701. if (!retire::encode(static_cast<std::uint32_t>(generation), body))
  702. return luaL_error(state, "actor retirement encoder failed");
  703. return queue_slot_auth(state, actor, retire::kSchema, retire::kBits, body);
  704. }
  705. /** Binds one exact combatant to its package-authored squad member. */
  706. [[nodiscard]] int slot_bind_combatant_to_squad(lua_State* state) {
  707. const auto* const handle =
  708. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  709. // Named arguments this call accepts. Any other key is refused.
  710. static constexpr std::array<std::string_view, 0> kDeclared{};
  711. refuse_unknown_arguments(state, kDeclared);
  712. SlotDefinition definition{};
  713. if (!current_slot(state, *handle, definition) || !exact_combatant_slot(definition)) {
  714. return luaL_error(state, "activity slot is not an exact type-2 combatant");
  715. }
  716. CallFrame& frame = active_frame(state);
  717. Intent intent{};
  718. intent.kind = IntentKind::bindCombatantToSquad;
  719. intent.firstRow = definition.nativeRow;
  720. return queue_intent(state, frame, intent);
  721. }
  722. /**
  723. * Reads the optional `with` list: more object slots that answer on this one's revision.
  724. * @return False with the Lua error already raised.
  725. */
  726. [[nodiscard]] bool parse_object_burst(lua_State* state, Intent& intent) {
  727. lua_getfield(state, 2, "with");
  728. if (lua_isnoneornil(state, -1)) {
  729. lua_pop(state, 1);
  730. return true;
  731. }
  732. if (!lua_istable(state, -1)) {
  733. lua_pop(state, 1);
  734. static_cast<void>(luaL_argerror(state, 2, "object burst list is not a table"));
  735. return false;
  736. }
  737. const int list = lua_gettop(state);
  738. const lua_Integer count = static_cast<lua_Integer>(lua_rawlen(state, list));
  739. if (count < 0
  740. || static_cast<std::size_t>(count)
  741. > ::sunrise::state::activity::mission::kIntentBurstCapacity) {
  742. lua_pop(state, 1);
  743. static_cast<void>(luaL_argerror(state, 2, "object burst list is too long"));
  744. return false;
  745. }
  746. for (lua_Integer entry = 1; entry <= count; ++entry) {
  747. lua_rawgeti(state, list, entry);
  748. SlotDefinition member{};
  749. const bool resolved =
  750. resolve_slot(state, lua_gettop(state), member) && exact_object_slot(member);
  751. lua_pop(state, 1);
  752. if (!resolved) {
  753. lua_pop(state, 1);
  754. static_cast<void>(
  755. luaL_argerror(state, 2, "object burst names a slot that is not an exact type-4"));
  756. return false;
  757. }
  758. intent.burstRows[static_cast<std::size_t>(entry - 1)] = member.nativeRow;
  759. }
  760. lua_pop(state, 1);
  761. intent.burstRowCount = static_cast<std::uint8_t>(count);
  762. return true;
  763. }
  764. /** Instantiates or removes the package-owned entry one type-4 slot selects. */
  765. [[nodiscard]] int slot_set_object_active(lua_State* state) {
  766. const auto* const handle =
  767. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  768. SlotDefinition definition{};
  769. if (!current_slot(state, *handle, definition) || !exact_object_slot(definition)) {
  770. return luaL_error(state, "activity slot is not an exact type-4 authored object");
  771. }
  772. // Named arguments this call accepts. Any other key is refused.
  773. static constexpr std::array<std::string_view, 2> kDeclared{"active", "with"};
  774. refuse_unknown_arguments(state, kDeclared);
  775. CallFrame& frame = active_frame(state);
  776. Intent intent{};
  777. intent.kind = IntentKind::setObjectActive;
  778. intent.firstRow = definition.nativeRow;
  779. intent.entryIndex = 0;
  780. intent.active = optional_boolean_argument(state, "active", true);
  781. if (!parse_object_burst(state, intent)) {
  782. return 0;
  783. }
  784. return queue_intent(state, frame, intent);
  785. }
  786. /** Sets one verified type-23 channel through the same guarded route. */
  787. [[nodiscard]] int slot_set_channel(lua_State* state) {
  788. const auto* const handle =
  789. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  790. SlotDefinition definition{};
  791. if (!current_slot(state, *handle, definition)) {
  792. return luaL_error(state, "activity slot is stale or invalid");
  793. }
  794. if (!exact_device_slot(definition)) {
  795. return luaL_error(state, "activity slot is not an exact type-23 device");
  796. }
  797. // Both parameters carry their own bound, so neither the lane nor the range is tested here.
  798. static constexpr std::array<std::string_view, 3> kDeclared{"channel", "value", "snap"};
  799. refuse_unknown_arguments(state, kDeclared);
  800. const DeviceChannelHandle channel =
  801. checked_argument<DeviceChannelHandle>(state, "channel", kDeviceChannelMetatable);
  802. const UnitScalarHandle value =
  803. checked_argument<UnitScalarHandle>(state, "value", kUnitScalarMetatable);
  804. const bool snap = optional_boolean_argument(state, "snap", false);
  805. CallFrame& frame = active_frame(state);
  806. Intent intent{};
  807. intent.kind = IntentKind::setDeviceChannel;
  808. intent.firstRow = definition.nativeRow;
  809. intent.deviceValue = value.value;
  810. intent.deviceChannel = channel.channel;
  811. intent.deviceSnap = snap;
  812. return queue_intent(state, frame, intent);
  813. }
  814. /** Applies one named device transition through the same guarded channel route. */
  815. [[nodiscard]] int slot_transition(lua_State* state) {
  816. const auto* const handle =
  817. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  818. SlotDefinition definition{};
  819. if (!current_slot(state, *handle, definition)) {
  820. return luaL_error(state, "activity slot is stale or invalid");
  821. }
  822. if (!exact_device_slot(definition)) {
  823. return luaL_error(state, "activity slot is not an exact type-23 device");
  824. }
  825. // The handle is a row of the closed vocabulary, so no word is matched here.
  826. static constexpr std::array<std::string_view, 2> kDeclared{"transition", "snap"};
  827. refuse_unknown_arguments(state, kDeclared);
  828. const DeviceTransitionHandle requested =
  829. checked_argument<DeviceTransitionHandle>(state, "transition", kDeviceTransitionMetatable);
  830. const bool snap = optional_boolean_argument(state, "snap", false);
  831. const DeviceTransition& transition = kDeviceTransitions[requested.row];
  832. CallFrame& frame = active_frame(state);
  833. Intent intent{};
  834. intent.kind = IntentKind::setDeviceChannel;
  835. intent.firstRow = definition.nativeRow;
  836. intent.deviceValue = transition.value;
  837. intent.deviceChannel = static_cast<std::uint8_t>(transition.channel);
  838. intent.deviceSnap = snap;
  839. return queue_intent(state, frame, intent);
  840. }
  841. /**
  842. * Fires one type-31 configured trigger. One authored typed reference must be live and eligible.
  843. * The pulse carries no caller value. `enabled` is fixed true and the auxiliary stays zero, and
  844. * the Host mints the generation from the target's own guard so a replay cannot reorder.
  845. */
  846. [[nodiscard]] int slot_fire_trigger(lua_State* state) {
  847. const auto* const handle =
  848. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  849. SlotDefinition definition{};
  850. if (!current_slot(state, *handle, definition)) {
  851. return luaL_error(state, "activity slot is stale or invalid");
  852. }
  853. if (!exact_trigger_slot(definition)) {
  854. return luaL_error(state, "activity slot is not an exact type-31 trigger");
  855. }
  856. // The pulse has no parameters, so an unsafe call cannot be spelled.
  857. static constexpr std::array<std::string_view, 0> kDeclared{};
  858. refuse_unknown_arguments(state, kDeclared);
  859. CallFrame& frame = active_frame(state);
  860. Intent intent{};
  861. intent.kind = IntentKind::fireTrigger;
  862. intent.firstRow = definition.nativeRow;
  863. return queue_intent(state, frame, intent);
  864. }
  865. /** Publishes one scene generation and its cumulative authored event keys. */
  866. [[nodiscard]] int slot_set_scene_events(lua_State* state) {
  867. namespace scene=middleware::bap::activity_message::scene_events;
  868. const auto* handle=static_cast<const SlotHandle*>(luaL_checkudata(state,1,kSlotMetatable));
  869. SlotDefinition slot{};
  870. if (!current_slot(state,*handle,slot) || slot.slotType!=43 || slot.componentClass!=0x80806382U
  871. || slot.authSchema!=scene::kSchema || (slot.flags&format::kSlotSchemaJoinExact)==0)
  872. return luaL_error(state,"scene events require an exact type-43 scene");
  873. static constexpr std::array<std::string_view,2> declared{"generation","events"};
  874. refuse_unknown_arguments(state,declared);
  875. lua_getfield(state,2,"generation"); const auto generation=luaL_checkinteger(state,-1);lua_pop(state,1);
  876. if (generation<=0 || generation>0x7FFFFFFF) return luaL_error(state,"scene generation must be positive int32");
  877. lua_getfield(state,2,"events");luaL_checktype(state,-1,LUA_TTABLE);
  878. const auto count=lua_rawlen(state,-1);
  879. if (count>scene::kMaximumEvents) return luaL_error(state,"scene event list exceeds 32 keys");
  880. std::array<std::uint32_t,scene::kMaximumEvents> events{};
  881. for (std::size_t i=0;i<count;++i) {
  882. lua_rawgeti(state,-1,static_cast<lua_Integer>(i+1));const auto event=luaL_checkinteger(state,-1);lua_pop(state,1);
  883. if (event<=0 || event>=0xFFFFFFFFLL) return luaL_error(state,"invalid scene event key");
  884. events[i]=static_cast<std::uint32_t>(event);
  885. }
  886. lua_pop(state,1);
  887. std::array<std::byte,scene::kMaximumBytes> body{};std::size_t bytes{},bits{};
  888. if (!scene::encode(static_cast<std::int32_t>(generation),std::span(events).first(count),body,bytes,bits))
  889. return luaL_error(state,"scene event keys must be unique");
  890. return queue_slot_auth(state,slot,scene::kSchema,bits,std::span(body).first(bytes));
  891. }
  892. /** Lua `play_sequence` on a slot. Errors unless the slot is an exact type-5 sequence. */
  893. [[nodiscard]] int slot_play_sequence(lua_State* state) {
  894. const auto* const handle =
  895. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  896. SlotDefinition definition{};
  897. if (!current_slot(state, *handle, definition) || !exact_sequence_slot(definition)) {
  898. return luaL_error(state, "activity slot is not an exact type-5 authored sequence");
  899. }
  900. // Named arguments this call accepts. Any other key is refused.
  901. static constexpr std::array<std::string_view, 0> kDeclared{};
  902. refuse_unknown_arguments(state, kDeclared);
  903. CallFrame& frame = active_frame(state);
  904. Intent intent{};
  905. intent.kind = IntentKind::playSequence;
  906. intent.firstRow = definition.nativeRow;
  907. return queue_intent(state, frame, intent);
  908. }
  909. /** Lua `set_cinematic_active` on a slot. Errors unless the slot is an exact cinematic. */
  910. [[nodiscard]] int slot_set_cinematic_active(lua_State* state) {
  911. const auto* const handle =
  912. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  913. SlotDefinition definition{};
  914. if (!current_slot(state, *handle, definition) || !exact_cinematic_slot(definition)) {
  915. return luaL_error(state, "activity slot is not an exact type-6 authored cinematic");
  916. }
  917. // Named arguments this call accepts. Any other key is refused.
  918. static constexpr std::array<std::string_view, 1> kDeclared{"active"};
  919. refuse_unknown_arguments(state, kDeclared);
  920. CallFrame& frame = active_frame(state);
  921. Intent intent{};
  922. intent.kind = IntentKind::setCinematicActive;
  923. intent.firstRow = definition.nativeRow;
  924. intent.active = optional_boolean_argument(state, "active", true);
  925. return queue_intent(state, frame, intent);
  926. }
  927. /** Queues the parameter-free reset of every objective lane owned by one type-3 slot. */
  928. [[nodiscard]] int slot_reset_objectives(lua_State* state) {
  929. const auto* const handle =
  930. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  931. SlotDefinition definition{};
  932. if (!current_slot(state, *handle, definition) || !exact_objective_reset_slot(definition)) {
  933. return luaL_error(state, "activity slot is not an exact type-3 objective reset");
  934. }
  935. // Named arguments this call accepts. Any other key is refused.
  936. static constexpr std::array<std::string_view, 0> kDeclared{};
  937. refuse_unknown_arguments(state, kDeclared);
  938. CallFrame& frame = active_frame(state);
  939. Intent intent{};
  940. intent.kind = IntentKind::resetObjectives;
  941. intent.firstRow = definition.nativeRow;
  942. return queue_intent(state, frame, intent);
  943. }
  944. /** Advances the exact objective bit authored by one type-38 task slot. */
  945. [[nodiscard]] int slot_advance_task(lua_State* state) {
  946. const auto* const handle =
  947. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  948. SlotDefinition definition{};
  949. if (!current_slot(state, *handle, definition) || !exact_task_slot(definition)) {
  950. return luaL_error(state, "activity slot is not an exact type-38 authored task");
  951. }
  952. // Named arguments this call accepts. Any other key is refused.
  953. static constexpr std::array<std::string_view, 0> kDeclared{};
  954. refuse_unknown_arguments(state, kDeclared);
  955. CallFrame& frame = active_frame(state);
  956. Intent intent{};
  957. intent.kind = IntentKind::advanceTask;
  958. intent.firstRow = definition.nativeRow;
  959. return queue_intent(state, frame, intent);
  960. }
  961. /**
  962. * Starts one state of the actor a type-42 sensor drives. With no `state` the slot's target must
  963. * declare exactly one state; a generated `state` row must belong to this slot.
  964. */
  965. [[nodiscard]] int slot_play_performance(lua_State* state) {
  966. const auto* const handle =
  967. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  968. SlotDefinition definition{};
  969. if (!current_slot(state, *handle, definition) || !exact_performance_slot(definition)) {
  970. return luaL_error(state, "activity slot is not an exact type-42 performance sensor");
  971. }
  972. // Named arguments this call accepts. Any other key is refused.
  973. static constexpr std::array<std::string_view, 1> kDeclared{"state"};
  974. refuse_unknown_arguments(state, kDeclared);
  975. lua_Integer slotRow = static_cast<lua_Integer>(definition.nativeRow);
  976. lua_Integer nameHash = 0;
  977. if (!lua_isnoneornil(state, 2)) {
  978. lua_getfield(state, 2, "state");
  979. if (!lua_isnil(state, -1)) {
  980. luaL_checktype(state, -1, LUA_TTABLE);
  981. slotRow = directive_integer(state, -1, "slot_row");
  982. nameHash = directive_integer(state, -1, "name_hash");
  983. }
  984. lua_pop(state, 1);
  985. }
  986. if (slotRow < 0 || slotRow > (std::numeric_limits<std::uint32_t>::max)() || nameHash < 0
  987. || nameHash > (std::numeric_limits<std::uint32_t>::max)()) {
  988. return luaL_error(state, "performance state declaration is outside its native field width");
  989. }
  990. Impl* const impl = impl_from_state(state);
  991. PerformanceStateDefinition resolved{};
  992. if (impl == nullptr || impl->definitions.resolvePerformanceState == nullptr
  993. || !impl->definitions.resolvePerformanceState(impl->definitions.context,
  994. static_cast<std::uint32_t>(slotRow),
  995. static_cast<std::uint32_t>(nameHash),
  996. resolved)
  997. || resolved.slotRow != definition.nativeRow) {
  998. return luaL_error(state, "performance state does not belong to this slot");
  999. }
  1000. CallFrame& frame = active_frame(state);
  1001. Intent intent{};
  1002. intent.kind = IntentKind::playPerformance;
  1003. intent.firstRow = definition.nativeRow;
  1004. intent.secondRow = resolved.nameHash;
  1005. return queue_intent(state, frame, intent);
  1006. }
  1007. /** Fires one bounded cue from an exact type-53 authored dialogue list. */
  1008. [[nodiscard]] int slot_play_dialogue_cue(lua_State* state) {
  1009. const auto* const handle =
  1010. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  1011. SlotDefinition definition{};
  1012. if (!current_slot(state, *handle, definition) || !exact_dialogue_slot(definition)) {
  1013. return luaL_error(state, "activity slot is not an exact type-53 authored dialogue");
  1014. }
  1015. // Named arguments this call accepts. Any other key is refused.
  1016. static constexpr std::array<std::string_view, 1> kDeclared{"cue"};
  1017. refuse_unknown_arguments(state, kDeclared);
  1018. const lua_Integer cue = checked_integer_argument(state, "cue");
  1019. if (cue < 0 || cue > (std::numeric_limits<std::uint16_t>::max)()) {
  1020. return luaL_error(state, "dialogue cue must be a non-negative 16-bit integer");
  1021. }
  1022. CallFrame& frame = active_frame(state);
  1023. Intent intent{};
  1024. intent.kind = IntentKind::playDialogueCue;
  1025. intent.firstRow = definition.nativeRow;
  1026. intent.secondRow = static_cast<std::uint32_t>(cue);
  1027. return queue_intent(state, frame, intent);
  1028. }
  1029. /** Reads one Slot row member, its syntax methods, and its authorized actions. */
  1030. [[nodiscard]] int slot_index(lua_State* state) {
  1031. const auto* const handle =
  1032. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  1033. SlotDefinition definition{};
  1034. if (!current_slot(state, *handle, definition)) {
  1035. return luaL_error(state, "activity slot is stale");
  1036. }
  1037. const std::string_view key = lua_string_view(state, 2);
  1038. if (key == "row") {
  1039. lua_pushinteger(state, definition.localRow);
  1040. } else if (key == "id") {
  1041. lua_pushlstring(state, definition.id.data(), definition.id.size());
  1042. } else if (key == "name") {
  1043. lua_pushlstring(state, definition.name.data(), definition.name.size());
  1044. } else if (key == "object_id") {
  1045. lua_pushlstring(state, definition.objectId.data(), definition.objectId.size());
  1046. } else if (key == "object_tag") {
  1047. lua_pushinteger(state, definition.objectTag);
  1048. } else if (key == "registry_key") {
  1049. lua_pushinteger(state, definition.registryKey);
  1050. } else if (key == "slot_index") {
  1051. lua_pushinteger(state, definition.slotIndex);
  1052. } else if (key == "slot_type") {
  1053. lua_pushinteger(state, definition.slotType);
  1054. } else if (key == "component_class") {
  1055. lua_pushinteger(state, definition.componentClass);
  1056. } else if (key == "sense_schema") {
  1057. lua_pushinteger(state, definition.senseSchema);
  1058. } else if (key == "auth_schema") {
  1059. lua_pushinteger(state, definition.authSchema);
  1060. } else if (key == "sense_schema_id") {
  1061. lua_pushlstring(state, definition.senseSchemaId.data(), definition.senseSchemaId.size());
  1062. } else if (key == "auth_schema_id") {
  1063. lua_pushlstring(state, definition.authSchemaId.data(), definition.authSchemaId.size());
  1064. } else if (key == "auth_type" || key == "auth_min_bits" || key == "auth_max_bits"
  1065. || key == "auth_component_offset" || key == "auth_dynamic"
  1066. || key == "auth_writable") {
  1067. const auth_catalog::Type* const auth = auth_catalog::find(
  1068. static_cast<std::uint8_t>(definition.slotType), definition.authSchema);
  1069. if (auth == nullptr) {
  1070. lua_pushnil(state);
  1071. } else if (key == "auth_type") {
  1072. lua_pushlstring(state, auth->name.data(), auth->name.size());
  1073. } else if (key == "auth_min_bits") {
  1074. lua_pushinteger(state, auth->minimumBits);
  1075. } else if (key == "auth_max_bits") {
  1076. lua_pushinteger(state, auth->maximumBits);
  1077. } else if (key == "auth_component_offset") {
  1078. if (auth->hasContiguousMirror) {
  1079. lua_pushinteger(state, auth->componentOffset);
  1080. } else {
  1081. lua_pushnil(state);
  1082. }
  1083. } else if (key == "auth_dynamic") {
  1084. lua_pushboolean(state, auth->hasDynamicBody);
  1085. } else {
  1086. lua_pushboolean(state, auth->writable);
  1087. }
  1088. } else if (key == "flags") {
  1089. lua_pushinteger(state, definition.flags);
  1090. } else if (key == "set_object_filter") {
  1091. lua_pushcfunction(state, &slot_set_object_filter);
  1092. } else if (key == "set_mission_effect") {
  1093. lua_pushcfunction(state, &slot_set_mission_effect);
  1094. } else if (key == "watch_damage") {
  1095. lua_pushcfunction(state, &slot_watch_damage);
  1096. } else if (key == "set_object_active") {
  1097. lua_pushcfunction(state, &slot_set_object_active);
  1098. } else if (key == "set_channel") {
  1099. lua_pushcfunction(state, &slot_set_channel);
  1100. } else if (key == "transition") {
  1101. lua_pushcfunction(state, &slot_transition);
  1102. } else if (key == "set_occupancy_condition") {
  1103. lua_pushcfunction(state, &slot_set_occupancy_condition);
  1104. } else if (key == "set_directive") {
  1105. lua_pushcfunction(state, &slot_set_directive);
  1106. } else if (key == "clear_directives") {
  1107. lua_pushcfunction(state, &slot_clear_directives);
  1108. } else if (key == "set_darkness_zone") {
  1109. lua_pushcfunction(state, &slot_set_darkness_zone);
  1110. } else if (key == "set_music_section") {
  1111. lua_pushcfunction(state, &slot_set_music_section);
  1112. } else if (key == "set_interactable_object") {
  1113. lua_pushcfunction(state, &slot_set_interactable_object);
  1114. } else if (key == "set_ghost_link") {
  1115. lua_pushcfunction(state, &slot_set_ghost_link);
  1116. } else if (key == "set_engagement_state") {
  1117. lua_pushcfunction(state, &slot_set_engagement_state);
  1118. } else if (key == "set_public_event_state") {
  1119. lua_pushcfunction(state, &slot_set_public_event_state);
  1120. } else if (key == "assign_combat_objective") {
  1121. lua_pushcfunction(state, &slot_assign_combat_objective);
  1122. } else if (key == "play_actor_path") {
  1123. lua_pushcfunction(state, &slot_play_actor_path);
  1124. } else if (key == "deliver_squad") {
  1125. lua_pushcfunction(state, &slot_deliver_squad);
  1126. } else if (key == "deliver_squads") {
  1127. lua_pushcfunction(state, &slot_deliver_squads);
  1128. } else if (key == "play_actor_action") {
  1129. lua_pushcfunction(state, &slot_play_actor_action);
  1130. } else if (key == "retire_actor") {
  1131. lua_pushcfunction(state, &slot_retire_actor);
  1132. } else if (key == "bind_combatant_to_squad") {
  1133. lua_pushcfunction(state, &slot_bind_combatant_to_squad);
  1134. } else if (key == "run_atoms") {
  1135. lua_pushcfunction(state, &slot_run_atoms);
  1136. } else if (key == "fire_trigger") {
  1137. lua_pushcfunction(state, &slot_fire_trigger);
  1138. } else if (key == "play_sequence") {
  1139. lua_pushcfunction(state, &slot_play_sequence);
  1140. } else if (key == "set_scene_events") {
  1141. lua_pushcfunction(state, &slot_set_scene_events);
  1142. } else if (key == "set_cinematic_active") {
  1143. lua_pushcfunction(state, &slot_set_cinematic_active);
  1144. } else if (key == "reset_objectives") {
  1145. lua_pushcfunction(state, &slot_reset_objectives);
  1146. } else if (key == "advance_task") {
  1147. lua_pushcfunction(state, &slot_advance_task);
  1148. } else if (key == "play_dialogue_cue") {
  1149. lua_pushcfunction(state, &slot_play_dialogue_cue);
  1150. } else if (key == "play_performance") {
  1151. lua_pushcfunction(state, &slot_play_performance);
  1152. } else {
  1153. lua_pushnil(state);
  1154. }
  1155. return 1;
  1156. }
  1157. void register_slot_metatables(lua_State* state) {
  1158. register_metatable(state, kSlotMetatable, &slot_index);
  1159. }
  1160. } // namespace sunrise::server::activity::mission::lua_vm::detail