diff --git a/lua/client/i18n/en_US.lua b/lua/client/i18n/en_US.lua index 070b2aee99ba0b6ede54cee6bcb460ff1aa32164..5cefe51ffaf4511e9c8c8a58f5e5227b2fbdc13f 100644 --- a/lua/client/i18n/en_US.lua +++ b/lua/client/i18n/en_US.lua @@ -404,6 +404,7 @@ Fk:loadTranslationTable({ -- skill ["#InvokeSkill"] = '%from used skill "%arg"', + ["#InvokeSkillTo"] = '%from used skill "%arg" to %to', -- judge ["#StartJudgeReason"] = "%from started a judgement (%arg)", diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index f6ff38a7f5abcaaf7135e1789fdb188fa0742f51..c11f078d0f71443a9eeccb28e1cc7e6d8af267ef 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -477,6 +477,7 @@ Fk:loadTranslationTable{ -- skill ["#InvokeSkill"] = "%from 发动了〖%arg〗", + ["#InvokeSkillTo"] = "%from 对 %to 发动了〖%arg〗", -- judge ["#StartJudgeReason"] = "%from 开始了 %arg 的判定", diff --git a/lua/core/card.lua b/lua/core/card.lua index 10faad655b2dc22d5d8e8621a705d480b69b2547..9b86283c485c994b06bffcf9a4f4662d914bcf66 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -514,4 +514,12 @@ function Card.static:getIdList(c) return ret end +--- 获得卡牌的标记并初始化为表 +---@param mark string @ 标记 +---@return table +function Card:getTableMark(mark) + local ret = self:getMark(mark) + return type(ret) == "table" and ret or {} +end + return Card diff --git a/lua/core/skill_type/active.lua b/lua/core/skill_type/active.lua index 0fdc04f97043bb173c83a07756ce494671ff1cfb..fa3e4f6c871aeb5a7da5ee60e7e9b703ee9f2c21 100644 --- a/lua/core/skill_type/active.lua +++ b/lua/core/skill_type/active.lua @@ -26,8 +26,8 @@ end -- 判断该技能是否可主动发动 ---@param player Player @ 使用者 ----@param card Card @ 牌 ----@param extra_data UseExtraData @ 额外数据 +---@param card? Card @ 牌,若该技能是卡牌的效果技能,需输入此值 +---@param extra_data? UseExtraData @ 额外数据 ---@return bool function ActiveSkill:canUse(player, card, extra_data) return self:isEffectable(player) @@ -46,8 +46,8 @@ end ---@param to_select integer @ 待选目标 ---@param selected integer[] @ 已选目标 ---@param selected_cards integer[] @ 已选牌 ----@param card Card @ 牌 ----@param extra_data UseExtraData @ 额外数据 +---@param card? Card @ 牌 +---@param extra_data? UseExtraData @ 额外数据 ---@return bool function ActiveSkill:targetFilter(to_select, selected, selected_cards, card, extra_data) return false @@ -83,8 +83,8 @@ function ActiveSkill:getMinTargetNum() end -- 获得技能的最大目标数 ----@param player Player @ 使用者 ----@param card Card @ 牌 +---@param player? Player @ 使用者 +---@param card? Card @ 牌 ---@return number @ 最大目标数 function ActiveSkill:getMaxTargetNum(player, card) local ret @@ -99,11 +99,13 @@ function ActiveSkill:getMaxTargetNum(player, card) ret = ret[#ret] end - local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable - for _, skill in ipairs(status_skills) do - local correct = skill:getExtraTargetNum(player, self, card) - if correct == nil then correct = 0 end - ret = ret + correct + if player and card then + local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable + for _, skill in ipairs(status_skills) do + local correct = skill:getExtraTargetNum(player, self, card) + if correct == nil then correct = 0 end + ret = ret + correct + end end return ret end @@ -217,7 +219,7 @@ end ---@param selected integer[] @ 已选目标 ---@param selected_cards integer[] @ 已选牌 ---@param player Player @ 使用者 ----@param card Card @ 牌 +---@param card? Card @ 牌 ---@return bool function ActiveSkill:feasible(selected, selected_cards, player, card) return #selected >= self:getMinTargetNum() and #selected <= self:getMaxTargetNum(player, card) diff --git a/lua/core/skill_type/trigger.lua b/lua/core/skill_type/trigger.lua index 83b890f9dcace364d9a9a4dcd28794aa34469590..6c94820ba5d0af9624c6adbf5968239684e080b7 100644 --- a/lua/core/skill_type/trigger.lua +++ b/lua/core/skill_type/trigger.lua @@ -72,9 +72,17 @@ function TriggerSkill:doCost(event, target, player, data) self.cost_data = cost_data_bak if ret then + local skill_data = {cost_data = cost_data_bak, tos = {}, cards = {}} + if type(cost_data_bak) == "table" then + if type(cost_data_bak.tos) == "table" and #cost_data_bak.tos > 0 and type(cost_data_bak.tos[1]) == "number" and + room:getPlayerById(cost_data_bak.tos[1]) ~= nil then + skill_data.tos = cost_data_bak.tos + end + if type(cost_data_bak.cards) == "table" then skill_data.cards = cost_data_bak.cards end + end return room:useSkill(player, self, function() return self:use(event, target, player, data) - end) + end, skill_data) end end diff --git a/lua/server/ai/random_ai.lua b/lua/server/ai/random_ai.lua index deea7ff5d0130923920a10a3139a0607a6d69b3f..2ef243a8f5e0e40785a29cb693938304c1b27088 100644 --- a/lua/server/ai/random_ai.lua +++ b/lua/server/ai/random_ai.lua @@ -6,76 +6,118 @@ local RandomAI = AI:subclass("RandomAI") ---@param self RandomAI ---@param skill ActiveSkill ---@param card? Card -function RandomAI:useActiveSkill(skill, card) +---@param extra_data? table +function RandomAI:useActiveSkill(skill, card, extra_data) local room = self.room local player = self.player + extra_data = extra_data or Util.DummyTable if skill:isInstanceOf(ViewAsSkill) then return "" end - local filter_func = skill.cardFilter - if card then - filter_func = Util.FalseFunc + if self.command == "PlayCard" and (not skill:canUse(player, card) or (card and player:prohibitUse(card))) then + return "" end - if self.command == "PlayCard" and card and card.class and card:isInstanceOf(Card) - and (not skill:canUse(player, card) or player:prohibitUse(card)) then - return "" + local interaction_data + if skill and skill.interaction then + skill.interaction.data = nil + interaction_data = skill:interaction() + if type(interaction_data) == "table" then + if interaction_data.type == "spin" then + interaction_data = math.random(interaction_data.from, interaction_data.to) + elseif interaction_data.type == "combo" then + interaction_data = interaction_data.default + else + -- use default data when handling custom interaction + interaction_data = interaction_data.default or interaction_data.default_choice or nil + end + end + if interaction_data == nil then return "" end + skill.interaction.data = interaction_data end local max_try_times = 100 local selected_targets = {} local selected_cards = {} - -- FIXME: ... - -- local min = skill:getMinTargetNum() - -- local max = skill:getMaxTargetNum(player, card) - -- local min_card = skill:getMinCardNum() - -- local max_card = skill:getMaxCardNum() - -- FIXME: ViewAsSkill can be buggy here + + -- TODO: ng that 'must_targets' & 'exclusive_targets' should be rebuilt later + local limited_targets = {} + for _, name in ipairs({"must_targets","exclusive_targets","include_targets"}) do + if type(extra_data[name]) == "table" then + table.insertTableIfNeed(limited_targets, extra_data[name]) + end + end + + local all_cards = player:getCardIds{ Player.Hand, Player.Equip } + if skill.expand_pile then + if type(skill.expand_pile) == "string" then + table.insertTableIfNeed(all_cards, player.special_cards[skill.expand_pile] or {}) + elseif type(skill.expand_pile) == "table" then + table.insertTableIfNeed(all_cards, skill.expand_pile) + end + end + + --local max_target_num = skill:getMaxTargetNum(player, card) + local card_filter_func = card and Util.FalseFunc or skill.cardFilter + local firstTry for _ = 0, max_try_times do - if skill:feasible(selected_targets, selected_cards, self.player, card) then break end - local avail_targets = table.filter(room:getAlivePlayers(), function(p) - local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard'zixing') - if ret and card then - if player:isProhibited(p, card) then - ret = false - end - end - return ret + if not firstTry and skill:feasible(selected_targets, selected_cards, self.player, card) then + firstTry = {table.simpleClone(selected_targets), table.simpleClone(selected_cards)} + end + if firstTry and math.random() < 0.1 then break end + local avail_targets = table.filter(room.alive_players, function(p) + return not table.contains(selected_targets, p.id) and (#limited_targets == 0 or table.contains(limited_targets, p.id)) + and skill:targetFilter(p.id, selected_targets, selected_cards, card) + and (not card or not player:isProhibited(p, card)) end) - avail_targets = table.map(avail_targets, function(p) return p.id end) - local avail_cards = table.filter(player:getCardIds{ Player.Hand, Player.Equip }, function(id) - return filter_func(skill, id, selected_cards, selected_targets) + local avail_cards = table.filter(all_cards, function(id) + return not table.contains(selected_cards, id) and card_filter_func(skill, id, selected_cards, selected_targets) end) - - if #avail_targets == 0 and #avail_cards == 0 then break end - table.insertIfNeed(selected_targets, table.random(avail_targets)) - table.insertIfNeed(selected_cards, table.random(avail_cards)) + local random_list = table.connect(avail_targets, avail_cards) + if #random_list == 0 then break end + local randomIndex = math.random(#random_list) + if randomIndex <= #avail_targets then + table.insertIfNeed(selected_targets, random_list[randomIndex].id) + else + table.insertIfNeed(selected_cards, random_list[randomIndex]) + end end - if skill:feasible(selected_targets, selected_cards, self.player, card) then + local feasibleCheck = function () return skill:feasible(selected_targets, selected_cards, self.player, card) end + if firstTry and not feasibleCheck() then + selected_targets = firstTry[1] + selected_cards = firstTry[2] + end + if feasibleCheck() then local ret = json.encode{ card = card and card.id or json.encode{ skill = skill.name, subcards = selected_cards, }, targets = selected_targets, + interaction_data = interaction_data, } - -- print(ret) return ret end return "" end ----@param self RandomAI + ---@param skill ViewAsSkill -function RandomAI:useVSSkill(skill, pattern, cancelable, extra_data) +---@param pattern? string @ no 'pattern' means it needs to pass the 'canUse' check +---@param cancelable? bool +---@param extra_data? table +---@param cardResponsing? bool +function RandomAI:useVSSkill(skill, pattern, cancelable, extra_data, cardResponsing) local player = self.player local room = self.room local precondition - if not skill then return nil end + cancelable = cancelable or (cancelable == nil) + extra_data = extra_data or Util.DummyTable + if not skill then return "" end - if self.command == "PlayCard" then + if not pattern then precondition = skill:enabledAtPlay(player) - if not precondition then return nil end + if not precondition then return "" end local exp = Exppattern:Parse(skill.pattern) local cnames = {} for _, m in ipairs(exp.matchers) do @@ -83,35 +125,107 @@ function RandomAI:useVSSkill(skill, pattern, cancelable, extra_data) end for _, n in ipairs(cnames) do local c = Fk:cloneCard(n) - precondition = c.skill:canUse(Self, c) + precondition = c.skill:canUse(player, c, extra_data) if precondition then break end end else - precondition = skill:enabledAtResponse(player) - if not precondition then return nil end - local exp = Exppattern:Parse(pattern) - precondition = exp:matchExp(skill.pattern) + precondition = skill:enabledAtResponse(player, cardResponsing) and Exppattern:Parse(pattern):matchExp(skill.pattern) end - if (not precondition) or math.random() < 0.2 then return nil end + if (not precondition) or (cancelable and math.random() < 0.2) then return "" end + + local interaction_data + if skill.interaction then + skill.interaction.data = nil + interaction_data = skill:interaction() + if type(interaction_data) == "table" then + if interaction_data.type == "spin" then + interaction_data = math.random(interaction_data.from, interaction_data.to) + elseif interaction_data.type == "combo" then + interaction_data = interaction_data.default + else + -- use default data when handling custom interaction + interaction_data = interaction_data.default or interaction_data.default_choice or nil + end + end + if interaction_data == nil then return "" end + skill.interaction.data = interaction_data + end local selected_cards = {} + local selected_targets = {} + local card local max_try_time = 100 + local all_cards = player:getCardIds{ Player.Hand, Player.Equip } + if skill.expand_pile then + if type(skill.expand_pile) == "string" then + table.insertTableIfNeed(all_cards, player.special_cards[skill.expand_pile] or {}) + elseif type(skill.expand_pile) == "table" then + table.insertTableIfNeed(all_cards, skill.expand_pile) + end + end for _ = 0, max_try_time do - local avail_cards = table.filter(player:getCardIds{ Player.Hand, Player.Equip }, function(id) - return skill:cardFilter(id, selected_cards) + card = skill:viewAs(selected_cards) + if card then break end + local avail_cards = table.filter(all_cards, function(id) + return not table.contains(selected_cards, id) and skill:cardFilter(id, selected_cards) end) if #avail_cards == 0 then break end table.insert(selected_cards, table.random(avail_cards)) - if skill:viewAs(selected_cards) then - return { - skill = skill.name, - subcards = selected_cards, + end + + if not card then return "" end + + if cardResponsing then + if not player:prohibitResponse(card) then + return json.encode{ + card = json.encode{ + skill = skill.name, + subcards = selected_cards, + }, + targets = {}, + interaction_data = interaction_data, } end + return "" + end + + if player:prohibitUse(card) then return "" end + + if pattern or player:canUse(card, extra_data) then + + local limited_targets = {} + for _, name in ipairs({"must_targets","exclusive_targets","include_targets"}) do + if type(extra_data[name]) == "table" then + table.insertTableIfNeed(limited_targets, extra_data[name]) + end + end + + for _ = 0, max_try_time do + if card.skill:feasible(selected_targets, selected_cards, player, card) then break end + local avail_targets = table.filter(room.alive_players, function(p) + return not table.contains(selected_targets, p.id) and (#limited_targets == 0 or table.contains(limited_targets, p.id)) + and card.skill:targetFilter(p.id, selected_targets, selected_cards, card, extra_data) + and not player:isProhibited(p, card) + end) + if #avail_targets == 0 then break end + table.insert(selected_targets, table.random(avail_targets).id) + end + if card.skill:feasible(selected_targets, selected_cards, player, card, extra_data) then + local ret = json.encode{ + card = json.encode{ + skill = skill.name, + subcards = selected_cards, + }, + targets = selected_targets, + interaction_data = interaction_data, + } + return ret + end end - return nil + + return "" end ---@type table @@ -120,6 +234,7 @@ local random_cb = {} random_cb["AskForUseActiveSkill"] = function(self, jsonData) local data = json.decode(jsonData) local skill = Fk.skills[data[1]] + if not skill then return "" end local cancelable = data[3] if cancelable and math.random() < 0.25 then return "" end local extra_data = data[4] @@ -127,13 +242,47 @@ random_cb["AskForUseActiveSkill"] = function(self, jsonData) skill[k] = v end if skill:isInstanceOf(ViewAsSkill) then - return RandomAI.useVSSkill(skill) + return self:useVSSkill(skill, nil, cancelable, extra_data) + end + local player = self.player + if skill.name == "choose_cards_skill" then + local exp = Exppattern:Parse(extra_data.pattern) + local cards = table.filter(player:getCardIds(extra_data.include_equip and "he" or "h"), function(cid) + return exp:match(Fk:getCardById(cid)) + end) + local maxNum = extra_data.num + local minNum = extra_data.min_num + cards = table.random(cards, math.random(minNum, maxNum)) + return json.encode{ + card = json.encode{ + skill = skill.name, + subcards = cards, + }, + targets = {}, + } end - return RandomAI.useActiveSkill(self, skill) + return self:useActiveSkill(skill) end random_cb["AskForSkillInvoke"] = function(self, jsonData) - return table.random{"1", ""} + local skill_name, prompt = table.unpack(json.decode(jsonData)) + local chance = 0.55 + if Fk.skills[skill_name] ~= nil and self.player:hasSkill(skill_name) then + chance = 0.8 + end + if math.random() < chance then + return "1" + end + return "" +end + +random_cb["AskForChoice"] = function(self, jsonData) + local data = json.decode(jsonData) + local choices = data[1] + if table.contains(choices, "Cancel") and #choices > 1 and math.random() < 0.6 then + table.removeOne(choices, "Cancel") + end + return table.random(choices) end random_cb["AskForUseCard"] = function(self, jsonData) @@ -141,61 +290,74 @@ random_cb["AskForUseCard"] = function(self, jsonData) local data = json.decode(jsonData) local card_name = data[1] local pattern = data[2] or card_name - local cancelable = data[4] or true - local exp = Exppattern:Parse(pattern) + local prompt = data[3] + local cancelable = data[4] + local extra_data = data[5] or Util.DummyTable - local avail_cards = table.map(player:getCardIds("he"), Util.Id2CardMapper) - avail_cards = table.filter(avail_cards, function(c) + if card_name == "peach" then + if type(extra_data.must_targets) == "table" and extra_data.must_targets[1] ~= player.id and math.random() < 0.8 then + return "" + end + end + + if (cancelable and math.random() < 0.15) then return "" end + + local cards = table.map(self.player:getCardIds("he&"), Util.Id2CardMapper) + local exp = Exppattern:Parse(pattern) + cards = table.filter(cards, function(c) return exp:match(c) and not player:prohibitUse(c) end) - if #avail_cards > 0 then - if math.random() < 0.25 then return "" end - for _, card in ipairs(avail_cards) do - local skill = card.skill - local max_try_times = 100 - local selected_targets = {} - local min = skill:getMinTargetNum() - local max = skill:getMaxTargetNum(player, card) - local min_card = skill:getMinCardNum() - local max_card = skill:getMaxCardNum() - for _ = 0, max_try_times do - if skill:feasible(selected_targets, { card.id }, self.player, card) then - return json.encode{ - card = table.random(avail_cards).id, - targets = selected_targets, - } - end - local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) - return skill:targetFilter(p.id, selected_targets, {card.id}, card or Fk:cloneCard'zixing') - end) - avail_targets = table.map(avail_targets, function(p) return p.id end) - - if #avail_targets == 0 and #avail_cards == 0 then break end - table.insertIfNeed(selected_targets, table.random(avail_targets)) - end + local vss = table.filter(player:getAllSkills(), function(s) + return s:isInstanceOf(ViewAsSkill) + end) + table.insertTable(cards, vss) + + while #cards > 0 do + local sth = table.remove(cards, math.random(#cards)) + if sth:isInstanceOf(Card) then + local ret = self:useActiveSkill(sth.skill, sth, extra_data) + if ret ~= "" then return ret end + else + local ret = self:useVSSkill(sth, pattern, cancelable, extra_data) + if ret ~= "" then return ret end end end + return "" end random_cb["AskForResponseCard"] = function(self, jsonData) local data = json.decode(jsonData) local pattern = data[2] - local cancelable = true + local cancelable = data[4] or true + local extra_data = data[5] or Util.DummyTable + local player = self.player + + local cards = table.map(self.player:getCardIds("he&"), Util.Id2CardMapper) local exp = Exppattern:Parse(pattern) - local avail_cards = table.filter(self.player:getCardIds{ Player.Hand, Player.Equip }, function(id) - return exp:match(Fk:getCardById(id)) + cards = table.filter(cards, function(c) + return exp:match(c) and not player:prohibitResponse(c) end) - if #avail_cards > 0 then return json.encode{ - card = table.random(avail_cards), - targets = {}, - } end - -- TODO: vs skill + + local vss = table.filter(player:getAllSkills(), function(s) + return s:isInstanceOf(ViewAsSkill) + end) + table.insertTable(cards, vss) + + while #cards > 0 do + local sth = table.remove(cards, math.random(#cards)) + if sth:isInstanceOf(Card) then + return json.encode{ card = sth.id, targets = {} } + else + local ret = self:useVSSkill(sth, pattern, cancelable, extra_data, true) + if ret ~= "" then return ret end + end + end return "" end random_cb["PlayCard"] = function(self, jsonData) - local cards = table.map(self.player:getCardIds(Player.Hand), Util.Id2CardMapper) + local cards = table.map(self.player:getCardIds("h&"), Util.Id2CardMapper) local actives = table.filter(self.player:getAllSkills(), function(s) return s:isInstanceOf(ActiveSkill) end) @@ -206,16 +368,13 @@ random_cb["PlayCard"] = function(self, jsonData) table.insertTable(cards, vss) while #cards > 0 do - local sth = table.random(cards) + local sth = table.remove(cards, math.random(#cards)) if sth:isInstanceOf(Card) then local card = sth local skill = card.skill ---@type ActiveSkill if math.random() > 0.15 then local ret = RandomAI.useActiveSkill(self, skill, card) if ret ~= "" then return ret end - table.removeOne(cards, card) - else - table.removeOne(cards, card) end elseif sth:isInstanceOf(ActiveSkill) then local active = sth @@ -223,14 +382,12 @@ random_cb["PlayCard"] = function(self, jsonData) local ret = RandomAI.useActiveSkill(self, active, nil) if ret ~= "" then return ret end end - table.removeOne(cards, active) else local vs = sth if math.random() > 0.20 then local ret = self:useVSSkill(vs) - -- TODO: handle vs result + if ret ~= "" then return ret end end - table.removeOne(cards, vs) end end diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index 4e6c2b7ff14f9d2ab72b5b9f0558d9b922ae4df2..7d384936a1aaf817126dc783d9853215e7d67733 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -369,6 +369,7 @@ function Phase:main() end, [Player.Play] = function() while not player.dead do + if player._phase_end then break end logic:trigger(fk.StartPlayCard, player, nil, true) room:notifyMoveFocus(player, "PlayCard") local result = room:doRequest(player, "PlayCard", player.id) @@ -378,10 +379,6 @@ function Phase:main() if type(useResult) == "table" then room:useCard(useResult) end - - if player._phase_end then - break - end end end, [Player.Discard] = function() diff --git a/lua/server/events/skill.lua b/lua/server/events/skill.lua index df4ffea42930b83e5a9d956f108c08723a560075..4703cd9c4c7bef6b5ebfca5b7497d898cbd887e3 100644 --- a/lua/server/events/skill.lua +++ b/lua/server/events/skill.lua @@ -3,22 +3,72 @@ ---@class GameEvent.SkillEffect : GameEvent local SkillEffect = GameEvent:subclass("GameEvent.SkillEffect") function SkillEffect:main() - local effect_cb, player, _skill = table.unpack(self.data) + local effect_cb, player, skill, skill_data = table.unpack(self.data) local room = self.room local logic = room.logic - local skill = _skill.main_skill and _skill.main_skill or _skill + local main_skill = skill.main_skill and skill.main_skill or skill + skill_data = skill_data or Util.DummyTable + local cost_data = skill_data.cost_data or Util.DummyTable - if player then - player:addSkillUseHistory(skill.name) + if player and not skill.cardSkill then + player:revealBySkillName(main_skill.name) + + local tos = skill_data.tos or {} + local mute, no_indicate = skill.mute, skill.no_indicate + if type(cost_data) == "table" then + if cost_data.mute then mute = cost_data.mute end + if cost_data.no_indicate then no_indicate = cost_data.no_indicate end + end + if not mute then + if skill.attached_equip then + local equip = Fk.all_card_types[skill.attached_equip] + if equip then + local pkgPath = "./packages/" .. equip.package.extensionName + local soundName = pkgPath .. "/audio/card/" .. equip.name + room:broadcastPlaySound(soundName) + if not no_indicate and #tos > 0 then + room:sendLog{ + type = "#InvokeSkillTo", + from = player.id, + arg = skill.name, + to = tos, + } + else + room:sendLog{ + type = "#InvokeSkill", + from = player.id, + arg = skill.name, + } + end + room:setEmotion(player, pkgPath .. "/image/anim/" .. equip.name) + end + else + player:broadcastSkillInvoke(skill.name) + room:notifySkillInvoked(player, skill.name, nil, no_indicate and {} or tos) + end + end + if not no_indicate then + room:doIndicate(player.id, tos) + end + + if skill:isSwitchSkill() then + local switchSkillName = skill.switchSkillName + room:setPlayerMark( + player, + MarkEnum.SwithSkillPreName .. switchSkillName, + player:getSwitchSkillState(switchSkillName, true) + ) + end + + player:addSkillUseHistory(main_skill.name) end local cost_data_bak = skill.cost_data - logic:trigger(fk.SkillEffect, player, skill) + logic:trigger(fk.SkillEffect, player, main_skill) skill.cost_data = cost_data_bak - local ret = effect_cb() - - logic:trigger(fk.AfterSkillEffect, player, skill) + local ret = effect_cb and effect_cb() or false + logic:trigger(fk.AfterSkillEffect, player, main_skill) return ret end diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 9388b89ac63a34a57777ccf53e858196adb8e6f8..91c44c98fd00de4a499b94e05d440c7a6c962237 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -774,7 +774,7 @@ function GameLogic:damageByCardEffect(is_exact) local c_event = d_event:findParent(GameEvent.CardEffect, false, 2) if c_event == nil then return false end return damage.card == c_event.data[1].card and - (not is_exact or d_event.data[1].from.id == c_event.data[1].from) + (not is_exact or (damage.from or {}).id == c_event.data[1].from) end function GameLogic:dumpEventStack(detailed) diff --git a/lua/server/room.lua b/lua/server/room.lua index d0580f9cdda31c3f913a527e23cab1fbd73bca4d..297533cfc270770bf829c9a8cfd6a7fc253d5b8b 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -1101,7 +1101,8 @@ end ---@param player ServerPlayer @ 发动技能的那个玩家 ---@param skill_name string @ 技能名 ---@param skill_type? string | AnimationType @ 技能的动画效果,默认是那个技能的anim_type -function Room:notifySkillInvoked(player, skill_name, skill_type) +---@param tos? table @ 技能目标,填空则不声明 +function Room:notifySkillInvoked(player, skill_name, skill_type, tos) local bigAnim = false if not skill_type then local skill = Fk.skills[skill_name] @@ -1116,11 +1117,20 @@ function Room:notifySkillInvoked(player, skill_name, skill_type) if skill_type == "big" then bigAnim = true end - self:sendLog{ - type = "#InvokeSkill", - from = player.id, - arg = skill_name, - } + if tos and #tos > 0 then + self:sendLog{ + type = "#InvokeSkillTo", + from = player.id, + arg = skill_name, + to = tos, + } + else + self:sendLog{ + type = "#InvokeSkill", + from = player.id, + arg = skill_name, + } + end if not bigAnim then self:doAnimate("InvokeSkill", { @@ -2241,15 +2251,12 @@ function Room:handleUseCardReply(player, data) if skill.interaction then skill.interaction.data = data.interaction_data end if skill:isInstanceOf(ActiveSkill) then self:useSkill(player, skill, function() - if not skill.no_indicate then - self:doIndicate(player.id, targets) - end skill:onUse(self, { from = player.id, cards = selected_cards, tos = targets, }) - end) + end, {tos = targets, cards = selected_cards, cost_data = {}}) return nil elseif skill:isInstanceOf(ViewAsSkill) then Self = player @@ -3840,38 +3847,9 @@ end ---@param player ServerPlayer @ 发动技能的玩家 ---@param skill Skill @ 发动的技能 ---@param effect_cb fun() @ 实际要调用的函数 -function Room:useSkill(player, skill, effect_cb) - player:revealBySkillName(skill.name) - if not skill.mute then - if skill.attached_equip then - local equip = Fk.all_card_types[skill.attached_equip] - local pkgPath = "./packages/" .. equip.package.extensionName - local soundName = pkgPath .. "/audio/card/" .. equip.name - self:broadcastPlaySound(soundName) - self:sendLog{ - type = "#InvokeSkill", - from = player.id, - arg = skill.name, - } - self:setEmotion(player, pkgPath .. "/image/anim/" .. equip.name) - else - player:broadcastSkillInvoke(skill.name) - self:notifySkillInvoked(player, skill.name) - end - end - - if skill:isSwitchSkill() then - local switchSkillName = skill.switchSkillName - self:setPlayerMark( - player, - MarkEnum.SwithSkillPreName .. switchSkillName, - player:getSwitchSkillState(switchSkillName, true) - ) - end - - if effect_cb then - return execGameEvent(GameEvent.SkillEffect, effect_cb, player, skill) - end +---@param skill_data? table @ 技能的信息 +function Room:useSkill(player, skill, effect_cb, skill_data) + return execGameEvent(GameEvent.SkillEffect, effect_cb, player, skill, skill_data or Util.DummyTable) end ---@param player ServerPlayer @@ -4238,4 +4216,49 @@ function Room:endTurn() self:setTag("endTurn", true) end +--清理遗留在处理区的卡牌 +---@param cards? integer[] @ 待清理的卡牌。不填则清理处理区所有牌 +---@param skillName? string @ 技能名 +function Room:cleanProcessingArea(cards, skillName) + local throw = cards and table.filter(cards, function(id) return self:getCardArea(id) == Card.Processing end) or self.processing_area + if #throw > 0 then + self:moveCardTo(throw, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile, skillName) + end +end + +--- 为角色或牌的表型标记添加值 +---@param sth ServerPlayer|Card @ 更新标记的玩家或卡牌 +---@param mark string @ 标记的名称 +---@param value any @ 要增加的值 +function Room:addTableMark(sth, mark, value) + local t = sth:getTableMark(mark) + table.insert(t, value) + if sth:isInstanceOf(Card) then + self:setCardMark(sth, mark, t) + else + self:setPlayerMark(sth, mark, t) + end +end + +--- 为角色或牌的表型标记移除值 +---@param sth ServerPlayer|Card @ 更新标记的玩家或卡牌 +---@param mark string @ 标记的名称 +---@param value any @ 要移除的值 +function Room:removeTableMark(sth, mark, value) + local t = sth:getTableMark(mark) + table.removeOne(t, value) + if sth:isInstanceOf(Card) then + self:setCardMark(sth, mark, #t > 0 and t or 0) + else + self:setPlayerMark(sth, mark, #t > 0 and t or 0) + end +end + + +function Room:__index(k) + if k == "room_settings" then + return self.settings + end +end + return Room diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index fed16201fdc7163a4a69cb8acfda341242a9133c..c47aa21ba3565d6a18690312c7f621ac5c580014 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -331,6 +331,7 @@ function ServerPlayer:turnOver() self.room.logic:trigger(fk.TurnedOver, self) end +---@param cards integer|integer[]|Card|Card[] function ServerPlayer:showCards(cards) cards = Card:getIdList(cards) for _, id in ipairs(cards) do diff --git a/standard/init.lua b/standard/init.lua index aa189e49ef5f11d30cea274833b889d2145112cb..6d48f1176f17318a26209f1e5f3be4d1258bf7d1 100644 --- a/standard/init.lua +++ b/standard/init.lua @@ -162,15 +162,15 @@ local tuxi = fk.CreateTriggerSkill{ local result = room:askForChoosePlayers(player, targets, 1, 2, "#tuxi-ask", self.name) if #result > 0 then - self.cost_data = result + room:sortPlayersByAction(result) + self.cost_data = {tos = result} return true end end, on_use = function(self, event, target, player, data) local room = player.room - room:sortPlayersByAction(self.cost_data) - for _, id in ipairs(self.cost_data) do - if player.dead then return end + for _, id in ipairs(self.cost_data.tos) do + if player.dead then break end local p = room:getPlayerById(id) if not p.dead and not p:isKongcheng() then local c = room:askForCardChosen(player, p, "h", self.name)