5#include <SDL3/SDL_scancode.h>
18 return player <
gameState.numberOfPlayers &&
32 const rules::GameState&
gameState)
noexcept
34 state.jailCardDesired = {};
44 if (owner ==
state.playerA)
46 else if (owner ==
state.playerB)
60 std::int64_t
value)
noexcept
75 state.cashDesired[0] = 0;
76 state.cashDesired[1] = 0;
90 state.cashDesired[2] = 0;
91 state.cashDesired[3] = 0;
93 for (
const auto& item :
state.items)
97 if (item.numberA ==
state.playerA)
state.cashDesired[2] = item.numberD;
98 else if (item.numberA ==
state.playerB)
state.cashDesired[3] = item.numberD;
102 item.numberD <
static_cast<std::int64_t
>(
gameState.cards.size()))
104 const auto deck =
static_cast<std::size_t
>(item.numberD);
106 if (owner ==
state.playerA)
state.jailCardDesired[
deck] = 1u << 2u;
107 else if (owner ==
state.playerB)
state.jailCardDesired[
deck] = 1u << 3u;
122 message.text.front() <
'0' || message.text.front() >
'9')
124 return message.text.front() -
'0';
129 return a.contains(
x,
y) ||
b.contains(
x,
y);
134 const int x = 4 +
static_cast<int>(side) * 600;
135 constexpr int y = 324;
136 return {local.left +
x, local.top +
y, local.right +
x, local.bottom +
y};
142 const auto rawKind =
static_cast<std::int64_t
>(
kind);
143 const auto it = std::find_if(
state.items.begin(),
state.items.end(),
144 [&](
const actions::Message& item) {
145 return item.numberC == rawKind && (!value || item.numberD == *value);
147 if (
it ==
state.items.end())
return false;
153 State&
state,
const rules::GameState&
gameState, std::uint8_t side, std::int64_t amount)
157 actions::Message item{};
158 item.numberA = side ?
state.playerB :
state.playerA;
159 item.numberB = side ?
state.playerA :
state.playerB;
161 item.numberD = amount;
167 state.cashOriginalOffers = {
state.cashDesired[2],
state.cashDesired[3]};
168 state.cashDialogSide = side;
169 state.cashTradeAmount = 0;
171 state.cashDialogClosing =
false;
172 state.cashDialogVisible =
true;
182 {{0, 225}}, {{600, 225}}, {{200, 244}}, {{400, 244}}
186 5, 15, 25, 35, 12, 28, 1, 3,
187 6, 8, 9, 11, 13, 14, 16, 18,
188 19, 21, 23, 24, 26, 27, 29, 31,
193 PropertyProjection& projection,
196 bool modernHoldingsLayout)
noexcept
200 std::array<bool, 11>
columns{};
206 if (
bit == 0 || (visible &
bit) == 0)
continue;
208 if (order < 0)
continue;
210 const int column = order / 3;
245 if (
bit == 0 || (visible &
bit) == 0)
continue;
247 if (order < 0)
continue;
249 const int row = order % 3;
250 const int column = order / 3;
268 if (modernHoldingsLayout &&
box <= 1 && cardCount <= 8)
284 const int left = origin[0] +
x;
285 const int top = origin[1] +
y;
286 projection.hitRects[
static_cast<std::size_t
>(
box)]
287 [
static_cast<std::size_t
>(square)] =
293 projection.priorities[
static_cast<std::size_t
>(
box)]
317 return {origin[0], origin[1], origin[0] + 200, origin[1] + 225};
322 if (
state.contractList.empty())
state.contractListOffset = 0;
323 else state.contractListOffset = std::clamp(
324 state.contractListOffset, 0,
325 static_cast<int>(
state.contractList.size()) - 1);
332 state.contractList.clear();
336 if ((
state.contractProperties &
mask) == 0)
continue;
338 state.contractListOffset =
static_cast<int>(
state.contractList.size());
339 state.contractList.push_back({0,
mask,
false});
346 state.contractDialogVisible =
false;
347 state.contractDialogMode = 0;
348 state.contractDialogSide = 0;
349 state.contractProperties = 0;
350 state.contractAmount = 0;
351 state.contractListOffset = 0;
352 state.contractList.clear();
357 state.cashDialogVisible =
false;
358 state.cashDialogClosing =
false;
374 state.contractDialogVisible =
true;
376 state.contractDialogMode = mode;
377 state.contractDialogSide = side;
378 state.contractListOffset = 0;
379 state.contractList.clear();
382 state.contractProperties = 0;
383 state.contractAmount = 0;
389 const auto to = side ?
state.playerB :
state.playerA;
393 if (
state.contractList.size() >= 28)
break;
394 if (
hit.toPlayer == to &&
hit.hitType == hitType &&
395 hit.hitCount != 0 && !
hit.tradedItem)
396 state.contractList.push_back({
hit.hitCount,
hit.properties,
false});
399 else if (mode == 5 || mode == 6)
401 const auto to = side ?
state.playerA :
state.playerB;
402 const auto rawKind =
static_cast<std::int64_t
>(
kind);
403 for (
const auto& item :
state.items)
405 if (
state.contractList.size() >= 28)
break;
406 if (item.numberC ==
rawKind && item.numberD != 0 &&
407 item.numberB == to && item.numberE >= 0 &&
408 item.numberE <= std::numeric_limits<std::uint32_t>::max())
410 state.contractList.push_back({
411 static_cast<std::int32_t
>(item.numberD),
412 static_cast<std::uint32_t
>(item.numberE),
false});
420 [[
nodiscard]] std::pair<rules::PlayerNumber, rules::PlayerNumber>
435 std::uint32_t properties)
437 actions::Message item{};
440 item.numberC =
static_cast<std::int64_t
>(
kind);
441 item.numberD =
count;
442 item.numberE = properties;
449 const uimsg::Message& message)
451 const auto kind =
state.contractDialogKind;
452 const int x =
static_cast<int>(message.numberA);
453 const int y =
static_cast<int>(message.numberB);
455 if (
state.contractDialogMode == 2 &&
460 state.contractAmount /= 10;
467 else state.contractDialogMode = 3;
481 if (
state.contractDialogMode == 0)
491 state.contractDialogSide =
static_cast<std::uint8_t
>(1u - (
box % 2u));
492 state.contractDialogMode = 1;
493 state.contractProperties = 0;
494 state.contractList.clear();
495 state.contractListOffset = 0;
499 else if (
state.contractDialogMode == 1)
506 state.contractDialogMode = 2;
507 state.contractAmount = 0;
514 const int box = *
hit / 100;
515 const int square = *
hit % 100;
518 if (
state.contractDialogSide == 0)
520 (((projection.after[0] | projection.afterMortgaged[0]) &
bit) != 0);
523 (((projection.after[1] | projection.afterMortgaged[1]) &
bit) != 0);
531 else if (
state.contractDialogMode == 2)
535 else state.contractDialogMode = 3;
538 else if (
state.contractDialogMode == 3)
542 for (
unsigned int bit = 0;
bit < 28; ++
bit)
545 if ((
state.contractProperties &
mask) == 0)
continue;
554 else if (
state.contractDialogMode == 4 ||
555 state.contractDialogMode == 5 ||
556 state.contractDialogMode == 6)
560 if (
state.contractDialogMode != 6)
562 const auto [from, to] =
566 if (!
entry.selected)
continue;
567 if (
state.contractDialogMode == 4)
577 from, to, 0,
entry.properties);
579 to, from, 0,
entry.properties);
587 if (
state.contractDialogMode != 6)
592 const auto index =
static_cast<std::size_t
>(
593 state.contractListOffset +
static_cast<int>(
row));
609 if (!
hit.tradedItem)
continue;
611 hit.tradedItem =
false;
620 player.cashGivenInTrade.fill(0);
628 const actions::Message& message)
noexcept
631 if (!
kind || message.numberA < 0 || message.numberB < 0 ||
632 message.numberA >=
gameState.numberOfPlayers ||
633 message.numberB >=
gameState.numberOfPlayers)
641 return message.numberD >= 0;
643 return message.numberD >= 0 &&
646 return message.numberD >= 0 &&
647 message.numberD <
static_cast<std::int64_t
>(
gameState.cards.size());
650 return message.numberD >= std::numeric_limits<std::int32_t>::min() &&
651 message.numberD <= std::numeric_limits<std::int32_t>::max() &&
652 message.numberE >= 0 &&
653 message.numberE <= std::numeric_limits<std::uint32_t>::max();
664 state.editMode =
true;
665 state.playerSelectVisible =
false;
666 state.ignoreEntryClick =
false;
667 state.proposed =
false;
668 state.showPropose =
false;
669 state.aiProposing =
false;
671 state.desiredTradePanels = -1;
672 state.cashDesired = {};
673 state.jailCardDesired = {};
674 state.immunityFutureDesired = {};
675 state.cashDialogVisible =
false;
676 state.cashDialogSide = 0;
677 state.cashTradeAmount = 0;
678 state.cashOriginalOffers = {};
680 state.cashDialogClosing =
false;
681 state.contractDialogVisible =
false;
683 state.contractDialogMode = 0;
684 state.contractDialogSide = 0;
685 state.contractProperties = 0;
686 state.contractAmount = 0;
687 state.contractListOffset = 0;
688 state.contractList.clear();
689 state.propertyMove.reset();
708 state.ignoreEntryClick =
true;
713 state.desiredTradePanels =
714 static_cast<int>(
state.playerA) * 10 +
state.playerB;
718 state.showPropose =
true;
722 state.playerSelectVisible =
true;
723 state.desiredTradePanels =
759 state.ignoreEntryClick =
false;
765 const int x =
static_cast<int>(message.numberA);
766 const int y =
static_cast<int>(message.numberB);
783 ? std::optional<rules::PlayerNumber>{player}
796 state.playerB = player;
797 state.playerSelectVisible =
false;
798 state.ignoreEntryClick =
false;
799 state.immunityFutureDesired = {};
800 state.cashDesired[1] = 0;
801 state.cashDesired[3] = 0;
802 state.desiredTradePanels =
803 static_cast<int>(
state.playerA) * 10 +
state.playerB;
807 state.showPropose =
true;
823 if (
bit == 0)
continue;
825 gameState.squares[
static_cast<std::size_t
>(square)];
826 std::optional<std::size_t> side;
832 projection.beforeMortgaged[*side] |=
bit;
834 projection.before[*side] |=
bit;
837 for (
const auto& item :
state.items)
844 std::optional<std::size_t> side;
845 if (item.numberA ==
state.playerA) side = 0;
846 else if (item.numberA ==
state.playerB) side = 1;
849 const int square =
static_cast<int>(item.numberD);
851 if (
bit == 0)
continue;
852 projection.before[*side] &=
~bit;
853 projection.beforeMortgaged[*side] &=
~bit;
854 if (
gameState.squares[
static_cast<std::size_t
>(square)].mortgaged)
855 projection.offeredMortgaged[*side] |=
bit;
857 projection.offered[*side] |=
bit;
860 projection.after[0] =
861 (projection.before[0] | projection.offered[1]) & ~projection.offered[0];
862 projection.after[1] =
863 (projection.before[1] | projection.offered[0]) & ~projection.offered[1];
864 projection.afterMortgaged[0] =
865 (projection.beforeMortgaged[0] | projection.offeredMortgaged[1]) &
866 ~projection.offeredMortgaged[0];
867 projection.afterMortgaged[1] =
868 (projection.beforeMortgaged[1] | projection.offeredMortgaged[0]) &
869 ~projection.offeredMortgaged[1];
872 projection, 0, projection.before[0] | projection.beforeMortgaged[0],
state.modernHoldingsLayout);
874 projection, 1, projection.before[1] | projection.beforeMortgaged[1],
state.modernHoldingsLayout);
876 projection, 2, projection.offered[0] | projection.offeredMortgaged[0],
state.modernHoldingsLayout);
878 projection, 3, projection.offered[1] | projection.offeredMortgaged[1],
state.modernHoldingsLayout);
912 state.immunityFutureDesired = {};
913 if ((!
gameState.options.futureRentTradingAllowed &&
914 !
gameState.options.immunitiesTradingAllowed) ||
920 const std::array<PropertyMask, 2> after{{
921 projection.after[0] | projection.afterMortgaged[0],
922 projection.after[1] | projection.afterMortgaged[1]}};
923 constexpr std::array<rules::TradeItemKind, 2>
kinds{{
930 for (std::size_t side = 0; side < 2; ++side)
932 const auto player = side ?
state.playerB :
state.playerA;
935 if (
hit.toPlayer == player &&
hit.hitType == hitType &&
939 static_cast<std::uint8_t
>(1u << side);
945 const auto otherAfter = after[side ? 1u : 0u];
946 const auto rawKind =
static_cast<std::int64_t
>(
kind);
952 item.numberD <= 0 || item.numberE < 0 ||
953 item.numberE > std::numeric_limits<std::uint32_t>::max())
958 const auto properties =
static_cast<std::uint32_t
>(item.numberE);
962 static_cast<std::uint8_t
>(1u << (side + 2u));
968 state.items.pop_back();
969 for (std::size_t
q = 0;
q <
state.items.size(); ++
q)
975 candidate.numberE <= std::numeric_limits<std::uint32_t>::max() &&
979 state.items.pop_back();
1000 origin[0], origin[1], origin[0] + 200, origin[1] + 225};
1001 if (bounds.contains(
x,
y))
1007 if (
box < 0)
return std::nullopt;
1012 projection.hitRects[
static_cast<std::size_t
>(
box)]
1013 [
static_cast<std::size_t
>(square)];
1016 return box * 100 + square;
1018 return std::nullopt;
1034 const int x =
static_cast<int>(message.
numberA);
1035 const int y =
static_cast<int>(message.
numberB);
1037 if (
state.contractDialogVisible &&
1042 result.consumed =
true;
1047 if (!
state.editMode)
1051 (
state.immunityFutureDesired[0] & (1u << 2u)))
1055 (
state.immunityFutureDesired[0] & (1u << 3u)))
1059 (
state.immunityFutureDesired[1] & (1u << 2u)))
1063 (
state.immunityFutureDesired[1] & (1u << 3u)))
1069 if (
state.cashDialogClosing)
1071 result.consumed =
true;
1075 if (
state.cashDialogVisible &&
1080 result.consumed =
true;
1082 state.cashTradeAmount = amount;
1084 state.cashDesired[
state.cashDialogSide + 2u] = amount;
1090 constexpr std::array<std::pair<Rect, std::int64_t>, 7>
denominations{{
1091 {{7, 10, 31, 43}, 500}, {{32, 10, 56, 43}, 100},
1092 {{57, 10, 81, 43}, 50}, {{82, 10, 106, 43}, 20},
1093 {{107, 10, 131, 43}, 10}, {{132, 10, 156, 43}, 5},
1094 {{157, 10, 183, 43}, 1}}};
1098 if (
state.cashTradeAmount + amount <= 999999)
1106 state.cashDesired[(1u -
state.cashDialogSide) + 2u] = 0;
1110 state.cashDialogClosing =
true;
1111 state.cashDialogVisible =
false;
1116 state.cashTradeAmount = 0;
1118 state.cashDesired[
state.cashDialogSide + 2u] = 0;
1127 if (
state.cashOriginalOffers[0] == 0 &&
state.cashOriginalOffers[1] == 0)
1129 else if (
state.cashOriginalOffers[0] == 0)
1141 state.cashDesired[2] =
state.cashOriginalOffers[0];
1142 state.cashDesired[3] =
state.cashOriginalOffers[1];
1145 state.cashDialogClosing =
true;
1146 state.cashDialogVisible =
false;
1160 state.cashDesired[(1u -
state.cashDialogSide) + 2u] = 0;
1164 state.cashDialogClosing =
true;
1165 state.cashDialogVisible =
false;
1171 result.consumed =
true;
1186 const auto it = std::find_if(
state.items.begin(),
state.items.end(),
1188 return item.numberC == kind && item.numberA == sender;
1208 if (!
state.propertyMove)
1213 const int box = *
hit / 100;
1214 const int square = *
hit % 100;
1218 [
static_cast<std::size_t
>(square)];
1219 bool changed =
false;
1220 if (
box == 0 ||
box == 1)
1222 const std::size_t side =
static_cast<std::size_t
>(
box);
1228 gameState.squares[
static_cast<std::size_t
>(square)].owner;
1229 item.numberB = side == 0 ?
state.playerB :
state.playerA;
1232 item.numberD = square;
1238 square,
static_cast<std::uint8_t
>(
box),
1239 static_cast<std::uint8_t
>(
box + 2),
1240 gameState.squares[
static_cast<std::size_t
>(square)].mortgaged,
1242 updated.hitRects[
static_cast<std::size_t
>(
box + 2)]
1243 [
static_cast<std::size_t
>(square)]};
1255 square,
static_cast<std::uint8_t
>(
box),
1256 static_cast<std::uint8_t
>(
box - 2),
1257 gameState.squares[
static_cast<std::size_t
>(square)].mortgaged,
1259 updated.hitRects[
static_cast<std::size_t
>(
box - 2)]
1260 [
static_cast<std::size_t
>(square)]};
1269 const std::uint8_t
bit =
static_cast<std::uint8_t
>(1u <<
slot);
1275 state.jailCardDesired[
deck] =
static_cast<std::uint8_t
>(1u << (
slot + 2));
1280 item.numberD =
static_cast<std::int64_t
>(
deck);
1285 state.jailCardDesired[
deck] =
static_cast<std::uint8_t
>(1u << (
slot - 2));
1287 static_cast<std::int64_t
>(
deck));
1305 (
state.immunityFutureDesired[0] & (1u << 0u)))
1312 (
state.immunityFutureDesired[0] & (1u << 1u)))
1319 (
state.immunityFutureDesired[0] & (1u << 2u)))
1326 (
state.immunityFutureDesired[0] & (1u << 3u)))
1333 (
state.immunityFutureDesired[1] & (1u << 0u)))
1340 (
state.immunityFutureDesired[1] & (1u << 1u)))
1347 (
state.immunityFutureDesired[1] & (1u << 2u)))
1354 (
state.immunityFutureDesired[1] & (1u << 3u)))
1361 gameState.options.futureRentTradingAllowed)
1368 gameState.options.immunitiesTradingAllowed)
1379 for (
const auto& item :
state.items)
1385 if (item.numberA ==
state.playerA)
give =
true;
1386 if (item.numberB ==
state.playerA)
get =
true;
1392 action.fromPlayer =
state.tradeFrom;
1395 result.outgoing.push_back(std::move(action));
1399 result.proposeMissingOffer =
true;
1419 std::vector<actions::Message>
batch;
1445 stored.binaryDataA.clear();
1487 hit.properties ==
static_cast<std::uint32_t
>(
stored.numberE) &&
1488 hit.hitType == hitType && !
hit.tradedItem)
1490 const auto maximum =
static_cast<std::int64_t
>(99 -
hit.hitCount);
1491 if (
stored.numberD > maximum)
stored.numberD = maximum;
1516 state.items.pop_back();
1530 std::int32_t hitCount,
1531 std::uint32_t properties,
1538 if (hitCount > 99) hitCount = 99;
1541 std::optional<std::size_t> matching;
1542 std::optional<std::size_t>
freeSpot;
1543 for (std::size_t
i = 0;
i <
gameState.countHits.size(); ++
i)
1547 if (
hit.toPlayer == to &&
hit.properties == properties &&
1565 hit.tradedItem =
false;
1574 if (
hit.toPlayer == to &&
hit.properties == properties &&
1575 hit.hitType == hitType && !
hit.tradedItem)
1577 hitCount = std::min(hitCount, 99 -
hit.hitCount);
1587 hit.tradedItem =
false;
1593 hit.fromPlayer = from;
1594 hit.hitCount = hitCount;
1595 hit.properties = properties;
1596 hit.hitType = hitType;
1622 state.playerSelectVisible =
false;
1625 state.showPropose =
false;
1646 const auto formerView =
state.formerView;
1648 state.formerView = formerView;
1650 state.proposed =
true;
1654 state.playerSelectVisible =
false;
1656 if (!
state.contractDialogVisible ||
state.contractDialogMode != 6)
1658 state.showPropose =
false;
1668 state.editMode =
false;
1682 gameState.players[player].aiPlayerLevel > 0;
1700 for (
auto& item :
state.items) item.fromPlayer =
oldB;
1702 state.desiredTradePanels =
1703 static_cast<int>(
state.playerA) * 10 +
state.playerB;
1704 state.editMode =
true;
1708 state.showPropose =
true;
1717 state.proposed =
false;
1728 for (
auto& item :
state.items)
1730 state.showPropose =
false;
1731 state.proposed =
true;
1739 state.playerSelectVisible =
false;
1741 state.editMode =
false;
1742 state.showPropose =
false;
1746 if (!
state.contractDialogVisible ||
state.contractDialogMode != 6)
1760 if (from ==
state.playerA)
state.playerB = to;
1761 else if (to ==
state.playerA)
state.playerB = from;
1762 state.desiredTradePanels =
1763 static_cast<int>(
state.playerA) * 10 +
state.playerB;
1775 .offeredInTradeTo = to;
1779 .jailOfferedInTradeTo = to;
1788 static_cast<std::int32_t
>(message.
numberD),
1789 static_cast<std::uint32_t
>(message.
numberE),
true);
std::array< rules::PlayerNumber, rules::MaxPlayers > players
std::atomic< float > left
std::shared_ptr< const data::HmdTextureImage > source
@ NotifyTradeAcceptanceDecision
PropertyMask propertyBit(int square) noexcept
int propertyBarOrder(int square) noexcept
constexpr PlayerNumber BankPlayer
std::uint8_t PlayerNumber
constexpr std::size_t SquareCount
constexpr PlayerNumber MaxPlayers
constexpr PlayerNumber NobodyPlayer
constexpr Rect CashTradeAT2
PropertyProjection projectProperties(const State &state, const rules::GameState &gameState) noexcept
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
constexpr Rect FutureTradeBT
constexpr Rect CashTradeAT1
constexpr int PlayerSelectY
std::optional< int > propertyHit(const PropertyProjection &projection, int x, int y) noexcept
constexpr Rect ImmunityTradeAM
constexpr Rect CashTradeAM1
constexpr std::array< Rect, 4 > CommunityJailRects
constexpr Rect ImmunityTradeAT
constexpr Rect ContractDownRect
constexpr Rect CashTradeAM2
bool abortIfParticipantOffBoard(State &state, rules::GameState &gameState, display::Screen2D desiredView) noexcept
RuleUpdate processRuleMessage(State &state, rules::GameState &gameState, const actions::Message &message, display::Screen2D currentView, std::uint32_t localHumanMask, bool restoreAutonomousPortfolio)
constexpr Rect CancelRect
bool beginLocalTrade(State &state, const rules::GameState &gameState, rules::PlayerNumber source) noexcept
constexpr Rect ContractUpRect
bool selectPartner(State &state, const rules::GameState &gameState, rules::PlayerNumber player) noexcept
constexpr Rect FutureTradeBM
constexpr Rect ImmunityTradeBT
constexpr Rect ImmunityTradeBM
constexpr Rect CashTradeBM1
InputUpdate processInput(State &state, const rules::GameState &gameState, display::Screen2D desiredView, const uimsg::Message &message)
std::vector< actions::Message > planEditorSubmission(const State &state, rules::PlayerNumber editor, std::uint32_t localHumanMask)
constexpr std::array< Rect, 5 > ContractListRects
void reset(State &state) noexcept
constexpr Rect FutureTradeAM
constexpr std::uint8_t OffBoardSquare
std::uint32_t PropertyMask
constexpr Rect CashTradeBM2
constexpr Rect CashTradeBT1
constexpr int PlayerSelectX
void refreshContractProjection(State &state, const rules::GameState &gameState) noexcept
std::optional< rules::PlayerNumber > planPartnerSelection(State &state, const rules::GameState &gameState, display::Screen2D desiredView, const uimsg::Message &message) noexcept
constexpr Rect CashTradeBT2
constexpr std::array< Rect, 4 > ChanceJailRects
constexpr Rect ProposeRect
constexpr Rect ContractOkayRect
std::optional< Rect > playerTokenRect(const State &state, const rules::GameState &gameState, rules::PlayerNumber player) noexcept
constexpr std::size_t MaxTradeMessages
constexpr Rect FutureNewRect
constexpr Rect FutureTradeAT
constexpr Rect ImmunityNewRect
bool addTradeItem(State &state, const rules::GameState &gameState, const actions::Message &message)
std::vector< std::uint8_t > binaryData
constexpr bool contains(int x, int y) const noexcept