diff --git a/lua/client/client.lua b/lua/client/client.lua
index ac33aec7..65ca82e5 100644
--- a/lua/client/client.lua
+++ b/lua/client/client.lua
@@ -538,13 +538,20 @@ fk.client_callback["SetPlayerMark"] = function(jsonData)
end
fk.client_callback["Chat"] = function(jsonData)
- -- jsonData: { int type, string msg }
+ -- jsonData: { int type, int sender, string msg }
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
if not p then
for _, pl in ipairs(ClientInstance.observers) do
- if pl.id == data.type then
+ if pl.id == data.sender then
p = pl; break
end
end
@@ -618,6 +625,10 @@ fk.client_callback["RemoveVirtualEquip"] = function(jsonData)
player:removeVirtualEquip(data.id)
end
+fk.client_callback["Heartbeat"] = function()
+ ClientInstance.client:notifyServer("Heartbeat", "")
+end
+
-- Create ClientInstance (used by Lua)
ClientInstance = Client:new()
dofile "lua/client/client_util.lua"
diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua
index 792d22ef..60d0db59 100644
--- a/lua/client/i18n/zh_CN.lua
+++ b/lua/client/i18n/zh_CN.lua
@@ -28,6 +28,8 @@ Fk:loadTranslationTable{
["General Packages"] = "武将拓展包",
["Card Packages"] = "卡牌拓展包",
+ ["$OnlineInfo"] = "大厅人数:%1,总在线人数:%2",
+
["Generals Overview"] = "武将一览",
["Cards Overview"] = "卡牌一览",
["Scenarios Overview"] = "玩法一览",
diff --git a/image/card/equipIcon/horse.png b/packages/standard_cards/image/card/equipIcon/horse.png
similarity index 100%
rename from image/card/equipIcon/horse.png
rename to packages/standard_cards/image/card/equipIcon/horse.png
diff --git a/qml/Logic.js b/qml/Logic.js
index 7b8f32ad..719b22a9 100644
--- a/qml/Logic.js
+++ b/qml/Logic.js
@@ -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) {
// jsonData: { string userName, string general, string time, string msg }
let current = mainStack.currentItem; // lobby(TODO) or room
let data = JSON.parse(jsonData);
- let pid = data.type;
+ let pid = data.sender;
let userName = data.userName;
let general = Backend.translate(data.general);
let time = data.time;
diff --git a/qml/Pages/Common/ChatBox.qml b/qml/Pages/Common/ChatBox.qml
index 732d3012..bfc4654d 100644
--- a/qml/Pages/Common/ChatBox.qml
+++ b/qml/Pages/Common/ChatBox.qml
@@ -44,7 +44,7 @@ Rectangle {
ClientInstance.notifyServer(
"Chat",
JSON.stringify({
- type: isLobby,
+ type: isLobby ? 1 : 2,
msg: text
})
);
diff --git a/qml/Pages/Lobby.qml b/qml/Pages/Lobby.qml
index 143b7489..9dbf3569 100644
--- a/qml/Pages/Lobby.qml
+++ b/qml/Pages/Lobby.qml
@@ -3,6 +3,7 @@ import QtQuick.Controls
import QtQuick.Window
import QtQuick.Layouts
import "LobbyElement"
+import "Common"
import "Logic.js" as Logic
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 {
@@ -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("" + raw.userName + ": " + raw.msg);
+ }
+
Component.onCompleted: {
toast.show(Backend.translate("$WelcomeToLobby"));
}
diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml
index b9e59edf..51d491db 100644
--- a/qml/Pages/Room.qml
+++ b/qml/Pages/Room.qml
@@ -669,6 +669,7 @@ Item {
}
function addToChat(pid, raw, msg) {
+ if (raw.type === 1) return;
chat.append(msg);
let photo = Logic.getPhotoOrSelf(pid);
if (photo === undefined)
diff --git a/src/network/router.cpp b/src/network/router.cpp
index fe72c5bc..ad5d5e7c 100644
--- a/src/network/router.cpp
+++ b/src/network/router.cpp
@@ -162,7 +162,7 @@ void Router::handlePacket(const QByteArray& rawPacket)
lobby_actions["UpdateAvatar"] = [](ServerPlayer *sender, const QString &jsonData){
auto arr = String2Json(jsonData).array();
auto avatar = arr[0].toString();
- static QRegularExpression nameExp("[\\000-\\057\\072-\\100\\133-\\140\\173-\\177]");
+ static QRegularExpression nameExp("['\";#]+|(--)|(/\\*)|(\\*/)|(--\\+)");
if (!nameExp.match(avatar).hasMatch()) {
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
.arg(avatar).arg(sender->getId());
@@ -238,6 +238,10 @@ void Router::handlePacket(const QByteArray& rawPacket)
else
{
ServerPlayer *player = qobject_cast(parent());
+ if (command == "Heartbeat") {
+ player->alive = true;
+ return;
+ }
Room *room = player->getRoom();
if (room->isLobby() && lobby_actions.contains(command))
diff --git a/src/server/room.cpp b/src/server/room.cpp
index 34868e39..8a3d353f 100644
--- a/src/server/room.cpp
+++ b/src/server/room.cpp
@@ -314,9 +314,11 @@ void Room::doBroadcastNotify(const QList targets,
void Room::chat(ServerPlayer *sender, const QString &jsonData) {
auto doc = String2Json(jsonData).object();
auto type = doc["type"].toInt();
- doc["type"] = sender->getId();
+ doc["sender"] = sender->getId();
if (type == 1) {
- // TODO: server chatting
+ doc["userName"] = sender->getScreenName();
+ auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
+ doBroadcastNotify(players, "Chat", json);
} else {
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json);
diff --git a/src/server/server.cpp b/src/server/server.cpp
index 48067ea7..4014521b 100644
--- a/src/server/server.cpp
+++ b/src/server/server.cpp
@@ -1,4 +1,5 @@
#include "server.h"
+#include "player.h"
#include "server_socket.h"
#include "client_socket.h"
#include "room.h"
@@ -7,6 +8,7 @@
#include "util.h"
#include "parser.h"
#include "packman.h"
+#include
Server *ServerInstance;
@@ -33,6 +35,28 @@ Server::Server(QObject* parent)
createRoom(nullptr, "Lobby", INT32_MAX);
connect(lobby(), &Room::playerAdded, 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()
@@ -119,6 +143,15 @@ void Server::updateRoomList()
"UpdateRoomList",
QString(jsonData)
);
+
+ lobby()->doBroadcastNotify(
+ lobby()->getPlayers(),
+ "UpdatePlayerNum",
+ QString(JsonArray2Bytes(QJsonArray({
+ lobby()->getPlayers().length(),
+ this->players.count(),
+ })))
+ );
}
sqlite3 *Server::getDatabase() {
@@ -206,7 +239,7 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString& name, co
{
// First check the name and password
// 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());
unsigned char buf[4096] = {0};
diff --git a/src/server/serverplayer.cpp b/src/server/serverplayer.cpp
index 6e61334d..b47d8117 100644
--- a/src/server/serverplayer.cpp
+++ b/src/server/serverplayer.cpp
@@ -11,6 +11,9 @@ ServerPlayer::ServerPlayer(Room *room)
setState(Player::Online);
this->room = room;
server = room->getServer();
+ connect(this, &ServerPlayer::kicked, this, &ServerPlayer::kick);
+
+ alive = true;
}
ServerPlayer::~ServerPlayer()
@@ -112,3 +115,10 @@ void ServerPlayer::prepareForRequest(const QString& command, const QString& data
requestCommand = command;
requestData = data;
}
+
+void ServerPlayer::kick() {
+ if (socket != nullptr) {
+ socket->disconnectFromHost();
+ }
+ setSocket(nullptr);
+}
diff --git a/src/server/serverplayer.h b/src/server/serverplayer.h
index 626c0089..89df7327 100644
--- a/src/server/serverplayer.h
+++ b/src/server/serverplayer.h
@@ -33,8 +33,12 @@ public:
void prepareForRequest(const QString &command,
const QString &data);
+ volatile bool alive; // For heartbeat
+ void kick();
+
signals:
void disconnected();
+ void kicked();
private:
ClientSocket *socket; // socket for communicating with client
diff --git a/src/server/shell.cpp b/src/server/shell.cpp
index 89af9a26..b8944a97 100644
--- a/src/server/shell.cpp
+++ b/src/server/shell.cpp
@@ -27,6 +27,7 @@ void Shell::helpCommand(QStringList &) {
qInfo("%s: Enable a package.", "enable");
qInfo("%s: Disable a package.", "disable");
qInfo("%s: Upgrade a package.", "upgrade");
+ qInfo("%s: Kick a player by his id.", "kick");
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() {
setObjectName("Shell");
signal(SIGINT, sigintHandler);
@@ -131,6 +150,7 @@ Shell::Shell() {
handlers["lspkg"] = &Shell::lspkgCommand;
handlers["enable"] = &Shell::enableCommand;
handlers["disable"] = &Shell::disableCommand;
+ handlers["kick"] = &Shell::kickCommand;
}
handler_map = handlers;
}
diff --git a/src/server/shell.h b/src/server/shell.h
index 229e81db..e283702c 100644
--- a/src/server/shell.h
+++ b/src/server/shell.h
@@ -36,6 +36,7 @@ private:
void lspkgCommand(QStringList &);
void enableCommand(QStringList &);
void disableCommand(QStringList &);
+ void kickCommand(QStringList &);
};
#endif