Initial kingdom choosing (#143)

- 实现副势力概念,用于应对双势力机制;
- 完善神将及拥有副势力的武将开局选择势力的机制;
- 完成势力技概念;
- 实现ViewAsSkill在响应时对使用和打出的区分。

---------

Co-authored-by: notify <notify-ctrl@qq.com>
This commit is contained in:
Ho-spair 2023-04-30 18:55:59 +08:00 committed by GitHub
parent 34ce4c9859
commit 19a2cc5ed7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 175 additions and 29 deletions

View File

@ -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

View File

@ -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"] = "副将",

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,9 +437,13 @@ function Room:setPlayerGeneral(player, general, changeKingdom)
if changeKingdom then
player.kingdom = Fk.generals[general].kingdom
if noBroadcast then
self:notifyProperty(player, player, "kingdom")
else
self:broadcastProperty(player, "kingdom")
end
end
end
---@param player ServerPlayer
---@param general string
@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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,9 +54,33 @@ CardItem {
Repeater {
id: hpRepeater
model: (hp > 5 || hp !== maxHp) ? 1 : hp
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
}
}
}
Text {
@ -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;