Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
StatsDeedFloaterTextPlayback.cpp
Go to the documentation of this file.
2
4#include "AIUtility.hpp"
5#include "FontRuntime.hpp"
7#include "MoneyFormat.hpp"
10
11#include <algorithm>
12#include <string_view>
13
14namespace monopoly::statsui
15{
16 namespace
17 {
18 constexpr std::uint32_t SurfaceWidth = 400;
19 constexpr std::uint32_t SurfaceHeight = 235;
20 constexpr std::uint32_t OwnerMessageId = 3205;
21 constexpr std::uint32_t CurrentRentMessageId = 3206;
22 constexpr std::uint32_t GameEarningsMessageId = 3207;
23 constexpr std::uint32_t FutureValueMessageId = 3208;
24 constexpr std::uint32_t BankNameMessageId = 900;
25 constexpr std::uint32_t TextColour = 0x00FFFFFFU;
26
27 [[nodiscard]] std::expected<std::string, std::string> messageText(
28 const data::LanguageCatalog& catalog, std::uint32_t id)
29 {
30 const auto text = catalog.message(id);
31 if (!text) return std::unexpected(text.error().detail);
32 const auto encoded = fonts::transcodeUtf8(std::u16string_view(**text));
33 if (!encoded) return std::unexpected(encoded.error().detail);
34 return *encoded;
35 }
36
37 [[nodiscard]] bool contains(
38 const DeedPlayback::Published& item, int x, int y) noexcept
39 {
40 return x >= item.x && x < item.x + DeedCardHitWidth &&
41 y >= item.y && y < item.y + DeedCardHitHeight;
42 }
43
44 [[nodiscard]] std::expected<std::optional<int>, std::string>
46 const State& state, const rules::GameState& gameState,
47 const PlayerPlaybackInputs& inputs)
48 {
49 if (state.screen != Screen::Deed || !state.mouseKnown)
50 return std::optional<int>{};
51 const auto grid = planDeedGrid(state, gameState, inputs);
52 if (!grid) return std::unexpected(grid.error());
53 for (const auto& item : *grid)
54 if (contains(item, state.mouseX, state.mouseY))
55 return std::optional<int>{item.square};
56 return std::optional<int>{};
57 }
58
59 void fillRect(data::LegacyBitmapRGBA8& image, Rect rect,
60 std::uint32_t colour, std::uint8_t alpha = 255)
61 {
62 const int scale=int(image.width/SurfaceWidth);
63 rect={rect.left*scale,rect.top*scale,rect.right*scale,rect.bottom*scale};
64 for (int y = std::max(0, rect.top);
66 for (int x = std::max(0, rect.left);
68 {
69 const auto offset =
70 (static_cast<std::size_t>(y) * image.width +
71 static_cast<std::size_t>(x)) * 4U;
72 image.pixels[offset] =
73 static_cast<std::uint8_t>(colour);
74 image.pixels[offset + 1] =
75 static_cast<std::uint8_t>(colour >> 8U);
76 image.pixels[offset + 2] =
77 static_cast<std::uint8_t>(colour >> 16U);
78 image.pixels[offset + 3] = alpha;
79 }
80 }
81
82 [[nodiscard]] std::expected<void, std::string> print(
83 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
84 std::string_view text, int x, int y)
85 {
86 if (text.empty()) return {};
87 if(image.width==SurfaceWidth*3)
88 {
89 const int logicalSize=font.settings().size;
90 const auto enlarged=font.setSize(logicalSize*3);
91 if(!enlarged)return std::unexpected(enlarged.error().detail);
92 const auto glyphs=font.render(text,TextColour,true);
93 const auto restored=font.setSize(logicalSize);
94 if(!restored)return std::unexpected(restored.error().detail);
95 if(!glyphs)return std::unexpected(glyphs.error().detail);
97 if(!copied)return std::unexpected(copied.error());
98 return {};
99 }
100 const auto blitted = font.blitText(
101 image, text, x, y, TextColour);
102 if (!blitted) return std::unexpected(blitted.error().detail);
103 return {};
104 }
105
106 [[nodiscard]] std::expected<void, std::string> printRight(
107 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
108 std::string_view text, int right, int y, int extraX = 0)
109 {
110 const auto metrics = font.measure(text);
111 if (!metrics) return std::unexpected(metrics.error().detail);
112 return print(image, font, text,
113 right - metrics->width + extraX, y);
114 }
115
116 [[nodiscard]] std::expected<void, std::string> setLabelFont(
117 fonts::Runtime& font)
118 {
119 const auto size = font.setSize(10);
120 if (!size) return std::unexpected(size.error().detail);
121 font.setWeight(700);
122 return {};
123 }
124
125 [[nodiscard]] std::expected<void, std::string> setValueFont(
126 fonts::Runtime& font)
127 {
128 const auto size = font.setSize(14);
129 if (!size) return std::unexpected(size.error().detail);
130 font.setWeight(500);
131 return {};
132 }
133
134 [[nodiscard]] std::expected<void, std::string> printOwner(
135 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
136 std::string_view owner)
137 {
138 if (auto result = setValueFont(font); !result) return result;
139 int size = 14;
140 auto metrics = font.measure(owner);
141 if (!metrics) return std::unexpected(metrics.error().detail);
142 while (metrics->width > 164 && size > 1)
143 {
144 --size;
145 const auto resized = font.setSize(size);
146 if (!resized)
147 return std::unexpected(resized.error().detail);
148 font.setWeight(700);
149 metrics = font.measure(owner);
150 if (!metrics)
151 return std::unexpected(metrics.error().detail);
152 }
153 return printRight(image, font, owner, 164, 28, 10);
154 }
155
156 [[nodiscard]] std::expected<void, std::string> printFutureLabel(
157 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
158 std::string_view label)
159 {
160 if (auto result = setLabelFont(font); !result) return result;
161 const auto metrics = font.measure(label);
162 if (!metrics) return std::unexpected(metrics.error().detail);
163 if (metrics->width <= 164)
164 return print(image, font, label, 15, 182);
165
166 const auto lines = font.wrap(label, 164);
167 if (!lines) return std::unexpected(lines.error().detail);
168 const auto lineMetrics = font.measure("TEST");
169 if (!lineMetrics)
170 return std::unexpected(lineMetrics.error().detail);
171 int y = 167;
172 for (std::size_t i = 0;
173 i < std::min<std::size_t>(2, lines->size()); ++i)
174 {
175 if (auto result = print(
176 image, font, (*lines)[i], 15, y);
177 !result)
178 return result;
179 y += lineMetrics->height;
180 }
181 return {};
182 }
183
184 struct RestoreFont final
185 {
186 fonts::Runtime& font;
187 std::optional<fonts::Settings> caller;
189 {
190 if(!caller)(void)font.restoreSettings(0);
191 else
192 {
193 if(font.settings().fontPath!=caller->fontPath || font.settings().familyName!=caller->familyName)
194 (void)font.setFont(caller->fontPath,caller->familyName);
195 (void)font.setSize(caller->size);font.setWeight(caller->weight);
196 font.setItalic(caller->italic);font.setUnderline(caller->underline);font.setStrikeOut(caller->strikeOut);
197 }
198 }
199 };
200 }
201
202 std::expected<void, std::string> DeedFloaterTextPlayback::sync(
203 const State& state,
206 int monetarySystem,
210 {
211 std::optional<int> square;
212 // Match UDStats normal-hover !IsPopUpIDOn; the picker owns its preview.
214 {
215 const auto hovered = hoveredSquare(state, gameState, inputs);
216 if (!hovered) return std::unexpected(hovered.error());
217 square = *hovered;
218 }
219
220 if (!square)
221 {
222 if (!visible_) return {};
223 if (!surface_ ||
224 !playback.commands().enqueue(sequence::StopSequenceCommand{
225 *surface_, DeedFloaterTextPriority, false}))
226 return std::unexpected(
227 "UDStats Deed floater text stop queue is full");
228 visible_ = false;
229 return {};
230 }
231
232 if (*square < 0 ||
233 *square >= static_cast<int>(rules::SquareCount))
234 return std::unexpected(
235 "UDStats Deed floater text square is out of range");
236 if (fontRuntime == nullptr || !fontRuntime->ready())
237 return std::unexpected(
238 "UDStats Deed floater text font runtime is unavailable");
239
240 const auto resources = playback.resources();
241 const auto language = resources ? resources->language() : nullptr;
242 if (!language || !language->catalog)
243 return std::unexpected(
244 "UDStats Deed floater text has no language catalog");
245 if (!visible_ &&
246 playback.commands().pendingCount() >=
248 return std::unexpected(
249 "UDStats Deed floater text start queue is full");
250
251 const auto& catalog = *language->catalog;
252 const auto ownerLabel = messageText(catalog, OwnerMessageId);
253 const auto rentLabel = messageText(catalog, CurrentRentMessageId);
254 const auto earningsLabel = messageText(catalog, GameEarningsMessageId);
255 const auto futureLabel = messageText(catalog, FutureValueMessageId);
256 if (!ownerLabel) return std::unexpected(ownerLabel.error());
257 if (!rentLabel) return std::unexpected(rentLabel.error());
258 if (!earningsLabel) return std::unexpected(earningsLabel.error());
259 if (!futureLabel) return std::unexpected(futureLabel.error());
260
261 const auto& squareState =
262 gameState.squares[static_cast<std::size_t>(*square)];
263 std::string owner;
264 if (squareState.owner >= rules::MaxPlayers)
265 {
266 const auto bank = messageText(catalog, BankNameMessageId);
267 if (!bank) return std::unexpected(bank.error());
268 owner = *bank;
269 }
270 else
271 {
272 const auto encodedOwner = fonts::transcodeUtf8(std::wstring_view(
273 gameState.players[squareState.owner].name));
274 if (!encodedOwner) return std::unexpected(encodedOwner.error().detail);
275 owner = *encodedOwner;
276 }
277
279 if (squareState.owner < rules::MaxPlayers)
281 gameState, squareState.owner);
282 const auto squareType =
283 static_cast<rules::board::SquareType>(*square);
284 const auto rent = ai::rentIfSteppedOn(
286 const auto edition = resources->context().board;
287 const auto rentText = money::format(
288 rent, monetarySystem, false, edition);
289 const auto earningsText = money::format(
290 squareState.gameEarnings, monetarySystem, false, edition);
291 const auto futureText = money::format(
292 rent, monetarySystem, false, edition);
293 if (!rentText) return std::unexpected(rentText.error());
294 if (!earningsText) return std::unexpected(earningsText.error());
295 if (!futureText) return std::unexpected(futureText.error());
296
297 const bool showLeft = state.mouseX > 400;
298 const int desiredX = showLeft ? 10 : 410;
299 const std::size_t transitionCommands = !visible_ ? 1U :
300 (currentX_ != desiredX ? 2U : 0U);
303 playback.commands().pendingCount())
304 return std::unexpected(
305 "sequence command queue cannot fit UDStats Deed floater text transition");
306
307 const std::string key =
308 std::to_string(*square) + ':' +
309 std::to_string(desiredX) + ':' +
310 std::to_string(monetarySystem) + ':' + owner + ':' +
311 *rentText + ':' + *earningsText + ':' + *futureText + ':' +
312 *ownerLabel + ':' + *rentLabel + ':' +
313 *earningsLabel + ':' + *futureLabel;
314
315 const auto callerSettings=fontRuntime->settings();
316 const auto* savedSettings=fontRuntime->savedSettings(0);
317 if(!savedSettings)return std::unexpected("font setting slot is unavailable");
318 std::optional<fonts::Settings> effectiveSettings{*savedSettings};
319 // Read-only cache probe: never disturb TTF glyph caches on warm hover.
320 // Label/value routines choose fixed sizes/weights during actual raster.
321 effectiveSettings->size=10;effectiveSettings->weight=700;
322 const bool contentChanged =
323 !contentKey_ || *contentKey_ != key || modernAA!=modernAA_ || effectiveSettings!=fontSettings_;
324 if (visible_ && currentX_ == desiredX && contentChanged &&
325 playback.commands().pendingCount() >= sequence::SequenceCommandQueue::Capacity)
326 return std::unexpected("sequence command queue cannot fit deed text redraw");
327 if (contentChanged)
328 {
329 RestoreFont restore{*fontRuntime,modernAA ? std::optional{callerSettings} : std::nullopt};
330 if (const auto restored = fontRuntime->restoreSettings(0);
331 !restored)
332 return std::unexpected(restored.error().detail);
333
334 const unsigned scale=modernAA?3U:1U;
336 SurfaceWidth*scale, SurfaceHeight*scale, {}};
337 image.pixels.assign(
338 SurfaceWidth * SurfaceHeight * scale * scale * 4U, 0U);
339 fillRect(image, {193, 5, 200, 230}, modernAA ? 0x0026230DU : 0x00000000U);
340
341 if (auto result = setLabelFont(*fontRuntime); !result)
342 return result;
343 if (auto result = print(
344 image, *fontRuntime, *ownerLabel, 15, 10);
345 !result)
346 return result;
347
348 if (auto result = printOwner(
349 image, *fontRuntime, owner);
350 !result)
351 return result;
352
353 if (auto result = setLabelFont(*fontRuntime); !result)
354 return result;
355 if (auto result = print(
356 image, *fontRuntime, *rentLabel, 15, 65);
357 !result)
358 return result;
359 if (auto result = setValueFont(*fontRuntime); !result)
360 return result;
361 if (auto result = printRight(
362 image, *fontRuntime, *rentText, 164, 83);
363 !result)
364 return result;
365
366 if (auto result = setLabelFont(*fontRuntime); !result)
367 return result;
368 if (auto result = print(
369 image, *fontRuntime, *earningsLabel, 15, 118);
370 !result)
371 return result;
372 if (auto result = setValueFont(*fontRuntime); !result)
373 return result;
374 if (auto result = printRight(
375 image, *fontRuntime, *earningsText, 164, 136);
376 !result)
377 return result;
378
379 if (auto result = printFutureLabel(
381 !result)
382 return result;
383 if (auto result = setValueFont(*fontRuntime); !result)
384 return result;
385 if (auto result = printRight(
386 image, *fontRuntime, *futureText, 164, 200);
387 !result)
388 return result;
389
390 if (!surface_)
391 {
392 const auto created = playback.runtimeBitmaps().create(
394 if (!created) return std::unexpected(created.error());
395 surface_ = *created;
396 }
397 const auto updated = playback.runtimeBitmaps().update(
398 *surface_, std::move(image),modernAA ? std::optional<std::array<float,4>>{{0,0,float(SurfaceWidth),float(SurfaceHeight)}} : std::nullopt,modernAA);
399 if (!updated) return updated;
400 contentKey_ = key;fontSettings_=effectiveSettings;modernAA_=modernAA;
401 }
402
403 if (visible_ && currentX_ == desiredX && contentChanged)
404 {
405 const auto forced = playback.forceRedraw(
406 *surface_, DeedFloaterTextPriority);
407 if (!forced) return forced;
408 }
409
410 if (!visible_ || currentX_ != desiredX)
411 {
412 const std::size_t needed = visible_ ? 2U : 1U;
413 if (needed >
415 playback.commands().pendingCount())
416 return std::unexpected(
417 "sequence command queue cannot fit UDStats Deed floater text transition");
418
419 const auto program = sequence::SequenceProgram::rawBitmap(
421 if (!program) return std::unexpected(program.error().detail);
422
423 if (visible_)
424 {
425 if (!playback.commands().enqueue(
427 *surface_, DeedFloaterTextPriority, false}))
428 return std::unexpected(
429 "validated UDStats Deed floater text stop rejected");
430 }
431 if (!playback.commands().enqueue(
433 *program, DeedFloaterTextPriority, {},
435 return std::unexpected(
436 "validated UDStats Deed floater text start rejected");
437 visible_ = true;
438 currentX_ = desiredX;
439 }
440 return {};
441 }
442
443 void DeedFloaterTextPlayback::reset() noexcept
444 {
445 surface_.reset();
446 contentKey_.reset();
447 fontSettings_.reset();modernAA_=false;
448 currentX_ = 0;
449 visible_ = false;
450 }
451}
std::size_t offset
std::string_view text
std::atomic< float > right
LegacyBitmapRGBA8 image
fonts::Runtime & font
fonts::Metrics metrics
std::uint64_t key
unsigned state
unsigned index
std::string_view label
int colour
Rect rect
std::optional< fonts::Settings > caller
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 State &state, const rules::GameState &gameState, const PlayerPlaybackInputs &inputs, int monetarySystem, display::Screen2D desiredView, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback, bool deedPopupVisible=false, bool modernAA=false)
std::int64_t rentIfSteppedOn(const rules::GameState &state, SquareType square, rules::board::PropertySet propertiesOwned) noexcept
rules::board::PropertySet propertiesOwnedByPlayer(const rules::GameState &state, rules::PlayerNumber player) noexcept
Definition AIUtility.cpp:49
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::expected< std::string, std::string > format(std::int64_t defaultValue, int monetarySystem, bool printSymbol, data::BoardEdition edition)
std::vector< ObjectSpec > catalog(bool europe)
std::uint32_t PropertySet
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 DeedFloaterTextPriority
std::expected< std::vector< DeedPlayback::Published >, std::string > planDeedGrid(const State &state, const rules::GameState &gameState, const PlayerPlaybackInputs &inputs)
std::size_t size()
std::vector< std::uint8_t > pixels