Enhancement (#116)

给Card堆一个伤害牌属性
能添加虚拟牌为子卡
封装了“视为使用xx牌”的函数
护甲机制
interaction现在可以作为一个函数,以实现动态化
冰属性伤害
使用牌堆中的牌不再报错
This commit is contained in:
notify 2023-04-13 20:17:39 +08:00 committed by GitHub
parent a735013411
commit 9ae119028c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 307 additions and 174 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -82,7 +82,9 @@ function Client:moveCards(moves)
table.remove(from.player_cards[Player.Hand]) table.remove(from.player_cards[Player.Hand])
end end
else else
from:removeCards(move.fromArea, move.ids, move.fromSpecialName) if table.contains({ Player.Hand, Player.Equip, Player.Judge, Player.Special }, move.fromArea) then
from:removeCards(move.fromArea, move.ids, move.fromSpecialName)
end
end end
elseif move.fromArea == Card.DiscardPile then elseif move.fromArea == Card.DiscardPile then
table.removeOne(self.discard_pile, move.ids[1]) table.removeOne(self.discard_pile, move.ids[1])

View File

@ -14,7 +14,8 @@ function GetGeneralData(name)
extension = general.package.extensionName, extension = general.package.extensionName,
kingdom = general.kingdom, kingdom = general.kingdom,
hp = general.hp, hp = general.hp,
maxHp = general.maxHp maxHp = general.maxHp,
shield = general.shield,
} }
end end
@ -168,28 +169,13 @@ function CanUseCard(card, player)
player = ClientInstance:getPlayerById(player) player = ClientInstance:getPlayerById(player)
local ret = c.skill:canUse(player, c) local ret = c.skill:canUse(player, c)
if ret then ret = ret and not player:prohibitUse(c)
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:prohibitUse(player, c) then
ret = false
break
end
end
end
return json.encode(ret) return json.encode(ret)
end end
function CardProhibitedUse(cid) function CardProhibitedUse(cid)
local c = Fk:getCardById(cid) local c = Fk:getCardById(cid)
local ret = false local ret = Self:prohibitUse(c)
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:prohibitUse(Self, c) then
ret = true
break
end
end
return json.encode(ret) return json.encode(ret)
end end
@ -211,16 +197,7 @@ function CanUseCardToTarget(card, to_select, selected)
end end
local ret = c.skill:targetFilter(to_select, selected, selected_cards, c) local ret = c.skill:targetFilter(to_select, selected, selected_cards, c)
if ret then ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), c)
local r = Fk:currentRoom()
local status_skills = r.status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:isProhibited(Self, r:getPlayerById(to_select), c) then
ret = false
break
end
end
end
return json.encode(ret) return json.encode(ret)
end end
@ -403,14 +380,7 @@ end
function CardProhibitedResponse(cid) function CardProhibitedResponse(cid)
local c = Fk:getCardById(cid) local c = Fk:getCardById(cid)
local ret = false local ret = Self:prohibitResponse(c)
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:prohibitResponse(Self, c) then
ret = true
break
end
end
return json.encode(ret) return json.encode(ret)
end end
@ -453,12 +423,19 @@ end
function GetInteractionOfSkill(skill_name) function GetInteractionOfSkill(skill_name)
local skill = Fk.skills[skill_name] local skill = Fk.skills[skill_name]
return skill and json.encode(skill.interaction) or "null" if skill and skill.interaction then
if type(skill.interaction) == "function" then
return json.encode(skill:interaction())
else
return json.encode(skill.interaction)
end
end
return "null"
end end
function SetInteractionDataOfSkill(skill_name, data) function SetInteractionDataOfSkill(skill_name, data)
local skill = Fk.skills[skill_name] local skill = Fk.skills[skill_name]
if skill and type(skill.interaction) == "table" then if skill and skill.interaction then
skill.interaction.data = json.decode(data) skill.interaction.data = json.decode(data)
end end
end end

View File

@ -171,6 +171,7 @@ Fk:loadTranslationTable{
["normal_damage"] = "无属性", ["normal_damage"] = "无属性",
["fire_damage"] = "火属性", ["fire_damage"] = "火属性",
["thunder_damage"] = "雷属性", ["thunder_damage"] = "雷属性",
["ice_damage"] = "冰属性",
["phase_judge"] = "判定阶段", ["phase_judge"] = "判定阶段",
["phase_draw"] = "摸牌阶段", ["phase_draw"] = "摸牌阶段",

View File

@ -20,6 +20,7 @@
---@field public skillNames string[] ---@field public skillNames string[]
---@field public skill Skill ---@field public skill Skill
---@field public special_skills string[] | nil ---@field public special_skills string[] | nil
---@field public is_damage_card boolean
local Card = class("Card") local Card = class("Card")
---@alias Suit integer ---@alias Suit integer
@ -162,7 +163,7 @@ local function updateColorAndNumber(card)
number = math.min(number + c.number, 13) number = math.min(number + c.number, 13)
if color ~= c.color then if color ~= c.color then
if not different_color then if not different_color then
if color ~= Card.NoColor then if c.color ~= Card.NoColor then
different_color = true different_color = true
end end
color = c.color color = c.color
@ -183,8 +184,16 @@ function Card:addSubcard(card)
table.insert(self.subcards, card) table.insert(self.subcards, card)
else else
assert(card:isInstanceOf(Card)) assert(card:isInstanceOf(Card))
assert(not card:isVirtual(), "Can not add virtual card as subcard") -- assert(not card:isVirtual(), "Can not add virtual card as subcard")
table.insert(self.subcards, card.id) if card:isVirtual() then
table.insertTable(self.subcards, card.subcards)
else
table.insert(self.subcards, card.id)
end
for _, skill in ipairs(card.skillNames) do
self.skillName = skill
end
end end
updateColorAndNumber(self) updateColorAndNumber(self)

View File

@ -13,6 +13,7 @@
---@field public kingdom string @ 武将所属势力 ---@field public kingdom string @ 武将所属势力
---@field public hp integer @ 武将初始体力 ---@field public hp integer @ 武将初始体力
---@field public maxHp integer @ 武将初始最大体力 ---@field public maxHp integer @ 武将初始最大体力
---@field public shield integer @ 初始护甲
---@field public gender Gender @ 武将性别 ---@field public gender Gender @ 武将性别
---@field public skills Skill[] @ 武将技能 ---@field public skills Skill[] @ 武将技能
---@field public other_skills string[] @ 武将身上属于其他武将的技能,通过字符串调用 ---@field public other_skills string[] @ 武将身上属于其他武将的技能,通过字符串调用
@ -40,6 +41,7 @@ function General:initialize(package, name, kingdom, hp, maxHp, gender)
self.hp = hp self.hp = hp
self.maxHp = maxHp or hp self.maxHp = maxHp or hp
self.gender = gender or General.Male self.gender = gender or General.Male
self.shield = 0
self.skills = {} -- skills first added to this general self.skills = {} -- skills first added to this general
self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde self.other_skills = {} -- skill belongs other general, e.g. "mashu" of pangde

View File

@ -10,6 +10,7 @@
---@field public id integer @ 玩家的id每名玩家的id是唯一的。机器人的id是负数。 ---@field public id integer @ 玩家的id每名玩家的id是唯一的。机器人的id是负数。
---@field public hp integer @ 体力值 ---@field public hp integer @ 体力值
---@field public maxHp integer @ 体力上限 ---@field public maxHp integer @ 体力上限
---@field public shield integer @ 护甲数
---@field public kingdom string @ 势力 ---@field public kingdom string @ 势力
---@field public role string @ 身份 ---@field public role string @ 身份
---@field public general string @ 武将 ---@field public general string @ 武将
@ -667,4 +668,50 @@ function Player:getAllSkills()
return ret return ret
end end
---@param to Player
---@param card Card
function Player:isProhibited(to, card)
local r = Fk:currentRoom()
local status_skills = r.status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:isProhibited(self, to, card) then
return true
end
end
return false
end
---@param card Card
function Player:prohibitUse(card)
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:prohibitUse(self, card) then
return true
end
end
return false
end
---@param card Card
function Player:prohibitResponse(card)
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:prohibitResponse(self, card) then
return true
end
end
return false
end
---@param card Card
function Player:prohibitDiscard(card)
local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or {}
for _, skill in ipairs(status_skills) do
if skill:prohibitDiscard(self, card) then
return true
end
end
return false
end
return Player return Player

View File

@ -1,5 +1,7 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
local Util = {}
-- the iterator of QList object -- the iterator of QList object
local qlist_iterator = function(list, n) local qlist_iterator = function(list, n)
if n < list:length() - 1 then if n < list:length() - 1 then
@ -52,9 +54,11 @@ function table:map(func)
end end
-- frequenly used filter & map functions -- frequenly used filter & map functions
IdMapper = function(e) return e.id end Util.IdMapper = function(e) return e.id end
Id2CardMapper = function(id) return Fk:getCardById(id) end Util.Id2CardMapper = function(id) return Fk:getCardById(id) end
Id2PlayerMapper = function(id) return Fk:currentRoom():getPlayerById(id) end Util.Id2PlayerMapper = function(id)
return Fk:currentRoom():getPlayerById(id)
end
---@generic T ---@generic T
---@param self T[] ---@param self T[]
@ -467,4 +471,4 @@ function AimGroup:getCancelledTargets(aimGroup)
return aimGroup[AimGroup.Cancelled] return aimGroup[AimGroup.Cancelled]
end end
return { TargetGroup, AimGroup } return { TargetGroup, AimGroup, Util }

View File

@ -345,6 +345,7 @@ end
---@class CardSpec: Card ---@class CardSpec: Card
---@field public skill Skill ---@field public skill Skill
---@field public equip_skill Skill ---@field public equip_skill Skill
---@field public is_damage_card boolean
local defaultCardSkill = fk.CreateActiveSkill{ local defaultCardSkill = fk.CreateActiveSkill{
name = "default_card_skill", name = "default_card_skill",
@ -355,144 +356,105 @@ local defaultCardSkill = fk.CreateActiveSkill{
end end
} }
---@param spec CardSpec local function preprocessCardSpec(spec)
---@return BasicCard
function fk.CreateBasicCard(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") assert(type(spec.name) == "string" or type(spec.class_name) == "string")
if not spec.name then spec.name = spec.class_name if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end if spec.number then assert(type(spec.number) == "number") end
end
local card = BasicCard:new(spec.name, spec.suit, spec.number) local function readCardSpecToCard(card, spec)
card.skill = spec.skill or defaultCardSkill card.skill = spec.skill or defaultCardSkill
card.special_skills = spec.special_skills card.special_skills = spec.special_skills
card.is_damage_card = spec.is_damage_card
end
---@param spec CardSpec
---@return BasicCard
function fk.CreateBasicCard(spec)
preprocessCardSpec(spec)
local card = BasicCard:new(spec.name, spec.suit, spec.number)
readCardSpecToCard(card, spec)
return card return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return TrickCard ---@return TrickCard
function fk.CreateTrickCard(spec) function fk.CreateTrickCard(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end
local card = TrickCard:new(spec.name, spec.suit, spec.number) local card = TrickCard:new(spec.name, spec.suit, spec.number)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills
return card return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return DelayedTrickCard ---@return DelayedTrickCard
function fk.CreateDelayedTrickCard(spec) function fk.CreateDelayedTrickCard(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end
local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number) local card = DelayedTrickCard:new(spec.name, spec.suit, spec.number)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills
return card return card
end end
local function readCardSpecToEquip(card, spec)
card.equip_skill = spec.equip_skill
if spec.on_install then card.onInstall = spec.on_install end
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
end
---@param spec CardSpec ---@param spec CardSpec
---@return Weapon ---@return Weapon
function fk.CreateWeapon(spec) function fk.CreateWeapon(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name if spec.attack_range then
elseif not spec.class_name then spec.class_name = spec.name end assert(type(spec.attack_range) == "number" and spec.attack_range >= 0)
if spec.suit then assert(type(spec.suit) == "number") end end
if spec.number then assert(type(spec.number) == "number") end
if spec.attack_range then assert(type(spec.attack_range) == "number" and spec.attack_range >= 0) end
local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range) local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills readCardSpecToEquip(card, spec)
card.equip_skill = spec.equip_skill
if spec.on_install then card.onInstall = spec.on_install end
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
return card return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return Armor ---@return Armor
function fk.CreateArmor(spec) function fk.CreateArmor(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end
local card = Armor:new(spec.name, spec.suit, spec.number) local card = Armor:new(spec.name, spec.suit, spec.number)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.equip_skill = spec.equip_skill readCardSpecToEquip(card, spec)
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
return card return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return DefensiveRide ---@return DefensiveRide
function fk.CreateDefensiveRide(spec) function fk.CreateDefensiveRide(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end
local card = DefensiveRide:new(spec.name, spec.suit, spec.number) local card = DefensiveRide:new(spec.name, spec.suit, spec.number)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills readCardSpecToEquip(card, spec)
card.equip_skill = spec.equip_skill
if spec.on_install then card.onInstall = spec.on_install end
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
return card return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return OffensiveRide ---@return OffensiveRide
function fk.CreateOffensiveRide(spec) function fk.CreateOffensiveRide(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end
local card = OffensiveRide:new(spec.name, spec.suit, spec.number) local card = OffensiveRide:new(spec.name, spec.suit, spec.number)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills readCardSpecToEquip(card, spec)
card.equip_skill = spec.equip_skill
if spec.on_install then card.onInstall = spec.on_install end
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
return card return card
end end
---@param spec CardSpec ---@param spec CardSpec
---@return Treasure ---@return Treasure
function fk.CreateTreasure(spec) function fk.CreateTreasure(spec)
assert(type(spec.name) == "string" or type(spec.class_name) == "string") preprocessCardSpec(spec)
if not spec.name then spec.name = spec.class_name
elseif not spec.class_name then spec.class_name = spec.name end
if spec.suit then assert(type(spec.suit) == "number") end
if spec.number then assert(type(spec.number) == "number") end
local card = Treasure:new(spec.name, spec.suit, spec.number) local card = Treasure:new(spec.name, spec.suit, spec.number)
card.skill = spec.skill or defaultCardSkill readCardSpecToCard(card, spec)
card.special_skills = spec.special_skills readCardSpecToEquip(card, spec)
card.equip_skill = spec.equip_skill
if spec.on_install then card.onInstall = spec.on_install end
if spec.on_uninstall then card.onUninstall = spec.on_uninstall end
return card return card
end end

View File

@ -17,8 +17,8 @@ json = require "json"
math.randomseed(os.time()) math.randomseed(os.time())
-- 加载实用类让Lua编写起来更轻松。 -- 加载实用类让Lua编写起来更轻松。
local GroupUtils = require "core.util" local Utils = require "core.util"
TargetGroup, AimGroup = table.unpack(GroupUtils) TargetGroup, AimGroup, Util = table.unpack(Utils)
dofile "lua/core/debug.lua" dofile "lua/core/debug.lua"
-- 加载游戏核心类 -- 加载游戏核心类

View File

@ -1,5 +1,36 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
local damage_nature_table = {
[fk.NormalDamage] = "normal_damage",
[fk.FireDamage] = "fire_damage",
[fk.ThunderDamage] = "thunder_damage",
[fk.IceDamage] = "ice_damage",
}
local function sendDamageLog(room, damageStruct)
if damageStruct.from then
room:sendLog{
type = "#Damage",
to = {damageStruct.from.id},
from = damageStruct.to.id,
arg = damageStruct.damage,
arg2 = damage_nature_table[damageStruct.damageType],
}
else
room:sendLog{
type = "#DamageWithNoFrom",
from = damageStruct.to.id,
arg = damageStruct.damage,
arg2 = damage_nature_table[damageStruct.damageType],
}
end
room:sendLogEvent("Damage", {
to = damageStruct.to.id,
damageType = damage_nature_table[damageStruct.damageType],
damageNum = damageStruct.damage,
})
end
GameEvent.functions[GameEvent.ChangeHp] = function(self) GameEvent.functions[GameEvent.ChangeHp] = function(self)
local player, num, reason, skillName, damageStruct = table.unpack(self.data) local player, num, reason, skillName, damageStruct = table.unpack(self.data)
local self = self.room local self = self.room
@ -25,32 +56,7 @@ GameEvent.functions[GameEvent.ChangeHp] = function(self)
self:broadcastProperty(player, "hp") self:broadcastProperty(player, "hp")
if reason == "damage" then if reason == "damage" then
local damage_nature_table = { sendDamageLog(self, damageStruct)
[fk.NormalDamage] = "normal_damage",
[fk.FireDamage] = "fire_damage",
[fk.ThunderDamage] = "thunder_damage",
}
if damageStruct.from then
self:sendLog{
type = "#Damage",
to = {damageStruct.from.id},
from = player.id,
arg = 0 - num,
arg2 = damage_nature_table[damageStruct.damageType],
}
else
self:sendLog{
type = "#DamageWithNoFrom",
from = player.id,
arg = 0 - num,
arg2 = damage_nature_table[damageStruct.damageType],
}
end
self:sendLogEvent("Damage", {
to = player.id,
damageType = damage_nature_table[damageStruct.damageType],
damageNum = damageStruct.damage,
})
elseif reason == "loseHp" then elseif reason == "loseHp" then
self:sendLog{ self:sendLog{
type = "#LoseHP", type = "#LoseHP",
@ -125,8 +131,21 @@ GameEvent.functions[GameEvent.Damage] = function(self)
return false return false
end end
if not self:changeHp(damageStruct.to, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then -- 先扣减护甲,再扣体力值
self.logic:breakEvent(false) local shield_to_lose = math.min(damageStruct.damage, damageStruct.to.shield)
self:changeShield(damageStruct.to, -shield_to_lose)
if shield_to_lose < damageStruct.damage then
if not self:changeHp(
damageStruct.to,
shield_to_lose - damageStruct.damage,
"damage",
damageStruct.skillName,
damageStruct) then
self.logic:breakEvent(false)
end
else
sendDamageLog(self, damageStruct)
end end
stages = { stages = {

View File

@ -122,6 +122,7 @@ function GameLogic:prepareForStart()
local general = Fk.generals[p.general] local general = Fk.generals[p.general]
p.maxHp = general.maxHp p.maxHp = general.maxHp
p.hp = general.hp p.hp = general.hp
p.shield = general.shield
-- TODO: setup AI here -- TODO: setup AI here
if p.role ~= "lord" then if p.role ~= "lord" then
@ -132,6 +133,7 @@ function GameLogic:prepareForStart()
end end
room:broadcastProperty(p, "maxHp") room:broadcastProperty(p, "maxHp")
room:broadcastProperty(p, "hp") room:broadcastProperty(p, "hp")
room:broadcastProperty(p, "shield")
end end
local allCardIds = Fk:getAllCardIds() local allCardIds = Fk:getAllCardIds()

View File

@ -1882,6 +1882,39 @@ function Room:responseCard(cardResponseEvent)
return execGameEvent(GameEvent.RespondCard, cardResponseEvent) return execGameEvent(GameEvent.RespondCard, cardResponseEvent)
end end
---@param card_name string @ 想要视为使用的牌名
---@param subcards integer[] @ 子卡可以留空或者直接nil
---@param from ServerPlayer @ 使用来源
---@param tos ServerPlayer | ServerPlayer[] @ 目标角色(列表)
---@param skillName string @ 技能名
---@param extra boolean @ 是否计入次数
function Room:useVirtualCard(card_name, subcards, from, tos, skillName, extra)
local card = Fk:cloneCard(card_name)
card.skillName = skillName
if from:prohibitUse(card) then return false end
if tos.class then tos = { tos } end
for i, p in ipairs(tos) do
if from:isProhibited(p, card) then
table.remove(tos, i)
end
end
if #tos == 0 then return false end
if subcards then card:addSubcards(Card:getIdList(subcards)) end
local use = {} ---@type CardUseStruct
use.from = from.id
use.tos = table.map(tos, function(p) return { p.id } end)
use.card = card
use.extraUse = extra
self:useCard(use)
return true
end
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- 移动牌 -- 移动牌
------------------------------------------------------------------------ ------------------------------------------------------------------------
@ -1992,6 +2025,16 @@ function Room:changeHp(player, num, reason, skillName, damageStruct)
return execGameEvent(GameEvent.ChangeHp, player, num, reason, skillName, damageStruct) return execGameEvent(GameEvent.ChangeHp, player, num, reason, skillName, damageStruct)
end end
--- 改变玩家的护甲数
---@param player ServerPlayer
---@param num integer @ 变化量
function Room:changeShield(player, num)
if num == 0 then return end
player.shield = math.max(player.shield + num, 0)
player.shield = math.min(player.shield, 5)
self:broadcastProperty(player, "shield")
end
--- 令一名玩家失去体力。 --- 令一名玩家失去体力。
---@param player ServerPlayer @ 玩家 ---@param player ServerPlayer @ 玩家
---@param num integer @ 失去的数量 ---@param num integer @ 失去的数量

View File

@ -141,6 +141,7 @@ function ServerPlayer:marshal(player)
room:notifyProperty(player, self, "maxHp") room:notifyProperty(player, self, "maxHp")
room:notifyProperty(player, self, "hp") room:notifyProperty(player, self, "hp")
room:notifyProperty(player, self, "shield")
room:notifyProperty(player, self, "gender") room:notifyProperty(player, self, "gender")
room:notifyProperty(player, self, "kingdom") room:notifyProperty(player, self, "kingdom")

View File

@ -51,6 +51,7 @@
fk.NormalDamage = 1 fk.NormalDamage = 1
fk.ThunderDamage = 2 fk.ThunderDamage = 2
fk.FireDamage = 3 fk.FireDamage = 3
fk.IceDamage = 4
--- DamageStruct 用来描述和伤害事件有关的数据。 --- DamageStruct 用来描述和伤害事件有关的数据。
---@class DamageStruct ---@class DamageStruct

View File

@ -27,6 +27,7 @@ local thunderSlashSkill = fk.CreateActiveSkill{
local thunderSlash = fk.CreateBasicCard{ local thunderSlash = fk.CreateBasicCard{
name = "thunder__slash", name = "thunder__slash",
skill = thunderSlashSkill, skill = thunderSlashSkill,
is_damage_card = true,
} }
extension:addCards{ extension:addCards{
@ -64,6 +65,7 @@ local fireSlashSkill = fk.CreateActiveSkill{
local fireSlash = fk.CreateBasicCard{ local fireSlash = fk.CreateBasicCard{
name = "fire__slash", name = "fire__slash",
skill = fireSlashSkill, skill = fireSlashSkill,
is_damage_card = true,
} }
extension:addCards{ extension:addCards{
@ -259,6 +261,7 @@ local fireAttackSkill = fk.CreateActiveSkill{
local fireAttack = fk.CreateTrickCard{ local fireAttack = fk.CreateTrickCard{
name = "fire_attack", name = "fire_attack",
skill = fireAttackSkill, skill = fireAttackSkill,
is_damage_card = true,
} }
extension:addCards{ extension:addCards{
fireAttack:clone(Card.Heart, 2), fireAttack:clone(Card.Heart, 2),

View File

@ -37,6 +37,7 @@ local slash = fk.CreateBasicCard{
name = "slash", name = "slash",
number = 7, number = 7,
suit = Card.Spade, suit = Card.Spade,
is_damage_card = true,
skill = slashSkill, skill = slashSkill,
} }
@ -310,6 +311,7 @@ local duel = fk.CreateTrickCard{
name = "duel", name = "duel",
suit = Card.Spade, suit = Card.Spade,
number = 1, number = 1,
is_damage_card = true,
skill = duelSkill, skill = duelSkill,
} }
@ -452,6 +454,7 @@ local savageAssault = fk.CreateTrickCard{
name = "savage_assault", name = "savage_assault",
suit = Card.Spade, suit = Card.Spade,
number = 7, number = 7,
is_damage_card = true,
skill = savageAssaultSkill, skill = savageAssaultSkill,
} }
@ -499,6 +502,7 @@ local archeryAttack = fk.CreateTrickCard{
name = "archery_attack", name = "archery_attack",
suit = Card.Heart, suit = Card.Heart,
number = 1, number = 1,
is_damage_card = true,
skill = archeryAttackSkill, skill = archeryAttackSkill,
} }

View File

@ -63,11 +63,11 @@ local test_active = fk.CreateActiveSkill{
return true return true
end, end,
card_filter = function(self, card) card_filter = function(self, card)
if self.interaction.data == "joy" then -- if self.interaction.data == "joy" then
--local c = Fk:getCardById(card) --local c = Fk:getCardById(card)
--return Self:getPileNameOfId(card) == self.name and c.color == Card.Red --return Self:getPileNameOfId(card) == self.name and c.color == Card.Red
return true return true
end -- end
end, end,
card_num = 2, card_num = 2,
target_filter = function() return true end, target_filter = function() return true end,
@ -86,11 +86,12 @@ local test_active = fk.CreateActiveSkill{
-- room:takeAG(from, id) -- room:takeAG(from, id)
-- room:delay(2000) -- room:delay(2000)
-- room:closeAG(from) -- room:closeAG(from)
local cards = room:askForCardsChosen(from, from, 2, 3, "hej", "") -- local cards = room:askForCardsChosen(from, from, 2, 3, "hej", "")
from:addToPile(self.name, cards) -- from:addToPile(self.name, cards)
from.kingdom = "wei" -- from.kingdom = "wei"
room:broadcastProperty(from, "kingdom") -- room:broadcastProperty(from, "kingdom")
-- p(cards) -- p(cards)
room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true)
end, end,
} }
local test_vs = fk.CreateViewAsSkill{ local test_vs = fk.CreateViewAsSkill{
@ -98,16 +99,18 @@ local test_vs = fk.CreateViewAsSkill{
card_filter = function(self, to_select, selected) card_filter = function(self, to_select, selected)
return #selected == 0 return #selected == 0
end, end,
interaction = UI.ComboBox { interaction = function(self)
choices = { return UI.ComboBox {
"ex_nihilo", choices = {
"duel", "ex_nihilo",
"snatch", "duel",
"dismantlement", "snatch",
"savage_assault", "dismantlement",
"archery_attack", "savage_assault",
"archery_attack",
}
} }
}, end,
view_as = function(self, cards) view_as = function(self, cards)
if #cards ~= 1 then if #cards ~= 1 then
return nil return nil
@ -120,6 +123,7 @@ local test_vs = fk.CreateViewAsSkill{
end, end,
} }
local test2 = General(extension, "mouxusheng", "wu", 4, 4, General.Female) local test2 = General(extension, "mouxusheng", "wu", 4, 4, General.Female)
test2.shield = 4
test2:addSkill("rende") test2:addSkill("rende")
test2:addSkill(cheat) test2:addSkill(cheat)
test2:addSkill(test_active) test2:addSkill(test_active)

View File

@ -192,6 +192,7 @@ Item {
netstate: model.netstate netstate: model.netstate
maxHp: model.maxHp maxHp: model.maxHp
hp: model.hp hp: model.hp
shield: model.shield
seatNumber: model.seatNumber seatNumber: model.seatNumber
dead: model.dead dead: model.dead
dying: model.dying dying: model.dying
@ -256,6 +257,7 @@ Item {
self.kingdom: dashboardModel.kingdom self.kingdom: dashboardModel.kingdom
self.netstate: dashboardModel.netstate self.netstate: dashboardModel.netstate
self.maxHp: dashboardModel.maxHp self.maxHp: dashboardModel.maxHp
self.shield: dashboardModel.shield
self.hp: dashboardModel.hp self.hp: dashboardModel.hp
self.seatNumber: dashboardModel.seatNumber self.seatNumber: dashboardModel.seatNumber
self.dead: dashboardModel.dead self.dead: dashboardModel.dead
@ -785,6 +787,7 @@ Item {
netstate: "online", netstate: "online",
maxHp: 0, maxHp: 0,
hp: 0, hp: 0,
shield: 0,
seatNumber: 1, seatNumber: 1,
dead: false, dead: false,
dying: false, dying: false,
@ -808,6 +811,7 @@ Item {
netstate: "online", netstate: "online",
maxHp: 0, maxHp: 0,
hp: 0, hp: 0,
shield: 0,
seatNumber: i + 1, seatNumber: i + 1,
dead: false, dead: false,
dying: false, dying: false,

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick import QtQuick
import "PhotoElement"
import "../skin-bank.js" as SkinBank import "../skin-bank.js" as SkinBank
/* Layout of general card: /* Layout of general card:
@ -18,6 +19,7 @@ CardItem {
property string kingdom property string kingdom
property int hp property int hp
property int maxHp property int maxHp
property int shieldNum
property string pkgName: "" property string pkgName: ""
name: "" name: ""
// description: Sanguosha.getGeneralDescription(name) // description: Sanguosha.getGeneralDescription(name)
@ -40,6 +42,7 @@ CardItem {
y: 4 y: 4
spacing: 1 spacing: 1
Repeater { Repeater {
id: hpRepeater
model: (hp > 5 || hp !== maxHp) ? 1 : hp model: (hp > 5 || hp !== maxHp) ? 1 : hp
Image { Image {
source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama" source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama"
@ -56,6 +59,14 @@ CardItem {
} }
} }
Shield {
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: hpRepeater.model > 4 ? 16 : 0
scale: 0.8
value: shieldNum
}
Text { Text {
width: 20 width: 20
height: 80 height: 80
@ -114,6 +125,7 @@ CardItem {
kingdom = data.kingdom; kingdom = data.kingdom;
hp = data.hp; hp = data.hp;
maxHp = data.maxHp; maxHp = data.maxHp;
shieldNum = data.shield;
let splited = name.split("__"); let splited = name.split("__");
if (splited.length > 1) { if (splited.length > 1) {

View File

@ -20,6 +20,7 @@ Item {
property alias handcards: handcardAreaItem.length property alias handcards: handcardAreaItem.length
property int maxHp: 0 property int maxHp: 0
property int hp: 0 property int hp: 0
property int shield: 0
property int seatNumber: 1 property int seatNumber: 1
property bool dead: false property bool dead: false
property bool dying: false property bool dying: false
@ -150,6 +151,7 @@ Item {
x: 8 x: 8
value: root.hp value: root.hp
maxValue: root.maxHp maxValue: root.maxHp
shieldNum: root.shield
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 36 anchors.bottomMargin: 36
} }

View File

@ -2,23 +2,31 @@
import QtQuick import QtQuick
import ".." import ".."
import "../../skin-bank.js" as SkinBank
Column { Column {
id: root id: root
property int maxValue: 4 property int maxValue: 4
property int value: 4 property int value: 4
property var colors: ["#F4180E", "#F4180E", "#E3B006", "#25EC27"] property var colors: ["#F4180E", "#F4180E", "#E3B006", "#25EC27"]
property int shieldNum: 0
Shield {
id: shield
value: shieldNum
}
Repeater { Repeater {
id: repeater id: repeater
model: maxValue <= 4 ? maxValue : 0 model: column.visible ? 0 : maxValue
Magatama { Magatama {
state: (maxValue - 1 - index) >= value ? 0 : (value >= 3 || value >= maxValue ? 3 : (value <= 0 ? 0 : value)) state: (maxValue - 1 - index) >= value ? 0 : (value >= 3 || value >= maxValue ? 3 : (value <= 0 ? 0 : value))
} }
} }
Column { Column {
visible: maxValue > 4 id: column
visible: maxValue > 4 || value > maxValue || (shieldNum > 0 && maxValue > 3)
spacing: -4 spacing: -4
Magatama { Magatama {
@ -43,11 +51,15 @@ Column {
GlowText { GlowText {
id: splitter id: splitter
height: 12
width: root.width width: root.width
text: "/" text: "/"
z: -10 z: -10
rotation: 40
color: hpItem.color color: hpItem.color
font: hpItem.font font.family: fontLibian.name
font.pixelSize: 14
font.bold: true
horizontalAlignment: hpItem.horizontalAlignment horizontalAlignment: hpItem.horizontalAlignment
glow.color: hpItem.glow.color glow.color: hpItem.glow.color

View File

@ -0,0 +1,22 @@
import QtQuick
import "../../skin-bank.js" as SkinBank
Image {
id: root
property int value: 0
width: 20
height: 21
visible: (value > 0)
source: SkinBank.MAGATAMA_DIR + "shield"
Text {
text: value
anchors.horizontalCenter: parent.horizontalCenter
y: -2
font.family: fontLibian.name
font.pixelSize: 20
font.bold: true
color: "white"
style: Text.Outline
}
}