| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- #include <array>
- #include <bit>
- #include <cstddef>
- #include <cstdint>
- #include <limits>
- #include <span>
- #include <string_view>
- #include "../../../middleware/bap/activity_message/damage_monitor_auth.h"
- #include "../../../middleware/bap/activity_message/darkness_zone_auth.h"
- #include "../../../middleware/bap/activity_message/ghost_link_auth.h"
- #include "../../../middleware/bap/activity_message/interactable_object_auth.h"
- #include "../../../middleware/encoding/bit_writer.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 {
- /** Templates need a positive counter; the native reducer chooses the transported value. */
- constexpr std::int32_t kUncommittedCounter = 1;
- /** @return True when one live Slot row is an exact type-23 device. */
- [[nodiscard]] bool exact_device_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kDeviceSlotType
- && definition.componentClass == format::kDeviceComponentClass
- && definition.senseSchema == format::kDeviceSenseSchema
- && definition.authSchema == format::kDeviceAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is an exact type-4 authored object. */
- [[nodiscard]] bool exact_object_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kObjectSlotType
- && definition.componentClass == format::kObjectComponentClass
- && definition.senseSchema == format::kObjectSenseSchema
- && definition.authSchema == format::kObjectAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is an exact type-65 Ghost-link sensor. */
- [[nodiscard]] bool exact_ghost_link_slot(const SlotDefinition& definition) noexcept {
- namespace ghost = middleware::bap::activity_message::ghost_link;
- return definition.slotType == ghost::kSlotType
- && definition.componentClass == ghost::kComponentClass
- && definition.senseSchema == ghost::kSenseSchema
- && definition.authSchema == ghost::kAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is an exact type-31 configured trigger. */
- [[nodiscard]] bool exact_trigger_slot(const SlotDefinition& definition) noexcept {
- namespace auth = middleware::bap::activity_message::scriptable_auth;
- return definition.slotType == auth::kType31SlotType
- && definition.authSchema == auth::kType31Schema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /** @return True when one live Slot row is an exact type-30 occupancy condition. */
- [[nodiscard]] bool exact_occupancy_slot(const SlotDefinition& definition) noexcept {
- return definition.slotType == format::kOccupancySlotType
- && definition.componentClass == format::kOccupancyComponentClass
- && definition.senseSchema == format::kOccupancySenseSchema
- && definition.authSchema == format::kOccupancyAuthSchema
- && (definition.flags & format::kSlotSchemaJoinExact) != 0;
- }
- /**
- * Reads the optional `with` list: more object slots that answer on this one's revision.
- * @return False with the Lua error already raised.
- */
- [[nodiscard]] bool parse_object_burst(lua_State* state, Intent& intent) {
- lua_getfield(state, 2, "with");
- if (lua_isnoneornil(state, -1)) {
- lua_pop(state, 1);
- return true;
- }
- if (!lua_istable(state, -1)) {
- lua_pop(state, 1);
- static_cast<void>(luaL_argerror(state, 2, "object burst list is not a table"));
- return false;
- }
- const int list = lua_gettop(state);
- const lua_Integer count = static_cast<lua_Integer>(lua_rawlen(state, list));
- if (count < 0
- || static_cast<std::size_t>(count)
- > ::sunrise::state::activity::mission::kIntentBurstCapacity) {
- lua_pop(state, 1);
- static_cast<void>(luaL_argerror(state, 2, "object burst list is too long"));
- return false;
- }
- for (lua_Integer entry = 1; entry <= count; ++entry) {
- lua_rawgeti(state, list, entry);
- SlotDefinition member{};
- const bool resolved =
- resolve_slot(state, lua_gettop(state), member) && exact_object_slot(member);
- lua_pop(state, 1);
- if (!resolved) {
- lua_pop(state, 1);
- static_cast<void>(
- luaL_argerror(state, 2, "object burst names a slot that is not an exact type-4"));
- return false;
- }
- intent.burstRows[static_cast<std::size_t>(entry - 1)] = member.nativeRow;
- }
- lua_pop(state, 1);
- intent.burstRowCount = static_cast<std::uint8_t>(count);
- return true;
- }
- /** The occupancy Auth body is a fixed 87 bits: a 55-bit client reference then one int32. */
- constexpr std::size_t kOccupancyAuthBitCount = 87;
- constexpr std::size_t kOccupancyAuthByteCount = 11;
- /** Volumes one filter may test. Every predicate still shares the type-34 capacity. */
- constexpr std::size_t kMaximumFilterVolumes = 5;
- /** Type-34 predicate modes: 0 tests the flag or reference as given, 1 tests inside a volume. */
- constexpr std::int8_t kFilterModeDirect = 0;
- constexpr std::int8_t kFilterModeInside = 1;
- /** Appends one predicate. @return False when the type-34 body is already full. */
- [[nodiscard]] bool append_predicate(scriptable_auth::Type34Body& body,
- const scriptable_auth::Type34Predicate& predicate) noexcept {
- if (body.count >= scriptable_auth::kType34PredicateCapacity) {
- return false;
- }
- body.predicates[body.count++] = predicate;
- return true;
- }
- } // namespace
- /**
- * Sets the object filter and caller value carried by one type-30 condition. Without a filter the
- * reference is absent, and the client measures its default player set.
- */
- [[nodiscard]] int slot_set_occupancy_condition(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, 2> kDeclared{"filter", "value"};
- refuse_unknown_arguments(state, kDeclared);
- SlotHandle reference{};
- const bool filtered = optional_argument(state, "filter", kSlotMetatable, reference);
- const lua_Integer value = checked_integer_argument(state, "value");
- SlotDefinition slot{};
- SlotDefinition playerSet{};
- if (!current_slot(state, *handle, slot)
- || (filtered && !current_slot(state, reference, playerSet))) {
- return luaL_error(state, "activity slot is stale or invalid");
- }
- if (!exact_occupancy_slot(slot)) {
- return luaL_error(state, "activity slot is not an exact type-30 occupancy condition");
- }
- // The Auth field is a full-range int32 and lua_Integer is wider, so the lane is still checked.
- if (value < (std::numeric_limits<std::int32_t>::min)()
- || value > (std::numeric_limits<std::int32_t>::max)()) {
- return luaL_error(state, "value must be a 32-bit signed integer");
- }
- std::array<std::byte, kOccupancyAuthByteCount> body{};
- middleware::encoding::bits::Writer writer(body);
- const std::uint32_t encodedValue =
- std::bit_cast<std::uint32_t>(static_cast<std::int32_t>(value)) + 0x80000000U;
- const bool referenceWritten =
- filtered ? writer.write(playerSet.registryKey, 32)
- && writer.write(static_cast<std::uint32_t>(playerSet.slotType) + 1U, 7)
- && writer.write(static_cast<std::uint32_t>(playerSet.slotIndex) + 32768U, 16)
- : auth_fields::write_absent_client_ref(writer);
- if (!referenceWritten || !writer.write(encodedValue, 32)
- || writer.bit_count() != kOccupancyAuthBitCount) {
- return luaL_error(state, "occupancy condition native encoder failed");
- }
- return queue_slot_auth(state, slot, format::kOccupancyAuthSchema, kOccupancyAuthBitCount, body);
- }
- /** Enables the native darkness restriction; roster assembly supplies its matching bubble. */
- [[nodiscard]] int slot_set_darkness_zone(lua_State* state) {
- namespace darkness = middleware::bap::activity_message::darkness_zone;
- 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{"enabled", "wipe_seconds"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || slot.slotType != darkness::kSlotType
- || slot.componentClass != darkness::kComponentClass || slot.authSchema != darkness::kSchema
- || (slot.flags & format::kSlotSchemaJoinExact) == 0) {
- return luaL_error(state, "darkness zone requires the exact hard-wipe globals sensor");
- }
- const lua_Integer wipe = optional_integer_argument(state, "wipe_seconds", darkness::kNoWipe);
- std::array<std::byte, darkness::kBytes> body{};
- if (wipe < darkness::kNoWipe || wipe > darkness::kMaximumWipeSeconds
- || !darkness::encode(
- optional_boolean_argument(state, "enabled", false), body, static_cast<int>(wipe))) {
- return luaL_error(state, "darkness zone encoder failed");
- }
- return queue_slot_auth(state, slot, darkness::kSchema, darkness::kBits, body);
- }
- /**
- * Native typed object filters: players, one or several objects, a squad, and volume intersection.
- * Every predicate shares the type-34 capacity; a filter that would exceed it is refused.
- */
- [[nodiscard]] int slot_set_object_filter(lua_State* state) {
- namespace auth = scriptable_auth;
- 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, 6> kDeclared{
- "players", "target", "targets", "squad", "inside", "inside_any"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || slot.slotType != auth::kType34SlotType
- || slot.authSchema != auth::kType34Schema) {
- return luaL_error(state, "object filter requires an authored type-34 sensor");
- }
- constexpr const char* kCapacityError = "object filter exceeds the type-34 predicate capacity";
- auth::Type34Body body{};
- lua_getfield(state, 2, "inside_any");
- const bool volumes = !lua_isnil(state, -1);
- if (volumes) {
- luaL_checktype(state, -1, LUA_TTABLE);
- const std::size_t count = lua_rawlen(state, -1);
- if (count == 0 || count > kMaximumFilterVolumes) {
- return luaL_error(state, "inside_any volume count is outside the filter capacity");
- }
- for (std::size_t index = 1; index <= count; ++index) {
- lua_rawgeti(state, -1, static_cast<lua_Integer>(index));
- const auto* const volumeHandle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
- SlotDefinition volume{};
- if (!current_slot(state, *volumeHandle, volume)
- || volume.slotType != auth::kType60SlotType) {
- return luaL_error(state, "inside_any requires authored type-60 volumes");
- }
- if (!append_predicate(
- body,
- auth::Type34ModeFlagSlotRef{kFilterModeDirect,
- true,
- {volume.registryKey,
- static_cast<std::int8_t>(auth::kType60SlotType),
- static_cast<std::int16_t>(volume.slotIndex)}})) {
- return luaL_error(state, kCapacityError);
- }
- lua_pop(state, 1);
- }
- }
- lua_pop(state, 1);
- if (optional_boolean_argument(state, "players", false)
- && !append_predicate(body, auth::Type34ModeOnlyB{static_cast<std::int8_t>(volumes)})) {
- return luaL_error(state, kCapacityError);
- }
- auth::Type2LaneClientRef target{};
- if (!optional_slot_reference(state, "target", auth::kType4SlotType, target)) {
- return luaL_error(state, "filter target must be an authored type-4 object");
- }
- if (target.slotIndex >= 0
- && !append_predicate(body, auth::Type34ModeSlotRefC{kFilterModeDirect, target})) {
- return luaL_error(state, kCapacityError);
- }
- // Several authored objects at once (a beam hop-on names every crystal it tethers).
- lua_getfield(state, 2, "targets");
- if (!lua_isnil(state, -1)) {
- luaL_checktype(state, -1, LUA_TTABLE);
- const std::size_t count = lua_rawlen(state, -1);
- if (count == 0) {
- return luaL_error(state, "targets must name at least one object");
- }
- for (std::size_t index = 1; index <= count; ++index) {
- lua_rawgeti(state, -1, static_cast<lua_Integer>(index));
- const auto* const targetHandle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
- SlotDefinition object{};
- if (!current_slot(state, *targetHandle, object)
- || object.slotType != auth::kType4SlotType
- || object.slotIndex > auth_fields::kMaximumClientRefIndex) {
- return luaL_error(state, "targets require authored type-4 objects");
- }
- if (!append_predicate(
- body,
- auth::Type34ModeSlotRefC{kFilterModeDirect,
- {object.registryKey,
- static_cast<std::int8_t>(auth::kType4SlotType),
- static_cast<std::int16_t>(object.slotIndex)}})) {
- return luaL_error(state, kCapacityError);
- }
- lua_pop(state, 1);
- }
- }
- lua_pop(state, 1);
- // A squad reference selects the boss a shield or tether hop-on hosts on.
- auth::Type2LaneClientRef squad{};
- if (!optional_slot_reference(state, "squad", format::kSquadSlotType, squad)) {
- return luaL_error(state, "filter squad must be an authored type-1 squad");
- }
- if (squad.slotIndex >= 0
- && !append_predicate(body, auth::Type34ModeSlotRefC{kFilterModeDirect, squad})) {
- return luaL_error(state, kCapacityError);
- }
- auth::Type2LaneClientRef inside{};
- if (!optional_slot_reference(state, "inside", auth::kType60SlotType, inside)) {
- return luaL_error(state, "filter inside must be an authored type-60 volume");
- }
- if (inside.slotIndex >= 0
- && !append_predicate(body, auth::Type34ModeFlagSlotRef{kFilterModeInside, false, inside})) {
- return luaL_error(state, kCapacityError);
- }
- std::array<std::byte, auth::kType34MaximumByteCount> bytes{};
- std::size_t written = 0;
- std::size_t bits = 0;
- if (!auth::encode_type34(body, bytes, written, bits)) {
- return luaL_error(state, "object filter encoder failed");
- }
- return queue_slot_auth(state, slot, auth::kType34Schema, bits, std::span(bytes).first(written));
- }
- /** Binds an authored damage monitor to one exact object; a new revision re-binds it. */
- [[nodiscard]] int slot_watch_damage(lua_State* state) {
- namespace damage = middleware::bap::activity_message::damage_monitor;
- 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, 1> kDeclared{"target"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || slot.slotType != damage::kSlotType
- || slot.authSchema != damage::kAuthSchema) {
- return luaL_error(state, "damage watch requires an authored type-20 monitor");
- }
- lua_getfield(state, 2, "target");
- const auto* const targetHandle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, -1, kSlotMetatable));
- SlotDefinition target{};
- const bool exact = current_slot(state, *targetHandle, target) && exact_object_slot(target);
- lua_pop(state, 1);
- if (!exact) {
- return luaL_error(state, "damage target must be an authored type-4 object");
- }
- std::array<std::byte, damage::kBytes> body{};
- std::size_t written = 0;
- if (!damage::encode(target.registryKey,
- static_cast<std::uint16_t>(target.slotIndex),
- kUncommittedCounter,
- body,
- written)) {
- return luaL_error(state, "damage monitor encoder failed");
- }
- return queue_slot_auth(
- state, slot, damage::kAuthSchema, damage::kBits, body, IntentKind::watchDamage);
- }
- /** Spawns authored entry zero and subscribes to accepted native player use. */
- [[nodiscard]] int slot_set_interactable_object(lua_State* state) {
- namespace object = middleware::bap::activity_message::interactable_object;
- 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, 3> kDeclared{"track_owner", "active", "used"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || !exact_object_slot(slot)) {
- return luaL_error(state, "interaction requires an exact authored object");
- }
- const bool trackOwner = optional_boolean_argument(state, "track_owner", false);
- std::array<std::byte, object::kOwnerBytes> body{};
- std::size_t written = 0;
- if (!object::encode(kUncommittedCounter,
- body,
- written,
- trackOwner,
- optional_boolean_argument(state, "active", true),
- optional_boolean_argument(state, "used", false))) {
- return luaL_error(state, "interactable object encoder failed");
- }
- return queue_slot_auth(state,
- slot,
- object::kSchema,
- trackOwner ? object::kOwnerBits : object::kBits,
- std::span(body).first(written),
- IntentKind::setInteractableObject);
- }
- /** Arms or disarms the authored Ghost-link scan. The Host stamps the generation this body needs. */
- [[nodiscard]] int slot_set_ghost_link(lua_State* state) {
- namespace ghost = middleware::bap::activity_message::ghost_link;
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- // Only this named argument belongs to this API.
- static constexpr std::array<std::string_view, 1> kDeclared{"active"};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || !exact_ghost_link_slot(slot)) {
- return luaL_error(state, "activity slot is not an exact type-65 Ghost-link sensor");
- }
- std::array<std::byte, ghost::kByteCount> body{};
- std::size_t written = 0;
- if (!ghost::encode(
- kUncommittedCounter, optional_boolean_argument(state, "active", true), body, written)) {
- return luaL_error(state, "Ghost-link encoder failed");
- }
- return queue_slot_auth(state,
- slot,
- ghost::kAuthSchema,
- ghost::kBitCount,
- std::span(body).first(written),
- IntentKind::setGhostLink);
- }
- /**
- * Reads the level the client last reported for one Ghost link.
- * @return The generation, progress and active fields, or nil before all three were reported.
- */
- [[nodiscard]] int slot_ghost_link(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- // The read has no parameters, so an unsafe call cannot be spelled.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot) || !exact_ghost_link_slot(slot)) {
- return luaL_error(state, "activity slot is not an exact type-65 Ghost-link sensor");
- }
- const Impl* const impl = impl_from_state(state);
- for (std::size_t index = 0; index < impl->ghostLevelCount; ++index) {
- const GhostLinkRow& row = impl->ghostLevels[index];
- if (row.registryKey != slot.registryKey || row.objectTag != slot.objectTag
- || static_cast<std::uint32_t>(row.slotIndex) != slot.slotIndex
- || row.level.seen != kGhostSeenAll) {
- continue;
- }
- lua_createtable(state, 0, 3);
- lua_pushinteger(state, row.level.generation);
- lua_setfield(state, -2, "generation");
- lua_pushnumber(state, row.level.progress);
- lua_setfield(state, -2, "progress");
- lua_pushboolean(state, row.level.active);
- lua_setfield(state, -2, "active");
- return 1;
- }
- lua_pushnil(state);
- return 1;
- }
- /** Instantiates or removes the package-owned entry one type-4 slot selects. */
- [[nodiscard]] int slot_set_object_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_object_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-4 authored object");
- }
- // Named arguments this call accepts. Any other key is refused.
- static constexpr std::array<std::string_view, 2> kDeclared{"active", "with"};
- refuse_unknown_arguments(state, kDeclared);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::setObjectActive;
- intent.firstRow = definition.nativeRow;
- intent.entryIndex = 0;
- intent.active = optional_boolean_argument(state, "active", true);
- if (!parse_object_burst(state, intent)) {
- return 0;
- }
- return queue_intent(state, frame, intent);
- }
- /** Sets one verified type-23 channel through the same guarded route. */
- [[nodiscard]] int slot_set_channel(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition)) {
- return luaL_error(state, "activity slot is stale or invalid");
- }
- if (!exact_device_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-23 device");
- }
- // Both parameters carry their own bound, so neither the lane nor the range is tested here.
- static constexpr std::array<std::string_view, 3> kDeclared{"channel", "value", "snap"};
- refuse_unknown_arguments(state, kDeclared);
- const DeviceChannelHandle channel =
- checked_argument<DeviceChannelHandle>(state, "channel", kDeviceChannelMetatable);
- const UnitScalarHandle value =
- checked_argument<UnitScalarHandle>(state, "value", kUnitScalarMetatable);
- const bool snap = optional_boolean_argument(state, "snap", false);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::setDeviceChannel;
- intent.firstRow = definition.nativeRow;
- intent.deviceValue = value.value;
- intent.deviceChannel = channel.channel;
- intent.deviceSnap = snap;
- return queue_intent(state, frame, intent);
- }
- /** Applies one named device transition through the same guarded channel route. */
- [[nodiscard]] int slot_transition(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition)) {
- return luaL_error(state, "activity slot is stale or invalid");
- }
- if (!exact_device_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-23 device");
- }
- // The handle is a row of the closed vocabulary, so no word is matched here.
- static constexpr std::array<std::string_view, 2> kDeclared{"transition", "snap"};
- refuse_unknown_arguments(state, kDeclared);
- const DeviceTransitionHandle requested =
- checked_argument<DeviceTransitionHandle>(state, "transition", kDeviceTransitionMetatable);
- const bool snap = optional_boolean_argument(state, "snap", false);
- const DeviceTransition& transition = kDeviceTransitions[requested.row];
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::setDeviceChannel;
- intent.firstRow = definition.nativeRow;
- intent.deviceValue = transition.value;
- intent.deviceChannel = static_cast<std::uint8_t>(transition.channel);
- intent.deviceSnap = snap;
- return queue_intent(state, frame, intent);
- }
- /**
- * Fires one type-31 configured trigger. One authored typed reference must be live and eligible.
- * The pulse carries no caller value. `enabled` is fixed true and the auxiliary stays zero, and
- * the Host mints the generation from the target's own guard so a replay cannot reorder.
- */
- [[nodiscard]] int slot_fire_trigger(lua_State* state) {
- const auto* const handle =
- static_cast<const SlotHandle*>(luaL_checkudata(state, 1, kSlotMetatable));
- SlotDefinition definition{};
- if (!current_slot(state, *handle, definition)) {
- return luaL_error(state, "activity slot is stale or invalid");
- }
- if (!exact_trigger_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-31 trigger");
- }
- // The pulse has no parameters, so an unsafe call cannot be spelled.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- CallFrame& frame = active_frame(state);
- Intent intent{};
- intent.kind = IntentKind::fireTrigger;
- intent.firstRow = definition.nativeRow;
- intent.active = true;
- return queue_intent(state, frame, intent);
- }
- /** Disarms one type-31 trigger, so an arm after its first fire stops reporting. */
- [[nodiscard]] int slot_disarm_trigger(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_trigger_slot(definition)) {
- return luaL_error(state, "activity slot is not an exact type-31 trigger");
- }
- // The disarm has no parameters, so any named argument is refused.
- static constexpr std::array<std::string_view, 0> kDeclared{};
- refuse_unknown_arguments(state, kDeclared);
- Intent intent{};
- intent.kind = IntentKind::fireTrigger;
- intent.firstRow = definition.nativeRow;
- intent.active = false;
- return queue_intent(state, active_frame(state), intent);
- }
- /**
- * A newer desired command hides every earlier applied result on the same device channel.
- * @param state Lua call holding the device slot and named channel argument.
- * @return True only after a report satisfied the latest desired request.
- */
- int slot_applied(lua_State* state) {
- const auto* 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, 1> kDeclared{"channel"};
- refuse_unknown_arguments(state, kDeclared);
- const auto channel =
- checked_argument<DeviceChannelHandle>(state, "channel", kDeviceChannelMetatable);
- SlotDefinition slot{};
- if (!current_slot(state, *handle, slot)
- || slot.slotType != ::sunrise::state::activity_sdk::format::kDeviceSlotType) {
- return luaL_error(state, "device completion requires an authored device slot");
- }
- const Impl* impl = impl_from_state(state);
- const auto pending = [&](const auto& intent) noexcept {
- return intent.kind == IntentKind::setDeviceChannel && intent.firstRow == slot.nativeRow
- && intent.deviceChannel == channel.channel
- && intent.attemptGeneration == impl->attempt.generation;
- };
- for (const auto& intent : active_frame(state).candidate.intents) {
- if (pending(intent)) {
- lua_pushboolean(state, false);
- return 1;
- }
- }
- for (std::size_t index = impl->outboxRead; index < impl->outbox.size(); ++index) {
- if (pending(impl->outbox[index])) {
- lua_pushboolean(state, false);
- return 1;
- }
- }
- bool applied = false;
- for (const auto& request : impl->deviceRequests) {
- if (request.slotRow == slot.nativeRow && request.channel == channel.channel
- && request.attemptGeneration == impl->attempt.generation
- && impl->deviceRequestGeneration != 0
- && request.sourceGeneration == impl->deviceRequestGeneration) {
- applied = request.applied;
- break;
- }
- }
- lua_pushboolean(state, applied);
- return 1;
- }
- } // namespace sunrise::server::activity::mission::lua_vm::detail
|