#include "chat_room.h" #include #include "server.h" #include #include #include #include #include #include #include namespace Yc { namespace Lib { ChatRoom::ChatRoom(std::shared_ptr parent, Json::Value roomParams) : _room(roomParams), _parent(std::move(parent)), _blocked(false), _stop(false), _roundRunning(false), _diceGameRunning(false), _currentRound(0), _totalRounds(0) { _name = roomParams["name"].asString(); _password = roomParams["password"].asString(); for (auto &user : roomParams["allowed"]) { _allowedUsers.push_back(user.asString()); } _type = (RoomType)roomParams["type"].asInt(); _roundLength = roomParams["roundlength"].asInt(); _lastRoundEnd = std::time(NULL); thread = std::make_unique(&ChatRoom::run, this); } ChatRoom::~ChatRoom() { setStop(); // Stop all users first for (auto& user : _users) { if (user) { user->stop(); } } // Wait for all user threads to finish for (auto& user : _users) { if (user && user->thread.joinable()) { user->thread.join(); } } // Clear users list _users.clear(); // Now stop and join the room thread if (thread && thread->joinable()) { thread->join(); } } void ChatRoom::run() { // Nachrichtentypen, die nicht an den Auslöser zurückgeschickt werden static const std::unordered_set kSuppressToSender = { "room_entered", "user_entered_room", "user_color_changed" }; while (!_stop) { if (_msgQueue.size() > 0 && !_blocked) { _blocked = true; while (_msgQueue.size() > 0) { Message message = _msgQueue.front(); for (auto &user : _users) { // Konfigurierbare Unterdrückung für Systemmeldungen an den Sender if (message.type == ChatUser::system) { bool suppress = false; // message.messageTr kann JSON (mit tr) oder plain String sein Json::Value maybeJson = getJsonTree(message.messageTr); if (maybeJson.isObject() && maybeJson.isMember("tr")) { suppress = kSuppressToSender.count(maybeJson["tr"].asString()) > 0; } else { suppress = kSuppressToSender.count(message.messageTr) > 0; } if (suppress && user->name() == message.userName) continue; } user->sendMsg(message.type, message.messageTr, message.userName, message.color); } _msgQueue.pop(); } _blocked = false; } if ((_type & dice) == dice) { _handleDice(); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } bool ChatRoom::addUser(std::string _userName, std::string color, std::string _password, int socket) { if (_password != "" && _password == _password && std::find(std::begin(_allowedUsers), std::end(_allowedUsers), _userName) == std::end(_allowedUsers)) { return false; } std::shared_ptr newUser; if (_database) { // Verwende den neuen Konstruktor mit Datenbank newUser = std::make_shared(shared_from_this(), _userName, color, socket, _database); } else { // Fallback auf den alten Konstruktor newUser = std::make_shared(shared_from_this(), _userName, color, socket); } _users.push_back(newUser); // Sende zuerst alle wichtigen Nachrichten, bevor der checkerTask Thread startet #ifdef YC_DEBUG std::cout << "[Debug] addUser: Starting message sequence for user: " << newUser->name() << std::endl; #endif if (_parent) { Json::Value roomList = _parent->jsonRoomList(); #ifdef YC_DEBUG std::cout << "[Debug] addUser: Sending roomList to user: " << newUser->name() << std::endl; #endif newUser->sendMsg(ChatUser::roomList, roomList, "", ""); } // Kurze Pause, damit der WebSocket-Handshake vollständig abgeschlossen ist #ifdef YC_DEBUG std::cout << "[Debug] addUser: Waiting 100ms for WebSocket handshake..." << std::endl; #endif std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sende aktuelle Userliste an den neuen User Json::Value currentUserList = userList(); #ifdef YC_DEBUG std::cout << "[Debug] addUser: Sending userListe to user: " << newUser->name() << std::endl; #endif newUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); // Sende aktualisierte Userliste an alle anderen User im Raum for (auto &existingUser : _users) { if (existingUser != newUser) { #ifdef YC_DEBUG std::cout << "[Debug] addUser: Sending userListe to existing user: " << existingUser->name() << std::endl; #endif existingUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); } } // Broadcast an andere Nutzer: Benutzer X hat den Chat betreten (mit Farbinfo) #ifdef YC_DEBUG std::cout << "[Debug] addUser: Sending 'user_entered_chat' message for user: " << newUser->name() << std::endl; #endif addMessage(ChatUser::system, "user_entered_chat", newUser->name(), newUser->color()); // Starte den checkerTask Thread erst nach dem Senden aller wichtigen Nachrichten #ifdef YC_DEBUG std::cout << "[Debug] addUser: Starting checkerTask thread for user: " << newUser->name() << std::endl; #endif newUser->start(); _initRound(); return true; } bool ChatRoom::addUser(std::string _userName, std::string color, std::string _password, void* wsi) { if (_password != "" && _password == _password && std::find(std::begin(_allowedUsers), std::end(_allowedUsers), _userName) == std::end(_allowedUsers)) { return false; } std::shared_ptr newUser; if (_database) { // Verwende den neuen WebSocket-Konstruktor newUser = std::make_shared(shared_from_this(), _userName, color, wsi, _database); } else { // Fallback: WebSocket ohne Datenbank nicht unterstützt return false; } _users.push_back(newUser); // Sende zuerst alle wichtigen Nachrichten, bevor der checkerTask Thread startet #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Starting message sequence for user: " << newUser->name() << std::endl; #endif if (_parent) { Json::Value roomList = _parent->jsonRoomList(); #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Sending roomList to user: " << newUser->name() << std::endl; #endif newUser->sendMsg(ChatUser::roomList, roomList, "", ""); } // Kurze Pause, damit der WebSocket-Handshake vollständig abgeschlossen ist #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Waiting 100ms for WebSocket handshake..." << std::endl; #endif std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sende aktuelle Userliste an den neuen User Json::Value currentUserList = userList(); #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Sending userListe to user: " << newUser->name() << std::endl; #endif newUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); // Sende aktualisierte Userliste an alle anderen User im Raum for (auto &existingUser : _users) { if (existingUser != newUser) { #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Sending userListe to existing user: " << existingUser->name() << std::endl; #endif existingUser->sendMsg(ChatUser::userListe, currentUserList, "", ""); } } // Broadcast an andere Nutzer: Benutzer X hat den Chat betreten (mit Farbinfo) #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Sending 'user_entered_chat' message for user: " << newUser->name() << std::endl; #endif addMessage(ChatUser::system, "user_entered_chat", newUser->name(), newUser->color()); // Starte den checkerTask Thread erst nach dem Senden aller wichtigen Nachrichten #ifdef YC_DEBUG std::cout << "[Debug] addUser(WebSocket): Starting checkerTask thread for user: " << newUser->name() << std::endl; #endif newUser->start(); _initRound(); return true; } bool ChatRoom::addUser(std::shared_ptr user, std::string password) { if (password == _password) { _users.push_back(user); user->setParent(shared_from_this()); _initRound(); return true; } return false; } bool ChatRoom::userNameExists(std::string userName) { for (const auto &user : _users) { if (user->name() == userName) { return true; } } return false; } void ChatRoom::removeUser(std::string _token, bool silent) { for (auto it = _users.begin(); it != _users.end(); ++it) { if ((*it)->validateToken(_token)) { if (!silent) { addMessage(ChatUser::system, std::string("leaved_chat"), (*it)->name(), (*it)->color()); } _users.erase(it); break; } } } void ChatRoom::removeUser(std::shared_ptr userToRemove, bool silent) { for (auto it = _users.begin(); it != _users.end(); ++it) { if (*it == userToRemove) { if (!silent) { addMessage(ChatUser::system, std::string("leaved_chat"), (*it)->name(), (*it)->color()); } _users.erase(it); break; } } } void ChatRoom::removeUserDisconnected(std::shared_ptr userToRemove) { for (auto it = _users.begin(); it != _users.end(); ++it) { if (*it == userToRemove) { // Spezielle Nachricht für Verbindungsabbrüche addMessage(ChatUser::system, std::string("user_disconnected"), (*it)->name(), (*it)->color()); _users.erase(it); break; } } } void ChatRoom::setStop() { _stop = true; } void ChatRoom::addMessage(ChatUser::MsgType type, const char *messageText, std::string userName, std::string color) { addMessage(type, (std::string)messageText, userName, color); } void ChatRoom::addMessage(ChatUser::MsgType type, std::string messageText, std::string userName, std::string color) { Message message; message.type = type; message.messageTr = messageText; message.userName = userName; message.color = color; _msgQueue.push(message); } void ChatRoom::addMessage(ChatUser::MsgType type, Json::Value messageText, std::string userName, std::string color) { addMessage(type, getJsonString(messageText), userName, color); } void ChatRoom::addUserWhenQueueEmpty(std::shared_ptr user) { while (_msgQueue.size() > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } _users.push_back(user); user->setParent(shared_from_this()); // Sende aktuelle Userliste an den User nach Raumwechsel Json::Value currentUserList = userList(); user->sendMsg(ChatUser::userListe, currentUserList, "", ""); } bool ChatRoom::userToNewRoom(std::shared_ptr user, std::string newRoom, std::string password) { return _parent->changeRoom(user, newRoom, password); } unsigned int ChatRoom::flags() { unsigned int value = (unsigned int)_type; if (_password != "") { value += (unsigned int)ChatRoom::password; } return value; } Json::Value ChatRoom::userList() { Json::Value users = Json::arrayValue; for (auto &user : _users) { Json::Value jsonUser = Json::objectValue; jsonUser["name"] = user->name(); jsonUser["color"] = user->color(); users.append(jsonUser); } return users; } ChatRoom::RoomType ChatRoom::type() { return _type; } bool ChatRoom::isType(ChatRoom::RoomType type) { return ((_type & type) == type); } bool ChatRoom::canDice() { return (_roundRunning || (_type & rounds) == rounds) && (_type & dice) == dice; } unsigned int ChatRoom::addDice(std::shared_ptr user, int diceValue) { if (!canDice()) { return 1; } for (auto &listUser : _diceValues) { if (listUser.first == user) { return 2; } } _diceValues.push_back(std::make_pair(user, diceValue)); addMessage(ChatUser::dice, std::to_string(diceValue), user->name(), user->color()); return 0; } bool ChatRoom::accessAllowed(std::string userName, std::string password) { return (_allowedUsers.size() == 0 || _password == "" || _password == password || std::find(_allowedUsers.begin(), _allowedUsers.end(), userName) != _allowedUsers.end()); } bool ChatRoom::userIsInRoom(std::string userName) { for (auto &user : _users) { if (userName == user->name()) { return true; } } return false; } std::shared_ptr ChatRoom::findUserByName(std::string userName) { for (auto &user : _users) { if (userName == user->name()) { return user; } } return nullptr; } void ChatRoom::_handleDice() { if (((_type & rounds) == rounds)) { if (_roundRunning && (_users.size() < 2 || _roundStart + _roundLength >= time(NULL))) { _lastRoundEnd = time(NULL); _roundRunning = false; addMessage(ChatUser::system, "round_ends"); _showDiceRoundResults(); } else if (!_roundRunning && _lastRoundEnd <= time(NULL) - 15 && _users.size() >= 2) { _roundStart = time(NULL); _roundRunning = true; _diceValues.clear(); addMessage(ChatUser::system, "next_round_starts_now"); } } } void ChatRoom::_initRound() { // Nur in Räumen mit Runden & Würfeln ankündigen if (_users.size() == 2 && ((_type & rounds) == rounds) && ((_type & dice) == dice)) { _lastRoundEnd = time(NULL); addMessage(ChatUser::system, "next_round_starts_soon"); } } void ChatRoom::_showDiceRoundResults() { std::sort(_diceValues.begin(), _diceValues.end(), [=](const std::pair, int> &val1, const std::pair, int> &val2) { return (val1.second > val2.second); }); Json::Value userList = Json::arrayValue; for (auto &user : _diceValues) { Json::Value entry = Json::objectValue; entry["name"] = user.first->name(); entry["value"] = user.second; userList.append(entry); } addMessage(ChatUser::result, userList); } std::string ChatRoom::name() { return _name; } // Neue Würfel-Funktionen bool ChatRoom::startDiceGame(int rounds, std::shared_ptr admin) { if (!_isUserAdmin(admin)) { return false; // Kein Admin } if (_diceGameRunning) { return false; // Spiel läuft bereits } if (!canDice()) { return false; // Raum unterstützt kein Würfeln } _diceGameRunning = true; _currentRound = 1; _totalRounds = rounds; _gameStartTime = std::chrono::steady_clock::now(); _gameResults.clear(); // Alle User für das Spiel vorbereiten for (auto& user : _users) { _gameResults[user->name()] = std::vector(); _hasRolledThisRound[user->name()] = false; } // Erste Runde starten _startDiceRound(); // Nachricht an alle senden Json::Value msg = Json::objectValue; msg["tr"] = "dice_game_started"; msg["rounds"] = rounds; msg["admin"] = admin->name(); addMessage(ChatUser::system, msg, "", ""); return true; } bool ChatRoom::rollDice(std::shared_ptr user, int diceValue) { if (!_diceGameRunning) { return false; // Kein Spiel aktiv } if (diceValue < 1 || diceValue > 6) { return false; // Ungültiger Würfelwert } std::string userName = user->name(); if (_hasRolledThisRound[userName]) { return false; // Bereits gewürfelt } // Würfelwert speichern DiceResult result; result.userName = userName; result.diceValue = diceValue; result.rollTime = std::chrono::steady_clock::now(); result.valid = true; _gameResults[userName].push_back(result); _hasRolledThisRound[userName] = true; // Nachricht an alle senden Json::Value msg = Json::objectValue; msg["tr"] = "dice_rolled"; msg["user"] = userName; msg["value"] = diceValue; msg["round"] = _currentRound; addMessage(ChatUser::dice, msg, userName, user->color()); // Prüfen ob alle gewürfelt haben bool allRolled = true; for (auto& pair : _hasRolledThisRound) { if (!pair.second) { allRolled = false; break; } } if (allRolled) { // Alle haben gewürfelt, Runde beenden _endDiceRound(); } return true; } void ChatRoom::endDiceGame() { if (!_diceGameRunning) { return; } _diceGameRunning = false; _showGameResults(); // Nachricht an alle senden Json::Value msg = Json::objectValue; msg["tr"] = "dice_game_ended"; addMessage(ChatUser::system, msg, "", ""); } void ChatRoom::_startDiceRound() { if (_currentRound > _totalRounds) { endDiceGame(); return; } _roundStartTime = std::chrono::steady_clock::now(); // Alle User für neue Runde zurücksetzen for (auto& pair : _hasRolledThisRound) { pair.second = false; } // Nachricht an alle senden Json::Value msg = Json::objectValue; msg["tr"] = "dice_round_started"; msg["round"] = _currentRound; msg["total_rounds"] = _totalRounds; addMessage(ChatUser::system, msg, "", ""); // Timer für 15 Sekunden starten _startRoundTimer(); } void ChatRoom::_endDiceRound() { // Ungültige Ergebnisse für User die nicht gewürfelt haben for (auto& pair : _hasRolledThisRound) { if (!pair.second) { DiceResult result; result.userName = pair.first; result.diceValue = 0; result.rollTime = std::chrono::steady_clock::now(); result.valid = false; _gameResults[pair.first].push_back(result); } } // Nachricht an alle senden Json::Value msg = Json::objectValue; msg["tr"] = "dice_round_ended"; msg["round"] = _currentRound; addMessage(ChatUser::system, msg, "", ""); _currentRound++; if (_currentRound <= _totalRounds) { // Nächste Runde starten std::this_thread::sleep_for(std::chrono::seconds(2)); // 2 Sekunden Pause _startDiceRound(); } else { // Spiel beenden endDiceGame(); } } void ChatRoom::_startRoundTimer() { _roundTimerThread = std::thread([this]() { std::this_thread::sleep_for(std::chrono::seconds(15)); // Prüfen ob Runde noch läuft if (_diceGameRunning && _currentRound <= _totalRounds) { _endDiceRound(); } }); _roundTimerThread.detach(); } void ChatRoom::_showGameResults() { Json::Value results = Json::objectValue; results["tr"] = "dice_game_results"; results["total_rounds"] = _totalRounds; Json::Value userResults = Json::arrayValue; for (auto& pair : _gameResults) { Json::Value userResult = Json::objectValue; userResult["user"] = pair.first; Json::Value rounds = Json::arrayValue; int totalScore = 0; int validRolls = 0; for (auto& result : pair.second) { Json::Value round = Json::objectValue; round["round"] = rounds.size() + 1; round["value"] = result.diceValue; round["valid"] = result.valid; rounds.append(round); if (result.valid) { totalScore += result.diceValue; validRolls++; } } userResult["rounds"] = rounds; userResult["total_score"] = totalScore; userResult["valid_rolls"] = validRolls; userResult["average"] = validRolls > 0 ? (double)totalScore / validRolls : 0.0; userResults.append(userResult); } results["user_results"] = userResults; addMessage(ChatUser::result, results, "", ""); } bool ChatRoom::_isUserAdmin(std::shared_ptr user) const { // Einfache Admin-Prüfung: Erster User im Raum ist Admin // In einer echten Implementierung würde man hier Rechte aus der Datenbank prüfen if (_users.empty()) return false; return _users[0] == user; } void ChatRoom::reloadRoomList() { // Neue Raumliste vom Server holen Json::Value roomList = _parent->jsonRoomList(); // An alle User im Raum senden for (auto& user : _users) { user->sendMsg(ChatUser::roomList, roomList, "", ""); } // System-Nachricht an alle senden Json::Value msg = Json::objectValue; msg["tr"] = "room_list_reloaded"; addMessage(ChatUser::system, msg, "", ""); } void ChatRoom::setDatabase(std::shared_ptr database) { _database = database; } } // namespace Lib } // namespace Yc