1 /*
2  Copyright (C) 2010 - 2018 by Gabriel Morin <gabrielmorin (at) gmail (dot) com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13  */
14 
15 /**
16  * @file
17  */
18 
19 #include "whiteboard/manager.hpp"
20 
21 #include "whiteboard/action.hpp"
22 #include "whiteboard/highlighter.hpp"
23 #include "whiteboard/mapbuilder.hpp"
24 #include "whiteboard/move.hpp"
25 #include "whiteboard/attack.hpp"
26 #include "whiteboard/recall.hpp"
27 #include "whiteboard/recruit.hpp"
28 #include "whiteboard/side_actions.hpp"
29 #include "whiteboard/utility.hpp"
30 
31 #include "actions/create.hpp"
32 #include "actions/undo.hpp"
33 #include "arrow.hpp"
34 #include "chat_events.hpp"
35 #include "fake_unit_manager.hpp"
36 #include "fake_unit_ptr.hpp"
37 #include "formula/string_utils.hpp"
38 #include "game_board.hpp"
39 #include "preferences/game.hpp"
40 #include "game_state.hpp"
41 #include "gettext.hpp"
42 #include "gui/dialogs/simple_item_selector.hpp"
43 #include "key.hpp"
44 #include "pathfind/pathfind.hpp"
45 #include "play_controller.hpp"
46 #include "replay_helper.hpp"
47 #include "resources.hpp"
48 #include "synced_context.hpp"
49 #include "team.hpp"
50 #include "units/unit.hpp"
51 #include "units/animation_component.hpp"
52 #include "units/udisplay.hpp"
53 
54 #include "utils/functional.hpp"
55 
56 #include <sstream>
57 
58 namespace wb {
59 
manager()60 manager::manager():
61 		active_(false),
62 		inverted_behavior_(false),
63 		self_activate_once_(true),
64 #if 0
65 		print_help_once_(true),
66 #endif
67 		wait_for_side_init_(true),
68 		planned_unit_map_active_(false),
69 		executing_actions_(false),
70 		executing_all_actions_(false),
71 		preparing_to_end_turn_(false),
72 		gamestate_mutated_(false),
73 		activation_state_lock_(new bool),
74 		unit_map_lock_(new bool),
75 		mapbuilder_(),
76 		highlighter_(),
77 		route_(),
78 		move_arrows_(),
79 		fake_units_(),
80 		temp_move_unit_underlying_id_(0),
81 		key_poller_(new CKey),
82 		hidden_unit_hexes_(),
83 		net_buffer_(resources::gameboard->teams().size()),
84 		team_plans_hidden_(resources::gameboard->teams().size()),
85 		units_owning_moves_()
86 {
87 	if(preferences::hide_whiteboard()) {
88 		team_plans_hidden_.flip();
89 	}
90 	LOG_WB << "Manager initialized.\n";
91 }
92 
~manager()93 manager::~manager()
94 {
95 	LOG_WB << "Manager destroyed.\n";
96 }
97 
98 //Used for chat-spamming debug info
99 #if 0
100 static void print_to_chat(const std::string& title, const std::string& message)
101 {
102 	display::get_singleton()->add_chat_message(time(nullptr), title, 0, message,
103 			events::chat_handler::MESSAGE_PRIVATE, false);
104 }
105 #endif
106 
print_help_once()107 void manager::print_help_once()
108 {
109 #if 0
110 	if (!print_help_once_)
111 		return;
112 	else
113 		print_help_once_ = false;
114 
115 	print_to_chat("whiteboard", std::string("Type :wb to activate/deactivate planning mode.")
116 		+ "  Hold TAB to temporarily deactivate/activate it.");
117 	std::stringstream hotkeys;
118 	const hotkey::hotkey_item& hk_execute = hotkey::get_hotkey(hotkey::HOTKEY_WB_EXECUTE_ACTION);
119 	if(!hk_execute.null()) {
120 		//print_to_chat("[execute action]", "'" + hk_execute.get_name() + "'");
121 		hotkeys << "Execute: " << hk_execute.get_name() << ", ";
122 	}
123 	const hotkey::hotkey_item& hk_execute_all = hotkey::get_hotkey(hotkey::HOTKEY_WB_EXECUTE_ALL_ACTIONS);
124 	if(!hk_execute_all.null()) {
125 		//print_to_chat("[execute action]", "'" + hk_execute_all.get_name() + "'");
126 		hotkeys << "Execute all: " << hk_execute_all.get_name() << ", ";
127 	}
128 	const hotkey::hotkey_item& hk_delete = hotkey::get_hotkey(hotkey::HOTKEY_WB_DELETE_ACTION);
129 	if(!hk_delete.null()) {
130 		//print_to_chat("[delete action]", "'" + hk_delete.get_name() + "'");
131 		hotkeys << "Delete: " << hk_delete.get_name() << ", ";
132 	}
133 	const hotkey::hotkey_item& hk_bump_up = hotkey::get_hotkey(hotkey::HOTKEY_WB_BUMP_UP_ACTION);
134 	if(!hk_bump_up.null()) {
135 		//print_to_chat("[move action earlier in queue]", "'" + hk_bump_up.get_name() + "'");
136 		hotkeys << "Move earlier: " << hk_bump_up.get_name() << ", ";
137 	}
138 	const hotkey::hotkey_item& hk_bump_down = hotkey::get_hotkey(hotkey::HOTKEY_WB_BUMP_DOWN_ACTION);
139 	if(!hk_bump_down.null()) {
140 		//print_to_chat("[move action later in queue]", "'" + hk_bump_down.get_name() + "'");
141 		hotkeys << "Move later: " << hk_bump_down.get_name() << ", ";
142 	}
143 	print_to_chat("HOTKEYS:", hotkeys.str() + "\n");
144 #endif
145 }
146 
can_modify_game_state() const147 bool manager::can_modify_game_state() const
148 {
149 	if(wait_for_side_init_
150 					|| resources::gameboard == nullptr
151 					|| executing_actions_
152 					|| resources::gameboard->is_observer()
153 					|| resources::controller->is_linger_mode())
154 	{
155 		return false;
156 	}
157 	else
158 	{
159 		return true;
160 	}
161 }
162 
can_activate() const163 bool manager::can_activate() const
164 {
165 	//any more than one reference means a lock on whiteboard state was requested
166 	if(!activation_state_lock_.unique())
167 		return false;
168 
169 	return can_modify_game_state();
170 }
171 
set_active(bool active)172 void manager::set_active(bool active)
173 {
174 	if(!can_activate())
175 	{
176 		active_ = false;
177 		LOG_WB << "Whiteboard can't be activated now.\n";
178 	}
179 	else if (active != active_)
180 	{
181 		active_ = active;
182 		erase_temp_move();
183 
184 		if (active_)
185 		{
186 			if(should_clear_undo()) {
187 				if(!resources::controller->current_team().auto_shroud_updates()) {
188 					synced_context::run_and_throw("update_shroud", replay_helper::get_update_shroud());
189 					synced_context::run_and_throw("auto_shroud", replay_helper::get_auto_shroud(true));
190 				}
191 				resources::undo_stack->clear();
192 			}
193 			validate_viewer_actions();
194 			LOG_WB << "Whiteboard activated! " << *viewer_actions() << "\n";
195 			create_temp_move();
196 		} else {
197 			LOG_WB << "Whiteboard deactivated!\n";
198 		}
199 	}
200 }
201 
set_invert_behavior(bool invert)202 void manager::set_invert_behavior(bool invert)
203 {
204 	//any more than one reference means a lock on whiteboard state was requested
205 	if(!activation_state_lock_.unique())
206 		return;
207 
208 	bool block_whiteboard_activation = false;
209 	if(!can_activate())
210 	{
211 		 block_whiteboard_activation = true;
212 	}
213 
214 	if (invert)
215 	{
216 		if (!inverted_behavior_)
217 		{
218 			if (active_)
219 			{
220 				DBG_WB << "Whiteboard deactivated temporarily.\n";
221 				inverted_behavior_ = true;
222 				set_active(false);
223 			}
224 			else if (!block_whiteboard_activation)
225 			{
226 				DBG_WB << "Whiteboard activated temporarily.\n";
227 				inverted_behavior_ = true;
228 				set_active(true);
229 			}
230 		}
231 	}
232 	else
233 	{
234 		if (inverted_behavior_)
235 		{
236 			if (active_)
237 			{
238 				DBG_WB << "Whiteboard set back to deactivated status.\n";
239 				inverted_behavior_ = false;
240 				set_active(false);
241 			}
242 			else if (!block_whiteboard_activation)
243 			{
244 				DBG_WB << "Whiteboard set back to activated status.\n";
245 				inverted_behavior_ = false;
246 				set_active(true);
247 			}
248 		}
249 	}
250 }
251 
can_enable_execution_hotkeys() const252 bool manager::can_enable_execution_hotkeys() const
253 {
254 	return can_enable_modifier_hotkeys() && viewer_side() == resources::controller->current_side()
255 			&& viewer_actions()->turn_size(0) > 0;
256 }
257 
can_enable_modifier_hotkeys() const258 bool manager::can_enable_modifier_hotkeys() const
259 {
260 	return can_modify_game_state() && !viewer_actions()->empty();
261 }
262 
can_enable_reorder_hotkeys() const263 bool manager::can_enable_reorder_hotkeys() const
264 {
265 	return can_enable_modifier_hotkeys() && highlighter_ && highlighter_->get_bump_target();
266 }
267 
allow_leader_to_move(const unit & leader) const268 bool manager::allow_leader_to_move(const unit& leader) const
269 {
270 	if(!has_actions())
271 		return true;
272 
273 	//Look for another leader on another keep in the same castle
274 	{ wb::future_map future; // start planned unit map scope
275 
276 		// TODO: when the game executes all whiteboard moves at turn end applying the future map
277 		//       will fail because we are currently executing actions, and if one of those actions
278 		//       was a movement of the leader this function will be called, resulting the the error
279 		//       mesage below, we silence that message for now by adding (!executing_actions_)
280 		//
281 		//       Also this check is generally flawed, for example it could happen that the leader found
282 		//       by find_backup_leader would be moved to that location _after_ the unit would be recruited
283 		//       It could also happen that the original leader can be moved back to that location before
284 		//       the unit is recruited.
285 		if(!has_planned_unit_map() && !executing_actions_) {
286 			WRN_WB << "Unable to build future map to determine whether leader's allowed to move." << std::endl;
287 		}
288 		if(find_backup_leader(leader))
289 			return true;
290 	} // end planned unit map scope
291 
292 	if(viewer_actions()->empty()) {
293 		return true;
294 	}
295 
296 	//Look for planned recruits that depend on this leader
297 	for(action_const_ptr action : *viewer_actions())
298 	{
299 		recruit_const_ptr recruit = std::dynamic_pointer_cast<class recruit const>(action);
300 		recall_const_ptr recall = std::dynamic_pointer_cast<class recall const>(action);
301 		if(recruit || recall)
302 		{
303 			map_location const target_hex = recruit?recruit->get_recruit_hex():recall->get_recall_hex();
304 			if (dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(leader, target_hex))
305 				return false;
306 		}
307 	}
308 	return true;
309 }
310 
on_init_side()311 void manager::on_init_side()
312 {
313 	//Turn should never start with action auto-execution already enabled!
314 	assert(!executing_all_actions_ && !executing_actions_);
315 
316 	update_plan_hiding(); /* validates actions */
317 	wait_for_side_init_ = false;
318 	LOG_WB << "on_init_side()\n";
319 
320 	if (self_activate_once_ && preferences::enable_whiteboard_mode_on_start())
321 	{
322 		self_activate_once_ = false;
323 		set_active(true);
324 	}
325 }
326 
on_finish_side_turn(int side)327 void manager::on_finish_side_turn(int side)
328 {
329 	preparing_to_end_turn_ = false;
330 	wait_for_side_init_ = true;
331 	if(side == viewer_side() && !viewer_actions()->empty()) {
332 		viewer_actions()->synced_turn_shift();
333 	}
334 	highlighter_.reset();
335 	erase_temp_move();
336 	LOG_WB << "on_finish_side_turn()\n";
337 }
338 
pre_delete_action(action_ptr)339 void manager::pre_delete_action(action_ptr)
340 {
341 }
342 
post_delete_action(action_ptr action)343 void manager::post_delete_action(action_ptr action)
344 {
345 	// The fake unit representing the destination of a chain of planned moves should have the regular animation.
346 	// If the last remaining action of the unit that owned this move is a move as well,
347 	// adjust its appearance accordingly.
348 
349 	side_actions_ptr side_actions = resources::gameboard->teams().at(action->team_index()).get_side_actions();
350 
351 	unit_ptr actor = action->get_unit();
352 	if(actor) { // The unit might have died following the execution of an attack
353 		side_actions::iterator action_it = side_actions->find_last_action_of(*actor);
354 		if(action_it != side_actions->end()) {
355 			move_ptr move = std::dynamic_pointer_cast<class move>(*action_it);
356 			if(move && move->get_fake_unit()) {
357 				move->get_fake_unit()->anim_comp().set_standing(true);
358 			}
359 		}
360 	}
361 }
362 
hide_all_plans()363 static void hide_all_plans()
364 {
365 	for(team& t : resources::gameboard->teams()){
366 		t.get_side_actions()->hide();
367 	}
368 }
369 
370 /* private */
update_plan_hiding(size_t team_index)371 void manager::update_plan_hiding(size_t team_index)
372 {
373 	//We don't control the "viewing" side ... we're probably an observer
374 	if(!resources::gameboard->teams().at(team_index).is_local_human())
375 		hide_all_plans();
376 	else // normal circumstance
377 	{
378 		for(team& t : resources::gameboard->teams())
379 		{
380 			//make sure only appropriate teams are hidden
381 			if(!t.is_network_human())
382 				team_plans_hidden_[t.side()-1] = false;
383 
384 			if(t.is_enemy(team_index+1) || team_plans_hidden_[t.side()-1])
385 				t.get_side_actions()->hide();
386 			else
387 				t.get_side_actions()->show();
388 		}
389 	}
390 	validate_viewer_actions();
391 }
update_plan_hiding()392 void manager::update_plan_hiding()
393 	{update_plan_hiding(viewer_team());}
394 
on_viewer_change(size_t team_index)395 void manager::on_viewer_change(size_t team_index)
396 {
397 	if(!wait_for_side_init_)
398 		update_plan_hiding(team_index);
399 }
400 
on_change_controller(int side,const team & t)401 void manager::on_change_controller(int side, const team& t)
402 {
403 	wb::side_actions& sa = *t.get_side_actions();
404 	if(t.is_local_human()) // we own this side now
405 	{
406 		//tell everyone to clear this side's actions -- we're starting anew
407 		queue_net_cmd(sa.team_index(),sa.make_net_cmd_clear());
408 		sa.clear();
409 		//refresh the hidden_ attribute of every team's side_actions
410 		update_plan_hiding();
411 	}
412 	else if(t.is_local_ai() || t.is_network_ai()) // no one owns this side anymore
413 		sa.clear(); // clear its plans away -- the ai doesn't plan ... yet
414 	else if(t.is_network()) // Another client is taking control of the side
415 	{
416 		if(side==viewer_side()) // They're taking OUR side away!
417 			hide_all_plans(); // give up knowledge of everyone's plans, in case we became an observer
418 
419 		//tell them our plans -- they may not have received them up to this point
420 		size_t num_teams = resources::gameboard->teams().size();
421 		for(size_t i=0; i<num_teams; ++i)
422 		{
423 			team& local_team = resources::gameboard->teams().at(i);
424 			if(local_team.is_local_human() && !local_team.is_enemy(side))
425 				queue_net_cmd(i,local_team.get_side_actions()->make_net_cmd_refresh());
426 		}
427 	}
428 }
429 
on_kill_unit()430 void manager::on_kill_unit()
431 {
432 	if(highlighter_ != nullptr) {
433 		highlighter_->set_selection_candidate(unit_ptr());
434 	}
435 }
436 
current_side_has_actions()437 bool manager::current_side_has_actions()
438 {
439 	if(current_side_actions()->empty()) {
440 		return false;
441 	}
442 
443 	side_actions::range_t range = current_side_actions()->iter_turn(0);
444 	return range.first != range.second; //non-empty range
445 }
446 
validate_viewer_actions()447 void manager::validate_viewer_actions()
448 {
449 	LOG_WB << "'gamestate_mutated_' flag dirty, validating actions.\n";
450 	gamestate_mutated_ = false;
451 	if(has_planned_unit_map()) {
452 		real_map();
453 	} else {
454 		future_map();
455 	}
456 }
457 
458 //helper fcn
draw_numbers(const map_location & hex,side_actions::numbers_t numbers)459 static void draw_numbers(const map_location& hex, side_actions::numbers_t numbers)
460 {
461 	std::vector<int>& numbers_to_draw = numbers.numbers_to_draw;
462 	std::vector<size_t>& team_numbers = numbers.team_numbers;
463 	int& main_number = numbers.main_number;
464 	std::set<size_t>& secondary_numbers = numbers.secondary_numbers;
465 
466 	const double x_offset_base = 0.0;
467 	const double y_offset_base = 0.2;
468 	//position 0,0 in the hex is the upper left corner
469 	//0.8 = horizontal coord., close to the right side of the hex
470 	const double x_origin = 0.8 - numbers_to_draw.size() * x_offset_base;
471 	//0.5 = halfway in the hex vertically
472 	const double y_origin = 0.5 - numbers_to_draw.size() * (y_offset_base / 2);
473 	double x_offset = 0, y_offset = 0;
474 
475 	size_t size = numbers_to_draw.size();
476 	for(size_t i=0; i<size; ++i)
477 	{
478 		int number = numbers_to_draw[i];
479 
480 		std::string number_text = std::to_string(number);
481 		size_t font_size;
482 		if (static_cast<int>(i) == main_number) {
483 			font_size = 19;
484 		}
485 		else if (secondary_numbers.find(i)!=secondary_numbers.end()) {
486 			font_size = 17;
487 		}
488 		else {
489 			font_size = 15;
490 		}
491 
492 		color_t color = team::get_side_color(static_cast<int>(team_numbers[i]+1));
493 		const double x_in_hex = x_origin + x_offset;
494 		const double y_in_hex = y_origin + y_offset;
495 		display::get_singleton()->draw_text_in_hex(hex, display::LAYER_ACTIONS_NUMBERING,
496 				number_text, font_size, color, x_in_hex, y_in_hex);
497 		x_offset += x_offset_base;
498 		y_offset += y_offset_base;
499 	}
500 }
501 
502 
503 namespace
504 {
505 	//Helper struct that finds all units teams whose planned actions are currently visible
506 	//Only used by manager::pre_draw().
507 	//Note that this structure is used as a functor.
508 	struct move_owners_finder: public visitor
509 	{
510 
511 	public:
move_owners_finderwb::__anon9bf82fd60111::move_owners_finder512 		move_owners_finder(): move_owners_() { }
513 
operator ()wb::__anon9bf82fd60111::move_owners_finder514 		void operator()(action* action) {
515 			action->accept(*this);
516 		}
517 
get_units_owning_moveswb::__anon9bf82fd60111::move_owners_finder518 		const std::set<size_t>& get_units_owning_moves() {
519 			return move_owners_;
520 		}
521 
visitwb::__anon9bf82fd60111::move_owners_finder522 		virtual void visit(move_ptr move) {
523 			if(size_t id = move->get_unit_id()) {
524 				move_owners_.insert(id);
525 			}
526 		}
527 
visitwb::__anon9bf82fd60111::move_owners_finder528 		virtual void visit(attack_ptr attack) {
529 			//also add attacks if they have an associated move
530 			if(attack->get_route().steps.size() >= 2) {
531 				if(size_t id = attack->get_unit_id()) {
532 					move_owners_.insert(id);
533 				}
534 			}
535 		}
visitwb::__anon9bf82fd60111::move_owners_finder536 		virtual void visit(recruit_ptr){}
visitwb::__anon9bf82fd60111::move_owners_finder537 		virtual void visit(recall_ptr){}
visitwb::__anon9bf82fd60111::move_owners_finder538 		virtual void visit(suppose_dead_ptr){}
539 
540 	private:
541 		std::set<size_t> move_owners_;
542 	};
543 }
544 
pre_draw()545 void manager::pre_draw()
546 {
547 	if (can_modify_game_state() && has_actions() && unit_map_lock_.unique()) {
548 		move_owners_finder move_finder;
549 		for_each_action(std::ref(move_finder));
550 		units_owning_moves_ = move_finder.get_units_owning_moves();
551 
552 		for (size_t unit_id : units_owning_moves_) {
553 			unit_map::iterator unit_iter = resources::gameboard->units().find(unit_id);
554 			if(unit_iter.valid()) {
555 				ghost_owner_unit(&*unit_iter);
556 			}
557 		}
558 	}
559 }
560 
post_draw()561 void manager::post_draw()
562 {
563 	for (size_t unit_id : units_owning_moves_)
564 	{
565 		unit_map::iterator unit_iter = resources::gameboard->units().find(unit_id);
566 		if (unit_iter.valid()) {
567 			unghost_owner_unit(&*unit_iter);
568 		}
569 	}
570 	units_owning_moves_.clear();
571 }
572 
draw_hex(const map_location & hex)573 void manager::draw_hex(const map_location& hex)
574 {
575 	/**
576 	 * IMPORTANT: none of the code in this method can call anything which would
577 	 * cause a hex to be invalidated (i.e. by calling in turn any variant of display::invalidate()).
578 	 * Doing so messes up the iterator currently going over the list of invalidated hexes to draw.
579 	 */
580 
581 	if (!wait_for_side_init_ && has_actions())
582 	{
583 		//call draw() for all actions
584 		for_each_action(std::bind(&action::draw_hex, std::placeholders::_1, hex));
585 
586 		//Info about the action numbers to be displayed on screen.
587 		side_actions::numbers_t numbers;
588 		for (team& t : resources::gameboard->teams())
589 		{
590 			side_actions& sa = *t.get_side_actions();
591 			if(!sa.hidden())
592 				sa.get_numbers(hex,numbers);
593 		}
594 		draw_numbers(hex,numbers); // helper fcn
595 	}
596 
597 }
598 
on_mouseover_change(const map_location & hex)599 void manager::on_mouseover_change(const map_location& hex)
600 {
601 
602 	map_location selected_hex = resources::controller->get_mouse_handler_base().get_selected_hex();
603 	bool hex_has_unit;
604 	{ wb::future_map future; // start planned unit map scope
605 		hex_has_unit = resources::gameboard->units().find(selected_hex) != resources::gameboard->units().end();
606 	} // end planned unit map scope
607 	if (!((selected_hex.valid() && hex_has_unit)
608 			|| has_temp_move() || wait_for_side_init_ || executing_actions_))
609 	{
610 		if (!highlighter_)
611 		{
612 			highlighter_.reset(new highlighter(viewer_actions()));
613 		}
614 		highlighter_->set_mouseover_hex(hex);
615 		highlighter_->highlight();
616 	}
617 }
618 
on_gamestate_change()619 void manager::on_gamestate_change()
620 {
621 	DBG_WB << "Manager received gamestate change notification.\n";
622 	// if on_gamestate_change() is called while the future unit map is applied,
623 	// it means that the future unit map scope is used where it shouldn't be.
624 	assert(!planned_unit_map_active_);
625 	// Set mutated flag so action queue gets validated on next future map build
626 	gamestate_mutated_ = true;
627 	//Clear exclusive draws that might not get a chance to be cleared the normal way
628 	display::get_singleton()->clear_exclusive_draws();
629 }
630 
send_network_data()631 void manager::send_network_data()
632 {
633 	size_t size = net_buffer_.size();
634 	for(size_t team_index=0; team_index<size; ++team_index)
635 	{
636 		config& buf_cfg = net_buffer_[team_index];
637 
638 		if(buf_cfg.empty())
639 			continue;
640 
641 		config packet;
642 		config& wb_cfg = packet.add_child("whiteboard",buf_cfg);
643 		wb_cfg["side"] = static_cast<int>(team_index+1);
644 		wb_cfg["to_sides"] = resources::gameboard->teams().at(team_index).allied_human_teams();
645 
646 		buf_cfg.clear();
647 
648 		resources::controller->send_to_wesnothd(packet, "whiteboard");
649 
650 		size_t count = wb_cfg.child_count("net_cmd");
651 		LOG_WB << "Side " << (team_index+1) << " sent wb data (" << count << " cmds).\n";
652 	}
653 }
654 
process_network_data(const config & cfg)655 void manager::process_network_data(const config& cfg)
656 {
657 	if(const config& wb_cfg = cfg.child("whiteboard"))
658 	{
659 		size_t count = wb_cfg.child_count("net_cmd");
660 		LOG_WB << "Received wb data (" << count << ").\n";
661 
662 		team& team_from = resources::gameboard->get_team(wb_cfg["side"]);
663 		for(const side_actions::net_cmd& cmd : wb_cfg.child_range("net_cmd"))
664 			team_from.get_side_actions()->execute_net_cmd(cmd);
665 	}
666 }
667 
queue_net_cmd(size_t team_index,const side_actions::net_cmd & cmd)668 void manager::queue_net_cmd(size_t team_index, const side_actions::net_cmd& cmd)
669 {
670 	assert(team_index < net_buffer_.size());
671 	net_buffer_[team_index].add_child("net_cmd",cmd);
672 }
673 
create_temp_move()674 void manager::create_temp_move()
675 {
676 	route_.reset();
677 
678 	/*
679 	 * CHECK PRE-CONDITIONS
680 	 * (This section has multiple return paths.)
681 	 */
682 
683 	if ( !active_ || !can_modify_game_state() )
684 		return;
685 
686 	const pathfind::marked_route& route =
687 			resources::controller->get_mouse_handler_base().get_current_route();
688 
689 	if (route.steps.empty() || route.steps.size() < 2) return;
690 
691 	unit* temp_moved_unit =
692 			future_visible_unit(resources::controller->get_mouse_handler_base().get_selected_hex(), viewer_side());
693 	if (!temp_moved_unit) temp_moved_unit =
694 			future_visible_unit(resources::controller->get_mouse_handler_base().get_last_hex(), viewer_side());
695 	if (!temp_moved_unit) return;
696 	if (temp_moved_unit->side() != display::get_singleton()->viewing_side()) return;
697 
698 	/*
699 	 * DONE CHECKING PRE-CONDITIONS, CREATE THE TEMP MOVE
700 	 * (This section has only one return path.)
701 	 */
702 
703 	temp_move_unit_underlying_id_ = temp_moved_unit->underlying_id();
704 
705 	//@todo: May be appropriate to replace these separate components by a temporary
706 	//      wb::move object
707 
708 	route_.reset(new pathfind::marked_route(route));
709 	//NOTE: route_->steps.back() = dst, and route_->steps.front() = src
710 
711 	size_t turn = 0;
712 	std::vector<map_location>::iterator prev_itor = route.steps.begin();
713 	std::vector<map_location>::iterator curr_itor = prev_itor;
714 	std::vector<map_location>::iterator end_itor  = route.steps.end();
715 	for(; curr_itor!=end_itor; ++curr_itor)
716 	{
717 		const map_location& hex = *curr_itor;
718 
719 		//search for end-of-turn marks
720 		pathfind::marked_route::mark_map::const_iterator w =
721 				route.marks.find(hex);
722 		if(w != route.marks.end() && w->second.turns > 0)
723 		{
724 			turn = w->second.turns-1;
725 
726 			if(turn >= move_arrows_.size())
727 				move_arrows_.resize(turn+1);
728 			if(turn >= fake_units_.size())
729 				fake_units_.resize(turn+1);
730 
731 			arrow_ptr& move_arrow = move_arrows_[turn];
732 			fake_unit_ptr& fake_unit = fake_units_[turn];
733 
734 			if(!move_arrow)
735 			{
736 				// Create temp arrow
737 				move_arrow.reset(new arrow());
738 				move_arrow->set_color(team::get_side_color_id(
739 						viewer_side()));
740 				move_arrow->set_style(arrow::STYLE_HIGHLIGHTED);
741 			}
742 
743 			arrow_path_t path(prev_itor,curr_itor+1);
744 			move_arrow->set_path(path);
745 
746 			if(path.size() >= 2)
747 			{
748 				// Bug #20299 demonstrates a situation where an incorrect fake/ghosted unit can be used.
749 				// So before assuming that a pre-existing fake_unit can be re-used, check that its ID matches the unit being moved.
750 				if(!fake_unit || fake_unit.get_unit_ptr()->id() != temp_moved_unit->id())
751 				{
752 					// Create temp ghost unit
753 					fake_unit = fake_unit_ptr(temp_moved_unit->clone(), resources::fake_units);
754 					fake_unit->anim_comp().set_ghosted(true);
755 				}
756 
757 				unit_display::move_unit(path, fake_unit.get_unit_ptr(), false); //get facing right
758 				fake_unit->anim_comp().invalidate(*game_display::get_singleton());
759 				fake_unit->set_location(*curr_itor);
760 				fake_unit->anim_comp().set_ghosted(true);
761 			}
762 			else //zero-hex path -- don't bother drawing a fake unit
763 				fake_unit.reset();
764 
765 			prev_itor = curr_itor;
766 		}
767 	}
768 	//in case path shortens on next step and one ghosted unit has to be removed
769 	int ind = fake_units_.size() - 1;
770 	fake_units_[ind]->anim_comp().invalidate(*game_display::get_singleton());
771 	//toss out old arrows and fake units
772 	move_arrows_.resize(turn+1);
773 	fake_units_.resize(turn+1);
774 }
775 
erase_temp_move()776 void manager::erase_temp_move()
777 {
778 	move_arrows_.clear();
779 	for(const fake_unit_ptr& tmp : fake_units_) {
780 		if(tmp) {
781 			tmp->anim_comp().invalidate(*game_display::get_singleton());
782 		}
783 	}
784 	fake_units_.clear();
785 	route_.reset();
786 	temp_move_unit_underlying_id_ = 0;
787 }
788 
save_temp_move()789 void manager::save_temp_move()
790 {
791 	if (has_temp_move() && !executing_actions_ && !resources::controller->is_linger_mode())
792 	{
793 		side_actions& sa = *viewer_actions();
794 		unit* u = future_visible_unit(route_->steps.front());
795 		assert(u);
796 		size_t first_turn = sa.get_turn_num_of(*u);
797 
798 		validate_viewer_actions();
799 
800 		assert(move_arrows_.size() == fake_units_.size());
801 		size_t size = move_arrows_.size();
802 		for(size_t i=0; i<size; ++i)
803 		{
804 			arrow_ptr move_arrow = move_arrows_[i];
805 			if(!arrow::valid_path(move_arrow->get_path()))
806 				continue;
807 
808 			size_t turn = first_turn + i;
809 
810 			//@todo Using a marked_route here is wrong, since right now it's not marked
811 			//either switch over to a plain route for planned moves, or mark it correctly
812 			pathfind::marked_route route;
813 			route.steps = move_arrow->get_path();
814 			// path_cost() is incomplete as it for example doesn't handle skirmisher, we let the move action generate the costs on it own.
815 			// route.move_cost = path_cost(route.steps,*u);
816 			route.move_cost = -1;
817 
818 			sa.queue_move(turn, *u, route, move_arrow, std::move(fake_units_[i]));
819 		}
820 		erase_temp_move();
821 
822 		LOG_WB << *viewer_actions() << "\n";
823 		print_help_once();
824 	}
825 }
826 
get_temp_move_unit() const827 unit_map::iterator manager::get_temp_move_unit() const
828 {
829 	return resources::gameboard->units().find(temp_move_unit_underlying_id_);
830 }
831 
save_temp_attack(const map_location & attacker_loc,const map_location & defender_loc,int weapon_choice)832 void manager::save_temp_attack(const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
833 {
834 	if (active_ && !executing_actions_ && !resources::controller->is_linger_mode())
835 	{
836 		assert(weapon_choice >= 0);
837 
838 		arrow_ptr move_arrow;
839 		fake_unit_ptr* fake_unit = nullptr;
840 		map_location source_hex;
841 
842 		if (route_ && !route_->steps.empty())
843 		{
844 			//attack-move
845 			assert(move_arrows_.size() == 1);
846 			assert(fake_units_.size() == 1);
847 			move_arrow = move_arrows_.front();
848 			fake_unit = &fake_units_.front();
849 
850 			assert(route_->steps.back() == attacker_loc);
851 			source_hex = route_->steps.front();
852 
853 			(**fake_unit).anim_comp().set_disabled_ghosted(true);
854 		}
855 		else
856 		{
857 			//simple attack
858 			move_arrow.reset(new arrow);
859 			source_hex = attacker_loc;
860 			route_.reset(new pathfind::marked_route);
861 			// We'll pass as parameter a one-hex route with no marks.
862 			route_->steps.push_back(attacker_loc);
863 		}
864 
865 		unit* attacking_unit = future_visible_unit(source_hex);
866 		assert(attacking_unit);
867 
868 		validate_viewer_actions();
869 
870 		side_actions& sa = *viewer_actions();
871 		sa.queue_attack(sa.get_turn_num_of(*attacking_unit), *attacking_unit, defender_loc, weapon_choice, *route_, move_arrow, fake_unit ? std::move(*fake_unit) : fake_unit_ptr());
872 
873 		print_help_once();
874 
875 		display::get_singleton()->invalidate(defender_loc);
876 		display::get_singleton()->invalidate(attacker_loc);
877 		erase_temp_move();
878 		LOG_WB << *viewer_actions() << "\n";
879 	}
880 }
881 
save_recruit(const std::string & name,int side_num,const map_location & recruit_hex)882 bool manager::save_recruit(const std::string& name, int side_num, const map_location& recruit_hex)
883 {
884 	bool created_planned_recruit = false;
885 
886 	if (active_ && !executing_actions_ && !resources::controller->is_linger_mode()) {
887 		if (side_num != display::get_singleton()->viewing_side())
888 		{
889 			LOG_WB <<"manager::save_recruit called for a different side than viewing side.\n";
890 			created_planned_recruit = false;
891 		}
892 		else
893 		{
894 			side_actions& sa = *viewer_actions();
895 			unit* recruiter;
896 			{ wb::future_map raii;
897 				recruiter = find_recruiter(side_num-1,recruit_hex);
898 			} // end planned unit map scope
899 			assert(recruiter);
900 			size_t turn = sa.get_turn_num_of(*recruiter);
901 			sa.queue_recruit(turn,name,recruit_hex);
902 			created_planned_recruit = true;
903 
904 			print_help_once();
905 		}
906 	}
907 	return created_planned_recruit;
908 }
909 
save_recall(const unit & unit,int side_num,const map_location & recall_hex)910 bool manager::save_recall(const unit& unit, int side_num, const map_location& recall_hex)
911 {
912 	bool created_planned_recall = false;
913 
914 	if (active_ && !executing_actions_ && !resources::controller->is_linger_mode())
915 	{
916 		if (side_num != display::get_singleton()->viewing_side())
917 		{
918 			LOG_WB <<"manager::save_recall called for a different side than viewing side.\n";
919 			created_planned_recall = false;
920 		}
921 		else
922 		{
923 			side_actions& sa = *viewer_actions();
924 			size_t turn = sa.num_turns();
925 			if(turn > 0)
926 				--turn;
927 			sa.queue_recall(turn,unit,recall_hex);
928 			created_planned_recall = true;
929 
930 			print_help_once();
931 		}
932 	}
933 	return created_planned_recall;
934 }
935 
save_suppose_dead(unit & curr_unit,const map_location & loc)936 void manager::save_suppose_dead(unit& curr_unit, const map_location& loc)
937 {
938 	if(active_ && !executing_actions_ && !resources::controller->is_linger_mode())
939 	{
940 		validate_viewer_actions();
941 		side_actions& sa = *viewer_actions();
942 		sa.queue_suppose_dead(sa.get_turn_num_of(curr_unit),curr_unit,loc);
943 	}
944 }
945 
contextual_execute()946 void manager::contextual_execute()
947 {
948 	validate_viewer_actions();
949 	if (can_enable_execution_hotkeys())
950 	{
951 		erase_temp_move();
952 
953 		//For exception-safety, this struct sets executing_actions_ to false on destruction.
954 		variable_finalizer<bool> finally(executing_actions_, false);
955 
956 		action_ptr action;
957 		side_actions::iterator it = viewer_actions()->end();
958 		unit const* selected_unit = future_visible_unit(resources::controller->get_mouse_handler_base().get_selected_hex(), viewer_side());
959 		if (selected_unit &&
960 				(it = viewer_actions()->find_first_action_of(*selected_unit)) != viewer_actions()->end())
961 		{
962 			executing_actions_ = true;
963 			viewer_actions()->execute(it);
964 		}
965 		else if (highlighter_ && (action = highlighter_->get_execute_target()) &&
966 				 (it = viewer_actions()->get_position_of(action)) != viewer_actions()->end())
967 		{
968 			executing_actions_ = true;
969 			viewer_actions()->execute(it);
970 		}
971 		else //we already check above for viewer_actions()->empty()
972 		{
973 			executing_actions_ = true;
974 			viewer_actions()->execute_next();
975 		}
976 	} //Finalizer struct sets executing_actions_ to false
977 }
978 
allow_end_turn()979 bool manager::allow_end_turn()
980 {
981 	preparing_to_end_turn_ = true;
982 	return execute_all_actions();
983 }
984 
execute_all_actions()985 bool manager::execute_all_actions()
986 {
987 	if (has_planned_unit_map())
988 	{
989 		ERR_WB << "Modifying action queue while temp modifiers are applied1!!!" << std::endl;
990 	}
991 	//exception-safety: finalizers set variables to false on destruction
992 	//i.e. when method exits naturally or exception is thrown
993 	variable_finalizer<bool> finalize_executing_actions(executing_actions_, false);
994 	variable_finalizer<bool> finalize_executing_all_actions(executing_all_actions_, false);
995 
996 	validate_viewer_actions();
997 	if(viewer_actions()->empty() || viewer_actions()->turn_size(0) == 0)
998 	{
999 		//No actions to execute, job done.
1000 		return true;
1001 	}
1002 
1003 	assert(can_enable_execution_hotkeys());
1004 
1005 	erase_temp_move();
1006 
1007 	// Build unit map once to ensure spent gold and other calculations are refreshed
1008 	set_planned_unit_map();
1009 	assert(has_planned_unit_map());
1010 	set_real_unit_map();
1011 
1012 	executing_actions_ = true;
1013 	executing_all_actions_ = true;
1014 
1015 	side_actions_ptr sa = viewer_actions();
1016 
1017 	if (has_planned_unit_map())
1018 	{
1019 		ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
1020 	}
1021 
1022 	//LOG_WB << "Before executing all actions, " << *sa << "\n";
1023 
1024 	while (sa->turn_begin(0) != sa->turn_end(0))
1025 	{
1026 		bool action_successful = sa->execute(sa->begin());
1027 
1028 		// Interrupt on incomplete action
1029 		if (!action_successful)
1030 		{
1031 			return false;
1032 		}
1033 	}
1034 	return true;
1035 }
1036 
contextual_delete()1037 void manager::contextual_delete()
1038 {
1039 	validate_viewer_actions();
1040 	if(can_enable_modifier_hotkeys()) {
1041 		erase_temp_move();
1042 
1043 		action_ptr action;
1044 		side_actions::iterator it = viewer_actions()->end();
1045 		unit const* selected_unit = future_visible_unit(resources::controller->get_mouse_handler_base().get_selected_hex(), viewer_side());
1046 		if(selected_unit && (it = viewer_actions()->find_last_action_of(*selected_unit)) != viewer_actions()->end()) {
1047 			viewer_actions()->remove_action(it);
1048 			///@todo Shouldn't we probably deselect the unit at this point?
1049 		} else if(highlighter_ && (action = highlighter_->get_delete_target()) && (it = viewer_actions()->get_position_of(action)) != viewer_actions()->end()) {
1050 			viewer_actions()->remove_action(it);
1051 			validate_viewer_actions();
1052 			highlighter_->set_mouseover_hex(highlighter_->get_mouseover_hex());
1053 			highlighter_->highlight();
1054 		} else { //we already check above for viewer_actions()->empty()
1055 			it = (viewer_actions()->end() - 1);
1056 			action = *it;
1057 			viewer_actions()->remove_action(it);
1058 			validate_viewer_actions();
1059 		}
1060 	}
1061 }
1062 
contextual_bump_up_action()1063 void manager::contextual_bump_up_action()
1064 {
1065 	validate_viewer_actions();
1066 	if(can_enable_reorder_hotkeys()) {
1067 		action_ptr action = highlighter_->get_bump_target();
1068 		if(action) {
1069 			viewer_actions()->bump_earlier(viewer_actions()->get_position_of(action));
1070 			validate_viewer_actions(); // Redraw arrows
1071 		}
1072 	}
1073 }
1074 
contextual_bump_down_action()1075 void manager::contextual_bump_down_action()
1076 {
1077 	validate_viewer_actions();
1078 	if(can_enable_reorder_hotkeys()) {
1079 		action_ptr action = highlighter_->get_bump_target();
1080 		if(action) {
1081 			viewer_actions()->bump_later(viewer_actions()->get_position_of(action));
1082 			validate_viewer_actions(); // Redraw arrows
1083 		}
1084 	}
1085 }
1086 
has_actions() const1087 bool manager::has_actions() const
1088 {
1089 	assert(resources::gameboard);
1090 	return wb::has_actions();
1091 }
1092 
unit_has_actions(unit const * unit) const1093 bool manager::unit_has_actions(unit const* unit) const
1094 {
1095 	assert(unit != nullptr);
1096 	assert(resources::gameboard);
1097 	return viewer_actions()->unit_has_actions(*unit);
1098 }
1099 
get_spent_gold_for(int side)1100 int manager::get_spent_gold_for(int side)
1101 {
1102 	if(wait_for_side_init_)
1103 		return 0;
1104 
1105 	return resources::gameboard->get_team(side).get_side_actions()->get_gold_spent();
1106 }
1107 
should_clear_undo() const1108 bool manager::should_clear_undo() const
1109 {
1110 	return resources::controller->is_networked_mp() && resources::controller->current_team().is_local();
1111 }
1112 
options_dlg()1113 void manager::options_dlg()
1114 {
1115 	int v_side = viewer_side();
1116 
1117 	int selection = 0;
1118 
1119 	std::vector<team*> allies;
1120 	std::vector<std::string> options;
1121 	utils::string_map t_vars;
1122 
1123 	options.emplace_back(_("SHOW ALL allies’ plans"));
1124 	options.emplace_back(_("HIDE ALL allies’ plans"));
1125 
1126 	//populate list of networked allies
1127 	for(team &t : resources::gameboard->teams())
1128 	{
1129 		//Exclude enemies, AIs, and local players
1130 		if(t.is_enemy(v_side) || !t.is_network())
1131 			continue;
1132 
1133 		allies.push_back(&t);
1134 
1135 		t_vars["player"] = t.current_player();
1136 		size_t t_index = t.side()-1;
1137 		if(team_plans_hidden_[t_index])
1138 			options.emplace_back(VGETTEXT("Show plans for $player", t_vars));
1139 		else
1140 			options.emplace_back(VGETTEXT("Hide plans for $player", t_vars));
1141 	}
1142 
1143 	gui2::dialogs::simple_item_selector dlg("", _("Whiteboard Options"), options);
1144 	dlg.show();
1145 	selection = dlg.selected_index();
1146 
1147 	if(selection == -1)
1148 		return;
1149 
1150 	switch(selection)
1151 	{
1152 	case 0:
1153 		for(team* t : allies) {
1154 			team_plans_hidden_[t->side()-1]=false;
1155 		}
1156 		break;
1157 	case 1:
1158 		for(team* t : allies) {
1159 			team_plans_hidden_[t->side()-1]=true;
1160 		}
1161 		break;
1162 	default:
1163 		if(selection > 1)
1164 		{
1165 			size_t t_index = allies[selection-2]->side()-1;
1166 			//toggle ...
1167 			bool hidden = team_plans_hidden_[t_index];
1168 			team_plans_hidden_[t_index] = !hidden;
1169 		}
1170 		break;
1171 	}
1172 	update_plan_hiding();
1173 }
1174 
set_planned_unit_map()1175 void manager::set_planned_unit_map()
1176 {
1177 	if (!can_modify_game_state()) {
1178 		LOG_WB << "Not building planned unit map: cannot modify game state now.\n";
1179 		return;
1180 	}
1181 	//any more than one reference means a lock on unit map was requested
1182 	if(!unit_map_lock_.unique()) {
1183 		LOG_WB << "Not building planned unit map: unit map locked.\n";
1184 		return;
1185 	}
1186 	if (planned_unit_map_active_) {
1187 		WRN_WB << "Not building planned unit map: already set." << std::endl;
1188 		return;
1189 	}
1190 
1191 	log_scope2("whiteboard", "Building planned unit map");
1192 	mapbuilder_.reset(new mapbuilder(resources::gameboard->units()));
1193 	mapbuilder_->build_map();
1194 
1195 	planned_unit_map_active_ = true;
1196 }
1197 
set_real_unit_map()1198 void manager::set_real_unit_map()
1199 {
1200 	if (planned_unit_map_active_)
1201 	{
1202 		assert(!executing_actions_);
1203 		assert(!wait_for_side_init_);
1204 		if(mapbuilder_)
1205 		{
1206 			log_scope2("whiteboard", "Restoring regular unit map.");
1207 			mapbuilder_.reset();
1208 		}
1209 		planned_unit_map_active_ = false;
1210 	}
1211 	else
1212 	{
1213 		LOG_WB << "Not disabling planned unit map: already disabled.\n";
1214 	}
1215 }
1216 
validate_actions_if_needed()1217 void manager::validate_actions_if_needed()
1218 {
1219 	if (gamestate_mutated_)	{
1220 		validate_viewer_actions();
1221 	}
1222 }
1223 
future_map()1224 future_map::future_map():
1225 		initial_planned_unit_map_(resources::whiteboard && resources::whiteboard->has_planned_unit_map())
1226 {
1227 	if (!resources::whiteboard)
1228 		return;
1229 	if (!initial_planned_unit_map_)
1230 		resources::whiteboard->set_planned_unit_map();
1231 	// check if if unit map was successfully applied
1232 	if (!resources::whiteboard->has_planned_unit_map()) {
1233 		DBG_WB << "Scoped future unit map failed to apply.\n";
1234 	}
1235 }
1236 
~future_map()1237 future_map::~future_map()
1238 {
1239 	try {
1240 	if (!resources::whiteboard)
1241 		return;
1242 	if (!initial_planned_unit_map_ && resources::whiteboard->has_planned_unit_map())
1243 		resources::whiteboard->set_real_unit_map();
1244 	} catch (...) {}
1245 }
1246 
future_map_if_active()1247 future_map_if_active::future_map_if_active():
1248 		initial_planned_unit_map_(resources::whiteboard && resources::whiteboard->has_planned_unit_map()),
1249 		whiteboard_active_(resources::whiteboard && resources::whiteboard->is_active())
1250 {
1251 	if (!resources::whiteboard)
1252 		return;
1253 	if (!whiteboard_active_)
1254 		return;
1255 	if (!initial_planned_unit_map_)
1256 		resources::whiteboard->set_planned_unit_map();
1257 	// check if if unit map was successfully applied
1258 	if (!resources::whiteboard->has_planned_unit_map()) {
1259 		DBG_WB << "Scoped future unit map failed to apply.\n";
1260 	}
1261 }
1262 
~future_map_if_active()1263 future_map_if_active::~future_map_if_active()
1264 {
1265 	try {
1266 	if (!resources::whiteboard)
1267 		return;
1268 	if (!initial_planned_unit_map_ && resources::whiteboard->has_planned_unit_map())
1269 		resources::whiteboard->set_real_unit_map();
1270 	} catch (...) {}
1271 }
1272 
1273 
real_map()1274 real_map::real_map():
1275 		initial_planned_unit_map_(resources::whiteboard && resources::whiteboard->has_planned_unit_map()),
1276 		unit_map_lock_(resources::whiteboard ? resources::whiteboard->unit_map_lock_ : std::make_shared<bool>(false))
1277 {
1278 	if (!resources::whiteboard)
1279 		return;
1280 	if (initial_planned_unit_map_)
1281 		resources::whiteboard->set_real_unit_map();
1282 }
1283 
~real_map()1284 real_map::~real_map()
1285 {
1286 	if (!resources::whiteboard)
1287 		return;
1288 	assert(!resources::whiteboard->has_planned_unit_map());
1289 	if (initial_planned_unit_map_)
1290 	{
1291 		resources::whiteboard->set_planned_unit_map();
1292 	}
1293 }
1294 
1295 } // end namespace wb
1296