static attack_itors::iterator get_attack_iter(unit& u, attack_ptr atk) { // This is slightly inefficient since it walks the attack list a second time... return std::find_if(u.attacks().begin(), u.attacks().end(), [&atk](const attack_type& atk2) { return &atk2 == atk; }); }
battle_context_unit_stats::battle_context_unit_stats(const unit &u, const map_location& u_loc, int u_attack_num, bool attacking, const unit &opp, const map_location& opp_loc, const attack_type *opp_weapon, const unit_map& units) : weapon(NULL), attack_num(u_attack_num), is_attacker(attacking), is_poisoned(u.get_state(unit::STATE_POISONED)), is_slowed(u.get_state(unit::STATE_SLOWED)), slows(false), drains(false), petrifies(false), plagues(false), poisons(false), backstab_pos(false), swarm(false), firststrike(false), experience(u.experience()), max_experience(u.max_experience()), level(u.level()), rounds(1), hp(0), max_hp(u.max_hitpoints()), chance_to_hit(0), damage(0), slow_damage(0), drain_percent(0), drain_constant(0), num_blows(0), swarm_min(0), swarm_max(0), plague_type() { // Get the current state of the unit. if (attack_num >= 0) { weapon = &u.attacks()[attack_num]; } if(u.hitpoints() < 0) { LOG_CF << "Unit with " << u.hitpoints() << " hitpoints found, set to 0 for damage calculations\n"; hp = 0; } else if(u.hitpoints() > u.max_hitpoints()) { // If a unit has more hp than its maximum, the engine will fail // with an assertion failure due to accessing the prob_matrix // out of bounds. hp = u.max_hitpoints(); } else { hp = u.hitpoints(); } // Get the weapon characteristics, if any. if (weapon) { weapon->set_specials_context(u_loc, opp_loc, attacking, opp_weapon); if (opp_weapon) opp_weapon->set_specials_context(opp_loc, u_loc, !attacking, weapon); slows = weapon->get_special_bool("slow"); drains = !opp.get_state("undrainable") && weapon->get_special_bool("drains"); petrifies = weapon->get_special_bool("petrifies"); poisons = !opp.get_state("unpoisonable") && weapon->get_special_bool("poison") && !opp.get_state(unit::STATE_POISONED); backstab_pos = is_attacker && backstab_check(u_loc, opp_loc, units, *resources::teams); rounds = weapon->get_specials("berserk").highest("value", 1).first; firststrike = weapon->get_special_bool("firststrike"); // Handle plague. unit_ability_list plague_specials = weapon->get_specials("plague"); plagues = !opp.get_state("unplagueable") && !plague_specials.empty() && strcmp(opp.undead_variation().c_str(), "null") && !resources::game_map->is_village(opp_loc); if (plagues) { plague_type = (*plague_specials.front().first)["type"].str(); if (plague_type.empty()) plague_type = u.type().base_id(); } // Compute chance to hit. chance_to_hit = opp.defense_modifier( resources::game_map->get_terrain(opp_loc)) + weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0); if(chance_to_hit > 100) { chance_to_hit = 100; } unit_ability_list cth_specials = weapon->get_specials("chance_to_hit"); unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos); chance_to_hit = cth_effects.get_composite_value(); // Compute base damage done with the weapon. int base_damage = weapon->modified_damage(backstab_pos); // Get the damage multiplier applied to the base damage of the weapon. int damage_multiplier = 100; // Time of day bonus. damage_multiplier += combat_modifier(u_loc, u.alignment(), u.is_fearless()); // Leadership bonus. int leader_bonus = 0; if (under_leadership(units, u_loc, &leader_bonus).valid()) damage_multiplier += leader_bonus; // Resistance modifier. damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc); // Compute both the normal and slowed damage. damage = round_damage(base_damage, damage_multiplier, 10000); slow_damage = round_damage(base_damage, damage_multiplier, 20000); if (is_slowed) damage = slow_damage; // Compute drain amounts only if draining is possible. if(drains) { unit_ability_list drain_specials = weapon->get_specials("drains"); // Compute the drain percent (with 50% as the base for backward compatibility) unit_abilities::effect drain_percent_effects(drain_specials, 50, backstab_pos); drain_percent = drain_percent_effects.get_composite_value(); } // Add heal_on_hit (the drain constant) unit_ability_list heal_on_hit_specials = weapon->get_specials("heal_on_hit"); unit_abilities::effect heal_on_hit_effects(heal_on_hit_specials, 0, backstab_pos); drain_constant += heal_on_hit_effects.get_composite_value(); drains = drain_constant || drain_percent; // Compute the number of blows and handle swarm. weapon->modified_attacks(backstab_pos, swarm_min, swarm_max); swarm = swarm_min != swarm_max; num_blows = calc_blows(hp); } }
/** @todo FIXME: Hand previous defender unit in here. */ int battle_context::choose_defender_weapon(const unit &attacker, const unit &defender, unsigned attacker_weapon, const unit_map& units, const map_location& attacker_loc, const map_location& defender_loc, const combatant *prev_def) { VALIDATE(attacker_weapon < attacker.attacks().size(), _("An invalid attacker weapon got selected.")); const attack_type &att = attacker.attacks()[attacker_weapon]; std::vector<unsigned int> choices; // What options does defender have? unsigned int i; for (i = 0; i < defender.attacks().size(); ++i) { const attack_type &def = defender.attacks()[i]; if (def.range() == att.range() && def.defense_weight() > 0) { choices.push_back(i); } } if (choices.empty()) return -1; if (choices.size() == 1) return choices[0]; // Multiple options: // First pass : get the best weight and the minimum simple rating for this weight. // simple rating = number of blows * damage per blows (resistance taken in account) * cth * weight // Elligible attacks for defense should have a simple rating greater or equal to this weight. double max_weight = 0.0; int min_rating = 0; for (i = 0; i < choices.size(); ++i) { const attack_type &def = defender.attacks()[choices[i]]; if (def.defense_weight() >= max_weight) { max_weight = def.defense_weight(); const battle_context_unit_stats def_stats(defender, defender_loc, choices[i], false, attacker, attacker_loc, &att, units); int rating = static_cast<int>(def_stats.num_blows * def_stats.damage * def_stats.chance_to_hit * def.defense_weight()); if (def.defense_weight() > max_weight || rating < min_rating ) { min_rating = rating; } } } // Multiple options: simulate them, save best. for (i = 0; i < choices.size(); ++i) { const attack_type &def = defender.attacks()[choices[i]]; battle_context_unit_stats *att_stats = new battle_context_unit_stats(attacker, attacker_loc, attacker_weapon, true, defender, defender_loc, &def, units); battle_context_unit_stats *def_stats = new battle_context_unit_stats(defender, defender_loc, choices[i], false, attacker, attacker_loc, &att, units); combatant *att_comb = new combatant(*att_stats); combatant *def_comb = new combatant(*def_stats, prev_def); att_comb->fight(*def_comb); int simple_rating = static_cast<int>(def_stats->num_blows * def_stats->damage * def_stats->chance_to_hit * def.defense_weight()); if (simple_rating >= min_rating && ( !attacker_combatant_ || better_combat(*def_comb, *att_comb, *defender_combatant_, *attacker_combatant_, 1.0) ) ) { delete attacker_combatant_; delete defender_combatant_; delete attacker_stats_; delete defender_stats_; attacker_combatant_ = att_comb; defender_combatant_ = def_comb; attacker_stats_ = att_stats; defender_stats_ = def_stats; } else { delete att_comb; delete def_comb; delete att_stats; delete def_stats; } } return defender_stats_->attack_num; }
int battle_context::choose_attacker_weapon(const unit &attacker, const unit &defender, const unit_map& units, const map_location& attacker_loc, const map_location& defender_loc, double harm_weight, int *defender_weapon, const combatant *prev_def) { std::vector<unsigned int> choices; // What options does attacker have? unsigned int i; for (i = 0; i < attacker.attacks().size(); ++i) { const attack_type &att = attacker.attacks()[i]; if (att.attack_weight() > 0) { choices.push_back(i); } } if (choices.empty()) return -1; if (choices.size() == 1) { *defender_weapon = choose_defender_weapon(attacker, defender, choices[0], units, attacker_loc, defender_loc, prev_def); return choices[0]; } // Multiple options: simulate them, save best. battle_context_unit_stats *best_att_stats = NULL, *best_def_stats = NULL; combatant *best_att_comb = NULL, *best_def_comb = NULL; for (i = 0; i < choices.size(); ++i) { const attack_type &att = attacker.attacks()[choices[i]]; int def_weapon = choose_defender_weapon(attacker, defender, choices[i], units, attacker_loc, defender_loc, prev_def); // If that didn't simulate, do so now. if (!attacker_combatant_) { const attack_type *def = NULL; if (def_weapon >= 0) { def = &defender.attacks()[def_weapon]; } attacker_stats_ = new battle_context_unit_stats(attacker, attacker_loc, choices[i], true, defender, defender_loc, def, units); defender_stats_ = new battle_context_unit_stats(defender, defender_loc, def_weapon, false, attacker, attacker_loc, &att, units); attacker_combatant_ = new combatant(*attacker_stats_); defender_combatant_ = new combatant(*defender_stats_, prev_def); attacker_combatant_->fight(*defender_combatant_); } if (!best_att_comb || better_combat(*attacker_combatant_, *defender_combatant_, *best_att_comb, *best_def_comb, harm_weight)) { delete best_att_comb; delete best_def_comb; delete best_att_stats; delete best_def_stats; best_att_comb = attacker_combatant_; best_def_comb = defender_combatant_; best_att_stats = attacker_stats_; best_def_stats = defender_stats_; } else { delete attacker_combatant_; delete defender_combatant_; delete attacker_stats_; delete defender_stats_; } attacker_combatant_ = NULL; defender_combatant_ = NULL; attacker_stats_ = NULL; defender_stats_ = NULL; } attacker_combatant_ = best_att_comb; defender_combatant_ = best_def_comb; attacker_stats_ = best_att_stats; defender_stats_ = best_def_stats; *defender_weapon = defender_stats_->attack_num; return attacker_stats_->attack_num; }
bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_location& loc, const unit* u2) const { if (!vcfg["name"].blank() && vcfg["name"].t_str() != u.name()) { return false; } if (!vcfg["id"].empty()) { std::vector<std::string> id_list = utils::split(vcfg["id"]); if (std::find(id_list.begin(), id_list.end(), u.id()) == id_list.end()) { return false; } } // Allow 'speaker' as an alternative to id, since people use it so often if (!vcfg["speaker"].blank() && vcfg["speaker"].str() != u.id()) { return false; } if (vcfg.has_child("filter_location")) { if (vcfg.count_children("filter_location") > 1) { FAIL("Encountered multiple [filter_location] children of a standard unit filter. " "This is not currently supported and in all versions of wesnoth would have " "resulted in the later children being ignored. You must use [and] or similar " "to achieve the desired result."); } terrain_filter filt(vcfg.child("filter_location"), &fc_, use_flat_tod_); if (!filt.match(loc)) { return false; } } if(vcfg.has_child("filter_side")) { if (vcfg.count_children("filter_side") > 1) { FAIL("Encountered multiple [filter_side] children of a standard unit filter. " "This is not currently supported and in all versions of wesnoth would have " "resulted in the later children being ignored. You must use [and] or similar " "to achieve the desired result."); } side_filter filt(vcfg.child("filter_side"), &fc_); if(!filt.match(u.side())) return false; } // Also allow filtering on location ranges outside of the location filter if (!vcfg["x"].blank() || !vcfg["y"].blank()){ if(vcfg["x"] == "recall" && vcfg["y"] == "recall") { //locations on the map are considered to not be on a recall list if (fc_.get_disp_context().map().on_board(loc)) { return false; } } else if(vcfg["x"].empty() && vcfg["y"].empty()) { return false; } else if(!loc.matches_range(vcfg["x"], vcfg["y"])) { return false; } } // The type could be a comma separated list of types if (!vcfg["type"].empty()) { std::vector<std::string> types = utils::split(vcfg["type"]); if (std::find(types.begin(), types.end(), u.type_id()) == types.end()) { return false; } } // Shorthand for all advancements of a given type if (!vcfg["type_tree"].empty()) { std::set<std::string> types; for(const std::string type : utils::split(vcfg["type_tree"])) { if(types.count(type)) { continue; } if(const unit_type* ut = unit_types.find(type)) { const auto& tree = ut->advancement_tree(); types.insert(tree.begin(), tree.end()); types.insert(type); } } if(types.find(u.type_id()) == types.end()) { return false; } } // The variation_type could be a comma separated list of types if (!vcfg["variation"].empty()) { std::vector<std::string> types = utils::split(vcfg["variation"]); if (std::find(types.begin(), types.end(), u.variation()) == types.end()) { return false; } } // The has_variation_type could be a comma separated list of types if (!vcfg["has_variation"].empty()) { bool match = false; // If this unit is a variation itself then search in the base unit's variations. const unit_type* const type = u.variation().empty() ? &u.type() : unit_types.find(u.type().base_id()); assert(type); for (const std::string& variation_id : utils::split(vcfg["has_variation"])) { if (type->has_variation(variation_id)) { match = true; break; } } if (!match) return false; } if (!vcfg["ability"].empty()) { bool match = false; for (const std::string& ability_id : utils::split(vcfg["ability"])) { if (u.has_ability_by_id(ability_id)) { match = true; break; } } if (!match) return false; } if (!vcfg["race"].empty()) { std::vector<std::string> races = utils::split(vcfg["race"]); if (std::find(races.begin(), races.end(), u.race()->id()) == races.end()) { return false; } } if (!vcfg["gender"].blank() && string_gender(vcfg["gender"]) != u.gender()) { return false; } if (!vcfg["side"].empty() && vcfg["side"].to_int(-999) != u.side()) { std::vector<std::string> sides = utils::split(vcfg["side"]); const std::string u_side = std::to_string(u.side()); if (std::find(sides.begin(), sides.end(), u_side) == sides.end()) { return false; } } // handle statuses list if (!vcfg["status"].empty()) { bool status_found = false; for (const std::string status : utils::split(vcfg["status"])) { if(u.get_state(status)) { status_found = true; break; } } if(!status_found) { return false; } } if (vcfg.has_child("has_attack")) { const vconfig& weap_filter = vcfg.child("has_attack"); bool has_weapon = false; for(const attack_type& a : u.attacks()) { if(a.matches_filter(weap_filter.get_parsed_config())) { has_weapon = true; break; } } if(!has_weapon) { return false; } } else if (!vcfg["has_weapon"].blank()) { std::string weapon = vcfg["has_weapon"]; bool has_weapon = false; for(const attack_type& a : u.attacks()) { if(a.id() == weapon) { has_weapon = true; break; } } if(!has_weapon) { return false; } } if (!vcfg["role"].blank() && vcfg["role"].str() != u.get_role()) { return false; } if (!vcfg["ai_special"].blank() && ((vcfg["ai_special"].str() == "guardian") != u.get_state(unit::STATE_GUARDIAN))) { return false; } if (!vcfg["canrecruit"].blank() && vcfg["canrecruit"].to_bool() != u.can_recruit()) { return false; } if (!vcfg["recall_cost"].blank() && vcfg["recall_cost"].to_int(-1) != u.recall_cost()) { return false; } if (!vcfg["level"].blank() && vcfg["level"].to_int(-1) != u.level()) { return false; } if (!vcfg["defense"].blank() && vcfg["defense"].to_int(-1) != u.defense_modifier(fc_.get_disp_context().map().get_terrain(loc))) { return false; } if (!vcfg["movement_cost"].blank() && vcfg["movement_cost"].to_int(-1) != u.movement_cost(fc_.get_disp_context().map().get_terrain(loc))) { return false; } // Now start with the new WML based comparison. // If a key is in the unit and in the filter, they should match // filter only => not for us // unit only => not filtered config unit_cfg; // No point in serializing the unit once for each [filter_wml]! for (const vconfig& wmlcfg : vcfg.get_children("filter_wml")) { config fwml = wmlcfg.get_parsed_config(); /* Check if the filter only cares about variables. If so, no need to serialize the whole unit. */ config::all_children_itors ci = fwml.all_children_range(); if (fwml.all_children_count() == 1 && fwml.attribute_count() == 1 && ci.front().key == "variables") { if (!u.variables().matches(ci.front().cfg)) return false; } else { if (unit_cfg.empty()) u.write(unit_cfg); if (!unit_cfg.matches(fwml)) return false; } } for (const vconfig& vision : vcfg.get_children("filter_vision")) { std::set<int> viewers; // Use standard side filter side_filter ssf(vision, &fc_); std::vector<int> sides = ssf.get_teams(); viewers.insert(sides.begin(), sides.end()); bool found = false; for (const int viewer : viewers) { bool fogged = fc_.get_disp_context().teams()[viewer - 1].fogged(loc); bool hiding = u.invisible(loc, fc_.get_disp_context()); bool unit_hidden = fogged || hiding; if (vision["visible"].to_bool(true) != unit_hidden) { found = true; break; } } if (!found) {return false;} } if (vcfg.has_child("filter_adjacent")) { const unit_map& units = fc_.get_disp_context().units(); map_location adjacent[6]; get_adjacent_tiles(loc, adjacent); for (const vconfig& adj_cfg : vcfg.get_children("filter_adjacent")) { int match_count=0; unit_filter filt(adj_cfg, &fc_, use_flat_tod_); config::attribute_value i_adjacent = adj_cfg["adjacent"]; std::vector<map_location::DIRECTION> dirs; if (i_adjacent.blank()) { dirs = map_location::default_dirs(); } else { dirs = map_location::parse_directions(i_adjacent); } std::vector<map_location::DIRECTION>::const_iterator j, j_end = dirs.end(); for (j = dirs.begin(); j != j_end; ++j) { unit_map::const_iterator unit_itor = units.find(adjacent[*j]); if (unit_itor == units.end() || !filt(*unit_itor, u)) { continue; } boost::optional<bool> is_enemy; if (!adj_cfg["is_enemy"].blank()) { is_enemy = adj_cfg["is_enemy"].to_bool(); } if (!is_enemy || *is_enemy == fc_.get_disp_context().teams()[u.side() - 1].is_enemy(unit_itor->side())) { ++match_count; } } static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-6"); config::attribute_value i_count = adj_cfg["count"]; if(!in_ranges(match_count, !i_count.blank() ? utils::parse_ranges(i_count) : default_counts)) { return false; } } } if (!vcfg["find_in"].blank()) { // Allow filtering by searching a stored variable of units if (const game_data * gd = fc_.get_game_data()) { try { variable_access_const vi = gd->get_variable_access_read(vcfg["find_in"]); bool found_id = false; for (const config& c : vi.as_array()) { if(c["id"] == u.id()) found_id = true; } if(!found_id) { return false; } } catch(const invalid_variablename_exception&) { return false; } } } if (!vcfg["formula"].blank()) { try { const unit_callable main(loc,u); game_logic::map_formula_callable callable(&main); if (u2) { std::shared_ptr<unit_callable> secondary(new unit_callable(*u2)); callable.add("other", variant(secondary.get())); // It's not destroyed upon scope exit because the variant holds a reference } const game_logic::formula form(vcfg["formula"]); if(!form.evaluate(callable).as_bool()) { return false; } return true; } catch(game_logic::formula_error& e) { lg::wml_error() << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n"; // Formulae with syntax errors match nothing return false; } } if (!vcfg["lua_function"].blank()) { if (game_lua_kernel * lk = fc_.get_lua_kernel()) { bool b = lk->run_filter(vcfg["lua_function"].str().c_str(), u); if (!b) return false; } } return true; }
int battle_context::choose_attacker_weapon(const unit& attacker, const unit& defender, const unit_map& units, const map_location& attacker_loc, const map_location& defender_loc, double harm_weight, int* defender_weapon, const combatant* prev_def) { std::vector<unsigned int> choices; // What options does attacker have? unsigned int i; for(i = 0; i < attacker.attacks().size(); ++i) { const attack_type& att = attacker.attacks()[i]; if(att.attack_weight() > 0) { choices.push_back(i); } } if(choices.empty()) { return -1; } if(choices.size() == 1) { *defender_weapon = choose_defender_weapon(attacker, defender, choices[0], units, attacker_loc, defender_loc, prev_def); const_attack_ptr def_weapon = *defender_weapon >= 0 ? defender.attacks()[*defender_weapon].shared_from_this() : nullptr; attacker_stats_.reset(new battle_context_unit_stats( attacker, attacker_loc, choices[0], true, defender, defender_loc, def_weapon, units)); if(attacker_stats_->disable) { return -1; } const attack_type& att = attacker.attacks()[choices[0]]; defender_stats_.reset(new battle_context_unit_stats( defender, defender_loc, *defender_weapon, false, attacker, attacker_loc, att.shared_from_this(), units)); return choices[0]; } // Multiple options: simulate them, save best. std::unique_ptr<battle_context_unit_stats> best_att_stats(nullptr); std::unique_ptr<battle_context_unit_stats> best_def_stats(nullptr); std::unique_ptr<combatant> best_att_comb(nullptr); std::unique_ptr<combatant> best_def_comb(nullptr); for(i = 0; i < choices.size(); ++i) { const attack_type& att = attacker.attacks()[choices[i]]; int def_weapon = choose_defender_weapon(attacker, defender, choices[i], units, attacker_loc, defender_loc, prev_def); // If that didn't simulate, do so now. if(!attacker_combatant_) { const_attack_ptr def = nullptr; if(def_weapon >= 0) { def = defender.attacks()[def_weapon].shared_from_this(); } attacker_stats_.reset(new battle_context_unit_stats( attacker, attacker_loc, choices[i], true, defender, defender_loc, def, units)); if(attacker_stats_->disable) { continue; } defender_stats_.reset(new battle_context_unit_stats( defender, defender_loc, def_weapon, false, attacker, attacker_loc, att.shared_from_this(), units)); attacker_combatant_.reset(new combatant(*attacker_stats_)); defender_combatant_.reset(new combatant(*defender_stats_, prev_def)); attacker_combatant_->fight(*defender_combatant_); } else { if(attacker_stats_ != nullptr && attacker_stats_->disable) { continue; } } if(!best_att_comb || better_combat(*attacker_combatant_, *defender_combatant_, *best_att_comb, *best_def_comb, harm_weight) ) { best_att_comb = std::move(attacker_combatant_); best_def_comb = std::move(defender_combatant_); best_att_stats = std::move(attacker_stats_); best_def_stats = std::move(defender_stats_); } attacker_combatant_.reset(); defender_combatant_.reset(); attacker_stats_.reset(); defender_stats_.reset(); } attacker_combatant_ = std::move(best_att_comb); defender_combatant_ = std::move(best_def_comb); attacker_stats_ = std::move(best_att_stats); defender_stats_ = std::move(best_def_stats); // These currently mean the same thing, but assumptions like that have been broken before if(!defender_stats_ || !attacker_stats_) { return -1; } *defender_weapon = defender_stats_->attack_num; return attacker_stats_->attack_num; }
void tunit_preview_pane::set_displayed_unit(const unit& u) { // Sets the current type id for the profile button callback to use current_type_ = u.type_id(); if(icon_type_) { std::string mods = u.image_mods(); if(u.can_recruit()) { mods += "~BLIT(" + unit::leader_crown() + ")"; } for(const std::string& overlay : u.overlays()) { mods += "~BLIT(" + overlay + ")"; } mods += "~SCALE_INTO_SHARP(144,144)" + image_mods_; icon_type_->set_label(u.absolute_image() + mods); } if(label_name_) { std::string name; if(!u.name().empty()) { name = "<span size='large'>" + u.name() + "</span>" + "\n" + "<small><span color='#a69275'>" + u.type_name() + "</span></small>"; } else { name = "<span size='large'>" + u.type_name() + "</span>\n"; } label_name_->set_label(name); label_name_->set_use_markup(true); } if(label_level_) { std::string l_str = vgettext("Lvl $lvl", {{"lvl", std::to_string(u.level())}}); label_level_->set_label("<b>" + l_str + "</b>"); label_level_->set_use_markup(true); } if(icon_race_) { icon_race_->set_label("icons/unit-groups/race_" + u.race()->id() + "_30.png"); icon_race_->set_tooltip(u.race()->name(u.gender())); } if(icon_alignment_) { const std::string& alignment_name = u.alignment().to_string(); icon_alignment_->set_label("icons/alignments/alignment_" + alignment_name + "_30.png"); icon_alignment_->set_tooltip(unit_type::alignment_description( u.alignment(), u.gender())); } if(label_details_minimal_) { std::stringstream str; const std::string name = "<span size='large'>" + (!u.name().empty() ? u.name() : " ") + "</span>"; str << name << "\n"; str << "<span color='#a69275'>" << u.type_name() << "</span>" << "\n"; str << "Lvl " << u.level() << "\n"; str << u.alignment() << "\n"; str << utils::join(u.trait_names(), ", ") << "\n"; str << font::span_color(u.hp_color()) << _("HP: ") << u.hitpoints() << "/" << u.max_hitpoints() << "</span>" << "\n"; str << font::span_color(u.xp_color()) << _("XP: ") << u.experience() << "/" << u.max_experience() << "</span>"; label_details_minimal_->set_label(str.str()); label_details_minimal_->set_use_markup(true); } if(tree_details_) { std::stringstream str; str << "<small>"; str << font::span_color(u.hp_color()) << "<b>" << _("HP: ") << "</b>" << u.hitpoints() << "/" << u.max_hitpoints() << "</span>" << " | "; str << font::span_color(u.xp_color()) << "<b>" << _("XP: ") << "</b>" << u.experience() << "/" << u.max_experience() << "</span>" << " | "; str << "<b>" << _("MP: ") << "</b>" << u.movement_left() << "/" << u.total_movement(); str << "</small>"; tree_details_->clear(); add_name_tree_node( tree_details_->get_root_node(), "item", str.str() ); if (!u.trait_names().empty()) { auto& header_node = add_name_tree_node( tree_details_->get_root_node(), "header", "<b>" + _("Traits") + "</b>" ); assert(u.trait_names().size() == u.trait_descriptions().size()); for (size_t i = 0; i < u.trait_names().size(); ++i) { add_name_tree_node( header_node, "item", u.trait_names()[i], u.trait_descriptions()[i] ); } } if (!u.get_ability_list().empty()) { auto& header_node = add_name_tree_node( tree_details_->get_root_node(), "header", "<b>" + _("Abilities") + "</b>" ); for (const auto& ab : u.ability_tooltips()) { add_name_tree_node( header_node, "item", std::get<1>(ab), std::get<2>(ab) ); } } print_attack_details(u.attacks(), tree_details_->get_root_node()); } }