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 <set>
20 #include <sstream>
21 #include <iterator>
22 
23 #include "whiteboard/side_actions.hpp"
24 
25 #include "whiteboard/action.hpp"
26 #include "whiteboard/attack.hpp"
27 #include "whiteboard/manager.hpp"
28 #include "whiteboard/mapbuilder.hpp"
29 #include "whiteboard/move.hpp"
30 #include "whiteboard/recall.hpp"
31 #include "whiteboard/recruit.hpp"
32 #include "whiteboard/suppose_dead.hpp"
33 #include "whiteboard/highlighter.hpp"
34 #include "whiteboard/utility.hpp"
35 
36 #include "actions/create.hpp"
37 #include "actions/undo.hpp"
38 #include "display.hpp"
39 #include "game_end_exceptions.hpp"
40 #include "game_state.hpp"
41 #include "map/map.hpp"
42 #include "play_controller.hpp"
43 #include "resources.hpp"
44 #include "units/unit.hpp"
45 #include "utils/iterable_pair.hpp"
46 
47 namespace wb
48 {
49 
50 /** Dumps side_actions on a stream, for debug purposes. */
operator <<(std::ostream & out,const wb::side_actions & side_actions)51 std::ostream &operator<<(std::ostream &out, const wb::side_actions& side_actions)
52 {
53 	out << "Content of side_actions:";
54 	for(size_t turn = 0; turn < side_actions.num_turns(); ++turn) {
55 		out << "\n  Turn " << turn;
56 
57 		int count = 1;
58 		for(wb::side_actions::const_iterator it = side_actions.turn_begin(turn); it != side_actions.turn_end(turn); ++it) {
59 			out << "\n    (" << count++ << ") " << *it;
60 		}
61 
62 		if(side_actions.turn_size(turn) == 0) {
63 			out << "\n    (empty)";
64 		}
65 	}
66 
67 	if(side_actions.empty()) {
68 		out << " (empty)";
69 	}
70 
71 	return out;
72 }
73 
side_actions_container()74 side_actions_container::side_actions_container()
75 	: actions_()
76 	, turn_beginnings_()
77 {
78 }
79 
get_turn_impl(size_t begin,size_t end,const_iterator it) const80 size_t side_actions_container::get_turn_impl(size_t begin, size_t end, const_iterator it) const
81 {
82 	if(begin+1 >= end) {
83 		if(begin+1 != end) {
84 			ERR_WB << "get_turn: begin >= end\n";
85 		}
86 		else if(it < turn_beginnings_[begin]) {
87 			ERR_WB << "get_turn failed\n";
88 		}
89 		return begin;
90 	}
91 	size_t mid = (begin+end) / 2;
92 	if(it < turn_beginnings_[mid]) {
93 		return get_turn_impl(begin, mid, it);
94 	} else {
95 		return get_turn_impl(mid, end, it);
96 	}
97 }
98 
get_turn(const_iterator it) const99 size_t side_actions_container::get_turn(const_iterator it) const
100 {
101 	return get_turn_impl(0, num_turns(), it);
102 }
103 
position_in_turn(const_iterator it) const104 size_t side_actions_container::position_in_turn(const_iterator it) const
105 {
106 	return it - turn_begin( get_turn(it) );
107 }
108 
turn_begin(size_t turn_num)109 side_actions_container::iterator side_actions_container::turn_begin(size_t turn_num){
110 	if(turn_num >= num_turns()) {
111 		return end();
112 	} else {
113 		return turn_beginnings_[turn_num];
114 	}
115 }
116 
turn_begin(size_t turn_num) const117 side_actions_container::const_iterator side_actions_container::turn_begin(size_t turn_num) const
118 {
119 	if(turn_num >= num_turns()) {
120 		return end();
121 	} else {
122 		return turn_beginnings_[turn_num];
123 	}
124 }
125 
push_front(size_t turn,action_ptr action)126 side_actions_container::iterator side_actions_container::push_front(size_t turn, action_ptr action){
127 	if(turn_size(turn) == 0) {
128 		return queue(turn, action);
129 	}
130 
131 	iterator res = insert(turn_begin(turn), action);
132 	if(res != end()) {
133 		bool current_turn_unplanned = turn_size(0) == 0;
134 		turn_beginnings_[turn] = res;
135 
136 		if(current_turn_unplanned && turn == 1) {
137 			turn_beginnings_.front() = res;
138 		}
139 	}
140 	return res;
141 }
142 
insert(iterator position,action_ptr action)143 side_actions_container::iterator side_actions_container::insert(iterator position, action_ptr action)
144 {
145 	assert(position <= end());
146 
147 	bool first = position == begin();
148 
149 	std::pair<iterator,bool> res = actions_.insert(position, action);
150 	if(!res.second) {
151 		return end();
152 	}
153 	if(first) {
154 		// If we are inserting before the first action, then the inserted action should became the first of turn 0.
155 		turn_beginnings_.front() = begin();
156 	}
157 	return res.first;
158 }
159 
queue(size_t turn_num,action_ptr action)160 side_actions_container::iterator side_actions_container::queue(size_t turn_num, action_ptr action)
161 {
162 	// Are we inserting an action in the future while the current turn is empty?
163 	// That is, are we in the sole case where an empty turn can be followed by a non-empty one.
164 	bool future_only = turn_num == 1 && num_turns() == 0;
165 
166 	bool current_turn_unplanned = turn_size(0) == 0;
167 
168 	//for a little extra safety, since we should never resize by much at a time
169 	assert(turn_num <= num_turns() || future_only);
170 
171 	std::pair<iterator,bool> res = actions_.insert(turn_end(turn_num), action);
172 	if(!res.second) {
173 		return end();
174 	}
175 
176 	if(future_only) {
177 		// No action are planned for the current turn but we are planning an action for turn 1 (the next turn).
178 		turn_beginnings_.push_back(res.first);
179 	}
180 	if(turn_num >= num_turns()) {
181 		turn_beginnings_.push_back(res.first);
182 	} else if(current_turn_unplanned && turn_num == 0) {
183 		// We are planning the first action of the current turn while others actions are planned in the future.
184 		turn_beginnings_.front() = res.first;
185 	}
186 
187 	return res.first;
188 }
189 
bump_earlier(iterator position)190 side_actions_container::iterator side_actions_container::bump_earlier(iterator position)
191 {
192 	assert(position > begin());
193 	assert(position < end());
194 
195 	action_ptr rhs = *position;
196 	action_ptr lhs = *(position - 1);
197 
198 	actions_.replace(position, lhs);
199 	actions_.replace(position - 1, rhs);
200 	return position - 1;
201 }
202 
bump_later(iterator position)203 side_actions_container::iterator side_actions_container::bump_later(iterator position)
204 {
205 	return bump_earlier(position + 1);
206 }
207 
erase(iterator position)208 side_actions_container::iterator side_actions_container::erase(iterator position)
209 {
210 	//precondition
211 	assert(position < end());
212 
213 	//prepare
214 	iterator next = position + 1;
215 	bool deleting_last_element = next == end();
216 
217 	// pre-processing (check if position is at the beginning of a turn)
218 	action_limits::iterator beginning = std::find(turn_beginnings_.begin(), turn_beginnings_.end(), position);
219 	if(beginning != turn_beginnings_.end()) {
220 		if(deleting_last_element) {
221 			if(size() == 1) {
222 				// If we are deleting our sole action, we can clear turn_beginnings_ (and we have to if this last action is in turn 1)
223 				turn_beginnings_.clear();
224 			} else {
225 				// Otherwise, we just delete the last turn
226 				turn_beginnings_.pop_back();
227 			}
228 		} else {
229 #if 1
230 			for(auto& it : turn_beginnings_) {
231 				if (it == position) {
232 					it = next;
233 				}
234 			}
235 #else
236 			size_t turn_of_position = std::distance(turn_beginnings_.begin(), beginning);
237 			// If we still have action this turn
238 			if(get_turn(next) == turn_of_position) {
239 				*beginning = next; // We modify the beginning of the turn
240 			} else {
241 				assert(turn_of_position == 0);
242 				*beginning = turn_end(0); // Otherwise, we are emptying the current turn.
243 			}
244 #endif
245 		}
246 	}
247 
248 	//erase!
249 	return actions_.erase(position);
250 }
251 
erase(iterator first,iterator last)252 side_actions_container::iterator side_actions_container::erase(iterator first, iterator last){
253 	// @todo rewrite using boost::multi_index::erase(iterator,iterator) for efficiency.
254 	if(first>=last) {
255 		return last;
256 	}
257 	for(iterator it = last-1; it>first; --it) {
258 		it = erase(it);
259 	}
260 	return erase(first);
261 }
262 
263 
side_actions()264 side_actions::side_actions()
265 	: actions_()
266 	, team_index_(0)
267 	, team_index_defined_(false)
268 	, gold_spent_(0)
269 	, hidden_(false)
270 {
271 }
272 
set_team_index(size_t team_index)273 void side_actions::set_team_index(size_t team_index)
274 {
275 	assert(!team_index_defined_);
276 	team_index_ = team_index;
277 	team_index_defined_ = true;
278 }
279 
get_numbers(const map_location & hex,numbers_t & result)280 void side_actions::get_numbers(const map_location& hex, numbers_t& result)
281 {
282 	if(empty()) {
283 		return;
284 	}
285 
286 	std::vector<int>& numbers_to_draw = result.numbers_to_draw;
287 	std::vector<size_t>& team_numbers = result.team_numbers;
288 	int& main_number = result.main_number;
289 	std::set<size_t>& secondary_numbers = result.secondary_numbers;
290 	std::shared_ptr<highlighter> hlighter = resources::whiteboard->get_highlighter().lock();
291 
292 	for(const_iterator it = begin(); it != end(); ++it) {
293 		if((*it)->is_numbering_hex(hex)) {
294 			//store number corresponding to iterator's position + 1
295 			size_t number = (it - begin()) + 1;
296 			size_t index = numbers_to_draw.size();
297 			numbers_to_draw.push_back(number);
298 			team_numbers.push_back(team_index());
299 
300 			if(hlighter) {
301 				if(hlighter->get_main_highlight().lock() == *it) {
302 					main_number = index;
303 				}
304 
305 				for(weak_action_ptr action : hlighter->get_secondary_highlights()) {
306 					if(action.lock() == *it) {
307 						secondary_numbers.insert(index);
308 					}
309 				}
310 			}
311 		}
312 	}
313 }
314 
execute_next()315 bool side_actions::execute_next()
316 {
317 	if(!empty()) {
318 		return execute(begin());
319 	} else { //nothing is executable right now
320 		return false;
321 	}
322 }
323 
execute(side_actions::iterator position)324 bool side_actions::execute(side_actions::iterator position)
325 {
326 	if(resources::whiteboard->has_planned_unit_map()) {
327 		ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
328 	}
329 
330 	if(actions_.empty() || position == actions_.end()) {
331 		return false;
332 	}
333 
334 	assert(position < turn_end(0)); //can't execute actions from future turns
335 
336 	LOG_WB << "Before execution, " << *this << "\n";
337 
338 	action_ptr action = *position;
339 
340 	if(!action->valid()) {
341 		LOG_WB << "Invalid action sent to execution, deleting.\n";
342 		synced_erase(position);
343 		return true;
344 	}
345 
346 	bool action_successful;
347 	// Determines whether action should be deleted. Interrupted moves return action_complete == false.
348 	bool action_complete;
349 	try	{
350 		 action->execute(action_successful, action_complete);
351 	} catch (const return_to_play_side_exception&) {
352 		synced_erase(position);
353 		LOG_WB << "End turn exception caught during execution, deleting action. " << *this << "\n";
354 		//validate actions at next map rebuild
355 		resources::whiteboard->on_gamestate_change();
356 		throw;
357 	}
358 
359 	if(resources::whiteboard->should_clear_undo()) {
360 		if(resources::controller->current_team().auto_shroud_updates()) {
361 			resources::undo_stack->clear();
362 		}
363 		else {
364 			WRN_WB << "not clearing undo stack because dsu is active\n";
365 		}
366 	}
367 
368 	std::stringstream ss;
369 	ss << "After " << (action_successful? "successful": "failed") << " execution ";
370 	if(action_complete) {
371 		ss << "with deletion, ";
372 		synced_erase(position);
373 	}
374 	else { //action may have revised itself; let's tell our allies.
375 		ss << "without deletion, ";
376 		resources::whiteboard->queue_net_cmd(team_index_,make_net_cmd_replace(position,*position));
377 
378 		//Idea that needs refining: move action at the end of the queue if it failed executing:
379 			//actions_.erase(position);
380 			//actions_.insert(end(), action);
381 	}
382 	ss << *this << "\n";
383 	LOG_WB << ss.str();
384 
385 	resources::whiteboard->validate_viewer_actions();
386 	return action_successful;
387 }
388 
hide()389 void side_actions::hide()
390 {
391 	if(hidden_) {
392 		return;
393 	}
394 
395 	hidden_ = true;
396 
397 	for(action_ptr act : *this) {
398 		act->hide();
399 	}
400 }
show()401 void side_actions::show()
402 {
403 	if(!hidden_) {
404 		return;
405 	}
406 
407 	hidden_ = false;
408 
409 	for(action_ptr act : *this) {
410 		act->show();
411 	}
412 }
413 
insert_action(iterator position,action_ptr action)414 side_actions::iterator side_actions::insert_action(iterator position, action_ptr action)
415 {
416 	if(resources::whiteboard->has_planned_unit_map()) {
417 		ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
418 	}
419 	iterator valid_position = synced_insert(position, action);
420 	LOG_WB << "Inserted into turn #" << get_turn(valid_position) << " at position #"
421 			<< actions_.position_in_turn(valid_position) << " : " << action <<"\n";
422 	resources::whiteboard->validate_viewer_actions();
423 	return valid_position;
424 }
425 
queue_action(size_t turn_num,action_ptr action)426 side_actions::iterator side_actions::queue_action(size_t turn_num, action_ptr action)
427 {
428 	if(resources::whiteboard->has_planned_unit_map()) {
429 		ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
430 	}
431 	iterator result = synced_enqueue(turn_num, action);
432 	LOG_WB << "Queue into turn #" << turn_num << " : " << action <<"\n";
433 	resources::whiteboard->validate_viewer_actions();
434 	return result;
435 }
436 
437 namespace
438 {
439 	/**
440 	 * Check whether a move is swapable with a given action.
441 	 */
442 	struct swapable_with_move: public visitor
443 	{
444 	public:
swapable_with_movewb::__anonfd8ec0900111::swapable_with_move445 		swapable_with_move(side_actions &sa, side_actions::iterator position, move_ptr second): sa_(sa), valid_(false), position_(position), second_(second) {}
validwb::__anonfd8ec0900111::swapable_with_move446 		bool valid() const { return valid_; }
447 
visitwb::__anonfd8ec0900111::swapable_with_move448 		void visit(move_ptr first) {
449 			valid_ = second_->get_dest_hex() != first->get_source_hex();
450 		}
451 
visitwb::__anonfd8ec0900111::swapable_with_move452 		void visit(attack_ptr first) {
453 			visit(std::static_pointer_cast<move>(first));
454 		}
455 
visitwb::__anonfd8ec0900111::swapable_with_move456 		void visit(recruit_ptr first) {
457 			check_recruit_recall(first->get_recruit_hex());
458 		}
459 
visitwb::__anonfd8ec0900111::swapable_with_move460 		void visit(recall_ptr first) {
461 			check_recruit_recall(first->get_recall_hex());
462 		}
463 
visitwb::__anonfd8ec0900111::swapable_with_move464 		void visit(suppose_dead_ptr) {
465 			valid_ = true;
466 		}
467 
468 	private:
469 		side_actions &sa_;
470 		bool valid_;
471 		side_actions::iterator position_;
472 		move_ptr second_;
473 
check_recruit_recallwb::__anonfd8ec0900111::swapable_with_move474 		void check_recruit_recall(const map_location &loc) {
475 			const unit_const_ptr leader = second_->get_unit();
476 			if(leader->can_recruit() && dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(*leader, loc)) {
477 				if(const unit_const_ptr backup_leader = find_backup_leader(*leader)) {
478 					side_actions::iterator it = sa_.find_first_action_of(*backup_leader);
479 					if(!(it == sa_.end() || position_ < it)) {
480 						return; //backup leader but he moves before us, refuse bump
481 					}
482 				} else {
483 					return; //no backup leader, refuse bump
484 				}
485 			}
486 			valid_ = true;
487 		}
488 	};
489 }
490 
491 //move action toward front of queue
bump_earlier(side_actions::iterator position,bool send_to_net)492 side_actions::iterator side_actions::bump_earlier(side_actions::iterator position, bool send_to_net)
493 {
494 	if(resources::whiteboard->has_planned_unit_map()) {
495 		ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
496 	}
497 
498 	assert(position <= end());
499 
500 	//Don't allow bumping the very first action any earlier, of course.
501 	//Also, don't allow bumping an action into a previous turn queue
502 	if(actions_.position_in_turn(position) == 0) {
503 		return end();
504 	}
505 
506 	side_actions::iterator previous = position - 1;
507 
508 	//Verify we're not moving an action out-of-order compared to other action of the same unit
509 	const unit_const_ptr previous_ptr = (*previous)->get_unit();
510 	const unit_const_ptr current_ptr = (*position)->get_unit();
511 	if(previous_ptr && current_ptr && previous_ptr.get() == current_ptr.get()) {
512 		return end();
513 	}
514 
515 	if(move_ptr second = std::dynamic_pointer_cast<move>(*position)) {
516 		swapable_with_move check(*this, position, second);
517 		(*previous)->accept(check);
518 		if(!check.valid()) {
519 			return end();
520 		}
521 	}
522 
523 	LOG_WB << "Before bumping earlier, " << *this << "\n";
524 
525 	int turn_number = get_turn(position);
526 	int action_number = actions_.position_in_turn(position);
527 	int last_position = turn_size(turn_number) - 1;
528 	LOG_WB << "In turn #" << turn_number
529 			<< ", bumping action #" << action_number << "/" << last_position
530 			<< " to position #" << action_number - 1  << "/" << last_position << ".\n";
531 
532 	if (send_to_net) {
533 		resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_bump_later(position - 1));
534 	}
535 	actions_.bump_earlier(position);
536 
537 	LOG_WB << "After bumping earlier, " << *this << "\n";
538 	return position - 1;
539 }
540 
541 //move action toward back of queue
bump_later(iterator position,bool send_to_net)542 side_actions::iterator side_actions::bump_later(iterator position, bool send_to_net)
543 {
544 	assert(position < end());
545 
546 	++position;
547 	if(position == end()) {
548 		return end();
549 	}
550 	position = bump_earlier(position, send_to_net);
551 	if(position == end()) {
552 		return end();
553 	}
554 	return position + 1;
555 }
556 
remove_action(side_actions::iterator position,bool validate_after_delete)557 side_actions::iterator side_actions::remove_action(side_actions::iterator position, bool validate_after_delete)
558 {
559 	if(resources::whiteboard->has_planned_unit_map()) {
560 		ERR_WB << "Modifying action queue while temp modifiers are applied!!!" << std::endl;
561 	}
562 
563 	assert(position < end());
564 
565 	LOG_WB << "Erasing action at turn #" << get_turn(position) << " position #" << actions_.position_in_turn(position) << "\n";
566 
567 
568 	if(resources::gameboard->get_team(team_index_ + 1).is_local()) {
569 		position = synced_erase(position);
570 	}
571 	else {
572 		// don't sync actions of sides that we don't control, this would only generate
573 		// 'illegal whiteboard data' server wanrings.
574 		// it might be better to instead don't even erase the action in this case to keep
575 		// the actionlist in sync with the owner client.
576 		position = safe_erase(position);
577 	}
578 
579 
580 	if(validate_after_delete) {
581 		resources::whiteboard->validate_viewer_actions();
582 	}
583 
584 	return position;
585 }
586 
find_first_action_at(map_location hex)587 side_actions::iterator side_actions::find_first_action_at(map_location hex)
588 {
589 	return find_first_action_of(actions_.get<container::by_hex>().equal_range(hex), begin(), std::less<iterator>());
590 }
591 
find_first_action_of(size_t unit_id,side_actions::iterator start_position)592 side_actions::iterator side_actions::find_first_action_of(size_t unit_id, side_actions::iterator start_position)
593 {
594 	return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit_id), start_position, std::less<iterator>());
595 }
596 
find_last_action_of(size_t unit_id,side_actions::const_iterator start_position) const597 side_actions::const_iterator side_actions::find_last_action_of(size_t unit_id, side_actions::const_iterator start_position) const {
598 	return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit_id), start_position, std::greater<iterator>());
599 }
600 
find_last_action_of(size_t unit_id,side_actions::iterator start_position)601 side_actions::iterator side_actions::find_last_action_of(size_t unit_id, side_actions::iterator start_position)
602 {
603 	return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit_id), start_position, std::greater<iterator>());
604 }
605 
find_last_action_of(size_t unit_id) const606 side_actions::const_iterator side_actions::find_last_action_of(size_t unit_id) const
607 {
608 	if(end() == begin()) {
609 		return end();
610 	}
611 	return find_last_action_of(unit_id, end() - 1);
612 }
613 
find_last_action_of(size_t unit_id)614 side_actions::iterator side_actions::find_last_action_of(size_t unit_id)
615 {
616 	if(end() == begin()) {
617 		return end();
618 	}
619 	return find_last_action_of(unit_id, end() - 1);
620 }
621 
find_first_action_of(const unit & unit,side_actions::iterator start_position)622 side_actions::iterator side_actions::find_first_action_of(const unit& unit, side_actions::iterator start_position)
623 {
624 	return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit.underlying_id()), start_position, std::less<iterator>());
625 }
626 
find_last_action_of(const unit & unit,side_actions::const_iterator start_position) const627 side_actions::const_iterator side_actions::find_last_action_of(const unit& unit, side_actions::const_iterator start_position) const {
628 	return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit.underlying_id()), start_position, std::greater<iterator>());
629 }
630 
find_last_action_of(const unit & unit,side_actions::iterator start_position)631 side_actions::iterator side_actions::find_last_action_of(const unit& unit, side_actions::iterator start_position)
632 {
633 	return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit.underlying_id()), start_position, std::greater<iterator>());
634 }
635 
find_last_action_of(const unit & unit) const636 side_actions::const_iterator side_actions::find_last_action_of(const unit& unit) const
637 {
638 	if(end() == begin()) {
639 		return end();
640 	}
641 	return find_last_action_of(unit, end() - 1);
642 }
643 
find_last_action_of(const unit & unit)644 side_actions::iterator side_actions::find_last_action_of(const unit& unit)
645 {
646 	if(end() == begin()) {
647 		return end();
648 	}
649 	return find_last_action_of(unit, end() - 1);
650 }
651 
unit_has_actions(const unit & unit)652 bool side_actions::unit_has_actions(const unit& unit)
653 {
654 	return actions_.get<container::by_unit>().find(unit.underlying_id()) != actions_.get<container::by_unit>().end();
655 }
656 
count_actions_of(const unit & unit)657 size_t side_actions::count_actions_of(const unit& unit)
658 {
659 	return actions_.get<container::by_unit>().count(unit.underlying_id());
660 }
661 
actions_of(const unit & target)662 std::deque<action_ptr> side_actions::actions_of(const unit& target)
663 {
664 	typedef container::action_set::index<container::by_unit>::type::iterator unit_iterator;
665 	std::pair<unit_iterator, unit_iterator> action_its = actions_.get<container::by_unit>().equal_range(target.underlying_id());
666 
667 	std::deque<action_ptr> actions (action_its.first, action_its.second);
668 	return actions;
669 }
670 
get_turn_num_of(const unit & u) const671 size_t side_actions::get_turn_num_of(const unit& u) const
672 {
673 	const_iterator itor = find_last_action_of(u);
674 	if(itor == end()) {
675 		return 0;
676 	}
677 	return get_turn(itor);
678 }
679 
change_gold_spent_by(int difference)680 void side_actions::change_gold_spent_by(int difference)
681 {
682 	DBG_WB << "Changing gold spent for side " << (team_index() + 1)	<< "; old value: "
683 			<< gold_spent_ << "; new value: " << (gold_spent_ + difference) << "\n";
684 	gold_spent_ += difference; assert(gold_spent_ >= 0);
685 }
686 
reset_gold_spent()687 void side_actions::reset_gold_spent()
688 {
689 	DBG_WB << "Resetting gold spent for side " << (team_index() + 1) << " to 0.\n";
690 	gold_spent_ = 0;
691 }
692 
update_recruited_unit(std::size_t old_id,unit & new_unit)693 void side_actions::update_recruited_unit(std::size_t old_id, unit& new_unit)
694 {
695 	for(const_iterator it = begin(); it != end(); ++it) {
696 		if(move_ptr mp = std::dynamic_pointer_cast<move>(*it)) {
697 			if(mp->raw_uid() == old_id) {
698 				actions_.modify(it, [&](action_ptr& p) {
699 					static_cast<move&>(*p).modify_unit(new_unit);
700 				});
701 			}
702 		}
703 	}
704 }
705 
706 
safe_insert(size_t turn,size_t pos,action_ptr act)707 side_actions::iterator side_actions::safe_insert(size_t turn, size_t pos, action_ptr act)
708 {
709 	assert(act);
710 	if(pos == 0) {
711 		return actions_.push_front(turn, act);
712 	} else {
713 		return actions_.insert(turn_begin(turn) + pos, act);
714 	}
715 }
716 
synced_erase(iterator itor)717 side_actions::iterator side_actions::synced_erase(iterator itor)
718 {
719 	resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_remove(itor));
720 	return safe_erase(itor);
721 }
722 
synced_insert(iterator itor,action_ptr act)723 side_actions::iterator side_actions::synced_insert(iterator itor, action_ptr act)
724 {
725 	resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_insert(itor, act));
726 	return actions_.insert(itor, act);
727 }
728 
synced_enqueue(size_t turn_num,action_ptr act)729 side_actions::iterator side_actions::synced_enqueue(size_t turn_num, action_ptr act)
730 {
731 	//raw_enqueue() creates actions_[turn_num] if it doesn't exist already, so we
732 	//have to do it first -- before subsequently calling actions_[turn_num].size().
733 	iterator result = actions_.queue(turn_num, act);
734 	if(result != end()) {
735 		resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_insert(turn_num, turn_size(turn_num) - 1, act));
736 		// The insert position is turn_size(turn_num)-1 since we already inserted the action.
737 	}
738 	return result;
739 }
740 
safe_erase(const iterator & itor)741 side_actions::iterator side_actions::safe_erase(const iterator& itor)
742 {
743 	action_ptr action = *itor;
744 	resources::whiteboard->pre_delete_action(action); //misc cleanup
745 	iterator return_itor = actions_.erase(itor);
746 	resources::whiteboard->post_delete_action(action);
747 	return return_itor;
748 }
queue_move(size_t turn,unit & mover,const pathfind::marked_route & route,arrow_ptr arrow,fake_unit_ptr fake_unit)749 side_actions::iterator side_actions::queue_move(size_t turn, unit& mover, const pathfind::marked_route& route, arrow_ptr arrow, fake_unit_ptr fake_unit)
750 {
751 	move_ptr new_move(std::make_shared<move>(team_index(), hidden_, std::ref(mover), route, arrow, std::move(fake_unit)));
752 	return queue_action(turn, new_move);
753 }
754 
queue_attack(size_t turn,unit & mover,const map_location & target_hex,int weapon_choice,const pathfind::marked_route & route,arrow_ptr arrow,fake_unit_ptr fake_unit)755 side_actions::iterator side_actions::queue_attack(size_t turn, unit& mover, const map_location& target_hex, int weapon_choice, const pathfind::marked_route& route, arrow_ptr arrow, fake_unit_ptr fake_unit)
756 {
757 	attack_ptr new_attack(std::make_shared<attack>(team_index(), hidden_, std::ref(mover), target_hex, weapon_choice, route, arrow, std::move(fake_unit)));
758 	return queue_action(turn, new_attack);
759 }
760 
queue_recruit(size_t turn,const std::string & unit_name,const map_location & recruit_hex)761 side_actions::iterator side_actions::queue_recruit(size_t turn, const std::string& unit_name, const map_location& recruit_hex)
762 {
763 	recruit_ptr new_recruit(std::make_shared<recruit>(team_index(), hidden_, unit_name, recruit_hex));
764 	return queue_action(turn, new_recruit);
765 }
766 
queue_recall(size_t turn,const unit & unit,const map_location & recall_hex)767 side_actions::iterator side_actions::queue_recall(size_t turn, const unit& unit, const map_location& recall_hex)
768 {
769 	recall_ptr new_recall(std::make_shared<recall>(team_index(), hidden_, unit, recall_hex));
770 	return queue_action(turn, new_recall);
771 }
772 
queue_suppose_dead(size_t turn,unit & curr_unit,const map_location & loc)773 side_actions::iterator side_actions::queue_suppose_dead(size_t turn, unit& curr_unit, const map_location& loc)
774 {
775 	suppose_dead_ptr new_suppose_dead(std::make_shared<suppose_dead>(team_index(), hidden_, std::ref(curr_unit), loc));
776 	return queue_action(turn, new_suppose_dead);
777 }
778 
execute_net_cmd(const net_cmd & cmd)779 void side_actions::execute_net_cmd(const net_cmd& cmd)
780 {
781 	std::string type = cmd["type"];
782 
783 	if(type=="insert") {
784 		size_t turn = cmd["turn"].to_int();
785 		size_t pos = cmd["pos"].to_int();
786 		action_ptr act = action::from_config(cmd.child("action"), hidden_);
787 		if(!act) {
788 			ERR_WB << "side_actions::execute_network_command(): received invalid action data!" << std::endl;
789 			return;
790 		}
791 
792 		iterator itor = safe_insert(turn, pos, act);
793 		if(itor >= end()) {
794 			ERR_WB << "side_actions::execute_network_command(): received invalid insertion position!" << std::endl;
795 			return;
796 		}
797 
798 		LOG_WB << "Command received: action inserted on turn #" << turn << ", position #" << pos << ": " << act << "\n";
799 
800 		//update numbering hexes as necessary
801 		++itor;
802 		for(iterator end_itor = end(); itor != end_itor; ++itor) {
803 			display::get_singleton()->invalidate((*itor)->get_numbering_hex());
804 		}
805 	} else if(type=="replace") {
806 		size_t turn = cmd["turn"].to_int();
807 		size_t pos = cmd["pos"].to_int();
808 		action_ptr act = action::from_config(cmd.child("action"), hidden_);
809 		if(!act) {
810 			ERR_WB << "side_actions::execute_network_command(): received invalid action data!" << std::endl;
811 			return;
812 		}
813 
814 		iterator itor = turn_begin(turn) + pos;
815 		if(itor >= end() || get_turn(itor) != turn) {
816 			ERR_WB << "side_actions::execute_network_command(): received invalid pos!" << std::endl;
817 			return;
818 		}
819 
820 		if(!actions_.replace(itor, act)){
821 			ERR_WB << "side_actions::execute_network_command(): replace failed!" << std::endl;
822 			return;
823 		}
824 
825 		LOG_WB << "Command received: action replaced on turn #" << turn << ", position #" << pos << ": " << act << "\n";
826 	} else if(type=="remove") {
827 		size_t turn = cmd["turn"].to_int();
828 		size_t pos = cmd["pos"].to_int();
829 
830 		iterator itor = turn_begin(turn) + pos;
831 		if(itor >= end() || get_turn(itor) != turn) {
832 			ERR_WB << "side_actions::execute_network_command(): received invalid pos!" << std::endl;
833 			return;
834 		}
835 
836 		itor = safe_erase(itor);
837 
838 		LOG_WB << "Command received: action removed on turn #" << turn << ", position #" << pos << "\n";
839 
840 		//update numbering hexes as necessary
841 		for(iterator end_itor = end(); itor != end_itor; ++itor) {
842 			display::get_singleton()->invalidate((*itor)->get_numbering_hex());
843 		}
844 	} else if(type=="bump_later") {
845 		size_t turn = cmd["turn"].to_int();
846 		size_t pos = cmd["pos"].to_int();
847 
848 		iterator itor = turn_begin(turn) + pos;
849 		if(itor+1 >= end() || get_turn(itor) != turn) {
850 			ERR_WB << "side_actions::execute_network_command(): received invalid pos!" << std::endl;
851 			return;
852 		}
853 
854 		action_ptr first_action = *itor;
855 		action_ptr second_action = itor[1];
856 		bump_later(itor, false);
857 
858 		LOG_WB << "Command received: action bumped later from turn #" << turn << ", position #" << pos << "\n";
859 
860 		//update numbering hexes as necessary
861 		display::get_singleton()->invalidate(first_action->get_numbering_hex());
862 		display::get_singleton()->invalidate(second_action->get_numbering_hex());
863 	} else if(type=="clear") {
864 		LOG_WB << "Command received: clear\n";
865 		clear();
866 	} else if(type=="refresh") {
867 		LOG_WB << "Command received: refresh\n";
868 		clear();
869 		for(const net_cmd& sub_cmd : cmd.child_range("net_cmd"))
870 			execute_net_cmd(sub_cmd);
871 	} else {
872 		ERR_WB << "side_actions::execute_network_command(): received invalid type!" << std::endl;
873 		return;
874 	}
875 
876 	resources::whiteboard->validate_viewer_actions();
877 }
878 
make_net_cmd_insert(size_t turn_num,size_t pos,action_const_ptr act) const879 side_actions::net_cmd side_actions::make_net_cmd_insert(size_t turn_num, size_t pos, action_const_ptr act) const
880 {
881 	net_cmd result;
882 	result["type"] = "insert";
883 	result["turn"] = static_cast<int>(turn_num);
884 	result["pos"] = static_cast<int>(pos);
885 	result.add_child("action", act->to_config());
886 	return result;
887 }
make_net_cmd_insert(const const_iterator & pos,action_const_ptr act) const888 side_actions::net_cmd side_actions::make_net_cmd_insert(const const_iterator& pos, action_const_ptr act) const
889 {
890 	if(pos == begin()) {
891 		return make_net_cmd_insert(0,0,act);
892 	} else {
893 		const_iterator prec = pos - 1;
894 		return make_net_cmd_insert(get_turn(prec), actions_.position_in_turn(prec)+1, act);
895 	}
896 }
make_net_cmd_replace(const const_iterator & pos,action_const_ptr act) const897 side_actions::net_cmd side_actions::make_net_cmd_replace(const const_iterator& pos, action_const_ptr act) const
898 {
899 	net_cmd result;
900 	result["type"] = "replace";
901 	result["turn"] = static_cast<int>(get_turn(pos));
902 	result["pos"] = static_cast<int>(actions_.position_in_turn(pos));
903 	result.add_child("action", act->to_config());
904 	return result;
905 }
make_net_cmd_remove(const const_iterator & pos) const906 side_actions::net_cmd side_actions::make_net_cmd_remove(const const_iterator& pos) const
907 {
908 	net_cmd result;
909 	result["type"] = "remove";
910 	result["turn"] = static_cast<int>(get_turn(pos));
911 	result["pos"] = static_cast<int>(actions_.position_in_turn(pos));
912 	return result;
913 }
make_net_cmd_bump_later(const const_iterator & pos) const914 side_actions::net_cmd side_actions::make_net_cmd_bump_later(const const_iterator& pos) const
915 {
916 	net_cmd result;
917 	result["type"] = "bump_later";
918 	result["turn"] = static_cast<int>(get_turn(pos));
919 	result["pos"] = static_cast<int>(actions_.position_in_turn(pos));
920 	return result;
921 }
make_net_cmd_clear() const922 side_actions::net_cmd side_actions::make_net_cmd_clear() const
923 {
924 	net_cmd result;
925 	result["type"] = "clear";
926 	return result;
927 }
make_net_cmd_refresh() const928 side_actions::net_cmd side_actions::make_net_cmd_refresh() const
929 {
930 	net_cmd result;
931 	result["type"] = "refresh";
932 
933 	for(const_iterator itor = begin(), end_itor = end(); itor != end_itor; ++itor) {
934 		result.add_child("net_cmd", make_net_cmd_insert(get_turn(itor), actions_.position_in_turn(itor), *itor));
935 	}
936 
937 	return result;
938 }
939 
raw_turn_shift()940 void side_actions::raw_turn_shift()
941 {
942 	//find units who still have plans for turn 0 (i.e. were too lazy to finish their jobs)
943 	std::set<unit_const_ptr> lazy_units;
944 	for(const action_ptr& act : iter_turn(0)) {
945 		unit_const_ptr u = act->get_unit();
946 		if(u) {
947 			lazy_units.insert(u);
948 		}
949 	}
950 
951 	//push their plans back one turn
952 	std::set<unit_const_ptr>::iterator lazy_end = lazy_units.end();
953 	iterator itor = end();
954 	while(itor != begin()) {
955 		--itor;
956 		action_ptr act = *itor;
957 
958 		if(lazy_units.find(act->get_unit()) != lazy_end) {
959 			safe_insert(get_turn(itor)+1, 0, act);
960 			itor = actions_.erase(itor);
961 		}
962 	}
963 
964 	//push any remaining first-turn plans into the second turn
965 	for(iterator act=turn_begin(0), end=turn_end(0); act!=end; ++act) {
966 		safe_insert(1, 0, *act);
967 	}
968 
969 	//shift everything forward one turn
970 	actions_.erase(turn_begin(0), turn_end(0));
971 	actions_.turn_shift();
972 }
973 
synced_turn_shift()974 void side_actions::synced_turn_shift()
975 {
976 	raw_turn_shift();
977 	resources::whiteboard->queue_net_cmd(team_index(), make_net_cmd_refresh());
978 }
979 
980 } //end namespace wb
981