Symphony Of Empires
unit.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 // unit.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <string>
26 #include <cassert>
27 #include <queue>
28 #include <unordered_map>
29 #include <unordered_set>
30 #include <algorithm>
31 #include <glm/glm.hpp>
32 #include <glm/vec3.hpp>
33 #include <glm/gtc/constants.hpp>
34 #include <glm/trigonometric.hpp>
35 #include <glm/geometric.hpp>
36 
37 #include "eng3d/pathfind.hpp"
38 #include "eng3d/log.hpp"
39 #include "eng3d/utils.hpp"
40 
42 #include "action.hpp"
43 #include "unit.hpp"
44 #include "province.hpp"
45 #include "world.hpp"
46 
47 template<>
48 struct std::hash<ProvinceId> {
49  std::size_t operator()(const ProvinceId& id) const noexcept {
50  return std::hash<decltype(id.id)>{}(id.id);
51  }
52 };
53 
54 //
55 // Unit
56 //
57 float Unit::attack(Unit& enemy) {
58  const auto prev_size = enemy.size;
60  // It's important that a size of zero nullifies the attack, this prevents the edge case
61  // of 1 v 1 units that kill each other
62  const float damage = (g_world.unit_types[type_id].attack * static_cast<float>(this->size)) * this->experience * 0.08f;
63  enemy.size -= glm::min(enemy.size, damage);
64  return prev_size - enemy.size;
65 }
66 
67 void Unit::set_target(const Province& province) {
68  if(province != this->province_id()) {
69  if(this->can_move()) {
70  this->target_province_id = province.get_id();
71  this->has_target = true;
72  this->days_left_until_move = this->days_to_move_to(province);
73  }
74  }
75 }
76 
77 glm::vec2 Unit::get_pos() const {
78  const auto& world = World::get_instance();
79  auto& province = world.provinces[this->province_id()];
80  return province.get_pos();
81 }
82 
84  // Don't know if this is cleaner than getting it from unit manager :thinking:
85  const auto& world = World::get_instance();
86  return world.unit_manager.unit_province[cached_id];
87 }
88 
89 float Unit::days_to_move_to(const Province& _province) const {
90  const auto& world = World::get_instance();
91  auto& start_province = world.provinces[this->province_id()];
92  auto& end_province = _province;
93 
94  const glm::vec2 world_size{ world.width, world.height };
95  const auto distance = start_province.euclidean_distance(end_province, world_size, 100.f) * world.terrain_types[start_province.terrain_type_id].penalty * world.terrain_types[end_province.terrain_type_id].penalty;
96  return distance;
97 }
98 
99 bool Unit::update_movement(UnitManager& unit_manager) {
100  if(this->has_target_province()) {
101  this->days_left_until_move--;
102  if(this->days_left_until_move <= 0) {
103  unit_manager.move_unit(this->get_id(), this->target_province_id);
104  this->stop_movement();
105  // Follow the path
106  if(!this->path.empty()) {
107  this->set_target(World::get_instance().provinces[this->path.back()]);
108  this->path.pop_back();
109  }
110  return true;
111  }
112  }
113  return false;
114 }
115 
117 void UnitManager::init(World& world) {
118  province_units.resize(world.provinces.size());
119 }
120 
121 void UnitManager::add_unit(Unit unit, ProvinceId unit_current_province) {
122  const auto index = units.add(unit);
123  units[index].cached_id = index;
124  if(index >= unit_province.size()) {
125  unit_province.push_back(unit_current_province);
126  } else {
127  unit_province[index] = unit_current_province;
128  }
129 
130  if(static_cast<size_t>(unit_current_province) >= province_units.size())
131  province_units.resize(static_cast<size_t>(unit_current_province) + 1);
132  province_units[unit_current_province].push_back(index);
133 
134  if(g_server != nullptr)
136 }
137 
139  if(g_server != nullptr)
141 
142  const auto current_province_id = unit_province[unit_id];
143  Eng3D::fast_erase(province_units[current_province_id], unit_id);
144  units.remove(unit_id);
145 
146  // Assert there was no duplication (id remaining after removal is troubling)
147  const auto& p = province_units[current_province_id];
148  assert(std::find(p.begin(), p.end(), unit_id) == p.end());
149 }
150 
151 void UnitManager::move_unit(UnitId unit_id, ProvinceId target_province_id) {
152  assert(units[unit_id].can_move()); // Must be able to move to perform this...
153  assert(unit_province[unit_id] != target_province_id); // Not setting to same province
154  auto& world = World::get_instance();
155 
156  assert(units[unit_id].province_id() == unit_province[unit_id]);
157  const auto current_province_id = unit_province[unit_id];
158  Eng3D::fast_erase(province_units[current_province_id], unit_id);
159 
160  // Assert there was no duplication (id remaining after removal is troubling)
161  const auto& p1 = province_units[current_province_id];
162  assert(std::find(p1.begin(), p1.end(), unit_id) == p1.end());
163  // Assert no duplication
164  const auto& p2 = province_units[target_province_id];
165  assert(std::find(p2.begin(), p2.end(), unit_id) == p2.end());
166 
167  unit_province[unit_id] = target_province_id;
168  province_units[target_province_id].push_back(unit_id);
169  if(g_server != nullptr)
170  g_server->broadcast(Action::UnitMove::form_packet(units[unit_id], world.provinces[target_province_id]));
171 
172  ProvinceId id;
173  for(const auto& unit_ids : province_units) {
174  for(const auto unit_id : unit_ids) {
175  const auto& unit = this->units[unit_id];
176  assert(unit.province_id() == id);
177  }
178  id = (ProvinceId)((size_t)id + 1);
179  }
180 
181  Eng3D::Log::debug("game", string_format("Moving unit id=%zu in %s->%s", (size_t)unit_id, g_world.provinces[current_province_id].name.c_str(), g_world.provinces[target_province_id].name.c_str()));
182 }
183 
184 void Unit::set_owner(const Nation& nation) {
185  this->owner_id = nation;
186 }
187 
188 void Unit::set_path(const Province& target) {
189  auto& world = World::get_instance();
190  auto start_id = world.unit_manager.get_unit_current_province(this->get_id());
191  if(start_id == target.get_id())
192  return;
193  this->path = Eng3D::Pathfind::get_path<ProvinceId>(start_id, target,
195  [&world](ProvinceId province_id) -> std::vector<ProvinceId> {
196  const auto& province = world.provinces[province_id];
197  auto result = province.neighbour_ids;
198  return result;
199  },
201  [&world](ProvinceId province1_id, ProvinceId province2_id) -> float {
202  const auto& province1 = world.provinces[province1_id];
203  const auto& province2 = world.provinces[province2_id];
204  const glm::vec2 world_size{ world.width, world.height };
205  constexpr auto radius = 100.f;
206  const auto sphere_coord1 = Eng3D::get_sphere_coord(world_size, province1.get_pos(), radius);
207  const auto sphere_coord2 = Eng3D::get_sphere_coord(world_size, province2.get_pos(), radius);
208  float cos_angle = glm::dot(sphere_coord1, sphere_coord2) / (radius * radius);
209  float angle = glm::acos(cos_angle);
210  float distance = angle / (2 * glm::pi<float>());
211  return distance;
212  });
213 
214  assert(this->path.size() >= 2); // Start + [optional] + End
215  assert(this->path.back() == start_id);
216  this->path.pop_back(); // Pop the start point
217 
218  this->set_target(world.provinces[this->path.back()]);
219  this->path.pop_back();
220 }
221 
222 float Unit::get_strength() const {
223  const auto& type = g_world.unit_types[this->type_id];
224  return (this->size * (type.attack + type.defense)) / 1000.f;
225 }
void broadcast(const Eng3D::Networking::Packet &packet)
This will broadcast the given packet to all clients currently on the server.
Definition: network.cpp:292
A single province, which is used to simulate economy in a "bulk-tiles" way instead of doing economica...
Definition: province.hpp:48
std::vector< ProvinceId > neighbour_ids
Definition: province.hpp:124
float euclidean_distance(const Province &other_province, glm::vec2 world_size, float radius) const
Definition: province.cpp:59
Roughly a batallion, consisting of approximately 500 soldiers each.
Definition: unit.hpp:80
float size
Definition: unit.hpp:132
float days_to_move_to(const Province &province) const
Definition: unit.cpp:89
void set_owner(const Nation &nation)
Definition: unit.cpp:184
UnitTypeId type_id
Definition: unit.hpp:127
void set_target(const Province &province)
Definition: unit.cpp:67
NationId owner_id
Definition: unit.hpp:128
void stop_movement()
Definition: unit.hpp:95
bool can_move() const
Checks if the unit can move (if it can set_province)
Definition: unit.hpp:107
float experience
Definition: unit.hpp:134
float get_strength() const
Definition: unit.cpp:222
ProvinceId province_id() const
Definition: unit.cpp:83
bool has_target_province() const
Definition: unit.hpp:117
float attack(Unit &enemy)
Definition: unit.cpp:57
glm::vec2 get_pos() const
Definition: unit.cpp:77
void set_path(const Province &target)
Definition: unit.cpp:188
bool update_movement(UnitManager &unit_manager)
Definition: unit.cpp:99
void add_unit(Unit unit, ProvinceId unit_current_province)
Definition: unit.cpp:121
Eng3D::Freelist< Unit > units
Definition: unit.hpp:173
std::vector< std::vector< UnitId > > province_units
Definition: unit.hpp:176
std::vector< ProvinceId > unit_province
Definition: unit.hpp:175
void init(World &world)
Fill in the relationship vectors for each nation.
Definition: unit.cpp:117
void move_unit(UnitId unit, ProvinceId target_province)
Definition: unit.cpp:151
void remove_unit(UnitId unit)
Definition: unit.cpp:138
Definition: world.hpp:114
static World & get_instance()
Definition: world.hpp:121
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
std::string string_format(const std::string_view format, Args &&... args)
String formatter.
Definition: string.hpp:100
void fast_erase(C &c, T value) noexcept
Definition: utils.hpp:128
glm::vec3 get_sphere_coord(glm::vec2 size, glm::vec2 pos, float radius)
Definition: utils.hpp:149
Server * g_server
static Eng3D::Networking::Packet form_packet(const Unit &unit)
Definition: action.cpp:110
static Eng3D::Networking::Packet form_packet(const Unit &unit, const Province &province)
Definition: action.cpp:136
static Eng3D::Networking::Packet form_packet(const Unit &unit)
Definition: action.cpp:130
size_t add(T &e)
Definition: freelist.hpp:35
void remove(size_t index)
Definition: freelist.hpp:48
constexpr Id get_id() const
Definition: entity.hpp:152
Id cached_id
Id used to speed up Id lookups on any context.
Definition: entity.hpp:106
std::size_t operator()(const ProvinceId &id) const noexcept
Definition: unit.cpp:49
World g_world
Definition: world.cpp:59