activity_sdk_lua_missions.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. #include <algorithm>
  2. #include <array>
  3. #include <bit>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <cstdio>
  7. #include <limits>
  8. #include <string>
  9. #include <string_view>
  10. #include <unordered_map>
  11. #include <unordered_set>
  12. #include <vector>
  13. #include "../../../middleware/bap/activity_message/auth_schema_catalog.h"
  14. #include "activity_sdk_lua_missions_internal.h"
  15. namespace sunrise::client::content::activity::sdk_generation::lua_artifacts::internal {
  16. namespace {
  17. namespace auth_catalog = middleware::bap::activity_message::auth_schema_catalog;
  18. void append_int(std::string& output, std::int32_t value) {
  19. std::array<char, 16> buffer{};
  20. const int length = std::snprintf(buffer.data(), buffer.size(), "%d", value);
  21. output.append(buffer.data(), static_cast<std::size_t>(length));
  22. }
  23. void append_float(std::string& output, std::uint32_t bits) {
  24. std::array<char, 32> buffer{};
  25. const int length = std::snprintf(
  26. buffer.data(), buffer.size(), "%.9g", static_cast<double>(std::bit_cast<float>(bits)));
  27. output.append(buffer.data(), static_cast<std::size_t>(length));
  28. }
  29. /** Builds the exact occurrence-bound identity consumed by the mission scene API. */
  30. [[nodiscard]] bool scene_symbol_id(const Source& source,
  31. const format::Occurrence& occurrence,
  32. const format::Slot& slot,
  33. std::string& output) {
  34. // Occurrence ids carry this prefix; the scene symbol keeps the remainder.
  35. constexpr std::string_view kOccurrencePrefix = "object-occurrence/";
  36. const std::string_view occurrenceId = text(source, occurrence.id);
  37. const std::string_view suffix = occurrenceId.starts_with(kOccurrencePrefix)
  38. ? occurrenceId.substr(kOccurrencePrefix.size())
  39. : occurrenceId;
  40. if (suffix.empty() || slot.slotIndex > (std::numeric_limits<std::uint16_t>::max)()
  41. || slot.slotType > (std::numeric_limits<std::uint16_t>::max)()) {
  42. return false;
  43. }
  44. std::array<char, 32> tail{};
  45. const int length =
  46. std::snprintf(tail.data(), tail.size(), "/%04x/%04x", slot.slotIndex, slot.slotType);
  47. if (length <= 0 || static_cast<std::size_t>(length) >= tail.size()) {
  48. return false;
  49. }
  50. output.assign("symbol/");
  51. output.append(suffix);
  52. output.append(tail.data(), static_cast<std::size_t>(length));
  53. return true;
  54. }
  55. /** Collects each global slot row used by one scenario exactly once. */
  56. [[nodiscard]] bool scenario_slots(const Source& source,
  57. const format::Scenario& scenario,
  58. std::vector<std::uint32_t>& output) {
  59. output.clear();
  60. if (!range_inside(scenario.occurrences, source.occurrences.size())) {
  61. return false;
  62. }
  63. for (std::size_t offset = 0; offset < scenario.occurrences.count; ++offset) {
  64. const format::Occurrence& occurrence =
  65. source.occurrences[scenario.occurrences.first + offset];
  66. if (occurrence.objectIndex >= source.objects.size()) {
  67. return false;
  68. }
  69. const format::Object& object = source.objects[occurrence.objectIndex];
  70. if (!range_inside(object.slots, source.slots.size())) {
  71. return false;
  72. }
  73. for (std::size_t slotOffset = 0; slotOffset < object.slots.count; ++slotOffset) {
  74. output.push_back(object.slots.first + static_cast<std::uint32_t>(slotOffset));
  75. }
  76. }
  77. std::sort(output.begin(), output.end());
  78. output.erase(std::unique(output.begin(), output.end()), output.end());
  79. return true;
  80. }
  81. /** Folds slot names into keys. A repeated name takes its object tag, then its slot index. */
  82. [[nodiscard]] std::vector<std::string> unique_slot_keys(const Source& source,
  83. const std::vector<std::uint32_t>& rows,
  84. const std::vector<std::string>& names) {
  85. std::vector<std::string> keys(rows.size());
  86. std::unordered_map<std::string, std::size_t> counts;
  87. for (std::size_t index = 0; index < rows.size(); ++index) {
  88. keys[index] = identifier(names[index], true);
  89. ++counts[keys[index]];
  90. }
  91. // Every member of a repeated group is suffixed, so a key never depends on row order.
  92. std::unordered_map<std::string, std::size_t> taggedCounts;
  93. for (std::size_t index = 0; index < rows.size(); ++index) {
  94. if (counts[keys[index]] > 1) {
  95. const format::Slot& slot = source.slots[rows[index]];
  96. const std::uint32_t tag = slot.objectIndex < source.objects.size()
  97. ? source.objects[slot.objectIndex].objectTag
  98. : 0U;
  99. std::array<char, 16> suffix{};
  100. const int length = std::snprintf(suffix.data(), suffix.size(), "_%08X", tag);
  101. keys[index].append(suffix.data(), static_cast<std::size_t>(length));
  102. }
  103. ++taggedCounts[keys[index]];
  104. }
  105. for (std::size_t index = 0; index < rows.size(); ++index) {
  106. if (taggedCounts[keys[index]] > 1) {
  107. std::array<char, 16> suffix{};
  108. const int length =
  109. std::snprintf(suffix.data(),
  110. suffix.size(),
  111. "_%04X",
  112. static_cast<unsigned>(source.slots[rows[index]].slotIndex));
  113. keys[index].append(suffix.data(), static_cast<std::size_t>(length));
  114. }
  115. }
  116. return keys;
  117. }
  118. } // namespace
  119. /** Emits one concrete mission module with no pack or resolver access. */
  120. bool render_mission(const Source& source,
  121. const RenderIndex& index,
  122. std::uint32_t scenarioIndex,
  123. const format::Scenario& scenario,
  124. SourceModule& module) {
  125. const std::string_view name = text(source, scenario.name);
  126. module.stem = stem(name, scenario.tag);
  127. std::string output = "-- Generated from installed mission data. Do not edit.\n"
  128. "local sdk = require(\"sunrise.activity_sdk\")\n\n"
  129. "---@type SunriseMission\n"
  130. "local mission = {\n name = ";
  131. append_string(output, name);
  132. output.append(",\n id = ");
  133. append_string(output, text(source, scenario.id));
  134. output.append(",\n tag = ");
  135. append_hex(output, scenario.tag);
  136. output.append(",\n states = {\n");
  137. std::unordered_set<std::string> stateKeys;
  138. std::string stateConstants = "mission.State = {\n";
  139. if (!range_inside(scenario.states, source.states.size())) {
  140. return false;
  141. }
  142. for (std::size_t offset = 0; offset < scenario.states.count; ++offset) {
  143. const format::State& state = source.states[scenario.states.first + offset];
  144. const std::string_view id = text(source, state.id);
  145. const std::string key = append_unique_key(output, stateKeys, id, state.stateHash);
  146. output.append("{ id = ");
  147. append_string(output, id);
  148. output.append(", ordinal = ");
  149. append_uint(output, state.stateOrdinal);
  150. output.append(", slice_set_index = ");
  151. append_uint(output, state.sliceSetIndex);
  152. output.append(", region_index = ");
  153. append_uint(output, state.sliceSetIndex + state.stateOrdinal);
  154. output.append(", hash = ");
  155. append_hex(output, state.stateHash);
  156. output.append(", value = ");
  157. append_uint(output, state.publicValue);
  158. output.append(" },\n");
  159. stateConstants.append(" ");
  160. stateConstants.append(key);
  161. stateConstants.append(" = ");
  162. append_uint(stateConstants, state.publicValue);
  163. stateConstants.append(",\n");
  164. }
  165. output.append(" },\n slots = {\n");
  166. std::unordered_map<std::uint32_t, std::string> slotKeyByRow;
  167. std::string slotConstants = "mission.Slot = {\n";
  168. std::array<std::string, auth_catalog::kTypes.size()> authConstants{};
  169. std::vector<std::uint32_t> slots;
  170. if (!scenario_slots(source, scenario, slots)) {
  171. return false;
  172. }
  173. std::vector<std::string> slotNames;
  174. slotNames.reserve(slots.size());
  175. for (const std::uint32_t row : slots) {
  176. const format::Slot& slot = source.slots[row];
  177. const auth_catalog::Type* const auth =
  178. auth_catalog::find(static_cast<std::uint8_t>(slot.slotType), slot.authSchema);
  179. std::string nameValue(text(source, slot.name));
  180. if (nameValue.empty()) {
  181. std::array<char, 24> suffix{};
  182. const int length = std::snprintf(
  183. suffix.data(), suffix.size(), "_%04X", static_cast<unsigned>(slot.slotIndex));
  184. nameValue.assign(auth == nullptr ? "slot" : auth->name);
  185. nameValue.append(suffix.data(), static_cast<std::size_t>(length));
  186. }
  187. slotNames.push_back(std::move(nameValue));
  188. }
  189. const std::vector<std::string> slotKeys = unique_slot_keys(source, slots, slotNames);
  190. slotKeyByRow.reserve(slots.size());
  191. for (std::size_t slotOrdinal = 0; slotOrdinal < slots.size(); ++slotOrdinal) {
  192. const std::uint32_t row = slots[slotOrdinal];
  193. const format::Slot& slot = source.slots[row];
  194. const auth_catalog::Type* const auth =
  195. auth_catalog::find(static_cast<std::uint8_t>(slot.slotType), slot.authSchema);
  196. const std::string_view nameValue = slotNames[slotOrdinal];
  197. const std::string& key = slotKeys[slotOrdinal];
  198. slotKeyByRow.emplace(row, key);
  199. output.append(" ");
  200. output.append(key);
  201. output.append(" = ");
  202. output.append("{ id = ");
  203. append_string(output, text(source, slot.id));
  204. output.append(", name = ");
  205. append_string(output, nameValue);
  206. output.append(", index = ");
  207. append_uint(output, slot.slotIndex);
  208. output.append(", type = ");
  209. append_uint(output, slot.slotType);
  210. output.append(", component_class = ");
  211. append_hex(output, slot.componentClass);
  212. output.append(", sense_schema = ");
  213. append_uint(output, slot.senseSchema);
  214. output.append(", auth_schema = ");
  215. append_uint(output, slot.authSchema);
  216. if ((slot.flags & format::kSlotAuthoredSceneUnresourced) != 0) {
  217. output.append(", unresourced = true");
  218. }
  219. if (auth != nullptr) {
  220. output.append(", auth_type = ");
  221. append_string(output, auth->name);
  222. output.append(", auth_min_bits = ");
  223. append_uint(output, auth->minimumBits);
  224. output.append(", auth_max_bits = ");
  225. append_uint(output, auth->maximumBits);
  226. output.append(", auth_dynamic = ");
  227. output.append(auth->hasDynamicBody ? "true" : "false");
  228. output.append(", auth_writable = ");
  229. output.append(auth->writable ? "true" : "false");
  230. if (auth->hasContiguousMirror) {
  231. output.append(", auth_component_offset = ");
  232. append_uint(output, auth->componentOffset);
  233. }
  234. }
  235. output.append(" },\n");
  236. slotConstants.append(" ");
  237. slotConstants.append(key);
  238. slotConstants.append(" = ");
  239. append_string(slotConstants, text(source, slot.id));
  240. slotConstants.append(",\n");
  241. if (auth != nullptr) {
  242. const std::size_t authRow =
  243. static_cast<std::size_t>(auth - auth_catalog::kTypes.data());
  244. authConstants[authRow].append(" ");
  245. authConstants[authRow].append(key);
  246. authConstants[authRow].append(" = mission.Slot.");
  247. authConstants[authRow].append(key);
  248. authConstants[authRow].append(",\n");
  249. }
  250. }
  251. output.append(" },\n squads = {\n");
  252. std::unordered_set<std::string> squadKeys;
  253. std::string squadConstants = "mission.Squad = {\n";
  254. if (scenarioIndex >= index.squadsByScenario.size()) {
  255. return false;
  256. }
  257. for (const std::uint32_t squadRow : index.squadsByScenario[scenarioIndex]) {
  258. const format::Squad& squad = source.squads[squadRow];
  259. const std::string_view id = text(source, squad.id);
  260. const auto slotKey = slotKeyByRow.find(squad.slotIndex);
  261. const std::string_view squadName =
  262. slotKey == slotKeyByRow.end() ? id : std::string_view{slotKey->second};
  263. const std::string key = append_unique_key(output, squadKeys, squadName, squad.slotIndex);
  264. output.append("{ id = ");
  265. append_string(output, id);
  266. output.append(", slot = ");
  267. append_uint(output, squad.slotIndex);
  268. output.append(", spawner_config = ");
  269. append_hex(output, squad.spawnerConfigTag);
  270. output.append(", spawn_rule_config = ");
  271. append_hex(output, squad.spawnRuleConfigTag);
  272. output.append(", members = {\n");
  273. if (!range_inside(squad.members, source.squadMembers.size())) {
  274. return false;
  275. }
  276. for (std::size_t memberOffset = 0; memberOffset < squad.members.count; ++memberOffset) {
  277. const format::SquadMember& member =
  278. source.squadMembers[squad.members.first + memberOffset];
  279. const bool exactActor = (member.flags & format::kSquadMemberActorClassExact) != 0;
  280. if (!exactActor) {
  281. output.append(" { actor_class = nil, behavior_config = nil, "
  282. "default_faction = nil },\n");
  283. continue;
  284. }
  285. if (member.actorClassIndex == format::kAbsentIndex
  286. || member.actorClassIndex >= source.actorClasses.size()
  287. || member.actorClassIndex >= source.actorBehaviorProfiles.size()) {
  288. return false;
  289. }
  290. const format::ActorClass& actor = source.actorClasses[member.actorClassIndex];
  291. const format::ActorBehaviorProfile& profile =
  292. source.actorBehaviorProfiles[member.actorClassIndex];
  293. if (profile.actorClassIndex != member.actorClassIndex) {
  294. return false;
  295. }
  296. output.append(" { actor_class = ");
  297. append_hex(output, actor.definitionTag);
  298. output.append(", behavior_config = ");
  299. append_hex(output, profile.behaviorConfigTag);
  300. output.append(", default_faction = ");
  301. append_int(output, profile.defaultFaction);
  302. output.append(" },\n");
  303. }
  304. output.append(" }, anchors = {\n");
  305. if (!range_inside(squad.anchors, source.squadAnchors.size())) {
  306. return false;
  307. }
  308. for (std::size_t anchorOffset = 0; anchorOffset < squad.anchors.count; ++anchorOffset) {
  309. const format::SquadAnchor& anchor =
  310. source.squadAnchors[squad.anchors.first + anchorOffset];
  311. output.append(" { x = ");
  312. append_float(output, anchor.positionBits[0]);
  313. output.append(", y = ");
  314. append_float(output, anchor.positionBits[1]);
  315. output.append(", z = ");
  316. append_float(output, anchor.positionBits[2]);
  317. output.append(", point = ");
  318. append_uint(output, anchor.pointOrdinal);
  319. output.append(" },\n");
  320. }
  321. output.append(" } },\n");
  322. squadConstants.append(" ");
  323. squadConstants.append(key);
  324. squadConstants.append(" = ");
  325. append_string(squadConstants, id);
  326. squadConstants.append(",\n");
  327. }
  328. output.append(" },\n scenes = {\n");
  329. std::unordered_set<std::string> sceneKeys;
  330. std::string sceneConstants = "mission.Scene = {\n";
  331. if (!range_inside(scenario.occurrences, source.occurrences.size())) {
  332. return false;
  333. }
  334. for (std::size_t occurrenceOffset = 0; occurrenceOffset < scenario.occurrences.count;
  335. ++occurrenceOffset) {
  336. const std::uint32_t occurrenceRow =
  337. scenario.occurrences.first + static_cast<std::uint32_t>(occurrenceOffset);
  338. const format::Occurrence& occurrence = source.occurrences[occurrenceRow];
  339. if (occurrence.objectIndex >= source.objects.size()) {
  340. return false;
  341. }
  342. const format::Object& object = source.objects[occurrence.objectIndex];
  343. if (!range_inside(object.slots, source.slots.size())) {
  344. return false;
  345. }
  346. for (std::size_t slotOffset = 0; slotOffset < object.slots.count; ++slotOffset) {
  347. const std::uint32_t slotRow =
  348. object.slots.first + static_cast<std::uint32_t>(slotOffset);
  349. const auto found = index.scenesBySlot.find(slotRow);
  350. const auto slotKey = slotKeyByRow.find(slotRow);
  351. if (found == index.scenesBySlot.end() || found->second.size() != 1
  352. || slotKey == slotKeyByRow.end()) {
  353. continue;
  354. }
  355. const format::Slot& slot = source.slots[slotRow];
  356. const format::AuthoredSceneResource& scene =
  357. source.authoredSceneResources[found->second.front()];
  358. std::string id;
  359. if (!scene_symbol_id(source, occurrence, slot, id)) {
  360. return false;
  361. }
  362. std::string key = slotKey->second;
  363. if (!sceneKeys.insert(key).second) {
  364. key.append("__");
  365. key.append(identifier(text(source, occurrence.id), true));
  366. if (!sceneKeys.insert(key).second) {
  367. std::array<char, 16> suffix{};
  368. const int length =
  369. std::snprintf(suffix.data(), suffix.size(), "_%08X", occurrenceRow);
  370. key.append(suffix.data(), static_cast<std::size_t>(length));
  371. if (!sceneKeys.insert(key).second) {
  372. return false;
  373. }
  374. }
  375. }
  376. output.append(" ");
  377. output.append(key);
  378. output.append(" = ");
  379. output.append("{ id = ");
  380. append_string(output, id);
  381. output.append(", slot = ");
  382. append_uint(output, slotRow);
  383. output.append(", config_tag = ");
  384. append_hex(output, scene.configTag);
  385. output.append(", resource_tag = ");
  386. append_hex(output, scene.resourceTag);
  387. // The graph's gates in order: what set_scene_events publishes to run the scene.
  388. bool anyKey = false;
  389. for (const format::AuthoredSceneEventKey& gate : source.authoredSceneEventKeys) {
  390. if (gate.sceneSlotIndex != slotRow) {
  391. continue;
  392. }
  393. output.append(anyKey ? ", " : ", event_keys = { ");
  394. append_hex(output, gate.key);
  395. anyKey = true;
  396. }
  397. if (anyKey) {
  398. output.append(" }");
  399. }
  400. output.append(" },\n");
  401. sceneConstants.append(" ");
  402. sceneConstants.append(key);
  403. sceneConstants.append(" = ");
  404. append_string(sceneConstants, id);
  405. sceneConstants.append(",\n");
  406. }
  407. }
  408. output.append(" },\n tasks = {\n");
  409. std::unordered_set<std::string> taskKeys;
  410. std::string taskConstants = "mission.Task = {\n";
  411. std::vector<std::uint32_t> tasks;
  412. for (const std::uint32_t slot : slots) {
  413. const auto found = index.tasksBySlot.find(slot);
  414. if (found != index.tasksBySlot.end()) {
  415. tasks.insert(tasks.end(), found->second.begin(), found->second.end());
  416. }
  417. }
  418. std::sort(tasks.begin(), tasks.end());
  419. tasks.erase(std::unique(tasks.begin(), tasks.end()), tasks.end());
  420. for (const std::uint32_t taskRow : tasks) {
  421. const format::TaskTarget& task = source.taskTargets[taskRow];
  422. const std::string_view id = text(source, task.id);
  423. const std::string key = append_unique_key(output, taskKeys, id, task.taskSlotIndex);
  424. output.append("{ id = ");
  425. append_string(output, id);
  426. output.append(", task_slot = ");
  427. append_uint(output, task.taskSlotIndex);
  428. output.append(", objective_slot = ");
  429. append_uint(output, task.objectiveSlotIndex);
  430. output.append(", objective_bit = ");
  431. append_uint(output, task.bitIndex);
  432. output.append(", config_tag = ");
  433. append_hex(output, task.configTag);
  434. output.append(" },\n");
  435. taskConstants.append(" ");
  436. taskConstants.append(key);
  437. taskConstants.append(" = ");
  438. append_string(taskConstants, id);
  439. taskConstants.append(",\n");
  440. }
  441. output.append(" },\n}\n\n");
  442. stateConstants.append("}\n");
  443. slotConstants.append("}\n");
  444. squadConstants.append("}\n");
  445. sceneConstants.append("}\n");
  446. taskConstants.append("}\n");
  447. std::string taskGroupConstants = "mission.TaskGroup = {\n";
  448. for (const std::uint32_t slot : slots) {
  449. const auto found = index.combatGroupsBySlot.find(slot);
  450. const auto key = slotKeyByRow.find(slot);
  451. if (found == index.combatGroupsBySlot.end() || key == slotKeyByRow.end()) {
  452. continue;
  453. }
  454. taskGroupConstants.append(" ");
  455. taskGroupConstants.append(key->second);
  456. taskGroupConstants.append(" = {\n");
  457. for (const std::uint32_t row : found->second) {
  458. const format::CombatObjectiveGroup& group = source.combatObjectiveGroups[row];
  459. taskGroupConstants.append(" GROUP_");
  460. append_uint(taskGroupConstants, group.groupIndex);
  461. taskGroupConstants.append(" = { slot_row = ");
  462. append_uint(taskGroupConstants, group.slotIndex);
  463. taskGroupConstants.append(", group_index = ");
  464. append_uint(taskGroupConstants, group.groupIndex);
  465. taskGroupConstants.append(" },\n");
  466. }
  467. taskGroupConstants.append(" },\n");
  468. }
  469. taskGroupConstants.append("}\n");
  470. std::string abilityConstants = "mission.ActorAbility = {\n";
  471. for (const std::uint32_t slot : slots) {
  472. const auto found = index.abilitiesBySlot.find(slot);
  473. const auto key = slotKeyByRow.find(slot);
  474. if (found == index.abilitiesBySlot.end() || key == slotKeyByRow.end()) {
  475. continue;
  476. }
  477. abilityConstants.append(" ").append(key->second).append(" = {\n");
  478. std::uint32_t previousGroup = 0;
  479. for (const std::uint32_t row : found->second) {
  480. const format::ActorAbility& ability = source.actorAbilities[row];
  481. std::array<char, 32> symbol{};
  482. if (ability.groupHash != previousGroup) {
  483. if (previousGroup != 0) {
  484. abilityConstants.append(" },\n");
  485. }
  486. std::snprintf(symbol.data(), symbol.size(), "GROUP_%08X", ability.groupHash);
  487. abilityConstants.append(" ").append(symbol.data()).append(" = {\n");
  488. previousGroup = ability.groupHash;
  489. }
  490. std::snprintf(symbol.data(), symbol.size(), "KEY_%08X", ability.requestHash);
  491. abilityConstants.append(" ")
  492. .append(symbol.data())
  493. .append(" = { slot_row = ");
  494. append_uint(abilityConstants, ability.slotIndex);
  495. abilityConstants.append(", group_hash = ");
  496. append_hex(abilityConstants, ability.groupHash);
  497. abilityConstants.append(", request_hash = ");
  498. append_hex(abilityConstants, ability.requestHash);
  499. abilityConstants.append(" },\n");
  500. }
  501. if (previousGroup != 0) {
  502. abilityConstants.append(" },\n");
  503. }
  504. abilityConstants.append(" },\n");
  505. }
  506. abilityConstants.append("}\n");
  507. std::string dialogueCueConstants = "mission.DialogueCue = {\n";
  508. std::string dialogueCueTextConstants = "mission.DialogueCueVariants = {\n";
  509. std::string dialogueDefinitionConstants = "mission.DialogueDefinition = {\n";
  510. for (const std::uint32_t slot : slots) {
  511. const auto found = index.dialogueBySlot.find(slot);
  512. const auto slotKey = slotKeyByRow.find(slot);
  513. if (slotKey == slotKeyByRow.end() || slot >= source.slots.size()) {
  514. continue;
  515. }
  516. const format::Slot& definition = source.slots[slot];
  517. if ((definition.flags & format::kSlotDialogueCuesExact) == 0 || definition.reserved == 0) {
  518. continue;
  519. }
  520. dialogueCueConstants.append(" ");
  521. dialogueCueConstants.append(slotKey->second);
  522. dialogueCueConstants.append(" = {\n");
  523. dialogueCueTextConstants.append(" ");
  524. dialogueCueTextConstants.append(slotKey->second);
  525. dialogueCueTextConstants.append(" = {\n");
  526. dialogueDefinitionConstants.append(" ");
  527. dialogueDefinitionConstants.append(slotKey->second);
  528. dialogueDefinitionConstants.append(" = {\n");
  529. for (std::uint32_t cue = 0; cue < definition.reserved; ++cue) {
  530. dialogueCueConstants.append(" CUE_");
  531. append_uint(dialogueCueConstants, cue);
  532. dialogueCueConstants.append(" = ");
  533. append_uint(dialogueCueConstants, cue);
  534. dialogueCueConstants.append(",\n");
  535. if (found != index.dialogueBySlot.end()) {
  536. for (const std::uint32_t rowIndex : found->second) {
  537. const format::DialogueCueText& row = source.dialogueCueTexts[rowIndex];
  538. if (row.cueIndex == cue) {
  539. dialogueDefinitionConstants.append(" [");
  540. append_uint(dialogueDefinitionConstants, cue);
  541. dialogueDefinitionConstants.append("] = ");
  542. append_hex(dialogueDefinitionConstants, row.definitionHash);
  543. dialogueDefinitionConstants.append(",\n");
  544. break;
  545. }
  546. }
  547. }
  548. dialogueCueTextConstants.append(" [");
  549. append_uint(dialogueCueTextConstants, cue);
  550. dialogueCueTextConstants.append("] = {");
  551. std::unordered_set<std::string_view> emittedTexts;
  552. if (found != index.dialogueBySlot.end()) {
  553. for (const std::uint32_t rowIndex : found->second) {
  554. const format::DialogueCueText& row = source.dialogueCueTexts[rowIndex];
  555. const std::string_view candidate = text(source, row.text);
  556. if (row.cueIndex != cue || candidate.empty()
  557. || !emittedTexts.emplace(candidate).second) {
  558. continue;
  559. }
  560. dialogueCueTextConstants.push_back('\n');
  561. dialogueCueTextConstants.append(" ");
  562. append_string(dialogueCueTextConstants, candidate);
  563. dialogueCueTextConstants.push_back(',');
  564. }
  565. }
  566. if (!emittedTexts.empty()) {
  567. dialogueCueTextConstants.append("\n ");
  568. }
  569. dialogueCueTextConstants.append("},\n");
  570. }
  571. dialogueCueConstants.append(" },\n");
  572. dialogueCueTextConstants.append(" },\n");
  573. dialogueDefinitionConstants.append(" },\n");
  574. }
  575. dialogueCueConstants.append("}\n");
  576. dialogueCueTextConstants.append("}\n");
  577. dialogueDefinitionConstants.append("}\n");
  578. std::unordered_set<std::string> directiveKeys;
  579. std::string directiveConstants = "mission.Directive = {\n";
  580. for (const std::uint32_t slot : slots) {
  581. const auto found = index.directivesBySlot.find(slot);
  582. if (found == index.directivesBySlot.end()) {
  583. continue;
  584. }
  585. for (const std::uint32_t rowIndex : found->second) {
  586. const format::DirectiveElement& row = source.directiveElements[rowIndex];
  587. (void)append_unique_key(
  588. directiveConstants, directiveKeys, text(source, row.title), row.nameHash);
  589. directiveConstants.append("{ id = ");
  590. append_string(directiveConstants, text(source, row.id));
  591. directiveConstants.append(", slot_row = ");
  592. append_uint(directiveConstants, row.slotIndex);
  593. directiveConstants.append(", name_hash = ");
  594. append_hex(directiveConstants, row.nameHash);
  595. directiveConstants.append(", element = ");
  596. append_uint(directiveConstants, static_cast<std::uint32_t>(row.elementIndex));
  597. directiveConstants.append(", title = ");
  598. append_string(directiveConstants, text(source, row.title));
  599. directiveConstants.append(", description = ");
  600. append_string(directiveConstants, text(source, row.description));
  601. directiveConstants.append(" },\n");
  602. }
  603. }
  604. directiveConstants.append("}\n");
  605. // One entry per type-42 sensor: the distinct state names its target squad's members declare.
  606. std::string performanceConstants = "mission.PerformanceState = {\n";
  607. for (const std::uint32_t slotRow : slots) {
  608. const format::Slot& slot = source.slots[slotRow];
  609. if (slot.slotType != format::kPerformanceSlotType
  610. || slot.authSchema != format::kPerformanceAuthSchema) {
  611. continue;
  612. }
  613. std::uint32_t squadSlot = format::kAbsentIndex;
  614. std::size_t edges = 0;
  615. for (const format::AuthoredSceneSquadEdge& edge : source.authoredSceneSquadEdges) {
  616. if (edge.sceneSlotIndex == slotRow
  617. && (edge.flags & format::kAuthoredSceneSquadPerformanceTargetExact) != 0) {
  618. squadSlot = edge.squadSlotIndex;
  619. ++edges;
  620. }
  621. }
  622. if (edges != 1) {
  623. continue;
  624. }
  625. std::vector<std::uint32_t> names;
  626. for (const format::Squad& squad : source.squads) {
  627. if (squad.scenarioIndex != scenarioIndex || squad.slotIndex != squadSlot
  628. || !range_inside(squad.members, source.squadMembers.size())) {
  629. continue;
  630. }
  631. for (std::size_t offset = 0; offset < squad.members.count; ++offset) {
  632. const std::uint32_t actorClass =
  633. source.squadMembers[squad.members.first + offset].actorClassIndex;
  634. for (const format::ActorStateName& row : source.actorStateNames) {
  635. if (row.actorClassIndex == actorClass
  636. && std::find(names.begin(), names.end(), row.nameHash) == names.end()) {
  637. names.push_back(row.nameHash);
  638. }
  639. }
  640. }
  641. }
  642. if (names.empty()) {
  643. continue;
  644. }
  645. performanceConstants.append(" ");
  646. performanceConstants.append(slotKeyByRow.at(slotRow));
  647. performanceConstants.append(" = {\n");
  648. for (std::size_t ordinal = 0; ordinal < names.size(); ++ordinal) {
  649. std::array<char, 24> key{};
  650. const int length = std::snprintf(
  651. key.data(), key.size(), "STATE_%08X", static_cast<unsigned>(names[ordinal]));
  652. performanceConstants.append(" ");
  653. performanceConstants.append(key.data(), static_cast<std::size_t>(length));
  654. performanceConstants.append(" = { slot_row = ");
  655. append_uint(performanceConstants, slotRow);
  656. performanceConstants.append(", name_hash = ");
  657. append_hex(performanceConstants, names[ordinal]);
  658. performanceConstants.append(", ordinal = ");
  659. append_uint(performanceConstants, static_cast<std::uint32_t>(ordinal + 1));
  660. performanceConstants.append(" },\n");
  661. }
  662. performanceConstants.append(" },\n");
  663. }
  664. performanceConstants.append("}\n");
  665. std::string authSlotConstants = "mission.Auth = {\n";
  666. for (std::size_t authRow = 0; authRow < authConstants.size(); ++authRow) {
  667. if (authConstants[authRow].empty()) {
  668. continue;
  669. }
  670. authSlotConstants.append(" ");
  671. authSlotConstants.append(auth_catalog::kTypes[authRow].name);
  672. authSlotConstants.append(" = {\n");
  673. authSlotConstants.append(authConstants[authRow]);
  674. authSlotConstants.append(" },\n");
  675. }
  676. authSlotConstants.append("}\n");
  677. std::string actorMessageConstants = "mission.ActorMessage = {\n";
  678. for (const format::ActorMessageSchema& row : source.actorMessageSchemas) {
  679. actorMessageConstants.append(" ");
  680. actorMessageConstants.append(identifier(text(source, row.name), true));
  681. actorMessageConstants.append(" = { definition = ");
  682. append_hex(actorMessageConstants, row.definitionHandle);
  683. actorMessageConstants.append(", durable_key = ");
  684. append_hex(actorMessageConstants, row.durableKey);
  685. actorMessageConstants.append(", owner_class = ");
  686. append_hex(actorMessageConstants, row.ownerClass);
  687. actorMessageConstants.append(", handler_slot = ");
  688. append_uint(actorMessageConstants, row.handlerSlot);
  689. actorMessageConstants.append(", body_type = ");
  690. append_uint(actorMessageConstants, row.bodyType);
  691. actorMessageConstants.append(" },\n");
  692. }
  693. actorMessageConstants.append("}\n");
  694. std::string actorCommandConstants = "mission.ActorCommand = {\n";
  695. std::string actorCommandDefinitions = "mission.ActorCommandDefinition = {\n";
  696. std::string factionConstants = "mission.Faction = {\n";
  697. bool factionValuesWritten = false;
  698. for (const format::ActorCommandDefinition& row : source.actorCommandDefinitions) {
  699. const std::string commandName = identifier(text(source, row.name), true);
  700. actorCommandConstants.append(" ");
  701. actorCommandConstants.append(commandName);
  702. actorCommandConstants.append(" = ");
  703. append_uint(actorCommandConstants, row.selector);
  704. actorCommandConstants.append(",\n");
  705. actorCommandDefinitions.append(" ");
  706. actorCommandDefinitions.append(commandName);
  707. actorCommandDefinitions.append(" = { selector = mission.ActorCommand.");
  708. actorCommandDefinitions.append(commandName);
  709. actorCommandDefinitions.append(", payload = ");
  710. append_hex(actorCommandDefinitions, row.payloadHandle);
  711. actorCommandDefinitions.append(" },\n");
  712. if (row.effect == format::ActorCommandEffect::setFaction && !factionValuesWritten) {
  713. factionConstants.append(" ");
  714. factionConstants.append(identifier(text(source, row.factionNoneName), true));
  715. factionConstants.append(" = ");
  716. append_int(factionConstants, row.factionNone);
  717. factionConstants.append(",\n ");
  718. factionConstants.append(identifier(text(source, row.factionRemovedName), true));
  719. factionConstants.append(" = ");
  720. append_int(factionConstants, row.factionRemoved);
  721. factionConstants.append(",\n ");
  722. factionConstants.append(identifier(text(source, row.factionHostileToAllName), true));
  723. factionConstants.append(" = ");
  724. append_int(factionConstants, row.factionHostileToAll);
  725. factionConstants.append(",\n");
  726. factionValuesWritten = true;
  727. }
  728. }
  729. actorCommandConstants.append("}\n");
  730. actorCommandDefinitions.append("}\n");
  731. factionConstants.append("}\n");
  732. std::string simulationEventConstants = "mission.SimulationEvent = {\n";
  733. std::string simulationEventDefinitions = "mission.SimulationEventDefinition = {\n";
  734. for (const format::SimulationEventDefinition& row : source.simulationEventDefinitions) {
  735. const std::string eventName = identifier(text(source, row.name), true);
  736. simulationEventConstants.append(" ");
  737. simulationEventConstants.append(eventName);
  738. simulationEventConstants.append(" = ");
  739. append_uint(simulationEventConstants, row.eventType);
  740. simulationEventConstants.append(",\n");
  741. simulationEventDefinitions.append(" ");
  742. simulationEventDefinitions.append(eventName);
  743. simulationEventDefinitions.append(" = { event_type = mission.SimulationEvent.");
  744. simulationEventDefinitions.append(eventName);
  745. simulationEventDefinitions.append(", primary_schema = ");
  746. if (row.primarySchema == format::kAbsentIndex) {
  747. simulationEventDefinitions.append("nil");
  748. } else {
  749. append_hex(simulationEventDefinitions, row.primarySchema);
  750. }
  751. simulationEventDefinitions.append(", secondary_schema = ");
  752. if (row.secondarySchema == format::kAbsentIndex) {
  753. simulationEventDefinitions.append("nil");
  754. } else {
  755. append_hex(simulationEventDefinitions, row.secondarySchema);
  756. }
  757. simulationEventDefinitions.append(" },\n");
  758. }
  759. simulationEventConstants.append("}\n");
  760. simulationEventDefinitions.append("}\n");
  761. std::string runtimeFieldTypes = "mission.RuntimeFieldType = {\n";
  762. // Lua name published for each runtime codec family.
  763. constexpr std::array<std::pair<format::RuntimeCodecFamily, std::string_view>, 3> kFamilies{{
  764. {format::RuntimeCodecFamily::activity, "ACTIVITY"},
  765. {format::RuntimeCodecFamily::sobjectModeZero, "SOBJECT_MODE_ZERO"},
  766. {format::RuntimeCodecFamily::sobjectModeOne, "SOBJECT_MODE_ONE"},
  767. }};
  768. for (const auto& [family, familyName] : kFamilies) {
  769. runtimeFieldTypes.append(" ");
  770. runtimeFieldTypes.append(familyName);
  771. runtimeFieldTypes.append(" = {\n");
  772. for (const format::RuntimeTypeDefinition& row : source.runtimeTypeDefinitions) {
  773. if ((row.codecFamilies & static_cast<std::uint32_t>(family)) == 0) {
  774. continue;
  775. }
  776. runtimeFieldTypes.append(" ");
  777. runtimeFieldTypes.append(identifier(text(source, row.name), true));
  778. runtimeFieldTypes.append(" = ");
  779. append_uint(runtimeFieldTypes, row.typeCode);
  780. runtimeFieldTypes.append(",\n");
  781. }
  782. runtimeFieldTypes.append(" },\n");
  783. }
  784. runtimeFieldTypes.append("}\n");
  785. output.append(stateConstants);
  786. output.append(slotConstants);
  787. output.append(authSlotConstants);
  788. output.append(actorMessageConstants);
  789. output.append(actorCommandConstants);
  790. output.append(actorCommandDefinitions);
  791. output.append(factionConstants);
  792. output.append(simulationEventConstants);
  793. output.append(simulationEventDefinitions);
  794. output.append(runtimeFieldTypes);
  795. output.append(squadConstants);
  796. output.append(sceneConstants);
  797. output.append(taskConstants);
  798. output.append(taskGroupConstants);
  799. output.append(abilityConstants);
  800. output.append(dialogueCueConstants);
  801. output.append(dialogueCueTextConstants);
  802. output.append(dialogueDefinitionConstants);
  803. output.append(directiveConstants);
  804. output.append(performanceConstants);
  805. const auto world = index.worldsByScenarioTag.find(scenario.tag);
  806. if (world != index.worldsByScenarioTag.end()
  807. && world->second < source.scenarioWorldSources.size()) {
  808. output.append(source.scenarioWorldSources[world->second].source);
  809. }
  810. output.append("\nreturn mission\n");
  811. module.source = std::move(output);
  812. return true;
  813. }
  814. } // namespace sunrise::client::content::activity::sdk_generation::lua_artifacts::internal