Files
yourchat/src/core/chat_room.cpp
Torsten Schulz (local) 228e9b7ea2 Füge Unterstützung für WebSocket-Nutzer in ChatUser und ChatRoom hinzu
- Implementiere einen neuen Konstruktor in der Klasse `ChatUser`, der einen WebSocket-Pointer akzeptiert, um die Benutzerkommunikation über WebSockets zu ermöglichen.
- Aktualisiere die Methode `addUser` in `ChatRoom`, um den neuen Konstruktor zu verwenden und sicherzustellen, dass Benutzer korrekt hinzugefügt werden.
- Ergänze die Logik in der `send`-Methode von `ChatUser`, um Nachrichten über WebSockets zu senden, wenn ein gültiger WebSocket-Pointer vorhanden ist.
- Füge Debug-Ausgaben hinzu, um den Ablauf beim Hinzufügen von Benutzern und beim Senden von Nachrichten über WebSockets zu protokollieren.
2025-09-05 13:26:52 +02:00

743 lines
27 KiB
C++
Executable File

#include "chat_room.h"
#include <algorithm>
#include "server.h"
#include <unistd.h>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <utility>
#include <thread>
#include <unordered_set>
namespace Yc
{
namespace Lib
{
ChatRoom::ChatRoom(std::shared_ptr<Server> 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<std::thread>(&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<std::string> 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<ChatUser> newUser;
if (_database) {
// Verwende den neuen Konstruktor mit Datenbank
newUser = std::make_shared<ChatUser>(shared_from_this(), _userName, color, socket, _database);
} else {
// Fallback auf den alten Konstruktor
newUser = std::make_shared<ChatUser>(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<ChatUser> newUser;
if (_database) {
// Verwende den neuen WebSocket-Konstruktor
newUser = std::make_shared<ChatUser>(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<ChatUser> 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<ChatUser> 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<ChatUser> 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<ChatUser> 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<ChatUser> 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<ChatUser> 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<ChatUser> 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<std::shared_ptr<ChatUser>, int> &val1, const std::pair<std::shared_ptr<ChatUser>, 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<ChatUser> 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<DiceResult>();
_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<ChatUser> 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<ChatUser> 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<Yc::Lib::Database> database)
{
_database = database;
}
} // namespace Lib
} // namespace Yc