wesnoth/src/display.cpp
Dave White 111cd8cbfa made resizing the window in the title screen work properly, ...
...and made basic keyboard shortcuts work in the title scren
2004-05-05 00:40:15 +00:00

2151 lines
58 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "actions.hpp"
#include "display.hpp"
#include "events.hpp"
#include "font.hpp"
#include "game.hpp"
#include "game_config.hpp"
#include "hotkeys.hpp"
#include "image.hpp"
#include "language.hpp"
#include "log.hpp"
#include "preferences.hpp"
#include "sdl_utils.hpp"
#include "show_dialog.hpp"
#include "sound.hpp"
#include "team.hpp"
#include "tooltips.hpp"
#include "unit_display.hpp"
#include "util.hpp"
#include "SDL_image.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <iostream>
#include <sstream>
std::map<gamemap::location,double> display::debugHighlights_;
namespace {
const int MinZoom = 36;
const int DefaultZoom = 72;
const int MaxZoom = 200;
const size_t SideBarGameStatus_x = 16;
const size_t SideBarGameStatus_y = 220;
const SDL_Rect empty_rect = {0,0,0,0};
}
display::display(unit_map& units, CVideo& video, const gamemap& map,
const gamestatus& status, const std::vector<team>& t, const config& theme_cfg,
const config& built_terrains)
: screen_(video), xpos_(0), ypos_(0),
zoom_(DefaultZoom), map_(map), units_(units),
minimap_(NULL), redrawMinimap_(false),
pathsList_(NULL), status_(status),
teams_(t), lastDraw_(0), drawSkips_(0),
invalidateAll_(true), invalidateUnit_(true),
invalidateGameStatus_(true), panelsDrawn_(false),
currentTeam_(0), activeTeam_(0), hideEnergy_(false),
deadAmount_(0.0), advancingAmount_(0.0), updatesLocked_(0),
turbo_(false), grid_(false), sidebarScaling_(1.0),
theme_(theme_cfg,screen_area()), builder_(built_terrains, map),
firstTurn_(true), map_labels_(*this,map),
tod_hex_mask1(NULL), tod_hex_mask2(NULL)
{
if(non_interactive())
updatesLocked_++;
energy_bar_rect_.x = -1;
create_buttons();
std::fill(reportRects_,reportRects_+reports::NUM_REPORTS,empty_rect);
image::set_zoom(zoom_);
gameStatusRect_.w = 0;
unitDescriptionRect_.w = 0;
unitProfileRect_.w = 0;
//clear the screen contents
SDL_Surface* const disp = screen_.getSurface();
SDL_Rect area = screen_area();
SDL_FillRect(disp,&area,SDL_MapRGB(disp->format,0,0,0));
}
display::~display()
{
SDL_FreeSurface(minimap_);
prune_chat_messages(true);
}
Uint32 display::rgb(Uint8 red, Uint8 green, Uint8 blue)
{
return 0xFF000000 | (red << 16) | (green << 8) | blue;
}
void display::new_turn()
{
const time_of_day& tod = status_.get_time_of_day();
if(!turbo() && !firstTurn_) {
image::set_image_mask("");
const time_of_day& old_tod = status_.get_previous_time_of_day();
if(old_tod.image_mask != tod.image_mask) {
const scoped_sdl_surface old_mask(image::get_image(old_tod.image_mask,image::UNMASKED));
const scoped_sdl_surface new_mask(image::get_image(tod.image_mask,image::UNMASKED));
const int niterations = 10;
const int frame_time = 30;
const int starting_ticks = SDL_GetTicks();
for(int i = 0; i != niterations; ++i) {
if(old_mask != NULL) {
const double proportion = 1.0 - double(i)/double(niterations);
tod_hex_mask1.assign(adjust_surface_alpha(old_mask,proportion));
}
if(new_mask != NULL) {
const double proportion = double(i)/double(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);
}
firstTurn_ = false;
image::set_colour_adjustment(tod.red,tod.green,tod.blue);
image::set_image_mask(tod.image_mask);
}
void display::adjust_colours(int r, int g, int b)
{
const time_of_day& tod = status_.get_time_of_day();
image::set_colour_adjustment(tod.red+r,tod.green+g,tod.blue+b);
}
gamemap::location display::hide_unit(const gamemap::location& loc, bool hide_energy)
{
const gamemap::location res = hiddenUnit_;
hiddenUnit_ = loc;
hideEnergy_ = hide_energy;
return res;
}
int display::x() const { return screen_.getx(); }
int display::mapx() const { return x() - 140; }
int display::y() const { return screen_.gety(); }
const SDL_Rect& display::map_area() const
{
return theme_.main_map_location(screen_area());
}
const SDL_Rect& display::minimap_area() const
{
return theme_.mini_map_location(screen_area());
}
SDL_Rect display::screen_area() const
{
const SDL_Rect res = {0,0,x(),y()};
return res;
}
void display::select_hex(gamemap::location hex)
{
if(team_valid() && teams_[currentTeam_].fogged(hex.x,hex.y)) {
return;
}
invalidate(selectedHex_);
selectedHex_ = hex;
invalidate(selectedHex_);
invalidate_unit();
}
void display::highlight_hex(gamemap::location hex)
{
const int has_unit = units_.count(mouseoverHex_) + units_.count(hex);
invalidate(mouseoverHex_);
mouseoverHex_ = hex;
invalidate(mouseoverHex_);
invalidate_game_status();
if(has_unit)
invalidate_unit();
}
gamemap::location display::hex_clicked_on(int xclick, int yclick)
{
const SDL_Rect& rect = map_area();
if(point_in_rect(xclick,yclick,rect) == false) {
return gamemap::location();
}
xclick -= rect.x;
yclick -= rect.y;
const int tile_width = hex_width();
const int xtile = (xpos_ + xclick)/tile_width;
const int ytile = (ypos_ + yclick - (is_odd(xtile) ? zoom_/2 : 0))/zoom_; //(yclick + is_odd(xtile) ? -yclick/2 : 0)/zoom_;
return gamemap::location(xtile,ytile);
}
int display::get_location_x(const gamemap::location& loc) const
{
return map_area().x + loc.x*hex_width() - xpos_;
}
int display::get_location_y(const gamemap::location& loc) const
{
return map_area().y + loc.y*zoom_ - ypos_ + (is_odd(loc.x) ? zoom_/2 : 0);
}
gamemap::location display::minimap_location_on(int x, int y)
{
const SDL_Rect rect = minimap_area();
if(x < rect.x || y < rect.y ||
x >= rect.x + rect.w || y >= rect.y + rect.h) {
return gamemap::location();
}
const double xdiv = double(rect.w) / double(map_.x());
const double ydiv = double(rect.h) / double(map_.y());
return gamemap::location(int((x - rect.x)/xdiv),int((y-rect.y)/ydiv));
}
void display::scroll(int xmove, int ymove)
{
const int orig_x = xpos_;
const int orig_y = ypos_;
xpos_ += xmove;
ypos_ += ymove;
bounds_check_position();
//only invalidate if we've actually moved
if(orig_x != xpos_ || orig_y != ypos_) {
map_labels_.scroll(orig_x - xpos_, orig_y - ypos_);
invalidate_all();
}
}
int display::hex_size() const
{
return zoom_;
}
int display::hex_width() const
{
return (zoom_*3)/4;
}
double display::zoom(int amount)
{
if(amount == 0 || !team_valid()) {
return double(zoom_)/double(DefaultZoom);
}
const int orig_xpos = xpos_;
const int orig_ypos = ypos_;
xpos_ /= zoom_;
ypos_ /= zoom_;
const int orig_zoom = zoom_;
zoom_ += amount;
if(zoom_ < MinZoom || zoom_ > MaxZoom) {
zoom_ = orig_zoom;
xpos_ = orig_xpos;
ypos_ = orig_ypos;
return double(zoom_)/double(DefaultZoom);
}
xpos_ *= zoom_;
ypos_ *= zoom_;
xpos_ += amount*2;
ypos_ += amount*2;
const int prev_zoom = zoom_;
bounds_check_position();
if(zoom_ != prev_zoom) {
xpos_ = orig_xpos;
ypos_ = orig_ypos;
zoom_ = orig_zoom;
return double(zoom_)/double(DefaultZoom);
}
energy_bar_rect_.x = -1;
std::cerr << "zoomed to: " << zoom_ << "\n";
image::set_zoom(zoom_);
map_labels_.recalculate_labels();
invalidate_all();
return double(zoom_)/double(DefaultZoom);
}
void display::default_zoom()
{
zoom(DefaultZoom - zoom_);
}
void display::scroll_to_tile(int x, int y, SCROLL_TYPE scroll_type, bool check_fogged)
{
if(update_locked() || (check_fogged && fogged(x,y)))
return;
const gamemap::location loc(x,y);
if(map_.on_board(loc) == false)
return;
const int xpos = get_location_x(loc);
const int ypos = get_location_y(loc);
const int speed = preferences::scroll_speed()*2;
const SDL_Rect& area = map_area();
const int desiredxpos = area.w/2 - zoom_/2;
const int desiredypos = area.h/2 - zoom_/2;
const int xmove = xpos - desiredxpos;
const int ymove = ypos - desiredypos;
int num_moves = (abs(xmove) > abs(ymove) ? abs(xmove):abs(ymove))/speed;
if(scroll_type == WARP || turbo())
num_moves = 1;
for(int i = 0; i != num_moves; ++i) {
events::pump();
//accelerate scroll rate if either shift key is held down
if((i%4) != 0 && i != num_moves-1 && turbo()) {
continue;
}
scroll(xmove/num_moves,ymove/num_moves);
draw();
}
invalidate_all();
draw();
}
void display::scroll_to_tiles(int x1, int y1, int x2, int y2,
SCROLL_TYPE scroll_type, bool check_fogged)
{
const gamemap::location loc1(x1,y1), loc2(x2,y2);
const int xpos1 = get_location_x(loc1);
const int ypos1 = get_location_y(loc1);
const int xpos2 = get_location_x(loc2);;
const int ypos2 = get_location_y(loc2);;
const int diffx = abs(xpos1 - xpos2);
const int diffy = abs(ypos1 - ypos2);
if(diffx > map_area().w/hex_width() || diffy > map_area().h/zoom_) {
scroll_to_tile(x1,y1,scroll_type,check_fogged);
} else {
scroll_to_tile((x1+x2)/2,(y1+y2)/2,scroll_type,check_fogged);
}
}
void display::bounds_check_position()
{
const int min_zoom1 = map_area().w/((map_.x()*3)/4);
const int min_zoom2 = map_area().h/map_.y();
const int min_zoom = maximum<int>(min_zoom1,min_zoom2);
const int orig_zoom = zoom_;
if(zoom_ < min_zoom) {
zoom_ = min_zoom;
}
if(zoom_ > MaxZoom) {
zoom_ = MaxZoom;
}
const int tile_width = hex_width();
const int xend = tile_width*map_.x() + tile_width/3;
const int yend = zoom_*map_.y() + zoom_/2;
if(xpos_ + map_area().w > xend)
xpos_ -= xpos_ + map_area().w - xend;
if(ypos_ + map_area().h > yend)
ypos_ -= ypos_ + map_area().h - yend;
if(xpos_ < 0)
xpos_ = 0;
if(ypos_ < 0)
ypos_ = 0;
if(zoom_ != orig_zoom)
image::set_zoom(zoom_);
}
void display::redraw_everything()
{
if(update_locked() || teams_.empty())
return;
bounds_check_position();
for(size_t n = 0; n != reports::NUM_REPORTS; ++n) {
reportRects_[n] = empty_rect;
reportSurfaces_[n].assign(NULL);
reports_[n] = reports::report();
}
tooltips::clear_tooltips();
theme_.set_resolution(screen_area());
create_buttons();
panelsDrawn_ = false;
map_labels_.recalculate_labels();
invalidate_all();
draw(true,true);
}
namespace {
void draw_panel(display& disp, const theme::panel& panel)
{
log_scope("draw panel");
scoped_sdl_surface surf(image::get_image(panel.image(),image::UNSCALED));
const SDL_Rect screen = disp.screen_area();
SDL_Rect& loc = panel.location(screen);
if(surf->w != loc.w || surf->h != loc.h) {
surf.assign(scale_surface(surf.get(),loc.w,loc.h));
}
std::cerr << "drawing panel " << loc.x << "," << loc.y << "," << loc.w << "," << loc.h << "\n";
disp.blit_surface(loc.x,loc.y,surf);
update_rect(loc);
}
void draw_label(display& disp, SDL_Surface* target, const theme::label& label)
{
log_scope("draw label");
const std::string& text = label.text();
const std::string& icon = label.icon();
SDL_Rect& loc = label.location(disp.screen_area());
if(icon.empty() == false) {
scoped_sdl_surface surf(image::get_image(icon,image::UNSCALED));
if(surf->w != loc.w || surf->h != loc.h) {
surf.assign(scale_surface(surf.get(),loc.w,loc.h));
}
SDL_BlitSurface(surf.get(),NULL,target,&loc);
if(text.empty() == false) {
tooltips::add_tooltip(loc,text);
}
} else if(text.empty() == false) {
font::draw_text(&disp,loc,label.font_size(),font::NORMAL_COLOUR,text,loc.x,loc.y);
}
update_rect(loc);
}
}
void display::draw(bool update,bool force)
{
if(!panelsDrawn_) {
SDL_Surface* const screen = screen_.getSurface();
const std::vector<theme::panel>& panels = theme_.panels();
for(std::vector<theme::panel>::const_iterator p = panels.begin(); p != panels.end(); ++p) {
draw_panel(*this,*p);
}
const std::vector<theme::label>& labels = theme_.labels();
for(std::vector<theme::label>::const_iterator i = labels.begin(); i != labels.end(); ++i) {
draw_label(*this,screen,*i);
}
for(std::vector<gui::button>::iterator b = buttons_.begin(); b != buttons_.end(); ++b) {
b->draw();
}
//invalidate the reports so they are redrawn
std::fill(reports_,reports_+sizeof(reports_)/sizeof(*reports_),reports::report());
invalidateGameStatus_ = true;
panelsDrawn_ = true;
}
if(invalidateAll_ && !map_.empty()) {
for(int x = -1; x <= map_.x(); ++x)
for(int y = -1; y <= map_.y(); ++y)
draw_tile(x,y);
invalidateAll_ = false;
redrawMinimap_ = true;
} else if(!map_.empty()) {
for(std::set<gamemap::location>::const_iterator it =
invalidated_.begin(); it != invalidated_.end(); ++it) {
draw_tile(it->x,it->y);
}
invalidated_.clear();
}
if(redrawMinimap_) {
redrawMinimap_ = false;
const SDL_Rect area = minimap_area();
draw_minimap(area.x,area.y,area.w,area.h);
}
if(!map_.empty()) {
draw_sidebar();
}
prune_chat_messages();
const int max_skips = 5;
const int time_between_draws = 20;
const int current_time = SDL_GetTicks();
const int wait_time = lastDraw_ + time_between_draws - current_time;
//force a wait for 10 ms every frame.
//TODO: review whether this is the correct thing to do
SDL_Delay(maximum<int>(10,wait_time));
if(update) {
lastDraw_ = SDL_GetTicks();
if(wait_time >= 0 || drawSkips_ >= max_skips || force)
update_display();
else
drawSkips_++;
}
}
void display::update_display()
{
if(updatesLocked_ > 0)
return;
screen_.flip();
}
void display::draw_sidebar()
{
if(teams_.empty())
return;
if(invalidateUnit_) {
//we display the unit the mouse is over if it is over a unit
//otherwise we display the unit that is selected
std::map<gamemap::location,unit>::const_iterator i =
find_visible_unit(units_,mouseoverHex_,
map_,
status_.get_time_of_day().lawful_bonus,
teams_,teams_[viewing_team()]);
if(i == units_.end() || fogged(i->first.x,i->first.y)) {
i = find_visible_unit(units_,selectedHex_,
map_,
status_.get_time_of_day().lawful_bonus,
teams_,teams_[viewing_team()]);
}
if(i != units_.end() && !fogged(i->first.x,i->first.y))
for(size_t r = reports::UNIT_REPORTS_BEGIN; r != reports::UNIT_REPORTS_END; ++r)
draw_report(reports::TYPE(r));
invalidateUnit_ = false;
}
if(invalidateGameStatus_) {
draw_game_status(mapx()+SideBarGameStatus_x,SideBarGameStatus_y);
invalidateGameStatus_ = false;
}
}
void display::draw_game_status(int x, int y)
{
if(teams_.empty())
return;
for(size_t r = reports::STATUS_REPORTS_BEGIN; r != reports::STATUS_REPORTS_END; ++r) {
draw_report(reports::TYPE(r));
}
}
void display::draw_image_for_report(scoped_sdl_surface& img, scoped_sdl_surface& surf, SDL_Rect& rect)
{
SDL_Rect visible_area = get_non_transperant_portion(img);
SDL_Rect target = rect;
if(visible_area.x != 0 || visible_area.y != 0 || visible_area.w != img->w || visible_area.h != img->h) {
if(visible_area.w == 0 || visible_area.h == 0) {
return;
}
if(visible_area.w > rect.w || visible_area.h > rect.h) {
img.assign(get_surface_portion(img,visible_area));
img.assign(scale_surface(img,rect.w,rect.h));
visible_area.x = 0;
visible_area.y = 0;
visible_area.w = img->w;
visible_area.h = img->h;
} else {
target.x = rect.x + (rect.w - visible_area.w)/2;
target.y = rect.y + (rect.h - visible_area.h)/2;
target.w = visible_area.w;
target.h = visible_area.h;
}
SDL_BlitSurface(img,&visible_area,screen_.getSurface(),&target);
} else {
if(img->w != rect.w || img->h != rect.h) {
img.assign(scale_surface(img,rect.w,rect.h));
}
SDL_BlitSurface(img,NULL,screen_.getSurface(),&target);
}
}
void display::draw_report(reports::TYPE report_num)
{
if(!team_valid())
return;
const theme::status_item* const item = theme_.get_status_item(reports::report_name(report_num));
if(item != NULL) {
reports::report report = reports::generate_report(report_num,map_,
units_, teams_,
teams_[viewing_team()],
currentTeam_+1,activeTeam_+1,
selectedHex_,mouseoverHex_,status_,observers_);
SDL_Rect& rect = reportRects_[report_num];
const SDL_Rect& new_rect = item->location(screen_area());
//report and its location is unchanged since last time. Do nothing.
if(rect == new_rect && reports_[report_num] == report) {
return;
}
reports_[report_num] = report;
scoped_sdl_surface& surf = reportSurfaces_[report_num];
if(surf != NULL) {
SDL_BlitSurface(surf,NULL,screen_.getSurface(),&rect);
update_rect(rect);
}
//if the rectangle has just changed, assign the surface to it
if(new_rect != rect || surf == NULL) {
surf.assign(NULL);
rect = new_rect;
//if the rectangle is present, and we are blitting text, then
//we need to backup the surface. (Images generally won't need backing
//up unless they are transperant, but that is done later)
if(rect.w > 0 && rect.h > 0) {
surf.assign(get_surface_portion(screen_.getSurface(),rect));
if(reportSurfaces_[report_num] == NULL) {
std::cerr << "Could not backup background for report!\n";
}
}
update_rect(rect);
}
tooltips::clear_tooltips(rect);
SDL_Rect area = rect;
int x = rect.x, y = rect.y;
if(!report.empty()) {
// Add prefix, postfix elements. Make sure that they get the same tooltip as the guys
// around them.
std::string str = item->prefix();
if(str.empty() == false) {
report.insert(report.begin(), reports::element(str,"",report.begin()->tooltip));
}
str = item->postfix();
if(str.empty() == false) {
report.push_back(reports::element(str,"",report.end()->tooltip));
}
// Loop through and display each report element
size_t tallest = 0;
for(reports::report::iterator i = report.begin(); i != report.end(); ++i) {
if(i->text.empty() == false) {
// Draw a text element
area = font::draw_text(this,rect,item->font_size(),font::NORMAL_COLOUR,i->text,x,y);
if(area.h > tallest) tallest = area.h;
if(i->text[i->text.size() - 1] == '\n') {
x = rect.x;
y += tallest;
tallest = 0;
} else {
x += area.w;
}
} else if(i->image.empty() == false) {
// Draw an image element
scoped_sdl_surface img(image::get_image(i->image,image::UNSCALED));
if(img == NULL) {
std::cerr << "could not find image for report: '" << i->image << "'\n";
continue;
}
area.x = x;
area.y = y;
area.w = minimum<int>(rect.w + rect.x - x, img->w);
area.h = minimum<int>(rect.h + rect.y - y, img->h);
draw_image_for_report(img,surf,area);
if(area.h > tallest) tallest = area.h;
x += area.w;
} else {
// No text or image, skip this element
continue;
}
if(i->tooltip.empty() == false) {
tooltips::add_tooltip(area,i->tooltip);
}
}
}
} else {
reportSurfaces_[report_num].assign(NULL);
}
}
void display::draw_unit_details(int x, int y, const gamemap::location& loc,
const unit& u, SDL_Rect& description_rect, int profilex, int profiley,
SDL_Rect* clip_rect)
{
if(teams_.empty())
return;
tooltips::clear_tooltips(description_rect);
SDL_Rect clipRect = clip_rect != NULL ? *clip_rect : screen_area();
const scoped_sdl_surface background(image::get_image(game_config::rightside_image,image::UNSCALED));
const scoped_sdl_surface background_bot(image::get_image(game_config::rightside_image_bot,image::UNSCALED));
if(background == NULL || background_bot == NULL)
return;
SDL_Surface* const screen = screen_.getSurface();
if(description_rect.w > 0 && description_rect.x >= mapx()) {
SDL_Rect srcrect = description_rect;
srcrect.y -= background->h;
srcrect.x -= mapx();
SDL_BlitSurface(background_bot,&srcrect,screen,&description_rect);
update_rect(description_rect);
}
std::string status = string_table["healthy"];
if(map_.on_board(loc) &&
u.invisible(map_.underlying_terrain(map_[loc.x][loc.y]),
status_.get_time_of_day().lawful_bonus,loc,
units_,teams_)) {
status = font::GOOD_TEXT + string_table["invisible"];
}
if(u.has_flag("slowed")) {
status = font::BAD_TEXT + string_table["slowed"];
}
if(u.has_flag("poisoned")) {
status = font::BAD_TEXT + string_table["poisoned"];
}
std::stringstream details;
details << font::LARGE_TEXT << u.description() << "\n"
<< font::LARGE_TEXT << u.type().language_name()
<< "\n-(" << string_table["level"] << " "
<< u.type().level() << ")\n"
<< status << "\n"
<< translate_string(unit_type::alignment_description(u.type().alignment()))
<< "\n"
<< u.traits_description() << "\n";
const std::vector<std::string>& abilities = u.type().abilities();
for(std::vector<std::string>::const_iterator a = abilities.begin(); a != abilities.end(); ++a) {
details << translate_string_default("ability_" + *a, *a) << "\n";
}
//display in green/white/red depending on hitpoints
if(u.hitpoints() <= u.max_hitpoints()/3)
details << font::BAD_TEXT;
else if(u.hitpoints() > 2*(u.max_hitpoints()/3))
details << font::GOOD_TEXT;
details << string_table["hp"] << ": " << u.hitpoints()
<< "/" << u.max_hitpoints() << "\n";
if(u.type().advances_to().empty()) {
details << string_table["xp"] << ": " << u.experience() << "/-";
} else {
//if killing a unit the same level as us would level us up,
//then display in green
if(u.max_experience() - u.experience() < game_config::kill_experience) {
details << font::GOOD_TEXT;
}
details << string_table["xp"] << ": " << u.experience() << "/" << u.max_experience();
}
details << "\n"
<< string_table["moves"] << ": " << u.movement_left() << "/"
<< u.total_movement()
<< "\n";
const std::vector<attack_type>& attacks = u.attacks();
for(std::vector<attack_type>::const_iterator at_it = attacks.begin();
at_it != attacks.end(); ++at_it) {
const std::string& lang_weapon = string_table["weapon_name_" + at_it->name()];
const std::string& lang_type = string_table["weapon_type_" + at_it->type()];
const std::string& lang_special = string_table["weapon_special_" + at_it->special()];
details << "\n"
<< (lang_weapon.empty() ? at_it->name():lang_weapon) << " ("
<< (lang_type.empty() ? at_it->type():lang_type) << ")\n"
<< (lang_special.empty() ? at_it->special():lang_special)<<"\n"
<< at_it->damage() << "-" << at_it->num_attacks() << " -- "
<< (at_it->range() == attack_type::SHORT_RANGE ?
string_table["short_range"] :
string_table["long_range"]);
if(at_it->hexes() > 1) {
details << " (" << at_it->hexes() << ")";
}
details << "\n\n";
}
//choose the font size based on how much room we have to play
//with on the right-side panel
const size_t font_size = this->y() >= 700 ? 13 : 10;
description_rect = font::draw_text(this,clipRect,font_size,font::NORMAL_COLOUR,
details.str(),x,y);
update_rect(description_rect);
y += description_rect.h;
const scoped_sdl_surface profile(image::get_image(u.type().image(),image::UNSCALED));
if(profile == NULL)
return;
//blit the unit profile
{
const size_t profilew = 50;
const size_t profileh = 50;
SDL_Rect srcrect = { (profile->w-profilew)/2,(profile->h-profileh)/2,
profilew,profileh };
SDL_Rect dstrect = srcrect;
dstrect.x = profilex;
dstrect.y = profiley;
SDL_BlitSurface(profile,&srcrect,video().getSurface(),&dstrect);
update_rect(profilex,profiley,profilew,profileh);
}
}
void display::draw_minimap(int x, int y, int w, int h)
{
const scoped_sdl_surface surface(getMinimap(w,h));
if(surface == NULL)
return;
SDL_Rect minimap_location = {x,y,w,h};
clip_rect_setter clip_setter(video().getSurface(),minimap_location);
SDL_Rect loc = minimap_location;
SDL_BlitSurface(surface,NULL,video().getSurface(),&loc);
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
if(fogged(u->first.x,u->first.y) ||
(teams_[currentTeam_].is_enemy(u->second.side()) &&
u->second.invisible(map_.underlying_terrain(map_[u->first.x][u->first.y]),
status_.get_time_of_day().lawful_bonus,u->first,
units_,teams_)))
continue;
const int side = u->second.side();
const SDL_Color& col = font::get_side_colour(side);
const Uint16 mapped_col = SDL_MapRGB(video().getSurface()->format,col.r,col.g,col.b);
SDL_Rect rect = {x + (u->first.x*w)/map_.x(),
y + (u->first.y*h)/map_.y(),
w/map_.x(), h/map_.y() };
SDL_FillRect(video().getSurface(),&rect,mapped_col);
}
const double xscaling = double(surface->w)/double(map_.x());
const double yscaling = double(surface->h)/double(map_.y());
const int xbox = static_cast<int>(xscaling*xpos_/(zoom_*0.75));
const int ybox = static_cast<int>(yscaling*ypos_/zoom_);
const int wbox = static_cast<int>(xscaling*map_area().w/(zoom_*0.75) - xscaling);
const int hbox = static_cast<int>(yscaling*map_area().h/zoom_ - yscaling);
const Uint16 boxcolour = Uint16(SDL_MapRGB(surface->format,0xFF,0xFF,0xFF));
SDL_Surface* const screen = screen_.getSurface();
gui::draw_rectangle(x+xbox,y+ybox,wbox,hbox,boxcolour,screen);
update_rect(minimap_location);
}
void display::draw_terrain_palette(int x, int y, gamemap::TERRAIN selected)
{
const int max_h = 35;
SDL_Rect invalid_rect;
invalid_rect.x = x;
invalid_rect.y = y;
invalid_rect.w = 0;
SDL_Surface* const screen = screen_.getSurface();
std::vector<gamemap::TERRAIN> terrains = map_.get_terrain_precedence();
for(std::vector<gamemap::TERRAIN>::const_iterator i = terrains.begin();
i != terrains.end(); ++i) {
const scoped_sdl_surface image(getTerrain(*i,image::SCALED,-1,-1));
if(image == NULL) {
std::cerr << "image for terrain '" << *i << "' not found\n";
return;
}
if(x + image->w >= this->x() || y + image->h >= this->y()) {
std::cerr << "terrain palette can't fit: " << x + image->w << " > " << this->x() << " or " << y+image->h << " > " << this->y() << "\n";
return;
}
SDL_Rect dstrect;
dstrect.x = x;
dstrect.y = y;
dstrect.w = image->w;
dstrect.h = image->h;
if(dstrect.h > max_h)
dstrect.h = max_h;
SDL_BlitSurface(image,NULL,screen,&dstrect);
gui::draw_rectangle(x,y,image->w-1,max_h-1,
*i == selected?0xF000:0,screen);
y += max_h+2;
if(image->w > invalid_rect.w)
invalid_rect.w = image->w;
}
invalid_rect.h = y - invalid_rect.y;
update_rect(invalid_rect);
}
gamemap::TERRAIN display::get_terrain_on(int palx, int paly, int x, int y)
{
const int height = 37;
if(y < paly || x < palx)
return 0;
const std::vector<gamemap::TERRAIN>& terrains=map_.get_terrain_precedence();
if(static_cast<size_t>(y) > paly+terrains.size()*height)
return 0;
const size_t index = (y - paly)/height;
if(index >= terrains.size())
return 0;
return terrains[index];
}
void display::draw_unit_on_tile(int x, int y, SDL_Surface* unit_image_override,
double highlight_ratio, Uint32 blend_with)
{
if(updatesLocked_)
return;
const gamemap::location loc(x,y);
int xpos = get_location_x(loc);
int ypos = get_location_y(loc);
SDL_Rect clip_rect = map_area();
if(xpos > clip_rect.x + clip_rect.w || ypos > clip_rect.y + clip_rect.h ||
xpos + zoom_ < clip_rect.x || ypos + zoom_ < clip_rect.y) {
return;
}
SDL_Surface* const dst = screen_.getSurface();
clip_rect_setter set_clip_rect(dst,clip_rect);
double unit_energy = 0.0;
Uint16 energy_colour = 0;
const int max_energy = 80;
double energy_size = 1.0;
if(unit_image_override != NULL)
sdl_add_ref(unit_image_override);
scoped_sdl_surface unit_image(unit_image_override);
scoped_sdl_surface energy_image(NULL);
//see if there is a unit on this tile
const unit_map::const_iterator it = units_.find(gamemap::location(x,y));
if(it != units_.end() && (loc != hiddenUnit_ || !hideEnergy_)) {
if(unit_image == NULL)
unit_image.assign(image::get_image(it->second.image(),it->second.stone() ? image::GREYED : image::SCALED));
if(unit_image == NULL) {
return;
}
const int unit_move = it->second.movement_left();
const int unit_total_move = it->second.total_movement();
const std::string* energy_file = NULL;
if(size_t(it->second.side()) != currentTeam_+1) {
if(team_valid() &&
teams_[currentTeam_].is_enemy(it->second.side())) {
energy_file = &game_config::enemy_energy_image;
} else {
energy_file = &game_config::ally_energy_image;
}
} else {
if(activeTeam_ == currentTeam_ && unit_move == unit_total_move) {
energy_file = &game_config::unmoved_energy_image;
} else if(activeTeam_ == currentTeam_ && unit_can_move(loc,units_,map_,teams_)) {
energy_file = &game_config::partmoved_energy_image;
} else {
energy_file = &game_config::moved_energy_image;
}
}
assert(energy_file != NULL);
if(energy_file == NULL) {
std::cerr << "energy file is NULL\n";
return;
}
energy_image.assign(image::get_image(*energy_file,image::SCALED,image::NO_ADJUST_COLOUR));
if(energy_image.get() == NULL) {
std::cerr << "failed to get energy image: '" << *energy_file << "'\n";
return;
}
unit_energy = minimum<double>(1.0,double(it->second.hitpoints()) / double(it->second.max_hitpoints()));
if(highlight_ratio == 1.0)
highlight_ratio = it->second.alpha();
if(it->second.invisible(map_.underlying_terrain(map_[x][y]),
status_.get_time_of_day().lawful_bonus,loc,
units_,teams_) &&
highlight_ratio > 0.5) {
highlight_ratio = 0.5;
}
if(loc == selectedHex_ && highlight_ratio == 1.0) {
highlight_ratio = 1.5;
blend_with = rgb(255,255,255);
}
{
int er = 0;
int eg = 0;
int eb = 0;
if(unit_energy < 0.33) {
er = 200;
} else if(unit_energy < 0.66) {
er = 200;
eg = 200;
} else {
eg = 200;
}
energy_colour = ::SDL_MapRGB(screen_.getSurface()->format,er,eg,eb);
}
if(it->second.max_hitpoints() < max_energy) {
energy_size = double(it->second.max_hitpoints())/double(max_energy);
}
if(it->second.facing_left() == false) {
//reverse the image here. image::reverse_image is more efficient, however
//it can be used only if we are sure that unit_image came from image::get_image.
//Since we aren't sure of that in the case of overrides, use the less efficient
//flip_surface if the image has been over-ridden.
if(unit_image_override == NULL) {
unit_image.assign(image::reverse_image(unit_image));
} else {
unit_image.assign(flip_surface(unit_image));
}
}
}
if(deadUnit_ == gamemap::location(x,y)) {
highlight_ratio = deadAmount_;
}
if(unit_image == NULL || energy_image == NULL || fogged(x,y) ||
(teams_[currentTeam_].is_enemy(it->second.side()) &&
it->second.invisible(map_.underlying_terrain(map_[x][y]),
status_.get_time_of_day().lawful_bonus,loc,
units_,teams_))) {
return;
}
const gamemap::TERRAIN terrain = map_.get_terrain(loc);
const int height_adjust = it->second.is_flying() ? 0 : int(map_.get_terrain_info(terrain).unit_height_adjust()*zoom());
const double submerge = it->second.is_flying() ? 0.0 : map_.get_terrain_info(terrain).unit_submerge();
if(loc == advancingUnit_ && it != units_.end()) {
//the unit is advancing - set the advancing colour to white if it's a
//non-chaotic unit, otherwise black
blend_with = it->second.type().alignment() == unit_type::CHAOTIC ?
rgb(16,16,16) : rgb(255,255,255);
highlight_ratio = advancingAmount_;
} else if(it->second.poisoned() && highlight_ratio == 1.0) {
//the unit is poisoned - draw with a green hue
blend_with = rgb(0,255,0);
highlight_ratio = 0.75;
}
const bool energy_uses_alpha = highlight_ratio < 1.0 && blend_with == 0;
if(loc != hiddenUnit_) {
scoped_sdl_surface ellipse_front(NULL);
scoped_sdl_surface ellipse_back(NULL);
if(preferences::show_side_colours()) {
char buf[50];
sprintf(buf,"misc/ellipse-%d-top.png",it->second.side());
ellipse_back.assign(image::get_image(buf));
sprintf(buf,"misc/ellipse-%d-bottom.png",it->second.side());
ellipse_front.assign(image::get_image(buf));
}
draw_unit(xpos,ypos - height_adjust,unit_image,false,
highlight_ratio,blend_with,submerge,ellipse_back,ellipse_front);
}
const SDL_Rect& energy_bar_loc = calculate_energy_bar();
double total_energy = double(energy_bar_loc.h);
const int skip_energy_rows = int(total_energy*(1.0-energy_size));
total_energy -= double(skip_energy_rows);
const int lost_energy = int((1.0-unit_energy)*total_energy);
const int show_energy_after = energy_bar_loc.y + lost_energy;
if(energy_uses_alpha) {
energy_image.assign(adjust_surface_alpha(energy_image,highlight_ratio));
}
SDL_Rect first_energy = {0,0,energy_image->w,energy_bar_loc.y};
SDL_Rect second_energy = {0,energy_bar_loc.y+skip_energy_rows,energy_image->w,0};
second_energy.h = energy_image->w - second_energy.y;
blit_surface(xpos,ypos,energy_image,&first_energy,&clip_rect);
blit_surface(xpos,ypos+first_energy.h,energy_image,&second_energy,&clip_rect);
if(skip_energy_rows + lost_energy < energy_bar_loc.h) {
SDL_Rect filled_energy_area = { xpos + energy_bar_loc.x, ypos+show_energy_after,
energy_bar_loc.w, energy_bar_loc.h - skip_energy_rows - lost_energy };
SDL_FillRect(dst,&filled_energy_area,energy_colour);
}
}
void display::draw_tile_adjacent(int x, int y, image::TYPE image_type, ADJACENT_TERRAIN_TYPE type)
{
const gamemap::location loc(x,y);
int xpos = int(get_location_x(loc));
int ypos = int(get_location_y(loc));
SDL_Rect clip_rect = map_area();
if(xpos > clip_rect.x + clip_rect.w || ypos > clip_rect.y + clip_rect.h ||
xpos + zoom_ < clip_rect.x || ypos + zoom_ < clip_rect.y) {
return;
}
SDL_Surface* const dst = screen_.getSurface();
clip_rect_setter set_clip_rect(dst,clip_rect);
const std::vector<shared_sdl_surface>& adj = getAdjacentTerrain(x,y,image_type,type);
std::vector<shared_sdl_surface>::const_iterator i;
for(i = adj.begin(); i != adj.end(); ++i) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(*i,NULL,dst,&dstrect);
}
const std::vector<shared_sdl_surface>& built = getBuiltTerrain(x,y,image_type,type);
for(i = built.begin(); i != built.end(); ++i) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(*i,NULL,dst,&dstrect);
}
}
void display::draw_tile(int x, int y, SDL_Surface* unit_image, double alpha, Uint32 blend_to)
{
if(updatesLocked_)
return;
const gamemap::location loc(x,y);
int xpos = int(get_location_x(loc));
int ypos = int(get_location_y(loc));
SDL_Rect clip_rect = map_area();
if(xpos >= clip_rect.x + clip_rect.w || ypos >= clip_rect.y + clip_rect.h ||
xpos + zoom_ < clip_rect.x || ypos + zoom_ < clip_rect.y) {
return;
}
SDL_Surface* const dst = screen_.getSurface();
clip_rect_setter set_clip_rect(dst,clip_rect);
const bool is_shrouded = shrouded(x,y);
gamemap::TERRAIN terrain = gamemap::VOID_TERRAIN;
if(!is_shrouded) {
terrain = map_.get_terrain(loc);
}
image::TYPE image_type = image::SCALED;
const time_of_day& tod = status_.get_time_of_day();
const time_of_day& tod_at = timeofday_at(status_,units_,loc);
std::string mask = tod_at.image_mask;
if(tod_hex_mask1 != NULL || tod_hex_mask2 != NULL || tod.image_mask != tod_at.image_mask) {
image_type = image::UNMASKED;
mask = tod_at.image_mask;
}
//find if this tile should be greyed
if(pathsList_ != NULL && pathsList_->routes.find(gamemap::location(x,y)) ==
pathsList_->routes.end()) {
image_type = image::GREYED;
}
unit_map::iterator un = find_visible_unit(units_, loc, map_,
status_.get_time_of_day().lawful_bonus,teams_,teams_[currentTeam_]);
if(loc == mouseoverHex_ && map_.on_board(mouseoverHex_) ||
loc == selectedHex_ && (un != units_.end())) {
image_type = image::BRIGHTENED;
}
else if (highlighted_locations_.find(loc) != highlighted_locations_.end()) {
image_type = image::BRIGHTENED;
}
scoped_sdl_surface surface(getTerrain(terrain,image_type,x,y));
if(surface == NULL) {
std::cerr << "Could not get terrain surface\n";
return;
}
update_rect(xpos,ypos,surface->w,surface->h);
//note that dstrect can be changed by SDL_BlitSurface and so a new instance should be
//initialized to pass to each call to SDL_BlitSurface
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(surface,NULL,dst,&dstrect);
if(!is_shrouded) {
scoped_sdl_surface flag(getFlag(terrain,x,y));
if(flag != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(flag,NULL,dst,&dstrect);
}
draw_tile_adjacent(x,y,image_type,ADJACENT_BACKGROUND);
typedef std::multimap<gamemap::location,std::string>::const_iterator Itor;
for(std::pair<Itor,Itor> overlays = overlays_.equal_range(loc);
overlays.first != overlays.second; ++overlays.first) {
scoped_sdl_surface overlay_surface(image::get_image(overlays.first->second));
if(overlay_surface != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(overlay_surface,NULL,dst,&dstrect);
}
}
}
if(!is_shrouded) {
draw_footstep(loc,xpos,ypos);
}
if(fogged(x,y)) {
const scoped_sdl_surface fog_surface(image::get_image("terrain/fog.png"));
if(fog_surface != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(fog_surface,NULL,dst,&dstrect);
}
}
draw_unit_on_tile(x,y,unit_image,alpha,blend_to);
if(!shrouded(x,y)) {
draw_tile_adjacent(x,y,image_type,ADJACENT_FOREGROUND);
}
//draw the time-of-day mask on top of the hex
if(tod_hex_mask1 != NULL || tod_hex_mask2 != NULL) {
if(tod_hex_mask1 != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(tod_hex_mask1,NULL,dst,&dstrect);
}
if(tod_hex_mask2 != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(tod_hex_mask2,NULL,dst,&dstrect);
}
} else if(mask != "") {
const scoped_sdl_surface img(image::get_image(mask,image::UNMASKED,image::NO_ADJUST_COLOUR));
if(img != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(img,NULL,dst,&dstrect);
}
}
if(grid_) {
scoped_sdl_surface grid_surface(image::get_image("terrain/grid.png"));
if(grid_surface != NULL) {
SDL_Rect dstrect = { xpos, ypos, 0, 0 };
SDL_BlitSurface(grid_surface,NULL,dst,&dstrect);
}
}
if(game_config::debug && debugHighlights_.count(gamemap::location(x,y))) {
const scoped_sdl_surface cross(image::get_image(game_config::cross_image));
if(cross != NULL)
draw_unit(xpos,ypos,cross,false,debugHighlights_[loc],0);
}
}
void display::draw_footstep(const gamemap::location& loc, int xloc, int yloc)
{
std::vector<gamemap::location>::const_iterator i =
std::find(route_.steps.begin(),route_.steps.end(),loc);
if(i == route_.steps.begin() || i == route_.steps.end())
return;
const bool show_time = (i+1 == route_.steps.end());
const bool left_foot = is_even(i - route_.steps.begin());
//generally we want the footsteps facing toward the direction they're going
//to go next.
//if we're on the last step, then we want them facing according to where
//they came from, so we move i back by one
if(i+1 == route_.steps.end() && i != route_.steps.begin())
--i;
gamemap::location::DIRECTION direction = gamemap::location::NORTH;
if(i+1 != route_.steps.end()) {
for(int n = 0; n != 6; ++n) {
direction = gamemap::location::DIRECTION(n);
if(i->get_direction(direction) == *(i+1)) {
break;
}
}
}
static const std::string left_nw(game_config::foot_left_nw);
static const std::string left_n(game_config::foot_left_n);
static const std::string right_nw(game_config::foot_right_nw);
static const std::string right_n(game_config::foot_right_n);
const std::string* image_str = &left_nw;
if(left_foot) {
if(direction == gamemap::location::NORTH ||
direction == gamemap::location::SOUTH) {
image_str = &left_n;
} else {
image_str = &left_nw;
}
} else {
if(direction == gamemap::location::NORTH ||
direction == gamemap::location::SOUTH) {
image_str = &right_n;
} else {
image_str = &right_nw;
}
}
scoped_sdl_surface image(image::get_image(*image_str));
if(image == NULL) {
std::cerr << "Could not find image: " << *image_str << "\n";
return;
}
const bool hflip = !(direction > gamemap::location::NORTH &&
direction <= gamemap::location::SOUTH);
const bool vflip = (direction >= gamemap::location::SOUTH_EAST &&
direction <= gamemap::location::SOUTH_WEST);
if(!hflip) {
image.assign(image::reverse_image(image));
}
draw_unit(xloc,yloc,image,vflip,0.5);
if(show_time && route_.move_left > 0 && route_.move_left < 10) {
//draw number in yellow if terrain is light, else draw in black
gamemap::TERRAIN terrain = map_.get_terrain(loc);
const bool tile_is_light = map_.get_terrain_info(terrain).is_light();
SDL_Color text_colour = tile_is_light ? font::DARK_COLOUR : font::YELLOW_COLOUR;
const SDL_Rect& rect = map_area();
std::string str(1,'x');
str[0] = '1' + route_.move_left;
const SDL_Rect& text_area = font::draw_text(NULL,rect,18,text_colour,str,0,0);
const int x = xloc + zoom_/2 - text_area.w/2;
const int y = yloc + zoom_/2 - text_area.h/2;
font::draw_text(this,rect,18,text_colour,str,x,y);
}
}
namespace {
const std::string& get_direction(size_t n)
{
const static std::string dirs[6] = {"-n","-ne","-se","-s","-sw","-nw"};
return dirs[n >= sizeof(dirs)/sizeof(*dirs) ? 0 : n];
}
}
bool angle_is_northern(size_t n)
{
const static bool results[6] = {true,false,false,false,false,true};
return results[n >= sizeof(results)/sizeof(*results) ? 0 : n];
}
const std::string& get_angle_direction(size_t n)
{
const static std::string dirs[6] = {"-ne","-e","-se","-sw","-w","-nw"};
return dirs[n >= sizeof(dirs)/sizeof(*dirs) ? 0 : n];
}
std::vector<shared_sdl_surface> display::getAdjacentTerrain(int x, int y, image::TYPE image_type, ADJACENT_TERRAIN_TYPE terrain_type)
{
std::vector<shared_sdl_surface> res;
gamemap::location loc(x,y);
const gamemap::TERRAIN current_terrain = map_.get_terrain(loc);
gamemap::location adjacent[6];
get_adjacent_tiles(loc,adjacent);
int tiles[6];
for(int i = 0; i != 6; ++i) {
if(terrain_type == ADJACENT_FOREGROUND && shrouded(adjacent[i].x,adjacent[i].y))
tiles[i] = gamemap::VOID_TERRAIN;
else if(terrain_type == ADJACENT_FOREGROUND && !fogged(x,y) && fogged(adjacent[i].x,adjacent[i].y))
tiles[i] = gamemap::FOGGED;
else
tiles[i] = map_.get_terrain(adjacent[i]);
}
static std::vector<gamemap::TERRAIN> fog_shroud;
if(fog_shroud.empty()) {
fog_shroud.push_back(gamemap::VOID_TERRAIN);
fog_shroud.push_back(gamemap::FOGGED);
}
const std::vector<gamemap::TERRAIN>& precedence = (terrain_type == ADJACENT_BACKGROUND) ?
map_.get_terrain_precedence() : fog_shroud;
std::vector<gamemap::TERRAIN>::const_iterator terrain =
std::find(precedence.begin(),precedence.end(),current_terrain);
if(terrain == precedence.end()) {
terrain = precedence.begin();
} else {
++terrain;
while(terrain != precedence.end() &&
map_.get_terrain_info(*terrain).equal_precedence()) {
++terrain;
}
}
for(; terrain != precedence.end(); ++terrain){
//find somewhere that doesn't have overlap to use as a starting point
int start;
for(start = 0; start != 6; ++start) {
if(tiles[start] != *terrain)
break;
}
if(start == 6) {
start = 0;
}
//find all the directions overlap occurs from
for(int i = (start+1)%6, n = 0; i != start && n != 6; ++n) {
if(tiles[i] == *terrain) {
shared_sdl_surface surface(NULL);
std::ostringstream stream;
for(int n = 0; *terrain == tiles[i] && n != 6; i = (i+1)%6, ++n) {
stream << get_direction(i);
const shared_sdl_surface new_surface(getTerrain(
*terrain,image_type,x,y,stream.str()));
if(new_surface == NULL) {
//if we don't have any surface at all,
//then move onto the next overlapped area
if(surface == NULL)
i = (i+1)%6;
break;
}
surface = new_surface;
}
if(surface != NULL)
res.push_back(surface);
} else {
i = (i+1)%6;
}
}
}
return res;
}
std::vector<shared_sdl_surface> display::getBuiltTerrain(int x, int y, image::TYPE image_type, ADJACENT_TERRAIN_TYPE terrain_type)
{
std::vector<shared_sdl_surface> res;
gamemap::location loc(x,y);
terrain_builder::ADJACENT_TERRAIN_TYPE builder_terrain_type =
(terrain_type == ADJACENT_FOREGROUND ?
terrain_builder::ADJACENT_FOREGROUND : terrain_builder::ADJACENT_BACKGROUND);
const std::vector<std::string>* const terrains = builder_.get_terrain_at(loc,builder_terrain_type);
if(terrains != NULL) {
for(std::vector<std::string>::const_iterator it = terrains->begin(); it != terrains->end(); ++it) {
const std::string image = "terrain/" + *it;
const shared_sdl_surface surface(getTerrain(image,image_type,x,y,true));
if(surface != NULL) {
res.push_back(surface);
}
}
}
return res;
}
SDL_Surface* display::getTerrain(const std::string& image, image::TYPE image_type,
int x, int y, bool search_tod)
{
SDL_Surface* im = NULL;
const time_of_day& tod = status_.get_time_of_day();
const time_of_day& tod_at = timeofday_at(status_,units_,gamemap::location(x,y));
//see if there is a time-of-day specific version of this image
if(search_tod) {
const std::string tod_image = image + "-" + tod.id + ".png";
im = image::get_image(tod_image,image_type);
if(im != NULL) {
return im;
}
}
const std::string file = image + ".png";
im = image::get_image(file,image_type);
if(im == NULL) {
return NULL;
}
//see if this tile is illuminated to a different colour than it'd
//normally be displayed as
const int radj = tod_at.red - tod.red;
const int gadj = tod_at.green - tod.green;
const int badj = tod_at.blue - tod.blue;
if((radj|gadj|badj) != 0 && im != NULL) {
const scoped_sdl_surface backup(im);
std::cerr << "adjusting surface colour " << radj << "," << gadj << "," << badj << "\n";
im = adjust_surface_colour(im,radj,gadj,badj);
std::cerr << "done adjust...\n";
if(im == NULL)
std::cerr << "could not adjust surface..\n";
}
return im;
}
SDL_Surface* display::getTerrain(gamemap::TERRAIN terrain, image::TYPE image_type,
int x, int y, const std::string& direction)
{
std::string image = "terrain/" + (direction.empty() ?
map_.get_terrain_info(terrain).image(x,y) :
map_.get_terrain_info(terrain).adjacent_image());
image += direction;
SDL_Surface* im = getTerrain(image, image_type, x, y, direction.empty());
if(im == NULL && direction.empty()) {
im = image::get_image("terrain/" +
map_.get_terrain_info(terrain).default_image() + ".png");
}
return im;
}
SDL_Surface* display::getFlag(gamemap::TERRAIN terrain, int x, int y)
{
const bool village = map_.is_village(terrain);
if(!village)
return NULL;
const gamemap::location loc(x,y);
for(size_t i = 0; i != teams_.size(); ++i) {
if(teams_[i].owns_village(loc) && (!fogged(x,y) || !shrouded(x,y) && !teams_[currentTeam_].is_enemy(i+1))) {
char buf[50];
sprintf(buf,"terrain/flag-team%d.png",i+1);
return image::get_image(buf);
}
}
return NULL;
}
void display::blit_surface(int x, int y, SDL_Surface* surface, SDL_Rect* srcrect, SDL_Rect* clip_rect)
{
SDL_Surface* const target = video().getSurface();
SDL_Rect dst = {x,y,0,0};
if(clip_rect != NULL) {
const clip_rect_setter clip_setter(target,*clip_rect);
SDL_BlitSurface(surface,srcrect,target,&dst);
} else {
SDL_BlitSurface(surface,srcrect,target,&dst);
}
}
SDL_Surface* display::getMinimap(int w, int h)
{
if(minimap_ == NULL) {
std::cerr << "regetting minimap\n";
minimap_ = image::getMinimap(w,h,map_,
status_.get_time_of_day().lawful_bonus,
team_valid() ? &teams_[currentTeam_] : NULL);
std::cerr << "done regetting minimap\n";
}
sdl_add_ref(minimap_);
return minimap_;
}
void display::set_paths(const paths* paths_list)
{
pathsList_ = paths_list;
invalidate_all();
}
void display::invalidate_route()
{
for(std::vector<gamemap::location>::const_iterator i = route_.steps.begin();
i != route_.steps.end(); ++i) {
invalidate(*i);
}
}
void display::set_route(const paths::route* route)
{
invalidate_route();
if(route != NULL)
route_ = *route;
else
route_.steps.clear();
invalidate_route();
}
void display::remove_footstep(const gamemap::location& loc)
{
const std::vector<gamemap::location>::iterator it = std::find(route_.steps.begin(),route_.steps.end(),loc);
if(it != route_.steps.end())
route_.steps.erase(it);
}
void display::float_label(const gamemap::location& loc, const std::string& text,
int red, int green, int blue)
{
if(preferences::show_floating_labels() == false || fogged(loc.x,loc.y)) {
return;
}
const SDL_Color colour = {red,green,blue,255};
font::add_floating_label(text,24,colour,get_location_x(loc)+zoom_/2,get_location_y(loc),
0,-2,60,screen_area());
}
void display::draw_unit(int x, int y, SDL_Surface* image,
bool upside_down, double alpha, Uint32 blendto, double submerged,
SDL_Surface* ellipse_back, SDL_Surface* ellipse_front)
{
if(ellipse_back != NULL) {
draw_unit(x,y,ellipse_back,false,blendto == 0 ? alpha : 1.0,0,submerged);
}
sdl_add_ref(image);
scoped_sdl_surface surf(image);
if(upside_down) {
surf.assign(flop_surface(surf));
}
if(alpha > 1.0) {
surf.assign(brighten_image(surf,alpha));
} else if(alpha != 1.0 && blendto != 0) {
surf.assign(blend_surface(surf,1.0-alpha,blendto));
} else if(alpha != 1.0) {
surf.assign(adjust_surface_alpha(surf,alpha));
}
if(surf == NULL) {
std::cerr << "surface lost...\n";
return;
}
const int submerge_height = minimum<int>(surf->h,maximum<int>(0,int(surf->h*(1.0-submerged))));
SDL_Rect clip_rect = map_area();
SDL_Rect srcrect = {0,0,surf->w,submerge_height};
blit_surface(x,y,surf,&srcrect,&clip_rect);
if(submerge_height != surf->h) {
surf.assign(adjust_surface_alpha(surf,0.2));
srcrect.y = submerge_height;
srcrect.h = surf->h-submerge_height;
y += submerge_height;
blit_surface(x,y,surf,&srcrect,&clip_rect);
}
if(ellipse_front != NULL) {
draw_unit(x,y,ellipse_front,false,blendto == 0 ? alpha : 1.0,0,submerged);
}
}
struct is_energy_colour {
bool operator()(Uint32 colour) const { return (colour&0xFF000000) < 0x99000000 && (colour&0x00FF0000) > 0x00990000; }
};
const SDL_Rect& display::calculate_energy_bar()
{
if(energy_bar_rect_.x != -1) {
return energy_bar_rect_;
}
int first_row = -1, last_row = -1, first_col = -1, last_col = -1;
scoped_sdl_surface image(image::get_image(game_config::unmoved_energy_image,image::SCALED));
image.assign(make_neutral_surface(image));
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_colour());
const int count = std::count_if(itor,i2,is_energy_colour());
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 = {first_col,first_row,last_col-first_col,last_row+1-first_row};
energy_bar_rect_ = res;
return energy_bar_rect_;
}
void display::invalidate(const gamemap::location& loc)
{
if(!invalidateAll_) {
invalidated_.insert(loc);
}
}
void display::invalidate_all()
{
invalidateAll_ = true;
invalidated_.clear();
update_rect(map_area());
}
void display::invalidate_unit()
{
invalidateUnit_ = true;
}
void display::recalculate_minimap()
{
if(minimap_ != NULL) {
SDL_FreeSurface(minimap_);
minimap_ = NULL;
}
redraw_minimap();
}
void display::redraw_minimap()
{
redrawMinimap_ = true;
}
void display::invalidate_game_status()
{
invalidateGameStatus_ = true;
}
void display::add_overlay(const gamemap::location& loc, const std::string& img)
{
overlays_.insert(std::pair<gamemap::location,std::string>(loc,img));
}
void display::remove_overlay(const gamemap::location& loc)
{
overlays_.erase(loc);
}
void display::write_overlays(config& cfg) const
{
for(std::multimap<gamemap::location,std::string>::const_iterator i = overlays_.begin();
i != overlays_.end(); ++i) {
config& item = cfg.add_child("item");
i->first.write(item);
item["image"] = i->second;
}
}
void display::set_team(size_t team)
{
assert(team < teams_.size());
currentTeam_ = team;
labels().recalculate_shroud();
}
void display::set_playing_team(size_t team)
{
assert(team < teams_.size());
activeTeam_ = team;
invalidate_game_status();
}
void display::set_advancing_unit(const gamemap::location& loc, double amount)
{
advancingUnit_ = loc;
advancingAmount_ = amount;
draw_tile(loc.x,loc.y);
}
void display::lock_updates(bool value)
{
if(value == true)
++updatesLocked_;
else
--updatesLocked_;
}
bool display::update_locked() const
{
return updatesLocked_ > 0;
}
bool display::turbo() const
{
bool res = turbo_;
if(keys_[SDLK_LSHIFT] || keys_[SDLK_RSHIFT])
res = !res;
return res;
}
void display::set_turbo(bool turbo)
{
turbo_ = turbo;
}
void display::set_grid(bool grid)
{
grid_ = grid;
}
void display::debug_highlight(const gamemap::location& loc, double amount)
{
assert(game_config::debug);
debugHighlights_[loc] += amount;
}
void display::clear_debug_highlights()
{
debugHighlights_.clear();
}
bool display::shrouded(int x, int y) const
{
if(team_valid())
return teams_[currentTeam_].shrouded(x,y);
else
return false;
}
bool display::fogged(int x, int y) const
{
if(team_valid())
return teams_[currentTeam_].fogged(x,y);
else
return false;
}
bool display::team_valid() const
{
return currentTeam_ < teams_.size();
}
size_t display::viewing_team() const
{
return currentTeam_;
}
size_t display::playing_team() const
{
return activeTeam_;
}
const theme& display::get_theme() const
{
return theme_;
}
const theme::menu* display::menu_pressed(int mousex, int mousey, bool button_pressed)
{
for(std::vector<gui::button>::iterator i = buttons_.begin(); i != buttons_.end(); ++i) {
if(i->process(mousex,mousey,button_pressed)) {
const size_t index = i - buttons_.begin();
assert(index < theme_.menus().size());
return &theme_.menus()[index];
}
}
return NULL;
}
void display::create_buttons()
{
buttons_.clear();
const std::vector<theme::menu>& buttons = theme_.menus();
for(std::vector<theme::menu>::const_iterator i = buttons.begin(); i != buttons.end(); ++i) {
gui::button b(*this,i->title(),gui::button::TYPE_PRESS,i->image());
const SDL_Rect& loc = i->location(screen_area());
b.set_xy(loc.x,loc.y);
buttons_.push_back(b);
}
}
void display::add_observer(const std::string& name)
{
observers_.insert(name);
}
void display::remove_observer(const std::string& name)
{
observers_.erase(name);
}
namespace {
const int max_chat_messages = 6;
const int chat_message_border = 5;
const int chat_message_x = 10;
const int chat_message_y = 10;
const SDL_Color chat_message_colour = {200,200,200,200};
const SDL_Color chat_message_bg = {0,0,0,100};
}
void display::add_chat_message(const std::string& speaker, int side, const std::string& message, display::MESSAGE_TYPE type)
{
std::string msg = message;
gui::text_to_lines(msg,80);
int ypos = chat_message_x;
for(std::vector<chat_message>::const_iterator m = chat_messages_.begin(); m != chat_messages_.end(); ++m) {
ypos += font::get_floating_label_rect(m->handle).h;
}
std::stringstream str;
if(type == MESSAGE_PUBLIC) {
str << "<" << speaker << ">";
} else {
str << font::NULL_MARKUP << "*" << speaker << "*";
}
SDL_Color speaker_colour = {255,255,255,255};
if(side >= 1) {
speaker_colour = font::get_side_colour(side);
}
const SDL_Rect rect = map_area();
const int speaker_handle = font::add_floating_label(str.str(),12,speaker_colour,
rect.x+chat_message_x,rect.y+ypos,
0,0,-1,rect,font::LEFT_ALIGN,&chat_message_bg,chat_message_border);
const int message_handle = font::add_floating_label(msg,12,chat_message_colour,
rect.x + chat_message_x + font::get_floating_label_rect(speaker_handle).w,rect.y+ypos,
0,0,-1,rect,font::LEFT_ALIGN,&chat_message_bg,chat_message_border);
chat_messages_.push_back(chat_message(speaker_handle,message_handle));
prune_chat_messages();
}
void display::prune_chat_messages(bool remove_all)
{
const int message_ttl = remove_all ? 0 : 1200000;
if(chat_messages_.empty() == false && (chat_messages_.front().created_at+message_ttl < SDL_GetTicks() || chat_messages_.size() > max_chat_messages)) {
const int movement = font::get_floating_label_rect(chat_messages_.front().handle).h;
font::remove_floating_label(chat_messages_.front().speaker_handle);
font::remove_floating_label(chat_messages_.front().handle);
chat_messages_.erase(chat_messages_.begin());
for(std::vector<chat_message>::const_iterator i = chat_messages_.begin(); i != chat_messages_.end(); ++i) {
font::move_floating_label(i->speaker_handle,0,-movement);
font::move_floating_label(i->handle,0,-movement);
}
prune_chat_messages(remove_all);
}
}
void display::rebuild_terrain(const gamemap::location &loc) {
builder_.rebuild_terrain(loc);
}
void display::add_highlighted_loc(const gamemap::location &hex) {
// Only invalidate and insert if this is a new addition, for
// efficiency.
if (highlighted_locations_.find(hex) == highlighted_locations_.end()) {
highlighted_locations_.insert(hex);
invalidate(hex);
}
}
void display::clear_highlighted_locs() {
for (std::set<gamemap::location>::const_iterator it = highlighted_locations_.begin();
it != highlighted_locations_.end(); it++) {
invalidate(*it);
}
highlighted_locations_.clear();
}
void display::remove_highlighted_loc(const gamemap::location &hex) {
std::set<gamemap::location>::iterator it = highlighted_locations_.find(hex);
// Only invalidate and remove if the hex was found, for efficiency.
if (it != highlighted_locations_.end()) {
highlighted_locations_.erase(it);
invalidate(hex);
}
}