wesnoth/src/whiteboard/manager.cpp
Chris Beck 504e4932b5 split off animation fcs from unit to unit_animation_component
This is a strict refactor, all we do is move the functions and
variables used just for animations to "unit_animation_component",
and include the necessary headers appropriate.

With a bit more work we can probably remove the graphics related
headers from unit.hpp
2014-06-17 02:18:46 -04:00

1249 lines
34 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (C) 2010 - 2014 by Gabriel Morin <gabrielmorin (at) gmail (dot) com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
*/
#include "manager.hpp"
#include "action.hpp"
#include "highlighter.hpp"
#include "mapbuilder.hpp"
#include "move.hpp"
#include "attack.hpp"
#include "recall.hpp"
#include "recruit.hpp"
#include "side_actions.hpp"
#include "utility.hpp"
#include "actions/create.hpp"
#include "actions/undo.hpp"
#include "arrow.hpp"
#include "chat_events.hpp"
#include "fake_unit.hpp"
#include "fake_unit_manager.hpp"
#include "formula_string_utils.hpp"
#include "game_board.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "gui/dialogs/simple_item_selector.hpp"
#include "key.hpp"
#include "network.hpp"
#include "pathfind/pathfind.hpp"
#include "play_controller.hpp"
#include "resources.hpp"
#include "team.hpp"
#include "unit.hpp"
#include "unit_animation_component.hpp"
#include "unit_display.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <sstream>
namespace wb {
manager::manager():
active_(false),
inverted_behavior_(false),
self_activate_once_(true),
#if 0
print_help_once_(true),
#endif
wait_for_side_init_(true),
planned_unit_map_active_(false),
executing_actions_(false),
executing_all_actions_(false),
preparing_to_end_turn_(false),
gamestate_mutated_(false),
activation_state_lock_(new bool),
unit_map_lock_(new bool),
mapbuilder_(),
highlighter_(),
route_(),
move_arrows_(),
fake_units_(),
temp_move_unit_underlying_id_(0),
key_poller_(new CKey),
hidden_unit_hexes_(),
net_buffer_(resources::teams->size()),
team_plans_hidden_(resources::teams->size(),preferences::hide_whiteboard()),
units_owning_moves_()
{
LOG_WB << "Manager initialized.\n";
}
manager::~manager()
{
LOG_WB << "Manager destroyed.\n";
}
//Used for chat-spamming debug info
#if 0
static void print_to_chat(const std::string& title, const std::string& message)
{
resources::screen->add_chat_message(time(NULL), title, 0, message,
events::chat_handler::MESSAGE_PRIVATE, false);
}
#endif
void manager::print_help_once()
{
#if 0
if (!print_help_once_)
return;
else
print_help_once_ = false;
print_to_chat("whiteboard", std::string("Type :wb to activate/deactivate planning mode.")
+ " Hold TAB to temporarily deactivate/activate it.");
std::stringstream hotkeys;
const hotkey::hotkey_item& hk_execute = hotkey::get_hotkey(hotkey::HOTKEY_WB_EXECUTE_ACTION);
if(!hk_execute.null()) {
//print_to_chat("[execute action]", "'" + hk_execute.get_name() + "'");
hotkeys << "Execute: " << hk_execute.get_name() << ", ";
}
const hotkey::hotkey_item& hk_execute_all = hotkey::get_hotkey(hotkey::HOTKEY_WB_EXECUTE_ALL_ACTIONS);
if(!hk_execute_all.null()) {
//print_to_chat("[execute action]", "'" + hk_execute_all.get_name() + "'");
hotkeys << "Execute all: " << hk_execute_all.get_name() << ", ";
}
const hotkey::hotkey_item& hk_delete = hotkey::get_hotkey(hotkey::HOTKEY_WB_DELETE_ACTION);
if(!hk_delete.null()) {
//print_to_chat("[delete action]", "'" + hk_delete.get_name() + "'");
hotkeys << "Delete: " << hk_delete.get_name() << ", ";
}
const hotkey::hotkey_item& hk_bump_up = hotkey::get_hotkey(hotkey::HOTKEY_WB_BUMP_UP_ACTION);
if(!hk_bump_up.null()) {
//print_to_chat("[move action earlier in queue]", "'" + hk_bump_up.get_name() + "'");
hotkeys << "Move earlier: " << hk_bump_up.get_name() << ", ";
}
const hotkey::hotkey_item& hk_bump_down = hotkey::get_hotkey(hotkey::HOTKEY_WB_BUMP_DOWN_ACTION);
if(!hk_bump_down.null()) {
//print_to_chat("[move action later in queue]", "'" + hk_bump_down.get_name() + "'");
hotkeys << "Move later: " << hk_bump_down.get_name() << ", ";
}
print_to_chat("HOTKEYS:", hotkeys.str() + "\n");
#endif
}
bool manager::can_modify_game_state() const
{
if(wait_for_side_init_
|| resources::teams == NULL
|| executing_actions_
|| is_observer()
|| resources::controller->is_linger_mode())
{
return false;
}
else
{
return true;
}
}
bool manager::can_activate() const
{
//any more than one reference means a lock on whiteboard state was requested
if(!activation_state_lock_.unique())
return false;
return can_modify_game_state();
}
void manager::set_active(bool active)
{
if(!can_activate())
{
active_ = false;
LOG_WB << "Whiteboard can't be activated now.\n";
}
else if (active != active_)
{
active_ = active;
erase_temp_move();
if (active_)
{
if(should_clear_undo())
resources::undo_stack->clear();
validate_viewer_actions();
LOG_WB << "Whiteboard activated! " << *viewer_actions() << "\n";
create_temp_move();
} else {
LOG_WB << "Whiteboard deactivated!\n";
}
}
}
void manager::set_invert_behavior(bool invert)
{
//any more than one reference means a lock on whiteboard state was requested
if(!activation_state_lock_.unique())
return;
bool block_whiteboard_activation = false;
if(!can_activate())
{
block_whiteboard_activation = true;
}
if (invert)
{
if (!inverted_behavior_)
{
if (active_)
{
DBG_WB << "Whiteboard deactivated temporarily.\n";
inverted_behavior_ = true;
set_active(false);
}
else if (!block_whiteboard_activation)
{
DBG_WB << "Whiteboard activated temporarily.\n";
inverted_behavior_ = true;
set_active(true);
}
}
}
else
{
if (inverted_behavior_)
{
if (active_)
{
DBG_WB << "Whiteboard set back to deactivated status.\n";
inverted_behavior_ = false;
set_active(false);
}
else if (!block_whiteboard_activation)
{
DBG_WB << "Whiteboard set back to activated status.\n";
inverted_behavior_ = false;
set_active(true);
}
}
}
}
bool manager::can_enable_execution_hotkeys() const
{
return can_enable_modifier_hotkeys() && viewer_side() == resources::controller->current_side()
&& viewer_actions()->turn_size(0) > 0;
}
bool manager::can_enable_modifier_hotkeys() const
{
return can_modify_game_state() && !viewer_actions()->empty();
}
bool manager::can_enable_reorder_hotkeys() const
{
return can_enable_modifier_hotkeys() && highlighter_ && highlighter_->get_bump_target();
}
bool manager::allow_leader_to_move(unit const& leader) const
{
if(!has_actions())
return true;
//Look for another leader on another keep in the same castle
{ wb::future_map future; // start planned unit map scope
if(!has_planned_unit_map()) {
WRN_WB << "Unable to build future map to determine whether leader's allowed to move." << std::endl;
}
if(find_backup_leader(leader))
return true;
} // end planned unit map scope
if(viewer_actions()->empty()) {
return true;
}
//Look for planned recruits that depend on this leader
BOOST_FOREACH(action_const_ptr action, *viewer_actions())
{
recruit_const_ptr recruit = boost::dynamic_pointer_cast<class recruit const>(action);
recall_const_ptr recall = boost::dynamic_pointer_cast<class recall const>(action);
if(recruit || recall)
{
map_location const target_hex = recruit?recruit->get_recruit_hex():recall->get_recall_hex();
if ( can_recruit_on(leader, target_hex) )
return false;
}
}
return true;
}
void manager::on_init_side()
{
//Turn should never start with action auto-execution already enabled!
assert(!executing_all_actions_ && !executing_actions_);
update_plan_hiding(); /* validates actions */
wait_for_side_init_ = false;
LOG_WB << "on_init_side()\n";
if (self_activate_once_ && preferences::enable_whiteboard_mode_on_start())
{
self_activate_once_ = false;
set_active(true);
}
}
void manager::on_finish_side_turn(int side)
{
preparing_to_end_turn_ = false;
wait_for_side_init_ = true;
if(side == viewer_side() && !viewer_actions()->empty()) {
viewer_actions()->synced_turn_shift();
}
highlighter_.reset();
erase_temp_move();
LOG_WB << "on_finish_side_turn()\n";
}
void manager::pre_delete_action(action_ptr)
{
}
void manager::post_delete_action(action_ptr action)
{
// The fake unit representing the destination of a chain of planned moves should have the regular animation.
// If the last remaining action of the unit that owned this move is a move as well,
// adjust its appearance accordingly.
side_actions_ptr side_actions = resources::teams->at(action->team_index()).get_side_actions();
UnitPtr actor = action->get_unit();
if(actor) { // The unit might have died following the execution of an attack
side_actions::iterator action_it = side_actions->find_last_action_of(*actor);
if(action_it != side_actions->end()) {
move_ptr move = boost::dynamic_pointer_cast<class move>(*action_it);
if(move && move->get_fake_unit()) {
move->get_fake_unit()->anim_comp().set_standing(true);
}
}
}
}
static void hide_all_plans()
{
BOOST_FOREACH(team& t, *resources::teams)
t.get_side_actions()->hide();
}
/* private */
void manager::update_plan_hiding(size_t team_index)
{
//We don't control the "viewing" side ... we're probably an observer
if(!resources::teams->at(team_index).is_human())
hide_all_plans();
else // normal circumstance
{
BOOST_FOREACH(team& t, *resources::teams)
{
//make sure only appropriate teams are hidden
if(!t.is_network_human())
team_plans_hidden_[t.side()-1] = false;
if(t.is_enemy(team_index+1) || team_plans_hidden_[t.side()-1])
t.get_side_actions()->hide();
else
t.get_side_actions()->show();
}
}
validate_viewer_actions();
}
void manager::update_plan_hiding()
{update_plan_hiding(viewer_team());}
void manager::on_viewer_change(size_t team_index)
{
if(!wait_for_side_init_)
update_plan_hiding(team_index);
}
void manager::on_change_controller(int side, const team& t)
{
wb::side_actions& sa = *t.get_side_actions();
if(t.is_human()) // we own this side now
{
//tell everyone to clear this side's actions -- we're starting anew
resources::whiteboard->queue_net_cmd(sa.team_index(),sa.make_net_cmd_clear());
sa.clear();
//refresh the hidden_ attribute of every team's side_actions
update_plan_hiding();
}
else if(t.is_ai() || t.is_network_ai()) // no one owns this side anymore
sa.clear(); // clear its plans away -- the ai doesn't plan ... yet
else if(t.is_network()) // Another client is taking control of the side
{
if(side==viewer_side()) // They're taking OUR side away!
hide_all_plans(); // give up knowledge of everyone's plans, in case we became an observer
//tell them our plans -- they may not have received them up to this point
size_t num_teams = resources::teams->size();
for(size_t i=0; i<num_teams; ++i)
{
team& local_team = resources::teams->at(i);
if(local_team.is_human() && !local_team.is_enemy(side))
resources::whiteboard->queue_net_cmd(i,local_team.get_side_actions()->make_net_cmd_refresh());
}
}
}
bool manager::current_side_has_actions()
{
if(current_side_actions()->empty()) {
return false;
}
side_actions::range_t range = current_side_actions()->iter_turn(0);
return range.first != range.second; //non-empty range
}
void manager::validate_viewer_actions()
{
LOG_WB << "'gamestate_mutated_' flag dirty, validating actions.\n";
gamestate_mutated_ = false;
if(has_planned_unit_map()) {
real_map();
} else {
future_map();
}
}
//helper fcn
static void draw_numbers(map_location const& hex, side_actions::numbers_t numbers)
{
std::vector<int>& numbers_to_draw = numbers.numbers_to_draw;
std::vector<size_t>& team_numbers = numbers.team_numbers;
int& main_number = numbers.main_number;
std::set<size_t>& secondary_numbers = numbers.secondary_numbers;
const double x_offset_base = 0.0;
const double y_offset_base = 0.2;
//position 0,0 in the hex is the upper left corner
//0.8 = horizontal coord., close to the right side of the hex
const double x_origin = 0.8 - numbers_to_draw.size() * x_offset_base;
//0.5 = halfway in the hex vertically
const double y_origin = 0.5 - numbers_to_draw.size() * (y_offset_base / 2);
double x_offset = 0, y_offset = 0;
size_t size = numbers_to_draw.size();
for(size_t i=0; i<size; ++i)
{
int number = numbers_to_draw[i];
std::string number_text = boost::lexical_cast<std::string>(number);
size_t font_size;
if (int(i) == main_number) font_size = 19;
else if (secondary_numbers.find(i)!=secondary_numbers.end()) font_size = 17;
else font_size = 15;
SDL_Color color = team::get_side_color(static_cast<int>(team_numbers[i]+1));
const double x_in_hex = x_origin + x_offset;
const double y_in_hex = y_origin + y_offset;
resources::screen->draw_text_in_hex(hex, display::LAYER_ACTIONS_NUMBERING,
number_text, font_size, color, x_in_hex, y_in_hex);
x_offset += x_offset_base;
y_offset += y_offset_base;
}
}
namespace
{
//Helper struct that finds all units teams whose planned actions are currently visible
//Only used by manager::pre_draw().
//Note that this structure is used as a functor.
struct move_owners_finder: public visitor
{
public:
move_owners_finder(): move_owners_() { }
void operator()(action_ptr action) {
action->accept(*this);
}
std::set<size_t> const& get_units_owning_moves() {
return move_owners_;
}
virtual void visit(move_ptr move) {
if(size_t id = move->get_unit_id()) {
move_owners_.insert(id);
}
}
virtual void visit(attack_ptr attack) {
//also add attacks if they have an associated move
if(attack->get_route().steps.size() >= 2) {
if(size_t id = attack->get_unit_id()) {
move_owners_.insert(id);
}
}
}
virtual void visit(recruit_ptr){}
virtual void visit(recall_ptr){}
virtual void visit(suppose_dead_ptr){}
private:
std::set<size_t> move_owners_;
};
}
void manager::pre_draw()
{
if (can_modify_game_state() && has_actions()) {
move_owners_finder move_finder;
for_each_action(boost::ref(move_finder));
units_owning_moves_ = move_finder.get_units_owning_moves();
BOOST_FOREACH(size_t unit_id, units_owning_moves_) {
unit_map::iterator unit_iter = resources::units->find(unit_id);
assert(unit_iter.valid());
ghost_owner_unit(&*unit_iter);
}
}
}
void manager::post_draw()
{
BOOST_FOREACH(size_t unit_id, units_owning_moves_)
{
unit_map::iterator unit_iter = resources::units->find(unit_id);
if (unit_iter.valid()) {
unghost_owner_unit(&*unit_iter);
}
}
units_owning_moves_.clear();
}
void manager::draw_hex(const map_location& hex)
{
/**
* IMPORTANT: none of the code in this method can call anything which would
* cause a hex to be invalidated (i.e. by calling in turn any variant of display::invalidate()).
* Doing so messes up the iterator currently going over the list of invalidated hexes to draw.
*/
if (!wait_for_side_init_ && has_actions())
{
//call draw() for all actions
for_each_action(boost::bind(&action::draw_hex, _1, hex));
//Info about the action numbers to be displayed on screen.
side_actions::numbers_t numbers;
BOOST_FOREACH(team& t, *resources::teams)
{
side_actions& sa = *t.get_side_actions();
if(!sa.hidden())
sa.get_numbers(hex,numbers);
}
draw_numbers(hex,numbers); // helper fcn
}
}
void manager::on_mouseover_change(const map_location& hex)
{
map_location selected_hex = resources::controller->get_mouse_handler_base().get_selected_hex();
bool hex_has_unit;
{ wb::future_map future; // start planned unit map scope
hex_has_unit = resources::units->find(selected_hex) != resources::units->end();
} // end planned unit map scope
if (!((selected_hex.valid() && hex_has_unit)
|| has_temp_move() || wait_for_side_init_ || executing_actions_))
{
if (!highlighter_)
{
highlighter_.reset(new highlighter(*resources::units, viewer_actions()));
}
highlighter_->set_mouseover_hex(hex);
highlighter_->highlight();
}
}
void manager::on_gamestate_change()
{
DBG_WB << "Manager received gamestate change notification.\n";
// if on_gamestate_change() is called while the future unit map is applied,
// it means that the future unit map scope is used where it shouldn't be.
assert(!planned_unit_map_active_);
// Set mutated flag so action queue gets validated on next future map build
gamestate_mutated_ = true;
//Clear exclusive draws that might not get a chance to be cleared the normal way
resources::screen->clear_exclusive_draws();
}
void manager::send_network_data()
{
size_t size = net_buffer_.size();
for(size_t team_index=0; team_index<size; ++team_index)
{
config& buf_cfg = net_buffer_[team_index];
if(buf_cfg.empty())
continue;
config packet;
config& wb_cfg = packet.add_child("whiteboard",buf_cfg);
wb_cfg["side"] = static_cast<int>(team_index+1);
wb_cfg["team_name"] = resources::teams->at(team_index).team_name();
buf_cfg = config();
network::send_data(packet,0,"whiteboard");
size_t count = wb_cfg.child_count("net_cmd");
LOG_WB << "Side " << (team_index+1) << " sent wb data (" << count << " cmds).\n";
}
}
void manager::process_network_data(config const& cfg)
{
if(config const& wb_cfg = cfg.child("whiteboard"))
{
size_t count = wb_cfg.child_count("net_cmd");
LOG_WB << "Received wb data (" << count << ").\n";
team& team_from = resources::teams->at(wb_cfg["side"]-1);
BOOST_FOREACH(side_actions::net_cmd const& cmd, wb_cfg.child_range("net_cmd"))
team_from.get_side_actions()->execute_net_cmd(cmd);
}
}
void manager::queue_net_cmd(size_t team_index, side_actions::net_cmd const& cmd)
{
net_buffer_[team_index].add_child("net_cmd",cmd);
}
void manager::create_temp_move()
{
route_.reset();
/*
* CHECK PRE-CONDITIONS
* (This section has multiple return paths.)
*/
if ( !active_ || !can_modify_game_state() )
return;
pathfind::marked_route const& route =
resources::controller->get_mouse_handler_base().get_current_route();
if (route.steps.empty() || route.steps.size() < 2) return;
unit* temp_moved_unit =
future_visible_unit(resources::controller->get_mouse_handler_base().get_selected_hex(), viewer_side());
if (!temp_moved_unit) temp_moved_unit =
future_visible_unit(resources::controller->get_mouse_handler_base().get_last_hex(), viewer_side());
if (!temp_moved_unit) return;
if (temp_moved_unit->side() != resources::screen->viewing_side()) return;
/*
* DONE CHECKING PRE-CONDITIONS, CREATE THE TEMP MOVE
* (This section has only one return path.)
*/
temp_move_unit_underlying_id_ = temp_moved_unit->underlying_id();
//@todo: May be appropriate to replace these separate components by a temporary
// wb::move object
route_.reset(new pathfind::marked_route(route));
//NOTE: route_->steps.back() = dst, and route_->steps.front() = src
size_t turn = 0;
std::vector<map_location>::iterator prev_itor = route.steps.begin();
std::vector<map_location>::iterator curr_itor = prev_itor;
std::vector<map_location>::iterator end_itor = route.steps.end();
for(; curr_itor!=end_itor; ++curr_itor)
{
const map_location& hex = *curr_itor;
//search for end-of-turn marks
pathfind::marked_route::mark_map::const_iterator w =
route.marks.find(hex);
if(w != route.marks.end() && w->second.turns > 0)
{
turn = w->second.turns-1;
if(turn >= move_arrows_.size())
move_arrows_.resize(turn+1);
if(turn >= fake_units_.size())
fake_units_.resize(turn+1);
arrow_ptr& move_arrow = move_arrows_[turn];
fake_unit_ptr& fake_unit = fake_units_[turn];
if(!move_arrow)
{
// Create temp arrow
move_arrow.reset(new arrow());
move_arrow->set_color(team::get_side_color_index(
viewer_side()));
move_arrow->set_style(arrow::STYLE_HIGHLIGHTED);
}
arrow_path_t path(prev_itor,curr_itor+1);
move_arrow->set_path(path);
if(path.size() >= 2)
{
if(!fake_unit)
{
// Create temp ghost unit
fake_unit.reset(new class fake_unit(*temp_moved_unit));
fake_unit->place_on_fake_unit_manager( resources::fake_units);
fake_unit->anim_comp().set_ghosted(true);
}
unit_display::move_unit(path, *fake_unit, false); //get facing right
fake_unit->anim_comp().invalidate(*game_display::get_singleton());
fake_unit->set_location(*curr_itor);
fake_unit->anim_comp().set_ghosted(true);
}
else //zero-hex path -- don't bother drawing a fake unit
fake_unit.reset();
prev_itor = curr_itor;
}
}
//in case path shortens on next step and one ghosted unit has to be removed
int ind = fake_units_.size() - 1;
fake_units_[ind]->anim_comp().invalidate(*game_display::get_singleton());
//toss out old arrows and fake units
move_arrows_.resize(turn+1);
fake_units_.resize(turn+1);
}
void manager::erase_temp_move()
{
move_arrows_.clear();
BOOST_FOREACH(fake_unit_ptr const& tmp, fake_units_) {
if(tmp) {
tmp->anim_comp().invalidate(*game_display::get_singleton());
}
}
fake_units_.clear();
route_.reset();
temp_move_unit_underlying_id_ = 0;
}
void manager::save_temp_move()
{
if (has_temp_move() && !executing_actions_ && !resources::controller->is_linger_mode())
{
side_actions& sa = *viewer_actions();
unit* u = future_visible_unit(route_->steps.front());
assert(u);
size_t first_turn = sa.get_turn_num_of(*u);
validate_viewer_actions();
assert(move_arrows_.size() == fake_units_.size());
size_t size = move_arrows_.size();
for(size_t i=0; i<size; ++i)
{
arrow_ptr move_arrow = move_arrows_[i];
if(!arrow::valid_path(move_arrow->get_path()))
continue;
size_t turn = first_turn + i;
fake_unit_ptr fake_unit = fake_units_[i];
//@todo Using a marked_route here is wrong, since right now it's not marked
//either switch over to a plain route for planned moves, or mark it correctly
pathfind::marked_route route;
route.steps = move_arrow->get_path();
route.move_cost = path_cost(route.steps,*u);
sa.queue_move(turn,*u,route,move_arrow,fake_unit);
}
erase_temp_move();
LOG_WB << *viewer_actions() << "\n";
print_help_once();
}
}
unit_map::iterator manager::get_temp_move_unit() const
{
return resources::units->find(temp_move_unit_underlying_id_);
}
void manager::save_temp_attack(const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
{
if (active_ && !executing_actions_ && !resources::controller->is_linger_mode())
{
assert(weapon_choice >= 0);
arrow_ptr move_arrow;
fake_unit_ptr fake_unit;
map_location source_hex;
if (route_ && !route_->steps.empty())
{
//attack-move
assert(move_arrows_.size() == 1);
assert(fake_units_.size() == 1);
move_arrow = move_arrows_.front();
fake_unit = fake_units_.front();
assert(route_->steps.back() == attacker_loc);
source_hex = route_->steps.front();
fake_unit->anim_comp().set_disabled_ghosted(true);
}
else
{
//simple attack
move_arrow.reset(new arrow);
source_hex = attacker_loc;
route_.reset(new pathfind::marked_route);
// We'll pass as parameter a one-hex route with no marks.
route_->steps.push_back(attacker_loc);
}
unit* attacking_unit = future_visible_unit(source_hex);
assert(attacking_unit);
validate_viewer_actions();
side_actions& sa = *viewer_actions();
sa.queue_attack(sa.get_turn_num_of(*attacking_unit),*attacking_unit,defender_loc,weapon_choice,*route_,move_arrow,fake_unit);
print_help_once();
resources::screen->invalidate(defender_loc);
resources::screen->invalidate(attacker_loc);
erase_temp_move();
LOG_WB << *viewer_actions() << "\n";
}
}
bool manager::save_recruit(const std::string& name, int side_num, const map_location& recruit_hex)
{
bool created_planned_recruit = false;
if (active_ && !executing_actions_ && !resources::controller->is_linger_mode()) {
if (side_num != resources::screen->viewing_side())
{
LOG_WB <<"manager::save_recruit called for a different side than viewing side.\n";
created_planned_recruit = false;
}
else
{
side_actions& sa = *viewer_actions();
unit* recruiter;
{ wb::future_map raii;
recruiter = find_recruiter(side_num-1,recruit_hex);
} // end planned unit map scope
assert(recruiter);
size_t turn = sa.get_turn_num_of(*recruiter);
sa.queue_recruit(turn,name,recruit_hex);
created_planned_recruit = true;
print_help_once();
}
}
return created_planned_recruit;
}
bool manager::save_recall(const unit& unit, int side_num, const map_location& recall_hex)
{
bool created_planned_recall = false;
if (active_ && !executing_actions_ && !resources::controller->is_linger_mode())
{
if (side_num != resources::screen->viewing_side())
{
LOG_WB <<"manager::save_recall called for a different side than viewing side.\n";
created_planned_recall = false;
}
else
{
side_actions& sa = *viewer_actions();
size_t turn = sa.num_turns();
if(turn > 0)
--turn;
sa.queue_recall(turn,unit,recall_hex);
created_planned_recall = true;
print_help_once();
}
}
return created_planned_recall;
}
void manager::save_suppose_dead(unit& curr_unit, map_location const& loc)
{
if(active_ && !executing_actions_ && !resources::controller->is_linger_mode())
{
validate_viewer_actions();
side_actions& sa = *viewer_actions();
sa.queue_suppose_dead(sa.get_turn_num_of(curr_unit),curr_unit,loc);
}
}
void manager::contextual_execute()
{
validate_viewer_actions();
if (can_enable_execution_hotkeys())
{
erase_temp_move();
//For exception-safety, this struct sets executing_actions_ to false on destruction.
variable_finalizer<bool> finally(executing_actions_, false);
action_ptr action;
side_actions::iterator it = viewer_actions()->end();
unit const* selected_unit = future_visible_unit(resources::controller->get_mouse_handler_base().get_selected_hex(), viewer_side());
if (selected_unit &&
(it = viewer_actions()->find_first_action_of(*selected_unit)) != viewer_actions()->end())
{
executing_actions_ = true;
viewer_actions()->execute(it);
}
else if (highlighter_ && (action = highlighter_->get_execute_target()) &&
(it = viewer_actions()->get_position_of(action)) != viewer_actions()->end())
{
executing_actions_ = true;
viewer_actions()->execute(it);
}
else //we already check above for viewer_actions()->empty()
{
executing_actions_ = true;
viewer_actions()->execute_next();
}
} //Finalizer struct sets executing_actions_ to false
}
bool manager::allow_end_turn()
{
preparing_to_end_turn_ = true;
return execute_all_actions();
}
bool manager::execute_all_actions()
{
//exception-safety: finalizers set variables to false on destruction
//i.e. when method exits naturally or exception is thrown
variable_finalizer<bool> finalize_executing_actions(executing_actions_, false);
variable_finalizer<bool> finalize_executing_all_actions(executing_all_actions_, false);
validate_viewer_actions();
if(viewer_actions()->empty() || viewer_actions()->turn_size(0) == 0)
{
//No actions to execute, job done.
return true;
}
assert(can_enable_execution_hotkeys());
erase_temp_move();
// Build unit map once to ensure spent gold and other calculations are refreshed
set_planned_unit_map();
assert(has_planned_unit_map());
set_real_unit_map();
executing_actions_ = true;
executing_all_actions_ = true;
side_actions_ptr sa = viewer_actions();
if (resources::whiteboard->has_planned_unit_map())
{
ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
}
//LOG_WB << "Before executing all actions, " << *sa << "\n";
while (sa->turn_begin(0) != sa->turn_end(0))
{
bool action_successful = sa->execute(sa->begin());
// Interrupt on incomplete action
if (!action_successful)
{
return false;
}
}
return true;
}
void manager::contextual_delete()
{
validate_viewer_actions();
if(can_enable_modifier_hotkeys()) {
erase_temp_move();
action_ptr action;
side_actions::iterator it = viewer_actions()->end();
unit const* selected_unit = future_visible_unit(resources::controller->get_mouse_handler_base().get_selected_hex(), viewer_side());
if(selected_unit && (it = viewer_actions()->find_first_action_of(*selected_unit)) != viewer_actions()->end()) {
///@todo Shouldn't it be "find_last_action_of" instead of "find_first_action_of" above?
viewer_actions()->remove_action(it);
///@todo Shouldn't we probably deselect the unit at this point?
} else if(highlighter_ && (action = highlighter_->get_delete_target()) && (it = viewer_actions()->get_position_of(action)) != viewer_actions()->end()) {
viewer_actions()->remove_action(it);
validate_viewer_actions();
highlighter_->set_mouseover_hex(highlighter_->get_mouseover_hex());
highlighter_->highlight();
} else { //we already check above for viewer_actions()->empty()
it = (viewer_actions()->end() - 1);
action = *it;
viewer_actions()->remove_action(it);
validate_viewer_actions();
}
}
}
void manager::contextual_bump_up_action()
{
validate_viewer_actions();
if(can_enable_reorder_hotkeys()) {
action_ptr action = highlighter_->get_bump_target();
if(action) {
viewer_actions()->bump_earlier(viewer_actions()->get_position_of(action));
validate_viewer_actions(); // Redraw arrows
}
}
}
void manager::contextual_bump_down_action()
{
validate_viewer_actions();
if(can_enable_reorder_hotkeys()) {
action_ptr action = highlighter_->get_bump_target();
if(action) {
viewer_actions()->bump_later(viewer_actions()->get_position_of(action));
validate_viewer_actions(); // Redraw arrows
}
}
}
bool manager::has_actions() const
{
assert(resources::teams);
return wb::has_actions();
}
bool manager::unit_has_actions(unit const* unit) const
{
assert(unit != NULL);
assert(resources::teams);
return viewer_actions()->unit_has_actions(*unit);
}
int manager::get_spent_gold_for(int side)
{
if(wait_for_side_init_)
return 0;
return resources::teams->at(side - 1).get_side_actions()->get_gold_spent();
}
void manager::options_dlg()
{
int v_side = viewer_side();
int selection = 0;
std::vector<team*> allies;
std::vector<std::string> options;
utils::string_map t_vars;
options.push_back(_("SHOW ALL allies plans"));
options.push_back(_("HIDE ALL allies plans"));
//populate list of networked allies
BOOST_FOREACH(team &t, *resources::teams)
{
//Exclude enemies, AIs, and local players
if(t.is_enemy(v_side) || !t.is_network())
continue;
allies.push_back(&t);
t_vars["player"] = t.current_player();
size_t t_index = t.side()-1;
if(team_plans_hidden_[t_index])
options.push_back(vgettext("Show plans for $player", t_vars));
else
options.push_back(vgettext("Hide plans for $player", t_vars));
}
gui2::tsimple_item_selector dlg("", _("Whiteboard Options"), options);
dlg.show(resources::screen->video());
selection = dlg.selected_index();
if(selection == -1)
return;
switch(selection)
{
case 0:
BOOST_FOREACH(team* t, allies)
team_plans_hidden_[t->side()-1]=false;
break;
case 1:
BOOST_FOREACH(team* t, allies)
team_plans_hidden_[t->side()-1]=true;
break;
default:
if(selection > 1)
{
size_t t_index = allies[selection-2]->side()-1;
//toggle ...
bool hidden = team_plans_hidden_[t_index];
team_plans_hidden_[t_index] = !hidden;
}
break;
}
update_plan_hiding();
}
void manager::set_planned_unit_map()
{
if (!can_modify_game_state()) {
LOG_WB << "Not building planned unit map: cannot modify game state now.\n";
return;
}
//any more than one reference means a lock on unit map was requested
if(!unit_map_lock_.unique()) {
LOG_WB << "Not building planned unit map: unit map locked.\n";
return;
}
if (planned_unit_map_active_) {
WRN_WB << "Not building planned unit map: already set." << std::endl;
return;
}
log_scope2("whiteboard", "Building planned unit map");
mapbuilder_.reset(new mapbuilder(*resources::units));
mapbuilder_->build_map();
planned_unit_map_active_ = true;
}
void manager::set_real_unit_map()
{
if (planned_unit_map_active_)
{
assert(!executing_actions_);
assert(!wait_for_side_init_);
if(mapbuilder_)
{
log_scope2("whiteboard", "Restoring regular unit map.");
mapbuilder_.reset();
}
planned_unit_map_active_ = false;
}
else
{
LOG_WB << "Not disabling planned unit map: already disabled.\n";
}
}
void manager::validate_actions_if_needed()
{
if (gamestate_mutated_) {
validate_viewer_actions();
}
}
future_map::future_map():
initial_planned_unit_map_(resources::whiteboard && resources::whiteboard->has_planned_unit_map())
{
if (!resources::whiteboard)
return;
if (!initial_planned_unit_map_)
resources::whiteboard->set_planned_unit_map();
// check if if unit map was successfully applied
if (!resources::whiteboard->has_planned_unit_map()) {
DBG_WB << "Scoped future unit map failed to apply.\n";
}
}
future_map::~future_map()
{
try {
if (!resources::whiteboard)
return;
if (!initial_planned_unit_map_ && resources::whiteboard->has_planned_unit_map())
resources::whiteboard->set_real_unit_map();
} catch (...) {}
}
future_map_if_active::future_map_if_active():
initial_planned_unit_map_(resources::whiteboard && resources::whiteboard->has_planned_unit_map()),
whiteboard_active_(resources::whiteboard && resources::whiteboard->is_active())
{
if (!resources::whiteboard)
return;
if (!whiteboard_active_)
return;
if (!initial_planned_unit_map_)
resources::whiteboard->set_planned_unit_map();
// check if if unit map was successfully applied
if (!resources::whiteboard->has_planned_unit_map()) {
DBG_WB << "Scoped future unit map failed to apply.\n";
}
}
future_map_if_active::~future_map_if_active()
{
try {
if (!resources::whiteboard)
return;
if (!initial_planned_unit_map_ && resources::whiteboard->has_planned_unit_map())
resources::whiteboard->set_real_unit_map();
} catch (...) {}
}
real_map::real_map():
initial_planned_unit_map_(resources::whiteboard && resources::whiteboard->has_planned_unit_map()),
unit_map_lock_(resources::whiteboard ? resources::whiteboard->unit_map_lock_ : boost::shared_ptr<bool>(new bool(false)))
{
if (!resources::whiteboard)
return;
if (initial_planned_unit_map_)
resources::whiteboard->set_real_unit_map();
}
real_map::~real_map()
{
if (!resources::whiteboard)
return;
assert(!resources::whiteboard->has_planned_unit_map());
if (initial_planned_unit_map_)
{
resources::whiteboard->set_planned_unit_map();
}
}
} // end namespace wb