FreeKill/lua/server/gameevent.lua

358 lines
11 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- SPDX-License-Identifier: GPL-3.0-or-later
---@class GameEvent: Object
---@field public id integer @ 事件的id随着时间推移自动增加并分配给新事件
---@field public end_id integer @ 事件的对应结束id如果整个事件中未插入事件那么end_id就是自己的id
---@field public room Room @ room实例
---@field public event integer @ 该事件对应的EventType
---@field public data any @ 事件的附加数据,视类型而定
---@field public parent GameEvent @ 事件的父事件(栈中的上一层事件)
---@field public prepare_func fun(self: GameEvent) @ 事件即将开始时执行的函数
---@field public main_func fun(self: GameEvent) @ 事件的主函数
---@field public clear_func fun(self: GameEvent) @ 事件结束时执行的函数
---@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 killed boolean @ 事件因为终止一切结算而被中断(所谓的“被杀”)
---@field public revived boolean @ 事件被killed但因为在cleaner中发生而被复活
local GameEvent = class("GameEvent")
---@type (fun(self: GameEvent): bool)[]
GameEvent.prepare_funcs = {}
---@type (fun(self: GameEvent): bool)[]
GameEvent.functions = {}
---@type (fun(self: GameEvent): bool)[]
GameEvent.cleaners = {}
---@type (fun(self: GameEvent): bool)[]
GameEvent.exit_funcs = {}
local function wrapCoFunc(f, ...)
if not f then return nil end
local args = {...}
return function() return f(table.unpack(args)) end
end
local dummyFunc = Util.DummyFunc
function GameEvent:initialize(event, ...)
self.id = -1
self.end_id = -1
self.room = RoomInstance
self.event = event
self.data = { ... }
self.prepare_func = GameEvent.prepare_funcs[event] or dummyFunc
self.main_func = wrapCoFunc(GameEvent.functions[event], self) or dummyFunc
self.clear_func = GameEvent.cleaners[event] or dummyFunc
self.extra_clear_funcs = Util.DummyTable
self.exit_func = GameEvent.exit_funcs[event] or dummyFunc
self.extra_exit_funcs = Util.DummyTable
self.interrupted = false
end
-- 静态函数实际定义在events/init.lua
function GameEvent:translate(id)
error('static')
end
function GameEvent:__tostring()
return string.format("<%s #%d>", GameEvent:translate(self.event), self.id)
end
function GameEvent:addCleaner(f)
if self.extra_clear_funcs == Util.DummyTable then
self.extra_clear_funcs = {}
end
table.insert(self.extra_clear_funcs, f)
end
function GameEvent:addExitFunc(f)
if self.extra_exit_funcs == Util.DummyTable then
self.extra_exit_funcs = {}
end
table.insert(self.extra_exit_funcs, f)
end
function GameEvent:prependExitFunc(f)
if self.extra_exit_funcs == Util.DummyTable then
self.extra_exit_funcs = {}
end
table.insert(self.extra_exit_funcs, 1, f)
end
function GameEvent:findParent(eventType, includeSelf)
if includeSelf and self.event == eventType then return self end
local e = self.parent
repeat
if e.event == eventType then return e end
e = e.parent
until not e
return nil
end
-- 找n个id介于from和to之间的事件。
local function bin_search(events, from, to, n, func)
local left = 1
local right = #events
local mid
local ret = {}
if from < events[1].id then
mid = 1
elseif from > events[right].id then
return ret
else
while true do
if left > right then return ret end
mid = (left + right) // 2
local id = events[mid].id
local id_left = mid == 1 and -math.huge or events[mid - 1].id
if from < id then
if from >= id_left then
break
end
right = mid - 1
else
left = mid + 1
end
end
end
for i = mid, #events do
local v = events[i]
if v.id <= to and func(v) then
table.insert(ret, v)
end
if #ret >= n then break end
end
return ret
end
-- 从某个区间中找出类型符合且符合func函数检测的至多n个事件。
---@param eventType integer @ 要查找的事件类型
---@param n integer @ 最多找多少个
---@param func fun(e: GameEvent): boolean? @ 过滤用的函数
---@param endEvent? GameEvent @ 区间终止点,默认为本事件结束
---@return GameEvent[] @ 找到的符合条件的所有事件最多n个但不保证有n个
function GameEvent:searchEvents(eventType, n, func, endEvent)
local logic = self.room.logic
local events = logic.event_recorder[eventType] or Util.DummyTable
local from = self.id
local to = endEvent and endEvent.id or self.end_id
if math.abs(to) == 1 then to = #logic.all_game_events end
n = n or 1
func = func or Util.TrueFunc
local ret
if #events < 6 then
ret = {}
for _, v in ipairs(events) do
if v.id >= from and v.id <= to and func(v) then
table.insert(ret, v)
end
if #ret >= n then break end
end
else
ret = bin_search(events, from, to, n, func)
end
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)
local zhuran_jmp, zhuran_msg -- SB老朱然
while true do
local err, yield_result, extra_yield_result = coroutine.resume(clear_co)
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
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
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
end
function GameEvent:shutdown()
-- yield to self and break
coroutine.yield(self, "__breakEvent")
end
return GameEvent