diff --git a/generic.proto b/generic.proto index 93ad063..287fea9 100644 --- a/generic.proto +++ b/generic.proto @@ -17,6 +17,7 @@ message Error { OutOfRange = 8; NotYourTurn = 9; RoomNotAvailable = 10; + RoomFull = 11; } Code code = 2; } @@ -32,13 +33,15 @@ message Operation { OK = 2; SimpleAuth = 3; _MinAuth = 4; - SettingsSync = 5; + RoomInfoSync = 5; PlayfieldSync = 6; RobotUpdate = 7; YourTurn = 8; MakeRoom = 9; JoinRoom = 10; JoinRandomRoom = 11; + LeaveRoom = 12; + UserInfoSync = 13; } Code code = 1; } @@ -58,11 +61,17 @@ message Settings { optional uint32 protectCooldown = 5; optional uint32 healCooldown = 6; optional uint32 healPerTurn = 7; + optional uint32 maxPlayers = 8; } +message UserReference { + uint64 clientId = 2; + uint64 userId = 3; +} message PublicUserInfo { string name = 1; + UserReference reference = 2; } message UserInfo { PublicUserInfo publicInfo = 1; diff --git a/generic.txt b/generic.txt new file mode 100644 index 0000000..b834d06 --- /dev/null +++ b/generic.txt @@ -0,0 +1,27 @@ +Server + +void Disconnect() // client closes connection +void Error(Error) +void OK() +SimpleAuth(SimpleAuth) -> UserInfoSync // client wants to authenticate anonymously +RobotUpdate(Robot) -> RobotUpdate // client wants to change robot +void RoomInfoSync(RoomInfo) <- TODO // client wants to change room info +MakeRoom() -> RoomInfoSync // client creates a room +JoinRoom(???) -> RoomInfoSync // client joins specific room +JoinRandomRoom() -> RoomInfoSync // client joins random room +LeaveRoom() -> LeaveRoom // client leaves current room + + +Client + +Disconnect() // server closes connection +Error(Error) +OK() +RobotUpdate(Robot) // this robot has changed +RoomInfoSync(RoomInfo) // your current rooms info has changed +UserInfoSync(UserInfo) // your user info has been changed +PlayfielSync(PlayfieldSync) // playfield has been created +RoomInfoSync(RoomInfo) // room info has changed +YourTurn() -> RobotUpdate // it's your turn +JoinRoom(PublicUserInfo) // player joins your current room +LeaveRoom(UserReference) // player leaves your current room diff --git a/server/Connection.cpp b/server/Connection.cpp index 0b32e55..a1b3e5c 100644 --- a/server/Connection.cpp +++ b/server/Connection.cpp @@ -1,4 +1,5 @@ #include "Connection.hpp" +#include "World.hpp" #include #include @@ -19,6 +20,12 @@ asio::awaitable Client::receivePacket() { case Generic::Operation::SimpleAuth: { packet.data = std::make_unique(co_await receiveProtobuf(30)); } break; + case Generic::Operation::RobotUpdate: { + packet.data = std::make_unique(co_await receiveProtobuf()); + } break; + case Generic::Operation::UserInfoSync: + case Generic::Operation::MakeRoom: + case Generic::Operation::JoinRandomRoom: case Generic::Operation::Disconnect: case Generic::Operation::OK: break; @@ -65,14 +72,45 @@ asio::awaitable Client::handlePacket(const Packet& packet) { //TODO... // Set username userInfo.mutable_publicinfo()->set_name(std::move(authData.username())); + // Set temporary user and client id + auto userRef = userInfo.mutable_publicinfo()->mutable_reference(); + userRef->set_userid(global.getTemporaryId()); + userRef->set_clientid(global.getTemporaryId()); // Mark as authed authed = true; - // Send OK - co_await sendOK(); + // Send user info + Packet userInfoSync; + userInfoSync.op.set_code(Generic::Operation::UserInfoSync); + userInfoSync.data = std::make_unique(userInfo); + } break; + case Generic::Operation::MakeRoom: { + // Create room + auto room = std::make_shared(global); + global.rooms.push_back(room); + co_await room->addClient(shared_from_this()); + } break; + case Generic::Operation::JoinRandomRoom: { + // Get random room + auto room = global.getRandomRoom(); + // Check that room was found + if (!room) { + throw Error("No available room could be found", Generic::Error::RoomNotAvailable); + } + // Add client to it + co_await room->addClient(shared_from_this()); + } break; + case Generic::Operation::LeaveRoom: { + // Check that client is in any room + if (!currentRoom) { + throw Error("You are not in any room", Generic::Error::IllegalOperation); + } + // Remove client from room + co_await currentRoom->removeClient(shared_from_this()); } break; default: {} } - co_return; + // Send OK + co_await sendOK(); } asio::awaitable Client::connect() { @@ -111,6 +149,16 @@ asio::awaitable Client::connect() { socket.close(ec); } +std::shared_ptr Global::getRandomRoom() const { + //TODO: Real rng + for (const auto& room : rooms) { + if (room->isOpen()) { + return room; + } + } + return nullptr; +} + asio::awaitable Server::listen(int port) { try { // Create acceptor diff --git a/server/Connection.hpp b/server/Connection.hpp index 3aadb92..bf358e3 100644 --- a/server/Connection.hpp +++ b/server/Connection.hpp @@ -56,6 +56,8 @@ class Client : public std::enable_shared_from_this { bool good = true; public: + World::Room *currentRoom = nullptr; + Client(asio::io_context& ioc, Global& global) : socket(ioc), global(global) {} asio::awaitable receivePacket(); @@ -109,6 +111,13 @@ public: struct Global { std::vector> clients; std::vector> rooms; + uint64_t highestTemporaryId = 0; + + std::shared_ptr getRandomRoom() const; + + uint64_t getTemporaryId() { + return ++highestTemporaryId; + } }; diff --git a/server/World.cpp b/server/World.cpp index 5550144..ecccabc 100644 --- a/server/World.cpp +++ b/server/World.cpp @@ -29,6 +29,9 @@ void fillSettings(Generic::Settings& settings) { if (!settings.has_healperturn()) { settings.set_healperturn(2); } + if (!settings.has_maxplayers()) { + settings.set_maxplayers(4); + } } boost::asio::awaitable Playfield::reset() { @@ -138,4 +141,56 @@ Generic::RoomInfo Room::getFullRoomInfo() const { } return fres; } + +boost::asio::awaitable Room::addClient(const std::shared_ptr& client) { + // Check that room is not full + if (isFull()) { + throw Connection::Error("Room is full", Generic::Error::RoomFull); + } + // Check that room has no game running + if (isRunning()) { + throw Connection::Error("Room is in game", Generic::Error::RoomNotAvailable); + } + // Broadcast join + Connection::Packet packet; + packet.op.set_code(Generic::Operation::JoinRoom); + packet.data = std::make_unique(client->getUserInfo().publicinfo()); + for (auto& client : clients) { + co_await client->sendPacket(packet); + } + // Add client + clients.push_back(client); + client->currentRoom = this; + // Send room info to client + packet.op.set_code(Generic::Operation::RoomInfoSync); + packet.data = std::make_unique(getFullRoomInfo()); + co_await client->sendPacket(packet); +} + +boost::asio::awaitable Room::removeClient(const std::shared_ptr& client) { + // Broadcast leave + Connection::Packet packet; + packet.op.set_code(Generic::Operation::LeaveRoom); + packet.data = std::make_unique(client->getUserInfo().publicinfo().reference()); + for (auto& client : clients) { + co_await client->sendPacket(packet); + } + // Remove client from list + clients.erase(std::find(clients.begin(), clients.end(), client)); + client->currentRoom = nullptr; + // Remove room if now empty + if (clients.empty()) { + removeRoom(); + } +} + +void Room::removeRoom() { + global.rooms.erase(std::find_if(global.rooms.begin(), global.rooms.end(), [this] (auto o) {return o.get() == this;})); +} + +boost::asio::awaitable Room::removeAllClients() { + for (auto& client : clients) { + co_await removeClient(client); + } +} } diff --git a/server/World.hpp b/server/World.hpp index 4996dc1..91b3824 100644 --- a/server/World.hpp +++ b/server/World.hpp @@ -11,6 +11,7 @@ namespace Connection { class Client; +class Global; } namespace World { @@ -92,15 +93,32 @@ public: }; struct Room { + Connection::Global &global; Generic::RoomInfo info; std::vector> clients; std::unique_ptr playfield = nullptr; + Room(Connection::Global& global) + : global(global) {} + Generic::RoomInfo getFullRoomInfo() const; + boost::asio::awaitable addClient(const std::shared_ptr& client); + boost::asio::awaitable removeClient(const std::shared_ptr& client); + void removeRoom(); + boost::asio::awaitable removeAllClients(); bool isMasterClient(const Connection::Client *client) const { return client == clients.front().get(); } + bool isFull() const { + return clients.size() >= info.settings().maxplayers(); + } + bool isRunning() const { + return bool(playfield); + } + bool isOpen() const { + return !isFull() && !isRunning(); + } }; } #endif