Observer chat (#125)

新增旁观者聊天栏兼公告栏
增加服务端广播消息功能
修复单机启动失败时被心跳包卡死机的情况
修复发配音时undefined
聊天信息服务端回显
This commit is contained in:
notify 2023-04-18 23:26:15 +08:00 committed by GitHub
parent ba8aae852d
commit 0e51614588
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 151 additions and 9 deletions

View File

@ -121,7 +121,7 @@ callbacks["UpdatePlayerNum"] = (j) => {
callbacks["Chat"] = function(jsonData) { callbacks["Chat"] = function(jsonData) {
// jsonData: { string userName, string general, string time, string msg } // jsonData: { string userName, string general, string time, string msg }
let current = mainStack.currentItem; // lobby(TODO) or room let current = mainStack.currentItem; // lobby or room
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let pid = data.sender; let pid = data.sender;
let userName = data.userName; let userName = data.userName;
@ -134,3 +134,8 @@ callbacks["Chat"] = function(jsonData) {
else else
current.addToChat(pid, data, `[${time}] ${userName}(${general}): ${msg}`); current.addToChat(pid, data, `[${time}] ${userName}(${general}): ${msg}`);
} }
callbacks["ServerMessage"] = function(jsonData) {
let current = mainStack.currentItem; // lobby or room
current.sendDanmaku('<font color="gold"><b>[Server] </b></font>' + jsonData);
}

79
qml/Pages/Danmaku.qml Normal file
View File

@ -0,0 +1,79 @@
import QtQuick
Item {
id: root
width: 900
height: 20
visible: false
property bool newTxtAvailable: true
property var stashedTxt: []
property int currentRunning: 0
onNewTxtAvailableChanged: {
if (!newTxtAvailable || stashedTxt.length === 0) {
return;
}
let t = stashedTxt.splice(0, 1)[0];
let obj = txtComponent.createObject(root, { text: t });
obj.finished.connect(() => obj.destroy());
obj.start();
}
onCurrentRunningChanged: {
visible = !!currentRunning;
}
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.7
}
Component {
id: txtComponent
Text {
id: txt
color: "white"
font.pixelSize: 18
font.family: fontLibian.name
textFormat: TextEdit.RichText
y: -1
property bool changedAvail: false
signal finished()
onXChanged: {
if (root.width - x - 40 > width && !changedAvail) {
root.newTxtAvailable = true;
changedAvail = true;
}
}
PropertyAnimation on x {
id: txtAnim
running: false
from: root.width
to: -txt.width
duration: (root.width + txt.width) * 5
onFinished: {
root.currentRunning--;
txt.finished();
}
}
function start() {
root.newTxtAvailable = false;
root.currentRunning++;
txtAnim.start();
}
}
}
function sendLog(txt) {
root.stashedTxt.push(txt);
if (root.newTxtAvailable) {
root.newTxtAvailable = false;
root.newTxtAvailable = true;
}
}
}

View File

@ -234,10 +234,19 @@ Item {
radius: 4 radius: 4
} }
Danmaku {
id: danmaku
width: parent.width
}
function addToChat(pid, raw, msg) { function addToChat(pid, raw, msg) {
if (raw.type !== 1) return; if (raw.type !== 1) return;
lobbyChat.append(msg); lobbyChat.append(msg);
toast.show("<b>" + raw.userName + "</b>: " + raw.msg); danmaku.sendLog("<b>" + raw.userName + "</b>: " + raw.msg);
}
function sendDanmaku(msg) {
danmaku.sendLog(msg);
} }
Component.onCompleted: { Component.onCompleted: {

View File

@ -625,6 +625,11 @@ Item {
} }
} }
Danmaku {
id: danmaku
width: parent.width
}
Shortcut { Shortcut {
sequence: "T" sequence: "T"
onActivated: { onActivated: {
@ -677,12 +682,16 @@ Item {
if (raw.type === 1) return; if (raw.type === 1) return;
if (raw.msg.startsWith("$")) { if (raw.msg.startsWith("$")) {
if (specialChat(pid, data, raw.msg.slice(1))) return; if (specialChat(pid, raw, raw.msg.slice(1))) return;
} }
chat.append(msg); chat.append(msg);
let photo = Logic.getPhotoOrSelf(pid); let photo = Logic.getPhotoOrSelf(pid);
if (photo === undefined) if (photo === undefined) {
let user = raw.userName;
let m = raw.msg;
danmaku.sendLog(`${user}: ${m}`);
return; return;
}
photo.chat(raw.msg); photo.chat(raw.msg);
} }
@ -720,8 +729,10 @@ Item {
chat.append(`[${time}] ${userName}(${general}): ${m}`); chat.append(`[${time}] ${userName}(${general}): ${m}`);
let photo = Logic.getPhotoOrSelf(pid); let photo = Logic.getPhotoOrSelf(pid);
if (photo === undefined) if (photo === undefined) {
danmaku.sendLog(`${userName}: ${m}`);
return true; return true;
}
photo.chat(m); photo.chat(m);
return true; return true;
@ -743,8 +754,10 @@ Item {
chat.append(`[${time}] ${userName}(${general}): ${m}`); chat.append(`[${time}] ${userName}(${general}): ${m}`);
let photo = Logic.getPhotoOrSelf(pid); let photo = Logic.getPhotoOrSelf(pid);
if (photo === undefined) if (photo === undefined) {
danmaku.sendLog(`${userName}: ${m}`);
return true; return true;
}
photo.chat(m); photo.chat(m);
return true; return true;
@ -757,6 +770,10 @@ Item {
log.append(msg); log.append(msg);
} }
function sendDanmaku(msg) {
danmaku.sendLog(msg);
}
function showDistance(show) { function showDistance(show) {
for (let i = 0; i < photoModel.count; i++) { for (let i = 0; i < photoModel.count; i++) {
let item = photos.itemAt(i); let item = photos.itemAt(i);

View File

@ -295,6 +295,9 @@ void Room::chat(ServerPlayer *sender, const QString &jsonData) {
doBroadcastNotify(players, "Chat", json); doBroadcastNotify(players, "Chat", json);
doBroadcastNotify(observers, "Chat", json); doBroadcastNotify(observers, "Chat", json);
} }
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
doc["msg"].toString().toUtf8().constData());
} }
void Room::gameOver() { void Room::gameOver() {

View File

@ -3,6 +3,7 @@
#include "server.h" #include "server.h"
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qobject.h>
#include "client_socket.h" #include "client_socket.h"
#include "packman.h" #include "packman.h"
@ -49,7 +50,12 @@ Server::Server(QObject *parent) : QObject(parent) {
} }
} }
QThread::sleep(20); for (int i = 0; i < 10; i++) {
if (!this->isListening) {
return;
}
QThread::sleep(2);
}
foreach (auto p, this->players.values()) { foreach (auto p, this->players.values()) {
if (p->getState() == Player::Online && !p->alive) { if (p->getState() == Player::Online && !p->alive) {
@ -58,6 +64,7 @@ Server::Server(QObject *parent) : QObject(parent) {
} }
} }
}); });
heartbeatThread->setParent(this);
heartbeatThread->setObjectName("Heartbeat"); heartbeatThread->setObjectName("Heartbeat");
heartbeatThread->start(); heartbeatThread->start();
} }
@ -70,7 +77,9 @@ Server::~Server() {
} }
bool Server::listen(const QHostAddress &address, ushort port) { bool Server::listen(const QHostAddress &address, ushort port) {
return server->listen(address, port); bool ret = server->listen(address, port);
isListening = ret;
return ret;
} }
void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity, void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
@ -139,6 +148,12 @@ void Server::updateRoomList() {
sqlite3 *Server::getDatabase() { return db; } sqlite3 *Server::getDatabase() { return db; }
void Server::broadcast(const QString &command, const QString &jsonData) {
foreach (ServerPlayer *p, players.values()) {
p->doNotify(command, jsonData);
}
}
void Server::processNewConnection(ClientSocket *client) { void Server::processNewConnection(ClientSocket *client) {
qInfo() << client->peerAddress() << "connected"; qInfo() << client->peerAddress() << "connected";
// version check, file check, ban IP, reconnect, etc // version check, file check, ban IP, reconnect, etc

View File

@ -33,6 +33,8 @@ public:
sqlite3 *getDatabase(); sqlite3 *getDatabase();
void broadcast(const QString &command, const QString &jsonData);
bool isListening;
signals: signals:
void roomCreated(Room *room); void roomCreated(Room *room);
void playerAdded(ServerPlayer *player); void playerAdded(ServerPlayer *player);

View File

@ -30,6 +30,7 @@ void Shell::helpCommand(QStringList &) {
qInfo("%s: Disable a package.", "disable"); qInfo("%s: Disable a package.", "disable");
qInfo("%s: Upgrade a package.", "upgrade"); qInfo("%s: Upgrade a package.", "upgrade");
qInfo("%s: Kick a player by his id.", "kick"); qInfo("%s: Kick a player by his id.", "kick");
qInfo("%s: Broadcast message.", "msg");
qInfo("For more commands, check the documentation."); qInfo("For more commands, check the documentation.");
} }
@ -133,10 +134,19 @@ void Shell::kickCommand(QStringList &list) {
auto p = ServerInstance->findPlayer(id); auto p = ServerInstance->findPlayer(id);
if (p) { if (p) {
p->kicked(); p->kicked();
qInfo("Success");
} }
} }
void Shell::msgCommand(QStringList &list) {
if (list.isEmpty()) {
qWarning("The 'msg' command needs message body.");
return;
}
auto msg = list.join(' ');
ServerInstance->broadcast("ServerMessage", msg);
}
Shell::Shell() { Shell::Shell() {
setObjectName("Shell"); setObjectName("Shell");
signal(SIGINT, sigintHandler); signal(SIGINT, sigintHandler);
@ -154,6 +164,7 @@ Shell::Shell() {
handlers["enable"] = &Shell::enableCommand; handlers["enable"] = &Shell::enableCommand;
handlers["disable"] = &Shell::disableCommand; handlers["disable"] = &Shell::disableCommand;
handlers["kick"] = &Shell::kickCommand; handlers["kick"] = &Shell::kickCommand;
handlers["msg"] = &Shell::msgCommand;
} }
handler_map = handlers; handler_map = handlers;
} }

View File

@ -39,6 +39,7 @@ private:
void enableCommand(QStringList &); void enableCommand(QStringList &);
void disableCommand(QStringList &); void disableCommand(QStringList &);
void kickCommand(QStringList &); void kickCommand(QStringList &);
void msgCommand(QStringList &);
}; };
#endif #endif