diff --git a/Fk/PhotoElement/LimitSkillItem.qml b/Fk/PhotoElement/LimitSkillItem.qml index b2f267d4..4acd5d14 100644 --- a/Fk/PhotoElement/LimitSkillItem.qml +++ b/Fk/PhotoElement/LimitSkillItem.qml @@ -30,7 +30,7 @@ Item { Text { id: x - opacity: skilltype === "limit" ? 1 : 0 + opacity: (skilltype === "limit" || skilltype === "quest") ? 1 : 0 text: "X" font.family: fontLibian.name font.pixelSize: 28 @@ -62,6 +62,12 @@ Item { } else if (skilltype === 'switch') { visible = true; bg.source = SkinBank.LIMIT_SKILL_DIR + (usedtimes < 1 ? 'switch' : 'switch-yin'); + } else if (skilltype === 'quest') { + visible = true + if (usedtimes > 1) { + x.visible = true; + bg.source = SkinBank.LIMIT_SKILL_DIR + "limit-used"; + } } } } diff --git a/lua/client/client.lua b/lua/client/client.lua index 6341b1b1..bfd8fb6e 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -541,7 +541,7 @@ local function updateLimitSkill(pid, skill, times) 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 }) - elseif skill.frequency == Skill.Limited or skill.frequency == Skill.Wake then + 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 end @@ -557,6 +557,10 @@ fk.client_callback["AddSkill"] = function(jsonData) ClientInstance:notifyUI("AddSkill", jsonData) end + if skill.frequency == Skill.Quest then + return + end + updateLimitSkill(id, skill, target:usedSkillTimes(skill_name, Player.HistoryGame)) end @@ -663,7 +667,9 @@ fk.client_callback["AddSkillUseHistory"] = function(jsonData) local playerid, skill_name, time = data[1], data[2], data[3] local player = ClientInstance:getPlayerById(playerid) player:addSkillUseHistory(skill_name, time) - if not Fk.skills[skill_name] then return end + + local skill = Fk.skills[skill_name] + if not skill or skill.frequency == Skill.Quest then return end updateLimitSkill(playerid, Fk.skills[skill_name], player:usedSkillTimes(skill_name, Player.HistoryGame)) end @@ -672,7 +678,9 @@ fk.client_callback["SetSkillUseHistory"] = function(jsonData) local id, skill_name, time, scope = data[1], data[2], data[3], data[4] local player = ClientInstance:getPlayerById(id) player:setSkillUseHistory(skill_name, time, scope) - if not Fk.skills[skill_name] then return end + + local skill = Fk.skills[skill_name] + if not skill or skill.frequency == Skill.Quest then return end updateLimitSkill(id, Fk.skills[skill_name], player:usedSkillTimes(skill_name, Player.HistoryGame)) end @@ -702,6 +710,12 @@ fk.client_callback["ChangeSelf"] = function(jsonData) ClientInstance:notifyUI("ChangeSelf", data.id) end +fk.client_callback["UpdateQuestSkillUI"] = function(jsonData) + local data = json.decode(jsonData) + local player, skillName, usedTimes = data[1], data[2], data[3] + updateLimitSkill(player, Fk.skills[skillName], usedTimes) +end + -- Create ClientInstance (used by Lua) ClientInstance = Client:new() dofile "lua/client/client_util.lua" diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index b76650bf..ce655910 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -286,6 +286,8 @@ function GetSkillData(skill_name) frequency = "limit" elseif skill.frequency == Skill.Wake then frequency = "wake" + elseif skill.frequency == Skill.Quest then + frequency = "quest" end return json.encode{ skill = Fk:translate(skill_name), diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 33a34330..4be1ab2d 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -154,6 +154,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["AskForChoice"] = "选择", ["AskForKingdom"] = "选择势力", ["AskForPindian"] = "拼点", + ["AskForMoveCardInBoard"] = "移动卡牌", ["PlayCard"] = "出牌", ["AskForCardChosen"] = "选牌", diff --git a/lua/core/player.lua b/lua/core/player.lua index 427d3c3d..e4aafb81 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -372,7 +372,7 @@ function Player:getAttackRange() local status_skills = Fk:currentRoom().status_skills[AttackRangeSkill] or {} for _, skill in ipairs(status_skills) do - local correct = skill:getCorrect(self, other) + local correct = skill:getCorrect(self) baseAttackRange = baseAttackRange + correct end @@ -429,6 +429,14 @@ function Player:inMyAttackRange(other) if self == other then return false end + + local status_skills = Fk:currentRoom().status_skills[AttackRangeSkill] or {} + for _, skill in ipairs(status_skills) do + if skill:withinAttackRange(self, other) then + return true + end + end + local baseAttackRange = self:getAttackRange() return self:distanceTo(other) <= baseAttackRange end @@ -743,6 +751,10 @@ function Player:getSwitchSkillState(skillName, afterUse) end function Player:canMoveCardInBoardTo(to, id) + if self == to then + return false + end + local card = Fk:getCardById(id) assert(card.type == Card.TypeEquip or card.sub_type == Card.SubtypeDelayedTrick) @@ -755,4 +767,34 @@ function Player:canMoveCardInBoardTo(to, id) end end +function Player:canMoveCardsInBoardTo(to, flag) + if self == to then + return false + end + + assert(flag == nil or flag == "e" or flag == "j") + + local areas = {} + if flag == "e" then + table.insert(areas, Player.Equip) + elseif flag == "j" then + table.insert(areas, Player.Judge) + else + areas = { Player.Equip, Player.Judge } + end + + for _, cardId in ipairs(self:getCardIds(areas)) do + if self:canMoveCardInBoardTo(to, cardId) then + return true + end + end + + return false +end + +function Player:getQuestSkillState(skillName) + local questSkillState = self:getMark(MarkEnum.QuestSkillPreName .. skillName) + return type(questSkillState) == "string" and questSkillState or nil +end + return Player diff --git a/lua/core/skill.lua b/lua/core/skill.lua index 26941b0d..4cc6e406 100644 --- a/lua/core/skill.lua +++ b/lua/core/skill.lua @@ -22,6 +22,7 @@ Skill.NotFrequent = 2 Skill.Compulsory = 3 Skill.Limited = 4 Skill.Wake = 5 +Skill.Quest = 6 --- 构造函数,不可随意调用。 ---@param name string @ 技能名 diff --git a/lua/core/skill_type/active.lua b/lua/core/skill_type/active.lua index f71ebdcf..ad717298 100644 --- a/lua/core/skill_type/active.lua +++ b/lua/core/skill_type/active.lua @@ -120,11 +120,11 @@ function ActiveSkill:getMaxCardNum() end end -function ActiveSkill:getDistanceLimit(player, card) +function ActiveSkill:getDistanceLimit(player, card, to) local ret = self.distance_limit or 0 local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or {} for _, skill in ipairs(status_skills) do - local correct = skill:getDistanceLimit(player, self, card) + local correct = skill:getDistanceLimit(player, self, card, to) if correct == nil then correct = 0 end ret = ret + correct end diff --git a/lua/core/skill_type/attack_range.lua b/lua/core/skill_type/attack_range.lua index 1c8a62bd..51d7dafe 100644 --- a/lua/core/skill_type/attack_range.lua +++ b/lua/core/skill_type/attack_range.lua @@ -6,8 +6,12 @@ local AttackRangeSkill = StatusSkill:subclass("AttackRangeSkill") ---@param from Player ---@param to Player ---@return integer -function AttackRangeSkill:getCorrect(from, to) +function AttackRangeSkill:getCorrect(from) return 0 end +function AttackRangeSkill:withinAttackRange(from, to) + return false +end + return AttackRangeSkill diff --git a/lua/core/skill_type/target_mod.lua b/lua/core/skill_type/target_mod.lua index db2b13d3..13e9614b 100644 --- a/lua/core/skill_type/target_mod.lua +++ b/lua/core/skill_type/target_mod.lua @@ -7,14 +7,14 @@ local TargetModSkill = StatusSkill:subclass("TargetModSkill") ---@param card_skill ActiveSkill ---@param scope integer ---@param card Card -function TargetModSkill:getResidueNum(player, card_skill, scope, card) +function TargetModSkill:getResidueNum(player, card_skill, scope, card, to) return 0 end ---@param player Player ---@param card_skill ActiveSkill ---@param card Card -function TargetModSkill:getDistanceLimit(player, card_skill, card) +function TargetModSkill:getDistanceLimit(player, card_skill, card, to) return 0 end diff --git a/lua/core/skill_type/usable_skill.lua b/lua/core/skill_type/usable_skill.lua index 24748c3f..be19cf68 100644 --- a/lua/core/skill_type/usable_skill.lua +++ b/lua/core/skill_type/usable_skill.lua @@ -12,12 +12,12 @@ function UsableSkill:initialize(name, frequency) self.max_use_time = {9999, 9999, 9999, 9999} end -function UsableSkill:getMaxUseTime(player, scope, card) +function UsableSkill:getMaxUseTime(player, scope, card, to) scope = scope or Player.HistoryTurn local ret = self.max_use_time[scope] local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or {} for _, skill in ipairs(status_skills) do - local correct = skill:getResidueNum(player, self, scope, card) + local correct = skill:getResidueNum(player, self, scope, card, to) if correct == nil then correct = 0 end ret = ret + correct end diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index c834c724..21159154 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -300,17 +300,23 @@ function fk.CreateProhibitSkill(spec) end ---@class AttackRangeSpec: StatusSkillSpec ----@field public correct_func fun(self: AttackRangeSkill, from: Player, to: Player) +---@field public correct_func fun(self: AttackRangeSkill, from: Player) +---@field public within_func fun(self: AttackRangeSkill, from: Player, to: Player) ---@param spec AttackRangeSpec ---@return AttackRangeSkill function fk.CreateAttackRangeSkill(spec) assert(type(spec.name) == "string") - assert(type(spec.correct_func) == "function") + assert(type(spec.correct_func) == "function" or type(spec.within_func) == "function") local skill = AttackRangeSkill:new(spec.name) readStatusSpecToSkill(skill, spec) - skill.getCorrect = spec.correct_func + if spec.correct_func then + skill.getCorrect = spec.correct_func + end + if spec.within_func then + skill.withinAttackRange = spec.within_func + end return skill end @@ -338,8 +344,8 @@ function fk.CreateMaxCardsSkill(spec) end ---@class TargetModSpec: StatusSkillSpec ----@field public residue_func fun(self: TargetModSkill, player: Player, skill: ActiveSkill, scope: integer, card: Card) ----@field public distance_limit_func fun(self: TargetModSkill, player: Player, skill: ActiveSkill, card: Card) +---@field public residue_func fun(self: TargetModSkill, player: Player, skill: ActiveSkill, scope: integer, card: Card, to: Player) +---@field public distance_limit_func fun(self: TargetModSkill, player: Player, skill: ActiveSkill, card: Card, to: Player) ---@field public extra_target_func fun(self: TargetModSkill, player: Player, skill: ActiveSkill, card: Card) ---@param spec TargetModSpec diff --git a/lua/server/event.lua b/lua/server/event.lua index f1849411..2c31c4a2 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -43,7 +43,9 @@ fk.FinishJudge = 27 fk.RoundStart = 28 fk.RoundEnd = 29 +fk.BeforeTurnOver = 79 fk.TurnedOver = 30 +fk.BeforeChainStateChange = 80 fk.ChainStateChanged = 31 fk.PreDamage = 32 @@ -106,4 +108,7 @@ fk.CardShown = 77 -- 78 = GamePrepared -fk.NumOfEvents = 79 +-- 79 = BeforeTurnOver +-- 80 = BeforeChainStateChange + +fk.NumOfEvents = 81 diff --git a/lua/server/events/hp.lua b/lua/server/events/hp.lua index df031549..c01bdc71 100644 --- a/lua/server/events/hp.lua +++ b/lua/server/events/hp.lua @@ -101,6 +101,14 @@ end GameEvent.functions[GameEvent.Damage] = function(self) local damageStruct = table.unpack(self.data) local self = self.room + if damageStruct.card and damageStruct.skillName == damageStruct.card.name .. "_skill" and not damageStruct.chain then + local cardEffectData = self.logic:getCurrentEvent():findParent(GameEvent.CardEffect) + if cardEffectData then + local cardEffectEvent = cardEffectData.data[1] + damageStruct.damage = damageStruct.damage + (cardEffectEvent.additionalDamage or 0) + end + end + if damageStruct.damage < 1 then return false end @@ -131,6 +139,15 @@ GameEvent.functions[GameEvent.Damage] = function(self) return false end + if damageStruct.card and damageStruct.damage > 0 then + local parentUseData = self.logic:getCurrentEvent():findParent(GameEvent.UseCard) + if parentUseData then + local cardUseEvent = parentUseData.data[1] + cardUseEvent.damageDealt = cardUseEvent.damageDealt or {} + cardUseEvent.damageDealt[damageStruct.to.id] = (cardUseEvent.damageDealt[damageStruct.to.id] or 0) + damageStruct.damage + end + end + -- 先扣减护甲,再扣体力值 local shield_to_lose = math.min(damageStruct.damage, damageStruct.to.shield) self:changeShield(damageStruct.to, -shield_to_lose) @@ -191,6 +208,14 @@ end GameEvent.functions[GameEvent.Recover] = function(self) local recoverStruct = table.unpack(self.data) local self = self.room + if recoverStruct.card then + local cardEffectData = self.logic:getCurrentEvent():findParent(GameEvent.CardEffect) + if cardEffectData then + local cardEffectEvent = cardEffectData.data[1] + recoverStruct.num = recoverStruct.num + (cardEffectEvent.additionalRecover or 0) + end + end + if recoverStruct.num < 1 then return false end diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index f80474dc..8706c6eb 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -18,6 +18,7 @@ dofile "lua/server/events/movecard.lua" GameEvent.UseCard = 9 GameEvent.RespondCard = 10 +GameEvent.CardEffect = 20 dofile "lua/server/events/usecard.lua" GameEvent.SkillEffect = 11 @@ -51,6 +52,7 @@ local eventTranslations = { [GameEvent.MoveCards] = "GameEvent.MoveCards", [GameEvent.UseCard] = "GameEvent.UseCard", [GameEvent.RespondCard] = "GameEvent.RespondCard", + [GameEvent.CardEffect] = "GameEvent.CardEffect", [GameEvent.SkillEffect] = "GameEvent.SkillEffect", [GameEvent.Judge] = "GameEvent.Judge", [GameEvent.DrawInitial] = "GameEvent.DrawInitial", diff --git a/lua/server/events/usecard.lua b/lua/server/events/usecard.lua index 68af1924..d7045f29 100644 --- a/lua/server/events/usecard.lua +++ b/lua/server/events/usecard.lua @@ -275,3 +275,39 @@ GameEvent.cleaners[GameEvent.RespondCard] = function(self) }) end end + +GameEvent.functions[GameEvent.CardEffect] = function(self) + local cardEffectEvent = table.unpack(self.data) + local self = self.room + + for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do + if cardEffectEvent.isCancellOut then + local user = cardEffectEvent.from and self:getPlayerById(cardEffectEvent.from) or nil + if self.logic:trigger(fk.CardEffectCancelledOut, user, cardEffectEvent) then + cardEffectEvent.isCancellOut = false + else + self.logic:breakEvent() + end + end + + if + not cardEffectEvent.toCard and + ( + not (self:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to) + or #self:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0 + ) + then + self.logic:breakEvent() + end + + if table.contains((cardEffectEvent.nullifiedTargets or {}), cardEffectEvent.to) then + self.logic:breakEvent() + end + + if cardEffectEvent.from and self.logic:trigger(event, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) then + self.logic:breakEvent() + end + + self:handleCardEffect(event, cardEffectEvent) + end +end diff --git a/lua/server/mark_enum.lua b/lua/server/mark_enum.lua index d809081a..573b3d5c 100644 --- a/lua/server/mark_enum.lua +++ b/lua/server/mark_enum.lua @@ -5,4 +5,16 @@ MarkEnum = {} ---@field StraightToWake string @ 跳过觉醒标记(值为技能名通过+连接) MarkEnum.StraightToWake = "_straight_to_wake" +---@field SwithSkillPreName string @ 转换技状态标记前缀(整体为前缀+转换技技能) MarkEnum.SwithSkillPreName = "__switcher_" +---@field SwithSkillPreName string @ 转换技状态标记前缀(整体为前缀+转换技技能) +MarkEnum.QuestSkillPreName = "__questPre_" + +---@field AddMaxCards string @ 增加标记值数量的手牌上限 +MarkEnum.AddMaxCards = "AddMaxCards" +---@field AddMaxCardsInTurn string @ 于本回合内增加标记值数量的手牌上限 +MarkEnum.AddMaxCardsInTurn = "AddMaxCards-turn" +---@field MinusMaxCards string @ 减少标记值数量的手牌上限 +MarkEnum.MinusMaxCards = "MinusMaxCards" +---@field AddMaxCards string @ 于本回合内减少标记值数量的手牌上限 +MarkEnum.MinusMaxCardsInTurn = "MinusMaxCards-turn" diff --git a/lua/server/room.lua b/lua/server/room.lua index d3941e78..4be3b672 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -927,7 +927,7 @@ Room.askForUseViewAsSkill = Room.askForUseActiveSkill ---@param pattern string @ 弃牌需要符合的规则 ---@param prompt string @ 提示信息 ---@return integer[] @ 弃掉的牌的id列表,可能是空的 -function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt) +function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt, skipDiscard) cancelable = cancelable or false pattern = pattern or "" @@ -975,7 +975,10 @@ function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, can toDiscard = table.random(canDiscards, minNum) end - self:throwCard(toDiscard, skillName, player, player) + if not skipDiscard then + self:throwCard(toDiscard, skillName, player, player) + end + return toDiscard end @@ -1390,6 +1393,23 @@ end ---@param event_data CardEffectEvent|nil @ 事件信息 ---@return CardUseStruct | nil @ 返回关于本次使用牌的数据,以便后续处理 function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extra_data, event_data) + if event_data and (event_data.disresponsive or table.contains(event_data.disresponsiveList or {}, player.id)) then + return nil + end + + if event_data and event_data.prohibitedCardNames and card_name then + local splitedCardNames = card_name:split(",") + splitedCardNames = table.filter(splitedCardNames, function(name) + return not table.contains(event_data.prohibitedCardNames, name) + end) + + if #splitedCardNames == 0 then + return nil + end + + card_name = table.concat(splitedCardNames, ",") + end + local command = "AskForUseCard" self:notifyMoveFocus(player, card_name) cancelable = cancelable or false @@ -1429,8 +1449,13 @@ end ---@param prompt string @ 提示信息 ---@param cancelable boolean @ 能否取消 ---@param extra_data any @ 额外数据 +---@param effectData CardEffectEvent @ 关联的卡牌生效流程 ---@return Card | nil @ 打出的牌 -function Room:askForResponse(player, card_name, pattern, prompt, cancelable, extra_data) +function Room:askForResponse(player, card_name, pattern, prompt, cancelable, extra_data, effectData) + if effectData and (effectData.disresponsive or table.contains(effectData.disresponsiveList or {}, player.id)) then + return nil + end + local command = "AskForResponseCard" self:notifyMoveFocus(player, card_name) cancelable = cancelable or false @@ -1576,44 +1601,63 @@ end ---@field targetOne ServerPlayer ---@field targetTwo ServerPlayer ---@field skillName string +---@field flag string|null +---@field moveFrom ServerPlayer|null ---@return cardId -function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName) +function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName, flag, moveFrom) + if flag then + assert(flag == "e" or flag == "j") + end + local cards = {} local cardsPosition = {} - for _, equipId in ipairs(targetOne:getCardIds(Player.Equip)) do - if targetOne:canMoveCardInBoardTo(targetTwo, equipId) then - table.insert(cards, equipId) + + if not flag or flag == "e" then + if not moveFrom or moveFrom == targetOne then + for _, equipId in ipairs(targetOne:getCardIds(Player.Equip)) do + if targetOne:canMoveCardInBoardTo(targetTwo, equipId) then + table.insert(cards, equipId) + end + end end - end - for _, equipId in ipairs(targetTwo:getCardIds(Player.Equip)) do - if targetTwo:canMoveCardInBoardTo(targetOne, equipId) then - table.insert(cards, equipId) + if not moveFrom or moveFrom == targetTwo then + for _, equipId in ipairs(targetTwo:getCardIds(Player.Equip)) do + if targetTwo:canMoveCardInBoardTo(targetOne, equipId) then + table.insert(cards, equipId) + end + end + end + + if #cards > 0 then + table.sort(cards, function(prev, next) + local prevSubType = Fk:getCardById(prev).sub_type + local nextSubType = Fk:getCardById(next).sub_type + + return prevSubType < nextSubType + end) + + for _, id in ipairs(cards) do + table.insert(cardsPosition, self:getCardOwner(id) == targetOne and 0 or 1) + end end end - if #cards > 0 then - table.sort(cards, function(prev, next) - local prevSubType = Fk:getCardById(prev).sub_type - local nextSubType = Fk:getCardById(next).sub_type - - return prevSubType < nextSubType - end) - - for _, id in ipairs(cards) do - table.insert(cardsPosition, self:getCardOwner(id) == targetOne and 0 or 1) + if not flag or flag == "j" then + if not moveFrom or moveFrom == targetOne then + for _, trickId in ipairs(targetOne:getCardIds(Player.Judge)) do + if targetOne:canMoveCardInBoardTo(targetTwo, trickId) then + table.insert(cards, trickId) + table.insert(cardsPosition, 0) + end + end end - end - - for _, trickId in ipairs(targetOne:getCardIds(Player.Judge)) do - if targetOne:canMoveCardInBoardTo(targetTwo, trickId) then - table.insert(cards, trickId) - table.insert(cardsPosition, 0) - end - end - for _, trickId in ipairs(targetTwo:getCardIds(Player.Judge)) do - if targetTwo:canMoveCardInBoardTo(targetOne, trickId) then - table.insert(cards, trickId) - table.insert(cardsPosition, 1) + if not moveFrom or moveFrom == targetTwo then + for _, trickId in ipairs(targetTwo:getCardIds(Player.Judge)) do + if targetTwo:canMoveCardInBoardTo(targetOne, trickId) then + table.insert(cards, trickId) + table.insert(cardsPosition, 1) + end + end end end @@ -1648,6 +1692,42 @@ function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName) ) end +--- 询问一名玩家从targets中选择若干名玩家出来。 +---@param player ServerPlayer @ 要做选择的玩家 +---@param prompt string @ 提示信息 +---@param skillName string @ 技能名 +---@param cancelable boolean|null @ 是否可以取消选择 +---@param flag string|null @ 限定可移动的区域,值为‘e’或‘j’ +---@return integer[] @ 选择的玩家id列表,可能为空 +function Room:askForChooseToMoveCardInBoard(player, prompt, skillName, cancelable, flag) + if flag then + assert(flag == "e" or flag == "j") + end + cancelable = (not cancelable) and false or true + + local data = { + flag = flag, + skillName = skillName, + } + local _, ret = self:askForUseActiveSkill( + player, + "choose_players_to_move_card_in_board", + prompt or "", + cancelable, + data + ) + + if ret then + return ret.targets + else + if cancelable then + return {} + else + return self:canMoveCardInBoard(flag) + end + end +end + ------------------------------------------------------------------------ -- 使用牌 ------------------------------------------------------------------------ @@ -1698,6 +1778,7 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators) tos = aimGroup, firstTarget = firstTarget, additionalDamage = cardUseEvent.additionalDamage, + additionalRecover = cardUseEvent.additionalRecover, extra_data = cardUseEvent.extra_data, } @@ -1884,7 +1965,9 @@ function Room:doCardUseEffect(cardUseEvent) disresponsiveList = cardUseEvent.disresponsiveList, unoffsetableList = cardUseEvent.unoffsetableList, additionalDamage = cardUseEvent.additionalDamage, + additionalRecover = cardUseEvent.additionalRecover, cardIdsResponded = cardUseEvent.nullifiedTargets, + prohibitedCardNames = cardUseEvent.prohibitedCardNames, extra_data = cardUseEvent.extra_data, } @@ -1905,6 +1988,7 @@ function Room:doCardUseEffect(cardUseEvent) cardEffectEvent.subTargets = curAimEvent.subTargets cardEffectEvent.additionalDamage = curAimEvent.additionalDamage + cardEffectEvent.additionalRecover = curAimEvent.additionalRecover if curAimEvent.disresponsiveList then for _, disresponsivePlayer in ipairs(curAimEvent.disresponsiveList) do @@ -1938,118 +2022,109 @@ end --- 对卡牌效果数据进行生效 ---@param cardEffectEvent CardEffectEvent function Room:doCardEffect(cardEffectEvent) - for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do - if cardEffectEvent.isCancellOut then - local user = cardEffectEvent.from and self:getPlayerById(cardEffectEvent.from) or nil - if self.logic:trigger(fk.CardEffectCancelledOut, user, cardEffectEvent) then - cardEffectEvent.isCancellOut = false - else - break + return execGameEvent(GameEvent.CardEffect, cardEffectEvent) +end + +---@param cardEffectEvent CardEffectEvent +function Room:handleCardEffect(event, cardEffectEvent) + if event == fk.PreCardEffect then + if cardEffectEvent.card.skill:aboutToEffect(self, cardEffectEvent) then return end + if + cardEffectEvent.card.trueName == "slash" and + not (cardEffectEvent.unoffsetable or table.contains(cardEffectEvent.unoffsetableList or {}, cardEffectEvent.to)) + then + local loopTimes = 1 + if cardEffectEvent.fixedResponseTimes then + if type(cardEffectEvent.fixedResponseTimes) == "table" then + loopTimes = cardEffectEvent.fixedResponseTimes["jink"] or 1 + elseif type(cardEffectEvent.fixedResponseTimes) == "number" then + loopTimes = cardEffectEvent.fixedResponseTimes + end end - end - - if not cardEffectEvent.toCard and (not (self:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to) or #self:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0) then - break - end - - if table.contains((cardEffectEvent.nullifiedTargets or {}), cardEffectEvent.to) then - break - end - - if cardEffectEvent.from and self.logic:trigger(event, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) then - return - end - - if event == fk.PreCardEffect then - if cardEffectEvent.card.skill:aboutToEffect(self, cardEffectEvent) then return end - if cardEffectEvent.card.trueName == "slash" and - not ( - cardEffectEvent.disresponsive or - cardEffectEvent.unoffsetable or - table.contains(cardEffectEvent.disresponsiveList or {}, cardEffectEvent.to) or - table.contains(cardEffectEvent.unoffsetableList or {}, cardEffectEvent.to) - ) then - local loopTimes = 1 - if cardEffectEvent.fixedResponseTimes then - if type(cardEffectEvent.fixedResponseTimes) == "table" then - loopTimes = cardEffectEvent.fixedResponseTimes["jink"] or 1 - elseif type(cardEffectEvent.fixedResponseTimes) == "number" then - loopTimes = cardEffectEvent.fixedResponseTimes - end - end - - for i = 1, loopTimes do - local to = self:getPlayerById(cardEffectEvent.to) - local prompt = "" - if cardEffectEvent.from then - prompt = "#slash-jink:" .. cardEffectEvent.from .. "::" .. 1 - end - - local use = self:askForUseCard( - to, - "jink", - nil, - prompt, - true, - nil, - cardEffectEvent - ) - if use then - use.toCard = cardEffectEvent.card - use.responseToEvent = cardEffectEvent - self:useCard(use) - end - - if not cardEffectEvent.isCancellOut then - break - end - - cardEffectEvent.isCancellOut = i == loopTimes - end - elseif cardEffectEvent.card.type == Card.TypeTrick and - not cardEffectEvent.disresponsive then - local players = {} - for _, p in ipairs(self.alive_players) do - local cards = p:getCardIds(Player.Hand) - for _, cid in ipairs(cards) do - if Fk:getCardById(cid).name == "nullification" and - not table.contains(cardEffectEvent.disresponsiveList or {}, p.id) then - table.insert(players, p) - break - end - end - if not table.contains(players, p) then - for _, s in ipairs(p.player_skills) do - if s.pattern and Exppattern:Parse("nullification"):matchExp(s.pattern) - and not table.contains(cardEffectEvent.disresponsiveList or {}, p.id) then - table.insert(players, p) - break - end - end - end - end + for i = 1, loopTimes do + local to = self:getPlayerById(cardEffectEvent.to) local prompt = "" - if cardEffectEvent.to then - prompt = "#AskForNullification::" .. cardEffectEvent.to .. ":" .. cardEffectEvent.card.name - elseif cardEffectEvent.from then - prompt = "#AskForNullificationWithoutTo:" .. cardEffectEvent.from .. "::" .. cardEffectEvent.card.name + if cardEffectEvent.from then + prompt = "#slash-jink:" .. cardEffectEvent.from .. "::" .. 1 end - local use = self:askForNullification(players, nil, nil, prompt) + + local use = self:askForUseCard( + to, + "jink", + nil, + prompt, + true, + nil, + cardEffectEvent + ) if use then use.toCard = cardEffectEvent.card use.responseToEvent = cardEffectEvent self:useCard(use) end + + if not cardEffectEvent.isCancellOut then + break + end + + cardEffectEvent.isCancellOut = i == loopTimes + end + elseif + cardEffectEvent.card.type == Card.TypeTrick and + not (cardEffectEvent.disresponsive or cardEffectEvent.unoffsetable) and + not table.contains(cardEffectEvent.prohibitedCardNames or {}, "nullification") + then + local players = {} + for _, p in ipairs(self.alive_players) do + local cards = p:getCardIds(Player.Hand) + for _, cid in ipairs(cards) do + if + Fk:getCardById(cid).name == "nullification" and + not ( + table.contains(cardEffectEvent.disresponsiveList or {}, p.id) or + table.contains(cardEffectEvent.unoffsetableList or {}, p.id) + ) + then + table.insert(players, p) + break + end + end + if not table.contains(players, p) then + for _, s in ipairs(p.player_skills) do + if + s.pattern and + Exppattern:Parse("nullification"):matchExp(s.pattern) and + not ( + table.contains(cardEffectEvent.disresponsiveList or {}, p.id) or + table.contains(cardEffectEvent.unoffsetableList or {}, p.id) + ) + then + table.insert(players, p) + break + end + end + end + end + + local prompt = "" + if cardEffectEvent.to then + prompt = "#AskForNullification::" .. cardEffectEvent.to .. ":" .. cardEffectEvent.card.name + elseif cardEffectEvent.from then + prompt = "#AskForNullificationWithoutTo:" .. cardEffectEvent.from .. "::" .. cardEffectEvent.card.name + end + local use = self:askForNullification(players, nil, nil, prompt) + if use then + use.toCard = cardEffectEvent.card + use.responseToEvent = cardEffectEvent + self:useCard(use) end end - - if event == fk.CardEffecting then - if cardEffectEvent.card.skill then - execGameEvent(GameEvent.SkillEffect, function () - cardEffectEvent.card.skill:onEffect(self, cardEffectEvent) - end) - end + elseif event == fk.CardEffecting then + if cardEffectEvent.card.skill then + execGameEvent(GameEvent.SkillEffect, function () + cardEffectEvent.card.skill:onEffect(self, cardEffectEvent) + end) end end end @@ -2687,6 +2762,44 @@ function Room:getCardsFromPileByRule(pattern, num, fromPile) return cardPack end +---@param flag string|null +---@param players ServerPlayer[]|null +---@return PlayerId[] @ 可能为空 +function Room:canMoveCardInBoard(flag, players) + if flag then + assert(flag == "e" or flag == "j") + end + + players = players or self.alive_players + + local targets = {} + table.find(players, function(p) + local canMoveTo = table.find(players, function(another) + return p ~= another and p:canMoveCardsInBoardTo(another, flag) + end) + + if canMoveTo then + targets = {p.id, canMoveTo.id} + end + return canMoveTo + end) + + return targets +end + +function Room:updateQuestSkillState(player, skillName, failed) + assert(Fk.skills[skillName].frequency == Skill.Quest) + + self:setPlayerMark(player, MarkEnum.QuestSkillPreName .. skillName, failed and "failed" or "succeed") + local updateValue = failed and 2 or 1 + + self:doBroadcastNotify("UpdateQuestSkillUI", json.encode{ + player.id, + skillName, + updateValue, + }) +end + function CreateRoom(_room) RoomInstance = Room:new(_room) end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index b024d758..7930407a 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -324,6 +324,10 @@ function ServerPlayer:getNextAlive() end function ServerPlayer:turnOver() + if self.room.logic:trigger(fk.BeforeTurnOver, self) then + return + end + self.faceup = not self.faceup self.room:broadcastProperty(self, "faceup") @@ -332,6 +336,7 @@ function ServerPlayer:turnOver() from = self.id, arg = self.faceup and "face_up" or "face_down", } + self.room.logic:trigger(fk.TurnedOver, self) end @@ -593,6 +598,10 @@ end ---@param chained boolean function ServerPlayer:setChainState(chained) + if self.room.logic:trigger(fk.BeforeChainStateChange, self) then + return + end + self.chained = chained self.room:broadcastProperty(self, "chained") self.room:sendLog{ @@ -600,6 +609,8 @@ function ServerPlayer:setChainState(chained) from = self.id, arg = self.chained and "chained" or "not-chained" } + + self.room.logic:trigger(fk.ChainStateChanged, self) end ---@param from ServerPlayer diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index cdfc7a16..dd0aedd5 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -93,8 +93,11 @@ fk.IceDamage = 4 ---@field public disresponsiveList integer[]|null ---@field public unoffsetableList integer[]|null ---@field public additionalDamage integer|null +---@field public additionalRecover integer|null ---@field public customFrom integer|null ---@field public cardsResponded Card[]|null +---@field public prohibitedCardNames string[]|null +---@field public damageDealt table|null ---@class AimStruct ---@field public from integer @@ -106,6 +109,7 @@ fk.IceDamage = 4 ---@field public nullifiedTargets integer[]|null ---@field public firstTarget boolean ---@field public additionalDamage integer|null +---@field public additionalRecover integer|null ---@field public disresponsive boolean|null ---@field public unoffsetableList boolean|null ---@field public additionalResponseTimes table|integer|null @@ -124,6 +128,7 @@ fk.IceDamage = 4 ---@field public disresponsiveList integer[]|null ---@field public unoffsetableList integer[]|null ---@field public additionalDamage integer|null +---@field public additionalRecover integer|null ---@field public customFrom integer|null ---@field public cardsResponded Card[]|null ---@field public disresponsive boolean|null @@ -131,6 +136,7 @@ fk.IceDamage = 4 ---@field public isCancellOut boolean|null ---@field public fixedResponseTimes table|integer|null ---@field public fixedAddTimesResponsors integer[] +---@field public prohibitedCardNames string[]|null ---@class SkillEffectEvent ---@field public from integer diff --git a/packages/maneuvering/init.lua b/packages/maneuvering/init.lua index 6788710e..47b2c0e7 100644 --- a/packages/maneuvering/init.lua +++ b/packages/maneuvering/init.lua @@ -18,7 +18,7 @@ local thunderSlashSkill = fk.CreateActiveSkill{ from = room:getPlayerById(from), to = room:getPlayerById(to), card = effect.card, - damage = 1 + (effect.additionalDamage or 0), + damage = 1, damageType = fk.ThunderDamage, skillName = self.name }) @@ -56,7 +56,7 @@ local fireSlashSkill = fk.CreateActiveSkill{ from = room:getPlayerById(from), to = room:getPlayerById(to), card = effect.card, - damage = 1 + (effect.additionalDamage or 0), + damage = 1, damageType = fk.FireDamage, skillName = self.name }) @@ -80,7 +80,7 @@ local analepticSkill = fk.CreateActiveSkill{ name = "analeptic_skill", max_turn_use_time = 1, can_use = function(self, player, card) - return player:usedCardTimes("analeptic", Player.HistoryTurn) < self:getMaxUseTime(Self, Player.HistoryTurn, card) + return player:usedCardTimes("analeptic", Player.HistoryTurn) < self:getMaxUseTime(Self, Player.HistoryTurn, card, Self) end, on_use = function(self, room, use) if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then @@ -250,7 +250,7 @@ local fireAttackSkill = fk.CreateActiveSkill{ from = from, to = to, card = cardEffectEvent.card, - damage = 1 + (cardEffectEvent.additionalDamage or 0), + damage = 1, damageType = fk.FireDamage, skillName = self.name }) @@ -276,7 +276,7 @@ local supplyShortageSkill = fk.CreateActiveSkill{ local player = Fk:currentRoom():getPlayerById(to_select) if Self ~= player then return not player:hasDelayedTrick("supply_shortage") and - Self:distanceTo(player) <= self:getDistanceLimit(Self, card) + Self:distanceTo(player) <= self:getDistanceLimit(Self, card, Fk:currentRoom():getPlayerById(to_select)) end end return false diff --git a/packages/standard/aux_skills.lua b/packages/standard/aux_skills.lua index e5387e9d..989c9c26 100644 --- a/packages/standard/aux_skills.lua +++ b/packages/standard/aux_skills.lua @@ -74,7 +74,27 @@ local maxCardsSkill = fk.CreateMaxCardsSkill{ name = "max_cards_skill", global = true, correct_func = function(self, player) - return player:getMark("AddMaxCards") + player:getMark("AddMaxCards-turn") - player:getMark("MinusMaxCards") - player:getMark("MinusMaxCards-turn") + return + player:getMark(MarkEnum.AddMaxCards) + + player:getMark(MarkEnum.AddMaxCardsInTurn) - + player:getMark(MarkEnum.MinusMaxCards) - + player:getMark(MarkEnum.MinusMaxCardsInTurn) + end, +} + +local choosePlayersToMoveCardInBoardSkill = fk.CreateActiveSkill{ + name = "choose_players_to_move_card_in_board", + target_num = 2, + card_filter = function(self, to_select) + return false + end, + target_filter = function(self, to_select, selected, cards) + local target = Fk:currentRoom():getPlayerById(to_select) + if #selected > 0 then + return Fk:currentRoom():getPlayerById(selected[1]):canMoveCardsInBoardTo(target, self.flag) + end + + return #target:getCardIds({ Player.Equip, Player.Judge }) > 0 end, } @@ -83,4 +103,5 @@ AuxSkills = { chooseCardsSkill, choosePlayersSkill, maxCardsSkill, + choosePlayersToMoveCardInBoardSkill, } diff --git a/packages/standard/i18n/zh_CN.lua b/packages/standard/i18n/zh_CN.lua index db842004..8694d674 100644 --- a/packages/standard/i18n/zh_CN.lua +++ b/packages/standard/i18n/zh_CN.lua @@ -418,6 +418,7 @@ Fk:loadTranslationTable{ ["discard_skill"] = "弃牌", ["choose_cards_skill"] = "选牌", ["choose_players_skill"] = "选择角色", + ["choose_players_to_move_card_in_board"] = "选择角色", ["game_rule"] = "弃牌阶段", } diff --git a/packages/standard/init.lua b/packages/standard/init.lua index 1ab74b47..2edd8a14 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -1085,9 +1085,7 @@ local lijian = fk.CreateActiveSkill{ new_use.from = use.tos[2] new_use.tos = { { use.tos[1] } } new_use.card = duel - new_use.unoffsetableList = table.map(room:getAlivePlayers(), function(e) - return e.id - end) + new_use.prohibitedCardNames = { "nullification" } room:useCard(new_use) end, } diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index 7a6c6af0..753603e6 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -30,13 +30,16 @@ local slashSkill = fk.CreateActiveSkill{ max_phase_use_time = 1, target_num = 1, can_use = function(self, player, card) - return player:usedCardTimes("slash", Player.HistoryPhase) < self:getMaxUseTime(Self, Player.HistoryPhase, card) + return + table.find(Fk:currentRoom().alive_players, function(p) + return player:usedCardTimes("slash", Player.HistoryPhase) < self:getMaxUseTime(Self, Player.HistoryPhase, card, p) + end) end, target_filter = function(self, to_select, selected, _, card) if #selected < self:getMaxTargetNum(Self, card) then local player = Fk:currentRoom():getPlayerById(to_select) return Self ~= player and - (self:getDistanceLimit(Self, card) -- for no distance limit for slash + (self:getDistanceLimit(Self, card, Fk:currentRoom():getPlayerById(to_select)) -- for no distance limit for slash + Self:getAttackRange() >= Self:distanceTo(player)) end @@ -49,7 +52,7 @@ local slashSkill = fk.CreateActiveSkill{ from = room:getPlayerById(from), to = room:getPlayerById(to), card = effect.card, - damage = 1 + (effect.additionalDamage or 0), + damage = 1, damageType = fk.NormalDamage, skillName = self.name }) @@ -224,8 +227,10 @@ local snatchSkill = fk.CreateActiveSkill{ target_filter = function(self, to_select, selected, _, card) if #selected == 0 then local player = Fk:currentRoom():getPlayerById(to_select) - return Self ~= player and Self:distanceTo(player) <= self:getDistanceLimit(Self, card) -- for no distance limit for snatch - and not player:isAllNude() + return + Self ~= player and + Self:distanceTo(player) <= self:getDistanceLimit(Self, card, Fk:currentRoom():getPlayerById(to_select)) and -- for no distance limit for snatch + not player:isAllNude() end end, target_num = 1, @@ -276,10 +281,6 @@ local duelSkill = fk.CreateActiveSkill{ local currentResponser = to while currentResponser:isAlive() do - if effect.disresponsive or table.contains(effect.disresponsiveList or {}, currentResponser.id) then - break - end - local loopTimes = 1 if effect.fixedResponseTimes then local canFix = currentResponser == to @@ -298,7 +299,7 @@ local duelSkill = fk.CreateActiveSkill{ local cardResponded for i = 1, loopTimes do - cardResponded = room:askForResponse(currentResponser, 'slash') + cardResponded = room:askForResponse(currentResponser, 'slash', nil, nil, false, nil, effect) if cardResponded then room:responseCard({ from = currentResponser.id, @@ -323,7 +324,7 @@ local duelSkill = fk.CreateActiveSkill{ from = responsers[currentTurn % 2 + 1], to = currentResponser, card = effect.card, - damage = 1 + (effect.additionalDamage or 0), + damage = 1, damageType = fk.NormalDamage, skillName = self.name, }) @@ -364,7 +365,7 @@ local collateralSkill = fk.CreateActiveSkill{ local to = room:getPlayerById(effect.to) if not to:getEquipment(Card.SubtypeWeapon) then return end local use = room:askForUseCard(to, "slash", nil, nil, nil, - { must_targets = effect.subTargets }) + { must_targets = effect.subTargets }, effect) if use then room:useCard(use) @@ -443,10 +444,7 @@ local savageAssaultSkill = fk.CreateActiveSkill{ name = "savage_assault_skill", on_use = aoe_on_use, on_effect = function(self, room, effect) - local cardResponded = nil - if not (effect.disresponsive or table.contains(effect.disresponsiveList or {}, effect.to)) then - cardResponded = room:askForResponse(room:getPlayerById(effect.to), 'slash') - end + local cardResponded = room:askForResponse(room:getPlayerById(effect.to), 'slash', nil, nil, false, nil, effect) if cardResponded then room:responseCard({ @@ -459,7 +457,7 @@ local savageAssaultSkill = fk.CreateActiveSkill{ from = room:getPlayerById(effect.from), to = room:getPlayerById(effect.to), card = effect.card, - damage = 1 + (effect.additionalDamage or 0), + damage = 1, damageType = fk.NormalDamage, skillName = self.name, }) @@ -484,10 +482,7 @@ local archeryAttackSkill = fk.CreateActiveSkill{ name = "archery_attack_skill", on_use = aoe_on_use, on_effect = function(self, room, effect) - local cardResponded = nil - if not (effect.disresponsive or table.contains(effect.disresponsiveList or {}, effect.to)) then - cardResponded = room:askForResponse(room:getPlayerById(effect.to), 'jink') - end + local cardResponded = room:askForResponse(room:getPlayerById(effect.to), 'jink', nil, nil, false, nil, effect) if cardResponded then room:responseCard({ @@ -500,7 +495,7 @@ local archeryAttackSkill = fk.CreateActiveSkill{ from = room:getPlayerById(effect.from), to = room:getPlayerById(effect.to), card = effect.card, - damage = 1 + (effect.additionalDamage or 0), + damage = 1, damageType = fk.NormalDamage, skillName = self.name, }) diff --git a/packages/test/init.lua b/packages/test/init.lua index 3d96b548..47becb4f 100644 --- a/packages/test/init.lua +++ b/packages/test/init.lua @@ -37,7 +37,7 @@ local cheat = fk.CreateActiveSkill{ return end - local cardName = room:askForChoice(from, allCardNames, "cheat") + local cardName = room:askForChoice(from, allCardNames, "cheat", nil, nil, true) local toGain = nil if #allCardMapper[cardName] > 0 then toGain = allCardMapper[cardName][math.random(1, #allCardMapper[cardName])]