From a67175f8eba0b2595ec4e8e6e134167fde8c60b3 Mon Sep 17 00:00:00 2001 From: Notify-ctrl <70358032+Notify-ctrl@users.noreply.github.com> Date: Sun, 27 Mar 2022 14:49:41 +0800 Subject: [PATCH] Room (#4) * Run room in lua * handle offline player * Better toast and other * delete useless stuff * todo: edit profile * use pch --- CMakeLists.txt | 1 - doc/dev/database.md | 2 +- doc/dev/protocol.md | 4 +- lua/client/client.lua | 18 +++ lua/core/player.lua | 4 +- lua/core/util.lua | 64 ++++++++++ lua/freekill.lua | 2 + lua/lib/sha256.lua | 195 ++++++++++++++++++++++++++++++ lua/server/room.lua | 40 ++++-- lua/server/server.lua | 32 ++++- lua/server/serverplayer.lua | 11 ++ lua/util.lua | 26 ---- qml/Logic.js | 10 +- qml/Pages/CreateRoom.qml | 2 +- qml/Pages/Init.qml | 2 - qml/Pages/Lobby.qml | 9 +- qml/Pages/Room.qml | 24 +++- qml/Pages/RoomElement/Photo.qml | 1 + qml/Pages/RoomLogic.js | 13 +- qml/Toast.qml | 56 +++++++++ qml/ToastManager.qml | 37 ++++++ qml/main.qml | 40 +----- src/CMakeLists.txt | 7 +- src/client/client.cpp | 13 +- src/client/client.h | 17 +-- src/client/clientplayer.cpp | 2 +- src/client/clientplayer.h | 7 +- src/core/player.cpp | 4 +- src/core/player.h | 8 +- src/core/{global.cpp => util.cpp} | 4 +- src/core/{global.h => util.h} | 5 - src/main.cpp | 13 +- src/network/client_socket.cpp | 4 +- src/network/client_socket.h | 7 -- src/network/router.cpp | 5 +- src/network/router.h | 7 +- src/network/server_socket.cpp | 1 - src/network/server_socket.h | 4 - src/pch.h | 18 +++ src/server/gamelogic.cpp | 11 -- src/server/gamelogic.h | 17 --- src/server/room.cpp | 51 ++++---- src/server/room.h | 27 ++--- src/server/server.cpp | 83 +++++++++---- src/server/server.h | 23 ++-- src/server/serverplayer.cpp | 36 ++++-- src/server/serverplayer.h | 13 +- src/swig/client.i | 4 +- src/swig/freekill.i | 1 + src/swig/player.i | 10 +- src/swig/qt.i | 42 +++++-- src/swig/server.i | 45 ++++--- src/ui/qmlbackend.cpp | 29 +++-- src/ui/qmlbackend.h | 37 ++---- 54 files changed, 780 insertions(+), 368 deletions(-) create mode 100644 lua/core/util.lua create mode 100644 lua/lib/sha256.lua delete mode 100644 lua/util.lua create mode 100644 qml/Toast.qml create mode 100644 qml/ToastManager.qml rename src/core/{global.cpp => util.cpp} (97%) rename src/core/{global.h => util.h} (83%) create mode 100644 src/pch.h delete mode 100644 src/server/gamelogic.cpp delete mode 100644 src/server/gamelogic.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34ebe281..aa963182 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,6 @@ set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) set(REQUIRED_QT_VERSION "5.15.2") -include_directories(${PROJECT_SOURCE_DIR}/) include_directories(include/lua) include_directories(include/sqlite3) include_directories(src) diff --git a/doc/dev/database.md b/doc/dev/database.md index 1e56b1b5..c621d61a 100644 --- a/doc/dev/database.md +++ b/doc/dev/database.md @@ -1,4 +1,4 @@ -# FreeKill 的数据库(TODO) +# FreeKill 的数据库 > [dev](./index.md) > 数据库 diff --git a/doc/dev/protocol.md b/doc/dev/protocol.md index 472ab510..0521a5f0 100644 --- a/doc/dev/protocol.md +++ b/doc/dev/protocol.md @@ -38,7 +38,7 @@ $ ./FreeKill -s 1. 检查IP是否被封禁。 // TODO: 数据库 2. 检查客户端的延迟是否小于30秒。 3. 在网络检测环节,若客户端网速达标的话,客户端应该会发回一个字符串。这个字符串保存着用户的用户名和密码,服务端检查这个字符串是否合法。 -4. 上述检查都通过后,重连(TODO) +4. 上述检查都通过后,重连(TODO:) 5. 不要重连的话,服务端便为新连接新建一个`ServerPlayer`对象,并将其添加到大厅中。 ___ @@ -78,6 +78,8 @@ ___ 对于情况4,因为游戏已经开始,所以不能直接删除玩家,需要把玩家的状态设为“离线”并继续游戏。在游戏结束后,若玩家仍未重连,则按情况2、3处理。 +> Note: 这部分处理见于ServerPlayer类的析构函数。 + ___ ## 断线重连(TODO) diff --git a/lua/client/client.lua b/lua/client/client.lua index 3d335877..f19e7a47 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -17,5 +17,23 @@ function Client:initialize() 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) ClientInstance = Client:new() diff --git a/lua/core/player.lua b/lua/core/player.lua index fdf2380c..5ae61626 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -1,4 +1,4 @@ -local Player = class("Skill") +local Player = class("Player") function Player:initialize() self.hp = nil @@ -25,3 +25,5 @@ function Player:setHp(maxHp, initialHp) self.maxHp = maxHp self.hp = initialHp or maxHp end + +return Player diff --git a/lua/core/util.lua b/lua/core/util.lua new file mode 100644 index 00000000..be9ee423 --- /dev/null +++ b/lua/core/util.lua @@ -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 diff --git a/lua/freekill.lua b/lua/freekill.lua index a262b832..f02a215d 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -7,6 +7,7 @@ package.path = package.path .. ";./lua/lib/?.lua" -- load libraries class = require "middleclass" json = require "json" +require "sha256" Util = require "util" DebugMode = true @@ -22,5 +23,6 @@ Sanguosha = require "engine" General = require "general" Card = require "card" Skill = require "skill" +Player = require "player" -- load packages diff --git a/lua/lib/sha256.lua b/lua/lib/sha256.lua new file mode 100644 index 00000000..33867635 --- /dev/null +++ b/lua/lib/sha256.lua @@ -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 + diff --git a/lua/server/room.lua b/lua/server/room.lua index 186e38e8..9790f082 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -1,18 +1,34 @@ -Room = class("Room") +local Room = class("Room") --- Just same as "static int roomId" in cpp --- However id 0 is for lobby, so we start at 1 -local roomId = 1 - -function Room:initialize() - self.id = roomId - roomId = roomId + 1 - self.room = ServerInstace:findRoom(self.id) +function Room:initialize(_room) + self.room = _room + self.players = {} + self.gameFinished = false end -function Room:getCProperties() - self.name = self.room:getName() - self.capacity = self.room:getCapacity() +-- When this function returns, the Room(C++) thread stopped. +function Room:run() + 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 return Room diff --git a/lua/server/server.lua b/lua/server/server.lua index 9b0da680..6f9e2411 100644 --- a/lua/server/server.lua +++ b/lua/server/server.lua @@ -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 = {} @@ -13,12 +16,21 @@ function Server:initialize() end end - self.rooms = {} -- hashtable: uid --> room - self.players = {} -- hashtable: uid --> splayer -end + self.server.startRoom = function(_self, _room) + local room = Room:new(_room) + 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 freekill.server_callback["CreateRoom"] = function(jsonData) @@ -28,7 +40,6 @@ freekill.server_callback["CreateRoom"] = function(jsonData) local roomName = data[2] local capacity = data[3] freekill.ServerInstance:createRoom(owner, roomName, capacity) - ServerInstance:createRoom() end freekill.server_callback["EnterRoom"] = function(jsonData) @@ -57,4 +68,13 @@ freekill.server_callback["DoLuaScript"] = function(jsonData) assert(load(data[2]))() 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() diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 8b137891..125bd32c 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -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 diff --git a/lua/util.lua b/lua/util.lua deleted file mode 100644 index 10342124..00000000 --- a/lua/util.lua +++ /dev/null @@ -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 diff --git a/qml/Logic.js b/qml/Logic.js index b6e03d3b..959b5054 100644 --- a/qml/Logic.js +++ b/qml/Logic.js @@ -1,17 +1,23 @@ var callbacks = {}; callbacks["NetworkDelayTest"] = function(jsonData) { - Backend.notifyServer("Setup", JSON.stringify([ + ClientInstance.notifyServer("Setup", JSON.stringify([ config.screenName, config.password ])); } callbacks["ErrorMsg"] = function(jsonData) { - toast.show(jsonData); + toast.show(jsonData, 5000); mainWindow.busy = false; } +callbacks["BackToStart"] = function(jsonData) { + while (mainStack.depth > 1) { + mainStack.pop(); + } +} + callbacks["EnterLobby"] = function(jsonData) { // depth == 1 means the lobby page is not present in mainStack if (mainStack.depth === 1) { diff --git a/qml/Pages/CreateRoom.qml b/qml/Pages/CreateRoom.qml index e404307b..cd7ec04d 100644 --- a/qml/Pages/CreateRoom.qml +++ b/qml/Pages/CreateRoom.qml @@ -45,7 +45,7 @@ Item { onClicked: { mainWindow.busy = true; mainStack.pop(); - Backend.notifyServer( + ClientInstance.notifyServer( "CreateRoom", JSON.stringify([roomName.text, playerNum.value]) ); diff --git a/qml/Pages/Init.qml b/qml/Pages/Init.qml index 4664a963..32622c56 100644 --- a/qml/Pages/Init.qml +++ b/qml/Pages/Init.qml @@ -33,7 +33,6 @@ Item { config.screenName = screenNameEdit.text; config.password = passwordEdit.text; mainWindow.busy = true; - toast.show("Connecting to host..."); Backend.joinServer(server_addr.text); } } @@ -43,7 +42,6 @@ Item { config.screenName = screenNameEdit.text; config.password = passwordEdit.text; mainWindow.busy = true; - toast.show("Connecting to host..."); Backend.startServer(9527); Backend.joinServer("127.0.0.1"); } diff --git a/qml/Pages/Lobby.qml b/qml/Pages/Lobby.qml index 625327cb..547bfe33 100644 --- a/qml/Pages/Lobby.qml +++ b/qml/Pages/Lobby.qml @@ -42,7 +42,7 @@ Item { onExited: { parent.color = "black" } onClicked: { mainWindow.busy = true; - Backend.notifyServer( + ClientInstance.notifyServer( "EnterRoom", JSON.stringify([roomId]) ); @@ -86,8 +86,11 @@ Item { } ColumnLayout { - Text { - text: "Avatar" + Button { + text: "Edit Profile" + onClicked: { + + } } Button { text: "Create Room" diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index 7590298d..530aef9a 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -11,6 +11,9 @@ Item { property int playerNum: 0 property var dashboardModel + property bool isOwner: false + property bool isStarted: false + // tmp Text { anchors.centerIn: parent @@ -20,9 +23,14 @@ Item { text: "quit" anchors.bottom: parent.bottom onClicked: { - Backend.notifyServer("QuitRoom", "[]"); + ClientInstance.notifyServer("QuitRoom", "[]"); } } + Button { + text: "start game" + visible: isOwner && !isStarted + anchors.centerIn: parent + } // For debugging RowLayout { @@ -36,7 +44,7 @@ Item { Button { text: "DoLuaScript" onClicked: { - Backend.notifyServer("DoLuaScript", JSON.stringify([lua.text])); + ClientInstance.notifyServer("DoLuaScript", JSON.stringify([lua.text])); } } } @@ -74,6 +82,7 @@ Item { faceturned: modelData.faceturned chained: modelData.chained drank: modelData.drank + isOwner: modelData.isOwner } } @@ -113,14 +122,15 @@ Item { self.faceturned: dashboardModel.faceturned self.chained: dashboardModel.chained self.drank: dashboardModel.drank + self.isOwner: dashboardModel.isOwner } Component.onCompleted: { toast.show("Sucesessfully entered room."); dashboardModel = { - general: "liubei", - screenName: config.screenName, + general: Self.avatar, + screenName: Self.screenName, role: "unknown", kingdom: "qun", netstate: "online", @@ -131,7 +141,8 @@ Item { dying: false, faceturned: false, chained: false, - drank: false + drank: false, + isOwner: false } playerNum = config.roomCapacity; @@ -151,7 +162,8 @@ Item { dying: false, faceturned: false, chained: false, - drank: false + drank: false, + isOwner: false }); } photoModel = photoModel; // Force the Repeater reload diff --git a/qml/Pages/RoomElement/Photo.qml b/qml/Pages/RoomElement/Photo.qml index 2a9850aa..171b72d4 100644 --- a/qml/Pages/RoomElement/Photo.qml +++ b/qml/Pages/RoomElement/Photo.qml @@ -22,6 +22,7 @@ Item { property bool faceturned: false property bool chained: false property bool drank: false + property bool isOwner: false Image { id: back diff --git a/qml/Pages/RoomLogic.js b/qml/Pages/RoomLogic.js index 76cff902..3309a9ad 100644 --- a/qml/Pages/RoomLogic.js +++ b/qml/Pages/RoomLogic.js @@ -68,7 +68,8 @@ callbacks["AddPlayer"] = function(jsonData) { dying: false, faceturned: false, chained: false, - drank: false + drank: false, + isOwner: false }; photoModel = photoModel; arrangePhotos(); @@ -95,7 +96,8 @@ callbacks["RemovePlayer"] = function(jsonData) { dying: false, faceturned: false, chained: false, - drank: false + drank: false, + isOwner: false }; photoModel = photoModel; arrangePhotos(); @@ -103,3 +105,10 @@ callbacks["RemovePlayer"] = function(jsonData) { } } } + +/* +callbacks["RoomOwner"] = function(jsonData) { + // jsonData: int uid of the owner + toast.show(J) +} +*/ diff --git a/qml/Toast.qml b/qml/Toast.qml new file mode 100644 index 00000000..2cf878a9 --- /dev/null +++ b/qml/Toast.qml @@ -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); + } + } + } +} diff --git a/qml/ToastManager.qml b/qml/ToastManager.qml new file mode 100644 index 00000000..efb374bd --- /dev/null +++ b/qml/ToastManager.qml @@ -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} +} diff --git a/qml/main.qml b/qml/main.qml index 14b95671..1e5f74c2 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -49,46 +49,8 @@ Window { id: config } - Rectangle { + ToastManager { 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 { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a2fb3d29..ed26a31b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,13 @@ set(freekill_SRCS "main.cpp" "core/player.cpp" - "core/global.cpp" + "core/util.cpp" "network/server_socket.cpp" "network/client_socket.cpp" "network/router.cpp" "server/server.cpp" "server/serverplayer.cpp" "server/room.cpp" - "server/gamelogic.cpp" "client/client.cpp" "client/clientplayer.cpp" "ui/qmlbackend.cpp" @@ -16,7 +15,7 @@ set(freekill_SRCS ) set(freekill_HEADERS - "core/global.h" + "core/util.h" "core/player.h" "network/server_socket.h" "network/client_socket.h" @@ -24,7 +23,6 @@ set(freekill_HEADERS "server/server.h" "server/serverplayer.h" "server/room.h" - "server/gamelogic.h" "client/client.h" "client/clientplayer.h" "ui/qmlbackend.h" @@ -40,6 +38,7 @@ endif () source_group("Include" FILES ${freekill_HEADERS}) 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) file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i") add_custom_command( diff --git a/src/client/client.cpp b/src/client/client.cpp index 07f85d3b..d71152a9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1,6 +1,8 @@ #include "client.h" #include "client_socket.h" #include "clientplayer.h" +#include "qmlbackend.h" +#include "util.h" Client *ClientInstance; ClientPlayer *Self; @@ -9,7 +11,10 @@ Client::Client(QObject* parent) : QObject(parent), callback(0) { 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; 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); } -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) { int type = Router::TYPE_REPLY | Router::SRC_CLIENT | Router::DEST_SERVER; diff --git a/src/client/client.h b/src/client/client.h index 1f25d217..1161c07f 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -1,11 +1,8 @@ #ifndef _CLIENT_H #define _CLIENT_H -#include -#include #include "router.h" #include "clientplayer.h" -#include "global.h" class Client : public QObject { Q_OBJECT @@ -15,16 +12,10 @@ public: void connectToHost(const QHostAddress &server, ushort port); - // TODO: database of the server - // void signup - // void login + Q_INVOKABLE void replyToServer(const QString &command, const QString &jsonData); + Q_INVOKABLE void notifyServer(const QString &command, const QString &jsonData); - void requestServer(const QString &command, - 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); + Q_INVOKABLE void callLua(const QString &command, const QString &jsonData); LuaFunction callback; signals: @@ -32,7 +23,7 @@ signals: private: Router *router; - QMap players; + QMap players; lua_State *L; }; diff --git a/src/client/clientplayer.cpp b/src/client/clientplayer.cpp index d4461390..07334f7f 100644 --- a/src/client/clientplayer.cpp +++ b/src/client/clientplayer.cpp @@ -1,6 +1,6 @@ #include "clientplayer.h" -ClientPlayer::ClientPlayer(uint id, QObject* parent) +ClientPlayer::ClientPlayer(int id, QObject* parent) : Player(parent) { setId(id); diff --git a/src/client/clientplayer.h b/src/client/clientplayer.h index 733a85c8..54df4901 100644 --- a/src/client/clientplayer.h +++ b/src/client/clientplayer.h @@ -5,8 +5,13 @@ class ClientPlayer : public Player { 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: - ClientPlayer(uint id, QObject *parent = nullptr); + ClientPlayer(int id, QObject *parent = nullptr); ~ClientPlayer(); private: diff --git a/src/core/player.cpp b/src/core/player.cpp index 89083e33..beb95f13 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -12,12 +12,12 @@ Player::~Player() { } -uint Player::getId() const +int Player::getId() const { return id; } -void Player::setId(uint id) +void Player::setId(int id) { this->id = id; } diff --git a/src/core/player.h b/src/core/player.h index 8b84d626..6476616d 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -1,8 +1,6 @@ #ifndef _PLAYER_H #define _PLAYER_H -#include - // Common part of ServerPlayer and ClientPlayer // dont initialize it directly class Player : public QObject { @@ -19,8 +17,8 @@ public: explicit Player(QObject *parent = nullptr); ~Player(); - uint getId() const; - void setId(uint id); + int getId() const; + void setId(int id); QString getScreenName() const; void setScreenName(const QString &name); @@ -43,7 +41,7 @@ signals: void readyChanged(); private: - uint id; + int id; QString screenName; // screenName should not be same. QString avatar; State state; diff --git a/src/core/global.cpp b/src/core/util.cpp similarity index 97% rename from src/core/global.cpp rename to src/core/util.cpp index 292f0f4e..c8602b76 100644 --- a/src/core/global.cpp +++ b/src/core/util.cpp @@ -1,6 +1,4 @@ -#include "global.h" -#include -#include +#include "util.h" extern "C" { int luaopen_freekill(lua_State *); diff --git a/src/core/global.h b/src/core/util.h similarity index 83% rename from src/core/global.h rename to src/core/util.h index c99eea40..3f1676fa 100644 --- a/src/core/global.h +++ b/src/core/util.h @@ -1,12 +1,7 @@ #ifndef _GLOBAL_H #define _GLOBAL_H -#include -#include -#include - // utilities -typedef int LuaFunction; lua_State *CreateLuaState(); bool DoLuaScript(lua_State *L, const char *script); diff --git a/src/main.cpp b/src/main.cpp index 1d9fcf6e..faf9f3c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,3 @@ -#include -#include -#include -#include -#include #include "qmlbackend.h" #include "server.h" @@ -29,16 +24,18 @@ int main(int argc, char *argv[]) Server *server = new Server; if (!server->listen(QHostAddress::Any, serverPort)) { fprintf(stderr, "cannot listen on port %d!\n", serverPort); - exit(1); + app.exit(1); } return app.exec(); } QQmlApplicationEngine engine; + QmlBackend backend; + backend.setEngine(&engine); + engine.rootContext()->setContextProperty("Backend", &backend); - QUrl currentDir = QUrl::fromLocalFile(QDir::currentPath()); - engine.rootContext()->setContextProperty("AppPath", currentDir); + engine.rootContext()->setContextProperty("AppPath", QUrl::fromLocalFile(QDir::currentPath())); #ifdef QT_DEBUG bool debugging = true; #else diff --git a/src/network/client_socket.cpp b/src/network/client_socket.cpp index fa608635..64cfb53a 100644 --- a/src/network/client_socket.cpp +++ b/src/network/client_socket.cpp @@ -1,6 +1,4 @@ #include "client_socket.h" -#include -#include ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { @@ -92,6 +90,6 @@ void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) 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)); } diff --git a/src/network/client_socket.h b/src/network/client_socket.h index 819b7aec..3db14196 100644 --- a/src/network/client_socket.h +++ b/src/network/client_socket.h @@ -1,13 +1,6 @@ #ifndef _CLIENT_SOCKET_H #define _CLIENT_SOCKET_H -#include -#include -#include -#include - -class QTcpSocket; - class ClientSocket : public QObject { Q_OBJECT diff --git a/src/network/router.cpp b/src/network/router.cpp index 6faecd8b..c0aa2697 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -1,7 +1,6 @@ -#include -#include #include "router.h" #include "client.h" +#include "client_socket.h" #include "server.h" #include "serverplayer.h" @@ -159,7 +158,7 @@ void Router::handlePacket(const QByteArray& rawPacket) // Add the uid of sender to jsonData QJsonArray arr = QJsonDocument::fromJson(jsonData.toUtf8()).array(); arr.prepend( - (int)qobject_cast(parent())->getId() + qobject_cast(parent())->getId() ); ServerInstance->callLua(command, QJsonDocument(arr).toJson()); } diff --git a/src/network/router.h b/src/network/router.h index 3b2c987a..3b459cc7 100644 --- a/src/network/router.h +++ b/src/network/router.h @@ -1,12 +1,7 @@ #ifndef _ROUTER_H #define _ROUTER_H -#include -#include -#include -#include -#include -#include "client_socket.h" +class ClientSocket; class Router : public QObject { Q_OBJECT diff --git a/src/network/server_socket.cpp b/src/network/server_socket.cpp index dc52d992..cf401a02 100644 --- a/src/network/server_socket.cpp +++ b/src/network/server_socket.cpp @@ -1,6 +1,5 @@ #include "server_socket.h" #include "client_socket.h" -#include ServerSocket::ServerSocket() { diff --git a/src/network/server_socket.h b/src/network/server_socket.h index ec9966a9..03c52312 100644 --- a/src/network/server_socket.h +++ b/src/network/server_socket.h @@ -1,10 +1,6 @@ #ifndef _SERVER_SOCKET_H #define _SERVER_SOCKET_H -#include -#include - -class QTcpServer; class ClientSocket; class ServerSocket : public QObject { diff --git a/src/pch.h b/src/pch.h new file mode 100644 index 00000000..0422516c --- /dev/null +++ b/src/pch.h @@ -0,0 +1,18 @@ +#ifndef _PCH_H +#define _PCH_H + +// core gui qml +#include +#include +#include + +// network +#include +#include + +// other libraries +typedef int LuaFunction; +#include "lua.hpp" +#include "sqlite3.h" + +#endif // _PCH_H diff --git a/src/server/gamelogic.cpp b/src/server/gamelogic.cpp deleted file mode 100644 index c20467ea..00000000 --- a/src/server/gamelogic.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "gamelogic.h" - -GameLogic::GameLogic(Room *room) -{ - -} - -void GameLogic::run() -{ - -} diff --git a/src/server/gamelogic.h b/src/server/gamelogic.h deleted file mode 100644 index fbe323eb..00000000 --- a/src/server/gamelogic.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef _GAMELOGIC_H -#define _GAMELOGIC_H - -#include -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 diff --git a/src/server/room.cpp b/src/server/room.cpp index 6c72948e..05d58931 100644 --- a/src/server/room.cpp +++ b/src/server/room.cpp @@ -1,15 +1,14 @@ #include "room.h" #include "serverplayer.h" #include "server.h" -#include -#include Room::Room(Server* server) { - static uint roomId = 0; + static int roomId = 0; id = roomId; roomId++; this->server = server; + gameStarted = false; if (!isLobby()) { connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer); connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer); @@ -27,7 +26,7 @@ Server *Room::getServer() const return server; } -uint Room::getId() const +int Room::getId() const { return id; } @@ -47,12 +46,12 @@ void Room::setName(const QString &name) this->name = name; } -uint Room::getCapacity() const +int Room::getCapacity() const { return capacity; } -void Room::setCapacity(uint capacity) +void Room::setCapacity(int capacity) { this->capacity = capacity; } @@ -75,6 +74,9 @@ ServerPlayer *Room::getOwner() const void Room::setOwner(ServerPlayer *owner) { this->owner = owner; + QJsonArray jsonData; + jsonData << owner->getId(); + owner->doNotify("RoomOwner", QJsonDocument(jsonData).toJson()); } void Room::addPlayer(ServerPlayer *player) @@ -85,6 +87,7 @@ void Room::addPlayer(ServerPlayer *player) // First, notify other players the new player is entering if (!isLobby()) { + jsonData << player->getId(); jsonData << player->getScreenName(); jsonData << player->getAvatar(); doBroadcastNotify(getPlayers(), "AddPlayer", QJsonDocument(jsonData).toJson()); @@ -97,15 +100,19 @@ void Room::addPlayer(ServerPlayer *player) } else { // Second, let the player enter room and add other players jsonData = QJsonArray(); - jsonData << (int)this->capacity; + jsonData << this->capacity; player->doNotify("EnterRoom", QJsonDocument(jsonData).toJson()); foreach (ServerPlayer *p, getOtherPlayers(player)) { jsonData = QJsonArray(); + jsonData << p->getId(); jsonData << p->getScreenName(); jsonData << p->getAvatar(); player->doNotify("AddPlayer", QJsonDocument(jsonData).toJson()); } + + if (isFull()) + start(); } emit playerAdded(player); } @@ -126,7 +133,6 @@ void Room::removePlayer(ServerPlayer *player) emit abandoned(); } else if (player == owner) { setOwner(players.first()); - owner->doNotify("RoomOwner", "[]"); } } @@ -142,7 +148,7 @@ QList Room::getOtherPlayers(ServerPlayer* expect) const return others; } -ServerPlayer *Room::findPlayer(uint id) const +ServerPlayer *Room::findPlayer(int id) const { foreach (ServerPlayer *p, players) { if (p->getId() == id) @@ -151,19 +157,9 @@ ServerPlayer *Room::findPlayer(uint id) const return nullptr; } -void Room::setGameLogic(GameLogic *logic) +bool Room::isStarted() const { - this->logic = logic; -} - -GameLogic *Room::getGameLogic() const -{ - return logic; -} - -void Room::startGame() -{ - // TODO + return gameStarted; } void Room::doRequest(const QList targets, int timeout) @@ -184,9 +180,20 @@ void Room::doBroadcastNotify(const QList targets, } } +void Room::gameOver() +{ + gameStarted = false; + // clean offline players + foreach (ServerPlayer *p, players) { + if (p->getState() == Player::Offline) { + p->deleteLater(); + } + } +} void Room::run() { - // TODO + gameStarted = true; + getServer()->roomStart(this); } diff --git a/src/server/room.h b/src/server/room.h index 9a0d8ccc..379fb268 100644 --- a/src/server/room.h +++ b/src/server/room.h @@ -1,11 +1,8 @@ #ifndef _ROOM_H #define _ROOM_H -#include -#include class Server; class ServerPlayer; -class GameLogic; class Room : public QThread { Q_OBJECT @@ -16,12 +13,12 @@ public: // Property reader & setter // ==================================={ Server *getServer() const; - uint getId() const; + int getId() const; bool isLobby() const; QString getName() const; void setName(const QString &name); - uint getCapacity() const; - void setCapacity(uint capacity); + int getCapacity() const; + void setCapacity(int capacity); bool isFull() const; bool isAbandoned() const; @@ -32,13 +29,11 @@ public: void removePlayer(ServerPlayer *player); QList getPlayers() const; QList getOtherPlayers(ServerPlayer *expect) const; - ServerPlayer *findPlayer(uint id) const; + ServerPlayer *findPlayer(int id) const; - void setGameLogic(GameLogic *logic); - GameLogic *getGameLogic() const; + bool isStarted() const; // ====================================} - void startGame(); void doRequest(const QList targets, int timeout); void doNotify(const QList targets, int timeout); @@ -48,13 +43,11 @@ public: const QString &jsonData ); + void gameOver(); + signals: void abandoned(); - void aboutToStart(); - void started(); - void finished(); - void playerAdded(ServerPlayer *player); void playerRemoved(ServerPlayer *player); @@ -63,14 +56,14 @@ protected: private: Server *server; - uint id; // Lobby's id is 0 + int id; // Lobby's id is 0 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 ServerPlayer *owner; // who created this room? QList players; - GameLogic *logic; + bool gameStarted; }; #endif // _ROOM_H diff --git a/src/server/server.cpp b/src/server/server.cpp index d433c5e1..5cf9d48f 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -2,13 +2,9 @@ #include "server_socket.h" #include "client_socket.h" #include "room.h" +#include "router.h" #include "serverplayer.h" -#include "global.h" -#include -#include -#include -#include -#include +#include "util.h" Server *ServerInstance; @@ -22,8 +18,9 @@ Server::Server(QObject* parent) this, &Server::processNewConnection); // create lobby - createRoom(NULL, "Lobby", UINT32_MAX); + createRoom(nullptr, "Lobby", INT32_MAX); connect(lobby(), &Room::playerAdded, this, &Server::updateRoomList); + connect(lobby(), &Room::playerRemoved, this, &Server::updateRoomList); L = CreateLuaState(); DoLuaScript(L, "lua/freekill.lua"); @@ -45,21 +42,22 @@ bool Server::listen(const QHostAddress& address, ushort 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); connect(room, &Room::abandoned, this, &Server::onRoomAbandoned); - room->setName(name); - room->setCapacity(capacity); - room->setOwner(owner); - room->addPlayer(owner); if (room->isLobby()) m_lobby = room; else 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); } @@ -69,21 +67,25 @@ Room *Server::lobby() const return m_lobby; } -ServerPlayer *Server::findPlayer(uint id) const +ServerPlayer *Server::findPlayer(int id) const { return players.value(id); } +void Server::removePlayer(int id) { + players.remove(id); +} + void Server::updateRoomList() { QJsonArray arr; foreach (Room *room, rooms) { QJsonArray obj; - obj << (int)room->getId(); // roomId + obj << room->getId(); // roomId obj << room->getName(); // roomName obj << "Role"; // gameMode obj << room->getPlayers().count(); // playerNum - obj << (int)room->getCapacity(); // capacity + obj << room->getCapacity(); // capacity arr << obj; } 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]+"); QByteArray passwordHash = QCryptographicHash::hash(password.toLatin1(), QCryptographicHash::Sha256).toHex(); bool passed = false; + QString error_msg; QJsonObject result; + if (nameExp.exactMatch(name)) { // Then we check the database, 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 passed = true; } else { - // check if password is the same - passed = (passwordHash == arr[0].toString()); + // check if this username already login + int id = result["id"].toArray()[0].toString().toInt(); + if (!players.value(id)) + // check if password is the same + 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) { + // create new ServerPlayer and setup ServerPlayer *player = new ServerPlayer(lobby()); player->setSocket(client); client->disconnect(this); - connect(client, &ClientSocket::disconnected, this, [player](){ - qDebug() << "Player" << player->getId() << "disconnected"; - }); + connect(player, &ServerPlayer::disconnected, this, &Server::onUserDisconnected); + connect(player, &Player::stateChanged, this, &Server::onUserStateChanged); player->setScreenName(name); 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); + + // 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); } else { - qDebug() << client->peerAddress() << "entered wrong password"; + qDebug() << client->peerAddress() << "lost connection:" << error_msg; QJsonArray body; body << -2; body << (Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT); body << "ErrorMsg"; - body << "username or password error"; + body << error_msg; client->send(QJsonDocument(body).toJson(QJsonDocument::Compact)); client->disconnectFromHost(); return; @@ -218,10 +238,21 @@ void Server::onRoomAbandoned() void Server::onUserDisconnected() { - qobject_cast(sender())->setStateString("offline"); + ServerPlayer *player = qobject_cast(sender()); + qDebug() << "Player" << player->getId() << "disconnected"; + Room *room = player->getRoom(); + if (room->isStarted()) { + player->setState(Player::Offline); + } else { + player->deleteLater(); + } } void Server::onUserStateChanged() { - // TODO + Player *player = qobject_cast(sender()); + QJsonArray arr; + arr << player->getId(); + arr << player->getStateString(); + callLua("PlayerStateChanged", QJsonDocument(arr).toJson()); } diff --git a/src/server/server.h b/src/server/server.h index 8e12ce1b..39756289 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -1,20 +1,11 @@ #ifndef _SERVER_H #define _SERVER_H -#include -#include -#include -#include -#include -#include - class ServerSocket; class ClientSocket; class Room; class ServerPlayer; -typedef int LuaFunction; - class Server : public QObject { Q_OBJECT @@ -24,17 +15,21 @@ public: bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); - void createRoom(ServerPlayer *owner, const QString &name, uint capacity); - Room *findRoom(uint id) const; + void createRoom(ServerPlayer *owner, const QString &name, int capacity); + Room *findRoom(int id) const; Room *lobby() const; - ServerPlayer *findPlayer(uint id) const; + ServerPlayer *findPlayer(int id) const; + void removePlayer(int id); void updateRoomList(); void callLua(const QString &command, const QString &jsonData); LuaFunction callback; + void roomStart(Room *room); + LuaFunction startRoom; + signals: void roomCreated(Room *room); void playerAdded(ServerPlayer *player); @@ -51,8 +46,8 @@ public slots: private: ServerSocket *server; Room *m_lobby; - QMap rooms; - QHash players; + QMap rooms; + QHash players; lua_State *L; sqlite3 *db; diff --git a/src/server/serverplayer.cpp b/src/server/serverplayer.cpp index 4b2f9cd7..b6a85947 100644 --- a/src/server/serverplayer.cpp +++ b/src/server/serverplayer.cpp @@ -1,6 +1,8 @@ #include "serverplayer.h" #include "room.h" #include "server.h" +#include "router.h" +#include "client_socket.h" ServerPlayer::ServerPlayer(Room *room) { @@ -8,22 +10,41 @@ ServerPlayer::ServerPlayer(Room *room) router = new Router(this, socket, Router::TYPE_SERVER); this->room = room; + server = room->getServer(); } 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(); } void ServerPlayer::setSocket(ClientSocket *socket) { - this->socket = 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; + } + router->setSocket(socket); } Server *ServerPlayer::getServer() const { - return room->getServer(); + return server; } Room *ServerPlayer::getRoom() const @@ -47,19 +68,14 @@ void ServerPlayer::doRequest(const QString& command, const QString& jsonData, in 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) { int type = Router::TYPE_NOTIFICATION | Router::SRC_SERVER | Router::DEST_CLIENT; 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; } diff --git a/src/server/serverplayer.h b/src/server/serverplayer.h index 217e1c51..b0bbdebc 100644 --- a/src/server/serverplayer.h +++ b/src/server/serverplayer.h @@ -2,9 +2,9 @@ #define _SERVERPLAYER_H #include "player.h" -#include "router.h" -#include + class ClientSocket; +class Router; class Server; class Room; @@ -24,11 +24,14 @@ public: void doRequest(const QString &command, const QString &jsonData, int timeout = -1); - void doReply(const QString &command, const QString &jsonData); void doNotify(const QString &command, const QString &jsonData); void prepareForRequest(const QString &command, - const QVariant &data = QVariant()); + const QString &data); + +signals: + void disconnected(); + private: ClientSocket *socket; // socket for communicating with client Router *router; @@ -36,7 +39,7 @@ private: Room *room; // Room that player is in, maybe lobby QString requestCommand; - QVariant requestData; + QString requestData; }; #endif // _SERVERPLAYER_H diff --git a/src/swig/client.i b/src/swig/client.i index e776dced..783e1f62 100644 --- a/src/swig/client.i +++ b/src/swig/client.i @@ -2,7 +2,7 @@ %nodefaultdtor QmlBackend; class QmlBackend : public QObject { public: - void emitNotifyUI(const char *command, const char *json_data); + void emitNotifyUI(const QString &command, const QString &json_data); }; extern QmlBackend *Backend; @@ -11,8 +11,6 @@ extern QmlBackend *Backend; %nodefaultdtor Client; class Client : public QObject { public: - void requestServer(const QString &command, - const QString &json_data, int timeout = -1); void replyToServer(const QString &command, const QString &json_data); void notifyServer(const QString &command, const QString &json_data); diff --git a/src/swig/freekill.i b/src/swig/freekill.i index 0136f693..95d87efe 100644 --- a/src/swig/freekill.i +++ b/src/swig/freekill.i @@ -7,6 +7,7 @@ #include "clientplayer.h" #include "room.h" #include "qmlbackend.h" +#include "util.h" %} %include "naturalvar.i" diff --git a/src/swig/player.i b/src/swig/player.i index 87e7af37..6ef1c59f 100644 --- a/src/swig/player.i +++ b/src/swig/player.i @@ -9,8 +9,8 @@ public: Offline }; - unsigned int getId() const; - void setId(unsigned int id); + int getId() const; + void setId(int id); QString getScreenName() const; void setScreenName(const QString &name); @@ -39,8 +39,6 @@ extern ClientPlayer *Self; %nodefaultdtor ServerPlayer; class ServerPlayer : public Player { public: - void setSocket(ClientSocket *socket); - Server *getServer() const; Room *getRoom() const; void setRoom(Room *room); @@ -49,9 +47,7 @@ public: void doRequest(const QString &command, 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 prepareForRequest(const QString &command, - const QVariant &data = QVariant()); + void prepareForRequest(const QString &command, const QString &data); }; diff --git a/src/swig/qt.i b/src/swig/qt.i index 03efe7ca..1d32f4ef 100644 --- a/src/swig/qt.i +++ b/src/swig/qt.i @@ -1,12 +1,36 @@ -class QObject { +// Make the base classes look like "complete" +class QObject {}; +class QThread {}; + +template +class QList { public: - QString objectName(); - void setObjectName(const char *name); - bool inherits(const char *class_name); - bool setProperty(const char *name, const QVariant &value); - QVariant property(const char *name) const; - void setParent(QObject *parent); - void deleteLater(); + QList(); + ~QList(); + int length() const; + void append(const T &elem); + void prepend(const T &elem); + bool isEmpty() const; + 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 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; +%template(PlayerList) QList; +%template(IntList) QList; +%template(BoolList) QList; diff --git a/src/swig/server.i b/src/swig/server.i index 85645b8f..576ed725 100644 --- a/src/swig/server.i +++ b/src/swig/server.i @@ -2,15 +2,12 @@ %nodefaultdtor Server; class Server : public QObject { public: - void createRoom(ServerPlayer *owner, const QString &name, unsigned int capacity); - Room *findRoom(unsigned int id) const; - Room *lobby() const; - - ServerPlayer *findPlayer(unsigned int id) const; - - void updateRoomList(); + void createRoom(ServerPlayer *owner, const QString &name, int capacity); + Room *findRoom(int id) const; + ServerPlayer *findPlayer(int id) const; LuaFunction callback; + LuaFunction startRoom; }; %{ @@ -29,6 +26,21 @@ void Server::callLua(const QString& command, const QString& json_data) 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; @@ -40,12 +52,12 @@ public: // Property reader & setter // ==================================={ Server *getServer() const; - unsigned int getId() const; + int getId() const; bool isLobby() const; QString getName() const; void setName(const QString &name); - unsigned int getCapacity() const; - void setCapacity(unsigned int capacity); + int getCapacity() const; + void setCapacity(int capacity); bool isFull() const; bool isAbandoned() const; @@ -55,14 +67,19 @@ public: void addPlayer(ServerPlayer *player); void removePlayer(ServerPlayer *player); QList getPlayers() const; - ServerPlayer *findPlayer(unsigned int id) const; + ServerPlayer *findPlayer(int id) const; - void setGameLogic(GameLogic *logic); - GameLogic *getGameLogic() const; + bool isStarted() const; // ====================================} - void startGame(); void doRequest(const QList targets, int timeout); void doNotify(const QList targets, int timeout); + void doBroadcastNotify( + const QList targets, + const QString &command, + const QString &jsonData + ); + + void gameOver(); }; diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 71671a35..0327548f 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -8,13 +8,23 @@ QmlBackend::QmlBackend(QObject* parent) : QObject(parent) { Backend = this; + engine = nullptr; } +QQmlApplicationEngine *QmlBackend::getEngine() const +{ + return engine; +} + +void QmlBackend::setEngine(QQmlApplicationEngine *engine) +{ + this->engine = engine; +} void QmlBackend::startServer(ushort port) { if (!ServerInstance) { - class Server *server = new class Server(this); + Server *server = new Server(this); if (!server->listen(QHostAddress::Any, port)) { server->deleteLater(); @@ -26,10 +36,11 @@ void QmlBackend::startServer(ushort port) void QmlBackend::joinServer(QString address) { 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){ client->deleteLater(); emit notifyUI("ErrorMsg", msg); + emit notifyUI("BackToStart", "[]"); }); QString addr = "127.0.0.1"; ushort port = 9527u; @@ -45,17 +56,11 @@ void QmlBackend::joinServer(QString address) 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() { delete ClientInstance; } + +void QmlBackend::emitNotifyUI(const QString &command, const QString &jsonData) { + emit notifyUI(command, jsonData); +} diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index b00acbe1..2ba72c3f 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -1,41 +1,28 @@ #ifndef _QMLBACKEND_H #define _QMLBACKEND_H -#include -#include -#include "client.h" - class QmlBackend : public QObject { Q_OBJECT public: - enum WindowType { - Server, - Lobby, - Room, - NotStarted - }; - QmlBackend(QObject *parent = nullptr); - // For lua use - void emitNotifyUI(const char *command, const char *jsonData) { - emit notifyUI(command, jsonData); - } + QQmlApplicationEngine *getEngine() const; + void setEngine(QQmlApplicationEngine *engine); + + 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: 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: - WindowType type; + QQmlApplicationEngine *engine; }; extern QmlBackend *Backend;