- Füge Überprüfungen hinzu, um sicherzustellen, dass der Socket gültig ist, bevor Nachrichten gesendet werden. - Implementiere detaillierte Fehlerprotokollierung für verschiedene Socket-Fehler, um die Diagnose zu erleichtern. - Ergänze eine kurze Verzögerung im ChatRoom, um den Abschluss des WebSocket-Handshakes zu gewährleisten.
651 lines
22 KiB
C++
Executable File
651 lines
22 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);
|
|
newUser->start();
|
|
if (_parent) {
|
|
Json::Value roomList = _parent->jsonRoomList();
|
|
newUser->sendMsg(ChatUser::roomList, roomList, "", "");
|
|
}
|
|
|
|
// Kurze Pause, damit der WebSocket-Handshake vollständig abgeschlossen ist
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
// Sende aktuelle Userliste an den neuen User
|
|
Json::Value currentUserList = userList();
|
|
newUser->sendMsg(ChatUser::userListe, currentUserList, "", "");
|
|
|
|
// Sende aktualisierte Userliste an alle anderen User im Raum
|
|
for (auto &existingUser : _users) {
|
|
if (existingUser != newUser) {
|
|
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());
|
|
_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
|