Add tooltips to unit_attack dialogs (#6462)

In tooltips, the special and weapon_abilities are listed by stats (damage, attacks and chance to hit), and the owner of each is specified.
This commit is contained in:
newfrenchy83 2022-05-18 08:54:28 +02:00 committed by GitHub
parent f7859ad104
commit 81d41a861d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 274 additions and 34 deletions

View file

@ -46,9 +46,52 @@
[window_definition]
id = tooltip
id = tooltip_transparent
description = "The window to show a large tooltip."
[resolution]
left_border = 15
right_border = 15
top_border = 15
bottom_border = 15
[background]
[draw]
[rectangle]
x = 0
y = 0
w = "(width)"
h = "(height)"
fill_color = "0, 0, 0, 192"
border_thickness = 1
border_color = "0, 0, 0, 255"
[/rectangle]
[/draw]
[/background]
[foreground]
[draw]
[/draw]
[/foreground]
[/resolution]
[/window_definition]
[window_definition]
id = tooltip
description = "The window to show a floating tooltip."
[resolution]
left_border = 15

View file

@ -141,7 +141,7 @@ def reevaluate_best_size(w, s)
[window]
id = "tooltip"
description = "The tooltip popup window with large tooltips, eg in the main menu."
description = "The tooltip popup window with floating tooltips."
[resolution]
definition = "tooltip"
@ -190,6 +190,57 @@ def reevaluate_best_size(w, s)
[/window]
[window]
id = "tooltip_transparent"
description = "The tooltip popup window with floating tooltips, and transparent background."
[resolution]
definition = "tooltip_transparent"
automatic_placement = false
functions = "{__GUI_WINDOW_FUNCTIONS}"
x = "{__GUI_WINDOW_X}"
y = "{__GUI_WINDOW_Y}"
width = "{__GUI_WINDOW_WIDTH}"
height = "{__GUI_WINDOW_HEIGHT}"
reevaluate_best_size = "{__GUI_WINDOW_REEVALUATE_BEST_SIZE}"
# TODO tooltips in this window make little sense.
# Have to think of a nice solution.
[tooltip]
id = "tooltip_transparent"
[/tooltip]
[helptip]
id = "tooltip_transparent"
[/helptip]
[grid]
[row]
[column]
[label]
id = "label"
definition = "default_small"
use_markup = true
wrap = true
[/label]
[/column]
[/row]
[/grid]
[/resolution]
[/window]
#undef __GUI_WINDOW_REEVALUATE_BEST_SIZE
#undef __GUI_WINDOW_HEIGHT
#undef __GUI_WINDOW_WIDTH

View file

@ -282,11 +282,11 @@
[/linked_group]
[tooltip]
id = "tooltip"
id = "tooltip_transparent"
[/tooltip]
[helptip]
id = "tooltip"
id = "tooltip_transparent"
[/helptip]
[grid]

View file

@ -115,19 +115,51 @@ void unit_attack::pre_show(window& window)
attacker_itor_->get_location(), false, attacker.weapon
);
const std::set<std::string> checking_tags_other = {"disable", "berserk", "drains", "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
std::string attw_specials = attacker_weapon.weapon_specials(attacker.backstab_pos);
std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"});
std::string attw_specials_atk = attacker_weapon.weapon_specials_value({"attacks", "swarm"});
std::string attw_specials_cth = attacker_weapon.weapon_specials_value({"chance_to_hit"});
std::string attw_specials_others = attacker_weapon.weapon_specials_value(checking_tags_other);
bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0 && defender_weapon.num_attacks() == 0 && defender.chance_to_hit == 0);
std::string defw_specials = defender_attack ? defender_weapon.weapon_specials() : "";
std::string defw_specials_dmg = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage"}) : "";
std::string defw_specials_atk = defender_attack ? defender_weapon.weapon_specials_value({"attacks", "swarm"}) : "";
std::string defw_specials_cth = defender_attack ? defender_weapon.weapon_specials_value({"chance_to_hit"}) : "";
std::string defw_specials_others = defender_attack ? defender_weapon.weapon_specials_value(checking_tags_other) : "";
if(!attw_specials.empty()) {
attw_specials = " " + attw_specials;
}
if(!attw_specials_dmg.empty()) {
attw_specials_dmg = " " + attw_specials_dmg;
}
if(!attw_specials_atk.empty()) {
attw_specials_atk = " " + attw_specials_atk;
}
if(!attw_specials_cth.empty()) {
attw_specials_cth = " " + attw_specials_cth;
}
if(!attw_specials_others.empty()) {
attw_specials_others = "\n" + ("<b>"+translation::dsgettext("wesnoth", "Other aspects: ")+"</b>") + "\n" + ("<i>"+attw_specials_others+"</i>");
}
if(!defw_specials.empty()) {
defw_specials = " " + defw_specials;
}
if(!defw_specials_dmg.empty()) {
defw_specials_dmg = " " + defw_specials_dmg;
}
if(!defw_specials_atk.empty()) {
defw_specials_atk = " " + defw_specials_atk;
}
if(!defw_specials_cth.empty()) {
defw_specials_cth = " " + defw_specials_cth;
}
if(!defw_specials_others.empty()) {
defw_specials_others = "\n" + ("<b>"+translation::dsgettext("wesnoth", "Other aspects: ")+"</b>") + "\n" + ("<i>"+defw_specials_others+"</i>");
}
std::stringstream attacker_stats, defender_stats;
std::stringstream attacker_stats, defender_stats, attacker_tooltip, defender_tooltip;
// 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"
@ -135,11 +167,23 @@ void unit_attack::pre_show(window& window)
<< attw_specials << "\n"
<< font::span_color(a_cth_color) << attacker.chance_to_hit << "%</span>";
attacker_tooltip << translation::dsgettext("wesnoth", "Weapon: ") << "<b>" << attw_name << "</b>" << "\n"
<< translation::dsgettext("wesnoth", "Damage: ") << attacker.damage << "<i>" << attw_specials_dmg << "</i>" << "\n"
<< translation::dsgettext("wesnoth", "Attacks: ") << attacker.num_blows << "<i>" << attw_specials_atk << "</i>" << "\n"
<< translation::dsgettext("wesnoth", "Chance to hit: ") << font::span_color(a_cth_color) << attacker.chance_to_hit << "%</span>"<< "<i>" << attw_specials_cth << "</i>"
<< attw_specials_others;
defender_stats << "<b>" << defw_name << "</b>" << "\n"
<< defender.damage << font::weapon_numbers_sep << defender.num_blows
<< defw_specials << "\n"
<< font::span_color(d_cth_color) << defender.chance_to_hit << "%</span>";
defender_tooltip << translation::dsgettext("wesnoth", "Weapon: ") << "<b>" << defw_name << "</b>" << "\n"
<< translation::dsgettext("wesnoth", "Damage: ") << defender.damage << "<i>" << defw_specials_dmg << "</i>" << "\n"
<< translation::dsgettext("wesnoth", "Attacks: ") << defender.num_blows << "<i>" << defw_specials_atk << "</i>" << "\n"
<< translation::dsgettext("wesnoth", "Chance to hit: ") << font::span_color(d_cth_color) << defender.chance_to_hit << "%</span>"<< "<i>" << defw_specials_cth << "</i>"
<< defw_specials_others;
std::map<std::string, string_map> data;
string_map item;
@ -148,15 +192,19 @@ void unit_attack::pre_show(window& window)
item["label"] = attacker_weapon.icon();
data.emplace("attacker_weapon_icon", item);
item["tooltip"] = attacker_tooltip.str();
item["label"] = attacker_stats.str();
data.emplace("attacker_weapon", item);
item["tooltip"] = "";
item["label"] = "<span color='#a69275'>" + font::unicode_em_dash + " " + range + " " + font::unicode_em_dash + "</span>";
data.emplace("range", item);
item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
item["label"] = defender_stats.str();
data.emplace("defender_weapon", item);
item["tooltip"] = "";
item["label"] = defender_weapon.icon();
data.emplace("defender_weapon_icon", item);

View file

@ -22,6 +22,7 @@
#include "display_context.hpp"
#include "font/text_formatting.hpp"
#include "game_board.hpp"
#include "gettext.hpp"
#include "global.hpp"
#include "lexical_cast.hpp"
#include "log.hpp"
@ -834,24 +835,18 @@ std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
/**
* static used in weapon_specials (bool only_active, bool is_backstab) and
* @return a string and a set_string for the weapon_specials function below.
* @param[in,out] weapon_abilities the string modified and returned
* @param[in,out] temp_string the string modified and returned
* @param[in] active the boolean for determine if @name can be added or not
* @param[in] sp reference to ability to check
* @param[in] name string who must be or not added
* @param[in,out] checking_name the reference for checking if @name already added
*/
static void add_name(std::string& weapon_abilities, bool active, const config::any_child sp, std::set<std::string>& checking_name, bool affect_adjacent)
static void add_name(std::string& temp_string, bool active, const std::string name, std::set<std::string>& checking_name)
{
if (active) {
const std::string& name = sp.cfg["name"].str();
if (!name.empty() && checking_name.count(name) == 0) {
checking_name.insert(name);
if (!weapon_abilities.empty()) weapon_abilities += ", ";
if (affect_adjacent) {
weapon_abilities += sp.cfg["affect_enemies"].to_bool() ? font::span_color(font::BAD_COLOR, name) : font::span_color(font::GOOD_COLOR, name);
} else {
weapon_abilities += font::span_color(font::BUTTON_COLOR, name);
}
if (!temp_string.empty()) temp_string += ", ";
temp_string += font::span_color(font::BUTTON_COLOR, name);
}
}
}
@ -883,35 +878,102 @@ std::string attack_type::weapon_specials(bool is_backstab) const
if (!active) res += "</span>";
}
}
std::string weapon_abilities;
std::string temp_string;
std::set<std::string> checking_name;
weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name);
weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, {}, "affect_allies");
if(!temp_string.empty() && !res.empty()) {
temp_string = ", \n" + temp_string;
res += temp_string;
} else if (!temp_string.empty()){
res = temp_string;
}
return res;
}
static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name, const std::string from_str)
{
if(!temp_string.empty()){
temp_string = translation::dsgettext("wesnoth", from_str.c_str()) + temp_string;
weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ? "\n" : "";
weapon_abilities += temp_string;
temp_string.clear();
checking_name.clear();
}
}
std::string attack_type::weapon_specials_value(const std::set<std::string> checking_tags) const
{
//log_scope("weapon_specials_value");
std::string temp_string, weapon_abilities;
std::set<std::string> checking_name;
for (const config::any_child sp : specials_.all_children_range()) {
if((checking_tags.count(sp.key) != 0)){
const bool active = special_active(sp.cfg, AFFECT_SELF, sp.key);
add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
}
}
add_name_list(temp_string, weapon_abilities, checking_name, "");
weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, true);
add_name_list(temp_string, weapon_abilities, checking_name, "Owned: ");
weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_allies", true);
add_name_list(temp_string, weapon_abilities, checking_name, "Taught: ");
weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_enemies", true);
add_name_list(temp_string, weapon_abilities, checking_name, "Taught: (by an enemy): ");
if(other_attack_) {
for (const config::any_child sp : other_attack_->specials_.all_children_range()) {
if((checking_tags.count(sp.key) != 0)){
const bool active = other_attack_->special_active(sp.cfg, AFFECT_OTHER, sp.key);
add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
}
}
}
weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
add_name_list(temp_string, weapon_abilities, checking_name, "Used by opponent: ");
return weapon_abilities;
}
void attack_type::weapon_specials_impl_self(std::string& temp_string, unit_const_ptr self, const_attack_ptr self_attack, const_attack_ptr other_attack, const map_location& self_loc, AFFECTS whom,
std::set<std::string>& checking_name, const std::set<std::string>& checking_tags, bool leader_bool)
{
if(self){
for (const config::any_child sp : self->abilities().all_children_range()){
bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(sp.key) != 0) : true;
const bool active = tag_checked && check_self_abilities_impl(self_attack, other_attack, sp.cfg, self, self_loc, whom, sp.key, leader_bool);
add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
}
}
}
void attack_type::weapon_specials_impl_adj(std::string& temp_string, unit_const_ptr self, const_attack_ptr self_attack, const_attack_ptr other_attack, const map_location& self_loc, AFFECTS whom,
std::set<std::string>& checking_name, const std::set<std::string>& checking_tags, const std::string& affect_adjacents, bool leader_bool)
{
assert(display::get_singleton());
const unit_map& units = display::get_singleton()->get_units();
if(self_){
for (const config::any_child sp : self_->abilities().all_children_range()){
const bool active = check_self_abilities_impl(shared_from_this(), other_attack_, sp.cfg, self_, self_loc_, AFFECT_SELF, sp.key);
add_name(weapon_abilities, active, sp, checking_name, false);
}
const auto adjacent = get_adjacent_tiles(self_loc_);
if(self){
const auto adjacent = get_adjacent_tiles(self_loc);
for(unsigned i = 0; i < adjacent.size(); ++i) {
const unit_map::const_iterator it = units.find(adjacent[i]);
if (it == units.end() || it->incapacitated())
continue;
if(&*it == self_.get())
if(&*it == self.get())
continue;
for (const config::any_child sp : it->abilities().all_children_range()){
const bool active = check_adj_abilities_impl(shared_from_this(), other_attack_, sp.cfg, self_, *it, i, self_loc_, AFFECT_SELF, sp.key);
add_name(weapon_abilities, active, sp, checking_name, true);
bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(sp.key) != 0) : true;
bool default_bool = (affect_adjacents == "affect_allies") ? true : false;
bool affect_allies = (!affect_adjacents.empty()) ? sp.cfg[affect_adjacents].to_bool(default_bool) : true;
const bool active = tag_checked && check_adj_abilities_impl(self_attack, other_attack, sp.cfg, self, *it, i, self_loc, whom, sp.key, leader_bool) && affect_allies;
add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
}
}
}
if(!weapon_abilities.empty() && !res.empty()) {
weapon_abilities = ", \n" + weapon_abilities;
res += weapon_abilities;
} else if (!weapon_abilities.empty()){
res = weapon_abilities;
}
return res;
}

View file

@ -83,6 +83,7 @@ public:
unit_ability_list get_specials(const std::string& special) const;
std::vector<std::pair<t_string, t_string>> special_tooltips(boost::dynamic_bitset<>* active_list = nullptr) const;
std::string weapon_specials(bool is_backstab=false) const;
std::string weapon_specials_value(const std::set<std::string> checking_tags) const;
/** Calculates the number of attacks this weapon has, considering specials. */
void modified_attacks(bool is_backstab, unsigned & min_attacks,
@ -150,6 +151,41 @@ private:
bool special_active(const config& special, AFFECTS whom, const std::string& tag_name,
bool include_backstab=true, const std::string& filter_self ="filter_self") const;
/** weapon_specials_impl_self and weapon_specials_impl_adj : check if special name can be added.
* @param[in,out] temp_string the string modified and returned
* @param[in] self the unit checked.
* @param[in] self_attack the attack used by unit checked in this function.
* @param[in] other_attack the attack used by opponent to unit checked.
* @param[in] self_loc location of the unit checked.
* @param[in] whom determine if unit affected or not by special ability.
* @param[in,out] checking_name the reference for checking if a name is already added
* @param[in] checking_tags the reference for checking if special ability type can be used
* @param[in] leader_bool If true, [leadership] abilities are checked.
*/
static void weapon_specials_impl_self(
std::string& temp_string,
unit_const_ptr self,
const_attack_ptr self_attack,
const_attack_ptr other_attack,
const map_location& self_loc,
AFFECTS whom,
std::set<std::string>& checking_name,
const std::set<std::string>& checking_tags={},
bool leader_bool=false
);
static void weapon_specials_impl_adj(
std::string& temp_string,
unit_const_ptr self,
const_attack_ptr self_attack,
const_attack_ptr other_attack,
const map_location& self_loc,
AFFECTS whom,
std::set<std::string>& checking_name,
const std::set<std::string>& checking_tags={},
const std::string& affect_adjacents="",
bool leader_bool=false
);
/** check_self_abilities_impl : return an boolean value for checking of activities of abilities used like weapon
* @return True if the special @a tag_name is active.
* @param self_attack the attack used by unit checked in this function.