pathfind::teleport_map::teleport_map( const std::vector<teleport_group>& groups , const unit& u , const team &viewing_team , const bool see_all , const bool ignore_units) : teleport_map_() , sources_() , targets_() { foreach(const teleport_group& group, groups) { teleport_pair locations; group.get_teleport_pair(locations, u, ignore_units); if (!see_all && !group.always_visible() && viewing_team.is_enemy(u.side())) { teleport_pair filter_locs; foreach(const map_location &loc, locations.first) if(!viewing_team.fogged(loc)) filter_locs.first.insert(loc); foreach(const map_location &loc, locations.second) if(!viewing_team.fogged(loc)) filter_locs.second.insert(loc); locations.first.swap(filter_locs.first); locations.second.swap(filter_locs.second); }
std::set<map_location> get_teleport_locations(const unit &u, const unit_map &units, const team &viewing_team, bool see_all, bool ignore_units) { std::set<map_location> res; if (!u.get_ability_bool("teleport")) return res; const team ¤t_team = (*resources::teams)[u.side() - 1]; const map_location &loc = u.get_location(); foreach (const map_location &l, current_team.villages()) { // This must be a vacant village (or occupied by the unit) // to be able to teleport. if (!see_all && viewing_team.is_enemy(u.side()) && viewing_team.fogged(l)) continue; if (!ignore_units && l != loc && get_visible_unit(units, l, viewing_team, see_all)) continue; res.insert(l); } return res; }
marked_route mark_route(const plain_route &rt, const std::vector<map_location>& waypoints, const unit &u, const team &viewing_team, const unit_map &units, const std::vector<team> &teams, const gamemap &map) { marked_route res; if (rt.steps.empty()) return res; res.steps = rt.steps; int turns = 0; int movement = u.movement_left(); const team& unit_team = teams[u.side()-1]; bool zoc = false; std::vector<map_location>::const_iterator i = rt.steps.begin(), w = waypoints.begin(); // TODO fix the name confusion with waypoints and route.waypoints for (; i !=rt.steps.end(); i++) { bool last_step = (i+1 == rt.steps.end()); // move_cost of the next step is irrelevant for the last step assert(last_step || map.on_board(*(i+1))); const int move_cost = last_step ? 0 : u.movement_cost(map[*(i+1)]); bool capture = false; bool pass_here = false; if (w != waypoints.end() && *i == *w) { w++; pass_here = true; } if (last_step || zoc || move_cost > movement) { // check if we stop an a village and so maybe capture it // if it's an enemy unit and a fogged village, we assume a capture // (if he already owns it, we can't know that) // if it's not an enemy, we can always know if he owns the village bool capture = map.is_village(*i) && ( !unit_team.owns_village(*i) || (viewing_team.is_enemy(u.side()) && viewing_team.fogged(*i)) ); ++turns; bool invisible = u.invisible(*i,units,teams,false); res.waypoints[*i] = marked_route::waypoint(turns, pass_here, zoc, capture, invisible); if (last_step) break; // finished and we used dummy move_cost movement = u.total_movement(); if(move_cost > movement) { return res; //we can't reach destination } } else if (pass_here) { bool invisible = u.invisible(*i,units,teams,false); res.waypoints[*i] = marked_route::waypoint(0, pass_here, zoc, false, invisible); } zoc = enemy_zoc(units, teams, *(i + 1), viewing_team,u.side()) && !u.get_ability_bool("skirmisher", *(i+1)); if (zoc || capture) { movement = 0; } else { movement -= move_cost; } } return res; }
report generate_report(TYPE type, const team &viewing_team, int current_side, int playing_side, const map_location& loc, const map_location& mouseover, const std::set<std::string> &observers, const config& level, bool show_everything) { unit_map& units = *resources::units; gamemap& map = *resources::game_map; std::vector<team>& teams = *resources::teams; const unit *u = NULL; if ((int(type) >= int(UNIT_REPORTS_BEGIN) && int(type) < int(UNIT_REPORTS_END)) || type == POSITION){ u = get_visible_unit(loc, viewing_team, show_everything); if (!u && type != POSITION) { return report(); } } std::ostringstream str; std::ostringstream tooltip; using utils::signed_percent; using font::span_color; switch(type) { case UNIT_NAME: // str << font::SMALL_TEXT << u->name(); str << u->name(); tooltip << _("Name: ") << u->name(); return report(str.str(), "", tooltip.str()); case UNIT_TYPE: { if (!u->packed()) { str << "<245,230,193>" << u->type_name(); } else { str << "<245,230,193>" << u->packee_type()->type_name(); str << "[" << u->type_name() << "]"; } if (game_config::tiny_gui) { str << "(" << hero::adaptability_str2(ftofxp12(u->adaptability_[u->arms()])) << ")"; } tooltip << _("Type: ") << u->type_name(); const std::string help_page = "unit_" + u->type_id(); return report(str.str(), "", tooltip.str(), help_page); } case UNIT_RACE: { str << "<166,146,117>" << u->race()->name(u->gender()); const std::string help_page = "..race_" + u->race()->id(); return report(str.str(), "", tooltip.str(), help_page); } case UNIT_SIDE: { std::string flag_icon = teams[u->side() - 1].flag_icon(); std::string old_rgb = game_config::flag_rgb; std::string new_rgb = team::get_side_color_index(u->side()); std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")"; if(flag_icon.empty()) { flag_icon = game_config::images::flag_icon; } image::locator flag_icon_img(flag_icon, mods); return report("", flag_icon_img, teams[u->side() - 1].current_player()); } case UNIT_LEVEL: { str << u->level(); tooltip << _("Level: ") << "<b>" << u->level() << "</b>\n"; const std::vector<std::string>& adv_to = u->advances_to(); if(adv_to.empty()) { tooltip << _("No advancement"); } else { tooltip << _("Advances to:") << "\n" << "<b>\t" << utils::join(adv_to, "\n\t") << "</b>"; } return report(str.str(), "", tooltip.str()); } case UNIT_AMLA: { report res; typedef std::pair<std::string, std::string> pair_string; foreach(const pair_string& ps, u->amla_icons()) { res.add_image(ps.first,ps.second); } return(res); } case UNIT_TRAITS: { report res; const std::vector<t_string>& traits = u->trait_names(); unsigned int nb = traits.size(); for(unsigned int i = 0; i < nb; ++i) { str << traits[i]; if(i != nb - 1 ) str << ", "; tooltip << _("Trait: ") << "<b>" << traits[i] << "</b>\n"; res.add_text(flush(str), flush(tooltip)); } return res; } case UNIT_STATUS: { report res; if (map.on_board(loc) && u->invisible(loc)) { add_status(res, "misc/invisible.png", N_("invisible: "), N_("This unit is invisible. It cannot be seen or attacked by enemy units.")); } if (u->get_state(unit::STATE_SLOWED)) { add_status(res, "misc/slowed.png", N_("slowed: "), N_("This unit has been slowed. It will only deal half its normal damage when attacking and its movement cost is doubled.")); } if (u->get_state(unit::STATE_BROKEN)) { add_status(res, "misc/broken.png", N_("broken: "), N_("This unit has been broken. It will suffer 1.5 times its normal damage when defending.")); } if (u->get_state(unit::STATE_POISONED)) { add_status(res, "misc/poisoned.png", N_("poisoned: "), N_("This unit is poisoned. It will lose 8 HP every turn until it can seek a cure to the poison in a village or from a friendly unit with the 'cures' ability.\n\nUnits cannot be killed by poison alone. The poison will not reduce it below 1 HP.")); } if (u->get_state(unit::STATE_PETRIFIED)) { add_status(res, "misc/petrified.png", N_("petrified: "), N_("This unit has been petrified. It may not move or attack.")); } return res; } case UNIT_ALIGNMENT: { const std::string &align = unit_type::alignment_description(u->alignment(), u->gender()); const std::string &align_id = unit_type::alignment_id(u->alignment()); int cm = combat_modifier(loc, u->alignment(), u->is_fearless()); str << align << " (" << signed_percent(cm) << ")"; tooltip << _("Alignment: ") << "<b>" << align << "</b>\n" << string_table[align_id + "_description"]; return report(str.str(), "", tooltip.str(), "time_of_day"); } case UNIT_ABILITIES: { report res; const std::vector<std::string> &abilities = u->ability_tooltips(); for(std::vector<std::string>::const_iterator i = abilities.begin(); i != abilities.end(); ++i) { const std::string& name = gettext(i->c_str()); str << name; if(i+2 != abilities.end()) str << ", "; ++i; //FIXME pull out ability's name from description tooltip << _("Ability: ") << *i; const std::string help_page = "ability_" + name; res.add_text(flush(str), flush(tooltip), help_page); } return res; } case UNIT_HP: { str << font::color2markup(u->hp_color()) << u->hitpoints() << '/' << u->max_hitpoints(); return report(str.str(), "", tooltip.str()); } case UNIT_XP: { str << font::color2markup(u->xp_color()) << u->experience() << '/' << u->max_experience(); return report(str.str(), "", tooltip.str()); } case UNIT_ADVANCEMENT_OPTIONS: { report res; typedef std::pair<std::string, std::string> pair_string; foreach(const pair_string& ps, u->advancement_icons()){ res.add_image(ps.first,ps.second); } return res; } case UNIT_WEAPONS: { if (u->second().valid()) { str << u->second().name(); } if (u->third().valid()) { str << " " << u->third().name(); } return report(str.str(), "", tooltip.str()); } case UNIT_IMAGE: { // const std::vector<Uint32>& old_rgb = u->second.team_rgb_range(); // color_range new_rgb = team::get_side_color_range(u->second.side()); report res("", image::locator(u->absolute_image(), u->image_mods()), ""); if (!u->is_artifical() && u->character() != NO_CHARACTER) { res.add_image(unit_types.character(u->character()).image_, ""); res.back().rect.w = 16; res.back().rect.h = 16; } return res; } case TIME_OF_DAY: { time_of_day tod; if (viewing_team.shrouded(mouseover)) { // Don't show time on shrouded tiles. tod = resources::tod_manager->get_time_of_day(); } else if (viewing_team.fogged(mouseover)) { // Don't show illuminated time on fogged tiles. tod = resources::tod_manager->get_time_of_day(mouseover); } else { tod = resources::tod_manager->time_of_day_at(mouseover); } int b = tod.lawful_bonus; int c = tod.liminal_bonus; tooltip << tod.name << '\n' << _("Lawful units: ") << signed_percent(b) << "\n" << _("Neutral units: ") << signed_percent(0) << "\n" << _("Chaotic units: ") << signed_percent(-b); if (tod.liminal_present) tooltip << "\n" << _("Liminal units: ") << signed_percent(c); std::string tod_image = tod.image; if (tod.lawful_bonus_modified > 0) tod_image += "~BRIGHTEN()"; else if (tod.lawful_bonus_modified < 0) tod_image += "~DARKEN()"; if (preferences::flip_time()) tod_image += "~FL(horiz)"; if (game_config::tiny_gui) { return report("", tod_image, ""); } else { return report("", tod_image, tooltip.str(), "time_of_day"); } } case TURN: { str << teams[playing_side-1].name() << "[" << resources::tod_manager->turn() << "]"; int nb = resources::tod_manager->number_of_turns(); if (nb != -1) str << '/' << nb; break; } // For the following status reports, show them in gray text // when it is not the active player's turn. case GOLD: { //Supposes the full/"pathfind" unit map is applied int fake_gold = viewing_team.gold(); if (current_side != playing_side) { str << font::GRAY_TEXT; } else if (fake_gold < 0) { str << font::RED_TEXT; } int cost_exponent = viewing_team.cost_exponent(); int hundred = cost_exponent / 100; str << viewing_team.gold() << "(" << hundred << "." << (cost_exponent - hundred * 100) / 10 << ")"; break; } case VILLAGES: { const team_data data = calculate_team_data(viewing_team,current_side); if (current_side != playing_side) str << font::GRAY_TEXT; str << data.villages << '/'; if (viewing_team.uses_shroud()) { int unshrouded_villages = 0; std::vector<map_location>::const_iterator i = map.villages().begin(); for (; i != map.villages().end(); ++i) { if (!viewing_team.shrouded(*i)) ++unshrouded_villages; } str << unshrouded_villages; } else { str << map.villages().size(); } break; } case UPKEEP: { const team_data data = calculate_team_data(viewing_team,current_side); if (current_side != playing_side) str << font::GRAY_TEXT; str << data.expenses << "(" << data.upkeep << ")"; break; } case EXPENSES: { const team_data data = calculate_team_data(viewing_team,current_side); if (current_side != playing_side) str << font::GRAY_TEXT; str << data.expenses; break; } case INCOME: { team_data data = calculate_team_data(viewing_team, current_side); if (current_side != playing_side) str << font::GRAY_TEXT; else if (data.net_income < 0) str << font::BAD_TEXT; str << data.net_income; break; } case TACTIC: { int tactic_point = viewing_team.tactic_point(); if (current_side != playing_side) { str << font::GRAY_TEXT; } else if (tactic_point >= game_config::max_tactic_point) { str << font::RED_TEXT; } else if (tactic_point > game_config::max_tactic_point * 2 / 3) { str << "<255,255,0>"; } else { str << font::GOOD_TEXT; } str << viewing_team.tactic_point(); break; } case TERRAIN: { if(!map.on_board(mouseover) || viewing_team.shrouded(mouseover)) break; const t_translation::t_terrain terrain = map.get_terrain(mouseover); if (terrain == t_translation::OFF_MAP_USER) break; const t_translation::t_list& underlying = map.underlying_union_terrain(terrain); if(map.is_village(mouseover)) { int owner = village_owner(mouseover, teams) + 1; if(owner == 0 || viewing_team.fogged(mouseover)) { str << map.get_terrain_info(terrain).income_description(); } else if(owner == current_side) { str << map.get_terrain_info(terrain).income_description_own(); } else if(viewing_team.is_enemy(owner)) { str << map.get_terrain_info(terrain).income_description_enemy(); } else { str << map.get_terrain_info(terrain).income_description_ally(); } str << " "; } else { str << map.get_terrain_info(terrain).description(); } if(underlying.size() != 1 || underlying.front() != terrain) { str << " ("; for(t_translation::t_list::const_iterator i = underlying.begin(); i != underlying.end(); ++i) { str << map.get_terrain_info(*i).name(); if(i+1 != underlying.end()) { str << ","; } } str << ")"; } break; } case POSITION: { // coordinate [terrain] resitance if (!map.on_board(mouseover)) { break; } const t_translation::t_terrain terrain = map[mouseover]; if (terrain == t_translation::OFF_MAP_USER) break; str << mouseover; if (game_config::tiny_gui) { str << "\n"; } if (!viewing_team.shrouded(mouseover)) { const t_translation::t_list& underlying = map.underlying_union_terrain(terrain); if (!game_config::tiny_gui) { str << _("Terrains") << ":"; } if (map.is_village(mouseover)) { int owner = village_owner(mouseover, teams) + 1; if(owner == 0 || viewing_team.fogged(mouseover)) { str << map.get_terrain_info(terrain).income_description(); } else if(owner == current_side) { str << map.get_terrain_info(terrain).income_description_own(); } else if(viewing_team.is_enemy(owner)) { str << map.get_terrain_info(terrain).income_description_enemy(); } else { str << map.get_terrain_info(terrain).income_description_ally(); } str << " "; } else { str << map.get_terrain_info(terrain).description(); } if (underlying.size() != 1 || underlying.front() != terrain) { str << " ("; for(t_translation::t_list::const_iterator i = underlying.begin(); i != underlying.end(); ++i) { str << map.get_terrain_info(*i).name(); if(i+1 != underlying.end()) { str << ","; } } str << ")"; } // display ownership std::map<const map_location, int>::const_iterator it = unit_map::economy_areas_.find(mouseover); if (it != unit_map::economy_areas_.end()) { str << "(" << units.city_from_cityno(it->second)->name() << ")"; } else if (terrain == t_translation::TERRAIN_ECONOMY_AREA) { str << "(--)"; } str << " "; if (game_config::tiny_gui) { str << "\n"; } } if (!u) break; if (viewing_team.shrouded(mouseover)) break; int move_cost = u->movement_cost(terrain); int defense = 100 - u->defense_modifier(terrain); if (!game_config::tiny_gui) { str << " "; } if (move_cost < unit_movement_type::UNREACHABLE) { str << "(" << defense << "%," << move_cost << ")"; } else if (mouseover == loc) { str << "(" << defense << "%,-)"; } else { str << "(-,-)"; } break; } case STRATUM: { const hero* rpg_hero = rpg::h; std::string stratum_icon; if (rpg::stratum == hero_stratum_wander) { stratum_icon = "/misc/stratum-wander.png"; } else if (rpg::stratum == hero_stratum_citizen) { stratum_icon = "/misc/stratum-citizen.png"; } else if (rpg::stratum == hero_stratum_mayor) { stratum_icon = "/misc/stratum-mayor.png"; } else if (rpg::stratum == hero_stratum_leader) { stratum_icon = "/misc/stratum-leader.png"; } image::locator stratum_icon_img(stratum_icon); return report("", stratum_icon_img, ""); } case MERITORIOUS: { str << rpg::h->meritorious_; break; } case SIDE_PLAYING: { std::string flag_icon = teams[playing_side-1].flag_icon(); std::string old_rgb = game_config::flag_rgb; std::string new_rgb = team::get_side_color_index(playing_side); std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")"; if(flag_icon.empty()) { flag_icon = game_config::images::flag_icon; } image::locator flag_icon_img(flag_icon, mods); return report("", flag_icon_img, teams[playing_side-1].name()); } case OBSERVERS: { if(observers.empty()) { return report(); } str << _("Observers:") << "\n"; for(std::set<std::string>::const_iterator i = observers.begin(); i != observers.end(); ++i) { str << *i << "\n"; } return report("",game_config::images::observer,str.str()); } #ifdef DISABLE_EDITOR case EDITOR_SELECTED_TERRAIN: case EDITOR_LEFT_BUTTON_FUNCTION: return report(); #else case EDITOR_SELECTED_TERRAIN: { if (editor::selected_terrain.empty()) return report(); else return report(editor::selected_terrain); } case EDITOR_LEFT_BUTTON_FUNCTION: { if (editor::left_button_function.empty()) return report(); else return report(editor::left_button_function); } #endif case REPORT_COUNTDOWN: { int min; int sec; if (viewing_team.countdown_time() > 0){ sec = viewing_team.countdown_time() / 1000; char const *end = naps; if (current_side != playing_side) str << span_color(font::GRAY_COLOR); else if (sec < 60) str << "<span foreground=\"#c80000\">"; else if (sec < 120) str << "<span foreground=\"#c8c800\">"; else end = ""; min = sec / 60; str << min << ":"; sec = sec % 60; if (sec < 10) { str << "0"; } str << sec << end; break; } // Intentional fall-through to REPORT_CLOCK // if the time countdown isn't valid. // If there is no turn time limit, // then we display the clock instead. } case REPORT_CLOCK: { time_t t = std::time(NULL); struct tm *lt = std::localtime(&t); if (lt) { char temp[10]; size_t s = std::strftime(temp, 10, preferences::clock_format().c_str(), lt); if(s>0) { return report(temp); } else { return report(); } } else { return report(); } } default: assert(false); break; } return report(str.str()); }
/** * Clears shroud from a single location. * This also records sighted events for later firing. * * In a few cases, this will also clear corner hexes that otherwise would * not normally get cleared. * @param tm The team whose fog/shroud is affected. * @param loc The location to clear. * @param view_loc The location viewer is assumed at (for sighted events). * @param event_non_loc The unit at this location cannot be sighted * (used to prevent a unit from sighting itself). * @param viewer_id The underlying ID of the unit doing the sighting (for events). * @param check_units If false, there is no checking for an uncovered unit. * @param enemy_count Incremented if an enemy is uncovered. * @param friend_count Incremented if a friend is uncovered. * @param spectator Will be told if a unit is uncovered. * * @return whether or not information was uncovered (i.e. returns true if * the specified location was fogged/ shrouded under shared vision/maps). */ bool shroud_clearer::clear_loc(team &tm, const map_location &loc, const map_location &view_loc, const map_location &event_non_loc, std::size_t viewer_id, bool check_units, std::size_t &enemy_count, std::size_t &friend_count, move_unit_spectator * spectator) { const gamemap &map = resources::gameboard->map(); // This counts as clearing a tile for the return value if it is on the // board and currently fogged under shared vision. (No need to explicitly // check for shrouded since shrouded implies fogged.) bool was_fogged = tm.fogged(loc); bool result = was_fogged && map.on_board(loc); // Clear the border as well as the board, so that the half-hexes // at the edge can also be cleared of fog/shroud. if ( map.on_board_with_border(loc) ) { // Both functions should be executed so don't use || which // uses short-cut evaluation. // (This is different than the return value because shared vision does // not apply here.) if ( tm.clear_shroud(loc) | tm.clear_fog(loc) ) { // If we are near a corner, the corner might also need to be cleared. // This happens at the lower-left corner and at either the upper- or // lower- right corner (depending on the width). // Lower-left corner: if ( loc.x == 0 && loc.y == map.h()-1 ) { const map_location corner(-1, map.h()); tm.clear_shroud(corner); tm.clear_fog(corner); } // Lower-right corner, odd width: else if ( is_odd(map.w()) && loc.x == map.w()-1 && loc.y == map.h()-1 ) { const map_location corner(map.w(), map.h()); tm.clear_shroud(corner); tm.clear_fog(corner); } // Upper-right corner, even width: else if ( is_even(map.w()) && loc.x == map.w()-1 && loc.y == 0) { const map_location corner(map.w(), -1); tm.clear_shroud(corner); tm.clear_fog(corner); } } } // Possible screen invalidation. if ( was_fogged ) { display::get_singleton()->invalidate(loc); // Need to also invalidate adjacent hexes to get rid of the // "fog edge" graphics. adjacent_loc_array_t adjacent; get_adjacent_tiles(loc, adjacent.data()); for (unsigned i = 0; i < adjacent.size(); ++i ) display::get_singleton()->invalidate(adjacent[i]); } // Check for units? if ( result && check_units && loc != event_non_loc ) { // Uncovered a unit? unit_map::const_iterator sight_it = resources::gameboard->find_visible_unit(loc, tm); if ( sight_it.valid() ) { record_sighting(*sight_it, loc, viewer_id, view_loc); // Track this? if ( !sight_it->get_state(unit::STATE_PETRIFIED) ) { if ( tm.is_enemy(sight_it->side()) ) { ++enemy_count; if ( spectator ) spectator->add_seen_enemy(sight_it); } else { ++friend_count; if ( spectator ) spectator->add_seen_friend(sight_it); } } } } return result; }
report generate_report(TYPE type, std::map<reports::TYPE, std::string> report_contents, const team ¤t_team, int current_side, int playing_side, const map_location& loc, const map_location& mouseover, const map_location& displayed_unit_hex, const std::set<std::string> &observers, const config& level, bool show_everything) { unit_map &units = *resources::units; gamemap &map = *resources::game_map; std::vector<team> &teams = *resources::teams; const unit *u = NULL; if((int(type) >= int(UNIT_REPORTS_BEGIN) && int(type) < int(UNIT_REPORTS_END)) || type == POSITION){ u = get_visible_unit(units, displayed_unit_hex, current_team, show_everything); if (!u && type != POSITION) { return report(); } } std::ostringstream str; switch(type) { case UNIT_NAME: str << "<b>" << u->name() << "</b>"; return report(str.str(), "", u->name()); case UNIT_TYPE: str << span_color(font::unit_type_color) << u->type_name() << naps; return report(str.str(), "", u->unit_description()); case UNIT_RACE: str << span_color(font::race_color) << u->race()->name(u->gender()) << naps; break; case UNIT_SIDE: { std::string flag_icon = teams[u->side() - 1].flag_icon(); std::string old_rgb = game_config::flag_rgb; std::string new_rgb = team::get_side_colour_index(u->side()); std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")"; if(flag_icon.empty()) { flag_icon = game_config::flag_icon_image; } image::locator flag_icon_img(flag_icon, mods); return report("", flag_icon_img, teams[u->side() - 1].current_player()); } case UNIT_LEVEL: str << u->level(); break; case UNIT_AMLA: { report res; const std::vector<std::pair<std::string,std::string> > &amla_icons=u->amla_icons(); for(std::vector<std::pair<std::string,std::string> >::const_iterator i=amla_icons.begin();i!=amla_icons.end();i++){ res.add_image(i->first,i->second); } return(res); } case UNIT_TRAITS: return report(u->traits_description(), "", u->modification_description("trait")); case UNIT_STATUS: { report res; if (map.on_board(displayed_unit_hex) && u->invisible(displayed_unit_hex, units, teams)) { add_status(res, "misc/invisible.png", N_("invisible: "), N_("This unit is invisible. It cannot be seen or attacked by enemy units.")); } if (u->get_state(unit::STATE_SLOWED)) { add_status(res, "misc/slowed.png", N_("slowed: "), N_("This unit has been slowed. It will only deal half its normal damage when attacking and its movement cost is doubled.")); } if (u->get_state(unit::STATE_POISONED)) { add_status(res, "misc/poisoned.png", N_("poisoned: "), N_("This unit is poisoned. It will lose 8 HP every turn until it can seek a cure to the poison in a village or from a friendly unit with the 'cures' ability.\n\nUnits cannot be killed by poison alone. The poison will not reduce it below 1 HP.")); } if (u->get_state(unit::STATE_PETRIFIED)) { add_status(res, "misc/petrified.png", N_("petrified: "), N_("This unit has been petrified. It may not move or attack.")); } return res; } case UNIT_ALIGNMENT: { const std::string &align = unit_type::alignment_description(u->alignment(), u->gender()); const std::string &align_id = unit_type::alignment_id(u->alignment()); std::stringstream ss; int cm = combat_modifier(units, displayed_unit_hex, u->alignment(), u->is_fearless()); ss << align << " (" << (cm >= 0 ? "+" : "−") << abs(cm) << "%)"; return report(ss.str(), "", string_table[align_id + "_description"]); } case UNIT_ABILITIES: { report res; const std::vector<std::string> &abilities = u->ability_tooltips(); for(std::vector<std::string>::const_iterator i = abilities.begin(); i != abilities.end(); ++i) { str << gettext(i->c_str()); if(i+2 != abilities.end()) str << ","; ++i; res.add_text(flush(str), *i); } return res; } case UNIT_HP: { std::ostringstream tooltip; str << span_color(u->hp_color()) << u->hitpoints() << '/' << u->max_hitpoints() << naps; std::set<std::string> resistances_table; string_map resistances = u->get_base_resistances(); bool att_def_diff = false; for(string_map::iterator resist = resistances.begin(); resist != resistances.end(); ++resist) { std::ostringstream line; line << gettext(resist->first.c_str()) << ": "; // Some units have different resistances when // attacking or defending. int res_att = 100 - u->resistance_against(resist->first, true, displayed_unit_hex); int res_def = 100 - u->resistance_against(resist->first, false, displayed_unit_hex); if (res_att == res_def) { line << res_def << "%\n"; } else { line << res_att << "% / " << res_def << "%\n"; att_def_diff = true; } resistances_table.insert(line.str()); } tooltip << _("Resistances: "); if (att_def_diff) tooltip << _("(Att / Def)"); tooltip << "\n"; // the STL set will give alphabetical sorting for(std::set<std::string>::iterator line = resistances_table.begin(); line != resistances_table.end(); ++line) { tooltip << (*line); } return report(str.str(), "", tooltip.str()); } case UNIT_XP: { std::ostringstream tooltip; str << span_color(u->xp_color()) << u->experience() << '/' << u->max_experience() << naps; tooltip << _("Experience Modifier: ") << ((level["experience_modifier"] != "") ? level["experience_modifier"] : "100") << "%"; return report(str.str(), "", tooltip.str()); } case UNIT_ADVANCEMENT_OPTIONS: { report res; const std::map<std::string,std::string> &adv_icons = u->advancement_icons(); for(std::map<std::string,std::string>::const_iterator i=adv_icons.begin();i!=adv_icons.end();i++){ res.add_image(i->first,i->second); } return res; } case UNIT_DEFENSE: { const t_translation::t_terrain terrain = map[displayed_unit_hex]; int def = 100 - u->defense_modifier(terrain); SDL_Color color = int_to_color(game_config::red_to_green(def)); str << span_color(color) << def << "%</span>"; break; } case UNIT_MOVES: { float movement_frac = 1.0; if (u->side() == playing_side) { movement_frac = static_cast<float>(u->movement_left()) / std::max(1.0f, static_cast<float>(u->total_movement())); if (movement_frac > 1.0) movement_frac = 1.0; } int grey = 128 + static_cast<int>((255-128) * movement_frac); SDL_Color c = { grey, grey, grey, 0 }; str << span_color(c) << u->movement_left() << '/' << u->total_movement() << naps; break; } case UNIT_WEAPONS: { report res; std::ostringstream tooltip; size_t team_index = u->side() - 1; if(team_index >= teams.size()) { std::cerr << "illegal team index in reporting: " << team_index << "\n"; return res; } foreach (const attack_type &at, u->attacks()) { at.set_specials_context(displayed_unit_hex, map_location(), *u); std::string lang_type = gettext(at.type().c_str()); str << span_color(font::weapon_color); if (u->get_state(unit::STATE_SLOWED)) { str << round_damage(at.damage(), 1, 2) << '-'; } else { str << at.damage() << '-'; } int nattacks = at.num_attacks(); // Compute swarm attacks: unit_ability_list swarm = at.get_specials("swarm"); if(!swarm.empty()) { int swarm_max_attacks = swarm.highest("swarm_attacks_max",nattacks).first; int swarm_min_attacks = swarm.highest("swarm_attacks_min").first; int hitp = u->hitpoints(); int mhitp = u->max_hitpoints(); nattacks = swarm_min_attacks + (swarm_max_attacks - swarm_min_attacks) * hitp / mhitp; } str << nattacks; str << ' ' << at.name() << ' ' << at.accuracy_parry_description(); tooltip << at.name() << "\n"; int effdmg; if (u->get_state(unit::STATE_SLOWED)) { effdmg = round_damage(at.damage(),1,2); } else { effdmg = at.damage(); } tooltip << effdmg << ' ' << _n("tooltip^damage", "damage", effdmg) << ", "; tooltip << nattacks << ' ' << _n("tooltip^attack", "attacks", nattacks); int accuracy = at.accuracy(); if(accuracy) { // Help xgettext with a directive to recognise the string as a non C printf-like string // xgettext:no-c-format tooltip << " " << (accuracy > 0 ? "+" : "") << accuracy << _("tooltip^% accuracy"); } int parry = at.parry(); if(parry) { // xgettext:no-c-format tooltip << " " << (parry > 0 ? "+" : "") << parry << _("tooltip^% parry"); } str << "</span>\n"; res.add_text(flush(str), flush(tooltip)); std::string range = gettext(at.range().c_str()); str << span_color(font::weapon_details_color) << " " << range << "--" << lang_type << "</span>\n"; tooltip << _("weapon range: ") << range <<"\n"; tooltip << _("damage type: ") << lang_type << "\n"; // Find all the unit types on the map, and // show this weapon's bonus against all the different units. // Don't show invisible units, except if they are in our team or allied. std::set<std::string> seen_units; std::map<int,std::vector<std::string> > resistances; for(unit_map::const_iterator u_it = units.begin(); u_it != units.end(); ++u_it) { if(teams[team_index].is_enemy(u_it->second.side()) && !current_team.fogged(u_it->first) && seen_units.count(u_it->second.type_id()) == 0 && ( !current_team.is_enemy(u_it->second.side()) || !u_it->second.invisible(u_it->first,units,teams))) { seen_units.insert(u_it->second.type_id()); int resistance = u_it->second.resistance_against(at, false, u_it->first) - 100; resistances[resistance].push_back(u_it->second.type_name()); } } for(std::map<int,std::vector<std::string> >::reverse_iterator resist = resistances.rbegin(); resist != resistances.rend(); ++resist) { std::sort(resist->second.begin(),resist->second.end()); tooltip << (resist->first >= 0 ? "+" : "") << resist->first << "% " << _("vs") << " "; for(std::vector<std::string>::const_iterator i = resist->second.begin(); i != resist->second.end(); ++i) { if(i != resist->second.begin()) { tooltip << ", "; } tooltip << *i; } tooltip << "\n"; } res.add_text(flush(str), flush(tooltip)); const std::vector<t_string> &specials = at.special_tooltips(); if(! specials.empty()) { for(std::vector<t_string>::const_iterator sp_it = specials.begin(); sp_it != specials.end(); ++sp_it) { str << span_color(font::weapon_details_color) << " " << *sp_it << "</span>\n"; ++sp_it; tooltip << *sp_it << '\n'; } res.add_text(flush(str), flush(tooltip)); } } return res; } case UNIT_IMAGE: { // const std::vector<Uint32>& old_rgb = u->second.team_rgb_range(); // color_range new_rgb = team::get_side_color_range(u->second.side()); return report("", image::locator(u->absolute_image(), u->image_mods()), ""); } case UNIT_PROFILE: return report("", u->profile(), ""); case TIME_OF_DAY: { time_of_day tod = resources::tod_manager->time_of_day_at(units, mouseover, *resources::game_map); const std::string tod_image = tod.image + (preferences::flip_time() ? "~FL(horiz)" : ""); // Don't show illuminated time on fogged/shrouded tiles if (current_team.fogged(mouseover) || current_team.shrouded(mouseover)) { tod = resources::tod_manager->get_time_of_day(false, mouseover); } std::stringstream tooltip; tooltip << tod.name << "\n" << _("Lawful units: ") << (tod.lawful_bonus > 0 ? "+" : "") << tod.lawful_bonus << "%\n" << _("Neutral units: ") << "0%\n" << _("Chaotic units: ") << (tod.lawful_bonus < 0 ? "+" : "") << (tod.lawful_bonus*-1) << "%"; return report("",tod_image,tooltip.str()); } case TURN: { str << resources::tod_manager->turn(); int nb = resources::tod_manager->number_of_turns(); if (nb != -1) str << '/' << nb; break; } // For the following status reports, show them in gray text // when it is not the active player's turn. case GOLD: { char const *end = naps; if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); else if (current_team.gold() < 0) str << span_color(font::BAD_COLOUR); else end = ""; str << current_team.gold() << end; break; } case VILLAGES: { const team_data data = calculate_team_data(current_team,current_side,units); if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); str << data.villages << '/'; if (current_team.uses_shroud()) { int unshrouded_villages = 0; std::vector<map_location>::const_iterator i = map.villages().begin(); for (; i != map.villages().end(); i++) { if (!current_team.shrouded(*i)) unshrouded_villages++; } str << unshrouded_villages; } else { str << map.villages().size(); } if (current_side != playing_side) str << naps; break; } case NUM_UNITS: { if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); str << side_units(units, current_side); if (current_side != playing_side) str << naps; break; } case UPKEEP: { const team_data data = calculate_team_data(current_team,current_side,units); if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); str << data.expenses << " (" << data.upkeep << ")"; if (current_side != playing_side) str << naps; break; } case EXPENSES: { const team_data data = calculate_team_data(current_team,current_side,units); if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); str << data.expenses; if (current_side != playing_side) str << naps; break; } case INCOME: { team_data data = calculate_team_data(current_team, current_side, units); char const *end = naps; if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); else if (data.net_income < 0) str << span_color(font::BAD_COLOUR); else end = ""; str << data.net_income << end; break; } case TERRAIN: { if(!map.on_board(mouseover) || current_team.shrouded(mouseover)) break; const t_translation::t_terrain terrain = map.get_terrain(mouseover); if (terrain == t_translation::OFF_MAP_USER) break; const t_translation::t_list& underlying = map.underlying_union_terrain(terrain); if(map.is_village(mouseover)) { int owner = village_owner(mouseover, teams) + 1; if(owner == 0 || current_team.fogged(mouseover)) { str << map.get_terrain_info(terrain).income_description(); } else if(owner == current_side) { str << map.get_terrain_info(terrain).income_description_own(); } else if(current_team.is_enemy(owner)) { str << map.get_terrain_info(terrain).income_description_enemy(); } else { str << map.get_terrain_info(terrain).income_description_ally(); } str << " "; } else { str << map.get_terrain_info(terrain).description(); } if(underlying.size() != 1 || underlying.front() != terrain) { str << " ("; for(t_translation::t_list::const_iterator i = underlying.begin(); i != underlying.end(); ++i) { str << map.get_terrain_info(*i).name(); if(i+1 != underlying.end()) { str << ","; } } str << ")"; } break; } case POSITION: { if(!map.on_board(mouseover)) { break; } const t_translation::t_terrain terrain = map[mouseover]; if (terrain == t_translation::OFF_MAP_USER) break; str << mouseover; if (!u) break; if(displayed_unit_hex != mouseover && displayed_unit_hex != loc) break; if(current_team.shrouded(mouseover)) break; int move_cost = u->movement_cost(terrain); int defense = 100 - u->defense_modifier(terrain); if(move_cost < unit_movement_type::UNREACHABLE) { str << " (" << defense << "%," << move_cost << ")"; } else if (mouseover == displayed_unit_hex) { str << " (" << defense << "%,-)"; } else { str << " (-)"; } break; } case SIDE_PLAYING: { std::string flag_icon = teams[playing_side-1].flag_icon(); std::string old_rgb = game_config::flag_rgb; std::string new_rgb = team::get_side_colour_index(playing_side); std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")"; if(flag_icon.empty()) { flag_icon = game_config::flag_icon_image; } image::locator flag_icon_img(flag_icon, mods); return report("",flag_icon_img,teams[playing_side-1].current_player()); } case OBSERVERS: { if(observers.empty()) { return report(); } str << _("Observers:") << "\n"; for(std::set<std::string>::const_iterator i = observers.begin(); i != observers.end(); ++i) { str << *i << "\n"; } return report("",game_config::observer_image,str.str()); } case SELECTED_TERRAIN: { std::map<TYPE, std::string>::const_iterator it = report_contents.find(SELECTED_TERRAIN); if (it != report_contents.end()) { return report(it->second); } else { return report(); } } case EDIT_LEFT_BUTTON_FUNCTION: { std::map<TYPE, std::string>::const_iterator it = report_contents.find(EDIT_LEFT_BUTTON_FUNCTION); if (it != report_contents.end()) { return report(it->second); } else { return report(); } } case REPORT_COUNTDOWN: { int min; int sec; if (current_team.countdown_time() > 0){ sec = current_team.countdown_time() / 1000; char const *end = naps; if (current_side != playing_side) str << span_color(font::GRAY_COLOUR); else if (sec < 60) str << "<span foreground=\"#c80000\">"; else if (sec < 120) str << "<span foreground=\"#c8c800\">"; else end = ""; min = sec / 60; str << min << ":"; sec = sec % 60; if (sec < 10) { str << "0"; } str << sec << end; break; } // Intentional fall-through to REPORT_CLOCK // if the time countdown isn't valid. // If there is no turn time limit, // then we display the clock instead. } case REPORT_CLOCK: { time_t t = std::time(NULL); struct tm *lt = std::localtime(&t); if (lt) { char temp[10]; size_t s = std::strftime(temp, 10, preferences::clock_format().c_str(), lt); if(s>0) { return report(temp); } else { return report(); } } else { return report(); } } default: assert(false); break; } return report(str.str()); }