wesnoth/src/game_display.cpp
2012-02-06 20:27:34 +00:00

1486 lines
43 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* During a game, show map & info-panels at top+right.
*/
#include "global.hpp"
#include "game_display.hpp"
#include "wesconfig.h"
#ifdef HAVE_LIBDBUS
#include <dbus/dbus.h>
#endif
#ifdef HAVE_GROWL
#include <Growl/GrowlApplicationBridge-Carbon.h>
#include <Carbon/Carbon.h>
Growl_Delegate growl_obj;
#endif
#include "foreach.hpp"
#include "game_preferences.hpp"
#include "halo.hpp"
#include "log.hpp"
#include "map.hpp"
#include "map_label.hpp"
#include "marked-up_text.hpp"
#include "reports.hpp"
#include "resources.hpp"
#include "tod_manager.hpp"
#include "sound.hpp"
#include "whiteboard/manager.hpp"
static lg::log_domain log_display("display");
#define ERR_DP LOG_STREAM(err, log_display)
#define LOG_DP LOG_STREAM(info, log_display)
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
std::map<map_location,fixed_t> game_display::debugHighlights_;
game_display::game_display(unit_map& units, CVideo& video, const gamemap& map,
const tod_manager& tod, const std::vector<team>& t,
const config& theme_cfg, const config& level) :
display(video, &map, theme_cfg, level),
units_(units),
fake_units_(),
exclusive_unit_draw_requests_(),
attack_indicator_src_(),
attack_indicator_dst_(),
energy_bar_rects_(),
route_(),
tod_manager_(tod),
teams_(t),
level_(level),
displayedUnitHex_(),
overlays_(),
currentTeam_(0),
activeTeam_(0),
sidebarScaling_(1.0),
first_turn_(true),
in_game_(false),
observers_(),
chat_messages_(),
reach_map_(),
reach_map_old_(),
reach_map_changed_(true),
game_mode_(RUNNING),
flags_()
{
singleton_ = this;
// Inits the flag list and the team colors used by ~TC
flags_.reserve(teams_.size());
std::vector<std::string> side_colors;
side_colors.reserve(teams_.size());
for(size_t i = 0; i != teams_.size(); ++i) {
std::string side_color = team::get_side_color_index(i+1);
side_colors.push_back(side_color);
std::string flag = teams_[i].flag();
std::string old_rgb = game_config::flag_rgb;
std::string new_rgb = side_color;
if(flag.empty()) {
flag = game_config::images::flag;
}
LOG_DP << "Adding flag for team " << i << " from animation " << flag << "\n";
// Must recolor flag image
animated<image::locator> temp_anim;
std::vector<std::string> items = utils::split(flag);
std::vector<std::string>::const_iterator itor = items.begin();
for(; itor != items.end(); ++itor) {
const std::vector<std::string>& items = utils::split(*itor, ':');
std::string str;
int time;
if(items.size() > 1) {
str = items.front();
time = atoi(items.back().c_str());
} else {
str = *itor;
time = 100;
}
std::stringstream temp;
temp << str << "~RC(" << old_rgb << ">"<< new_rgb << ")";
image::locator flag_image(temp.str());
temp_anim.add_frame(time, flag_image);
}
flags_.push_back(temp_anim);
flags_.back().start_animation(rand()%flags_.back().get_end_time(), true);
}
image::set_team_colors(&side_colors);
clear_screen();
}
game_display* game_display::create_dummy_display(CVideo& video)
{
static unit_map dummy_umap;
static config dummy_cfg;
static gamemap dummy_map(dummy_cfg, "");
static tod_manager dummy_tod(dummy_cfg, 0);
static std::vector<team> dummy_teams;
return new game_display(dummy_umap, video, dummy_map, dummy_tod,
dummy_teams, dummy_cfg, dummy_cfg);
}
game_display::~game_display()
{
// SDL_FreeSurface(minimap_);
prune_chat_messages(true);
singleton_ = NULL;
}
void game_display::new_turn()
{
const time_of_day& tod = tod_manager_.get_time_of_day();
if( !first_turn_) {
const time_of_day& old_tod = tod_manager_.get_previous_time_of_day();
if(old_tod.image_mask != tod.image_mask) {
const surface old_mask(image::get_image(old_tod.image_mask,image::SCALED_TO_HEX));
const surface new_mask(image::get_image(tod.image_mask,image::SCALED_TO_HEX));
const int niterations = static_cast<int>(10/turbo_speed());
const int frame_time = 30;
const int starting_ticks = SDL_GetTicks();
for(int i = 0; i != niterations; ++i) {
if(old_mask != NULL) {
const fixed_t proportion = ftofxp(1.0) - fxpdiv(i,niterations);
tod_hex_mask1.assign(adjust_surface_alpha(old_mask,proportion));
}
if(new_mask != NULL) {
const fixed_t proportion = fxpdiv(i,niterations);
tod_hex_mask2.assign(adjust_surface_alpha(new_mask,proportion));
}
invalidate_all();
draw();
const int cur_ticks = SDL_GetTicks();
const int wanted_ticks = starting_ticks + i*frame_time;
if(cur_ticks < wanted_ticks) {
SDL_Delay(wanted_ticks - cur_ticks);
}
}
}
tod_hex_mask1.assign(NULL);
tod_hex_mask2.assign(NULL);
}
first_turn_ = false;
display::update_tod();
invalidate_all();
draw();
}
void game_display::select_hex(map_location hex)
{
if(hex.valid() && fogged(hex)) {
return;
}
display::select_hex(hex);
display_unit_hex(hex);
}
void game_display::highlight_hex(map_location hex)
{
wb::future_map future; //< Lasts for whole method.
const unit *u = get_visible_unit(hex, teams_[viewing_team()], !viewpoint_);
if (u) {
displayedUnitHex_ = hex;
invalidate_unit();
} else {
u = get_visible_unit(mouseoverHex_, teams_[viewing_team()], !viewpoint_);
if (u) {
// mouse moved from unit hex to non-unit hex
if (units_.count(selectedHex_)) {
displayedUnitHex_ = selectedHex_;
invalidate_unit();
}
}
}
display::highlight_hex(hex);
invalidate_game_status();
}
void game_display::display_unit_hex(map_location hex)
{
if (!hex.valid())
return;
wb::future_map future; //< Lasts for whole method.
const unit *u = get_visible_unit(hex, teams_[viewing_team()], !viewpoint_);
if (u) {
displayedUnitHex_ = hex;
invalidate_unit();
}
}
void game_display::invalidate_unit_after_move(const map_location& src, const map_location& dst)
{
if (src == displayedUnitHex_) {
displayedUnitHex_ = dst;
invalidate_unit();
}
}
void game_display::scroll_to_leader(unit_map& units, int side, SCROLL_TYPE scroll_type,bool force)
{
unit_map::const_iterator leader = units.find_leader(side);
if(leader != units_.end()) {
// YogiHH: I can't see why we need another key_handler here,
// therefore I will comment it out :
/*
const hotkey::basic_handler key_events_handler(gui_);
*/
scroll_to_tile(leader->get_location(), scroll_type, true, force);
}
}
void game_display::pre_draw() {
process_reachmap_changes();
/**
* @todo FIXME: must modify changed, but best to do it at the
* floating_label level
*/
prune_chat_messages();
}
void game_display::draw_invalidated()
{
halo::unrender(invalidated_);
display::draw_invalidated();
foreach(unit* temp_unit, fake_units_) {
const map_location& loc = temp_unit->get_location();
exclusive_unit_draw_requests_t::iterator request = exclusive_unit_draw_requests_.find(loc);
if (invalidated_.find(loc) != invalidated_.end()
&& (request == exclusive_unit_draw_requests_.end() || request->second == temp_unit->id()))
temp_unit->redraw_unit();
}
foreach (const map_location& loc, invalidated_) {
unit_map::iterator u_it = units_.find(loc);
exclusive_unit_draw_requests_t::iterator request = exclusive_unit_draw_requests_.find(loc);
if (u_it != units_.end()
&& (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id()))
u_it->redraw_unit();
}
}
void game_display::post_commit()
{
halo::render();
}
void game_display::draw_hex(const map_location& loc)
{
const bool on_map = get_map().on_board(loc);
const bool is_shrouded = shrouded(loc);
const bool is_fogged = fogged(loc);
const int xpos = get_location_x(loc);
const int ypos = get_location_y(loc);
image::TYPE image_type = get_image_type(loc);
display::draw_hex(loc);
if(on_map && loc == mouseoverHex_) {
tdrawing_layer hex_top_layer = LAYER_MOUSEOVER_BOTTOM;
if( get_visible_unit(loc, teams_[viewing_team()] ) != NULL ) {
hex_top_layer = LAYER_MOUSEOVER_TOP;
}
drawing_buffer_add( hex_top_layer,
loc, xpos, ypos, image::get_image("misc/hover-hex-top.png", image::SCALED_TO_HEX));
drawing_buffer_add(LAYER_MOUSEOVER_BOTTOM,
loc, xpos, ypos, image::get_image("misc/hover-hex-bottom.png", image::SCALED_TO_HEX));
}
if(!is_shrouded) {
typedef overlay_map::const_iterator Itor;
std::pair<Itor,Itor> overlays = overlays_.equal_range(loc);
for( ; overlays.first != overlays.second; ++overlays.first) {
if ((overlays.first->second.team_name == "" ||
overlays.first->second.team_name.find(teams_[playing_team()].team_name()) != std::string::npos)
&& !(is_fogged && !overlays.first->second.visible_in_fog))
{
drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos,
image::get_image(overlays.first->second.image,image_type));
}
}
// village-control flags.
drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, get_flag(loc));
}
// Draw reach_map information.
// We remove the reachability mask of the unit
// that we want to attack.
if (!is_shrouded && !reach_map_.empty()
&& reach_map_.find(loc) == reach_map_.end() && loc != attack_indicator_dst_) {
static const image::locator unreachable(game_config::images::unreachable);
drawing_buffer_add(LAYER_REACHMAP, loc, xpos, ypos,
image::get_image(unreachable,image::SCALED_TO_HEX));
}
resources::whiteboard->draw_hex(loc);
if (!(resources::whiteboard->is_active() && resources::whiteboard->has_temp_move()))
{
// Footsteps indicating a movement path
const std::vector<surface>& footstepImages = footsteps_images(loc);
if (footstepImages.size() != 0) {
drawing_buffer_add(LAYER_FOOTSTEPS, loc, xpos, ypos, footstepImages);
}
}
// Draw the attack direction indicator
if(on_map && loc == attack_indicator_src_) {
drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc, xpos, ypos,
image::get_image("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::SCALED_TO_HEX));
} else if (on_map && loc == attack_indicator_dst_) {
drawing_buffer_add(LAYER_ATTACK_INDICATOR, loc, xpos, ypos,
image::get_image("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::SCALED_TO_HEX));
}
// Linger overlay unconditionally otherwise it might give glitches
// so it's drawn over the shroud and fog.
if(game_mode_ != RUNNING) {
static const image::locator linger(game_config::images::linger);
drawing_buffer_add(LAYER_LINGER_OVERLAY, loc, xpos, ypos,
image::get_image(linger, image::TOD_COLORED));
}
if(on_map && loc == selectedHex_ && !game_config::images::selected.empty()) {
static const image::locator selected(game_config::images::selected);
drawing_buffer_add(LAYER_SELECTED_HEX, loc, xpos, ypos,
image::get_image(selected, image::SCALED_TO_HEX));
}
// Show def% and turn to reach info
if(!is_shrouded && on_map) {
draw_movement_info(loc);
}
if(game_config::debug) {
int debugH = debugHighlights_[loc];
if (debugH) {
std::string txt = lexical_cast<std::string>(debugH);
draw_text_in_hex(loc, LAYER_MOVE_INFO, txt, 18, font::BAD_COLOR);
}
}
//simulate_delay += 1;
}
const time_of_day& game_display::get_time_of_day(const map_location& loc) const
{
return tod_manager_.get_time_of_day(loc);
}
bool game_display::has_time_area() const
{
return tod_manager_.has_time_area();
}
void game_display::draw_report(const std::string &report_name)
{
if(!team_valid()) {
return;
}
refresh_report(report_name, reports::generate_report(report_name));
}
void game_display::draw_sidebar()
{
draw_report("report_clock");
draw_report("report_countdown");
if(teams_.empty()) {
return;
}
if (invalidateGameStatus_)
{
wb::future_map future; // start planned unit map scope
// We display the unit the mouse is over if it is over a unit,
// otherwise we display the unit that is selected.
foreach (const std::string &name, reports::report_list()) {
draw_report(name);
}
invalidateGameStatus_ = false;
}
}
void game_display::draw_minimap_units()
{
double xscaling = 1.0 * minimap_location_.w / get_map().w();
double yscaling = 1.0 * minimap_location_.h / get_map().h();
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
if (fogged(u->get_location()) ||
(teams_[currentTeam_].is_enemy(u->side()) &&
u->invisible(u->get_location())) ||
u->get_hidden()) {
continue;
}
int side = u->side();
const SDL_Color col = team::get_minimap_color(side);
const Uint32 mapped_col = SDL_MapRGB(video().getSurface()->format,col.r,col.g,col.b);
double u_x = u->get_location().x * xscaling;
double u_y = (u->get_location().y + (is_odd(u->get_location().x) ? 1 : -1)/4.0) * yscaling;
// use 4/3 to compensate the horizontal hexes imbrication
double u_w = 4.0 / 3.0 * xscaling;
double u_h = yscaling;
SDL_Rect r = create_rect(minimap_location_.x + round_double(u_x)
, minimap_location_.y + round_double(u_y)
, round_double(u_w)
, round_double(u_h));
sdl_fill_rect(video().getSurface(), &r, mapped_col);
}
}
void game_display::draw_bar(const std::string& image, int xpos, int ypos,
const map_location& loc, size_t height, double filled,
const SDL_Color& col, fixed_t alpha)
{
filled = std::min<double>(std::max<double>(filled,0.0),1.0);
height = static_cast<size_t>(height*get_zoom_factor());
surface surf(image::get_image(image,image::SCALED_TO_HEX));
// We use UNSCALED because scaling (and bilinear interpolaion)
// is bad for calculate_energy_bar.
// But we will do a geometric scaling later.
surface bar_surf(image::get_image(image));
if(surf == NULL || bar_surf == NULL) {
return;
}
// calculate_energy_bar returns incorrect results if the surface colors
// have changed (for example, due to bilinear interpolaion)
const SDL_Rect& unscaled_bar_loc = calculate_energy_bar(bar_surf);
SDL_Rect bar_loc;
if (surf->w == bar_surf->w && surf->h == bar_surf->h)
bar_loc = unscaled_bar_loc;
else {
const fixed_t xratio = fxpdiv(surf->w,bar_surf->w);
const fixed_t yratio = fxpdiv(surf->h,bar_surf->h);
const SDL_Rect scaled_bar_loc = create_rect(
fxptoi(unscaled_bar_loc. x * xratio)
, fxptoi(unscaled_bar_loc. y * yratio + 127)
, fxptoi(unscaled_bar_loc. w * xratio + 255)
, fxptoi(unscaled_bar_loc. h * yratio + 255));
bar_loc = scaled_bar_loc;
}
if(height > bar_loc.h) {
height = bar_loc.h;
}
//if(alpha != ftofxp(1.0)) {
// surf.assign(adjust_surface_alpha(surf,alpha));
// if(surf == NULL) {
// return;
// }
//}
const size_t skip_rows = bar_loc.h - height;
SDL_Rect top = create_rect(0, 0, surf->w, bar_loc.y);
SDL_Rect bot = create_rect(0, bar_loc.y + skip_rows, surf->w, 0);
bot.h = surf->w - bot.y;
drawing_buffer_add(LAYER_UNIT_BAR, loc, xpos, ypos, surf, top);
drawing_buffer_add(LAYER_UNIT_BAR, loc, xpos, ypos + top.h, surf, bot);
size_t unfilled = static_cast<size_t>(height * (1.0 - filled));
if(unfilled < height && alpha >= ftofxp(0.3)) {
const Uint8 r_alpha = std::min<unsigned>(unsigned(fxpmult(alpha,255)),255);
surface filled_surf = create_compatible_surface(bar_surf, bar_loc.w, height - unfilled);
SDL_Rect filled_area = create_rect(0, 0, bar_loc.w, height-unfilled);
sdl_fill_rect(filled_surf,&filled_area,SDL_MapRGBA(bar_surf->format,col.r,col.g,col.b, r_alpha));
drawing_buffer_add(LAYER_UNIT_BAR, loc, xpos + bar_loc.x, ypos + bar_loc.y + unfilled, filled_surf);
}
}
void game_display::set_game_mode(const tgame_mode game_mode)
{
if(game_mode != game_mode_) {
game_mode_ = game_mode;
invalidate_all();
}
}
void game_display::draw_movement_info(const map_location& loc)
{
// Search if there is a mark here
pathfind::marked_route::mark_map::iterator w = route_.marks.find(loc);
// Don't use empty route or the first step (the unit will be there)
if(w != route_.marks.end()
&& !route_.steps.empty() && route_.steps.front() != loc) {
const unit_map::const_iterator un =
resources::whiteboard->get_temp_move_unit().valid() ?
resources::whiteboard->get_temp_move_unit() : units_.find(route_.steps.front());
if(un != units_.end()) {
// Display the def% of this terrain
int def = 100 - un->defense_modifier(get_map().get_terrain(loc));
std::stringstream def_text;
def_text << def << "%";
SDL_Color color = int_to_color(game_config::red_to_green(def, false));
// simple mark (no turn point) use smaller font
int def_font = w->second.turns > 0 ? 18 : 16;
draw_text_in_hex(loc, LAYER_MOVE_INFO, def_text.str(), def_font, color);
int xpos = get_location_x(loc);
int ypos = get_location_y(loc);
if (w->second.invisible) {
drawing_buffer_add(LAYER_MOVE_INFO, loc, xpos, ypos,
image::get_image("misc/hidden.png", image::SCALED_TO_HEX));
}
if (w->second.zoc) {
drawing_buffer_add(LAYER_MOVE_INFO, loc, xpos, ypos,
image::get_image("misc/zoc.png", image::SCALED_TO_HEX));
}
if (w->second.capture) {
drawing_buffer_add(LAYER_MOVE_INFO, loc, xpos, ypos,
image::get_image("misc/capture.png", image::SCALED_TO_HEX));
}
//we display turn info only if different from a simple last "1"
if (w->second.turns > 1 || (w->second.turns == 1 && loc != route_.steps.back())) {
std::stringstream turns_text;
turns_text << w->second.turns;
draw_text_in_hex(loc, LAYER_MOVE_INFO, turns_text.str(), 17, font::NORMAL_COLOR, 0.5,0.8);
}
// The hex is full now, so skip the "show enemy moves"
return;
}
}
// When out-of-turn, it's still interesting to check out the terrain defs of the selected unit
else if (selectedHex_.valid() && loc == mouseoverHex_)
{
const unit_map::const_iterator selectedUnit = find_visible_unit(selectedHex_,resources::teams->at(currentTeam_));
const unit_map::const_iterator mouseoveredUnit = find_visible_unit(mouseoverHex_,resources::teams->at(currentTeam_));
if(selectedUnit != units_.end() && mouseoveredUnit == units_.end()) {
// Display the def% of this terrain
int def = 100 - selectedUnit->defense_modifier(get_map().get_terrain(loc));
std::stringstream def_text;
def_text << def << "%";
SDL_Color color = int_to_color(game_config::red_to_green(def, false));
// use small font
int def_font = 16;
draw_text_in_hex(loc, LAYER_MOVE_INFO, def_text.str(), def_font, color);
}
}
if (!reach_map_.empty()) {
reach_map::iterator reach = reach_map_.find(loc);
if (reach != reach_map_.end() && reach->second > 1) {
const std::string num = lexical_cast<std::string>(reach->second);
draw_text_in_hex(loc, LAYER_MOVE_INFO, num, 16, font::YELLOW_COLOR);
}
}
}
std::vector<surface> game_display::footsteps_images(const map_location& loc)
{
std::vector<surface> res;
if (route_.steps.size() < 2) {
return res; // no real "route"
}
std::vector<map_location>::const_iterator i =
std::find(route_.steps.begin(),route_.steps.end(),loc);
if( i == route_.steps.end()) {
return res; // not on the route
}
// Check which footsteps images of game_config we will use
int move_cost = 1;
const unit_map::const_iterator u = units_.find(route_.steps.front());
if(u != units_.end()) {
move_cost = u->movement_cost(get_map().get_terrain(loc));
}
int image_number = std::min<int>(move_cost, game_config::foot_speed_prefix.size());
if (image_number < 1) {
return res; // Invalid movement cost or no images
}
const std::string foot_speed_prefix = game_config::foot_speed_prefix[image_number-1];
surface teleport = NULL;
// We draw 2 half-hex (with possibly different directions),
// but skip the first for the first step.
const int first_half = (i == route_.steps.begin()) ? 1 : 0;
// and the second for the last step
const int second_half = (i+1 == route_.steps.end()) ? 0 : 1;
for (int h = first_half; h <= second_half; ++h) {
const std::string sense( h==0 ? "-in" : "-out" );
if (!tiles_adjacent(*(i+(h-1)), *(i+h))) {
std::string teleport_image =
h==0 ? game_config::foot_teleport_enter : game_config::foot_teleport_exit;
teleport = image::get_image(teleport_image, image::SCALED_TO_HEX);
continue;
}
// In function of the half, use the incoming or outgoing direction
map_location::DIRECTION dir = (i+(h-1))->get_relative_dir(*(i+h));
std::string rotate;
if (dir > map_location::SOUTH_EAST) {
// No image, take the opposite direction and do a 180 rotation
dir = i->get_opposite_dir(dir);
rotate = "~FL(horiz)~FL(vert)";
}
const std::string image = foot_speed_prefix
+ sense + "-" + i->write_direction(dir)
+ ".png" + rotate;
res.push_back(image::get_image(image, image::SCALED_TO_HEX));
}
// we draw teleport image (if any) in last
if (teleport != NULL) res.push_back(teleport);
return res;
}
surface game_display::get_flag(const map_location& loc)
{
t_translation::t_terrain terrain = get_map().get_terrain(loc);
if(!get_map().is_village(terrain)) {
return surface(NULL);
}
for(size_t i = 0; i != teams_.size(); ++i) {
if(teams_[i].owns_village(loc) &&
(!fogged(loc) || !teams_[currentTeam_].is_enemy(i+1)))
{
flags_[i].update_last_draw_time();
const image::locator &image_flag = animate_map_ ?
flags_[i].get_current_frame() : flags_[i].get_first_frame();
return image::get_image(image_flag, image::TOD_COLORED);
}
}
return surface(NULL);
}
void game_display::highlight_reach(const pathfind::paths &paths_list)
{
unhighlight_reach();
highlight_another_reach(paths_list);
}
void game_display::highlight_another_reach(const pathfind::paths &paths_list)
{
// Fold endpoints of routes into reachability map.
foreach (const pathfind::paths::step &dest, paths_list.destinations) {
reach_map_[dest.curr]++;
}
reach_map_changed_ = true;
}
void game_display::unhighlight_reach()
{
reach_map_ = reach_map();
reach_map_changed_ = true;
}
void game_display::process_reachmap_changes()
{
if (!reach_map_changed_) return;
if (reach_map_.empty() != reach_map_old_.empty()) {
// Invalidate everything except the non-darkened tiles
reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
rect_of_hexes hexes = get_visible_hexes();
rect_of_hexes::iterator i = hexes.begin(), end = hexes.end();
for (;i != end; ++i) {
reach_map::iterator reach = full.find(*i);
if (reach == full.end()) {
// Location needs to be darkened or brightened
invalidate(*i);
} else if (reach->second != 1) {
// Number needs to be displayed or cleared
invalidate(*i);
}
}
} else if (!reach_map_.empty()) {
// Invalidate only changes
reach_map::iterator reach, reach_old;
for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
reach_old = reach_map_old_.find(reach->first);
if (reach_old == reach_map_old_.end()) {
invalidate(reach->first);
} else {
if (reach_old->second != reach->second) {
invalidate(reach->first);
}
reach_map_old_.erase(reach_old);
}
}
for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
invalidate(reach_old->first);
}
}
reach_map_old_ = reach_map_;
reach_map_changed_ = false;
}
void game_display::invalidate_route()
{
for(std::vector<map_location>::const_iterator i = route_.steps.begin();
i != route_.steps.end(); ++i) {
invalidate(*i);
}
}
void game_display::set_route(const pathfind::marked_route *route)
{
invalidate_route();
if(route != NULL) {
route_ = *route;
} else {
route_.steps.clear();
route_.marks.clear();
}
invalidate_route();
}
void game_display::float_label(const map_location& loc, const std::string& text,
int red, int green, int blue)
{
if(preferences::show_floating_labels() == false || fogged(loc)) {
return;
}
font::floating_label flabel(text);
flabel.set_font_size(font::SIZE_XLARGE);
const SDL_Color color = create_color(red, green, blue);
flabel.set_color(color);
flabel.set_position(get_location_x(loc)+zoom_/2, get_location_y(loc));
flabel.set_move(0, -2 * turbo_speed());
flabel.set_lifetime(60/turbo_speed());
flabel.set_scroll_mode(font::ANCHOR_LABEL_MAP);
font::add_floating_label(flabel);
}
struct is_energy_color {
bool operator()(Uint32 color) const { return (color&0xFF000000) > 0x10000000 &&
(color&0x00FF0000) < 0x00100000 &&
(color&0x0000FF00) < 0x00001000 &&
(color&0x000000FF) < 0x00000010; }
};
const SDL_Rect& game_display::calculate_energy_bar(surface surf)
{
const std::map<surface,SDL_Rect>::const_iterator i = energy_bar_rects_.find(surf);
if(i != energy_bar_rects_.end()) {
return i->second;
}
int first_row = -1, last_row = -1, first_col = -1, last_col = -1;
surface image(make_neutral_surface(surf));
const_surface_lock image_lock(image);
const Uint32* const begin = image_lock.pixels();
for(int y = 0; y != image->h; ++y) {
const Uint32* const i1 = begin + image->w*y;
const Uint32* const i2 = i1 + image->w;
const Uint32* const itor = std::find_if(i1,i2,is_energy_color());
const int count = std::count_if(itor,i2,is_energy_color());
if(itor != i2) {
if(first_row == -1) {
first_row = y;
}
first_col = itor - i1;
last_col = first_col + count;
last_row = y;
}
}
const SDL_Rect res = create_rect(first_col
, first_row
, last_col-first_col
, last_row+1-first_row);
energy_bar_rects_.insert(std::pair<surface,SDL_Rect>(surf,res));
return calculate_energy_bar(surf);
}
void game_display::invalidate_animations_location(const map_location& loc) {
if (get_map().is_village(loc)) {
const int owner = player_teams::village_owner(loc);
if (owner >= 0 && flags_[owner].need_update()
&& (!fogged(loc) || !teams_[currentTeam_].is_enemy(owner+1))) {
invalidate(loc);
}
}
}
void game_display::invalidate_animations()
{
display::invalidate_animations();
foreach (unit& u, units_) {
u.refresh();
}
foreach(unit* temp_unit, fake_units_) {
temp_unit->refresh();
}
std::vector<unit*> unit_list;
foreach (unit &u, units_) {
unit_list.push_back(&u);
}
foreach (unit *u, fake_units_) {
unit_list.push_back(u);
}
bool new_inval;
do {
new_inval = false;
#ifdef _OPENMP
#pragma omp parallel for reduction(|:new_inval) shared(unit_list) schedule(guided)
#endif //_OPENMP
for(int i=0; i < static_cast<int>(unit_list.size()); i++) {
new_inval |= unit_list[i]->invalidate(unit_list[i]->get_location());
}
}while(new_inval);
}
int& game_display::debug_highlight(const map_location& loc)
{
assert(game_config::debug);
return debugHighlights_[loc];
}
game_display::fake_unit::fake_unit(unit const & u) : unit(u), my_display_(NULL){ }
game_display::fake_unit::fake_unit(fake_unit const & a) : unit(a), my_display_(NULL){ }
game_display::fake_unit & game_display::fake_unit::operator=(fake_unit const & a) {
if(this != &a){
this->unit::operator=(a);
my_display_= a.my_display_;
}
return *this;
}
game_display::fake_unit & game_display::fake_unit::operator=(unit const & a) {
this->unit::operator=(a);
return *this;
}
game_display::fake_unit::~fake_unit() {
///The whole fake_unit exists for this one line to remove the temp unit from the
///fake_unit deque in the event of an exception
if(my_display_){remove_from_game_display();}
}
void game_display::fake_unit::place_on_game_display(game_display * display){
assert(my_display_ == NULL); //Can only be placed on 1 game_display
my_display_=display;
my_display_->place_temporary_unit(this);
}
int game_display::fake_unit::remove_from_game_display(){
int ret(0);
if(my_display_ != NULL){
ret = my_display_->remove_temporary_unit(this);
my_display_=NULL;
}
return ret;
}
void game_display::place_temporary_unit(unit *u)
{
if(std::find(fake_units_.begin(),fake_units_.end(), u) != fake_units_.end()) {
ERR_NG << "In game_display::place_temporary_unit: attempt to add duplicate fake unit." << std::endl;
} else {
fake_units_.push_back(u);
invalidate(u->get_location());
}
}
int game_display::remove_temporary_unit(unit *u)
{
int removed = 0;
std::deque<unit*>::iterator it =
std::remove(fake_units_.begin(), fake_units_.end(), u);
if (it != fake_units_.end()) {
removed = std::distance(it, fake_units_.end());
//std::remove doesn't remove anything without using erase afterwards.
fake_units_.erase(it, fake_units_.end());
invalidate(u->get_location());
// Redraw with no location to get rid of haloes
u->clear_haloes();
}
if (removed > 1) {
ERR_NG << "Error: duplicate temp unit found in game_display::remove_temporary_unit" << std::endl;
}
return removed;
}
bool game_display::add_exclusive_draw(const map_location& loc, unit& unit)
{
if (loc.valid() && exclusive_unit_draw_requests_.find(loc) == exclusive_unit_draw_requests_.end())
{
exclusive_unit_draw_requests_[loc] = unit.id();
return true;
}
else
{
return false;
}
}
std::string game_display::remove_exclusive_draw(const map_location& loc)
{
std::string id = "";
if(loc.valid())
{
id = exclusive_unit_draw_requests_[loc];
//id will be set to the default "" string by the [] operator if the map doesn't have anything for that loc.
exclusive_unit_draw_requests_.erase(loc);
}
return id;
}
void game_display::set_attack_indicator(const map_location& src, const map_location& dst)
{
if (attack_indicator_src_ != src || attack_indicator_dst_ != dst) {
invalidate(attack_indicator_src_);
invalidate(attack_indicator_dst_);
attack_indicator_src_ = src;
attack_indicator_dst_ = dst;
invalidate(attack_indicator_src_);
invalidate(attack_indicator_dst_);
}
}
void game_display::clear_attack_indicator()
{
set_attack_indicator(map_location::null_location, map_location::null_location);
}
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)
{
const int halo_handle = halo::add(get_location_x(loc) + hex_size() / 2,
get_location_y(loc) + hex_size() / 2, halo, loc);
const overlay item(img, halo, halo_handle, team_name, visible_under_fog);
overlays_.insert(overlay_map::value_type(loc,item));
}
void game_display::remove_overlay(const map_location& loc)
{
typedef overlay_map::const_iterator Itor;
std::pair<Itor,Itor> itors = overlays_.equal_range(loc);
while(itors.first != itors.second) {
halo::remove(itors.first->second.halo_handle);
++itors.first;
}
overlays_.erase(loc);
}
void game_display::remove_single_overlay(const map_location& loc, const std::string& toDelete)
{
//Iterate through the values with key of loc
typedef overlay_map::iterator Itor;
overlay_map::iterator iteratorCopy;
std::pair<Itor,Itor> itors = overlays_.equal_range(loc);
while(itors.first != itors.second) {
//If image or halo of overlay struct matches toDelete, remove the overlay
if(itors.first->second.image == toDelete || itors.first->second.halo == toDelete) {
iteratorCopy = itors.first;
++itors.first;
halo::remove(iteratorCopy->second.halo_handle);
overlays_.erase(iteratorCopy);
}
else {
++itors.first;
}
}
}
void game_display::parse_team_overlays()
{
const team& curr_team = teams_[playing_team()];
const team& prev_team = teams_[playing_team()-1 < teams_.size() ? playing_team()-1 : teams_.size()-1];
foreach (const game_display::overlay_map::value_type i, overlays_) {
const overlay& ov = i.second;
if (!ov.team_name.empty() &&
((ov.team_name.find(curr_team.team_name()) + 1) != 0) !=
((ov.team_name.find(prev_team.team_name()) + 1) != 0))
{
invalidate(i.first);
}
}
}
std::string game_display::current_team_name() const
{
if (team_valid())
{
return teams_[currentTeam_].team_name();
}
return std::string();
}
#ifdef HAVE_LIBDBUS
/** Use KDE 4 notifications. */
static bool kde_style = false;
struct wnotify
{
wnotify()
: id()
, owner()
, message()
{
}
uint32_t id;
std::string owner;
std::string message;
};
static std::list<wnotify> notifications;
static DBusHandlerResult filter_dbus_signal(DBusConnection *, DBusMessage *buf, void *)
{
if (!dbus_message_is_signal(buf, "org.freedesktop.Notifications", "NotificationClosed")) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
uint32_t id;
dbus_message_get_args(buf, NULL,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_INVALID);
std::list<wnotify>::iterator i = notifications.begin(),
i_end = notifications.end();
while (i != i_end && i->id != id) ++i;
if (i != i_end)
notifications.erase(i);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusConnection *get_dbus_connection()
{
if (preferences::get("disable_notifications", false)) {
return NULL;
}
static bool initted = false;
static DBusConnection *connection = NULL;
if (!initted)
{
initted = true;
if (getenv("KDE_SESSION_VERSION")) {
// This variable is defined for KDE 4 only.
kde_style = true;
}
DBusError err;
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (!connection) {
ERR_DP << "Failed to open DBus session: " << err.message << '\n';
dbus_error_free(&err);
return NULL;
}
dbus_connection_add_filter(connection, filter_dbus_signal, NULL, NULL);
}
if (connection) {
dbus_connection_read_write(connection, 0);
while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {}
}
return connection;
}
static uint32_t send_dbus_notification(DBusConnection *connection, uint32_t replaces_id,
const std::string &owner, const std::string &message)
{
DBusMessage *buf = dbus_message_new_method_call(
kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
kde_style ? "/VisualNotifications" : "/org/freedesktop/Notifications",
kde_style ? "org.kde.VisualNotifications" : "org.freedesktop.Notifications",
"Notify");
const char *app_name = "Battle for Wesnoth";
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &app_name,
DBUS_TYPE_UINT32, &replaces_id,
DBUS_TYPE_INVALID);
if (kde_style) {
const char *event_id = "";
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &event_id,
DBUS_TYPE_INVALID);
}
std::string app_icon_ = game_config::path + "/images/wesnoth-icon-small.png";
const char *app_icon = app_icon_.c_str();
const char *summary = owner.c_str();
const char *body = message.c_str();
const char **actions = NULL;
dbus_message_append_args(buf,
DBUS_TYPE_STRING, &app_icon,
DBUS_TYPE_STRING, &summary,
DBUS_TYPE_STRING, &body,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &actions, 0,
DBUS_TYPE_INVALID);
DBusMessageIter iter, hints;
dbus_message_iter_init_append(buf, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &hints);
dbus_message_iter_close_container(&iter, &hints);
int expire_timeout = kde_style ? 5000 : -1;
dbus_message_append_args(buf,
DBUS_TYPE_INT32, &expire_timeout,
DBUS_TYPE_INVALID);
DBusError err;
dbus_error_init(&err);
DBusMessage *ret = dbus_connection_send_with_reply_and_block(connection, buf, 1000, &err);
dbus_message_unref(buf);
if (!ret) {
ERR_DP << "Failed to send visual notification: " << err.message << '\n';
dbus_error_free(&err);
if (kde_style) {
ERR_DP << " Retrying with the freedesktop protocol.\n";
kde_style = false;
return send_dbus_notification(connection, replaces_id, owner, message);
}
return 0;
}
uint32_t id;
dbus_message_get_args(ret, NULL,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_INVALID);
dbus_message_unref(ret);
// TODO: remove once closing signals for KDE are handled in filter_dbus_signal.
if (kde_style) return 0;
return id;
}
#endif
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL)
void game_display::send_notification(const std::string& owner, const std::string& message)
#else
void game_display::send_notification(const std::string& /*owner*/, const std::string& /*message*/)
#endif
{
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL)
Uint8 app_state = SDL_GetAppState();
// Do not show notifications when the window is visible...
if ((app_state & SDL_APPACTIVE) != 0)
{
// ... and it has a focus.
if ((app_state & (SDL_APPMOUSEFOCUS | SDL_APPINPUTFOCUS)) != 0) {
return;
}
}
#endif
#ifdef HAVE_LIBDBUS
DBusConnection *connection = get_dbus_connection();
if (!connection) return;
std::list<wnotify>::iterator i = notifications.begin(),
i_end = notifications.end();
while (i != i_end && i->owner != owner) ++i;
if (i != i_end) {
i->message += "\n";
i->message += message;
send_dbus_notification(connection, i->id, owner, i->message);
return;
}
uint32_t id = send_dbus_notification(connection, 0, owner, message);
if (!id) return;
wnotify visual;
visual.id = id;
visual.owner = owner;
visual.message = message;
notifications.push_back(visual);
#endif
#ifdef HAVE_GROWL
CFStringRef app_name = CFStringCreateWithCString(NULL, "Wesnoth", kCFStringEncodingUTF8);
CFStringRef cf_owner = CFStringCreateWithCString(NULL, owner.c_str(), kCFStringEncodingUTF8);
CFStringRef cf_message = CFStringCreateWithCString(NULL, message.c_str(), kCFStringEncodingUTF8);
//Should be changed as soon as there are more than 2 types of notifications
CFStringRef cf_note_name = CFStringCreateWithCString(NULL, owner == "Turn changed" ? "Turn changed" : "Chat message", kCFStringEncodingUTF8);
growl_obj.applicationName = app_name;
growl_obj.registrationDictionary = NULL;
growl_obj.applicationIconData = NULL;
growl_obj.growlIsReady = NULL;
growl_obj.growlNotificationWasClicked = NULL;
growl_obj.growlNotificationTimedOut = NULL;
Growl_SetDelegate(&growl_obj);
Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(cf_owner, cf_message, cf_note_name, NULL, NULL, NULL, NULL);
CFRelease(app_name);
CFRelease(cf_owner);
CFRelease(cf_message);
CFRelease(cf_note_name);
#endif
}
void game_display::set_team(size_t teamindex, bool show_everything)
{
assert(teamindex < teams_.size());
currentTeam_ = teamindex;
if (!show_everything)
{
labels().set_team(&teams_[teamindex]);
viewpoint_ = &teams_[teamindex];
}
else
{
labels().set_team(NULL);
viewpoint_ = NULL;
}
labels().recalculate_labels();
if(resources::whiteboard)
resources::whiteboard->on_viewer_change(teamindex);
}
void game_display::set_playing_team(size_t teamindex)
{
assert(teamindex < teams_.size());
activeTeam_ = teamindex;
invalidate_game_status();
}
void game_display::begin_game()
{
in_game_ = true;
create_buttons();
invalidate_all();
}
namespace {
const int chat_message_border = 5;
const int chat_message_x = 10;
const int chat_message_y = 10;
const SDL_Color chat_message_color = {255,255,255,255};
const SDL_Color chat_message_bg = {0,0,0,140};
}
void game_display::add_chat_message(const time_t& time, const std::string& speaker,
int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type,
bool bell)
{
const bool whisper = speaker.find("whisper: ") == 0;
std::string sender = speaker;
if (whisper) {
sender.assign(speaker, 9, speaker.size());
}
if (!preferences::parse_should_show_lobby_join(sender, message)) return;
if (preferences::is_ignored(sender)) return;
preferences::parse_admin_authentication(sender, message);
if (bell) {
if ((type == events::chat_handler::MESSAGE_PRIVATE && (!is_observer() || whisper))
|| utils::word_match(message, preferences::login())) {
sound::play_UI_sound(game_config::sounds::receive_message_highlight);
} else if (preferences::is_friend(sender)) {
sound::play_UI_sound(game_config::sounds::receive_message_friend);
} else if (sender == "server") {
sound::play_UI_sound(game_config::sounds::receive_message_server);
} else {
sound::play_UI_sound(game_config::sounds::receive_message);
}
}
bool action = false;
std::string msg;
if (message.find("/me ") == 0) {
msg.assign(message, 4, message.size());
action = true;
} else {
msg += message;
}
try {
// We've had a joker who send an invalid utf-8 message to crash clients
// so now catch the exception and ignore the message.
msg = font::word_wrap_text(msg,font::SIZE_SMALL,map_outside_area().w*3/4);
} catch (utils::invalid_utf8_exception&) {
ERR_NG << "Invalid utf-8 found, chat message is ignored.\n";
return;
}
int ypos = chat_message_x;
for(std::vector<chat_message>::const_iterator m = chat_messages_.begin(); m != chat_messages_.end(); ++m) {
ypos += std::max(font::get_floating_label_rect(m->handle).h,
font::get_floating_label_rect(m->speaker_handle).h);
}
SDL_Color speaker_color = {255,255,255,255};
if(side >= 1) {
speaker_color = int_to_color(team::get_side_color_range(side).mid());
}
SDL_Color message_color = chat_message_color;
std::stringstream str;
std::stringstream message_str;
if(type == events::chat_handler::MESSAGE_PUBLIC) {
if(action) {
str << "<" << speaker << " " << msg << ">";
message_color = speaker_color;
message_str << " ";
} else {
if (!speaker.empty())
str << "<" << speaker << ">";
message_str << msg;
}
} else {
if(action) {
str << "*" << speaker << " " << msg << "*";
message_color = speaker_color;
message_str << " ";
} else {
if (!speaker.empty())
str << "*" << speaker << "*";
message_str << msg;
}
}
// Prepend message with timestamp.
std::stringstream message_complete;
message_complete << preferences::get_chat_timestamp(time) << str.str();
const SDL_Rect rect = map_outside_area();
font::floating_label spk_flabel(message_complete.str());
spk_flabel.set_font_size(font::SIZE_SMALL);
spk_flabel.set_color(speaker_color);
spk_flabel.set_position(rect.x + chat_message_x, rect.y + ypos);
spk_flabel.set_clip_rect(rect);
spk_flabel.set_alignment(font::LEFT_ALIGN);
spk_flabel.set_bg_color(chat_message_bg);
spk_flabel.set_border_size(chat_message_border);
spk_flabel.use_markup(false);
int speaker_handle = font::add_floating_label(spk_flabel);
font::floating_label msg_flabel(message_str.str());
msg_flabel.set_font_size(font::SIZE_SMALL);
msg_flabel.set_color(message_color);
msg_flabel.set_position(rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,
rect.y + ypos);
msg_flabel.set_clip_rect(rect);
msg_flabel.set_alignment(font::LEFT_ALIGN);
msg_flabel.set_bg_color(chat_message_bg);
msg_flabel.set_border_size(chat_message_border);
msg_flabel.use_markup(false);
int message_handle = font::add_floating_label(msg_flabel);
// Send system notification if appropriate.
send_notification(speaker, message);
chat_messages_.push_back(chat_message(speaker_handle,message_handle));
prune_chat_messages();
}
void game_display::prune_chat_messages(bool remove_all)
{
const unsigned message_aging = preferences::chat_message_aging();
const unsigned message_ttl = remove_all ? 0 : message_aging * 60 * 1000;
const unsigned max_chat_messages = preferences::chat_lines();
int movement = 0;
if(message_aging != 0 || remove_all || chat_messages_.size() > max_chat_messages) {
while (!chat_messages_.empty() &&
(chat_messages_.front().created_at + message_ttl < SDL_GetTicks() ||
chat_messages_.size() > max_chat_messages))
{
const chat_message &old = chat_messages_.front();
movement += font::get_floating_label_rect(old.handle).h;
font::remove_floating_label(old.speaker_handle);
font::remove_floating_label(old.handle);
chat_messages_.erase(chat_messages_.begin());
}
}
foreach (const chat_message &cm, chat_messages_) {
font::move_floating_label(cm.speaker_handle, 0, - movement);
font::move_floating_label(cm.handle, 0, - movement);
}
}
game_display *game_display::singleton_ = NULL;