diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 0e975be9..96ded9e2 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -290,6 +290,11 @@ Fk:loadTranslationTable{ ["normal_damage"] = "无属性", ["fire_damage"] = "火属性", ["thunder_damage"] = "雷属性", + + ["phase_judge"] = "判定阶段", + ["phase_draw"] = "摸牌阶段", + ["phase_play"] = "出牌阶段", + ["phase_discard"] = "弃牌阶段", } -- related to sendLog @@ -319,6 +324,9 @@ Fk:loadTranslationTable{ ["$DrawCards"] = "%from 摸了 %arg 张牌 %card", ["$DiscardCards"] = "%from 弃置了 %arg 张牌 %card", + -- phase + ["#PhaseSkipped"] = "%from 跳过了 %arg", + -- useCard ["#UseCard"] = "%from 使用了牌 %card", ["#UseCardToTargets"] = "%from 使用了牌 %card,目标是 %to", @@ -327,6 +335,7 @@ Fk:loadTranslationTable{ ["#ResponsePlayCard"] = "%from 打出了牌 %card", -- judge + ["#StartJudgeReason"] = "%from 开始了 %arg 的判定", ["#InitialJudge"] = "%from 的判定牌为 %card", ["#ChangedJudge"] = "%from 发动“%arg”把 %to 的判定牌改为 %card", ["#JudgeResult"] = "%from 的判定结果为 %card", diff --git a/lua/core/skill_type/active_skill.lua b/lua/core/skill_type/active_skill.lua index 8a00264e..795ce3f1 100644 --- a/lua/core/skill_type/active_skill.lua +++ b/lua/core/skill_type/active_skill.lua @@ -54,4 +54,9 @@ function ActiveSkill:onUse(room, cardUseEvent) end ---@param cardEffectEvent CardEffectEvent | SkillEffectEvent function ActiveSkill:onEffect(room, cardEffectEvent) end +-- Delayed Trick Only +---@param room Room +---@param cardEffectEvent CardEffectEvent | SkillEffectEvent +function ActiveSkill:onNullified(room, cardEffectEvent) end + return ActiveSkill diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 8b0ee2b4..27c4e669 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -90,6 +90,7 @@ end ---@field feasible fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): boolean ---@field on_use fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct): boolean ---@field on_effect fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean +---@field on_nullified fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean ---@param spec ActiveSkillSpec ---@return ActiveSkill @@ -102,6 +103,7 @@ function fk.CreateActiveSkill(spec) if spec.feasible then skill.feasible = spec.feasible end if spec.on_use then skill.onUse = spec.on_use end if spec.on_effect then skill.onEffect = spec.on_effect end + if spec.on_nullified then skill.onNullified = spec.on_nullified end return skill end diff --git a/lua/server/room.lua b/lua/server/room.lua index 13f2e647..3d0335ba 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -812,7 +812,7 @@ function Room:useCard(cardUseEvent) from = from, to = cardUseEvent.tos or {}, }) - if cardUseEvent.tos then + if cardUseEvent.tos and #cardUseEvent.tos > 0 then local to = {} for _, t in ipairs(cardUseEvent.tos) do table.insert(to, t[1]) @@ -927,7 +927,7 @@ function Room:useCard(cardUseEvent) local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1] if not self:getPlayerById(target).dead then local findSameCard = false - for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Equip)) do + for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do if Fk:getCardById(cardId).trueName == Fk:getCardById(cardUseEvent.cardId) then findSameCard = true end @@ -1026,7 +1026,9 @@ end function Room:doCardEffect(cardEffectEvent) for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do if cardEffectEvent.isCancellOut then - self.logic:trigger(fk.CardEffectCancelledOut, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) + if cardEffectEvent.from then + self.logic:trigger(fk.CardEffectCancelledOut, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) + end break end @@ -1038,7 +1040,7 @@ function Room:doCardEffect(cardEffectEvent) break end - if self.logic:trigger(event, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) then + if cardEffectEvent.from and self.logic:trigger(event, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) then return end @@ -1252,7 +1254,7 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam to = target.id end - self.moveCards{ + self:moveCards{ ids = ids, from = self.owner_map[ids[1]], to = to, @@ -1620,10 +1622,19 @@ function Room:judge(data) local who = data.who self.logic:trigger(fk.StartJudge, who, data) data.card = Fk:getCardById(self:getNCards(1)[1]) + + if data.reason ~= "" then + self:sendLog{ + type = "#StartJudgeReason", + from = who.id, + arg = data.reason, + } + end + self:sendLog{ type = "#InitialJudge", from = who.id, - card = {data.card}, + card = {data.card.id}, } self:moveCardTo(data.card, Card.Processing, nil, fk.ReasonPrey) @@ -1632,7 +1643,7 @@ function Room:judge(data) self:sendLog{ type = "#JudgeResult", from = who.id, - card = {data.card}, + card = {data.card.id}, } self.logic:trigger(fk.FinishJudge, who, data) diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 1404bb22..b41db5d0 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -8,6 +8,7 @@ ---@field reply_ready boolean ---@field reply_cancel boolean ---@field phases Phase[] +---@field skipped_phases Phase[] ---@field phase_state table[] ---@field phase_index integer local ServerPlayer = Player:subclass("ServerPlayer") @@ -26,6 +27,7 @@ function ServerPlayer:initialize(_self) self.reply_ready = false self.reply_cancel = false self.phases = {} + self.skipped_phases = {} end ---@param command string @@ -136,6 +138,13 @@ function ServerPlayer:changePhase(from_phase, to_phase) return false end +local phase_name_table = { + [Player.Judge] = "phase_judge", + [Player.Draw] = "phase_draw", + [Player.Play] = "phase_play", + [Player.Discard] = "phase_discard", +} + ---@param phase_table Phase[] function ServerPlayer:play(phase_table) phase_table = phase_table or {} @@ -161,7 +170,7 @@ function ServerPlayer:play(phase_table) for i = 1, #phases do phase_state[i] = { phase = phases[i], - skipped = false + skipped = self.skipped_phases[phases[i]] or false } end @@ -180,7 +189,10 @@ function ServerPlayer:play(phase_table) local logic = self.room.logic self.phase = Player.PhaseNone - local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change) + local skip = phase_state[i].skipped + if not skip then + skip = logic:trigger(fk.EventPhaseChanging, self, phase_change) + end phases[i] = phase_change.to phase_state[i].phase = phases[i] @@ -188,7 +200,7 @@ function ServerPlayer:play(phase_table) room:notifyProperty(self, self, "phase") local cancel_skip = true - if phases[i] ~= Player.NotActive and (phase_state[i].skipped or skip) then + if phases[i] ~= Player.NotActive and (skip) then cancel_skip = logic:trigger(fk.EventPhaseSkipping, self) end @@ -201,7 +213,33 @@ function ServerPlayer:play(phase_table) if self.phase ~= Player.NotActive then logic:trigger(fk.EventPhaseEnd, self) - else break end + else + self.skipped_phases = {} + end + else + room:sendLog{ + type = "#PhaseSkipped", + from = self.id, + arg = phase_name_table[self.phase], + } + end + end +end + +---@param phase Phase +function ServerPlayer:skip(phase) + if not table.contains({ + Player.Judge, + Player.Draw, + Player.Play, + Player.Discard + }, phase) then + return + end + self.skipped_phases[phase] = true + for _, t in ipairs(self.phase_state) do + if t.phase == phase then + t.skipped = true end end end diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index 34308656..1e87744c 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -15,6 +15,8 @@ ---@alias CardEffectEvent { from: integer, tos: TargetGroup, cardId: integer, toCardId: integer|null, responseToEvent: CardUseStruct|null, nullifiedTargets: interger[]|null, extraUse: boolean|null, disresponsiveList: integer[]|null, unoffsetableList: integer[]|null, addtionalDamage: integer|null, customFrom: integer|null, cardIdsResponded: integer[]|null } ---@alias SkillEffectEvent { from: integer, tos: integer[], cards: integer[] } +---@alias JudgeStruct { who: ServerPlayer, card: Card, reason: string } + ---@alias CardMoveReason integer fk.ReasonJustMove = 1 diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index 0953caf7..49bee7fc 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -118,7 +118,22 @@ GameRule = fk.CreateTriggerSkill{ end, [Player.Judge] = function() - + local cards = player:getCardIds(Player.Judge) + for i = #cards, 1, -1 do + local card = Fk:getCardById(cards[i]) + room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name) + + ---@type CardEffectEvent + local effect_data = { + cardId = cards[i], + 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() room:drawCards(player, 2, self.name) diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index e1f2ba01..75a8e594 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -125,6 +125,11 @@ local peachSkill = fk.CreateActiveSkill{ can_use = function(self, player) return player:isWounded() end, + on_use = function(self, room, use) + if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then + use.tos = { { use.from } } + end + end, on_effect = function(self, room, effect) local to = effect.to local from = effect.from @@ -367,10 +372,64 @@ extension:addCards({ amazingGrace:clone(Card.Heart, 4), }) +local lightningSkill = fk.CreateActiveSkill{ + name = "lightning_skill", + can_use = function(self, player) + local judge = player:getCardIds(Player.Judge) + for _, id in ipairs(judge) do + local cd = Fk:getCardById(id) + if cd.name == "lightning" then + return false + end + end + return true + end, + on_use = function(self, room, use) + if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then + use.tos = { { use.from } } + end + end, + on_effect = function(self, room, effect) + local to = room:getPlayerById(effect.to) + local judge = { + who = to, + reason = "lightning", + } + room:judge(judge) + local result = judge.card + if result.suit == Card.Spade and result.number >= 2 and result.number <= 9 then + room:damage{ + to = to.id, + damage = 3, + damageType = fk.ThunderDamage, + skillName = self.name, + } + + room:moveCards{ + ids = { effect.cardId }, + toArea = Card.DiscardPile, + moveReason = fk.ReasonPutIntoDiscardPile + } + else + self:onNullified(room, effect) + end + end, + on_nullified = function(self, room, effect) + local to = room:getPlayerById(effect.to) + local nextp = to:getNextAlive() + room:moveCards{ + ids = { effect.cardId }, + to = nextp.id, + toArea = Card.PlayerJudge, + moveReason = fk.ReasonPut + } + end, +} local lightning = fk.CreateDelayedTrickCard{ name = "lightning", suit = Card.Spade, number = 1, + skill = lightningSkill, } Fk:loadTranslationTable{ ["lightning"] = "闪电", @@ -381,10 +440,53 @@ extension:addCards({ lightning:clone(Card.Heart, 12), }) +local indulgenceSkill = fk.CreateActiveSkill{ + name = "indulgence_skill", + target_filter = function(self, to_select, selected) + if #selected == 0 then + local player = Fk:currentRoom():getPlayerById(to_select) + if Self ~= player then + local judge = player:getCardIds(Player.Judge) + for _, id in ipairs(judge) do + local cd = Fk:getCardById(id) + if cd.name == "indulgence" then + return false + end + end + return true + end + end + return false + end, + feasible = function(self, selected) + return #selected == 1 + end, + on_effect = function(self, room, effect) + local to = room:getPlayerById(effect.to) + local judge = { + who = to, + reason = "indulgence", + } + room:judge(judge) + local result = judge.card + if result.suit ~= Card.Heart then + to:skip(Player.Play) + end + self:onNullified(room, effect) + end, + on_nullified = function(self, room, effect) + room:moveCards{ + ids = { effect.cardId }, + toArea = Card.DiscardPile, + moveReason = fk.ReasonPutIntoDiscardPile + } + end, +} local indulgence = fk.CreateDelayedTrickCard{ name = "indulgence", suit = Card.Spade, number = 6, + skill = indulgenceSkill, } Fk:loadTranslationTable{ ["indulgence"] = "乐不思蜀", diff --git a/src/network/client_socket.cpp b/src/network/client_socket.cpp index 78c51acc..73af6dac 100644 --- a/src/network/client_socket.cpp +++ b/src/network/client_socket.cpp @@ -24,6 +24,7 @@ void ClientSocket::init() this, &ClientSocket::getMessage); connect(socket, &QTcpSocket::errorOccurred, this, &ClientSocket::raiseError); + socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1); } void ClientSocket::connectToHost(const QString &address, ushort port)