The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
simulated_actions.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 - 2025
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
22
23#include "game_board.hpp"
24#include "game_config.hpp"
25#include "log.hpp"
26#include "map/map.hpp"
27#include "random.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
36namespace ai {
37
38static 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
44void helper_check_village(const map_location& loc, int side);
45void helper_place_unit(const unit& u, const map_location& loc);
47
48bool 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
98bool 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
126bool 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);
130 unit_ptr recall_unit = own_team.recall_list().extract_if_matches_id(unit_id);
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
142bool 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
156bool 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.
184void 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
215void 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:1475
map_location loc
Definition move.cpp:172
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
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
virtual const std::vector< team > & teams() const override
team & get_team(int i)
virtual const unit_map & units() const override
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:74
int recall_cost() const
Definition team.hpp:198
recall_list_manager & recall_list()
Definition team.hpp:242
void spend_gold(const int amount)
Definition team.hpp:221
unit_iterator find(std::size_t id)
Definition map.cpp:302
umap_retval_pair_t replace(const map_location &l, const unit_ptr &p)
Works like unit_map::add; but l is emptied first, if needed.
Definition map.cpp:216
unit_iterator find_leader(int side)
Definition map.cpp:320
umap_retval_pair_t insert(const 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:1253
A single unit type that the player may recruit.
Definition types.hpp:43
const t_string & type_name() const
The name of the unit in the current language setting.
Definition types.hpp:142
int cost() const
Definition types.hpp:176
This class represents a single unit of a specific type.
Definition unit.hpp:39
unit_ptr clone() const
Definition unit.hpp:127
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition unit.hpp:107
@ STATE_SLOWED
Definition unit.hpp:775
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition unit.hpp:777
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition unit.hpp:776
Standard logging facilities (interface).
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)
int combat_xp(int level)
rng * generator
This generator is automatically synced during synced context.
Definition random.cpp:60
game_board * gameboard
Definition resources.cpp:20
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:39
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:34
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:46
bool valid() const
Definition map.hpp:273
unit_type_data unit_types
Definition types.cpp:1499