mission_script_lua_presentation_api.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. #include <array>
  2. #include <charconv>
  3. #include <cmath>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <limits>
  7. #include <span>
  8. #include <string_view>
  9. #include "../../../middleware/bap/activity_message/music_section_auth.h"
  10. #include "mission_script_lua_internal.h"
  11. namespace sunrise::server::activity::mission::lua_vm::detail {
  12. namespace format = state::activity_sdk::format;
  13. namespace scriptable_auth = middleware::bap::activity_message::scriptable_auth;
  14. namespace auth_fields = middleware::bap::activity_message::auth_fields;
  15. namespace {
  16. [[nodiscard]] bool exact_sequence_slot(const SlotDefinition& definition) noexcept {
  17. return definition.slotType == format::kSequenceSlotType
  18. && definition.componentClass == format::kSequenceComponentClass
  19. && definition.authSchema == format::kSequenceAuthSchema
  20. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  21. }
  22. [[nodiscard]] bool exact_cinematic_slot(const SlotDefinition& definition) noexcept {
  23. return definition.slotType == format::kCinematicSlotType
  24. && definition.componentClass == format::kCinematicComponentClass
  25. && definition.authSchema == format::kCinematicAuthSchema
  26. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  27. }
  28. /** @return True when one live Slot row is the exact type-3 objective reset control. */
  29. [[nodiscard]] bool exact_objective_reset_slot(const SlotDefinition& definition) noexcept {
  30. namespace auth = middleware::bap::activity_message::scriptable_auth;
  31. return definition.slotType == auth::kType3SlotType
  32. && definition.authSchema == auth::kType3Schema
  33. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  34. }
  35. [[nodiscard]] bool exact_task_slot(const SlotDefinition& definition) noexcept {
  36. return definition.slotType == format::kTaskSlotType
  37. && definition.componentClass == format::kTaskComponentClass
  38. && definition.authSchema == format::kTaskAuthSchema
  39. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  40. }
  41. [[nodiscard]] bool exact_dialogue_slot(const SlotDefinition& definition) noexcept {
  42. return definition.slotType == format::kDialogueSlotType
  43. && definition.componentClass == format::kDialogueComponentClass
  44. && definition.authSchema == format::kDialogueAuthSchema
  45. && (definition.flags & format::kSlotSchemaJoinExact) != 0
  46. && (definition.flags & format::kSlotDialogueCuesExact) != 0;
  47. }
  48. /** @return True when one live Slot row is the exact type-68 HUD directive state. */
  49. [[nodiscard]] bool exact_directive_slot(const SlotDefinition& definition) noexcept {
  50. return definition.slotType == scriptable_auth::kType68SlotType
  51. && definition.authSchema == scriptable_auth::kType68Schema
  52. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  53. }
  54. /** @return True when one live Slot row is the exact type-71 public-event sensor. */
  55. [[nodiscard]] bool exact_public_event_slot(const SlotDefinition& definition) noexcept {
  56. return definition.slotType == scriptable_auth::kType71SlotType
  57. && definition.authSchema == scriptable_auth::kType71Schema
  58. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  59. }
  60. /** @return True when one live Slot row is an exact type-42 performance sensor. */
  61. [[nodiscard]] bool exact_performance_slot(const SlotDefinition& definition) noexcept {
  62. return definition.slotType == scriptable_auth::kType42SlotType
  63. && definition.componentClass == scriptable_auth::kType42ComponentClass
  64. && definition.authSchema == scriptable_auth::kType42Schema
  65. && (definition.flags & format::kSlotSchemaJoinExact) != 0;
  66. }
  67. } // namespace
  68. /** Shows one generated directive through the exact type-68 Auth schema. */
  69. [[nodiscard]] int slot_set_directive(lua_State* state) {
  70. const auto* const handle =
  71. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  72. // Named arguments this call accepts. Any other key is refused.
  73. static constexpr std::array<std::string_view, 6> kDeclared{
  74. "directive", "state", "navpoint", "audience", "waypoint", "progress"};
  75. refuse_unknown_arguments(state, kDeclared);
  76. SlotDefinition slot{};
  77. if (!current_slot(state, *handle, slot)) {
  78. return luaL_error(state, "activity slot is stale or invalid");
  79. }
  80. if (!exact_directive_slot(slot)) {
  81. return luaL_error(state, "activity slot is not an exact type-68 directive state");
  82. }
  83. lua_getfield(state, 2, "directive");
  84. luaL_checktype(state, -1, LUA_TTABLE);
  85. const lua_Integer slotRow = directive_integer(state, -1, "slot_row");
  86. const lua_Integer nameHash = directive_integer(state, -1, "name_hash");
  87. const lua_Integer element = directive_integer(state, -1, "element");
  88. lua_pop(state, 1);
  89. const lua_Integer directiveState = optional_integer_argument(state, "state", 0);
  90. if (slotRow < 0 || slotRow > (std::numeric_limits<std::uint32_t>::max)() || nameHash < 0
  91. || nameHash > (std::numeric_limits<std::uint32_t>::max)() || element < 0
  92. || element > (std::numeric_limits<std::int32_t>::max)() || directiveState < 0
  93. || directiveState > 2) {
  94. return luaL_error(state, "directive declaration is outside its native field width");
  95. }
  96. Impl* const impl = impl_from_state(state);
  97. DirectiveElementDefinition resolved{};
  98. if (impl == nullptr || impl->definitions.resolveDirectiveElement == nullptr
  99. || !impl->definitions.resolveDirectiveElement(impl->definitions.context,
  100. static_cast<std::uint32_t>(slotRow),
  101. static_cast<std::uint32_t>(nameHash),
  102. static_cast<std::int32_t>(element),
  103. resolved)
  104. || resolved.slotRow != slot.nativeRow) {
  105. return luaL_error(state, "directive does not belong to this slot");
  106. }
  107. scriptable_auth::Type68Preset preset{.nameHash = resolved.nameHash,
  108. .elementIndex = resolved.elementIndex,
  109. .state = static_cast<std::int8_t>(directiveState),
  110. .visible = true};
  111. // Progress rides only on an element that declares a counter; the HUD ignores it otherwise.
  112. if (push_argument(state, "progress") != LUA_TNIL) {
  113. luaL_checktype(state, -1, LUA_TTABLE);
  114. const std::size_t count = lua_rawlen(state, -1);
  115. if ((resolved.flags & format::kDirectiveElementCounter) == 0) {
  116. return luaL_error(state, "directive progress requires an element with a counter");
  117. }
  118. if (count == 0 || count > preset.progress.size()) {
  119. return luaL_error(state, "directive progress holds one to four integers");
  120. }
  121. for (std::size_t index = 1; index <= count; ++index) {
  122. lua_rawgeti(state, -1, static_cast<lua_Integer>(index));
  123. if (!lua_isinteger(state, -1)) {
  124. return luaL_error(state, "directive progress values must be integers");
  125. }
  126. const lua_Integer value = lua_tointeger(state, -1);
  127. if (value < (std::numeric_limits<std::int32_t>::min)()
  128. || value > (std::numeric_limits<std::int32_t>::max)()) {
  129. return luaL_error(state, "directive progress is outside its native field width");
  130. }
  131. preset.progress[index - 1] = static_cast<std::int32_t>(value);
  132. lua_pop(state, 1);
  133. }
  134. }
  135. lua_pop(state, 1);
  136. if (!optional_slot_reference(
  137. state, "audience", scriptable_auth::kType70SlotType, preset.audience)) {
  138. return luaL_error(state,
  139. "directive audience requires an authored type-70 engagement sensor");
  140. }
  141. if (!optional_slot_reference(
  142. state, "navpoint", scriptable_auth::kType47SlotType, preset.navpoint)) {
  143. return luaL_error(state, "directive navpoint requires a current authored type-47 slot");
  144. }
  145. if (!optional_slot_reference(
  146. state, "waypoint", scriptable_auth::kType60SlotType, preset.waypoint)) {
  147. return luaL_error(state, "directive waypoint requires a current authored type-60 volume");
  148. }
  149. if (preset.navpoint.slotIndex >= 0) {
  150. lua_getfield(state, 2, "navpoint");
  151. const auto* const navpoint =
  152. static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
  153. SlotDefinition navpointSlot{};
  154. if (current_slot(state, *navpoint, navpointSlot)) {
  155. preset.navpointNameHash = navpointSlot.nameHash;
  156. preset.navpointBubbleHash = navpointSlot.bubbleHash;
  157. }
  158. lua_pop(state, 1);
  159. }
  160. std::array<std::byte, scriptable_auth::kType68ByteCount> body{};
  161. std::size_t written = 0;
  162. if (!scriptable_auth::encode_type68(preset, body, written) || written != body.size()) {
  163. return luaL_error(state, "directive native encoder failed");
  164. }
  165. return queue_slot_auth(
  166. state, slot, scriptable_auth::kType68Schema, scriptable_auth::kType68BitCount, body);
  167. }
  168. /** Hides the active directive without naming an authored element. */
  169. [[nodiscard]] int slot_clear_directives(lua_State* state) {
  170. const auto* const handle =
  171. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  172. // Named arguments this call accepts. Any other key is refused.
  173. static constexpr std::array<std::string_view, 0> kDeclared{};
  174. refuse_unknown_arguments(state, kDeclared);
  175. SlotDefinition slot{};
  176. if (!current_slot(state, *handle, slot) || !exact_directive_slot(slot)) {
  177. return luaL_error(state, "activity slot is not an exact type-68 directive state");
  178. }
  179. scriptable_auth::Type68Preset preset{};
  180. preset.visible = false;
  181. std::array<std::byte, scriptable_auth::kType68ByteCount> body{};
  182. std::size_t written = 0;
  183. if (!scriptable_auth::encode_type68(preset, body, written) || written != body.size()) {
  184. return luaL_error(state, "directive native encoder failed");
  185. }
  186. return queue_slot_auth(
  187. state, slot, scriptable_auth::kType68Schema, scriptable_auth::kType68BitCount, body);
  188. }
  189. /** Selects one authored section in a native music sensor's selection mask. */
  190. [[nodiscard]] int slot_set_music_section(lua_State* state) {
  191. namespace music = middleware::bap::activity_message::music_section;
  192. const auto* const handle =
  193. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  194. // Only these named arguments belong to this API.
  195. static constexpr std::array<std::string_view, 2> kDeclared{"section", "enabled"};
  196. refuse_unknown_arguments(state, kDeclared);
  197. SlotDefinition slot{};
  198. const lua_Integer section = checked_integer_argument(state, "section");
  199. if (!current_slot(state, *handle, slot) || slot.slotType != music::kSlotType
  200. || slot.componentClass != music::kComponentClass || slot.authSchema != music::kSchema
  201. || (slot.flags & format::kSlotSchemaJoinExact) == 0 || section < 0
  202. || section >= static_cast<lua_Integer>(music::kSectionCount)) {
  203. return luaL_error(state, "music requires an exact type-11 sensor and a section index");
  204. }
  205. std::array<std::byte, music::kBytes> body{};
  206. std::size_t written = 0;
  207. if (!music::encode(static_cast<std::uint8_t>(section),
  208. optional_boolean_argument(state, "enabled", true),
  209. body,
  210. written)) {
  211. return luaL_error(state, "music section encoder failed");
  212. }
  213. return queue_slot_auth(state, slot, music::kSchema, music::kBits, body);
  214. }
  215. /** Names the player, the event area and the leave timeout one public-event sensor watches. */
  216. [[nodiscard]] int slot_set_public_event_state(lua_State* state) {
  217. const auto* const handle =
  218. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  219. SlotDefinition slot{};
  220. if (!current_slot(state, *handle, slot) || !exact_public_event_slot(slot)) {
  221. return luaL_error(state, "activity slot is not an exact type-71 public-event sensor");
  222. }
  223. // Named arguments this call accepts. Any other key is refused.
  224. static constexpr std::array<std::string_view, 4> kDeclared{
  225. "state", "player", "area", "leave_seconds"};
  226. refuse_unknown_arguments(state, kDeclared);
  227. const lua_Integer eventState = optional_integer_argument(state, "state", 0);
  228. if (eventState < (std::numeric_limits<std::int32_t>::min)()
  229. || eventState > (std::numeric_limits<std::int32_t>::max)()) {
  230. return luaL_error(state, "state must be a 32-bit signed integer");
  231. }
  232. // Absent, the sensor watches this link's own player. Present, it is a decimal string, the
  233. // form every 64-bit key crosses into Lua in.
  234. std::uint64_t player = impl_from_state(state)->identity.playerKey;
  235. if (push_argument(state, "player") != LUA_TNIL) {
  236. lua_pop(state, 1);
  237. const std::string_view playerText = borrowed_string_argument(state, "player");
  238. const auto parsed =
  239. std::from_chars(playerText.data(), playerText.data() + playerText.size(), player);
  240. if (parsed.ec != std::errc{} || parsed.ptr != playerText.data() + playerText.size()) {
  241. return luaL_error(state, "player must be a decimal player key string");
  242. }
  243. lua_pop(state, 1);
  244. } else {
  245. lua_pop(state, 1);
  246. }
  247. if (player == 0) {
  248. return luaL_error(state, "no player key is known for this activity link");
  249. }
  250. const SlotHandle areaHandle = checked_argument<SlotHandle>(state, "area", kSlotMetatable);
  251. const lua_Number seconds = checked_number_argument(state, "leave_seconds");
  252. SlotDefinition area{};
  253. if (!current_slot(state, areaHandle, area)) {
  254. return luaL_error(state, "area slot is stale or invalid");
  255. }
  256. if (!std::isfinite(seconds) || seconds < 0.0
  257. || seconds > static_cast<lua_Number>((std::numeric_limits<float>::max)())) {
  258. return luaL_error(state, "leave_seconds must be a finite non-negative number");
  259. }
  260. const scriptable_auth::Type71Body body{
  261. .state = static_cast<std::int32_t>(eventState),
  262. .playerIdentity = player,
  263. .areaRegistryKey = area.registryKey,
  264. .areaSlotType = static_cast<std::uint8_t>(area.slotType),
  265. .areaSlotIndex = static_cast<std::uint16_t>(area.slotIndex),
  266. .leaveSeconds = static_cast<float>(seconds),
  267. };
  268. std::array<std::byte, scriptable_auth::kType71ByteCount> bytes{};
  269. std::size_t written = 0;
  270. if (!scriptable_auth::encode_type71(body, bytes, written) || written != bytes.size()) {
  271. return luaL_error(state, "public-event native encoder failed");
  272. }
  273. return queue_slot_auth(
  274. state, slot, scriptable_auth::kType71Schema, scriptable_auth::kType71BitCount, bytes);
  275. }
  276. /** Lua `play_sequence` on a slot. Errors unless the slot is an exact type-5 sequence. */
  277. [[nodiscard]] int slot_play_sequence(lua_State* state) {
  278. const auto* const handle =
  279. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  280. SlotDefinition definition{};
  281. if (!current_slot(state, *handle, definition) || !exact_sequence_slot(definition)) {
  282. return luaL_error(state, "activity slot is not an exact type-5 authored sequence");
  283. }
  284. // Named arguments this call accepts. Any other key is refused.
  285. static constexpr std::array<std::string_view, 0> kDeclared{};
  286. refuse_unknown_arguments(state, kDeclared);
  287. CallFrame& frame = active_frame(state);
  288. Intent intent{};
  289. intent.kind = IntentKind::playSequence;
  290. intent.firstRow = definition.nativeRow;
  291. return queue_intent(state, frame, intent);
  292. }
  293. /** Lua `set_cinematic_active` on a slot. Errors unless the slot is an exact cinematic. */
  294. [[nodiscard]] int slot_set_cinematic_active(lua_State* state) {
  295. const auto* const handle =
  296. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  297. SlotDefinition definition{};
  298. if (!current_slot(state, *handle, definition) || !exact_cinematic_slot(definition)) {
  299. return luaL_error(state, "activity slot is not an exact type-6 authored cinematic");
  300. }
  301. // Named arguments this call accepts. Any other key is refused.
  302. static constexpr std::array<std::string_view, 1> kDeclared{"active"};
  303. refuse_unknown_arguments(state, kDeclared);
  304. CallFrame& frame = active_frame(state);
  305. Intent intent{};
  306. intent.kind = IntentKind::setCinematicActive;
  307. intent.firstRow = definition.nativeRow;
  308. intent.active = optional_boolean_argument(state, "active", true);
  309. return queue_intent(state, frame, intent);
  310. }
  311. /** Queues the parameter-free reset of every objective lane owned by one type-3 slot. */
  312. [[nodiscard]] int slot_reset_objectives(lua_State* state) {
  313. const auto* const handle =
  314. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  315. SlotDefinition definition{};
  316. if (!current_slot(state, *handle, definition) || !exact_objective_reset_slot(definition)) {
  317. return luaL_error(state, "activity slot is not an exact type-3 objective reset");
  318. }
  319. // Named arguments this call accepts. Any other key is refused.
  320. static constexpr std::array<std::string_view, 0> kDeclared{};
  321. refuse_unknown_arguments(state, kDeclared);
  322. CallFrame& frame = active_frame(state);
  323. Intent intent{};
  324. intent.kind = IntentKind::resetObjectives;
  325. intent.firstRow = definition.nativeRow;
  326. return queue_intent(state, frame, intent);
  327. }
  328. /** Advances the exact objective bit authored by one type-38 task slot. */
  329. [[nodiscard]] int slot_advance_task(lua_State* state) {
  330. const auto* const handle =
  331. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  332. SlotDefinition definition{};
  333. if (!current_slot(state, *handle, definition) || !exact_task_slot(definition)) {
  334. return luaL_error(state, "activity slot is not an exact type-38 authored task");
  335. }
  336. // Named arguments this call accepts. Any other key is refused.
  337. static constexpr std::array<std::string_view, 0> kDeclared{};
  338. refuse_unknown_arguments(state, kDeclared);
  339. CallFrame& frame = active_frame(state);
  340. Intent intent{};
  341. intent.kind = IntentKind::advanceTask;
  342. intent.firstRow = definition.nativeRow;
  343. return queue_intent(state, frame, intent);
  344. }
  345. /**
  346. * Starts one state of the actor a type-42 sensor drives. With no `state` the slot's target must
  347. * declare exactly one state; a generated `state` row must belong to this slot.
  348. */
  349. [[nodiscard]] int slot_play_performance(lua_State* state) {
  350. const auto* const handle =
  351. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  352. SlotDefinition definition{};
  353. if (!current_slot(state, *handle, definition) || !exact_performance_slot(definition)) {
  354. return luaL_error(state, "activity slot is not an exact type-42 performance sensor");
  355. }
  356. // Named arguments this call accepts. Any other key is refused.
  357. static constexpr std::array<std::string_view, 1> kDeclared{"state"};
  358. refuse_unknown_arguments(state, kDeclared);
  359. lua_Integer slotRow = static_cast<lua_Integer>(definition.nativeRow);
  360. lua_Integer nameHash = 0;
  361. if (!lua_isnoneornil(state, 2)) {
  362. lua_getfield(state, 2, "state");
  363. if (!lua_isnil(state, -1)) {
  364. luaL_checktype(state, -1, LUA_TTABLE);
  365. slotRow = directive_integer(state, -1, "slot_row");
  366. nameHash = directive_integer(state, -1, "name_hash");
  367. }
  368. lua_pop(state, 1);
  369. }
  370. if (slotRow < 0 || slotRow > (std::numeric_limits<std::uint32_t>::max)() || nameHash < 0
  371. || nameHash > (std::numeric_limits<std::uint32_t>::max)()) {
  372. return luaL_error(state, "performance state declaration is outside its native field width");
  373. }
  374. Impl* const impl = impl_from_state(state);
  375. PerformanceStateDefinition resolved{};
  376. if (impl == nullptr || impl->definitions.resolvePerformanceState == nullptr
  377. || !impl->definitions.resolvePerformanceState(impl->definitions.context,
  378. static_cast<std::uint32_t>(slotRow),
  379. static_cast<std::uint32_t>(nameHash),
  380. resolved)
  381. || resolved.slotRow != definition.nativeRow) {
  382. return luaL_error(state, "performance state does not belong to this slot");
  383. }
  384. CallFrame& frame = active_frame(state);
  385. Intent intent{};
  386. intent.kind = IntentKind::playPerformance;
  387. intent.firstRow = definition.nativeRow;
  388. intent.secondRow = resolved.nameHash;
  389. return queue_intent(state, frame, intent);
  390. }
  391. /** Fires one bounded cue from an exact type-53 authored dialogue list. */
  392. [[nodiscard]] int slot_play_dialogue_cue(lua_State* state) {
  393. const auto* const handle =
  394. static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
  395. SlotDefinition definition{};
  396. if (!current_slot(state, *handle, definition) || !exact_dialogue_slot(definition)) {
  397. return luaL_error(state, "activity slot is not an exact type-53 authored dialogue");
  398. }
  399. // Named arguments this call accepts. Any other key is refused.
  400. static constexpr std::array<std::string_view, 2> kDeclared{"cue", "filter"};
  401. refuse_unknown_arguments(state, kDeclared);
  402. const lua_Integer cue = checked_integer_argument(state, "cue");
  403. if (cue < 0 || cue > (std::numeric_limits<std::uint16_t>::max)()) {
  404. return luaL_error(state, "dialogue cue must be a non-negative 16-bit integer");
  405. }
  406. CallFrame& frame = active_frame(state);
  407. Intent intent{};
  408. intent.kind = IntentKind::playDialogueCue;
  409. intent.firstRow = definition.nativeRow;
  410. intent.secondRow = static_cast<std::uint32_t>(cue);
  411. lua_getfield(state, 2, "filter");
  412. if (!lua_isnil(state, -1)) {
  413. const auto* const volume =
  414. static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
  415. SlotDefinition volumeSlot{};
  416. if (!current_slot(state, *volume, volumeSlot)
  417. || volumeSlot.slotType
  418. != static_cast<std::uint32_t>(scriptable_auth::kType53FilterSlotType)) {
  419. return luaL_error(state, "dialogue filter requires a current authored type-60 volume");
  420. }
  421. intent.dialogueFilterRow = static_cast<std::int32_t>(volumeSlot.nativeRow);
  422. }
  423. lua_pop(state, 1);
  424. return queue_intent(state, frame, intent);
  425. }
  426. } // namespace sunrise::server::activity::mission::lua_vm::detail