activity_sdk_scene_cast.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #include <limits>
  2. #include "../../middleware/gameplay/external/actor_entity_registry.h"
  3. #include "activity_sdk_scene_dependencies.h"
  4. #include "activity_sdk_scene_spawn.h"
  5. namespace sunrise::server::activity::activity_sdk_mission {
  6. namespace {
  7. namespace sdk = state::activity_sdk;
  8. namespace auth = middleware::bap::activity_message::scriptable_auth;
  9. using Source = state::gameplay::entity_identity::ActorSourceReference;
  10. /** Only exact Type 2 rows may enter a scene's inverse parent join. */
  11. [[nodiscard]] bool exact_actor(const sdk::format::Slot& actor) noexcept {
  12. return actor.slotType == auth::kType2SlotType
  13. && actor.componentClass == auth::kType2ComponentClass
  14. && actor.authSchema == auth::kType2Schema && actor.senseSchema == auth::kType2SenseSchema
  15. && (actor.flags & sdk::format::kSlotSchemaJoinExact) != 0
  16. && actor.slotIndex <= (std::numeric_limits<std::uint16_t>::max)();
  17. }
  18. /** A source must name one squad occurrence in the scene's own object and state. */
  19. [[nodiscard]] SceneStatus source_squad(const sdk::Catalog& catalog,
  20. std::uint32_t occurrenceRow,
  21. const Source& source,
  22. std::uint32_t& output) noexcept {
  23. output = sdk::format::kAbsentIndex;
  24. const auto& occurrence = catalog.occurrences()[occurrenceRow];
  25. for (std::size_t index = 0; index < catalog.squads().size(); ++index) {
  26. const auto& squad = catalog.squads()[index];
  27. if (squad.scenarioIndex != occurrence.scenarioIndex
  28. || squad.objectIndex != occurrence.objectIndex
  29. || squad.occurrenceIndex >= catalog.occurrences().size()
  30. || squad.slotIndex >= catalog.slots().size()) {
  31. continue;
  32. }
  33. const auto& sourceOccurrence = catalog.occurrences()[squad.occurrenceIndex];
  34. if (sourceOccurrence.scenarioIndex != occurrence.scenarioIndex
  35. || sourceOccurrence.stateIndex != occurrence.stateIndex
  36. || sourceOccurrence.objectIndex != occurrence.objectIndex) {
  37. continue;
  38. }
  39. const auto& slot = catalog.slots()[squad.slotIndex];
  40. if (slot.slotIndex != source.index || slot.slotType != source.type) {
  41. continue;
  42. }
  43. if (output != sdk::format::kAbsentIndex) {
  44. return SceneStatus::ambiguousTarget;
  45. }
  46. output = static_cast<std::uint32_t>(index);
  47. }
  48. return output == sdk::format::kAbsentIndex ? SceneStatus::targetUnavailable
  49. : SceneStatus::ready;
  50. }
  51. /** Retained ownership uses physical identity without a publication route. */
  52. [[nodiscard]] host::ScriptableTarget physical_target(const sdk::Catalog& catalog,
  53. std::uint32_t slotRow) noexcept {
  54. const auto& slot = catalog.slots()[slotRow];
  55. const auto& object = catalog.objects()[slot.objectIndex];
  56. return {.objectTag = object.objectTag,
  57. .registryKey = object.objectKey,
  58. .authSchema = slot.authSchema,
  59. .slotIndex = static_cast<std::uint16_t>(slot.slotIndex),
  60. .slotType = static_cast<std::uint8_t>(slot.slotType)};
  61. }
  62. } // namespace
  63. /**
  64. * Resolves the complete cast before exposing any pair. A squad participant joins only when one
  65. * exact actor control in the scene's own state resolves to it; the client binds every other role
  66. * from its content, so such a participant is counted as omitted, not refused.
  67. * @param catalog Authenticated SDK data.
  68. * @param world Exact generated package references.
  69. * @param occurrenceRow Selected scene occurrence.
  70. * @param sceneSlotRow Type 43 slot owned by that occurrence.
  71. * @param output Receives the complete plan; cleared on failure.
  72. * @return Ready only when every cast squad has exactly one actor control.
  73. */
  74. SceneStatus collect_scene_spawn_plan(const sdk::Catalog& catalog,
  75. const state::build_data::scriptables::Snapshot& world,
  76. std::uint32_t occurrenceRow,
  77. std::uint32_t sceneSlotRow,
  78. SceneSpawnPlan& output) noexcept {
  79. output = {};
  80. const auto occurrences = catalog.occurrences();
  81. const auto slots = catalog.slots();
  82. const auto objects = catalog.objects();
  83. if (occurrenceRow >= occurrences.size() || sceneSlotRow >= slots.size()) {
  84. return SceneStatus::invalidOccurrence;
  85. }
  86. const auto& occurrence = occurrences[occurrenceRow];
  87. const auto& scene = slots[sceneSlotRow];
  88. if (occurrence.objectIndex >= objects.size() || scene.objectIndex != occurrence.objectIndex) {
  89. return SceneStatus::invalidSlot;
  90. }
  91. const auto resources = sdk::slot_authored_scene_resources(catalog, scene);
  92. if (resources.empty()) {
  93. return SceneStatus::missingResource;
  94. }
  95. if (resources.size() != 1) {
  96. return SceneStatus::ambiguousResource;
  97. }
  98. SceneSquadParticipants participants{};
  99. const auto resolved = scene_squad_participants(catalog, scene, resources.front(), participants);
  100. if (resolved != SceneStatus::ready) {
  101. return resolved;
  102. }
  103. SceneSpawnPlan candidate{};
  104. for (std::size_t index = 0; index < participants.count; ++index) {
  105. const auto& reference = participants.rows[index].reference;
  106. const Source source{reference.rosterKey,
  107. static_cast<std::uint16_t>(reference.slotIndex),
  108. static_cast<std::uint8_t>(reference.slotType),
  109. true,
  110. true};
  111. auto& pair = candidate.pairs[candidate.count];
  112. const auto parent = source_squad(catalog, occurrenceRow, source, pair.squadRow);
  113. if (parent == SceneStatus::targetUnavailable) {
  114. pair = {};
  115. ++candidate.omitted;
  116. continue;
  117. }
  118. if (parent != SceneStatus::ready) {
  119. return parent;
  120. }
  121. for (const auto& actorOccurrence : occurrences) {
  122. if (actorOccurrence.scenarioIndex != occurrence.scenarioIndex
  123. || actorOccurrence.stateIndex != occurrence.stateIndex) {
  124. continue;
  125. }
  126. if (actorOccurrence.objectIndex >= objects.size()) {
  127. return SceneStatus::invalidOccurrence;
  128. }
  129. const auto& object = objects[actorOccurrence.objectIndex];
  130. for (const auto& actor : sdk::object_slots(catalog, object)) {
  131. if (!exact_actor(actor)) {
  132. continue;
  133. }
  134. const auto actorIndex = static_cast<std::uint32_t>(&actor - slots.data());
  135. if (pair.actorSlotRow == actorIndex) {
  136. continue;
  137. }
  138. const Source actorSource{object.objectKey,
  139. static_cast<std::uint16_t>(actor.slotIndex),
  140. static_cast<std::uint8_t>(actor.slotType),
  141. true,
  142. true};
  143. Source parentSource{};
  144. if (!middleware::gameplay::external::resolve_combatant_squad_source(
  145. world, actorSource, parentSource)
  146. || parentSource != source) {
  147. continue;
  148. }
  149. if (pair.actorSlotRow != sdk::format::kAbsentIndex) {
  150. return SceneStatus::ambiguousTarget;
  151. }
  152. pair.actorSlotRow = actorIndex;
  153. }
  154. }
  155. if (pair.actorSlotRow == sdk::format::kAbsentIndex) {
  156. pair = {};
  157. ++candidate.omitted;
  158. continue;
  159. }
  160. pair.actorTarget = physical_target(catalog, pair.actorSlotRow);
  161. pair.sourceTarget = physical_target(catalog, catalog.squads()[pair.squadRow].slotIndex);
  162. ++candidate.count;
  163. }
  164. output = candidate;
  165. return SceneStatus::ready;
  166. }
  167. } // namespace sunrise::server::activity::activity_sdk_mission