diff --git a/lua/server/event.lua b/lua/server/event.lua index f2cdb6bf..22937331 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -8,6 +8,7 @@ fk.NonTrigger = 1 fk.GameStart = 2 fk.TurnStart = 3 +fk.TurnEnd = 72 fk.EventPhaseStart = 4 fk.EventPhaseProceeding = 5 fk.EventPhaseEnd = 6 @@ -92,4 +93,6 @@ fk.PindianCardsDisplayed = 69 fk.PindianResultConfirmed = 70 fk.PindianFinished = 71 +-- 72 = TurnEnd + fk.NumOfEvents = 72 diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index e5ca889d..3c951b2e 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -2,8 +2,33 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self) local room = self.room - for _, p in ipairs(room.alive_players) do - room.logic:trigger(fk.DrawInitialCards, p, { num = 4 }) + for _, player in ipairs(room.alive_players) do + local draw_data = { num = 4 } + room.logic:trigger(fk.DrawInitialCards, player, draw_data) + if draw_data.num > 0 then + -- TODO: need a new function to call the UI + local cardIds = room:getNCards(draw_data.num) + player:addCards(Player.Hand, cardIds) + for _, id in ipairs(cardIds) do + Fk:filterCard(id, player) + end + local move_to_notify = {} ---@type CardsMoveStruct + move_to_notify.toArea = Card.PlayerHand + move_to_notify.to = player.id + move_to_notify.moveInfo = {} + move_to_notify.moveReason = fk.ReasonDraw + for _, id in ipairs(cardIds) do + table.insert(move_to_notify.moveInfo, + { cardId = id, fromArea = Card.DrawPile }) + end + room:notifyMoveCards(nil, {move_to_notify}) + + for _, id in ipairs(cardIds) do + room:setCardArea(id, Card.PlayerHand, player.id) + end + + room.logic:trigger(fk.AfterDrawInitialCards, player, data) + end end end @@ -12,6 +37,12 @@ GameEvent.functions[GameEvent.Round] = function(self) local logic = room.logic local p + if room:getTag("FirstRound") then + room:setTag("FirstRound", false) + end + room:setTag("RoundCount", room:getTag("RoundCount") + 1) + room:doBroadcastNotify("UpdateRoundNum", room:getTag("RoundCount")) + logic:trigger(fk.RoundStart, room.current) repeat @@ -24,14 +55,165 @@ GameEvent.functions[GameEvent.Round] = function(self) logic:trigger(fk.RoundEnd, p) end +GameEvent.cleaners[GameEvent.Round] = function(self) + local room = self.room + + for _, p in ipairs(room.players) do + p:setCardUseHistory("", 0, Player.HistoryRound) + p:setSkillUseHistory("", 0, Player.HistoryRound) + for name, _ in pairs(p.mark) do + if name:endsWith("-round") then + room:setPlayerMark(p, name, 0) + end + end + end +end + GameEvent.functions[GameEvent.Turn] = function(self) local room = self.room room.logic:trigger(fk.TurnStart, room.current) + room:sendLog{ type = "$AppendSeparator" } + local player = room.current if not player.faceup then player:turnOver() elseif not player.dead then player:play() end + + room.logic:trigger(fk.TurnEnd, room.current) +end + +GameEvent.cleaners[GameEvent.Turn] = function(self) + local room = self.room + + for _, p in ipairs(room.players) do + p:setCardUseHistory("", 0, Player.HistoryTurn) + p:setSkillUseHistory("", 0, Player.HistoryTurn) + for name, _ in pairs(p.mark) do + if name:endsWith("-turn") then + room:setPlayerMark(p, name, 0) + end + end + end + + if self.interrupted then + room.current.phase = Player.Finish + room.logic:trigger(fk.EventPhaseStart, room.current, nil, true) + room.logic:trigger(fk.EventPhaseEnd, room.current, nil, true) + + room.current.phase = Player.NotActive + room:notifyProperty(room.current, room.current, "phase") + room.logic:trigger(fk.EventPhaseChanging, room.current, + { from = Player.Finish, to = Player.NotActive }, true) + room.logic:trigger(fk.EventPhaseStart, room.current, nil, true) + + room.current.skipped_phases = {} + + room.logic:trigger(fk.TurnEnd, room.current, nil, true) + end +end + +GameEvent.functions[GameEvent.Phase] = function(self) + local room = self.room + local logic = room.logic + + local player = self.data[1] + if not logic:trigger(fk.EventPhaseStart, player) then + if player.phase ~= Player.NotActive then + logic:trigger(fk.EventPhaseProceeding, player) + + switch(player.phase, { + [Player.PhaseNone] = function() + error("You should never proceed PhaseNone") + end, + [Player.RoundStart] = function() + + end, + [Player.Start] = function() + + end, + [Player.Judge] = function() + local cards = player:getCardIds(Player.Judge) + for i = #cards, 1, -1 do + local card + card = player:removeVirtualEquip(cards[i]) + if not card then + card = Fk:getCardById(cards[i]) + end + room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name) + + ---@type CardEffectEvent + local effect_data = { + card = card, + to = player.id, + tos = { {player.id} }, + } + room:doCardEffect(effect_data) + if effect_data.isCancellOut and card.skill then + card.skill:onNullified(room, effect_data) + end + end + end, + [Player.Draw] = function() + local data = { + n = 2 + } + room.logic:trigger(fk.DrawNCards, player, data) + room:drawCards(player, data.n, self.name) + room.logic:trigger(fk.AfterDrawNCards, player, data) + end, + [Player.Play] = function() + while not player.dead do + room:notifyMoveFocus(player, "PlayCard") + local result = room:doRequest(player, "PlayCard", player.id) + if result == "" then break end + + local use = room:handleUseCardReply(player, result) + if use then + room:useCard(use) + end + end + end, + [Player.Discard] = function() + local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards() + if discardNum > 0 then + room:askForDiscard(player, discardNum, discardNum, false, self.name) + end + end, + [Player.Finish] = function() + + end, + [Player.NotActive] = function() + + end, + }) + end + end + + if self.phase ~= Player.NotActive then + logic:trigger(fk.EventPhaseEnd, self) + else + self.skipped_phases = {} + end +end + +GameEvent.cleaners[GameEvent.Phase] = function(self) + local room = self.room + local player = self.data[1] + + for _, p in ipairs(room.players) do + p:setCardUseHistory("", 0, Player.HistoryPhase) + p:setSkillUseHistory("", 0, Player.HistoryPhase) + for name, _ in pairs(p.mark) do + if name:endsWith("-phase") then + room:setPlayerMark(p, name, 0) + end + end + end + + if self.interrupted then + room.logic:trigger(fk.EventPhaseEnd, player, nil, true) + end end diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index a72a0fb5..f80474dc 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -31,9 +31,10 @@ dofile "lua/server/events/judge.lua" GameEvent.DrawInitial = 15 GameEvent.Round = 16 GameEvent.Turn = 17 +GameEvent.Phase = 18 dofile "lua/server/events/gameflow.lua" -GameEvent.Pindian = 18 +GameEvent.Pindian = 19 dofile "lua/server/events/pindian.lua" -- TODO: fix this @@ -55,7 +56,10 @@ local eventTranslations = { [GameEvent.DrawInitial] = "GameEvent.DrawInitial", [GameEvent.Round] = "GameEvent.Round", [GameEvent.Turn] = "GameEvent.Turn", + [GameEvent.Phase] = "GameEvent.Phase", [GameEvent.Pindian] = "GameEvent.Pindian", + + [GameEvent.BreakEvent] = "GameEvent.BreakEvent", } function GameEvent.static:translate(id) diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index 54db591f..cd70bf01 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -25,11 +25,15 @@ function GameEvent:initialize(event, ...) self.event = event self.data = { ... } self.main_func = wrapCoFunc(GameEvent.functions[event], self) or dummyFunc - self.clear_func = wrapCoFunc(GameEvent.cleaners[event], self) or dummyFunc + self.clear_func = GameEvent.cleaners[event] or dummyFunc self.extra_clear_funcs = {} self.interrupted = false end +function GameEvent:__tostring() + return GameEvent:translate(self.event) +end + function GameEvent:findParent(eventType) local e = self.parent repeat @@ -46,6 +50,19 @@ function GameEvent:clear() self:clear_func() end +local function breakEvent(self, extra_yield_result) + local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self) + 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 @@ -67,6 +84,7 @@ function GameEvent:exec() self.interrupted = true self:clear() ret = true + coroutine.close(co) break end @@ -76,23 +94,27 @@ function GameEvent:exec() elseif type(yield_result) == "table" and yield_result.class and yield_result:isInstanceOf(GameEvent) then - -- yield to corresponding GameEvent, first pop self from stack - self.interrupted = true - self:clear() - logic.game_event_stack:pop(self) - -- then, call yield - coroutine.yield(yield_result) + if self ~= yield_result then + -- yield to corresponding GameEvent, first pop self from stack + self.interrupted = true + self:clear() + logic.game_event_stack:pop(self) + coroutine.close(co) + + -- then, call yield + coroutine.yield(yield_result, extra_yield_result) + 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 - local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self) - local notcanceled = cancelEvent:exec() - if not notcanceled then - self.interrupted = true - self:clear() - ret = true - extra_ret = extra_yield_result + if breakEvent(self) then + coroutine.close(co) break end @@ -100,6 +122,7 @@ function GameEvent:exec() -- normally exit, simply break the loop self:clear() extra_ret = yield_result + coroutine.close(co) break end end @@ -108,4 +131,9 @@ function GameEvent:exec() return ret, extra_ret end +function GameEvent:shutdown() + -- yield to self and break + coroutine.yield(self, "__breakEvent") +end + return GameEvent diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 3018b7c8..a5104a1d 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -271,7 +271,7 @@ end ---@param event Event ---@param target ServerPlayer ---@param data any -function GameLogic:trigger(event, target, data) +function GameLogic:trigger(event, target, data, refresh_only) local room = self.room local broken = false local skills = self.skill_table[event] or {} @@ -291,7 +291,7 @@ function GameLogic:trigger(event, target, data) player = player.next end until player == _target end - if #skills == 0 then return end + if #skills == 0 or refresh_only then return end local prio_tab = self.skill_priority_table[event] local prev_prio = math.huge @@ -368,7 +368,7 @@ function GameLogic:dumpEventStack(detailed) end if not detailed then - print("Stack level #" .. i .. ": " .. GameEvent:translate(top.event)) + print("Stack level #" .. i .. ": " .. tostring(top)) else print("\nStack level #" .. i .. ":") inspect{ @@ -388,4 +388,9 @@ function GameLogic:breakEvent(ret) coroutine.yield("__breakEvent", ret) end +function GameLogic:breakTurn() + local event = self:getCurrentEvent():findParent(GameEvent.Turn) + event:shutdown() +end + return GameLogic diff --git a/lua/server/room.lua b/lua/server/room.lua index 957ffa68..91ac0d58 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -92,6 +92,9 @@ function Room:initialize(_room) -- If ret == true, then when err_msg is true, that means no request end + coroutine.close(main_co) + coroutine.close(request_co) + if not self.game_finished then self:doBroadcastNotify("GameOver", "") self.room:gameOver() diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 323d25a0..2488df87 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -333,19 +333,23 @@ function ServerPlayer:changePhase(from_phase, to_phase) table.remove(self.phases, 1) end - if not logic:trigger(fk.EventPhaseStart, self) then - if self.phase ~= Player.NotActive then - logic:trigger(fk.EventPhaseProceeding, self) - end - end - - if self.phase ~= Player.NotActive then - logic:trigger(fk.EventPhaseEnd, self) - end + GameEvent(GameEvent.Phase, self):exec() return false end +function ServerPlayer:gainAnExtraPhase(phase) + local room = self.room + local current = self.phase + self.phase = phase + room:notifyProperty(self, self, "phase") + + GameEvent(GameEvent.Phase, self):exec() + + self.phase = current + room:notifyProperty(self, self, "phase") +end + local phase_name_table = { [Player.Judge] = "phase_judge", [Player.Draw] = "phase_draw", @@ -413,17 +417,7 @@ function ServerPlayer:play(phase_table) end if (not skip) or (cancel_skip) then - if not logic:trigger(fk.EventPhaseStart, self) then - if self.phase ~= Player.NotActive then - logic:trigger(fk.EventPhaseProceeding, self) - end - end - - if self.phase ~= Player.NotActive then - logic:trigger(fk.EventPhaseEnd, self) - else - self.skipped_phases = {} - end + GameEvent(GameEvent.Phase, self):exec() else room:sendLog{ type = "#PhaseSkipped", diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index e7f22cbd..d0b3c931 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -41,9 +41,7 @@ end GameRule = fk.CreateTriggerSkill{ name = "game_rule", events = { - fk.GameStart, fk.DrawInitialCards, - fk.EventPhaseProceeding, fk.EventPhaseEnd, fk.EventPhaseChanging, - fk.RoundStart, + fk.GameStart, fk.AskForPeaches, fk.AskForPeachesDone, fk.GameOverJudge, fk.BuryVictim, }, @@ -67,146 +65,6 @@ GameRule = fk.CreateTriggerSkill{ end switch(event, { - [fk.DrawInitialCards] = function() - if data.num > 0 then - -- TODO: need a new function to call the UI - local cardIds = room:getNCards(data.num) - player:addCards(Player.Hand, cardIds) - for _, id in ipairs(cardIds) do - Fk:filterCard(id, player) - end - local move_to_notify = {} ---@type CardsMoveStruct - move_to_notify.toArea = Card.PlayerHand - move_to_notify.to = player.id - move_to_notify.moveInfo = {} - move_to_notify.moveReason = fk.ReasonDraw - for _, id in ipairs(cardIds) do - table.insert(move_to_notify.moveInfo, - { cardId = id, fromArea = Card.DrawPile }) - end - room:notifyMoveCards(nil, {move_to_notify}) - - for _, id in ipairs(cardIds) do - room:setCardArea(id, Card.PlayerHand, player.id) - end - - room.logic:trigger(fk.AfterDrawInitialCards, player, data) - end - end, - [fk.RoundStart] = function() - if room:getTag("FirstRound") then - room:setTag("FirstRound", false) - end - - room:setTag("RoundCount", room:getTag("RoundCount") + 1) - room:doBroadcastNotify("UpdateRoundNum", room:getTag("RoundCount")) - - for _, p in ipairs(room.players) do - p:setCardUseHistory("", 0, Player.HistoryRound) - p:setSkillUseHistory("", 0, Player.HistoryRound) - for name, _ in pairs(p.mark) do - if name:endsWith("-round") then - room:setPlayerMark(p, name, 0) - end - end - end - - room:sendLog{ type = "$AppendSeparator" } - end, - [fk.EventPhaseProceeding] = function() - switch(player.phase, { - [Player.PhaseNone] = function() - error("You should never proceed PhaseNone") - end, - [Player.RoundStart] = function() - - end, - [Player.Start] = function() - - end, - [Player.Judge] = function() - local cards = player:getCardIds(Player.Judge) - for i = #cards, 1, -1 do - local card - card = player:removeVirtualEquip(cards[i]) - if not card then - card = Fk:getCardById(cards[i]) - end - room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name) - - ---@type CardEffectEvent - local effect_data = { - card = card, - to = player.id, - tos = { {player.id} }, - } - room:doCardEffect(effect_data) - if effect_data.isCancellOut and card.skill then - card.skill:onNullified(room, effect_data) - end - end - end, - [Player.Draw] = function() - local data = { - n = 2 - } - room.logic:trigger(fk.DrawNCards, player, data) - room:drawCards(player, data.n, self.name) - room.logic:trigger(fk.AfterDrawNCards, player, data) - end, - [Player.Play] = function() - while not player.dead do - room:notifyMoveFocus(player, "PlayCard") - local result = room:doRequest(player, "PlayCard", player.id) - if result == "" then break end - - local use = room:handleUseCardReply(player, result) - if use then - room:useCard(use) - end - end - end, - [Player.Discard] = function() - local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards() - if discardNum > 0 then - room:askForDiscard(player, discardNum, discardNum, false, self.name) - end - end, - [Player.Finish] = function() - - end, - [Player.NotActive] = function() - - end, - }) - end, - [fk.EventPhaseEnd] = function() - if player.phase == Player.Play then - for _, p in ipairs(room.players) do - p:setCardUseHistory("", 0, Player.HistoryPhase) - p:setSkillUseHistory("", 0, Player.HistoryPhase) - for name, _ in pairs(p.mark) do - if name:endsWith("-phase") then - room:setPlayerMark(p, name, 0) - end - end - end - end - end, - [fk.EventPhaseChanging] = function() - -- TODO: copy but dont copy all - if data.to == Player.NotActive then - for _, p in ipairs(room.players) do - p:setCardUseHistory("", 0, Player.HistoryTurn) - p:setSkillUseHistory("", 0, Player.HistoryTurn) - for name, _ in pairs(p.mark) do - if name:endsWith("-turn") then - room:setPlayerMark(p, name, 0) - end - end - end - end - end, [fk.AskForPeaches] = function() local dyingPlayer = room:getPlayerById(data.who) while dyingPlayer.hp < 1 do diff --git a/packages/test/init.lua b/packages/test/init.lua index 5038cb24..9b41e82d 100644 --- a/packages/test/init.lua +++ b/packages/test/init.lua @@ -125,8 +125,11 @@ local test_vs = fk.CreateViewAsSkill{ } local test_trig = fk.CreateTriggerSkill{ name = "test_trig", - events = {fk.TurnStart}, - can_trigger = function() p("a") end, + events = {fk.Damage}, + on_use = function(self, event, target, player, data) + player:drawCards(1, self.name) + player.room.logic:breakTurn() + end, } local test2 = General(extension, "mouxusheng", "wu", 4, 4, General.Female) test2.shield = 4