state_account_item_action_runtime.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #include <Windows.h>
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <limits>
  6. #include "../../middleware/datagen/family4/loadout/loadout_resolver.h"
  7. #include "../../middleware/web_service/messages/opcode1901.h"
  8. #include "../build_data/runtime.h"
  9. #include "runtime.h"
  10. #include "state_account_transaction_helpers.h"
  11. #include "storage/internal.h"
  12. namespace sunrise::state {
  13. using namespace runtime::detail;
  14. namespace authored_inventory = account::inventory;
  15. namespace item_details = build_data::items::details;
  16. namespace family4_loadout = middleware::datagen::family4::loadout;
  17. namespace opcode1901 = middleware::web_service::messages::opcode1901;
  18. /** Prepares one checked ordinary-socket selection without publishing account State. */
  19. bool prepare_socket_plug(std::uint64_t targetInstanceSoid,
  20. std::uint8_t socketLane,
  21. std::uint16_t plugDefinitionIndex,
  22. PendingSocketPlug& mutation) noexcept {
  23. mutation = {};
  24. const AccountState snapshot = account_snapshot();
  25. std::size_t characterIndex = snapshot.characterCount;
  26. if (targetInstanceSoid != 0 && account::valid(snapshot)) {
  27. for (std::size_t index = 0; index < snapshot.characterCount; ++index) {
  28. if (snapshot.characters[index].selected) {
  29. characterIndex = index;
  30. break;
  31. }
  32. }
  33. }
  34. if (characterIndex >= snapshot.characterCount
  35. || !stage_socket_plug(snapshot,
  36. characterIndex,
  37. targetInstanceSoid,
  38. socketLane,
  39. plugDefinitionIndex,
  40. mutation)) {
  41. report_socket_plug("prepare",
  42. "fail",
  43. "ownership_definition_or_compatibility",
  44. 0,
  45. targetInstanceSoid,
  46. 0,
  47. socketLane,
  48. plugDefinitionIndex,
  49. 0,
  50. 0,
  51. false,
  52. 0);
  53. return false;
  54. }
  55. report_socket_plug("prepare",
  56. "ok",
  57. "ready",
  58. mutation.characterSoid,
  59. mutation.targetInstanceSoid,
  60. mutation.targetDefinitionIndex,
  61. mutation.socketLane,
  62. mutation.plugDefinitionIndex,
  63. mutation.targetBucketId,
  64. mutation.plugBucketId,
  65. mutation.targetEquipped,
  66. mutation.itemIndex);
  67. return true;
  68. }
  69. /** Prepares a socket transition for the exact instance identity carried by opcode 1901. */
  70. bool prepare_character_selector_socket_plug(std::uint64_t instanceIdentityToken,
  71. std::uint8_t requestedSocketLane,
  72. std::uint16_t plugDefinitionIndex,
  73. PendingSocketPlug& mutation) noexcept {
  74. mutation = {};
  75. const AccountState snapshot = account_snapshot();
  76. std::size_t characterIndex = snapshot.characterCount;
  77. if (account::valid(snapshot)) {
  78. for (std::size_t index = 0; index < snapshot.characterCount; ++index) {
  79. if (snapshot.characters[index].selected) {
  80. characterIndex = index;
  81. break;
  82. }
  83. }
  84. }
  85. if (characterIndex >= snapshot.characterCount || instanceIdentityToken == 0
  86. || requestedSocketLane >= authored_inventory::kPlugCapacity) {
  87. report_socket_plug("prepare_location",
  88. "fail",
  89. "selection_or_slot",
  90. 0,
  91. 0,
  92. 0,
  93. requestedSocketLane,
  94. plugDefinitionIndex,
  95. 0,
  96. 0,
  97. false,
  98. static_cast<std::size_t>(instanceIdentityToken));
  99. return false;
  100. }
  101. family4_loadout::ResolvedLoadout resolvedLoadout{};
  102. if (!family4_loadout::resolve(snapshot, characterIndex, resolvedLoadout)) {
  103. return false;
  104. }
  105. const family4_loadout::ResolvedItem* resolvedTarget = nullptr;
  106. for (std::size_t index = 0; index < resolvedLoadout.itemCount; ++index) {
  107. const family4_loadout::ResolvedItem& item = resolvedLoadout.items[index];
  108. if (!opcode1901::identifies_instance(instanceIdentityToken, item.instance.instanceSoid)) {
  109. continue;
  110. }
  111. if (resolvedTarget != nullptr) {
  112. return false;
  113. }
  114. resolvedTarget = &item;
  115. }
  116. const bool expectedEquipped = resolvedTarget != nullptr && resolvedTarget->equipped;
  117. std::uint8_t resolvedSocketLane = static_cast<std::uint8_t>(authored_inventory::kPlugCapacity);
  118. build_data::items::Definition targetDefinition{};
  119. item_details::Definition targetDetail{};
  120. bool ambiguousSocketLane = false;
  121. if (resolvedTarget != nullptr
  122. && build_data::find_item_definition_index(resolvedTarget->instance.baseDefinitionIndex,
  123. targetDefinition)
  124. && targetDefinition.definitionIndex == resolvedTarget->instance.baseDefinitionIndex
  125. && build_data::find_configured_item_detail(targetDefinition.definitionIndex, targetDetail)
  126. && targetDetail.definitionIndex == targetDefinition.definitionIndex
  127. && targetDetail.definitionHash == targetDefinition.definitionHash
  128. && targetDetail.bucketId == targetDefinition.bucketId
  129. && targetDetail.ordinarySocketState == item_details::OrdinarySocketState::present
  130. && targetDetail.ordinarySocketCount <= authored_inventory::kPlugCapacity) {
  131. // Most action kinds are the physical ordinary-socket lane. Prefer that exact lane when
  132. // its installed pool accepts the plug; this disambiguates armour items whose two mod
  133. // sockets intentionally expose the same pool. Some action kinds are semantic categories
  134. // instead (notably shaders), so retain the unique-compatible-lane fallback for those.
  135. if (requestedSocketLane < targetDetail.ordinarySocketCount
  136. && build_data::is_socket_plug_allowed(
  137. targetDefinition.definitionIndex, requestedSocketLane, plugDefinitionIndex)) {
  138. resolvedSocketLane = requestedSocketLane;
  139. } else {
  140. for (std::size_t lane = 0; lane < targetDetail.ordinarySocketCount; ++lane) {
  141. if (!build_data::is_socket_plug_allowed(targetDefinition.definitionIndex,
  142. static_cast<std::uint8_t>(lane),
  143. plugDefinitionIndex)) {
  144. continue;
  145. }
  146. if (resolvedSocketLane < authored_inventory::kPlugCapacity) {
  147. ambiguousSocketLane = true;
  148. break;
  149. }
  150. resolvedSocketLane = static_cast<std::uint8_t>(lane);
  151. }
  152. }
  153. }
  154. if (resolvedTarget == nullptr || resolvedTarget->instance.instanceSoid == 0
  155. || ambiguousSocketLane || resolvedSocketLane >= authored_inventory::kPlugCapacity
  156. || !stage_socket_plug(snapshot,
  157. characterIndex,
  158. resolvedTarget->instance.instanceSoid,
  159. resolvedSocketLane,
  160. plugDefinitionIndex,
  161. mutation)
  162. || mutation.targetEquipped != expectedEquipped) {
  163. report_socket_plug("prepare_location",
  164. "fail",
  165. "equipment_or_compatibility",
  166. snapshot.characters[characterIndex].soid,
  167. resolvedTarget != nullptr ? resolvedTarget->instance.instanceSoid : 0,
  168. 0,
  169. resolvedSocketLane,
  170. plugDefinitionIndex,
  171. 0,
  172. 0,
  173. expectedEquipped,
  174. static_cast<std::size_t>(instanceIdentityToken));
  175. mutation = {};
  176. return false;
  177. }
  178. report_socket_plug("prepare_location",
  179. "ok",
  180. "ready",
  181. mutation.characterSoid,
  182. mutation.targetInstanceSoid,
  183. mutation.targetDefinitionIndex,
  184. mutation.socketLane,
  185. mutation.plugDefinitionIndex,
  186. mutation.targetBucketId,
  187. mutation.plugBucketId,
  188. mutation.targetEquipped,
  189. mutation.itemIndex);
  190. return true;
  191. }
  192. /** Produces the complete account after-image while the prepared socket action remains current. */
  193. bool preview_socket_plug(const PendingSocketPlug& mutation, AccountState& after) noexcept {
  194. after = {};
  195. if (!mutation.prepared || mutation.accountSoid == 0 || mutation.characterSoid == 0
  196. || mutation.targetInstanceSoid == 0 || mutation.characterIndex >= kCharacterCapacity
  197. || mutation.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
  198. || mutation.afterProfileItemCount > authored_inventory::kProfileItemCapacity) {
  199. return false;
  200. }
  201. const AccountState current = account_snapshot();
  202. if (mutation.characterIndex >= current.characterCount
  203. || current.primarySoid != mutation.accountSoid
  204. || !same_character(current.characters[mutation.characterIndex], mutation.beforeCharacter)
  205. || !same_profile_inventory(
  206. current, mutation.beforeProfileItems, mutation.expectedProfileItemCount)) {
  207. return false;
  208. }
  209. PendingSocketPlug canonical{};
  210. if (!stage_socket_plug(current,
  211. mutation.characterIndex,
  212. mutation.targetInstanceSoid,
  213. mutation.socketLane,
  214. mutation.requestedPlugDefinitionIndex,
  215. canonical,
  216. mutation.plugDefinitionHash)
  217. || canonical.accountSoid != mutation.accountSoid
  218. || canonical.characterSoid != mutation.characterSoid
  219. || canonical.targetDefinitionHash != mutation.targetDefinitionHash
  220. || canonical.plugDefinitionHash != mutation.plugDefinitionHash
  221. || canonical.materialRequirementSetHash != mutation.materialRequirementSetHash
  222. || canonical.characterIndex != mutation.characterIndex
  223. || canonical.expectedProfileItemCount != mutation.expectedProfileItemCount
  224. || canonical.afterProfileItemCount != mutation.afterProfileItemCount
  225. || canonical.itemIndex != mutation.itemIndex
  226. || canonical.targetDefinitionIndex != mutation.targetDefinitionIndex
  227. || canonical.plugDefinitionIndex != mutation.plugDefinitionIndex
  228. || canonical.requestedPlugDefinitionIndex != mutation.requestedPlugDefinitionIndex
  229. || canonical.materialRequirementSetIndex != mutation.materialRequirementSetIndex
  230. || canonical.socketLane != mutation.socketLane
  231. || canonical.targetBucketId != mutation.targetBucketId
  232. || canonical.plugBucketId != mutation.plugBucketId
  233. || canonical.materialRequirementCount != mutation.materialRequirementCount
  234. || canonical.profileChanged != mutation.profileChanged
  235. || canonical.targetEquipped != mutation.targetEquipped
  236. || !same_character(canonical.beforeCharacter, mutation.beforeCharacter)
  237. || !same_character(canonical.afterCharacter, mutation.afterCharacter)) {
  238. return false;
  239. }
  240. after = current;
  241. after.profileItems = canonical.afterProfileItems;
  242. after.profileItemCount = canonical.afterProfileItemCount;
  243. after.characters[mutation.characterIndex] = canonical.afterCharacter;
  244. const bool balancesChanged = !same_profile_inventory(
  245. after, mutation.beforeProfileItems, mutation.expectedProfileItemCount);
  246. family4_loadout::ResolvedLoadout resolved{};
  247. return balancesChanged == mutation.profileChanged
  248. && same_profile_inventory(
  249. after, mutation.afterProfileItems, mutation.afterProfileItemCount)
  250. && account::valid(after) && valid_profile_inventory(after)
  251. && family4_loadout::resolve(after, mutation.characterIndex, resolved);
  252. }
  253. /** Commits one prepared socket-and-material after-image behind exact staleness guards. */
  254. bool commit_socket_plug(PendingSocketPlug& mutation) noexcept {
  255. const PendingSocketPlug prepared = mutation;
  256. mutation = {};
  257. const auto fail = [&prepared](std::string_view reason) noexcept {
  258. report_socket_plug("commit",
  259. "fail",
  260. reason,
  261. prepared.characterSoid,
  262. prepared.targetInstanceSoid,
  263. prepared.targetDefinitionIndex,
  264. prepared.socketLane,
  265. prepared.plugDefinitionIndex,
  266. prepared.targetBucketId,
  267. prepared.plugBucketId,
  268. prepared.targetEquipped,
  269. prepared.itemIndex);
  270. return false;
  271. };
  272. const std::size_t itemCapacity = prepared.targetEquipped
  273. ? authored_inventory::kEquipmentSlotCount
  274. : authored_inventory::kCharacterItemCapacity;
  275. if (!prepared.prepared || prepared.accountSoid == 0 || prepared.characterSoid == 0
  276. || prepared.targetInstanceSoid == 0
  277. || prepared.targetDefinitionHash == authored_inventory::kNoDefinitionHash
  278. || prepared.plugDefinitionHash == authored_inventory::kNoDefinitionHash
  279. || prepared.characterIndex >= kCharacterCapacity || prepared.itemIndex >= itemCapacity
  280. || prepared.expectedProfileItemCount > authored_inventory::kProfileItemCapacity
  281. || prepared.afterProfileItemCount > authored_inventory::kProfileItemCapacity
  282. || prepared.socketLane >= authored_inventory::kPlugCapacity
  283. || prepared.beforeCharacter.soid != prepared.characterSoid
  284. || prepared.afterCharacter.soid != prepared.characterSoid) {
  285. return fail("mutation");
  286. }
  287. report_socket_plug("commit_begin",
  288. "ok",
  289. "ready",
  290. prepared.characterSoid,
  291. prepared.targetInstanceSoid,
  292. prepared.targetDefinitionIndex,
  293. prepared.socketLane,
  294. prepared.plugDefinitionIndex,
  295. prepared.targetBucketId,
  296. prepared.plugBucketId,
  297. prepared.targetEquipped,
  298. prepared.itemIndex);
  299. AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
  300. AccountState candidate = runtime::storage::g_state.account;
  301. if (prepared.characterIndex >= candidate.characterCount
  302. || candidate.primarySoid != prepared.accountSoid
  303. || !same_profile_inventory(
  304. candidate, prepared.beforeProfileItems, prepared.expectedProfileItemCount)
  305. || !same_character(candidate.characters[prepared.characterIndex],
  306. prepared.beforeCharacter)) {
  307. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  308. return fail("stale");
  309. }
  310. PendingSocketPlug canonical{};
  311. if (!stage_socket_plug(candidate,
  312. prepared.characterIndex,
  313. prepared.targetInstanceSoid,
  314. prepared.socketLane,
  315. prepared.requestedPlugDefinitionIndex,
  316. canonical,
  317. prepared.plugDefinitionHash)
  318. || canonical.characterSoid != prepared.characterSoid
  319. || canonical.accountSoid != prepared.accountSoid
  320. || canonical.targetDefinitionHash != prepared.targetDefinitionHash
  321. || canonical.plugDefinitionHash != prepared.plugDefinitionHash
  322. || canonical.materialRequirementSetHash != prepared.materialRequirementSetHash
  323. || canonical.characterIndex != prepared.characterIndex
  324. || canonical.expectedProfileItemCount != prepared.expectedProfileItemCount
  325. || canonical.afterProfileItemCount != prepared.afterProfileItemCount
  326. || canonical.itemIndex != prepared.itemIndex
  327. || canonical.targetDefinitionIndex != prepared.targetDefinitionIndex
  328. || canonical.plugDefinitionIndex != prepared.plugDefinitionIndex
  329. || canonical.requestedPlugDefinitionIndex != prepared.requestedPlugDefinitionIndex
  330. || canonical.materialRequirementSetIndex != prepared.materialRequirementSetIndex
  331. || canonical.socketLane != prepared.socketLane
  332. || canonical.targetBucketId != prepared.targetBucketId
  333. || canonical.plugBucketId != prepared.plugBucketId
  334. || canonical.materialRequirementCount != prepared.materialRequirementCount
  335. || canonical.profileChanged != prepared.profileChanged
  336. || canonical.targetEquipped != prepared.targetEquipped
  337. || !same_character(canonical.beforeCharacter, prepared.beforeCharacter)
  338. || !same_character(canonical.afterCharacter, prepared.afterCharacter)) {
  339. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  340. return fail("transition");
  341. }
  342. candidate.profileItems = canonical.afterProfileItems;
  343. candidate.profileItemCount = canonical.afterProfileItemCount;
  344. if (!same_profile_inventory(
  345. candidate, prepared.afterProfileItems, prepared.afterProfileItemCount)) {
  346. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  347. return fail("materials");
  348. }
  349. candidate.characters[prepared.characterIndex] = canonical.afterCharacter;
  350. family4_loadout::ResolvedLoadout checked{};
  351. if (!account::valid(candidate) || !valid_profile_inventory(candidate)
  352. || !family4_loadout::resolve(candidate, prepared.characterIndex, checked)) {
  353. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  354. return fail("account_or_resolve");
  355. }
  356. runtime::storage::g_state.account = candidate;
  357. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  358. report_socket_plug("commit_end",
  359. "ok",
  360. "published",
  361. prepared.characterSoid,
  362. prepared.targetInstanceSoid,
  363. prepared.targetDefinitionIndex,
  364. prepared.socketLane,
  365. prepared.plugDefinitionIndex,
  366. prepared.targetBucketId,
  367. prepared.plugBucketId,
  368. prepared.targetEquipped,
  369. prepared.itemIndex);
  370. return true;
  371. }
  372. /** Prepares one complete native item-state value for an owned selected-character instance. */
  373. bool prepare_item_state(std::uint64_t targetInstanceSoid,
  374. std::uint16_t targetDefinitionIndex,
  375. std::uint32_t flags,
  376. PendingItemState& mutation) noexcept {
  377. mutation = {};
  378. const AccountState snapshot = account_snapshot();
  379. std::size_t characterIndex = snapshot.characterCount;
  380. if (account::valid(snapshot)) {
  381. for (std::size_t index = 0; index < snapshot.characterCount; ++index) {
  382. if (snapshot.characters[index].selected) {
  383. characterIndex = index;
  384. break;
  385. }
  386. }
  387. }
  388. if (characterIndex >= snapshot.characterCount
  389. || !stage_item_state(
  390. snapshot, characterIndex, targetInstanceSoid, targetDefinitionIndex, flags, mutation)) {
  391. report_item_state(
  392. "prepare",
  393. "fail",
  394. "selection_or_item",
  395. characterIndex < snapshot.characterCount ? snapshot.characters[characterIndex].soid : 0,
  396. targetInstanceSoid,
  397. targetDefinitionIndex,
  398. 0,
  399. flags,
  400. false,
  401. 0);
  402. mutation = {};
  403. return false;
  404. }
  405. report_item_state("prepare",
  406. "ok",
  407. "ready",
  408. mutation.characterSoid,
  409. mutation.targetInstanceSoid,
  410. mutation.targetDefinitionIndex,
  411. mutation.beforeFlags,
  412. mutation.afterFlags,
  413. mutation.targetEquipped,
  414. mutation.itemIndex);
  415. return true;
  416. }
  417. /** Commits one prepared item-state after-image behind an exact character staleness guard. */
  418. bool commit_item_state(PendingItemState& mutation) noexcept {
  419. const PendingItemState prepared = mutation;
  420. mutation = {};
  421. const auto fail = [&prepared](std::string_view reason) noexcept {
  422. report_item_state("commit",
  423. "fail",
  424. reason,
  425. prepared.characterSoid,
  426. prepared.targetInstanceSoid,
  427. prepared.targetDefinitionIndex,
  428. prepared.beforeFlags,
  429. prepared.afterFlags,
  430. prepared.targetEquipped,
  431. prepared.itemIndex);
  432. return false;
  433. };
  434. const std::size_t capacity = prepared.targetEquipped
  435. ? authored_inventory::kEquipmentSlotCount
  436. : authored_inventory::kCharacterItemCapacity;
  437. if (!prepared.prepared || prepared.characterSoid == 0 || prepared.targetInstanceSoid == 0
  438. || prepared.characterIndex >= kCharacterCapacity || prepared.itemIndex >= capacity
  439. || prepared.beforeCharacter.soid != prepared.characterSoid
  440. || prepared.afterCharacter.soid != prepared.characterSoid
  441. || prepared.beforeFlags == prepared.afterFlags) {
  442. return fail("mutation");
  443. }
  444. AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
  445. AccountState candidate = runtime::storage::g_state.account;
  446. if (prepared.characterIndex >= candidate.characterCount
  447. || !same_character(candidate.characters[prepared.characterIndex],
  448. prepared.beforeCharacter)) {
  449. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  450. return fail("stale");
  451. }
  452. PendingItemState canonical{};
  453. if (!stage_item_state(candidate,
  454. prepared.characterIndex,
  455. prepared.targetInstanceSoid,
  456. prepared.targetDefinitionIndex,
  457. prepared.afterFlags,
  458. canonical)
  459. || canonical.characterSoid != prepared.characterSoid
  460. || canonical.characterIndex != prepared.characterIndex
  461. || canonical.itemIndex != prepared.itemIndex
  462. || canonical.targetDefinitionIndex != prepared.targetDefinitionIndex
  463. || canonical.beforeFlags != prepared.beforeFlags
  464. || canonical.afterFlags != prepared.afterFlags
  465. || canonical.targetEquipped != prepared.targetEquipped
  466. || !same_character(canonical.beforeCharacter, prepared.beforeCharacter)
  467. || !same_character(canonical.afterCharacter, prepared.afterCharacter)) {
  468. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  469. return fail("transition");
  470. }
  471. candidate.characters[prepared.characterIndex] = prepared.afterCharacter;
  472. family4_loadout::ResolvedLoadout checked{};
  473. if (!account::valid(candidate)
  474. || !family4_loadout::resolve(candidate, prepared.characterIndex, checked)) {
  475. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  476. return fail("account_or_resolve");
  477. }
  478. runtime::storage::g_state.account = candidate;
  479. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  480. report_item_state("commit",
  481. "ok",
  482. "published",
  483. prepared.characterSoid,
  484. prepared.targetInstanceSoid,
  485. prepared.targetDefinitionIndex,
  486. prepared.beforeFlags,
  487. prepared.afterFlags,
  488. prepared.targetEquipped,
  489. prepared.itemIndex);
  490. return true;
  491. }
  492. } // namespace sunrise::state