19 using Tiles = std::unordered_map<data::DataTag, std::shared_ptr<const data::BitmapRuntimeAsset>>;
25 constexpr std::array<std::uint16_t, 4>
Priorities{5229, 5231, 5224, 5226};
33 if (
cp <= 0x7F) result.push_back(
static_cast<char>(
cp));
36 result.push_back(
static_cast<char>(0xC0 | (
cp >> 6)));
37 result.push_back(
static_cast<char>(0x80 | (
cp & 63)));
39 else if (
cp <= 0xFFFF)
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)));
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)));
54 std::string
utf8(std::basic_string_view<Char>
text)
57 for (std::size_t
i = 0;
i <
text.size(); ++
i)
59 auto cp =
static_cast<std::uint32_t
>(
text[
i]);
60 if constexpr (
sizeof(
Char) == 2)
62 if (
cp >= 0xD800 &&
cp <= 0xDBFF &&
i + 1 <
text.size())
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; }
69 if (
cp > 0x10FFFF || (
cp >= 0xD800 &&
cp <= 0xDFFF))
cp = 0xFFFD;
75 const data::LanguageCatalog& language, std::uint32_t
id)
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));
82 std::expected<std::string, std::string> playerName(
84 const data::LanguageCatalog& language)
87 return utf8(std::wstring_view(game.players[player].name));
89 return std::unexpected(
"UDChat sender is neither player nor spectator");
91 if (!
text)
return std::unexpected(
text.error());
92 return utf8(std::u16string_view(*
text));
96 if (
text.empty())
return;
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);
109 std::array<std::uint8_t, 4> color)
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);
118 std::expected<void, std::string>
textBlit(data::LegacyBitmapRGBA8&
image,
120 std::uint32_t color, std::uint8_t
alpha)
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);
138 std::expected<void, std::string>
tileBlit(data::LegacyBitmapRGBA8&
image,
146 if (!program)
return std::unexpected(
"UDChat border: " + program.error().detail);
147 for (
const auto& description : (*program)->descriptions())
149 if (!std::holds_alternative<data::SequenceBitmapData>(description.record.data) ||
150 !description.contentsDataId)
continue;
151 const auto content = *description.contentsDataId;
154 if (!
metadata || !
bytes)
return std::unexpected(
"UDChat border bitmap is unavailable");
155 data::BitmapRuntimeCache decoder;
157 if (!asset)
return std::unexpected(asset.error().detail);
158 cache.emplace(tag, *asset);
161 return std::unexpected(
"UDChat border has no bitmap description");
163 std::expected<void, std::string> background(data::LegacyBitmapRGBA8&
image,
164 bool fluff,
bool shaded,
int windowWidth,
int windowHeight, std::uint8_t
alpha,
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});
173 for (
int i = 1;
i < (windowWidth / 5) - 1; ++
i)
175 if (
auto result =
tile(0x00C1, 5 *
i, 0); !result)
return result;
177 if (
auto result =
tile(0x00A5, 5 *
i, windowHeight - 4); !result)
return result;
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;
183 for (
int i = 0;
i < ((windowHeight - 19) / 5) - 1; ++
i)
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;
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)
201 std::string
contentKey(
const State&
state,
const rules::GameState& game,
204 std::ostringstream
key;
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 <<
':'
211 <<
state.eligibleRecipients <<
':' <<
state.sizing <<
':' <<
state.fluffSizing <<
':';
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 ||
230 return std::unexpected(
"UDChat layout geometry or category is invalid");
236 if (!
metrics ||
metrics->height <= 0)
return std::unexpected(
"UDChat font height unavailable");
242 if (!title)
return std::unexpected(title.error());
243 result.
title = *title;
244 for (std::size_t
i = 0;
i <
state.count; ++
i)
248 ? std::expected<std::string, std::string>(
utf8(std::wstring_view(
entry.displayName)))
250 if (!
name)
return std::unexpected(
name.error());
252 if (
entry.privateMessage)
258 if (
entry.text.empty() &&
entry.cannedTextId != 0)
260 if (
entry.cannedTextId < 1 ||
entry.cannedTextId > 104)
261 return std::unexpected(
"UDChat canned message id is outside the retail range");
263 if (!body)
return std::unexpected(body.error());
264 text +=
utf8(std::u16string_view(*body));
270 std::make_move_iterator(
wrapped->begin()), std::make_move_iterator(
wrapped->end()));
285 if (!body)
return std::unexpected(body.error());
286 const auto full =
utf8(std::u16string_view(*body));
288 if (!
lines)
return std::unexpected(
lines.error().detail);
289 std::string line =
lines->front();
290 if (
lines->size() > 1)
298 if (
measured->width <=
state.fluffWindowWidth - 26 || line ==
"...")
break;
303 if (
i ==
state.fluffSelectedLine)
321 std::array<data::LegacyBitmapRGBA8, 4>
images;
328 redraw = !contentKey_ || *contentKey_ !=
key;
331 const auto resources =
playback.resources();
332 if (!resources || !resources->language() || !resources->language()->catalog)
333 return std::unexpected(
"UDChat language catalog unavailable");
335 if (!layout)
return std::unexpected(layout.error());
339 if (
state.fluffOpen &&
state.fluffSelectedLine >= 0)
342 for (std::size_t
i = 0;
i <
images.size(); ++
i)
360 state.fluffWindowWidth - 158, 16, 0x00DCDCDC, 255); !
drawn)
return drawn;
365 for (std::size_t
row = 0;
row < layout->visibleOutputLines && layout->outputOffset +
row < layout->outputLines.size(); ++
row)
367 6, 18 +
static_cast<int>(
row) * layout->fontHeight,
state.windowWidth - 26,
372 const int editY =
state.windowHeight - 4 - layout->fontHeight;
374 std::string
edit = layout->editText;
378 if (!size)
return std::unexpected(size.error().detail);
389 layout->fontHeight, 0, 255); !
drawn)
return drawn;
394 const int visible = (
state.fluffWindowHeight - 22) / layout->fontHeight;
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});
403 6, 18 +
row * layout->fontHeight,
state.fluffWindowWidth - 26,
404 layout->fontHeight, selected ? 0x00FFFF00 : 0x00BE1010,
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)
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;
420 const int position =
static_cast<int>(
offset *
static_cast<std::size_t
>(std::max(0,
height-75)) /
count);
426 std::size_t commands = 0;
428 for (std::size_t
i = 0;
i < surfaces_.size(); ++
i)
430 const auto& surface = surfaces_[
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])));
435 commands += (surface.visible ? 1 : 0) + (
wanted[
i] ? 1 : 0);
440 return std::unexpected(
"sequence command queue cannot fit UDChat text transition");
442 for (std::size_t
i = 0;
i < surfaces_.size(); ++
i)
444 auto& surface = surfaces_[
i];
445 auto id = surface.id;
447 surface.height !=
static_cast<std::uint32_t
>(
heights[
i]));
456 if (
auto updated =
playback.runtimeBitmaps().update(
457 *
id, std::move(
images[
i])); !updated)
469 return std::unexpected(
"validated UDChat text stop rejected");
473 if (!program)
return std::unexpected(program.error().detail);
476 return std::unexpected(
"validated UDChat text start rejected");
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;
494 if (
state.boxActive) contentKey_ = std::move(
key);
std::array< rules::PlayerNumber, rules::MaxPlayers > players
LegacyBitmapMetadata metadata
std::vector< std::byte > bytes
std::expected< void, std::string > sync(const State &state, const rules::GameState &gameState, rules::PlayerNumber sender, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback)
static constexpr std::size_t Capacity
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::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
std::uint8_t PlayerNumber
constexpr PlayerNumber SpectatorPlayer
constexpr PlayerNumber MaxPlayers
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
std::size_t visibleOutputLines
std::u16string selectedFluffText
std::vector< std::string > fluffLines
std::vector< std::string > outputLines