convert help_topic_generator to new markup (partial)

This commit is contained in:
Subhraman Sarkar 2024-08-02 12:30:24 +05:30 committed by Celtic Minstrel
parent 78ca6f50a9
commit fe9de2f638

View file

@ -20,11 +20,11 @@
#include "font/sdl_ttf_compat.hpp"
#include "formula/string_utils.hpp" // for VNGETTEXT
#include "game_config.hpp" // for debug, menu_contract, etc
#include "preferences/preferences.hpp" // for encountered_terrains, etc
#include "gettext.hpp" // for _, gettext, N_
#include "language.hpp" // for string_table, symbol_table
#include "log.hpp" // for LOG_STREAM, logger, etc
#include "movetype.hpp" // for movetype, movetype::effects, etc
#include "preferences/preferences.hpp" // for encountered_terrains, etc
#include "units/race.hpp" // for unit_race, etc
#include "terrain/terrain.hpp" // for terrain_type
#include "terrain/translation.hpp" // for operator==, ter_list, etc
@ -140,13 +140,13 @@ std::string terrain_topic_generator::operator()() const {
if (!type_.icon_image().empty())
ss << "<img>src='images/buttons/icon-base-32.png~RC(magenta>" << type_.id()
<< ")~BLIT("<< "terrain/" << type_.icon_image() << "_30.png)" << "'</img> ";
<< ")~BLIT("<< "terrain/" << type_.icon_image() << "_30.png)" << "'</img>";
if (!type_.editor_image().empty())
ss << "<img>src='" << type_.editor_image() << "'</img> ";
ss << "<img>src='" << type_.editor_image() << "'</img>";
if (!type_.help_topic_text().empty())
ss << "\n\n" << type_.help_topic_text().str() << "\n";
ss << "\n" << type_.help_topic_text().str() << "\n";
else
ss << "\n";
@ -185,7 +185,7 @@ std::string terrain_topic_generator::operator()() const {
}
if(!special_notes.empty()) {
ss << "\n" << _("Special Notes:") << '\n';
ss << "\n\n" << _("<header>Special Notes</header>") << "\n";
for(const auto& note : special_notes) {
ss << font::unicode_bullet << " " << note << '\n';
}
@ -309,20 +309,8 @@ std::string unit_topic_generator::operator()() const {
const int screen_width = video::game_canvas_size().x;
ss << _("Level") << " " << type_.level();
ss << "\n\n";
ss << "<img>src='" << male_type.image();
ss << "~RC(" << male_type.flag_rgb() << ">red)";
if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
ss << "' box='no'</img> ";
if (female_type.image() != male_type.image()) {
ss << "<img>src='" << female_type.image();
ss << "~RC(" << female_type.flag_rgb() << ">red)";
if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
ss << "' box='no'</img> ";
}
// Portraits
const std::string &male_portrait = male_type.small_profile().empty() ?
male_type.big_profile() : male_type.small_profile();
const std::string &female_portrait = female_type.small_profile().empty() ?
@ -344,15 +332,29 @@ std::string unit_topic_generator::operator()() const {
// TODO: figure out why the second checks don't match but the last does
if (has_male_portrait) {
ss << "<img>src='" << male_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
ss << "<img>src='" << male_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img>";
}
if (has_female_portrait) {
ss << "<img>src='" << female_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
ss << "<img>src='" << female_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img>";
}
ss << "\n\n\n";
// Unit Images
ss << "<img>src='" << male_type.image();
ss << "~RC(" << male_type.flag_rgb() << ">red)";
if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
ss << "' box='no'</img>";
if (female_type.image() != male_type.image()) {
ss << "<img>src='" << female_type.image();
ss << "~RC(" << female_type.flag_rgb() << ">red)";
if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
ss << "' box='no'</img>";
}
ss << "\n";
// #endif
// Print cross-references to units that this unit advances from/to.
// Cross reference to the topics containing information about those units.
@ -444,7 +446,6 @@ std::string unit_topic_generator::operator()() const {
ss << make_link(var_name, ref_id);
}
ss << "\n"; //added even if empty, to avoid shifting
// Print the race of the unit, cross-reference it to the respective topic.
const std::string race_id = type_.race_id();
@ -454,7 +455,7 @@ std::string unit_topic_generator::operator()() const {
}
ss << _("Race: ");
ss << make_link(race_name, "..race_" + race_id);
ss << "\n\n";
ss << "\n";
// Print the possible traits of the unit, cross-reference them
// to their respective topics.
@ -493,28 +494,29 @@ std::string unit_topic_generator::operator()() const {
ss << traits_label;
if (line2) {
std::stringstream must_have_count;
must_have_count << " (" << must_have_traits.size() << ") : ";
must_have_count << "\n (" << must_have_traits.size() << ") : ";
std::stringstream random_count;
random_count << " (" << (type_.num_traits() - must_have_traits.size() - must_have_nameless_traits) << ") : ";
int second_line_whitespace = font::pango_line_width(traits_label+must_have_count.str(), normal_font_size)
- font::pango_line_width(random_count.str(), normal_font_size);
// int second_line_whitespace = font::pango_line_width(traits_label+must_have_count.str(), normal_font_size)
// - font::pango_line_width(random_count.str(), normal_font_size);
// This ensures that the second line is justified so that the ':' characters are aligned.
ss << must_have_count.str();
print_trait_list(ss, must_have_traits);
ss << "\n" << jump(second_line_whitespace) << random_count.str();
// ss << "\n" << jump(second_line_whitespace) << random_count.str();
ss << "\n" << random_count.str();
print_trait_list(ss, random_traits);
} else {
ss << ": ";
print_trait_list(ss, must_have_traits);
}
ss << "\n\n";
ss << "\n";
} else {
if (line2) {
ss << _("Traits") << " (" << (type_.num_traits() - must_have_nameless_traits) << ") : ";
print_trait_list(ss, random_traits);
ss << "\n\n";
ss << "\n";
}
}
}
@ -578,33 +580,33 @@ std::string unit_topic_generator::operator()() const {
// TRANSLATORS: This string is used in the help page of a single unit. If the translation
// uses spaces, use non-breaking spaces as appropriate for the target language to prevent
// unpleasant line breaks (issue #3256).
ss << _("HP:") << font::nbsp << type_.hitpoints() << jump(30)
ss << _("HP:") << font::nbsp << type_.hitpoints() << " "
// TRANSLATORS: This string is used in the help page of a single unit. If the translation
// uses spaces, use non-breaking spaces as appropriate for the target language to prevent
// unpleasant line breaks (issue #3256).
<< _("Moves:") << font::nbsp << type_.movement() << jump(30);
<< _("Moves:") << font::nbsp << type_.movement() << " ";
if (type_.vision() != type_.movement()) {
// TRANSLATORS: This string is used in the help page of a single unit. If the translation
// uses spaces, use non-breaking spaces as appropriate for the target language to prevent
// unpleasant line breaks (issue #3256).
ss << _("Vision:") << font::nbsp << type_.vision() << jump(30);
ss << _("Vision:") << font::nbsp << type_.vision() << " ";
}
if (type_.jamming() > 0) {
// TRANSLATORS: This string is used in the help page of a single unit. If the translation
// uses spaces, use non-breaking spaces as appropriate for the target language to prevent
// unpleasant line breaks (issue #3256).
ss << _("Jamming:") << font::nbsp << type_.jamming() << jump(30);
ss << _("Jamming:") << font::nbsp << type_.jamming() << " ";
}
// TRANSLATORS: This string is used in the help page of a single unit. If the translation
// uses spaces, use non-breaking spaces as appropriate for the target language to prevent
// unpleasant line breaks (issue #3256).
ss << _("Cost:") << font::nbsp << type_.cost() << jump(30)
ss << _("Cost:") << font::nbsp << type_.cost() << " "
// TRANSLATORS: This string is used in the help page of a single unit. If the translation
// uses spaces, use non-breaking spaces as appropriate for the target language to prevent
// unpleasant line breaks (issue #3256).
<< _("Alignment:") << font::nbsp
<< make_link(type_.alignment_description(type_.alignment(), type_.genders().front()), "time_of_day")
<< jump(30);
<< " ";
if (type_.can_advance() || type_.modification_advancements()) {
// TRANSLATORS: This string is used in the help page of a single unit. It uses
// non-breaking spaces to prevent unpleasant line breaks (issue #3256). In the
@ -614,21 +616,317 @@ std::string unit_topic_generator::operator()() const {
// Print the detailed description about the unit.
ss << "\n\n" << detailed_description;
if(const auto notes = type_.special_notes(); !notes.empty()) {
ss << "\n\n" << _("Special Notes:") << '\n';
ss << "\n\n" << _("<header>Special Notes</header>") << "\n";
for(const auto& note : notes) {
ss << font::unicode_bullet << " " << note << '\n';
ss << font::unicode_bullet << " <i>" << note << "</i>" << '\n';
}
}
// Print the attacks table
ss << "\n<header>Attacks</header>";
if (!type_.attacks().empty()) {
// Start table
ss << "<table col=6/>";
// Print headers for the table.
ss
<< " " << "<jump/>"
<< _("<b>Name</b>") << "<jump/>"
<< _("<b>Strikes</b>") << "<jump/>"
<< _("<b>Range</b>") << "<jump/>"
<< _("<b>Type</b>") << "<jump/>"
<< _("<b>Special</b>") << "<br/>";
std::stringstream attack_ss;
// Print information about every attack.
for(const attack_type& attack : type_.attacks()) {
std::string lang_weapon = attack.name();
std::string lang_type = string_table["type_" + attack.type()];
// Attack icon
attack_ss << "<img>src='" << attack.icon() << "'</img> <jump/>";
// attack name
attack_ss << lang_weapon << "<jump/>";
// damage x strikes
attack_ss << attack.damage() << font::weapon_numbers_sep << attack.num_attacks()
<< " " << attack.accuracy_parry_description() << "<jump/>";
// range
const std::string range_icon = "icons/profiles/" + attack.range() + "_attack.png~SCALE_INTO(16,16)";
attack_ss << "<img src='" << range_icon << "'/>";
if (attack.min_range() > 1 || attack.max_range() > 1) {
attack_ss << attack.min_range() << "-" << attack.max_range() << ' ';
}
attack_ss << string_table["range_" + attack.range()] << "<jump/>";
// type
const std::string type_icon = "icons/profiles/" + attack.type() + ".png~SCALE_INTO(16,16)";
attack_ss << "<img src='" << type_icon << "'/>";
attack_ss << lang_type << "<jump/>";
// special
std::vector<std::pair<t_string, t_string>> specials = attack.special_tooltips();
if (!specials.empty()) {
std::string lang_special = "";
const std::size_t specials_size = specials.size();
for (std::size_t i = 0; i != specials_size; ++i) {
const std::string ref_id = std::string("weaponspecial_")
+ specials[i].first.base_str();
lang_special = (specials[i].first);
attack_ss << make_link(lang_special, ref_id);
if (i+1 != specials_size) {
attack_ss << ", "; //comma placed before next special
}
}
} else {
attack_ss << "none";
}
attack_ss << "<br/>";
}
ss << attack_ss.str();
ss << "<endtable/>";
}
// Generate the movement type of the unit, with resistance, defense, movement, jamming and vision data updated according to any 'musthave' traits which always apply
movetype movement_type = type_.movement_type();
config::const_child_itors traits = type_.possible_traits();
if (!traits.empty() && type_.num_traits() > 0) {
for (const config & t : traits) {
if (t["availability"].str() == "musthave") {
for (const config & effect : t.child_range("effect")) {
if (!effect.has_child("filter") // If this is musthave but has a unit filter, it might not always apply, so don't apply it in the help.
&& movetype::effects.find(effect["apply_to"].str()) != movetype::effects.end()) {
movement_type.merge(effect, effect["replace"].to_bool());
}
}
}
}
}
// Print the resistance table of the unit.
ss << "\n<header>Resistances</header>";
// Start table
ss << "<table col=2/>";
ss << _("<b>Attack Type</b>") << "<jump/>";
ss << _("<b>Resistance</b>") << "<br/>";
utils::string_map_res dam_tab = movement_type.damage_table();
for(std::pair<std::string, std::string> dam_it : dam_tab) {
int resistance = 100;
try {
resistance -= std::stoi(dam_it.second);
} catch(std::invalid_argument&) {}
std::string resist = std::to_string(resistance) + '%';
const std::size_t pos = resist.find('-');
if (pos != std::string::npos) {
resist.replace(pos, 1, font::unicode_minus);
}
std::string color = unit_helper::resistance_color(resistance);
const std::string lang_type = string_table["type_" + dam_it.first];
const std::string type_icon = "icons/profiles/" + dam_it.first + ".png~SCALE_INTO(16,16)";
ss << "<img src='" << type_icon << "'/>";
ss << lang_type << "<jump/>";
std::stringstream str;
str << "<format>color=\"" << color << "\" text='"<< resist << "'</format>";
ss << str.str() << "<br/>";
}
ss << "<endtable/>";
if (std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data()) {
// Print the terrain modifier table of the unit.
ss << "\n<header>Terrain Modifiers</header>";
ss << "<table col=3/>";
ss << _("<b>Terrain</b>") << "<jump/>";
ss << _("<b>Defense</b>") << "<jump/>";
ss << _("<b>Movement Cost</b>") << "<br/>";
// ss << _("<b>Defense Cap</b>") << "<jump/>";
// ss << _("<b>Vision Cost</b>") << "<jump/>";
// ss << _("<b>Jamming Cost</b>") << "<br/>";
std::set<terrain_movement_info> terrain_moves;
for (t_translation::terrain_code terrain : prefs::get().encountered_terrains()) {
if (terrain == t_translation::FOGGED || terrain == t_translation::VOID_TERRAIN || t_translation::terrain_matches(terrain, t_translation::ALL_OFF_MAP)) {
continue;
}
const terrain_type& info = tdata->get_terrain_info(terrain);
const int moves = movement_type.movement_cost(terrain);
const bool cannot_move = moves > type_.movement();
if (cannot_move && info.hide_if_impassable()) {
continue;
}
if (info.is_indivisible() && info.is_nonnull()) {
terrain_movement_info movement_info =
{
info.name(),
info.id(),
100 - movement_type.defense_modifier(terrain),
moves,
movement_type.vision_cost(terrain),
movement_type.jamming_cost(terrain),
movement_type.get_defense().capped(terrain)
};
terrain_moves.insert(movement_info);
}
}
for(const terrain_movement_info &m : terrain_moves)
{
// std::vector<item> row;
bool high_res = false;
const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
const std::string terrain_image = "icons/terrain/terrain_type_" + m.id + (high_res ? "_30.png" : ".png");
const std::string final_image = tc_base + "~RC(magenta>" + m.id + ")~BLIT(" + terrain_image + ")";
// row.emplace_back("<img>src='" + final_image + "'</img> " +
// make_link(m.name, "..terrain_" + m.id),
// font::pango_line_width(m.name, normal_font_size) + (high_res ? 32 : 16) );
ss << "<img>src='" + final_image + "'</img> " + make_link(m.name, "..terrain_" + m.id) << "<jump/>";
//defense - range: +10 % .. +70 %
// passing false to select the more saturated red-to-green scale
std::string color = game_config::red_to_green(m.defense, false).to_hex_string();
std::stringstream str;
std::stringstream str_unformatted;
str << "<format>color='" << color << "' text='"<< m.defense << "%'</format>";
str_unformatted << m.defense << "%";
// row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
ss << str.str() << "<jump/>";
//movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
str.str(clear_stringstream);
str_unformatted.str(clear_stringstream);
const bool cannot_move = m.movement_cost > type_.movement(); // cannot move in this terrain
double movement_red_to_green = 100.0 - 25.0 * m.movement_cost;
// passing true to select the less saturated red-to-green scale
std::string movement_color = game_config::red_to_green(movement_red_to_green, true).to_hex_string();
str << "<format>color='" << movement_color << "' text='";
// A 5 MP margin; if the movement costs go above
// the unit's max moves + 5, we replace it with dashes.
if(cannot_move && (m.movement_cost > type_.movement() + 5)) {
str_unformatted << font::unicode_figure_dash;
} else if(cannot_move) {
str_unformatted << "(" << m.movement_cost << ")";
} else {
str_unformatted << m.movement_cost;
}
if(m.movement_cost != 0) {
const int movement_hexes_per_turn = type_.movement() / m.movement_cost;
str_unformatted << " ";
for(int i = 0; i < movement_hexes_per_turn; ++i) {
// Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
str_unformatted << "\u2b23\u200b";
}
}
str << str_unformatted.str() << "'</format>";
// row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
ss << str.str() << "<br/>";
//defense cap
// if (has_terrain_defense_caps) {
// str.str(clear_stringstream);
// str_unformatted.str(clear_stringstream);
// if (m.defense_cap) {
// str << "<format>color='"<< color <<"' text='" << m.defense << "%'</format>";
// str_unformatted << m.defense << "%";
// } else {
// str << "<format>color=white text='" << font::unicode_figure_dash << "'</format>";
// str_unformatted << font::unicode_figure_dash;
// }
// row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
// }
//vision
// if (has_vision) {
// str.str(clear_stringstream);
// str_unformatted.str(clear_stringstream);
// const bool cannot_view = m.vision_cost > type_.vision(); // cannot view in this terrain
// double vision_red_to_green = 100.0 - 25.0 * m.vision_cost;
// // passing true to select the less saturated red-to-green scale
// std::string vision_color = game_config::red_to_green(vision_red_to_green, true).to_hex_string();
// str << "<format>color='" << vision_color << "' text='";
// // A 5 MP margin; if the vision costs go above
// // the unit's vision + 5, we replace it with dashes.
// if(cannot_view && (m.vision_cost > type_.vision() + 5)) {
// str_unformatted << font::unicode_figure_dash;
// } else if(cannot_view) {
// str_unformatted << "(" << m.vision_cost << ")";
// } else {
// str_unformatted << m.vision_cost;
// }
// if(m.vision_cost != 0) {
// const int vision_hexes_per_turn = type_.vision() / m.vision_cost;
// str_unformatted << " ";
// for(int i = 0; i < vision_hexes_per_turn; ++i) {
// // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
// str_unformatted << "\u2b23\u200b";
// }
// }
// str << str_unformatted.str() << "'</format>";
// row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
// }
// //jamming
// if (has_jamming) {
// str.str(clear_stringstream);
// str_unformatted.str(clear_stringstream);
// const bool cannot_jam = m.jamming_cost > type_.jamming(); // cannot jam in this terrain
// double jamming_red_to_green = 100.0 - 25.0 * m.jamming_cost;
// // passing true to select the less saturated red-to-green scale
// std::string jamming_color = game_config::red_to_green(jamming_red_to_green, true).to_hex_string();
// str << "<format>color='" << jamming_color << "' text='";
// // A 5 MP margin; if the jamming costs go above
// // the unit's jamming + 5, we replace it with dashes.
// if (cannot_jam && m.jamming_cost > type_.jamming() + 5) {
// str_unformatted << font::unicode_figure_dash;
// } else if(cannot_jam) {
// str_unformatted << "(" << m.jamming_cost << ")";
// } else {
// str_unformatted << m.jamming_cost;
// }
// if(m.jamming_cost != 0) {
// const int jamming_hexes_per_turn = type_.jamming() / m.jamming_cost;
// str_unformatted << " ";
// for(int i = 0; i < jamming_hexes_per_turn; ++i) {
// // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
// str_unformatted << "\u2b23\u200b";
// }
// }
// str << str_unformatted.str() << "'</format>";
// row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
// }
}
ss << "<endtable/>";
}
/*
// Padding for range and damage type icons
const auto padding = 4; // matches the alignment of the terrain rows
// Print the different attacks a unit has, if it has any.
if (!type_.attacks().empty()) {
// Print headers for the table.
ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
<< "'</header>\n\n";
ss << "\n<header>text='" << escape(_("unit help^Attacks"))
<< "'</header>\n";
table_spec table;
std::vector<item> first_row;
@ -932,11 +1230,16 @@ std::string unit_topic_generator::operator()() const {
table.push_back(row);
}
*/
// ss << generate_table(table);
// } else {
// WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.";
// }
// PLAIN_LOG << ss.str();
ss << generate_table(table);
} else {
WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.";
}
return ss.str();
}