Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
ChatTextPlayback.cpp
Go to the documentation of this file.
2
3#include "FontRuntime.hpp"
6
7#include <algorithm>
8#include <array>
9#include <sstream>
10#include <utility>
11#include <iterator>
12#include <variant>
13#include <vector>
14
15namespace monopoly::chat
16{
17 namespace
18 {
19 using Tiles = std::unordered_map<data::DataTag, std::shared_ptr<const data::BitmapRuntimeAsset>>;
20 constexpr std::uint32_t FluffBase = 3000;
21 constexpr std::uint32_t SpectatorText = 3105;
22 constexpr std::uint32_t PrivateText = 3106;
23 // Existing controls occupy 5230/5225. Keep their background below and
24 // their clipped text above, matching the two UDChat runtime surfaces.
25 constexpr std::array<std::uint16_t, 4> Priorities{5229, 5231, 5224, 5226};
26 struct RestoreFont
27 {
28 fonts::Runtime& font;
29 ~RestoreFont() { (void)font.restoreSettings(0); }
30 };
31 void appendCodePoint(std::string& result, std::uint32_t cp)
32 {
33 if (cp <= 0x7F) result.push_back(static_cast<char>(cp));
34 else if (cp <= 0x7FF)
35 {
36 result.push_back(static_cast<char>(0xC0 | (cp >> 6)));
37 result.push_back(static_cast<char>(0x80 | (cp & 63)));
38 }
39 else if (cp <= 0xFFFF)
40 {
41 result.push_back(static_cast<char>(0xE0 | (cp >> 12)));
42 result.push_back(static_cast<char>(0x80 | ((cp >> 6) & 63)));
43 result.push_back(static_cast<char>(0x80 | (cp & 63)));
44 }
45 else
46 {
47 result.push_back(static_cast<char>(0xF0 | (cp >> 18)));
48 result.push_back(static_cast<char>(0x80 | ((cp >> 12) & 63)));
49 result.push_back(static_cast<char>(0x80 | ((cp >> 6) & 63)));
50 result.push_back(static_cast<char>(0x80 | (cp & 63)));
51 }
52 }
53 template<class Char>
54 std::string utf8(std::basic_string_view<Char> text)
55 {
56 std::string result;
57 for (std::size_t i = 0; i < text.size(); ++i)
58 {
59 auto cp = static_cast<std::uint32_t>(text[i]);
60 if constexpr (sizeof(Char) == 2)
61 {
62 if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < text.size())
63 {
64 const auto low = static_cast<std::uint32_t>(text[i + 1]);
65 if (low >= 0xDC00 && low <= 0xDFFF)
66 { cp = 0x10000 + ((cp - 0xD800) << 10) + low - 0xDC00; ++i; }
67 }
68 }
69 if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) cp = 0xFFFD;
70 appendCodePoint(result, cp);
71 }
72 return result;
73 }
74 std::expected<std::u16string, std::string> languageText(
75 const data::LanguageCatalog& language, std::uint32_t id)
76 {
77 const auto text = language.lookup(id);
78 if (!text) return std::unexpected(text.error().detail);
79 if (!*text) return std::unexpected("UDChat missing LANG text " + std::to_string(id));
80 return ***text;
81 }
82 std::expected<std::string, std::string> playerName(
83 const rules::GameState& game, rules::PlayerNumber player,
84 const data::LanguageCatalog& language)
85 {
86 if (player < rules::MaxPlayers)
87 return utf8(std::wstring_view(game.players[player].name));
88 if (player != rules::SpectatorPlayer)
89 return std::unexpected("UDChat sender is neither player nor spectator");
90 auto text = languageText(language, SpectatorText);
91 if (!text) return std::unexpected(text.error());
92 return utf8(std::u16string_view(*text));
93 }
94 void removeLastCodePoint(std::string& text)
95 {
96 if (text.empty()) return;
97 std::size_t offset = text.size() - 1;
98 while (offset > 0 && (static_cast<unsigned char>(text[offset]) & 0xC0) == 0x80) --offset;
99 text.resize(offset);
100 }
101 data::LegacyBitmapRGBA8 blank(int width, int height)
102 {
103 data::LegacyBitmapRGBA8 result{static_cast<std::uint32_t>(width),
104 static_cast<std::uint32_t>(height), {}};
105 result.pixels.resize(static_cast<std::size_t>(width) * height * 4, 0);
106 return result;
107 }
108 void fill(data::LegacyBitmapRGBA8& image, int x, int y, int width, int height,
109 std::array<std::uint8_t, 4> color)
110 {
111 for (int row = std::max(y, 0); row < std::min(y + height, static_cast<int>(image.height)); ++row)
112 for (int col = std::max(x, 0); col < std::min(x + width, static_cast<int>(image.width)); ++col)
113 {
114 const auto at = (static_cast<std::size_t>(row) * image.width + col) * 4;
115 std::copy(color.begin(), color.end(), image.pixels.begin() + at);
116 }
117 }
118 std::expected<void, std::string> textBlit(data::LegacyBitmapRGBA8& image,
119 fonts::Runtime& font, std::string_view text, int x, int y, int width, int height,
120 std::uint32_t color, std::uint8_t alpha)
121 {
122 if (text.empty() || width <= 0 || height <= 0 || alpha == 0) return {};
123 auto rendered = font.render(text, color);
124 if (!rendered) return std::unexpected(rendered.error().detail);
125 auto clipped = blank(std::min(width, static_cast<int>(rendered->width)),
126 std::min(height, static_cast<int>(rendered->height)));
127 for (std::uint32_t row = 0; row < clipped.height; ++row)
128 for (std::uint32_t col = 0; col < clipped.width; ++col)
129 {
130 const auto from = (static_cast<std::size_t>(row) * rendered->width + col) * 4;
131 const auto to = (static_cast<std::size_t>(row) * clipped.width + col) * 4;
132 std::copy_n(rendered->pixels.begin() + from, 4, clipped.pixels.begin() + to);
133 clipped.pixels[to + 3] = static_cast<std::uint8_t>(
134 static_cast<unsigned>(clipped.pixels[to + 3]) * alpha / 255);
135 }
137 }
138 std::expected<void, std::string> tileBlit(data::LegacyBitmapRGBA8& image,
139 engine::SequencePlayback& playback, Tiles& cache,
140 data::DataTag tag, int x, int y)
141 {
142 if (const auto found = cache.find(tag); found != cache.end())
144 const auto id = data::packDataId(data::LegacyGroupId::Main, tag);
145 auto program = sequence::SequenceProgram::load(playback.resources(), id);
146 if (!program) return std::unexpected("UDChat border: " + program.error().detail);
147 for (const auto& description : (*program)->descriptions())
148 {
149 if (!std::holds_alternative<data::SequenceBitmapData>(description.record.data) ||
150 !description.contentsDataId) continue;
151 const auto content = *description.contentsDataId;
152 const auto metadata = playback.resources()->data().metadata(content);
153 const auto bytes = playback.resources()->data().load(content);
154 if (!metadata || !bytes) return std::unexpected("UDChat border bitmap is unavailable");
155 data::BitmapRuntimeCache decoder;
156 auto asset = decoder.resolve(content, metadata->type, *bytes);
157 if (!asset) return std::unexpected(asset.error().detail);
158 cache.emplace(tag, *asset);
160 }
161 return std::unexpected("UDChat border has no bitmap description");
162 }
163 std::expected<void, std::string> background(data::LegacyBitmapRGBA8& image,
164 bool fluff, bool shaded, int windowWidth, int windowHeight, std::uint8_t alpha,
165 engine::SequencePlayback& playback, Tiles& cache)
166 {
167 fill(image, 0, 0, windowWidth - 1, 18,
168 fluff ? std::array<std::uint8_t, 4>{127,127,127,255} :
169 std::array<std::uint8_t, 4>{0,255,255,255});
170 if (!shaded) fill(image, 0, 18, windowWidth - 1, windowHeight - 19, {220,220,220,255});
171 const auto tile = [&](data::DataTag tag, int x, int y)
172 { return tileBlit(image, playback, cache, tag, x, y); };
173 for (int i = 1; i < (windowWidth / 5) - 1; ++i)
174 {
175 if (auto result = tile(0x00C1, 5 * i, 0); !result) return result;
176 if (!shaded)
177 if (auto result = tile(0x00A5, 5 * i, windowHeight - 4); !result) return result;
178 }
179 if (auto result = tile(0x00C2, 0, 0); !result) return result;
180 if (auto result = tile(fluff ? 0x00B0 : 0x00C3, windowWidth - 22, 0); !result) return result;
181 if (!shaded)
182 {
183 for (int i = 0; i < ((windowHeight - 19) / 5) - 1; ++i)
184 {
185 if (auto result = tile(0x00B3, 0, 18 + 5 * i); !result) return result;
186 if (auto result = tile(0x00B9, windowWidth - 18, 18 + 5 * i); !result) return result;
187 }
188 if (auto result = tile(0x00A6, 0, windowHeight - 19); !result) return result;
189 if (auto result = tile(0x00A7, windowWidth - 20, windowHeight - 19); !result) return result;
190 if (auto result = tile(0x00A4, windowWidth - 18, 18); !result) return result;
191 if (auto result = tile(0x00A3, windowWidth - 18, windowHeight - 35); !result) return result;
192 for (std::uint32_t y = 18; y < image.height; ++y)
193 for (std::uint32_t x = 0; x < image.width; ++x)
194 {
195 auto& pixelAlpha = image.pixels[(static_cast<std::size_t>(y) * image.width + x) * 4 + 3];
196 pixelAlpha = static_cast<std::uint8_t>(static_cast<unsigned>(pixelAlpha) * alpha / 255);
197 }
198 }
199 return {};
200 }
201 std::string contentKey(const State& state, const rules::GameState& game,
202 rules::PlayerNumber sender, const fonts::Runtime& font)
203 {
204 std::ostringstream key;
205 key << state.historyRevision << ':' << state.first << ':' << state.count << ':' << state.outputOffset
206 << ':' << state.followLatest << ':' << state.windowWidth << ':' << state.windowHeight << ':'
207 << state.fluffWindowWidth << ':' << state.fluffWindowHeight << ':' << state.shaded << ':'
208 << state.fluffOpen << ':' << state.fluffShaded << ':' << state.fluffCategory << ':'
209 << state.fluffLineOffset << ':' << state.fluffSelectedLine << ':' << state.fontSize << ':'
210 << state.textAlphaIndex << ':' << state.backgroundAlphaIndex << ':' << unsigned(sender) << ':'
211 << state.eligibleRecipients << ':' << state.sizing << ':' << state.fluffSizing << ':';
212 const auto append = [&](std::string_view text) { key << text.size() << ':' << text; };
213 append(font.settings().fontPath.string());
214 append(utf8(std::u16string_view(state.draft)));
215 for (const auto& player : game.players) append(utf8(std::wstring_view(player.name)));
216 return key.str();
217 }
218 }
219
220 std::expected<TextLayout, std::string> buildTextLayout(const State& state,
223 {
224 if (!fontRuntime.ready()) return std::unexpected("UDChat font runtime unavailable");
225 if (state.windowWidth < 27 || state.windowHeight < 23 || state.fluffWindowWidth < 27 ||
226 state.fluffWindowHeight < 23 || state.windowWidth > 1601 || state.windowHeight > 1201 ||
227 state.fluffWindowWidth > 1601 || state.fluffWindowHeight > 1201 ||
228 state.fontSize < 7 || state.fontSize > 14 || state.count > HistoryCapacity ||
229 state.fluffCategory >= FluffCategoryLineCounts.size())
230 return std::unexpected("UDChat layout geometry or category is invalid");
231 if (auto saved = fontRuntime.saveSettings(0); !saved) return std::unexpected(saved.error().detail);
232 RestoreFont restore{fontRuntime};
233 fontRuntime.resetCharacteristics();
234 if (auto sized = fontRuntime.setSize(state.fontSize); !sized) return std::unexpected(sized.error().detail);
235 const auto metrics = fontRuntime.measure("Xy");
236 if (!metrics || metrics->height <= 0) return std::unexpected("UDChat font height unavailable");
237 TextLayout result;
238 result.fontHeight = metrics->height;
239 result.visibleOutputLines = static_cast<std::size_t>(std::max(0,
240 (state.windowHeight - 23 - result.fontHeight) / result.fontHeight));
241 const auto title = playerName(gameState, sender, language);
242 if (!title) return std::unexpected(title.error());
243 result.title = *title;
244 for (std::size_t i = 0; i < state.count; ++i)
245 {
246 const auto& entry = state.history[(state.first + i) % HistoryCapacity];
247 auto name = entry.senderNameCaptured
248 ? std::expected<std::string, std::string>(utf8(std::wstring_view(entry.displayName)))
249 : playerName(gameState, entry.from, language);
250 if (!name) return std::unexpected(name.error());
251 std::string text = *name + ": ";
252 if (entry.privateMessage)
253 {
254 const auto marker = languageText(language, PrivateText);
255 if (!marker) return std::unexpected(marker.error());
256 text += utf8(std::u16string_view(*marker)) + " ";
257 }
258 if (entry.text.empty() && entry.cannedTextId != 0)
259 {
260 if (entry.cannedTextId < 1 || entry.cannedTextId > 104)
261 return std::unexpected("UDChat canned message id is outside the retail range");
262 const auto body = languageText(language, FluffBase + static_cast<std::uint32_t>(entry.cannedTextId));
263 if (!body) return std::unexpected(body.error());
264 text += utf8(std::u16string_view(*body));
265 }
266 else text += utf8(std::u16string_view(entry.text));
267 auto wrapped = fontRuntime.wrap(text, state.windowWidth - 26);
268 if (!wrapped) return std::unexpected(wrapped.error().detail);
269 result.outputLines.insert(result.outputLines.end(),
270 std::make_move_iterator(wrapped->begin()), std::make_move_iterator(wrapped->end()));
271 }
272 result.outputOffset = state.followLatest
273 ? (result.outputLines.size() > result.visibleOutputLines ? result.outputLines.size() - result.visibleOutputLines : 0)
274 : (result.outputLines.empty() ? 0 : std::min(state.outputOffset, result.outputLines.size() - 1));
275 result.editText = utf8(std::u16string_view(state.draft));
276 if (state.fluffOpen)
277 {
278 const auto first = FluffBase + static_cast<std::uint32_t>(FluffCategoryMessageStarts[state.fluffCategory]);
279 const auto heading = languageText(language, first - 1);
280 if (!heading) return std::unexpected(heading.error());
281 result.fluffTitle = utf8(std::u16string_view(*heading));
282 for (int i = 0; i < FluffCategoryLineCounts[state.fluffCategory]; ++i)
283 {
284 const auto body = languageText(language, first + static_cast<std::uint32_t>(i));
285 if (!body) return std::unexpected(body.error());
286 const auto full = utf8(std::u16string_view(*body));
287 auto lines = fontRuntime.wrap(full, state.fluffWindowWidth - 26);
288 if (!lines) return std::unexpected(lines.error().detail);
289 std::string line = lines->front();
290 if (lines->size() > 1)
291 {
292 for (int dots = 0; dots < 3; ++dots) removeLastCodePoint(line);
293 line += "...";
294 for (;;)
295 {
296 const auto measured = fontRuntime.measure(line);
297 if (!measured) return std::unexpected(measured.error().detail);
298 if (measured->width <= state.fluffWindowWidth - 26 || line == "...") break;
299 line.resize(line.size() - 3); removeLastCodePoint(line); line += "...";
300 }
301 }
302 result.fluffLines.push_back(std::move(line));
303 if (i == state.fluffSelectedLine)
304 { result.editText = full; result.selectedFluffText = *body; }
305 }
306 }
307 return result;
308 }
309
310 std::expected<void, std::string> TextPlayback::sync(const State& state,
313 {
314 const std::array<bool, 4> wanted{state.boxActive, state.boxActive,
315 state.boxActive && state.fluffOpen, state.boxActive && state.fluffOpen};
316 const std::array<int, 4> xs{state.windowX,state.windowX,state.fluffWindowX,state.fluffWindowX};
317 const std::array<int, 4> ys{state.windowY,state.windowY,state.fluffWindowY,state.fluffWindowY};
318 const std::array<int, 4> widths{state.windowWidth-1,state.windowWidth-1,state.fluffWindowWidth-1,state.fluffWindowWidth-1};
319 const std::array<int, 4> heights{state.shaded?18:state.windowHeight-1,state.shaded?18:state.windowHeight-1,
320 state.fluffShaded?18:state.fluffWindowHeight-1,state.fluffShaded?18:state.fluffWindowHeight-1};
321 std::array<data::LegacyBitmapRGBA8, 4> images;
322 bool redraw = false;
323 std::string key;
324 if (state.boxActive)
325 {
326 if (!fontRuntime || !fontRuntime->ready()) return std::unexpected("UDChat font runtime unavailable");
328 redraw = !contentKey_ || *contentKey_ != key;
329 if (redraw)
330 {
331 const auto resources = playback.resources();
332 if (!resources || !resources->language() || !resources->language()->catalog)
333 return std::unexpected("UDChat language catalog unavailable");
334 auto layout = buildTextLayout(state, gameState, sender, *fontRuntime, *resources->language()->catalog);
335 if (!layout) return std::unexpected(layout.error());
336 if (&state == &stateReadOnly())
337 {
338 setOutputLayoutMetrics(layout->outputLines.size(), layout->fontHeight, layout->visibleOutputLines);
339 if (state.fluffOpen && state.fluffSelectedLine >= 0)
340 setFluffSelectionText(selectedFluffMessageId(), layout->selectedFluffText);
341 }
342 for (std::size_t i = 0; i < images.size(); ++i)
343 if (wanted[i]) images[i] = blank(widths[i], heights[i]);
344 if (auto painted = background(images[0], false, state.shaded, state.windowWidth,
345 state.windowHeight, alphaForIndex(state.backgroundAlphaIndex), playback, tiles_); !painted) return painted;
346 if (wanted[2])
347 if (auto painted = background(images[2], true, state.fluffShaded, state.fluffWindowWidth,
348 state.fluffWindowHeight, alphaForIndex(state.backgroundAlphaIndex), playback, tiles_); !painted) return painted;
349 if (auto saved = fontRuntime->saveSettings(0); !saved) return std::unexpected(saved.error().detail);
350 RestoreFont restore{*fontRuntime};
351 fontRuntime->resetCharacteristics();
352 if (auto sized = fontRuntime->setSize(7); !sized) return std::unexpected(sized.error().detail);
353 int recipientCount = 0;
354 for (std::uint32_t mask = state.eligibleRecipients; mask; mask >>= 1) recipientCount += mask & 1U;
355 const int titleRight = state.windowWidth - 1 - (5 + 6 * 18 + (recipientCount - 1) * 18);
356 if (auto drawn = textBlit(images[1], *fontRuntime, layout->title, 23, 3,
357 titleRight - 23, 15, 0x00DCDCDC, 255); !drawn) return drawn;
358 if (wanted[3])
359 if (auto drawn = textBlit(images[3], *fontRuntime, layout->fluffTitle, 15, 2,
360 state.fluffWindowWidth - 158, 16, 0x00DCDCDC, 255); !drawn) return drawn;
361 if (auto sized = fontRuntime->setSize(state.fontSize); !sized) return std::unexpected(sized.error().detail);
362 const auto alpha = alphaForIndex(state.textAlphaIndex);
363 if (!state.shaded)
364 {
365 for (std::size_t row = 0; row < layout->visibleOutputLines && layout->outputOffset + row < layout->outputLines.size(); ++row)
366 if (auto drawn = textBlit(images[1], *fontRuntime, layout->outputLines[layout->outputOffset + row],
367 6, 18 + static_cast<int>(row) * layout->fontHeight, state.windowWidth - 26,
368 layout->fontHeight, 0x00101010, alpha); !drawn) return drawn;
369 if (!state.sizing && !state.fluffSizing)
370 {
371 const int editWidth = state.windowWidth - 25;
372 const int editY = state.windowHeight - 4 - layout->fontHeight;
373 fill(images[1], 5, editY, editWidth, layout->fontHeight, {255,255,255,255});
374 std::string edit = layout->editText;
375 for (;;)
376 {
377 const auto size = fontRuntime->measure(edit);
378 if (!size) return std::unexpected(size.error().detail);
379 if (size->width <= editWidth - 2 || edit.empty()) break;
380 if (state.fluffSelectedLine >= 0) removeLastCodePoint(edit);
381 else
382 {
383 std::size_t offset = 1;
384 while (offset < edit.size() && (static_cast<unsigned char>(edit[offset]) & 0xC0) == 0x80) ++offset;
385 edit.erase(0, offset);
386 }
387 }
388 if (auto drawn = textBlit(images[1], *fontRuntime, edit, 6, editY, editWidth - 2,
389 layout->fontHeight, 0, 255); !drawn) return drawn;
390 }
391 }
392 if (wanted[3] && !state.fluffShaded)
393 {
394 const int visible = (state.fluffWindowHeight - 22) / layout->fontHeight;
395 for (int row = 0; row < visible; ++row)
396 {
397 const int line = state.fluffLineOffset + row;
398 if (line < 0 || static_cast<std::size_t>(line) >= layout->fluffLines.size()) continue;
399 const bool selected = line == state.fluffSelectedLine;
400 if (selected) fill(images[3], 6, 18 + row * layout->fontHeight,
401 state.fluffWindowWidth - 26, layout->fontHeight, {16,16,255,255});
402 if (auto drawn = textBlit(images[3], *fontRuntime, layout->fluffLines[line],
403 6, 18 + row * layout->fontHeight, state.fluffWindowWidth - 26,
404 layout->fontHeight, selected ? 0x00FFFF00 : 0x00BE1010,
405 selected ? 255 : alpha); !drawn) return drawn;
406 if (selected)
407 for (int y = 18 + row * layout->fontHeight; y < 18 + (row + 1) * layout->fontHeight; ++y)
408 for (int x = 6; x < state.fluffWindowWidth - 20; ++x)
409 images[3].pixels[(static_cast<std::size_t>(y) * images[3].width + x) * 4 + 3] = alpha;
410 }
411 }
412 for (std::size_t window = 0; window < 2; ++window)
413 {
414 if (!wanted[window*2] || (window == 0 ? state.shaded : state.fluffShaded)) continue;
415 const auto count = window == 0 ? layout->outputLines.size() : layout->fluffLines.size();
416 const auto offset = window == 0 ? layout->outputOffset : static_cast<std::size_t>(std::max(0,state.fluffLineOffset));
417 if (count == 0) continue;
418 const int height = window == 0 ? state.windowHeight : state.fluffWindowHeight;
419 const int width = window == 0 ? state.windowWidth : state.fluffWindowWidth;
420 const int position = static_cast<int>(offset * static_cast<std::size_t>(std::max(0,height-75)) / count);
421 fill(images[window*2+1], width-15, 36+position, 11, 4, {255,255,255,alpha});
422 }
424 }
425 }
426 std::size_t commands = 0;
427 std::array<bool,4> transition{};
428 for (std::size_t i = 0; i < surfaces_.size(); ++i)
429 {
430 const auto& surface = surfaces_[i];
431 transition[i] = surface.visible != wanted[i] || (wanted[i] &&
432 (surface.x != xs[i] || surface.y != ys[i] || surface.width != static_cast<std::uint32_t>(widths[i]) ||
433 surface.height != static_cast<std::uint32_t>(heights[i])));
434 if (transition[i])
435 commands += (surface.visible ? 1 : 0) + (wanted[i] ? 1 : 0);
436 else if (wanted[i] && redraw && surface.visible)
437 ++commands;
438 }
439 if (commands > sequence::SequenceCommandQueue::Capacity - playback.commands().pendingCount())
440 return std::unexpected("sequence command queue cannot fit UDChat text transition");
441 std::vector<data::DataId> retiredSurfaces;
442 for (std::size_t i = 0; i < surfaces_.size(); ++i)
443 {
444 auto& surface = surfaces_[i];
445 auto id = surface.id;
446 const bool replace = wanted[i] && (!id || surface.width != static_cast<std::uint32_t>(widths[i]) ||
447 surface.height != static_cast<std::uint32_t>(heights[i]));
448 if (replace)
449 {
450 auto created = playback.runtimeBitmaps().create(widths[i], heights[i], true);
451 if (!created) return std::unexpected(created.error());
452 id = *created;
453 }
454 if (wanted[i] && redraw)
455 {
456 if (auto updated = playback.runtimeBitmaps().update(
457 *id, std::move(images[i])); !updated)
458 return updated;
459 if (!transition[i] && surface.visible)
460 {
461 const auto forced = playback.forceRedraw(*id, Priorities[i]);
462 if (!forced) return forced;
463 }
464 }
465 if (transition[i])
466 {
467 if (surface.visible)
468 if (!playback.commands().enqueue(sequence::StopSequenceCommand{*surface.id, Priorities[i], false}))
469 return std::unexpected("validated UDChat text stop rejected");
470 if (wanted[i])
471 {
473 if (!program) return std::unexpected(program.error().detail);
474 if (!playback.commands().enqueue(sequence::StartSequenceCommand{*program, Priorities[i], {},
476 return std::unexpected("validated UDChat text start rejected");
477 }
478 }
479 if (replace && surface.id)
480 retiredSurfaces.push_back(*surface.id);
481 if (wanted[i])
482 surface = Surface{id, static_cast<std::uint32_t>(widths[i]), static_cast<std::uint32_t>(heights[i]), xs[i], ys[i], true};
483 else surface.visible = false;
484 }
485
486 if (!retiredSurfaces.empty())
487 {
488 const auto processed = playback.processUserCommands();
489 if (!processed) return processed;
490 for (const auto id : retiredSurfaces)
491 (void)playback.runtimeBitmaps().remove(id);
492 }
493
494 if (state.boxActive) contentKey_ = std::move(key);
495 return {};
496 }
497
498 void TextPlayback::reset() noexcept
499 {
500 surfaces_ = {};
501 contentKey_.reset();
502 tiles_.clear();
503 }
504}
std::size_t count
std::array< rules::PlayerNumber, rules::MaxPlayers > players
std::size_t offset
const char * name
std::string_view text
LegacyBitmapMetadata metadata
LegacyBitmapRGBA8 image
std::vector< std::byte > bytes
fonts::Runtime & font
data::DataId id
fonts::Settings saved
fonts::Metrics metrics
std::uint64_t key
std::uint32_t height
std::uint32_t width
unsigned state
unsigned index
data::DataTag first
std::size_t at
std::expected< void, std::string > sync(const State &state, const rules::GameState &gameState, rules::PlayerNumber sender, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback)
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > rawBitmap(data::DataId id, data::LegacyDataType sourceType, DescriptionLimits limits={})
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > load(const data::DataSource &source, data::DataId id, std::size_t offset=0, DescriptionLimits limits={})
constexpr std::uint8_t alphaForIndex(int index) noexcept
constexpr std::size_t HistoryCapacity
const State & stateReadOnly() noexcept
std::expected< TextLayout, std::string > buildTextLayout(const State &state, const rules::GameState &gameState, rules::PlayerNumber sender, fonts::Runtime &fontRuntime, const data::LanguageCatalog &language)
std::int64_t selectedFluffMessageId() noexcept
constexpr std::array< int, 6 > FluffCategoryLineCounts
void setOutputLayoutMetrics(std::size_t wrappedLines, int height, std::size_t visibleLines) noexcept
void setFluffSelectionText(std::int64_t cannedTextId, std::u16string_view text)
constexpr std::array< std::int64_t, 6 > FluffCategoryMessageStarts
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::expected< void, std::string > blitStraightRGBA8(LegacyBitmapRGBA8 &destination, const LegacyBitmapRGBA8 &source, std::int32_t destinationX, std::int32_t destinationY, BitmapBlitMode mode)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber SpectatorPlayer
Definition RuleTypes.hpp:22
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
std::vector< std::string > fluffLines
std::vector< std::string > outputLines