The Battle for Wesnoth  1.19.0-dev
side_actions.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 - 2024
3  by Gabriel Morin <gabrielmorin (at) gmail (dot) com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  */
19 
20 #include <set>
21 #include <sstream>
22 
24 
25 #include "whiteboard/action.hpp"
26 #include "whiteboard/attack.hpp"
27 #include "whiteboard/manager.hpp"
28 #include "whiteboard/move.hpp"
29 #include "whiteboard/recall.hpp"
30 #include "whiteboard/recruit.hpp"
33 #include "whiteboard/utility.hpp"
34 
35 #include "actions/undo.hpp"
36 #include "display.hpp"
37 #include "game_end_exceptions.hpp"
38 #include "game_state.hpp"
39 #include "play_controller.hpp"
40 #include "resources.hpp"
41 #include "units/unit.hpp"
42 #include "utils/iterable_pair.hpp"
43 
44 namespace wb
45 {
46 
47 /** Dumps side_actions on a stream, for debug purposes. */
48 std::ostream &operator<<(std::ostream &out, const wb::side_actions& side_actions)
49 {
50  out << "Content of side_actions:";
51  for(std::size_t turn = 0; turn < side_actions.num_turns(); ++turn) {
52  out << "\n Turn " << turn;
53 
54  int count = 1;
56  out << "\n (" << count++ << ") " << *it;
57  }
58 
59  if(side_actions.turn_size(turn) == 0) {
60  out << "\n (empty)";
61  }
62  }
63 
64  if(side_actions.empty()) {
65  out << " (empty)";
66  }
67 
68  return out;
69 }
70 
72  : actions_()
73  , turn_beginnings_()
74 {
75 }
76 
77 std::size_t side_actions_container::get_turn_impl(std::size_t begin, std::size_t end, const_iterator it) const
78 {
79  if(begin+1 >= end) {
80  if(begin+1 != end) {
81  ERR_WB << "get_turn: begin >= end";
82  }
83  else if(it < turn_beginnings_[begin]) {
84  ERR_WB << "get_turn failed";
85  }
86  return begin;
87  }
88  std::size_t mid = (begin+end) / 2;
89  if(it < turn_beginnings_[mid]) {
90  return get_turn_impl(begin, mid, it);
91  } else {
92  return get_turn_impl(mid, end, it);
93  }
94 }
95 
97 {
98  return get_turn_impl(0, num_turns(), it);
99 }
100 
102 {
103  return it - turn_begin( get_turn(it) );
104 }
105 
107  if(turn_num >= num_turns()) {
108  return end();
109  } else {
110  return turn_beginnings_[turn_num];
111  }
112 }
113 
115 {
116  if(turn_num >= num_turns()) {
117  return end();
118  } else {
119  return turn_beginnings_[turn_num];
120  }
121 }
122 
124  if(turn_size(turn) == 0) {
125  return queue(turn, action);
126  }
127 
128  iterator res = insert(turn_begin(turn), action);
129  if(res != end()) {
130  bool current_turn_unplanned = turn_size(0) == 0;
131  turn_beginnings_[turn] = res;
132 
133  if(current_turn_unplanned && turn == 1) {
134  turn_beginnings_.front() = res;
135  }
136  }
137  return res;
138 }
139 
141 {
142  assert(position <= end());
143 
144  bool first = position == begin();
145 
146  std::pair<iterator,bool> res = actions_.insert(position, action);
147  if(!res.second) {
148  return end();
149  }
150  if(first) {
151  // If we are inserting before the first action, then the inserted action should became the first of turn 0.
152  turn_beginnings_.front() = begin();
153  }
154  return res.first;
155 }
156 
158 {
159  // Are we inserting an action in the future while the current turn is empty?
160  // That is, are we in the sole case where an empty turn can be followed by a non-empty one.
161  bool future_only = turn_num == 1 && num_turns() == 0;
162 
163  bool current_turn_unplanned = turn_size(0) == 0;
164 
165  //for a little extra safety, since we should never resize by much at a time
166  assert(turn_num <= num_turns() || future_only);
167 
168  std::pair<iterator,bool> res = actions_.insert(turn_end(turn_num), action);
169  if(!res.second) {
170  return end();
171  }
172 
173  if(future_only) {
174  // No action are planned for the current turn but we are planning an action for turn 1 (the next turn).
175  turn_beginnings_.push_back(res.first);
176  }
177  if(turn_num >= num_turns()) {
178  turn_beginnings_.push_back(res.first);
179  } else if(current_turn_unplanned && turn_num == 0) {
180  // We are planning the first action of the current turn while others actions are planned in the future.
181  turn_beginnings_.front() = res.first;
182  }
183 
184  return res.first;
185 }
186 
188 {
189  assert(position > begin());
190  assert(position < end());
191 
192  action_ptr rhs = *position;
193  action_ptr lhs = *(position - 1);
194 
195  actions_.replace(position, lhs);
196  actions_.replace(position - 1, rhs);
197  return position - 1;
198 }
199 
201 {
202  return bump_earlier(position + 1);
203 }
204 
206 {
207  //precondition
208  assert(position < end());
209 
210  //prepare
211  iterator next = position + 1;
212  bool deleting_last_element = next == end();
213 
214  // pre-processing (check if position is at the beginning of a turn)
215  action_limits::iterator beginning = std::find(turn_beginnings_.begin(), turn_beginnings_.end(), position);
216  if(beginning != turn_beginnings_.end()) {
217  if(deleting_last_element) {
218  if(size() == 1) {
219  // If we are deleting our sole action, we can clear turn_beginnings_ (and we have to if this last action is in turn 1)
220  turn_beginnings_.clear();
221  } else {
222  // Otherwise, we just delete the last turn
223  turn_beginnings_.pop_back();
224  }
225  } else {
226 #if 1
227  for(auto& it : turn_beginnings_) {
228  if (it == position) {
229  it = next;
230  }
231  }
232 #else
233  std::size_t turn_of_position = std::distance(turn_beginnings_.begin(), beginning);
234  // If we still have action this turn
235  if(get_turn(next) == turn_of_position) {
236  *beginning = next; // We modify the beginning of the turn
237  } else {
238  assert(turn_of_position == 0);
239  *beginning = turn_end(0); // Otherwise, we are emptying the current turn.
240  }
241 #endif
242  }
243  }
244 
245  //erase!
246  return actions_.erase(position);
247 }
248 
250  // @todo rewrite using boost::multi_index::erase(iterator,iterator) for efficiency.
251  if(first>=last) {
252  return last;
253  }
254  for(iterator it = last-1; it>first; --it) {
255  it = erase(it);
256  }
257  return erase(first);
258 }
259 
260 
262  : actions_()
263  , team_index_(0)
264  , team_index_defined_(false)
265  , gold_spent_(0)
266  , hidden_(false)
267 {
268 }
269 
270 void side_actions::set_team_index(std::size_t team_index)
271 {
272  assert(!team_index_defined_);
274  team_index_defined_ = true;
275 }
276 
278 {
279  if(empty()) {
280  return;
281  }
282 
283  std::vector<int>& numbers_to_draw = result.numbers_to_draw;
284  std::vector<std::size_t>& team_numbers = result.team_numbers;
285  int& main_number = result.main_number;
286  std::set<std::size_t>& secondary_numbers = result.secondary_numbers;
287  std::shared_ptr<highlighter> hlighter = resources::whiteboard->get_highlighter().lock();
288 
289  for(const_iterator it = begin(); it != end(); ++it) {
290  if((*it)->is_numbering_hex(hex)) {
291  //store number corresponding to iterator's position + 1
292  std::size_t number = (it - begin()) + 1;
293  std::size_t index = numbers_to_draw.size();
294  numbers_to_draw.push_back(number);
295  team_numbers.push_back(team_index());
296 
297  if(hlighter) {
298  if(hlighter->get_main_highlight().lock() == *it) {
299  main_number = index;
300  }
301 
302  for(weak_action_ptr action : hlighter->get_secondary_highlights()) {
303  if(action.lock() == *it) {
304  secondary_numbers.insert(index);
305  }
306  }
307  }
308  }
309  }
310 }
311 
313 {
314  if(!empty()) {
315  return execute(begin());
316  } else { //nothing is executable right now
317  return false;
318  }
319 }
320 
322 {
323  if(resources::whiteboard->has_planned_unit_map()) {
324  ERR_WB << "Modifying action queue while temp modifiers are applied!!!";
325  }
326 
327  if(actions_.empty() || position == actions_.end()) {
328  return false;
329  }
330 
331  assert(position < turn_end(0)); //can't execute actions from future turns
332 
333  LOG_WB << "Before execution, " << *this;
334 
335  action_ptr action = *position;
336 
337  if(!action->valid()) {
338  LOG_WB << "Invalid action sent to execution, deleting.";
339  synced_erase(position);
340  return true;
341  }
342 
343  bool action_successful;
344  // Determines whether action should be deleted. Interrupted moves return action_complete == false.
345  bool action_complete;
346  try {
347  action->execute(action_successful, action_complete);
348  } catch (const return_to_play_side_exception&) {
349  synced_erase(position);
350  LOG_WB << "End turn exception caught during execution, deleting action. " << *this;
351  //validate actions at next map rebuild
352  resources::whiteboard->on_gamestate_change();
353  throw;
354  }
355 
356  if(resources::whiteboard->should_clear_undo()) {
357  if(resources::controller->current_team().auto_shroud_updates()) {
359  }
360  else {
361  WRN_WB << "not clearing undo stack because dsu is active";
362  }
363  }
364 
365  std::stringstream ss;
366  ss << "After " << (action_successful? "successful": "failed") << " execution ";
367  if(action_complete) {
368  ss << "with deletion, ";
369  synced_erase(position);
370  }
371  else { //action may have revised itself; let's tell our allies.
372  ss << "without deletion, ";
373  resources::whiteboard->queue_net_cmd(team_index_,make_net_cmd_replace(position,*position));
374 
375  //Idea that needs refining: move action at the end of the queue if it failed executing:
376  //actions_.erase(position);
377  //actions_.insert(end(), action);
378  }
379  ss << *this << "\n";
380  LOG_WB << ss.str();
381 
382  resources::whiteboard->validate_viewer_actions();
383  return action_successful;
384 }
385 
387 {
388  if(hidden_) {
389  return;
390  }
391 
392  hidden_ = true;
393 
394  for(action_ptr act : *this) {
395  act->hide();
396  }
397 }
399 {
400  if(!hidden_) {
401  return;
402  }
403 
404  hidden_ = false;
405 
406  for(action_ptr act : *this) {
407  act->show();
408  }
409 }
410 
412 {
413  if(resources::whiteboard->has_planned_unit_map()) {
414  ERR_WB << "Modifying action queue while temp modifiers are applied!!!";
415  }
416  iterator valid_position = synced_insert(position, action);
417  LOG_WB << "Inserted into turn #" << get_turn(valid_position) << " at position #"
418  << actions_.position_in_turn(valid_position) << " : " << action;
419  resources::whiteboard->validate_viewer_actions();
420  return valid_position;
421 }
422 
424 {
425  if(resources::whiteboard->has_planned_unit_map()) {
426  ERR_WB << "Modifying action queue while temp modifiers are applied!!!";
427  }
428  iterator result = synced_enqueue(turn_num, action);
429  LOG_WB << "Queue into turn #" << turn_num << " : " << action;
430  resources::whiteboard->validate_viewer_actions();
431  return result;
432 }
433 
434 namespace
435 {
436  /**
437  * Check whether a move is swapable with a given action.
438  */
439  struct swapable_with_move: public visitor
440  {
441  public:
442  swapable_with_move(side_actions &sa, side_actions::iterator position, move_ptr second): sa_(sa), valid_(false), position_(position), second_(second) {}
443  bool valid() const { return valid_; }
444 
445  void visit(move_ptr first) {
446  valid_ = second_->get_dest_hex() != first->get_source_hex();
447  }
448 
449  void visit(attack_ptr first) {
450  visit(std::static_pointer_cast<move>(first));
451  }
452 
453  void visit(recruit_ptr first) {
454  check_recruit_recall(first->get_recruit_hex());
455  }
456 
457  void visit(recall_ptr first) {
458  check_recruit_recall(first->get_recall_hex());
459  }
460 
461  void visit(suppose_dead_ptr) {
462  valid_ = true;
463  }
464 
465  private:
466  side_actions &sa_;
467  bool valid_;
468  side_actions::iterator position_;
469  move_ptr second_;
470 
471  void check_recruit_recall(const map_location &loc) {
472  const unit_const_ptr leader = second_->get_unit();
473  if(leader->can_recruit() && dynamic_cast<game_state&>(*resources::filter_con).can_recruit_on(*leader, loc)) {
474  if(const unit_const_ptr backup_leader = find_backup_leader(*leader)) {
475  side_actions::iterator it = sa_.find_first_action_of(*backup_leader);
476  if(!(it == sa_.end() || position_ < it)) {
477  return; //backup leader but he moves before us, refuse bump
478  }
479  } else {
480  return; //no backup leader, refuse bump
481  }
482  }
483  valid_ = true;
484  }
485  };
486 }
487 
488 //move action toward front of queue
490 {
491  if(resources::whiteboard->has_planned_unit_map()) {
492  ERR_WB << "Modifying action queue while temp modifiers are applied!!!";
493  }
494 
495  assert(position <= end());
496 
497  //Don't allow bumping the very first action any earlier, of course.
498  //Also, don't allow bumping an action into a previous turn queue
499  if(actions_.position_in_turn(position) == 0) {
500  return end();
501  }
502 
503  side_actions::iterator previous = position - 1;
504 
505  //Verify we're not moving an action out-of-order compared to other action of the same unit
506  const unit_const_ptr previous_ptr = (*previous)->get_unit();
507  const unit_const_ptr current_ptr = (*position)->get_unit();
508  if(previous_ptr && current_ptr && previous_ptr.get() == current_ptr.get()) {
509  return end();
510  }
511 
512  if(move_ptr second = std::dynamic_pointer_cast<move>(*position)) {
513  swapable_with_move check(*this, position, second);
514  (*previous)->accept(check);
515  if(!check.valid()) {
516  return end();
517  }
518  }
519 
520  LOG_WB << "Before bumping earlier, " << *this;
521 
522  int turn_number = get_turn(position);
523  int action_number = actions_.position_in_turn(position);
524  int last_position = turn_size(turn_number) - 1;
525  LOG_WB << "In turn #" << turn_number
526  << ", bumping action #" << action_number << "/" << last_position
527  << " to position #" << action_number - 1 << "/" << last_position
528  << ".";
529 
530  if (send_to_net) {
531  resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_bump_later(position - 1));
532  }
533  actions_.bump_earlier(position);
534 
535  LOG_WB << "After bumping earlier, " << *this;
536  return position - 1;
537 }
538 
539 //move action toward back of queue
541 {
542  assert(position < end());
543 
544  ++position;
545  if(position == end()) {
546  return end();
547  }
548  position = bump_earlier(position, send_to_net);
549  if(position == end()) {
550  return end();
551  }
552  return position + 1;
553 }
554 
556 {
557  if(resources::whiteboard->has_planned_unit_map()) {
558  ERR_WB << "Modifying action queue while temp modifiers are applied!!!";
559  }
560 
561  assert(position < end());
562 
563  LOG_WB << "Erasing action at turn #" << get_turn(position) << " position #" << actions_.position_in_turn(position);
564 
565 
566  if(resources::gameboard->get_team(team_index_ + 1).is_local()) {
567  position = synced_erase(position);
568  }
569  else {
570  // don't sync actions of sides that we don't control, this would only generate
571  // 'illegal whiteboard data' server wanrings.
572  // it might be better to instead don't even erase the action in this case to keep
573  // the actionlist in sync with the owner client.
574  position = safe_erase(position);
575  }
576 
577 
578  if(validate_after_delete) {
579  resources::whiteboard->validate_viewer_actions();
580  }
581 
582  return position;
583 }
584 
586 {
587  return find_first_action_of(actions_.get<container::by_hex>().equal_range(hex), begin(), std::less<iterator>());
588 }
589 
591 {
592  return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit_id), start_position, std::less<iterator>());
593 }
594 
596  return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit_id), start_position, std::greater<iterator>());
597 }
598 
600 {
601  return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit_id), start_position, std::greater<iterator>());
602 }
603 
605 {
606  if(end() == begin()) {
607  return end();
608  }
609  return find_last_action_of(unit_id, end() - 1);
610 }
611 
613 {
614  if(end() == begin()) {
615  return end();
616  }
617  return find_last_action_of(unit_id, end() - 1);
618 }
619 
621 {
622  return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit.underlying_id()), start_position, std::less<iterator>());
623 }
624 
626  return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit.underlying_id()), start_position, std::greater<iterator>());
627 }
628 
630 {
631  return find_first_action_of(actions_.get<container::by_unit>().equal_range(unit.underlying_id()), start_position, std::greater<iterator>());
632 }
633 
635 {
636  if(end() == begin()) {
637  return end();
638  }
639  return find_last_action_of(unit, end() - 1);
640 }
641 
643 {
644  if(end() == begin()) {
645  return end();
646  }
647  return find_last_action_of(unit, end() - 1);
648 }
649 
651 {
653 }
654 
656 {
657  return actions_.get<container::by_unit>().count(unit.underlying_id());
658 }
659 
660 std::deque<action_ptr> side_actions::actions_of(const unit& target)
661 {
663  std::pair<unit_iterator, unit_iterator> action_its = actions_.get<container::by_unit>().equal_range(target.underlying_id());
664 
665  std::deque<action_ptr> actions (action_its.first, action_its.second);
666  return actions;
667 }
668 
669 std::size_t side_actions::get_turn_num_of(const unit& u) const
670 {
672  if(itor == end()) {
673  return 0;
674  }
675  return get_turn(itor);
676 }
677 
679 {
680  DBG_WB << "Changing gold spent for side " << (team_index() + 1) << "; old value: "
681  << gold_spent_ << "; new value: " << (gold_spent_ + difference) << "\n";
682  gold_spent_ += difference; assert(gold_spent_ >= 0);
683 }
684 
686 {
687  DBG_WB << "Resetting gold spent for side " << (team_index() + 1) << " to 0.";
688  gold_spent_ = 0;
689 }
690 
691 void side_actions::update_recruited_unit(std::size_t old_id, unit& new_unit)
692 {
693  for(const_iterator it = begin(); it != end(); ++it) {
694  if(move_ptr mp = std::dynamic_pointer_cast<move>(*it)) {
695  if(mp->raw_uid() == old_id) {
696  actions_.modify(it, [&](action_ptr& p) {
697  static_cast<move&>(*p).modify_unit(new_unit);
698  });
699  }
700  }
701  }
702 }
703 
704 side_actions::iterator side_actions::safe_insert(std::size_t turn, std::size_t pos, action_ptr act)
705 {
706  assert(act);
707  if(pos == 0) {
708  return actions_.push_front(turn, act);
709  } else {
710  return actions_.insert(turn_begin(turn) + pos, act);
711  }
712 }
713 
715 {
717  return safe_erase(itor);
718 }
719 
721 {
722  resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_insert(itor, act));
723  return actions_.insert(itor, act);
724 }
725 
727 {
728  //raw_enqueue() creates actions_[turn_num] if it doesn't exist already, so we
729  //have to do it first -- before subsequently calling actions_[turn_num].size().
730  iterator result = actions_.queue(turn_num, act);
731  if(result != end()) {
732  resources::whiteboard->queue_net_cmd(team_index_, make_net_cmd_insert(turn_num, turn_size(turn_num) - 1, act));
733  // The insert position is turn_size(turn_num)-1 since we already inserted the action.
734  }
735  return result;
736 }
737 
739 {
740  action_ptr action = *itor;
741  resources::whiteboard->pre_delete_action(action); //misc cleanup
742  iterator return_itor = actions_.erase(itor);
743  resources::whiteboard->post_delete_action(action);
744  return return_itor;
745 }
747 {
748  move_ptr new_move(std::make_shared<move>(team_index(), hidden_, std::ref(mover), route, arrow, std::move(fake_unit)));
749  return queue_action(turn, new_move);
750 }
751 
752 side_actions::iterator side_actions::queue_attack(std::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)
753 {
754  attack_ptr new_attack(std::make_shared<attack>(team_index(), hidden_, std::ref(mover), target_hex, weapon_choice, route, arrow, std::move(fake_unit)));
755  return queue_action(turn, new_attack);
756 }
757 
758 side_actions::iterator side_actions::queue_recruit(std::size_t turn, const std::string& unit_name, const map_location& recruit_hex)
759 {
760  recruit_ptr new_recruit(std::make_shared<recruit>(team_index(), hidden_, unit_name, recruit_hex));
761  return queue_action(turn, new_recruit);
762 }
763 
764 side_actions::iterator side_actions::queue_recall(std::size_t turn, const unit& unit, const map_location& recall_hex)
765 {
766  recall_ptr new_recall(std::make_shared<recall>(team_index(), hidden_, unit, recall_hex));
767  return queue_action(turn, new_recall);
768 }
769 
771 {
772  suppose_dead_ptr new_suppose_dead(std::make_shared<suppose_dead>(team_index(), hidden_, std::ref(curr_unit), loc));
773  return queue_action(turn, new_suppose_dead);
774 }
775 
777 {
778  std::string type = cmd["type"];
779 
780  if(type=="insert") {
781  std::size_t turn = cmd["turn"].to_int();
782  std::size_t pos = cmd["pos"].to_int();
784  if(!act) {
785  ERR_WB << "side_actions::execute_network_command(): received invalid action data!";
786  return;
787  }
788 
789  iterator itor = safe_insert(turn, pos, act);
790  if(itor >= end()) {
791  ERR_WB << "side_actions::execute_network_command(): received invalid insertion position!";
792  return;
793  }
794 
795  LOG_WB << "Command received: action inserted on turn #" << turn << ", position #" << pos << ": " << act;
796 
797  //update numbering hexes as necessary
798  ++itor;
799  for(iterator end_itor = end(); itor != end_itor; ++itor) {
800  display::get_singleton()->invalidate((*itor)->get_numbering_hex());
801  }
802  } else if(type=="replace") {
803  std::size_t turn = cmd["turn"].to_int();
804  std::size_t pos = cmd["pos"].to_int();
806  if(!act) {
807  ERR_WB << "side_actions::execute_network_command(): received invalid action data!";
808  return;
809  }
810 
811  iterator itor = turn_begin(turn) + pos;
812  if(itor >= end() || get_turn(itor) != turn) {
813  ERR_WB << "side_actions::execute_network_command(): received invalid pos!";
814  return;
815  }
816 
817  if(!actions_.replace(itor, act)){
818  ERR_WB << "side_actions::execute_network_command(): replace failed!";
819  return;
820  }
821 
822  LOG_WB << "Command received: action replaced on turn #" << turn << ", position #" << pos << ": " << act;
823  } else if(type=="remove") {
824  std::size_t turn = cmd["turn"].to_int();
825  std::size_t pos = cmd["pos"].to_int();
826 
827  iterator itor = turn_begin(turn) + pos;
828  if(itor >= end() || get_turn(itor) != turn) {
829  ERR_WB << "side_actions::execute_network_command(): received invalid pos!";
830  return;
831  }
832 
833  itor = safe_erase(itor);
834 
835  LOG_WB << "Command received: action removed on turn #" << turn << ", position #" << pos;
836 
837  //update numbering hexes as necessary
838  for(iterator end_itor = end(); itor != end_itor; ++itor) {
839  display::get_singleton()->invalidate((*itor)->get_numbering_hex());
840  }
841  } else if(type=="bump_later") {
842  std::size_t turn = cmd["turn"].to_int();
843  std::size_t pos = cmd["pos"].to_int();
844 
845  iterator itor = turn_begin(turn) + pos;
846  if(itor+1 >= end() || get_turn(itor) != turn) {
847  ERR_WB << "side_actions::execute_network_command(): received invalid pos!";
848  return;
849  }
850 
851  action_ptr first_action = *itor;
852  action_ptr second_action = itor[1];
853  bump_later(itor, false);
854 
855  LOG_WB << "Command received: action bumped later from turn #" << turn << ", position #" << pos;
856 
857  //update numbering hexes as necessary
858  display::get_singleton()->invalidate(first_action->get_numbering_hex());
859  display::get_singleton()->invalidate(second_action->get_numbering_hex());
860  } else if(type=="clear") {
861  LOG_WB << "Command received: clear";
862  clear();
863  } else if(type=="refresh") {
864  LOG_WB << "Command received: refresh";
865  clear();
866  for(const net_cmd& sub_cmd : cmd.child_range("net_cmd"))
867  execute_net_cmd(sub_cmd);
868  } else {
869  ERR_WB << "side_actions::execute_network_command(): received invalid type!";
870  return;
871  }
872 
873  resources::whiteboard->validate_viewer_actions();
874 }
875 
876 side_actions::net_cmd side_actions::make_net_cmd_insert(std::size_t turn_num, std::size_t pos, action_const_ptr act) const
877 {
878  net_cmd result;
879  result["type"] = "insert";
880  result["turn"] = static_cast<int>(turn_num);
881  result["pos"] = static_cast<int>(pos);
882  result.add_child("action", act->to_config());
883  return result;
884 }
886 {
887  if(pos == begin()) {
888  return make_net_cmd_insert(0,0,act);
889  } else {
890  const_iterator prec = pos - 1;
891  return make_net_cmd_insert(get_turn(prec), actions_.position_in_turn(prec)+1, act);
892  }
893 }
895 {
896  net_cmd result;
897  result["type"] = "replace";
898  result["turn"] = static_cast<int>(get_turn(pos));
899  result["pos"] = static_cast<int>(actions_.position_in_turn(pos));
900  result.add_child("action", act->to_config());
901  return result;
902 }
904 {
905  net_cmd result;
906  result["type"] = "remove";
907  result["turn"] = static_cast<int>(get_turn(pos));
908  result["pos"] = static_cast<int>(actions_.position_in_turn(pos));
909  return result;
910 }
912 {
913  net_cmd result;
914  result["type"] = "bump_later";
915  result["turn"] = static_cast<int>(get_turn(pos));
916  result["pos"] = static_cast<int>(actions_.position_in_turn(pos));
917  return result;
918 }
920 {
921  net_cmd result;
922  result["type"] = "clear";
923  return result;
924 }
926 {
927  net_cmd result;
928  result["type"] = "refresh";
929 
930  for(const_iterator itor = begin(), end_itor = end(); itor != end_itor; ++itor) {
931  result.add_child("net_cmd", make_net_cmd_insert(get_turn(itor), actions_.position_in_turn(itor), *itor));
932  }
933 
934  return result;
935 }
936 
938 {
939  //find units who still have plans for turn 0 (i.e. were too lazy to finish their jobs)
940  std::set<unit_const_ptr> lazy_units;
941  for(const action_ptr& act : iter_turn(0)) {
942  unit_const_ptr u = act->get_unit();
943  if(u) {
944  lazy_units.insert(u);
945  }
946  }
947 
948  //push their plans back one turn
949  std::set<unit_const_ptr>::iterator lazy_end = lazy_units.end();
950  iterator itor = end();
951  while(itor != begin()) {
952  --itor;
953  action_ptr act = *itor;
954 
955  if(lazy_units.find(act->get_unit()) != lazy_end) {
956  safe_insert(get_turn(itor)+1, 0, act);
957  itor = actions_.erase(itor);
958  }
959  }
960 
961  //push any remaining first-turn plans into the second turn
962  for(iterator act=turn_begin(0), end=turn_end(0); act!=end; ++act) {
963  safe_insert(1, 0, *act);
964  }
965 
966  //shift everything forward one turn
969 }
970 
972 {
973  raw_turn_shift();
975 }
976 
977 } //end namespace wb
void clear()
Clears the stack of undoable (and redoable) actions.
Definition: undo.cpp:201
Arrows destined to be drawn on the map.
Definition: arrow.hpp:30
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:367
child_itors child_range(config_key_type key)
Definition: config.cpp:273
config & add_child(config_key_type key)
Definition: config.cpp:441
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3137
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:95
Holds a temporary unit that can be drawn on the map without being placed in the unit_map.
bool can_recruit_on(const map_location &leader_loc, const map_location &recruit_loc, int side) const
Checks to see if a leader at leader_loc could recruit on recruit_loc.
Definition: game_state.cpp:322
Exception used to escape form the ai or ui code to playsingle_controller::play_side.
This class represents a single unit of a specific type.
Definition: unit.hpp:133
Abstract base class for all the whiteboard planned actions.
Definition: action.hpp:34
bool valid()
Returns whether this action is valid or not.
Definition: action.hpp:135
static action_ptr from_config(const config &, bool hidden)
Constructs an object of a subclass of wb::action using a config.
Definition: action.cpp:60
virtual void execute(bool &success, bool &complete)=0
Output parameters: success: Whether or not to continue an execute-all after this execution complete: ...
A planned move, represented on the map by an arrow and a ghosted unit in the destination hex.
Definition: move.hpp:36
void modify_unit(unit &new_unit)
Definition: move.cpp:314
bool modify(iterator position, Modifier mod)
iterator turn_end(std::size_t turn_num)
action_set::index< chronological >::type::iterator iterator
action_limits turn_beginnings_
Contains a list of iterator to the beginning of each turn.
iterator end()
Returns the iterator for the position after the last executed action within the actions queue.
std::size_t get_turn(const_iterator it) const
Returns the turn of a given iterator planned execution.
iterator bump_later(iterator position)
Moves an action later in the execution order.
bool replace(iterator it, action_ptr act)
Replaces the action at a given position with another action.
iterator push_front(std::size_t turn, action_ptr action)
Pushes an action in front of a given turn.
bool empty() const
Indicates whether the action queue is empty.
iterator queue(std::size_t turn_num, action_ptr action)
Queues an action to be executed last.
action_set::index< chronological >::type::const_iterator const_iterator
iterator turn_begin(std::size_t turn_num)
Returns the iterator for the first (executed earlier) action of a given turn within the actions queue...
action_set::index< T >::type & get()
Returns a given index.
std::size_t size() const
Returns the number of actions in the action queue.
iterator erase(iterator position)
Deletes the action at the specified position.
iterator begin()
Returns the iterator for the first (executed earlier) action within the actions queue.
iterator bump_earlier(iterator position)
Moves an action earlier in the execution order.
std::size_t turn_size(std::size_t turn_num) const
Returns the number of actions planned for turn turn_num.
std::size_t get_turn_impl(std::size_t begin, std::size_t end, const_iterator it) const
Binary search to find the occurring turn of the action pointed by an iterator.
void turn_shift()
Shift turn.
std::size_t position_in_turn(const_iterator it) const
Returns the position of a given iterator in its turn.
iterator insert(iterator position, action_ptr action)
Inserts an action at the specified position.
std::size_t num_turns() const
Returns the number of turns that have plans.
This internal whiteboard class holds the planned action queues for a team, and offers many utility me...
std::size_t team_index()
Returns the team index this action queue belongs to.
iterator find_first_action_at(map_location hex)
Find the first action occurring at a given hex.
iterator queue_move(std::size_t turn_num, unit &mover, const pathfind::marked_route &route, arrow_ptr arrow, fake_unit_ptr fake_unit)
Queues a move to be executed last.
iterator queue_recruit(std::size_t turn_num, const std::string &unit_name, const map_location &recruit_hex)
Queues a recruit to be executed last.
iterator queue_recall(std::size_t turn_num, const unit &unit, const map_location &recall_hex)
Queues a recall to be executed last.
net_cmd make_net_cmd_remove(const const_iterator &pos) const
std::size_t num_turns() const
Returns the number of turns that have plans.
net_cmd make_net_cmd_replace(const const_iterator &pos, action_const_ptr) const
iterator find_last_action_of(const unit &unit, iterator start_position)
Finds the last action that belongs to this unit, starting the search backwards from the specified pos...
bool empty() const
Indicates whether the action queue is empty.
iterator find_first_action_of(std::pair< T, T > between, iterator limit, Compare comp)
Find the (chronologically) first action between the iterators between.first and between....
iterator queue_suppose_dead(std::size_t turn_num, unit &curr_unit, const map_location &loc)
Queues a suppose_dead to be executed last.
std::deque< action_ptr > actions_of(const unit &unit)
void hide()
Sets whether or not the contents should be drawn on the screen.
void reset_gold_spent()
Set gold spent back to zero.
iterator turn_begin(std::size_t turn_num)
iterator bump_earlier(iterator position, bool send_to_net=true)
Moves an action earlier in the execution order.
std::size_t get_turn_num_of(const unit &) const
Determines the appropriate turn number for the next action planned for this unit.
std::size_t team_index_
void get_numbers(const map_location &hex, numbers_t &result)
Gets called when display is drawing a hex to determine which numbers to draw on it.
std::size_t turn_size(std::size_t turn_num) const
Returns the number of actions planned for turn turn_num.
void change_gold_spent_by(int difference)
Used to track gold spending by recruits/recalls when building the future unit map.
iterator safe_insert(std::size_t turn_num, std::size_t pos, action_ptr to_insert)
iterator begin()
Returns the iterator for the first (executed earlier) action within the actions queue.
iterator synced_enqueue(std::size_t turn_num, action_ptr to_insert)
int gold_spent_
Used to store gold "spent" in planned recruits/recalls when the future unit map is applied.
container::const_iterator const_iterator
net_cmd make_net_cmd_clear() const
iterator remove_action(iterator position, bool validate_after_delete=true)
Deletes the action at the specified position.
bool execute(iterator position)
Executes the specified action, if it exists in the queue.
container::iterator iterator
void update_recruited_unit(std::size_t old_id, unit &new_unit)
After a recruit action was executed the id of the unit was changed so we need to update the unitid of...
std::size_t get_turn(const_iterator it) const
Returns the turn of a given iterator planned execution.
iterator bump_later(iterator position, bool send_to_net=true)
Moves an action later in the execution order.
iterator synced_insert(iterator itor, action_ptr to_insert)
void clear()
Empties the action queue.
iterator synced_erase(iterator itor)
std::size_t count_actions_of(const unit &unit)
iterator safe_erase(const iterator &itor)
bool execute_next()
Executes the first action in the queue, and then deletes it.
iterator end()
Returns the iterator for the position after the last executed action within the actions queue.
bool unit_has_actions(const unit &unit)
void execute_net_cmd(const net_cmd &)
net_cmd make_net_cmd_bump_later(const const_iterator &pos) const
range_t iter_turn(std::size_t turn_num)
Returns an iterator range corresponding to the requested turn.
void set_team_index(std::size_t team_index)
Must be called only once, right after the team that owns this side_actions is added to the teams vect...
net_cmd make_net_cmd_refresh() const
iterator insert_action(iterator position, action_ptr action)
Inserts an action at the specified position.
iterator queue_attack(std::size_t turn_num, unit &mover, const map_location &target_hex, int weapon_choice, const pathfind::marked_route &route, arrow_ptr arrow, fake_unit_ptr fake_unit)
Queues an attack or attack-move to be executed last.
iterator turn_end(std::size_t turn_num)
net_cmd make_net_cmd_insert(std::size_t turn_num, std::size_t pos, action_const_ptr) const
iterator queue_action(std::size_t turn_num, action_ptr action)
Queues an action to be executed last.
Abstract base class for all the visitors (cf GoF Visitor Design Pattern) the whiteboard uses.
Definition: visitor.hpp:33
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
std::size_t underlying_id() const
This unit's unique internal ID.
Definition: unit.hpp:392
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:50
game_board * gameboard
Definition: resources.cpp:20
actions::undo_list * undo_stack
Definition: resources.cpp:32
play_controller * controller
Definition: resources.cpp:21
filter_context * filter_con
Definition: resources.cpp:23
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
Definition: display.hpp:45
std::shared_ptr< recruit > recruit_ptr
Definition: typedefs.hpp:72
std::weak_ptr< action > weak_action_ptr
Definition: typedefs.hpp:64
std::shared_ptr< suppose_dead > suppose_dead_ptr
Definition: typedefs.hpp:76
std::ostream & operator<<(std::ostream &s, action_ptr action)
Definition: action.cpp:34
std::shared_ptr< move > move_ptr
Definition: typedefs.hpp:68
std::shared_ptr< action > action_ptr
Definition: typedefs.hpp:62
std::shared_ptr< attack > attack_ptr
Definition: typedefs.hpp:70
std::shared_ptr< arrow > arrow_ptr
Definition: typedefs.hpp:60
unit_const_ptr find_backup_leader(const unit &leader)
For a given leader on a keep, find another leader on another keep in the same castle.
Definition: utility.cpp:62
std::shared_ptr< action const > action_const_ptr
Definition: typedefs.hpp:63
std::shared_ptr< recall > recall_ptr
Definition: typedefs.hpp:74
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< attack_type > attack_ptr
Definition: ptr.hpp:33
static config unit_name(const unit *u)
Definition: reports.cpp:157
Encapsulates the map of the game.
Definition: location.hpp:38
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::set< std::size_t > secondary_numbers
std::vector< int > numbers_to_draw
std::vector< std::size_t > team_numbers
Tag for action_set's hashed_non_unique index.
Tag for action_set's hashed_non_unique index.
mock_party p
#define WRN_WB
Definition: typedefs.hpp:26
#define ERR_WB
Definition: typedefs.hpp:25
#define DBG_WB
Definition: typedefs.hpp:28
#define LOG_WB
Definition: typedefs.hpp:27
Various functions that implement the undoing (and redoing) of in-game commands.