The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
match_history.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 - 2025
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13*/
14
16
18#include "gettext.hpp"
19#include "filesystem.hpp"
22#include "gui/widgets/label.hpp"
31
32using namespace std::chrono_literals;
33
34static lg::log_domain log_network("network");
35#define DBG_NW LOG_STREAM(debug, log_network)
36#define ERR_NW LOG_STREAM(err, log_network)
37
38namespace gui2::dialogs
39{
40REGISTER_DIALOG(mp_match_history)
41
42mp_match_history::mp_match_history(const std::string& player_name, wesnothd_connection& connection, bool wait_for_response)
43 : modal_dialog(window_id())
44 , player_name_(player_name)
45 , connection_(connection)
46 , offset_(0)
47 , wait_for_response_(wait_for_response)
48{
49 register_label("title", true, VGETTEXT("Match History — $player", {{"player", player_name_}}));
50}
51
53{
55
56 button& newer_history = find_widget<button>("newer_history");
57 button& older_history = find_widget<button>("older_history");
58 connect_signal_mouse_left_click(newer_history, std::bind(&mp_match_history::newer_history_offset, this));
59 connect_signal_mouse_left_click(older_history, std::bind(&mp_match_history::older_history_offset, this));
60
61 button& search = find_widget<button>("search");
62 connect_signal_mouse_left_click(search, std::bind(&mp_match_history::new_search, this));
63
64 text_box& search_player = find_widget<text_box>("search_player");
65 search_player.set_value(player_name_);
66
67 std::vector<config> content_types;
68 content_types.emplace_back("label", _("Scenario"));
69 content_types.emplace_back("label", _("Era"));
70 content_types.emplace_back("label", _("Modification"));
71
72 find_widget<menu_button>("search_content_type").set_values(content_types);
73
75}
76
78{
79 int old_offset = offset_;
80 std::string old_player_name = player_name_;
81 text_box& search_player = find_widget<text_box>("search_player");
82 player_name_ = search_player.get_value();
83
84 // display update failed, set the offset back to what it was before
85 if(!update_display()) {
86 offset_ = old_offset;
87 player_name_ = old_player_name;
88 } else {
89 label& title = find_widget<label>("title");
90 title.set_label(VGETTEXT("Match History — $player", {{"player", player_name_}}));
91 }
92}
93
95{
96 offset_ -= 10;
97 // display update failed, set the offset back to what it was before
98 if(!update_display()) {
99 offset_ += 10;
100 }
101}
102
104{
105 offset_ += 10;
106 // display update failed, set the offset back to what it was before
107 if(!update_display()) {
108 offset_ -= 10;
109 }
110}
111
112namespace
113{
114std::string key_with_fallback(const config::attribute_value& val)
115{
116 // Note that using the fallback arg of str() doesn't work since the value is set,
117 // it's just set as an empty string, and the fallback is only returned if the value
118 // is actually unset.
119 if(std::string s = val.str(); !s.empty()) {
120 return s;
121 } else {
123 }
124}
125};
126
128{
129 const config history = request_history();
130
131 // request failed, nothing to do
132 if(history.child_count("game_history_results") == 0) {
133 return false;
134 }
135
136 listbox* history_box = find_widget<listbox>("history", false, true);
137 history_box->clear();
138
139 listbox* tab_bar = find_widget<listbox>("tab_bar", false, true);
140 connect_signal_notify_modified(*tab_bar, std::bind(&mp_match_history::tab_switch_callback, this));
141 tab_bar->select_row(0);
142
143 int i = 0;
144 for(const config& game : history.mandatory_child("game_history_results").child_range("game_history_result")) {
145 widget_data row;
146 grid& history_grid = history_box->add_row(row);
147
148 dynamic_cast<label*>(history_grid.find("game_name", false))->set_label(key_with_fallback(game["game_name"]));
149 dynamic_cast<label*>(history_grid.find("scenario_name", false))->set_label(key_with_fallback(game["scenario_name"]));
150 dynamic_cast<label*>(history_grid.find("era_name", false))->set_label(markup::span_color("#baac7d", _("Era: ")) + key_with_fallback(game["era_name"]));
151 dynamic_cast<label*>(history_grid.find("game_start", false))->set_label(key_with_fallback(game["game_start"]) + _(" UTC+0"));
152 dynamic_cast<label*>(history_grid.find("version", false))->set_label(key_with_fallback(game["version"]));
153
154 button* replay_download = dynamic_cast<button*>(history_grid.find("replay_download", false));
155 std::string replay_url = game["replay_url"].str();
156 if(!replay_url.empty()) {
157 std::string filename = utils::split(replay_url, '/').back();
158 std::string local_save = filesystem::get_saves_dir()+"/"+filename;
159
160 connect_signal_mouse_left_click(*replay_download, std::bind(&network::download, replay_url, local_save));
161 } else {
162 replay_download->set_active(false);
163 }
164
165 std::vector<std::string> player_list;
166 std::vector<std::string> player_faction_list;
167 for(const config& player : game.child_range("player")) {
168 player_list.emplace_back(font::unicode_bullet + " " + player["name"].str() + ":");
169 player_faction_list.emplace_back(player["faction"].str());
170 }
171
172 label* players = dynamic_cast<label*>(history_grid.find("players", false));
173 players->set_label(utils::join(player_list, "\n"));
174
175 label* factions = dynamic_cast<label*>(history_grid.find("player_factions", false));
176 factions->set_label(utils::join(player_faction_list, "\n"));
177
178 history_grid.find("player_grid", false)->set_visible(gui2::widget::visibility::invisible);
179
180 label* modifications = dynamic_cast<label*>(history_grid.find("modifications", false));
181 const auto& children = game.child_range("modification");
182 if(!children.empty()) {
183 std::vector<std::string> modifications_list;
184
185 for(const config& modification : game.child_range("modification")) {
186 modifications_list.emplace_back(font::unicode_bullet + " " + modification["name"].str());
187 }
188
189 modifications->set_label(utils::join(modifications_list, "\n"));
190 }
192
193 i++;
194 if(i == 10) {
195 break;
196 }
197 }
198
199 // this is already the most recent history, can't get anything newer
200 if(offset_ == 0) {
201 button* newer_history = find_widget<button>("newer_history", false, true);
202 newer_history->set_active(false);
203 } else {
204 button* newer_history = find_widget<button>("newer_history", false, true);
205 newer_history->set_active(true);
206 }
207
208 // the server returns up to 11 and the client displays at most 10
209 // if fewer than 11 rows are returned, then there are no older rows left to get next
210 if(history.child_count("game_history_result") < 11) {
211 button* older_history = find_widget<button>("older_history", false, true);
212 older_history->set_active(false);
213 } else {
214 button* older_history = find_widget<button>("older_history", false, true);
215 older_history->set_active(true);
216 }
217
218 return true;
219}
220
222{
223 config request;
224 config& child = request.add_child("game_history_request");
225 child["offset"] = offset_;
226 child["search_player"] = player_name_;
227 child["search_game_name"] = find_widget<text_box>("search_game_name").get_value();
228 child["search_content_type"] = find_widget<menu_button>("search_content_type").get_value();
229 child["search_content"] = find_widget<text_box>("search_content").get_value();
230 DBG_NW << request.debug();
231 connection_.send_data(request);
232
233 int times_waited = 0;
234 while(true) {
235 config response;
236
237 // I'm not really sure why this works to be honest
238 // I would've expected that there would be a risk of regular lobby responses showing up here since it's a reference to the lobby's network connection
239 // however testing has resulted in showing that this is not the case
240 // lobby responses are received in the lobby's network_handler() method when this method is not running
241 // lobby responses are not received while this method is running, and are handled in the lobby after it completes
242 // history results are never received in the lobby
243 if(connection_.receive_data(response)) {
244 if(response.child_count("game_history_results") == 0) {
245 DBG_NW << "Received non-history data: " << response.debug();
246 if(!response["error"].str().empty()) {
247 ERR_NW << "Received error from server: " << response["error"].str();
248 gui2::show_error_message(_("The server responded with an error:")+" "+response["error"].str());
249 return {};
250 }
251 } else if(response.mandatory_child("game_history_results").child_count("game_history_result") == 0) {
252 DBG_NW << "Player has no game history data.";
253 gui2::show_error_message(_("No game history found."));
254 return {};
255 } else {
256 DBG_NW << "Received history data: " << response.debug();
257 return response;
258 }
259 } else {
260 DBG_NW << "Received no data";
261 }
262
263 if(times_waited > 20 || !wait_for_response_) {
264 ERR_NW << "Timed out waiting for history data, returning nothing";
266 gui2::show_error_message(_("Request timed out."));
267 }
268 return {};
269 }
270
271 times_waited++;
272 std::this_thread::sleep_for(250ms);
273 }
274
275 DBG_NW << "Something else happened while waiting for history data, returning nothing";
276 gui2::show_error_message(_("Request encountered an unexpected error, please check the logs."));
277 return {};
278}
279
281{
282 listbox* history_box = find_widget<listbox>("history", false, true);
283 listbox* tab_bar = find_widget<listbox>("tab_bar", false, true);
284 int tab = tab_bar->get_selected_row();
285
286 for(unsigned i = 0; i < history_box->get_item_count(); i++) {
287 grid* history_grid = history_box->get_row_grid(i);
288 if(tab == 0) {
289 history_grid->find("player_grid", false)->set_visible(gui2::widget::visibility::invisible);
290 history_grid->find("modifications", false)->set_visible(gui2::widget::visibility::invisible);
291 } else if(tab == 1) {
292 history_grid->find("player_grid", false)->set_visible(gui2::widget::visibility::visible);
293 history_grid->find("modifications", false)->set_visible(gui2::widget::visibility::invisible);
294 } else if(tab == 2) {
295 history_grid->find("player_grid", false)->set_visible(gui2::widget::visibility::invisible);
296 history_grid->find("modifications", false)->set_visible(gui2::widget::visibility::visible);
297 }
298 }
299}
300
301} // namespace dialogs
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
child_itors child_range(std::string_view key)
Definition config.cpp:268
std::string debug() const
Definition config.cpp:1214
std::size_t child_count(std::string_view key) const
Definition config.cpp:292
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition config.cpp:362
Simple push button.
Definition button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition button.cpp:64
Abstract base class for all modal dialogs.
const config request_history()
Requests game history from the server based on the offset.
int offset_
The offset to start retrieving history data at - should be increments of 10.
bool wait_for_response_
Whether to wait a few seconds for a response or not.
std::string player_name_
The username of the player whose history is being viewed.
void older_history_offset()
Decrements the offset to use for querying data by 10 and updates the information displayed by the dia...
void new_search()
Executes a new search for the entered username.
void tab_switch_callback()
Handles changing the selected horizontal listbox item for the specified game history row.
wesnothd_connection & connection_
A reference to the lobby's network connection to wesnothd.
void newer_history_offset()
Increments the offset to use for querying data by 10 and updates the information displayed by the dia...
bool update_display()
Updates the dialog with the information returned by the server.
virtual void pre_show() override
Actions to be taken before showing the window.
Base container class.
Definition grid.hpp:32
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
Definition grid.cpp:645
The listbox class.
Definition listbox.hpp:41
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition listbox.cpp:92
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition listbox.cpp:256
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition listbox.cpp:267
void clear()
Removes all the rows in the listbox, clearing it.
Definition listbox.cpp:147
int get_selected_row() const
Returns the first selected row.
Definition listbox.cpp:290
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition listbox.cpp:153
virtual void set_label(const t_string &text)
std::string get_value() const
virtual void set_value(const std::string &text)
The set_value is virtual for the password_box class.
A widget that allows the user to input text in single line.
Definition text_box.hpp:125
void set_visible(const visibility visible)
Definition widget.cpp:479
@ visible
The user sets the widget visible, that means:
@ invisible
The user set the widget invisible, that means:
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition window.hpp:313
A class that represents a TCP/IP connection to the wesnothd server.
bool receive_data(config &result)
Receives the next pending data pack from the server, if available.
void send_data(const configr_of &request)
Queues the given data to be sent to the server.
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition gettext.hpp:100
This file contains the window object, this object is a top level container which has the event manage...
static lg::log_domain log_network("network")
#define DBG_NW
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
#define ERR_NW
std::string get_saves_dir()
const std::string unicode_em_dash
Definition constants.cpp:44
const std::string unicode_bullet
Definition constants.cpp:47
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition message.cpp:201
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition markup.hpp:110
bool download(const std::string &url, const std::string &local_path)
STL namespace.
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
std::string filename
Filename.
static map_location::direction s
#define i