Symphony Of Empires
ai.cpp
Go to the documentation of this file.
1 // Symphony of Empires
2 // Copyright (C) 2021, Symphony of Empires contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // server/ai.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <cstdio>
26 #include <cstdlib>
27 #include <cstring>
28 #include <set>
29 #include <tbb/blocked_range.h>
30 #include <tbb/concurrent_vector.h>
31 #include <tbb/parallel_for.h>
32 #include <tbb/combinable.h>
33 
34 #include "eng3d/binary_image.hpp"
35 #include "eng3d/serializer.hpp"
36 #include "eng3d/log.hpp"
37 #include "eng3d/rand.hpp"
38 
39 #include "diplomacy.hpp"
40 #include "indpobj.hpp"
41 #include "province.hpp"
42 #include "server/economy.hpp"
43 #include "world.hpp"
44 #include "action.hpp"
45 #include "server/lua_api.hpp"
47 #include "server/ai.hpp"
48 
49 namespace AI {
50  void init(World& world);
51  void do_tick(World& world);
52 }
53 
54 std::vector<ProvinceId> g_water_provinces;
55 std::vector<AIManager> ai_man;
56 
57 void AI::init(World& world) {
58  g_water_provinces.reserve(world.provinces.size());
59  for(const auto& province : world.provinces)
60  if(world.terrain_types[province.terrain_type_id].is_water_body)
61  g_water_provinces.push_back(province);
62  ai_man.resize(world.nations.size());
63  for(auto& ai : ai_man)
64  ai.recalc_weights();
65 }
66 
67 void AI::do_tick(World& world) {
68  // Calculate military strengths of each nation
69  std::vector<float> nation_strengths(world.nations.size(), 0.f);
70  for(const auto& province : world.provinces) {
71  const auto& units = world.unit_manager.get_province_units(province.get_id());
72  for(const auto unit_id : units) {
73  const auto& unit = world.unit_manager.units[unit_id];
74  nation_strengths[unit.owner_id] += unit.get_strength();
75  }
76  }
77  for(const auto& nation : world.nations)
78  ai_man[nation].military_strength = nation_strengths[nation];
79 
80  // Do the AI turns in parallel
83  struct UnitMove {
84  NationId nation_id;
85  UnitId unit_id;
86  ProvinceId target_province_id;
87  };
89  struct BuildUnit {
90  NationId nation_id;
91  UnitTypeId unit_type_id;
92  ProvinceId province_id;
93  BuildingId building_id;
94  };
96  struct BuildingInvestment {
97  NationId nation_id;
98  ProvinceId province_id;
99  BuildingId building_id;
100  float amount;
101  };
103  tbb::parallel_for(tbb::blocked_range(world.nations.begin(), world.nations.end()), [&](auto& nations_range) {
104  for(auto& nation : nations_range) {
105  auto& ai = ai_man[nation];
106  if(!nation.exists()) { // Unconditionally surrender iff at war
107  if(!(world.time % world.ticks_per_month) && nation.ai_controlled)
108  for(auto& treaty : world.treaties)
109  for(auto& [other_nation, approval] : treaty.approval_status)
110  if(other_nation == nation)
111  approval = TreatyApproval::ACCEPTED;
112  continue;
113  }
114 
115  // Diplomacy
116  if(nation.ai_controlled) {
117  // Ally other people also warring the people we're warring
118  auto our_strength = ai.military_strength;
119  auto enemy_strength = 0.f;
120  std::vector<NationId> enemy_ids, ally_ids;
121  for(const auto& other : world.nations) {
122  if(other.get_id() != nation.get_id()) {
123  const auto& relation = world.get_relation(nation, other);
124  if(relation.has_war) {
125  enemy_strength += ai_man[other].military_strength;
126  enemy_ids.push_back(other);
127  } else if(relation.is_allied()) {
128  our_strength += ai_man[other].military_strength;
129  ally_ids.push_back(other);
130  }
131  }
132  }
133  auto advantage = glm::max(our_strength, 1.f) / glm::max(enemy_strength, glm::epsilon<float>());
134  if(advantage < ai.strength_threshold) {
135  // The enemy is bigger; so re-evaluate stances
136  for(const auto& other : world.nations) {
137  if(other.get_id() != nation.get_id()) {
138  const auto& relation = world.get_relation(nation, other);
139  if(!relation.has_war) {
140  // Propose an alliance iff we have mutual enemies
141  for(const auto enemy_id : enemy_ids) {
142  const auto& enemy_rel = world.get_relation(other, enemy_id);
143  if(enemy_rel.has_war)
144  alliance_proposals.local().emplace_back(nation, other);
145  }
146  }
147  }
148  }
149  }
150  }
151 
152  // War/unit management
153  if(nation.ai_do_cmd_troops) {
154  ai.calc_weights(nation);
155  ai.collect_eval_provinces(world, nation);
156  ai.calc_nation_risk(world, nation);
157  ai.calc_province_risk(world, nation);
158 
159  // Move units to provinces with highest risk
160  for(const auto province_id : ai.eval_provinces) {
161  const auto& province = world.provinces[province_id];
162  const auto& unit_ids = world.unit_manager.get_province_units(province_id);
163  for(const auto unit_id : unit_ids) {
164  auto& unit = world.unit_manager.units[unit_id];
165  if(unit.owner_id != nation || !unit.can_move()) continue;
166 
167  bool can_set_target = true;
168  if(unit.has_target_province())
169  can_set_target = ai.get_rand() > ai.override_threshold;
170 
171  if(can_set_target) {
172  const auto& highest_risk = ai.get_highest_priority_province(world, province, unit);
173  // Above we made sure high_risk province is valid for us to step in
174  //if(!world.terrain_types[highest_risk->terrain_type_id].is_water_body) continue;
175  if(highest_risk.get_id() != province.get_id()) {
176  UnitMove cmd{};
177  cmd.nation_id = nation.get_id();
178  cmd.unit_id = unit.get_id();
179  cmd.target_province_id = highest_risk.get_id();
180  unit_movements.local().push_back(cmd);
181  }
182  }
183  }
184  }
185  }
186 
187  // Build units inside buildings that are not doing anything
188  for(const auto province_id : nation.controlled_provinces) {
189  auto& province = world.provinces[province_id];
190  for(const auto& building_type : world.building_types) {
191  if(!building_type.can_build_military()) continue;
192  auto& building = province.buildings[static_cast<size_t>(building_type.get_id())];
193  if(!building.can_do_output(province, building_type.input_ids))
194  continue;
196  auto& unit_type = world.unit_types[rand() % world.unit_types.size()];
197 
198  BuildUnit cmd{};
199  cmd.nation_id = nation.get_id();
200  cmd.province_id = province_id;
201  cmd.building_id = BuildingId(building_type.get_id());
202  cmd.unit_type_id = unit_type.get_id();
203  build_units.local().push_back(cmd);
204  }
205  }
206 
207  // How do we know which factories we should be investing om? We first have to know
208  // if we can invest them in the first place, which is what "can_directly_control_factories"
209  // answers for us.
210  if(nation.ai_controlled && nation.can_directly_control_factories()) {
211  for(const auto province_id : nation.controlled_provinces) {
212  auto& province = world.provinces[province_id];
213 
214  // Obtain list of products
215  struct ProductInfo {
216  decltype(province.products.begin()) product;
217  CommodityId id;
218  };
219  std::vector<ProductInfo> v;
220  for(auto it = province.products.begin(); it != province.products.end(); it++)
221  v.push_back(ProductInfo{ it, std::distance(province.products.begin(), it) });
222  // Sort by most important to fullfill (higher D/S ratio)
223  std::sort(v.begin(), v.end(), [&](const auto& a, const auto& b) {
224  return a.product->ds_ratio() > b.product->ds_ratio();
225  });
226 
227  for (const auto& product_info : v) {
228  // We now have the most demanded product indice, we will now find an industry type
229  // we should invest on to make more of that product
230  const auto it = std::find_if(world.building_types.begin(), world.building_types.end(), [&](const auto& e) {
231  return e.output_id == product_info.id;
232  });
233 
234  if(it != world.building_types.end()) {
235  // For now, investing 15% of the budget on this industry seems reasonable
236  const auto investment = nation.budget / 15.f;
237  BuildingInvestment cmd{};
238  cmd.nation_id = nation.get_id();
239  cmd.province_id = province_id;
240  cmd.building_id = BuildingId(it->get_id());
241  cmd.amount = investment;
242  building_investments.local().push_back(cmd);
243  break;
244  }
245  }
246  }
247  }
248  }
249  });
250 
251  unit_movements.combine_each([&](const auto& list) {
252  for(const auto& e : list) {
253  const auto& target_province = world.provinces[e.target_province_id];
254  auto& unit = world.unit_manager.units[e.unit_id];
255  unit.set_target(target_province);
256  }
257  });
258 
259  build_units.combine_each([&](const auto& list) {
260  for(const auto& e : list) {
261  auto& province = world.provinces[e.province_id];
262  const auto& unit_type = world.unit_types[e.unit_type_id];
263  province.buildings[e.building_id].work_on_unit(unit_type);
264  }
265  });
266 
267  building_investments.combine_each([&](const auto& list) {
268  for(const auto& e : list) {
269  auto& nation = world.nations[e.nation_id];
270  nation.budget -= e.amount;
271  auto& province = world.provinces[e.province_id];
272  province.buildings[e.building_id].estate_state.invest(e.amount);
273  }
274  });
275 
278  alliance_proposals.combine_each([&](const auto& alliance_proposals_range) {
279  for(const auto& [nation_id, other_id] : alliance_proposals_range) {
280  const auto& nation = world.nations[nation_id];
281  const auto& other_nation = world.nations[other_id];
282  world.fire_special_event("special_alliance", nation.ref_name.c_str(), other_nation.ref_name.c_str());
283  }
284  });
285 }
std::vector< AIManager > ai_man
Definition: ai.cpp:55
std::vector< ProvinceId > g_water_provinces
Definition: ai.cpp:54
Eng3D::Freelist< Unit > units
Definition: unit.hpp:173
std::vector< UnitId > get_province_units(ProvinceId province_id) const
Definition: unit.hpp:165
Definition: world.hpp:114
UnitManager unit_manager
Definition: world.hpp:145
Definition: ai.cpp:49
void init(World &world)
Definition: ai.cpp:57
void do_tick(World &world)
Definition: ai.cpp:67
void parallel_for(T range, F &&func)