diff --git a/disasterserver/CMakeLists.txt b/disasterserver/CMakeLists.txt index 62d6204..70ecefe 100644 --- a/disasterserver/CMakeLists.txt +++ b/disasterserver/CMakeLists.txt @@ -28,6 +28,8 @@ set(COMPILER_OPTIONS ) file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +# file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) + if(ANDROID) add_library(DisasterServerCXX SHARED "Android.cpp" ${SOURCES}) diff --git a/disasterserver/Client.hpp b/disasterserver/Client.hpp index 7ec8283..30d31ef 100644 --- a/disasterserver/Client.hpp +++ b/disasterserver/Client.hpp @@ -123,6 +123,11 @@ namespace DisasterServer void setExeChance(uint8_t chance) { exe_chance = chance; } uint8_t getExeChance() { return exe_chance; } + SurvCharacters getSurvCharacter() const { return survChar; } + void setSurvCharacter(SurvCharacters character) { survChar = character; } + ExesCharacters getExeCharacter() const { return exeChar; } + void setExeCharacter(ExesCharacters character) { exeChar = character; } + void setTimeout(double value) { timeout = value; } double getTimeout() const { return timeout; } bool isShouldTimeout() { return should_timeout; } diff --git a/disasterserver/GameStateController.cpp b/disasterserver/GameStateController.cpp index b788578..f1a9b99 100644 --- a/disasterserver/GameStateController.cpp +++ b/disasterserver/GameStateController.cpp @@ -5,7 +5,7 @@ using namespace DisasterServer; -GameStateController::GameStateController(Server *server) : server(server), lobby(server, this) { +GameStateController::GameStateController(Server *server): server(server), lobby(server, this), charSelect(server, this) { } GameStateController::~GameStateController() = default; @@ -24,9 +24,11 @@ bool GameStateController::playerJoined(Client &peer) { switch (state) { case States::LOBBY: - case States::CHARSELECT: - case States::MAPVOTE: { + case States::MAPVOTE: return lobby.joined(peer); + + case States::CHARSELECT: { + return charSelect.joined(peer); } case States::GAME: { return true; @@ -48,11 +50,14 @@ void GameStateController::playerLeft(Client &peer) { switch (state) { case States::LOBBY: - case States::CHARSELECT: case States::MAPVOTE: lobby.leaved(peer); break; + case States::CHARSELECT: + charSelect.leaved(peer); + break; + case States::GAME: //game_state_tick(server); break; @@ -63,22 +68,24 @@ void GameStateController::playerLeft(Client &peer) { } } - void GameStateController::tick() { switch (state) { case States::LOBBY: - case States::CHARSELECT: case States::MAPVOTE: lobby.tick(); break; + case States::CHARSELECT: + charSelect.tick(); + break; + case States::GAME: - //game_state_tick(server); + // game_state_tick(server); break; case States::RESULTS: - //results_state_tick(server); + // results_state_tick(server); break; } } @@ -137,18 +144,21 @@ bool GameStateController::handle(Client &peer, Packet &packet) { } switch (state) { case States::LOBBY: - case States::CHARSELECT: - case States::MAPVOTE: { + case States::MAPVOTE: lobby.handle(peer, packet); break; - } - case States::GAME: { + + case States::CHARSELECT: + charSelect.handle(peer, packet); break; - } - case States::RESULTS: { + + case States::GAME: + // game state handle + break; + + case States::RESULTS: + // results state handle break; - } - default: break; } return true; diff --git a/disasterserver/GameStateController.hpp b/disasterserver/GameStateController.hpp index cdd3ecb..bf29a32 100644 --- a/disasterserver/GameStateController.hpp +++ b/disasterserver/GameStateController.hpp @@ -3,6 +3,7 @@ #include "Core/Packet.hpp" #include "States/LobbyState.hpp" +#include "States/CharSelect.hpp" namespace DisasterServer { @@ -38,20 +39,25 @@ namespace DisasterServer States state = States::LOBBY; LobbyState lobby; + CharSelectState charSelect; public: - explicit GameStateController(Server *server); + explicit GameStateController(Server* server); ~GameStateController(); - bool playerJoined(Client &peer); - void playerLeft(Client &peer); + bool playerJoined(Client& peer); + void playerLeft(Client& peer); void tick(); - bool handle(Client &peer, Packet &packet); + bool handle(Client& peer, Packet& packet); - commandHash cmd_parse(std::string &string); - bool cmd_handle(const Client & client, commandHash hash, const std::string & string); + commandHash cmd_parse(std::string& string); + bool cmd_handle(const Client& client, commandHash hash, const std::string& string); - States getCurrentState() const { return this->state; } + States getCurrentState() const { return state; } void setState(States state) { this->state = state; } + + CharSelectState& getCharSelect() { + return charSelect; + } }; } diff --git a/disasterserver/States/CharSelect.cpp b/disasterserver/States/CharSelect.cpp new file mode 100644 index 0000000..ac314c7 --- /dev/null +++ b/disasterserver/States/CharSelect.cpp @@ -0,0 +1,326 @@ +#include "CharSelect.hpp" + +#include +#include + +#include "Server.hpp" +#include "GameStateController.hpp" +#include "Core/Colors.hpp" + +namespace DisasterServer { + +namespace { + +constexpr std::array EXE_NAMES = { + "Classic Exe", + "Chaos", + "Exetior", + "Exeller" +}; + +constexpr std::array SURV_NAMES = { + "Tails", + "Knuckles", + "Eggman", + "Amy", + "Cream", + "Sally" +}; + +} + +CharSelectState::CharSelectState(Server* server, GameStateController* controller) : State(server), controller(controller) { +} + +bool CharSelectState::isValidExeCharacter(uint8_t id) const { + return id >= 1 && id <= 4; +} + +bool CharSelectState::isValidSurvivorCharacter(uint8_t id) const { + return id >= 1 && id <= 6; +} + +bool CharSelectState::chooseExe() { + uint32_t weight = 0; + + for (auto& peer : server->getPeers()) { + if (!peer || !peer->isInGame()) + continue; + + peer->setExeCharacter(ExesCharacters::NONE); + peer->setSurvCharacter(SurvCharacters::NONE); + + weight += peer->getExeChance(); + } + + if (weight == 0) + weight++; + + uint32_t rnd = static_cast(std::rand()) % weight; + + for (auto& peer : server->getPeers()) { + if (!peer || !peer->isInGame()) + continue; + + if (peer->getExeChance() >= 100) { + exe = peer->getId(); + return true; + } + + if (rnd < peer->getExeChance() && !peer->isModified()) { + Info("{} (id {}, c {}) is exe!", peer->getNickname(), peer->getId(), peer->getExeChance()); + + peer->setExeChance(1 + std::rand() % 1); + + exe = peer->getId(); + return true; + } + + rnd -= peer->getExeChance(); + } + + exe = static_cast(-1); + return false; +} + +bool CharSelectState::checkState() { + bool shouldStart = true; + + for (auto& peer : server->getPeers()) { + if (!peer || !peer->isInGame()) + continue; + + if (peer->getExeCharacter() == ExesCharacters::NONE && + peer->getSurvCharacter() == SurvCharacters::NONE) { + shouldStart = false; + break; + } + } + + if (shouldStart) { + controller->setState(States::LOBBY); + return true; + } + + return true; +} + +bool CharSelectState::init(int8_t selectedMap) { + Debug("Attempting to enter ST_CHARSELECT..."); + + if (!chooseExe()) { + Err("Failed to pick exe for some reason!"); + + controller->setState(States::LOBBY); + return true; + } + + map = selectedMap; + + controller->setState(States::CHARSELECT); + + countdownSec = 30; + countdown = TICKSPERSEC; + + avail.fill(true); + + Packet pack(PacketType::SERVER_LOBBY_EXE); + + pack.write(exe); + pack.write(map); + + pack.sendBroadcast(*server, true); + + Packet timePack(PacketType::SERVER_CHAR_TIME_SYNC); + + timePack.write(countdownSec); + timePack.sendBroadcast(*server, true); + + Info("{}Server is now in {}Character Select{}", CLRCODE_YLW, CLRCODE_PUR, CLRCODE_RST); + + return true; +} + +bool CharSelectState::handle(Client& client, Packet& packet) { + switch (packet.getPacketType()) { + + case PacketType::CLIENT_REQUEST_EXECHARACTER: { + if (!client.isInGame()) + break; + + if (exe != client.getId()) { + client.disconnect(DisconnectReason::OTHER, "Invalid exe character request" + ); + return false; + } + + uint8_t id = packet.read(); + + if (!isValidExeCharacter(id)) { + client.disconnect(DisconnectReason::OTHER, "Invalid exe character"); + return false; + } + + const auto character = + static_cast(id - 1); + + client.setExeCharacter(character); + + Packet response( + PacketType::SERVER_LOBBY_EXECHARACTER_RESPONSE + ); + + response.write(id); + + if (!response.send(client, true)) + return false; + + Packet change( + PacketType::SERVER_LOBBY_CHARACTER_CHANGE + ); + + change.write(client.getId()); + change.write(id); + change.sendBroadcast(*server, true); + + Info("{}{}{} (id {}) choses [{}{}{}]!", client.getNickname(), CLRCODE_RST, "", client.getId(), CLRCODE_RED, EXE_NAMES[id - 1], CLRCODE_RST); + return checkState(); + } + + case PacketType::CLIENT_REQUEST_CHARACTER: { + if (!client.isInGame()) + break; + + if (client.getSurvCharacter() != SurvCharacters::NONE) + break; + + if (exe == client.getId()) { + client.disconnect(DisconnectReason::OTHER, "Exe cannot select survivor character"); + return false; + } + + const uint8_t id = packet.read(); + + if (!isValidSurvivorCharacter(id)) { + client.disconnect(DisconnectReason::OTHER, "Invalid survivor character"); + return false; + } + + const size_t index = id - 1; + const bool available = avail[index]; + + if (available) + avail[index] = false; + + Packet response( + PacketType::SERVER_LOBBY_CHARACTER_RESPONSE + ); + + response.write(id); + response.write(available); + + if (!response.send(client, true)) + return false; + + if (available) { + client.setSurvCharacter( + static_cast(index) + ); + + Packet change( + PacketType::SERVER_LOBBY_CHARACTER_CHANGE + ); + + change.write(client.getId()); + change.write(id); + change.sendBroadcast(*server, true); + } + + Info("{}{}{} (id {}) choses [{}{}{}]!", client.getNickname(), CLRCODE_RST, "", client.getId(), CLRCODE_GRN, SURV_NAMES[index], CLRCODE_RST); + return checkState(); + } + + case PacketType::CLIENT_CHAT_MESSAGE: { + const clientId pid = packet.read(); + (void)pid; + + const std::string message = packet.readString(); + + if (message.size() > 40) { + client.disconnect(DisconnectReason::OTHER, "Chat message too long"); + return false; + } + + client.setTimeout(0); + + Info("{}{}{} (id {}): {}", client.getNickname(), CLRCODE_RST, "", client.getId(), message); + break; + } + + default: + /* + * placeholder + */ + break; + } + + return true; +} + +void CharSelectState::tick() { + if (countdown <= 0) { + countdown += TICKSPERSEC; + + if (--countdownSec == 0) { + for (auto& peer : server->getPeers()) { + if (!peer || !peer->isInGame()) + continue; + + if (peer->getExeCharacter() == ExesCharacters::NONE && + peer->getSurvCharacter() == SurvCharacters::NONE) { + + peer->disconnect( + DisconnectReason::AFKTIMEOUT + ); + } + } + } + + Packet pack(PacketType::SERVER_CHAR_TIME_SYNC); + + pack.write(countdownSec); + pack.sendBroadcast(*server, true); + } + + countdown -= server->getDelta(); +} + +bool CharSelectState::joined(Client& client) { + (void)client; + return true; +} + +bool CharSelectState::leaved(Client& client) { + if (client.getSurvCharacter() != SurvCharacters::NONE) { + const auto character = client.getSurvCharacter(); + const size_t index = static_cast(character); + + if (index < avail.size()) + avail[index] = true; + } + + size_t inGame = 0; + + for (auto& peer : server->getPeers()) { + if (peer && peer->isInGame()) + ++inGame; + } + + if (inGame <= 1 || client.getId() == exe) { + controller->setState(States::LOBBY); + return true; + } + + return checkState(); +} +} \ No newline at end of file diff --git a/disasterserver/States/CharSelect.hpp b/disasterserver/States/CharSelect.hpp new file mode 100644 index 0000000..230e759 --- /dev/null +++ b/disasterserver/States/CharSelect.hpp @@ -0,0 +1,52 @@ +#ifndef DISASTERSERVER_CHARSELECTSTATE_HPP +#define DISASTERSERVER_CHARSELECTSTATE_HPP + +#include +#include + +#include "State.hpp" +#include "Core/Packet.hpp" + +namespace DisasterServer { + +class GameStateController; + +class CharSelectState : public State { +private: + GameStateController* controller = nullptr; + + double countdown = 0; + uint8_t countdownSec = 30; + + int8_t map = 0; + clientId exe = 0; + + std::array avail{}; + + bool checkState(); + bool chooseExe(); + + bool isValidSurvivorCharacter(uint8_t id) const; + bool isValidExeCharacter(uint8_t id) const; + +public: + CharSelectState(Server* server, GameStateController* controller); + + ~CharSelectState() override = default; + + bool init(int8_t map); + + void tick() override; + + bool handle(Client& client, Packet& packet) override; + + bool joined(Client& client) override; + bool leaved(Client& client); + + clientId getExe() const { return exe; } + int8_t getMap() const { return map; } +}; + +} + +#endif // DISASTERSERVER_CHARSELECTSTATE_HPP \ No newline at end of file diff --git a/disasterserver/States/LobbyState.cpp b/disasterserver/States/LobbyState.cpp index 5f15801..2828788 100644 --- a/disasterserver/States/LobbyState.cpp +++ b/disasterserver/States/LobbyState.cpp @@ -188,7 +188,7 @@ bool LobbyState::tick() { countdown += TICKSPERSEC; if (--countdown_sec == 0) { - return init(); + return controller->getCharSelect().init(map); } RAssert(sendCountdown()); @@ -247,6 +247,23 @@ bool LobbyState::handle(Client &client, Packet &packet) { } case CMD_MAP: { + controller->getCharSelect().init(0); + break; + } + + case CMD_HELP: { + this->server->send_message(client, "~-----~ {}command list:{} ~-----~", CLRCODE_GRN, CLRCODE_RST); + this->server->send_message(client, "|- .vk~ - vote kick "); + this->server->send_message(client, "|- .info~ - information about server"); + this->server->send_message(client, "|- .vp~ - vote practice mode (wip)"); + break; + } + + case CMD_INFO: { + this->server->send_message(client, "|build from &{} @{}~", __DATE__, __TIME__); + this->server->send_message(client, "{}hander{} - original binary", CLRCODE_YLW, CLRCODE_RST); + this->server->send_message(client, "{}miles{}glitch{} - rewritten server to c++", CLRCODE_BLU, CLRCODE_PUR, CLRCODE_RST); + this->server->send_message(client, "{}faker{}null{}0{} - help with code", CLRCODE_GRA, CLRCODE_RED, CLRCODE_GRN, CLRCODE_RST); break; }