The Battle for Wesnoth  1.17.0-dev
simulated_actions.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
3  by Guorui Xi <kevin.xgr@gmail.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  * Implement simulated actions
18  * @file
19  */
20 
21 #include "ai/simulated_actions.hpp"
22 
23 #include "game_board.hpp"
24 #include "game_config.hpp"
25 #include "log.hpp"
26 #include "map/map.hpp"
27 #include "random.hpp"
28 #include "recall_list_manager.hpp"
29 #include "resources.hpp"
30 #include "team.hpp"
31 #include "units/unit.hpp"
32 #include "units/helper.hpp"
33 #include "units/ptr.hpp"
34 #include "units/types.hpp"
35 
36 namespace ai {
37 
38 static lg::log_domain log_ai_sim_actions("ai/sim_actions");
39 #define DBG_AI_SIM_ACTIONS LOG_STREAM(debug, log_ai_sim_actions)
40 #define LOG_AI_SIM_ACTIONS LOG_STREAM(info, log_ai_sim_actions)
41 #define WRN_AI_SIM_ACTIONS LOG_STREAM(warn, log_ai_sim_actions)
42 #define ERR_AI_SIM_ACTIONS LOG_STREAM(err, log_ai_sim_actions)
43 
44 void helper_check_village(const map_location& loc, int side);
45 void helper_place_unit(const unit& u, const map_location& loc);
46 void helper_advance_unit(const map_location& loc);
47 
48 bool simulated_attack(const map_location& attacker_loc, const map_location& defender_loc, double attacker_hp, double defender_hp){
49  LOG_AI_SIM_ACTIONS << "Simulated attack" << std::endl;
50 
52  unit_map::iterator defend_unit = resources::gameboard->units().find(defender_loc);
53 
54  LOG_AI_SIM_ACTIONS << attack_unit->type_name() << " at " << attacker_loc << " attack "
55  << defend_unit->type_name() << " at " << defender_loc << std::endl;
56  LOG_AI_SIM_ACTIONS << "attacker's hp before attack: " << attack_unit->hitpoints() << std::endl;
57  LOG_AI_SIM_ACTIONS << "defender's hp before attack: " << defend_unit->hitpoints() << std::endl;
58 
59  attack_unit->set_hitpoints(static_cast<int>(attacker_hp));
60  defend_unit->set_hitpoints(static_cast<int>(defender_hp));
61 
62  LOG_AI_SIM_ACTIONS << "attacker's hp after attack: " << attack_unit->hitpoints() << std::endl;
63  LOG_AI_SIM_ACTIONS << "defender's hp after attack: " << defend_unit->hitpoints() << std::endl;
64 
65  int attacker_xp = game_config::combat_xp(defend_unit->level());
66  int defender_xp = game_config::combat_xp(attack_unit->level());
67  bool attacker_died = false;
68  bool defender_died = false;
69  if(attack_unit->hitpoints() <= 0){
70  attacker_xp = 0;
71  defender_xp = game_config::kill_xp(attack_unit->level());
72  (resources::gameboard->units()).erase(attacker_loc);
73  attacker_died = true;
74  }
75 
76  if(defend_unit->hitpoints() <= 0){
77  defender_xp = 0;
78  attacker_xp = game_config::kill_xp(defend_unit->level());
79  (resources::gameboard->units()).erase(defender_loc);
80  defender_died = true;
81  }
82 
83  if(!attacker_died){
84  attack_unit->set_experience(attack_unit->experience()+attacker_xp);
85  helper_advance_unit(attacker_loc);
86  simulated_stopunit(attacker_loc, true, true);
87  }
88 
89  if(!defender_died){
90  defend_unit->set_experience(defend_unit->experience()+defender_xp);
91  helper_advance_unit(defender_loc);
92  simulated_stopunit(defender_loc, true, true);
93  }
94 
95  return true;
96 }
97 
98 bool simulated_move(int side, const map_location& from, const map_location& to, int steps, map_location& unit_location)
99 {
100  LOG_AI_SIM_ACTIONS << "Simulated move" << std::endl;
101 
102  // In simulation, AI should not know if there is a enemy's ambusher.
103  auto [move_unit, success] = resources::gameboard->units().move(from, to);
104 
105  if(!success) {
106  // This happened because in some CAs like get_village_phase and move_leader_to_keep phase,
107  // if the destination is already occupied will not be checked before execute. Just silent
108  // errors in ai/actions and tell rca the game state isn't changed.
109  unit_location = to;
110  return false;
111  }
112 
113  move_unit->set_movement(move_unit->movement_left()-steps); // Following original logic, remove_movement_ will be considered outside.
114 
115  unit_location = move_unit->get_location(); // For check_after.
116 
117  LOG_AI_SIM_ACTIONS << move_unit->type_name() << " move from " << from << " to " << to << std::endl;
118 
119  if(resources::gameboard->map().is_village(to)){
120  helper_check_village(to, side);
121  }
122 
123  return true;
124 }
125 
126 bool simulated_recall(int side, const std::string& unit_id, const map_location& recall_location){
127  LOG_AI_SIM_ACTIONS << "Simulated recall" << std::endl;
128 
129  team own_team = resources::gameboard->get_team(side);
131 
132  helper_place_unit(*recall_unit, recall_location);
133 
134  own_team.spend_gold(recall_unit->recall_cost()<0 ? own_team.recall_cost() : recall_unit->recall_cost());
135 
136  LOG_AI_SIM_ACTIONS << "recall " << recall_unit->type_name() << " at "
137  << recall_location << " spend " << own_team.recall_cost() << " gold" << std::endl;
138 
139  return true;
140 }
141 
142 bool simulated_recruit(int side, const unit_type* u, const map_location& recruit_location){
143  LOG_AI_SIM_ACTIONS << "Simulated recruit" << std::endl;
144 
145  unit_ptr recruit_unit = unit::create(*u, side, false); // Random traits, name and gender are not needed. This will cause "duplicate id conflicts" inside unit_map::insert(), but engine will manage this issue correctly.
146  helper_place_unit(*recruit_unit, recruit_location);
147 
149 
150  LOG_AI_SIM_ACTIONS << "recruit " << u->type_name() << " at "
151  << recruit_location << " spend " << u->cost() << " gold" << std::endl;
152 
153  return true;
154 }
155 
156 bool simulated_stopunit(const map_location& unit_location, bool remove_movement, bool remove_attacks){
157  LOG_AI_SIM_ACTIONS << "Simulated stopunit" << std::endl;
158 
159  unit_map::iterator stop_unit = resources::gameboard->units().find(unit_location);
160  bool changed = false;
161  if(remove_movement){
162  stop_unit->set_movement(0, true);
163  LOG_AI_SIM_ACTIONS << "remove (" << stop_unit->get_location() << ") " << stop_unit->type_name() << "'s movement" << std::endl;
164  changed = true;
165  }
166  if(remove_attacks){
167  stop_unit->set_attacks(0);
168  LOG_AI_SIM_ACTIONS << "remove (" << stop_unit->get_location() << ") " << stop_unit->type_name() << "'s attacks" << std::endl;
169  changed = true;
170  }
171 
172  return changed;
173 }
174 
176  LOG_AI_SIM_ACTIONS << "Simulated synced_command" << std::endl;
177 
178  DBG_AI_SIM_ACTIONS << "Trigger dummy synced_command_result::do_execute()" << std::endl;
179 
180  return false;
181 }
182 
183 // Helper functions.
184 void helper_check_village(const map_location& loc, int side){
185  std::vector<team> &teams = resources::gameboard->teams();
186  team *t = static_cast<unsigned>(side - 1) < teams.size() ? &teams[side - 1] : nullptr;
187  if(t && t->owns_village(loc)){
188  return;
189  }
190 
191  bool has_leader = resources::gameboard->units().find_leader(side).valid();
192 
193  // Strip the village off all other sides.
194  int old_owner_side = 0;
195  for(std::vector<team>::iterator i = teams.begin(); i != teams.end(); ++i){
196  int i_side = std::distance(teams.begin(), i) + 1;
197  if(!t || has_leader || t->is_enemy(i_side)){
198  if(i->owns_village(loc)){
199  old_owner_side = i_side;
200  i->lose_village(loc);
201  DBG_AI_SIM_ACTIONS << "side " << i_side << " losts village at " << loc << std::endl;
202  }
203  }
204  }
205 
206  // Get the village if have leader.
207  if (!t) return;
208 
209  if(has_leader){
210  t->get_village(loc, old_owner_side, nullptr);
211  DBG_AI_SIM_ACTIONS << "side " << side << " gets village at " << loc << std::endl;
212  }
213 }
214 
215 void helper_place_unit(const unit& u, const map_location& loc){
216  unit_ptr new_unit = u.clone();
217  new_unit->set_movement(0, true);
218  new_unit->set_attacks(0);
219  new_unit->heal_fully();
220  new_unit->set_location(loc);
221 
222  auto [new_unit_itor, success] = resources::gameboard->units().insert(new_unit);
223  assert(success);
224 
225  if(resources::gameboard->map().is_village(loc)){
226  helper_check_village(loc, new_unit_itor->side());
227  }
228 }
229 
231  // Choose advanced unit type randomly.
232  // First check if the unit has enough experience and can advance.
233  // Then get all possible options, include modification advancements, like {AMLA DEFAULT} in cfg.
234  // And then randomly choose one to advanced to.
235 
237 
238  if(!unit_helper::will_certainly_advance(advance_unit))
239  return;
240 
241  const std::vector<std::string>& options = advance_unit->advances_to();
242  std::vector<config> mod_options = advance_unit->get_modification_advances();
243  int options_num = unit_helper::number_of_possible_advances(*advance_unit);
244 
245  std::size_t advance_choice = randomness::generator->get_random_int(0, options_num-1);
246  unit_ptr advanced_unit = (*advance_unit).clone();
247 
248  if(advance_choice < options.size()){
249  std::string advance_unit_typename = options[advance_choice];
250  const unit_type *advanced_type = unit_types.find(advance_unit_typename);
251  if(!advanced_type) {
252  ERR_AI_SIM_ACTIONS << "Simulating advancing to unknown unit type: " << advance_unit_typename;
253  assert(false && "simulating to unknown unit type");
254  }
255  advanced_unit->set_experience(advanced_unit->experience_overflow());
256  advanced_unit->advance_to(*advanced_type);
257  advanced_unit->heal_fully();
258  advanced_unit->set_state(unit::STATE_POISONED, false);
259  advanced_unit->set_state(unit::STATE_SLOWED, false);
260  advanced_unit->set_state(unit::STATE_PETRIFIED, false);
261  }else{
262  const config &mod_option = mod_options[advance_choice-options.size()];
263  advanced_unit->set_experience(advanced_unit->experience_overflow());
264  advanced_unit->add_modification("advancement", mod_option);
265  }
266 
267  resources::gameboard->units().replace(loc, advanced_unit);
268  LOG_AI_SIM_ACTIONS << advance_unit->type_name() << " at " << loc << " advanced to " << advanced_unit->type_name() << std::endl;
269 }
270 
271 }// End namespace
int kill_xp(int level)
Definition: game_config.hpp:48
int combat_xp(int level)
Definition: game_config.hpp:53
bool simulated_synced_command()
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1263
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:85
virtual const unit_map & units() const override
Definition: game_board.hpp:112
This class represents a single unit of a specific type.
Definition: unit.hpp:121
void helper_place_unit(const unit &u, const map_location &loc)
umap_retval_pair_t insert(unit_ptr p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:134
unit_iterator find_leader(int side)
Definition: map.cpp:328
bool will_certainly_advance(const unit_map::iterator &u)
Encapsulates the logic for deciding whether an iterator u points to a unit that can advance...
Definition: helper.cpp:35
bool recall_unit(const std::string &id, team &current_team, const map_location &loc, const map_location &from, map_location::DIRECTION facing, bool show, bool use_undo)
Recalls the unit with the indicated ID for the provided team.
Definition: create.cpp:737
unit_ptr clone() const
Definition: unit.hpp:210
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:852
unit_type_data unit_types
Definition: types.cpp:1482
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:853
#define DBG_AI_SIM_ACTIONS
bool simulated_attack(const map_location &attacker_loc, const map_location &defender_loc, double attacker_hp, double defender_hp)
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:190
A single unit type that the player may recruit.
Definition: types.hpp:45
umap_retval_pair_t replace(const map_location &l, unit_ptr p)
Works like unit_map::add; but l is emptied first, if needed.
Definition: map.cpp:224
const config & options()
Definition: game.cpp:569
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:72
A small explanation about what&#39;s going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:61
team & get_team(int i)
Definition: game_board.hpp:97
bool simulated_recall(int side, const std::string &unit_id, const map_location &recall_location)
int cost() const
Definition: types.hpp:175
static lg::log_domain log_ai_sim_actions("ai/sim_actions")
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:141
game_board * gameboard
Definition: resources.cpp:21
void erase(const std::string &key)
Definition: general.cpp:201
bool is_enemy(int n) const
Definition: team.hpp:255
umap_retval_pair_t move(const map_location &src, const map_location &dst)
Moves a unit from location src to location dst.
Definition: map.cpp:93
void spend_gold(const int amount)
Definition: team.hpp:220
int recall_cost() const
Definition: team.hpp:205
Encapsulates the map of the game.
Definition: location.hpp:38
unit_iterator find(std::size_t id)
Definition: map.cpp:310
void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
Function which will advance the unit at loc to &#39;advance_to&#39;.
void helper_check_village(const map_location &loc, int side)
#define ERR_AI_SIM_ACTIONS
std::size_t i
Definition: function.cpp:967
void helper_advance_unit(const map_location &loc)
bool simulated_stopunit(const map_location &unit_location, bool remove_movement, bool remove_attacks)
int number_of_possible_advances(const unit &u)
Determines the total number of available advancements (of any kind) for a given unit.
Definition: helper.cpp:30
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:52
unit_ptr extract_if_matches_id(const std::string &unit_id, int *pos=nullptr)
Find a unit by id, and extract from this object if found.
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
void recruit_unit(const unit_type &u_type, int side_num, const map_location &loc, const map_location &from, bool show, bool use_undo)
Recruits a unit of the given type for the given side.
Definition: create.cpp:709
game_events::pump_result_t get_village(const map_location &, const int owner_side, game_data *fire_event)
Acquires a village from owner_side.
Definition: team.cpp:439
double t
Definition: astarsearch.cpp:65
bool simulated_move(int side, const map_location &from, const map_location &to, int steps, map_location &unit_location)
void attack_unit(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display)
Performs an attack.
Definition: attack.cpp:1556
bool simulated_recruit(int side, const unit_type *u, const map_location &recruit_location)
Standard logging facilities (interface).
#define LOG_AI_SIM_ACTIONS
recall_list_manager & recall_list()
Definition: team.hpp:227
bool owns_village(const map_location &loc) const
Definition: team.hpp:197
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
bool valid() const
Definition: map.hpp:274
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
void move_unit(const std::vector< map_location > &path, unit_ptr u, bool animate, map_location::DIRECTION dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:510
Implement simulated actions.