00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
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();
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
00162
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
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
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
00290
00291
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
00342
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
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
00376
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 }
00558
00559
00560
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 }
00585