Some progress on the server code
This commit is contained in:
commit
3b1136e7e0
8 changed files with 654 additions and 0 deletions
74
.gitignore
vendored
Normal file
74
.gitignore
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# This file is used to ignore files which are generated
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.autosave
|
||||||
|
*.a
|
||||||
|
*.core
|
||||||
|
*.moc
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*_pch.h.cpp
|
||||||
|
*_resource.rc
|
||||||
|
*.qm
|
||||||
|
.#*
|
||||||
|
*.*#
|
||||||
|
core
|
||||||
|
!core/
|
||||||
|
tags
|
||||||
|
.DS_Store
|
||||||
|
.directory
|
||||||
|
*.debug
|
||||||
|
Makefile*
|
||||||
|
*.prl
|
||||||
|
*.app
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Thumbs.db
|
||||||
|
*.res
|
||||||
|
*.rc
|
||||||
|
/.qmake.cache
|
||||||
|
/.qmake.stash
|
||||||
|
|
||||||
|
# qtcreator generated files
|
||||||
|
*.pro.user*
|
||||||
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
|
# xemacs temporary files
|
||||||
|
*.flc
|
||||||
|
|
||||||
|
# Vim temporary files
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# Visual Studio generated files
|
||||||
|
*.ib_pdb_index
|
||||||
|
*.idb
|
||||||
|
*.ilk
|
||||||
|
*.pdb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.vcproj
|
||||||
|
*vcproj.*.*.user
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.vcxproj
|
||||||
|
*vcxproj.*
|
||||||
|
|
||||||
|
# MinGW generated files
|
||||||
|
*.Debug
|
||||||
|
*.Release
|
||||||
|
|
||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
# --------
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
20
CMakeLists.txt
Normal file
20
CMakeLists.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(mislaborate LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
find_package(Protobuf REQUIRED)
|
||||||
|
include_directories(${Protobuf_INCLUDE_DIRS})
|
||||||
|
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS generic.proto)
|
||||||
|
|
||||||
|
add_executable(mislaborate-server
|
||||||
|
server.cpp
|
||||||
|
server/Connection.hpp server/World.hpp
|
||||||
|
server/Connection.cpp server/World.cpp
|
||||||
|
${PROTO_SRCS} ${PROTO_HDRS}
|
||||||
|
)
|
||||||
|
target_link_libraries(mislaborate-server PRIVATE ${Protobuf_LIBRARIES})
|
||||||
|
target_compile_definitions(mislaborate-server PUBLIC BOOST_ASIO_HAS_CO_AWAIT)
|
74
generic.proto
Normal file
74
generic.proto
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
package Generic;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message Error {
|
||||||
|
string description = 1;
|
||||||
|
enum Code {
|
||||||
|
Unknown = 0;
|
||||||
|
BadString = 1;
|
||||||
|
TooLarge = 2;
|
||||||
|
BadUsername = 3;
|
||||||
|
UsernameAlreadyTaken = 4;
|
||||||
|
InvalidOpcode = 5;
|
||||||
|
AuthenticationRequired = 6;
|
||||||
|
IllegalOperation = 7;
|
||||||
|
OutOfRange = 8;
|
||||||
|
}
|
||||||
|
Code code = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SimpleAuth {
|
||||||
|
string username = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Operation {
|
||||||
|
enum Code {
|
||||||
|
Disconnect = 0;
|
||||||
|
Error = 1;
|
||||||
|
OK = 2;
|
||||||
|
SimpleAuth = 3;
|
||||||
|
_MinAuth = 4;
|
||||||
|
SettingsSync = 5;
|
||||||
|
PlayfieldSync = 6;
|
||||||
|
RobotUpdate = 7;
|
||||||
|
}
|
||||||
|
Code code = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message Vector2 {
|
||||||
|
float x = 1;
|
||||||
|
float y = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message Settings {
|
||||||
|
optional uint32 robotsPerClient = 1;
|
||||||
|
optional float maxDistancePerTurn = 2;
|
||||||
|
optional uint32 maxRobotHealth = 3;
|
||||||
|
optional uint32 maxRobotDamage = 4;
|
||||||
|
optional uint32 protectCooldown = 5;
|
||||||
|
optional uint32 healCooldown = 6;
|
||||||
|
optional uint32 healPerTurn = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Robot {
|
||||||
|
uint32 id = 1;
|
||||||
|
uint32 client = 2;
|
||||||
|
Vector2 position = 3;
|
||||||
|
enum State {
|
||||||
|
Idle = 0;
|
||||||
|
Attack = 1;
|
||||||
|
Protect = 2;
|
||||||
|
Heal = 3;
|
||||||
|
Dead = 4;
|
||||||
|
}
|
||||||
|
State state = 4;
|
||||||
|
uint32 health = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlayfieldSync {
|
||||||
|
repeated Robot robots = 1;
|
||||||
|
}
|
21
server.cpp
Normal file
21
server.cpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#include "generic.pb.h"
|
||||||
|
#include "server/Connection.hpp"
|
||||||
|
#include "server/World.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
boost::asio::io_context ioc;
|
||||||
|
Connection::Server server(ioc);
|
||||||
|
int port = 78432;
|
||||||
|
boost::asio::co_spawn(ioc, server.listen(port), boost::asio::detached);
|
||||||
|
std::cout << "Server has initialized and will now start listening on port " << port << '.' << std::endl;
|
||||||
|
ioc.run();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << "Failed to launch: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
127
server/Connection.cpp
Normal file
127
server/Connection.cpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
#include "Connection.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace Connection {
|
||||||
|
asio::awaitable<Packet> Client::receivePacket() {
|
||||||
|
Packet packet;
|
||||||
|
// Receive operation
|
||||||
|
packet.op = co_await receiveProtobuf<Generic::Operation>();
|
||||||
|
// Receive data
|
||||||
|
switch (packet.op.code()) {
|
||||||
|
case Generic::Operation::Error: {
|
||||||
|
packet.data = std::make_unique<Generic::Error>(co_await receiveProtobuf<Generic::Error>());
|
||||||
|
} break;
|
||||||
|
case Generic::Operation::SimpleAuth: {
|
||||||
|
packet.data = std::make_unique<Generic::SimpleAuth>(co_await receiveProtobuf<Generic::SimpleAuth>(30));
|
||||||
|
} break;
|
||||||
|
case Generic::Operation::Disconnect:
|
||||||
|
case Generic::Operation::OK:
|
||||||
|
break;
|
||||||
|
default: throw FatalError("Invalid opcode: "+std::to_string(packet.op.code()), Generic::Error::InvalidOpcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> Client::sendProtobuf(const google::protobuf::Message& buffer) {
|
||||||
|
// Send size
|
||||||
|
uint16_t size = buffer.ByteSizeLong();
|
||||||
|
co_await asio::async_write(socket, asio::buffer(&size, sizeof(size)), asio::use_awaitable);
|
||||||
|
// Send buffer
|
||||||
|
std::vector<char> rawBuffer(size);
|
||||||
|
buffer.SerializeToArray(rawBuffer.data(), rawBuffer.size());
|
||||||
|
co_await asio::async_write(socket, asio::buffer(rawBuffer.data(), rawBuffer.size()), asio::use_awaitable);
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> Client::sendPacket(const Packet& packet) {
|
||||||
|
// Send operation
|
||||||
|
co_await sendProtobuf(packet.op);
|
||||||
|
// Send data
|
||||||
|
if (packet.data) {
|
||||||
|
co_await sendProtobuf(*packet.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> Client::handlePacket(const Packet& packet) {
|
||||||
|
// Check if authentication is required and completed
|
||||||
|
if (packet.op.code() >= Generic::Operation::_MinAuth && !isAuthed()) {
|
||||||
|
throw FatalError("Authentication required", Generic::Error::AuthenticationRequired);
|
||||||
|
}
|
||||||
|
// Handle the packet
|
||||||
|
switch (packet.op.code()) {
|
||||||
|
case Generic::Operation::Disconnect: {
|
||||||
|
good = 0;
|
||||||
|
} break;
|
||||||
|
case Generic::Operation::SimpleAuth: {
|
||||||
|
const Generic::SimpleAuth& authData = *static_cast<Generic::SimpleAuth*>(packet.data.get());
|
||||||
|
// Check that username is not already taken
|
||||||
|
if (std::find_if(clients.begin(), clients.end(), [&, this] (const auto& o) {return o->getUsername() == authData.username();}) != clients.end()) {
|
||||||
|
throw Error("Username has already been taken", Generic::Error::UsernameAlreadyTaken);
|
||||||
|
}
|
||||||
|
// Check that username is "good" (no unprintable characters and stuff like that)
|
||||||
|
//TODO...
|
||||||
|
// Set username
|
||||||
|
username = std::move(authData.username());
|
||||||
|
// Send OK
|
||||||
|
co_await sendOK();
|
||||||
|
} break;
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> Client::connect() {
|
||||||
|
// Send initial "OK"
|
||||||
|
try {
|
||||||
|
co_await sendOK();
|
||||||
|
} catch (...) {
|
||||||
|
good = false;
|
||||||
|
}
|
||||||
|
// Run while connection is good
|
||||||
|
while (good) {
|
||||||
|
std::optional<Error> error;
|
||||||
|
try {
|
||||||
|
co_await handlePacket(co_await receivePacket());
|
||||||
|
} catch (FatalError& e) {
|
||||||
|
error = std::move(e);
|
||||||
|
good = false;
|
||||||
|
} catch (Error& e) {
|
||||||
|
error = std::move(e);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
error = Error("Internal server error: "+std::string(e.what()), Generic::Error::Unknown);
|
||||||
|
good = false;
|
||||||
|
}
|
||||||
|
if (error.has_value()) {
|
||||||
|
co_await sendPacket(error.value().packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to send disconnect
|
||||||
|
try {
|
||||||
|
Packet packet;
|
||||||
|
packet.op.set_code(Generic::Operation::Disconnect);
|
||||||
|
co_await sendPacket(packet);
|
||||||
|
} catch (...) {}
|
||||||
|
// Close socket
|
||||||
|
boost::system::error_code ec;
|
||||||
|
socket.close(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> Server::listen(int port) {
|
||||||
|
try {
|
||||||
|
// Create acceptor
|
||||||
|
asio::ip::tcp::acceptor acceptor(ioc, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port));
|
||||||
|
// Handle all incoming connections
|
||||||
|
while (true) {
|
||||||
|
auto client = std::make_shared<Client>(ioc, clients);
|
||||||
|
co_await acceptor.async_accept(client->getSocket(), asio::use_awaitable);
|
||||||
|
asio::co_spawn(ioc, client->connect(), asio::detached);
|
||||||
|
clients.push_back(std::move(client));
|
||||||
|
}
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << "Failed to stay alive: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
server/Connection.hpp
Normal file
111
server/Connection.hpp
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
#ifndef _CONNECTION_HPP
|
||||||
|
#define _CONNECTION_HPP
|
||||||
|
#include "generic.pb.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <vector>
|
||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio/awaitable.hpp>
|
||||||
|
#include <boost/asio/use_awaitable.hpp>
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
#include <boost/asio/read.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace Connection {
|
||||||
|
using namespace boost;
|
||||||
|
|
||||||
|
|
||||||
|
struct Packet {
|
||||||
|
Generic::Operation op;
|
||||||
|
std::unique_ptr<google::protobuf::Message> data = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Error : public std::runtime_error {
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
Error(const std::string& description, Generic::Error::Code code) : std::runtime_error(description) {
|
||||||
|
packet.op.set_code(Generic::Operation::Error);
|
||||||
|
auto error = std::make_unique<Generic::Error>();
|
||||||
|
error->set_description(description);
|
||||||
|
error->set_code(code);
|
||||||
|
packet.data = std::move(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FatalError : public Error {
|
||||||
|
using Error::Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Client : public std::enable_shared_from_this<Client> {
|
||||||
|
std::vector<std::shared_ptr<Client>>& clients;
|
||||||
|
asio::ip::tcp::socket socket;
|
||||||
|
std::string username;
|
||||||
|
bool good = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Client(asio::io_context& ioc, std::vector<std::shared_ptr<Client>>& clients) : socket(ioc), clients(clients) {}
|
||||||
|
|
||||||
|
asio::awaitable<Packet> receivePacket();
|
||||||
|
asio::awaitable<void> sendProtobuf(const google::protobuf::Message& buffer);
|
||||||
|
asio::awaitable<void> sendPacket(const Packet& packet);
|
||||||
|
asio::awaitable<void> handlePacket(const Packet& packet);
|
||||||
|
asio::awaitable<void> connect();
|
||||||
|
|
||||||
|
template<class Buffer>
|
||||||
|
asio::awaitable<Buffer> receiveProtobuf(uint16_t maxSize = 1024) {
|
||||||
|
// Receive size
|
||||||
|
uint16_t size;
|
||||||
|
co_await asio::async_read(socket, asio::buffer(&size, sizeof(size)), asio::use_awaitable);
|
||||||
|
// Check size
|
||||||
|
if (size > maxSize) {
|
||||||
|
throw FatalError("Buffer too large ("+std::to_string(size)+" > "+std::to_string(maxSize)+')', Generic::Error::TooLarge);
|
||||||
|
}
|
||||||
|
// Receive protobuf
|
||||||
|
Buffer protobuf;
|
||||||
|
if (size) {
|
||||||
|
std::vector<char> rawProtobuf(size);
|
||||||
|
co_await asio::async_read(socket, asio::buffer(rawProtobuf.data(), rawProtobuf.size()), asio::use_awaitable);
|
||||||
|
// Parse protobuf
|
||||||
|
protobuf.ParseFromArray(rawProtobuf.data(), static_cast<int>(size));
|
||||||
|
}
|
||||||
|
// Return it
|
||||||
|
co_return protobuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> sendOK() {
|
||||||
|
Packet packet;
|
||||||
|
packet.op.set_code(Generic::Operation::OK);
|
||||||
|
co_await sendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
const auto& getUsername() const {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
bool isGood() const {
|
||||||
|
return good;
|
||||||
|
}
|
||||||
|
bool isAuthed() const {
|
||||||
|
return good && !username.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Server {
|
||||||
|
asio::io_context& ioc;
|
||||||
|
std::vector<std::shared_ptr<Client>> clients;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Server(asio::io_context& ioc) : ioc(ioc) {}
|
||||||
|
|
||||||
|
asio::awaitable<void> listen(int port);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
130
server/World.cpp
Normal file
130
server/World.cpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#include "World.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace World {
|
||||||
|
void fillSettings(Generic::Settings& settings) {
|
||||||
|
if (!settings.has_maxdistanceperturn()) {
|
||||||
|
settings.set_maxdistanceperturn(0.02f);
|
||||||
|
}
|
||||||
|
if (!settings.has_robotsperclient()) {
|
||||||
|
settings.set_robotsperclient(50);
|
||||||
|
}
|
||||||
|
if (!settings.has_maxrobothealth()) {
|
||||||
|
settings.set_maxrobothealth(20);
|
||||||
|
}
|
||||||
|
if (!settings.has_maxrobotdamage()) {
|
||||||
|
settings.set_maxrobotdamage(4);
|
||||||
|
}
|
||||||
|
if (!settings.has_protectcooldown()) {
|
||||||
|
settings.set_protectcooldown(50);
|
||||||
|
}
|
||||||
|
if (!settings.has_healcooldown()) {
|
||||||
|
settings.set_healcooldown(20);
|
||||||
|
}
|
||||||
|
if (!settings.has_healperturn()) {
|
||||||
|
settings.set_healperturn(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::awaitable<void> Playfield::reset() {
|
||||||
|
robots.clear();
|
||||||
|
const std::vector<std::vector<Vector2>> robotsPlacementMap = {
|
||||||
|
{{0.5f, 0.5f}},
|
||||||
|
{{0.0f, 0.0f}, {1.0f, 1.0f}},
|
||||||
|
{{0.0f, 0.0f}, {0.5f, 0.5f}, {1.0f, 1.0f}},
|
||||||
|
{{0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}}
|
||||||
|
};
|
||||||
|
const auto& robotsPlacement = robotsPlacementMap.at(clients.size());
|
||||||
|
for (unsigned client = 0; client != clients.size(); client++) {
|
||||||
|
const auto genericTeamRobotsPlacement = robotsPlacement[client].toGeneric();
|
||||||
|
for (unsigned robot = 0; robot != settings.robotsperclient(); robot++) {
|
||||||
|
// Create robot
|
||||||
|
Generic::Robot genericRobot;
|
||||||
|
genericRobot.set_id(robot);
|
||||||
|
genericRobot.set_client(client);
|
||||||
|
genericRobot.set_state(Generic::Robot::Idle);
|
||||||
|
genericRobot.set_health(20);
|
||||||
|
*genericRobot.mutable_position() = genericTeamRobotsPlacement;
|
||||||
|
// Add to list
|
||||||
|
robots.push_back({genericRobot});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Broadcast new playfield
|
||||||
|
auto playfieldSync = getPlayfieldSync();
|
||||||
|
for (auto& client : clients) {
|
||||||
|
Connection::Packet packet;
|
||||||
|
packet.op.set_code(Generic::Operation::RobotUpdate);
|
||||||
|
packet.data = std::make_unique<Generic::PlayfieldSync>(playfieldSync);
|
||||||
|
co_await client->sendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::awaitable<void> Playfield::updateRobot(const Connection::Client *sender, Generic::Robot& newRobot) {
|
||||||
|
turn++;
|
||||||
|
// Get local robot
|
||||||
|
auto& localRobot = getLocalRobot(sender, newRobot.id());
|
||||||
|
// Get distance travelled
|
||||||
|
auto distanceTravelled = getDistanceBetween(*localRobot, newRobot);
|
||||||
|
if (distanceTravelled != 0.0f) {
|
||||||
|
// Check that robot is able to travel
|
||||||
|
if (newRobot.state() != Generic::Robot::Idle) {
|
||||||
|
throw Connection::Error("Can't travel right now", Generic::Error::IllegalOperation);
|
||||||
|
}
|
||||||
|
// Check that robot didn't travel too far
|
||||||
|
if (distanceTravelled > settings.maxdistanceperturn()) {
|
||||||
|
throw Connection::Error("Can't travel that far ("+std::to_string(distanceTravelled)+" > "+std::to_string(settings.maxdistanceperturn())+')', Generic::Error::IllegalOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for illegal changes
|
||||||
|
if (localRobot->health() != newRobot.health()) {
|
||||||
|
throw Connection::Error("Can't change value", Generic::Error::IllegalOperation);
|
||||||
|
}
|
||||||
|
// Apply heal
|
||||||
|
if (newRobot.state() == Generic::Robot::Heal) {
|
||||||
|
newRobot.set_health(localRobot->health() + settings.healperturn());
|
||||||
|
if (newRobot.health() > settings.maxrobothealth()) {
|
||||||
|
newRobot.set_health(settings.maxrobothealth());
|
||||||
|
newRobot.set_state(Generic::Robot::Idle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for state change
|
||||||
|
if (localRobot->state() != newRobot.state()) {
|
||||||
|
// Enforce cooldowns
|
||||||
|
if (newRobot.state() == Generic::Robot::Protect && turn - localRobot.stateTimer < settings.protectcooldown()
|
||||||
|
|| newRobot.state() == Generic::Robot::Heal && turn - localRobot.stateTimer < settings.healcooldown()) {
|
||||||
|
throw Connection::Error("Can't change state yet (cooldown still in progress)", Generic::Error::IllegalOperation);
|
||||||
|
}
|
||||||
|
// Reset timer
|
||||||
|
localRobot.stateTimer = turn;
|
||||||
|
}
|
||||||
|
// Apply changes to local robot
|
||||||
|
*localRobot = newRobot;
|
||||||
|
// Broadcast new robot
|
||||||
|
for (auto& client : clients) {
|
||||||
|
Connection::Packet packet;
|
||||||
|
packet.op.set_code(Generic::Operation::RobotUpdate);
|
||||||
|
packet.data = std::make_unique<Generic::Robot>(*localRobot);
|
||||||
|
co_await client->sendPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalRobot& Playfield::getLocalRobot(const Connection::Client *sender, unsigned robot) {
|
||||||
|
// Check that new robot is in range
|
||||||
|
if (robot >= robots.size()) {
|
||||||
|
throw Connection::Error("Robot is out of range ("+std::to_string(robot)+" >= "+std::to_string(robots.size())+')', Generic::Error::OutOfRange);
|
||||||
|
}
|
||||||
|
// Get local robot
|
||||||
|
auto& localRobot = robots[robot];
|
||||||
|
// Check that robot belongs to sender
|
||||||
|
if (clients[localRobot->client()].get() == sender) {
|
||||||
|
throw Connection::Error("Robot does not belong to you", Generic::Error::IllegalOperation);
|
||||||
|
}
|
||||||
|
// Return local robot
|
||||||
|
return localRobot;
|
||||||
|
}
|
||||||
|
}
|
97
server/World.hpp
Normal file
97
server/World.hpp
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef _WORLD_HPP
|
||||||
|
#define _WORLD_HPP
|
||||||
|
#include "generic.pb.h"
|
||||||
|
#include "Connection.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <boost/asio/awaitable.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace World {
|
||||||
|
void fillSettings(Generic::Settings& settings);
|
||||||
|
|
||||||
|
|
||||||
|
struct Vector2 {
|
||||||
|
float x, y;
|
||||||
|
|
||||||
|
inline static Vector2 makeFromGeneric(const Generic::Vector2& generic) {
|
||||||
|
return {generic.x(), generic.y()};
|
||||||
|
}
|
||||||
|
void fromGeneric(const Generic::Vector2& generic) {
|
||||||
|
x = generic.x();
|
||||||
|
y = generic.y();
|
||||||
|
}
|
||||||
|
auto toGeneric() const {
|
||||||
|
Generic::Vector2 fres;
|
||||||
|
fres.set_x(x);
|
||||||
|
fres.set_y(y);
|
||||||
|
return fres;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getDistanceTo(const Vector2& o) const {
|
||||||
|
return fabs(x - o.x) + fabs(y - o.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct LocalRobot {
|
||||||
|
Generic::Robot generic;
|
||||||
|
uint64_t stateTimer = 0;
|
||||||
|
|
||||||
|
auto& operator *() {
|
||||||
|
return generic;
|
||||||
|
}
|
||||||
|
auto operator ->() {
|
||||||
|
return &generic;
|
||||||
|
}
|
||||||
|
const auto& operator *() const {
|
||||||
|
return generic;
|
||||||
|
}
|
||||||
|
const auto operator ->() const {
|
||||||
|
return &generic;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Playfield {
|
||||||
|
std::vector<LocalRobot> robots;
|
||||||
|
Generic::Settings settings;
|
||||||
|
uint64_t turn = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<std::shared_ptr<Connection::Client>> clients;
|
||||||
|
|
||||||
|
Playfield(const std::vector<std::shared_ptr<Connection::Client>>& clients, const Generic::Settings& settings)
|
||||||
|
: clients(clients), settings(settings) {
|
||||||
|
fillSettings(this->settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::awaitable<void> reset();
|
||||||
|
boost::asio::awaitable<void> updateRobot(const Connection::Client *sender, Generic::Robot& newRobot);
|
||||||
|
LocalRobot& getLocalRobot(const Connection::Client *sender, unsigned robot);
|
||||||
|
|
||||||
|
float getDistanceBetween(const Generic::Robot& a, const Generic::Robot& b) {
|
||||||
|
return Vector2::makeFromGeneric(a.position()).getDistanceTo(Vector2::makeFromGeneric(b.position()));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getPlayfieldSync() const {
|
||||||
|
Generic::PlayfieldSync fres;
|
||||||
|
for (const auto& robot : robots) {
|
||||||
|
*fres.add_robots() = robot.generic;
|
||||||
|
}
|
||||||
|
return fres;
|
||||||
|
}
|
||||||
|
bool isMasterClient(const Connection::Client *client) const {
|
||||||
|
return client == clients.front().get();
|
||||||
|
}
|
||||||
|
const auto& getSettings() const {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
auto getTurn() const {
|
||||||
|
return turn;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
Loading…
Add table
Reference in a new issue