diff --git a/lua/core/util.lua b/lua/core/util.lua index ded584c7..975c5dd2 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -505,7 +505,9 @@ end function Stack:pop() if self.p == 0 then return nil end self.p = self.p - 1 - return self.t[self.p + 1] + local ret = self.t[self.p + 1] + self.t[self.p + 1] = nil + return ret end diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index a5e308f5..dbbe92b5 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -5,6 +5,8 @@ -- 某类事件对应的结束事件,其id刚好就是那个事件的相反数 -- GameEvent.EventFinish = -1 +GameEvent.Game = 0 + GameEvent.ChangeHp = 1 GameEvent.Damage = 2 GameEvent.LoseHp = 3 @@ -44,10 +46,10 @@ dofile "lua/server/events/pindian.lua" -- 20 = CardEffect GameEvent.ChangeProperty = 21 -dofile "lua/server/events/misc.lua" --- TODO: fix this -GameEvent.BreakEvent = 999 +-- 新的clear函数专用 +GameEvent.ClearEvent = 9999 +dofile "lua/server/events/misc.lua" for _, l in ipairs(Fk._custom_events) do local name, p, m, c, e = l.name, l.p, l.m, l.c, l.e @@ -58,6 +60,8 @@ for _, l in ipairs(Fk._custom_events) do end local eventTranslations = { + [GameEvent.Game] = "GameEvent.Game", + [GameEvent.ChangeHp] = "GameEvent.ChangeHp", [GameEvent.Damage] = "GameEvent.Damage", [GameEvent.LoseHp] = "GameEvent.LoseHp", @@ -80,7 +84,7 @@ local eventTranslations = { [GameEvent.ChangeProperty] = "GameEvent.ChangeProperty", - [GameEvent.BreakEvent] = "GameEvent.BreakEvent", + [GameEvent.ClearEvent] = "GameEvent.ClearEvent", } function GameEvent.static:translate(id) diff --git a/lua/server/events/misc.lua b/lua/server/events/misc.lua index ff30ffbc..6b73e3e4 100644 --- a/lua/server/events/misc.lua +++ b/lua/server/events/misc.lua @@ -1,5 +1,9 @@ -- SPDX-License-Identifier: GPL-3.0-or-later +GameEvent.functions[GameEvent.Game] = function(self) + self.room.logic:run() +end + GameEvent.functions[GameEvent.ChangeProperty] = function(self) local data = table.unpack(self.data) local room = self.room @@ -120,3 +124,25 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self) logic:trigger(fk.AfterPropertyChange, player, data) end + +GameEvent.functions[GameEvent.ClearEvent] = function(self) + local event = self.data[1] + local logic = self.room.logic + event:clear_func() + for _, f in ipairs(event.extra_clear_funcs) do + if type(f) == "function" then f(event) end + end + + -- cleaner顺利执行完了,出栈吧 + local end_id = logic.current_event_id + 1 + if event.id ~= end_id - 1 then + logic.all_game_events[end_id] = event.event + logic.current_event_id = end_id + event.end_id = end_id + else + event.end_id = event.id + end + + logic.game_event_stack:pop() + logic.cleaner_stack:pop() +end diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index f7d3840b..1a1e36b6 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -13,9 +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 exec_ret boolean? @ exec函数的返回值,可能不存在 ---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀 ---@field public killed boolean @ 事件因为终止一切结算而被中断(所谓的“被杀”) ----@field public revived boolean @ 事件被killed,但因为在cleaner中发生而被复活 local GameEvent = class("GameEvent") ---@type (fun(self: GameEvent): bool)[] @@ -163,190 +163,28 @@ function GameEvent:searchEvents(eventType, n, func, endEvent) return ret end -function GameEvent:clear() - local clear_co = coroutine.create(function() - self:clear_func() - for _, f in ipairs(self.extra_clear_funcs) do - if type(f) == "function" then f(self) end - end - end) +function GameEvent:exec() + local room = self.room + local logic = room.logic + self.parent = logic:getCurrentEvent() - local zhuran_jmp, zhuran_msg -- SB老朱然 + if self:prepare_func() then return true end - while true do - local err, yield_result, extra_yield_result = coroutine.resume(clear_co) + logic:pushEvent(self) - if err == false then - -- handle error, then break - if not string.find(yield_result, "__manuallyBreak") then - fk.qCritical(yield_result .. "\n" .. debug.traceback(clear_co)) - end - coroutine.close(clear_co) - break - end + local co = coroutine.create(self.main_func) + self._co = co - if yield_result == "__handleRequest" then - -- yield to requestLoop - coroutine.yield(yield_result, extra_yield_result) + coroutine.yield(self, "__newEvent") - 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 - logic.all_game_events[end_id] = self.event - logic.current_event_id = end_id - self.end_id = end_id - else - self.end_id = self.id - end - - 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 Pcall(f, self) end end -end -local function breakEvent(self, extra_yield_result) - local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self) - cancelEvent.toId = self.id - local notcanceled = cancelEvent:exec() - local ret, extra_ret = false, nil - if not notcanceled then - self.interrupted = true - self:clear() - ret = true - extra_ret = extra_yield_result - end - return ret, extra_ret -end - -function GameEvent:exec() - local room = self.room - local logic = room.logic - local ret = false -- false or nil means this event is running normally - local extra_ret - self.parent = logic:getCurrentEvent() - - if self:prepare_func() then return true end - - logic.game_event_stack:push(self) - - logic.current_event_id = logic.current_event_id + 1 - self.id = logic.current_event_id - logic.all_game_events[self.id] = self - logic.event_recorder[self.event] = logic.event_recorder[self.event] or {} - 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) - - if err == false then - -- handle error, then break - if not string.find(yield_result, "__manuallyBreak") then - fk.qCritical(yield_result .. "\n" .. debug.traceback(co)) - end - self.interrupted = true - self:clear() - ret = true - coroutine.close(co) - break - end - - 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) then - - 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) - break - end - end - - elseif yield_result == "__breakEvent" then - -- try to break this event - if breakEvent(self) then - coroutine.close(co) - break - end - - else - -- normally exit, simply break the loop - self:clear() - extra_ret = yield_result - coroutine.close(co) - break - end - end - - return ret, extra_ret + return self.interrupted, self.exec_ret end function GameEvent:shutdown() diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 6818aedf..0f8f184c 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -7,6 +7,7 @@ ---@field public refresh_skill_table table ---@field public skills string[] ---@field public game_event_stack Stack +---@field public cleaner_stack Stack ---@field public role_table string[][] ---@field public all_game_events GameEvent[] ---@field public event_recorder table @@ -20,6 +21,7 @@ function GameLogic:initialize(room) self.refresh_skill_table = {} self.skills = {} -- skillName[] self.game_event_stack = Stack:new() + self.cleaner_stack = Stack:new() self.all_game_events = {} self.event_recorder = {} self.current_event_id = 0 @@ -389,9 +391,7 @@ 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) or cur_event.revived - -- ^^^^^^^^^^^^^^^^^ - -- 如果事件复活了,那么其实说明事件已经死过了,赶紧break掉 + and room:getPlayerById(data.who).hp > 0) or cur_event.killed if broken then break end end @@ -408,6 +408,138 @@ function GameLogic:trigger(event, target, data, refresh_only) return broken end +-- 此为启动事件管理器并启动第一个事件的初始函数 +function GameLogic:start() + local root_event = GameEvent:new(GameEvent.Game) + + self:pushEvent(root_event) + + -- 此时的协程:room.main_co + -- 事件管理器协程,同时也是Game事件 + -- 当新事件想要exec时,就切回此处,由这里负责调度协程 + -- 一个事件结束后也切回此处,然后resume + local co = coroutine.create(root_event.main_func) + root_event._co = co + + local jump_to -- shutdown函数用 + + while true do + -- 对于cleaner和正常事件,处理更后面来的 + local ne = self:getCurrentEvent() + local ce = self:getCurrentCleaner() + local e = ce and (ce.id >= ne.id and ce or ne) or ne + + -- 如果正在jump的话,判断是否需要继续clean,否则正常继续 + if e == ne and jump_to ~= nil then + e.interrupted = true + e.killed = e ~= jump_to + self:clearEvent(e) + coroutine.close(e._co) + if e == jump_to then jump_to = nil end -- shutdown结束了 + e = self:getCurrentCleaner() + end + + -- ret, evt解释: + -- * true, nil: 中止 + -- * false, nil: 正常结束 + -- * true, GameEvent: 中止直到某event + -- * false, GameEvent: 未结束,插入新event + -- 若jump_to不为nil,表示正在中断至某某事件 + local ret, evt = self:resumeEvent(e) + if evt == nil then + e.interrupted = ret + self:clearEvent(e) + coroutine.close(e._co) + elseif ret == true then + -- 跳到越早发生的事件越好 + if not jump_to then + jump_to = evt + else + jump_to = jump_to.id < evt.id and jump_to or evt + end + end + end +end + +---@param event GameEvent +function GameLogic:pushEvent(event) + self.game_event_stack:push(event) + + self.current_event_id = self.current_event_id + 1 + event.id = self.current_event_id + self.all_game_events[event.id] = event + self.event_recorder[event.event] = self.event_recorder[event.event] or {} + table.insert(self.event_recorder[event.event], event) +end + +-- 一般来说从GameEvent:exec切回start再被start调用 +-- 作用是启动新事件 都是结构差不多的函数 +---@param event GameEvent +---@return boolean, GameEvent? +function GameLogic:resumeEvent(event, ...) + local ret, evt + + local co = event._co + + while true do + local err, yield_result, extra_yield_result = coroutine.resume(co, ...) + + if err == false then + -- handle error, then break + if not string.find(yield_result, "__manuallyBreak") then + fk.qCritical(yield_result .. "\n" .. debug.traceback(co)) + end + ret = true + break + end + + 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) then + + if extra_yield_result == "__newEvent" then + ret, evt = false, yield_result + break + elseif extra_yield_result == "__breakEvent" then + ret, evt = true, yield_result + if event.event ~= GameEvent.ClearEvent then break end + end + + elseif yield_result == "__breakEvent" then + ret = true + if event.event ~= GameEvent.ClearEvent then break end + + else + ret = false + event.exec_ret = yield_result + break + end + end + + return ret, evt +end + +---@return GameEvent +function GameLogic:getCurrentCleaner() + return self.cleaner_stack.t[self.cleaner_stack.p] +end + +-- 事件中的清理。 +-- cleaner单独开协程运行,exitFunc须转到上个事件的协程内执行 +-- 注意插入新event +---@param event GameEvent +function GameLogic:clearEvent(event) + if event.event == GameEvent.ClearEvent then return end + local ce = GameEvent(GameEvent.ClearEvent, event) + ce.id = self.current_event_id + local co = coroutine.create(ce.main_func) + ce._co = co + self.cleaner_stack:push(ce) +end + ---@return GameEvent function GameLogic:getCurrentEvent() return self.game_event_stack.t[self.game_event_stack.p] diff --git a/lua/server/room.lua b/lua/server/room.lua index ab836da6..8d161157 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -254,9 +254,11 @@ function Room:run() end local mode = Fk.game_modes[self.settings.gameMode] - self.logic = (mode.logic and mode.logic() or GameLogic):new(self) - if mode.rule then self.logic:addTriggerSkill(mode.rule) end - self.logic:run() + local logic = (mode.logic and mode.logic() or GameLogic):new(self) + self.logic = logic + if mode.rule then logic:addTriggerSkill(mode.rule) end + -- GameEvent(GameEvent.Game):exec() + logic:start() end ------------------------------------------------------------------------ diff --git a/lua/server/scheduler.lua b/lua/server/scheduler.lua index d2c04e77..2c1a16dd 100644 --- a/lua/server/scheduler.lua +++ b/lua/server/scheduler.lua @@ -93,6 +93,12 @@ local function mainLoop() if over then -- verbose('[#] %s is finished, removing ...', tostring(room)) + for _, e in ipairs(room.logic.game_event_stack.t) do + coroutine.close(e._co) + end + for _, e in ipairs(room.logic.cleaner_stack.t) do + coroutine.close(e._co) + end room.logic = nil runningRooms[room.id] = nil else diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 2a86b2f0..9e16670b 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -321,6 +321,16 @@ 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, diff --git a/src/swig/qt.i b/src/swig/qt.i index 366c6560..523a10b3 100644 --- a/src/swig/qt.i +++ b/src/swig/qt.i @@ -46,7 +46,7 @@ public: static int GetMicroSecond(lua_State *L) { struct timeval tv; gettimeofday(&tv, nullptr); - long long microsecond = tv.tv_sec * 1000000 + tv.tv_usec; + long long microsecond = (long long)tv.tv_sec * 1000000 + tv.tv_usec; lua_pushnumber(L, microsecond); return 1; }