mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-03 23:29:59 +00:00

Now initialize and reset TC in titlescreen for add-ons and campaigns icons (previously was only initialized after having played a game) This also restore coloring for case like ~TC(not a playing side)
1210 lines
35 KiB
C++
1210 lines
35 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2007 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 version 2
|
|
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 game_display.cpp
|
|
//! During a game, show map & info-panels at top+right.
|
|
|
|
#include "global.hpp"
|
|
|
|
#include "actions.hpp"
|
|
#include "cursor.hpp"
|
|
#include "game_display.hpp"
|
|
#include "events.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "font.hpp"
|
|
#include "game_config.hpp"
|
|
#include "gamestatus.hpp"
|
|
#include "gettext.hpp"
|
|
#include "halo.hpp"
|
|
#include "hotkeys.hpp"
|
|
#include "language.hpp"
|
|
#include "log.hpp"
|
|
#include "marked-up_text.hpp"
|
|
#include "minimap.hpp"
|
|
#include "game_preferences.hpp"
|
|
#include "sdl_utils.hpp"
|
|
#include "sound.hpp"
|
|
#include "team.hpp"
|
|
#include "theme.hpp"
|
|
#include "tooltips.hpp"
|
|
#include "unit_display.hpp"
|
|
#include "util.hpp"
|
|
#include "wassert.hpp"
|
|
|
|
#include "SDL_image.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
#define ERR_DP LOG_STREAM(err, display)
|
|
#define INFO_DP LOG_STREAM(info, display)
|
|
|
|
std::map<gamemap::location,fixed_t> game_display::debugHighlights_;
|
|
|
|
game_display::game_display(unit_map& units, CVideo& video, const gamemap& map,
|
|
const gamestatus& status, const std::vector<team>& t,
|
|
const config& theme_cfg, const config& cfg, const config& level) :
|
|
display(video, map, theme_cfg, cfg, level),
|
|
units_(units),
|
|
temp_unit_(NULL),
|
|
status_(status),
|
|
teams_(t),
|
|
level_(level),
|
|
invalidateUnit_(true),
|
|
currentTeam_(0), activeTeam_(0),
|
|
sidebarScaling_(1.0),
|
|
first_turn_(true), in_game_(false),
|
|
tod_hex_mask1(NULL), tod_hex_mask2(NULL), reach_map_changed_(true)
|
|
{
|
|
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_colour_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::flag_image;
|
|
}
|
|
|
|
LOG_STREAM(info, display) << "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 the screen contents
|
|
surface const disp(screen_.getSurface());
|
|
SDL_Rect area = screen_area();
|
|
SDL_FillRect(disp,&area,SDL_MapRGB(disp->format,0,0,0));
|
|
}
|
|
|
|
game_display::~game_display()
|
|
{
|
|
// SDL_FreeSurface(minimap_);
|
|
prune_chat_messages(true);
|
|
singleton_ = NULL;
|
|
}
|
|
|
|
void game_display::new_turn()
|
|
{
|
|
const time_of_day& tod = status_.get_time_of_day();
|
|
|
|
if( !first_turn_) {
|
|
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 surface old_mask(image::get_image(old_tod.image_mask,image::UNMASKED));
|
|
const surface new_mask(image::get_image(tod.image_mask,image::UNMASKED));
|
|
|
|
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;
|
|
|
|
image::set_colour_adjustment(tod.red,tod.green,tod.blue);
|
|
image::set_image_mask(tod.image_mask);
|
|
|
|
invalidate_all();
|
|
draw();
|
|
}
|
|
|
|
void game_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);
|
|
}
|
|
|
|
void game_display::select_hex(gamemap::location hex)
|
|
{
|
|
if(hex.valid() && fogged(hex)) {
|
|
return;
|
|
}
|
|
display::select_hex(hex);
|
|
invalidate_unit();
|
|
}
|
|
|
|
void game_display::highlight_hex(gamemap::location hex)
|
|
{
|
|
const int has_unit = units_.count(mouseoverHex_) + units_.count(hex);
|
|
|
|
display::highlight_hex(hex);
|
|
invalidate_game_status();
|
|
|
|
if(has_unit) {
|
|
invalidate_unit();
|
|
}
|
|
}
|
|
|
|
void game_display::scroll_to_leader(unit_map& units, int side)
|
|
{
|
|
const unit_map::iterator leader = find_leader(units,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->first, ONSCREEN);
|
|
}
|
|
}
|
|
|
|
|
|
void game_display::draw(bool update,bool force)
|
|
{
|
|
if (screen_.update_locked()) {
|
|
return;
|
|
}
|
|
|
|
bool changed = display::draw_init();
|
|
|
|
//log_scope("Drawing");
|
|
invalidate_animations();
|
|
|
|
process_reachmap_changes();
|
|
|
|
//! @todo FIXME: must modify changed, but best to do it
|
|
//! at the floating_label level
|
|
prune_chat_messages();
|
|
|
|
if(map_.empty()) {
|
|
display::draw_wrap(update, force, changed);
|
|
return;
|
|
}
|
|
|
|
halo::unrender(invalidated_);
|
|
|
|
//int simulate_delay = 0;
|
|
if(!invalidated_.empty()) {
|
|
changed = true;
|
|
|
|
// z-ordered set to store invalidated units
|
|
std::set<gamemap::location, ordered_draw> unit_invals;
|
|
|
|
const time_of_day& tod = status_.get_time_of_day();
|
|
const std::string shroud_image = "terrain/" +
|
|
map_.get_terrain_info(t_translation::VOID_TERRAIN).minimap_image() + ".png";
|
|
const std::string fog_image = "terrain/" +
|
|
map_.get_terrain_info(t_translation::FOGGED).minimap_image() + ".png";
|
|
|
|
SDL_Rect clip_rect = map_area();
|
|
surface const dst(screen_.getSurface());
|
|
clip_rect_setter set_clip_rect(dst, clip_rect);
|
|
|
|
std::set<gamemap::location>::const_iterator it;
|
|
for(it = invalidated_.begin(); it != invalidated_.end(); ++it) {
|
|
int xpos = get_location_x(*it);
|
|
int ypos = get_location_y(*it);
|
|
|
|
// Store invalidated units
|
|
if ((temp_unit_ && temp_unit_loc_==*it) || units_.find(*it) != units_.end()) {
|
|
unit_invals.insert(*it);
|
|
}
|
|
|
|
SDL_Rect hex_rect = {xpos, ypos, zoom_, zoom_};
|
|
if(!rects_overlap(hex_rect,clip_rect)) {
|
|
continue;
|
|
}
|
|
|
|
// If the terrain is off the map,
|
|
// it shouldn't be included for reachmap,
|
|
// fog, shroud and the grid.
|
|
// In the future it may not depend on
|
|
// whether the location is on the map,
|
|
// but whether it's an _off^* terrain.
|
|
// (atm not too happy with how the grid looks)
|
|
// (the shroud has some glitches due to
|
|
// commented out code, but enabling it looks worse).
|
|
const bool on_map = map_.on_board(*it);
|
|
const bool off_map_tile = (map_.get_terrain(*it) == t_translation::OFF_MAP_USER);
|
|
const bool is_shrouded = shrouded(*it);
|
|
|
|
image::TYPE image_type = image::SCALED_TO_HEX;
|
|
|
|
// We highlight hex under the mouse,
|
|
// origin of attack or under a selected unit.
|
|
if (on_map && (*it == mouseoverHex_ || *it == attack_indicator_src_)) {
|
|
image_type = image::BRIGHTENED;
|
|
} else if (on_map && *it == selectedHex_) {
|
|
unit_map::iterator un = find_visible_unit(units_, *it, map_,
|
|
teams_,teams_[currentTeam_]);
|
|
if (un != units_.end()) {
|
|
image_type = image::BRIGHTENED;
|
|
}
|
|
}
|
|
|
|
// Currently only used in editor
|
|
/*
|
|
else if (highlighted_locations_.find(*it) != highlighted_locations_.end()) {
|
|
image_type = image::SEMI_BRIGHTENED;
|
|
}
|
|
*/
|
|
|
|
tile_stack_clear();
|
|
|
|
if(!is_shrouded /*|| !on_map*/) {
|
|
// unshrouded terrain (the normal case)
|
|
tile_stack_append(get_terrain_images(*it,tod.id, image_type, ADJACENT_BACKGROUND));
|
|
|
|
// village-control flags.
|
|
tile_stack_append(get_flag(*it));
|
|
|
|
typedef overlay_map::const_iterator Itor;
|
|
|
|
for(std::pair<Itor,Itor> overlays = overlays_.equal_range(*it);
|
|
overlays.first != overlays.second; ++overlays.first) {
|
|
|
|
tile_stack_append(image::get_image(overlays.first->second.image,image_type));
|
|
}
|
|
}
|
|
|
|
if(!is_shrouded /*|| !on_map*/) {
|
|
tile_stack_append(get_terrain_images(*it,tod.id,image_type,ADJACENT_FOREGROUND));
|
|
}
|
|
|
|
// Draw the time-of-day mask on top of the terrain in the hex.
|
|
// tod may differ from tod if hex is illuminated.
|
|
std::string tod_hex_mask = timeofday_at(status_,units_,*it,map_).image_mask;
|
|
if(tod_hex_mask1 != NULL || tod_hex_mask2 != NULL) {
|
|
tile_stack_append(tod_hex_mask1);
|
|
tile_stack_append(tod_hex_mask2);
|
|
} else if(tod_hex_mask != "") {
|
|
tile_stack_append(image::get_image(tod_hex_mask,image::UNMASKED));
|
|
}
|
|
|
|
// Draw the grid, if that's been enabled
|
|
if(grid_ && !is_shrouded && on_map && !off_map_tile) {
|
|
tile_stack_append(image::get_image(game_config::grid_image, image::SCALED_TO_HEX));
|
|
}
|
|
|
|
// 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(*it) == reach_map_.end() && *it != attack_indicator_dst_) {
|
|
tile_stack_append(image::get_image(game_config::unreachable_image,image::UNMASKED));
|
|
}
|
|
|
|
// Draw cross images for debug highlights
|
|
if(game_config::debug && debugHighlights_.count(*it)) {
|
|
tile_stack_append(image::get_image(game_config::cross_image, image::SCALED_TO_HEX));
|
|
}
|
|
|
|
// Add the top layer overlay surfaces
|
|
if(!hex_overlay_.empty()) {
|
|
std::map<gamemap::location, surface>::const_iterator itor = hex_overlay_.find(*it);
|
|
if(itor != hex_overlay_.end())
|
|
tile_stack_append(itor->second);
|
|
}
|
|
|
|
// Footsteps indicating a movement path
|
|
tile_stack_append(footsteps_images(*it));
|
|
|
|
// Paint selection and mouseover overlays
|
|
if(*it == selectedHex_ && on_map && selected_hex_overlay_ != NULL)
|
|
tile_stack_append(selected_hex_overlay_);
|
|
if(*it == mouseoverHex_ && on_map && mouseover_hex_overlay_ != NULL)
|
|
tile_stack_append(mouseover_hex_overlay_);
|
|
|
|
// Draw the attack direction indicator
|
|
if(on_map && *it == attack_indicator_src_) {
|
|
tile_stack_append(image::get_image("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::UNMASKED));
|
|
} else if (on_map && *it == attack_indicator_dst_) {
|
|
tile_stack_append(image::get_image("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::UNMASKED));
|
|
}
|
|
|
|
// Apply shroud and fog
|
|
if(is_shrouded) {
|
|
// We apply void also on off-map tiles
|
|
// to shroud the half-hexes too
|
|
tile_stack_append(image::get_image(shroud_image, image::SCALED_TO_HEX));
|
|
} else if(fogged(*it)) {
|
|
tile_stack_append(image::get_image(fog_image, image::SCALED_TO_HEX));
|
|
}
|
|
|
|
if(!is_shrouded) {
|
|
tile_stack_append(get_terrain_images(*it, tod.id, image::SCALED_TO_HEX, ADJACENT_FOGSHROUD));
|
|
}
|
|
|
|
tile_stack_render(xpos, ypos);
|
|
|
|
// Show def% and turn to reach infos
|
|
if(!is_shrouded && on_map) {
|
|
draw_movement_info(*it);
|
|
}
|
|
//simulate_delay += 1;
|
|
|
|
// If the tile is at the border, we start to blend it
|
|
if(!on_map && !off_map_tile) {
|
|
draw_border(*it, xpos, ypos);
|
|
}
|
|
}
|
|
|
|
// Units can overlap multiple hexes, so we need
|
|
// to redraw them last and in the good sequence.
|
|
std::set<gamemap::location, struct display::ordered_draw>::const_iterator it2;
|
|
for(it2 = unit_invals.begin(); it2 != unit_invals.end(); ++it2) {
|
|
unit_map::iterator u_it = units_.find(*it2);
|
|
if (u_it != units_.end()) {
|
|
u_it->second.redraw_unit(*this, *it2);
|
|
//simulate_delay += 1;
|
|
}
|
|
|
|
if (temp_unit_ && temp_unit_loc_ == *it2) {
|
|
temp_unit_->redraw_unit(*this, temp_unit_loc_);
|
|
//simulate_delay += 1;
|
|
}
|
|
}
|
|
|
|
invalidated_.clear();
|
|
}
|
|
|
|
halo::render();
|
|
|
|
draw_sidebar();
|
|
//! @todo FIXME: This changed can probably be smarter
|
|
changed = true;
|
|
|
|
// Simulate slow PC:
|
|
//SDL_Delay(2*simulate_delay + rand() % 20);
|
|
|
|
display::draw_wrap(update, force, changed);
|
|
}
|
|
|
|
void game_display::draw_report(reports::TYPE report_num)
|
|
{
|
|
bool brighten;
|
|
|
|
if(!team_valid()) {
|
|
return;
|
|
}
|
|
|
|
reports::report report = reports::generate_report(report_num,report_,map_,
|
|
units_, teams_,
|
|
teams_[viewing_team()],
|
|
size_t(currentTeam_+1),size_t(activeTeam_+1),
|
|
selectedHex_,mouseoverHex_,status_,observers_,level_);
|
|
|
|
brighten = false;
|
|
if(report_num == reports::TIME_OF_DAY) {
|
|
time_of_day tod = timeofday_at(status_,units_,mouseoverHex_,map_);
|
|
// Don't show illuminated time on fogged/shrouded tiles
|
|
if (teams_[viewing_team()].fogged(mouseoverHex_.x, mouseoverHex_.y) ||
|
|
teams_[viewing_team()].shrouded(mouseoverHex_.x, mouseoverHex_.y)) {
|
|
|
|
tod = status_.get_time_of_day(false,mouseoverHex_);
|
|
}
|
|
brighten = (tod.bonus_modified > 0);
|
|
}
|
|
|
|
refresh_report(report_num, report, brighten);
|
|
}
|
|
|
|
void game_display::draw_game_status()
|
|
{
|
|
|
|
if(teams_.empty()) {
|
|
return;
|
|
}
|
|
|
|
for(size_t r = reports::STATUS_REPORTS_BEGIN; r != reports::STATUS_REPORTS_END; ++r) {
|
|
draw_report(reports::TYPE(r));
|
|
}
|
|
|
|
// The mouse-over needs to update the visibility icon
|
|
draw_report(reports::UNIT_STATUS);
|
|
}
|
|
|
|
void game_display::draw_sidebar()
|
|
{
|
|
draw_report(reports::REPORT_CLOCK);
|
|
draw_report(reports::REPORT_COUNTDOWN);
|
|
|
|
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.
|
|
unit_map::const_iterator i =
|
|
find_visible_unit(units_,mouseoverHex_,
|
|
map_,
|
|
teams_,teams_[viewing_team()]);
|
|
|
|
if(i == units_.end()) {
|
|
i = find_visible_unit(units_,selectedHex_,
|
|
map_,
|
|
teams_,teams_[viewing_team()]);
|
|
}
|
|
|
|
if(i != units_.end()) {
|
|
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();
|
|
invalidateGameStatus_ = false;
|
|
}
|
|
}
|
|
|
|
void game_display::draw_minimap_units(int x, int y, int w, int h)
|
|
{
|
|
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
|
|
if(fogged(u->first) ||
|
|
(teams_[currentTeam_].is_enemy(u->second.side()) &&
|
|
u->second.invisible(u->first,units_,teams_))) {
|
|
continue;
|
|
}
|
|
|
|
const int side = u->second.side();
|
|
const SDL_Color col = team::get_minimap_colour(side);
|
|
const Uint32 mapped_col = SDL_MapRGB(video().getSurface()->format,col.r,col.g,col.b);
|
|
SDL_Rect rect = { x + (u->first.x * w) / map_.w(),
|
|
y + (u->first.y * h + (is_odd(u->first.x) ? h / 2 : 0)) / map_.h(),
|
|
w / map_.w(), h / map_.h() };
|
|
SDL_FillRect(video().getSurface(),&rect,mapped_col);
|
|
}
|
|
}
|
|
|
|
void game_display::draw_bar(const std::string& image, int xpos, int ypos, size_t height, double filled, const SDL_Color& col, fixed_t alpha)
|
|
{
|
|
filled = minimum<double>(maximum<double>(filled,0.0),1.0);
|
|
height = static_cast<size_t>(height*get_zoom_factor());
|
|
#ifdef USE_TINY_GUI
|
|
height /= 2;
|
|
#endif
|
|
|
|
surface surf(image::get_image(image,image::UNMASKED));
|
|
|
|
// 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 = {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 = {0,0,surf->w,bar_loc.y};
|
|
SDL_Rect bot = {0,bar_loc.y+skip_rows,surf->w,0};
|
|
bot.h = surf->w - bot.y;
|
|
|
|
video().blit_surface(xpos,ypos,surf,&top);
|
|
video().blit_surface(xpos,ypos+top.h,surf,&bot);
|
|
|
|
const size_t unfilled = static_cast<const size_t>(height*(1.0 - filled));
|
|
|
|
if(unfilled < height && alpha >= ftofxp(0.3)) {
|
|
SDL_Rect filled_area = {xpos+bar_loc.x,ypos+bar_loc.y+unfilled,bar_loc.w,height-unfilled};
|
|
const Uint32 colour = SDL_MapRGB(video().getSurface()->format,col.r,col.g,col.b);
|
|
const Uint8 r_alpha = minimum<unsigned>(unsigned(fxpmult(alpha,255)),255);
|
|
fill_rect_alpha(filled_area,colour,r_alpha,video().getSurface());
|
|
}
|
|
}
|
|
|
|
void game_display::draw_movement_info(const gamemap::location& loc)
|
|
{
|
|
// Search if there is a turn waypoint here
|
|
std::map<gamemap::location, int>::iterator turn_waypoint_iter = route_.turn_waypoints.find(loc);
|
|
|
|
// Don't use empty route or the first step (the unit will be there)
|
|
if(turn_waypoint_iter != route_.turn_waypoints.end()
|
|
&& !route_.steps.empty() && route_.steps.front() != loc) {
|
|
const unit_map::const_iterator un = units_.find(route_.steps.front());
|
|
if(un != units_.end()) {
|
|
// Display the def% of this terrain
|
|
const int def = 100 - un->second.defense_modifier(map_.get_terrain(loc));
|
|
std::stringstream def_text;
|
|
def_text << def << "%";
|
|
|
|
// With 11 colors, the last one will be used only for def=100
|
|
int val = (game_config::defense_color_scale.size()-1) * def/100;
|
|
SDL_Color color = int_to_color(game_config::defense_color_scale[val]);
|
|
|
|
draw_text_in_hex(loc, def_text.str(), 18, color);
|
|
|
|
// Display the number of turn to reach only if > 0
|
|
int turns_to_reach = turn_waypoint_iter->second;
|
|
if(turns_to_reach > 0 && turns_to_reach < 10) {
|
|
std::stringstream turns_text;
|
|
turns_text << "(" << turns_to_reach << ")";
|
|
const SDL_Color turns_color = font::NORMAL_COLOUR;
|
|
draw_text_in_hex(loc, turns_text.str(), 16, turns_color, 0.5, 0.8);
|
|
}
|
|
|
|
// The hex is full now, so skip the "show enemy moves"
|
|
return;
|
|
}
|
|
}
|
|
|
|
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, num, 16, font::YELLOW_COLOUR);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<surface> game_display::footsteps_images(const gamemap::location& loc)
|
|
{
|
|
std::vector<surface> res;
|
|
|
|
if (route_.steps.size() < 2) {
|
|
return res; // no real "route"
|
|
}
|
|
|
|
std::vector<gamemap::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->second.movement_cost(map_.get_terrain(loc));
|
|
}
|
|
int image_number = minimum<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];
|
|
|
|
// 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++) {
|
|
std::string rotate;
|
|
|
|
// In function of the half, use the incoming or outgoing direction
|
|
gamemap::location::DIRECTION dir = (i-1+h)->get_relative_dir(*(i+h));
|
|
if (dir > gamemap::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
|
|
+ (h==0 ? "-in-" : "-out-") + i->write_direction(dir)
|
|
+ ".png" + rotate;
|
|
|
|
res.push_back(image::get_image(image, image::UNMASKED));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
surface game_display::get_flag(const gamemap::location& loc)
|
|
{
|
|
t_translation::t_letter terrain = map_.get_terrain(loc);
|
|
|
|
if(!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();
|
|
image::locator image_flag = preferences::animate_map() ?
|
|
flags_[i].get_current_frame() : flags_[i].get_first_frame();
|
|
return image::get_image(image_flag, image::SCALED_TO_HEX);
|
|
}
|
|
}
|
|
|
|
return surface(NULL);
|
|
}
|
|
|
|
void game_display::highlight_reach(const paths &paths_list)
|
|
{
|
|
unhighlight_reach();
|
|
highlight_another_reach(paths_list);
|
|
}
|
|
|
|
void game_display::highlight_another_reach(const paths &paths_list)
|
|
{
|
|
paths::routes_map::const_iterator r;
|
|
|
|
// Fold endpoints of routes into reachability map.
|
|
for (r = paths_list.routes.begin(); r != paths_list.routes.end(); ++r) {
|
|
reach_map_[r->first]++;
|
|
}
|
|
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_;
|
|
gamemap::location topleft;
|
|
gamemap::location bottomright;
|
|
get_visible_hex_bounds(topleft, bottomright);
|
|
for(int x = topleft.x; x <= bottomright.x; ++x) {
|
|
for(int y = topleft.y; y <= bottomright.y; ++y) {
|
|
gamemap::location loc(x, y);
|
|
reach_map::iterator reach = full.find(loc);
|
|
if (reach == full.end()) {
|
|
// Location needs to be darkened or brightened
|
|
invalidate(loc);
|
|
} else if (reach->second != 1) {
|
|
// Number needs to be displayed or cleared
|
|
invalidate(loc);
|
|
}
|
|
}
|
|
}
|
|
} 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<gamemap::location>::const_iterator i = route_.steps.begin();
|
|
i != route_.steps.end(); ++i) {
|
|
invalidate(*i);
|
|
}
|
|
}
|
|
|
|
void game_display::set_route(const paths::route* route)
|
|
{
|
|
invalidate_route();
|
|
|
|
if(route != NULL) {
|
|
route_ = *route;
|
|
} else {
|
|
route_.steps.clear();
|
|
route_.turn_waypoints.clear();
|
|
}
|
|
|
|
invalidate_route();
|
|
}
|
|
|
|
void game_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 game_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)) {
|
|
return;
|
|
}
|
|
|
|
const SDL_Color color = {red,green,blue,255};
|
|
int lifetime = static_cast<int>(60/turbo_speed());
|
|
font::add_floating_label(text,font::SIZE_XLARGE,color,get_location_x(loc)+zoom_/2,get_location_y(loc),
|
|
0,-2*turbo_speed(),lifetime,screen_area(),font::CENTER_ALIGN,NULL,0,font::ANCHOR_LABEL_MAP);
|
|
}
|
|
|
|
struct is_energy_colour {
|
|
bool operator()(Uint32 colour) const { return (colour&0xFF000000) < 0x50000000 &&
|
|
(colour&0x00FF0000) > 0x00990000 &&
|
|
(colour&0x0000FF00) > 0x00009900 &&
|
|
(colour&0x000000FF) > 0x00000099; }
|
|
};
|
|
|
|
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));
|
|
|
|
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_rects_.insert(std::pair<surface,SDL_Rect>(surf,res));
|
|
return calculate_energy_bar(surf);
|
|
}
|
|
|
|
void game_display::invalidate(const gamemap::location& loc)
|
|
{
|
|
if(!invalidateAll_) {
|
|
if (invalidated_.insert(loc).second) {
|
|
// Units can overlap adjacent tiles.
|
|
unit_map::iterator u = units_.find(loc);
|
|
|
|
if (u != units_.end()) {
|
|
std::set<gamemap::location> overlaps = u->second.overlaps(u->first);
|
|
for (std::set<gamemap::location>::iterator i = overlaps.begin(); i != overlaps.end(); i++) {
|
|
invalidate(*i);
|
|
}
|
|
}
|
|
if (temp_unit_ && temp_unit_loc_ == loc ) {
|
|
std::set<gamemap::location> overlaps = temp_unit_->overlaps(temp_unit_loc_);
|
|
for (std::set<gamemap::location>::iterator i = overlaps.begin(); i != overlaps.end(); i++) {
|
|
invalidate(*i);
|
|
}
|
|
}
|
|
// If neighbour has a unit which overlaps us, invalidate him
|
|
gamemap::location adjacent[6];
|
|
get_adjacent_tiles(loc, adjacent);
|
|
for (unsigned int i = 0; i < 6; i++) {
|
|
u = units_.find(adjacent[i]);
|
|
if (u != units_.end()) {
|
|
std::set<gamemap::location> overlaps = u->second.overlaps(u->first);
|
|
if (overlaps.find(loc) != overlaps.end()) {
|
|
invalidate(u->first);
|
|
}
|
|
}
|
|
if (temp_unit_ && temp_unit_loc_ == adjacent[i] ) {
|
|
std::set<gamemap::location> overlaps = temp_unit_->overlaps(temp_unit_loc_);
|
|
if (overlaps.find(loc) != overlaps.end()) {
|
|
invalidate(temp_unit_loc_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void game_display::invalidate_animations()
|
|
{
|
|
new_animation_frame();
|
|
|
|
unit_map::iterator unit;
|
|
for(unit=units_.begin() ; unit != units_.end() ; unit++) {
|
|
if (unit->second.get_animation() && unit->second.get_animation()->need_update())
|
|
invalidate(unit->first);
|
|
unit->second.refresh(*this,unit->first);
|
|
}
|
|
if (temp_unit_ ) {
|
|
if (temp_unit_->get_animation() && temp_unit_->get_animation()->need_update())
|
|
invalidate(temp_unit_loc_);
|
|
temp_unit_->refresh(*this, temp_unit_loc_);
|
|
}
|
|
|
|
if (!preferences::animate_map()) {return;}
|
|
|
|
gamemap::location topleft;
|
|
gamemap::location bottomright;
|
|
get_visible_hex_bounds(topleft, bottomright);
|
|
|
|
for(int x = topleft.x; x <= bottomright.x; ++x) {
|
|
for(int y = topleft.y; y <= bottomright.y; ++y) {
|
|
const gamemap::location loc(x,y);
|
|
if (!shrouded(loc)) {
|
|
if (builder_.update_animation(loc)) {
|
|
invalidate(loc);
|
|
} else if (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::debug_highlight(const gamemap::location& loc, fixed_t amount)
|
|
{
|
|
wassert(game_config::debug);
|
|
debugHighlights_[loc] += amount;
|
|
}
|
|
|
|
void game_display::place_temporary_unit(unit &u, const gamemap::location& loc)
|
|
{
|
|
temp_unit_ = &u;
|
|
temp_unit_loc_ = loc;
|
|
invalidate(loc);
|
|
}
|
|
|
|
void game_display::remove_temporary_unit()
|
|
{
|
|
if(!temp_unit_) return;
|
|
|
|
invalidate(temp_unit_loc_);
|
|
// Redraw with no location to get rid of haloes
|
|
temp_unit_->clear_haloes();
|
|
temp_unit_ = NULL;
|
|
}
|
|
|
|
void game_display::set_attack_indicator(const gamemap::location& src, const gamemap::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(gamemap::location::null_location, gamemap::location::null_location);
|
|
}
|
|
|
|
void game_display::add_overlay(const gamemap::location& loc, const std::string& img, const std::string& halo)
|
|
{
|
|
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);
|
|
overlays_.insert(overlay_map::value_type(loc,item));
|
|
}
|
|
|
|
void game_display::remove_overlay(const gamemap::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::write_overlays(config& cfg) const
|
|
{
|
|
for(overlay_map::const_iterator i = overlays_.begin(); i != overlays_.end(); ++i) {
|
|
config& item = cfg.add_child("item");
|
|
i->first.write(item);
|
|
item["image"] = i->second.image;
|
|
item["halo"] = i->second.halo;
|
|
}
|
|
}
|
|
|
|
const std::string game_display::current_team_name() const
|
|
{
|
|
if (team_valid())
|
|
{
|
|
return teams_[currentTeam_].team_name();
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
void game_display::set_team(size_t teamindex, bool observe)
|
|
{
|
|
wassert(teamindex < teams_.size());
|
|
currentTeam_ = teamindex;
|
|
if (!observe)
|
|
{
|
|
labels().set_team(&teams_[teamindex]);
|
|
viewpoint_ = &teams_[teamindex];
|
|
}
|
|
else
|
|
{
|
|
labels().set_team(0);
|
|
viewpoint_ = NULL;
|
|
}
|
|
labels().recalculate_labels();
|
|
}
|
|
|
|
void game_display::set_playing_team(size_t teamindex)
|
|
{
|
|
wassert(teamindex < teams_.size());
|
|
activeTeam_ = teamindex;
|
|
invalidate_game_status();
|
|
}
|
|
|
|
|
|
//! timestring() returns the current date as a string.
|
|
//! Uses preferences::clock_format() for formatting.
|
|
static std::string timestring ()
|
|
{
|
|
time_t now = time (NULL);
|
|
struct tm *lt = localtime(&now);
|
|
char buf[10];
|
|
strftime(buf,sizeof(buf),preferences::clock_format().c_str(),lt);
|
|
|
|
return buf;
|
|
}
|
|
|
|
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_colour = {255,255,255,255};
|
|
const SDL_Color chat_message_bg = {0,0,0,140};
|
|
}
|
|
|
|
void game_display::add_chat_message(const std::string& speaker, int side, const std::string& message, game_display::MESSAGE_TYPE type, bool bell)
|
|
{
|
|
config* cignore;
|
|
bool ignored = false;
|
|
if ((cignore = preferences::get_prefs()->child("relationship"))){
|
|
for(std::map<std::string,t_string>::const_iterator i = cignore->values.begin();
|
|
i != cignore->values.end(); ++i){
|
|
if(speaker == i->first || speaker == "whisper: " + i->first){
|
|
if (i->second == "ignored"){
|
|
ignored = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ignored){
|
|
bool action;
|
|
std::string msg;
|
|
|
|
if (bell) {
|
|
sound::play_UI_sound(game_config::sounds::receive_message);
|
|
}
|
|
|
|
if(message.find("/me ") == 0) {
|
|
msg.assign(message,4,message.size());
|
|
action = true;
|
|
} else {
|
|
msg = message;
|
|
action = false;
|
|
}
|
|
msg = font::word_wrap_text(msg,font::SIZE_SMALL,map_outside_area().w*3/4);
|
|
|
|
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;
|
|
}
|
|
SDL_Color speaker_colour = {255,255,255,255};
|
|
if(side >= 1) {
|
|
speaker_colour = int_to_color(team::get_side_color_range(side).mid());
|
|
}
|
|
|
|
SDL_Color message_colour = chat_message_colour;
|
|
std::stringstream str;
|
|
std::stringstream message_str;
|
|
if(type == MESSAGE_PUBLIC) {
|
|
if(action) {
|
|
str << "<" << speaker << " " << msg << ">";
|
|
message_colour = speaker_colour;
|
|
message_str << " ";
|
|
} else {
|
|
str << "<" << speaker << ">";
|
|
message_str << msg;
|
|
}
|
|
} else {
|
|
if(action) {
|
|
str << "*" << speaker << " " << msg << "*";
|
|
message_colour = speaker_colour;
|
|
message_str << " ";
|
|
} else {
|
|
str << "*" << speaker << "*";
|
|
message_str << msg;
|
|
}
|
|
}
|
|
|
|
// prepend message with timestamp
|
|
std::stringstream message_complete;
|
|
if (preferences::chat_timestamp()) {
|
|
message_complete << timestring() << " ";
|
|
}
|
|
message_complete << str.str();
|
|
|
|
const SDL_Rect rect = map_outside_area();
|
|
const int speaker_handle = font::add_floating_label(message_complete.str(),font::SIZE_SMALL,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(message_str.str(),font::SIZE_SMALL,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 game_display::prune_chat_messages(bool remove_all)
|
|
{
|
|
const unsigned int message_ttl = remove_all ? 0 : 1200000;
|
|
const unsigned int max_chat_messages = preferences::chat_lines();
|
|
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);
|
|
}
|
|
}
|
|
|
|
game_display *game_display::singleton_ = NULL;
|
|
|