From 9d9217da2cc1159d5ccccb46b66d7aab7ff55ed4 Mon Sep 17 00:00:00 2001 From: notify Date: Fri, 19 Apr 2024 20:53:19 +0800 Subject: [PATCH] Cppdev (#350) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 对旁观和重连进行优化,减轻服务器CPU负担 - 加强Lua与C++交互能力,现在可以直接传const QVariant &参数 - 借助上一条,删除了客户端侧代码绝大多数冗余的json.encode/JSON.parse - 微调swig代码减少生成量,将int映射到lua integer而不是number --- CMakeLists.txt | 61 +----- Fk/Logic.js | 16 +- Fk/Pages/RoomLogic.js | 134 +++++-------- Fk/main.qml | 14 +- lua/client/client.lua | 387 +++++++++++++++++++++++++----------- lua/client/client_util.lua | 130 ++++++------ lua/core/abstract_room.lua | 2 +- lua/core/exppattern.lua | 14 +- lua/core/util.lua | 7 + lua/freekill.lua | 25 ++- lua/lsp/freekill.lua | 6 +- lua/lsp/player.lua | 26 +-- lua/lsp/server.lua | 13 ++ lua/lsp/sqlite.lua | 21 ++ lua/server/request.lua | 56 +----- lua/server/room.lua | 39 ++++ lua/server/serverplayer.lua | 208 +++++-------------- src/CMakeLists.txt | 15 -- src/applink.c | 0 src/core/packman.cpp | 8 +- src/core/packman.h | 4 + src/main.cpp | 53 +---- src/network/router.cpp | 24 +-- src/network/router.h | 4 - src/network/server_socket.h | 2 + src/pch.h | 6 +- src/swig/client.i | 3 +- src/swig/freekill-wasm.i | 19 -- src/swig/naturalvar.i | 46 +++++ src/swig/qt.i | 52 +++-- src/ui/qmlbackend.cpp | 113 ++++++++--- src/ui/qmlbackend.h | 16 +- 32 files changed, 753 insertions(+), 771 deletions(-) mode change 100755 => 100644 src/applink.c delete mode 100644 src/swig/freekill-wasm.i diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a8ce2c8..bcc46b14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ find_package(Lua) find_package(SQLite3) set(CMAKE_AUTOMOC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) set(REQUIRED_QT_VERSION "6.4") @@ -45,15 +45,8 @@ include_directories(src/network) include_directories(src/server) include_directories(src/ui) -if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") -# Fix include problem -include_directories("/usr/include/openssl-1.1/") -endif() - file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i") -if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") - set(SWIG_SOURCE ${PROJECT_SOURCE_DIR}/src/swig/freekill-wasm.i) -elseif (DEFINED FK_SERVER_ONLY) +if (DEFINED FK_SERVER_ONLY) set(SWIG_SOURCE ${PROJECT_SOURCE_DIR}/src/swig/freekill-nogui.i) else () set(SWIG_SOURCE ${PROJECT_SOURCE_DIR}/src/swig/freekill.i) @@ -87,54 +80,4 @@ add_custom_command( COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver ) -if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") - - set(CMAKE_MODULE_LINKER_FLAGS ${CMAKE_MODULE_LINKER_FLAGS} - "-s INITIAL_MEMORY=64MB" - ) - - file(GLOB_RECURSE FK_SCRIPT_FILES - RELATIVE ${PROJECT_SOURCE_DIR} - *.lua *.qml *.js *.fkp *.sql zh_CN.qm - ) - qt_add_resources(FreeKill "scripts_qrc" - PREFIX "/" - FILES ${FK_SCRIPT_FILES} - ) - - qt_add_resources(FreeKill "font_qrc" - PREFIX "/" - FILES "fonts/FZLBGBK.ttf" - ) - - file(GLOB_RECURSE FK_IMG_FILES - RELATIVE ${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/image/*.jpg - ${PROJECT_SOURCE_DIR}/image/*.png - ) - qt_add_resources(FreeKill "img_qrc" - PREFIX "/" - FILES ${FK_IMG_FILES} - ) - file(GLOB_RECURSE FK_AUDIO_FILES - RELATIVE ${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/audio/*.mp3 - ) - qt_add_resources(FreeKill "audio_qrc" - PREFIX "/" - FILES ${FK_AUDIO_FILES} - ) - file(GLOB_RECURSE FK_PKG_FILES - RELATIVE ${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/packages/*.mp3 - ${PROJECT_SOURCE_DIR}/packages/*.jpg - ${PROJECT_SOURCE_DIR}/packages/*.png - ) - qt_add_resources(FreeKill "pkg_qrc" - PREFIX "/" - FILES ${FK_PKG_FILES} - ) - -endif() - add_subdirectory(src) diff --git a/Fk/Logic.js b/Fk/Logic.js index 4fef708c..80aad400 100644 --- a/Fk/Logic.js +++ b/Fk/Logic.js @@ -98,8 +98,7 @@ callbacks["BackToStart"] = (jsonData) => { } } -callbacks["SetServerSettings"] = (j) => { - const data = JSON.parse(j); +callbacks["SetServerSettings"] = (data) => { const [ motd, hiddenPacks, enableBots ] = data; config.serverMotd = motd; config.serverHiddenPacks = hiddenPacks; @@ -127,9 +126,8 @@ callbacks["EnterLobby"] = (jsonData) => { config.saveConf(); } -callbacks["EnterRoom"] = (jsonData) => { +callbacks["EnterRoom"] = (data) => { // jsonData: int capacity, int timeout - const data = JSON.parse(jsonData); config.roomCapacity = data[0]; config.roomTimeout = data[1] - 1; const roomSettings = data[2]; @@ -139,11 +137,11 @@ callbacks["EnterRoom"] = (jsonData) => { mainWindow.busy = false; } -callbacks["UpdateRoomList"] = (jsonData) => { +callbacks["UpdateRoomList"] = (data) => { const current = mainStack.currentItem; // should be lobby if (mainStack.depth === 2) { current.roomModel.clear(); - JSON.parse(jsonData).forEach(room => { + data.forEach(room => { const [roomId, roomName, gameMode, playerNum, capacity, hasPassword, outdated] = room; current.roomModel.append({ @@ -154,10 +152,9 @@ callbacks["UpdateRoomList"] = (jsonData) => { } } -callbacks["UpdatePlayerNum"] = (j) => { +callbacks["UpdatePlayerNum"] = (data) => { const current = mainStack.currentItem; // should be lobby if (mainStack.depth === 2) { - const data = JSON.parse(j); const l = data[0]; const s = data[1]; current.lobbyPlayerNum = l; @@ -165,10 +162,9 @@ callbacks["UpdatePlayerNum"] = (j) => { } } -callbacks["Chat"] = (jsonData) => { +callbacks["Chat"] = (data) => { // jsonData: { string userName, string general, string time, string msg } const current = mainStack.currentItem; // lobby or room - const data = JSON.parse(jsonData); const pid = data.sender; const userName = data.userName; const general = luatr(data.general); diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index 6c7f0b73..e7cf92aa 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -421,10 +421,8 @@ function setCardFootnote(id, footnote) { card.footnoteVisible = true; } -callbacks["SetCardFootnote"] = (j) => { - const data = JSON.parse(j); - const id = data[0]; - const note = data[1]; +callbacks["SetCardFootnote"] = (data) => { + const [id, note] = data; setCardFootnote(id, note); } @@ -444,10 +442,8 @@ function setCardVirtName(id, name) { card.virt_name = name; } -callbacks["SetCardVirtName"] = (j) => { - const data = JSON.parse(j); - const ids = data[0]; - const note = data[1]; +callbacks["SetCardVirtName"] = (data) => { + const [ids, note] = data; ids.forEach(id => setCardVirtName(id, note)); } @@ -508,8 +504,7 @@ function processPrompt(prompt) { return raw; } -callbacks["MaxCard"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["MaxCard"] = (data) => { const id = data.id; const cardMax = data.pcardMax; const photo = getPhoto(id); @@ -530,7 +525,7 @@ function changeSelf(id) { dashboard.self = photos.itemAt(i); } } - callbacks["ArrangeSeats"](JSON.stringify(order)); + callbacks["ArrangeSeats"](order); // update dashboard dashboard.update(); @@ -542,12 +537,11 @@ function changeSelf(id) { } } -callbacks["AddPlayer"] = (jsonData) => { +callbacks["AddPlayer"] = (data) => { // jsonData: int id, string screenName, string avatar, bool ready for (let i = 0; i < photoModel.count; i++) { const item = photoModel.get(i); if (item.id === -1) { - const data = JSON.parse(jsonData); const uid = data[0]; const name = data[1]; const avatar = data[2]; @@ -740,9 +734,9 @@ function updateSelectedTargets(playerid, selected) { } } -callbacks["RemovePlayer"] = (jsonData) => { +callbacks["RemovePlayer"] = (data) => { // jsonData: int uid - const uid = JSON.parse(jsonData)[0]; + const uid = data[0]; const model = getPhotoModel(uid); if (typeof(model) !== "undefined") { model.id = -1; @@ -753,9 +747,9 @@ callbacks["RemovePlayer"] = (jsonData) => { } } -callbacks["RoomOwner"] = (jsonData) => { +callbacks["RoomOwner"] = (data) => { // jsonData: int uid of the owner - const uid = JSON.parse(jsonData)[0]; + const uid = data[0]; roomScene.isOwner = (Self.id === uid); @@ -777,8 +771,7 @@ function checkAllReady() { roomScene.isAllReady = allReady; } -callbacks["ReadyChanged"] = (j) => { - const data = JSON.parse(j); +callbacks["ReadyChanged"] = (data) => { const id = data[0]; const ready = data[1]; @@ -793,8 +786,7 @@ callbacks["ReadyChanged"] = (j) => { } } -callbacks["NetStateChanged"] = (j) => { - const data = JSON.parse(j); +callbacks["NetStateChanged"] = (data) => { const id = data[0]; let state = data[1]; @@ -805,9 +797,8 @@ callbacks["NetStateChanged"] = (j) => { model.netstate = state; } -callbacks["PropertyUpdate"] = (jsonData) => { +callbacks["PropertyUpdate"] = (data) => { // jsonData: int id, string property_name, value - const data = JSON.parse(jsonData); const uid = data[0]; const property_name = data[1]; let value = data[2]; @@ -863,9 +854,8 @@ callbacks["StartGame"] = (jsonData) => { } } -callbacks["ArrangeSeats"] = (jsonData) => { +callbacks["ArrangeSeats"] = (order) => { // jsonData: seat order - const order = JSON.parse(jsonData); for (let i = 0; i < photoModel.count; i++) { const item = photoModel.get(i); @@ -895,10 +885,9 @@ function cancelAllFocus() { } } -callbacks["MoveFocus"] = (jsonData) => { +callbacks["MoveFocus"] = (data) => { // jsonData: int[] focuses, string command cancelAllFocus(); - const data = JSON.parse(jsonData); const focuses = data[0]; const command = data[1]; @@ -925,9 +914,8 @@ callbacks["MoveFocus"] = (jsonData) => { } } -callbacks["PlayerRunned"] = (jsonData) => { +callbacks["PlayerRunned"] = (data) => { // jsonData: int runner, int robot - const data = JSON.parse(jsonData); const runner = data[0]; const robot = data[1]; @@ -937,9 +925,8 @@ callbacks["PlayerRunned"] = (jsonData) => { } } -callbacks["AskForGeneral"] = (jsonData) => { +callbacks["AskForGeneral"] = (data) => { // jsonData: string[] Generals - const data = JSON.parse(jsonData); const generals = data[0]; const n = data[1]; const convert = data[2]; @@ -960,9 +947,8 @@ callbacks["AskForGeneral"] = (jsonData) => { box.updatePosition(); } -callbacks["AskForSkillInvoke"] = (jsonData) => { +callbacks["AskForSkillInvoke"] = (data) => { // jsonData: [ string name, string prompt ] - const data = JSON.parse(jsonData); const skill = data[0]; const prompt = data[1]; roomScene.promptText = prompt ? processPrompt(prompt) @@ -973,8 +959,7 @@ callbacks["AskForSkillInvoke"] = (jsonData) => { roomScene.cancelButton.enabled = true; } -callbacks["AskForGuanxing"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["AskForGuanxing"] = (data) => { const cards = []; const min_top_cards = data.min_top_cards; const max_top_cards = data.max_top_cards; @@ -1011,8 +996,7 @@ callbacks["AskForGuanxing"] = (jsonData) => { }); } -callbacks["AskForExchange"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["AskForExchange"] = (data) => { const cards = []; const cards_name = []; const capacities = []; @@ -1041,10 +1025,9 @@ callbacks["AskForExchange"] = (jsonData) => { }); } -callbacks["AskForChoice"] = (jsonData) => { +callbacks["AskForChoice"] = (data) => { // jsonData: [ string[] choices, string skill ] // TODO: multiple choices, e.g. benxi_ol - const data = JSON.parse(jsonData); const choices = data[0]; const all_choices = data[1]; const skill_name = data[2]; @@ -1073,10 +1056,9 @@ callbacks["AskForChoice"] = (jsonData) => { }); } -callbacks["AskForChoices"] = (jsonData) => { +callbacks["AskForChoices"] = (data) => { // jsonData: [ string[] choices, string skill ] // TODO: multiple choices, e.g. benxi_ol - const data = JSON.parse(jsonData); const choices = data[0]; const all_choices = data[1]; const min_num = data[2][0]; @@ -1115,10 +1097,9 @@ callbacks["AskForChoices"] = (jsonData) => { }); } -callbacks["AskForCardChosen"] = (jsonData) => { +callbacks["AskForCardChosen"] = (data) => { // jsonData: [ int[] handcards, int[] equips, int[] delayedtricks, // string reason ] - const data = JSON.parse(jsonData); const reason = data._reason; const prompt = data._prompt; if (prompt === "") { @@ -1145,10 +1126,9 @@ callbacks["AskForCardChosen"] = (jsonData) => { box.cardSelected.connect(cid => replyToServer(cid)); } -callbacks["AskForCardsChosen"] = (jsonData) => { +callbacks["AskForCardsChosen"] = (data) => { // jsonData: [ int[] handcards, int[] equips, int[] delayedtricks, // int min, int max, string reason ] - const data = JSON.parse(jsonData); const min = data._min; const max = data._max; const reason = data._reason; @@ -1182,8 +1162,8 @@ callbacks["AskForCardsChosen"] = (jsonData) => { }); } -callbacks["AskForPoxi"] = (jsonData) => { - const { type, data, extra_data, cancelable } = JSON.parse(jsonData); +callbacks["AskForPoxi"] = (dat) => { + const { type, data, extra_data, cancelable } = dat; roomScene.state = "replying"; roomScene.popupBox.sourceComponent = @@ -1208,8 +1188,7 @@ callbacks["AskForPoxi"] = (jsonData) => { }); } -callbacks["AskForMoveCardInBoard"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["AskForMoveCardInBoard"] = (data) => { const { cards, cardsPosition, generalNames, playerIds } = data; roomScene.state = "replying"; @@ -1241,15 +1220,13 @@ callbacks["AskForMoveCardInBoard"] = (jsonData) => { }); } -callbacks["MoveCards"] = (jsonData) => { +callbacks["MoveCards"] = (moves) => { // jsonData: merged moves - const moves = JSON.parse(jsonData); moveCards(moves); } -callbacks["PlayCard"] = (jsonData) => { +callbacks["PlayCard"] = (playerId) => { // jsonData: int playerId - const playerId = parseInt(jsonData); if (playerId === Self.id) { roomScene.setPrompt(luatr("#PlayCard"), true); roomScene.state = "playing"; @@ -1257,9 +1234,8 @@ callbacks["PlayCard"] = (jsonData) => { } } -callbacks["LoseSkill"] = (jsonData) => { +callbacks["LoseSkill"] = (data) => { // jsonData: [ int player_id, string skill_name ] - const data = JSON.parse(jsonData); const id = data[0]; const skill_name = data[1]; const prelight = data[2]; @@ -1268,9 +1244,8 @@ callbacks["LoseSkill"] = (jsonData) => { } } -callbacks["AddSkill"] = (jsonData) => { +callbacks["AddSkill"] = (data) => { // jsonData: [ int player_id, string skill_name ] - const data = JSON.parse(jsonData); const id = data[0]; const skill_name = data[1]; const prelight = data[2]; @@ -1279,17 +1254,15 @@ callbacks["AddSkill"] = (jsonData) => { } } -callbacks["PrelightSkill"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["PrelightSkill"] = (data) => { const skill_name = data[0]; const prelight = data[1]; dashboard.prelightSkill(skill_name, prelight); } -callbacks["AskForUseActiveSkill"] = (jsonData) => { +callbacks["AskForUseActiveSkill"] = (data) => { // jsonData: string skill_name, string prompt - const data = JSON.parse(jsonData); const skill_name = data[0]; const prompt = data[1]; const cancelable = data[2]; @@ -1323,9 +1296,8 @@ callbacks["GameLog"] = (jsonData) => { roomScene.addToLog(jsonData) } -callbacks["AskForUseCard"] = (jsonData) => { +callbacks["AskForUseCard"] = (data) => { // jsonData: card, pattern, prompt, cancelable, {} - const data = JSON.parse(jsonData); const cardname = data[0]; const pattern = data[1]; const prompt = data[2]; @@ -1355,9 +1327,8 @@ callbacks["AskForUseCard"] = (jsonData) => { cancelButton.enabled = true; } -callbacks["AskForResponseCard"] = (jsonData) => { +callbacks["AskForResponseCard"] = (data) => { // jsonData: card_name, pattern, prompt, cancelable, {} - const data = JSON.parse(jsonData); const cardname = data[0]; const pattern = data[1]; const prompt = data[2]; @@ -1381,8 +1352,7 @@ callbacks["WaitForNullification"] = () => { roomScene.state = "notactive"; } -callbacks["SetPlayerMark"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["SetPlayerMark"] = (data) => { const player = getPhoto(data[0]); const mark = data[1]; const value = data[2] instanceof Object ? data[2] : data[2].toString(); @@ -1394,8 +1364,7 @@ callbacks["SetPlayerMark"] = (jsonData) => { } } -callbacks["SetBanner"] = (jsonData) => { - const data = JSON.parse(jsonData); +callbacks["SetBanner"] = (data) => { const mark = data[0]; const value = data[1] instanceof Object ? data[1] : data[1].toString(); let area = roomScene.banner; @@ -1406,9 +1375,8 @@ callbacks["SetBanner"] = (jsonData) => { } } -callbacks["Animate"] = (jsonData) => { +callbacks["Animate"] = (data) => { // jsonData: [Object object] - const data = JSON.parse(jsonData); switch (data.type) { case "Indicate": data.to.forEach(item => { @@ -1471,9 +1439,8 @@ callbacks["Animate"] = (jsonData) => { } } -callbacks["LogEvent"] = (jsonData) => { +callbacks["LogEvent"] = (data) => { // jsonData: [Object object] - const data = JSON.parse(jsonData); switch (data.type) { case "Damage": { const item = getPhotoOrDashboard(data.to); @@ -1557,8 +1524,7 @@ callbacks["GameOver"] = (jsonData) => { // roomScene.isStarted = false; } -callbacks["FillAG"] = (j) => { - const data = JSON.parse(j); +callbacks["FillAG"] = (data) => { const ids = data[0]; roomScene.manualBox.sourceComponent = Qt.createComponent("../RoomElement/AG.qml"); @@ -1570,9 +1536,8 @@ callbacks["AskForAG"] = (j) => { roomScene.manualBox.item.interactive = true; } -callbacks["TakeAG"] = (j) => { +callbacks["TakeAG"] = (data) => { if (!roomScene.manualBox.item) return; - const data = JSON.parse(j); const pid = data[0]; const cid = data[1]; const item = getPhoto(pid); @@ -1584,8 +1549,7 @@ callbacks["TakeAG"] = (j) => { callbacks["CloseAG"] = () => roomScene.manualBox.item.close(); -callbacks["CustomDialog"] = (j) => { - const data = JSON.parse(j); +callbacks["CustomDialog"] = (data) => { const path = data.path; const dat = data.data; roomScene.state = "replying"; @@ -1595,8 +1559,7 @@ callbacks["CustomDialog"] = (j) => { } } -callbacks["MiniGame"] = (j) => { - const data = JSON.parse(j); +callbacks["MiniGame"] = (data) => { const game = data.type; const dat = data.data; const gdata = lcall("GetMiniGame", game, Self.id, JSON.stringify(dat)); @@ -1607,15 +1570,13 @@ callbacks["MiniGame"] = (j) => { } } -callbacks["UpdateMiniGame"] = (j) => { - const data = JSON.parse(j); +callbacks["UpdateMiniGame"] = (data) => { if (roomScene.popupBox.item) { roomScene.popupBox.item.updateData(data); } } -callbacks["UpdateLimitSkill"] = (j) => { - const data = JSON.parse(j); +callbacks["UpdateLimitSkill"] = (data) => { const id = data[0]; const skill = data[1]; const time = data[2]; @@ -1636,8 +1597,7 @@ callbacks["UpdateRoundNum"] = (j) => { roomScene.miscStatus.roundNum = data; } -callbacks["UpdateGameData"] = (j) => { - const data = JSON.parse(j); +callbacks["UpdateGameData"] = (data) => { const id = data[0]; const total = data[1]; const win = data[2]; diff --git a/Fk/main.qml b/Fk/main.qml index 54a913a8..7e454912 100644 --- a/Fk/main.qml +++ b/Fk/main.qml @@ -261,21 +261,11 @@ Window { // fake global functions function lcall(funcName, ...params) { - const ret = Backend.callLuaFunction(funcName, [...params]); - try { - return JSON.parse(ret); - } catch (e) { - return ret; - } + return Backend.callLuaFunction(funcName, [...params]); } function leval(lua) { - const ret = Backend.evalLuaExp(`return json.encode(${lua})`); - try { - return JSON.parse(ret); - } catch (e) { - return ret; - } + return Backend.evalLuaExp(`return ${lua}`); } function luatr(src) { diff --git a/lua/client/client.lua b/lua/client/client.lua index 8239e487..b700e794 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -8,6 +8,7 @@ ---@field public current ClientPlayer @ 当前回合玩家 ---@field public discard_pile integer[] @ 弃牌堆 ---@field public observing boolean +---@field public record any Client = AbstractRoom:subclass('Client') -- load client classes @@ -23,11 +24,17 @@ local pattern_refresh_commands = { "AskForResponseCard", } +-- 无需进行JSON.parse,但可能传入JSON字符串的command +local no_decode_commands = { + "ErrorMsg", + "Heartbeat", +} + function Client:initialize() AbstractRoom.initialize(self) self.client = fk.ClientInstance - self.notifyUI = function(self, command, jsonData) - fk.Backend:emitNotifyUI(command, jsonData) + self.notifyUI = function(self, command, data) + fk.Backend:notifyUI(command, data) end self.client.callback = function(_self, command, jsonData, isRequest) if self.recording then @@ -35,6 +42,18 @@ function Client:initialize() end local cb = fk.client_callback[command] + local data + if table.contains(no_decode_commands, command) then + data = jsonData + else + local err, ret = pcall(json.decode, jsonData) + if err == false then + -- 不关心报错 + data = jsonData + else + data = ret + end + end if table.contains(pattern_refresh_commands, command) then Fk.currentResponsePattern = nil @@ -42,9 +61,9 @@ function Client:initialize() end if (type(cb) == "function") then - cb(jsonData) + cb(data) else - self:notifyUI(command, jsonData); + self:notifyUI(command, data) end end @@ -58,7 +77,7 @@ function Client:initialize() end ---@param id integer ----@return ClientPlayer? +---@return ClientPlayer function Client:getPlayerById(id) if id == Self.id then return Self end for _, p in ipairs(self.players) do @@ -95,7 +114,7 @@ function Client:moveCards(moves) for _, move in ipairs(moves) do if move.from and move.fromArea then local from = self:getPlayerById(move.from) - self:notifyUI("MaxCard", json.encode{ + self:notifyUI("MaxCard", { pcardMax = from:getMaxCards(), id = move.from, }) @@ -114,7 +133,7 @@ function Client:moveCards(moves) if move.to and move.toArea then local ids = move.ids - self:notifyUI("MaxCard", json.encode{ + self:notifyUI("MaxCard", { pcardMax = self:getPlayerById(move.to):getMaxCards(), id = move.to, }) @@ -172,6 +191,7 @@ local function parseMsg(msg, nocolor) local from = getPlayerStr(data.from, "#0C8F0C") + ---@type any local to = data.to or Util.DummyTable local to_str = {} for _, id in ipairs(to) do @@ -179,6 +199,7 @@ local function parseMsg(msg, nocolor) end to = table.concat(to_str, ", ") + ---@type any local card = data.card or Util.DummyTable local allUnknown = true local unknownCount = 0 @@ -238,20 +259,16 @@ end function Client:setCardNote(ids, msg) for _, id in ipairs(ids) do if id ~= -1 then - self:notifyUI("SetCardFootnote", json.encode{ id, parseMsg(msg, true) }) + self:notifyUI("SetCardFootnote", { id, parseMsg(msg, true) }) end end end -fk.client_callback["SetCardFootnote"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["SetCardFootnote"] = function(data) ClientInstance:setCardNote(data[1], data[2]); end -fk.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 function setup(id, name, avatar) local self = fk.Self self:setId(id) self:setScreenName(name) @@ -259,16 +276,21 @@ fk.client_callback["Setup"] = function(jsonData) Self = ClientPlayer:new(fk.Self) end -fk.client_callback["EnterRoom"] = function(jsonData) +fk.client_callback["Setup"] = function(data) + -- jsonData: [ int id, string screenName, string avatar ] + local id, name, avatar = data[1], data[2], data[3] + setup(id, name, avatar) +end + +fk.client_callback["EnterRoom"] = function(_data) Self = ClientPlayer:new(fk.Self) ClientInstance = Client:new() -- clear old client data ClientInstance.players = {Self} ClientInstance.alive_players = {Self} ClientInstance.discard_pile = {} - local _data = json.decode(jsonData) local data = _data[3] - ClientInstance.enter_room_data = jsonData; + ClientInstance.enter_room_data = json.encode(_data); ClientInstance.room_settings = data table.insertTableIfNeed( data.disabledPack, @@ -276,25 +298,23 @@ fk.client_callback["EnterRoom"] = function(jsonData) ) ClientInstance.disabled_packs = data.disabledPack ClientInstance.disabled_generals = data.disabledGenerals - ClientInstance:notifyUI("EnterRoom", jsonData) + ClientInstance:notifyUI("EnterRoom", _data) end -fk.client_callback["AddPlayer"] = function(jsonData) +fk.client_callback["AddPlayer"] = function(data) -- 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, time = data[1], data[2], data[3], data[5] local player = fk.ClientInstance:addPlayer(id, name, avatar) player:addTotalGameTime(time or 0) -- 以防再次智迟 local p = ClientPlayer:new(player) table.insert(ClientInstance.players, p) table.insert(ClientInstance.alive_players, p) - ClientInstance:notifyUI("AddPlayer", jsonData) + ClientInstance:notifyUI("AddPlayer", data) end -fk.client_callback["RemovePlayer"] = function(jsonData) +fk.client_callback["RemovePlayer"] = function(data) -- jsonData: [ int id ] - local data = json.decode(jsonData) local id = data[1] for _, p in ipairs(ClientInstance.players) do if p.player:getId() == id then @@ -305,14 +325,13 @@ fk.client_callback["RemovePlayer"] = function(jsonData) end if id ~= Self.id then fk.ClientInstance:removePlayer(id) - ClientInstance:notifyUI("RemovePlayer", jsonData) + ClientInstance:notifyUI("RemovePlayer", data) end end -fk.client_callback["AddObserver"] = function(jsonData) +fk.client_callback["AddObserver"] = function(data) -- jsonData: [ int id, string screenName, string avatar ] -- when observer enter the room, we create lua clientplayer for them - local data = json.decode(jsonData) local id, name, avatar = data[1], data[2], data[3] local player = { getId = function() return id end, @@ -323,8 +342,7 @@ fk.client_callback["AddObserver"] = function(jsonData) table.insert(ClientInstance.observers, p) end -fk.client_callback["RemoveObserver"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["RemoveObserver"] = function(data) local id = data[1] for _, p in ipairs(ClientInstance.observers) do if p.player:getId() == id then @@ -334,8 +352,7 @@ fk.client_callback["RemoveObserver"] = function(jsonData) end end -fk.client_callback["ArrangeSeats"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["ArrangeSeats"] = function(data) local n = #ClientInstance.players local players = {} @@ -352,12 +369,11 @@ fk.client_callback["ArrangeSeats"] = function(jsonData) ClientInstance.players = players - ClientInstance:notifyUI("ArrangeSeats", jsonData) + ClientInstance:notifyUI("ArrangeSeats", data) end -fk.client_callback["PropertyUpdate"] = function(jsonData) +fk.client_callback["PropertyUpdate"] = function(data) -- jsonData: [ int id, string property_name, value ] - local data = json.decode(jsonData) local id, name, value = data[1], data[2], data[3] local p = ClientInstance:getPlayerById(id) p[name] = value @@ -370,16 +386,15 @@ fk.client_callback["PropertyUpdate"] = function(jsonData) end end - ClientInstance:notifyUI("PropertyUpdate", jsonData) - ClientInstance:notifyUI("MaxCard", json.encode{ + ClientInstance:notifyUI("PropertyUpdate", data) + ClientInstance:notifyUI("MaxCard", { pcardMax = ClientInstance:getPlayerById(id):getMaxCards(), id = id, }) end -fk.client_callback["AskForCardChosen"] = function(jsonData) +fk.client_callback["AskForCardChosen"] = function(data) -- jsonData: [ int target_id, string flag, int reason ] - local data = json.decode(jsonData) local id, flag, reason, prompt = data[1], data[2], data[3], data[4] local target = ClientInstance:getPlayerById(id) local hand = target.player_cards[Player.Hand] @@ -411,13 +426,13 @@ fk.client_callback["AskForCardChosen"] = function(jsonData) ui_data._reason = reason ui_data._prompt = prompt end - ClientInstance:notifyUI("AskForCardChosen", json.encode(ui_data)) + ClientInstance:notifyUI("AskForCardChosen", ui_data) end -fk.client_callback["AskForCardsChosen"] = function(jsonData) +fk.client_callback["AskForCardsChosen"] = function(data) -- jsonData: [ int target_id, int min, int max, string flag, int reason ] - local data = json.decode(jsonData) - local id, min, max, flag, reason, prompt = data[1], data[2], data[3], data[4], data[5], data[6] + local id, min, max, flag, reason, prompt = table.unpack(data) + --data[1], data[2], data[3], data[4], data[5], data[6] local target = ClientInstance:getPlayerById(id) local hand = target.player_cards[Player.Hand] local equip = target.player_cards[Player.Equip] @@ -452,7 +467,7 @@ fk.client_callback["AskForCardsChosen"] = function(jsonData) ui_data._reason = reason ui_data._prompt = prompt end - ClientInstance:notifyUI("AskForCardsChosen", json.encode(ui_data)) + ClientInstance:notifyUI("AskForCardsChosen", ui_data) end --- separated moves to many moves(one card per move) @@ -646,23 +661,21 @@ local function sendMoveCardLog(move) end end -fk.client_callback["MoveCards"] = function(jsonData) +fk.client_callback["MoveCards"] = function(raw_moves) -- jsonData: CardsMoveStruct[] - local raw_moves = json.decode(jsonData) local separated = separateMoves(raw_moves) ClientInstance:moveCards(separated) local merged = mergeMoves(separated) - ClientInstance:notifyUI("MoveCards", json.encode(merged)) + ClientInstance:notifyUI("MoveCards", merged) for _, move in ipairs(merged) do sendMoveCardLog(move) end end -fk.client_callback["ShowCard"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["ShowCard"] = function(data) local from = data.from local cards = data.cards - ClientInstance:notifyUI("MoveCards", json.encode{ + ClientInstance:notifyUI("MoveCards", { { ids = cards, fromArea = Card.DrawPile, @@ -679,15 +692,14 @@ local function updateLimitSkill(pid, skill, times) if skill:isSwitchSkill() then local _times = ClientInstance:getPlayerById(pid):getSwitchSkillState(skill.switchSkillName) == fk.SwitchYang and 0 or 1 if times == -1 then _times = -1 end - ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.switchSkillName, _times }) + ClientInstance:notifyUI("UpdateLimitSkill", { pid, skill.switchSkillName, _times }) elseif skill.frequency == Skill.Limited or skill.frequency == Skill.Wake or skill.frequency == Skill.Quest then - ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.name, times }) + ClientInstance:notifyUI("UpdateLimitSkill", { pid, skill.name, times }) end end -fk.client_callback["LoseSkill"] = function(jsonData) +fk.client_callback["LoseSkill"] = function(data) -- jsonData: [ int player_id, string skill_name ] - local data = json.decode(jsonData) local id, skill_name, fake = data[1], data[2], data[3] local target = ClientInstance:getPlayerById(id) local skill = Fk.skills[skill_name] @@ -695,7 +707,7 @@ fk.client_callback["LoseSkill"] = function(jsonData) if not fake then target:loseSkill(skill) if skill.visible then - ClientInstance:notifyUI("LoseSkill", jsonData) + ClientInstance:notifyUI("LoseSkill", data) end elseif skill.visible then -- 按理说能弄得更好的但还是复制粘贴舒服 @@ -711,7 +723,7 @@ fk.client_callback["LoseSkill"] = function(jsonData) if table.find(sks, function(s) return s:isInstanceOf(TriggerSkill) end) then chk = true - ClientInstance:notifyUI("LoseSkill", jsonData) + ClientInstance:notifyUI("LoseSkill", data) end local active = table.filter(sks, function(s) @@ -720,13 +732,13 @@ fk.client_callback["LoseSkill"] = function(jsonData) if #active > 0 then chk = true - ClientInstance:notifyUI("LoseSkill", json.encode { + ClientInstance:notifyUI("LoseSkill", { id, skill_name, }) end if not chk then - ClientInstance:notifyUI("LoseSkill", json.encode { + ClientInstance:notifyUI("LoseSkill", { id, skill_name, }) end @@ -735,9 +747,8 @@ fk.client_callback["LoseSkill"] = function(jsonData) updateLimitSkill(id, skill, -1) end -fk.client_callback["AddSkill"] = function(jsonData) +fk.client_callback["AddSkill"] = function(data) -- jsonData: [ int player_id, string skill_name ] - local data = json.decode(jsonData) local id, skill_name, fake = data[1], data[2], data[3] local target = ClientInstance:getPlayerById(id) local skill = Fk.skills[skill_name] @@ -745,7 +756,7 @@ fk.client_callback["AddSkill"] = function(jsonData) if not fake then target:addSkill(skill) if skill.visible then - ClientInstance:notifyUI("AddSkill", jsonData) + ClientInstance:notifyUI("AddSkill", data) end elseif skill.visible then -- 添加假技能:服务器只会传一个主技能来。 @@ -758,7 +769,7 @@ fk.client_callback["AddSkill"] = function(jsonData) if table.find(sks, function(s) return s:isInstanceOf(TriggerSkill) end) then chk = true - ClientInstance:notifyUI("AddSkill", jsonData) + ClientInstance:notifyUI("AddSkill", data) end local active = table.filter(sks, function(s) @@ -767,14 +778,14 @@ fk.client_callback["AddSkill"] = function(jsonData) if #active > 0 then chk = true - ClientInstance:notifyUI("AddSkill", json.encode { + ClientInstance:notifyUI("AddSkill", { id, skill_name, }) end -- 面板上总得有点啥东西表明自己有技能吧 = = if not chk then - ClientInstance:notifyUI("AddSkill", json.encode { + ClientInstance:notifyUI("AddSkill", { id, skill_name, }) end @@ -787,30 +798,28 @@ fk.client_callback["AddSkill"] = function(jsonData) updateLimitSkill(id, skill, target:usedSkillTimes(skill_name, Player.HistoryGame)) end -fk.client_callback["AskForUseActiveSkill"] = function(jsonData) +fk.client_callback["AskForUseActiveSkill"] = function(data) -- jsonData: [ string skill_name, string prompt, bool cancelable. json extra_data ] - local data = json.decode(jsonData) local skill = Fk.skills[data[1]] local extra_data = data[4] skill._extra_data = extra_data Fk.currentResponseReason = extra_data.skillName - ClientInstance:notifyUI("AskForUseActiveSkill", jsonData) + ClientInstance:notifyUI("AskForUseActiveSkill", data) end -fk.client_callback["AskForUseCard"] = function(jsonData) - Fk.currentResponsePattern = json.decode(jsonData)[2] - ClientInstance:notifyUI("AskForUseCard", jsonData) +fk.client_callback["AskForUseCard"] = function(data) + Fk.currentResponsePattern = data[2] + ClientInstance:notifyUI("AskForUseCard", data) end -fk.client_callback["AskForResponseCard"] = function(jsonData) - Fk.currentResponsePattern = json.decode(jsonData)[2] - ClientInstance:notifyUI("AskForResponseCard", jsonData) +fk.client_callback["AskForResponseCard"] = function(data) + Fk.currentResponsePattern = data[2] + ClientInstance:notifyUI("AskForResponseCard", data) end -fk.client_callback["SetPlayerMark"] = function(jsonData) +fk.client_callback["SetPlayerMark"] = function(data) -- jsonData: [ int id, string mark, int value ] - local data = json.decode(jsonData) local player, mark, value = data[1], data[2], data[3] local p = ClientInstance:getPlayerById(player) p:setMark(mark, value) @@ -825,37 +834,34 @@ fk.client_callback["SetPlayerMark"] = function(jsonData) if text == "#hidden" then return end end end - ClientInstance:notifyUI("SetPlayerMark", jsonData) + ClientInstance:notifyUI("SetPlayerMark", data) end end -fk.client_callback["SetBanner"] = function(jsonData) +fk.client_callback["SetBanner"] = function(data) -- jsonData: [ int id, string mark, int value ] - local data = json.decode(jsonData) local mark, value = data[1], data[2] ClientInstance:setBanner(mark, value) if string.sub(mark, 1, 1) == "@" then - ClientInstance:notifyUI("SetBanner", jsonData) + ClientInstance:notifyUI("SetBanner", data) end end -fk.client_callback["SetCardMark"] = function(jsonData) +fk.client_callback["SetCardMark"] = function(data) -- jsonData: [ int id, string mark, int value ] - local data = json.decode(jsonData) local card, mark, value = data[1], data[2], data[3] Fk:getCardById(card):setMark(mark, value) - ClientInstance:notifyUI("UpdateCard", tostring(card)) + ClientInstance:notifyUI("UpdateCard", card) end -fk.client_callback["Chat"] = function(jsonData) +fk.client_callback["Chat"] = function(data) -- jsonData: { int type, int sender, string msg } - local data = json.decode(jsonData) if data.type == 1 then data.general = "" data.time = os.date("%H:%M:%S") - ClientInstance:notifyUI("Chat", json.encode(data)) + ClientInstance:notifyUI("Chat", data) return end @@ -873,37 +879,32 @@ fk.client_callback["Chat"] = function(jsonData) end data.userName = p.player:getScreenName() data.time = os.date("%H:%M:%S") - ClientInstance:notifyUI("Chat", json.encode(data)) + ClientInstance:notifyUI("Chat", data) end -fk.client_callback["GameLog"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["GameLog"] = function(data) ClientInstance:appendLog(data) end -fk.client_callback["LogEvent"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["LogEvent"] = function(data) if data.type == "Death" then table.removeOne( ClientInstance.alive_players, ClientInstance:getPlayerById(data.to) ) end - ClientInstance:notifyUI("LogEvent", jsonData) + ClientInstance:notifyUI("LogEvent", data) end -fk.client_callback["AddCardUseHistory"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["AddCardUseHistory"] = function(data) Self:addCardUseHistory(data[1], data[2]) end -fk.client_callback["SetCardUseHistory"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["SetCardUseHistory"] = function(data) Self:setCardUseHistory(data[1], data[2], data[3]) end -fk.client_callback["AddSkillUseHistory"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["AddSkillUseHistory"] = function(data) local playerid, skill_name, time = data[1], data[2], data[3] local player = ClientInstance:getPlayerById(playerid) player:addSkillUseHistory(skill_name, time) @@ -913,8 +914,7 @@ fk.client_callback["AddSkillUseHistory"] = function(jsonData) updateLimitSkill(playerid, Fk.skills[skill_name], player:usedSkillTimes(skill_name, Player.HistoryGame)) end -fk.client_callback["SetSkillUseHistory"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["SetSkillUseHistory"] = function(data) local id, skill_name, time, scope = data[1], data[2], data[3], data[4] local player = ClientInstance:getPlayerById(id) player:setSkillUseHistory(skill_name, time, scope) @@ -924,8 +924,7 @@ fk.client_callback["SetSkillUseHistory"] = function(jsonData) updateLimitSkill(id, Fk.skills[skill_name], player:usedSkillTimes(skill_name, Player.HistoryGame)) end -fk.client_callback["AddVirtualEquip"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["AddVirtualEquip"] = function(data) local cname = data.name local player = ClientInstance:getPlayerById(data.player) local subcards = data.subcards @@ -934,8 +933,7 @@ fk.client_callback["AddVirtualEquip"] = function(jsonData) player:addVirtualEquip(c) end -fk.client_callback["RemoveVirtualEquip"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["RemoveVirtualEquip"] = function(data) local player = ClientInstance:getPlayerById(data.player) player:removeVirtualEquip(data.id) end @@ -944,38 +942,35 @@ fk.client_callback["Heartbeat"] = function() ClientInstance.client:notifyServer("Heartbeat", "") end -fk.client_callback["ChangeSelf"] = function(jsonData) - local data = json.decode(jsonData) - ClientInstance:getPlayerById(data.id).player_cards[Player.Hand] = data.handcards - ClientInstance:getPlayerById(data.id).special_cards = data.special_cards +fk.client_callback["ChangeSelf"] = function(data) + local p = ClientInstance:getPlayerById(data.id) + p.player_cards[Player.Hand] = data.handcards + p.special_cards = data.special_cards ClientInstance:notifyUI("ChangeSelf", data.id) end -fk.client_callback["UpdateQuestSkillUI"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["UpdateQuestSkillUI"] = function(data) local player, skillName, usedTimes = data[1], data[2], data[3] updateLimitSkill(player, Fk.skills[skillName], usedTimes) end -fk.client_callback["UpdateGameData"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["UpdateGameData"] = function(data) local player, total, win, run = data[1], data[2], data[3], data[4] player = ClientInstance:getPlayerById(player) if player then player.player:setGameData(total, win, run) end - ClientInstance:notifyUI("UpdateGameData", jsonData) + ClientInstance:notifyUI("UpdateGameData", data) end -fk.client_callback["AddTotalGameTime"] = function(jsonData) - local data = json.decode(jsonData) +fk.client_callback["AddTotalGameTime"] = function(data) local player, time = data[1], data[2] player = ClientInstance:getPlayerById(player) if player then player.player:addTotalGameTime(time) if player == Self then - ClientInstance:notifyUI("AddTotalGameTime", jsonData) + ClientInstance:notifyUI("AddTotalGameTime", data) end end end @@ -1051,30 +1046,184 @@ fk.client_callback["EnterLobby"] = function(jsonData) c:notifyUI("EnterLobby", jsonData) end -fk.client_callback["PrintCard"] = function(j) - local data = json.decode(j) +fk.client_callback["PrintCard"] = function(data) local n, s, num = table.unpack(data) local cd = Fk:cloneCard(n, s, num) Fk:_addPrintedCard(cd) end -fk.client_callback["AddBuddy"] = function(j) +fk.client_callback["AddBuddy"] = function(data) local c = ClientInstance - local data = json.decode(j) local id, hand = table.unpack(data) local to = c:getPlayerById(id) Self:addBuddy(to) to.player_cards[Player.Hand] = hand end -fk.client_callback["RmBuddy"] = function(j) +fk.client_callback["RmBuddy"] = function(data) local c = ClientInstance - local id = tonumber(j) + local id = data local to = c:getPlayerById(id) Self:removeBuddy(to) to.player_cards[Player.Hand] = table.map(to.player_cards, function() return -1 end) end +local function loadPlayerSummary(pdata) + local f = fk.client_callback["PropertyUpdate"] + local id = pdata.d[1] + local properties = { + "general", "deputyGeneral", "maxHp", "hp", "shield", "gender", "kingdom", + "dead", "role", "rest", "seat", "phase", "faceup", "chained", + "sealedSlots", + } + + for _, k in ipairs(properties) do + if pdata.p[k] ~= nil then + f{ id, k, pdata.p[k] } + end + end + + local card_moves = {} + local cards = pdata.c + if #cards[Player.Hand] ~= 0 then + local info = {} + for _, i in ipairs(cards[Player.Hand]) do + table.insert(info, { cardId = i, fromArea = Card.DrawPile }) + end + local move = { moveInfo = info, to = id, toArea = Card.PlayerHand } + table.insert(card_moves, move) + end + if #cards[Player.Equip] ~= 0 then + local info = {} + for _, i in ipairs(cards[Player.Equip]) do + table.insert(info, { cardId = i, fromArea = Card.DrawPile }) + end + local move = { moveInfo = info, to = id, toArea = Card.PlayerEquip } + table.insert(card_moves, move) + end + if #cards[Player.Judge] ~= 0 then + local info = {} + for _, i in ipairs(cards[Player.Judge]) do + table.insert(info, { cardId = i, fromArea = Card.DrawPile }) + end + local move = { moveInfo = info, to = id, toArea = Card.PlayerJudge } + table.insert(card_moves, move) + end + + for k, v in pairs(pdata.sc) do + local info = {} + for _, i in ipairs(v) do + table.insert(info, { cardId = i, fromArea = Card.DrawPile }) + end + local move = { + moveInfo = info, + to = id, + toArea = Card.PlayerSpecial, + specialName = k, + specialVisible = Self.id == id, + } + table.insert(card_moves, move) + end + + if #card_moves > 0 then + -- TODO: visibility + fk.client_callback["MoveCards"](card_moves) + end + + f = fk.client_callback["SetPlayerMark"] + for k, v in pairs(pdata.m) do + f{ id, k, v } + end + + f = fk.client_callback["AddSkill"] + for _, v in pairs(pdata.s) do + f{ id, v } + end + + f = fk.client_callback["AddCardUseHistory"] + for k, v in pairs(pdata.ch) do + if v[1] > 0 then + f{ k, v[1] } + end + end + + f = fk.client_callback["SetSkillUseHistory"] + for k, v in pairs(pdata.sh) do + if v[4] > 0 then + f{ id, k, v[1], 1 } + f{ id, k, v[2], 2 } + f{ id, k, v[3], 3 } + f{ id, k, v[4], 4 } + end + end +end + +local function loadRoomSummary(data) + local players = data.p + + fk.client_callback["StartGame"]("") + + for _, pid in ipairs(data.circle) do + if pid ~= data.you then + fk.client_callback["AddPlayer"](players[tostring(pid)].d) + end + end + + fk.client_callback["ArrangeSeats"](data.circle) + + for _, d in ipairs(data.pc) do + local cd = Fk:cloneCard(table.unpack(d)) + Fk:_addPrintedCard(cd) + end + + for cid, marks in pairs(data.cm) do + for k, v in pairs(marks) do + Fk:getCardById(tonumber(cid)):setMark(k, v) + ClientInstance:notifyUI("UpdateCard", cid) + end + end + + for k, v in pairs(data.b) do + fk.client_callback["SetBanner"]{ k, v } + end + + for _, pid in ipairs(data.circle) do + local pdata = data.p[tostring(pid)] + loadPlayerSummary(pdata) + end + + ClientInstance:notifyUI("UpdateDrawPile", data.dp) + ClientInstance:notifyUI("UpdateRoundNum", data.rnd) +end + +fk.client_callback["Reconnect"] = function(data) + local players = data.p + local setup_data = players[tostring(data.you)].d + setup(setup_data[1], setup_data[2], setup_data[3]) + fk.client_callback["AddTotalGameTime"]{ setup_data[1], setup_data[5] } + + local enter_room_data = data.d + table.insert(enter_room_data, 1, #data.circle) + fk.client_callback["EnterLobby"]("") + fk.client_callback["EnterRoom"](enter_room_data) + + loadRoomSummary(data) +end + +fk.client_callback["Observe"] = function(data) + local players = data.p + + local setup_data = players[tostring(data.you)].d + setup(setup_data[1], setup_data[2], setup_data[3]) + + local enter_room_data = data.d + table.insert(enter_room_data, 1, #data.circle) + fk.client_callback["EnterRoom"](enter_room_data) + fk.client_callback["StartGame"]("") + + loadRoomSummary(data) +end + -- Create ClientInstance (used by Lua) ClientInstance = Client:new() dofile "lua/client/client_util.lua" diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 38f93967..130ed61d 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -9,7 +9,7 @@ end function GetGeneralData(name) local general = Fk.generals[name] if general == nil then general = Fk.generals["diaochan"] end - return json.encode { + return { package = general.package.name, extension = general.package.extensionName, kingdom = general.kingdom, @@ -65,16 +65,16 @@ function GetGeneralDetail(name) table.insertIfNeed(ret.companions, g.name) end end - return json.encode(ret) + return ret end function GetSameGenerals(name) - return json.encode(Fk:getSameGenerals(name)) + return Fk:getSameGenerals(name) end function IsCompanionWith(general, general2) local _general, _general2 = Fk.generals[general], Fk.generals[general2] - return json.encode(_general:isCompanionWith(_general2)) + return _general:isCompanionWith(_general2) end local cardSubtypeStrings = { @@ -89,7 +89,7 @@ local cardSubtypeStrings = { function GetCardData(id, virtualCardForm) local card = Fk:getCardById(id) - if card == nil then return json.encode{ + if card == nil then return { cid = id, known = false } end @@ -124,7 +124,7 @@ function GetCardData(id, virtualCardForm) ret.subtype = cardSubtypeStrings[virtualCard.sub_type] end end - return json.encode(ret) + return ret end function GetCardExtensionByName(cardName) @@ -133,11 +133,11 @@ function GetCardExtensionByName(cardName) end function GetAllMods() - return json.encode(Fk.extensions) + return Fk.extensions end function GetAllModNames() - return json.encode(Fk.extension_names) + return Fk.extension_names end function GetAllGeneralPack() @@ -147,28 +147,28 @@ function GetAllGeneralPack() table.insert(ret, name) end end - return json.encode(ret) + return ret end function GetGenerals(pack_name) - if not Fk.packages[pack_name] then return "[]" end + if not Fk.packages[pack_name] then return {} end local ret = {} for _, g in ipairs(Fk.packages[pack_name].generals) do if not g.total_hidden then table.insert(ret, g.name) end end - return json.encode(ret) + return ret end function SearchAllGenerals(word) local ret = {} for _, name in ipairs(Fk.package_names) do if Fk.packages[name].type == Package.GeneralPack then - table.insertTable(ret, json.decode(SearchGenerals(name, word))) + table.insertTable(ret, SearchGenerals(name, word)) end end - return json.encode(ret) + return ret end function SearchGenerals(pack_name, word) @@ -179,7 +179,7 @@ function SearchGenerals(pack_name, word) table.insert(ret, g.name) end end - return json.encode(ret) + return ret end function UpdatePackageEnable(pkg, enabled) @@ -220,7 +220,7 @@ function GetAllCardPack() table.insert(ret, name) end end - return json.encode(ret) + return ret end function GetCards(pack_name) @@ -228,7 +228,7 @@ function GetCards(pack_name) for _, c in ipairs(Fk.packages[pack_name].cards) do table.insert(ret, c.id) end - return json.encode(ret) + return ret end function GetCardSkill(cid) @@ -236,7 +236,7 @@ function GetCardSkill(cid) end function GetCardSpecialSkills(cid) - return json.encode(Fk:getCardById(cid).special_skills or Util.DummyTable) + return Fk:getCardById(cid).special_skills or Util.DummyTable end function DistanceTo(from, to) @@ -246,21 +246,21 @@ function DistanceTo(from, to) end function GetPile(id, name) - return json.encode(ClientInstance:getPlayerById(id):getPile(name) or {}) + return ClientInstance:getPlayerById(id):getPile(name) or Util.DummyTable end function GetAllPiles(id) - return json.encode(ClientInstance:getPlayerById(id).special_cards or Util.DummyTable) + return ClientInstance:getPlayerById(id).special_cards or Util.DummyTable end function GetPlayerSkills(id) local p = ClientInstance:getPlayerById(id) - return json.encode(table.map(p.player_skills, function(s) + return table.map(p.player_skills, function(s) return s.visible and { name = s.name, description = Fk:getDescription(s.name), } or nil - end)) + end) end ---@param card string | integer @@ -278,11 +278,11 @@ function CanUseCard(card, player, extra_data_str) if skill:isInstanceOf(ViewAsSkill) then c = skill:viewAs(selected_cards) if not c then - return "false" + return false end else -- ActiveSkill should return true here - return "true" + return true end end @@ -294,13 +294,13 @@ function CanUseCard(card, player, extra_data_str) if min_target > 0 then for _, p in ipairs(ClientInstance.players) do if c.skill:targetFilter(p.id, {}, {}, c, extra_data) then - return "true" + return true end end - return "false" + return false end end - return json.encode(ret) + return ret end function CardProhibitedUse(card) @@ -317,11 +317,11 @@ function CardProhibitedUse(card) end end if c == nil then - return "true" + return true else ret = Self:prohibitUse(c) end - return json.encode(ret) + return ret end ---@param card string | integer @@ -331,7 +331,7 @@ end function CanUseCardToTarget(card, to_select, selected, extra_data_str) local extra_data = extra_data_str == "" and nil or json.decode(extra_data_str) if ClientInstance:getPlayerById(to_select).dead then - return "false" + return false end local c ---@type Card local selected_cards @@ -345,7 +345,7 @@ function CanUseCardToTarget(card, to_select, selected, extra_data_str) local ret = c.skill:targetFilter(to_select, selected, selected_cards, c, extra_data) ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), c) - return json.encode(ret) + return ret end ---@param card string | integer @@ -362,7 +362,7 @@ function CanSelectCardForSkill(card, to_select, selected_targets) end local ret = c.skill:cardFilter(to_select, selected_cards, selected_targets) - return json.encode(ret) + return ret end ---@param card string | integer @@ -379,14 +379,14 @@ function CardFeasible(card, selected_targets) end local ret = c.skill:feasible(selected_targets, selected_cards, Self, c) - return json.encode(ret) + return ret end -- Handle skills function GetSkillData(skill_name) local skill = Fk.skills[skill_name] - if not skill then return "null" end + if not skill then return nil end local freq = "notactive" if skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill) then freq = "active" @@ -399,7 +399,7 @@ function GetSkillData(skill_name) elseif skill.frequency == Skill.Quest then frequency = "quest" end - return json.encode{ + return { skill = Fk:translate(skill_name), orig_skill = skill_name, extension = skill.package.extensionName, @@ -439,7 +439,7 @@ function ActiveCanUse(skill_name, extra_data_str) end end end - return json.encode(ret) + return ret end function ActiveSkillPrompt(skill_name, selected, selected_targets) @@ -452,7 +452,7 @@ function ActiveSkillPrompt(skill_name, selected, selected_targets) ret = skill.prompt end end - return json.encode(ret or "") + return ret or "" end function ActiveCardFilter(skill_name, to_select, selected, selected_targets) @@ -465,7 +465,7 @@ function ActiveCardFilter(skill_name, to_select, selected, selected_targets) ret = skill:cardFilter(to_select, selected) end end - return json.encode(ret) + return ret end function ActiveTargetFilter(skill_name, to_select, selected, selected_cards, extra_data) @@ -482,7 +482,7 @@ function ActiveTargetFilter(skill_name, to_select, selected, selected_cards, ext end end end - return json.encode(ret) + return ret end function ActiveFeasible(skill_name, selected, selected_cards) @@ -498,7 +498,7 @@ function ActiveFeasible(skill_name, selected, selected_cards) end end end - return json.encode(ret) + return ret end function CanViewAs(skill_name, card_ids) @@ -511,7 +511,7 @@ function CanViewAs(skill_name, card_ids) ret = true end end - return json.encode(ret) + return ret end -- card_name may be id, name of card, or json string @@ -532,12 +532,12 @@ function CardFitPattern(card_name, pattern) ret = exp:match(c) end else - return "true" + return true end else ret = exp:matchExp(card_name) end - return json.encode(ret) + return ret end function SkillFitPattern(skill_name, pattern) @@ -547,7 +547,7 @@ function SkillFitPattern(skill_name, pattern) local exp = Exppattern:Parse(pattern) ret = exp:matchExp(skill.pattern) end - return json.encode(ret) + return ret end function CardProhibitedResponse(card) @@ -564,11 +564,11 @@ function CardProhibitedResponse(card) end end if c == nil then - return "true" + return true else ret = Self:prohibitResponse(c) end - return json.encode(ret) + return ret end function SkillCanResponse(skill_name, cardResponsing) @@ -577,13 +577,13 @@ function SkillCanResponse(skill_name, cardResponsing) if skill and skill:isInstanceOf(ViewAsSkill) then ret = skill:enabledAtResponse(Self, cardResponsing) end - return json.encode(ret) + return ret end function GetVirtualEquip(player, cid) local c = ClientInstance:getPlayerById(player):getVirualEquip(cid) - if not c then return "null" end - return json.encode{ + if not c then return nil end + return { name = c.name, cid = c.subcards[1], } @@ -598,7 +598,7 @@ function GetExpandPileOfSkill(skillName) end if type(e) == "table" then - return json.encode(e) + return e else return e or "" end @@ -615,15 +615,15 @@ function GetGameModes() }) end table.sort(ret, function(a, b) return a.name > b.name end) - return json.encode(ret) + return ret end function GetInteractionOfSkill(skill_name) local skill = Fk.skills[skill_name] if skill and skill.interaction then - return json.encode(skill:interaction()) + return skill:interaction() end - return "null" + return nil end function SetInteractionDataOfSkill(skill_name, data) @@ -642,13 +642,13 @@ end function GetPlayerHandcards(pid) local c = ClientInstance local p = c:getPlayerById(pid) - return p and json.encode(p.player_cards[Player.Hand]) or "" + return p and p.player_cards[Player.Hand] or "" end function GetPlayerEquips(pid) local c = ClientInstance local p = c:getPlayerById(pid) - return json.encode(p.player_cards[Player.Equip]) + return p.player_cards[Player.Equip] end function ResetClientLua() @@ -673,20 +673,20 @@ function ResetAddPlayer(j) end function GetRoomConfig() - return json.encode(ClientInstance.room_settings) + return ClientInstance.room_settings end function GetPlayerGameData(pid) local c = ClientInstance local p = c:getPlayerById(pid) - if not p then return "[0, 0, 0, 0]" end + if not p then return {0, 0, 0, 0} end local raw = p.player:getGameData() local ret = {} for _, i in fk.qlist(raw) do table.insert(ret, i) end table.insert(ret, p.player:getTotalGameTime()) - return json.encode(ret) + return ret end function SetPlayerGameData(pid, data) @@ -708,7 +708,7 @@ end function CheckSurrenderAvailable(playedTime) local curMode = ClientInstance.room_settings.gameMode - return json.encode(Fk.game_modes[curMode]:surrenderFunc(playedTime)) + return Fk.game_modes[curMode]:surrenderFunc(playedTime) end function SaveRecord() @@ -756,22 +756,22 @@ end function PoxiFilter(poxi_type, to_select, selected, data, extra_data) local poxi = Fk.poxi_methods[poxi_type] - if not poxi then return "false" end - return json.encode(poxi.card_filter(to_select, selected, data, extra_data)) + if not poxi then return false end + return poxi.card_filter(to_select, selected, data, extra_data) end function PoxiFeasible(poxi_type, selected, data, extra_data) local poxi = Fk.poxi_methods[poxi_type] - if not poxi then return "false" end - return json.encode(poxi.feasible(selected, data, extra_data)) + if not poxi then return false end + return poxi.feasible(selected, data, extra_data) end function GetQmlMark(mtype, name, value, p) local spec = Fk.qml_marks[mtype] - if not spec then return "{}" end + if not spec then return {} end p = ClientInstance:getPlayerById(p) value = json.decode(value) - return json.encode { + return { qml_path = type(spec.qml_path) == "function" and spec.qml_path(name, value, p) or spec.qml_path, text = spec.how_to_show(name, value, p) } @@ -781,7 +781,7 @@ function GetMiniGame(gtype, p, data) local spec = Fk.mini_games[gtype] p = ClientInstance:getPlayerById(p) data = json.decode(data) - return json.encode { + return { qml_path = type(spec.qml_path) == "function" and spec.qml_path(p, data) or spec.qml_path, } end diff --git a/lua/core/abstract_room.lua b/lua/core/abstract_room.lua index 0a4d837b..b6685d19 100644 --- a/lua/core/abstract_room.lua +++ b/lua/core/abstract_room.lua @@ -38,7 +38,7 @@ function AbstractRoom:getPlayerById(id) end --- 获取一张牌所处的区域。 ---@param cardId integer | Card @ 要获得区域的那张牌,可以是Card或者一个id ---@return CardArea @ 这张牌的区域 -function AbstractRoom:getCardArea(cardId) end +function AbstractRoom:getCardArea(cardId) return Card.Unknown end function AbstractRoom:setBanner(name, value) if value == 0 then value = nil end diff --git a/lua/core/exppattern.lua b/lua/core/exppattern.lua index 1a959bd1..1e73c3f6 100644 --- a/lua/core/exppattern.lua +++ b/lua/core/exppattern.lua @@ -24,13 +24,13 @@ ]]-- ---@class Matcher ----@field public trueName string[] ----@field public number integer[] ----@field public suit string[] ----@field public place string[] ----@field public name string[] ----@field public cardType string[] ----@field public id integer[] +---@field public trueName? string[] +---@field public number? integer[] +---@field public suit? string[] +---@field public place? string[] +---@field public name? string[] +---@field public cardType? string[] +---@field public id? integer[] -- v0.2.6改动: cardType会被解析为trueName数组和name数组,而不是自己单独成立 diff --git a/lua/core/util.lua b/lua/core/util.lua index 1e2a823c..3d2f2234 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -449,11 +449,14 @@ end -- override default string.len string.rawlen = string.len + +---@param self string ---@diagnostic disable-next-line: duplicate-set-field function string:len() return utf8.len(self) end +---@param self string ---@param delimiter string ---@return string[] function string:split(delimiter) @@ -470,16 +473,20 @@ function string:split(delimiter) return result end +---@param self string function string:startsWith(start) return self:sub(1, #start) == start end +---@param self string function string:endsWith(e) return e == "" or self:sub(-#e) == e end FileIO = { pwd = fk.QmlBackend_pwd, + + ---@return string[] ls = function(filename) if filename == nil then return fk.QmlBackend_ls(".") diff --git a/lua/freekill.lua b/lua/freekill.lua index 24a90185..c0e04992 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -11,7 +11,30 @@ package.path = package.path .. ";./lua/lib/?.lua" class = require "middleclass" -- json: 提供json处理支持,能解析JSON和生成JSON -json = require "json" +-- 仍借助luajson处理简单类型。 +local luajson = require "json" +json = { + encode = function(val, t) + if type(val) ~= "table" then return luajson.encode(val) end + t = t or 1 -- Compact + ---@diagnostic disable-next-line + local doc = fk.QJsonDocument_fromVariant(val) + local ret = doc:toJson(t) + return ret + end, + decode = function(str) + if str == "null" then return nil end + local start = str:sub(1, 1) + if start ~= "[" and start ~= "{" then + return luajson.decode(str) + end + ---@diagnostic disable-next-line + local doc = fk.QJsonDocument_fromJson(str) + local ret = doc:toVariant() + -- if ret == "" then ret = luajson.decode(str) end + return ret + end, +} -- 初始化随机数种子 math.randomseed(os.time()) diff --git a/lua/lsp/freekill.lua b/lua/lsp/freekill.lua index 994ce5dc..daa6db04 100644 --- a/lua/lsp/freekill.lua +++ b/lua/lsp/freekill.lua @@ -24,10 +24,6 @@ SPlayerList = {} ---@return integer microsecond function fk:GetMicroSecond()end ---- construct a QList. ----@return fk.SPlayerList -function fk:SPlayerList()end - function fk.QmlBackend_pwd()end ---@return string[] @@ -44,3 +40,5 @@ function fk.qCritical(msg) end function fk.qInfo(msg) end function fk.qDebug(msg) end function fk.qWarning(msg) end + +fk.FK_VER = '0.0.0' diff --git a/lua/lsp/player.lua b/lua/lsp/player.lua index 3980a045..acd6bdcd 100644 --- a/lua/lsp/player.lua +++ b/lua/lsp/player.lua @@ -5,41 +5,21 @@ ---@class fk.Player FPlayer = {} ----@return integer id function FPlayer:getId()end - ----@return string name function FPlayer:getScreenName()end - ----@return string avatar function FPlayer:getAvatar()end ---@class fk.ServerPlayer : fk.Player FServerPlayer = {} ---- Send a request to client, and allow client to reply within *timeout* seconds. ---- ---- *timeout* must not be negative or **nil**. ----@param command string ----@param jsonData string ----@param timeout integer function FServerPlayer:doRequest(command,jsonData,timeout)end - ---- Wait for at most *timeout* seconds for reply from client. ---- ---- If *timeout* is negative or **nil**, the function will wait forever until get reply. ----@param timeout integer @ seconds to wait ----@return string @ JSON data ----@overload fun() function FServerPlayer:waitForReply(timeout)end - ---- Notice the client. ----@param command string ----@param jsonData string function FServerPlayer:doNotify(command,jsonData)end - function FServerPlayer:setBusy(_) end function FServerPlayer:isBusy(_) end function FServerPlayer:setThinking(_) end function FServerPlayer:getState() end + +---@type any +fk.Self = nil diff --git a/lua/lsp/server.lua b/lua/lsp/server.lua index 2205e73a..92209381 100644 --- a/lua/lsp/server.lua +++ b/lua/lsp/server.lua @@ -1 +1,14 @@ -- SPDX-License-Identifier: GPL-3.0-or-later + +---@diagnostic disable + +---@class fk.Room +local Room = {} + +function Room:getId() return 1 end + +---@return fk.SPlayerList +function Room:getPlayers() end + +---@return fk.SPlayerList +function Room:getObservers() end diff --git a/lua/lsp/sqlite.lua b/lua/lsp/sqlite.lua index 2205e73a..5e05ac54 100644 --- a/lua/lsp/sqlite.lua +++ b/lua/lsp/sqlite.lua @@ -1 +1,22 @@ -- SPDX-License-Identifier: GPL-3.0-or-later +-- 暂且用来当client.lua用了,别在意 + +---@class fk.Client +---@field callback fun(s: fk.Client, c: string, j: string, r: boolean) +local C = {} + +function C:replyToServer(c, j) end +function C:notifyServer(c, j) end +function C:addPlayer(id, name, avatar) end +function C:removePlayer(id) end +function C:changeSelf(id) end +function C:saveRecord(j, fname) end + +fk.ClientInstance = C + +---@class fk.QmlBackend +local B = {} + +function B:emitNotifyUI(c, j) end + +fk.Backend = B diff --git a/lua/server/request.lua b/lua/server/request.lua index 66604a3b..1fead55d 100644 --- a/lua/server/request.lua +++ b/lua/server/request.lua @@ -2,60 +2,8 @@ local function tellRoomToObserver(self, player) local observee = self.players[1] - player:doNotify("Setup", json.encode{ - observee.id, - observee._splayer:getScreenName(), - observee._splayer:getAvatar(), - }) - player:doNotify("EnterRoom", json.encode{ - #self.players, self.timeout, self.settings - }) - player:doNotify("StartGame", "") - - -- send player data - for _, p in ipairs(self:getOtherPlayers(observee, false, true)) do - player:doNotify("AddPlayer", json.encode{ - p.id, - p._splayer:getScreenName(), - p._splayer:getAvatar(), - false, - p._splayer:getTotalGameTime(), - }) - end - - local player_circle = {} - for i = 1, #self.players do - table.insert(player_circle, self.players[i].id) - end - player:doNotify("ArrangeSeats", json.encode(player_circle)) - - -- send printed_cards - for i = -2, -math.huge, -1 do - local c = Fk.printed_cards[i] - if not c then break end - player:doNotify("PrintCard", json.encode{ c.name, c.suit, c.number }) - end - - -- send card marks - for id, marks in pairs(self.card_marks) do - for k, v in pairs(marks) do - player:doNotify("SetCardMark", json.encode{ id, k, v }) - end - end - - -- send banners - for k, v in pairs(self.banners) do - player:doNotify("SetBanner", json.encode{ k, v }) - end - - for _, p in ipairs(self.players) do - self:notifyProperty(player, p, "general") - self:notifyProperty(player, p, "deputyGeneral") - p:marshal(player, true) - end - - player:doNotify("UpdateDrawPile", #self.draw_pile) - player:doNotify("UpdateRoundNum", self:getTag("RoundCount") or 0) + local summary = self:getSummary(observee, true) + player:doNotify("Observe", json.encode(summary)) table.insert(self.observers, {observee.id, player, player:getId()}) end diff --git a/lua/server/room.lua b/lua/server/room.lua index f2f8c802..f0f5825d 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -655,6 +655,45 @@ function Room:changeKingdom(player, kingdom, sendLog) }) end +--- 房间信息摘要,返回房间的大致信息 +--- 用于旁观和重连,但也可用于debug +function Room:getSummary(player, observe) + local printed_cards = {} + for i = -2, -math.huge, -1 do + local c = Fk.printed_cards[i] + if not c then break end + table.insert(printed_cards, { c.name, c.suit, c.number }) + end + + local players = {} + for _, p in ipairs(self.players) do + players[tostring(p.id)] = p:getSummary(player, observe) + end + + local cmarks = {} + for k, v in pairs(self.card_marks) do + cmarks[tostring(k)] = v + end + + return { + you = player.id or player:getId(), + -- data for EnterRoom + d = { + -- #self.players, 留给客户端自己思考 + self.timeout, + self.settings, + }, + pc = printed_cards, + cm = cmarks, + b = self.banners, + + circle = table.map(self.players, Util.IdMapper), + p = players, + rnd = self:getTag("RoundCount") or 0, + dp = #self.draw_pile, + } +end + ------------------------------------------------------------------------ -- 网络通信有关 ------------------------------------------------------------------------ diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index be723d0d..0a10edf0 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -192,199 +192,95 @@ function ServerPlayer:waitForReply(timeout) return result end +local function assign(t1, t2, k) + t1[k] = t2[k] +end + +-- 获取摘要信息。供重连/旁观使用 +-- 根据参数,返回一个大表保存自己的信息,客户端自行分析 ---@param player ServerPlayer ---@param observe? boolean -function ServerPlayer:marshal(player, observe) +function ServerPlayer:getSummary(player, observe) local room = self.room if not room.game_started then + local ret = { p = {} } -- If game does not starts, that mean we are entering room that -- all players are choosing their generals. -- Note that when we are in this function, the main thread must be -- calling delay() or waiting for reply. if self.role_shown then - room:notifyProperty(player, self, "role") + -- room:notifyProperty(player, self, "role") + ret.p.general = self.general + ret.p.deputyGeneral = self.deputyGeneral + ret.p.role = self.role end - return + return ret end - room:notifyProperty(player, self, "maxHp") - room:notifyProperty(player, self, "hp") - room:notifyProperty(player, self, "shield") - room:notifyProperty(player, self, "gender") - room:notifyProperty(player, self, "kingdom") + local properties = {} + + assign(properties, self, "general") + assign(properties, self, "deputyGeneral") + assign(properties, self, "maxHp") + assign(properties, self, "hp") + assign(properties, self, "shield") + assign(properties, self, "gender") + assign(properties, self, "kingdom") if self.dead then - room:notifyProperty(player, self, "dead") - room:notifyProperty(player, self, self.rest > 0 and "rest" or "role") + assign(properties, self, "dead") + assign(properties, self, self.rest > 0 and "rest" or "role") else - room:notifyProperty(player, self, "seat") - room:notifyProperty(player, self, "phase") + assign(properties, self, "seat") + assign(properties, self, "phase") end if not self.faceup then - room:notifyProperty(player, self, "faceup") + assign(properties, self, "faceup") end if self.chained then - room:notifyProperty(player, self, "chained") - end - - local card_moves = {} - if #self.player_cards[Player.Hand] ~= 0 then - local info = {} - for _, i in ipairs(self.player_cards[Player.Hand]) do - table.insert(info, { cardId = i, fromArea = Card.DrawPile }) - end - local move = { - moveInfo = info, - to = self.id, - toArea = Card.PlayerHand - } - table.insert(card_moves, move) - end - if #self.player_cards[Player.Equip] ~= 0 then - local info = {} - for _, i in ipairs(self.player_cards[Player.Equip]) do - table.insert(info, { cardId = i, fromArea = Card.DrawPile }) - end - local move = { - moveInfo = info, - to = self.id, - toArea = Card.PlayerEquip - } - table.insert(card_moves, move) - end - if #self.player_cards[Player.Judge] ~= 0 then - local info = {} - for _, i in ipairs(self.player_cards[Player.Judge]) do - table.insert(info, { cardId = i, fromArea = Card.DrawPile }) - end - local move = { - moveInfo = info, - to = self.id, - toArea = Card.PlayerJudge - } - table.insert(card_moves, move) - end - - for k, v in pairs(self.special_cards) do - local info = {} - for _, i in ipairs(v) do - table.insert(info, { cardId = i, fromArea = Card.DrawPile }) - end - local move = { - moveInfo = info, - to = self.id, - toArea = Card.PlayerSpecial, - specialName = k, - specialVisible = self == player, - } - table.insert(card_moves, move) - end - - if #card_moves > 0 then - room:notifyMoveCards({ player }, card_moves, observe and self.seat == 1) - end - - for k, v in pairs(self.mark) do - player:doNotify("SetPlayerMark", json.encode{self.id, k, v}) - end - - for _, s in ipairs(self.player_skills) do - player:doNotify("AddSkill", json.encode{self.id, s.name}) - end - - for k, v in pairs(self.cardUsedHistory) do - if v[1] > 0 then - player:doNotify("AddCardUseHistory", json.encode{k, v[1]}) - end - end - - for k, v in pairs(self.skillUsedHistory) do - if v[4] > 0 then - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[1], 1}) - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[2], 2}) - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[3], 3}) - player:doNotify("SetSkillUseHistory", json.encode{self.id, k, v[4], 4}) - end + assign(properties, self, "chained") end if self.role_shown then - room:notifyProperty(player, self, "role") + assign(properties, self, "role") end if #self.sealedSlots > 0 then - room:notifyProperty(player, self, "sealedSlots") + assign(properties, self, "sealedSlots") end + + local sp = self._splayer + + return { + -- data for Setup/AddPlayer + d = { + self.id, + sp:getScreenName(), + sp:getAvatar(), + false, + sp:getTotalGameTime(), + }, + p = properties, + ch = self.cardUsedHistory, + sh = self.skillUsedHistory, + m = self.mark, + s = table.map(self.player_skills, Util.NameMapper), + c = self.player_cards, + sc = self.special_cards, + } end function ServerPlayer:reconnect() local room = self.room self.serverplayer:setState(fk.Player_Online) - self:doNotify("Setup", json.encode{ - self.id, - self._splayer:getScreenName(), - self._splayer:getAvatar(), - }) - self:doNotify("AddTotalGameTime", json.encode { - self.id, - self._splayer:getTotalGameTime(), - }) - - self:doNotify("EnterLobby", "") - self:doNotify("EnterRoom", json.encode{ - #room.players, room.timeout, room.settings, - }) - self:doNotify("StartGame", "") + local summary = room:getSummary(self, false) + self:doNotify("Reconnect", json.encode(summary)) room:notifyProperty(self, self, "role") - - -- send player data - for _, p in ipairs(room:getOtherPlayers(self, false, true)) do - self:doNotify("AddPlayer", json.encode{ - p.id, - p._splayer:getScreenName(), - p._splayer:getAvatar(), - false, - p._splayer:getTotalGameTime(), - }) - end self:doNotify("RoomOwner", json.encode{ room.room:getOwner():getId() }) - local player_circle = {} - for i = 1, #room.players do - table.insert(player_circle, room.players[i].id) - end - self:doNotify("ArrangeSeats", json.encode(player_circle)) - - -- send printed_cards - for i = -2, -math.huge, -1 do - local c = Fk.printed_cards[i] - if not c then break end - self:doNotify("PrintCard", json.encode{ c.name, c.suit, c.number }) - end - - -- send card marks - for id, marks in pairs(room.card_marks) do - for k, v in pairs(marks) do - self:doNotify("SetCardMark", json.encode{ id, k, v }) - end - end - - -- send banners - for k, v in pairs(room.banners) do - self:doNotify("SetBanner", json.encode{ k, v }) - end - - for _, p in ipairs(room.players) do - room:notifyProperty(self, p, "general") - room:notifyProperty(self, p, "deputyGeneral") - p:marshal(self) - end - - self:doNotify("UpdateDrawPile", #room.draw_pile) - self:doNotify("UpdateRoundNum", room:getTag("RoundCount") or 0) - -- send fake skills for _, s in ipairs(self._manually_fake_skills) do self:doNotify("AddSkill", json.encode{ self.id, s.name, true }) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f63910b2..5f83432e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,21 +58,6 @@ elseif (ANDROID) QT_ANDROID_EXTRA_LIBS "${LUA_LIB};${SQLITE3_LIB};${CRYPTO_LIB};${SSL_LIB};${SSH_LIB};${GIT_LIB}" ) list(REMOVE_ITEM QT_LIB Qt6::QuickControls2) -elseif (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") - # WASM - list(REMOVE_ITEM freekill_SRCS - "network/server_socket.cpp" - #"network/client_socket.cpp" - #"network/router.cpp" - "server/server.cpp" - "server/serverplayer.cpp" - "server/room.cpp" - ) - # set(LUA_LIB ${PROJECT_SOURCE_DIR}/lib/wasm/liblua.a) - # set(CRYPTO_LIB ${PROJECT_SOURCE_DIR}/lib/wasm/libcrypto.a) - # set other libs by yourself - set(IDBFS_LIB idbfs.js) - include(${FK_WASM_TOOLCHAIN}) else () set(LUA_LIB lua5.4) set(SQLITE3_LIB sqlite3) diff --git a/src/applink.c b/src/applink.c old mode 100755 new mode 100644 diff --git a/src/core/packman.cpp b/src/core/packman.cpp index 7f3430f0..520a0378 100644 --- a/src/core/packman.cpp +++ b/src/core/packman.cpp @@ -95,7 +95,7 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) { connect(thread, &QThread::finished, [=]() { thread->deleteLater(); #ifndef FK_SERVER_ONLY - Backend->emitNotifyUI("DownloadComplete", ""); + Backend->notifyUI("DownloadComplete", ""); #endif }); } else { @@ -135,7 +135,7 @@ void PackMan::downloadNewPack(const QString &url, bool useThread) { connect(thread, &QThread::finished, [=]() { thread->deleteLater(); #ifndef FK_SERVER_ONLY - Backend->emitNotifyUI("DownloadComplete", ""); + Backend->notifyUI("DownloadComplete", ""); #endif }); } else { @@ -252,14 +252,14 @@ static int transfer_progress_cb(const git_indexer_progress *stats, auto msg = QString("Resolving deltas %1/%2") .arg(stats->indexed_deltas) .arg(stats->total_deltas); - Backend->emitNotifyUI("UpdateBusyText", msg); + Backend->notifyUI("UpdateBusyText", msg); } else if (stats->total_objects > 0) { auto msg = QString("Received %1/%2 objects (%3) in %4 KiB") .arg(stats->received_objects) .arg(stats->total_objects) .arg(stats->indexed_objects) .arg(stats->received_bytes / 1024); - Backend->emitNotifyUI("UpdateBusyText", msg); + Backend->notifyUI("UpdateBusyText", msg); } #endif } diff --git a/src/core/packman.h b/src/core/packman.h index 69c845f0..b1dd4b90 100644 --- a/src/core/packman.h +++ b/src/core/packman.h @@ -4,8 +4,11 @@ #define _PACKMAN_H #include + +// 管理拓展包所需的类,本质上是libgit2接口的再封装。 class PackMan : public QObject { Q_OBJECT + public: PackMan(QObject *parent = nullptr); ~PackMan(); @@ -20,6 +23,7 @@ public: Q_INVOKABLE void upgradePack(const QString &pack); Q_INVOKABLE void removePack(const QString &pack); Q_INVOKABLE QString listPackages(); + private: sqlite3 *db; diff --git a/src/main.cpp b/src/main.cpp index e9c7a453..6ea696ce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,11 +5,7 @@ using namespace fkShell; #include "packman.h" -#ifndef Q_OS_WASM #include "server.h" -#else -#include -#endif #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #include "shell.h" @@ -29,7 +25,7 @@ using namespace fkShell; #include "qmlbackend.h" #endif -#if defined(Q_OS_ANDROID) || defined(Q_OS_WASM) +#if defined(Q_OS_ANDROID) static bool copyPath(const QString &srcFilePath, const QString &tgtFilePath) { QFileInfo srcFileInfo(srcFilePath); if (srcFileInfo.isDir()) { @@ -196,7 +192,6 @@ int main(int argc, char *argv[]) { } } -#ifndef FK_CLIENT_ONLY // 分析命令行,如果有 -s 或者 --server 就在命令行直接开服务器 QCommandLineParser parser; parser.setApplicationDescription("FreeKill server"); @@ -244,7 +239,6 @@ int main(int argc, char *argv[]) { } return app->exec(); } -#endif #ifdef FK_SERVER_ONLY // 根本没编译 GUI 相关的功能,直接在此退出 @@ -252,17 +246,6 @@ int main(int argc, char *argv[]) { Please use ./FreeKill -s to start a server in command line."); #else -#ifdef Q_OS_WASM - EM_ASM ( - FS.mkdir('/assets'); - FS.mount(IDBFS, {}, '/assets'); - FS.chdir('/assets'); - FS.syncfs(true, function(err) { - }); - ); - copyPath(":/", QDir::currentPath()); -#endif - app = new QApplication(argc, argv); #ifdef DESKTOP_BUILD ((QApplication *)app)->setWindowIcon(QIcon("image/icon.png")); @@ -328,11 +311,12 @@ int main(int argc, char *argv[]) { Pacman = new PackMan; // 向 Qml 中先定义几个全局变量 - engine->rootContext()->setContextProperty("FkVersion", FK_VERSION); - engine->rootContext()->setContextProperty("Backend", &backend); - engine->rootContext()->setContextProperty("ModBackend", nullptr); - engine->rootContext()->setContextProperty("Pacman", Pacman); - engine->rootContext()->setContextProperty("SysLocale", localeName); + auto root = engine->rootContext(); + root->setContextProperty("FkVersion", FK_VERSION); + root->setContextProperty("Backend", &backend); + root->setContextProperty("ModBackend", nullptr); + root->setContextProperty("Pacman", Pacman); + root->setContextProperty("SysLocale", localeName); #ifdef QT_DEBUG bool debugging = true; @@ -344,9 +328,6 @@ int main(int argc, char *argv[]) { QString system; #if defined(Q_OS_ANDROID) system = "Android"; -#elif defined(Q_OS_WASM) - system = "Web"; - engine->rootContext()->setContextProperty("ServerAddr", "127.0.0.1:9527"); #elif defined(Q_OS_WIN32) system = "Win"; ::system("chcp 65001"); @@ -355,9 +336,9 @@ int main(int argc, char *argv[]) { #else system = "Other"; #endif - engine->rootContext()->setContextProperty("OS", system); + root->setContextProperty("OS", system); - engine->rootContext()->setContextProperty( + root->setContextProperty( "AppPath", QUrl::fromLocalFile(QDir::currentPath())); engine->addImportPath(QDir::currentPath()); @@ -378,20 +359,8 @@ int main(int argc, char *argv[]) { delete engine; delete Pacman; -#ifdef Q_OS_WASM - EM_ASM ( - FS.syncfs(function(err) {}); - ); -#endif - - if (info_log) { - fclose(info_log); - info_log = nullptr; - } - if (err_log) { - fclose(err_log); - info_log = nullptr; - } + if (info_log) fclose(info_log); + if (err_log) fclose(err_log); return ret; #endif diff --git a/src/network/router.cpp b/src/network/router.cpp index b0e6b412..fcbf71a5 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -5,10 +5,8 @@ #include "client_socket.h" #include "roomthread.h" #include -#ifndef FK_CLIENT_ONLY #include "server.h" #include "serverplayer.h" -#endif #include "util.h" Router::Router(QObject *parent, ClientSocket *socket, RouterType type) @@ -18,9 +16,7 @@ Router::Router(QObject *parent, ClientSocket *socket, RouterType type) setSocket(socket); expectedReplyId = -1; replyTimeout = 0; -#ifndef FK_CLIENT_ONLY extraReplyReadySemaphore = nullptr; -#endif } Router::~Router() { abortRequest(); } @@ -57,15 +53,12 @@ bool Router::isConsoleStart() const { return socket->peerAddress() == "127.0.0.1"; } -#ifndef FK_CLIENT_ONLY void Router::setReplyReadySemaphore(QSemaphore *semaphore) { extraReplyReadySemaphore = semaphore; } -#endif void Router::request(int type, const QString &command, const QString &jsonData, int timeout) { -#ifndef FK_CLIENT_ONLY // In case a request is called without a following waitForReply call if (replyReadySemaphore.available() > 0) replyReadySemaphore.acquire(replyReadySemaphore.available()); @@ -88,7 +81,6 @@ void Router::request(int type, const QString &command, const QString &jsonData, body << timeout; emit messageReady(JsonArray2Bytes(body)); -#endif } void Router::reply(int type, const QString &command, const QString &jsonData) { @@ -115,7 +107,6 @@ int Router::getTimeout() const { return requestTimeout; } // cancel last request from the sender void Router::cancelRequest() { -#ifndef FK_CLIENT_ONLY replyMutex.lock(); expectedReplyId = -1; replyTimeout = 0; @@ -124,22 +115,18 @@ void Router::cancelRequest() { if (replyReadySemaphore.available() > 0) replyReadySemaphore.acquire(replyReadySemaphore.available()); -#endif } QString Router::waitForReply(int timeout) { QString ret; -#ifndef FK_CLIENT_ONLY replyReadySemaphore.tryAcquire(1, timeout * 1000); replyMutex.lock(); ret = m_reply; replyMutex.unlock(); -#endif return ret; } void Router::abortRequest() { -#ifndef FK_CLIENT_ONLY replyMutex.lock(); if (expectedReplyId != -1) { replyReadySemaphore.release(); @@ -149,7 +136,6 @@ void Router::abortRequest() { extraReplyReadySemaphore = nullptr; } replyMutex.unlock(); -#endif } void Router::handlePacket(const QByteArray &rawPacket) { @@ -167,9 +153,7 @@ void Router::handlePacket(const QByteArray &rawPacket) { #ifndef FK_SERVER_ONLY ClientInstance->callLua(command, jsonData, false); #endif - } -#ifndef FK_CLIENT_ONLY - else { + } else { ServerPlayer *player = qobject_cast(parent()); if (command == "Heartbeat") { player->alive = true; @@ -179,7 +163,6 @@ void Router::handlePacket(const QByteArray &rawPacket) { Room *room = player->getRoom(); room->handlePacket(player, command, jsonData); } -#endif } else if (type & TYPE_REQUEST) { this->requestId = requestId; this->requestTimeout = packet[4].toInt(); @@ -192,9 +175,7 @@ void Router::handlePacket(const QByteArray &rawPacket) { // requesting server is not allowed Q_ASSERT(false); } - } -#ifndef FK_CLIENT_ONLY - else if (type & TYPE_REPLY) { + } else if (type & TYPE_REPLY) { QMutexLocker locker(&replyMutex); ServerPlayer *player = qobject_cast(parent()); @@ -226,5 +207,4 @@ void Router::handlePacket(const QByteArray &rawPacket) { locker.unlock(); emit replyReady(); } -#endif } diff --git a/src/network/router.h b/src/network/router.h index 7c8841b7..a21a1b5a 100644 --- a/src/network/router.h +++ b/src/network/router.h @@ -34,9 +34,7 @@ public: void installAESKey(const QByteArray &key); bool isConsoleStart() const; -#ifndef FK_CLIENT_ONLY void setReplyReadySemaphore(QSemaphore *semaphore); -#endif void request(int type, const QString &command, const QString &jsonData, int timeout); @@ -72,10 +70,8 @@ private: int expectedReplyId; int replyTimeout; QString m_reply; // should be json string -#ifndef FK_CLIENT_ONLY QSemaphore replyReadySemaphore; QSemaphore *extraReplyReadySemaphore; -#endif // Two Lua global table for callbacks and interactions // stored in the lua_State of the sender diff --git a/src/network/server_socket.h b/src/network/server_socket.h index 310b6913..7cbd2fa2 100644 --- a/src/network/server_socket.h +++ b/src/network/server_socket.h @@ -5,6 +5,7 @@ class ClientSocket; +// 只是对QTcpServer的简单封装 class ServerSocket : public QObject { Q_OBJECT @@ -17,6 +18,7 @@ signals: void new_connection(ClientSocket *socket); private slots: + // 新建一个ClientSocket,然后立刻交给Server相关函数处理。 void processNewConnection(); private: diff --git a/src/pch.h b/src/pch.h index af6355eb..1616d069 100644 --- a/src/pch.h +++ b/src/pch.h @@ -17,14 +17,10 @@ typedef int LuaFunction; #include "sqlite3.h" #define OPENSSL_API_COMPAT 0x10101000L -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_WASM) +#if !defined (Q_OS_ANDROID) #define DESKTOP_BUILD #endif -#if defined(Q_OS_WASM) -#define FK_CLIENT_ONLY -#endif - // You may define FK_SERVER_ONLY with cmake .. -D... #ifndef FK_SERVER_ONLY #include diff --git a/src/swig/client.i b/src/swig/client.i index 7d6ec92e..4fa12b5d 100644 --- a/src/swig/client.i +++ b/src/swig/client.i @@ -4,8 +4,7 @@ %nodefaultdtor QmlBackend; class QmlBackend : public QObject { public: - void emitNotifyUI(const QString &command, const QString &json_data); - + void notifyUI(const QString &command, const QVariant &data); static void cd(const QString &path); static QStringList ls(const QString &dir); static QString pwd(); diff --git a/src/swig/freekill-wasm.i b/src/swig/freekill-wasm.i deleted file mode 100644 index 10480f87..00000000 --- a/src/swig/freekill-wasm.i +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -%module fk - -%{ -#include "client.h" -#include "serverplayer.h" -#include "clientplayer.h" -#include "room.h" -#include "qmlbackend.h" -#include "util.h" -%} - -%include "naturalvar.i" -%include "qt.i" -%include "player.i" -%include "client.i" - -QString GetDisabledPacks(); diff --git a/src/swig/naturalvar.i b/src/swig/naturalvar.i index 5d943696..9aff29f1 100644 --- a/src/swig/naturalvar.i +++ b/src/swig/naturalvar.i @@ -4,6 +4,17 @@ // type bindings // ------------------------------------------------------ +%{ +#include +%} + +// Lua 5.4 特有的不能pushnumber, swig迟迟不更只好手动调教 +%typemap(out) int +%{ +lua_pushinteger(L, $1); +SWIG_arg ++; +%} + // LuaFunction(int) and lua function %naturalvar LuaFunction; %typemap(in) LuaFunction @@ -65,6 +76,7 @@ SWIG_arg ++; // QStringList %naturalvar QStringList; +/* 没有从lua传入QStringList的情况,注释! %typemap(in, checkfn = "lua_istable") QStringList %{ for (size_t i = 0; i < lua_rawlen(L, $input); ++i) { @@ -74,6 +86,7 @@ for (size_t i = 0; i < lua_rawlen(L, $input); ++i) { lua_pop(L, 1); } %} +*/ %typemap(out) QStringList %{ @@ -94,4 +107,37 @@ SWIG_arg++; $1 = lua_istable(L, $input) ? 1 : 0; %} +// QByteArray: 仅out +%typemap(out) QByteArray +%{ + lua_pushstring(L, $1.constData()); + SWIG_arg++; +%} + +// const QByteArray &: 仅in +%typemap(arginit) QByteArray const & + "QByteArray $1_str;" + +%typemap(in, checkfn = "lua_isstring") QByteArray const & +%{ + $1_str = QByteArray(lua_tostring(L, $input)); + $1 = &$1_str; +%} + +// QVariant: 用于json,out +%typemap(out) QVariant +%{ + QmlBackend::pushLuaValue(L, $1); + SWIG_arg++; +%} + +// const QVariant &: 用于json,in +%typemap(arginit) QVariant const & + "QVariant $1_var;" + +%typemap(in) QVariant const & +%{ + $1_var = QmlBackend::readLuaValue(L, $input); + $1 = &$1_var; +%} diff --git a/src/swig/qt.i b/src/swig/qt.i index 523a10b3..a24de19f 100644 --- a/src/swig/qt.i +++ b/src/swig/qt.i @@ -1,44 +1,26 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Make the base classes look like "complete" -class QObject {}; -class QThread { -public: - static void msleep(long msec); -}; +%nodefaultctor QObject; +%nodefaultdtor QObject; +class QObject {}; + +%nodefaultctor QThread; +%nodefaultdtor QThread; +class QThread {}; + +%nodefaultctor QList; +%nodefaultdtor QList; template class QList { public: - 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); + T at(int i) const; }; -%extend QList { - T at(int i) const - { - return $self->value(i); - } -} - %template(SPlayerList) QList; -%template(PlayerList) QList; %template(IntList) QList; -%template(BoolList) QList; %native(GetMicroSecond) int GetMicroSecond(lua_State *L); %{ @@ -56,3 +38,15 @@ void qDebug(const char *msg, ...); void qInfo(const char *msg, ...); void qWarning(const char *msg, ...); void qCritical(const char *msg, ...); + +class QJsonDocument { +public: + enum JsonFormat { + Indented, + Compact, + }; + static QJsonDocument fromJson(const QByteArray &json); + static QJsonDocument fromVariant(const QVariant &variant); + QByteArray toJson(QJsonDocument::JsonFormat format = 1) const; + QVariant toVariant() const; +}; diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 167ce034..ffdf2a32 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "qmlbackend.h" +#include #include +#include #ifndef FK_SERVER_ONLY #include @@ -16,9 +18,7 @@ #endif #include -#ifndef Q_OS_WASM #include "server.h" -#endif #include "client.h" #include "util.h" #include "replayer.h" @@ -81,7 +81,6 @@ void QmlBackend::setEngine(QQmlApplicationEngine *engine) { } void QmlBackend::startServer(ushort port) { -#ifndef Q_OS_WASM if (!ServerInstance) { Server *server = new Server(this); @@ -90,7 +89,6 @@ void QmlBackend::startServer(ushort port) { emit notifyUI("ErrorMsg", tr("Cannot start server!")); } } -#endif } void QmlBackend::joinServer(QString address) { @@ -144,10 +142,6 @@ void QmlBackend::quitLobby(bool close) { // ServerInstance->deleteLater(); } -void QmlBackend::emitNotifyUI(const QString &command, const QString &jsonData) { - emit notifyUI(command, jsonData); -} - QString QmlBackend::translate(const QString &src) { if (!ClientInstance) return src; @@ -179,6 +173,9 @@ void QmlBackend::pushLuaValue(lua_State *L, QVariant v) { case QMetaType::UInt: lua_pushinteger(L, v.toInt()); break; + case QMetaType::LongLong: + lua_pushinteger(L, v.toLongLong()); + break; case QMetaType::Double: lua_pushnumber(L, v.toDouble()); break; @@ -217,9 +214,79 @@ void QmlBackend::pushLuaValue(lua_State *L, QVariant v) { } } -QString QmlBackend::callLuaFunction(const QString &func_name, +// 要求返回一个QVariant而不对栈产生影响 +QVariant QmlBackend::readLuaValue(lua_State *L, int index, + QHash stack) { + + if (index == 0) index = lua_gettop(L); + auto tp = lua_type(L, index); + switch (tp) { + case LUA_TNIL: + return QVariant::fromValue(nullptr); + case LUA_TBOOLEAN: + return QVariant((bool)lua_toboolean(L, index)); + case LUA_TNUMBER: + return QVariant(lua_tonumber(L, index)); + case LUA_TSTRING: + return QVariant(lua_tostring(L, index)); + case LUA_TTABLE: { + auto p = lua_topointer(L, index); + if (stack[p]) { + luaL_error(L, "circular reference detected"); + return QVariant(); // won't return + } + stack[p] = true; + + lua_len(L, index); + int length = lua_tointeger(L, -1); + lua_pop(L, 1); + + if (length == 0) { + bool empty = true; + QVariantMap map; + + lua_pushnil(L); + while (lua_next(L, index) != 0) { + if (lua_type(L, -2) != LUA_TSTRING) { + luaL_error(L, "key of object must be string"); + return QVariant(); + } + + const char *key = lua_tostring(L, -2); + auto value = readLuaValue(L, lua_gettop(L), stack); + lua_pop(L, 1); + + map[key] = value; + empty = false; + } + + if (empty) { + return QVariantList(); + } else { + return map; + } + } else { + QVariantList arr; + for (int i = 1; i <= length; i++) { + lua_rawgeti(L, index, i); + arr << readLuaValue(L, lua_gettop(L), stack); + lua_pop(L, 1); + } + return arr; + } + break; + } + + // ignore function, userdata and thread + default: + luaL_error(L, "unexpected value type %s", lua_typename(L, tp)); + } + return QVariant(); // won't return +} + +QVariant QmlBackend::callLuaFunction(const QString &func_name, QVariantList params) { - if (!ClientInstance) return "{}"; + if (!ClientInstance) return QVariantMap(); lua_State *L = ClientInstance->getLuaState(); lua_getglobal(L, func_name.toLatin1().data()); @@ -229,19 +296,19 @@ QString QmlBackend::callLuaFunction(const QString &func_name, } int err = lua_pcall(L, params.length(), 1, 0); - const char *result = lua_tostring(L, -1); if (err) { - qCritical() << result; + qCritical() << lua_tostring(L, -1); lua_pop(L, 1); - return ""; + return QVariant(); } + auto result = readLuaValue(L); lua_pop(L, 1); - return QString(result); + return result; } -QString QmlBackend::evalLuaExp(const QString &lua) { - if (!ClientInstance) return "{}"; +QVariant QmlBackend::evalLuaExp(const QString &lua) { + if (!ClientInstance) return QVariantMap(); lua_State *L = ClientInstance->getLuaState(); int err; @@ -252,15 +319,15 @@ QString QmlBackend::evalLuaExp(const QString &lua) { return ""; } err = lua_pcall(L, 0, 1, 0); - const char *result = luaL_tolstring(L, -1, NULL); if (err) { - qCritical() << result; + qCritical() << lua_tostring(L, -1); lua_pop(L, 1); - return ""; + return QVariant(); } + auto result = readLuaValue(L); lua_pop(L, 1); - return QString(result); + return result; } QString QmlBackend::pubEncrypt(const QString &key, const QString &data) { @@ -505,13 +572,13 @@ void QmlBackend::setReplayer(Replayer *rep) { replayer = rep; if (rep) { connect(rep, &Replayer::duration_set, this, [this](int sec) { - this->emitNotifyUI("ReplayerDurationSet", QString::number(sec)); + this->notifyUI("ReplayerDurationSet", QString::number(sec)); }); connect(rep, &Replayer::elasped, this, [this](int sec) { - this->emitNotifyUI("ReplayerElapsedChange", QString::number(sec)); + this->notifyUI("ReplayerElapsedChange", QString::number(sec)); }); connect(rep, &Replayer::speed_changed, this, [this](qreal speed) { - this->emitNotifyUI("ReplayerSpeedChange", QString::number(speed)); + this->notifyUI("ReplayerSpeedChange", QString::number(speed)); }); connect(this, &QmlBackend::replayerToggle, rep, &Replayer::toggle); connect(this, &QmlBackend::replayerSlowDown, rep, &Replayer::slowDown); diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index 2f582910..0961c11b 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -23,6 +23,11 @@ public: static Q_INVOKABLE bool exists(const QString &file); static Q_INVOKABLE bool isDir(const QString &file); + // 这俩函数为啥要写在这。。 + static void pushLuaValue(lua_State *L, QVariant v); + static QVariant readLuaValue(lua_State *L, int index = 0, + QHash stack = QHash()); + #ifndef FK_SERVER_ONLY QQmlApplicationEngine *getEngine() const; void setEngine(QQmlApplicationEngine *engine); @@ -33,14 +38,11 @@ public: // Lobby Q_INVOKABLE void quitLobby(bool close = true); - // lua --> qml - void emitNotifyUI(const QString &command, const QString &jsonData); - // read data from lua, call lua functions Q_INVOKABLE QString translate(const QString &src); - Q_INVOKABLE QString callLuaFunction(const QString &func_name, + Q_INVOKABLE QVariant callLuaFunction(const QString &func_name, QVariantList params); - Q_INVOKABLE QString evalLuaExp(const QString &lua); + Q_INVOKABLE QVariant evalLuaExp(const QString &lua); Q_INVOKABLE QString pubEncrypt(const QString &key, const QString &data); Q_INVOKABLE QString loadConf(); @@ -74,7 +76,7 @@ public: Q_INVOKABLE void controlReplayer(QString type); signals: - void notifyUI(const QString &command, const QString &jsonData); + void notifyUI(const QString &command, const QVariant &data); void volumeChanged(qreal); void replayerToggle(); void replayerSpeedUp(); @@ -96,8 +98,6 @@ private: qreal m_volume; Replayer *replayer; - - void pushLuaValue(lua_State *L, QVariant v); #endif };