* Run room in lua

* handle offline player

* Better toast and other

* delete useless stuff

* todo: edit profile

* use pch
This commit is contained in:
Notify-ctrl 2022-03-27 14:49:41 +08:00 committed by Notify-ctrl
parent 3dc95ebc49
commit a67175f8eb
54 changed files with 780 additions and 368 deletions

View File

@ -17,7 +17,6 @@ set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
set(REQUIRED_QT_VERSION "5.15.2") set(REQUIRED_QT_VERSION "5.15.2")
include_directories(${PROJECT_SOURCE_DIR}/)
include_directories(include/lua) include_directories(include/lua)
include_directories(include/sqlite3) include_directories(include/sqlite3)
include_directories(src) include_directories(src)

View File

@ -1,4 +1,4 @@
# FreeKill 的数据库TODO # FreeKill 的数据库
> [dev](./index.md) > 数据库 > [dev](./index.md) > 数据库

View File

@ -38,7 +38,7 @@ $ ./FreeKill -s <port>
1. 检查IP是否被封禁。 // TODO: 数据库 1. 检查IP是否被封禁。 // TODO: 数据库
2. 检查客户端的延迟是否小于30秒。 2. 检查客户端的延迟是否小于30秒。
3. 在网络检测环节,若客户端网速达标的话,客户端应该会发回一个字符串。这个字符串保存着用户的用户名和密码,服务端检查这个字符串是否合法。 3. 在网络检测环节,若客户端网速达标的话,客户端应该会发回一个字符串。这个字符串保存着用户的用户名和密码,服务端检查这个字符串是否合法。
4. 上述检查都通过后重连TODO 4. 上述检查都通过后重连TODO:
5. 不要重连的话,服务端便为新连接新建一个`ServerPlayer`对象,并将其添加到大厅中。 5. 不要重连的话,服务端便为新连接新建一个`ServerPlayer`对象,并将其添加到大厅中。
___ ___
@ -78,6 +78,8 @@ ___
对于情况4因为游戏已经开始所以不能直接删除玩家需要把玩家的状态设为“离线”并继续游戏。在游戏结束后若玩家仍未重连则按情况2、3处理。 对于情况4因为游戏已经开始所以不能直接删除玩家需要把玩家的状态设为“离线”并继续游戏。在游戏结束后若玩家仍未重连则按情况2、3处理。
> Note: 这部分处理见于ServerPlayer类的析构函数。
___ ___
## 断线重连TODO ## 断线重连TODO

View File

@ -17,5 +17,23 @@ function Client:initialize()
end end
end end
freekill.client_callback["Setup"] = function(jsonData)
-- jsonData: [ int id, string screenName, string avatar ]
local data = json.decode(jsonData)
local id, name, avatar = data[1], data[2], data[3]
local self = freekill.Self
self:setId(id)
self:setScreenName(name)
self:setAvatar(avatar)
end
freekill.client_callback["AddPlayer"] = function(jsonData)
-- jsonData: [ int id, string screenName, string avatar ]
-- when other player enter the room, we create clientplayer(C and lua) for them
local data = json.decode(jsonData)
local id, name, avatar = data[1], data[2], data[3]
ClientInstance:notifyUI("AddPlayer", json.encode({ name, avatar }))
end
-- Create ClientInstance (used by Lua) -- Create ClientInstance (used by Lua)
ClientInstance = Client:new() ClientInstance = Client:new()

View File

@ -1,4 +1,4 @@
local Player = class("Skill") local Player = class("Player")
function Player:initialize() function Player:initialize()
self.hp = nil self.hp = nil
@ -25,3 +25,5 @@ function Player:setHp(maxHp, initialHp)
self.maxHp = maxHp self.maxHp = maxHp
self.hp = initialHp or maxHp self.hp = initialHp or maxHp
end end
return Player

64
lua/core/util.lua Normal file
View File

@ -0,0 +1,64 @@
-- the iterator of QList object
local qlist_iterator = function(list, n)
if n < list:length() - 1 then
return n + 1, list:at(n + 1) -- the next element of list
end
end
function freekill.qlist(list)
return qlist_iterator, list, -1
end
function table:contains(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end
for _, e in ipairs(self) do
if e == element then return true end
end
end
function table:insertTable(list)
for _, e in ipairs(list) do
table.insert(self, e)
end
end
Sql = {
open = function(filename)
return freekill.OpenDatabase(filename)
end,
close = function(db)
freekill.CloseDatabase(db)
end,
exec = function(db, sql)
freekill.ExecSQL(db, sql)
end,
exec_select = function(db, sql)
return json.decode(freekill.SelectFromDb(db, sql))
end,
}
function table:removeOne(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end
for i = 1, #self do
if self[i] == element then
table.remove(self, i)
return true
end
end
return false
end
local Util = class("Util")
function Util.static:createEnum(tbl, index)
assert(type(tbl) == "table")
local enumtbl = {}
local enumindex = index or 0
for i, v in ipairs(tbl) do
enumtbl[v] = enumindex + i
end
return enumtbl
end
return Util

View File

@ -7,6 +7,7 @@ package.path = package.path .. ";./lua/lib/?.lua"
-- load libraries -- load libraries
class = require "middleclass" class = require "middleclass"
json = require "json" json = require "json"
require "sha256"
Util = require "util" Util = require "util"
DebugMode = true DebugMode = true
@ -22,5 +23,6 @@ Sanguosha = require "engine"
General = require "general" General = require "general"
Card = require "card" Card = require "card"
Skill = require "skill" Skill = require "skill"
Player = require "player"
-- load packages -- load packages

195
lua/lib/sha256.lua Normal file
View File

@ -0,0 +1,195 @@
-- From http://pastebin.com/gsFrNjbt linked from http://www.computercraft.info/forums2/index.php?/topic/8169-sha-256-in-pure-lua/
--
-- Adaptation of the Secure Hashing Algorithm (SHA-244/256)
-- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm
--
-- Using an adapted version of the bit library
-- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua
--
local MOD = 2^32
local MODM = MOD-1
local function memoize(f)
local mt = {}
local t = setmetatable({}, mt)
function mt:__index(k)
local v = f(k)
t[k] = v
return v
end
return t
end
local function make_bitop_uncached(t, m)
local function bitop(a, b)
local res,p = 0,1
while a ~= 0 and b ~= 0 do
local am, bm = a % m, b % m
res = res + t[am][bm] * p
a = (a - am) / m
b = (b - bm) / m
p = p*m
end
res = res + (a + b) * p
return res
end
return bitop
end
local function make_bitop(t)
local op1 = make_bitop_uncached(t,2^1)
local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end)
return make_bitop_uncached(op2, 2 ^ (t.n or 1))
end
local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})
local function bxor(a, b, c, ...)
local z = nil
if b then
a = a % MOD
b = b % MOD
z = bxor1(a, b)
if c then z = bxor(z, c, ...) end
return z
elseif a then return a % MOD
else return 0 end
end
local function band(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = ((a + b) - bxor1(a,b)) / 2
if c then z = bit32_band(z, c, ...) end
return z
elseif a then return a % MOD
else return MODM end
end
local function bnot(x) return (-1 - x) % MOD end
local function rshift1(a, disp)
if disp < 0 then return lshift(a,-disp) end
return math.floor(a % 2 ^ 32 / 2 ^ disp)
end
local function rshift(x, disp)
if disp > 31 or disp < -31 then return 0 end
return rshift1(x % MOD, disp)
end
local function lshift(a, disp)
if disp < 0 then return rshift(a,-disp) end
return (a * 2 ^ disp) % 2 ^ 32
end
local function rrotate(x, disp)
x = x % MOD
disp = disp % 32
local low = band(x, 2 ^ disp - 1)
return rshift(x, disp) + lshift(low, 32 - disp)
end
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function str2hexa(s)
return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end))
end
local function num2s(l, n)
local s = ""
for i = 1, n do
local rem = l % 256
s = string.char(rem) .. s
l = (l - rem) / 256
end
return s
end
local function s232num(s, i)
local n = 0
for i = i, i + 3 do n = n*256 + string.byte(s, i) end
return n
end
local function preproc(msg, len)
local extra = 64 - ((len + 9) % 64)
len = num2s(8 * len, 8)
msg = msg .. "\128" .. string.rep("\0", extra) .. len
assert(#msg % 64 == 0)
return msg
end
local function initH256(H)
H[1] = 0x6a09e667
H[2] = 0xbb67ae85
H[3] = 0x3c6ef372
H[4] = 0xa54ff53a
H[5] = 0x510e527f
H[6] = 0x9b05688c
H[7] = 0x1f83d9ab
H[8] = 0x5be0cd19
return H
end
local function digestblock(msg, i, H)
local w = {}
for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end
for j = 17, 64 do
local v = w[j - 15]
local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3))
v = w[j - 2]
w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10))
end
local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
for i = 1, 64 do
local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
local maj = bxor(band(a, b), band(a, c), band(b, c))
local t2 = s0 + maj
local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
local ch = bxor (band(e, f), band(bnot(e), g))
local t1 = h + s1 + ch + k[i] + w[i]
h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
end
H[1] = band(H[1] + a)
H[2] = band(H[2] + b)
H[3] = band(H[3] + c)
H[4] = band(H[4] + d)
H[5] = band(H[5] + e)
H[6] = band(H[6] + f)
H[7] = band(H[7] + g)
H[8] = band(H[8] + h)
end
-- Made this global
function sha256(msg)
msg = preproc(msg, #msg)
local H = initH256({})
for i = 1, #msg, 64 do digestblock(msg, i, H) end
return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
end

View File

@ -1,18 +1,34 @@
Room = class("Room") local Room = class("Room")
-- Just same as "static int roomId" in cpp function Room:initialize(_room)
-- However id 0 is for lobby, so we start at 1 self.room = _room
local roomId = 1 self.players = {}
self.gameFinished = false
function Room:initialize()
self.id = roomId
roomId = roomId + 1
self.room = ServerInstace:findRoom(self.id)
end end
function Room:getCProperties() -- When this function returns, the Room(C++) thread stopped.
self.name = self.room:getName() function Room:run()
self.capacity = self.room:getCapacity() print 'Room is running!'
-- First, create players(Lua) from ServerPlayer(C++)
for _, p in freekill.qlist(self.room:getPlayers()) do
local player = ServerPlayer:new(p)
print(player:getId())
table.insert(self.players, p)
end
-- Second, assign role and adjust seats
-- Then let's choose general and start the game!
end
function Room:startGame()
while true do
if self.gameFinished then break end
end
end
function Room:gameOver()
self.gameFinished = true
-- dosomething
self.room:gameOver()
end end
return Room return Room

View File

@ -1,4 +1,7 @@
local Server = class("Server") Server = class('Server')
package.path = package.path .. ';./lua/server/?.lua'
Room = require "room"
ServerPlayer = require "serverplayer"
freekill.server_callback = {} freekill.server_callback = {}
@ -13,12 +16,21 @@ function Server:initialize()
end end
end end
self.rooms = {} -- hashtable: uid --> room self.server.startRoom = function(_self, _room)
self.players = {} -- hashtable: uid --> splayer local room = Room:new(_room)
end room.server = self
table.insert(self.rooms, room)
function Server:createRoom(owner, roomName, capacity) room:run()
-- If room.run returns, the game is over and lua room
-- should be destoried now.
-- This behavior does not affect C++ Room.
table.removeOne(self.rooms, room)
end
self.rooms = {}
self.players = {}
end end
freekill.server_callback["CreateRoom"] = function(jsonData) freekill.server_callback["CreateRoom"] = function(jsonData)
@ -28,7 +40,6 @@ freekill.server_callback["CreateRoom"] = function(jsonData)
local roomName = data[2] local roomName = data[2]
local capacity = data[3] local capacity = data[3]
freekill.ServerInstance:createRoom(owner, roomName, capacity) freekill.ServerInstance:createRoom(owner, roomName, capacity)
ServerInstance:createRoom()
end end
freekill.server_callback["EnterRoom"] = function(jsonData) freekill.server_callback["EnterRoom"] = function(jsonData)
@ -57,4 +68,13 @@ freekill.server_callback["DoLuaScript"] = function(jsonData)
assert(load(data[2]))() assert(load(data[2]))()
end end
freekill.server_callback["PlayerStateChanged"] = function(jsonData)
-- jsonData: [ int uid, string stateString ]
-- note: this function is not called by Router.
local data = json.decode(jsonData)
local id = data[1]
local stateString = data[2]
ServerInstance.players[id].state = stateString
end
ServerInstance = Server:new() ServerInstance = Server:new()

View File

@ -1 +1,12 @@
local ServerPlayer = Player:subclass("ServerPlayer")
function ServerPlayer:initialize(_self)
Player.initialize(self)
self.serverplayer = _self
end
function ServerPlayer:getId()
return self.serverplayer:getId()
end
return ServerPlayer

View File

@ -1,26 +0,0 @@
function table:contains(element)
if #self == 0 or type(self[1]) ~= type(element) then return false end
for _, e in ipairs(self) do
if e == element then return true end
end
end
function table:insertTable(list)
for _, e in ipairs(list) do
table.insert(self, e)
end
end
local Util = class("Util")
function Util.static:createEnum(tbl, index)
assert(type(tbl) == "table")
local enumtbl = {}
local enumindex = index or 0
for i, v in ipairs(tbl) do
enumtbl[v] = enumindex + i
end
return enumtbl
end
return Util

View File

@ -1,17 +1,23 @@
var callbacks = {}; var callbacks = {};
callbacks["NetworkDelayTest"] = function(jsonData) { callbacks["NetworkDelayTest"] = function(jsonData) {
Backend.notifyServer("Setup", JSON.stringify([ ClientInstance.notifyServer("Setup", JSON.stringify([
config.screenName, config.screenName,
config.password config.password
])); ]));
} }
callbacks["ErrorMsg"] = function(jsonData) { callbacks["ErrorMsg"] = function(jsonData) {
toast.show(jsonData); toast.show(jsonData, 5000);
mainWindow.busy = false; mainWindow.busy = false;
} }
callbacks["BackToStart"] = function(jsonData) {
while (mainStack.depth > 1) {
mainStack.pop();
}
}
callbacks["EnterLobby"] = function(jsonData) { callbacks["EnterLobby"] = function(jsonData) {
// depth == 1 means the lobby page is not present in mainStack // depth == 1 means the lobby page is not present in mainStack
if (mainStack.depth === 1) { if (mainStack.depth === 1) {

View File

@ -45,7 +45,7 @@ Item {
onClicked: { onClicked: {
mainWindow.busy = true; mainWindow.busy = true;
mainStack.pop(); mainStack.pop();
Backend.notifyServer( ClientInstance.notifyServer(
"CreateRoom", "CreateRoom",
JSON.stringify([roomName.text, playerNum.value]) JSON.stringify([roomName.text, playerNum.value])
); );

View File

@ -33,7 +33,6 @@ Item {
config.screenName = screenNameEdit.text; config.screenName = screenNameEdit.text;
config.password = passwordEdit.text; config.password = passwordEdit.text;
mainWindow.busy = true; mainWindow.busy = true;
toast.show("Connecting to host...");
Backend.joinServer(server_addr.text); Backend.joinServer(server_addr.text);
} }
} }
@ -43,7 +42,6 @@ Item {
config.screenName = screenNameEdit.text; config.screenName = screenNameEdit.text;
config.password = passwordEdit.text; config.password = passwordEdit.text;
mainWindow.busy = true; mainWindow.busy = true;
toast.show("Connecting to host...");
Backend.startServer(9527); Backend.startServer(9527);
Backend.joinServer("127.0.0.1"); Backend.joinServer("127.0.0.1");
} }

View File

@ -42,7 +42,7 @@ Item {
onExited: { parent.color = "black" } onExited: { parent.color = "black" }
onClicked: { onClicked: {
mainWindow.busy = true; mainWindow.busy = true;
Backend.notifyServer( ClientInstance.notifyServer(
"EnterRoom", "EnterRoom",
JSON.stringify([roomId]) JSON.stringify([roomId])
); );
@ -86,8 +86,11 @@ Item {
} }
ColumnLayout { ColumnLayout {
Text { Button {
text: "Avatar" text: "Edit Profile"
onClicked: {
}
} }
Button { Button {
text: "Create Room" text: "Create Room"

View File

@ -11,6 +11,9 @@ Item {
property int playerNum: 0 property int playerNum: 0
property var dashboardModel property var dashboardModel
property bool isOwner: false
property bool isStarted: false
// tmp // tmp
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
@ -20,9 +23,14 @@ Item {
text: "quit" text: "quit"
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
onClicked: { onClicked: {
Backend.notifyServer("QuitRoom", "[]"); ClientInstance.notifyServer("QuitRoom", "[]");
} }
} }
Button {
text: "start game"
visible: isOwner && !isStarted
anchors.centerIn: parent
}
// For debugging // For debugging
RowLayout { RowLayout {
@ -36,7 +44,7 @@ Item {
Button { Button {
text: "DoLuaScript" text: "DoLuaScript"
onClicked: { onClicked: {
Backend.notifyServer("DoLuaScript", JSON.stringify([lua.text])); ClientInstance.notifyServer("DoLuaScript", JSON.stringify([lua.text]));
} }
} }
} }
@ -74,6 +82,7 @@ Item {
faceturned: modelData.faceturned faceturned: modelData.faceturned
chained: modelData.chained chained: modelData.chained
drank: modelData.drank drank: modelData.drank
isOwner: modelData.isOwner
} }
} }
@ -113,14 +122,15 @@ Item {
self.faceturned: dashboardModel.faceturned self.faceturned: dashboardModel.faceturned
self.chained: dashboardModel.chained self.chained: dashboardModel.chained
self.drank: dashboardModel.drank self.drank: dashboardModel.drank
self.isOwner: dashboardModel.isOwner
} }
Component.onCompleted: { Component.onCompleted: {
toast.show("Sucesessfully entered room."); toast.show("Sucesessfully entered room.");
dashboardModel = { dashboardModel = {
general: "liubei", general: Self.avatar,
screenName: config.screenName, screenName: Self.screenName,
role: "unknown", role: "unknown",
kingdom: "qun", kingdom: "qun",
netstate: "online", netstate: "online",
@ -131,7 +141,8 @@ Item {
dying: false, dying: false,
faceturned: false, faceturned: false,
chained: false, chained: false,
drank: false drank: false,
isOwner: false
} }
playerNum = config.roomCapacity; playerNum = config.roomCapacity;
@ -151,7 +162,8 @@ Item {
dying: false, dying: false,
faceturned: false, faceturned: false,
chained: false, chained: false,
drank: false drank: false,
isOwner: false
}); });
} }
photoModel = photoModel; // Force the Repeater reload photoModel = photoModel; // Force the Repeater reload

View File

@ -22,6 +22,7 @@ Item {
property bool faceturned: false property bool faceturned: false
property bool chained: false property bool chained: false
property bool drank: false property bool drank: false
property bool isOwner: false
Image { Image {
id: back id: back

View File

@ -68,7 +68,8 @@ callbacks["AddPlayer"] = function(jsonData) {
dying: false, dying: false,
faceturned: false, faceturned: false,
chained: false, chained: false,
drank: false drank: false,
isOwner: false
}; };
photoModel = photoModel; photoModel = photoModel;
arrangePhotos(); arrangePhotos();
@ -95,7 +96,8 @@ callbacks["RemovePlayer"] = function(jsonData) {
dying: false, dying: false,
faceturned: false, faceturned: false,
chained: false, chained: false,
drank: false drank: false,
isOwner: false
}; };
photoModel = photoModel; photoModel = photoModel;
arrangePhotos(); arrangePhotos();
@ -103,3 +105,10 @@ callbacks["RemovePlayer"] = function(jsonData) {
} }
} }
} }
/*
callbacks["RoomOwner"] = function(jsonData) {
// jsonData: int uid of the owner
toast.show(J)
}
*/

56
qml/Toast.qml Normal file
View File

@ -0,0 +1,56 @@
import QtQuick 2.15
Rectangle {
function show(text, duration) {
message.text = text;
time = Math.max(duration, 2 * fadeTime);
animation.start();
}
id: root
readonly property real defaultTime: 3000
property real time: defaultTime
readonly property real fadeTime: 300
anchors.horizontalCenter: parent != null ? parent.horizontalCenter : undefined
height: message.height + 20
width: message.width + 40
radius: 16
opacity: 0
color: "#F2808A87"
Text {
id: message
color: "white"
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}
SequentialAnimation on opacity {
id: animation
running: false
NumberAnimation {
to: .9
duration: fadeTime
}
PauseAnimation {
duration: time - 2 * fadeTime
}
NumberAnimation {
to: 0
duration: fadeTime
}
onRunningChanged: {
if (!running) {
toast.model.remove(index);
}
}
}
}

37
qml/ToastManager.qml Normal file
View File

@ -0,0 +1,37 @@
import QtQuick 2.15
// copy from https://gist.github.com/jonmcclung/bae669101d17b103e94790341301c129
// and modified some code
ListView {
function show(text, duration) {
if (duration === undefined) {
duration = 3000;
}
model.insert(0, {text: text, duration: duration});
}
id: root
z: Infinity
spacing: 5
anchors.fill: parent
anchors.bottomMargin: 10
verticalLayoutDirection: ListView.BottomToTop
interactive: false
displaced: Transition {
NumberAnimation {
properties: "y"
easing.type: Easing.InOutQuad
}
}
delegate: Toast {
Component.onCompleted: {
show(text, duration);
}
}
model: ListModel {id: model}
}

View File

@ -49,46 +49,8 @@ Window {
id: config id: config
} }
Rectangle { ToastManager {
id: toast id: toast
opacity: 0
z: 998
anchors.horizontalCenter: parent.horizontalCenter
y: parent.height * 0.8
radius: 16
color: "#F2808A87"
height: toast_text.height + 20
width: toast_text.width + 40
Text {
id: toast_text
text: "FreeKill"
anchors.centerIn: parent
color: "white"
}
Behavior on opacity {
NumberAnimation {
duration: 240
easing.type: Easing.InOutQuad
}
}
SequentialAnimation {
id: keepAnim
running: toast.opacity == 1
PauseAnimation {
duration: 2800
}
ScriptAction {
script: {
toast.opacity = 0;
}
}
}
function show(text) {
opacity = 1;
toast_text.text = text;
}
} }
Connections { Connections {

View File

@ -1,14 +1,13 @@
set(freekill_SRCS set(freekill_SRCS
"main.cpp" "main.cpp"
"core/player.cpp" "core/player.cpp"
"core/global.cpp" "core/util.cpp"
"network/server_socket.cpp" "network/server_socket.cpp"
"network/client_socket.cpp" "network/client_socket.cpp"
"network/router.cpp" "network/router.cpp"
"server/server.cpp" "server/server.cpp"
"server/serverplayer.cpp" "server/serverplayer.cpp"
"server/room.cpp" "server/room.cpp"
"server/gamelogic.cpp"
"client/client.cpp" "client/client.cpp"
"client/clientplayer.cpp" "client/clientplayer.cpp"
"ui/qmlbackend.cpp" "ui/qmlbackend.cpp"
@ -16,7 +15,7 @@ set(freekill_SRCS
) )
set(freekill_HEADERS set(freekill_HEADERS
"core/global.h" "core/util.h"
"core/player.h" "core/player.h"
"network/server_socket.h" "network/server_socket.h"
"network/client_socket.h" "network/client_socket.h"
@ -24,7 +23,6 @@ set(freekill_HEADERS
"server/server.h" "server/server.h"
"server/serverplayer.h" "server/serverplayer.h"
"server/room.h" "server/room.h"
"server/gamelogic.h"
"client/client.h" "client/client.h"
"client/clientplayer.h" "client/clientplayer.h"
"ui/qmlbackend.h" "ui/qmlbackend.h"
@ -40,6 +38,7 @@ endif ()
source_group("Include" FILES ${freekill_HEADERS}) source_group("Include" FILES ${freekill_HEADERS})
add_executable(FreeKill ${freekill_SRCS}) add_executable(FreeKill ${freekill_SRCS})
target_precompile_headers(FreeKill PRIVATE "pch.h")
target_link_libraries(FreeKill ${LUA_LIB} ${SQLITE3_LIB} Qt5::Qml Qt5::Gui Qt5::Network Qt5::Multimedia) target_link_libraries(FreeKill ${LUA_LIB} ${SQLITE3_LIB} Qt5::Qml Qt5::Gui Qt5::Network Qt5::Multimedia)
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i") file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
add_custom_command( add_custom_command(

View File

@ -1,6 +1,8 @@
#include "client.h" #include "client.h"
#include "client_socket.h" #include "client_socket.h"
#include "clientplayer.h" #include "clientplayer.h"
#include "qmlbackend.h"
#include "util.h"
Client *ClientInstance; Client *ClientInstance;
ClientPlayer *Self; ClientPlayer *Self;
@ -9,7 +11,10 @@ Client::Client(QObject* parent)
: QObject(parent), callback(0) : QObject(parent), callback(0)
{ {
ClientInstance = this; ClientInstance = this;
Self = nullptr; Self = new ClientPlayer(0, this);
QQmlApplicationEngine *engine = Backend->getEngine();
engine->rootContext()->setContextProperty("ClientInstance", ClientInstance);
engine->rootContext()->setContextProperty("Self", Self);
ClientSocket *socket = new ClientSocket; ClientSocket *socket = new ClientSocket;
connect(socket, &ClientSocket::error_message, this, &Client::error_message); connect(socket, &ClientSocket::error_message, this, &Client::error_message);
@ -33,12 +38,6 @@ void Client::connectToHost(const QHostAddress& server, ushort port)
router->getSocket()->connectToHost(server, port); router->getSocket()->connectToHost(server, port);
} }
void Client::requestServer(const QString& command, const QString& jsonData, int timeout)
{
int type = Router::TYPE_REQUEST | Router::SRC_CLIENT | Router::DEST_SERVER;
router->request(type, command, jsonData, timeout);
}
void Client::replyToServer(const QString& command, const QString& jsonData) void Client::replyToServer(const QString& command, const QString& jsonData)
{ {
int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER; int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER;

View File

@ -1,11 +1,8 @@
#ifndef _CLIENT_H #ifndef _CLIENT_H
#define _CLIENT_H #define _CLIENT_H
#include <QObject>
#include <lua.hpp>
#include "router.h" #include "router.h"
#include "clientplayer.h" #include "clientplayer.h"
#include "global.h"
class Client : public QObject { class Client : public QObject {
Q_OBJECT Q_OBJECT
@ -15,16 +12,10 @@ public:
void connectToHost(const QHostAddress &server, ushort port); void connectToHost(const QHostAddress &server, ushort port);
// TODO: database of the server Q_INVOKABLE void replyToServer(const QString &command, const QString &jsonData);
// void signup Q_INVOKABLE void notifyServer(const QString &command, const QString &jsonData);
// void login
void requestServer(const QString &command, Q_INVOKABLE void callLua(const QString &command, const QString &jsonData);
const QString &jsonData, int timeout = -1);
void replyToServer(const QString &command, const QString &jsonData);
void notifyServer(const QString &command, const QString &jsonData);
void callLua(const QString &command, const QString &jsonData);
LuaFunction callback; LuaFunction callback;
signals: signals:
@ -32,7 +23,7 @@ signals:
private: private:
Router *router; Router *router;
QMap<uint, ClientPlayer *> players; QMap<int, ClientPlayer *> players;
lua_State *L; lua_State *L;
}; };

View File

@ -1,6 +1,6 @@
#include "clientplayer.h" #include "clientplayer.h"
ClientPlayer::ClientPlayer(uint id, QObject* parent) ClientPlayer::ClientPlayer(int id, QObject* parent)
: Player(parent) : Player(parent)
{ {
setId(id); setId(id);

View File

@ -5,8 +5,13 @@
class ClientPlayer : public Player { class ClientPlayer : public Player {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int id READ getId)
Q_PROPERTY(QString screenName READ getScreenName WRITE setScreenName)
Q_PROPERTY(QString avatar READ getAvatar WRITE setAvatar)
public: public:
ClientPlayer(uint id, QObject *parent = nullptr); ClientPlayer(int id, QObject *parent = nullptr);
~ClientPlayer(); ~ClientPlayer();
private: private:

View File

@ -12,12 +12,12 @@ Player::~Player()
{ {
} }
uint Player::getId() const int Player::getId() const
{ {
return id; return id;
} }
void Player::setId(uint id) void Player::setId(int id)
{ {
this->id = id; this->id = id;
} }

View File

@ -1,8 +1,6 @@
#ifndef _PLAYER_H #ifndef _PLAYER_H
#define _PLAYER_H #define _PLAYER_H
#include <QObject>
// Common part of ServerPlayer and ClientPlayer // Common part of ServerPlayer and ClientPlayer
// dont initialize it directly // dont initialize it directly
class Player : public QObject { class Player : public QObject {
@ -19,8 +17,8 @@ public:
explicit Player(QObject *parent = nullptr); explicit Player(QObject *parent = nullptr);
~Player(); ~Player();
uint getId() const; int getId() const;
void setId(uint id); void setId(int id);
QString getScreenName() const; QString getScreenName() const;
void setScreenName(const QString &name); void setScreenName(const QString &name);
@ -43,7 +41,7 @@ signals:
void readyChanged(); void readyChanged();
private: private:
uint id; int id;
QString screenName; // screenName should not be same. QString screenName; // screenName should not be same.
QString avatar; QString avatar;
State state; State state;

View File

@ -1,6 +1,4 @@
#include "global.h" #include "util.h"
#include <QtCore>
#include <QFileDevice>
extern "C" { extern "C" {
int luaopen_freekill(lua_State *); int luaopen_freekill(lua_State *);

View File

@ -1,12 +1,7 @@
#ifndef _GLOBAL_H #ifndef _GLOBAL_H
#define _GLOBAL_H #define _GLOBAL_H
#include <lua.hpp>
#include <sqlite3.h>
#include <QtCore>
// utilities // utilities
typedef int LuaFunction;
lua_State *CreateLuaState(); lua_State *CreateLuaState();
bool DoLuaScript(lua_State *L, const char *script); bool DoLuaScript(lua_State *L, const char *script);

View File

@ -1,8 +1,3 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QCommandLineParser>
#include <QDir>
#include "qmlbackend.h" #include "qmlbackend.h"
#include "server.h" #include "server.h"
@ -29,16 +24,18 @@ int main(int argc, char *argv[])
Server *server = new Server; Server *server = new Server;
if (!server->listen(QHostAddress::Any, serverPort)) { if (!server->listen(QHostAddress::Any, serverPort)) {
fprintf(stderr, "cannot listen on port %d!\n", serverPort); fprintf(stderr, "cannot listen on port %d!\n", serverPort);
exit(1); app.exit(1);
} }
return app.exec(); return app.exec();
} }
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
QmlBackend backend; QmlBackend backend;
backend.setEngine(&engine);
engine.rootContext()->setContextProperty("Backend", &backend); engine.rootContext()->setContextProperty("Backend", &backend);
QUrl currentDir = QUrl::fromLocalFile(QDir::currentPath()); engine.rootContext()->setContextProperty("AppPath", QUrl::fromLocalFile(QDir::currentPath()));
engine.rootContext()->setContextProperty("AppPath", currentDir);
#ifdef QT_DEBUG #ifdef QT_DEBUG
bool debugging = true; bool debugging = true;
#else #else

View File

@ -1,6 +1,4 @@
#include "client_socket.h" #include "client_socket.h"
#include <QTcpSocket>
#include <QHostAddress>
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) ClientSocket::ClientSocket() : socket(new QTcpSocket(this))
{ {
@ -92,6 +90,6 @@ void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error)
default: reason = tr("Unknow error"); break; default: reason = tr("Unknow error"); break;
} }
emit error_message(tr("Connection failed, error code = %1\n reason:\n %2") emit error_message(tr("Connection failed, error code = %1\n reason: %2")
.arg(socket_error).arg(reason)); .arg(socket_error).arg(reason));
} }

View File

@ -1,13 +1,6 @@
#ifndef _CLIENT_SOCKET_H #ifndef _CLIENT_SOCKET_H
#define _CLIENT_SOCKET_H #define _CLIENT_SOCKET_H
#include <QObject>
#include <QAbstractSocket>
#include <QHostAddress>
#include <QTimer>
class QTcpSocket;
class ClientSocket : public QObject { class ClientSocket : public QObject {
Q_OBJECT Q_OBJECT

View File

@ -1,7 +1,6 @@
#include <QJsonArray>
#include <QJsonDocument>
#include "router.h" #include "router.h"
#include "client.h" #include "client.h"
#include "client_socket.h"
#include "server.h" #include "server.h"
#include "serverplayer.h" #include "serverplayer.h"
@ -159,7 +158,7 @@ void Router::handlePacket(const QByteArray& rawPacket)
// Add the uid of sender to jsonData // Add the uid of sender to jsonData
QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array(); QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array();
arr.prepend( arr.prepend(
(int)qobject_cast<ServerPlayer *>(parent())->getId() qobject_cast<ServerPlayer *>(parent())->getId()
); );
ServerInstance->callLua(command, QJsonDocument(arr).toJson()); ServerInstance->callLua(command, QJsonDocument(arr).toJson());
} }

View File

@ -1,12 +1,7 @@
#ifndef _ROUTER_H #ifndef _ROUTER_H
#define _ROUTER_H #define _ROUTER_H
#include <QObject> class ClientSocket;
#include <QDateTime>
#include <QMutex>
#include <QVariant>
#include <QSemaphore>
#include "client_socket.h"
class Router : public QObject { class Router : public QObject {
Q_OBJECT Q_OBJECT

View File

@ -1,6 +1,5 @@
#include "server_socket.h" #include "server_socket.h"
#include "client_socket.h" #include "client_socket.h"
#include <QTcpServer>
ServerSocket::ServerSocket() ServerSocket::ServerSocket()
{ {

View File

@ -1,10 +1,6 @@
#ifndef _SERVER_SOCKET_H #ifndef _SERVER_SOCKET_H
#define _SERVER_SOCKET_H #define _SERVER_SOCKET_H
#include <QObject>
#include <QHostAddress>
class QTcpServer;
class ClientSocket; class ClientSocket;
class ServerSocket : public QObject { class ServerSocket : public QObject {

18
src/pch.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef _PCH_H
#define _PCH_H
// core gui qml
#include <QtCore>
#include <QGuiApplication>
#include <QtQml>
// network
#include <QTcpServer>
#include <QTcpSocket>
// other libraries
typedef int LuaFunction;
#include "lua.hpp"
#include "sqlite3.h"
#endif // _PCH_H

View File

@ -1,11 +0,0 @@
#include "gamelogic.h"
GameLogic::GameLogic(Room *room)
{
}
void GameLogic::run()
{
}

View File

@ -1,17 +0,0 @@
#ifndef _GAMELOGIC_H
#define _GAMELOGIC_H
#include <QThread>
class Room;
// Just like the class 'RoomThread' in QSanguosha
class GameLogic : public QThread {
Q_OBJECT
public:
explicit GameLogic(Room *room);
protected:
virtual void run();
};
#endif // _GAMELOGIC_H

View File

@ -1,15 +1,14 @@
#include "room.h" #include "room.h"
#include "serverplayer.h" #include "serverplayer.h"
#include "server.h" #include "server.h"
#include <QJsonArray>
#include <QJsonDocument>
Room::Room(Server* server) Room::Room(Server* server)
{ {
static uint roomId = 0; static int roomId = 0;
id = roomId; id = roomId;
roomId++; roomId++;
this->server = server; this->server = server;
gameStarted = false;
if (!isLobby()) { if (!isLobby()) {
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer); connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer); connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
@ -27,7 +26,7 @@ Server *Room::getServer() const
return server; return server;
} }
uint Room::getId() const int Room::getId() const
{ {
return id; return id;
} }
@ -47,12 +46,12 @@ void Room::setName(const QString &name)
this->name = name; this->name = name;
} }
uint Room::getCapacity() const int Room::getCapacity() const
{ {
return capacity; return capacity;
} }
void Room::setCapacity(uint capacity) void Room::setCapacity(int capacity)
{ {
this->capacity = capacity; this->capacity = capacity;
} }
@ -75,6 +74,9 @@ ServerPlayer *Room::getOwner() const
void Room::setOwner(ServerPlayer *owner) void Room::setOwner(ServerPlayer *owner)
{ {
this->owner = owner; this->owner = owner;
QJsonArray jsonData;
jsonData << owner->getId();
owner->doNotify("RoomOwner", QJsonDocument(jsonData).toJson());
} }
void Room::addPlayer(ServerPlayer *player) void Room::addPlayer(ServerPlayer *player)
@ -85,6 +87,7 @@ void Room::addPlayer(ServerPlayer *player)
// First, notify other players the new player is entering // First, notify other players the new player is entering
if (!isLobby()) { if (!isLobby()) {
jsonData << player->getId();
jsonData << player->getScreenName(); jsonData << player->getScreenName();
jsonData << player->getAvatar(); jsonData << player->getAvatar();
doBroadcastNotify(getPlayers(), "AddPlayer", QJsonDocument(jsonData).toJson()); doBroadcastNotify(getPlayers(), "AddPlayer", QJsonDocument(jsonData).toJson());
@ -97,15 +100,19 @@ void Room::addPlayer(ServerPlayer *player)
} else { } else {
// Second, let the player enter room and add other players // Second, let the player enter room and add other players
jsonData = QJsonArray(); jsonData = QJsonArray();
jsonData << (int)this->capacity; jsonData << this->capacity;
player->doNotify("EnterRoom", QJsonDocument(jsonData).toJson()); player->doNotify("EnterRoom", QJsonDocument(jsonData).toJson());
foreach (ServerPlayer *p, getOtherPlayers(player)) { foreach (ServerPlayer *p, getOtherPlayers(player)) {
jsonData = QJsonArray(); jsonData = QJsonArray();
jsonData << p->getId();
jsonData << p->getScreenName(); jsonData << p->getScreenName();
jsonData << p->getAvatar(); jsonData << p->getAvatar();
player->doNotify("AddPlayer", QJsonDocument(jsonData).toJson()); player->doNotify("AddPlayer", QJsonDocument(jsonData).toJson());
} }
if (isFull())
start();
} }
emit playerAdded(player); emit playerAdded(player);
} }
@ -126,7 +133,6 @@ void Room::removePlayer(ServerPlayer *player)
emit abandoned(); emit abandoned();
} else if (player == owner) { } else if (player == owner) {
setOwner(players.first()); setOwner(players.first());
owner->doNotify("RoomOwner", "[]");
} }
} }
@ -142,7 +148,7 @@ QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer* expect) const
return others; return others;
} }
ServerPlayer *Room::findPlayer(uint id) const ServerPlayer *Room::findPlayer(int id) const
{ {
foreach (ServerPlayer *p, players) { foreach (ServerPlayer *p, players) {
if (p->getId() == id) if (p->getId() == id)
@ -151,19 +157,9 @@ ServerPlayer *Room::findPlayer(uint id) const
return nullptr; return nullptr;
} }
void Room::setGameLogic(GameLogic *logic) bool Room::isStarted() const
{ {
this->logic = logic; return gameStarted;
}
GameLogic *Room::getGameLogic() const
{
return logic;
}
void Room::startGame()
{
// TODO
} }
void Room::doRequest(const QList<ServerPlayer *> targets, int timeout) void Room::doRequest(const QList<ServerPlayer *> targets, int timeout)
@ -184,9 +180,20 @@ void Room::doBroadcastNotify(const QList<ServerPlayer *> targets,
} }
} }
void Room::gameOver()
{
gameStarted = false;
// clean offline players
foreach (ServerPlayer *p, players) {
if (p->getState() == Player::Offline) {
p->deleteLater();
}
}
}
void Room::run() void Room::run()
{ {
// TODO gameStarted = true;
getServer()->roomStart(this);
} }

View File

@ -1,11 +1,8 @@
#ifndef _ROOM_H #ifndef _ROOM_H
#define _ROOM_H #define _ROOM_H
#include <QThread>
#include <QList>
class Server; class Server;
class ServerPlayer; class ServerPlayer;
class GameLogic;
class Room : public QThread { class Room : public QThread {
Q_OBJECT Q_OBJECT
@ -16,12 +13,12 @@ public:
// Property reader & setter // Property reader & setter
// ==================================={ // ==================================={
Server *getServer() const; Server *getServer() const;
uint getId() const; int getId() const;
bool isLobby() const; bool isLobby() const;
QString getName() const; QString getName() const;
void setName(const QString &name); void setName(const QString &name);
uint getCapacity() const; int getCapacity() const;
void setCapacity(uint capacity); void setCapacity(int capacity);
bool isFull() const; bool isFull() const;
bool isAbandoned() const; bool isAbandoned() const;
@ -32,13 +29,11 @@ public:
void removePlayer(ServerPlayer *player); void removePlayer(ServerPlayer *player);
QList<ServerPlayer*> getPlayers() const; QList<ServerPlayer*> getPlayers() const;
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const; QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
ServerPlayer *findPlayer(uint id) const; ServerPlayer *findPlayer(int id) const;
void setGameLogic(GameLogic *logic); bool isStarted() const;
GameLogic *getGameLogic() const;
// ====================================} // ====================================}
void startGame();
void doRequest(const QList<ServerPlayer *> targets, int timeout); void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout); void doNotify(const QList<ServerPlayer *> targets, int timeout);
@ -48,13 +43,11 @@ public:
const QString &jsonData const QString &jsonData
); );
void gameOver();
signals: signals:
void abandoned(); void abandoned();
void aboutToStart();
void started();
void finished();
void playerAdded(ServerPlayer *player); void playerAdded(ServerPlayer *player);
void playerRemoved(ServerPlayer *player); void playerRemoved(ServerPlayer *player);
@ -63,14 +56,14 @@ protected:
private: private:
Server *server; Server *server;
uint id; // Lobby's id is 0 int id; // Lobby's id is 0
QString name; // “阴间大乱斗” QString name; // “阴间大乱斗”
uint capacity; // by default is 5, max is 8 int capacity; // by default is 5, max is 8
bool m_abandoned; // If room is empty, delete it bool m_abandoned; // If room is empty, delete it
ServerPlayer *owner; // who created this room? ServerPlayer *owner; // who created this room?
QList<ServerPlayer *> players; QList<ServerPlayer *> players;
GameLogic *logic; bool gameStarted;
}; };
#endif // _ROOM_H #endif // _ROOM_H

View File

@ -2,13 +2,9 @@
#include "server_socket.h" #include "server_socket.h"
#include "client_socket.h" #include "client_socket.h"
#include "room.h" #include "room.h"
#include "router.h"
#include "serverplayer.h" #include "serverplayer.h"
#include "global.h" #include "util.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegExp>
#include <QCryptographicHash>
Server *ServerInstance; Server *ServerInstance;
@ -22,8 +18,9 @@ Server::Server(QObject* parent)
this, &Server::processNewConnection); this, &Server::processNewConnection);
// create lobby // create lobby
createRoom(NULL, "Lobby", UINT32_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);
L = CreateLuaState(); L = CreateLuaState();
DoLuaScript(L, "lua/freekill.lua"); DoLuaScript(L, "lua/freekill.lua");
@ -45,21 +42,22 @@ bool Server::listen(const QHostAddress& address, ushort port)
return server->listen(address, port); return server->listen(address, port);
} }
void Server::createRoom(ServerPlayer* owner, const QString &name, uint capacity) void Server::createRoom(ServerPlayer* owner, const QString &name, int capacity)
{ {
Room *room = new Room(this); Room *room = new Room(this);
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned); connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
room->setName(name);
room->setCapacity(capacity);
room->setOwner(owner);
room->addPlayer(owner);
if (room->isLobby()) if (room->isLobby())
m_lobby = room; m_lobby = room;
else else
rooms.insert(room->getId(), room); rooms.insert(room->getId(), room);
room->setName(name);
room->setCapacity(capacity);
room->addPlayer(owner);
if (!room->isLobby()) room->setOwner(owner);
} }
Room *Server::findRoom(uint id) const Room *Server::findRoom(int id) const
{ {
return rooms.value(id); return rooms.value(id);
} }
@ -69,21 +67,25 @@ Room *Server::lobby() const
return m_lobby; return m_lobby;
} }
ServerPlayer *Server::findPlayer(uint id) const ServerPlayer *Server::findPlayer(int id) const
{ {
return players.value(id); return players.value(id);
} }
void Server::removePlayer(int id) {
players.remove(id);
}
void Server::updateRoomList() void Server::updateRoomList()
{ {
QJsonArray arr; QJsonArray arr;
foreach (Room *room, rooms) { foreach (Room *room, rooms) {
QJsonArray obj; QJsonArray obj;
obj << (int)room->getId(); // roomId obj << room->getId(); // roomId
obj << room->getName(); // roomName obj << room->getName(); // roomName
obj << "Role"; // gameMode obj << "Role"; // gameMode
obj << room->getPlayers().count(); // playerNum obj << room->getPlayers().count(); // playerNum
obj << (int)room->getCapacity(); // capacity obj << room->getCapacity(); // capacity
arr << obj; arr << obj;
} }
lobby()->doBroadcastNotify( lobby()->doBroadcastNotify(
@ -158,7 +160,9 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString& name, co
QRegExp nameExp("[^\\0000-\\0057\\0072-\\0100\\0133-\\0140\\0173-\\0177]+"); QRegExp nameExp("[^\\0000-\\0057\\0072-\\0100\\0133-\\0140\\0173-\\0177]+");
QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex(); QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex();
bool passed = false; bool passed = false;
QString error_msg;
QJsonObject result; QJsonObject result;
if (nameExp.exactMatch(name)) { if (nameExp.exactMatch(name)) {
// Then we check the database, // Then we check the database,
QString sql_find = QString("SELECT * FROM userinfo \ QString sql_find = QString("SELECT * FROM userinfo \
@ -178,30 +182,46 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString& name, co
result = SelectFromDatabase(db, sql_find); // refresh result result = SelectFromDatabase(db, sql_find); // refresh result
passed = true; passed = true;
} else { } else {
// check if this username already login
int id = result["id"].toArray()[0].toString().toInt();
if (!players.value(id))
// check if password is the same // check if password is the same
passed = (passwordHash == arr[0].toString()); passed = (passwordHash == arr[0].toString());
if (!passed) error_msg = "username or password error";
else {
// TODO: reconnect here
error_msg = "others logged in with this name";
}
} }
} }
if (passed) { if (passed) {
// create new ServerPlayer and setup
ServerPlayer *player = new ServerPlayer(lobby()); ServerPlayer *player = new ServerPlayer(lobby());
player->setSocket(client); player->setSocket(client);
client->disconnect(this); client->disconnect(this);
connect(client, &ClientSocket::disconnected, this, [player](){ connect(player, &ServerPlayer::disconnected, this, &Server::onUserDisconnected);
qDebug() << "Player" << player->getId() << "disconnected"; connect(player, &Player::stateChanged, this, &Server::onUserStateChanged);
});
player->setScreenName(name); player->setScreenName(name);
player->setAvatar(result["avatar"].toArray()[0].toString()); player->setAvatar(result["avatar"].toArray()[0].toString());
player->setId(result["id"].toArray()[0].toInt()); player->setId(result["id"].toArray()[0].toString().toInt());
players.insert(player->getId(), player); players.insert(player->getId(), player);
// tell the lobby player's basic property
QJsonArray arr;
arr << player->getId();
arr << player->getScreenName();
arr << player->getAvatar();
player->doNotify("Setup", QJsonDocument(arr).toJson());
lobby()->addPlayer(player); lobby()->addPlayer(player);
} else { } else {
qDebug() << client->peerAddress() << "entered wrong password"; qDebug() << client->peerAddress() << "lost connection:" << error_msg;
QJsonArray body; QJsonArray body;
body << -2; body << -2;
body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT);
body << "ErrorMsg"; body << "ErrorMsg";
body << "username or password error"; body << error_msg;
client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); client->send(QJsonDocument(body).toJson(QJsonDocument::Compact));
client->disconnectFromHost(); client->disconnectFromHost();
return; return;
@ -218,10 +238,21 @@ void Server::onRoomAbandoned()
void Server::onUserDisconnected() void Server::onUserDisconnected()
{ {
qobject_cast<ServerPlayer *>(sender())->setStateString("offline"); ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
qDebug() << "Player" << player->getId() << "disconnected";
Room *room = player->getRoom();
if (room->isStarted()) {
player->setState(Player::Offline);
} else {
player->deleteLater();
}
} }
void Server::onUserStateChanged() void Server::onUserStateChanged()
{ {
// TODO Player *player = qobject_cast<Player *>(sender());
QJsonArray arr;
arr << player->getId();
arr << player->getStateString();
callLua("PlayerStateChanged", QJsonDocument(arr).toJson());
} }

View File

@ -1,20 +1,11 @@
#ifndef _SERVER_H #ifndef _SERVER_H
#define _SERVER_H #define _SERVER_H
#include <QObject>
#include <QHash>
#include <QMap>
#include <QHostAddress>
#include <lua.hpp>
#include <sqlite3.h>
class ServerSocket; class ServerSocket;
class ClientSocket; class ClientSocket;
class Room; class Room;
class ServerPlayer; class ServerPlayer;
typedef int LuaFunction;
class Server : public QObject { class Server : public QObject {
Q_OBJECT Q_OBJECT
@ -24,17 +15,21 @@ public:
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
void createRoom(ServerPlayer *owner, const QString &name, uint capacity); void createRoom(ServerPlayer *owner, const QString &name, int capacity);
Room *findRoom(uint id) const; Room *findRoom(int id) const;
Room *lobby() const; Room *lobby() const;
ServerPlayer *findPlayer(uint id) const; ServerPlayer *findPlayer(int id) const;
void removePlayer(int id);
void updateRoomList(); void updateRoomList();
void callLua(const QString &command, const QString &jsonData); void callLua(const QString &command, const QString &jsonData);
LuaFunction callback; LuaFunction callback;
void roomStart(Room *room);
LuaFunction startRoom;
signals: signals:
void roomCreated(Room *room); void roomCreated(Room *room);
void playerAdded(ServerPlayer *player); void playerAdded(ServerPlayer *player);
@ -51,8 +46,8 @@ public slots:
private: private:
ServerSocket *server; ServerSocket *server;
Room *m_lobby; Room *m_lobby;
QMap<uint, Room *> rooms; QMap<int, Room *> rooms;
QHash<uint, ServerPlayer *> players; QHash<int, ServerPlayer *> players;
lua_State *L; lua_State *L;
sqlite3 *db; sqlite3 *db;

View File

@ -1,6 +1,8 @@
#include "serverplayer.h" #include "serverplayer.h"
#include "room.h" #include "room.h"
#include "server.h" #include "server.h"
#include "router.h"
#include "client_socket.h"
ServerPlayer::ServerPlayer(Room *room) ServerPlayer::ServerPlayer(Room *room)
{ {
@ -8,22 +10,41 @@ ServerPlayer::ServerPlayer(Room *room)
router = new Router(this, socket, Router::TYPE_SERVER); router = new Router(this, socket, Router::TYPE_SERVER);
this->room = room; this->room = room;
server = room->getServer();
} }
ServerPlayer::~ServerPlayer() ServerPlayer::~ServerPlayer()
{ {
// clean up, quit room and server
room->removePlayer(this);
if (room != nullptr) {
// now we are in lobby, so quit lobby
room->removePlayer(this);
}
server->removePlayer(getId());
router->deleteLater(); router->deleteLater();
} }
void ServerPlayer::setSocket(ClientSocket *socket) void ServerPlayer::setSocket(ClientSocket *socket)
{ {
if (this->socket != nullptr) {
this->socket->disconnect(this);
disconnect(this->socket);
this->socket->deleteLater();
}
this->socket = nullptr;
if (socket != nullptr) {
connect(socket, &ClientSocket::disconnected, this, &ServerPlayer::disconnected);
this->socket = socket; this->socket = socket;
}
router->setSocket(socket); router->setSocket(socket);
} }
Server *ServerPlayer::getServer() const Server *ServerPlayer::getServer() const
{ {
return room->getServer(); return server;
} }
Room *ServerPlayer::getRoom() const Room *ServerPlayer::getRoom() const
@ -47,19 +68,14 @@ void ServerPlayer::doRequest(const QString& command, const QString& jsonData, in
router->request(type, command, jsonData, timeout); router->request(type, command, jsonData, timeout);
} }
void ServerPlayer::doReply(const QString& command, const QString& jsonData)
{
int type = Router::TYPE_REPLY | Router::SRC_SERVER | Router::DEST_CLIENT;
router->reply(type, command, jsonData);
}
void ServerPlayer::doNotify(const QString& command, const QString& jsonData) void ServerPlayer::doNotify(const QString& command, const QString& jsonData)
{ {
int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT; int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT;
router->notify(type, command, jsonData); router->notify(type, command, jsonData);
} }
void ServerPlayer::prepareForRequest(const QString& command, const QVariant& data) void ServerPlayer::prepareForRequest(const QString& command, const QString& data)
{ {
; requestCommand = command;
requestData = data;
} }

View File

@ -2,9 +2,9 @@
#define _SERVERPLAYER_H #define _SERVERPLAYER_H
#include "player.h" #include "player.h"
#include "router.h"
#include <QVariant>
class ClientSocket; class ClientSocket;
class Router;
class Server; class Server;
class Room; class Room;
@ -24,11 +24,14 @@ public:
void doRequest(const QString &command, void doRequest(const QString &command,
const QString &jsonData, int timeout = -1); const QString &jsonData, int timeout = -1);
void doReply(const QString &command, const QString &jsonData);
void doNotify(const QString &command, const QString &jsonData); void doNotify(const QString &command, const QString &jsonData);
void prepareForRequest(const QString &command, void prepareForRequest(const QString &command,
const QVariant &data = QVariant()); const QString &data);
signals:
void disconnected();
private: private:
ClientSocket *socket; // socket for communicating with client ClientSocket *socket; // socket for communicating with client
Router *router; Router *router;
@ -36,7 +39,7 @@ private:
Room *room; // Room that player is in, maybe lobby Room *room; // Room that player is in, maybe lobby
QString requestCommand; QString requestCommand;
QVariant requestData; QString requestData;
}; };
#endif // _SERVERPLAYER_H #endif // _SERVERPLAYER_H

View File

@ -2,7 +2,7 @@
%nodefaultdtor QmlBackend; %nodefaultdtor QmlBackend;
class QmlBackend : public QObject { class QmlBackend : public QObject {
public: public:
void emitNotifyUI(const char *command, const char *json_data); void emitNotifyUI(const QString &command, const QString &json_data);
}; };
extern QmlBackend *Backend; extern QmlBackend *Backend;
@ -11,8 +11,6 @@ extern QmlBackend *Backend;
%nodefaultdtor Client; %nodefaultdtor Client;
class Client : public QObject { class Client : public QObject {
public: public:
void requestServer(const QString &command,
const QString &json_data, int timeout = -1);
void replyToServer(const QString &command, const QString &json_data); void replyToServer(const QString &command, const QString &json_data);
void notifyServer(const QString &command, const QString &json_data); void notifyServer(const QString &command, const QString &json_data);

View File

@ -7,6 +7,7 @@
#include "clientplayer.h" #include "clientplayer.h"
#include "room.h" #include "room.h"
#include "qmlbackend.h" #include "qmlbackend.h"
#include "util.h"
%} %}
%include "naturalvar.i" %include "naturalvar.i"

View File

@ -9,8 +9,8 @@ public:
Offline Offline
}; };
unsigned int getId() const; int getId() const;
void setId(unsigned int id); void setId(int id);
QString getScreenName() const; QString getScreenName() const;
void setScreenName(const QString &name); void setScreenName(const QString &name);
@ -39,8 +39,6 @@ extern ClientPlayer *Self;
%nodefaultdtor ServerPlayer; %nodefaultdtor ServerPlayer;
class ServerPlayer : public Player { class ServerPlayer : public Player {
public: public:
void setSocket(ClientSocket *socket);
Server *getServer() const; Server *getServer() const;
Room *getRoom() const; Room *getRoom() const;
void setRoom(Room *room); void setRoom(Room *room);
@ -49,9 +47,7 @@ public:
void doRequest(const QString &command, void doRequest(const QString &command,
const QString &json_data, int timeout = -1); const QString &json_data, int timeout = -1);
void doReply(const QString &command, const QString &json_data);
void doNotify(const QString &command, const QString &json_data); void doNotify(const QString &command, const QString &json_data);
void prepareForRequest(const QString &command, void prepareForRequest(const QString &command, const QString &data);
const QVariant &data = QVariant());
}; };

View File

@ -1,12 +1,36 @@
class QObject { // Make the base classes look like "complete"
class QObject {};
class QThread {};
template <class T>
class QList {
public: public:
QString objectName(); QList();
void setObjectName(const char *name); ~QList();
bool inherits(const char *class_name); int length() const;
bool setProperty(const char *name, const QVariant &value); void append(const T &elem);
QVariant property(const char *name) const; void prepend(const T &elem);
void setParent(QObject *parent); bool isEmpty() const;
void deleteLater(); bool contains(const T &value) const;
T first() const;
T last() const;
void removeAt(int i);
int removeAll(const T &value);
bool removeOne(const T &value);
QList<T> mid(int pos, int length = -1) const;
int indexOf(const T &value, int from = 0);
void replace(int i, const T &value);
void swapItemsAt(int i, int j);
}; };
class QThread {}; %extend QList {
T at(int i) const
{
return $self->value(i);
}
}
%template(SPlayerList) QList<ServerPlayer *>;
%template(PlayerList) QList<const Player *>;
%template(IntList) QList<int>;
%template(BoolList) QList<bool>;

View File

@ -2,15 +2,12 @@
%nodefaultdtor Server; %nodefaultdtor Server;
class Server : public QObject { class Server : public QObject {
public: public:
void createRoom(ServerPlayer *owner, const QString &name, unsigned int capacity); void createRoom(ServerPlayer *owner, const QString &name, int capacity);
Room *findRoom(unsigned int id) const; Room *findRoom(int id) const;
Room *lobby() const; ServerPlayer *findPlayer(int id) const;
ServerPlayer *findPlayer(unsigned int id) const;
void updateRoomList();
LuaFunction callback; LuaFunction callback;
LuaFunction startRoom;
}; };
%{ %{
@ -29,6 +26,21 @@ void Server::callLua(const QString& command, const QString& json_data)
qDebug() << error_msg; qDebug() << error_msg;
} }
} }
void Server::roomStart(Room *room) {
Q_ASSERT(startRoom);
lua_rawgeti(L, LUA_REGISTRYINDEX, startRoom);
SWIG_NewPointerObj(L, this, SWIGTYPE_p_Server, 0);
SWIG_NewPointerObj(L, room, SWIGTYPE_p_Room, 0);
int error = lua_pcall(L, 2, 0, 0);
if (error) {
const char *error_msg = lua_tostring(L, -1);
qDebug() << error_msg;
}
}
%} %}
extern Server *ServerInstance; extern Server *ServerInstance;
@ -40,12 +52,12 @@ public:
// Property reader & setter // Property reader & setter
// ==================================={ // ==================================={
Server *getServer() const; Server *getServer() const;
unsigned int getId() const; int getId() const;
bool isLobby() const; bool isLobby() const;
QString getName() const; QString getName() const;
void setName(const QString &name); void setName(const QString &name);
unsigned int getCapacity() const; int getCapacity() const;
void setCapacity(unsigned int capacity); void setCapacity(int capacity);
bool isFull() const; bool isFull() const;
bool isAbandoned() const; bool isAbandoned() const;
@ -55,14 +67,19 @@ public:
void addPlayer(ServerPlayer *player); void addPlayer(ServerPlayer *player);
void removePlayer(ServerPlayer *player); void removePlayer(ServerPlayer *player);
QList<ServerPlayer *> getPlayers() const; QList<ServerPlayer *> getPlayers() const;
ServerPlayer *findPlayer(unsigned int id) const; ServerPlayer *findPlayer(int id) const;
void setGameLogic(GameLogic *logic); bool isStarted() const;
GameLogic *getGameLogic() const;
// ====================================} // ====================================}
void startGame();
void doRequest(const QList<ServerPlayer *> targets, int timeout); void doRequest(const QList<ServerPlayer *> targets, int timeout);
void doNotify(const QList<ServerPlayer *> targets, int timeout); void doNotify(const QList<ServerPlayer *> targets, int timeout);
void doBroadcastNotify(
const QList<ServerPlayer *> targets,
const QString &command,
const QString &jsonData
);
void gameOver();
}; };

View File

@ -8,13 +8,23 @@ QmlBackend::QmlBackend(QObject* parent)
: QObject(parent) : QObject(parent)
{ {
Backend = this; Backend = this;
engine = nullptr;
} }
QQmlApplicationEngine *QmlBackend::getEngine() const
{
return engine;
}
void QmlBackend::setEngine(QQmlApplicationEngine *engine)
{
this->engine = engine;
}
void QmlBackend::startServer(ushort port) void QmlBackend::startServer(ushort port)
{ {
if (!ServerInstance) { if (!ServerInstance) {
class Server *server = new class Server(this); Server *server = new Server(this);
if (!server->listen(QHostAddress::Any, port)) { if (!server->listen(QHostAddress::Any, port)) {
server->deleteLater(); server->deleteLater();
@ -26,10 +36,11 @@ void QmlBackend::startServer(ushort port)
void QmlBackend::joinServer(QString address) void QmlBackend::joinServer(QString address)
{ {
if (ClientInstance != nullptr) return; if (ClientInstance != nullptr) return;
class Client *client = new class Client(this); Client *client = new Client(this);
connect(client, &Client::error_message, [this, client](const QString &msg){ connect(client, &Client::error_message, [this, client](const QString &msg){
client->deleteLater(); client->deleteLater();
emit notifyUI("ErrorMsg", msg); emit notifyUI("ErrorMsg", msg);
emit notifyUI("BackToStart", "[]");
}); });
QString addr = "127.0.0.1"; QString addr = "127.0.0.1";
ushort port = 9527u; ushort port = 9527u;
@ -45,17 +56,11 @@ void QmlBackend::joinServer(QString address)
client->connectToHost(QHostAddress(addr), port); client->connectToHost(QHostAddress(addr), port);
} }
void QmlBackend::replyToServer(const QString& command, const QString& jsonData)
{
ClientInstance->replyToServer(command, jsonData);
}
void QmlBackend::notifyServer(const QString& command, const QString& jsonData)
{
ClientInstance->notifyServer(command, jsonData);
}
void QmlBackend::quitLobby() void QmlBackend::quitLobby()
{ {
delete ClientInstance; delete ClientInstance;
} }
void QmlBackend::emitNotifyUI(const QString &command, const QString &jsonData) {
emit notifyUI(command, jsonData);
}

View File

@ -1,41 +1,28 @@
#ifndef _QMLBACKEND_H #ifndef _QMLBACKEND_H
#define _QMLBACKEND_H #define _QMLBACKEND_H
#include <QObject>
#include <QJsonDocument>
#include "client.h"
class QmlBackend : public QObject { class QmlBackend : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum WindowType {
Server,
Lobby,
Room,
NotStarted
};
QmlBackend(QObject *parent = nullptr); QmlBackend(QObject *parent = nullptr);
// For lua use QQmlApplicationEngine *getEngine() const;
void emitNotifyUI(const char *command, const char *jsonData) { void setEngine(QQmlApplicationEngine *engine);
emit notifyUI(command, jsonData);
} Q_INVOKABLE void startServer(ushort port);
Q_INVOKABLE void joinServer(QString address);
// Lobby
Q_INVOKABLE void quitLobby();
// lua --> qml
void emitNotifyUI(const QString &command, const QString &jsonData);
signals: signals:
void notifyUI(const QString &command, const QString &jsonData); void notifyUI(const QString &command, const QString &jsonData);
public slots:
void startServer(ushort port);
void joinServer(QString address);
void replyToServer(const QString &command, const QString &jsonData);
void notifyServer(const QString &command, const QString &jsonData);
// Lobby
void quitLobby();
private: private:
WindowType type; QQmlApplicationEngine *engine;
}; };
extern QmlBackend *Backend; extern QmlBackend *Backend;