00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 #include "attack_prediction_display.hpp"
00018
00019 #include "attack_prediction.hpp"
00020 #include "gettext.hpp"
00021 #include "game_display.hpp"
00022 #include "language.hpp"
00023 #include "marked-up_text.hpp"
00024 #include "resources.hpp"
00025 #include "unit_abilities.hpp"
00026
00027
00028 static void format_prob(char str_buf[10], double prob)
00029 {
00030
00031 if(prob > 0.9995) {
00032 snprintf(str_buf, 10, "100 %%");
00033 } else if(prob >= 0.1) {
00034 snprintf(str_buf, 10, "%4.1f %%", 100.0 * prob);
00035 } else {
00036 snprintf(str_buf, 10, " %3.1f %%", 100.0 * prob);
00037 }
00038
00039 str_buf[9] = '\0';
00040 }
00041
00042
00043 const int battle_prediction_pane::inter_line_gap_ = 3;
00044 const int battle_prediction_pane::inter_column_gap_ = 30;
00045 const int battle_prediction_pane::inter_units_gap_ = 30;
00046 const int battle_prediction_pane::max_hp_distrib_rows_ = 10;
00047
00048 battle_prediction_pane::battle_prediction_pane(const battle_context &bc,
00049 const map_location &attacker_loc, const map_location &defender_loc) :
00050 gui::preview_pane(resources::screen->video()),
00051 bc_(bc),
00052 attacker_loc_(attacker_loc),
00053 defender_loc_(defender_loc),
00054 attacker_(*resources::units->find(attacker_loc)),
00055 defender_(*resources::units->find(defender_loc)),
00056 attacker_label_(),
00057 defender_label_(),
00058 attacker_label_width_(0),
00059 defender_label_width_(0),
00060 attacker_left_strings_(),
00061 attacker_right_strings_(),
00062 defender_left_strings_(),
00063 defender_right_strings_(),
00064 attacker_strings_width_(0),
00065 attacker_left_strings_width_(0),
00066 attacker_right_strings_width_(0),
00067 defender_strings_width_(0),
00068 defender_left_strings_width_(0),
00069 defender_right_strings_width_(0),
00070 units_strings_height_(0),
00071 hp_distrib_string_(),
00072 attacker_hp_distrib_(),
00073 defender_hp_distrib_(),
00074 hp_distrib_string_width_(0),
00075 attacker_hp_distrib_width_(0),
00076 defender_hp_distrib_width_(0),
00077 attacker_hp_distrib_height_(0),
00078 defender_hp_distrib_height_(0),
00079 hp_distribs_height_(0),
00080 attacker_width_(0),
00081 defender_width_(0),
00082 units_width_(0),
00083 dialog_width_(0),
00084 dialog_height_(0)
00085 {
00086
00087 combatant attacker_combatant(bc.get_attacker_stats());
00088 combatant defender_combatant(bc.get_defender_stats());
00089 attacker_combatant.fight(defender_combatant);
00090
00091 const battle_context_unit_stats& attacker_stats = bc.get_attacker_stats();
00092 const battle_context_unit_stats& defender_stats = bc.get_defender_stats();
00093
00094
00095 std::vector<std::pair<int, double> > hp_prob_vector;
00096 get_hp_prob_vector(attacker_combatant.hp_dist, hp_prob_vector);
00097 get_hp_distrib_surface(hp_prob_vector, attacker_stats, defender_stats, attacker_hp_distrib_,
00098 attacker_hp_distrib_width_, attacker_hp_distrib_height_);
00099 get_hp_prob_vector(defender_combatant.hp_dist, hp_prob_vector);
00100 get_hp_distrib_surface(hp_prob_vector, defender_stats, attacker_stats, defender_hp_distrib_,
00101 defender_hp_distrib_width_, defender_hp_distrib_height_);
00102 hp_distribs_height_ = std::max<int>(attacker_hp_distrib_height_, defender_hp_distrib_height_);
00103
00104
00105 attacker_label_ = _("Attacker");
00106 defender_label_ = _("Defender");
00107 attacker_label_width_ = font::line_width(attacker_label_, font::SIZE_PLUS, TTF_STYLE_BOLD);
00108 defender_label_width_ = font::line_width(defender_label_, font::SIZE_PLUS, TTF_STYLE_BOLD);
00109
00110
00111 get_unit_strings(attacker_stats, attacker_, attacker_loc_, static_cast<float>(attacker_combatant.untouched),
00112 defender_, defender_loc_, defender_stats.weapon,
00113 attacker_left_strings_, attacker_right_strings_,
00114 attacker_left_strings_width_, attacker_right_strings_width_, attacker_strings_width_);
00115
00116 get_unit_strings(defender_stats, defender_, defender_loc_, static_cast<float>(defender_combatant.untouched),
00117 attacker_, attacker_loc_, attacker_stats.weapon,
00118 defender_left_strings_, defender_right_strings_,
00119 defender_left_strings_width_, defender_right_strings_width_, defender_strings_width_);
00120
00121 units_strings_height_ = std::max<int>(attacker_left_strings_.size(), defender_left_strings_.size())
00122 * (font::SIZE_NORMAL + inter_line_gap_) + 14;
00123
00124 hp_distrib_string_ = _("Expected Battle Result (HP)");
00125 hp_distrib_string_width_ = font::line_width(hp_distrib_string_, font::SIZE_SMALL);
00126
00127 attacker_width_ = std::max<int>(attacker_label_width_, attacker_strings_width_);
00128 attacker_width_ = std::max<int>(attacker_width_, hp_distrib_string_width_);
00129 attacker_width_ = std::max<int>(attacker_width_, attacker_hp_distrib_width_);
00130 defender_width_ = std::max<int>(defender_label_width_, defender_strings_width_);
00131 defender_width_ = std::max<int>(defender_width_, hp_distrib_string_width_);
00132 defender_width_ = std::max<int>(defender_width_, defender_hp_distrib_width_);
00133 units_width_ = std::max<int>(attacker_width_, defender_width_);
00134
00135 dialog_width_ = 2 * units_width_ + inter_units_gap_;
00136 dialog_height_ = 15 + 24 + units_strings_height_ + 14 + 19 + hp_distribs_height_ + 18;
00137
00138
00139 set_measurements(dialog_width_, dialog_height_);
00140 }
00141
00142 void battle_prediction_pane::get_unit_strings(const battle_context_unit_stats& stats,
00143 const unit& u, const map_location& u_loc, float u_unscathed,
00144 const unit& opp, const map_location& opp_loc, const attack_type *opp_weapon,
00145 std::vector<std::string>& left_strings, std::vector<std::string>& right_strings,
00146 int& left_strings_width, int& right_strings_width, int& strings_width)
00147 {
00148 std::stringstream str;
00149 char str_buf[10];
00150
00151
00152 if(stats.weapon != NULL) {
00153
00154
00155 const attack_type *weapon = stats.weapon;
00156 weapon->set_specials_context(u_loc, opp_loc, *resources::units, stats.is_attacker, opp_weapon);
00157
00158
00159 unit_ability_list dmg_specials = weapon->get_specials("damage");
00160 unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), stats.backstab_pos);
00161
00162
00163 const unit_abilities::individual_effect *set_dmg_effect = NULL;
00164 unit_abilities::effect_list::const_iterator i;
00165 for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
00166 if(i->type == unit_abilities::SET) {
00167 set_dmg_effect = &*i;
00168 break;
00169 }
00170 }
00171
00172
00173 if(set_dmg_effect == NULL) {
00174 left_strings.push_back(weapon->name());
00175 str.str("");
00176 str << weapon->damage();
00177 right_strings.push_back(str.str());
00178 } else {
00179 assert(set_dmg_effect->ability);
00180
00181 left_strings.push_back((*set_dmg_effect->ability)["name"]);
00182 str.str("");
00183 str << set_dmg_effect->value;
00184 right_strings.push_back(str.str());
00185 }
00186
00187
00188 for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
00189 if(i->type == unit_abilities::ADD) {
00190 left_strings.push_back((*i->ability)["name"]);
00191 str.str("");
00192 if(i->value >= 0) str << "+" << i->value;
00193 else str << i->value;
00194 right_strings.push_back(str.str());
00195 }
00196 }
00197
00198
00199 for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
00200 if(i->type == unit_abilities::MUL) {
00201 left_strings.push_back((*i->ability)["name"]);
00202 str.str("");
00203 str << "* " << (i->value / 100);
00204 if(i->value % 100) {
00205 str << "." << ((i->value % 100) / 10);
00206 if(i->value % 10) str << (i->value % 10);
00207 }
00208 right_strings.push_back(str.str());
00209 }
00210 }
00211
00212
00213 int tod_modifier = combat_modifier(u_loc, u.alignment(), u.is_fearless());
00214 if(tod_modifier != 0) {
00215 left_strings.push_back(_("Time of day"));
00216 str.str("");
00217 str << utils::signed_percent(tod_modifier);
00218 right_strings.push_back(str.str());
00219 }
00220
00221
00222 int leadership_bonus = 0;
00223 under_leadership(*resources::units, u_loc, &leadership_bonus);
00224 if(leadership_bonus != 0) {
00225 left_strings.push_back(_("Leadership"));
00226 str.str("");
00227 str << utils::signed_percent(leadership_bonus);
00228 right_strings.push_back(str.str());
00229 }
00230
00231
00232
00233 int resistance_modifier = opp.damage_from(*weapon, !stats.is_attacker, opp_loc);
00234 if(resistance_modifier != 100) {
00235 str.str("");
00236 if(stats.is_attacker) str << _("Defender");
00237 else str << _("Attacker");
00238 if(resistance_modifier < 100) str << _(" resistance vs ");
00239 else str << _(" vulnerability vs ");
00240 str << string_table["type_" + weapon->type()];
00241 left_strings.push_back(str.str());
00242 str.str("");
00243 str << "* " << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
00244 right_strings.push_back(str.str());
00245 }
00246
00247
00248 if(stats.is_slowed) {
00249 left_strings.push_back(_("Slowed"));
00250 right_strings.push_back("/ 2");
00251 }
00252
00253
00254 left_strings.push_back(_("Total damage"));
00255 str.str("");
00256 str << stats.damage << utils::unicode_en_dash << stats.num_blows << " (" << stats.chance_to_hit << "%)";
00257 right_strings.push_back(str.str());
00258
00259
00260 } else {
00261 left_strings.push_back(_("No usable weapon"));
00262 right_strings.push_back("");
00263 }
00264
00265
00266 left_strings.push_back(_("Chance of being unscathed"));
00267 format_prob(str_buf, u_unscathed);
00268 right_strings.push_back(str_buf);
00269
00270 #if 0 // might not be en English!
00271
00272 for(int i = 0; i < (int) left_strings.size(); i++)
00273 if(left_strings[i].size() > 0) left_strings[i][0] = toupper(left_strings[i][0]);
00274 #endif
00275
00276
00277 left_strings_width = get_strings_max_length(left_strings);
00278 right_strings_width = get_strings_max_length(right_strings);
00279 strings_width = left_strings_width + inter_column_gap_ + right_strings_width;
00280 }
00281
00282 int battle_prediction_pane::get_strings_max_length(const std::vector<std::string>& strings)
00283 {
00284 int max_len = 0;
00285
00286 for(int i = 0; i < static_cast<int>(strings.size()); i++)
00287 max_len = std::max<int>(font::line_width(strings[i], font::SIZE_NORMAL), max_len);
00288
00289 return max_len;
00290 }
00291
00292 void battle_prediction_pane::get_hp_prob_vector(const std::vector<double>& hp_dist,
00293 std::vector<std::pair<int, double> >& hp_prob_vector)
00294 {
00295 hp_prob_vector.clear();
00296
00297
00298 std::vector<std::pair<double, int> > prob_hp_vector;
00299 int i;
00300
00301 for(i = 0; i < static_cast<int>(hp_dist.size()); i++) {
00302 double prob = hp_dist[i];
00303
00304
00305 if(prob > 0.001)
00306 prob_hp_vector.push_back(std::pair<double, int>(prob, i));
00307 }
00308
00309 std::sort(prob_hp_vector.begin(), prob_hp_vector.end());
00310
00311
00312 int nb_elem = std::min<int>(max_hp_distrib_rows_, prob_hp_vector.size());
00313
00314 for(i = prob_hp_vector.size() - nb_elem;
00315 i < static_cast<int>(prob_hp_vector.size()); i++) {
00316
00317 hp_prob_vector.push_back(std::pair<int, double>
00318 (prob_hp_vector[i].second, prob_hp_vector[i].first));
00319 }
00320
00321
00322 std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
00323 }
00324
00325 void battle_prediction_pane::draw_contents()
00326 {
00327
00328 int damage_line_skip = std::max<int>(attacker_left_strings_.size(), defender_left_strings_.size()) - 2;
00329
00330 draw_unit(0, damage_line_skip,
00331 attacker_left_strings_width_, attacker_left_strings_, attacker_right_strings_,
00332 attacker_label_, attacker_label_width_, attacker_hp_distrib_, attacker_hp_distrib_width_);
00333
00334 draw_unit(units_width_ + inter_units_gap_, damage_line_skip,
00335 defender_left_strings_width_, defender_left_strings_, defender_right_strings_,
00336 defender_label_, defender_label_width_, defender_hp_distrib_, defender_hp_distrib_width_);
00337 }
00338
00339 void battle_prediction_pane::draw_unit(int x_off, int damage_line_skip, int left_strings_width,
00340 const std::vector<std::string>& left_strings,
00341 const std::vector<std::string>& right_strings,
00342 const std::string& label, int label_width,
00343 surface& hp_distrib, int hp_distrib_width)
00344 {
00345 surface screen = resources::screen->get_screen_surface();
00346 int i;
00347
00348
00349
00350
00351
00352
00353
00354
00355
00356 SDL_Rect clip_rect = location();
00357 clip_rect.x += 10;
00358
00359
00360 int y_off = 15;
00361
00362
00363 font::draw_text_line(screen, clip_rect, font::SIZE_15, font::NORMAL_COLOR, label,
00364 clip_rect.x + x_off + (units_width_ - label_width) / 2, clip_rect.y + y_off, 0, TTF_STYLE_BOLD);
00365
00366 y_off += 24;
00367
00368
00369 for(i = 0; i < static_cast<int>(left_strings.size()) - 2; i++) {
00370 font::draw_text_line(screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOR, left_strings[i],
00371 clip_rect.x + x_off, clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i,
00372 0, TTF_STYLE_NORMAL);
00373
00374 font::draw_text_line(screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOR, right_strings[i],
00375 clip_rect.x + x_off + left_strings_width + inter_column_gap_,
00376 clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i, 0, TTF_STYLE_NORMAL);
00377 }
00378
00379
00380 y_off += damage_line_skip * (font::SIZE_NORMAL + inter_line_gap_) + 14;
00381
00382
00383 for(i = 0; i < 2; i++) {
00384 const std::string& left_string = left_strings[left_strings.size() - 2 + i];
00385 const std::string& right_string = right_strings[right_strings.size() - 2 + i];
00386
00387 font::draw_text_line(screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOR, left_string,
00388 clip_rect.x + x_off, clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i,
00389 0, TTF_STYLE_NORMAL);
00390
00391 font::draw_text_line(screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOR, right_string,
00392 clip_rect.x + x_off + left_strings_width + inter_column_gap_,
00393 clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i, 0, TTF_STYLE_NORMAL);
00394 }
00395
00396 y_off += 2 * (font::SIZE_NORMAL + inter_line_gap_) + 14;
00397
00398
00399 font::draw_text(screen, clip_rect, font::SIZE_SMALL, font::NORMAL_COLOR, hp_distrib_string_,
00400 clip_rect.x + x_off + (units_width_ - hp_distrib_string_width_) / 2, clip_rect.y + y_off);
00401
00402 y_off += 19;
00403
00404
00405 video().blit_surface(clip_rect.x + x_off + (units_width_ - hp_distrib_width) / 2, clip_rect.y + y_off, hp_distrib);
00406 }
00407
00408 void battle_prediction_pane::get_hp_distrib_surface(const std::vector<std::pair<int, double> >& hp_prob_vector,
00409 const battle_context_unit_stats& stats,
00410 const battle_context_unit_stats& opp_stats,
00411 surface& surf, int& width, int& height)
00412 {
00413
00414 int fs = font::SIZE_SMALL;
00415
00416
00417 int hp_sep = 24 + 6;
00418
00419
00420 int bar_space = 150;
00421
00422
00423 int percent_sep = 43 + 6;
00424
00425
00426 width = 30 + 2 + bar_space + 2 + percent_sep;
00427 height = 5 + (fs + 2) * hp_prob_vector.size();
00428
00429
00430 surf = create_neutral_surface(width, height);
00431
00432
00433 SDL_SetAlpha(surf, 0, SDL_ALPHA_OPAQUE);
00434
00435 SDL_Rect clip_rect = create_rect(0, 0, width, height);
00436 Uint32 grey_color = SDL_MapRGBA(surf->format, 0xb7, 0xc1, 0xc1, 255);
00437
00438 Uint32 background_color = SDL_MapRGBA(surf->format, 25, 25, 25, 255);
00439 sdl_fill_rect(surf, &clip_rect, background_color);
00440
00441
00442 SDL_Rect top_border_rect = create_rect(0, 0, width, 2);
00443 sdl_fill_rect(surf, &top_border_rect, grey_color);
00444
00445 SDL_Rect bottom_border_rect = create_rect(0, height - 2, width, 2);
00446 sdl_fill_rect(surf, &bottom_border_rect, grey_color);
00447
00448 SDL_Rect left_border_rect = create_rect(0, 0, 2, height);
00449 sdl_fill_rect(surf, &left_border_rect, grey_color);
00450
00451 SDL_Rect right_border_rect = create_rect(width - 2, 0, 2, height);
00452 sdl_fill_rect(surf, &right_border_rect, grey_color);
00453
00454 SDL_Rect hp_sep_rect = create_rect(hp_sep, 0, 2, height);
00455 sdl_fill_rect(surf, &hp_sep_rect, grey_color);
00456
00457 SDL_Rect percent_sep_rect = create_rect(width - percent_sep - 2, 0, 2, height);
00458 sdl_fill_rect(surf, &percent_sep_rect, grey_color);
00459
00460
00461 for(int i = 0; i < static_cast<int>(hp_prob_vector.size()); i++) {
00462 char str_buf[10];
00463
00464
00465 int hp = hp_prob_vector[hp_prob_vector.size() - i - 1].first;
00466 double prob = hp_prob_vector[hp_prob_vector.size() - i - 1].second;
00467
00468 SDL_Color row_color;
00469
00470
00471 if(hp == 0) {
00472 SDL_Color color = {0xe5, 0, 0, 0};
00473 row_color = color;
00474 }
00475
00476
00477 else if(hp < static_cast<int>(stats.hp)) {
00478
00479 if(opp_stats.petrifies) {
00480 SDL_Color color = {0x9a, 0x9a, 0x9a, 0};
00481 row_color = color;
00482 } else {
00483 SDL_Color color = {0xf4, 0xc9, 0, 0};
00484 row_color = color;
00485 }
00486 }
00487
00488
00489 else {
00490 SDL_Color color = {0x08, 0xca, 0, 0};
00491 row_color = color;
00492 }
00493
00494
00495 snprintf(str_buf, 10, "%d", hp);
00496 str_buf[9] = '\0';
00497 int hp_width = font::line_width(str_buf, fs);
00498
00499
00500 font::draw_text_line(surf, clip_rect, fs, font::NORMAL_COLOR, str_buf,
00501 hp_sep - hp_width - 2, 2 + (fs + 2) * i, 0, TTF_STYLE_NORMAL);
00502
00503 int bar_len = std::max<int>(static_cast<int>((prob * (bar_space - 4)) + 0.5), 2);
00504
00505 SDL_Rect bar_rect_1 = create_rect(hp_sep + 4, 6 + (fs + 2) * i, bar_len, 8);
00506 sdl_fill_rect(surf, &bar_rect_1, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 100));
00507
00508 SDL_Rect bar_rect_2 = create_rect(hp_sep + 4, 7 + (fs + 2) * i, bar_len, 6);
00509 sdl_fill_rect(surf, &bar_rect_2, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 66));
00510
00511 SDL_Rect bar_rect_3 = create_rect(hp_sep + 4, 8 + (fs + 2) * i, bar_len, 4);
00512 sdl_fill_rect(surf, &bar_rect_3, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 33));
00513
00514 SDL_Rect bar_rect_4 = create_rect(hp_sep + 4, 9 + (fs + 2) * i, bar_len, 2);
00515 sdl_fill_rect(surf, &bar_rect_4, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 0));
00516
00517
00518 format_prob(str_buf, prob);
00519 int prob_width = font::line_width(str_buf, fs);
00520 font::draw_text_line(surf, clip_rect, fs, font::NORMAL_COLOR, str_buf,
00521 width - prob_width - 4, 2 + (fs + 2) * i, 0, TTF_STYLE_NORMAL);
00522 }
00523 }
00524
00525 Uint32 battle_prediction_pane::blend_rgb(const surface& surf, unsigned char r, unsigned char g, unsigned char b, unsigned char drop)
00526 {
00527
00528 if(r < drop) r = 0; else r -= drop;
00529 if(g < drop) g = 0; else g -= drop;
00530 if(b < drop) b = 0; else b -= drop;
00531
00532 return SDL_MapRGB(surf->format, r, g, b);
00533 }
00534
00535 attack_prediction_displayer::RESULT attack_prediction_displayer::button_pressed(int selection)
00536 {
00537
00538 const size_t index = size_t(selection);
00539
00540 if(index < bc_vector_.size()) {
00541 battle_prediction_pane battle_pane(bc_vector_[index], attacker_loc_, defender_loc_);
00542 std::vector<gui::preview_pane*> preview_panes;
00543 preview_panes.push_back(&battle_pane);
00544
00545 gui::show_dialog(*resources::screen, NULL, _("Damage Calculations"), "", gui::OK_ONLY, NULL, &preview_panes);
00546 }
00547
00548 return gui::CONTINUE_DIALOG;
00549 }
00550
00551