Merge branch 'master' of https://github.com/wesnoth/wesnoth
This commit is contained in:
commit
25c31ed720
17 changed files with 945 additions and 1016 deletions
|
@ -4,8 +4,8 @@ Version 1.13.5+dev:
|
|||
* Fixed broken village encounters.
|
||||
* Delfador's Memoirs:
|
||||
* S9: Resolved inability to end level even when Delfador has the Staff (bug #24951)
|
||||
* S17: Resolved Wesnoth units returning to recall list not being healed properly (bug #24952)
|
||||
* S19: Resolved undead veterans victory condition not working properly.
|
||||
* S17: Resolved Wesnoth units returning to recall list not being healed properly (bug #24952)
|
||||
* S19: Resolved undead veterans victory condition not working properly.
|
||||
* Music and sound effects:
|
||||
* Added a preference to pause the music when the game loses focus.
|
||||
* Now the music fades out between scenarios.
|
||||
|
@ -65,6 +65,7 @@ Version 1.13.5+dev:
|
|||
multiple network and local players was ran.
|
||||
* Added a tab to run the wmlxgettext tool to GUI.pyw
|
||||
* Fixed problem with Spectre's hitpoint bar positioning.
|
||||
* Show correct number of attacks in case of swarm weapon special (bug #24978)
|
||||
|
||||
Version 1.13.5:
|
||||
* Campaigns:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#textdomain wesnoth-lib
|
||||
|
||||
#define _GUI_RESOLUTION RESOLUTION FONT_SIZE FONT_STYLE FONT_COLOR_ENABLED FONT_COLOR_DISABLED
|
||||
#define _GUI_RESOLUTION RESOLUTION FONT_SIZE DEFINITION FONT_STYLE FONT_COLOR_ENABLED FONT_COLOR_DISABLED
|
||||
[resolution]
|
||||
|
||||
{RESOLUTION}
|
||||
|
@ -64,7 +64,7 @@
|
|||
|
||||
[label]
|
||||
id = "_label"
|
||||
definition = "scroll_label"
|
||||
definition = {DEFINITION}
|
||||
[/label]
|
||||
|
||||
[/column]
|
||||
|
@ -112,6 +112,7 @@
|
|||
{_GUI_RESOLUTION
|
||||
({GUI_NORMAL__RESOLUTION})
|
||||
({GUI_NORMAL__FONT_SIZE__DEFAULT})
|
||||
("scroll_label")
|
||||
()
|
||||
({GUI__FONT_COLOR_ENABLED__DEFAULT})
|
||||
({GUI__FONT_COLOR_DISABLED__DEFAULT})
|
||||
|
@ -126,6 +127,7 @@
|
|||
{_GUI_RESOLUTION
|
||||
({GUI_NORMAL__RESOLUTION})
|
||||
({GUI_NORMAL__FONT_SIZE__SMALL})
|
||||
("scroll_label_small")
|
||||
()
|
||||
({GUI__FONT_COLOR_ENABLED__DEFAULT})
|
||||
({GUI__FONT_COLOR_DISABLED__DEFAULT})
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
|
||||
#define _GUI_ICON SIZE
|
||||
[image]
|
||||
x = 0
|
||||
y = 0
|
||||
w = {SIZE}
|
||||
h = {SIZE}
|
||||
x = "((size / 2) - (image_width / 2) where size = {SIZE})"
|
||||
y = "((size / 2) - (image_height / 2) where size = {SIZE})"
|
||||
name = "(icon)"
|
||||
[/image]
|
||||
#enddef
|
||||
|
|
|
@ -138,6 +138,8 @@
|
|||
[column]
|
||||
horizontal_alignment = "left"
|
||||
vertical_alignment = "top"
|
||||
border = "top,bottom"
|
||||
border_size = 1
|
||||
[label]
|
||||
definition = "default_small"
|
||||
id = "name"
|
||||
|
@ -155,6 +157,8 @@
|
|||
[column]
|
||||
horizontal_alignment = "left"
|
||||
vertical_alignment = "top"
|
||||
border = "top,bottom"
|
||||
border_size = 1
|
||||
[label]
|
||||
wrap = "true"
|
||||
definition = "default_small"
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -64,6 +64,8 @@
|
|||
[/column]
|
||||
[column]
|
||||
horizontal_alignment = "right"
|
||||
border = "all"
|
||||
border_size = 5
|
||||
[button]
|
||||
definition = "default"
|
||||
id = "start_whisper"
|
||||
|
@ -87,6 +89,7 @@
|
|||
[/row]
|
||||
[row]
|
||||
[column]
|
||||
horizontal_grow = "true"
|
||||
[grid]
|
||||
[row]
|
||||
[column]
|
||||
|
@ -95,7 +98,7 @@
|
|||
[button]
|
||||
definition = "default"
|
||||
id = "add_to_friends"
|
||||
label = _ "Add to Friends"
|
||||
label = _ "Add Friend"
|
||||
[/button]
|
||||
[/column]
|
||||
[column]
|
||||
|
@ -104,7 +107,7 @@
|
|||
[button]
|
||||
definition = "default"
|
||||
id = "add_to_ignores"
|
||||
label = _ "Add to Ignores"
|
||||
label = _ "Block"
|
||||
[/button]
|
||||
[/column]
|
||||
[column]
|
||||
|
@ -193,6 +196,9 @@
|
|||
[/row]
|
||||
[row]
|
||||
[column]
|
||||
horizontal_alignment = "right"
|
||||
border = "all"
|
||||
border_size = 5
|
||||
[button]
|
||||
definition = "default"
|
||||
id = "cancel"
|
||||
|
|
|
@ -86,6 +86,12 @@
|
|||
[/row]
|
||||
[/grid]
|
||||
[/column]
|
||||
|
||||
[column]
|
||||
[spacer]
|
||||
width = 25
|
||||
[/spacer]
|
||||
[/column]
|
||||
[/row]
|
||||
[/grid]
|
||||
[/toggle_panel]
|
||||
|
|
|
@ -11,7 +11,7 @@ Version 1.13.5+dev:
|
|||
* Now the music fades out between scenarios.
|
||||
* Units:
|
||||
* Changed the sound for the melee attack of the
|
||||
Loyalist Bowman, Orcish Crossbowman and Orcish Slurbow.
|
||||
Loyalist Bowman, Orcish Crossbowman and Orcish Slurbow.
|
||||
* Performance:
|
||||
* When a heuristic determines that it's probably faster, the game predicts battle
|
||||
outcome by simulating a few thousand fights instead of calculating exact
|
||||
|
@ -20,6 +20,7 @@ Version 1.13.5+dev:
|
|||
damage calculation method.
|
||||
* Miscellaneous and bug fixes:
|
||||
* Fixed a stray ; character appearing pre-entered in the command console.
|
||||
* Show correct number of attacks in case of swarm weapon special (bug #24978)
|
||||
|
||||
Version 1.13.5:
|
||||
* Campaigns:
|
||||
|
|
|
@ -11,19 +11,11 @@
|
|||
|
||||
namespace ng {
|
||||
|
||||
configure_engine::configure_engine(saved_game& state) :
|
||||
state_(state),
|
||||
parameters_(state_.mp_settings()),
|
||||
side_cfg_(state_.get_starting_pos().child_or_empty("side"))
|
||||
configure_engine::configure_engine(saved_game& state, const config* intial)
|
||||
: state_(state)
|
||||
, parameters_(state_.mp_settings())
|
||||
, initial_(intial ? intial : &state_.get_starting_pos())
|
||||
{
|
||||
if (!state_.get_starting_pos().has_child("side")) {
|
||||
std::stringstream msg;
|
||||
msg << "Configure Engine: No sides found in scenario, aborting.";
|
||||
std::cerr << msg.str();
|
||||
std::cerr << "Full scenario config:\n";
|
||||
std::cerr << state_.to_config().debug();
|
||||
throw game::error(msg.str());
|
||||
}
|
||||
|
||||
set_use_map_settings(use_map_settings_default());
|
||||
if(state_.classification().get_tagname() == "scenario") {
|
||||
|
@ -42,11 +34,6 @@ configure_engine::configure_engine(saved_game& state) :
|
|||
}
|
||||
}
|
||||
|
||||
void configure_engine::update_side_cfg()
|
||||
{
|
||||
side_cfg_ = state_.get_starting_pos().child_or_empty("side");
|
||||
}
|
||||
|
||||
void configure_engine::set_default_values() {
|
||||
set_use_map_settings(use_map_settings_default());
|
||||
set_game_name(game_name_default());
|
||||
|
@ -65,7 +52,7 @@ void configure_engine::set_default_values() {
|
|||
}
|
||||
|
||||
bool configure_engine::force_lock_settings() const {
|
||||
return state_.get_starting_pos()["force_lock_settings"].to_bool(!state_.classification().is_normal_mp_game());
|
||||
return initial_cfg()["force_lock_settings"].to_bool(!state_.classification().is_normal_mp_game());
|
||||
}
|
||||
|
||||
std::string configure_engine::game_name() const { return parameters_.name; }
|
||||
|
@ -133,22 +120,22 @@ std::string configure_engine::game_name_default() const {
|
|||
}
|
||||
int configure_engine::num_turns_default() const {
|
||||
return use_map_settings() ?
|
||||
settings::get_turns(state_.get_starting_pos()["turns"]) :
|
||||
settings::get_turns(initial_cfg()["turns"]) :
|
||||
preferences::turns();
|
||||
}
|
||||
int configure_engine::village_gold_default() const {
|
||||
return use_map_settings() && !side_cfg_.empty() ?
|
||||
settings::get_village_gold(side_cfg_["village_gold"], &state_.classification()) :
|
||||
return use_map_settings() && !side_cfg().empty() ?
|
||||
settings::get_village_gold(side_cfg()["village_gold"], &state_.classification()) :
|
||||
preferences::village_gold();
|
||||
}
|
||||
int configure_engine::village_support_default() const {
|
||||
return use_map_settings() && !side_cfg_.empty() ?
|
||||
settings::get_village_support(side_cfg_["village_support"]) :
|
||||
return use_map_settings() && !side_cfg().empty() ?
|
||||
settings::get_village_support(side_cfg()["village_support"]) :
|
||||
preferences::village_support();
|
||||
}
|
||||
int configure_engine::xp_modifier_default() const {
|
||||
return use_map_settings() ?
|
||||
settings::get_xp_modifier(state_.get_starting_pos()["experience_modifier"]) :
|
||||
settings::get_xp_modifier(initial_cfg()["experience_modifier"]) :
|
||||
preferences::xp_modifier();
|
||||
}
|
||||
int configure_engine::mp_countdown_init_time_default() const {
|
||||
|
@ -171,17 +158,17 @@ bool configure_engine::use_map_settings_default() const {
|
|||
}
|
||||
bool configure_engine::random_start_time_default() const {
|
||||
return use_map_settings() ?
|
||||
state_.get_starting_pos()["random_start_time"].to_bool(false) :
|
||||
initial_cfg()["random_start_time"].to_bool(false) :
|
||||
preferences::random_start_time();
|
||||
}
|
||||
bool configure_engine::fog_game_default() const {
|
||||
return use_map_settings() && !side_cfg_.empty() ?
|
||||
side_cfg_["fog"].to_bool(state_.classification().is_normal_mp_game()) :
|
||||
return use_map_settings() && !side_cfg().empty() ?
|
||||
side_cfg()["fog"].to_bool(state_.classification().is_normal_mp_game()) :
|
||||
preferences::fog();
|
||||
}
|
||||
bool configure_engine::shroud_game_default() const {
|
||||
return use_map_settings() && !side_cfg_.empty() ?
|
||||
side_cfg_["shroud"].to_bool(false) :
|
||||
return use_map_settings() && !side_cfg().empty() ?
|
||||
side_cfg()["shroud"].to_bool(false) :
|
||||
preferences::shroud();
|
||||
}
|
||||
bool configure_engine::allow_observers_default() const {
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace ng {
|
|||
class configure_engine
|
||||
{
|
||||
public:
|
||||
configure_engine(saved_game& state);
|
||||
configure_engine(saved_game& state, const config* intial = nullptr);
|
||||
|
||||
// Set all parameters to their default values
|
||||
void set_default_values();
|
||||
|
@ -112,10 +112,15 @@ public:
|
|||
const std::vector<std::string>& entry_point_titles() const;
|
||||
void write_parameters();
|
||||
|
||||
void update_side_cfg();
|
||||
void update_initial_cfg(const config& cfg)
|
||||
{
|
||||
initial_ = &cfg;
|
||||
}
|
||||
private:
|
||||
saved_game& state_;
|
||||
mp_game_settings& parameters_;
|
||||
/// Never nullptr.
|
||||
const config* initial_;
|
||||
// village gold, village support, fog, and shroud are per player, always show values of player 1.
|
||||
/**
|
||||
* @todo This might not be 100% correct, but at the moment
|
||||
|
@ -123,8 +128,15 @@ private:
|
|||
* This might change in the future.
|
||||
* NOTE when 'load game' is selected there are no sides.
|
||||
*/
|
||||
config side_cfg_;
|
||||
|
||||
const config& side_cfg() const
|
||||
{
|
||||
return initial_->child_or_empty("side");
|
||||
}
|
||||
const config& initial_cfg() const
|
||||
{
|
||||
return *initial_;
|
||||
}
|
||||
|
||||
std::vector<const config*> entry_points_;
|
||||
|
||||
std::vector<std::string> entry_point_titles_;
|
||||
|
|
|
@ -524,17 +524,18 @@ static void enter_create_mode(CVideo& video, const config& game_config,
|
|||
bool configure_canceled;
|
||||
bool connect_canceled;
|
||||
|
||||
if(gui2::new_widgets) {
|
||||
if(preferences::new_lobby()) {
|
||||
ng::create_engine create_eng(video, state);
|
||||
ng::configure_engine config_eng(state);
|
||||
|
||||
gui2::tmp_create_game dlg(game_config, create_eng, config_eng);
|
||||
gui2::tmp_create_game dlg(game_config, create_eng);
|
||||
|
||||
dlg.show(video);
|
||||
|
||||
if(wesnothd_connection) {
|
||||
wesnothd_connection->send_data(config("refresh_lobby"));
|
||||
}
|
||||
enter_connect_mode(video, game_config, state, wesnothd_connection, local_players_only);
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
|
@ -661,7 +662,7 @@ static void enter_lobby_mode(CVideo& video, const config& game_config,
|
|||
sdl::fill_rect(video.getSurface(), nullptr, color);
|
||||
|
||||
if(preferences::new_lobby()) {
|
||||
gui2::tlobby_main dlg(game_config, li, video, *wesnothd_connection);
|
||||
gui2::tlobby_main dlg(game_config, li, *wesnothd_connection);
|
||||
dlg.set_preferences_callback(
|
||||
std::bind(do_preferences_dialog,
|
||||
std::ref(video), std::ref(game_config)));
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
*/
|
||||
#define GETTEXT_DOMAIN "wesnoth-lib"
|
||||
|
||||
#include "gui/auxiliary/field.hpp"
|
||||
#include "gui/dialogs/lobby/lobby.hpp"
|
||||
|
||||
#include "gui/auxiliary/field.hpp"
|
||||
#include "gui/dialogs/lobby/player_info.hpp"
|
||||
#include "gui/dialogs/multiplayer/mp_join_game_password_prompt.hpp"
|
||||
#include "gui/dialogs/helper.hpp"
|
||||
|
@ -38,14 +39,15 @@
|
|||
#include "gui/widgets/toggle_panel.hpp"
|
||||
#include "gui/widgets/tree_view_node.hpp"
|
||||
|
||||
#include "formatter.hpp"
|
||||
#include "formula/string_utils.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "lobby_preferences.hpp"
|
||||
#include "log.hpp"
|
||||
#include "wesnothd_connection.hpp"
|
||||
#include "playmp_controller.hpp"
|
||||
#include "mp_ui_alerts.hpp"
|
||||
#include "playmp_controller.hpp"
|
||||
#include "wesnothd_connection.hpp"
|
||||
|
||||
#include "utils/functional.hpp"
|
||||
|
||||
|
@ -67,102 +69,65 @@ static lg::log_domain log_lobby("lobby");
|
|||
#define ERR_LB LOG_STREAM(err, log_lobby)
|
||||
#define SCOPE_LB log_scope2(log_lobby, __func__)
|
||||
|
||||
|
||||
namespace gui2
|
||||
{
|
||||
|
||||
REGISTER_DIALOG(lobby_main)
|
||||
|
||||
void tsub_player_list::init(gui2::twindow& w, const std::string& id)
|
||||
void tsub_player_list::init(twindow& w, const std::string& label, const bool unfolded)
|
||||
{
|
||||
list = find_widget<tlistbox>(&w, id, false, true);
|
||||
show_toggle
|
||||
= find_widget<ttoggle_button>(&w, id + "_show_toggle", false, true);
|
||||
show_toggle->set_icon_name("lobby/group-expanded.png");
|
||||
show_toggle->set_callback_state_change(
|
||||
std::bind(&tsub_player_list::show_toggle_callback, this, _1));
|
||||
count = find_widget<tlabel>(&w, id + "_count", false, true);
|
||||
label = find_widget<tlabel>(&w, id + "_label", false, true);
|
||||
|
||||
ttree_view& parent_tree = find_widget<ttree_view>(&w, "player_tree", false);
|
||||
|
||||
string_map tree_group_field;
|
||||
std::map<std::string, string_map> tree_group_item;
|
||||
tree_group_item["tree_view_node_label"]["label"] = label;
|
||||
|
||||
tree_group_field["label"] = id;
|
||||
tree_group_item["tree_view_node_label"] = tree_group_field;
|
||||
tree = &parent_tree.add_node("player_group", tree_group_item);
|
||||
|
||||
tree_label = find_widget<tlabel>(tree, "tree_view_node_label", false, true);
|
||||
|
||||
tree_label->set_label(label->label());
|
||||
}
|
||||
|
||||
void tsub_player_list::show_toggle_callback(gui2::twidget& /*widget*/)
|
||||
{
|
||||
if(show_toggle->get_value()) {
|
||||
list->set_visible(twidget::tvisible::invisible);
|
||||
show_toggle->set_icon_name("lobby/group-folded.png");
|
||||
} else {
|
||||
list->set_visible(twidget::tvisible::visible);
|
||||
show_toggle->set_icon_name("lobby/group-expanded.png");
|
||||
if(unfolded) {
|
||||
tree->unfold();
|
||||
}
|
||||
|
||||
tree_label = find_widget<tlabel>(tree, "tree_view_node_label", false, true);
|
||||
label_player_count = find_widget<tlabel>(tree, "player_count", false, true);
|
||||
|
||||
assert(tree_label);
|
||||
assert(label_player_count);
|
||||
}
|
||||
|
||||
void tsub_player_list::auto_hide()
|
||||
void tsub_player_list::update_player_count_label()
|
||||
{
|
||||
assert(tree);
|
||||
assert(tree_label);
|
||||
if(tree->empty()) {
|
||||
/**
|
||||
* @todo Make sure setting visible resizes the widget.
|
||||
*
|
||||
* It doesn't work here since invalidate_layout is blocked, but the
|
||||
* widget should also be able to handle it itself. Once done the
|
||||
* setting of the label text can also be removed.
|
||||
*/
|
||||
assert(label);
|
||||
tree_label->set_label(label->label() + " (0)");
|
||||
// tree_label->set_visible(twidget::tvisible::invisible);
|
||||
} else {
|
||||
assert(label);
|
||||
std::stringstream ss;
|
||||
ss << label->label() << " (" << tree->size() << ")";
|
||||
tree_label->set_label(ss.str());
|
||||
// tree_label->set_visible(twidget::tvisible::visible);
|
||||
}
|
||||
assert(label_player_count);
|
||||
|
||||
/**
|
||||
* @todo Make sure setting visible resizes the widget.
|
||||
*
|
||||
* It doesn't work here since invalidate_layout is blocked, but the
|
||||
* widget should also be able to handle it itself. Once done the
|
||||
* setting of the label text can also be removed.
|
||||
*/
|
||||
label_player_count->set_label((formatter() << "(" << tree->size() << ")").str());
|
||||
}
|
||||
|
||||
void tplayer_list::init(gui2::twindow& w)
|
||||
void tplayer_list::init(twindow& w)
|
||||
{
|
||||
active_game.init(w, "active_game");
|
||||
active_room.init(w, "active_room");
|
||||
other_rooms.init(w, "other_rooms");
|
||||
other_games.init(w, "other_games");
|
||||
sort_by_name = find_widget<ttoggle_button>(
|
||||
&w, "player_list_sort_name", false, true);
|
||||
sort_by_relation = find_widget<ttoggle_button>(
|
||||
&w, "player_list_sort_relation", false, true);
|
||||
active_game.init(w, _("Selected Game"));
|
||||
active_room.init(w, _("Current Room"));
|
||||
other_rooms.init(w, _("Lobby"), true);
|
||||
other_games.init(w, _("Other Games"));
|
||||
|
||||
sort_by_name = find_widget<ttoggle_button>(&w, "player_list_sort_name", false, true);
|
||||
sort_by_relation = find_widget<ttoggle_button>(&w, "player_list_sort_relation", false, true);
|
||||
|
||||
tree = find_widget<ttree_view>(&w, "player_tree", false, true);
|
||||
|
||||
find_widget<twidget>(&w, "old_player_list", false)
|
||||
.set_visible(twidget::tvisible::invisible);
|
||||
}
|
||||
|
||||
void tplayer_list::update_sort_icons()
|
||||
{
|
||||
if(sort_by_name->get_value()) {
|
||||
sort_by_name->set_icon_name("lobby/sort-az.png");
|
||||
} else {
|
||||
sort_by_name->set_icon_name("lobby/sort-az-off.png");
|
||||
}
|
||||
if(sort_by_relation->get_value()) {
|
||||
sort_by_relation->set_icon_name("lobby/sort-friend.png");
|
||||
} else {
|
||||
sort_by_relation->set_icon_name("lobby/sort-friend-off.png");
|
||||
}
|
||||
sort_by_name->set_icon_name(sort_by_name->get_value() ? "lobby/sort-az.png" : "lobby/sort-az-off.png");
|
||||
sort_by_relation->set_icon_name(sort_by_relation->get_value() ? "lobby/sort-friend.png" : "lobby/sort-friend-off.png");
|
||||
}
|
||||
|
||||
void tlobby_main::send_chat_message(const std::string& message,
|
||||
bool /*allies_only*/)
|
||||
{
|
||||
|
@ -310,39 +275,38 @@ void tlobby_main::append_to_chatbox(const std::string& text, size_t id, const bo
|
|||
|
||||
void tlobby_main::do_notify(t_notify_mode mode, const std::string & sender, const std::string & message)
|
||||
{
|
||||
switch(mode) {
|
||||
case NOTIFY_WHISPER:
|
||||
case NOTIFY_WHISPER_OTHER_WINDOW:
|
||||
case NOTIFY_OWN_NICK:
|
||||
mp_ui_alerts::private_message(true, sender, message);
|
||||
break;
|
||||
case NOTIFY_FRIEND_MESSAGE:
|
||||
mp_ui_alerts::friend_message(true, sender, message);
|
||||
break;
|
||||
case NOTIFY_SERVER_MESSAGE:
|
||||
mp_ui_alerts::server_message(true, sender, message);
|
||||
break;
|
||||
case NOTIFY_LOBBY_QUIT:
|
||||
mp_ui_alerts::player_leaves(true);
|
||||
break;
|
||||
case NOTIFY_LOBBY_JOIN:
|
||||
mp_ui_alerts::player_joins(true);
|
||||
break;
|
||||
case NOTIFY_MESSAGE:
|
||||
mp_ui_alerts::public_message(true, sender, message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch(mode) {
|
||||
case NOTIFY_WHISPER:
|
||||
case NOTIFY_WHISPER_OTHER_WINDOW:
|
||||
case NOTIFY_OWN_NICK:
|
||||
mp_ui_alerts::private_message(true, sender, message);
|
||||
break;
|
||||
case NOTIFY_FRIEND_MESSAGE:
|
||||
mp_ui_alerts::friend_message(true, sender, message);
|
||||
break;
|
||||
case NOTIFY_SERVER_MESSAGE:
|
||||
mp_ui_alerts::server_message(true, sender, message);
|
||||
break;
|
||||
case NOTIFY_LOBBY_QUIT:
|
||||
mp_ui_alerts::player_leaves(true);
|
||||
break;
|
||||
case NOTIFY_LOBBY_JOIN:
|
||||
mp_ui_alerts::player_joins(true);
|
||||
break;
|
||||
case NOTIFY_MESSAGE:
|
||||
mp_ui_alerts::public_message(true, sender, message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tlobby_main::tlobby_main(const config& game_config,
|
||||
lobby_info& info,
|
||||
CVideo& video, twesnothd_connection &wesnothd_connection)
|
||||
twesnothd_connection &wesnothd_connection)
|
||||
: legacy_result_(QUIT)
|
||||
, game_config_(game_config)
|
||||
, gamelistbox_(nullptr)
|
||||
, userlistbox_(nullptr)
|
||||
, roomlistbox_(nullptr)
|
||||
, chat_log_container_(nullptr)
|
||||
, chat_input_(nullptr)
|
||||
|
@ -362,7 +326,6 @@ tlobby_main::tlobby_main(const config& game_config,
|
|||
, gamelist_dirty_(false)
|
||||
, last_gamelist_update_(0)
|
||||
, gamelist_diff_update_(true)
|
||||
, video_(video)
|
||||
, wesnothd_connection_(wesnothd_connection)
|
||||
, lobby_update_timer_(0)
|
||||
, preferences_wrapper_()
|
||||
|
@ -401,7 +364,6 @@ static bool fullscreen(CVideo& video)
|
|||
{
|
||||
video.set_fullscreen(!preferences::fullscreen());
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -449,18 +411,21 @@ void add_tooltip_data(std::map<std::string, string_map>& map,
|
|||
void modify_grid_with_data(tgrid* grid,
|
||||
const std::map<std::string, string_map>& map)
|
||||
{
|
||||
for(const auto & v : map)
|
||||
{
|
||||
for(const auto & v : map) {
|
||||
const std::string& key = v.first;
|
||||
const string_map& strmap = v.second;
|
||||
|
||||
twidget* w = grid->find(key, false);
|
||||
if(w == nullptr)
|
||||
if(w == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tcontrol* c = dynamic_cast<tcontrol*>(w);
|
||||
if(c == nullptr)
|
||||
if(c == nullptr) {
|
||||
continue;
|
||||
for(const auto & vv : strmap)
|
||||
{
|
||||
}
|
||||
|
||||
for(const auto & vv : strmap) {
|
||||
if(vv.first == "label") {
|
||||
c->set_label(vv.second);
|
||||
} else if(vv.first == "tooltip") {
|
||||
|
@ -472,10 +437,8 @@ void modify_grid_with_data(tgrid* grid,
|
|||
|
||||
void set_visible_if_exists(tgrid* grid, const char* id, bool visible)
|
||||
{
|
||||
twidget* w = grid->find(id, false);
|
||||
if(w) {
|
||||
w->set_visible(visible ? twidget::tvisible::visible
|
||||
: twidget::tvisible::invisible);
|
||||
if(twidget* w = grid->find(id, false)) {
|
||||
w->set_visible(visible ? twidget::tvisible::visible : twidget::tvisible::invisible);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -484,11 +447,6 @@ std::string colorize(const std::string& str, const std::string& color)
|
|||
return "<span color=\"" + color + "\">" + str + "</span>";
|
||||
}
|
||||
|
||||
std::string tag(const std::string& str, const std::string& tag)
|
||||
{
|
||||
return "<" + tag + ">" + str + "</" + tag + ">";
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
void tlobby_main::update_gamelist()
|
||||
|
@ -497,23 +455,27 @@ void tlobby_main::update_gamelist()
|
|||
gamelistbox_->clear();
|
||||
gamelist_id_at_row_.clear();
|
||||
lobby_info_.make_games_vector();
|
||||
|
||||
int select_row = -1;
|
||||
for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
|
||||
const game_info& game = *lobby_info_.games()[i];
|
||||
|
||||
if(game.id == selected_game_id_) {
|
||||
select_row = i;
|
||||
}
|
||||
|
||||
gamelist_id_at_row_.push_back(game.id);
|
||||
LOG_LB << "Adding game to listbox (1)" << game.id << "\n";
|
||||
gamelistbox_->add_row(make_game_row_data(game));
|
||||
tgrid* grid = gamelistbox_->get_row_grid(gamelistbox_->get_item_count()
|
||||
- 1);
|
||||
adjust_game_row_contents(
|
||||
game, gamelistbox_->get_item_count() - 1, grid);
|
||||
|
||||
tgrid* grid = gamelistbox_->get_row_grid(gamelistbox_->get_item_count() - 1);
|
||||
adjust_game_row_contents(game, gamelistbox_->get_item_count() - 1, grid);
|
||||
}
|
||||
|
||||
if(select_row >= 0 && select_row != gamelistbox_->get_selected_row()) {
|
||||
gamelistbox_->select_row(select_row);
|
||||
}
|
||||
|
||||
update_selected_game();
|
||||
gamelist_dirty_ = false;
|
||||
last_gamelist_update_ = SDL_GetTicks();
|
||||
|
@ -530,11 +492,14 @@ void tlobby_main::update_gamelist_diff()
|
|||
int select_row = -1;
|
||||
unsigned list_i = 0;
|
||||
int list_rows_deleted = 0;
|
||||
|
||||
std::vector<int> next_gamelist_id_at_row;
|
||||
for(unsigned i = 0; i < lobby_info_.games().size(); ++i) {
|
||||
const game_info& game = *lobby_info_.games()[i];
|
||||
|
||||
if(game.display_status == game_info::NEW) {
|
||||
LOG_LB << "Adding game to listbox " << game.id << "\n";
|
||||
|
||||
if(list_i != gamelistbox_->get_item_count()) {
|
||||
gamelistbox_->add_row(make_game_row_data(game), list_i);
|
||||
DBG_LB << "Added a game listbox row not at the end" << list_i
|
||||
|
@ -543,10 +508,10 @@ void tlobby_main::update_gamelist_diff()
|
|||
} else {
|
||||
gamelistbox_->add_row(make_game_row_data(game));
|
||||
}
|
||||
tgrid* grid = gamelistbox_->get_row_grid(
|
||||
gamelistbox_->get_item_count() - 1);
|
||||
adjust_game_row_contents(
|
||||
game, gamelistbox_->get_item_count() - 1, grid);
|
||||
|
||||
tgrid* grid = gamelistbox_->get_row_grid(gamelistbox_->get_item_count() - 1);
|
||||
adjust_game_row_contents(game, gamelistbox_->get_item_count() - 1, grid);
|
||||
|
||||
list_i++;
|
||||
next_gamelist_id_at_row.push_back(game.id);
|
||||
} else {
|
||||
|
@ -556,6 +521,7 @@ void tlobby_main::update_gamelist_diff()
|
|||
wesnothd_connection_.send_data(config("refresh_lobby"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(list_i + list_rows_deleted >= gamelist_id_at_row_.size()) {
|
||||
ERR_LB << "gamelist_id_at_row_ overflow! " << list_i << " + "
|
||||
<< list_rows_deleted
|
||||
|
@ -564,8 +530,8 @@ void tlobby_main::update_gamelist_diff()
|
|||
wesnothd_connection_.send_data(config("refresh_lobby"));
|
||||
return;
|
||||
}
|
||||
int listbox_game_id
|
||||
= gamelist_id_at_row_[list_i + list_rows_deleted];
|
||||
|
||||
int listbox_game_id = gamelist_id_at_row_[list_i + list_rows_deleted];
|
||||
if(game.id != listbox_game_id) {
|
||||
ERR_LB << "Listbox game id does not match expected id "
|
||||
<< listbox_game_id << " " << game.id << " (row "
|
||||
|
@ -573,6 +539,7 @@ void tlobby_main::update_gamelist_diff()
|
|||
wesnothd_connection_.send_data(config("refresh_lobby"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(game.display_status == game_info::UPDATED) {
|
||||
LOG_LB << "Modifying game in listbox " << game.id << " (row "
|
||||
<< list_i << ")\n";
|
||||
|
@ -595,20 +562,24 @@ void tlobby_main::update_gamelist_diff()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < next_gamelist_id_at_row.size(); ++i) {
|
||||
if(next_gamelist_id_at_row[i] == selected_game_id_) {
|
||||
select_row = i;
|
||||
}
|
||||
}
|
||||
|
||||
next_gamelist_id_at_row.swap(gamelist_id_at_row_);
|
||||
if(select_row >= static_cast<int>(gamelistbox_->get_item_count())) {
|
||||
ERR_LB << "Would select a row beyond the listbox" << select_row << " "
|
||||
<< gamelistbox_->get_item_count() << "\n";
|
||||
select_row = gamelistbox_->get_item_count() - 1;
|
||||
}
|
||||
|
||||
if(select_row >= 0 && select_row != gamelistbox_->get_selected_row()) {
|
||||
gamelistbox_->select_row(select_row);
|
||||
}
|
||||
|
||||
update_selected_game();
|
||||
gamelist_dirty_ = false;
|
||||
last_gamelist_update_ = SDL_GetTicks();
|
||||
|
@ -632,43 +603,36 @@ void tlobby_main::update_gamelist_header()
|
|||
#endif
|
||||
}
|
||||
|
||||
std::map<std::string, string_map>
|
||||
tlobby_main::make_game_row_data(const game_info& game)
|
||||
std::map<std::string, string_map> tlobby_main::make_game_row_data(const game_info& game)
|
||||
{
|
||||
std::map<std::string, string_map> data;
|
||||
|
||||
const char* color_string;
|
||||
if(game.vacant_slots > 0) {
|
||||
if(game.reloaded || game.started) {
|
||||
color_string = "yellow";
|
||||
} else {
|
||||
color_string = "green";
|
||||
}
|
||||
color_string = (game.reloaded || game.started) ? "yellow" : "green";
|
||||
} else {
|
||||
if(game.observers) {
|
||||
color_string = "#ddd";
|
||||
} else {
|
||||
color_string = "red";
|
||||
}
|
||||
color_string = game.observers ? "#ddd" : "red";
|
||||
}
|
||||
|
||||
if(!game.have_era && (game.vacant_slots > 0 || game.observers)) {
|
||||
color_string = "#444";
|
||||
}
|
||||
|
||||
add_label_data(data, "status", colorize(game.status, color_string));
|
||||
add_label_data(data, "name", colorize(game.name, color_string));
|
||||
add_label_data(data, "name", colorize(game.name, color_string));
|
||||
|
||||
add_label_data(data, "era", game.era);
|
||||
add_label_data(data, "era_short", game.era_short);
|
||||
add_label_data(data, "map_info", game.map_info);
|
||||
add_label_data(data, "scenario", game.scenario);
|
||||
add_label_data(data, "map_size_text", game.map_size_info);
|
||||
add_label_data(data, "time_limit", game.time_limit);
|
||||
add_label_data(data, "gold_text", game.gold);
|
||||
add_label_data(data, "xp_text", game.xp);
|
||||
add_label_data(data, "vision_text", game.vision);
|
||||
add_label_data(data, "era", game.era);
|
||||
add_label_data(data, "era_short", game.era_short);
|
||||
add_label_data(data, "map_info", game.map_info);
|
||||
add_label_data(data, "scenario", game.scenario);
|
||||
add_label_data(data, "map_size_text", game.map_size_info);
|
||||
add_label_data(data, "time_limit", game.time_limit);
|
||||
add_label_data(data, "gold_text", game.gold);
|
||||
add_label_data(data, "xp_text", game.xp);
|
||||
add_label_data(data, "vision_text", game.vision);
|
||||
add_label_data(data, "time_limit_text", game.time_limit);
|
||||
add_label_data(data, "status", game.status);
|
||||
add_label_data(data, "status", game.status);
|
||||
|
||||
if(game.observers) {
|
||||
add_label_data(data, "observer_icon", "misc/eye.png");
|
||||
add_tooltip_data(data, "observer_icon", _("Observers allowed"));
|
||||
|
@ -679,18 +643,11 @@ tlobby_main::make_game_row_data(const game_info& game)
|
|||
|
||||
const char* vision_icon;
|
||||
if(game.fog) {
|
||||
if(game.shroud) {
|
||||
vision_icon = "misc/vision-fog-shroud.png";
|
||||
} else {
|
||||
vision_icon = "misc/vision-fog.png";
|
||||
}
|
||||
vision_icon = game.shroud ? "misc/vision-fog-shroud.png" : "misc/vision-fog.png";
|
||||
} else {
|
||||
if(game.shroud) {
|
||||
vision_icon = "misc/vision-shroud.png";
|
||||
} else {
|
||||
vision_icon = "misc/vision-none.png";
|
||||
}
|
||||
vision_icon = game.shroud ? "misc/vision-shroud.png" : "misc/vision-none.png";
|
||||
}
|
||||
|
||||
add_label_data(data, "vision_icon", vision_icon);
|
||||
add_tooltip_data(data, "vision_icon", game.vision);
|
||||
return data;
|
||||
|
@ -701,7 +658,6 @@ void tlobby_main::adjust_game_row_contents(const game_info& game,
|
|||
tgrid* grid)
|
||||
{
|
||||
find_widget<tcontrol>(grid, "name", false).set_use_markup(true);
|
||||
|
||||
find_widget<tcontrol>(grid, "status", false).set_use_markup(true);
|
||||
|
||||
ttoggle_panel& row_panel = find_widget<ttoggle_panel>(grid, "panel", false);
|
||||
|
@ -709,41 +665,38 @@ void tlobby_main::adjust_game_row_contents(const game_info& game,
|
|||
row_panel.set_callback_mouse_left_double_click(
|
||||
std::bind(&tlobby_main::join_or_observe, this, idx));
|
||||
|
||||
set_visible_if_exists(grid, "time_limit_icon", !game.time_limit.empty());
|
||||
set_visible_if_exists(grid, "vision_fog", game.fog);
|
||||
set_visible_if_exists(grid, "vision_shroud", game.shroud);
|
||||
set_visible_if_exists(grid, "vision_none", !(game.fog || game.shroud));
|
||||
set_visible_if_exists(grid, "observers_yes", game.observers);
|
||||
set_visible_if_exists(grid, "time_limit_icon", !game.time_limit.empty());
|
||||
set_visible_if_exists(grid, "vision_fog", game.fog);
|
||||
set_visible_if_exists(grid, "vision_shroud", game.shroud);
|
||||
set_visible_if_exists(grid, "vision_none", !(game.fog || game.shroud));
|
||||
set_visible_if_exists(grid, "observers_yes", game.observers);
|
||||
set_visible_if_exists(grid, "shuffle_sides_icon", game.shuffle_sides);
|
||||
set_visible_if_exists(grid, "observers_no", !game.observers);
|
||||
set_visible_if_exists(grid, "needs_password", game.password_required);
|
||||
set_visible_if_exists(grid, "reloaded", game.reloaded);
|
||||
set_visible_if_exists(grid, "started", game.started);
|
||||
set_visible_if_exists(grid, "use_map_settings", game.use_map_settings);
|
||||
set_visible_if_exists(grid, "no_era", !game.have_era);
|
||||
set_visible_if_exists(grid, "observers_no", !game.observers);
|
||||
set_visible_if_exists(grid, "needs_password", game.password_required);
|
||||
set_visible_if_exists(grid, "reloaded", game.reloaded);
|
||||
set_visible_if_exists(grid, "started", game.started);
|
||||
set_visible_if_exists(grid, "use_map_settings", game.use_map_settings);
|
||||
set_visible_if_exists(grid, "no_era", !game.have_era);
|
||||
|
||||
|
||||
tbutton* join_button = dynamic_cast<tbutton*>(grid->find("join", false));
|
||||
if(join_button) {
|
||||
if(tbutton* join_button = dynamic_cast<tbutton*>(grid->find("join", false))) {
|
||||
connect_signal_mouse_left_click(
|
||||
*join_button,
|
||||
std::bind(&tlobby_main::join_button_callback,
|
||||
std::bind(&tlobby_main::join_global_button_callback,
|
||||
this,
|
||||
std::ref(*window_)));
|
||||
join_button->set_active(game.can_join());
|
||||
}
|
||||
tbutton* observe_button
|
||||
= dynamic_cast<tbutton*>(grid->find("observe", false));
|
||||
if(observe_button) {
|
||||
|
||||
if(tbutton* observe_button = dynamic_cast<tbutton*>(grid->find("observe", false))) {
|
||||
connect_signal_mouse_left_click(
|
||||
*observe_button,
|
||||
std::bind(&tlobby_main::observe_button_callback,
|
||||
std::bind(&tlobby_main::observe_global_button_callback,
|
||||
this,
|
||||
std::ref(*window_)));
|
||||
observe_button->set_active(game.can_observe());
|
||||
}
|
||||
tminimap* minimap = dynamic_cast<tminimap*>(grid->find("minimap", false));
|
||||
if(minimap) {
|
||||
|
||||
if(tminimap* minimap = dynamic_cast<tminimap*>(grid->find("minimap", false))) {
|
||||
minimap->set_config(&game_config_);
|
||||
minimap->set_map_data(game.map_data);
|
||||
}
|
||||
|
@ -759,7 +712,6 @@ void tlobby_main::update_gamelist_filter()
|
|||
gamelistbox_->set_row_shown(lobby_info_.games_visibility());
|
||||
}
|
||||
|
||||
|
||||
void tlobby_main::update_playerlist()
|
||||
{
|
||||
if(delay_playerlist_update_)
|
||||
|
@ -776,10 +728,6 @@ void tlobby_main::update_playerlist()
|
|||
lobby = true;
|
||||
}
|
||||
}
|
||||
player_list_.active_game.list->clear();
|
||||
player_list_.active_room.list->clear();
|
||||
player_list_.other_rooms.list->clear();
|
||||
player_list_.other_games.list->clear();
|
||||
|
||||
assert(player_list_.active_game.tree);
|
||||
assert(player_list_.active_room.tree);
|
||||
|
@ -791,13 +739,14 @@ void tlobby_main::update_playerlist()
|
|||
player_list_.other_games.tree->clear();
|
||||
player_list_.other_rooms.tree->clear();
|
||||
|
||||
for(auto userptr : lobby_info_.users_sorted())
|
||||
{
|
||||
for(auto userptr : lobby_info_.users_sorted()) {
|
||||
user_info& user = *userptr;
|
||||
tsub_player_list* target_list(nullptr);
|
||||
std::map<std::string, string_map> data;
|
||||
std::stringstream icon_ss;
|
||||
|
||||
std::string name = user.name;
|
||||
|
||||
std::stringstream icon_ss;
|
||||
icon_ss << "lobby/status";
|
||||
switch(user.state) {
|
||||
case user_info::SEL_ROOM:
|
||||
|
@ -826,6 +775,7 @@ void tlobby_main::update_playerlist()
|
|||
<< user.state << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(user.relation) {
|
||||
case user_info::ME:
|
||||
/* fall through */
|
||||
|
@ -842,12 +792,15 @@ void tlobby_main::update_playerlist()
|
|||
ERR_LB << "Bad user relation in lobby: " << user.relation
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if(user.registered) {
|
||||
name = tag(name, "b");
|
||||
name = "<b>" + name + "</b>";
|
||||
}
|
||||
|
||||
icon_ss << ".png";
|
||||
add_label_data(data, "player", name);
|
||||
add_label_data(data, "main_icon", icon_ss.str());
|
||||
|
||||
if(!preferences::playerlist_group_players()) {
|
||||
target_list = &player_list_.other_rooms;
|
||||
}
|
||||
|
@ -865,25 +818,26 @@ void tlobby_main::update_playerlist()
|
|||
tree_group_field["use_markup"] = "true";
|
||||
tree_group_item["name"] = tree_group_field;
|
||||
|
||||
ttree_view_node& player
|
||||
= target_list->tree->add_child("player", tree_group_item);
|
||||
ttree_view_node& player = target_list->tree->add_child("player", tree_group_item);
|
||||
|
||||
find_widget<ttoggle_panel>(&player, "tree_view_node_label", false)
|
||||
.set_callback_mouse_left_double_click(std::bind(
|
||||
&tlobby_main::user_dialog_callback, this, userptr));
|
||||
}
|
||||
player_list_.active_game.auto_hide();
|
||||
player_list_.active_room.auto_hide();
|
||||
player_list_.other_rooms.auto_hide();
|
||||
player_list_.other_games.auto_hide();
|
||||
|
||||
player_list_.active_game.update_player_count_label();
|
||||
player_list_.active_room.update_player_count_label();
|
||||
player_list_.other_rooms.update_player_count_label();
|
||||
player_list_.other_games.update_player_count_label();
|
||||
|
||||
player_list_dirty_ = false;
|
||||
}
|
||||
|
||||
void tlobby_main::update_selected_game()
|
||||
{
|
||||
int idx = gamelistbox_->get_selected_row();
|
||||
const int idx = gamelistbox_->get_selected_row();
|
||||
bool can_join = false, can_observe = false;
|
||||
|
||||
if(idx >= 0) {
|
||||
const game_info& game = *lobby_info_.games()[idx];
|
||||
can_observe = game.can_observe();
|
||||
|
@ -892,9 +846,8 @@ void tlobby_main::update_selected_game()
|
|||
} else {
|
||||
selected_game_id_ = 0;
|
||||
}
|
||||
find_widget<tbutton>(window_, "observe_global", false)
|
||||
.set_active(can_observe);
|
||||
|
||||
find_widget<tbutton>(window_, "observe_global", false).set_active(can_observe);
|
||||
find_widget<tbutton>(window_, "join_global", false).set_active(can_join);
|
||||
|
||||
player_list_dirty_ = true;
|
||||
|
@ -928,11 +881,12 @@ void tlobby_main::pre_show(twindow& window)
|
|||
&tlobby_main::gamelist_change_callback>);
|
||||
#endif
|
||||
|
||||
window.keyboard_capture(gamelistbox_);
|
||||
|
||||
player_list_.init(window);
|
||||
|
||||
player_list_.sort_by_name->set_value(preferences::playerlist_sort_name());
|
||||
player_list_.sort_by_relation->set_value(
|
||||
preferences::playerlist_sort_relation());
|
||||
player_list_.sort_by_relation->set_value(preferences::playerlist_sort_relation());
|
||||
player_list_.update_sort_icons();
|
||||
|
||||
player_list_.sort_by_name->set_callback_state_change(
|
||||
|
@ -940,8 +894,7 @@ void tlobby_main::pre_show(twindow& window)
|
|||
player_list_.sort_by_relation->set_callback_state_change(
|
||||
std::bind(&tlobby_main::player_filter_callback, this, _1));
|
||||
|
||||
chat_log_container_ = find_widget<tmulti_page>(
|
||||
&window, "chat_log_container", false, true);
|
||||
chat_log_container_ = find_widget<tmulti_page>(&window, "chat_log_container", false, true);
|
||||
|
||||
window.set_enter_disabled(true);
|
||||
|
||||
|
@ -950,44 +903,45 @@ void tlobby_main::pre_show(twindow& window)
|
|||
chat_input_ = find_widget<ttext_box>(&window, "chat_input", false, true);
|
||||
assert(chat_input_);
|
||||
connect_signal_pre_key_press(
|
||||
*chat_input_,
|
||||
std::bind(&tlobby_main::chat_input_keypress_callback,
|
||||
this,
|
||||
_3,
|
||||
_4,
|
||||
_5,
|
||||
std::ref(window)));
|
||||
*chat_input_,
|
||||
std::bind(&tlobby_main::chat_input_keypress_callback,
|
||||
this,
|
||||
_3,
|
||||
_4,
|
||||
_5,
|
||||
std::ref(window)));
|
||||
|
||||
connect_signal_mouse_left_click(
|
||||
find_widget<tbutton>(&window, "create", false),
|
||||
std::bind(&tlobby_main::create_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
find_widget<tbutton>(&window, "create", false),
|
||||
std::bind(&tlobby_main::create_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
|
||||
connect_signal_mouse_left_click(
|
||||
find_widget<tbutton>(&window, "refresh", false),
|
||||
std::bind(&tlobby_main::refresh_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
find_widget<tbutton>(&window, "refresh", false),
|
||||
std::bind(&tlobby_main::refresh_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
|
||||
connect_signal_mouse_left_click(
|
||||
find_widget<tbutton>(&window, "show_preferences", false),
|
||||
std::bind(&tlobby_main::show_preferences_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
find_widget<tbutton>(&window, "show_preferences", false),
|
||||
std::bind(&tlobby_main::show_preferences_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
|
||||
connect_signal_mouse_left_click(
|
||||
find_widget<tbutton>(&window, "join_global", false),
|
||||
std::bind(&tlobby_main::join_global_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
find_widget<tbutton>(&window, "join_global", false),
|
||||
std::bind(&tlobby_main::join_global_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
find_widget<tbutton>(&window, "join_global", false).set_active(false);
|
||||
|
||||
connect_signal_mouse_left_click(
|
||||
find_widget<tbutton>(&window, "observe_global", false),
|
||||
std::bind(&tlobby_main::observe_global_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
find_widget<tbutton>(&window, "observe_global", false),
|
||||
std::bind(&tlobby_main::observe_global_button_callback,
|
||||
this,
|
||||
std::ref(window)));
|
||||
|
||||
find_widget<tbutton>(&window, "observe_global", false).set_active(false);
|
||||
|
||||
ttoggle_button& skip_replay
|
||||
|
@ -996,15 +950,11 @@ void tlobby_main::pre_show(twindow& window)
|
|||
skip_replay.set_callback_state_change(
|
||||
std::bind(&tlobby_main::skip_replay_changed_callback, this, _1));
|
||||
|
||||
filter_friends_ = find_widget<ttoggle_button>(
|
||||
&window, "filter_with_friends", false, true);
|
||||
filter_ignored_ = find_widget<ttoggle_button>(
|
||||
&window, "filter_without_ignored", false, true);
|
||||
filter_slots_ = find_widget<ttoggle_button>(
|
||||
&window, "filter_vacant_slots", false, true);
|
||||
filter_invert_ = find_widget<ttoggle_button>(
|
||||
&window, "filter_invert", false, true);
|
||||
filter_text_ = find_widget<ttext_box>(&window, "filter_text", false, true);
|
||||
filter_friends_ = find_widget<ttoggle_button>(&window, "filter_with_friends", false, true);
|
||||
filter_ignored_ = find_widget<ttoggle_button>(&window, "filter_without_ignored", false, true);
|
||||
filter_slots_ = find_widget<ttoggle_button>(&window, "filter_vacant_slots", false, true);
|
||||
filter_invert_ = find_widget<ttoggle_button>(&window, "filter_invert", false, true);
|
||||
filter_text_ = find_widget<ttext_box>(&window, "filter_text", false, true);
|
||||
|
||||
filter_friends_->set_callback_state_change(
|
||||
std::bind(&tlobby_main::game_filter_change_callback, this, _1));
|
||||
|
@ -1024,10 +974,8 @@ void tlobby_main::pre_show(twindow& window)
|
|||
|
||||
// Force first update to be directly.
|
||||
tlobby_main::network_handler();
|
||||
lobby_update_timer_
|
||||
= add_timer(game_config::lobby_network_timer,
|
||||
std::bind(&tlobby_main::network_handler, this),
|
||||
true);
|
||||
lobby_update_timer_ = add_timer(
|
||||
game_config::lobby_network_timer, std::bind(&tlobby_main::network_handler, this), true);
|
||||
}
|
||||
|
||||
void tlobby_main::post_show(twindow& /*window*/)
|
||||
|
@ -1061,29 +1009,28 @@ tlobby_chat_window* tlobby_main::search_create_window(const std::string& name,
|
|||
bool whisper,
|
||||
bool open_new)
|
||||
{
|
||||
for(auto & t : open_windows_)
|
||||
{
|
||||
for(auto & t : open_windows_) {
|
||||
if(t.name == name && t.whisper == whisper)
|
||||
return &t;
|
||||
}
|
||||
|
||||
if(open_new) {
|
||||
open_windows_.push_back(tlobby_chat_window(name, whisper));
|
||||
std::map<std::string, string_map> data;
|
||||
utils::string_map symbols;
|
||||
symbols["name"] = name;
|
||||
if(whisper) {
|
||||
add_label_data(data,
|
||||
"log_text",
|
||||
VGETTEXT("Whisper session with $name started. "
|
||||
"If you do not want to receive messages "
|
||||
"from this user, "
|
||||
"type /ignore $name\n",
|
||||
symbols));
|
||||
add_label_data(data, "log_text",
|
||||
VGETTEXT("Whisper session with $name started. "
|
||||
"If you do not want to receive messages "
|
||||
"from this user, "
|
||||
"type /ignore $name\n",
|
||||
symbols));
|
||||
} else {
|
||||
add_label_data(
|
||||
data, "log_text", VGETTEXT("<i>Room $name joined</i>", symbols));
|
||||
add_label_data(data, "log_text", VGETTEXT("<i>Room $name joined</i>", symbols));
|
||||
lobby_info_.open_room(name);
|
||||
}
|
||||
|
||||
chat_log_container_->add_page(data);
|
||||
std::map<std::string, string_map> data2;
|
||||
add_label_data(data2, "room", whisper ? "<" + name + ">" : name);
|
||||
|
@ -1224,8 +1171,7 @@ void tlobby_main::active_window_changed()
|
|||
|
||||
// clear pending messages notification in room listbox
|
||||
tgrid* grid = roomlistbox_->get_row_grid(active_window_);
|
||||
find_widget<timage>(grid, "pending_messages", false)
|
||||
.set_visible(twidget::tvisible::hidden);
|
||||
find_widget<timage>(grid, "pending_messages", false).set_visible(twidget::tvisible::hidden);
|
||||
t.pending_messages = 0;
|
||||
|
||||
player_list_dirty_ = true;
|
||||
|
@ -1243,10 +1189,11 @@ void tlobby_main::close_window(size_t idx)
|
|||
const tlobby_chat_window& t = open_windows_[idx];
|
||||
bool active_changed = idx == active_window_;
|
||||
DBG_LB << "Close window " << idx << " - " << t.name << "\n";
|
||||
if(t.name == "lobby" && t.whisper == false)
|
||||
return;
|
||||
if(open_windows_.size() == 1)
|
||||
|
||||
if((t.name == "lobby" && t.whisper == false) || open_windows_.size() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(t.whisper == false) {
|
||||
// closing a room window -- send a part to the server
|
||||
config data, msg;
|
||||
|
@ -1255,14 +1202,17 @@ void tlobby_main::close_window(size_t idx)
|
|||
data.add_child("room_part", msg);
|
||||
wesnothd_connection_.send_data(data);
|
||||
}
|
||||
|
||||
if(active_window_ == open_windows_.size() - 1) {
|
||||
active_window_--;
|
||||
}
|
||||
|
||||
if(t.whisper) {
|
||||
lobby_info_.get_whisper_log(t.name).clear();
|
||||
} else {
|
||||
lobby_info_.close_room(t.name);
|
||||
}
|
||||
|
||||
open_windows_.erase(open_windows_.begin() + idx);
|
||||
roomlistbox_->remove_row(idx);
|
||||
roomlistbox_->select_row(active_window_);
|
||||
|
@ -1284,19 +1234,18 @@ void tlobby_main::network_handler()
|
|||
gamelist_diff_update_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(player_list_dirty_) {
|
||||
update_gamelist_filter();
|
||||
update_playerlist();
|
||||
}
|
||||
try
|
||||
{
|
||||
|
||||
try {
|
||||
config data;
|
||||
if(wesnothd_connection_.receive_data(data)) {
|
||||
process_network_data(data);
|
||||
}
|
||||
}
|
||||
catch(wesnothd_error& e)
|
||||
{
|
||||
} catch(wesnothd_error& e) {
|
||||
LOG_LB << "caught wesnothd_error in network_handler: " << e.message
|
||||
<< "\n";
|
||||
throw;
|
||||
|
@ -1483,30 +1432,16 @@ void tlobby_main::process_room_query_response(const config& data)
|
|||
}
|
||||
}
|
||||
|
||||
void tlobby_main::join_button_callback(gui2::twindow& window)
|
||||
void tlobby_main::observe_global_button_callback(twindow& window)
|
||||
{
|
||||
LOG_LB << "join_button_callback\n";
|
||||
join_global_button_callback(window);
|
||||
}
|
||||
|
||||
void tlobby_main::observe_button_callback(gui2::twindow& window)
|
||||
{
|
||||
LOG_LB << "observe_button_callback\n";
|
||||
observe_global_button_callback(window);
|
||||
}
|
||||
|
||||
void tlobby_main::observe_global_button_callback(gui2::twindow& window)
|
||||
{
|
||||
LOG_LB << "observe_global_button_callback\n";
|
||||
if(do_game_join(gamelistbox_->get_selected_row(), true)) {
|
||||
legacy_result_ = OBSERVE;
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
void tlobby_main::join_global_button_callback(gui2::twindow& window)
|
||||
void tlobby_main::join_global_button_callback(twindow& window)
|
||||
{
|
||||
LOG_LB << "join_global_button_callback\n";
|
||||
if(do_game_join(gamelistbox_->get_selected_row(), false)) {
|
||||
legacy_result_ = JOIN;
|
||||
window.close();
|
||||
|
@ -1550,7 +1485,7 @@ bool tlobby_main::do_game_join(int idx, bool observe)
|
|||
join["observe"] = observe;
|
||||
if(join && !observe && game.password_required) {
|
||||
std::string password;
|
||||
if(!gui2::tmp_join_game_password_prompt::execute(password, video_)) {
|
||||
if(!gui2::tmp_join_game_password_prompt::execute(password, window_->video())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1563,7 +1498,7 @@ bool tlobby_main::do_game_join(int idx, bool observe)
|
|||
return true;
|
||||
}
|
||||
|
||||
void tlobby_main::send_message_button_callback(gui2::twindow& /*window*/)
|
||||
void tlobby_main::send_message_button_callback(twindow& /*window*/)
|
||||
{
|
||||
const std::string& input = chat_input_->get_value();
|
||||
if(input.empty())
|
||||
|
@ -1598,19 +1533,18 @@ void tlobby_main::close_window_button_callback(size_t idx)
|
|||
close_window(idx);
|
||||
}
|
||||
|
||||
void tlobby_main::create_button_callback(gui2::twindow& window)
|
||||
void tlobby_main::create_button_callback(twindow& window)
|
||||
{
|
||||
legacy_result_ = CREATE;
|
||||
window.close();
|
||||
}
|
||||
|
||||
void tlobby_main::refresh_button_callback(gui2::twindow& /*window*/)
|
||||
void tlobby_main::refresh_button_callback(twindow& /*window*/)
|
||||
{
|
||||
wesnothd_connection_.send_data(config("refresh_lobby"));
|
||||
}
|
||||
|
||||
|
||||
void tlobby_main::show_preferences_button_callback(gui2::twindow& window)
|
||||
void tlobby_main::show_preferences_button_callback(twindow& window)
|
||||
{
|
||||
if(preferences_callback_) {
|
||||
preferences_callback_();
|
||||
|
@ -1709,7 +1643,7 @@ void tlobby_main::game_filter_change_callback(gui2::twidget& /*widget*/)
|
|||
update_gamelist_filter();
|
||||
}
|
||||
|
||||
void tlobby_main::gamelist_change_callback(gui2::twindow& /*window*/)
|
||||
void tlobby_main::gamelist_change_callback(twindow& /*window*/)
|
||||
{
|
||||
update_selected_game();
|
||||
}
|
||||
|
@ -1717,10 +1651,10 @@ void tlobby_main::gamelist_change_callback(gui2::twindow& /*window*/)
|
|||
void tlobby_main::player_filter_callback(gui2::twidget& /*widget*/)
|
||||
{
|
||||
player_list_.update_sort_icons();
|
||||
preferences::set_playerlist_sort_relation(
|
||||
player_list_.sort_by_relation->get_value_bool());
|
||||
preferences::set_playerlist_sort_name(
|
||||
player_list_.sort_by_name->get_value_bool());
|
||||
|
||||
preferences::set_playerlist_sort_relation(player_list_.sort_by_relation->get_value_bool());
|
||||
preferences::set_playerlist_sort_name(player_list_.sort_by_name->get_value_bool());
|
||||
|
||||
player_list_dirty_ = true;
|
||||
// window_->invalidate_layout();
|
||||
}
|
||||
|
@ -1728,14 +1662,19 @@ void tlobby_main::player_filter_callback(gui2::twidget& /*widget*/)
|
|||
void tlobby_main::user_dialog_callback(user_info* info)
|
||||
{
|
||||
tlobby_player_info dlg(*this, *info, lobby_info_);
|
||||
|
||||
lobby_delay_gamelist_update_guard g(*this);
|
||||
|
||||
dlg.show(window_->video());
|
||||
|
||||
delay_playerlist_update_ = true;
|
||||
|
||||
if(dlg.result_open_whisper()) {
|
||||
tlobby_chat_window* t = whisper_window_open(info->name, true);
|
||||
switch_to_window(t);
|
||||
window_->invalidate_layout();
|
||||
}
|
||||
|
||||
selected_game_id_ = info->game_id;
|
||||
// the commented out code below should be enough, but that'd delete the
|
||||
// playerlist and the widget calling this callback, so instead the game
|
||||
|
@ -1765,6 +1704,7 @@ void tlobby_main::skip_replay_changed_callback(twidget& w)
|
|||
ttoggle_button& tb = dynamic_cast<ttoggle_button&>(w);
|
||||
preferences::set_skip_mp_replay(tb.get_value_bool());
|
||||
}
|
||||
|
||||
void tlobby_main::send_to_server(const config& cfg)
|
||||
{
|
||||
wesnothd_connection_.send_data(cfg);
|
||||
|
|
|
@ -50,19 +50,13 @@ struct tlobby_chat_window
|
|||
int pending_messages;
|
||||
};
|
||||
|
||||
struct tplayer_list;
|
||||
|
||||
struct tsub_player_list
|
||||
{
|
||||
void init(twindow& w, const std::string& id);
|
||||
void show_toggle_callback(twidget& widget);
|
||||
void auto_hide();
|
||||
tlabel* label;
|
||||
tlabel* count;
|
||||
ttoggle_button* show_toggle;
|
||||
tlistbox* list;
|
||||
void init(twindow& w, const std::string& label, const bool unfolded = false);
|
||||
void update_player_count_label();
|
||||
ttree_view_node* tree;
|
||||
tlabel* tree_label;
|
||||
tlabel* label_player_count;
|
||||
};
|
||||
|
||||
struct tplayer_list
|
||||
|
@ -83,7 +77,7 @@ struct tplayer_list
|
|||
class tlobby_main : public tdialog, private events::chat_handler
|
||||
{
|
||||
public:
|
||||
tlobby_main(const config& game_config, lobby_info& info, CVideo& video, twesnothd_connection &wesnothd_connection);
|
||||
tlobby_main(const config& game_config, lobby_info& info, twesnothd_connection &wesnothd_connection);
|
||||
|
||||
~tlobby_main();
|
||||
|
||||
|
@ -301,10 +295,6 @@ private:
|
|||
|
||||
void process_room_query_response(const config& data);
|
||||
|
||||
void join_button_callback(twindow& window);
|
||||
|
||||
void observe_button_callback(twindow& window);
|
||||
|
||||
void join_global_button_callback(twindow& window);
|
||||
|
||||
void observe_global_button_callback(twindow& window);
|
||||
|
@ -370,8 +360,6 @@ private:
|
|||
|
||||
tlistbox* gamelistbox_;
|
||||
|
||||
tlistbox* userlistbox_;
|
||||
|
||||
tlistbox* roomlistbox_;
|
||||
|
||||
tmulti_page* chat_log_container_;
|
||||
|
@ -414,8 +402,6 @@ private:
|
|||
|
||||
bool gamelist_diff_update_;
|
||||
|
||||
CVideo& video_;
|
||||
|
||||
twesnothd_connection &wesnothd_connection_;
|
||||
|
||||
/** Timer for updating the lobby. */
|
||||
|
|
|
@ -51,12 +51,12 @@ namespace gui2
|
|||
|
||||
REGISTER_DIALOG(mp_create_game)
|
||||
|
||||
tmp_create_game::tmp_create_game(const config& cfg, ng::create_engine& create_eng, ng::configure_engine& config_eng)
|
||||
tmp_create_game::tmp_create_game(const config& cfg, ng::create_engine& create_eng)
|
||||
: cfg_(cfg)
|
||||
, last_selected_level_(-1)
|
||||
, scenario_(nullptr)
|
||||
, create_engine_(create_eng)
|
||||
, config_engine_(new ng::configure_engine(create_engine_.get_state()))
|
||||
, config_engine_()
|
||||
, use_map_settings_(register_bool("use_map_settings",
|
||||
true, preferences::use_map_settings, preferences::set_use_map_settings,
|
||||
dialog_callback<tmp_create_game, &tmp_create_game::update_map_settings>))
|
||||
|
@ -98,7 +98,14 @@ tmp_create_game::tmp_create_game(const config& cfg, ng::create_engine& create_en
|
|||
level_types_.push_back({ng::level::TYPE::SP_CAMPAIGN, _("SP Campaigns")});
|
||||
}
|
||||
|
||||
config_engine_->set_default_values();
|
||||
level_types_.erase(std::remove_if(level_types_.begin(), level_types_.end(),
|
||||
[this](level_type_info& type_info) {
|
||||
return create_engine_.get_levels_by_type_unfiltered(type_info.first).empty();
|
||||
}), level_types_.end());
|
||||
|
||||
create_engine_.get_state() = saved_game();
|
||||
create_engine_.get_state().classification().campaign_type = game_classification::CAMPAIGN_TYPE::MULTIPLAYER;
|
||||
|
||||
}
|
||||
|
||||
void tmp_create_game::pre_show(twindow& window)
|
||||
|
@ -131,9 +138,7 @@ void tmp_create_game::pre_show(twindow& window)
|
|||
std::vector<std::string> game_types;
|
||||
|
||||
for(level_type_info& type_info : level_types_) {
|
||||
if(!create_engine_.get_levels_by_type_unfiltered(type_info.first).empty()) {
|
||||
game_types.push_back(type_info.second);
|
||||
}
|
||||
game_types.push_back(type_info.second);
|
||||
}
|
||||
|
||||
if(game_types.empty()) {
|
||||
|
@ -434,14 +439,14 @@ void tmp_create_game::update_details(twindow& window)
|
|||
|
||||
create_engine_.current_level().set_metadata();
|
||||
|
||||
create_engine_.prepare_for_scenario();
|
||||
create_engine_.prepare_for_new_level();
|
||||
//create_engine_.prepare_for_scenario();
|
||||
//create_engine_.prepare_for_new_level();
|
||||
//create_engine_.get_parameters();
|
||||
|
||||
//config_engine_.write_parameters();
|
||||
//config_engine_->update_side_cfg();
|
||||
config_engine_.reset(new ng::configure_engine(create_engine_.get_state()));
|
||||
|
||||
config_engine_->update_initial_cfg(create_engine_.current_level().data());
|
||||
config_engine_->set_default_values();
|
||||
// TODO: display a message?
|
||||
if(tcombobox* eras = find_widget<tcombobox>(&window, "eras", false, false)) {
|
||||
eras->set_active(create_engine_.current_level().allow_era_choice());
|
||||
|
@ -565,6 +570,48 @@ void tmp_create_game::post_show(twindow& window)
|
|||
{
|
||||
if(get_retval() == twindow::OK) {
|
||||
find_widget<tlistbox>(&window, "games_list", false);
|
||||
|
||||
|
||||
create_engine_.prepare_for_era_and_mods();
|
||||
|
||||
if (create_engine_.current_level_type() == ng::level::TYPE::CAMPAIGN ||
|
||||
create_engine_.current_level_type() == ng::level::TYPE::SP_CAMPAIGN) {
|
||||
|
||||
std::string difficulty = create_engine_.select_campaign_difficulty();
|
||||
if (difficulty == "CANCEL") {
|
||||
return;
|
||||
}
|
||||
|
||||
create_engine_.prepare_for_campaign(difficulty);
|
||||
}
|
||||
else if (create_engine_.current_level_type() == ng::level::TYPE::SCENARIO)
|
||||
{
|
||||
create_engine_.prepare_for_scenario();
|
||||
}
|
||||
else
|
||||
{
|
||||
//This means define= doesn't work for random generated scenarios
|
||||
create_engine_.prepare_for_other();
|
||||
}
|
||||
|
||||
create_engine_.prepare_for_new_level();
|
||||
|
||||
create_engine_.prepare_for_scenario();
|
||||
create_engine_.prepare_for_new_level();
|
||||
create_engine_.get_parameters();
|
||||
if (!config_engine_->force_lock_settings())
|
||||
{
|
||||
//config_engine_->set_use_map_settings(fog_->get_widget_value());
|
||||
config_engine_->set_num_turns(turns_->get_widget_value(window));
|
||||
config_engine_->set_village_gold(gold_->get_widget_value(window));
|
||||
config_engine_->set_village_support(support_->get_widget_value(window));
|
||||
config_engine_->set_xp_modifier(experience_->get_widget_value(window));
|
||||
config_engine_->set_random_start_time(start_time_->get_widget_value(window));
|
||||
config_engine_->set_fog_game(fog_->get_widget_value(window));
|
||||
config_engine_->set_shroud_game(shroud_->get_widget_value(window));
|
||||
config_engine_->write_parameters();
|
||||
}
|
||||
//config_engine_->set_options(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ class tmp_create_game : public tdialog
|
|||
typedef std::pair<ng::level::TYPE, std::string> level_type_info;
|
||||
|
||||
public:
|
||||
tmp_create_game(const config& cfg, ng::create_engine& create_eng, ng::configure_engine& config_eng);
|
||||
tmp_create_game(const config& cfg, ng::create_engine& create_eng);
|
||||
|
||||
private:
|
||||
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
|
||||
|
|
|
@ -137,13 +137,14 @@ static void set_weapon_info(twindow& window,
|
|||
|
||||
std::stringstream attacker_stats, defender_stats;
|
||||
|
||||
// Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does not consider the swarm weapon special
|
||||
attacker_stats << "<b>" << attw_name << "</b>" << "\n"
|
||||
<< attacker.damage << font::weapon_numbers_sep << attacker_weapon.num_attacks()
|
||||
<< attacker.damage << font::weapon_numbers_sep << attacker.num_blows
|
||||
<< attw_apecials << "\n"
|
||||
<< font::span_color(a_cth_color) << attacker.chance_to_hit << "%</span>";
|
||||
|
||||
defender_stats << "<b>" << defw_name << "</b>" << "\n"
|
||||
<< defender.damage << font::weapon_numbers_sep << defender_weapon.num_attacks()
|
||||
<< defender.damage << font::weapon_numbers_sep << defender.num_blows
|
||||
<< defw_specials << "\n"
|
||||
<< font::span_color(d_cth_color) << defender.chance_to_hit << "%</span>";
|
||||
|
||||
|
|
|
@ -195,8 +195,8 @@ void tlistbox::set_row_shown(const std::vector<bool>& shown)
|
|||
for(size_t i = 0; i < shown.size(); ++i) {
|
||||
generator_->set_item_shown(i, shown[i]);
|
||||
}
|
||||
generator_->place(generator_->get_origin(),
|
||||
generator_->calculate_best_size());
|
||||
tpoint best_size = generator_->calculate_best_size();
|
||||
generator_->place(generator_->get_origin(), { std::max(best_size.x, content_visible_area().w), best_size.y });
|
||||
resize_needed = !content_resize_request();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue