Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
StatsFutureImmunityTextPlayback.cpp
Go to the documentation of this file.
2
3#include "FontRuntime.hpp"
4#include "IBarLayout.hpp"
8
9#include <algorithm>
10#include <array>
11#include <string_view>
12
13namespace monopoly::statsui
14{
15 namespace
16 {
17 constexpr std::uint32_t PanelWidth = 197;
18 constexpr std::uint32_t PanelHeight = 223;
19 constexpr std::uint32_t FuturesForMessageId = 3180;
20 constexpr std::uint32_t ImmunitiesForMessageId = 3181;
21 constexpr std::uint32_t PropertyMessageId = 3182;
22 constexpr std::uint32_t HitsRemainingMessageId = 3183;
23 constexpr std::uint32_t SquareNameBase = 1001;
24 constexpr std::uint32_t TextColour = 0x00FAFAFAU;
25
26 [[nodiscard]] std::expected<std::string, std::string> messageText(
27 const data::LanguageCatalog& catalog, std::uint32_t id)
28 {
29 const auto text = catalog.message(id);
30 if (!text) return std::unexpected(text.error().detail);
31 const auto encoded = fonts::transcodeUtf8(std::u16string_view(**text));
32 if (!encoded) return std::unexpected(encoded.error().detail);
33 return *encoded;
34 }
35
36 [[nodiscard]] std::string expandPlayer(
37 std::string_view pattern, std::string_view playerName)
38 {
39 std::string output;
40 for (std::size_t i = 0; i < pattern.size(); ++i)
41 {
42 if (pattern[i] == '^' && i + 1 < pattern.size() &&
43 (pattern[i + 1] == 'P' || pattern[i + 1] == 'p'))
44 {
45 output.append(playerName);
46 ++i;
47 }
48 else output.push_back(pattern[i]);
49 }
50 return output;
51 }
52
53 [[nodiscard]] std::expected<std::string, std::string> squareName(
54 const data::LanguageCatalog& catalog, int city,
55 data::BoardEdition edition, int square)
56 {
57 if (square < 0 || square >= static_cast<int>(rules::SquareCount))
58 return std::unexpected(
59 "Future/Immunity square index is out of range");
60 if (city == -1 && edition == data::BoardEdition::Europe)
61 city = static_cast<int>(catalog.language()) - 2;
62 const auto id = SquareNameBase +
63 42U * static_cast<std::uint32_t>(std::max(city, 0)) +
64 static_cast<std::uint32_t>(square);
65 auto text = catalog.lookup(id);
66 if (!text) return std::unexpected(text.error().detail);
67 if (!*text)
68 return std::unexpected(
69 "Future/Immunity square name is missing from LANG");
70 if (!(**text)->empty() && (**text)->front() == u'*')
71 {
72 text = catalog.lookup(
73 SquareNameBase + static_cast<std::uint32_t>(square));
74 if (!text) return std::unexpected(text.error().detail);
75 if (!*text)
76 return std::unexpected(
77 "Future/Immunity base square name is missing from LANG");
78 }
79 const auto encoded = fonts::transcodeUtf8(std::u16string_view(***text));
80 if (!encoded) return std::unexpected(encoded.error().detail);
81 return *encoded;
82 }
83
84 [[nodiscard]] std::expected<void, std::string> print(
85 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
86 std::string_view text, int x, int y)
87 {
88 if (text.empty()) return {};
89 const auto rendered = font.render(text, TextColour);
90 if (!rendered) return std::unexpected(rendered.error().detail);
92 image, *rendered, x, y,
94 }
95
96 [[nodiscard]] std::expected<void, std::string> printCentered(
97 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
98 std::string_view text, int centerX, int y)
99 {
100 const auto metrics = font.measure(text);
101 if (!metrics) return std::unexpected(metrics.error().detail);
102 return print(image, font, text,
103 centerX - metrics->width / 2, y);
104 }
105
106 [[nodiscard]] std::expected<void, std::string> printClipped(
107 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
108 std::string_view text, int x, int y, int width, int height)
109 {
110 if (width <= 0 || height <= 0 || text.empty()) return {};
111 const auto rendered = font.renderClipped(
113 {0, 0, static_cast<std::uint32_t>(width),
114 static_cast<std::uint32_t>(height)});
115 if (!rendered) return std::unexpected(rendered.error().detail);
117 image, *rendered, x, y,
119 }
120
121 struct RestoreFont final
122 {
123 fonts::Runtime& font;
124 ~RestoreFont() { (void)font.restoreSettings(0); }
125 };
126 }
127 std::expected<void, std::string> FutureImmunityTextPlayback::sync(
131 int city,
134 {
135 const bool desired =
137 if (!desired)
138 {
139 if (!visible_) return {};
140 if (!surface_ ||
141 !playback.commands().enqueue(sequence::StopSequenceCommand{
142 *surface_, FutureImmunityTextPriority, false}))
143 return std::unexpected(
144 "Future/Immunity text stop queue is full");
145 visible_ = false;
146 return {};
147 }
148
149 if (state.player >= gameState.numberOfPlayers ||
150 state.player >= rules::MaxPlayers)
151 return std::unexpected(
152 "Future/Immunity text player is out of range");
153 if (fontRuntime == nullptr || !fontRuntime->ready())
154 return std::unexpected(
155 "Future/Immunity text font runtime is unavailable");
156
157 const auto resources = playback.resources();
158 const auto language = resources ? resources->language() : nullptr;
159 if (!language || !language->catalog)
160 return std::unexpected(
161 "Future/Immunity text has no language catalog");
162 if (!visible_ &&
163 playback.commands().pendingCount() >=
165 return std::unexpected(
166 "Future/Immunity text start queue is full");
167
168 const auto& catalog = *language->catalog;
169 const auto titlePattern = messageText(
170 catalog, state.kind == FutureImmunityKind::Future
172 if (!titlePattern) return std::unexpected(titlePattern.error());
173 const auto property = messageText(catalog, PropertyMessageId);
174 if (!property) return std::unexpected(property.error());
175 const auto hits = messageText(catalog, HitsRemainingMessageId);
176 if (!hits) return std::unexpected(hits.error());
177
178 const auto playerName = fonts::transcodeUtf8(std::wstring_view(
179 gameState.players[state.player].name));
180 if (!playerName) return std::unexpected(playerName.error().detail);
181 const auto title = expandPlayer(*titlePattern, *playerName);
182
183 std::array<std::string, 10> names{};
184 std::array<std::string, 10> hitCounts{};
185 const auto first = static_cast<std::size_t>(
186 std::max(state.scrollIndex, 0));
187 for (std::size_t row = 0; row < names.size(); ++row)
188 {
189 const auto index = first + row;
190 if (index >= state.rows.size()) break;
191 const auto name = squareName(
192 catalog, city, resources->context().board,
193 state.rows[index].square);
194 if (!name) return std::unexpected(name.error());
195 names[row] = *name;
196 hitCounts[row] = std::to_string(state.rows[index].hitCount);
197 if (hitCounts[row].size() < 2)
198 hitCounts[row].insert(hitCounts[row].begin(), ' ');
199 }
200
201 std::string key =
202 std::to_string(static_cast<int>(state.kind)) + ':' +
203 std::to_string(state.player) + ':' +
204 std::to_string(state.scrollIndex) + ':' + title + ':' +
205 *property + ':' + *hits;
206 for (std::size_t row = 0; row < names.size(); ++row)
207 {
208 key += '|';
209 key += names[row];
210 key += ':';
211 key += hitCounts[row];
212 }
213
214 const bool contentChanged =
215 !contentKey_ || *contentKey_ != key;
216 if (visible_ && contentChanged && playback.commands().pendingCount() >=
218 return std::unexpected("sequence command queue cannot fit text redraw");
219 if (contentChanged)
220 {
221 RestoreFont restore{*fontRuntime};
222 if (const auto restored = fontRuntime->restoreSettings(0);
223 !restored)
224 return std::unexpected(restored.error().detail);
225
227 image.pixels.assign(
228 PanelWidth * PanelHeight * 4U, 0U);
229
230 const int titleY =
231 state.kind == FutureImmunityKind::Future ? 9 : 10;
232 if (auto result = printCentered(
233 image, *fontRuntime, title, 98, titleY);
234 !result)
235 return result;
236
237 if (auto result = print(
238 image, *fontRuntime, *property, 15, 30);
239 !result)
240 return result;
241
242 const auto hitsMetrics = fontRuntime->measure(*hits);
243 if (!hitsMetrics)
244 return std::unexpected(hitsMetrics.error().detail);
245 if (hitsMetrics->width <= 80)
246 {
247 if (auto result = printCentered(
248 image, *fontRuntime, *hits, 150, 30);
249 !result)
250 return result;
251 }
252 else
253 {
254 const auto lines = fontRuntime->wrap(*hits, 80);
255 if (!lines)
256 return std::unexpected(lines.error().detail);
257 const auto lineMetrics = fontRuntime->measure("TEST");
258 if (!lineMetrics)
259 return std::unexpected(lineMetrics.error().detail);
260 const int x = catalog.language() == data::LanguageId::Danish
261 ? 110 : 120;
262 int y = 25;
263 for (std::size_t i = 0;
264 i < std::min<std::size_t>(2, lines->size()); ++i)
265 {
266 if (auto result = print(
267 image, *fontRuntime, (*lines)[i], x, y);
268 !result)
269 return result;
270 y += lineMetrics->height;
271 }
272 }
273
274 for (std::size_t row = 0; row < names.size(); ++row)
275 {
276 if (names[row].empty()) break;
277 const int y = 60 + static_cast<int>(row) * 15;
278 if (auto result = printClipped(
279 image, *fontRuntime, names[row],
280 15, y, 105, 15);
281 !result)
282 return result;
283 if (auto result = print(
284 image, *fontRuntime, hitCounts[row], 150, y);
285 !result)
286 return result;
287 }
288
289 if (!surface_)
290 {
291 const auto created = playback.runtimeBitmaps().create(
292 PanelWidth, PanelHeight, true);
293 if (!created) return std::unexpected(created.error());
294 surface_ = *created;
295 }
296 const auto updated = playback.runtimeBitmaps().update(
297 *surface_, std::move(image));
298 if (!updated) return updated;
299 contentKey_ = std::move(key);
300 }
301
302 if (visible_ && contentChanged)
303 {
304 const auto forced = playback.forceRedraw(
305 *surface_, FutureImmunityTextPriority);
306 if (!forced) return forced;
307 }
308
309 if (!visible_)
310 {
311 const auto program = sequence::SequenceProgram::rawBitmap(
313 if (!program) return std::unexpected(program.error().detail);
314 if (!playback.commands().enqueue(sequence::StartSequenceCommand{
315 *program, FutureImmunityTextPriority, {},
319 return std::unexpected(
320 "Future/Immunity text start queue is full");
321 visible_ = true;
322 }
323 return {};
324 }
325
326 void FutureImmunityTextPlayback::reset() noexcept
327 {
328 surface_.reset();
329 contentKey_.reset();
330 visible_ = false;
331 }
332}
const char * name
std::string_view text
LegacyBitmapRGBA8 image
fonts::Runtime & font
fonts::Metrics metrics
std::uint64_t key
std::uint32_t height
std::uint32_t width
unsigned state
unsigned index
data::DataTag first
std::deque< Bytes > output
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > rawBitmap(data::DataId id, data::LegacyDataType sourceType, DescriptionLimits limits={})
std::expected< void, std::string > sync(const FutureImmunityState &state, const rules::GameState &gameState, display::Screen2D desiredView, int city, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback)
std::expected< void, std::string > blitStraightRGBA8(LegacyBitmapRGBA8 &destination, const LegacyBitmapRGBA8 &source, std::int32_t destinationX, std::int32_t destinationY, BitmapBlitMode mode)
std::expected< std::string, Error > transcodeUtf8(std::u16string_view text)
std::vector< ObjectSpec > catalog(bool europe)
constexpr std::size_t SquareCount
Definition RuleTypes.hpp:24
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
constexpr std::uint16_t FutureImmunityTextPriority
constexpr Rect FutureImmunityPopupRect
std::vector< std::uint8_t > pixels