generate_report.cpp

Go to the documentation of this file.
00001 /* $Id: generate_report.cpp 26473 2008-05-09 01:41:19Z alink $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file generate_report.cpp
00016 //! Formatted output of various stats about units and the game.
00017 //! Used for the right sidebar and the top line of the main game-display.
00018 
00019 #include "global.hpp"
00020 
00021 #include "actions.hpp"
00022 #include "game_config.hpp"
00023 #include "gamestatus.hpp"
00024 #include "gettext.hpp"
00025 #include "language.hpp"
00026 #include "marked-up_text.hpp"
00027 #include "reports.hpp"
00028 #include "game_preferences.hpp"
00029 
00030 #include <cassert>
00031 #include <ctime>
00032 #include <map>
00033 #include <set>
00034 #include <sstream>
00035 
00036 namespace reports {
00037 
00038 report generate_report(TYPE type,
00039                        std::map<reports::TYPE, std::string> report_contents,
00040                        const gamemap& map, unit_map& units,
00041                        const std::vector<team>& teams, const team& current_team,
00042                        unsigned int current_side, unsigned int playing_side,
00043                        const gamemap::location& loc, const gamemap::location& mouseover, const gamemap::location& displayed_unit_hex,
00044                        const gamestatus& status, const std::set<std::string>& observers,
00045                        const config& level)
00046 {
00047     unit_map::iterator u = units.end();
00048 
00049     if((int(type) >= int(UNIT_REPORTS_BEGIN) && int(type) < int(UNIT_REPORTS_END)) || type == POSITION) {
00050 
00051         u = find_visible_unit(units,displayed_unit_hex,
00052                       map,
00053                       teams,current_team);
00054         if(u == units.end() && type != POSITION) {
00055             return report();
00056         }
00057     }
00058 
00059     std::stringstream str;
00060 
00061     switch(type) {
00062     case UNIT_NAME:
00063         return report(u->second.name(),"",u->second.name());
00064     case UNIT_TYPE:
00065             return report(u->second.type_name(),"",u->second.unit_description());
00066     case UNIT_RACE:
00067             return report(u->second.race()->name(u->second.gender()));
00068     case UNIT_SIDE: {
00069         std::string flag_icon = teams[u->second.side()-1].flag_icon();
00070         std::string old_rgb = game_config::flag_rgb;
00071         std::string new_rgb = team::get_side_colour_index(u->second.side());
00072         std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
00073 
00074         if(flag_icon.empty()) {
00075             flag_icon = game_config::flag_icon_image;
00076         }
00077 
00078         image::locator flag_icon_img(flag_icon, mods);
00079         return report("",flag_icon_img,teams[u->second.side()-1].current_player());
00080     }
00081     case UNIT_LEVEL:
00082         str << u->second.level();
00083         break;
00084     case UNIT_AMLA: {
00085       report res;
00086       const std::vector<std::pair<std::string,std::string> > & amla_icons=u->second.amla_icons();
00087       for(std::vector<std::pair<std::string,std::string> >::const_iterator i=amla_icons.begin();i!=amla_icons.end();i++){
00088         res.add_image(i->first,i->second);
00089       }
00090       return(res);
00091     }
00092     case UNIT_TRAITS:
00093         return report(u->second.traits_description(),"",u->second.modification_description("trait"));
00094     case UNIT_STATUS: {
00095         std::stringstream unit_status;
00096         std::stringstream tooltip;
00097         report res;
00098 
00099         if(map.on_board(u->first) && u->second.invisible(u->first,units,teams))
00100         {
00101             unit_status << "misc/invisible.png";
00102             tooltip << _("invisible: ") << _("This unit is invisible. It cannot be seen or attacked by enemy units.");
00103             res.add_image(unit_status,tooltip);
00104         }
00105         if(utils::string_bool(u->second.get_state("slowed"))) {
00106             unit_status << "misc/slowed.png";
00107             tooltip << _("slowed: ") << _("This unit has been slowed. It will only deal half its normal damage when attacking and its movement cost is doubled.");
00108             res.add_image(unit_status,tooltip);
00109         }
00110         if(utils::string_bool(u->second.get_state("poisoned"))) {
00111             unit_status << "misc/poisoned.png";
00112             tooltip << _("poisoned: ") << _("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\
00113 \n\
00114 Units cannot be killed by poison alone. The poison will not reduce it below 1 HP.");
00115             res.add_image(unit_status,tooltip);
00116         }
00117         if(utils::string_bool(u->second.get_state("stoned"))) {
00118             unit_status << "misc/stone.png";
00119             tooltip << _("stone: ") << _("This unit has been turned to stone. It may not move or attack.");
00120             res.add_image(unit_status,tooltip);
00121         }
00122 
00123         return res;
00124     }
00125     case UNIT_ALIGNMENT: {
00126         const std::string& align = unit_type::alignment_description(u->second.alignment());
00127         const std::string& align_id = unit_type::alignment_id(u->second.alignment());
00128         return report(align, "", string_table[align_id + "_description"]);
00129     }
00130     case UNIT_ABILITIES: {
00131         report res;
00132         std::stringstream tooltip;
00133         const std::vector<std::string>& abilities = u->second.ability_tooltips(u->first);
00134         for(std::vector<std::string>::const_iterator i = abilities.begin(); i != abilities.end(); ++i) {
00135             str << gettext(i->c_str());
00136             if(i+2 != abilities.end())
00137                 str << ",";
00138             ++i;
00139             tooltip << i->c_str(); //string_table[*i + "_description"];
00140             res.add_text(str,tooltip);
00141         }
00142 
00143         return res;
00144     }
00145     case UNIT_HP: {
00146         report res;
00147         std::stringstream tooltip;
00148         str << font::color2markup( u->second.hp_color() );
00149         str << u->second.hitpoints() << "/" << u->second.max_hitpoints();
00150 
00151         std::set<std::string> resistances_table;
00152 
00153         string_map resistances = u->second.get_base_resistances();
00154 
00155         bool att_def_diff = false;
00156         for(string_map::iterator resist = resistances.begin();
00157                 resist != resistances.end(); ++resist) {
00158             std::stringstream line;
00159             line << gettext(resist->first.c_str()) << ": ";
00160 
00161             // Some units have different resistances when
00162             // attacking or defending.
00163             int res_att = 100 - u->second.resistance_against(resist->first, true, u->first);
00164             int res_def = 100 - u->second.resistance_against(resist->first, false, u->first);
00165             if (res_att == res_def) {
00166                 line << res_def << "%\n";
00167             } else {
00168                 line << res_att << "% / " << res_def << "%\n";
00169                 att_def_diff = true;
00170             }
00171             resistances_table.insert(line.str());
00172         }
00173 
00174         tooltip << _("Resistances: ");
00175         if (att_def_diff) 
00176             tooltip << _("(Att / Def)");
00177         tooltip << "\n";
00178 
00179         // the STL set will give alphabetical sorting
00180         for(std::set<std::string>::iterator line = resistances_table.begin();
00181                 line != resistances_table.end(); ++line) {
00182             tooltip << (*line);
00183         }
00184 
00185         res.add_text(str,tooltip);
00186         return res ;
00187     }
00188     case UNIT_XP: {
00189       report res;
00190       std::stringstream tooltip;
00191 
00192       str << font::color2markup( u->second.xp_color() );
00193       str << u->second.experience() << "/" << u->second.max_experience();
00194 
00195       tooltip << _("Experience Modifier: ") << ((level["experience_modifier"] != "") ? level["experience_modifier"] : "100") << "%";
00196       res.add_text(str,tooltip);
00197 
00198       return res;
00199     }
00200     case UNIT_ADVANCEMENT_OPTIONS: {
00201       report res;
00202       const std::map<std::string,std::string>& adv_icons=u->second.advancement_icons();
00203       for(std::map<std::string,std::string>::const_iterator i=adv_icons.begin();i!=adv_icons.end();i++){
00204         res.add_image(i->first,i->second);
00205       }
00206       return(res);
00207 
00208     }
00209     case UNIT_MOVES: {
00210       float movement_frac = 1.0;
00211       if (u->second.side() == playing_side){
00212           movement_frac = u->second.movement_left() / maximum<float>(1.0, u->second.total_movement());
00213           if (movement_frac > 1.0) movement_frac = 1.0;
00214       }
00215 
00216       int grey = 128 + static_cast<int>((255-128) * movement_frac);
00217       str << "<" << grey << "," << grey << "," << grey <<">";
00218       str << u->second.movement_left() << "/" << u->second.total_movement();
00219         break;
00220     }
00221     case UNIT_WEAPONS: {
00222         report res;
00223         std::stringstream tooltip;
00224 
00225         const size_t team_index = u->second.side()-1;
00226         if(team_index >= teams.size()) {
00227             std::cerr << "illegal team index in reporting: " << team_index << "\n";
00228             return res;
00229         }
00230 
00231         std::vector<attack_type>& attacks = u->second.attacks();
00232         for(std::vector<attack_type>::iterator at_it = attacks.begin();
00233             at_it != attacks.end(); ++at_it) {
00234             at_it->set_specials_context(u->first,gamemap::location(),u->second);
00235             const std::string& lang_type = gettext(at_it->type().c_str());
00236             str.str("");
00237             str << "<245,230,193>";
00238             if(utils::string_bool(u->second.get_state("slowed"))) {
00239                 str << round_damage(at_it->damage(),1,2) << "-" ;
00240             } else {
00241                 str << at_it->damage() << "-" ;
00242             }
00243             int nattacks = at_it->num_attacks();
00244             // Compute swarm attacks:
00245             unit_ability_list swarm = at_it->get_specials("attacks");
00246             if(!swarm.empty()) {
00247                 int swarm_max_attacks = swarm.highest("attacks_max",nattacks).first;
00248                 int swarm_min_attacks = swarm.highest("attacks_min").first;
00249                 int hitp = u->second.hitpoints();
00250                 int mhitp = u->second.max_hitpoints();
00251 
00252                 nattacks = swarm_min_attacks + swarm_max_attacks * hitp / mhitp;
00253 
00254             } else {
00255                 nattacks = at_it->num_attacks();
00256             }
00257             str << nattacks;
00258             str << " " << at_it->name() << " " << at_it->accuracy_parry_description();
00259             tooltip << at_it->name() << "\n";
00260             int effdmg;
00261             if(utils::string_bool(u->second.get_state("slowed"))) {
00262                 effdmg = round_damage(at_it->damage(),1,2);
00263             } else {
00264                 effdmg = at_it->damage();
00265             }
00266             tooltip << effdmg   << " " << _n("tooltip^damage", "damage",  effdmg) << ", ";
00267             tooltip << nattacks << " " << _n("tooltip^attack", "attacks", nattacks);
00268 
00269             const int accuracy = at_it->accuracy();
00270             if(accuracy) {
00271                 tooltip << " " << (accuracy > 0 ? "+" : "") << accuracy << "% " << _n("tooltip^accuracy", "accuracy", accuracy);
00272             }
00273 
00274             const int parry = at_it->parry();
00275             if(parry) {
00276                 tooltip << " " << (parry > 0 ? "+" : "") << parry << "% " << _n("tooltip^parry", "parry", parry);
00277             }
00278 
00279             str<<"\n";
00280             res.add_text(str,tooltip);
00281 
00282             str << "<166,146,117>  ";
00283             std::string range = _(at_it->range().c_str());
00284             str << range << "--" << lang_type << "\n";
00285             str<<"\n";
00286 
00287             tooltip << _("weapon range: ") << range <<"\n";
00288             tooltip << _("damage type: ")  << lang_type << "\n";
00289             // Find all the unit types on the map, and
00290             // show this weapon's bonus against all the different units.
00291             // Don't show invisible units, except if they are in our team or allied.
00292             std::set<std::string> seen_units;
00293             std::map<int,std::vector<std::string> > resistances;
00294             for(unit_map::const_iterator u_it = units.begin(); u_it != units.end(); ++u_it) {
00295                 if(teams[team_index].is_enemy(u_it->second.side()) &&
00296                    !current_team.fogged(u_it->first) &&
00297                    seen_units.count(u_it->second.type_id()) == 0 &&
00298                    ( !current_team.is_enemy(u_it->second.side()) ||
00299                      !u_it->second.invisible(u_it->first,units,teams)))
00300                 {
00301                     seen_units.insert(u_it->second.type_id());
00302                     const int resistance = u_it->second.resistance_against(*at_it,false,u_it->first) - 100;
00303                     resistances[resistance].push_back(u_it->second.type_name());
00304                 }
00305             }
00306 
00307             for(std::map<int,std::vector<std::string> >::reverse_iterator resist = resistances.rbegin(); resist != resistances.rend(); ++resist) {
00308                 std::sort(resist->second.begin(),resist->second.end());
00309                 tooltip << (resist->first >= 0 ? "+" : "") << resist->first << "% " << _("vs") << " ";
00310                 for(std::vector<std::string>::const_iterator i = resist->second.begin(); i != resist->second.end(); ++i) {
00311                     if(i != resist->second.begin()) {
00312                         tooltip << ",";
00313                     }
00314 
00315                     tooltip << *i;
00316                 }
00317                 tooltip << "\n";
00318             }
00319 
00320             res.add_text(str,tooltip);
00321 
00322 
00323             const std::vector<std::string>& specials = at_it->special_tooltips();
00324 
00325             if(! specials.empty()) {
00326                 for(std::vector<std::string>::const_iterator sp_it = specials.begin(); sp_it != specials.end(); ++sp_it) {
00327                     str << "<166,146,117>  ";
00328                     str << gettext(sp_it->c_str());
00329                     str<<"\n";
00330                     ++sp_it;
00331                     tooltip << gettext(sp_it->c_str());
00332                 }
00333                 res.add_text(str,tooltip);
00334             }
00335         }
00336 
00337         return res;
00338     }
00339     case UNIT_IMAGE:
00340     {
00341 //      const std::vector<Uint32>& old_rgb = u->second.team_rgb_range();
00342 //      color_range new_rgb = team::get_side_color_range(u->second.side());
00343         return report("",image::locator(u->second.absolute_image(),u->second.image_mods()),"");
00344     }
00345     case UNIT_PROFILE:
00346         return report("",u->second.profile(),"");
00347     case TIME_OF_DAY: {
00348         time_of_day tod = timeofday_at(status,units,mouseover,map);
00349         const std::string tod_image = tod.image + (preferences::flip_time() ? "~FL(horiz)" : "");
00350 
00351         // Don't show illuminated time on fogged/shrouded tiles
00352         if (current_team.fogged(mouseover) || current_team.shrouded(mouseover)) {
00353             tod = status.get_time_of_day(false,mouseover);
00354         }
00355         std::stringstream tooltip;
00356 
00357         tooltip << tod.name << "\n"
00358                 << _("Lawful units: ")
00359                 << (tod.lawful_bonus > 0 ? "+" : "") << tod.lawful_bonus << "%\n"
00360                 << _("Neutral units: ") << "0%\n"
00361                 << _("Chaotic units: ")
00362                 << (tod.lawful_bonus < 0 ? "+" : "") << (tod.lawful_bonus*-1) << "%";
00363 
00364         return report("",tod_image,tooltip.str());
00365     }
00366     case TURN:
00367         str << status.turn();
00368 
00369         if(status.number_of_turns() != -1) {
00370             str << "/" << status.number_of_turns();
00371         }
00372 
00373         str << "\n";
00374         break;
00375     // For the following status reports, show them in gray text
00376     // when it is not the active player's turn.
00377     case GOLD:
00378         str << (current_side != playing_side ? font::GRAY_TEXT : (current_team.gold() < 0 ? font::BAD_TEXT : font::NULL_MARKUP)) << current_team.gold();
00379         break;
00380     case VILLAGES: {
00381         const team_data data = calculate_team_data(current_team,current_side,units);
00382         str << (current_side != playing_side ? font::GRAY_TEXT : font::NULL_MARKUP) << data.villages << "/";
00383         if (current_team.uses_shroud()) {
00384             int unshrouded_villages = 0;
00385             std::vector<gamemap::location>::const_iterator i = map.villages().begin();
00386             for (; i != map.villages().end(); i++) {
00387                 if (!current_team.shrouded(*i))
00388                      unshrouded_villages++;
00389             }
00390             str << unshrouded_villages;
00391         } else {
00392             str << map.villages().size();
00393         }
00394         break;
00395     }
00396     case NUM_UNITS: {
00397         str << (current_side != playing_side ? font::GRAY_TEXT : font::NULL_MARKUP) << team_units(units,current_side);
00398         break;
00399     }
00400     case UPKEEP: {
00401         const team_data data = calculate_team_data(current_team,current_side,units);
00402         str << (current_side != playing_side ? font::GRAY_TEXT : font::NULL_MARKUP) << data.expenses << " (" << data.upkeep << ")";
00403         break;
00404     }
00405     case EXPENSES: {
00406         const team_data data = calculate_team_data(current_team,current_side,units);
00407         str << (current_side != playing_side ? font::GRAY_TEXT : font::NULL_MARKUP) << data.expenses;
00408         break;
00409     }
00410     case INCOME: {
00411         const team_data data = calculate_team_data(current_team,current_side,units);
00412         str << (current_side != playing_side ? font::GRAY_TEXT : (data.net_income < 0 ? font::BAD_TEXT : font::NULL_MARKUP)) << data.net_income;
00413         break;
00414     }
00415     case TERRAIN: {
00416         if(!map.on_board(mouseover) || current_team.shrouded(mouseover))
00417             break;
00418 
00419         const t_translation::t_terrain terrain = map.get_terrain(mouseover);
00420         if (terrain == t_translation::OFF_MAP_USER)
00421             break;
00422 
00423         const t_translation::t_list& underlying = map.underlying_union_terrain(terrain);
00424 
00425         if(map.is_village(mouseover)) {
00426             const unsigned int owner = village_owner(mouseover,teams)+1;
00427             if(owner == 0 || current_team.fogged(mouseover)) {
00428                 str << map.get_terrain_info(terrain).income_description();
00429             } else if(owner == current_side) {
00430                 str << map.get_terrain_info(terrain).income_description_own();
00431             } else if(current_team.is_enemy(owner)) {
00432                 str << map.get_terrain_info(terrain).income_description_enemy();
00433             } else {
00434                 str << map.get_terrain_info(terrain).income_description_ally();
00435             }
00436             str << " ";
00437         } else {
00438                 str << map.get_terrain_info(terrain).name();
00439         }
00440 
00441         if(underlying.size() != 1 || underlying.front() != terrain) {
00442             str << " (";
00443 
00444             for(t_translation::t_list::const_iterator i =
00445                     underlying.begin(); i != underlying.end(); ++i) {
00446 
00447             str << map.get_terrain_info(*i).name();
00448                 if(i+1 != underlying.end()) {
00449                     str << ",";
00450                 }
00451             }
00452             str << ")";
00453         }
00454         break;
00455     }
00456     case POSITION: {
00457         if(!map.on_board(mouseover)) {
00458             break;
00459         }
00460 
00461         const t_translation::t_terrain terrain = map[mouseover];
00462 
00463         if (terrain == t_translation::OFF_MAP_USER)
00464             break;
00465 
00466         str << mouseover;
00467 
00468         if(u == units.end())
00469             break;
00470         if(displayed_unit_hex != mouseover && displayed_unit_hex != loc)
00471             break;
00472         if(current_team.shrouded(mouseover))
00473             break;
00474 
00475         const int move_cost = u->second.movement_cost(terrain);
00476         const int defense = 100 - u->second.defense_modifier(terrain);
00477 
00478         if(move_cost < 99) {
00479             str << " (" << defense << "%," << move_cost << ")";
00480         } else if (mouseover == displayed_unit_hex) {
00481             str << " (" << defense << "%,-)";
00482         } else {
00483             str << " (-)";
00484         }
00485 
00486         break;
00487     }
00488 
00489     case SIDE_PLAYING: {
00490         std::string flag_icon = teams[playing_side-1].flag_icon();
00491         std::string old_rgb = game_config::flag_rgb;
00492         std::string new_rgb = team::get_side_colour_index(playing_side);
00493         std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
00494 
00495         if(flag_icon.empty()) {
00496             flag_icon = game_config::flag_icon_image;
00497         }
00498 
00499         image::locator flag_icon_img(flag_icon, mods);
00500         return report("",flag_icon_img,teams[playing_side-1].current_player());
00501     }
00502 
00503     case OBSERVERS: {
00504         if(observers.empty()) {
00505             return report();
00506         }
00507 
00508         str << _("Observers:") << "\n";
00509 
00510         for(std::set<std::string>::const_iterator i = observers.begin(); i != observers.end(); ++i) {
00511             str << *i << "\n";
00512         }
00513 
00514         return report("",game_config::observer_image,str.str());
00515     }
00516     case SELECTED_TERRAIN: {
00517         std::map<TYPE, std::string>::const_iterator it =
00518             report_contents.find(SELECTED_TERRAIN);
00519         if (it != report_contents.end()) {
00520             return report(it->second);
00521         }
00522         else {
00523             return report();
00524         }
00525     }
00526     case EDIT_LEFT_BUTTON_FUNCTION: {
00527         std::map<TYPE, std::string>::const_iterator it =
00528             report_contents.find(EDIT_LEFT_BUTTON_FUNCTION);
00529         if (it != report_contents.end()) {
00530             return report(it->second);
00531         }
00532         else {
00533             return report();
00534         }
00535     }
00536     case REPORT_COUNTDOWN: {
00537         int min;
00538         int sec;
00539         if (current_team.countdown_time() > 0){
00540             sec = current_team.countdown_time() / 1000;
00541 
00542             str << (current_side != playing_side ? font::GRAY_TEXT : font::NORMAL_TEXT);
00543 
00544             if(sec < 60)
00545                 str << "<200,0,0>";
00546             else if(sec < 120)
00547                 str << "<200,200,0>";
00548 
00549             min = sec / 60;
00550             str << min << ":";
00551             sec = sec % 60;
00552             if (sec < 10) {
00553                 str << "0";
00554             }
00555             str << sec;
00556             break;
00557         } // Intentional fall-through to REPORT_CLOCK
00558           // if the time countdown isn't valid.
00559           // If there is no turn time limit,
00560           // then we display the clock instead.
00561         }
00562     case REPORT_CLOCK: {
00563         time_t t = time(NULL);
00564         struct tm *lt=localtime(&t);
00565         if (lt) {
00566             char temp[10];
00567             size_t s = strftime(temp,10,preferences::clock_format().c_str(),lt);
00568             if(s>0) {
00569                 return report(temp);
00570             } else {
00571                 return report();
00572             }
00573         } else {
00574             return report();
00575         }
00576     }
00577     default:
00578         assert(false);
00579         break;
00580     }
00581     return report(str.str());
00582 }
00583 
00584 } // end namespace reports
00585 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs