00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "global.hpp"
00022
00023 #include "game_display.hpp"
00024
00025 #include "wesconfig.h"
00026
00027 #ifdef HAVE_LIBDBUS
00028 #include <dbus/dbus.h>
00029 #endif
00030
00031 #ifdef HAVE_GROWL
00032 #include <Growl/GrowlApplicationBridge-Carbon.h>
00033 #include <Carbon/Carbon.h>
00034 Growl_Delegate growl_obj;
00035 #endif
00036
00037 #include "foreach.hpp"
00038 #include "game_preferences.hpp"
00039 #include "halo.hpp"
00040 #include "log.hpp"
00041 #include "map.hpp"
00042 #include "map_label.hpp"
00043 #include "marked-up_text.hpp"
00044 #include "reports.hpp"
00045 #include "resources.hpp"
00046 #include "tod_manager.hpp"
00047 #include "sound.hpp"
00048 #include "whiteboard/manager.hpp"
00049
00050 static lg::log_domain log_display("display");
00051 #define ERR_DP LOG_STREAM(err, log_display)
00052 #define LOG_DP LOG_STREAM(info, log_display)
00053
00054 static lg::log_domain log_engine("engine");
00055 #define ERR_NG LOG_STREAM(err, log_engine)
00056
00057 std::map<map_location,fixed_t> game_display::debugHighlights_;
00058
00059 game_display::game_display(unit_map& units, CVideo& video, const gamemap& map,
00060 const tod_manager& tod, const std::vector<team>& t,
00061 const config& theme_cfg, const config& level) :
00062 display(&units, video, &map, &t, theme_cfg, level),
00063 fake_units_(),
00064 attack_indicator_src_(),
00065 attack_indicator_dst_(),
00066 route_(),
00067 tod_manager_(tod),
00068 level_(level),
00069 displayedUnitHex_(),
00070 overlays_(),
00071 sidebarScaling_(1.0),
00072 first_turn_(true),
00073 in_game_(false),
00074 observers_(),
00075 chat_messages_(),
00076 reach_map_(),
00077 reach_map_old_(),
00078 reach_map_changed_(true),
00079 game_mode_(RUNNING),
00080 flags_()
00081 {
00082
00083
00084 flags_.reserve(teams_->size());
00085
00086 std::vector<std::string> side_colors;
00087 side_colors.reserve(teams_->size());
00088
00089 for(size_t i = 0; i != teams_->size(); ++i) {
00090 std::string side_color = team::get_side_color_index(i+1);
00091 side_colors.push_back(side_color);
00092 std::string flag = (*teams_)[i].flag();
00093 std::string old_rgb = game_config::flag_rgb;
00094 std::string new_rgb = side_color;
00095
00096 if(flag.empty()) {
00097 flag = game_config::images::flag;
00098 }
00099
00100 LOG_DP << "Adding flag for team " << i << " from animation " << flag << "\n";
00101
00102
00103 animated<image::locator> temp_anim;
00104
00105 std::vector<std::string> items = utils::split(flag);
00106 std::vector<std::string>::const_iterator itor = items.begin();
00107 for(; itor != items.end(); ++itor) {
00108 const std::vector<std::string>& items = utils::split(*itor, ':');
00109 std::string str;
00110 int time;
00111
00112 if(items.size() > 1) {
00113 str = items.front();
00114 time = atoi(items.back().c_str());
00115 } else {
00116 str = *itor;
00117 time = 100;
00118 }
00119 std::stringstream temp;
00120 temp << str << "~RC(" << old_rgb << ">"<< new_rgb << ")";
00121 image::locator flag_image(temp.str());
00122 temp_anim.add_frame(time, flag_image);
00123 }
00124 flags_.push_back(temp_anim);
00125
00126 flags_.back().start_animation(rand()%flags_.back().get_end_time(), true);
00127 }
00128 image::set_team_colors(&side_colors);
00129 clear_screen();
00130 }
00131
00132 game_display* game_display::create_dummy_display(CVideo& video)
00133 {
00134 static unit_map dummy_umap;
00135 static config dummy_cfg;
00136 static gamemap dummy_map(dummy_cfg, "");
00137 static tod_manager dummy_tod(dummy_cfg, 0);
00138 static std::vector<team> dummy_teams;
00139 return new game_display(dummy_umap, video, dummy_map, dummy_tod,
00140 dummy_teams, dummy_cfg, dummy_cfg);
00141 }
00142
00143 game_display::~game_display()
00144 {
00145
00146 prune_chat_messages(true);
00147 }
00148
00149 void game_display::new_turn()
00150 {
00151 const time_of_day& tod = tod_manager_.get_time_of_day();
00152
00153 if( !first_turn_) {
00154 const time_of_day& old_tod = tod_manager_.get_previous_time_of_day();
00155
00156 if(old_tod.image_mask != tod.image_mask) {
00157 const surface old_mask(image::get_image(old_tod.image_mask,image::SCALED_TO_HEX));
00158 const surface new_mask(image::get_image(tod.image_mask,image::SCALED_TO_HEX));
00159
00160 const int niterations = static_cast<int>(10/turbo_speed());
00161 const int frame_time = 30;
00162 const int starting_ticks = SDL_GetTicks();
00163 for(int i = 0; i != niterations; ++i) {
00164
00165 if(old_mask != NULL) {
00166 const fixed_t proportion = ftofxp(1.0) - fxpdiv(i,niterations);
00167 tod_hex_mask1.assign(adjust_surface_alpha(old_mask,proportion));
00168 }
00169
00170 if(new_mask != NULL) {
00171 const fixed_t proportion = fxpdiv(i,niterations);
00172 tod_hex_mask2.assign(adjust_surface_alpha(new_mask,proportion));
00173 }
00174
00175 invalidate_all();
00176 draw();
00177
00178 const int cur_ticks = SDL_GetTicks();
00179 const int wanted_ticks = starting_ticks + i*frame_time;
00180 if(cur_ticks < wanted_ticks) {
00181 SDL_Delay(wanted_ticks - cur_ticks);
00182 }
00183 }
00184 }
00185
00186 tod_hex_mask1.assign(NULL);
00187 tod_hex_mask2.assign(NULL);
00188 }
00189
00190 first_turn_ = false;
00191
00192 display::update_tod();
00193
00194 invalidate_all();
00195 draw();
00196 }
00197
00198 void game_display::select_hex(map_location hex)
00199 {
00200 if(hex.valid() && fogged(hex)) {
00201 return;
00202 }
00203 display::select_hex(hex);
00204
00205 display_unit_hex(hex);
00206 }
00207
00208 void game_display::highlight_hex(map_location hex)
00209 {
00210 wb::future_map future;
00211
00212 const unit *u = get_visible_unit(hex, (*teams_)[viewing_team()], !viewpoint_);
00213 if (u) {
00214 displayedUnitHex_ = hex;
00215 invalidate_unit();
00216 } else {
00217 u = get_visible_unit(mouseoverHex_, (*teams_)[viewing_team()], !viewpoint_);
00218 if (u) {
00219
00220 if (units_->count(selectedHex_)) {
00221 displayedUnitHex_ = selectedHex_;
00222 invalidate_unit();
00223 }
00224 }
00225 }
00226
00227 display::highlight_hex(hex);
00228 invalidate_game_status();
00229 }
00230
00231
00232 void game_display::display_unit_hex(map_location hex)
00233 {
00234 if (!hex.valid())
00235 return;
00236
00237 wb::future_map future;
00238
00239 const unit *u = get_visible_unit(hex, (*teams_)[viewing_team()], !viewpoint_);
00240 if (u) {
00241 displayedUnitHex_ = hex;
00242 invalidate_unit();
00243 }
00244 }
00245
00246 void game_display::invalidate_unit_after_move(const map_location& src, const map_location& dst)
00247 {
00248 if (src == displayedUnitHex_) {
00249 displayedUnitHex_ = dst;
00250 invalidate_unit();
00251 }
00252 }
00253
00254 void game_display::scroll_to_leader(unit_map& units, int side, SCROLL_TYPE scroll_type,bool force)
00255 {
00256 unit_map::const_iterator leader = units.find_leader(side);
00257
00258 if(leader != units_->end()) {
00259
00260
00261
00262
00263
00264 scroll_to_tile(leader->get_location(), scroll_type, true, force);
00265 }
00266 }
00267
00268 void game_display::pre_draw() {
00269 if (resources::whiteboard) {
00270 resources::whiteboard->pre_draw();
00271 }
00272 process_reachmap_changes();
00273
00274
00275
00276
00277 prune_chat_messages();
00278 }
00279
00280
00281 void game_display::post_draw() {
00282 if (resources::whiteboard) {
00283 resources::whiteboard->post_draw();
00284 }
00285 }
00286
00287 void game_display::draw_invalidated()
00288 {
00289 halo::unrender(invalidated_);
00290 display::draw_invalidated();
00291
00292 foreach(unit* temp_unit, fake_units_) {
00293 const map_location& loc = temp_unit->get_location();
00294 exclusive_unit_draw_requests_t::iterator request = exclusive_unit_draw_requests_.find(loc);
00295 if (invalidated_.find(loc) != invalidated_.end()
00296 && (request == exclusive_unit_draw_requests_.end() || request->second == temp_unit->id()))
00297 temp_unit->redraw_unit();
00298 }
00299
00300 }
00301
00302 void game_display::post_commit()
00303 {
00304 halo::render();
00305 }
00306
00307 void game_display::draw_hex(const map_location& loc)
00308 {
00309 const bool on_map = get_map().on_board(loc);
00310 const bool is_shrouded = shrouded(loc);
00311 const bool is_fogged = fogged(loc);
00312 const int xpos = get_location_x(loc);
00313 const int ypos = get_location_y(loc);
00314
00315 image::TYPE image_type = get_image_type(loc);
00316
00317 display::draw_hex(loc);
00318
00319 if(on_map && loc == mouseoverHex_) {
00320 tdrawing_layer hex_top_layer = LAYER_MOUSEOVER_BOTTOM;
00321 if( get_visible_unit(loc, (*teams_)[viewing_team()] ) != NULL ) {
00322 hex_top_layer = LAYER_MOUSEOVER_TOP;
00323 }
00324 drawing_buffer_add( hex_top_layer,
00325 loc, xpos, ypos, image::get_image("misc/hover-hex-top.png", image::SCALED_TO_HEX));
00326 drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM,
00327 loc, xpos, ypos, image::get_image("misc/hover-hex-bottom.png", image::SCALED_TO_HEX));
00328 }
00329
00330 if(!is_shrouded) {
00331 typedef overlay_map::const_iterator Itor;
00332 std::pair<Itor,Itor> overlays = overlays_.equal_range(loc);
00333 for( ; overlays.first != overlays.second; ++overlays.first) {
00334 if ((overlays.first->second.team_name == "" ||
00335 overlays.first->second.team_name.find((*teams_)[playing_team()].team_name()) != std::string::npos)
00336 && !(is_fogged && !overlays.first->second.visible_in_fog))
00337 {
00338 drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos,
00339 image::get_image(overlays.first->second.image,image_type));
00340 }
00341 }
00342
00343 drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, get_flag(loc));
00344 }
00345
00346
00347
00348
00349 if (!is_shrouded && !reach_map_.empty()
00350 && reach_map_.find(loc) == reach_map_.end() && loc != attack_indicator_dst_) {
00351 static const image::locator unreachable(game_config::images::unreachable);
00352 drawing_buffer_add(LAYER_REACHMAP, loc, xpos, ypos,
00353 image::get_image(unreachable,image::SCALED_TO_HEX));
00354 }
00355
00356 resources::whiteboard->draw_hex(loc);
00357
00358 if (!(resources::whiteboard->is_active() && resources::whiteboard->has_temp_move()))
00359 {
00360
00361 const std::vector<surface>& footstepImages = footsteps_images(loc);
00362 if (!footstepImages.empty()) {
00363 drawing_buffer_add(LAYER_FOOTSTEPS, loc, xpos, ypos, footstepImages);
00364 }
00365 }
00366
00367 if(on_map && loc == attack_indicator_src_) {
00368 drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc, xpos, ypos,
00369 image::get_image("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::SCALED_TO_HEX));
00370 } else if (on_map && loc == attack_indicator_dst_) {
00371 drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc, xpos, ypos,
00372 image::get_image("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::SCALED_TO_HEX));
00373 }
00374
00375
00376
00377 if(game_mode_ != RUNNING) {
00378 static const image::locator linger(game_config::images::linger);
00379 drawing_buffer_add(LAYER_LINGER_OVERLAY, loc, xpos, ypos,
00380 image::get_image(linger, image::TOD_COLORED));
00381 }
00382
00383 if(on_map && loc == selectedHex_ && !game_config::images::selected.empty()) {
00384 static const image::locator selected(game_config::images::selected);
00385 drawing_buffer_add(LAYER_SELECTED_HEX, loc, xpos, ypos,
00386 image::get_image(selected, image::SCALED_TO_HEX));
00387 }
00388
00389
00390 if(!is_shrouded && on_map) {
00391 draw_movement_info(loc);
00392 }
00393
00394 if(game_config::debug) {
00395 int debugH = debugHighlights_[loc];
00396 if (debugH) {
00397 std::string txt = lexical_cast<std::string>(debugH);
00398 draw_text_in_hex(loc, LAYER_MOVE_INFO, txt, 18, font::BAD_COLOR);
00399 }
00400 }
00401
00402 }
00403
00404 const time_of_day& game_display::get_time_of_day(const map_location& loc) const
00405 {
00406 return tod_manager_.get_time_of_day(loc);
00407 }
00408
00409 bool game_display::has_time_area() const
00410 {
00411 return tod_manager_.has_time_area();
00412 }
00413
00414 void game_display::draw_report(const std::string &report_name)
00415 {
00416 if(!team_valid()) {
00417 return;
00418 }
00419
00420 refresh_report(report_name, reports::generate_report(report_name));
00421 }
00422
00423 void game_display::draw_sidebar()
00424 {
00425 draw_report("report_clock");
00426 draw_report("report_countdown");
00427
00428 if(teams_->empty()) {
00429 return;
00430 }
00431
00432 if (invalidateGameStatus_)
00433 {
00434 wb::future_map future;
00435
00436
00437
00438 foreach (const std::string &name, reports::report_list()) {
00439 draw_report(name);
00440 }
00441 invalidateGameStatus_ = false;
00442 }
00443 }
00444
00445
00446 void game_display::set_game_mode(const tgame_mode game_mode)
00447 {
00448 if(game_mode != game_mode_) {
00449 game_mode_ = game_mode;
00450 invalidate_all();
00451 }
00452 }
00453
00454 void game_display::draw_movement_info(const map_location& loc)
00455 {
00456
00457 pathfind::marked_route::mark_map::iterator w = route_.marks.find(loc);
00458
00459
00460 if(w != route_.marks.end()
00461 && !route_.steps.empty() && route_.steps.front() != loc) {
00462 const unit_map::const_iterator un =
00463 resources::whiteboard->get_temp_move_unit().valid() ?
00464 resources::whiteboard->get_temp_move_unit() : units_->find(route_.steps.front());
00465 if(un != units_->end()) {
00466
00467 int def = 100 - un->defense_modifier(get_map().get_terrain(loc));
00468 std::stringstream def_text;
00469 def_text << def << "%";
00470
00471 SDL_Color color = int_to_color(game_config::red_to_green(def, false));
00472
00473
00474 int def_font = w->second.turns > 0 ? 18 : 16;
00475 draw_text_in_hex(loc, LAYER_MOVE_INFO, def_text.str(), def_font, color);
00476
00477 int xpos = get_location_x(loc);
00478 int ypos = get_location_y(loc);
00479
00480 if (w->second.invisible) {
00481 drawing_buffer_add(LAYER_MOVE_INFO, loc, xpos, ypos,
00482 image::get_image("misc/hidden.png", image::SCALED_TO_HEX));
00483 }
00484
00485 if (w->second.zoc) {
00486 drawing_buffer_add(LAYER_MOVE_INFO, loc, xpos, ypos,
00487 image::get_image("misc/zoc.png", image::SCALED_TO_HEX));
00488 }
00489
00490 if (w->second.capture) {
00491 drawing_buffer_add(LAYER_MOVE_INFO, loc, xpos, ypos,
00492 image::get_image("misc/capture.png", image::SCALED_TO_HEX));
00493 }
00494
00495
00496 if (w->second.turns > 1 || (w->second.turns == 1 && loc != route_.steps.back())) {
00497 std::stringstream turns_text;
00498 turns_text << w->second.turns;
00499 draw_text_in_hex(loc, LAYER_MOVE_INFO, turns_text.str(), 17, font::NORMAL_COLOR, 0.5,0.8);
00500 }
00501
00502
00503 return;
00504 }
00505 }
00506
00507 else if (selectedHex_.valid() && loc == mouseoverHex_)
00508 {
00509 const unit_map::const_iterator selectedUnit = find_visible_unit(selectedHex_,resources::teams->at(currentTeam_));
00510 const unit_map::const_iterator mouseoveredUnit = find_visible_unit(mouseoverHex_,resources::teams->at(currentTeam_));
00511 if(selectedUnit != units_->end() && mouseoveredUnit == units_->end()) {
00512
00513 int def = 100 - selectedUnit->defense_modifier(get_map().get_terrain(loc));
00514 std::stringstream def_text;
00515 def_text << def << "%";
00516
00517 SDL_Color color = int_to_color(game_config::red_to_green(def, false));
00518
00519
00520 int def_font = 16;
00521 draw_text_in_hex(loc, LAYER_MOVE_INFO, def_text.str(), def_font, color);
00522 }
00523 }
00524
00525 if (!reach_map_.empty()) {
00526 reach_map::iterator reach = reach_map_.find(loc);
00527 if (reach != reach_map_.end() && reach->second > 1) {
00528 const std::string num = lexical_cast<std::string>(reach->second);
00529 draw_text_in_hex(loc, LAYER_MOVE_INFO, num, 16, font::YELLOW_COLOR);
00530 }
00531 }
00532 }
00533
00534 std::vector<surface> game_display::footsteps_images(const map_location& loc)
00535 {
00536 std::vector<surface> res;
00537
00538 if (route_.steps.size() < 2) {
00539 return res;
00540 }
00541
00542 std::vector<map_location>::const_iterator i =
00543 std::find(route_.steps.begin(),route_.steps.end(),loc);
00544
00545 if( i == route_.steps.end()) {
00546 return res;
00547 }
00548
00549
00550 int move_cost = 1;
00551 const unit_map::const_iterator u = units_->find(route_.steps.front());
00552 if(u != units_->end()) {
00553 move_cost = u->movement_cost(get_map().get_terrain(loc));
00554 }
00555 int image_number = std::min<int>(move_cost, game_config::foot_speed_prefix.size());
00556 if (image_number < 1) {
00557 return res;
00558 }
00559 const std::string foot_speed_prefix = game_config::foot_speed_prefix[image_number-1];
00560
00561 surface teleport = NULL;
00562
00563
00564
00565 const int first_half = (i == route_.steps.begin()) ? 1 : 0;
00566
00567 const int second_half = (i+1 == route_.steps.end()) ? 0 : 1;
00568
00569 for (int h = first_half; h <= second_half; ++h) {
00570 const std::string sense( h==0 ? "-in" : "-out" );
00571
00572 if (!tiles_adjacent(*(i+(h-1)), *(i+h))) {
00573 std::string teleport_image =
00574 h==0 ? game_config::foot_teleport_enter : game_config::foot_teleport_exit;
00575 teleport = image::get_image(teleport_image, image::SCALED_TO_HEX);
00576 continue;
00577 }
00578
00579
00580 map_location::DIRECTION dir = (i+(h-1))->get_relative_dir(*(i+h));
00581
00582 std::string rotate;
00583 if (dir > map_location::SOUTH_EAST) {
00584
00585 dir = i->get_opposite_dir(dir);
00586 rotate = "~FL(horiz)~FL(vert)";
00587 }
00588
00589 const std::string image = foot_speed_prefix
00590 + sense + "-" + i->write_direction(dir)
00591 + ".png" + rotate;
00592
00593 res.push_back(image::get_image(image, image::SCALED_TO_HEX));
00594 }
00595
00596
00597 if (teleport != NULL) res.push_back(teleport);
00598
00599 return res;
00600 }
00601
00602 surface game_display::get_flag(const map_location& loc)
00603 {
00604 t_translation::t_terrain terrain = get_map().get_terrain(loc);
00605
00606 if(!get_map().is_village(terrain)) {
00607 return surface(NULL);
00608 }
00609
00610 for(size_t i = 0; i != teams_->size(); ++i) {
00611 if((*teams_)[i].owns_village(loc) &&
00612 (!fogged(loc) || !(*teams_)[currentTeam_].is_enemy(i+1)))
00613 {
00614 flags_[i].update_last_draw_time();
00615 const image::locator &image_flag = animate_map_ ?
00616 flags_[i].get_current_frame() : flags_[i].get_first_frame();
00617 return image::get_image(image_flag, image::TOD_COLORED);
00618 }
00619 }
00620
00621 return surface(NULL);
00622 }
00623
00624 void game_display::highlight_reach(const pathfind::paths &paths_list)
00625 {
00626 unhighlight_reach();
00627 highlight_another_reach(paths_list);
00628 }
00629
00630 void game_display::highlight_another_reach(const pathfind::paths &paths_list)
00631 {
00632
00633 foreach (const pathfind::paths::step &dest, paths_list.destinations) {
00634 reach_map_[dest.curr]++;
00635 }
00636 reach_map_changed_ = true;
00637 }
00638
00639 void game_display::unhighlight_reach()
00640 {
00641 reach_map_ = reach_map();
00642 reach_map_changed_ = true;
00643 }
00644
00645 void game_display::process_reachmap_changes()
00646 {
00647 if (!reach_map_changed_) return;
00648 if (reach_map_.empty() != reach_map_old_.empty()) {
00649
00650 reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
00651
00652 rect_of_hexes hexes = get_visible_hexes();
00653 rect_of_hexes::iterator i = hexes.begin(), end = hexes.end();
00654 for (;i != end; ++i) {
00655 reach_map::iterator reach = full.find(*i);
00656 if (reach == full.end()) {
00657
00658 invalidate(*i);
00659 } else if (reach->second != 1) {
00660
00661 invalidate(*i);
00662 }
00663 }
00664 } else if (!reach_map_.empty()) {
00665
00666 reach_map::iterator reach, reach_old;
00667 for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
00668 reach_old = reach_map_old_.find(reach->first);
00669 if (reach_old == reach_map_old_.end()) {
00670 invalidate(reach->first);
00671 } else {
00672 if (reach_old->second != reach->second) {
00673 invalidate(reach->first);
00674 }
00675 reach_map_old_.erase(reach_old);
00676 }
00677 }
00678 for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
00679 invalidate(reach_old->first);
00680 }
00681 }
00682 reach_map_old_ = reach_map_;
00683 reach_map_changed_ = false;
00684 }
00685
00686 void game_display::invalidate_route()
00687 {
00688 for(std::vector<map_location>::const_iterator i = route_.steps.begin();
00689 i != route_.steps.end(); ++i) {
00690 invalidate(*i);
00691 }
00692 }
00693
00694 void game_display::set_route(const pathfind::marked_route *route)
00695 {
00696 invalidate_route();
00697
00698 if(route != NULL) {
00699 route_ = *route;
00700 } else {
00701 route_.steps.clear();
00702 route_.marks.clear();
00703 }
00704
00705 invalidate_route();
00706 }
00707
00708 void game_display::float_label(const map_location& loc, const std::string& text,
00709 int red, int green, int blue)
00710 {
00711 if(preferences::show_floating_labels() == false || fogged(loc)) {
00712 return;
00713 }
00714
00715 font::floating_label flabel(text);
00716 flabel.set_font_size(font::SIZE_XLARGE);
00717 const SDL_Color color = create_color(red, green, blue);
00718 flabel.set_color(color);
00719 flabel.set_position(get_location_x(loc)+zoom_/2, get_location_y(loc));
00720 flabel.set_move(0, -2 * turbo_speed());
00721 flabel.set_lifetime(60/turbo_speed());
00722 flabel.set_scroll_mode(font::ANCHOR_LABEL_MAP);
00723
00724 font::add_floating_label(flabel);
00725 }
00726
00727 void game_display::invalidate_animations_location(const map_location& loc) {
00728 if (get_map().is_village(loc)) {
00729 const int owner = player_teams::village_owner(loc);
00730 if (owner >= 0 && flags_[owner].need_update()
00731 && (!fogged(loc) || !(*teams_)[currentTeam_].is_enemy(owner+1))) {
00732 invalidate(loc);
00733 }
00734 }
00735 }
00736
00737 void game_display::invalidate_animations()
00738 {
00739 display::invalidate_animations();
00740 foreach(unit* temp_unit, fake_units_) {
00741 temp_unit->refresh();
00742 }
00743 std::vector<unit*> unit_list;
00744 foreach (unit *u, fake_units_) {
00745 unit_list.push_back(u);
00746 }
00747 bool new_inval;
00748 do {
00749 new_inval = false;
00750 #ifdef _OPENMP
00751 #pragma omp parallel for reduction(|:new_inval) shared(unit_list) schedule(guided)
00752 #endif //_OPENMP
00753 for(int i=0; i < static_cast<int>(unit_list.size()); i++) {
00754 new_inval |= unit_list[i]->invalidate(unit_list[i]->get_location());
00755 }
00756 }while(new_inval);
00757 }
00758
00759 int& game_display::debug_highlight(const map_location& loc)
00760 {
00761 assert(game_config::debug);
00762 return debugHighlights_[loc];
00763 }
00764
00765 game_display::fake_unit::fake_unit(unit const & u) : unit(u), my_display_(NULL){ }
00766 game_display::fake_unit::fake_unit(fake_unit const & a) : unit(a), my_display_(NULL){ }
00767 game_display::fake_unit & game_display::fake_unit::operator=(fake_unit const & a) {
00768 if(this != &a){
00769 this->unit::operator=(a);
00770 my_display_= a.my_display_;
00771 }
00772 return *this;
00773 }
00774 game_display::fake_unit & game_display::fake_unit::operator=(unit const & a) {
00775 this->unit::operator=(a);
00776 return *this;
00777 }
00778
00779 game_display::fake_unit::~fake_unit() {
00780
00781
00782 if(my_display_){remove_from_game_display();}
00783 }
00784 void game_display::fake_unit::place_on_game_display(game_display * display){
00785 assert(my_display_ == NULL);
00786 my_display_=display;
00787 my_display_->place_temporary_unit(this);
00788 }
00789 int game_display::fake_unit::remove_from_game_display(){
00790 int ret(0);
00791 if(my_display_ != NULL){
00792 ret = my_display_->remove_temporary_unit(this);
00793 my_display_=NULL;
00794 }
00795 return ret;
00796 }
00797
00798 void game_display::place_temporary_unit(unit *u)
00799 {
00800 if(std::find(fake_units_.begin(),fake_units_.end(), u) != fake_units_.end()) {
00801 ERR_NG << "In game_display::place_temporary_unit: attempt to add duplicate fake unit." << std::endl;
00802 } else {
00803 fake_units_.push_back(u);
00804 invalidate(u->get_location());
00805 }
00806 }
00807
00808 int game_display::remove_temporary_unit(unit *u)
00809 {
00810 int removed = 0;
00811 std::deque<unit*>::iterator it =
00812 std::remove(fake_units_.begin(), fake_units_.end(), u);
00813 if (it != fake_units_.end()) {
00814 removed = std::distance(it, fake_units_.end());
00815
00816 fake_units_.erase(it, fake_units_.end());
00817 invalidate(u->get_location());
00818
00819 u->clear_haloes();
00820 }
00821 if (removed > 1) {
00822 ERR_NG << "Error: duplicate temp unit found in game_display::remove_temporary_unit" << std::endl;
00823 }
00824 return removed;
00825 }
00826
00827 void game_display::set_attack_indicator(const map_location& src, const map_location& dst)
00828 {
00829 if (attack_indicator_src_ != src || attack_indicator_dst_ != dst) {
00830 invalidate(attack_indicator_src_);
00831 invalidate(attack_indicator_dst_);
00832
00833 attack_indicator_src_ = src;
00834 attack_indicator_dst_ = dst;
00835
00836 invalidate(attack_indicator_src_);
00837 invalidate(attack_indicator_dst_);
00838 }
00839 }
00840
00841 void game_display::clear_attack_indicator()
00842 {
00843 set_attack_indicator(map_location::null_location, map_location::null_location);
00844 }
00845
00846 void game_display::add_overlay(const map_location& loc, const std::string& img, const std::string& halo,const std::string& team_name, bool visible_under_fog)
00847 {
00848 const int halo_handle = halo::add(get_location_x(loc) + hex_size() / 2,
00849 get_location_y(loc) + hex_size() / 2, halo, loc);
00850
00851 const overlay item(img, halo, halo_handle, team_name, visible_under_fog);
00852 overlays_.insert(overlay_map::value_type(loc,item));
00853 }
00854
00855 void game_display::remove_overlay(const map_location& loc)
00856 {
00857 typedef overlay_map::const_iterator Itor;
00858 std::pair<Itor,Itor> itors = overlays_.equal_range(loc);
00859 while(itors.first != itors.second) {
00860 halo::remove(itors.first->second.halo_handle);
00861 ++itors.first;
00862 }
00863
00864 overlays_.erase(loc);
00865 }
00866
00867 void game_display::remove_single_overlay(const map_location& loc, const std::string& toDelete)
00868 {
00869
00870 typedef overlay_map::iterator Itor;
00871 overlay_map::iterator iteratorCopy;
00872 std::pair<Itor,Itor> itors = overlays_.equal_range(loc);
00873 while(itors.first != itors.second) {
00874
00875 if(itors.first->second.image == toDelete || itors.first->second.halo == toDelete) {
00876 iteratorCopy = itors.first;
00877 ++itors.first;
00878 halo::remove(iteratorCopy->second.halo_handle);
00879 overlays_.erase(iteratorCopy);
00880 }
00881 else {
00882 ++itors.first;
00883 }
00884 }
00885 }
00886
00887 void game_display::parse_team_overlays()
00888 {
00889 const team& curr_team = (*teams_)[playing_team()];
00890 const team& prev_team = (*teams_)[playing_team()-1 < teams_->size() ? playing_team()-1 : teams_->size()-1];
00891 foreach (const game_display::overlay_map::value_type i, overlays_) {
00892 const overlay& ov = i.second;
00893 if (!ov.team_name.empty() &&
00894 ((ov.team_name.find(curr_team.team_name()) + 1) != 0) !=
00895 ((ov.team_name.find(prev_team.team_name()) + 1) != 0))
00896 {
00897 invalidate(i.first);
00898 }
00899 }
00900 }
00901
00902 std::string game_display::current_team_name() const
00903 {
00904 if (team_valid())
00905 {
00906 return (*teams_)[currentTeam_].team_name();
00907 }
00908 return std::string();
00909 }
00910
00911 #ifdef HAVE_LIBDBUS
00912
00913 static bool kde_style = false;
00914
00915 struct wnotify
00916 {
00917 wnotify()
00918 : id()
00919 , owner()
00920 , message()
00921 {
00922 }
00923
00924 uint32_t id;
00925 std::string owner;
00926 std::string message;
00927 };
00928
00929 static std::list<wnotify> notifications;
00930
00931 static DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
00932 {
00933 if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
00934 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
00935 }
00936
00937 uint32_t id;
00938 dbus_message_get_args(buf, NULL,
00939 DBUS_TYPE_UINT32, &id,
00940 DBUS_TYPE_INVALID);
00941
00942 std::list<wnotify>::iterator i = notifications.begin(),
00943 i_end = notifications.end();
00944 while (i != i_end && i->id != id) ++i;
00945 if (i != i_end)
00946 notifications.erase(i);
00947
00948 return DBUS_HANDLER_RESULT_HANDLED;
00949 }
00950
00951 static DBusConnection *get_dbus_connection()
00952 {
00953 if (preferences::get("disable_notifications", false)) {
00954 return NULL;
00955 }
00956
00957 static bool initted = false;
00958 static DBusConnection *connection = NULL;
00959
00960 if (!initted)
00961 {
00962 initted = true;
00963 if (getenv("KDE_SESSION_VERSION")) {
00964
00965 kde_style = true;
00966 }
00967 DBusError err;
00968 dbus_error_init(&err);
00969 connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
00970 if (!connection) {
00971 ERR_DP << "Failed to open DBus session: " << err.message << '\n';
00972 dbus_error_free(&err);
00973 return NULL;
00974 }
00975 dbus_connection_add_filter(connection, filter_dbus_signal, NULL, NULL);
00976 }
00977 if (connection) {
00978 dbus_connection_read_write(connection, 0);
00979 while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
00980 }
00981 return connection;
00982 }
00983
00984 static uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
00985 const std::string &owner, const std::string &message)
00986 {
00987 DBusMessage *buf = dbus_message_new_method_call(
00988 kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
00989 kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
00990 kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
00991 "Notify");
00992 const char *app_name = "Battle for Wesnoth";
00993 dbus_message_append_args(buf,
00994 DBUS_TYPE_STRING, &app_name,
00995 DBUS_TYPE_UINT32, &replaces_id,
00996 DBUS_TYPE_INVALID);
00997 if (kde_style) {
00998 const char *event_id = "";
00999 dbus_message_append_args(buf,
01000 DBUS_TYPE_STRING, &event_id,
01001 DBUS_TYPE_INVALID);
01002 }
01003 std::string app_icon_ = game_config::path + "/images/wesnoth-icon-small.png";
01004 const char *app_icon = app_icon_.c_str();
01005 const char *summary = owner.c_str();
01006 const char *body = message.c_str();
01007 const char **actions = NULL;
01008 dbus_message_append_args(buf,
01009 DBUS_TYPE_STRING, &app_icon,
01010 DBUS_TYPE_STRING, &summary,
01011 DBUS_TYPE_STRING, &body,
01012 DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
01013 DBUS_TYPE_INVALID);
01014 DBusMessageIter iter, hints;
01015 dbus_message_iter_init_append(buf, &iter);
01016 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
01017 dbus_message_iter_close_container(&iter, &hints);
01018 int expire_timeout = kde_style ? 5000 : -1;
01019 dbus_message_append_args(buf,
01020 DBUS_TYPE_INT32, &expire_timeout,
01021 DBUS_TYPE_INVALID);
01022 DBusError err;
01023 dbus_error_init(&err);
01024 DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
01025 dbus_message_unref(buf);
01026 if (!ret) {
01027 ERR_DP << "Failed to send visual notification: " << err.message << '\n';
01028 dbus_error_free(&err);
01029 if (kde_style) {
01030 ERR_DP << " Retrying with the freedesktop protocol.\n";
01031 kde_style = false;
01032 return send_dbus_notification(connection, replaces_id, owner, message);
01033 }
01034 return 0;
01035 }
01036 uint32_t id;
01037 dbus_message_get_args(ret, NULL,
01038 DBUS_TYPE_UINT32, &id,
01039 DBUS_TYPE_INVALID);
01040 dbus_message_unref(ret);
01041
01042 if (kde_style) return 0;
01043 return id;
01044 }
01045 #endif
01046
01047 #if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL)
01048 void game_display::send_notification(const std::string& owner, const std::string& message)
01049 #else
01050 void game_display::send_notification(const std::string& , const std::string& )
01051 #endif
01052 {
01053 #if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL)
01054 Uint8 app_state = SDL_GetAppState();
01055
01056
01057 if ((app_state & SDL_APPACTIVE) != 0)
01058 {
01059
01060 if ((app_state & (SDL_APPMOUSEFOCUS | SDL_APPINPUTFOCUS)) != 0) {
01061 return;
01062 }
01063 }
01064 #endif
01065
01066 #ifdef HAVE_LIBDBUS
01067 DBusConnection *connection = get_dbus_connection();
01068 if (!connection) return;
01069
01070 std::list<wnotify>::iterator i = notifications.begin(),
01071 i_end = notifications.end();
01072 while (i != i_end && i->owner != owner) ++i;
01073
01074 if (i != i_end) {
01075 i->message += "\n";
01076 i->message += message;
01077 send_dbus_notification(connection, i->id, owner, i->message);
01078 return;
01079 }
01080
01081 uint32_t id = send_dbus_notification(connection, 0, owner, message);
01082 if (!id) return;
01083 wnotify visual;
01084 visual.id = id;
01085 visual.owner = owner;
01086 visual.message = message;
01087 notifications.push_back(visual);
01088 #endif
01089
01090 #ifdef HAVE_GROWL
01091 CFStringRef app_name = CFStringCreateWithCString(NULL, "Wesnoth", kCFStringEncodingUTF8);
01092 CFStringRef cf_owner = CFStringCreateWithCString(NULL, owner.c_str(), kCFStringEncodingUTF8);
01093 CFStringRef cf_message = CFStringCreateWithCString(NULL, message.c_str(), kCFStringEncodingUTF8);
01094
01095 CFStringRef cf_note_name = CFStringCreateWithCString(NULL, owner == "Turn changed" ? "Turn changed" : "Chat message", kCFStringEncodingUTF8);
01096
01097 growl_obj.applicationName = app_name;
01098 growl_obj.registrationDictionary = NULL;
01099 growl_obj.applicationIconData = NULL;
01100 growl_obj.growlIsReady = NULL;
01101 growl_obj.growlNotificationWasClicked = NULL;
01102 growl_obj.growlNotificationTimedOut = NULL;
01103
01104 Growl_SetDelegate(&growl_obj);
01105 Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(cf_owner, cf_message, cf_note_name, NULL, NULL, NULL, NULL);
01106
01107 CFRelease(app_name);
01108 CFRelease(cf_owner);
01109 CFRelease(cf_message);
01110 CFRelease(cf_note_name);
01111 #endif
01112 }
01113
01114 void game_display::set_team(size_t teamindex, bool show_everything)
01115 {
01116 assert(teamindex < teams_->size());
01117 currentTeam_ = teamindex;
01118 if (!show_everything)
01119 {
01120 labels().set_team(&(*teams_)[teamindex]);
01121 viewpoint_ = &(*teams_)[teamindex];
01122 }
01123 else
01124 {
01125 labels().set_team(NULL);
01126 viewpoint_ = NULL;
01127 }
01128 labels().recalculate_labels();
01129 if(resources::whiteboard)
01130 resources::whiteboard->on_viewer_change(teamindex);
01131 }
01132
01133 void game_display::set_playing_team(size_t teamindex)
01134 {
01135 assert(teamindex < teams_->size());
01136 activeTeam_ = teamindex;
01137 invalidate_game_status();
01138 }
01139
01140 void game_display::begin_game()
01141 {
01142 in_game_ = true;
01143 create_buttons();
01144 invalidate_all();
01145 }
01146
01147 namespace {
01148 const int chat_message_border = 5;
01149 const int chat_message_x = 10;
01150 const int chat_message_y = 10;
01151 const SDL_Color chat_message_color = {255,255,255,255};
01152 const SDL_Color chat_message_bg = {0,0,0,140};
01153 }
01154
01155 void game_display::add_chat_message(const time_t& time, const std::string& speaker,
01156 int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type,
01157 bool bell)
01158 {
01159 const bool whisper = speaker.find("whisper: ") == 0;
01160 std::string sender = speaker;
01161 if (whisper) {
01162 sender.assign(speaker, 9, speaker.size());
01163 }
01164 if (!preferences::parse_should_show_lobby_join(sender, message)) return;
01165 if (preferences::is_ignored(sender)) return;
01166
01167 preferences::parse_admin_authentication(sender, message);
01168
01169 if (bell) {
01170 if ((type == events::chat_handler::MESSAGE_PRIVATE && (!is_observer() || whisper))
01171 || utils::word_match(message, preferences::login())) {
01172 sound::play_UI_sound(game_config::sounds::receive_message_highlight);
01173 } else if (preferences::is_friend(sender)) {
01174 sound::play_UI_sound(game_config::sounds::receive_message_friend);
01175 } else if (sender == "server") {
01176 sound::play_UI_sound(game_config::sounds::receive_message_server);
01177 } else {
01178 sound::play_UI_sound(game_config::sounds::receive_message);
01179 }
01180 }
01181
01182 bool action = false;
01183
01184 std::string msg;
01185
01186 if (message.find("/me ") == 0) {
01187 msg.assign(message, 4, message.size());
01188 action = true;
01189 } else {
01190 msg += message;
01191 }
01192
01193 try {
01194
01195
01196 msg = font::word_wrap_text(msg,font::SIZE_SMALL,map_outside_area().w*3/4);
01197 } catch (utils::invalid_utf8_exception&) {
01198 ERR_NG << "Invalid utf-8 found, chat message is ignored.\n";
01199 return;
01200 }
01201
01202 int ypos = chat_message_x;
01203 for(std::vector<chat_message>::const_iterator m = chat_messages_.begin(); m != chat_messages_.end(); ++m) {
01204 ypos += std::max(font::get_floating_label_rect(m->handle).h,
01205 font::get_floating_label_rect(m->speaker_handle).h);
01206 }
01207 SDL_Color speaker_color = {255,255,255,255};
01208 if(side >= 1) {
01209 speaker_color = int_to_color(team::get_side_color_range(side).mid());
01210 }
01211
01212 SDL_Color message_color = chat_message_color;
01213 std::stringstream str;
01214 std::stringstream message_str;
01215
01216 if(type == events::chat_handler::MESSAGE_PUBLIC) {
01217 if(action) {
01218 str << "<" << speaker << " " << msg << ">";
01219 message_color = speaker_color;
01220 message_str << " ";
01221 } else {
01222 if (!speaker.empty())
01223 str << "<" << speaker << ">";
01224 message_str << msg;
01225 }
01226 } else {
01227 if(action) {
01228 str << "*" << speaker << " " << msg << "*";
01229 message_color = speaker_color;
01230 message_str << " ";
01231 } else {
01232 if (!speaker.empty())
01233 str << "*" << speaker << "*";
01234 message_str << msg;
01235 }
01236 }
01237
01238
01239 std::stringstream message_complete;
01240 message_complete << preferences::get_chat_timestamp(time) << str.str();
01241
01242 const SDL_Rect rect = map_outside_area();
01243
01244 font::floating_label spk_flabel(message_complete.str());
01245 spk_flabel.set_font_size(font::SIZE_SMALL);
01246 spk_flabel.set_color(speaker_color);
01247 spk_flabel.set_position(rect.x + chat_message_x, rect.y + ypos);
01248 spk_flabel.set_clip_rect(rect);
01249 spk_flabel.set_alignment(font::LEFT_ALIGN);
01250 spk_flabel.set_bg_color(chat_message_bg);
01251 spk_flabel.set_border_size(chat_message_border);
01252 spk_flabel.use_markup(false);
01253
01254 int speaker_handle = font::add_floating_label(spk_flabel);
01255
01256 font::floating_label msg_flabel(message_str.str());
01257 msg_flabel.set_font_size(font::SIZE_SMALL);
01258 msg_flabel.set_color(message_color);
01259 msg_flabel.set_position(rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,
01260 rect.y + ypos);
01261 msg_flabel.set_clip_rect(rect);
01262 msg_flabel.set_alignment(font::LEFT_ALIGN);
01263 msg_flabel.set_bg_color(chat_message_bg);
01264 msg_flabel.set_border_size(chat_message_border);
01265 msg_flabel.use_markup(false);
01266
01267 int message_handle = font::add_floating_label(msg_flabel);
01268
01269
01270 send_notification(speaker, message);
01271
01272 chat_messages_.push_back(chat_message(speaker_handle,message_handle));
01273
01274 prune_chat_messages();
01275 }
01276
01277 void game_display::prune_chat_messages(bool remove_all)
01278 {
01279 const unsigned message_aging = preferences::chat_message_aging();
01280 const unsigned message_ttl = remove_all ? 0 : message_aging * 60 * 1000;
01281 const unsigned max_chat_messages = preferences::chat_lines();
01282 int movement = 0;
01283
01284 if(message_aging != 0 || remove_all || chat_messages_.size() > max_chat_messages) {
01285 while (!chat_messages_.empty() &&
01286 (chat_messages_.front().created_at + message_ttl < SDL_GetTicks() ||
01287 chat_messages_.size() > max_chat_messages))
01288 {
01289 const chat_message &old = chat_messages_.front();
01290 movement += font::get_floating_label_rect(old.handle).h;
01291 font::remove_floating_label(old.speaker_handle);
01292 font::remove_floating_label(old.handle);
01293 chat_messages_.erase(chat_messages_.begin());
01294 }
01295 }
01296
01297 foreach (const chat_message &cm, chat_messages_) {
01298 font::move_floating_label(cm.speaker_handle, 0, - movement);
01299 font::move_floating_label(cm.handle, 0, - movement);
01300 }
01301 }
01302
01303
01304