Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
UserInterface.cpp
Go to the documentation of this file.
1#include "UserInterface.hpp"
2#include "LegacyTextIds.hpp"
3#include "UISound.hpp"
4#include "Engine.hpp"
5#include "Display.hpp"
6#include "TimeStep.hpp"
7#include "PlayerSelection.hpp"
8#include "IBar.hpp"
9#include "Timers.hpp"
10#include "LocalPlayers.hpp"
11#include "PieceCamera.hpp"
12#include "RuleArchive.hpp"
13#include "Messaging.hpp"
14#include "ChatRuntime.hpp"
15#include "VoiceChatRuntime.hpp"
16#include "UDPennyVoice.hpp"
22
23#include "RuntimeState.hpp"
24
25#include <SDL3/SDL.h>
26
27#include <algorithm>
28#include <bit>
29#include <array>
30#include <cstddef>
31#include <cstdlib>
32#include <limits>
33#include <utility>
34
36{
37 namespace
38 {
39 dice::PromptState dicePrompt;
40 ibar::RuleProjection iBarRuleProjection;
41 auctionui::State auctionProjection;
42 tradeui::State tradeProjection;
43 optionsui::State optionsProjection;
44 optionsui::SaveRuntimeState optionsSaveProjection;
45 optionsui::CustomBoardState optionsCustomBoardProjection;
46 statsui::State statsProjection;
47 statsui::CalculatorUIState statsCalculatorProjection;
48 statsui::FutureImmunityState statsFutureImmunityProjection;
49
50 constexpr std::array<std::uint32_t, rules::SquareCount> ResyncPropertyBits{{
51 0, 1u<<0, 0, 1u<<1, 0, 1u<<2, 1u<<3, 0, 1u<<4, 1u<<5,
52 0, 1u<<6, 1u<<7, 1u<<8, 1u<<9, 1u<<10, 1u<<11, 0, 1u<<12, 1u<<13,
53 0, 1u<<14, 0, 1u<<15, 1u<<16, 1u<<17, 1u<<18, 1u<<19, 1u<<20, 1u<<21,
54 0, 1u<<22, 1u<<23, 0, 1u<<24, 1u<<25, 0, 1u<<26, 0, 1u<<27, 0, 0
55 }};
56
58 {
59 switch (action)
60 {
71 return true;
72 default:
73 return false;
74 }
75 }
76
78 rules::GameState& state,
79 const std::vector<std::uint8_t>& data,
80 std::uint8_t* resyncCause = nullptr)
81 {
82 constexpr std::size_t deckCount =
83 static_cast<std::size_t>(rules::DeckType::Count);
84 constexpr std::size_t expected = 1 + rules::MaxPlayers * 8 +
86 rules::SquareCount + 1 + 1 + 4 + 1;
87 if (data.size() != expected || data[0] != 1) return false;
88
89 std::size_t offset = 1;
90 auto readU8 = [&]() { return data[offset++]; };
91 auto readU32 = [&]() {
92 std::uint32_t value{};
93 for (int shift = 0; shift < 32; shift += 8)
94 value |= static_cast<std::uint32_t>(data[offset++]) << shift;
95 return value;
96 };
97 auto readI64 = [&]() {
98 std::uint64_t raw{};
99 for (int shift = 0; shift < 64; shift += 8)
100 raw |= static_cast<std::uint64_t>(data[offset++]) << shift;
101 return std::bit_cast<std::int64_t>(raw);
102 };
103
104 rules::GameState next = state;
105 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
106 next.players[player].cash = readI64();
107
108 std::array<std::uint32_t, rules::MaxPlayers> owned{};
109 for (auto& properties : owned) properties = readU32();
110 const std::uint32_t mortgaged = readU32();
111
112 for (auto& square : next.squares)
113 {
114 square.owner = rules::NobodyPlayer;
115 square.mortgaged = false;
116 square.houses = 0;
117 }
118
119 for (std::size_t squareNo = 0; squareNo <= 39; ++squareNo)
120 {
121 const auto bit = ResyncPropertyBits[squareNo];
122 if (bit == 0) continue;
124 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
125 {
126 if ((owned[player] & bit) == 0) continue;
127 if (owner != rules::NobodyPlayer) return false;
128 owner = player;
129 }
130 next.squares[squareNo].owner = owner;
131 next.squares[squareNo].mortgaged = (mortgaged & bit) != 0;
132 }
133
134 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
135 {
136 const auto square = readU8();
137 if (square >= rules::SquareCount) return false;
138 next.players[player].currentSquare = square;
139 }
140 for (std::size_t deck = 0; deck < deckCount; ++deck)
141 {
142 const auto owner = readU8();
143 if (owner > rules::NobodyPlayer) return false;
144 next.cards[deck].jailOwner = owner;
145 }
146 for (std::size_t square = 0; square < rules::SquareCount; ++square)
147 next.squares[square].houses = readU8();
148
149 const auto cause = readU8();
150 if (cause > 4) return false;
151 (void)readU8(); // authoritative RULE phase
152 const std::uint32_t firstMoves = readU32();
153 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
154 next.players[player].firstMoveMade = (firstMoves & (1u << player)) != 0;
155 const auto current = readU8();
156 // Before a game starts the authoritative state has no current
157 // player. Network admission can legitimately resync that state.
158 if (current >= rules::MaxPlayers && current != rules::NobodyPlayer)
159 return false;
160 next.currentPlayer = current;
161 state = std::move(next);
162 if (resyncCause) *resyncCause = cause;
163 return true;
164 }
165 }
236 namespace
237 {
239 pieces::PieceMoveIngress pieceMoveIngress;
240 pieces::PieceIdleState pieceIdleState;
241 dice::Ingress diceIngress;
242 std::optional<pieces::PieceIdleTransitionPlan> pendingPieceIdleTransition;
243 bool iBarGameJustLoaded = false;
245 std::int64_t lastHousingShortageCount = 2;
246 std::array<std::uint64_t, rules::MaxPlayers> lastRaiseMoneySoundTick{};
247 std::array<std::uint64_t, rules::MaxPlayers> lastBssmBuySoundTick{};
248 std::uint64_t lastTradeInitiatorSoundTick{};
249 inline constexpr std::uint64_t RaiseMoneyRepeatTicks = 40u * 60u;
250 inline constexpr std::uint64_t BssmRepeatTicks = 30u * 60u;
251 inline constexpr std::uint64_t TradeInitiatorRepeatTicks = 5u * 60u;
252
254 {
256 return;
257
258 const auto player = iBarRuleProjection.player;
259 if (player >= uiRuleState.numberOfPlayers || player >= rules::MaxPlayers)
260 return;
261
262 // UDIBar.cpp::UDIBAR_setIBarRulesState: the first non-Nothing
263 // RULE state after SetUpLoadedGame owns the center idle and camera.
264 iBarGameJustLoaded = false;
267 uiRuleState.players[player].currentSquare);
268
269 // SetUpLoadedGame has already rebuilt every resting slot. The
270 // retail handoff does not animate RestToCenter here: it simply
271 // excludes the selected player from resting occupancy and makes it
272 // CurrentPlayerInCenterIdle.
274 if (!pieceIdleState.initialize(uiRuleState, player))
275 pieceIdleState.reset();
276 }
277
278 void maybePlayRaiseMoneySuggestion(rules::PlayerNumber player, std::uint64_t tick) noexcept
279 {
280 if (player >= rules::MaxPlayers)
281 return;
282 if (tick <= lastRaiseMoneySoundTick[player] + RaiseMoneyRepeatTicks ||
284 return;
286 return;
289 lastRaiseMoneySoundTick[player] = tick;
290 }
291
292 void maybePlayFirstHouseComment(rules::PlayerNumber player, std::uint64_t tick) noexcept
293 {
294 if (player >= rules::MaxPlayers ||
295 tick <= lastBssmBuySoundTick[player] + BssmRepeatTicks)
296 return;
297 if (lastBssmBuySoundTick[player] == 0)
300 lastBssmBuySoundTick[player] = tick;
301 }
302
303 void maybePlayTradeInitiatorComment(std::uint64_t tick) noexcept
304 {
305 if (!engine::isUsaBoardEdition()) return;
306 const auto playerA = tradeProjection.playerA;
307 const auto playerB = tradeProjection.playerB;
308 if (playerA >= uiRuleState.numberOfPlayers || playerA >= rules::MaxPlayers ||
309 playerB >= uiRuleState.numberOfPlayers || playerB >= rules::MaxPlayers ||
311 return;
312
314 std::optional<udsound::PennybagsVoice> voice;
316 {
317 if (uiRuleState.players[playerA].aiPlayerLevel == 0)
318 {
322 }
323 else
325 }
326 else if (uiRuleState.players[playerA].aiPlayerLevel != 0 &&
327 uiRuleState.players[playerB].aiPlayerLevel != 0)
329
330 if (voice)
333 }
334
336 const std::optional<penny::TokenReaction>& reaction) noexcept
337 {
338 if (!reaction || player >= uiRuleState.numberOfPlayers ||
339 player >= rules::MaxPlayers)
340 return;
341 engine::playTokenVoice(uiRuleState.players[player].token,
342 reaction->line, reaction->policy, reaction->watchAfterStart);
343 }
344
346 const std::optional<penny::PennybagsReaction>& reaction) noexcept
347 {
348 if (!reaction) return;
350 reaction->policy, reaction->watchAfterStart);
351 }
352
354 {
355 const auto count = std::min<rules::PlayerNumber>(
356 uiRuleState.numberOfPlayers, rules::MaxPlayers);
357 if (count == 0) return rules::SpectatorPlayer;
358 auto player = uiRuleState.currentPlayer < count
359 ? uiRuleState.currentPlayer : static_cast<rules::PlayerNumber>(count - 1u);
361 {
362 if (ui::localplayers::slotIsLocalHumanPlayer(player)) return player;
363 player = player == 0 ? static_cast<rules::PlayerNumber>(count - 1u)
364 : static_cast<rules::PlayerNumber>(player - 1u);
365 }
367 }
368
369 [[nodiscard]] std::uint32_t chatEligibleRecipients() noexcept
370 {
371 std::uint32_t mask{};
372 for (rules::PlayerNumber player = 0; player < uiRuleState.numberOfPlayers &&
373 player < rules::MaxPlayers; ++player)
375 uiRuleState.players[player].aiPlayerLevel == 0)
376 mask |= 1u << player;
377 return mask;
378 }
379
380 [[nodiscard]] std::uint32_t localHumanPlayerMask() noexcept
381 {
382 std::uint32_t mask{};
383 const auto count = std::min<rules::PlayerNumber>(
384 uiRuleState.numberOfPlayers, rules::MaxPlayers);
385 for (rules::PlayerNumber player = 0; player < count; ++player)
387 mask |= (1u << player);
388 return mask;
389 }
390
391
393 bool startingNewGame)
394 {
395 // UDPsel.cpp original, NOTIFY_NUMBER_OF_PLAYERS :
396 // l'état complet n'est effacé que pour un compteur nul. La
397 // première notification non nulle initialise néanmoins les
398 // invariants d'affichage et les joueurs locaux.
399 if (startingNewGame)
400 {
401 uiRuleState = {};
402 }
403
404
405 uiRuleState.options.housesPerHotel =
406 5;
407
408
409 for (auto& square :
410 uiRuleState.squares)
411 {
412 square.owner =
414
415 square.houses = 0;
416 }
417
418
419 for (auto& player :
421 {
422 player.currentSquare = 41;
423 }
424 }
425 }
426
428
429 std::expected<void, std::string> sendReadyResponses(
430 std::uint32_t playerMask,
431 std::int64_t serial)
432 {
433 const auto count = std::min<rules::PlayerNumber>(
434 uiRuleState.numberOfPlayers, rules::MaxPlayers);
435 std::size_t required{};
436 for (rules::PlayerNumber player = 0; player < count; ++player)
437 {
438 if ((playerMask & (1u << player)) != 0 &&
440 {
441 ++required;
442 }
443 }
444
445 const auto queued = messaging::queuedActionCount();
446 if (queued > messaging::MessageQueueCapacity ||
448 {
449 return std::unexpected(
450 "message queue cannot fit I_AM_HERE responses");
451 }
452
453 for (rules::PlayerNumber player = 0; player < count; ++player)
454 {
455 if ((playerMask & (1u << player)) == 0 ||
457 {
458 continue;
459 }
462 {
463 return std::unexpected(
464 "validated I_AM_HERE response was rejected");
465 }
466 }
467 return {};
468 }
469
470
472 const std::vector<actions::Message>& batch)
473 {
474 if (batch.empty()) return true;
475 const auto queued = messaging::queuedActionCount();
476 if (queued > messaging::MessageQueueCapacity ||
477 batch.size() > messaging::MessageQueueCapacity - queued)
478 return false;
479 for (const auto& action : batch)
480 if (!messaging::sendAction(action)) return false;
481 return true;
482 }
483
484
485 bool beginTradeFromIBar(rules::PlayerNumber iBarPlayer) noexcept
486 {
487 if (!runtime::state().gameInProgress ||
488 display::state().desired2DView == display::Screen2D::Trade ||
489 iBarPlayer >= uiRuleState.numberOfPlayers ||
490 iBarPlayer >= rules::MaxPlayers ||
491 uiRuleState.players[iBarPlayer].currentSquare >= tradeui::OffBoardSquare)
492 {
493 return false;
494 }
495
497 uiRuleState, iBarPlayer);
499 return false;
500
502 const bool storedTradeValid =
506 !tradeProjection.items.empty();
507 const bool freshTrade = !storedTradeValid;
508 if (!storedTradeValid &&
510 {
511 return false;
512 }
514 tradeProjection.ignoreEntryClick = true;
515
517 if (freshTrade)
519 uiRuleState.numberOfPlayers < 3
523 return true;
524 }
526 {
527 if (!runtime::state().gameInProgress)
528 return false;
529
530 const auto& displayState = display::state();
531 auto previousView = displayState.current2DView;
532 if (!display::isIBarVisible(previousView))
533 previousView = displayState.desired2DView;
535 return false;
536
538 return true;
539 }
540
541
542
569
570
572 const actions::Message& message)
573 {
574 // ====================================================
575 // ProcessMessageToPlayer() boundary.
576 // ====================================================
577
579 {
580 return;
581 }
582
584 message.numberA == static_cast<std::int64_t>(actions::Type::NewGame) && message.numberB != 0)
585 {
588 }
589
590 // UDIBar.cpp resets the board demo idle timer on every delivered RULE message.
592 if (auto* accounts = engine::statsAccounts())
593 {
595 {
596 if (const auto reset = accounts->resetGame(); !reset)
597 SDL_Log("Stats account reset: %s", reset.error().c_str());
598 }
599 if (const auto resources = startup::resources())
600 {
601 const auto recorded = accounts->processRuleMessage(message, uiRuleState,
603 display::stateReadOnly().city, *resources);
604 if (!recorded) SDL_Log("Stats account history: %s", recorded.error().c_str());
605 }
606 }
607
608 const bool chatBroadcastTarget = message.numberA >= rules::MaxPlayers;
609 const bool chatLocalTarget = message.numberA >= 0 &&
610 message.numberA < rules::MaxPlayers &&
612 static_cast<rules::PlayerNumber>(message.numberA));
613
614 if (message.action != actions::Type::NotifyTextChat ||
616 {
617 // Userifce.cpp only delivers private text chat to the machine that
618 // owns the target slot. Broadcast/spectator targets (>= MaxPlayers)
619 // are visible everywhere. message.toPlayer is only the transport
620 // recipient and cannot replace this application-level filter.
623 }
624
625 if (message.action == actions::Type::NotifyVoiceChat &&
627 {
628 // The legacy UI applies the same application-level target filter
629 // before handing the RIFF-like voice packet to LE_SOUND_ChatReceive.
631 }
632
633 // Userifce.cpp retail splits error 71 between host-left warning and
634 // the "human replaced by computer" Pennybags comment.
635 if (message.action == actions::Type::NotifyErrorMessage && message.numberA == 71)
636 {
637 if (message.numberC == 6)
639 else
643 }
644
646 {
649 uiRuleState.options = std::move(received);
650 }
651
653 {
654 std::uint8_t cause{};
655 const bool resynced = applyClientResyncBlob(uiRuleState, message.binaryDataA, &cause);
657 if (resynced && cause == 2)
658 {
660 if (auto* accounts = engine::statsAccounts())
661 if (const auto reset = accounts->resetGame(); !reset)
662 SDL_Log("Stats loaded-game history reset: %s", reset.error().c_str());
666 for (auto& hit : uiRuleState.countHits)
667 {
668 hit.tradedItem = false;
669 hit.toPlayer = rules::NobodyPlayer;
670 }
672 if (!pieceIdleState.initialize(uiRuleState))
673 pieceIdleState.reset();
674 iBarRuleProjection.reset();
675 iBarRuleProjection.player = 0;
676 iBarGameJustLoaded = true;
677 }
678 // LE_SOUND_ChatOn calls ChatOff even when already recording. This
679 // announces CHAT to newly admitted peers before subsequent DATN.
680 if (resynced)
681 {
684 }
685 }
686
687 dicePrompt.process(message);
689 message, iBarRuleProjection.mode, timers::tickCount());
690
692 message.numberA == static_cast<std::int64_t>(actions::Type::TradeAccept))
693 {
694 // UDIBar::ActionCompleted restores IBarStateTrackOn for TradeAccept
695 // regardless of the accepted/failed result.
697 }
698
700 message.numberA == static_cast<std::int64_t>(actions::Type::StartTradeEditing) &&
701 message.numberB == 0 &&
702 message.numberC >= 0 && message.numberC < rules::MaxPlayers &&
704 static_cast<rules::PlayerNumber>(message.numberC)))
705 {
706 // UDTrade.cpp warns only on the machine that owns the human
707 // whose proposal/edit request was rejected.
709 }
710
712 message.numberA == static_cast<std::int64_t>(actions::Type::GoBankrupt) &&
713 message.numberB == 0)
714 {
715 // A rejected bankruptcy request keeps the mode and emits Warning.
717 }
718
720 message.fromPlayer == rules::BankPlayer &&
721 optionsSaveProjection.pendingSaveSlot &&
722 message.toPlayer == optionsSaveProjection.pendingSaveRequester)
723 {
727 }
728
730 message.numberA == static_cast<std::int64_t>(actions::Type::GetGameStateForSave) &&
731 message.numberB == 0 &&
732 message.numberC >= 0 && message.numberC < rules::MaxPlayers &&
734 static_cast<rules::PlayerNumber>(message.numberC)))
735 {
736 // A rejected retail save request cannot leave a stale pending slot:
737 // no NOTIFY_GAME_STATE_FOR_SAVE will follow this failed action.
738 if (message.fromPlayer == rules::BankPlayer &&
739 message.numberC == optionsSaveProjection.pendingSaveRequester)
741 // UDIBar uses WAV_tmpnext here, deliberately not the generic warning.
743 }
744
746 message.fromPlayer == rules::BankPlayer &&
748 optionsSaveProjection.pendingSaveSlot &&
749 message.numberC == optionsSaveProjection.pendingSaveRequester)
750 {
752 }
753
755 message.numberA >= 0 && message.numberA <= rules::NobodyPlayer)
756 {
758 static_cast<rules::PlayerNumber>(message.numberA));
759 }
760
762 message.numberB != 0)
764
765 if (message.action == actions::Type::NotifyPleasePay &&
766 message.numberA >= 0 && message.numberA < rules::MaxPlayers)
768 static_cast<rules::PlayerNumber>(message.numberA), timers::tickCount());
770 message.numberA >= 0 && message.numberA < rules::MaxPlayers &&
771 message.numberB != 0 &&
773 static_cast<rules::PlayerNumber>(message.numberA)))
775
777 message.numberA >= 0 && message.numberA < rules::MaxPlayers)
778 uiRuleState.players[static_cast<std::size_t>(message.numberA)].cash =
779 message.numberC;
780
782 message.numberA >= 0 && message.numberA < rules::SquareCount &&
783 message.numberB >= 0 && message.numberB <= rules::EscrowPlayer)
784 uiRuleState.squares[static_cast<std::size_t>(message.numberA)].owner =
785 static_cast<rules::PlayerNumber>(message.numberB);
786
788 message.numberA >= 0 && message.numberA < rules::SquareCount)
789 {
790 auto& square = uiRuleState.squares[static_cast<std::size_t>(message.numberA)];
791 square.mortgaged = message.numberB != 0;
792 if (square.owner < rules::MaxPlayers)
793 {
795 display::requestBssmCamera(static_cast<std::int32_t>(message.numberA),
796 message.numberB != 0 ? 2U : 3U);
797 }
798 }
799
801 message.numberA >= 0 && message.numberA < rules::SquareCount &&
802 message.numberB >= 0 && message.numberB <= 255 &&
803 message.numberC >= 0 && message.numberC <= 255)
804 {
805 auto& square = uiRuleState.squares[static_cast<std::size_t>(message.numberA)];
806 const auto previousHouses = square.houses;
807 square.houses = static_cast<std::uint8_t>(message.numberB);
808 uiRuleState.options.housesPerHotel = static_cast<std::uint8_t>(message.numberC);
809 if (square.owner < rules::MaxPlayers)
810 {
811 if (previousHouses < square.houses)
812 {
814 display::requestBssmCamera(static_cast<std::int32_t>(message.numberA), 0U);
815 }
816 else
817 {
819 display::requestBssmCamera(static_cast<std::int32_t>(message.numberA), 1U);
820 }
821 }
822 if (previousHouses < square.houses)
824 }
825
827 uiRuleState.freeParkingJackpotAmount = message.numberA;
828
830 message.numberA >= 0 && message.numberA <= rules::NobodyPlayer &&
831 message.numberB >= 0 &&
832 message.numberB < static_cast<std::int64_t>(rules::DeckType::Count))
833 {
834 uiRuleState.cards[static_cast<std::size_t>(message.numberB)].jailOwner =
835 static_cast<rules::PlayerNumber>(message.numberA);
836 }
837
840 message.numberA >= 0 && message.numberA < rules::MaxPlayers &&
841 message.numberD >= 0 && message.numberD < rules::MaxPlayers &&
842 message.numberB >= std::numeric_limits<std::int32_t>::min() &&
843 message.numberB <= std::numeric_limits<std::int32_t>::max() &&
844 message.numberE >= 0 &&
845 message.numberE <= std::numeric_limits<std::uint32_t>::max())
846 {
849 static_cast<rules::PlayerNumber>(message.numberD),
850 static_cast<rules::PlayerNumber>(message.numberA),
854 static_cast<std::int32_t>(message.numberB),
855 static_cast<std::uint32_t>(message.numberE), false);
856 }
857
859 message.numberB != 0 &&
860 message.numberA == static_cast<std::int64_t>(actions::Type::CardSeen))
861 {
863 }
864
866 message.numberA >= 0 && message.numberA < uiRuleState.numberOfPlayers &&
867 message.numberA < rules::MaxPlayers)
868 {
869 const auto player = static_cast<rules::PlayerNumber>(message.numberA);
871 }
872
874 message.numberB != 0 &&
875 message.numberA == static_cast<std::int64_t>(actions::Type::BuyOrAuctionDecision) &&
876 message.numberC >= 0 && message.numberC < uiRuleState.numberOfPlayers &&
877 message.numberC < rules::MaxPlayers)
878 {
879 const auto player = static_cast<rules::PlayerNumber>(message.numberC);
880 const auto property = uiRuleState.players[player].currentSquare;
881 if (message.numberD != 0)
883 uiRuleState, player, property));
884 const auto reaction = message.numberD != 0
885 ? penny::boughtPropertyReaction(uiRuleState, player, property)
888 }
890 message.numberC != static_cast<std::int64_t>(actions::Type::NotifyNewHighBid))
891 {
892 // Userifce.cpp::Process_NOTIFY_ARE_YOU_THERE responds immediately
893 // for ordinary roll-calls. Auction readiness remains deliberately
894 // deferred until its Pennybags/graphics intro reaches Begin.
895 (void)sendReadyResponses(static_cast<std::uint32_t>(message.numberA),
896 message.numberB);
897 }
898
900 auctionProjection, uiRuleState, message, display::state().desired2DView);
901 if (auctionUpdate.requestedBackdrop)
902 display::setBackdrop(*auctionUpdate.requestedBackdrop);
904 message.numberA == rules::BankPlayer)
905 {
906 // UDAuct.cpp clears the RULE button bar for every bank high-bid
907 // notification, including a zero-bid gong while Auction is visible.
909 iBarRuleProjection.player = uiRuleState.currentPlayer;
910 }
912 {
914 if (message.numberA >= 0 && message.numberA < rules::MaxPlayers)
915 originalBuyer = static_cast<rules::PlayerNumber>(message.numberA);
916 const std::uint32_t allowedPlayers = message.numberE > 0
917 ? static_cast<std::uint32_t>(message.numberE)
918 : 0u;
921 iBarRuleProjection.processHousingShortage(message, shortagePlayer);
926 {
927 if (message.numberD == 0 && lastHousingShortageCount != message.numberD)
932 }
933 }
934 else
935 {
937 message.numberA >= 0 && message.numberA < rules::MaxPlayers)
938 {
939 // UDTrade.cpp selects the editor before deciding whether this
940 // machine owns that slot, leaving Nobody for remote editors.
942 static_cast<rules::PlayerNumber>(message.numberA));
943 }
944
945 iBarRuleProjection.process(message);
947 {
948 // Retail ignores numberA for CurrentUIPlayer here and selects
949 // exactly the reconstructed TradeB slot. Spectators therefore
950 // keep Nobody even though the IBar may watch a remote pending
951 // player from numberA.
952 const auto tradeBPlayer = iBarRuleProjection.tradeBPlayer;
953 const std::uint32_t tradeBSet = tradeBPlayer < rules::MaxPlayers
954 ? (1u << tradeBPlayer)
955 : 0u;
958
959 const std::uint32_t pendingPlayers = message.numberA > 0
960 ? static_cast<std::uint32_t>(message.numberA)
961 : 0u;
963 uiRuleState, tradeBPlayer, pendingPlayers);
964 iBarRuleProjection.processTradeAcceptance(message, tradePlayer);
965 }
966 }
967
969 {
970 // UDTrade.cpp first sets Nothing/CurrentPlayer through
971 // UDIBAR_setIBarRulesState, which replaces IBarLastRulePlayer.
972 // Its following comparison therefore always restores tracking.
974 iBarRuleProjection.player = uiRuleState.currentPlayer;
976 }
977
979
983 display::state().desired2DView, localHumanPlayerMask(),
986 if (tradeUpdate.requestedBackdrop)
987 display::setBackdrop(*tradeUpdate.requestedBackdrop);
991 message.numberA == 1)
995 else if (message.action == actions::Type::NotifyTradeFinished &&
996 message.numberA == 0)
1001 message.numberA >= 0 && message.numberA < rules::MaxPlayers)
1002 {
1004 tradeProjection, static_cast<rules::PlayerNumber>(message.numberA),
1007 }
1008
1010 {
1011 const auto roll = diceIngress.process(
1012 uiRuleState, message, timers::tickCount());
1013 if (roll) lockGameQueue();
1014 }
1015
1019 {
1020 // UDIBar.cpp processes the move against the old UI square, then
1021 // updates UICurrentGameState except for the GoToJail destination.
1022 const auto movement = pieceMoveIngress.process(
1023 uiRuleState, message, display::stateReadOnly().optionTokenAnimationsOn);
1024 if (movement && movement->sourceQueueLockRequired)
1025 lockGameQueue();
1026 if (movement && movement->projectionUpdated &&
1029 {
1031 }
1032
1033 if (movement && message.numberA == 40 &&
1034 message.numberC >= 0 && message.numberC < uiRuleState.numberOfPlayers &&
1035 message.numberC < rules::MaxPlayers)
1036 {
1037 const auto player = static_cast<rules::PlayerNumber>(message.numberC);
1038 const bool localHuman = ui::localplayers::slotIsLocalHumanPlayer(player);
1040 uiRuleState, player, localHuman));
1042 uiRuleState, player, localHuman));
1043 }
1044 }
1045
1046 if (message.action == actions::Type::NotifyEndTurn &&
1047 message.numberA >= 0 && message.numberA < uiRuleState.numberOfPlayers)
1048 {
1049 const auto player = static_cast<rules::PlayerNumber>(message.numberA);
1050 uiRuleState.currentPlayer = player;
1053 }
1054
1055 if (message.action == actions::Type::NotifyStartTurn &&
1056 message.numberA >= 0 &&
1057 message.numberA < uiRuleState.numberOfPlayers)
1058 {
1059 const auto newCurrent = static_cast<rules::PlayerNumber>(message.numberA);
1060 const auto random100 = static_cast<std::uint32_t>(std::rand() % 100);
1061 std::optional<std::uint32_t> random8;
1062 if (uiRuleState.players[newCurrent].firstMoveMade &&
1063 uiRuleState.players[newCurrent].aiPlayerLevel != 0)
1064 random8 = static_cast<std::uint32_t>(std::rand() % 8);
1065 if (const auto reactions = penny::nextPlayerReactions(
1067 {
1070 }
1072 uiRuleState.players[newCurrent].currentSquare);
1074 {
1075 if (auto plan = pieceIdleState.planTurnChange(uiRuleState, newCurrent))
1076 {
1077 pendingPieceIdleTransition = std::move(*plan);
1078 lockGameQueue();
1079 }
1080 }
1081 // UDIBar.cpp assigns CurrentPlayer only after the idle plan and lock.
1083 runtime::state().gamePaused = false;
1084 uiRuleState.currentPlayer = newCurrent;
1087 }
1088
1090 {
1091 // UDIBar.cpp:3397-3403 uses the notification player directly.
1092 if (message.numberA >= 0 && message.numberA < uiRuleState.numberOfPlayers &&
1093 message.numberA < rules::MaxPlayers)
1094 {
1095 const auto player = static_cast<rules::PlayerNumber>(message.numberA);
1096 auto& displayState = display::state();
1097 displayState.desiredBoardCamera = pieces::selectAppropriateView(
1099 displayState.desiredBoardCamera,
1100 uiRuleState.players[player].currentSquare,
1101 0);
1102 }
1103 // UDIBar.cpp sets GameInProgress before leaving the roll prompt.
1105 runtime::state().gamePaused = false;
1106 }
1107
1109 {
1110 // UDIBar.cpp:3640-3643 always selects VIEW2D17_CORNER_JAIL.
1111 auto& displayState = display::state();
1112 displayState.desiredBoardCamera = pieces::selectAppropriateView(
1114 displayState.desiredBoardCamera,
1115 0,
1116 1);
1117 }
1118
1119 if (
1120 message.action ==
1122 {
1123 const std::int64_t count =
1124 std::clamp<std::int64_t>(
1125 message.numberA,
1126 0,
1127 static_cast<std::int64_t>(
1129 )
1130 );
1131
1132
1133 if (count == 0)
1135
1137 (count == 0 && (uiRuleState.numberOfPlayers != 0 ||
1139
1140
1142 {
1145
1146
1148 count == 0
1149 );
1150
1151
1152 // Original UDPSEL reset :
1153 //
1154 // NumberOfLocalPlayers = 0;
1155 // SlotIsALocal* = FALSE;
1156 // LocalPlayerSlots = NOBODY.
1158 }
1159
1160
1161 uiRuleState.numberOfPlayers =
1162 static_cast<rules::PlayerNumber>(
1163 count
1164 );
1165 }
1166
1167
1168 // CheckForAcceptingOurNewPlayer() doit précéder
1169 // UDPSEL_ProcessMessageToPlayer().
1172 message
1173 );
1174
1175
1176 switch (message.action)
1177 {
1179 {
1180 // Userifce.cpp original first spreads every token across GO
1181 // in reverse player order, with no current center idle.
1183 if (!pieceIdleState.initializeNewGame(uiRuleState))
1184 pieceIdleState.reset();
1185 for (auto& hit : uiRuleState.countHits)
1186 {
1187 hit.tradedItem = false;
1188 hit.toPlayer = rules::NobodyPlayer;
1189 }
1191 // Userifce.cpp original :
1192 // UDPSEL_GameHasJustStarted();
1193 // UDBOARD_SetBackdrop(DISPLAY_SCREEN_MainA);
1194 //
1195 // La persistance du journal de joueurs effectuee par
1196 // UDPSEL_GameHasJustStarted() n'a pas encore de backend
1197 // portable. La transition d'ecran, elle, est exacte et
1198 // reste differee jusqu'au prochain show DISPLAY.
1200 // Retail starts capture here only when MESS_NetworkMode is true.
1202 break;
1203 }
1204
1205
1207 {
1209 {
1212 uiRuleState, 0xFFFFFFFFu);
1216 }
1217 break;
1218 }
1219
1220
1222 {
1223 runtime::state().gamePaused = true;
1224 break;
1225 }
1226
1227
1228 default:
1229 break;
1230 }
1231
1232
1233 if (statsProjection.initialized &&
1235 {
1236 // UDStats content is live: cash/ownership/building notifications
1237 // invalidate the active sort while the Portfolio is visible.
1239 }
1240
1244
1246 message
1247 );
1248 }
1249
1250
1252 {
1253 return uiRuleState;
1254 }
1255
1256
1258 {
1259 return uiRuleState;
1260 }
1261
1266
1267 std::optional<pieces::PieceMovePlan> takePendingPieceMovePlan()
1268 {
1269 return pieceMoveIngress.takePlan();
1270 }
1271
1272 std::optional<pieces::PieceMoveSpecialRequest> takePendingPieceMoveSpecial()
1273 {
1274 return pieceMoveIngress.takeSpecial();
1275 }
1276 std::optional<pieces::PieceIdleTransitionPlan> takePendingPieceIdleTransitionPlan()
1277 {
1278 auto result = std::move(pendingPieceIdleTransition);
1280 return result;
1281 }
1282
1283 std::optional<dice::RollRequest> takePendingDiceRoll()
1284 {
1285 return diceIngress.take();
1286 }
1287
1288 void update()
1289 {
1292 display::stateReadOnly().desired2DView);
1295 display::stateReadOnly().desired2DView);
1299
1300 // ProcessPlayersUI(NULL) original entretient les effets UI
1301 // periodiques, mais ne valide pas une phase UDPSEL. Le commit
1302 // desired/current appartient exclusivement a DISPLAY_UDPSEL_Show().
1303 const auto player = ibar::resolveRulePlayer(iBarRuleProjection.player);
1307 }
1308
1310 {
1311 if (message.type == uimsg::Type::Quit)
1312 {
1314 return false;
1315 }
1319 // ProcessLibraryMessage() original appelle
1320 // AdvanceTimeStep() à chaque message ArtLib.
1322
1324 message, chatSender(), chatEligibleRecipients(),
1326 {
1327 update();
1329 }
1330
1331 // ProcessLibraryMessage() original distribue ensuite
1332 // le message aux modules UD actifs.
1333 //
1334 // Ordre historique des modules interactifs portes ici :
1335 // UDAUCT_ProcessMessage, UDBOARD_ProcessMessage, UDIBAR_ProcessMessage,
1336 // UDPSEL_ProcessMessage puis UDTRADE_ProcessMessage.
1337 if (const auto bid = auctionui::planBid(
1338 auctionProjection, uiRuleState.numberOfPlayers,
1340 {
1341 actions::Message action{};
1342 action.action = actions::Type::Bid;
1343 action.fromPlayer = bid->player;
1344 action.toPlayer = rules::BankPlayer;
1345 action.numberA = bid->amount;
1346 (void)messaging::sendAction(action);
1347 }
1350 const bool optionPreviewWasActive = optionsProjection.active &&
1352 optionsProjection.optionSnapshotLoaded;
1353 const auto originalMusicTune = optionsProjection.originalMusicTuneIndex;
1355 message
1356 );
1361 const auto saveInput = (customBoardInput.consumed || exitCreditsConsumed)
1366 if (!customBoardInput.consumed && !saveInput.consumed && !exitCreditsConsumed)
1368 optionsProjection, display::state().desired2DView, message);
1369 if (optionPreviewWasActive && !optionsInput.pressedOptionOkay &&
1370 (!optionsProjection.active || !optionsProjection.optionSnapshotLoaded ||
1374 if (saveInput.playClick) engine::playClickSound();
1375 const bool optionClicked = optionsInput.pressedMenuButton.has_value() ||
1376 optionsInput.pressedFileButton.has_value() ||
1377 optionsInput.pressedHelpButton.has_value() ||
1378 optionsInput.pressedOptionToggle.has_value() ||
1379 optionsInput.pressedMusicTune.has_value() ||
1380 optionsInput.pressedOptionOkay;
1382 if (optionsInput.pressedMusicTune)
1384 if (optionsInput.pressedHelpButton == optionsui::HelpButton::QuickHelp ||
1385 optionsInput.pressedHelpButton == optionsui::HelpButton::FullHelp)
1386 {
1387 const auto resources = startup::resources();
1388 const auto opened = resources
1389 ? (optionsInput.pressedHelpButton == optionsui::HelpButton::QuickHelp
1391 : optionsui::openFullHelp(*resources))
1392 : std::expected<void, std::string>(std::unexpected("Help resources unavailable"));
1394 }
1395
1396 if (customBoardInput.requestLoad)
1397 {
1398 const auto selection = optionsui::validateSelectedCustomBoard(
1400 const auto committed = selection
1401 ? playerselection::commitCustomBoard(selection->assetRoot)
1402 : std::expected<void, std::string>(
1403 std::unexpected(selection.error()));
1404 if (committed)
1405 {
1406 const auto previous = optionsCustomBoardProjection.previousView;
1408 optionsProjection.active = false;
1410 }
1412 }
1413 else if (customBoardInput.closeDialog)
1414 {
1415 const auto previous = optionsCustomBoardProjection.previousView;
1417 optionsProjection.active = false;
1419 }
1420
1421 bool loadGameDispatched = false;
1422 bool retainLoadDialog = false;
1423 if (saveInput.requestLoad)
1424 {
1427 bool sent = false;
1430 std::filesystem::path loadedCustomRoot;
1431 bool customBoardRemoved = false;
1432 bool boardReady = true;
1433 // RuleSave accepts an unassigned sender on an empty local/server
1434 // game, including Load from player selection before a slot exists.
1435 const bool mayLoad = localPlayer < rules::MaxPlayers ||
1436 (uiRuleState.numberOfPlayers == 0 && messaging::serverMode());
1437 if (blob && mayLoad)
1438 {
1439 const auto& metadata = optionsSaveProjection.slots[
1440 static_cast<std::size_t>(optionsSaveProjection.selectedSlot)].metadata;
1441 loadedCity = metadata.city;
1442 loadedSystem = metadata.system;
1443 if (loadedCity < 0)
1444 {
1445 const auto resources = startup::resources();
1446 if (!resources) boardReady = false;
1447 else
1448 {
1449 const bool usa = resources->context().board == data::BoardEdition::Usa;
1450 if (usa) loadedSystem = 13;
1452 metadata.customBoardName, *resources, loadedSystem);
1453 boardReady = restored.has_value();
1454 if (restored && *restored)
1455 {
1456 loadedCity = -1;
1458 }
1459 else if (restored)
1460 {
1461 // SetUpLoadedGame falls back only when the saved
1462 // first camera is missing (the board was removed).
1463 const int language = static_cast<int>(resources->context().language);
1464 loadedCity = usa || language < 2 || language > 10 ? 0 : language - 2;
1465 customBoardRemoved = true;
1466 }
1467 }
1468 }
1469 actions::Message action{};
1471 action.fromPlayer = localPlayer;
1472 action.toPlayer = rules::BankPlayer;
1473 action.numberB = 1; // retail load-game marker
1474 action.numberC = 1; // immunity/future save version
1475 action.binaryDataA = *blob;
1476 if (boardReady) sent = messaging::sendAction(action);
1477 }
1478 if (sent)
1479 {
1480 loadGameDispatched = true;
1481 auto& displayState = display::state();
1484 displayState.city, displayState.system);
1485 if (!metadata)
1486 {
1488 }
1489 else
1490 {
1491 displayState.city = loadedCity;
1492 displayState.system = loadedSystem;
1493 displayState.customBoardPath = std::move(loadedCustomRoot);
1495 }
1496 // SetUpLoadedGame() leaves Options immediately; the RULE resync
1497 // that follows completes the loaded-game projection.
1498 optionsProjection.active = false;
1500 }
1501 else
1502 {
1504 retainLoadDialog = true;
1505 }
1506 }
1507
1508 if (saveInput.requestSave)
1509 {
1511 const auto& displayState = display::stateReadOnly();
1512 const auto customPath = displayState.customBoardPath.u8string();
1515 displayState.city, displayState.system,
1516 std::string(reinterpret_cast<const char*>(customPath.data()), customPath.size()),
1517 localPlayer);
1518 const bool sent = prepared && localPlayer < rules::MaxPlayers &&
1521 if (!sent)
1522 {
1523 // A refused duplicate still belongs to the first request.
1524 // Only discard metadata prepared for this failed send.
1527 }
1528 }
1529
1530 if (saveInput.closeDialog && !retainLoadDialog)
1531 {
1533 if (!loadGameDispatched)
1535 }
1536
1537 if (optionsInput.pressedFileButton == optionsui::FileButton::NewGame)
1538 {
1539 // UDOPTIONS_ProcessFileOptionButtonPress sets the request flag,
1540 // removes the File screen and fakes Escape into UDIBAR.
1541 optionsProjection.active = false;
1543 }
1544 else if (optionsInput.pressedFileButton == optionsui::FileButton::Load)
1545 {
1548 if (opened)
1550 else
1552 }
1553 else if (optionsInput.pressedFileButton == optionsui::FileButton::Save)
1554 {
1557 if (opened)
1559 else
1561 }
1562 else if (optionsInput.pressedFileButton == optionsui::FileButton::Exit)
1563 {
1564 optionsProjection.active = false;
1566 }
1567 if (optionsInput.pressedMenuButton == optionsui::MenuButton::Option)
1568 {
1569 const auto& displayState = display::stateReadOnly();
1571 displayState.optionTokenVoicesOn, displayState.optionHostCommentsOn,
1572 displayState.optionMusicOn, displayState.optionMusicTuneIndex,
1573 displayState.optionTokenAnimationsOn,
1574 displayState.optionCameraMovementOn,
1575 displayState.optionLightingOn, displayState.game3DOn,
1576 displayState.optionFilteringOn);
1577 }
1578 if (optionsInput.pressedOptionOkay && optionsProjection.optionSnapshotLoaded)
1579 {
1580 const auto value = [&](optionsui::OptionToggle toggle)
1581 {
1582 return optionsProjection.optionOn[static_cast<std::size_t>(toggle)];
1583 };
1594 }
1595 if (optionsInput.requestedBackdrop)
1596 display::setBackdrop(*optionsInput.requestedBackdrop);
1597
1598
1599 if (!customBoardInput.consumed)
1602 {
1604 const char* basePath = SDL_GetBasePath();
1605 const auto opened = basePath && *basePath
1607 std::filesystem::path(basePath), previous)
1608 : std::expected<void, std::string>(
1609 std::unexpected("custom-board executable directory is unavailable"));
1610 if (opened)
1611 {
1612 optionsProjection.previousView = previous;
1614 optionsProjection.active = true;
1616 }
1618 }
1620 {
1623 if (opened)
1624 {
1627 optionsProjection.active = true;
1629 }
1631 }
1632 // Retail input order places UDSTATS after UDPSEL and before UDTRADE.
1633 // Entering Portfolio through IBar in this same message also initializes
1634 // the default Player/Turn projection immediately.
1638 statsProjection, display::state().desired2DView, message);
1639 if (auto* accounts = engine::statsAccounts()) accounts->scroll(historyScroll);
1642 statsProjection.screen, display::state().desired2DView, message);
1645 display::state().desired2DView, message);
1646 if (message.type == uimsg::Type::MouseLeftDown &&
1648 {
1649 if (const auto square = statsui::propertyActionHit(
1651 static_cast<int>(message.numberA),
1652 static_cast<int>(message.numberB)))
1653 (void)ibar::activateProperty(*square);
1654 }
1655
1657 const bool tradePartnerDialogWasVisible = tradeProjection.playerSelectVisible;
1658 if (const auto partner = tradeui::planPartnerSelection(
1659 tradeProjection, uiRuleState, display::state().desired2DView,
1660 message))
1661 {
1663 }
1665 {
1666 const auto tradeInput = tradeui::processInput(
1667 tradeProjection, uiRuleState, display::state().desired2DView, message);
1668 if (tradeInput.proposeMissingOffer)
1672 if (tradeInput.requestedBackdrop)
1673 display::setBackdrop(*tradeInput.requestedBackdrop);
1675 }
1676 update();
1677
1678 // Correspond à ProcessUIMessage() de Main.cpp.
1679 //
1680 // ProcessLibraryMessage() sera porté ici progressivement,
1681 // notamment AdvanceTimeStep(), clavier, souris et séquenceur.
1682
1683 // Source originale :
1684 // if (GameQuitRequested)
1685 // return FALSE;
1686
1688 }
1689}
1690
1691
1692
1693
std::size_t count
std::array< rules::PlayerNumber, rules::MaxPlayers > players
decision::EconomicActionPlan plan
std::vector< std::uint8_t > data
std::size_t offset
LegacyBitmapMetadata metadata
fonts::Settings saved
std::shared_ptr< const data::HmdTextureImage > source
unsigned state
unsigned index
std::uint16_t value
Definition Timers.cpp:16
std::optional< BidRequest > planBid(const State &state, rules::PlayerNumber numberOfPlayers, display::Screen2D desiredView, const uimsg::Message &message, std::uint32_t localHumanMask) noexcept
void reset(State &state) noexcept
Definition AuctionUI.cpp:44
RuleUpdate processRuleMessage(State &state, const rules::GameState &gameState, const actions::Message &message, display::Screen2D desiredView) noexcept
Definition AuctionUI.cpp:49
bool processInput(const uimsg::Message &message, rules::PlayerNumber sender, std::uint32_t eligibleRecipients, bool networkMode)
void setPlayerNames(const rules::GameState &gameState)
void reset() noexcept
bool processRuleMessage(const actions::Message &message)
State & state()
Definition Display.cpp:699
void processBoardInput(const uimsg::Message &message)
Definition Display.cpp:538
void applyMusicOption(bool musicOn) noexcept
Definition Display.cpp:427
void applyRuntimeOptions(bool tokenAnimationsOn, bool cameraMovementOn, bool lightingOn, bool board3DOn) noexcept
Definition Display.cpp:437
constexpr bool isIBarVisible(Screen2D screen) noexcept
Definition Display.hpp:49
void applyHostCommentsOption(bool enabled) noexcept
Definition Display.cpp:422
void requestBssmCamera(std::int32_t square, std::uint8_t action) noexcept
Definition Display.cpp:519
void noteBoardActivity() noexcept
Definition Display.cpp:412
void setBackdrop(Screen2D screen)
Definition Display.cpp:363
const State & stateReadOnly()
Definition Display.cpp:704
void applyTokenVoicesOption(bool enabled) noexcept
Definition Display.cpp:417
void applyMusicTune(std::uint8_t tuneIndex) noexcept
Definition Display.cpp:432
bool consumeOpeningMovieInput(const uimsg::Message &message)
Definition Engine.cpp:1665
void playTokenVoice(std::uint8_t token, udsound::TokenVoiceLine line, udsound::TokenVoiceClipPolicy policy, bool watchAfterStart) noexcept
Definition Engine.cpp:1934
void playClickSound() noexcept
Definition Engine.cpp:1898
bool modernTradePresentationEnabled() noexcept
Definition Engine.cpp:1203
void stopVoiceChat() noexcept
Definition Engine.cpp:1746
void playWarningSound() noexcept
Definition Engine.cpp:1889
void playJailChoiceHostComment() noexcept
Definition Engine.cpp:1957
statsui::AccountRuntime * statsAccounts() noexcept
Definition Engine.cpp:2093
fonts::Runtime * fontPlayback()
Definition Engine.cpp:1753
bool isUsaBoardEdition() noexcept
Definition Engine.cpp:1951
bool startVoiceChat() noexcept
Definition Engine.cpp:1704
void playBuildSound() noexcept
Definition Engine.cpp:1907
void playPennybagsVoice(udsound::PennybagsVoice line, udsound::TokenVoiceClipPolicy policy, bool watchAfterStart) noexcept
Definition Engine.cpp:1940
void playSaveFailureSound() noexcept
Definition Engine.cpp:1925
void playUnbuildSound() noexcept
Definition Engine.cpp:1916
bool spokenPostLockSlotEmpty() noexcept
Definition Engine.cpp:1946
bool activateProperty(int square)
Definition IBar.cpp:1276
void processRuleMessage(const actions::Message &message, RuleMode projectedMode, std::uint64_t tick) noexcept
Definition IBar.cpp:1074
bool requestNewGameConfirmation() noexcept
Definition IBar.cpp:785
void processLibraryMessage(const uimsg::Message &message)
Definition IBar.cpp:960
bool requestExitConfirmation() noexcept
Definition IBar.cpp:792
void restoreRuleTracking() noexcept
Definition IBar.cpp:1200
RuleMode resolveRuleMode(RuleMode projectedMode, rules::PlayerNumber projectedPlayer) noexcept
Definition IBar.cpp:1215
rules::PlayerNumber resolveRulePlayer(rules::PlayerNumber projectedPlayer) noexcept
Definition IBar.cpp:1228
const State & stateReadOnly()
Definition IBar.cpp:1291
constexpr std::int64_t ErrorSaveGameFailure
std::size_t queuedActionCount()
constexpr std::size_t MessageQueueCapacity
Definition Messaging.hpp:14
bool sendAction(const actions::Message &message)
void noteClientResynchronized()
CustomBoardInput processCustomBoardInput(CustomBoardState &state, const uimsg::Message &message) noexcept
void closeCustomBoardDialog(CustomBoardState &state) noexcept
bool beginFromIBar(State &state, display::Screen2D previousView) noexcept
void closeSaveDialog(SaveRuntimeState &state) noexcept
std::expected< std::optional< std::filesystem::path >, std::string > restoreSavedCustomBoard(std::string_view savedAssetRoot, const data::ResourceSnapshot &resources, int monetarySystem)
SaveDialogInput processSaveDialogInput(SaveRuntimeState &state, const uimsg::Message &message, fonts::Runtime *fontRuntime, std::shared_ptr< const data::ResourceSnapshot > resources)
void reset(State &state) noexcept
std::expected< void, std::string > openQuickHelp(State &state, const data::ResourceSnapshot &resources)
InputResult processInput(State &state, display::Screen2D desiredView, const uimsg::Message &message) noexcept
std::expected< void, std::string > persistPendingSave(SaveRuntimeState &state, std::span< const std::uint8_t > gameBlob)
std::expected< void, std::string > openCustomBoardDialog(CustomBoardState &state, const std::filesystem::path &moduleDirectory, display::Screen2D previousView)
void loadSupportedOptionValues(State &state, bool tokenVoicesOn, bool hostCommentsOn, bool musicOn, std::uint8_t musicTuneIndex, bool tokenAnimationsOn, bool cameraMovementOn, bool lightingOn, bool board3DOn, bool filteringOn) noexcept
std::expected< std::vector< std::uint8_t >, std::string > readSelectedGameBlob(const SaveRuntimeState &state)
void clearPendingSave(SaveRuntimeState &state) noexcept
std::expected< void, std::string > openFullHelp(const data::ResourceSnapshot &resources, const FullHelpOptions &options)
std::expected< CustomBoardSelection, std::string > validateSelectedCustomBoard(const CustomBoardState &state)
std::expected< void, std::string > refreshSaveSlots(SaveRuntimeState &state, FileDialogMode mode)
std::expected< void, std::string > applySelectedMetadata(const SaveRuntimeState &state, rules::GameState &ruleState, int &city, int &system)
std::expected< void, std::string > beginPendingSave(SaveRuntimeState &state, const rules::GameState &ruleState, int city, int system, std::string customBoardName, rules::PlayerNumber requester)
std::optional< PennybagsReaction > goToJailPennybagsReaction(const rules::GameState &state, rules::PlayerNumber player, bool localHuman) noexcept
std::optional< PennybagsReaction > boughtPropertyPennybagsReaction(const rules::GameState &state, rules::PlayerNumber player, std::uint8_t property) noexcept
std::optional< TokenReaction > goToJailReaction(const rules::GameState &state, rules::PlayerNumber player, bool localHuman) noexcept
std::optional< PennybagsReaction > buyOrAuctionPennybagsReaction(const rules::GameState &state, rules::PlayerNumber player) noexcept
std::optional< TurnStartReactions > nextPlayerReactions(const rules::GameState &state, rules::PlayerNumber player, std::uint32_t random100, std::optional< std::uint32_t > random8) noexcept
std::optional< TokenReaction > boughtPropertyReaction(const rules::GameState &state, rules::PlayerNumber player, std::uint8_t property) noexcept
std::optional< TokenReaction > choseAuctionReaction(const rules::GameState &state, rules::PlayerNumber player) noexcept
BoardCameraView pickCameraFor3Squares(std::int32_t square) noexcept
BoardCameraView selectAppropriateView(BoardViewSelectionType type, BoardCameraView desired, std::int32_t currentSquare, std::int32_t variable) noexcept
bool consumeCustomBoardRequest() noexcept
void processLibraryMessage(const uimsg::Message &message)
std::expected< void, std::string > commitCustomBoard(std::filesystem::path assetRoot)
void processMessage(const actions::Message &message)
bool decodeOptions(std::span< const std::uint8_t > data, GameOptions &result)
const PendingPhase & current(const GameState &state)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber SpectatorPlayer
Definition RuleTypes.hpp:22
constexpr PlayerNumber EscrowPlayer
Definition RuleTypes.hpp:19
constexpr std::size_t SquareCount
Definition RuleTypes.hpp:24
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept
bool processCalculatorInput(CalculatorUIState &state, const rules::GameState &gameState, display::Screen2D view, const uimsg::Message &message) noexcept
bool processFutureImmunityInput(FutureImmunityState &state, const rules::GameState &gameState, Screen statsScreen, display::Screen2D view, const uimsg::Message &message) noexcept
void refreshFutureImmunity(FutureImmunityState &state, const rules::GameState &gameState) noexcept
void refresh(State &state, const rules::GameState &gameState) noexcept
Definition StatsUI.cpp:282
std::optional< int > propertyActionHit(const State &state, const rules::GameState &gameState, int x, int y) noexcept
Definition StatsUI.cpp:427
void syncView(State &state, const rules::GameState &gameState, display::Screen2D view) noexcept
Definition StatsUI.cpp:297
void reset(State &state) noexcept
Definition StatsUI.cpp:273
void resetCalculatorUI(CalculatorUIState &state) noexcept
void resetFutureImmunity(FutureImmunityState &state) noexcept
void syncCalculatorView(CalculatorUIState &state, display::Screen2D view) noexcept
void syncFutureImmunityView(FutureImmunityState &state, Screen statsScreen, display::Screen2D view) noexcept
int historyScrollInput(State &state, display::Screen2D view, const uimsg::Message &message) noexcept
Definition StatsUI.cpp:440
bool processInput(State &state, const rules::GameState &gameState, display::Screen2D view, const uimsg::Message &message) noexcept
Definition StatsUI.cpp:465
std::uint64_t tickCount()
Definition Timers.cpp:183
bool addUiImmunity(rules::GameState &gameState, rules::PlayerNumber from, rules::PlayerNumber to, rules::CountHitType hitType, std::int32_t hitCount, std::uint32_t properties, bool tradingItem) noexcept
Definition TradeUI.cpp:1525
bool abortIfParticipantOffBoard(State &state, rules::GameState &gameState, display::Screen2D desiredView) noexcept
Definition TradeUI.cpp:882
RuleUpdate processRuleMessage(State &state, rules::GameState &gameState, const actions::Message &message, display::Screen2D currentView, std::uint32_t localHumanMask, bool restoreAutonomousPortfolio)
Definition TradeUI.cpp:1600
bool beginLocalTrade(State &state, const rules::GameState &gameState, rules::PlayerNumber source) noexcept
Definition TradeUI.cpp:693
bool selectPartner(State &state, const rules::GameState &gameState, rules::PlayerNumber player) noexcept
Definition TradeUI.cpp:787
InputUpdate processInput(State &state, const rules::GameState &gameState, display::Screen2D desiredView, const uimsg::Message &message)
Definition TradeUI.cpp:1021
std::vector< actions::Message > planEditorSubmission(const State &state, rules::PlayerNumber editor, std::uint32_t localHumanMask)
Definition TradeUI.cpp:1414
void reset(State &state) noexcept
Definition TradeUI.cpp:659
constexpr std::uint8_t OffBoardSquare
Definition TradeUI.hpp:25
std::optional< rules::PlayerNumber > planPartnerSelection(State &state, const rules::GameState &gameState, display::Screen2D desiredView, const uimsg::Message &message) noexcept
Definition TradeUI.cpp:748
PlayerNumber anyLocalPlayer(const GameState &uiState)
PlayerNumber tradeSourcePlayer(const GameState &uiState, PlayerNumber iBarCurrentPlayer)
void setCurrentUIPlayerFromPlayerSet(const GameState &uiState, std::uint32_t playerSet)
bool isLocalRecipient(PlayerNumber recipient)
bool slotIsLocalHumanPlayer(PlayerNumber slot)
bool slotIsLocalPlayer(PlayerNumber slot)
void setCurrentUIPlayerFromPlayerNumber(PlayerNumber player)
PlayerNumber tradeAcceptanceIBarPlayer(const GameState &uiState, PlayerNumber tradeBPlayer, std::uint32_t pendingPlayers)
PlayerNumber housingShortageIBarPlayer(const GameState &uiState, PlayerNumber originalBuyer, std::uint32_t allowedPlayers)
void processRuleMessage(GameState &uiState, const actions::Message &message)
optionsui::CustomBoardState & optionsCustomBoardState() noexcept
bool processUIMessage(const uimsg::Message &message)
rules::PlayerNumber chatSenderPlayer() noexcept
const optionsui::CustomBoardState & optionsCustomBoardStateReadOnly() noexcept
statsui::FutureImmunityState & statsFutureImmunityState() noexcept
std::expected< void, std::string > sendReadyResponses(std::uint32_t playerMask, std::int64_t serial)
bool dispatchTradeBatch(const std::vector< actions::Message > &batch)
const rules::GameState & ruleStateReadOnly()
std::optional< pieces::PieceMoveSpecialRequest > takePendingPieceMoveSpecial()
const optionsui::State & optionsStateReadOnly() noexcept
const optionsui::SaveRuntimeState & optionsSaveStateReadOnly() noexcept
const statsui::State & statsStateReadOnly() noexcept
bool beginTradeFromIBar(rules::PlayerNumber iBarPlayer) noexcept
statsui::CalculatorUIState & statsCalculatorState() noexcept
auctionui::State & auctionState() noexcept
bool beginOptionsFromIBar() noexcept
std::optional< pieces::PieceMovePlan > takePendingPieceMovePlan()
void processRuleMessage(const actions::Message &message)
std::optional< pieces::PieceIdleTransitionPlan > takePendingPieceIdleTransitionPlan()
const pieces::PieceIdleState & pieceIdleStateReadOnly()
const statsui::FutureImmunityState & statsFutureImmunityStateReadOnly() noexcept
std::optional< dice::RollRequest > takePendingDiceRoll()
const tradeui::State & tradeStateReadOnly() noexcept
statsui::State & statsState() noexcept
dice::PromptState & dicePromptState() noexcept
rules::GameState & ruleState()
tradeui::State & tradeState() noexcept
const ibar::RuleProjection & iBarRuleStateReadOnly() noexcept
optionsui::SaveRuntimeState & optionsSaveState() noexcept
const auctionui::State & auctionStateReadOnly() noexcept
const statsui::CalculatorUIState & statsCalculatorStateReadOnly() noexcept
optionsui::State & optionsState() noexcept
bool processRuleMessage(const actions::Message &message)
void resetSession() noexcept
rules::PlayerNumber toPlayer
Definition Actions.hpp:131
rules::PlayerNumber fromPlayer
Definition Actions.hpp:128
std::vector< std::uint8_t > binaryDataA
Definition Actions.hpp:143
pieces::BoardCameraView desiredBoardCamera
Definition Display.hpp:98