diff --git a/lua/client/client.lua b/lua/client/client.lua index fdbeef95..41321537 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -466,6 +466,19 @@ fk.client_callback["MoveCards"] = function(jsonData) ClientInstance:notifyUI("MoveCards", json.encode(merged)) end +fk.client_callback["ShowCard"] = function(jsonData) + local data = json.decode(jsonData) + local from = data.from + local cards = data.cards + ClientInstance:notifyUI("MoveCards", json.encode{ + { + ids = cards, + fromArea = Card.DrawPile, + toArea = Card.Processing, + } + }) +end + fk.client_callback["LoseSkill"] = function(jsonData) -- jsonData: [ int player_id, string skill_name ] local data = json.decode(jsonData) diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 68ef0aa6..8ea5cbc3 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -115,6 +115,10 @@ function GetCards(pack_name) return json.encode(ret) end +function GetCardSpecialSkills(cid) + return json.encode(Fk:getCardById(cid).special_skills or {}) +end + function DistanceTo(from, to) local a = ClientInstance:getPlayerById(from) local b = ClientInstance:getPlayerById(to) diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 3e6c95a9..68aefc9c 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -127,6 +127,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["PlayCard"] = "出牌", ["AskForCardChosen"] = "选牌", + ["AskForCardsChosen"] = "选牌", ["#AskForChooseCard"] = "%1:请选择其一张卡牌", ["$ChooseCard"] = "请选择一张卡牌", ["$Hand"] = "手牌区", @@ -202,6 +203,8 @@ Fk:loadTranslationTable{ ["$InstallEquip"] = "%from 装备了 %card", ["$UninstallEquip"] = "%from 卸载了 %card", + ["#ShowCard"] = "%from 展示了牌 %card", + -- phase ["#PhaseSkipped"] = "%from 跳过了 %arg", ["#GainAnExtraTurn"] = "%from 开始进行一个额外的回合", diff --git a/lua/core/card.lua b/lua/core/card.lua index 2cb014cb..bbb40cdd 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -11,6 +11,8 @@ ---@field area CardArea ---@field subcards integer[] ---@field skillName string @ for virtual cards +---@field skill Skill +---@field special_skills string[] | nil local Card = class("Card") ---@alias Suit integer @@ -88,6 +90,7 @@ end function Card:clone(suit, number) local newCard = self.class:new(self.name, suit, number) newCard.skill = self.skill + newCard.special_skills = self.special_skills newCard.equip_skill = self.equip_skill return newCard end diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index c7c0c3c3..f9af2cbc 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -344,6 +344,7 @@ function fk.CreateBasicCard(spec) local card = BasicCard:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills return card end @@ -358,6 +359,7 @@ function fk.CreateTrickCard(spec) local card = TrickCard:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills return card end @@ -372,6 +374,7 @@ function fk.CreateDelayedTrickCard(spec) local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills return card end @@ -387,6 +390,7 @@ function fk.CreateWeapon(spec) local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills card.equip_skill = spec.equip_skill if spec.on_install then card.onInstall = spec.on_install end @@ -406,6 +410,7 @@ function fk.CreateArmor(spec) local card = Armor:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill card.equip_skill = spec.equip_skill + card.special_skills = spec.special_skills if spec.on_install then card.onInstall = spec.on_install end if spec.on_uninstall then card.onUninstall = spec.on_uninstall end @@ -423,6 +428,7 @@ function fk.CreateDefensiveRide(spec) local card = DefensiveRide:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills card.equip_skill = spec.equip_skill if spec.on_install then card.onInstall = spec.on_install end @@ -441,6 +447,7 @@ function fk.CreateOffensiveRide(spec) local card = OffensiveRide:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills card.equip_skill = spec.equip_skill if spec.on_install then card.onInstall = spec.on_install end @@ -459,6 +466,7 @@ function fk.CreateTreasure(spec) local card = Treasure:new(spec.name, spec.suit, spec.number) card.skill = spec.skill or defaultCardSkill + card.special_skills = spec.special_skills card.equip_skill = spec.equip_skill if spec.on_install then card.onInstall = spec.on_install end diff --git a/lua/server/room.lua b/lua/server/room.lua index 437e4e03..57110675 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -750,18 +750,21 @@ end ---@param maxNum integer ---@param includeEquip boolean ---@param skillName string -function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable) +---@param pattern string +function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern) if minNum < 1 then return nil end cancelable = cancelable or false + pattern = pattern or "" local toDiscard = {} local data = { num = maxNum, min_num = minNum, include_equip = includeEquip, - reason = skillName + reason = skillName, + pattern = pattern, } local prompt = "#AskForDiscard:::" .. maxNum .. ":" .. minNum local _, ret = self:askForUseActiveSkill(player, "discard_skill", prompt, cancelable, data) @@ -817,18 +820,21 @@ end ---@param includeEquip boolean ---@param skillName string ---@param cancelable boolean -function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancelable) +---@param pattern string +function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern) if minNum < 1 then return nil end cancelable = cancelable or false + pattern = pattern or "" local chosenCards = {} local data = { num = maxNum, min_num = minNum, include_equip = includeEquip, - reason = skillName + reason = skillName, + pattern = pattern, } local prompt = "#askForCard:::" .. maxNum .. ":" .. minNum local _, ret = self:askForUseActiveSkill(player, "choose_cards_skill", prompt, cancelable, data) @@ -1078,6 +1084,16 @@ function Room:handleUseCardReply(player, data) end end else + if data.special_skill then + local skill = Fk.skills[data.special_skill] + assert(skill:isInstanceOf(ActiveSkill)) + skill:onUse(self, { + from = player.id, + cards = { card }, + tos = targets, + }) + return nil + end local use = {} ---@type CardUseStruct use.from = player.id use.tos = {} diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index b34ae3c0..a08a3408 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -290,6 +290,20 @@ function ServerPlayer:turnOver() self.room.logic:trigger(fk.TurnedOver, self) end +function ServerPlayer:showCards(cards) + cards = Card:getIdList(cards) + local room = self.room + room:sendLog{ + type = "#ShowCard", + from = self.id, + card = cards, + } + room:doBroadcastNotify("ShowCard", json.encode{ + from = self.id, + cards = cards, + }) +end + ---@param from_phase Phase ---@param to_phase Phase function ServerPlayer:changePhase(from_phase, to_phase) diff --git a/packages/maneuvering/init.lua b/packages/maneuvering/init.lua index 9a68a3f9..99712dde 100644 --- a/packages/maneuvering/init.lua +++ b/packages/maneuvering/init.lua @@ -199,6 +199,8 @@ local ironChainCardSkill = fk.CreateActiveSkill{ local ironChain = fk.CreateTrickCard{ name = "iron_chain", skill = ironChainCardSkill, + -- FIXME! FIXME! FIXME! + special_skills = { "zhiheng" }, } extension:addCards{ ironChain:clone(Card.Spade, 11), @@ -209,6 +211,45 @@ extension:addCards{ ironChain:clone(Card.Club, 13), } +local fireAttackSkill = fk.CreateActiveSkill{ + name = "fire_attack_skill", + target_num = 1, + target_filter = function(self, to_select) + return not Fk:currentRoom():getPlayerById(to_select):isKongcheng() + end, + on_effect = function(self, room, cardEffectEvent) + local from = room:getPlayerById(cardEffectEvent.from) + local to = room:getPlayerById(cardEffectEvent.to) + if to:isKongcheng() then return end + + local showCard = room:askForCard(to, 1, 1, false, self.name, false)[1] + to:showCards(showCard) + + showCard = Fk:getCardById(showCard) + local cards = room:askForDiscard(from, 1, 1, false, self.name, true, + ".|.|" .. showCard:getSuitString()) + if #cards > 0 then + room:damage({ + from = from, + to = to, + card = cardEffectEvent.card, + damage = 1, + damageType = fk.FireDamage, + skillName = self.name + }) + end + end, +} +local fireAttack = fk.CreateTrickCard{ + name = "fire_attack", + skill = fireAttackSkill, +} +extension:addCards{ + fireAttack:clone(Card.Heart, 2), + fireAttack:clone(Card.Heart, 3), + fireAttack:clone(Card.Diamond, 12), +} + local supplyShortageSkill = fk.CreateActiveSkill{ name = "supply_shortage_skill", distance_limit = 1, @@ -390,6 +431,9 @@ Fk:loadTranslationTable{ ["fire__slash"] = "火杀", ["analeptic"] = "酒", ["iron_chain"] = "铁锁连环", + ["_normal_use"] = "正常使用", + ["recast"] = "重铸", + ["fire_attack"] = "火攻", ["supply_shortage"] = "兵粮寸断", ["guding_blade"] = "古锭刀", ["vine"] = "藤甲", diff --git a/packages/standard/aux_skills.lua b/packages/standard/aux_skills.lua index 29bc9ef5..ee4afebf 100644 --- a/packages/standard/aux_skills.lua +++ b/packages/standard/aux_skills.lua @@ -5,11 +5,15 @@ local discardSkill = fk.CreateActiveSkill{ return false end + local checkpoint = true if not self.include_equip then - return Fk:currentRoom():getCardArea(to_select) ~= Player.Equip + checkpoint = checkpoint and (Fk:currentRoom():getCardArea(to_select) ~= Player.Equip) end - return true + if self.pattern ~= "" then + checkpoint = checkpoint and (Exppattern:Parse(self.pattern):match(Fk:getCardById(to_select))) + end + return checkpoint end, min_card_num = function(self) return self.min_num end, max_card_num = function(self) return self.num end, @@ -17,17 +21,7 @@ local discardSkill = fk.CreateActiveSkill{ local chooseCardsSkill = fk.CreateActiveSkill{ name = "choose_cards_skill", - card_filter = function(self, to_select, selected) - if #selected >= self.num then - return false - end - - if not self.include_equip then - return Fk:currentRoom():getCardArea(to_select) ~= Player.Equip - end - - return true - end, + card_filter = discardSkill.cardFilter, min_card_num = function(self) return self.min_num end, max_card_num = function(self) return self.num end, } diff --git a/packages/standard_cards/i18n/zh_CN.lua b/packages/standard_cards/i18n/zh_CN.lua index a7c8739e..99909dac 100644 --- a/packages/standard_cards/i18n/zh_CN.lua +++ b/packages/standard_cards/i18n/zh_CN.lua @@ -41,6 +41,7 @@ Fk:loadTranslationTable{ ["god_salvation"] = "桃园结义", ["amazing_grace"] = "五谷丰登", + ["amazing_grace_skill"] = "五谷选牌", ["lightning"] = "闪电", diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index 3f668e87..d75e2499 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -263,6 +263,14 @@ Item { onCardSelected: function(card) { Logic.enableTargets(card); + + if (typeof card === "number" && card !== -1 && roomScene.state === "playing") { + let skills = JSON.parse(Backend.callLuaFunction("GetCardSpecialSkills", [card])); + skills.unshift("_normal_use"); + specialCardSkills.model = skills; + } else { + specialCardSkills.model = []; + } } } @@ -332,6 +340,41 @@ Item { } } + Rectangle { + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + anchors.right: okCancel.left + anchors.rightMargin: 20 + color: "#88EEEEEE" + radius: 8 + visible: roomScene.state == "playing" && specialCardSkills.count > 1 + width: childrenRect.width + height: childrenRect.height - 20 + + RowLayout { + y: -10 + Repeater { + id: specialCardSkills + RadioButton { + property string orig_text: modelData + text: Backend.translate(modelData) + checked: index === 0 + onCheckedChanged: { + if (modelData === "_normal_use") { + Logic.enableTargets(dashboard.selected_card); + } else { + Logic.enableTargets(JSON.stringify({ + skill: modelData, + subcards: [dashboard.selected_card], + })); + } + } + } + } + } + } + + Row { id: okCancel anchors.bottom: parent.bottom @@ -574,6 +617,17 @@ Item { onActivated: Logic.doCancelButton(); } + function getCurrentCardUseMethod() { + for (let i = 1; i < specialCardSkills.count; i++) { + let item = specialCardSkills.itemAt(i); + if (item.checked) { + let ret = item.orig_text; + console.log(ret); + return ret; + } + } + } + function addToChat(pid, raw, msg) { chat.append(msg); let photo = Logic.getPhotoOrSelf(pid); diff --git a/qml/Pages/RoomElement/ChoiceBox.qml b/qml/Pages/RoomElement/ChoiceBox.qml index 4aed7af1..5593ca3c 100644 --- a/qml/Pages/RoomElement/ChoiceBox.qml +++ b/qml/Pages/RoomElement/ChoiceBox.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Layouts import ".." GraphicsBox { @@ -11,18 +12,20 @@ GraphicsBox { width: Math.max(140, body.width + 20) height: body.height + title.height + 20 - Column { + GridLayout { id: body x: 10 y: title.height + 5 - spacing: 10 + flow: GridLayout.TopToBottom + rows: 8 + columnSpacing: 10 Repeater { model: options MetroButton { + Layout.fillWidth: true text: Backend.translate(modelData) - anchors.horizontalCenter: parent.horizontalCenter onClicked: { result = index; diff --git a/qml/Pages/RoomLogic.js b/qml/Pages/RoomLogic.js index 0de2c752..879c8bf9 100644 --- a/qml/Pages/RoomLogic.js +++ b/qml/Pages/RoomLogic.js @@ -68,7 +68,8 @@ function doOkButton() { replyToServer(JSON.stringify( { card: dashboard.getSelectedCard(), - targets: selected_targets + targets: selected_targets, + special_skill: roomScene.getCurrentCardUseMethod(), } )); return;