wesnoth/src/display.hpp
2018-08-16 15:06:00 +03:00

964 lines
25 KiB
C++

/*
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
*
* map_display and display: classes which take care of
* displaying the map and game-data on the screen.
*
* The display is divided into two main sections:
* - the game area, which displays the tiles of the game board, and units on them,
* - and the side bar, which appears on the right hand side.
* The side bar display is divided into three sections:
* - the minimap, which is displayed at the top right
* - the game status, which includes the day/night image,
* the turn number, information about the current side,
* and information about the hex currently moused over (highlighted)
* - the unit status, which displays an image and stats
* for the current unit.
*/
#pragma once
class config;
class fake_unit_manager;
class terrain_builder;
class map_labels;
class arrow;
class reports;
class team;
struct overlay;
namespace halo
{
class manager;
}
namespace wb
{
class manager;
}
#include "animated.hpp"
#include "display_context.hpp"
#include "drawing_queue.hpp"
#include "events.hpp"
#include "font/standard_colors.hpp"
#include "game_config.hpp"
#include "picture.hpp" //only needed for enums (!)
#include "key.hpp"
#include "map/hex_rect.hpp"
#include "sdl/rect.hpp"
#include "sdl/surface.hpp"
#include "sdl/texture.hpp"
#include "theme.hpp"
#include "time_of_day.hpp"
#include "utils/functional.hpp"
#include "video.hpp"
#include <SDL_rect.h>
#include <boost/circular_buffer.hpp>
#include <chrono>
#include <cstdint>
#include <deque>
#include <list>
#include <map>
#include <memory>
class gamemap;
class display : public events::sdl_handler
{
public:
display(const display_context* dc,
std::weak_ptr<wb::manager> wb,
const config& theme_cfg,
const config& level,
bool auto_join = true);
virtual ~display();
/**
* Returns the display object if a display object exists. Otherwise it returns nullptr.
* Rhe display object represents the game gui which handles ThemeWML and drawing the map.
* A display object only exists during a game or while the map editor is running.
*/
static display* get_singleton()
{
return singleton_;
}
bool show_everything() const
{
return !dont_show_all_ && !is_blindfolded();
}
const gamemap& get_map() const
{
return dc_->map();
}
const std::vector<team>& get_teams() const
{
return dc_->teams();
}
/** The playing team is the team whose turn it is. */
std::size_t playing_team() const
{
return activeTeam_;
}
bool team_valid() const;
/** The viewing team is the team currently viewing the game. */
std::size_t viewing_team() const
{
return currentTeam_;
}
int viewing_side() const
{
return currentTeam_ + 1;
}
/**
* Sets the team controlled by the player using the computer.
* Data from this team will be displayed in the game status.
*/
void set_team(std::size_t team, bool observe = false);
/**
* set_playing_team sets the team whose turn it currently is
*/
void set_playing_team(std::size_t team);
/**
* Cancels all the exclusive draw requests.
*/
void clear_exclusive_draws()
{
exclusive_unit_draw_requests_.clear();
}
const unit_map& get_units() const
{
return dc_->units();
}
/**
* Allows a unit to request to be the only one drawn in its hex. Useful for situations where
* multiple units (one real, multiple temporary) can end up stacked, such as with the whiteboard.
* @param loc The location of the unit requesting exclusivity.
* @param unit The unit requesting exclusivity.
* @return false if there's already an exclusive draw request for this location.
*/
bool add_exclusive_draw(const map_location& loc, unit& unit);
/**
* Cancels an exclusive draw request.
* @return The id of the unit whose exclusive draw request was canceled, or else
* the empty string if there was no exclusive draw request for this location.
*/
std::string remove_exclusive_draw(const map_location& loc);
/**
* Functions to add and remove overlays from locations.
*
* An overlay is an image that is displayed on top of the tile.
* One tile may have multiple overlays.
*/
void add_overlay(const map_location& loc,
const std::string& image,
const std::string& halo = "",
const std::string& team_name = "",
const std::string& item_id = "",
bool visible_under_fog = true);
/** remove_overlay will remove all overlays on a tile. */
void remove_overlay(const map_location& loc);
/** remove_single_overlay will remove a single overlay from a tile */
void remove_single_overlay(const map_location& loc, const std::string& toDelete);
/**
* Updates internals that cache map size. This should be called when the map
* size has changed.
*/
void reload_map();
void change_display_context(const display_context* dc);
const display_context& current_display_context() const
{
return *dc_;
}
void reset_halo_manager();
void reset_halo_manager(halo::manager& hm);
halo::manager& get_halo_manager()
{
return *halo_man_;
}
/**
* Applies r,g,b coloring to the map.
*
* The color is usually taken from @ref get_time_of_day unless @a tod_override is given, in which
* case that color is used.
*
* @param tod_override The ToD to apply to the map instead of that of the current ToD's.
*/
void update_tod(const time_of_day* tod_override = nullptr);
/**
* Add r,g,b to the colors for all images displayed on the map.
*
* Used for special effects like flashes.
*/
void adjust_color_overlay(int r, int g, int b);
/** Gets the underlying screen object. */
CVideo& video()
{
return video_;
}
virtual bool in_game() const
{
return false;
}
virtual bool in_editor() const
{
return false;
}
/** Virtual functions shadowed in game_display. These are needed to generate reports easily, without dynamic
* casting. Hope to factor out eventually. */
virtual const map_location& displayed_unit_hex() const
{
return map_location::null_location();
}
/** In this case give an obviously wrong answer to fail fast, since this could actually cause a big bug. */
virtual int playing_side() const
{
return -100;
}
virtual const std::set<std::string>& observers() const
{
static const std::set<std::string> fake_obs = std::set<std::string>();
return fake_obs;
}
/**
* mapx is the width of the portion of the display which shows the game area.
* Between mapx and x is the sidebar region.
*/
const SDL_Rect& minimap_area() const
{
return theme_.mini_map_location(video_.screen_area());
}
const SDL_Rect& palette_area() const
{
return theme_.palette_location(video_.screen_area());
}
const SDL_Rect& unit_image_area() const
{
return theme_.unit_image_location(video_.screen_area());
}
/** Returns the maximum area used for the map regardless to resolution and view size */
const SDL_Rect& max_map_area() const;
/** Returns the area used for the map */
const SDL_Rect& map_area() const;
/**
* Returns the available area for a map, this may differ from the above.
* This area will get the background area applied to it.
*/
const SDL_Rect map_outside_area() const;
/** Check if the bbox of the hex at x,y has pixels outside the area rectangle. */
static bool outside_area(const SDL_Rect& area, const int x, const int y);
/**
* Function which returns the width of a hex in pixels,
* up to where the next hex starts.
* (i.e. not entirely from tip to tip -- use hex_size()
* to get the distance from tip to tip)
*/
static int hex_width()
{
return (zoom_ * 3) / 4;
}
/**
* Function which returns the size of a hex in pixels
* (from top tip to bottom tip or left edge to right edge).
*/
static int hex_size()
{
return zoom_;
}
/** Returns the current zoom factor. */
double get_zoom_factor() const
{
return static_cast<double>(zoom_) / static_cast<double>(game_config::tile_size);
}
/**
* given x,y co-ordinates of an onscreen pixel, will return the
* location of the hex that this pixel corresponds to.
* Returns an invalid location if the mouse isn't over any valid location.
*/
const map_location hex_clicked_on(int x, int y) const;
/**
* given x,y co-ordinates of a pixel on the map, will return the
* location of the hex that this pixel corresponds to.
* Returns an invalid location if the mouse isn't over any valid location.
*/
const map_location pixel_position_to_hex(int x, int y) const;
/**
* given x,y co-ordinates of the mouse, will return the location of the
* hex in the minimap that the mouse is currently over, or an invalid
* location if the mouse isn't over the minimap.
*/
map_location minimap_location_on(int x, int y);
const map_location& selected_hex() const
{
return selectedHex_;
}
const map_location& mouseover_hex() const
{
return mouseoverHex_;
}
virtual void select_hex(map_location hex);
virtual void highlight_hex(map_location hex);
/** Functions to get the on-screen positions of hexes. */
int get_location_x(const map_location& loc) const;
int get_location_y(const map_location& loc) const;
/**
* Wrapper to return the drawing origin for the specified location in screen coordinates.
* Combines @ref get_location_x and @ref get_location_y.
*
* @param loc The map location to look up.
*/
SDL_Point get_loc_drawing_origin(const map_location& loc) const;
/** Return the rectangular area of hexes overlapped by r (r is in screen coordinates) */
const rect_of_hexes hexes_under_rect(const SDL_Rect& r) const;
/** Returns the rectangular area of visible hexes */
const rect_of_hexes get_visible_hexes() const;
/** Returns true if location (x,y) is covered in shroud. */
bool shrouded(const map_location& loc) const;
/** Returns true if location (x,y) is covered in fog. */
bool fogged(const map_location& loc) const;
/**
* Determines whether a grid should be overlayed on the game board.
* (to more clearly show where hexes are)
*/
void set_grid(const bool grid)
{
grid_ = grid;
}
/** Getter for the x,y debug overlay on tiles */
bool get_draw_coordinates() const
{
return draw_coordinates_;
}
/** Setter for the x,y debug overlay on tiles */
void set_draw_coordinates(bool value)
{
draw_coordinates_ = value;
}
/** Getter for the terrain code debug overlay on tiles */
bool get_draw_terrain_codes() const
{
return draw_terrain_codes_;
}
/** Setter for the terrain code debug overlay on tiles */
void set_draw_terrain_codes(bool value)
{
draw_terrain_codes_ = value;
}
/** Getter for the number of bitmaps debug overlay on tiles */
bool get_draw_num_of_bitmaps() const
{
return draw_num_of_bitmaps_;
}
/** Setter for the terrain code debug overlay on tiles */
void set_draw_num_of_bitmaps(bool value)
{
draw_num_of_bitmaps_ = value;
}
/** Capture a (map-)screenshot into a surface. */
surface screenshot(bool map_screenshot = false);
theme& get_theme()
{
return theme_;
}
void set_theme(config theme_cfg);
/**
* Function to invalidate animated terrains and units which may have changed.
*/
void invalidate_animations();
void reset_standing_animations();
/**
* mouseover_hex_overlay_ require a prerendered surface
* and is drawn underneath the mouse's location
*/
void set_mouseover_hex_overlay(const surface& image)
{
mouseover_hex_overlay_ = image;
}
void clear_mouseover_hex_overlay()
{
mouseover_hex_overlay_ = nullptr;
}
/** Toggle to continuously redraw the screen. */
static void toggle_benchmark();
/**
* Toggle to debug foreground terrain.
* Separate background and foreground layer
* to better spot any error there.
*/
static void toggle_debug_foreground();
terrain_builder& get_builder()
{
return *builder_;
}
/** Rebuild all dynamic terrain. */
void rebuild_all();
void set_diagnostic(const std::string& msg);
/**
* Set/Get whether 'turbo' mode is on.
* When turbo mode is on, everything moves much faster.
*/
void set_turbo(const bool turbo)
{
turbo_ = turbo;
}
double turbo_speed() const;
void set_turbo_speed(const double speed)
{
turbo_speed_ = speed;
}
/** control unit idle animations and their frequency */
void set_idle_anim(bool ison)
{
idle_anim_ = ison;
}
bool idle_anim() const
{
return idle_anim_;
}
void set_idle_anim_rate(int rate);
double idle_anim_rate() const
{
return idle_anim_rate_;
}
void bounds_check_position();
void bounds_check_position(int& xpos, int& ypos) const;
/**
* Scrolls the display by xmov,ymov pixels.
* Invalidation and redrawing will be scheduled.
* @return true if the map actually moved.
*/
bool scroll(int xmov, int ymov, bool force = false);
/** Zooms the display in (true) or out (false). */
bool set_zoom(bool increase);
/** Sets the display zoom to the specified amount. */
bool set_zoom(unsigned int amount, const bool validate_value_and_set_index = true);
static bool zoom_at_max();
static bool zoom_at_min();
/** Sets the zoom amount to the default. */
void set_default_zoom();
bool view_locked() const
{
return view_locked_;
}
/** Sets whether the map view is locked (e.g. so the user can't scroll away) */
void set_view_locked(bool value)
{
view_locked_ = value;
}
enum SCROLL_TYPE { SCROLL, WARP, ONSCREEN, ONSCREEN_WARP };
/**
* Scroll such that location loc is on-screen.
* WARP jumps to loc; SCROLL uses scroll speed;
* ONSCREEN only scrolls if x,y is offscreen
* force : scroll even if preferences tell us not to,
* or the view is locked.
*/
void scroll_to_tile(
const map_location& loc, SCROLL_TYPE scroll_type = ONSCREEN, bool check_fogged = true, bool force = true);
/**
* Scroll such that location loc1 is on-screen.
* It will also try to make it such that loc2 is on-screen,
* but this is not guaranteed. For ONSCREEN scrolls add_spacing
* sets the desired minimum distance from the border in hexes.
*/
void scroll_to_tiles(map_location loc1,
map_location loc2,
SCROLL_TYPE scroll_type = ONSCREEN,
bool check_fogged = true,
double add_spacing = 0.0,
bool force = true);
/** Scroll to fit as many locations on-screen as possible, starting with the first. */
void scroll_to_tiles(const std::vector<map_location>::const_iterator& begin,
const std::vector<map_location>::const_iterator& end,
SCROLL_TYPE scroll_type = ONSCREEN,
bool check_fogged = true,
bool only_if_possible = false,
double add_spacing = 0.0,
bool force = true);
/** Scroll to fit as many locations on-screen as possible, starting with the first. */
void scroll_to_tiles(const std::vector<map_location>& locs,
SCROLL_TYPE scroll_type = ONSCREEN,
bool check_fogged = true,
bool only_if_possible = false,
double add_spacing = 0.0,
bool force = true)
{
scroll_to_tiles(locs.begin(), locs.end(), scroll_type, check_fogged, only_if_possible, add_spacing, force);
}
/** Expose the event, so observers can be notified about map scrolling. */
events::generic_event& scroll_event() const
{
return scroll_event_;
}
/** Check if a tile is fully visible on screen. */
bool tile_fully_on_screen(const map_location& loc) const;
/** Checks if location @a loc or one of the adjacent tiles is visible on screen. */
bool tile_nearly_on_screen(const map_location& loc) const;
map_labels& labels();
const map_labels& labels() const;
/** Holds options for calls to function 'announce' (@ref announce). */
struct announce_options
{
/** Lifetime measured in frames. */
int lifetime;
/**
* An announcement according these options should replace the
* previous announce (typical of fast announcing) or not
* (typical of movement feedback).
*/
bool discard_previous;
announce_options()
: lifetime(100)
, discard_previous(false)
{
}
};
/** Announce a message prominently. */
void announce(const std::string& msg,
const color_t& color = font::GOOD_COLOR,
const announce_options& options = announce_options());
virtual const time_of_day& get_time_of_day(const map_location& loc = map_location::null_location()) const;
virtual bool has_time_area() const
{
return false;
}
void blindfold(bool flag);
bool is_blindfolded() const;
void write(config& cfg) const;
virtual void handle_event(const SDL_Event&) override;
virtual void handle_window_event(const SDL_Event& event) override;
private:
void read(const config& cfg);
public:
/** Initialize the flag list for all sides. */
void init_flags();
/** Initialize the flag list for a single side. */
void init_flags(std::size_t side_index);
private:
int blindfold_ctr_;
protected:
// TODO sort
const display_context* dc_;
std::unique_ptr<halo::manager> halo_man_;
std::weak_ptr<wb::manager> wb_;
// =====================================================================================
// DRAWING CODE
// =====================================================================================
/**
* Main drawing function.
*
* This handles drawing everything in this class and is called each draw cycle.
* It also calls the various protected virtual functions declared below to allow specialized
* behavior in derived classes.
*
* Inherited from events::sdl_handler.
*/
virtual void draw() override final;
/**
* Called at the beginning of each draw cycle.
* Derived classes can use this to add extra actions before any drawing takes place.
* No action here by default.
*/
virtual void pre_draw()
{
}
/**
* Called at the end of each draw cycle.
* Derived classes can use this to add extra actions after all drawing takes place.
* No action here by default.
*/
virtual void post_draw()
{
}
private:
enum TERRAIN_TYPE { FOREGROUND, BACKGROUND };
/** Draws the background behind the gamemap. */
void draw_background();
/** Draws the visible map hex terrains. Used by @ref draw_gamemap. */
void draw_visible_hexes(const rect_of_hexes& visible_hexes, TERRAIN_TYPE layer);
/** Draws the gamemap itself and its various components, such as units, items, fog/shroud, etc. */
void draw_gamemap();
protected:
/** Draws the map's hex cursor. No action here by default. */
virtual void draw_hex_cursor(const map_location& /*loc*/)
{
}
/** Draws various map overlays such as game reachmap. */
virtual void draw_hex_overlays();
public:
void draw_debugging_aids();
/**
* Renders a texture directly to the screen (or current rendering target) scaled to the
* current zoom factor.
*
* @param tex The texture to render.
* @param x_pos The x coordinate to render at.
* @param y_pos The y coordinate to render at.
* @param extra_args Any additional arguments to pass to @ref CVideo::render_copy.
* This should not contain the texture or source/destination rects.
*/
template<typename... T>
void render_scaled_to_zoom(const texture& tex, const int x_pos, const int y_pos, T&&... extra_args) const
{
if(tex.null()) {
return;
}
texture::info info = tex.get_info();
// Scale the coordinates to the appropriate zoom factor.
const double zoom_factor = get_zoom_factor();
SDL_Rect dst {
x_pos,
y_pos,
static_cast<int>(info.w * zoom_factor),
static_cast<int>(info.h * zoom_factor),
};
video_.render_copy(tex, nullptr, &dst, std::forward<T>(extra_args)...);
}
/**
* Renders a texture directly to the screen (or current rendering target) scaled to the
* current zoom factor.
*
* @param tex The texture to render.
* @param loc The map location to render at.
* @param extra_args Any additional arguments to pass to @ref CVideo::render_copy.
* This should not contain the texture or source/destination rects.
*/
template<typename... T>
void render_scaled_to_zoom(const texture& tex, const map_location& loc, T&&... extra_args) const
{
if(tex.null()) {
return;
}
SDL_Point origin = get_loc_drawing_origin(loc);
render_scaled_to_zoom(tex, origin.x, origin.y, std::forward<T>(extra_args)...);
}
map_location get_middle_location() const;
/**
* Adds a floating label with the specified text at the given location.
*
* @param loc The map location to draw the label.
* @param text The label's text.
* @param font_size The label's font size. Will be adjusted by the zoom factor.
* @param color The label's color.
* @param fl_label_id The label's existing id. If not 0 the given label will be moved as
* the label with that id will be moved to @a loc.
* @param x_in_hex The relative x location within the hex to draw the label.
* @param y_in_hex The relative y location within the hex to draw the label.
* Note that (0.5, 0.5) indicates the center of the hex.
*
* @returns The new floating label's id.
*/
int draw_text_in_hex(const map_location& loc,
const std::string& text,
std::size_t font_size,
color_t color,
int fl_label_id = 0,
double x_in_hex = 0.5,
double y_in_hex = 0.5);
protected:
/** @returns the image type to be used for the passed hex */
virtual image::TYPE get_image_type(const map_location& loc);
/**
* Get the clipping rectangle for drawing.
* Virtual since the editor might use a slightly different approach.
*/
virtual const SDL_Rect get_clip_rect();
/** Draw the appropriate fog or shroud transition images for a specific hex. */
void draw_fog_shroud_transition_images(const map_location& loc, image::TYPE image_type);
void scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force = true);
static void fill_images_list(const std::string& prefix, std::vector<std::string>& images);
const std::string& get_variant(const std::vector<std::string>& variants, const map_location& loc) const;
using exclusive_unit_draw_requests_t = std::map<map_location, std::string>;
/// map of hexes where only one unit should be drawn, the one identified by the associated id string
exclusive_unit_draw_requests_t exclusive_unit_draw_requests_;
CVideo& video_;
std::size_t currentTeam_;
bool dont_show_all_; // const team *viewpoint_;
int xpos_, ypos_;
// camera_controller camera_controller_;
bool view_locked_;
theme theme_;
static unsigned int zoom_;
int zoom_index_;
static unsigned int last_zoom_;
const std::unique_ptr<fake_unit_manager> fake_unit_man_;
const std::unique_ptr<terrain_builder> builder_;
SDL_Rect minimap_location_;
bool grid_;
int diagnostic_label_;
double turbo_speed_;
bool turbo_;
const std::unique_ptr<map_labels> map_labels_;
/** Event raised when the map is being scrolled */
mutable events::generic_event scroll_event_;
boost::circular_buffer<unsigned> frametimes_; // in milliseconds
unsigned int fps_counter_;
std::chrono::seconds fps_start_;
unsigned int fps_actual_;
uint32_t last_frame_finished_ = 0u;
// Not set by the initializer:
surface mouseover_hex_overlay_;
// If we're transitioning from one time of day to the next,
// then we will use these two masks on top of all hexes when we blit.
surface tod_hex_mask1, tod_hex_mask2;
std::vector<std::string> fog_images_;
std::vector<std::string> shroud_images_;
map_location selectedHex_;
map_location mouseoverHex_;
CKey keys_;
/** Local cache for preferences::animate_map, since it is constantly queried. */
bool animate_map_;
/** Local version of preferences::animate_water, used to detect when it's changed. */
bool animate_water_;
private:
/** Animated flags for each team */
std::vector<animated<image::locator>> flags_;
protected:
// TODO sort
std::size_t activeTeam_;
/** Used to indicate to drawing functions that we are doing a map screenshot */
bool map_screenshot_;
public: // operations for the arrow framework
void add_arrow(arrow&);
void remove_arrow(arrow&);
/** Called by arrow objects when they change. You should not need to call this directly. */
void update_arrow(arrow& a);
protected:
// Tiles lit for showing where unit(s) can reach
typedef std::map<map_location, unsigned int> reach_map;
reach_map reach_map_;
typedef std::multimap<map_location, overlay> overlay_map;
virtual overlay_map& get_overlays() = 0;
private:
/** Handle for the label which displays frames per second. */
int fps_handle_;
/** Count work done for the debug info displayed under fps */
int drawn_hexes_;
bool idle_anim_;
double idle_anim_rate_;
/** Debug flag - overlay x,y coords on tiles */
bool draw_coordinates_;
/** Debug flag - overlay terrain codes on tiles */
bool draw_terrain_codes_;
/** Debug flag - overlay number of bitmaps on tiles */
bool draw_num_of_bitmaps_;
typedef std::list<arrow*> arrows_list_t;
typedef std::map<map_location, arrows_list_t> arrows_map_t;
/** Maps the list of arrows for each location */
arrows_map_t arrows_map_;
tod_color color_adjust_;
protected:
static display* singleton_;
};
struct blindfold
{
blindfold(display& d, bool lock = true)
: display_(d)
, blind(lock)
{
if(blind) {
display_.blindfold(true);
}
}
~blindfold()
{
unblind();
}
void unblind()
{
if(blind) {
display_.blindfold(false);
blind = false;
}
}
private:
display& display_;
bool blind;
};