The Battle for Wesnoth  1.19.5+dev
simulated_actions.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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";
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;
56  LOG_AI_SIM_ACTIONS << "attacker's hp before attack: " << attack_unit->hitpoints();
57  LOG_AI_SIM_ACTIONS << "defender's hp before attack: " << defend_unit->hitpoints();
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();
63  LOG_AI_SIM_ACTIONS << "defender's hp after attack: " << defend_unit->hitpoints();
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";
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;
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";
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";
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";
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";
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";
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";
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";
169  changed = true;
170  }
171 
172  return changed;
173 }
174 
176  LOG_AI_SIM_ACTIONS << "Simulated synced_command";
177 
178  DBG_AI_SIM_ACTIONS << "Trigger dummy synced_command_result::do_execute()";
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(team& tm : teams) {
196  int i_side = tm.side();
197  if(!t || has_leader || t->is_enemy(i_side)){
198  if(tm.owns_village(loc)){
199  old_owner_side = i_side;
200  tm.lose_village(loc);
201  DBG_AI_SIM_ACTIONS << "side " << i_side << " losts village at " << loc;
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;
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 
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();
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  return;
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();
269 }
270 
271 }// End namespace
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:1562
void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
Function which will advance the unit at loc to 'advance_to'.
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
team & get_team(int i)
Definition: game_board.hpp:92
virtual const unit_map & units() const override
Definition: game_board.hpp:107
int get_random_int(int min, int max)
Definition: random.hpp:51
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.
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
int recall_cost() const
Definition: team.hpp:180
void spend_gold(const int amount)
Definition: team.hpp:195
recall_list_manager & recall_list()
Definition: team.hpp:201
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:216
unit_iterator find(std::size_t id)
Definition: map.cpp:302
unit_iterator find_leader(int side)
Definition: map.cpp:320
umap_retval_pair_t insert(unit_ptr p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:135
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:92
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:1265
A single unit type that the player may recruit.
Definition: types.hpp:43
int cost() const
Definition: types.hpp:172
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:138
This class represents a single unit of a specific type.
Definition: unit.hpp:133
unit_ptr clone() const
Definition: unit.hpp:221
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:201
@ STATE_SLOWED
Definition: unit.hpp:860
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:862
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:861
Standard logging facilities (interface).
bool recall_unit(const std::string &id, team &current_team, const map_location &loc, const map_location &from, map_location::direction facing)
Recalls the unit with the indicated ID for the provided team.
Definition: create.cpp:738
void recruit_unit(const unit_type &u_type, int side_num, const map_location &loc, const map_location &from)
Recruits a unit of the given type for the given side.
Definition: create.cpp:716
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:59
bool simulated_attack(const map_location &attacker_loc, const map_location &defender_loc, double attacker_hp, double defender_hp)
bool simulated_stopunit(const map_location &unit_location, bool remove_movement, bool remove_attacks)
void helper_check_village(const map_location &loc, int side)
void helper_place_unit(const unit &u, const map_location &loc)
bool simulated_move(int side, const map_location &from, const map_location &to, int steps, map_location &unit_location)
bool simulated_recruit(int side, const unit_type *u, const map_location &recruit_location)
bool simulated_synced_command()
static lg::log_domain log_ai_sim_actions("ai/sim_actions")
bool simulated_recall(int side, const std::string &unit_id, const map_location &recall_location)
void helper_advance_unit(const map_location &loc)
int kill_xp(int level)
Definition: game_config.hpp:48
int combat_xp(int level)
Definition: game_config.hpp:53
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
game_board * gameboard
Definition: resources.cpp:20
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:505
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:34
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:29
std::string & erase(std::string &str, const std::size_t start, const std::size_t len)
Erases a portion of a UTF-8 string.
Definition: unicode.cpp:103
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
#define DBG_AI_SIM_ACTIONS
#define ERR_AI_SIM_ACTIONS
#define LOG_AI_SIM_ACTIONS
Implement simulated actions.
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: map.hpp:273
unit_type_data unit_types
Definition: types.cpp:1500