Files
Juan 23151496f1 Add chat bubbles above each skater
A player's recent chat lines show as speech bubbles above their skater: newest nearest the head, older lines stacking upward, each popping in with a slight overshoot before it fades. Emotes render as their images, a message that is nothing but a few emotes shows them jumbo, and a message longer than a handful of lines is cut with an ellipsis so one line can never wall the screen off.

Local display options live under Settings > Interface > Multiplayer (on/off, own messages, distance, duration and how many recent lines stack) and the console gains mp chat-bubbles, chat-bubbles-own, chat-bubbles-distance, chat-bubbles-duration and chat-bubbles-history.

The chat panel and the bubbles now share one rich-text module (chat_rich), and a filtered message keeps its emote names: the bad-word mask is applied, but emote tokens are put back from the original before they are parsed.
2026-10-05 22:46:40 -03:00

44 lines
2.2 KiB
C++

#pragma once
#include <imgui.h>
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
// Chat text with its :emote: images (chat_emotes.h), laid out in wrapped lines. Shared by the
// chat panel (chat_overlay.cpp) and the chat bubbles over each skater (nametag_overlay.cpp).
namespace dingosdk::overlay::chat_rich {
// Items refer to the message by offset, so a layout stays valid for as long as the text it
// was made from does not change.
struct RichItem {
std::size_t begin{}, length{}; // within the message: the text, or the emote's name
bool emote{};
ImVec2 at, size;
std::size_t line{};
};
struct RichText {
std::vector<RichItem> items;
float width{}; // the widest line, without trailing spaces
float height{};
std::size_t lines{}; // lines laid out (after any cut)
bool truncated{}; // cut at `max_lines`: "..." ends the last line
ImVec2 ellipsis{}; // where that "..." goes
};
// Whether `text` holds at least one known :emote:.
bool has_emotes(std::string_view text);
// Whether `text` is nothing but known :emotes: (and spaces); `count` says how many.
bool only_emotes(std::string_view text, std::size_t &count);
// `text` in lines no wider than `wrap`, the first starting `indent` in. Words longer than a
// line are broken where they must be. Emotes are `emote_scale` times the text height, centred
// on their line. With `max_lines`, anything past that many lines is cut.
RichText lay_out(ImFont *font, float size, std::string_view text, float wrap, float indent,
std::size_t max_lines = 0, float emote_scale = 1.3f);
// Draws a layout at `origin`, everything multiplied by `scale` (for animation).
void draw_rich(ImDrawList *draw, ImFont *font, float size, ImVec2 origin, std::string_view text, const RichText &rich,
ImU32 colour, float alpha, float scale = 1.0f);
// The bad-word filter keeps a message's length, so the original's known :emote: spans can be
// put back into the masked text: a filtered word must not break an emote's name. Returns
// `masked` unchanged when the two do not line up.
std::string restore_emotes(std::string_view masked, std::string_view raw);
} // namespace dingosdk::overlay::chat_rich