#include #include #include #include #include #include #include #include #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(luaL_checkudata(state, 1, kSlotMetatable)); // Named arguments this call accepts. Any other key is refused. static constexpr std::array 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::max)() || nameHash < 0 || nameHash > (std::numeric_limits::max)() || element < 0 || element > (std::numeric_limits::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(slotRow), static_cast(nameHash), static_cast(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(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(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::min)() || value > (std::numeric_limits::max)()) { return luaL_error(state, "directive progress is outside its native field width"); } preset.progress[index - 1] = static_cast(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(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 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(luaL_checkudata(state, 1, kSlotMetatable)); // Named arguments this call accepts. Any other key is refused. static constexpr std::array 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 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(luaL_checkudata(state, 1, kSlotMetatable)); // Only these named arguments belong to this API. static constexpr std::array 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(music::kSectionCount)) { return luaL_error(state, "music requires an exact type-11 sensor and a section index"); } std::array body{}; std::size_t written = 0; if (!music::encode(static_cast(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(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 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::min)() || eventState > (std::numeric_limits::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(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((std::numeric_limits::max)())) { return luaL_error(state, "leave_seconds must be a finite non-negative number"); } const scriptable_auth::Type71Body body{ .state = static_cast(eventState), .playerIdentity = player, .areaRegistryKey = area.registryKey, .areaSlotType = static_cast(area.slotType), .areaSlotIndex = static_cast(area.slotIndex), .leaveSeconds = static_cast(seconds), }; std::array 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(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 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(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 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(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 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(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 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(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 kDeclared{"state"}; refuse_unknown_arguments(state, kDeclared); lua_Integer slotRow = static_cast(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::max)() || nameHash < 0 || nameHash > (std::numeric_limits::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(slotRow), static_cast(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(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 kDeclared{"cue", "filter"}; refuse_unknown_arguments(state, kDeclared); const lua_Integer cue = checked_integer_argument(state, "cue"); if (cue < 0 || cue > (std::numeric_limits::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(cue); lua_getfield(state, 2, "filter"); if (!lua_isnil(state, -1)) { const auto* const volume = static_cast(luaL_checkudata(state, -1, kSlotMetatable)); SlotDefinition volumeSlot{}; if (!current_slot(state, *volume, volumeSlot) || volumeSlot.slotType != static_cast(scriptable_auth::kType53FilterSlotType)) { return luaL_error(state, "dialogue filter requires a current authored type-60 volume"); } intent.dialogueFilterRow = static_cast(volumeSlot.nativeRow); } lua_pop(state, 1); return queue_intent(state, frame, intent); } } // namespace sunrise::server::activity::mission::lua_vm::detail