From 183dae9ae1c0f8b07b32ceb74cc37d7e99b1594f Mon Sep 17 00:00:00 2001 From: notify Date: Sat, 7 Oct 2023 23:00:25 +0800 Subject: [PATCH] Enhancement2 (#275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TODO: 好友系统,目前在画饼状态 - 修老朱然bug,现在可以在cleaner环节胆守 - 修卢弈死了手谈不消失(UI) - 修重连时丢失房主信息 --- Fk/PhotoElement/LimitSkillItem.qml | 3 ++ docs/dev/friend_system.rst | 45 ++++++++++++++++++++++++++ lua/client/client.lua | 5 +-- lua/server/events/init.lua | 1 + lua/server/gameevent.lua | 52 +++++++++++++++++++++++++++++- lua/server/gamelogic.lua | 8 ++++- lua/server/serverplayer.lua | 1 + server/init.sql | 6 ++++ src/server/server.cpp | 21 ++++++++---- src/server/server.h | 4 +++ src/swig/server.i | 1 + 11 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 docs/dev/friend_system.rst diff --git a/Fk/PhotoElement/LimitSkillItem.qml b/Fk/PhotoElement/LimitSkillItem.qml index 4acd5d14..9493a0a7 100644 --- a/Fk/PhotoElement/LimitSkillItem.qml +++ b/Fk/PhotoElement/LimitSkillItem.qml @@ -58,6 +58,9 @@ Item { if (usedtimes >= 1) { x.visible = true; bg.source = SkinBank.LIMIT_SKILL_DIR + "limit-used"; + } else { + x.visible = false; + bg.source = SkinBank.LIMIT_SKILL_DIR + "limit"; } } else if (skilltype === 'switch') { visible = true; diff --git a/docs/dev/friend_system.rst b/docs/dev/friend_system.rst new file mode 100644 index 00000000..45f1114a --- /dev/null +++ b/docs/dev/friend_system.rst @@ -0,0 +1,45 @@ +关于好友系统 +============= + +“好友系统”这几个字可谓是囊括的面有点多啊。总而言之: + +- 添加好友、管理好友、删除好友 +- 和好友进行私聊 + +没了,就只有以上两点而已。 + +好友信息肯定要放在各个服务器自己的数据库中。 + +登入时自动获取好友列表和消息列表。因此好友列表有上限,最多50人,不然负载太大。 +列表至少要有好友的名字、头像,然后最好还有在线状态。前二者查数据库,第三者要根据id在Server范围查询玩家,然后根据是否找的出、Room是大厅还是确切Room、Room是否已开始分为离线、空闲、等待中、游戏中、观战中四个状态。 + +好友列表查出之后就要暂时放在ServerPlayer类里面,也就是放在RAM中。因为状态一变动就要广播所有好友自己的状态,变动的情况有: + +- 登入/登出; +- 进入房间/进入大厅; +- 游戏开始时/结束时 + +每当状态变化了就通知好友?还是好友进大厅的时候就获取一次信息?答案是要一直通知。 +但是现阶段可以先只针对大厅中的好友通知。毕竟房间里面没地方摆好友UI呢。 + +简而言之,只要进入大厅,就获取好友信息(和自动获取房间列表性质一致)。然后一直接收通知修改好友状态。 + +接下来就是处理如何加好友了,顺便处理私聊之事。 +首先加好友只能在Room中加,这就避免了搜索好友的问题。将加好友请求视为一种特殊的私信吧。 + +私信 +----- + +私信的问题在于已读和未读。未读信息要暂存在服务器;已读信息和聊天记录可以放在客户端本地的数据库中。 + +反正二者都要用到数据库啊。综上,涉及三张表:服务端需要好友关系表、未读信息表;客户端需要聊天记录表。 + +服务器端 - 好友关系表: + +.. code:: sql + + CREATE TABLE 好友 { + user1, user2, type + } + +type是个数字,可能是好友或者黑名单。 diff --git a/lua/client/client.lua b/lua/client/client.lua index 0a85f8e8..81c68554 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -621,8 +621,9 @@ end local function updateLimitSkill(pid, skill, times) if not skill.visible then return end if skill:isSwitchSkill() then - times = ClientInstance:getPlayerById(pid):getSwitchSkillState(skill.switchSkillName) == fk.SwitchYang and 0 or 1 - ClientInstance:notifyUI("UpdateLimitSkill", json.encode{ pid, skill.switchSkillName, times }) + 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 }) 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 }) end diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index 18df60d7..c5559f88 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -41,6 +41,7 @@ dofile "lua/server/events/gameflow.lua" GameEvent.Pindian = 19 dofile "lua/server/events/pindian.lua" +-- 20 = CardEffect GameEvent.ChangeProperty = 21 dofile "lua/server/events/misc.lua" diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index 4c884f96..40a24388 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -13,7 +13,9 @@ ---@field public extra_clear_funcs fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表 ---@field public exit_func fun(self: GameEvent) @ 事件结束后执行的函数 ---@field public extra_exit_funcs fun(self:GameEvent)[] @ 事件结束后执行的自定义函数 ----@field public interrupted boolean @ 事件是否是因为被强行中断而结束的 +---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀 +---@field public killed boolean @ 事件因为终止一切结算而被中断(所谓的“被杀”) +---@field public revived boolean @ 事件被killed,但因为在cleaner中发生而被复活 local GameEvent = class("GameEvent") ---@type (fun(self: GameEvent): bool)[] @@ -169,6 +171,8 @@ function GameEvent:clear() end end) + local zhuran_jmp, zhuran_msg -- SB老朱然 + while true do local err, yield_result, extra_yield_result = coroutine.resume(clear_co) @@ -185,12 +189,37 @@ function GameEvent:clear() if yield_result == "__handleRequest" then -- yield to requestLoop coroutine.yield(yield_result, extra_yield_result) + + elseif type(yield_result) == "table" and yield_result.class + and yield_result:isInstanceOf(GameEvent) and self ~= yield_result then + + -- 不是,谁TM还在cleaner里面玩老朱然啊 + -- 总之,cleaner不能断 + -- 倒是没必要手动resume,新一轮while true会自动resume,只要把返回值 + -- 传回去就行 + + -- 一般来说都是由cleaner中的trigger引起 + -- 以胆守合击为例就是trigger -> SkillEffect事件 -> UseCard事件 -> 胆守 + -- 此时胆守的话最后从SkillEffect事件的exec内部yield出来 + -- 当前协程就应该正在执行room:useSkill函数,resume会去只会让那个函数返回 + + if zhuran_jmp == nil or zhuran_jmp.id > yield_result.id then + zhuran_jmp = yield_result + zhuran_msg = extra_yield_result + end + + -- 自己本来应该被杀的但是因为自己正在执行self:clear()而逃过一劫啊 + -- 还是得标记一下被杀才行,顺便因为实际上没死所以标记被复活 + self.killed = true + self.revived = true + -- 什么都不做,等下轮while自己resume else coroutine.close(clear_co) break end end + -- cleaner顺利执行完了,出栈吧 local logic = RoomInstance.logic local end_id = logic.current_event_id + 1 if self.id ~= end_id - 1 then @@ -203,6 +232,21 @@ function GameEvent:clear() logic.game_event_stack:pop() + -- 好了确保cleaner走完了,此时中断就会进入下层事件的正常中断处理 + if zhuran_jmp then + coroutine.close(self._co) + coroutine.yield(zhuran_jmp, zhuran_msg) + + -- 此时仍可能出现在插结在其他事件的clear函数中 + -- 但就算被交付回去了,也能安然返回而不是继续while + -- 但愿如此吧 + end + + -- 保险而已,其实如果被杀的话应该已经在前面的yield终止了 + -- 但担心cleaner嵌套(三国杀是这样的)还是补一刀 + if self.killed then return end + + -- 恭喜没被杀掉,我们来执行一些事件结束之后的结算吧 Pcall(self.exit_func, self) for _, f in ipairs(self.extra_exit_funcs) do if type(f) == "function" then @@ -243,6 +287,7 @@ function GameEvent:exec() table.insert(logic.event_recorder[self.event], self) local co = coroutine.create(self.main_func) + self._co = co while true do local err, yield_result, extra_yield_result = coroutine.resume(co) @@ -269,12 +314,17 @@ function GameEvent:exec() if self ~= yield_result then -- yield to corresponding GameEvent, first pop self from stack self.interrupted = true + self.killed = true -- 老朱然!你不得好死 self:clear() -- logic.game_event_stack:pop(self) coroutine.close(co) -- then, call yield coroutine.yield(yield_result, extra_yield_result) + + -- 如果是在cleaner/exit里面发生此类中断的话是会被cleaner原地返回的 + -- 此时正常执行程序流就变成继续while循环了,这是不行的 + break elseif extra_yield_result == "__breakEvent" then if breakEvent(self) then coroutine.close(co) diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 63f5575e..249541c2 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -335,6 +335,10 @@ function GameLogic:trigger(event, target, data, refresh_only) local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable local _target = room.current -- for iteration local player = _target + local cur_event = self:getCurrentEvent() or {} + -- 如果当前事件被杀,就强制只refresh + -- 因为被杀的事件再进行正常trigger只可能在cleaner和exit了 + refresh_only = refresh_only or cur_event.killed if #skills_to_refresh > 0 then repeat do -- refresh skills. This should not be broken @@ -380,7 +384,9 @@ function GameLogic:trigger(event, target, data, refresh_only) skill_names = table.map(table.filter(skills, filter_func), Util.NameMapper) broken = broken or (event == fk.AskForPeaches - and room:getPlayerById(data.who).hp > 0) + and room:getPlayerById(data.who).hp > 0) or cur_event.revived + -- ^^^^^^^^^^^^^^^^^ + -- 如果事件复活了,那么其实说明事件已经死过了,赶紧break掉 if broken then break end end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 02aa5bea..0fcbfce9 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -339,6 +339,7 @@ function ServerPlayer:reconnect() p._splayer:getAvatar(), }) end + self:doNotify("RoomOwner", json.encode{ room.room:getOwner():getId() }) local player_circle = {} for i = 1, #room.players do diff --git a/server/init.sql b/server/init.sql index b7d86b84..acd3e7f8 100644 --- a/server/init.sql +++ b/server/init.sql @@ -25,6 +25,12 @@ CREATE TABLE IF NOT EXISTS banuuid ( uuid VARCHAR(32) ); +CREATE TABLE IF NOT EXISTS friendinfo ( + id1 INTEGER, + id2 INTEGER, + reltype INTEGER -- 1=好友 2=黑名单 +); + -- 胜率相关 CREATE TABLE IF NOT EXISTS winRate ( diff --git a/src/server/server.cpp b/src/server/server.cpp index f1ec91f1..e8eada4b 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -451,15 +451,14 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, client->disconnect(this); if (players.count() <= 10) { broadcast("ServerMessage", tr("%1 backed").arg(player->getScreenName())); - if (room->getOwner() == player) { - auto owner = room->getOwner(); - auto jsonData = QJsonArray(); - jsonData << owner->getId(); - player->doNotify("RoomOwner", JsonArray2Bytes(jsonData)); - } } if (room && !room->isLobby()) { + player->doNotify("SetServerSettings", JsonArray2Bytes({ + getConfig("motd"), + getConfig("hiddenPacks"), + getConfig("enableBots"), + })); room->pushRequest(QString("%1,reconnect").arg(id)); } else { // 懒得处理掉线玩家在大厅了!踢掉得了 @@ -670,6 +669,16 @@ void Server::temporarilyBan(int playerId) { emit player->kicked(); } +void Server::beginTransaction() { + transaction_mutex.lock(); + ExecSQL(db, "BEGIN;"); +} + +void Server::endTransaction() { + ExecSQL(db, "COMMIT;"); + transaction_mutex.unlock(); +} + void Server::readPendingDatagrams() { while (udpSocket->hasPendingDatagrams()) { QNetworkDatagram datagram = udpSocket->receiveDatagram(); diff --git a/src/server/server.h b/src/server/server.h index 2e3ddd19..477a1f05 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -47,6 +47,9 @@ public: bool checkBanWord(const QString &str); void temporarilyBan(int playerId); + void beginTransaction(); + void endTransaction(); + signals: void roomCreated(Room *room); void playerAdded(ServerPlayer *player); @@ -78,6 +81,7 @@ private: RSA *rsa; QString public_key; sqlite3 *db; + QMutex transaction_mutex; QString md5; static RSA *initServerRSA(); diff --git a/src/swig/server.i b/src/swig/server.i index 3b4b1597..3f73d8c6 100644 --- a/src/swig/server.i +++ b/src/swig/server.i @@ -9,6 +9,7 @@ public: int getId() const; QList getPlayers() const; + ServerPlayer *getOwner() const; QList getObservers() const; bool hasObserver(ServerPlayer *player) const;