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