mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-13 16:43:49 +00:00
761 lines
22 KiB
C++
761 lines
22 KiB
C++
/*
|
|
Copyright (C) 2003 - 2025
|
|
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
|
|
* During a game, show map & info-panels at top+right.
|
|
*/
|
|
|
|
#include "game_display.hpp"
|
|
|
|
#include <utility>
|
|
|
|
|
|
#include "cursor.hpp"
|
|
#include "display_chat_manager.hpp"
|
|
#include "fake_unit_manager.hpp"
|
|
#include "floating_label.hpp"
|
|
#include "game_board.hpp"
|
|
#include "preferences/preferences.hpp"
|
|
#include "log.hpp"
|
|
#include "map/map.hpp"
|
|
#include "font/standard_colors.hpp"
|
|
#include "reports.hpp"
|
|
#include "resources.hpp"
|
|
#include "tod_manager.hpp"
|
|
#include "color.hpp"
|
|
#include "synced_context.hpp"
|
|
#include "units/unit.hpp"
|
|
#include "units/drawer.hpp"
|
|
#include "utils/general.hpp"
|
|
#include "whiteboard/manager.hpp"
|
|
#include "overlay.hpp"
|
|
#include "draw.hpp"
|
|
|
|
static lg::log_domain log_display("display");
|
|
#define ERR_DP LOG_STREAM(err, log_display)
|
|
#define LOG_DP LOG_STREAM(info, log_display)
|
|
#define DBG_DP LOG_STREAM(debug, log_display)
|
|
|
|
static lg::log_domain log_engine("engine");
|
|
#define ERR_NG LOG_STREAM(err, log_engine)
|
|
|
|
|
|
game_display::game_display(game_board& board,
|
|
std::weak_ptr<wb::manager> wb,
|
|
reports& reports_object,
|
|
const std::string& theme_id,
|
|
const config& level)
|
|
: display(&board, std::move(wb), reports_object, theme_id, level)
|
|
, overlay_map_()
|
|
, attack_indicator_src_()
|
|
, attack_indicator_dst_()
|
|
, route_()
|
|
, displayedUnitHex_()
|
|
, first_turn_(true)
|
|
, in_game_(false)
|
|
, chat_man_(new display_chat_manager(*this))
|
|
, mode_(RUNNING)
|
|
, needs_rebuild_(false)
|
|
{
|
|
}
|
|
|
|
game_display::~game_display()
|
|
{
|
|
try {
|
|
chat_man_->prune_chat_messages(true);
|
|
} catch(...) {
|
|
DBG_DP << "Caught exception in game_display destructor: " << utils::get_unknown_exception_type();
|
|
}
|
|
}
|
|
|
|
void game_display::new_turn()
|
|
{
|
|
if(!first_turn_) {
|
|
const time_of_day& tod = resources::tod_manager->get_time_of_day();
|
|
const time_of_day& old_tod = resources::tod_manager->get_previous_time_of_day();
|
|
|
|
if(old_tod.image_mask != tod.image_mask) {
|
|
fade_tod_mask(old_tod.image_mask, tod.image_mask);
|
|
}
|
|
}
|
|
|
|
first_turn_ = false;
|
|
|
|
update_tod();
|
|
}
|
|
|
|
void game_display::select_hex(map_location hex)
|
|
{
|
|
if(hex.valid() && fogged(hex)) {
|
|
return;
|
|
}
|
|
display::select_hex(hex);
|
|
|
|
display_unit_hex(hex);
|
|
}
|
|
|
|
void game_display::highlight_hex(map_location hex)
|
|
{
|
|
wb::future_map_if future(!synced_context::is_synced()); /**< Lasts for whole method. */
|
|
|
|
const unit *u = context().get_visible_unit(hex, viewing_team(), !dont_show_all_);
|
|
if (u) {
|
|
displayedUnitHex_ = hex;
|
|
invalidate_unit();
|
|
} else {
|
|
u = context().get_visible_unit(mouseoverHex_, viewing_team(), !dont_show_all_);
|
|
if (u) {
|
|
// mouse moved from unit hex to non-unit hex
|
|
if (context().units().count(selectedHex_)) {
|
|
displayedUnitHex_ = selectedHex_;
|
|
invalidate_unit();
|
|
}
|
|
}
|
|
}
|
|
|
|
display::highlight_hex(hex);
|
|
invalidate_game_status();
|
|
}
|
|
|
|
|
|
void game_display::display_unit_hex(map_location hex)
|
|
{
|
|
if (!hex.valid())
|
|
return;
|
|
|
|
wb::future_map_if future(!synced_context::is_synced()); /**< Lasts for whole method. */
|
|
|
|
const unit *u = context().get_visible_unit(hex, viewing_team(), !dont_show_all_);
|
|
if (u) {
|
|
displayedUnitHex_ = hex;
|
|
invalidate_unit();
|
|
}
|
|
}
|
|
|
|
void game_display::invalidate_unit_after_move(const map_location& src, const map_location& dst)
|
|
{
|
|
if (src == displayedUnitHex_) {
|
|
displayedUnitHex_ = dst;
|
|
invalidate_unit();
|
|
}
|
|
}
|
|
|
|
void game_display::scroll_to_leader(int side, SCROLL_TYPE scroll_type,bool force)
|
|
{
|
|
unit_map::const_iterator leader = context().units().find_leader(side);
|
|
|
|
if(leader.valid() && leader->is_visible_to_team(viewing_team(), false)) {
|
|
scroll_to_tile(leader->get_location(), scroll_type, true, force);
|
|
}
|
|
}
|
|
|
|
void game_display::update()
|
|
{
|
|
display::update();
|
|
|
|
if (std::shared_ptr<wb::manager> w = wb_.lock()) {
|
|
w->pre_draw();
|
|
}
|
|
process_reachmap_changes();
|
|
/**
|
|
* @todo FIXME: must modify changed, but best to do it at the
|
|
* floating_label level
|
|
*/
|
|
chat_man_->prune_chat_messages();
|
|
}
|
|
|
|
|
|
void game_display::render()
|
|
{
|
|
display::render();
|
|
|
|
if (std::shared_ptr<wb::manager> w = wb_.lock()) {
|
|
w->post_draw();
|
|
}
|
|
}
|
|
|
|
void game_display::draw_invalidated()
|
|
{
|
|
display::draw_invalidated();
|
|
if (fake_unit_man_->empty()) {
|
|
return;
|
|
}
|
|
unit_drawer drawer = unit_drawer(*this);
|
|
|
|
for(const unit* temp_unit : *fake_unit_man_) {
|
|
const map_location& loc = temp_unit->get_location();
|
|
if(utils::contains(invalidated_, loc) && unit_can_draw_here(loc, *temp_unit)) {
|
|
drawer.redraw_unit(*temp_unit);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const std::string mouseover_normal_top = "misc/hover-hex-top.png~RC(magenta>gold)";
|
|
const std::string mouseover_normal_bot = "misc/hover-hex-bottom.png~RC(magenta>gold)";
|
|
|
|
const std::string mouseover_enemy_top = "misc/hover-hex-enemy-top.png~RC(magenta>red)";
|
|
const std::string mouseover_enemy_bot = "misc/hover-hex-enemy-bottom.png~RC(magenta>red)";
|
|
|
|
const std::string mouseover_self_top = "misc/hover-hex-top.png~RC(magenta>green)";
|
|
const std::string mouseover_self_bot = "misc/hover-hex-bottom.png~RC(magenta>green)";
|
|
|
|
const std::string mouseover_ally_top = "misc/hover-hex-top.png~RC(magenta>lightblue)";
|
|
const std::string mouseover_ally_bot = "misc/hover-hex-bottom.png~RC(magenta>lightblue)";
|
|
|
|
/**
|
|
* Function to return 2 half-hex footsteps images for the given location.
|
|
* Only loc is on the current route set by set_route.
|
|
*/
|
|
std::vector<texture> footsteps_images(const map_location& loc, const pathfind::marked_route& route, const display_context* dc)
|
|
{
|
|
std::vector<texture> res;
|
|
|
|
if (route.steps.size() < 2) {
|
|
return res; // no real "route"
|
|
}
|
|
|
|
std::vector<map_location>::const_iterator i =
|
|
std::find(route.steps.begin(),route.steps.end(),loc);
|
|
|
|
if( i == route.steps.end()) {
|
|
return res; // not on the route
|
|
}
|
|
|
|
// Check which footsteps images of game_config we will use
|
|
int move_cost = 1;
|
|
const unit_map::const_iterator u = dc->units().find(route.steps.front());
|
|
if(u != dc->units().end()) {
|
|
move_cost = u->movement_cost(dc->map().get_terrain(loc));
|
|
}
|
|
int image_number = std::min<int>(move_cost, game_config::foot_speed_prefix.size());
|
|
if (image_number < 1) {
|
|
return res; // Invalid movement cost or no images
|
|
}
|
|
const std::string foot_speed_prefix = game_config::foot_speed_prefix[image_number-1];
|
|
|
|
texture teleport;
|
|
|
|
// We draw 2 half-hex (with possibly different directions),
|
|
// but skip the first for the first step.
|
|
const int first_half = (i == route.steps.begin()) ? 1 : 0;
|
|
// and the second for the last step
|
|
const int second_half = (i+1 == route.steps.end()) ? 0 : 1;
|
|
|
|
for (int h = first_half; h <= second_half; ++h) {
|
|
const std::string sense( h==0 ? "-in" : "-out" );
|
|
|
|
if (!tiles_adjacent(*(i+(h-1)), *(i+h))) {
|
|
std::string teleport_image =
|
|
h==0 ? game_config::foot_teleport_enter : game_config::foot_teleport_exit;
|
|
teleport = image::get_texture(teleport_image, image::HEXED);
|
|
continue;
|
|
}
|
|
|
|
// In function of the half, use the incoming or outgoing direction
|
|
map_location::direction dir = (i+(h-1))->get_relative_dir(*(i+h));
|
|
|
|
std::string rotate;
|
|
if (dir > map_location::direction::south_east) {
|
|
// No image, take the opposite direction and do a 180 rotation
|
|
dir = i->get_opposite_direction(dir);
|
|
rotate = "~FL(horiz)~FL(vert)";
|
|
}
|
|
|
|
const std::string image = foot_speed_prefix
|
|
+ sense + "-" + i->write_direction(dir)
|
|
+ ".png" + rotate;
|
|
|
|
res.push_back(image::get_texture(image, image::HEXED));
|
|
}
|
|
|
|
// we draw teleport image (if any) in last
|
|
if (teleport != nullptr) res.push_back(teleport);
|
|
|
|
return res;
|
|
}
|
|
} //anonymous namespace
|
|
|
|
void game_display::draw_hex(const map_location& loc)
|
|
{
|
|
const bool on_map = context().map().on_board(loc);
|
|
const bool is_shrouded = shrouded(loc);
|
|
|
|
display::draw_hex(loc);
|
|
|
|
if(cursor::get() == cursor::WAIT) {
|
|
// Interaction is disabled, so we don't need anything else
|
|
return;
|
|
}
|
|
|
|
if(on_map && loc == mouseoverHex_ && !map_screenshot_) {
|
|
drawing_layer hex_top_layer = drawing_layer::mouseover_bottom;
|
|
const unit* u = context().get_visible_unit(loc, viewing_team());
|
|
if(u != nullptr) {
|
|
hex_top_layer = drawing_layer::mouseover_top;
|
|
}
|
|
|
|
const std::string* mo_top_path;
|
|
const std::string* mo_bot_path;
|
|
|
|
if(u == nullptr) {
|
|
mo_top_path = &mouseover_normal_top;
|
|
mo_bot_path = &mouseover_normal_bot;
|
|
} else if(viewing_team().is_enemy(u->side())) {
|
|
mo_top_path = &mouseover_enemy_top;
|
|
mo_bot_path = &mouseover_enemy_bot;
|
|
} else if(viewing_team().side() == u->side()) {
|
|
mo_top_path = &mouseover_self_top;
|
|
mo_bot_path = &mouseover_self_bot;
|
|
} else {
|
|
mo_top_path = &mouseover_ally_top;
|
|
mo_bot_path = &mouseover_ally_bot;
|
|
}
|
|
|
|
drawing_buffer_add(hex_top_layer, loc,
|
|
[tex = image::get_texture(*mo_top_path, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
|
|
|
|
drawing_buffer_add(drawing_layer::mouseover_bottom, loc,
|
|
[tex = image::get_texture(*mo_bot_path, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
|
|
}
|
|
|
|
// Draw reach_map information.
|
|
if(!is_shrouded && !reach_map_.empty() && reach_map_.find(loc) != reach_map_.end()) {
|
|
// draw the reachmap tint below units and high terrain graphics
|
|
std::string color = prefs::get().reach_map_color();
|
|
std::string tint_opacity = std::to_string(prefs::get().reach_map_tint_opacity());
|
|
|
|
drawing_buffer_add(drawing_layer::reachmap_highlight, loc, [tex = image::get_texture(game_config::reach_map_prefix + ".png~RC(magenta>"+color+")~O("+tint_opacity+"%)", image::HEXED)](const rect& dest) {
|
|
draw::blit(tex, dest);
|
|
});
|
|
// We remove the reachmap border mask of the hovered hex to avoid weird interactions with other visual objects.
|
|
if(loc != mouseoverHex_) {
|
|
// draw the highlight borders on top of units and terrain
|
|
drawing_buffer_add(drawing_layer::reachmap_border, loc, [images = get_reachmap_images(loc)](const rect& dest) {
|
|
for(const texture& t : images) {
|
|
draw::blit(t, dest);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if(std::shared_ptr<wb::manager> w = wb_.lock()) {
|
|
w->draw_hex(loc);
|
|
|
|
if(!(w->is_active() && w->has_temp_move())) {
|
|
std::vector<texture> footstepImages = footsteps_images(loc, route_, dc_);
|
|
if(!footstepImages.empty()) {
|
|
drawing_buffer_add(drawing_layer::footsteps, loc, [images = std::move(footstepImages)](const rect& dest) {
|
|
for(const texture& t : images) {
|
|
draw::blit(t, dest);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw the attack direction indicator
|
|
if(on_map && loc == attack_indicator_src_) {
|
|
drawing_buffer_add(drawing_layer::attack_indicator, loc,
|
|
[tex = image::get_texture("misc/attack-indicator-src-" + attack_indicator_direction() + ".png", image::HEXED)](const rect& dest)
|
|
{ draw::blit(tex, dest); }
|
|
);
|
|
} else if(on_map && loc == attack_indicator_dst_) {
|
|
drawing_buffer_add(drawing_layer::attack_indicator, loc,
|
|
[tex = image::get_texture("misc/attack-indicator-dst-" + attack_indicator_direction() + ".png", image::HEXED)](const rect& dest)
|
|
{ draw::blit(tex, dest); }
|
|
);
|
|
}
|
|
|
|
// Linger overlay unconditionally otherwise it might give glitches
|
|
// so it's drawn over the shroud and fog.
|
|
if(mode_ != RUNNING) {
|
|
static const image::locator linger(game_config::images::linger);
|
|
drawing_buffer_add(drawing_layer::linger_overlay, loc,
|
|
[tex = image::get_texture(linger, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
|
|
}
|
|
|
|
if(on_map && loc == selectedHex_ && !game_config::images::selected.empty()) {
|
|
static const image::locator selected(game_config::images::selected);
|
|
drawing_buffer_add(drawing_layer::selected_hex, loc,
|
|
[tex = image::get_texture(selected, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
|
|
}
|
|
|
|
// Show def% and turn to reach info
|
|
if(!is_shrouded && on_map) {
|
|
draw_movement_info(loc);
|
|
}
|
|
}
|
|
|
|
const time_of_day& game_display::get_time_of_day(const map_location& loc) const
|
|
{
|
|
return resources::tod_manager->get_time_of_day(loc);
|
|
}
|
|
|
|
bool game_display::has_time_area() const
|
|
{
|
|
return resources::tod_manager->has_time_area();
|
|
}
|
|
|
|
void game_display::layout()
|
|
{
|
|
display::layout();
|
|
|
|
// We need teams for the reports below
|
|
if(context().teams().empty()) {
|
|
return;
|
|
}
|
|
|
|
refresh_report("report_clock");
|
|
refresh_report("report_battery");
|
|
refresh_report("report_countdown");
|
|
|
|
if (invalidateGameStatus_)
|
|
{
|
|
wb::future_map future; // start planned unit map scope
|
|
|
|
// We display the unit the mouse is over if it is over a unit,
|
|
// otherwise we display the unit that is selected.
|
|
for (const std::string &name : reports_object_->report_list()) {
|
|
refresh_report(name);
|
|
}
|
|
invalidateGameStatus_ = false;
|
|
}
|
|
}
|
|
|
|
|
|
void game_display::set_game_mode(const game_mode mode)
|
|
{
|
|
if(mode != mode_) {
|
|
mode_ = mode;
|
|
invalidate_all();
|
|
}
|
|
}
|
|
|
|
void game_display::draw_movement_info(const map_location& loc)
|
|
{
|
|
// Search if there is a mark here
|
|
pathfind::marked_route::mark_map::iterator w = route_.marks.find(loc);
|
|
|
|
std::shared_ptr<wb::manager> wb = wb_.lock();
|
|
|
|
// Don't use empty route or the first step (the unit will be there)
|
|
if(w != route_.marks.end()
|
|
&& !route_.steps.empty() && route_.steps.front() != loc) {
|
|
const unit_map::const_iterator un =
|
|
(wb && wb->get_temp_move_unit().valid()) ?
|
|
wb->get_temp_move_unit() : context().units().find(route_.steps.front());
|
|
if(un != context().units().end()) {
|
|
// Display the def% of this terrain
|
|
int move_cost = un->movement_cost(context().map().get_terrain(loc));
|
|
int def = (move_cost == movetype::UNREACHABLE ?
|
|
0 : 100 - un->defense_modifier(context().map().get_terrain(loc)));
|
|
std::stringstream def_text;
|
|
def_text << def << "%";
|
|
|
|
color_t color = game_config::red_to_green(def, false);
|
|
|
|
// simple mark (no turn point) use smaller font
|
|
int def_font = w->second.turns > 0 ? 18 : 16;
|
|
draw_text_in_hex(loc, drawing_layer::move_info, def_text.str(), def_font, color);
|
|
|
|
drawing_buffer_add(drawing_layer::move_info, loc,
|
|
[inv = w->second.invisible, zoc = w->second.zoc, cap = w->second.capture](const rect& dest) {
|
|
if(inv) {
|
|
draw::blit(image::get_texture(image::locator{"misc/hidden.png"}, image::HEXED), dest);
|
|
}
|
|
|
|
if(zoc) {
|
|
draw::blit(image::get_texture(image::locator{"misc/zoc.png"}, image::HEXED), dest);
|
|
}
|
|
|
|
if(cap) {
|
|
draw::blit(image::get_texture(image::locator{"misc/capture.png"}, image::HEXED), dest);
|
|
}
|
|
});
|
|
|
|
//we display turn info only if different from a simple last "1"
|
|
if (w->second.turns > 1 || (w->second.turns == 1 && loc != route_.steps.back())) {
|
|
std::stringstream turns_text;
|
|
turns_text << w->second.turns;
|
|
draw_text_in_hex(loc, drawing_layer::move_info, turns_text.str(), 17, font::NORMAL_COLOR, 0.5,0.8);
|
|
}
|
|
|
|
// The hex is full now, so skip the "show enemy moves"
|
|
return;
|
|
}
|
|
}
|
|
// When out-of-turn, it's still interesting to check out the terrain defs of the selected unit
|
|
else if (selectedHex_.valid() && loc == mouseoverHex_)
|
|
{
|
|
const unit_map::const_iterator selectedUnit = resources::gameboard->find_visible_unit(selectedHex_,viewing_team());
|
|
const unit_map::const_iterator mouseoveredUnit = resources::gameboard->find_visible_unit(mouseoverHex_,viewing_team());
|
|
if(selectedUnit != context().units().end() && mouseoveredUnit == context().units().end()) {
|
|
// Display the def% of this terrain
|
|
int move_cost = selectedUnit->movement_cost(context().map().get_terrain(loc));
|
|
int def = (move_cost == movetype::UNREACHABLE ?
|
|
0 : 100 - selectedUnit->defense_modifier(context().map().get_terrain(loc)));
|
|
std::stringstream def_text;
|
|
def_text << def << "%";
|
|
|
|
color_t color = game_config::red_to_green(def, false);
|
|
|
|
// use small font
|
|
int def_font = 16;
|
|
draw_text_in_hex(loc, drawing_layer::move_info, def_text.str(), def_font, color);
|
|
}
|
|
}
|
|
|
|
if (!reach_map_.empty()) {
|
|
reach_map::iterator reach = reach_map_.find(loc);
|
|
if (reach != reach_map_.end() && reach->second > 1) {
|
|
const std::string num = std::to_string(reach->second);
|
|
draw_text_in_hex(loc, drawing_layer::move_info, num, 16, font::YELLOW_COLOR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void game_display::highlight_reach(const pathfind::paths &paths_list)
|
|
{
|
|
unhighlight_reach();
|
|
highlight_another_reach(paths_list);
|
|
}
|
|
|
|
void game_display::highlight_another_reach(const pathfind::paths &paths_list,
|
|
const map_location& goal)
|
|
{
|
|
// Fold endpoints of routes into reachability map.
|
|
for (const pathfind::paths::step &dest : paths_list.destinations) {
|
|
reach_map_[dest.curr]++;
|
|
}
|
|
reach_map_changed_ = true;
|
|
|
|
if(goal != map_location::null_location() && paths_list.destinations.contains(goal)) {
|
|
const auto& path_to_goal = paths_list.destinations.get_path(paths_list.destinations.find(goal));
|
|
const map_location enemy_unit_location = path_to_goal[0];
|
|
units_that_can_reach_goal_.insert(enemy_unit_location);
|
|
}
|
|
}
|
|
|
|
bool game_display::unhighlight_reach()
|
|
{
|
|
units_that_can_reach_goal_.clear();
|
|
if(!reach_map_.empty()) {
|
|
reach_map_.clear();
|
|
reach_map_changed_ = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void game_display::invalidate_route()
|
|
{
|
|
for(std::vector<map_location>::const_iterator i = route_.steps.begin();
|
|
i != route_.steps.end(); ++i) {
|
|
invalidate(*i);
|
|
}
|
|
}
|
|
|
|
void game_display::set_route(const pathfind::marked_route *route)
|
|
{
|
|
invalidate_route();
|
|
|
|
if(route != nullptr) {
|
|
route_ = *route;
|
|
} else {
|
|
route_.steps.clear();
|
|
route_.marks.clear();
|
|
}
|
|
|
|
invalidate_route();
|
|
}
|
|
|
|
void game_display::float_label(const map_location& loc, const std::string& text, const color_t& color)
|
|
{
|
|
if(prefs::get().floating_labels() == false || fogged(loc)) {
|
|
return;
|
|
}
|
|
|
|
rect loc_rect = get_location_rect(loc);
|
|
|
|
font::floating_label flabel(text);
|
|
flabel.set_font_size(int(font::SIZE_FLOAT_LABEL * get_zoom_factor()));
|
|
flabel.set_color(color);
|
|
flabel.set_position(loc_rect.center().x, loc_rect.y); // middle of top edge
|
|
flabel.set_move(0, -0.1 * turbo_speed() * get_zoom_factor());
|
|
flabel.set_lifetime(std::chrono::milliseconds{static_cast<int>(1000 / turbo_speed())});
|
|
flabel.set_scroll_mode(font::ANCHOR_LABEL_MAP);
|
|
|
|
font::add_floating_label(flabel);
|
|
}
|
|
|
|
void game_display::set_attack_indicator(const map_location& src, const map_location& dst)
|
|
{
|
|
if (attack_indicator_src_ != src || attack_indicator_dst_ != dst) {
|
|
invalidate(attack_indicator_src_);
|
|
invalidate(attack_indicator_dst_);
|
|
|
|
attack_indicator_src_ = src;
|
|
attack_indicator_dst_ = dst;
|
|
|
|
invalidate(attack_indicator_src_);
|
|
invalidate(attack_indicator_dst_);
|
|
}
|
|
}
|
|
|
|
void game_display::clear_attack_indicator()
|
|
{
|
|
set_attack_indicator(map_location::null_location(), map_location::null_location());
|
|
}
|
|
|
|
void game_display::begin_game()
|
|
{
|
|
in_game_ = true;
|
|
create_buttons();
|
|
invalidate_all();
|
|
}
|
|
|
|
void game_display::needs_rebuild(bool b) {
|
|
if (b) {
|
|
needs_rebuild_ = true;
|
|
}
|
|
}
|
|
|
|
bool game_display::maybe_rebuild() {
|
|
if (needs_rebuild_) {
|
|
needs_rebuild_ = false;
|
|
recalculate_minimap();
|
|
invalidate_all();
|
|
rebuild_all();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
display::overlay_map& game_display::get_overlays()
|
|
{
|
|
return overlay_map_;
|
|
}
|
|
|
|
std::vector<texture> game_display::get_reachmap_images(const map_location& loc) const
|
|
{
|
|
// Use get_direction() from display.cpp namespace display_direction
|
|
using namespace display_direction;
|
|
std::vector<std::string> names;
|
|
const auto adjacent = get_adjacent_tiles(loc);
|
|
|
|
enum visibility { REACH = 0, ENEMY = 1, CLEAR = 2 };
|
|
std::array<visibility, 6> tiles;
|
|
|
|
for(int i = 0; i < 6; ++i) {
|
|
// look for units adjacent to loc
|
|
std::string test_location = std::to_string(adjacent[i].x) + "," + std::to_string(adjacent[i].y);
|
|
const unit *u = context().get_visible_unit(adjacent[i], viewing_team());
|
|
if(reach_map_.find(adjacent[i]) != reach_map_.end()) {
|
|
DBG_DP << test_location << " is REACHABLE";
|
|
tiles[i] = REACH;
|
|
}
|
|
/**
|
|
* Make sure there is a unit selected or displayed when drawing the reachmap with enemy detection.
|
|
* Enemy detection needs to be "disabled" when the reach_map_team_index_ failsafes to viewing_team_index.
|
|
*/
|
|
else if(display::reach_map_team_index_ == display::viewing_team_index_) {
|
|
DBG_DP << test_location << " is NOT REACHABLE";
|
|
tiles[i] = CLEAR;
|
|
}
|
|
// Grab the reachmap-context team index updated in "display::process_reachmap_changes()" and test for adjacent enemy units
|
|
else if(u != nullptr && resources::gameboard->get_team(display::reach_map_team_index_).is_enemy(u->side())) {
|
|
DBG_DP << test_location << " has an ENEMY";
|
|
tiles[i] = ENEMY;
|
|
} else {
|
|
DBG_DP << test_location << " is NOT REACHABLE";
|
|
tiles[i] = CLEAR;
|
|
}
|
|
}
|
|
|
|
// Find out if location is in the inner part of reachmap (surrounded by reach)
|
|
int s;
|
|
for(s = 0; s != 6; ++s) {
|
|
if(tiles[s] != REACH) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(s == 6) {
|
|
// Completely surrounded by reach. This may have a special graphic.
|
|
DBG_DP << "Tried completely surrounding";
|
|
std::string name = game_config::reach_map_prefix + "-all.png";
|
|
if(image::exists(name)) {
|
|
names.push_back(std::move(name));
|
|
}
|
|
}
|
|
|
|
// Find all the directions overlap occurs from
|
|
for(int i = 0, cap1 = 0; cap1 != 6; ++cap1) {
|
|
if(tiles[i] != REACH) {
|
|
std::ostringstream stream;
|
|
std::string suffix;
|
|
std::string name;
|
|
stream << game_config::reach_map_prefix;
|
|
|
|
std::string color = prefs::get().reach_map_color();
|
|
std::string enemy_color = prefs::get().reach_map_enemy_color();
|
|
std::string border_opacity = std::to_string(prefs::get().reach_map_border_opacity());
|
|
|
|
if(tiles[i] == ENEMY) {
|
|
suffix = ".png~RC(magenta>"+enemy_color+")~O("+border_opacity+"%)";
|
|
} else {
|
|
suffix = ".png~RC(magenta>"+color+")~O("+border_opacity+"%)";
|
|
}
|
|
|
|
for(int cap2 = 0; tiles[i] != REACH && cap2 != 6; i = (i + 1) % 6, ++cap2) {
|
|
stream << get_direction(i);
|
|
if(!image::exists(stream.str() + ".png")) {
|
|
DBG_DP << "Image does not exist: " << stream.str() + ".png on " << loc;
|
|
// If we don't have any surface at all,
|
|
// then move onto the next overlapped area
|
|
if(name.empty()) {
|
|
i = (i + 1) % 6;
|
|
}
|
|
break;
|
|
} else {
|
|
name = stream.str();
|
|
}
|
|
}
|
|
|
|
if(!name.empty()) {
|
|
names.push_back(name + suffix);
|
|
}
|
|
} else {
|
|
i = (i + 1) % 6;
|
|
}
|
|
}
|
|
|
|
// now get the textures
|
|
std::vector<texture> res;
|
|
|
|
for(const std::string& name : names) {
|
|
DBG_DP << "Pushing: " << name;
|
|
if(texture tex = image::get_texture(name, image::HEXED)) {
|
|
res.push_back(std::move(tex));
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|