Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
StatsPlayerAuxPlayback.cpp
Go to the documentation of this file.
2
3#include "LegacyBitmap.hpp"
4
5#include <algorithm>
6#include <map>
7#include <memory>
8#include <set>
9#include <utility>
10#include <variant>
11
12namespace monopoly::statsui
13{
14 namespace
15 {
16 using Objects = std::vector<PlayerAuxPlayback::Published>;
17
18 [[nodiscard]] constexpr bool bssmMode(ibar::RuleMode mode) noexcept
19 {
20 return mode == ibar::RuleMode::Build ||
21 mode == ibar::RuleMode::Sell ||
24 }
25
26 [[nodiscard]] constexpr data::DataId languageId(data::DataTag tag) noexcept
27 {
29 }
30 struct AuxCatalog
31 {
35 };
36
37 [[nodiscard]] constexpr AuxCatalog catalog(data::BoardEdition edition) noexcept
38 {
39 if (edition == data::BoardEdition::Europe)
40 {
43 }
44 return {PlayerJailUsBaseTag,
46 }
47
48 [[nodiscard]] std::expected<std::pair<int, int>, std::string>
49 bitmapSize(const sequence::SequenceProgram& program)
50 {
51 const auto resources = program.resources();
52 if (!resources)
53 return std::unexpected("UDStats Player aux sequence has no resources");
54 for (const auto& description : program.descriptions())
55 {
56 if (!std::holds_alternative<data::SequenceBitmapData>(
57 description.record.data) || !description.contentsDataId)
58 continue;
59
60 const auto id = *description.contentsDataId;
61 const auto metadata = resources->data().metadata(id);
62 const auto bytes = resources->data().load(id);
63 if (!metadata || !bytes)
64 return std::unexpected("UDStats Player aux bitmap dependency failed");
66 {
68 if (!bitmap) return std::unexpected(bitmap.error().detail);
69 return std::pair{bitmap->width, bitmap->height};
70 }
72 {
73 const auto bitmap = data::inspectLegacyUap(**bytes);
74 if (!bitmap) return std::unexpected(bitmap.error().detail);
75 return std::pair{static_cast<int>(bitmap->width),
76 static_cast<int>(bitmap->height)};
77 }
78 }
79 return std::unexpected("UDStats Player aux sequence contains no bitmap");
80 }
81 [[nodiscard]] bool hasHit(
82 const rules::GameState& gameState,
84 rules::CountHitType type) noexcept
85 {
86 return std::any_of(gameState.countHits.begin(), gameState.countHits.end(),
87 [&](const rules::CountHitRecord& hit)
88 {
89 return hit.toPlayer == player && hit.hitType == type;
90 });
91 }
92
93 [[nodiscard]] std::expected<Objects, std::string> desiredObjects(
94 const State& state, const rules::GameState& gameState,
95 const PlayerPlaybackInputs& inputs,
96 engine::SequencePlayback& playback)
97 {
98 Objects result;
99 const auto count = std::min<std::size_t>(state.playerCount,
100 std::min<std::size_t>(gameState.numberOfPlayers, rules::MaxPlayers));
101 const bool localBssm = bssmMode(inputs.mode) && inputs.iBarPlayerLocalHuman;
102 const int boxWidth = count > 4 ? 130 : 198;
103 const int boxHeight = count > 4 ? 226 : 222;
104 const auto ids = catalog(playback.resources()->context().board);
105 const auto futureId = languageId(ids.future);
106 const auto immunityId = languageId(ids.immunity);
108 playback.resources(), futureId);
109 if (!futureProgram)
110 return std::unexpected("UDStats Future icon failed: " +
111 futureProgram.error().detail);
113 playback.resources(), immunityId);
114 if (!immunityProgram)
115 return std::unexpected("UDStats Immunity icon failed: " +
116 immunityProgram.error().detail);
117 const auto futureSize = bitmapSize(**futureProgram);
118 if (!futureSize) return std::unexpected(futureSize.error());
120 if (!immunitySize) return std::unexpected(immunitySize.error());
121
122 int iconGapOffset = 3;
123 for (std::size_t column = 0; column < count; ++column)
124 {
125 const auto player = state.playerOrder[column];
126 if (player >= gameState.numberOfPlayers || player >= rules::MaxPlayers)
127 return std::unexpected("UDStats Player aux references invalid player");
128 if (localBssm && player != inputs.iBarPlayer)
129 continue;
131 {
132 const int x = static_cast<int>(column) * boxWidth + boxWidth -
133 immunitySize->first - 10 + iconGapOffset;
134 const int y = 224 + boxHeight - immunitySize->second;
135 result.push_back({immunityId, PlayerAuxPriority, x, y,
136 FutureImmunityIcon{FutureImmunityKind::Immunity, player,
137 {x, y, x + immunitySize->first, y + immunitySize->second}}});
138 }
140 {
141 const int x = static_cast<int>(column) * boxWidth + boxWidth -
142 futureSize->first - 10 + iconGapOffset;
143 const int y = 224 + boxHeight - futureSize->second - 20;
144 result.push_back({futureId, PlayerAuxPriority, x, y,
145 FutureImmunityIcon{FutureImmunityKind::Future, player,
146 {x, y, x + futureSize->first, y + futureSize->second}}});
147 }
148
149 const int deedGapOffset = 3 + 3 * static_cast<int>(column);
150 for (std::size_t deck = 0; deck < gameState.cards.size(); ++deck)
151 {
152 if (gameState.cards[deck].jailOwner != player) continue;
153 result.push_back({languageId(static_cast<data::DataTag>(
154 ids.jailBase + deck)), PlayerAuxPriority,
155 static_cast<int>(column) * boxWidth + deedGapOffset + 10,
156 (modernPlayerGridActive(state, gameState, player) ? 394 : 385) +
157 static_cast<int>(deck) * 30});
158 }
159 iconGapOffset += 3;
160 }
161 return result;
162 }
163 }
165 {
166 state.modernJailLayoutQualified = false;
167 const auto resources = playback.resources();
168 if (!state.modernPlayerLayout || !resources || state.playerCount <= 4 ||
169 state.playerCount > rules::MaxPlayers ||
170 resources->context().board != data::BoardEdition::Usa ||
171 resources->context().language != data::LanguageId::EnglishUs) return;
172 constexpr std::array<std::array<int,4>,2> expected{{{{43,28,737,524}},{{42,26,714,504}}}};
173 for (std::size_t deck = 0; deck < expected.size(); ++deck)
174 {
175 const auto id = languageId(static_cast<data::DataTag>(PlayerJailUsBaseTag + deck));
176 const auto metadata = resources->data().metadata(id);
177 const auto bytes = resources->data().load(id);
178 if (!metadata || metadata->type != data::LegacyDataType::Uap || !bytes) return;
179 const auto image = data::inspectLegacyUap(**bytes);
180 const auto& e = expected[deck];
181 if (!image || image->width != e[0] || image->height != e[1] ||
182 image->originX != e[2] || image->originY != e[3] || image->flags != 6) return;
183 }
184 state.modernJailLayoutQualified = true;
185 }
186
187 std::vector<FutureImmunityIcon> PlayerAuxPlayback::iconHits() const
188 {
189 std::vector<FutureImmunityIcon> result;
190 result.reserve(current_.size());
191 for (const auto& object : current_)
192 if (object.icon) result.push_back(*object.icon);
193 return result;
194 }
195
196 std::expected<void, std::string> PlayerAuxPlayback::sync(
197 const State& state, const rules::GameState& gameState,
201 {
204 state.screen == Screen::Player)
205 {
207 if (!planned) return std::unexpected(planned.error());
208 desired = std::move(*planned);
209 }
210
211 if (desired == current_) return {};
212
213 std::map<data::DataId,
214 std::shared_ptr<const sequence::SequenceProgram>> programs;
215 for (const auto& object : desired)
216 {
217 if (programs.contains(object.id)) continue;
219 playback.resources(), object.id);
220 if (!loaded)
221 return std::unexpected(
222 "UDStats Player aux resource failed: " + loaded.error().detail);
223 programs.emplace(object.id, std::move(*loaded));
224 }
225 std::set<std::pair<data::DataId, std::uint16_t>> stops;
226 for (const auto& object : current_)
227 stops.emplace(object.id, object.priority);
228
229 const std::size_t required = stops.size() + desired.size();
231 playback.commands().pendingCount())
232 {
233 return std::unexpected(
234 "sequence command queue cannot fit UDStats Player aux transition");
235 }
236
237 for (const auto& [id, priority] : stops)
238 {
239 if (!playback.commands().enqueue(
241 return std::unexpected("validated UDStats Player aux stop rejected");
242 }
243
244 for (const auto& object : desired)
245 {
246 if (!playback.commands().enqueue(sequence::StartSequenceCommand{
247 programs.at(object.id), object.priority, {},
248 sequence::moveXYTransform(object.x, object.y)}))
249 return std::unexpected("validated UDStats Player aux start rejected");
250 }
251 current_ = std::move(desired);
252 return {};
253 }
254}
std::size_t count
LegacyBitmapMetadata metadata
LegacyBitmapRGBA8 image
std::vector< std::byte > bytes
data::DataId id
std::uint16_t priority
unsigned state
unsigned index
data::DataTag immunity
data::DataTag future
data::DataTag jailBase
int boxHeight
int boxWidth
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > load(const data::DataSource &source, data::DataId id, std::size_t offset=0, DescriptionLimits limits={})
std::expected< void, std::string > sync(const State &state, const rules::GameState &gameState, const PlayerPlaybackInputs &inputs, display::Screen2D desiredView, engine::SequencePlayback &playback)
void prepareLayout(State &state, const engine::SequencePlayback &playback) const
std::vector< FutureImmunityIcon > iconHits() const
std::uint32_t DataId
Definition DataBanks.hpp:10
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::expected< LegacyUapMetadata, BitmapError > inspectLegacyUap(std::span< const std::byte > bytes)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
std::expected< LegacyBitmapMetadata, BitmapError > inspectLegacyBitmap(std::span< const std::byte > bytes)
std::vector< ObjectSpec > catalog(bool europe)
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept
constexpr std::uint16_t PlayerAuxPriority
constexpr data::DataTag PlayerFutureUsTag
constexpr data::DataTag PlayerFutureEuropeTag
constexpr data::DataTag PlayerJailEuropeBaseTag
bool modernPlayerGridActive(const State &state, const rules::GameState &gameState, rules::PlayerNumber player) noexcept
Definition StatsUI.cpp:361
constexpr data::DataTag PlayerImmunityUsTag
constexpr data::DataTag PlayerImmunityEuropeTag
constexpr data::DataTag PlayerJailUsBaseTag