| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- #include <array>
- #include <charconv>
- #include <cmath>
- #include <cstddef>
- #include <cstdint>
- #include <limits>
- #include <span>
- #include <string_view>
- #include "../../../middleware/bap/activity_message/music_section_auth.h"
- #include "mission_script_lua_internal.h"
- namespace sunrise::server::activity::mission::lua_vm::detail {
- namespace format = state::activity_sdk::format;
- namespace scriptable_auth = middleware::bap::activity_message::scriptable_auth;
- namespace auth_fields = middleware::bap::activity_message::auth_fields;
- namespace {
- [[nodiscard]] bool exact_sequence_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kSequenceSlotType
- && definition.componentClass == format::kSequenceComponentClass
- && definition.authSchema == format::kSequenceAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- [[nodiscard]] bool exact_cinematic_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kCinematicSlotType
- && definition.componentClass == format::kCinematicComponentClass
- && definition.authSchema == format::kCinematicAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is the exact type-3 objective reset control. */
- [[nodiscard]] bool exact_objective_reset_slot(const SlotDefinition& definition) noexcept {
- namespace auth = middleware::bap::activity_message::scriptable_auth;
- return definition.slotType == auth::kType3SlotType
- && definition.authSchema == auth::kType3Schema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- [[nodiscard]] bool exact_task_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kTaskSlotType
- && definition.componentClass == format::kTaskComponentClass
- && definition.authSchema == format::kTaskAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- [[nodiscard]] bool exact_dialogue_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kDialogueSlotType
- && definition.componentClass == format::kDialogueComponentClass
- && definition.authSchema == format::kDialogueAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0
- && (definition.flags & format::kSlotDialogueCuesExact) != 0;
- }
- /** @return True when one live Slot row is the exact type-68 HUD directive state. */
- [[nodiscard]] bool exact_directive_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == scriptable_auth::kType68SlotType
- && definition.authSchema == scriptable_auth::kType68Schema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is the exact type-71 public-event sensor. */
- [[nodiscard]] bool exact_public_event_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == scriptable_auth::kType71SlotType
- && definition.authSchema == scriptable_auth::kType71Schema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is an exact type-42 performance sensor. */
- [[nodiscard]] bool exact_performance_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == scriptable_auth::kType42SlotType
- && definition.componentClass == scriptable_auth::kType42ComponentClass
- && definition.authSchema == scriptable_auth::kType42Schema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- } // namespace
- /** Shows one generated directive through the exact type-68 Auth schema. */
- [[nodiscard]] int slot_set_directive(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 6> kDeclared{
- "directive", "state", "navpoint", "audience", "waypoint", "progress"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot)) {
- return luaL_error(state, "activity slot is stale or invalid");
- }
- if (!exact_directive_slot(slot)) {
- return luaL_error(state, "activity slot is not an exact type-68 directive state");
- }
- lua_getfield(state, 2, "directive");
- luaL_checktype(state, -1, LUA_TTABLE);
- const lua_Integer slotRow = directive_integer(state, -1, "slot_row");
- const lua_Integer nameHash = directive_integer(state, -1, "name_hash");
- const lua_Integer element = directive_integer(state, -1, "element");
- lua_pop(state, 1);
- const lua_Integer directiveState = optional_integer_argument(state, "state", 0);
- if (slotRow < 0 || slotRow > (std::numeric_limits<std::uint32_t>::max)() || nameHash < 0
- || nameHash > (std::numeric_limits<std::uint32_t>::max)() || element < 0
- || element > (std::numeric_limits<std::int32_t>::max)() || directiveState < 0
- || directiveState > 2) {
- return luaL_error(state, "directive declaration is outside its native field width");
- }
- Impl* const impl = impl_from_state(state);
- DirectiveElementDefinition resolved{};
- if (impl == nullptr || impl->definitions.resolveDirectiveElement == nullptr
- || !impl->definitions.resolveDirectiveElement(impl->definitions.context,
- static_cast<std::uint32_t>(slotRow),
- static_cast<std::uint32_t>(nameHash),
- static_cast<std::int32_t>(element),
- resolved)
- || resolved.slotRow != slot.nativeRow) {
- return luaL_error(state, "directive does not belong to this slot");
- }
- scriptable_auth::Type68Preset preset{.nameHash = resolved.nameHash,
- .elementIndex = resolved.elementIndex,
- .state = static_cast<std::int8_t>(directiveState),
- .visible = true};
- // Progress rides only on an element that declares a counter; the HUD ignores it otherwise.
- if (push_argument(state, "progress") != LUA_TNIL) {
- luaL_checktype(state, -1, LUA_TTABLE);
- const std::size_t count = lua_rawlen(state, -1);
- if ((resolved.flags & format::kDirectiveElementCounter) == 0) {
- return luaL_error(state, "directive progress requires an element with a counter");
- }
- if (count == 0 || count > preset.progress.size()) {
- return luaL_error(state, "directive progress holds one to four integers");
- }
- for (std::size_t index = 1; index <= count; ++index) {
- lua_rawgeti(state, -1, static_cast<lua_Integer>(index));
- if (!lua_isinteger(state, -1)) {
- return luaL_error(state, "directive progress values must be integers");
- }
- const lua_Integer value = lua_tointeger(state, -1);
- if (value < (std::numeric_limits<std::int32_t>::min)()
- || value > (std::numeric_limits<std::int32_t>::max)()) {
- return luaL_error(state, "directive progress is outside its native field width");
- }
- preset.progress[index - 1] = static_cast<std::int32_t>(value);
- lua_pop(state, 1);
- }
- }
- lua_pop(state, 1);
- if (!optional_slot_reference(
- state, "audience", scriptable_auth::kType70SlotType, preset.audience)) {
- return luaL_error(state,
- "directive audience requires an authored type-70 engagement sensor");
- }
- if (!optional_slot_reference(
- state, "navpoint", scriptable_auth::kType47SlotType, preset.navpoint)) {
- return luaL_error(state, "directive navpoint requires a current authored type-47 slot");
- }
- if (!optional_slot_reference(
- state, "waypoint", scriptable_auth::kType60SlotType, preset.waypoint)) {
- return luaL_error(state, "directive waypoint requires a current authored type-60 volume");
- }
- if (preset.navpoint.slotIndex >= 0) {
- lua_getfield(state, 2, "navpoint");
- const auto* const navpoint =
- static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
- SlotDefinition navpointSlot{};
- if (current_slot(state, *navpoint, navpointSlot)) {
- preset.navpointNameHash = navpointSlot.nameHash;
- preset.navpointBubbleHash = navpointSlot.bubbleHash;
- }
- lua_pop(state, 1);
- }
- std::array<std::byte, scriptable_auth::kType68ByteCount> body{};
- std::size_t written = 0;
- if (!scriptable_auth::encode_type68(preset, body, written) || written != body.size()) {
- return luaL_error(state, "directive native encoder failed");
- }
- return queue_slot_auth(
- state, slot, scriptable_auth::kType68Schema, scriptable_auth::kType68BitCount, body);
- }
- /** Hides the active directive without naming an authored element. */
- [[nodiscard]] int slot_clear_directives(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || !exact_directive_slot(slot)) {
- return luaL_error(state, "activity slot is not an exact type-68 directive state");
- }
- scriptable_auth::Type68Preset preset{};
- preset.visible = false;
- std::array<std::byte, scriptable_auth::kType68ByteCount> body{};
- std::size_t written = 0;
- if (!scriptable_auth::encode_type68(preset, body, written) || written != body.size()) {
- return luaL_error(state, "directive native encoder failed");
- }
- return queue_slot_auth(
- state, slot, scriptable_auth::kType68Schema, scriptable_auth::kType68BitCount, body);
- }
- /** Selects one authored section in a native music sensor's selection mask. */
- [[nodiscard]] int slot_set_music_section(lua_State* state) {
- namespace music = middleware::bap::activity_message::music_section;
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- // Only these named arguments belong to this API.
- static constexpr std::array<std::string_view, 2> kDeclared{"section", "enabled"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- const lua_Integer section = checked_integer_argument(state, "section");
- if (!current_slot(state, *handle, slot) || slot.slotType != music::kSlotType
- || slot.componentClass != music::kComponentClass || slot.authSchema != music::kSchema
- || (slot.flags & format::kSlotSchemaJoinExact) == 0 || section < 0
- || section >= static_cast<lua_Integer>(music::kSectionCount)) {
- return luaL_error(state, "music requires an exact type-11 sensor and a section index");
- }
- std::array<std::byte, music::kBytes> body{};
- std::size_t written = 0;
- if (!music::encode(static_cast<std::uint8_t>(section),
- optional_boolean_argument(state, "enabled", true),
- body,
- written)) {
- return luaL_error(state, "music section encoder failed");
- }
- return queue_slot_auth(state, slot, music::kSchema, music::kBits, body);
- }
- /** Names the player, the event area and the leave timeout one public-event sensor watches. */
- [[nodiscard]] int slot_set_public_event_state(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || !exact_public_event_slot(slot)) {
- return luaL_error(state, "activity slot is not an exact type-71 public-event sensor");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 4> kDeclared{
- "state", "player", "area", "leave_seconds"};
- refuse_unknown_arguments(state, kDeclared);
- const lua_Integer eventState = optional_integer_argument(state, "state", 0);
- if (eventState < (std::numeric_limits<std::int32_t>::min)()
- || eventState > (std::numeric_limits<std::int32_t>::max)()) {
- return luaL_error(state, "state must be a 32-bit signed integer");
- }
- // Absent, the sensor watches this link's own player. Present, it is a decimal string, the
- // form every 64-bit key crosses into Lua in.
- std::uint64_t player = impl_from_state(state)->identity.playerKey;
- if (push_argument(state, "player") != LUA_TNIL) {
- lua_pop(state, 1);
- const std::string_view playerText = borrowed_string_argument(state, "player");
- const auto parsed =
- std::from_chars(playerText.data(), playerText.data() + playerText.size(), player);
- if (parsed.ec != std::errc{} || parsed.ptr != playerText.data() + playerText.size()) {
- return luaL_error(state, "player must be a decimal player key string");
- }
- lua_pop(state, 1);
- } else {
- lua_pop(state, 1);
- }
- if (player == 0) {
- return luaL_error(state, "no player key is known for this activity link");
- }
- const SlotHandle areaHandle = checked_argument<SlotHandle>(state, "area", kSlotMetatable);
- const lua_Number seconds = checked_number_argument(state, "leave_seconds");
- SlotDefinition area{};
- if (!current_slot(state, areaHandle, area)) {
- return luaL_error(state, "area slot is stale or invalid");
- }
- if (!std::isfinite(seconds) || seconds < 0.0
- || seconds > static_cast<lua_Number>((std::numeric_limits<float>::max)())) {
- return luaL_error(state, "leave_seconds must be a finite non-negative number");
- }
- const scriptable_auth::Type71Body body{
- .state = static_cast<std::int32_t>(eventState),
- .playerIdentity = player,
- .areaRegistryKey = area.registryKey,
- .areaSlotType = static_cast<std::uint8_t>(area.slotType),
- .areaSlotIndex = static_cast<std::uint16_t>(area.slotIndex),
- .leaveSeconds = static_cast<float>(seconds),
- };
- std::array<std::byte, scriptable_auth::kType71ByteCount> bytes{};
- std::size_t written = 0;
- if (!scriptable_auth::encode_type71(body, bytes, written) || written != bytes.size()) {
- return luaL_error(state, "public-event native encoder failed");
- }
- return queue_slot_auth(
- state, slot, scriptable_auth::kType71Schema, scriptable_auth::kType71BitCount, bytes);
- }
- /** Lua `play_sequence` on a slot. Errors unless the slot is an exact type-5 sequence. */
- [[nodiscard]] int slot_play_sequence(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition) || !exact_sequence_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-5 authored sequence");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::playSequence;
- intent.firstRow = definition.nativeRow;
- return queue_intent(state, frame, intent);
- }
- /** Lua `set_cinematic_active` on a slot. Errors unless the slot is an exact cinematic. */
- [[nodiscard]] int slot_set_cinematic_active(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition) || !exact_cinematic_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-6 authored cinematic");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 1> kDeclared{"active"};
- refuse_unknown_arguments(state, kDeclared);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::setCinematicActive;
- intent.firstRow = definition.nativeRow;
- intent.active = optional_boolean_argument(state, "active", true);
- return queue_intent(state, frame, intent);
- }
- /** Queues the parameter-free reset of every objective lane owned by one type-3 slot. */
- [[nodiscard]] int slot_reset_objectives(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition) || !exact_objective_reset_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-3 objective reset");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::resetObjectives;
- intent.firstRow = definition.nativeRow;
- return queue_intent(state, frame, intent);
- }
- /** Advances the exact objective bit authored by one type-38 task slot. */
- [[nodiscard]] int slot_advance_task(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition) || !exact_task_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-38 authored task");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::advanceTask;
- intent.firstRow = definition.nativeRow;
- return queue_intent(state, frame, intent);
- }
- /**
- * Starts one state of the actor a type-42 sensor drives. With no `state` the slot's target must
- * declare exactly one state; a generated `state` row must belong to this slot.
- */
- [[nodiscard]] int slot_play_performance(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition) || !exact_performance_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-42 performance sensor");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 1> kDeclared{"state"};
- refuse_unknown_arguments(state, kDeclared);
- lua_Integer slotRow = static_cast<lua_Integer>(definition.nativeRow);
- lua_Integer nameHash = 0;
- if (!lua_isnoneornil(state, 2)) {
- lua_getfield(state, 2, "state");
- if (!lua_isnil(state, -1)) {
- luaL_checktype(state, -1, LUA_TTABLE);
- slotRow = directive_integer(state, -1, "slot_row");
- nameHash = directive_integer(state, -1, "name_hash");
- }
- lua_pop(state, 1);
- }
- if (slotRow < 0 || slotRow > (std::numeric_limits<std::uint32_t>::max)() || nameHash < 0
- || nameHash > (std::numeric_limits<std::uint32_t>::max)()) {
- return luaL_error(state, "performance state declaration is outside its native field width");
- }
- Impl* const impl = impl_from_state(state);
- PerformanceStateDefinition resolved{};
- if (impl == nullptr || impl->definitions.resolvePerformanceState == nullptr
- || !impl->definitions.resolvePerformanceState(impl->definitions.context,
- static_cast<std::uint32_t>(slotRow),
- static_cast<std::uint32_t>(nameHash),
- resolved)
- || resolved.slotRow != definition.nativeRow) {
- return luaL_error(state, "performance state does not belong to this slot");
- }
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::playPerformance;
- intent.firstRow = definition.nativeRow;
- intent.secondRow = resolved.nameHash;
- return queue_intent(state, frame, intent);
- }
- /** Fires one bounded cue from an exact type-53 authored dialogue list. */
- [[nodiscard]] int slot_play_dialogue_cue(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition) || !exact_dialogue_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-53 authored dialogue");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 2> kDeclared{"cue", "filter"};
- refuse_unknown_arguments(state, kDeclared);
- const lua_Integer cue = checked_integer_argument(state, "cue");
- if (cue < 0 || cue > (std::numeric_limits<std::uint16_t>::max)()) {
- return luaL_error(state, "dialogue cue must be a non-negative 16-bit integer");
- }
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::playDialogueCue;
- intent.firstRow = definition.nativeRow;
- intent.secondRow = static_cast<std::uint32_t>(cue);
- lua_getfield(state, 2, "filter");
- if (!lua_isnil(state, -1)) {
- const auto* const volume =
- static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
- SlotDefinition volumeSlot{};
- if (!current_slot(state, *volume, volumeSlot)
- || volumeSlot.slotType
- != static_cast<std::uint32_t>(scriptable_auth::kType53FilterSlotType)) {
- return luaL_error(state, "dialogue filter requires a current authored type-60 volume");
- }
- intent.dialogueFilterRow = static_cast<std::int32_t>(volumeSlot.nativeRow);
- }
- lua_pop(state, 1);
- return queue_intent(state, frame, intent);
- }
- } // namespace sunrise::server::activity::mission::lua_vm::detail
|