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