mission_script_player_sense.h 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #pragma once
  2. #include <cstdint>
  3. #include <limits>
  4. #include <span>
  5. #include "../../../middleware/bap/activity_message/sense_update.h"
  6. namespace sunrise::server::activity::mission {
  7. /** Type-13 participation records carry one player each; this is their slot type and root schema. */
  8. inline constexpr std::uint8_t kParticipationSlotType = 13;
  9. inline constexpr std::uint32_t kParticipationSenseSchema = 0x80804F2FU;
  10. /** Object tag that owns the participation records. Assumed the same in every activity. */
  11. inline constexpr std::uint32_t kParticipationObjectTag = 0x80FEB3DCU;
  12. /** Player records occupy sixteen slots starting at slot five. */
  13. inline constexpr std::uint16_t kFirstParticipationSlot = 5;
  14. inline constexpr std::size_t kParticipationSlotCount = 16;
  15. /** The identity block carries the player key in field 0. */
  16. inline constexpr std::uint32_t kParticipationIdentitySchema = 0x808094DDU;
  17. /** The state block carries the region, loaded, settled and Ghost flags. */
  18. inline constexpr std::uint32_t kParticipationStateSchema = 0x808094E4U;
  19. inline constexpr std::uint16_t kParticipationKeyOrdinal = 0;
  20. inline constexpr std::uint16_t kParticipationRegionOrdinal = 0;
  21. inline constexpr std::uint16_t kParticipationLoadedOrdinal = 2;
  22. inline constexpr std::uint16_t kParticipationSettledOrdinal = 4;
  23. inline constexpr std::uint16_t kParticipationGhostOrdinal = 5;
  24. inline constexpr std::uint8_t kLifeSeenKey = 0x01;
  25. inline constexpr std::uint8_t kLifeSeenRegion = 0x02;
  26. inline constexpr std::uint8_t kLifeSeenLoaded = 0x04;
  27. inline constexpr std::uint8_t kLifeSeenSettled = 0x08;
  28. inline constexpr std::uint8_t kLifeSeenGhost = 0x10;
  29. inline constexpr std::uint8_t kLifeSeenAll =
  30. kLifeSeenKey | kLifeSeenRegion | kLifeSeenLoaded | kLifeSeenSettled | kLifeSeenGhost;
  31. /** All-one bits is not a player key. */
  32. inline constexpr std::uint64_t kInvalidPlayerKey = (std::numeric_limits<std::uint64_t>::max)();
  33. /** The region field is a signed 16-bit slice-set index. */
  34. inline constexpr std::int32_t kMaximumLifeRegion = (std::numeric_limits<std::int16_t>::max)();
  35. enum class PlayerLife : std::uint8_t { unknown, alive, dead };
  36. /** Last participation level seen for one player slot. */
  37. struct PlayerLifeObservation final {
  38. std::uint64_t playerKey{};
  39. std::int32_t region{-1};
  40. std::uint8_t seen{};
  41. bool loaded{};
  42. bool settled{};
  43. bool ghost{};
  44. /** Dead means loaded, settled and showing the Ghost. Anything incomplete is unknown. */
  45. [[nodiscard]] PlayerLife life() const noexcept {
  46. if (seen != kLifeSeenAll || playerKey == 0 || playerKey == kInvalidPlayerKey || region < 0
  47. || region > kMaximumLifeRegion || !loaded || !settled) {
  48. return PlayerLife::unknown;
  49. }
  50. return ghost ? PlayerLife::dead : PlayerLife::alive;
  51. }
  52. };
  53. /**
  54. * Merges one decoded type-13 body into the level. The body is a full snapshot, so an absent
  55. * identity or region withdraws the old evidence.
  56. */
  57. inline void
  58. update_player_life(PlayerLifeObservation& level,
  59. std::span<const middleware::bap::activity_message::sense_update::DecodedValue>
  60. values) noexcept {
  61. for (const auto& value : values) {
  62. const bool identity = value.schemaRow == kParticipationIdentitySchema
  63. && value.fieldOrdinal == kParticipationKeyOrdinal;
  64. if (!value.present) {
  65. if (identity) {
  66. level.playerKey = 0;
  67. level.seen &= static_cast<std::uint8_t>(~kLifeSeenKey);
  68. }
  69. if (value.schemaRow == kParticipationStateSchema
  70. && value.fieldOrdinal == kParticipationRegionOrdinal) {
  71. level.region = -1;
  72. level.seen &= static_cast<std::uint8_t>(~kLifeSeenRegion);
  73. }
  74. continue;
  75. }
  76. if (identity) {
  77. level.playerKey = value.unsignedValue;
  78. level.seen |= kLifeSeenKey;
  79. } else if (value.schemaRow == kParticipationStateSchema) {
  80. switch (value.fieldOrdinal) {
  81. case kParticipationRegionOrdinal:
  82. level.region = static_cast<std::int32_t>(value.signedValue);
  83. level.seen |= kLifeSeenRegion;
  84. break;
  85. case kParticipationLoadedOrdinal:
  86. level.loaded = value.unsignedValue != 0;
  87. level.seen |= kLifeSeenLoaded;
  88. break;
  89. case kParticipationSettledOrdinal:
  90. level.settled = value.unsignedValue != 0;
  91. level.seen |= kLifeSeenSettled;
  92. break;
  93. case kParticipationGhostOrdinal:
  94. level.ghost = value.unsignedValue != 0;
  95. level.seen |= kLifeSeenGhost;
  96. break;
  97. default:
  98. break;
  99. }
  100. }
  101. }
  102. }
  103. /** Life counts over one private activity's joined party. */
  104. struct FireteamLife final {
  105. std::uint16_t alive{};
  106. std::uint16_t dead{};
  107. std::uint16_t unknown{};
  108. /** An unknown member blocks the all-dead edge, so a loading player cannot cause a wipe. */
  109. [[nodiscard]] bool all_dead() const noexcept {
  110. return dead != 0 && alive == 0 && unknown == 0;
  111. }
  112. void add(PlayerLife life) noexcept {
  113. if (life == PlayerLife::alive) {
  114. ++alive;
  115. } else if (life == PlayerLife::dead) {
  116. ++dead;
  117. } else {
  118. ++unknown;
  119. }
  120. }
  121. bool operator==(const FireteamLife&) const = default;
  122. };
  123. } // namespace sunrise::server::activity::mission