Füge Unterstützung für Würfelspiele hinzu und verbessere Debugging-Optionen
- Implementiere neue Funktionen in der ChatRoom-Klasse für das Starten, Rollen und Beenden von Würfelspielen. - Füge eine Option zur Aktivierung von Debug-Logging in CMake hinzu, um die Entwicklung zu erleichtern. - Aktualisiere die ChatUser-Klasse, um die Interaktion mit dem Würfelspiel zu ermöglichen. - Verbessere die Socket-Verwaltung im Server, um WebSocket-Verbindungen zu unterstützen und die Handhabung von Anfragen zu optimieren. - Aktualisiere die Konfiguration, um die neue Funktionalität zu unterstützen und die Benutzererfahrung zu verbessern.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "chat_user.h"
|
||||
#include "server.h"
|
||||
#include "lib/tools.h"
|
||||
#include "lib/base.h"
|
||||
#include <json/json.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -12,12 +13,22 @@
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sstream>
|
||||
#include <cctype>
|
||||
|
||||
namespace Yc
|
||||
{
|
||||
namespace Lib
|
||||
{
|
||||
|
||||
static bool _isValidHexColor(const std::string &c) {
|
||||
if (c.size() != 7 || c[0] != '#') return false;
|
||||
for (size_t i = 1; i < c.size(); ++i) {
|
||||
char ch = c[i];
|
||||
if (!std::isxdigit(static_cast<unsigned char>(ch))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ChatUser::ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket)
|
||||
: _parent(std::move(parent)),
|
||||
_name(name),
|
||||
@@ -79,14 +90,20 @@ namespace Yc
|
||||
userJson["rights"] = rights;
|
||||
}
|
||||
_user = Yc::Object::User(userJson);
|
||||
// Prefer DB color if available
|
||||
if (_user.id() != 0 && !_user.color().empty()) {
|
||||
_color = _user.color();
|
||||
}
|
||||
_token = Yc::Lib::Tools::generateRandomString(32);
|
||||
sendMsg(token, _token, "", "");
|
||||
thread = std::make_unique<std::thread>(&ChatUser::checkerTask, this);
|
||||
// Beim Initial-Token direkt Name und aktuelle Farbe mitsenden, damit der Client "ich" korrekt färben kann
|
||||
sendMsg(token, _token, _name, _color);
|
||||
// Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor
|
||||
}
|
||||
|
||||
ChatUser::~ChatUser()
|
||||
{
|
||||
_parent->addMessage(ChatUser::system, std::string("leaved_chat"), std::string(_name), std::string(_color));
|
||||
// Hinweis: Thread wird nicht im Destruktor gejoint, um Deadlocks zu vermeiden!
|
||||
// Der Thread muss explizit von außen gestoppt und gejoint werden (z.B. im ChatRoom beim Entfernen des Users).
|
||||
}
|
||||
|
||||
std::string ChatUser::name() const
|
||||
@@ -111,6 +128,9 @@ namespace Yc
|
||||
|
||||
void ChatUser::sendMsg(MsgType type, std::string message, std::string userName, std::string color)
|
||||
{
|
||||
// Standardwerte für leere Felder setzen
|
||||
if (userName.empty()) userName = _name;
|
||||
if (color.empty()) color = _color;
|
||||
Json::Value sendMessage;
|
||||
sendMessage["type"] = type;
|
||||
sendMessage["message"] = message;
|
||||
@@ -121,6 +141,9 @@ namespace Yc
|
||||
|
||||
void ChatUser::sendMsg(MsgType type, Json::Value message, std::string userName, std::string color)
|
||||
{
|
||||
// Standardwerte für leere Felder setzen
|
||||
if (userName.empty()) userName = _name;
|
||||
if (color.empty()) color = _color;
|
||||
Json::Value sendMessage;
|
||||
sendMessage["type"] = type;
|
||||
sendMessage["message"] = message;
|
||||
@@ -131,28 +154,44 @@ namespace Yc
|
||||
|
||||
void ChatUser::checkerTask()
|
||||
{
|
||||
while (!_stop)
|
||||
{
|
||||
fd_set readSd;
|
||||
FD_ZERO(&readSd);
|
||||
FD_SET(_socket, &readSd);
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 500;
|
||||
int selectResult = select(_socket + 1, &readSd, NULL, NULL, &tv);
|
||||
if (selectResult == 1 && FD_ISSET(_socket, &readSd) == 1)
|
||||
try {
|
||||
while (!_stop)
|
||||
{
|
||||
_parent->removeUser(_token);
|
||||
_stop = true;
|
||||
delete this;
|
||||
return;
|
||||
fd_set readSd;
|
||||
FD_ZERO(&readSd);
|
||||
FD_SET(_socket, &readSd);
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 500;
|
||||
int selectResult = select(_socket + 1, &readSd, NULL, NULL, &tv);
|
||||
if (selectResult == 1 && FD_ISSET(_socket, &readSd) == 1)
|
||||
{
|
||||
char peek;
|
||||
ssize_t r = recv(_socket, &peek, 1, MSG_PEEK);
|
||||
if (r == 0) {
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] Verbindung zum Client abgebrochen (Token: <hidden>)" << std::endl;
|
||||
#endif
|
||||
_parent->removeUser(_token);
|
||||
_stop = true;
|
||||
if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) {
|
||||
thread.detach();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::string msg = readSocket(_socket);
|
||||
if (msg == "")
|
||||
{
|
||||
// Kein Inhalt gelesen; kann Idle/Keepalive sein
|
||||
continue;
|
||||
}
|
||||
handleMessage(msg);
|
||||
}
|
||||
std::string msg = readSocket(_socket);
|
||||
if (msg == "")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
handleMessage(msg);
|
||||
} catch (const std::exception& ex) {
|
||||
// swallow, optional hook
|
||||
} catch (...) {
|
||||
// swallow, optional hook
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,19 +210,29 @@ namespace Yc
|
||||
_parent = std::move(parent);
|
||||
}
|
||||
|
||||
void ChatUser::send(std::string out)
|
||||
{
|
||||
void ChatUser::send(std::string out) {
|
||||
// Entferne ggf. Token-Felder aus JSON-Strings und sende über Socket/WebSocket
|
||||
Base::sanitizeTokensInString(out);
|
||||
Base::send(_socket, out);
|
||||
}
|
||||
|
||||
void ChatUser::send(Json::Value out)
|
||||
{
|
||||
void ChatUser::send(Json::Value out) {
|
||||
// Entferne rekursiv alle Token-Felder und sende über Socket/WebSocket
|
||||
Base::sanitizeTokens(out);
|
||||
Base::send(_socket, out);
|
||||
}
|
||||
|
||||
void ChatUser::handleMessage(std::string message)
|
||||
{
|
||||
Json::Value jsonTree = getJsonTree(message);
|
||||
// Eingehende Nachricht (nur im Debug-Mode)
|
||||
#ifdef YC_DEBUG
|
||||
{
|
||||
Json::Value toLog = jsonTree;
|
||||
if (toLog.isMember("token")) toLog["token"] = "<hidden>";
|
||||
std::cout << "[Debug] <- Client(fd=" << _socket << ", user='" << _name << "'): " << getJsonString(toLog) << std::endl;
|
||||
}
|
||||
#endif
|
||||
if (jsonTree["token"].asString() != _token)
|
||||
{
|
||||
return;
|
||||
@@ -194,7 +243,38 @@ namespace Yc
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "dice")
|
||||
{
|
||||
doDice();
|
||||
// Neue Würfel-Behandlung für das Würfelspiel
|
||||
if (jsonTree.isMember("value")) {
|
||||
int diceValue = jsonTree["value"].asInt();
|
||||
if (!_parent->rollDice(shared_from_this(), diceValue)) {
|
||||
sendMsg(ChatUser::error, "dice_roll_failed", "", "");
|
||||
}
|
||||
} else {
|
||||
// Fallback: Einfacher Würfelwurf
|
||||
doDice();
|
||||
}
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "start_dice_game")
|
||||
{
|
||||
// Würfelspiel starten (nur Admin)
|
||||
if (jsonTree.isMember("rounds")) {
|
||||
int rounds = jsonTree["rounds"].asInt();
|
||||
if (rounds < 1 || rounds > 10) {
|
||||
sendMsg(ChatUser::error, "invalid_rounds", "", "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_parent->startDiceGame(rounds, shared_from_this())) {
|
||||
sendMsg(ChatUser::error, "dice_game_start_failed", "", "");
|
||||
}
|
||||
} else {
|
||||
sendMsg(ChatUser::error, "missing_rounds", "", "");
|
||||
}
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "end_dice_game")
|
||||
{
|
||||
// Würfelspiel beenden (nur Admin)
|
||||
_parent->endDiceGame();
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "scream")
|
||||
{
|
||||
@@ -202,7 +282,68 @@ namespace Yc
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "do")
|
||||
{
|
||||
_parent->addMessage(ChatUser::dosomething, jsonTree["message"].asString(), std::string(_name), std::string(_color));
|
||||
// "do" erwartet "value" (Aktion) und optional "to" (Ziel-User)
|
||||
std::string action = jsonTree.isMember("value") ? jsonTree["value"].asString() : "";
|
||||
std::string targetUser = jsonTree.isMember("to") ? jsonTree["to"].asString() : "";
|
||||
|
||||
if (action.empty()) {
|
||||
sendMsg(ChatUser::error, "missing_action", "", "");
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle strukturierte Nachricht mit Ziel-User und Aktion
|
||||
Json::Value doMsg = Json::objectValue;
|
||||
doMsg["tr"] = "user_action";
|
||||
doMsg["action"] = action;
|
||||
if (!targetUser.empty()) {
|
||||
doMsg["to"] = targetUser;
|
||||
|
||||
// Suche den Ziel-User und füge dessen Informationen hinzu
|
||||
auto targetUserObj = _parent->findUserByName(targetUser);
|
||||
if (targetUserObj) {
|
||||
doMsg["targetName"] = targetUserObj->name();
|
||||
doMsg["targetColor"] = targetUserObj->color();
|
||||
}
|
||||
}
|
||||
|
||||
// Debug-Ausgabe für "do"-Nachrichten
|
||||
#ifdef YC_DEBUG
|
||||
std::cout << "[Debug] Sending do message: type=" << ChatUser::dosomething << ", action=" << action << ", to=" << (targetUser.empty() ? "all" : targetUser) << std::endl;
|
||||
#endif
|
||||
|
||||
_parent->addMessage(ChatUser::dosomething, doMsg, std::string(_name), std::string(_color));
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "color")
|
||||
{
|
||||
std::string newColor = jsonTree.isMember("value") ? jsonTree["value"].asString() : "";
|
||||
std::string oldColor = _color;
|
||||
if (!_isValidHexColor(newColor)) {
|
||||
sendMsg(ChatUser::error, "invalid_color", "", "");
|
||||
return;
|
||||
}
|
||||
_color = newColor;
|
||||
_user.set_color(newColor);
|
||||
// Persistieren, falls DB-ID vorhanden
|
||||
try {
|
||||
if (_user.id() != 0) {
|
||||
auto server = _parent->getServer();
|
||||
auto db = server->_database;
|
||||
std::string query = "UPDATE chat.\"user\" SET color = '" + newColor + "', updated_at = NOW() WHERE id = " + std::to_string(_user.id()) + ";";
|
||||
(void)db->exec(query);
|
||||
}
|
||||
} catch (...) {
|
||||
// Ignoriere DB-Fehler still
|
||||
}
|
||||
// Bestätigung an User
|
||||
sendMsg(ChatUser::system, "color_changed", "", newColor);
|
||||
// Broadcast an andere: alte und neue Farbe mitsenden
|
||||
{
|
||||
Json::Value msg = Json::objectValue;
|
||||
msg["tr"] = "user_color_changed";
|
||||
msg["from"] = oldColor;
|
||||
msg["to"] = newColor;
|
||||
_parent->addMessage(ChatUser::system, msg, _name, newColor);
|
||||
}
|
||||
}
|
||||
else if (jsonTree["type"].asString() == "join")
|
||||
{
|
||||
@@ -230,10 +371,90 @@ namespace Yc
|
||||
changeRoom(room, password);
|
||||
}
|
||||
}
|
||||
else if (message.substr(0, 7) == "/color ")
|
||||
{
|
||||
std::string newColor = message.substr(7);
|
||||
std::string oldColor = _color;
|
||||
if (!_isValidHexColor(newColor)) {
|
||||
sendMsg(ChatUser::error, "invalid_color", "", "");
|
||||
return;
|
||||
}
|
||||
_color = newColor;
|
||||
_user.set_color(newColor);
|
||||
try {
|
||||
if (_user.id() != 0) {
|
||||
auto server = _parent->getServer();
|
||||
auto db = server->_database;
|
||||
std::string query = "UPDATE chat.\"user\" SET color = '" + newColor + "', updated_at = NOW() WHERE id = " + std::to_string(_user.id()) + ";";
|
||||
(void)db->exec(query);
|
||||
}
|
||||
} catch (...) {
|
||||
// ignore
|
||||
}
|
||||
sendMsg(ChatUser::system, "color_changed", "", newColor);
|
||||
{
|
||||
Json::Value msg = Json::objectValue;
|
||||
msg["tr"] = "user_color_changed";
|
||||
msg["from"] = oldColor;
|
||||
msg["to"] = newColor;
|
||||
_parent->addMessage(ChatUser::system, msg, _name, newColor);
|
||||
}
|
||||
}
|
||||
else if (message == "/dice")
|
||||
{
|
||||
doDice();
|
||||
}
|
||||
else if (message.substr(0, 15) == "/start_dice_game")
|
||||
{
|
||||
// Format: /start_dice_game <runden>
|
||||
if (message.length() > 16) {
|
||||
std::string roundsStr = message.substr(16);
|
||||
try {
|
||||
int rounds = std::stoi(roundsStr);
|
||||
if (rounds >= 1 && rounds <= 10) {
|
||||
if (!_parent->startDiceGame(rounds, shared_from_this())) {
|
||||
sendMsg(ChatUser::error, "dice_game_start_failed", "", "");
|
||||
}
|
||||
} else {
|
||||
sendMsg(ChatUser::error, "invalid_rounds", "", "");
|
||||
}
|
||||
} catch (...) {
|
||||
sendMsg(ChatUser::error, "invalid_rounds", "", "");
|
||||
}
|
||||
} else {
|
||||
sendMsg(ChatUser::error, "missing_rounds", "", "");
|
||||
}
|
||||
}
|
||||
else if (message == "/end_dice_game")
|
||||
{
|
||||
_parent->endDiceGame();
|
||||
}
|
||||
else if (message == "/reload_rooms")
|
||||
{
|
||||
// Raumliste neu laden
|
||||
_parent->reloadRoomList();
|
||||
}
|
||||
else if (message.substr(0, 5) == "/roll ")
|
||||
{
|
||||
// Format: /roll <würfelwert>
|
||||
if (message.length() > 6) {
|
||||
std::string diceStr = message.substr(6);
|
||||
try {
|
||||
int diceValue = std::stoi(diceStr);
|
||||
if (diceValue >= 1 && diceValue <= 6) {
|
||||
if (!_parent->rollDice(shared_from_this(), diceValue)) {
|
||||
sendMsg(ChatUser::error, "dice_roll_failed", "", "");
|
||||
}
|
||||
} else {
|
||||
sendMsg(ChatUser::error, "invalid_dice_value", "", "");
|
||||
}
|
||||
} catch (...) {
|
||||
sendMsg(ChatUser::error, "invalid_dice_value", "", "");
|
||||
}
|
||||
} else {
|
||||
sendMsg(ChatUser::error, "missing_dice_value", "", "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_parent->addMessage(ChatUser::message, std::string(message), std::string(_name), std::string(_color));
|
||||
@@ -271,5 +492,10 @@ namespace Yc
|
||||
}
|
||||
}
|
||||
|
||||
void ChatUser::start() {
|
||||
auto self = shared_from_this();
|
||||
thread = std::thread([self]() { self->checkerTask(); });
|
||||
}
|
||||
|
||||
} // namespace Lib
|
||||
} // namespace Yc
|
||||
|
||||
Reference in New Issue
Block a user