Symphony Of Empires
server_network.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/server_network.cpp
20 //
21 // Abstract:
22 // Does some important stuff.
23 // ----------------------------------------------------------------------------
24 
25 #include <cstring>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <mutex>
29 #include <chrono>
30 #include <thread>
31 #include "eng3d/log.hpp"
32 
33 #include "action.hpp"
34 #include "world.hpp"
36 #include "action.hpp"
37 #include "client/game_state.hpp"
38 
39 Server* g_server = nullptr;
40 Server::Server(GameState& _gs, const unsigned port, const unsigned max_conn)
41  : Eng3D::Networking::Server(port, max_conn),
42  gs{ _gs }
43 {
44  g_server = this;
45  Eng3D::Log::debug("server", Eng3D::translate_format("Deploying %zu threads for clients", n_clients));
46 
48  Policies policies;
49  Eng3D::Deser::deserialize(ar, policies);
50  client_data.selected_nation->set_policy(policies);
52  };
54  UnitId unit_id;
55  Eng3D::Deser::deserialize(ar, unit_id);
56  // Must control unit
57  auto& unit = g_world.unit_manager.units.at(unit_id);
58  if(client_data.selected_nation == nullptr || client_data.selected_nation->get_id() != unit.owner_id)
59  CXX_THROW(ServerException, "Nation does not control unit");
60 
61  ProvinceId province_id;
62  Eng3D::Deser::deserialize(ar, province_id);
63  auto& province = client_data.gs.world->provinces.at(province_id);
64 
65  if(unit.can_move()) {
66  Eng3D::Log::debug("server", translate_format("Unit changes targets to %s", province.ref_name.c_str()).c_str());
67  unit.set_path(province);
68  }
69  };
71  ProvinceId province_id;
72  Eng3D::Deser::deserialize(ar, province_id);
73  auto& province = client_data.gs.world->provinces.at(province_id);
74  BuildingTypeId building_type_id;
75  Eng3D::Deser::deserialize(ar, building_type_id);
76  NationId nation_id;
77  Eng3D::Deser::deserialize(ar, nation_id);
78  UnitTypeId unit_type_id;
79  Eng3D::Deser::deserialize(ar, unit_type_id);
81  auto& building = province.get_buildings().at(building_type_id);
82  const auto& unit_type = gs.world->unit_types.at(unit_type_id);
84  // Tell the building to build this specific unit type
85  building.work_on_unit(unit_type);
86  Eng3D::Log::debug("server", string_format("Building unit %s", unit_type.ref_name.c_str()));
87  };
89  ProvinceId province_id;
90  Eng3D::Deser::deserialize(ar, province_id);
91  auto& province = gs.world->provinces.at(province_id);
92  BuildingTypeId building_type_id;
93  Eng3D::Deser::deserialize(ar, building_type_id);
94  auto& building = province.buildings.at(building_type_id);
95  building.budget += building.get_upgrade_cost();
96  client_data.selected_nation->budget -= building.get_upgrade_cost();
97  Eng3D::Log::debug("server", string_format("Funding upgrade of buildin %s in %s", client_data.gs.world->building_types[building_type_id].ref_name.c_str(), client_data.selected_nation->ref_name.c_str()));
98  // Rebroadcast
99  this->broadcast(Action::BuildingAdd::form_packet(province, client_data.gs.world->building_types[building_type_id]));
100  };
102  ProvinceId province_id;
103  Eng3D::Deser::deserialize(ar, province_id);
104  auto& province = gs.world->provinces.at(province_id);
105  // Must not be already owned
106  if(client_data.selected_nation == nullptr)
107  CXX_THROW(ServerException, "You don't control a country");
108  province.owner_id = client_data.selected_nation->get_id();
109  // Rebroadcast
110  this->broadcast(packet);
111  };
113  std::string msg{};
114  Eng3D::Deser::deserialize(ar, msg);
115  Eng3D::Log::debug("server", "Message: " + msg);
116  // Rebroadcast
117  this->broadcast(packet);
118  };
120  TreatyId treaty_id;
121  Eng3D::Deser::deserialize(ar, treaty_id);
122  auto& treaty = gs.world->treaties.at(treaty_id);
123  TreatyApproval approval;
124  Eng3D::Deser::deserialize(ar, approval);
125  //Eng3D::Log::debug("server", selected_nation->ref_name + " approves treaty " + treaty->name + " A=" + (approval == TreatyApproval::ACCEPTED ? "YES" : "NO"));
126  if(!treaty.does_participate(*client_data.selected_nation))
127  CXX_THROW(ServerException, "Nation does not participate in treaty");
128  // Rebroadcast
129  this->broadcast(packet);
130  };
132  Treaty treaty{};
133  Eng3D::Deser::deserialize(ar, treaty.clauses);
134  Eng3D::Deser::deserialize(ar, treaty.name);
135  Eng3D::Deser::deserialize(ar, treaty.sender_id);
136  Eng3D::Deser::deserialize(ar, treaty.receiver_id);
137  // Validate data
138  if(treaty.clauses.empty())
139  CXX_THROW(ServerException, "Clause-less treaty");
140  // Obtain participants of the treaty
141  std::vector<NationId> approver_nations;
142  for(auto& clause : treaty.clauses) {
143  approver_nations.push_back(clause->receiver_id);
144  approver_nations.push_back(clause->sender_id);
145  }
146  std::sort(approver_nations.begin(), approver_nations.end());
147  auto last = std::unique(approver_nations.begin(), approver_nations.end());
148  approver_nations.erase(last, approver_nations.end());
149 
150  Eng3D::Log::debug("server", string_format("Participants of treaty %s", treaty.name.c_str()));
151  // Then fill as undecided (and ask nations to sign this treaty)
152  for(auto& nation_id : approver_nations) {
153  treaty.approval_status.emplace_back(nation_id, TreatyApproval::UNDECIDED);
154  Eng3D::Log::debug("server", g_world.nations[nation_id].ref_name.c_str());
155  }
156  // The sender automatically accepts the treaty (they are the ones who drafted it)
157  auto it = std::find_if(treaty.approval_status.end(), treaty.approval_status.end(), [&client_data](const auto& e) {
158  return e.first == *client_data.selected_nation;
159  });
160  it->second = TreatyApproval::ACCEPTED;
161  g_world.insert(treaty);
162 
163  // Rebroadcast to client
164  // We are going to add a treaty to the client
165  Eng3D::Deser::Archive tmp_ar{};
166  auto action = ActionType::TREATY_ADD;
167  Eng3D::Deser::serialize(tmp_ar, action);
168  Eng3D::Deser::serialize(tmp_ar, treaty);
169  auto tmp_packet = packet;
170  tmp_packet.data(tmp_ar.get_buffer(), tmp_ar.size());
171  this->broadcast(tmp_packet);
172  };
174  // Find event by reference name
175  Event event{};
176  Eng3D::Deser::deserialize(ar, event);
177  // Find decision by reference name
178  decltype(Decision::ref_name) ref_name;
179  Eng3D::Deser::deserialize(ar, ref_name);
180  auto decision = std::find_if(event.decisions.begin(), event.decisions.end(), [&ref_name](const auto& o) {
181  return o.ref_name.get_string() == ref_name.get_string();
182  });
183  if(decision == event.decisions.end())
184  CXX_THROW(ServerException, translate_format("Decision %s not found", ref_name.c_str()));
185  event.take_decision(*client_data.selected_nation, *decision);
186  //Eng3D::Log::debug("server", "Event " + local_event.ref_name + " takes descision " + ref_name + " by nation " + selected_nation->ref_name);
187  };
189  NationId nation_id;
190  Eng3D::Deser::deserialize(ar, nation_id);
191  auto& nation = gs.world->nations.at(nation_id);
192  Eng3D::Deser::deserialize(ar, nation.ai_do_cmd_troops);
193  Eng3D::Deser::deserialize(ar, nation.ai_controlled);
194  client_data.selected_nation = &nation;
195  Eng3D::Log::debug("server", Eng3D::translate_format("Nation %s selected by client %s", client_data.selected_nation->ref_name.c_str(), client_data.username.c_str()));
196  };
198  NationId nation_id;
199  Eng3D::Deser::deserialize(ar, nation_id);
200  auto& nation = gs.world->nations.at(nation_id);
201  Eng3D::Deser::deserialize(ar, client_data.username);
202  client_data.selected_nation = &nation;
203  // Tell all other clients about this player
204  this->broadcast(packet);
205  };
207  NationId nation_id;
208  Eng3D::Deser::deserialize(ar, nation_id);
209  auto& nation = gs.world->nations.at(nation_id);
210  client_data.selected_nation->declare_war(nation);
211  };
213  TechnologyId technology_id;
214  Eng3D::Deser::deserialize(ar, technology_id);
215  auto& technology = gs.world->technologies.at(technology_id);
216  if(!client_data.selected_nation->can_research(technology))
217  CXX_THROW(ServerException, "Can't research tech at the moment");
218  client_data.selected_nation->focus_tech_id = technology;
219  };
220 
222  for(size_t i = 0; i < n_clients; i++)
223  clients[i].is_connected = false;
224  clients_data.push_back(gs);
225  clients_extra_data.resize(n_clients, nullptr);
226  // "Starting" thread, this one will wake up all the other ones
227  clients[0].thread = std::make_unique<std::thread>(&Server::netloop, this, 0);
228 }
229 
230 void Server::on_connect(int conn_fd, int id) {
231  auto& cl = clients[id];
232  auto& client_data = clients_data[id];
233  Eng3D::Networking::Packet packet(conn_fd);
234  packet.pred = [this]() {
235  return this->run == true;
236  };
237  { // Read the data from client
239  packet.recv();
240  ar.set_buffer(packet.data(), packet.size());
241 
242  ActionType action;
243  Eng3D::Deser::deserialize(ar, action);
244  Eng3D::Deser::deserialize(ar, cl.username);
245  client_data.username = cl.username;
246  }
247 
248  { // Tell all other clients about the connection of this new client
250  Eng3D::Deser::serialize<ActionType>(ar, ActionType::CONNECT);
251  packet.data(ar.get_buffer(), ar.size());
252  broadcast(packet);
253  }
254 
255  {
257  Eng3D::Deser::serialize<ActionType>(ar, ActionType::CHAT_MESSAGE);
258  Eng3D::Deser::serialize<std::string>(ar, "[" + cl.username + "] has connected");
259  packet.data(ar.get_buffer(), ar.size());
260  broadcast(packet);
261  }
262 }
263 
265  Eng3D::Networking::Packet packet{};
267  Eng3D::Deser::serialize<ActionType>(ar, ActionType::DISCONNECT);
268  packet.data(ar.get_buffer(), ar.size());
269  broadcast(packet);
270 }
271 
273  auto& client_data = clients_data[id];
274  ActionType action;
275  Eng3D::Deser::deserialize(ar, action);
276  if(client_data.selected_nation == nullptr && !(action == ActionType::SET_USERNAME || action == ActionType::CHAT_MESSAGE || action == ActionType::SELECT_NATION))
277  CXX_THROW(ServerException, Eng3D::translate_format("Unallowed operation %i without selected nation", static_cast<int>(action)));
278 
279  const std::scoped_lock lock(g_world.world_mutex);
280  //switch(action) {
281  const auto it = action_handlers.find(action);
282  if(it == action_handlers.cend())
283  CXX_THROW(ServerException, string_format("Unhandled action %u", static_cast<unsigned int>(action)));
284  it->second(client_data, packet, ar);
285 
286  // Update the state of the UI with the editor
287  if(gs.editor) gs.update_tick = true;
288 }
289 
293 void Server::netloop(int id) {
294  this->do_netloop([this](int i) {
295  clients[i].thread = std::make_unique<std::thread>(&Server::netloop, this, i);
296  }, id);
297 }
ActionType
Definition: action.hpp:32
@ DIPLO_DECLARE_WAR
Definition: action.hpp:59
@ SET_USERNAME
Definition: action.hpp:36
@ CHAT_MESSAGE
Definition: action.hpp:39
@ CONNECT
Definition: action.hpp:37
@ PROVINCE_COLONIZE
Definition: action.hpp:42
@ DISCONNECT
Definition: action.hpp:38
@ FOCUS_TECH
Definition: action.hpp:61
@ TREATY_ADD
Definition: action.hpp:57
@ BUILDING_ADD
Definition: action.hpp:52
@ NATION_TAKE_DECISION
Definition: action.hpp:45
@ DRAFT_TREATY
Definition: action.hpp:56
@ UNIT_CHANGE_TARGET
Definition: action.hpp:49
@ SELECT_NATION
Definition: action.hpp:35
@ CHANGE_TREATY_APPROVAL
Definition: action.hpp:55
@ BUILDING_START_BUILDING_UNIT
Definition: action.hpp:54
@ NATION_ENACT_POLICY
Definition: action.hpp:44
std::function< bool()> pred
Definition: network.hpp:138
size_t size() const
Definition: network.hpp:125
std::unique_ptr< std::thread > thread
Definition: network.hpp:154
void do_netloop(std::function< void(int i)> on_wake_thread, int id)
Definition: network.cpp:298
ServerClient * clients
Definition: network.hpp:185
std::atomic< bool > run
Definition: network.hpp:161
void broadcast(const Eng3D::Networking::Packet &packet)
This will broadcast the given packet to all clients currently on the server.
Definition: network.cpp:292
std::atomic< bool > update_tick
Definition: game_state.hpp:150
World * world
Definition: game_state.hpp:156
TechnologyId focus_tech_id
Definition: nation.hpp:139
float budget
Definition: nation.hpp:129
void set_policy(const Policies &policies)
Enacts a policy on a nation.
Definition: nation.cpp:130
std::vector< float > commodity_production
Definition: nation.hpp:141
bool can_research(const Technology &tech) const
Definition: nation.cpp:218
void declare_war(Nation &nation, std::vector< TreatyClause::BaseClause * > clauses=std::vector< TreatyClause::BaseClause * >())
Declare war.
Definition: nation.cpp:43
std::vector< ClientData > clients_data
void on_connect(int conn_fd, int id) override
void netloop(int id)
This is the handling thread-function for handling a connection to a single client Sending packets wil...
Server(GameState &gs, unsigned port=1825, unsigned max_conn=4)
std::unordered_map< ActionType, std::function< void(ClientData &client_data, const Eng3D::Networking::Packet &packet, Eng3D::Deser::Archive &ar)> > action_handlers
void on_disconnect() override
void handler(const Eng3D::Networking::Packet &packet, Eng3D::Deser::Archive &ar, int id) override
std::vector< Nation * > clients_extra_data
Eng3D::Freelist< Unit > units
Definition: unit.hpp:173
UnitManager unit_manager
Definition: world.hpp:145
void insert(T &ptr)
Definition: world.hpp:150
std::mutex world_mutex
Definition: world.hpp:225
TreatyApproval
Definition: diplomacy.hpp:258
void deserialize(Eng3D::Deser::Archive &ar, T &obj)
Definition: serializer.hpp:154
void serialize(Eng3D::Deser::Archive &ar, const T &obj)
Definition: serializer.hpp:144
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
std::string translate_format(const std::string_view format, Args &&... args)
String formatter, with translation.
Definition: string.hpp:128
Server * g_server
static Eng3D::Networking::Packet form_packet(const Province &province, const BuildingType &building_type)
Definition: action.cpp:90
Base class that serves as archiver, stores (in memory) the data required for serialization/deserializ...
Definition: serializer.hpp:64
T & at(size_t index)
Definition: freelist.hpp:64
const char * c_str() const
Definition: string.hpp:55
constexpr Id get_id() const
Definition: entity.hpp:152
Definition: event.hpp:54
Eng3D::StringRef ref_name
Definition: entity.hpp:161
#define CXX_THROW(class,...)
Definition: utils.hpp:98
World g_world
Definition: world.cpp:59