diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index a5a11a74..984a8f60 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -13,6 +13,7 @@ function GetGeneralData(name) package = general.package.name, extension = general.package.extensionName, kingdom = general.kingdom, + subkingdom = general.subkingdom, hp = general.hp, maxHp = general.maxHp, shield = general.shield, @@ -323,7 +324,7 @@ function ActiveTargetFilter(skill_name, to_select, selected, selected_cards) elseif skill:isInstanceOf(ViewAsSkill) then local card = skill:viewAs(selected_cards) if card then - ret = card.skill:targetFilter(to_select, selected, selected_cards) + ret = card.skill:targetFilter(to_select, selected, selected_cards, card) ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), card) end end @@ -402,11 +403,11 @@ function CardProhibitedResponse(cid) return json.encode(ret) end -function SkillCanResponse(skill_name) +function SkillCanResponse(skill_name, cardResponsing) local skill = Fk.skills[skill_name] local ret = false if skill and skill:isInstanceOf(ViewAsSkill) then - ret = skill:enabledAtResponse(Self) + ret = skill:enabledAtResponse(Self, cardResponsing) end return json.encode(ret) end diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index fdb190d0..1ca448fc 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -139,6 +139,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["AskForGeneral"] = "选择武将", ["AskForGuanxing"] = "观星", ["AskForChoice"] = "选择", + ["AskForKingdom"] = "选择势力", ["AskForPindian"] = "拼点", ["PlayCard"] = "出牌", @@ -164,6 +165,8 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["pindianwin"] = "赢", ["pindiannotwin"] = "没赢", + ["#ChooseInitialKingdom"] = "请选择初始势力(不可变更)", + ["#RevealGeneral"] = "%from 亮出 %arg %arg2", ["mainGeneral"] = "主将", ["deputyGeneral"] = "副将", diff --git a/lua/core/engine.lua b/lua/core/engine.lua index 2b24a631..006b474d 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -50,6 +50,7 @@ function Engine:initialize() self.translations = {} -- srcText --> translated self.game_modes = {} self.disabled_packs = {} + self.kingdoms = {} self:loadPackages() self:addSkills(AuxSkills) @@ -182,6 +183,10 @@ function Engine:addGeneral(general) end self.generals[general.name] = general + if general.kingdom ~= "unknown" then + table.insertIfNeed(self.kingdoms, general.kingdom) + end + if general.name ~= general.trueName then local tName = general.trueName self.same_generals[tName] = self.same_generals[tName] or { tName } diff --git a/lua/core/general.lua b/lua/core/general.lua index 866015cd..6725856f 100644 --- a/lua/core/general.lua +++ b/lua/core/general.lua @@ -11,6 +11,7 @@ ---@field public name string @ 武将名字 ---@field public trueName string @ 武将真名,也许可以分辨标界? ---@field public kingdom string @ 武将所属势力 +---@field public subkingdom string @ 武将副势力 ---@field public hp integer @ 武将初始体力 ---@field public maxHp integer @ 武将初始最大体力 ---@field public shield integer @ 初始护甲 @@ -48,6 +49,7 @@ function General:initialize(package, name, kingdom, hp, maxHp, gender) self.maxHp = maxHp or hp self.gender = gender or General.Male self.shield = 0 + self.subkingdom = nil self.skills = {} -- skills first added to this general self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde diff --git a/lua/core/skill.lua b/lua/core/skill.lua index 2f18910d..bc82eb4e 100644 --- a/lua/core/skill.lua +++ b/lua/core/skill.lua @@ -38,6 +38,7 @@ function Skill:initialize(name, frequency) self.mute = false self.anim_type = "" self.related_skills = {} + self.attachedKingdom = {} local name_splited = name:split("__") self.trueName = name_splited[#name_splited] @@ -85,4 +86,8 @@ function Skill:isEffectable(player) return true end +function Skill:addAttachedKingdom(kingdom) + table.insertIfNeed(self.attachedKingdom, kingdom) +end + return Skill diff --git a/lua/core/skill_type/view_as.lua b/lua/core/skill_type/view_as.lua index 61d1451c..225ffade 100644 --- a/lua/core/skill_type/view_as.lua +++ b/lua/core/skill_type/view_as.lua @@ -31,8 +31,12 @@ function ViewAsSkill:enabledAtPlay(player) end ---@param player Player -function ViewAsSkill:enabledAtResponse(player) +function ViewAsSkill:enabledAtResponse(player, cardResponsing) return player:hasSkill(self) end +---@param player Player +---@param cardUseStruct CardUseStruct +function ViewAsSkill:beforeUse(player, cardUseStruct) end + return ViewAsSkill diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 32bc2afb..49525426 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -161,7 +161,11 @@ function fk.CreateActiveSkill(spec) local skill = ActiveSkill:new(spec.name, spec.frequency or Skill.NotFrequent) readUsableSpecToSkill(skill, spec) - if spec.can_use then skill.canUse = spec.can_use end + if spec.can_use then + skill.canUse = function(curSkill, player) + return spec.can_use(curSkill, player) and curSkill:isEffectable(player) + end + end if spec.card_filter then skill.cardFilter = spec.card_filter end if spec.target_filter then skill.targetFilter = spec.target_filter end if spec.feasible then @@ -193,6 +197,7 @@ end ---@field public pattern string ---@field public enabled_at_play fun(self: ViewAsSkill, player: Player): boolean ---@field public enabled_at_response fun(self: ViewAsSkill, player: Player): boolean +---@field public before_use fun(self: ViewAsSkill, player: Player) ---@param spec ViewAsSkillSpec ---@return ViewAsSkill @@ -211,10 +216,14 @@ function fk.CreateViewAsSkill(spec) skill.pattern = spec.pattern end if type(spec.enabled_at_play) == "function" then - skill.enabledAtPlay = spec.enabled_at_play + skill.enabledAtPlay = function(curSkill, player) + return spec.enabled_at_play(curSkill, player) and curSkill:isEffectable(player) + end end if type(spec.enabled_at_response) == "function" then - skill.enabledAtResponse = spec.enabled_at_response + skill.enabledAtResponse = function(curSkill, player, cardResponsing) + return spec.enabled_at_response(curSkill, player, cardResponsing) and curSkill:isEffectable(player) + end end if spec.interaction then @@ -229,6 +238,10 @@ function fk.CreateViewAsSkill(spec) }) end + if spec.before_use and type(spec.before_use) == "function" then + skill.beforeUse = spec.before_use + end + return skill end diff --git a/lua/server/event.lua b/lua/server/event.lua index 22937331..5b727789 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -8,7 +8,7 @@ fk.NonTrigger = 1 fk.GameStart = 2 fk.TurnStart = 3 -fk.TurnEnd = 72 +fk.TurnEnd = 73 fk.EventPhaseStart = 4 fk.EventPhaseProceeding = 5 fk.EventPhaseEnd = 6 @@ -81,18 +81,19 @@ fk.AskForPeaches = 59 fk.AskForPeachesDone = 60 fk.Death = 61 fk.BuryVictim = 62 -fk.BeforeGameOverJudge = 63 -fk.GameOverJudge = 64 -fk.GameFinished = 65 +fk.Deathed = 63 +fk.BeforeGameOverJudge = 64 +fk.GameOverJudge = 65 +fk.GameFinished = 66 -fk.AskForCardUse = 66 -fk.AskForCardResponse = 67 +fk.AskForCardUse = 67 +fk.AskForCardResponse = 68 -fk.StartPindian = 68 -fk.PindianCardsDisplayed = 69 -fk.PindianResultConfirmed = 70 -fk.PindianFinished = 71 +fk.StartPindian = 69 +fk.PindianCardsDisplayed = 70 +fk.PindianResultConfirmed = 71 +fk.PindianFinished = 72 --- 72 = TurnEnd +-- 73 = TurnEnd -fk.NumOfEvents = 72 +fk.NumOfEvents = 74 diff --git a/lua/server/events/death.lua b/lua/server/events/death.lua index f3df747f..752ed6a9 100644 --- a/lua/server/events/death.lua +++ b/lua/server/events/death.lua @@ -66,4 +66,6 @@ GameEvent.functions[GameEvent.Death] = function(self) logic:trigger(fk.GameOverJudge, victim, deathStruct) logic:trigger(fk.Death, victim, deathStruct) logic:trigger(fk.BuryVictim, victim, deathStruct) + + logic:trigger(fk.Deathed, victim, deathStruct) end diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 28ee901c..c30e8dce 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -83,6 +83,7 @@ function GameLogic:chooseGenerals() local n = room.settings.enableDeputy and 2 or 1 local lord = room:getLord() local lord_general = nil + if lord ~= nil then room.current = lord local generals = Fk:getGeneralsRandomly(generalNum) @@ -97,6 +98,23 @@ function GameLogic:chooseGenerals() end room:setPlayerGeneral(lord, lord_general, true) + if lord.kingdom == "god" or Fk.generals[lord_general].subkingdom then + local allKingdoms = {} + if lord.kingdom == "god" then + allKingdoms = table.simpleClone(Fk.kingdoms) + + local exceptedKingdoms = { "god" } + for _, kingdom in ipairs(exceptedKingdoms) do + table.removeOne(allKingdoms, kingdom) + end + else + local curGeneral = Fk.generals[lord_general] + allKingdoms = { curGeneral.kingdom, curGeneral.subkingdom } + end + + lord.kingdom = room:askForChoice(lord, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom") + room:broadcastProperty(lord, "kingdom") + end room:broadcastProperty(lord, "general") room:setDeputyGeneral(lord, deputy) room:broadcastProperty(lord, "deputyGeneral") @@ -116,19 +134,62 @@ function GameLogic:chooseGenerals() room:notifyMoveFocus(nonlord, "AskForGeneral") room:doBroadcastRequest("AskForGeneral", nonlord) + for _, p in ipairs(nonlord) do if p.general == "" and p.reply_ready then local generals = json.decode(p.client_reply) local general = generals[1] local deputy = generals[2] - room:setPlayerGeneral(p, general, true) + room:setPlayerGeneral(p, general, true, true) room:setDeputyGeneral(p, deputy) else - room:setPlayerGeneral(p, p.default_reply[1], true) + room:setPlayerGeneral(p, p.default_reply[1], true, true) room:setDeputyGeneral(p, p.default_reply[2]) end p.default_reply = "" end + + local specialKingdomPlayers = table.filter(nonlord, function(p) + return p.kingdom == "god" or Fk.generals[p.general].subkingdom + end) + + if #specialKingdomPlayers > 0 then + local choiceMap = {} + for _, p in ipairs(specialKingdomPlayers) do + local allKingdoms = {} + if p.kingdom == "god" then + allKingdoms = table.simpleClone(Fk.kingdoms) + + local exceptedKingdoms = { "god" } + for _, kingdom in ipairs(exceptedKingdoms) do + table.removeOne(allKingdoms, kingdom) + end + else + local curGeneral = Fk.generals[p.general] + allKingdoms = { curGeneral.kingdom, curGeneral.subkingdom } + end + + choiceMap[p.id] = allKingdoms + + local data = json.encode({ allKingdoms, "AskForKingdom", "#ChooseInitialKingdom" }) + p.request_data = data + end + + room:notifyMoveFocus(nonlord, "AskForKingdom") + room:doBroadcastRequest("AskForChoice", specialKingdomPlayers) + + for _, p in ipairs(specialKingdomPlayers) do + local kingdomChosen + if p.reply_ready then + kingdomChosen = p.client_reply + else + kingdomChosen = choiceMap[p.id][1] + end + + p.kingdom = kingdomChosen + room:notifyProperty(p, p, "kingdom") + end + end end function GameLogic:buildPlayerCircle() @@ -157,6 +218,7 @@ function GameLogic:broadcastGeneral() if p.role ~= "lord" then room:broadcastProperty(p, "general") + room:broadcastProperty(p, "kingdom") room:broadcastProperty(p, "deputyGeneral") elseif #players >= 5 then p.maxHp = p.maxHp + 1 @@ -188,6 +250,10 @@ function GameLogic:attachSkillToPlayers() return end + if #skill.attachedKingdom > 0 and not table.contains(skill.attachedKingdom, player.kingdom) then + return + end + room:handleAddLoseSkills(player, skillName, nil, false) end for _, p in ipairs(room.alive_players) do diff --git a/lua/server/room.lua b/lua/server/room.lua index e53d9441..35b07cd6 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -427,7 +427,8 @@ end ---@param player ServerPlayer ---@param general string ---@param changeKingdom boolean -function Room:setPlayerGeneral(player, general, changeKingdom) +---@param noBroadcast boolean|null +function Room:setPlayerGeneral(player, general, changeKingdom, noBroadcast) if Fk.generals[general] == nil then return end player.general = general player.gender = Fk.generals[general].gender @@ -436,7 +437,11 @@ function Room:setPlayerGeneral(player, general, changeKingdom) if changeKingdom then player.kingdom = Fk.generals[general].kingdom - self:broadcastProperty(player, "kingdom") + if noBroadcast then + self:notifyProperty(player, player, "kingdom") + else + self:broadcastProperty(player, "kingdom") + end end end @@ -1303,6 +1308,7 @@ function Room:handleUseCardReply(player, data) local c = skill:viewAs(selected_cards) if c then self:useSkill(player, skill) + local use = {} ---@type CardUseStruct use.from = player.id use.tos = {} @@ -1313,6 +1319,8 @@ function Room:handleUseCardReply(player, data) use.tos = nil end use.card = c + + skill:beforeUse(player, use) return use end end @@ -2399,6 +2407,7 @@ function Room:useSkill(player, skill, effect_cb) end end player:addSkillUseHistory(skill.name) + if effect_cb then return execGameEvent(GameEvent.SkillEffect, effect_cb) end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 7bbe3c96..674d96fa 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -601,7 +601,7 @@ end ---@param initialCard Card ---@return PindianStruct function ServerPlayer:pindian(tos, skillName, initialCard) - local pindianData = { from = self, tos = tos, reson = skillName, fromCard = initialCard, results = {} } + local pindianData = { from = self, tos = tos, reason = skillName, fromCard = initialCard, results = {} } self.room:pindian(pindianData) return pindianData end diff --git a/qml/Pages/Room.qml b/qml/Pages/Room.qml index 3201a7c3..64d03dc9 100644 --- a/qml/Pages/Room.qml +++ b/qml/Pages/Room.qml @@ -145,7 +145,7 @@ Item { script: { skillInteraction.source = ""; dashboard.enableCards(responding_card); - dashboard.enableSkills(responding_card); + dashboard.enableSkills(responding_card, respond_play); autoPending = false; progress.visible = true; okCancel.visible = true; diff --git a/qml/Pages/RoomElement/Dashboard.qml b/qml/Pages/RoomElement/Dashboard.qml index 19b3e11f..be62ff1f 100644 --- a/qml/Pages/RoomElement/Dashboard.qml +++ b/qml/Pages/RoomElement/Dashboard.qml @@ -346,13 +346,13 @@ RowLayout { } } - function enableSkills(cname) { + function enableSkills(cname, cardResponsing) { if (cname) { // if cname is presented, we are responding use or play. for (let i = 0; i < skillButtons.count; i++) { let item = skillButtons.itemAt(i); let fitpattern = JSON.parse(Backend.callLuaFunction("SkillFitPattern", [item.orig, cname])); - let canresp = JSON.parse(Backend.callLuaFunction("SkillCanResponse", [item.orig])); + let canresp = JSON.parse(Backend.callLuaFunction("SkillCanResponse", [item.orig, cardResponsing])); item.enabled = fitpattern && canresp; } return; diff --git a/qml/Pages/RoomElement/GeneralCardItem.qml b/qml/Pages/RoomElement/GeneralCardItem.qml index b0f64d7b..916ff71e 100644 --- a/qml/Pages/RoomElement/GeneralCardItem.qml +++ b/qml/Pages/RoomElement/GeneralCardItem.qml @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later import QtQuick +import Qt5Compat.GraphicalEffects import "PhotoElement" import "../skin-bank.js" as SkinBank @@ -17,6 +18,7 @@ import "../skin-bank.js" as SkinBank CardItem { property string kingdom + property string subkingdom: "wei" property int hp property int maxHp property int shieldNum @@ -34,9 +36,17 @@ CardItem { } Image { + scale: subkingdom ? 0.6 : 1 + transformOrigin: Item.TopLeft source: SkinBank.getGeneralCardDir(kingdom) + kingdom } + Image { + scale: 0.6; x: 9; y: 12 + transformOrigin: Item.TopLeft + source: subkingdom ? SkinBank.getGeneralCardDir(subkingdom) + subkingdom : "" + } + Row { x: 34 y: 4 @@ -44,8 +54,32 @@ CardItem { Repeater { id: hpRepeater model: (hp > 5 || hp !== maxHp) ? 1 : hp - Image { - source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama" + Item { + width: childrenRect.width + height: childrenRect.height + Image { + source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama" + } + Image { + id: subkingdomMagatama + visible: false + source: subkingdom ? SkinBank.getGeneralCardDir(subkingdom) + subkingdom + "-magatama" : "" + } + LinearGradient { + id: subkingdomMask + visible: false + anchors.fill: subkingdomMagatama + gradient: Gradient { + GradientStop { position: 0.35; color: "transparent" } + GradientStop { position: 0.50; color: "white" } + } + } + OpacityMask { + anchors.fill: subkingdomMagatama + source: subkingdomMagatama + maskSource: subkingdomMask + visible: subkingdom + } } } @@ -123,6 +157,7 @@ CardItem { onNameChanged: { let data = JSON.parse(Backend.callLuaFunction("GetGeneralData", [name])); kingdom = data.kingdom; + subkingdom = (data.subkingdom !== kingdom && data.subkingdom) || ""; hp = data.hp; maxHp = data.maxHp; shieldNum = data.shield;