Lobby chat (#103)

大厅聊天
心跳包
服务端控制台踢人
This commit is contained in:
notify 2023-04-05 15:13:58 +08:00 committed by GitHub
parent 46176e2477
commit f1820bb07b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 150 additions and 72 deletions

View File

@ -538,13 +538,20 @@ fk.client_callback["SetPlayerMark"] = function(jsonData)
end end
fk.client_callback["Chat"] = function(jsonData) fk.client_callback["Chat"] = function(jsonData)
-- jsonData: { int type, string msg } -- jsonData: { int type, int sender, string msg }
local data = json.decode(jsonData) local data = json.decode(jsonData)
local p = ClientInstance:getPlayerById(data.type) if data.type == 1 then
data.general = ""
data.time = os.date("%H:%M:%S")
ClientInstance:notifyUI("Chat", json.encode(data))
return
end
local p = ClientInstance:getPlayerById(data.sender)
-- TODO: observer chatting -- TODO: observer chatting
if not p then if not p then
for _, pl in ipairs(ClientInstance.observers) do for _, pl in ipairs(ClientInstance.observers) do
if pl.id == data.type then if pl.id == data.sender then
p = pl; break p = pl; break
end end
end end
@ -618,6 +625,10 @@ fk.client_callback["RemoveVirtualEquip"] = function(jsonData)
player:removeVirtualEquip(data.id) player:removeVirtualEquip(data.id)
end end
fk.client_callback["Heartbeat"] = function()
ClientInstance.client:notifyServer("Heartbeat", "")
end
-- Create ClientInstance (used by Lua) -- Create ClientInstance (used by Lua)
ClientInstance = Client:new() ClientInstance = Client:new()
dofile "lua/client/client_util.lua" dofile "lua/client/client_util.lua"

View File

@ -28,6 +28,8 @@ Fk:loadTranslationTable{
["General Packages"] = "武将拓展包", ["General Packages"] = "武将拓展包",
["Card Packages"] = "卡牌拓展包", ["Card Packages"] = "卡牌拓展包",
["$OnlineInfo"] = "大厅人数:%1总在线人数%2",
["Generals Overview"] = "武将一览", ["Generals Overview"] = "武将一览",
["Cards Overview"] = "卡牌一览", ["Cards Overview"] = "卡牌一览",
["Scenarios Overview"] = "玩法一览", ["Scenarios Overview"] = "玩法一览",

View File

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 406 B

View File

@ -101,11 +101,20 @@ callbacks["UpdateRoomList"] = function(jsonData) {
}); });
} }
callbacks["UpdatePlayerNum"] = (j) => {
let current = mainStack.currentItem; // should be lobby
let data = JSON.parse(j);
let l = data[0];
let s = data[1];
current.lobbyPlayerNum = l;
current.serverPlayerNum = s;
}
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(TODO) or room
let data = JSON.parse(jsonData); let data = JSON.parse(jsonData);
let pid = data.type; let pid = data.sender;
let userName = data.userName; let userName = data.userName;
let general = Backend.translate(data.general); let general = Backend.translate(data.general);
let time = data.time; let time = data.time;

View File

@ -44,7 +44,7 @@ Rectangle {
ClientInstance.notifyServer( ClientInstance.notifyServer(
"Chat", "Chat",
JSON.stringify({ JSON.stringify({
type: isLobby, type: isLobby ? 1 : 2,
msg: text msg: text
}) })
); );

View File

@ -3,6 +3,7 @@ import QtQuick.Controls
import QtQuick.Window import QtQuick.Window
import QtQuick.Layouts import QtQuick.Layouts
import "LobbyElement" import "LobbyElement"
import "Common"
import "Logic.js" as Logic import "Logic.js" as Logic
Item { Item {
@ -107,69 +108,6 @@ Item {
} }
} }
} }
/*
GridLayout {
flow: GridLayout.TopToBottom
rows: 4
TileButton {
iconSource: "configure"
text: Backend.translate("Edit Profile")
onClicked: {
lobby_dialog.source = "LobbyElement/EditProfile.qml";
lobby_drawer.open();
}
}
TileButton {
iconSource: "create_room"
text: Backend.translate("Create Room")
onClicked: {
lobby_dialog.source = "LobbyElement/CreateRoom.qml";
lobby_drawer.open();
config.observing = false;
}
}
TileButton {
iconSource: "general_overview"
text: Backend.translate("Generals Overview")
onClicked: {
mainStack.push(mainWindow.generalsOverviewPage);
mainStack.currentItem.loadPackages();
}
}
TileButton {
iconSource: "card_overview"
text: Backend.translate("Cards Overview")
onClicked: {
mainStack.push(mainWindow.cardsOverviewPage);
mainStack.currentItem.loadPackages();
}
}
TileButton {
iconSource: "rule_summary"
text: Backend.translate("Scenarios Overview")
}
TileButton {
iconSource: "replay"
text: Backend.translate("Replay")
}
TileButton {
iconSource: "about"
text: Backend.translate("About")
onClicked: {
mainStack.push(mainWindow.aboutPage);
}
}
TileButton {
iconSource: "quit"
text: Backend.translate("Exit Lobby")
onClicked: {
toast.show("Goodbye.");
Backend.quitLobby();
mainStack.pop();
}
}
}
*/
} }
Button { Button {
@ -257,6 +195,49 @@ Item {
} }
} }
property int lobbyPlayerNum: 0
property int serverPlayerNum: 0
function updateOnlineInfo() {
}
onLobbyPlayerNumChanged: updateOnlineInfo();
onServerPlayerNumChanged: updateOnlineInfo();
Rectangle {
id: info
color: "#88EEEEEE"
width: childrenRect.width + 8
height: childrenRect.height + 4
anchors.bottom: parent.bottom
anchors.left: parent.left
radius: 4
Text {
x: 4; y: 2
font.pixelSize: 16
text: Backend.translate("$OnlineInfo")
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
+ "Powered by FreeKill " + FkVersion
}
}
ChatBox {
id: lobbyChat
anchors.bottom: info.top
width: info.width
height: root.height * 0.6
isLobby: true
color: "#88EEEEEE"
radius: 4
}
function addToChat(pid, raw, msg) {
if (raw.type !== 1) return;
lobbyChat.append(msg);
toast.show("<b>" + raw.userName + "</b>: " + raw.msg);
}
Component.onCompleted: { Component.onCompleted: {
toast.show(Backend.translate("$WelcomeToLobby")); toast.show(Backend.translate("$WelcomeToLobby"));
} }

View File

@ -669,6 +669,7 @@ Item {
} }
function addToChat(pid, raw, msg) { function addToChat(pid, raw, msg) {
if (raw.type === 1) return;
chat.append(msg); chat.append(msg);
let photo = Logic.getPhotoOrSelf(pid); let photo = Logic.getPhotoOrSelf(pid);
if (photo === undefined) if (photo === undefined)

View File

@ -162,7 +162,7 @@ void Router::handlePacket(const QByteArray& rawPacket)
lobby_actions["UpdateAvatar"] = [](ServerPlayer *sender, const QString &jsonData){ lobby_actions["UpdateAvatar"] = [](ServerPlayer *sender, const QString &jsonData){
auto arr = String2Json(jsonData).array(); auto arr = String2Json(jsonData).array();
auto avatar = arr[0].toString(); auto avatar = arr[0].toString();
static QRegularExpression nameExp("[\\000-\\057\\072-\\100\\133-\\140\\173-\\177]"); static QRegularExpression nameExp("['\";#]+|(--)|(/\\*)|(\\*/)|(--\\+)");
if (!nameExp.match(avatar).hasMatch()) { if (!nameExp.match(avatar).hasMatch()) {
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;") auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
.arg(avatar).arg(sender->getId()); .arg(avatar).arg(sender->getId());
@ -238,6 +238,10 @@ void Router::handlePacket(const QByteArray& rawPacket)
else else
{ {
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent()); ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
if (command == "Heartbeat") {
player->alive = true;
return;
}
Room *room = player->getRoom(); Room *room = player->getRoom();
if (room->isLobby() && lobby_actions.contains(command)) if (room->isLobby() && lobby_actions.contains(command))

View File

@ -314,9 +314,11 @@ void Room::doBroadcastNotify(const QList<ServerPlayer *> targets,
void Room::chat(ServerPlayer *sender, const QString &jsonData) { void Room::chat(ServerPlayer *sender, const QString &jsonData) {
auto doc = String2Json(jsonData).object(); auto doc = String2Json(jsonData).object();
auto type = doc["type"].toInt(); auto type = doc["type"].toInt();
doc["type"] = sender->getId(); doc["sender"] = sender->getId();
if (type == 1) { if (type == 1) {
// TODO: server chatting doc["userName"] = sender->getScreenName();
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json);
} else { } else {
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact); auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json); doBroadcastNotify(players, "Chat", json);

View File

@ -1,4 +1,5 @@
#include "server.h" #include "server.h"
#include "player.h"
#include "server_socket.h" #include "server_socket.h"
#include "client_socket.h" #include "client_socket.h"
#include "room.h" #include "room.h"
@ -7,6 +8,7 @@
#include "util.h" #include "util.h"
#include "parser.h" #include "parser.h"
#include "packman.h" #include "packman.h"
#include <qjsonarray.h>
Server *ServerInstance; Server *ServerInstance;
@ -33,6 +35,28 @@ Server::Server(QObject* parent)
createRoom(nullptr, "Lobby", INT32_MAX); createRoom(nullptr, "Lobby", INT32_MAX);
connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList); connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList);
connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList); connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList);
auto heartbeatThread = QThread::create([=](){
while (true) {
foreach (auto p, this->players.values()) {
if (p->getState() == Player::Online) {
p->alive = false;
p->doNotify("Heartbeat", "");
}
}
// wait for reply
QThread::sleep(5);
foreach (auto p, this->players.values()) {
if (p->getState() == Player::Online && !p->alive) {
p->kicked();
}
}
}
});
heartbeatThread->setObjectName("Heartbeat");
heartbeatThread->start();
} }
Server::~Server() Server::~Server()
@ -119,6 +143,15 @@ void Server::updateRoomList()
"UpdateRoomList", "UpdateRoomList",
QString(jsonData) QString(jsonData)
); );
lobby()->doBroadcastNotify(
lobby()->getPlayers(),
"UpdatePlayerNum",
QString(JsonArray2Bytes(QJsonArray({
lobby()->getPlayers().length(),
this->players.count(),
})))
);
} }
sqlite3 *Server::getDatabase() { sqlite3 *Server::getDatabase() {
@ -206,7 +239,7 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString& name, co
{ {
// First check the name and password // First check the name and password
// Matches a string that does not contain special characters // Matches a string that does not contain special characters
static QRegularExpression nameExp("[\\000-\\057\\072-\\100\\133-\\140\\173-\\177]"); static QRegularExpression nameExp("['\";#]+|(--)|(/\\*)|(\\*/)|(--\\+)");
auto encryted_pw = QByteArray::fromBase64(password.toLatin1()); auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
unsigned char buf[4096] = {0}; unsigned char buf[4096] = {0};

View File

@ -11,6 +11,9 @@ ServerPlayer::ServerPlayer(Room *room)
setState(Player::Online); setState(Player::Online);
this->room = room; this->room = room;
server = room->getServer(); server = room->getServer();
connect(this, &ServerPlayer::kicked, this, &ServerPlayer::kick);
alive = true;
} }
ServerPlayer::~ServerPlayer() ServerPlayer::~ServerPlayer()
@ -112,3 +115,10 @@ void ServerPlayer::prepareForRequest(const QString& command, const QString& data
requestCommand = command; requestCommand = command;
requestData = data; requestData = data;
} }
void ServerPlayer::kick() {
if (socket != nullptr) {
socket->disconnectFromHost();
}
setSocket(nullptr);
}

View File

@ -33,8 +33,12 @@ public:
void prepareForRequest(const QString &command, void prepareForRequest(const QString &command,
const QString &data); const QString &data);
volatile bool alive; // For heartbeat
void kick();
signals: signals:
void disconnected(); void disconnected();
void kicked();
private: private:
ClientSocket *socket; // socket for communicating with client ClientSocket *socket; // socket for communicating with client

View File

@ -27,6 +27,7 @@ void Shell::helpCommand(QStringList &) {
qInfo("%s: Enable a package.", "enable"); qInfo("%s: Enable a package.", "enable");
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("For more commands, check the documentation."); qInfo("For more commands, check the documentation.");
} }
@ -115,6 +116,24 @@ void Shell::lspkgCommand(QStringList &) {
} }
} }
void Shell::kickCommand(QStringList &list) {
if (list.isEmpty()) {
qWarning("The 'kick' command needs a player id.");
return;
}
auto pid = list[0];
bool ok;
int id = pid.toInt(&ok);
if (!ok) return;
auto p = ServerInstance->findPlayer(id);
if (p) {
p->kicked();
qInfo("Success");
}
}
Shell::Shell() { Shell::Shell() {
setObjectName("Shell"); setObjectName("Shell");
signal(SIGINT, sigintHandler); signal(SIGINT, sigintHandler);
@ -131,6 +150,7 @@ Shell::Shell() {
handlers["lspkg"] = &Shell::lspkgCommand; handlers["lspkg"] = &Shell::lspkgCommand;
handlers["enable"] = &Shell::enableCommand; handlers["enable"] = &Shell::enableCommand;
handlers["disable"] = &Shell::disableCommand; handlers["disable"] = &Shell::disableCommand;
handlers["kick"] = &Shell::kickCommand;
} }
handler_map = handlers; handler_map = handlers;
} }

View File

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