The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
display_chat_manager.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 - 2025
3 by Chris Beck <render787@gmail.com>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
17
18#include "display.hpp"
19#include "floating_label.hpp"
20#include "game_board.hpp" // <-- only needed for is_observer()
22#include "log.hpp"
24#include "mp_ui_alerts.hpp"
26#include "color.hpp"
28#include "video.hpp" // only for faked
29
30static lg::log_domain log_engine("engine");
31#define ERR_NG LOG_STREAM(err, log_engine)
32
33namespace {
34 const int chat_message_border = 5;
35 const int chat_message_x = 10;
36 const color_t chat_message_color {255,255,255,SDL_ALPHA_OPAQUE};
37 const color_t chat_message_bg {0,0,0,140};
38}
39
41 : speaker_handle(speaker), handle(h), created_at(std::chrono::steady_clock::now())
42{}
43
44
45void display_chat_manager::add_chat_message(const std::chrono::system_clock::time_point& time, const std::string& speaker,
46 int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type,
47 bool bell)
48{
49 const bool whisper = speaker.find("whisper: ") == 0;
50 std::string sender = speaker;
51 if (whisper) {
52 sender.assign(speaker, 9, speaker.size());
53 add_whisperer( sender );
54 }
55 //remove disconnected user from whisperer
56 std::string::size_type pos = message.find(" has disconnected");
57 if (pos != std::string::npos){
58 for(std::set<std::string>::const_iterator w = whisperers().begin(); w != whisperers().end(); ++w){
59 if (*w == message.substr(0,pos)) remove_whisperer(*w);
60 }
61 }
62
63 if (!prefs::get().parse_should_show_lobby_join(sender, message)) return;
64 if (prefs::get().is_ignored(sender)) return;
65
66 //prefs::get().parse_admin_authentication(sender, message); TODO: replace
67
68 bool is_observer = false;
69 { //TODO: Clean this block up somehow
70
71 const game_board * board = dynamic_cast<const game_board*>(&my_disp_.context());
72
73 if (board) {
74 is_observer = board->is_observer();
75 }
76 }
77
78 if (bell) {
79 if ((type == events::chat_handler::MESSAGE_PRIVATE && (!is_observer || whisper))
80 || utils::word_match(message, prefs::get().login())) {
81 mp::ui_alerts::private_message(false, sender, message);
82 } else if (prefs::get().is_friend(sender)) {
83 mp::ui_alerts::friend_message(false, sender, message);
84 } else if (sender == "server") {
85 mp::ui_alerts::server_message(false, sender, message);
86 } else {
87 mp::ui_alerts::public_message(false, sender, message);
88 }
89 }
90
91 bool action = false;
92
93 std::string msg;
94
95 if (message.compare(0,4,"/me ") == 0) {
96 msg.assign(message, 4, message.size());
97 action = true;
98 } else {
99 msg = message;
100 }
101
102 try {
103 // We've had a joker who send an invalid utf-8 message to crash clients
104 // so now catch the exception and ignore the message.
107 ERR_NG << "Invalid utf-8 found, chat message is ignored.";
108 return;
109 }
110
111 int ypos = chat_message_x;
112 for(const auto& m : chat_messages_) {
113 ypos += std::max(font::get_floating_label_rect(m.handle).h,
114 font::get_floating_label_rect(m.speaker_handle).h);
115 }
116 color_t speaker_color;
117 if(side >= 1) {
118 speaker_color = team::get_font_color(side);
119 }
120
121 color_t message_color = chat_message_color;
122 std::stringstream str;
123 std::stringstream message_str;
124
126 if(action) {
127 str << "<" << speaker << " " << msg << ">";
128 message_color = speaker_color;
129 message_str << " ";
130 } else {
131 if (!speaker.empty())
132 str << "<" << speaker << ">";
133 message_str << msg;
134 }
135 } else {
136 if(action) {
137 str << "*" << speaker << " " << msg << "*";
138 message_color = speaker_color;
139 message_str << " ";
140 } else {
141 if (!speaker.empty())
142 str << "*" << speaker << "*";
143 message_str << msg;
144 }
145 }
146
147 // Prepend message with timestamp.
148 std::stringstream message_complete;
149 message_complete << prefs::get().get_chat_timestamp(time) << str.str();
150
152
153 font::floating_label spk_flabel(message_complete.str());
154 spk_flabel.set_font_size(font::SIZE_15);
155 spk_flabel.set_color(speaker_color);
156 spk_flabel.set_position(rect.x + chat_message_x, rect.y + ypos);
157 spk_flabel.set_clip_rect(rect);
159 spk_flabel.set_bg_color(chat_message_bg);
160 spk_flabel.set_border_size(chat_message_border);
161 spk_flabel.use_markup(false);
162
163 int speaker_handle = font::add_floating_label(spk_flabel);
164
165 font::floating_label msg_flabel(message_str.str());
166 msg_flabel.set_font_size(font::SIZE_15);
167 msg_flabel.set_color(message_color);
168 msg_flabel.set_position(rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,
169 rect.y + ypos);
170 msg_flabel.set_clip_rect(rect);
172 msg_flabel.set_bg_color(chat_message_bg);
173 msg_flabel.set_border_size(chat_message_border);
174 msg_flabel.use_markup(false);
175
176 int message_handle = font::add_floating_label(msg_flabel);
177
178 chat_messages_.emplace_back(speaker_handle,message_handle);
179
181}
182
184{
185 //NOTE: prune_chat_messages(false) seems to be only called when a new message is added, which in
186 // particular means the aging feature won't work unless new messages are addded regularly
187 const auto message_aging = prefs::get().chat_message_aging();
188 const unsigned max_chat_messages = prefs::get().chat_lines();
189 const bool enable_aging = message_aging != std::chrono::minutes{0};
190
191 const auto remove_before = enable_aging
192 ? std::chrono::steady_clock::now() - message_aging
193 : std::chrono::steady_clock::time_point::min();
194
195 int movement = 0;
196
197 if(enable_aging || remove_all || chat_messages_.size() > max_chat_messages) {
198 while (!chat_messages_.empty() &&
199 (remove_all ||
200 chat_messages_.front().created_at < remove_before ||
201 chat_messages_.size() > max_chat_messages))
202 {
203 const chat_message &old = chat_messages_.front();
204 movement += font::get_floating_label_rect(old.handle).h;
207 chat_messages_.erase(chat_messages_.begin());
208 }
209 }
210
211 for(const chat_message &cm : chat_messages_) {
212 font::move_floating_label(cm.speaker_handle, 0, - movement);
213 font::move_floating_label(cm.handle, 0, - movement);
214 }
215}
#define ERR_NG
void add_chat_message(const std::chrono::system_clock::time_point &time, const std::string &speaker, int side, const std::string &msg, events::chat_handler::MESSAGE_TYPE type, bool bell)
void remove_whisperer(const std::string &nick)
const std::set< std::string > & whisperers() const
void add_whisperer(const std::string &nick)
void prune_chat_messages(bool remove_all=false)
std::vector< chat_message > chat_messages_
bool is_observer() const
Check if we are an observer in this game.
rect map_outside_area() const
Returns the available area for a map, this may differ from the above.
Definition display.cpp:516
const display_context & context() const
Definition display.hpp:187
void set_position(double xpos, double ypos)
void set_alignment(ALIGN align)
void set_color(const color_t &color)
void set_border_size(int border)
void set_bg_color(const color_t &bg_color)
void set_clip_rect(const rect &r)
void set_font_size(int font_size)
Game board class.
static prefs & get()
std::chrono::minutes chat_message_aging()
std::string get_chat_timestamp(const std::chrono::system_clock::time_point &t)
bool is_ignored(const std::string &nick)
bool parse_should_show_lobby_join(const std::string &sender, const std::string &message)
std::string login()
static color_t get_font_color(int side)
Definition team.cpp:955
Thrown by operations encountering invalid UTF-8 data.
map_display and display: classes which take care of displaying the map and game-data on the screen.
static lg::log_domain log_engine("engine")
Standard logging facilities (interface).
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
rect get_floating_label_rect(int handle)
const int SIZE_15
Definition constants.cpp:28
void remove_floating_label(int handle, const std::chrono::milliseconds &fadeout)
removes the floating label given by 'handle' from the screen
void move_floating_label(int handle, double xmove, double ymove)
moves the floating label given by 'handle' by (xmove,ymove)
std::string pango_word_wrap(const std::string &unwrapped_text, int font_size, int max_width, int max_height, int max_lines, bool)
Uses Pango to word wrap text.
void public_message(bool is_lobby, const std::string &sender, const std::string &message)
void private_message(bool is_lobby, const std::string &sender, const std::string &message)
void friend_message(bool is_lobby, const std::string &sender, const std::string &message)
void server_message(bool is_lobby, const std::string &sender, const std::string &message)
STL namespace.
bool word_match(const std::string &message, const std::string &word)
Check if a message contains a word.
bool headless()
The game is running headless.
Definition video.cpp:146
int w
Definition pathfind.cpp:188
Transitional API for porting SDL_ttf-based code to Pango.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
#define h