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