Attack Predictions: added rest of the modifier fields and improved data readability

This commit is contained in:
Charles Dang 2017-03-22 13:47:01 +11:00
parent a3cd8224f6
commit 1387a647c3
2 changed files with 235 additions and 297 deletions

View file

@ -8,9 +8,6 @@
grow_factor = 0
[column]
#border = "all"
#border_size = 5
[spacer]
height = 15
[/spacer]
@ -19,6 +16,55 @@
[/row]
#enddef
#define _GUI_DATA_LABEL_ROW _ID_PREFIX _ID _LABEL _DEFINITON
# FIXME: doesn't work :(
#arg _DEFINITON
"default_small"
#endarg
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
id = {_ID_PREFIX} + "_" + {_ID} + "_label"
definition = {_DEFINITON}
label = {_LABEL}
use_markup = true
[/label]
[/column]
[/row]
#enddef
#define _GUI_DATA_VALUE_ROW _ID_PREFIX _ID
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
id = {_ID_PREFIX} + "_" + {_ID}
definition = "default_small"
use_markup = true
[/label]
[/column]
[/row]
#enddef
#
# NOTE: every data row *must* have a corresponding value row. MUST!
#
#define _GUI_DATA_PANEL _ID_PREFIX
[grid]
@ -31,72 +77,20 @@
[grid]
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
label = _ "Base damage"
definition = "default_small"
linked_group = "header"
[/label]
[/column]
[/row]
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
#label = _ "Opponent resistance against"
label = _ "Modifiers"
definition = "default_small"
linked_group = "header"
[/label]
[/column]
[/row]
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "base_damage" ( _ "Base damage") "default_small"}
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "resis" () "default_small"}
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "tod_modifier" ( _ "Time of day modifier") "default_small"}
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "leadership_modifier" ( _ "Leadership bonus") "default_small"}
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "slowed_modifier" ( _ "Slowed penalty") "default_small"}
{_GUI_SPACER_ROW}
[row]
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "total_damage" ("<b>" + _ "Total damage" + "</b>") "default"}
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
{_GUI_SPACER_ROW}
[label]
label = _ "Total damage"
definition = "default_small"
linked_group = "header"
[/label]
[/column]
[/row]
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
label = _ "Chance of being unscathed"
definition = "default_small"
linked_group = "header"
[/label]
[/column]
[/row]
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "chance_to_hit" ( _ "Chance to hit") "default_small"}
{_GUI_DATA_LABEL_ROW ({_ID_PREFIX}) "chance_unscathed" ( _ "Chance of being unscathed") "default_small"}
[/grid]
@ -108,80 +102,20 @@
vertical_grow = true
[grid]
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
id = {_ID_PREFIX} + "_attack"
definition = "default_small"
linked_group = "header"
use_markup = "true"
[/label]
[/column]
[/row]
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
id = {_ID_PREFIX} + "_resis"
definition = "default_small"
linked_group = "header"
use_markup = "true"
[/label]
[/column]
[/row]
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "base_damage"}
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "resis"}
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "tod_modifier"}
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "leadership_modifier"}
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "slowed_modifier"}
{_GUI_SPACER_ROW}
[row]
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "total_damage"}
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
{_GUI_SPACER_ROW}
[label]
id = {_ID_PREFIX} + "_damage"
definition = "default_small"
linked_group = "header"
use_markup = "true"
[/label]
[/column]
[/row]
[row]
[column]
border = "left,right,top"
border_size = 5
horizontal_alignment = "left"
[label]
id = {_ID_PREFIX} + "_no_damage_chance"
definition = "default_small"
linked_group = "header"
use_markup = "true"
[/label]
[/column]
[/row]
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "chance_to_hit"}
{_GUI_DATA_VALUE_ROW ({_ID_PREFIX}) "chance_unscathed"}
[/grid]
@ -315,36 +249,6 @@
fixed_width = "true"
[/linked_group]
[linked_group]
id = "header"
fixed_height = "true"
[/linked_group]
[linked_group]
id = "attack"
fixed_height = "true"
[/linked_group]
[linked_group]
id = "resis"
fixed_height = "true"
[/linked_group]
[linked_group]
id = "damage"
fixed_height = "true"
[/linked_group]
[linked_group]
id = "chance_unscathed"
fixed_height = "true"
[/linked_group]
[linked_group]
id = "bonuses"
fixed_height = "true"
[/linked_group]
[tooltip]
id = "tooltip"
[/tooltip]
@ -436,7 +340,9 @@
[/window]
#undef _GUI_DATA_COLUMN
#undef _GUI_HP_GRAPH
#undef _GUI_DATA_PANEL
#undef _GUI_SPACER_ROW
#undef _GUI_DATA_LABEL_ROW
#undef _GUI_DATA_VALUE_ROW
#undef _GUI_DATA_COLUMN
#undef _GUI_DATA_PANEL
#undef _GUI_HP_GRAPH

View file

@ -22,6 +22,7 @@
#include "font/text_formatting.hpp"
#include "formatter.hpp"
#include "formula/variant.hpp"
#include "game_board.hpp"
#include "game_config.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/widgets/drawing.hpp"
@ -30,6 +31,7 @@
#include "gui/widgets/window.hpp"
#include "gettext.hpp"
#include "language.hpp"
#include "resources.hpp"
#include "units/abilities.hpp"
#include "units/unit.hpp"
@ -73,148 +75,178 @@ static std::string get_probability_string(const double prob)
void attack_predictions::set_data(window& window, const combatant_data& attacker, const combatant_data& defender)
{
const std::string widget_id_prefix = attacker.stats_.is_attacker ? "attacker_" : "defender_";
// Each data widget in this dialog has its id prefixed by either of these identifiers.
const std::string widget_id_prefix = attacker.stats_.is_attacker ? "attacker" : "defender";
const auto get_prefixed_widget_id = [&widget_id_prefix](const std::string& id) {
return formatter() << widget_id_prefix << "_" << id;
};
// Helpers for setting or hiding labels
const auto set_label_helper = [&](const std::string& id, const std::string& value) {
find_widget<label>(&window, widget_id_prefix + id, false).set_label(value);
find_widget<label>(&window, get_prefixed_widget_id(id), false).set_label(value);
};
const auto hide_label_helper = [&](const std::string& id) {
find_widget<label>(&window, get_prefixed_widget_id(id), false).set_visible(widget::visibility::invisible);
find_widget<label>(&window, get_prefixed_widget_id(id) + "_label" , false).set_visible(widget::visibility::invisible);
};
std::stringstream ss;
// With a weapon.
if(attacker.stats_.weapon) {
// Set specials context (for safety, it should not have changed normally).
const attack_type* weapon = attacker.stats_.weapon;
weapon->set_specials_context(attacker.unit_.get_location(), defender.unit_.get_location(), attacker.stats_.is_attacker, defender.stats_.weapon);
// Get damage modifiers.
unit_ability_list dmg_specials = weapon->get_specials("damage");
unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), attacker.stats_.backstab_pos);
// Get the SET damage modifier, if any.
auto set_dmg_effect =
std::find_if(dmg_effect.begin(), dmg_effect.end(), [](const unit_abilities::individual_effect& e) { return e.type == unit_abilities::SET; });
// Either user the SET modifier or the base weapon damage.
if(set_dmg_effect == dmg_effect.end()) {
// TODO: formatting
ss << weapon->name() << ": " << weapon->damage();
} else {
assert(set_dmg_effect->ability);
ss << (*set_dmg_effect->ability)["name"] << ": " << set_dmg_effect->value;
}
// Process the ADD damage modifiers.
for(const auto& e : dmg_effect) {
if(e.type == unit_abilities::ADD) {
ss << "\n";
ss << (*e.ability)["name"] << ": ";
if(e.value >= 0) {
ss << '+';
}
ss << e.value;
}
}
// Process the MUL damage modifiers.
for(const auto& e : dmg_effect) {
if(e.type == unit_abilities::MUL) {
ss << "\n";
ss << (*e.ability)["name"] << ": " << font::unicode_multiplication_sign << (e.value / 100);
if(e.value % 100) {
ss << "." << ((e.value % 100) / 10);
if(e.value % 10) {
ss << (e.value % 10);
}
}
}
}
set_label_helper("attack", ss.str());
#if 0
// Time of day modifier.
int tod_modifier = combat_modifier(resources::gameboard->units(), resources::gameboard->map(), attacker.unit_.get_location(), u.alignment(), u.is_fearless());
if(tod_modifier != 0) {
left_strings.push_back(_("Time of day"));
str.str("");
ss << utils::signed_percent(tod_modifier);
right_strings.push_back(str.str());
}
// Leadership bonus.
int leadership_bonus = 0;
under_leadership(resources::gameboard->units(), attacker.unit_.get_location(), &leadership_bonus);
if(leadership_bonus != 0) {
left_strings.push_back(_("Leadership"));
str.str("");
ss << utils::signed_percent(leadership_bonus);
right_strings.push_back(str.str());
}
#endif
ss.str("");
// Resistance modifier.
const int resistance_modifier = defender.unit_.damage_from(*weapon, !attacker.stats_.is_attacker, defender.unit_.get_location());
if(resistance_modifier != 100) {
// TODO
//if(attacker.stats_.is_attacker) {
// ss << "Defender resistance vs ";
//} else {
// ss << "Attacker vulnerability vs ";
//}
ss << string_table["type_" + weapon->type()] << ": ";
ss << font::unicode_multiplication_sign << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
set_label_helper("resis", ss.str());
}
// Slowed penalty.
#if 0
if(attacker.stats_.is_slowed) {
left_strings.push_back(_("Slowed"));
right_strings.push_back("/ 2");
}
#endif
ss.str("");
// Total damage.
const int base_damage = weapon->damage();
color_t dmg_color = font::weapon_color;
if(attacker.stats_.damage > base_damage) {
dmg_color = font::good_dmg_color;
} else if(attacker.stats_.damage < base_damage) {
dmg_color = font::bad_dmg_color;
}
const color_t cth_color = game_config::red_to_green(attacker.stats_.chance_to_hit);
ss << font::span_color(dmg_color) << attacker.stats_.damage << "</span>"
<< font::weapon_numbers_sep << attacker.stats_.num_blows << " ("
<< font::span_color(cth_color) << attacker.stats_.chance_to_hit << "%</span>)";
set_label_helper("damage", ss.str());
} else {
// Without a weapon.
set_label_helper("attack", _("No usable weapon"));
}
//
// Always visible fields
//
// Unscathed probability
const color_t ndc_color = game_config::red_to_green(attacker.combatant_.untouched * 100);
// Unscathed probability.
set_label_helper("no_damage_chance",
(formatter() << font::span_color(ndc_color) << get_probability_string(attacker.combatant_.untouched) << "</span>").str());
drawing& graph_widget = find_widget<drawing>(&window, widget_id_prefix + "hp_graph", false);
ss << font::span_color(ndc_color) << get_probability_string(attacker.combatant_.untouched) << "</span>";
set_label_helper("chance_unscathed", ss.str());
// HP probability graph
drawing& graph_widget = find_widget<drawing>(&window, get_prefixed_widget_id("hp_graph"), false);
draw_hp_graph(graph_widget, attacker, defender);
//
// Weapon detail fields (only shown if a weapon is present)
//
if(!attacker.stats_.weapon) {
set_label_helper("attack", _("No usable weapon"));
return;
}
ss.str("");
// Set specials context (for safety, it should not have changed normally).
const attack_type* weapon = attacker.stats_.weapon;
weapon->set_specials_context(attacker.unit_.get_location(), defender.unit_.get_location(), attacker.stats_.is_attacker, defender.stats_.weapon);
// Get damage modifiers.
unit_ability_list dmg_specials = weapon->get_specials("damage");
unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), attacker.stats_.backstab_pos);
// Get the SET damage modifier, if any.
auto set_dmg_effect = std::find_if(dmg_effect.begin(), dmg_effect.end(),
[](const unit_abilities::individual_effect& e) { return e.type == unit_abilities::SET; }
);
// Either user the SET modifier or the base weapon damage.
if(set_dmg_effect == dmg_effect.end()) {
ss << weapon->damage() << " (<i>" << weapon->name() << "</i>)";
} else {
assert(set_dmg_effect->ability);
ss << set_dmg_effect->value << " (" << (*set_dmg_effect->ability)["name"] << ")";
}
// Process the ADD damage modifiers.
for(const auto& e : dmg_effect) {
if(e.type == unit_abilities::ADD) {
ss << "\n";
ss << (*e.ability)["name"] << ": ";
if(e.value >= 0) {
ss << '+';
}
ss << e.value;
}
}
// Process the MUL damage modifiers.
for(const auto& e : dmg_effect) {
if(e.type == unit_abilities::MUL) {
ss << "\n";
ss << (*e.ability)["name"] << ": " << font::unicode_multiplication_sign << (e.value / 100);
if(e.value % 100) {
ss << "." << ((e.value % 100) / 10);
if(e.value % 10) {
ss << (e.value % 10);
}
}
}
}
set_label_helper("base_damage", ss.str());
ss.str("");
// Resistance modifier.
const int resistance_modifier = defender.unit_.damage_from(*weapon, !attacker.stats_.is_attacker, defender.unit_.get_location());
if(resistance_modifier != 100) {
if(attacker.stats_.is_attacker) {
ss << _("Defender resistance vs") << " ";
} else {
ss << _("Attacker vulnerability vs") << " ";
}
ss << string_table["type_" + weapon->type()];
set_label_helper("resis_label", ss.str());
ss.str("");
ss << font::unicode_multiplication_sign << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
set_label_helper("resis", ss.str());
}
ss.str("");
// TODO: color format the modifiers
// Time of day modifier.
const unit& u = attacker.unit_;
const int tod_modifier = combat_modifier(resources::gameboard->units(), resources::gameboard->map(),
u.get_location(), u.alignment(), u.is_fearless());
if(tod_modifier != 0) {
set_label_helper("tod_modifier", utils::signed_percent(tod_modifier));
} else {
hide_label_helper("tod_modifier");
}
// Leadership bonus.
int leadership_bonus = 0;
under_leadership(resources::gameboard->units(), attacker.unit_.get_location(), &leadership_bonus);
if(leadership_bonus != 0) {
set_label_helper("leadership_modifier", utils::signed_percent(leadership_bonus));
} else {
hide_label_helper("leadership_modifier");
}
// Slowed penalty.
if(attacker.stats_.is_slowed) {
set_label_helper("slowed_modifier", "/ 2");
} else {
hide_label_helper("slowed_modifier");
}
// Total damage.
const int base_damage = weapon->damage();
color_t dmg_color = font::weapon_color;
if(attacker.stats_.damage > base_damage) {
dmg_color = font::good_dmg_color;
} else if(attacker.stats_.damage < base_damage) {
dmg_color = font::bad_dmg_color;
}
ss << font::span_color(dmg_color) << attacker.stats_.damage << "</span>"
<< font::weapon_numbers_sep << attacker.stats_.num_blows;
set_label_helper("total_damage", ss.str());
// Chance to hit
const color_t cth_color = game_config::red_to_green(attacker.stats_.chance_to_hit);
ss.str("");
ss << font::span_color(cth_color) << attacker.stats_.chance_to_hit << "%</span>";
set_label_helper("chance_to_hit", ss.str());
}
void attack_predictions::draw_hp_graph(drawing& hp_graph, const combatant_data& attacker, const combatant_data& defender)