* 修cost_data
* 修cardMark,仍需自动清理措施
* 修锁视技( **重要!使用牌之前会根据锁视技重新决定使用的卡牌!!**)
This commit is contained in:
notify 2023-06-23 22:18:11 +08:00 committed by GitHub
parent 6c0d5433c6
commit c3fd8fc9a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 3699 additions and 25 deletions

View File

@ -159,6 +159,7 @@ Item {
if (dashboard.pending_skill !== "") if (dashboard.pending_skill !== "")
dashboard.stopPending(); dashboard.stopPending();
dashboard.updateHandcards();
dashboard.disableAllCards(); dashboard.disableAllCards();
dashboard.disableSkills(); dashboard.disableSkills();
dashboard.retractAllPiles(); dashboard.retractAllPiles();
@ -176,6 +177,7 @@ Item {
ScriptAction { ScriptAction {
script: { script: {
skillInteraction.sourceComponent = undefined; skillInteraction.sourceComponent = undefined;
dashboard.updateHandcards();
dashboard.enableCards(); dashboard.enableCards();
dashboard.enableSkills(); dashboard.enableSkills();
progress.visible = true; progress.visible = true;
@ -191,6 +193,7 @@ Item {
ScriptAction { ScriptAction {
script: { script: {
skillInteraction.sourceComponent = undefined; skillInteraction.sourceComponent = undefined;
dashboard.updateHandcards();
dashboard.enableCards(responding_card); dashboard.enableCards(responding_card);
dashboard.enableSkills(responding_card, respond_play); dashboard.enableSkills(responding_card, respond_play);
autoPending = false; autoPending = false;
@ -205,6 +208,7 @@ Item {
ScriptAction { ScriptAction {
script: { script: {
skillInteraction.sourceComponent = undefined; skillInteraction.sourceComponent = undefined;
dashboard.updateHandcards();
dashboard.disableAllCards(); dashboard.disableAllCards();
dashboard.disableSkills(); dashboard.disableSkills();
progress.visible = true; progress.visible = true;

View File

@ -622,6 +622,33 @@ callbacks["PropertyUpdate"] = (jsonData) => {
} }
} }
callbacks["UpdateCard"] = (j) => {
const id = parseInt(j);
let card;
roomScene.tableCards.forEach((v) => {
if (v.cid === id) {
card = v;
return;
}
});
if (!card) {
roomScene.dashboard.handcardArea.cards.forEach((v) => {
if (v.cid === id) {
card = v;
return;
}
});
}
if (!card) {
return;
}
const data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
card.setData(data);
}
callbacks["StartGame"] = (jsonData) => { callbacks["StartGame"] = (jsonData) => {
roomScene.isStarted = true; roomScene.isStarted = true;

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 QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Fk import Fk
@ -33,6 +34,7 @@ Item {
property bool enabled: true // if false the card will be grey property bool enabled: true // if false the card will be grey
property alias card: cardItem property alias card: cardItem
property alias glow: glowItem property alias glow: glowItem
property var mark: ({})
function getColor() { function getColor() {
if (suit != "") if (suit != "")
@ -161,6 +163,57 @@ Item {
//glow.samples: 12 //glow.samples: 12
} }
Component {
id: cardMarkDelegate
Item {
width: root.width / 2
height: 16
Rectangle {
id: mark_rect
width: mark_text.width + 12
height: 16
// color: "#A50330"
radius: 4
// border.color: "snow"
// border.width: 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.7; color: "#A50330" }
GradientStop { position: 1.0; color: "transparent" }
}
}
Text {
id: mark_text
x: 2
font.pixelSize: 16
font.family: fontLibian.name
font.letterSpacing: -0.6
text: {
let ret = Backend.translate(modelData.k);
if (!modelData.k.startsWith("@@")) {
ret += modelData.v.toString();
}
return ret;
}
color: "white"
style: Text.Outline
styleColor: "purple"
}
}
}
GridLayout {
width: root.width
y: 60
columns: 2
rowSpacing: 1
columnSpacing: 0
Repeater {
model: mark
delegate: cardMarkDelegate
}
}
Rectangle { Rectangle {
visible: !root.selectable visible: !root.selectable
anchors.fill: parent anchors.fill: parent
@ -266,6 +319,7 @@ Item {
color = data.color; color = data.color;
subtype = data.subtype ? data.subtype : ""; subtype = data.subtype ? data.subtype : "";
virt_name = data.virt_name ? data.virt_name : ""; virt_name = data.virt_name ? data.virt_name : "";
mark = data.mark ?? {};
} }
function toData() function toData()
@ -278,6 +332,7 @@ Item {
color: color, color: color,
subtype: subtype, subtype: subtype,
virt_name: virt_name, virt_name: virt_name,
mark: mark,
}; };
return data; return data;
} }

View File

@ -405,6 +405,14 @@ RowLayout {
self.tremble(); self.tremble();
} }
function updateHandcards() {
Backend.callLuaFunction("FilterMyHandcards", []);
handcardAreaItem.cards.forEach(v => {
const data = JSON.parse(Backend.callLuaFunction("GetCardData", [v.cid]));
v.setData(data);
});
}
function update() { function update() {
unSelectAll(); unSelectAll();
disableSkills(); disableSkills();

View File

@ -52,6 +52,9 @@ function Client:initialize()
self.status_skills[class] = {table.unpack(skills)} self.status_skills[class] = {table.unpack(skills)}
end end
self.skill_costs = {}
self.card_marks = {}
self.filtered_cards = {}
self.disabled_packs = {} self.disabled_packs = {}
self.disabled_generals = {} self.disabled_generals = {}
end end
@ -657,11 +660,9 @@ fk.client_callback["SetCardMark"] = function(jsonData)
-- jsonData: [ int id, string mark, int value ] -- jsonData: [ int id, string mark, int value ]
local data = json.decode(jsonData) local data = json.decode(jsonData)
local card, mark, value = data[1], data[2], data[3] local card, mark, value = data[1], data[2], data[3]
ClientInstance:getCardById(card):setMark(mark, value) Fk:getCardById(card):setMark(mark, value)
if string.sub(mark, 1, 1) == "@" then ClientInstance:notifyUI("UpdateCard", tostring(card))
ClientInstance:notifyUI("SetCardMark", jsonData)
end
end end
fk.client_callback["Chat"] = function(jsonData) fk.client_callback["Chat"] = function(jsonData)

View File

@ -81,6 +81,14 @@ function GetCardData(id)
cid = id, cid = id,
known = false known = false
} end } end
local mark = {}
for k, v in pairs(card.mark) do
if k and k:startsWith("@") and v and v ~= 0 then
table.insert(mark, {
k = k, v = v,
})
end
end
local ret = { local ret = {
cid = id, cid = id,
name = card.name, name = card.name,
@ -88,6 +96,7 @@ function GetCardData(id)
number = card.number, number = card.number,
suit = card:getSuitString(), suit = card:getSuitString(),
color = card:getColorString(), color = card:getColorString(),
mark = mark,
subtype = cardSubtypeStrings[card.sub_type] subtype = cardSubtypeStrings[card.sub_type]
} }
if card.skillName ~= "" then if card.skillName ~= "" then
@ -605,4 +614,8 @@ function SetPlayerGameData(pid, data)
p.player:setGameData(table.unpack(data)) p.player:setGameData(table.unpack(data))
end end
function FilterMyHandcards()
Self:filterHandcards()
end
dofile "lua/client/i18n/init.lua" dofile "lua/client/i18n/init.lua"

View File

@ -18,6 +18,7 @@
---@field public mark table<string, integer> @ 当前拥有的所有标记,用烂了 ---@field public mark table<string, integer> @ 当前拥有的所有标记,用烂了
---@field public subcards integer[] @ 子卡ID表 ---@field public subcards integer[] @ 子卡ID表
---@field public skillName string @ 虚拟牌的技能名 for virtual cards ---@field public skillName string @ 虚拟牌的技能名 for virtual cards
---@field private _skillName string
---@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 @ 衍生技能,如重铸
@ -94,18 +95,27 @@ function Card:initialize(name, suit, number, color)
-- self.package = nil -- self.package = nil
self.id = 0 self.id = 0
self.type = 0 self.type = 0
self.sub_type = Card.SubTypeNone self.sub_type = Card.SubtypeNone
-- self.skill = nil -- self.skill = nil
self.subcards = {} self.subcards = {}
-- self.skillName = nil -- self.skillName = nil
self._skillName = "" self._skillName = ""
self.skillNames = {} self.skillNames = {}
self.mark = {} -- self.mark = {} -- 这个视情况了只有虚拟牌才有真正的self.mark真牌的话挂在currentRoom
end end
function Card:__index(k) function Card:__index(k)
if k == "skillName" then if k == "skillName" then
return self._skillName return self._skillName
elseif k == "mark" then
if not self:isVirtual() then
local mark_tab = Fk:currentRoom().card_marks
mark_tab[self.id] = mark_tab[self.id] or {}
return mark_tab[self.id]
else
self.mark = {}
return self.mark
end
end end
end end
@ -322,6 +332,15 @@ function Card:removeMark(mark, count)
end end
--- 为卡牌设置Mark至指定数量。 --- 为卡牌设置Mark至指定数量。
---
--- 关于标记的说明:
---
--- * @开头的为可见标记,其余为隐藏标记。
--- * -turn结尾、-phase结尾、-round结尾的如同玩家标记一样在这个时机自动清理。
--- * -noclear结尾的表示不要自动清理。
--- * 默认的自动清理策略是当卡牌离开手牌区后清除所有的标记。
--- * -turn之类的后缀会覆盖默认清理的方式。
--- * (TODO: 以上皆为画饼)
---@param mark string @ 标记 ---@param mark string @ 标记
---@param count integer @ 为标记删除的数量 ---@param count integer @ 为标记删除的数量
function Card:setMark(mark, count) function Card:setMark(mark, count)
@ -335,7 +354,11 @@ end
---@param mark string @ 标记 ---@param mark string @ 标记
---@return integer ---@return integer
function Card:getMark(mark) function Card:getMark(mark)
return (self.mark[mark] or 0) local ret = (self.mark[mark] or 0)
if (not self:isVirtual()) and next(self.mark) == nil then
self.mark = nil
end
return ret
end end
--- 判定卡牌是否拥有对应的Mark。 --- 判定卡牌是否拥有对应的Mark。

View File

@ -21,6 +21,7 @@
---@field public game_modes table<string, GameMode> @ 所有游戏模式 ---@field public game_modes table<string, GameMode> @ 所有游戏模式
---@field public currentResponsePattern string @ 要求用牌的种类(如要求用特定花色的桃···) ---@field public currentResponsePattern string @ 要求用牌的种类(如要求用特定花色的桃···)
---@field public currentResponseReason string @ 要求用牌的原因(如濒死,被特定牌指定,使用特定技能···) ---@field public currentResponseReason string @ 要求用牌的原因(如濒死,被特定牌指定,使用特定技能···)
---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌
local Engine = class("Engine") local Engine = class("Engine")
--- Engine的构造函数。 --- Engine的构造函数。
@ -57,6 +58,7 @@ end
local _foreign_keys = { local _foreign_keys = {
"currentResponsePattern", "currentResponsePattern",
"currentResponseReason", "currentResponseReason",
"filtered_cards",
} }
function Engine:__index(k) function Engine:__index(k)
@ -381,8 +383,6 @@ function Engine:getAllCardIds(except)
return result return result
end end
local filtered_cards = {}
--- 根据id返回相应的卡牌。 --- 根据id返回相应的卡牌。
---@param id integer @ 牌的id ---@param id integer @ 牌的id
---@param ignoreFilter boolean @ 是否要无视掉锁定视为技,直接获得真牌 ---@param ignoreFilter boolean @ 是否要无视掉锁定视为技,直接获得真牌
@ -390,7 +390,7 @@ local filtered_cards = {}
function Engine:getCardById(id, ignoreFilter) function Engine:getCardById(id, ignoreFilter)
local ret = self.cards[id] local ret = self.cards[id]
if not ignoreFilter then if not ignoreFilter then
ret = filtered_cards[id] or self.cards[id] ret = self.filtered_cards[id] or self.cards[id]
end end
return ret return ret
end end
@ -402,14 +402,14 @@ end
function Engine:filterCard(id, player, data) function Engine:filterCard(id, player, data)
local card = self:getCardById(id, true) local card = self:getCardById(id, true)
if player == nil then if player == nil then
filtered_cards[id] = nil self.filtered_cards[id] = nil
return return
end end
local skills = player:getAllSkills() local skills = player:getAllSkills()
local filters = self:currentRoom().status_skills[FilterSkill] or Util.DummyTable local filters = self:currentRoom().status_skills[FilterSkill] or Util.DummyTable
if #filters == 0 then if #filters == 0 then
filtered_cards[id] = nil self.filtered_cards[id] = nil
return return
end end
@ -446,11 +446,11 @@ function Engine:filterCard(id, player, data)
if card == nil then if card == nil then
card = self:getCardById(id) card = self:getCardById(id)
end end
filtered_cards[id] = card self.filtered_cards[id] = card
end end
if modify then if modify then
filtered_cards[id] = nil self.filtered_cards[id] = nil
data.card = card data.card = card
return return
end end

View File

@ -344,6 +344,12 @@ function Player:getHandcardNum()
return #self:getCardIds(Player.Hand) return #self:getCardIds(Player.Hand)
end end
function Player:filterHandcards()
for _, id in ipairs(self:getCardIds(Player.Hand)) do
Fk:filterCard(id, self)
end
end
--- 检索玩家装备区是否存在对应类型的装备。 --- 检索玩家装备区是否存在对应类型的装备。
---@param cardSubtype CardSubtype @ 卡牌子类 ---@param cardSubtype CardSubtype @ 卡牌子类
---@return integer|null @ 返回卡牌ID或nil ---@return integer|null @ 返回卡牌ID或nil

View File

@ -5,6 +5,8 @@
-- Note: these files are not used by FreeKill. -- Note: these files are not used by FreeKill.
-- Just for convenience when using sumneko.lua -- Just for convenience when using sumneko.lua
---@alias null nil
---@class fk ---@class fk
---FreeKill's lua API ---FreeKill's lua API
fk = {} fk = {}
@ -35,3 +37,8 @@ function fk.QmlBackend_exists(file)end
---@return boolean ---@return boolean
function fk.QmlBackend_isDir(file)end function fk.QmlBackend_isDir(file)end
function fk.qCritical(msg) end
function fk.qInfo(msg) end
function fk.qDebug(msg) end
function fk.qWarning(msg) end

View File

@ -14,6 +14,9 @@ function FPlayer:getScreenName()end
---@return string avatar ---@return string avatar
function FPlayer:getAvatar()end function FPlayer:getAvatar()end
---@class fk.ServerPlayer : fk.Player
FServerPlayer = {}
--- Send a request to client, and allow client to reply within *timeout* seconds. --- Send a request to client, and allow client to reply within *timeout* seconds.
--- ---
--- *timeout* must not be negative or **nil**. --- *timeout* must not be negative or **nil**.
@ -34,3 +37,9 @@ function FServerPlayer:waitForReply(timeout)end
---@param command string ---@param command string
---@param jsonData string ---@param jsonData string
function FServerPlayer:doNotify(command,jsonData)end function FServerPlayer:doNotify(command,jsonData)end
function FServerPlayer:setBusy(_) end
function FServerPlayer:isBusy(_) end
function FServerPlayer:setThinking(_) end
function FServerPlayer:getState() end

View File

@ -119,7 +119,9 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
self:doBroadcastNotify("UpdateDrawPile", #self.draw_pile) self:doBroadcastNotify("UpdateDrawPile", #self.draw_pile)
end end
Fk:filterCard(info.cardId, self:getPlayerById(data.to)) if not (data.to and data.toArea ~= Card.PlayerHand) then
Fk:filterCard(info.cardId, self:getPlayerById(data.to))
end
local currentCard = Fk:getCardById(info.cardId) local currentCard = Fk:getCardById(info.cardId)
if if

View File

@ -52,6 +52,7 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
Fk:filterCard(_card.id, room:getPlayerById(from), temp) Fk:filterCard(_card.id, room:getPlayerById(from), temp)
card = temp.card card = temp.card
end end
cardUseEvent.card = card
playCardEmotionAndSound(room, room:getPlayerById(from), card) playCardEmotionAndSound(room, room:getPlayerById(from), card)
room:doAnimate("Indicate", { room:doAnimate("Indicate", {

View File

@ -27,6 +27,7 @@
---@field public request_queue table<userdata, table> ---@field public request_queue table<userdata, table>
---@field public request_self table<integer, integer> ---@field public request_self table<integer, integer>
---@field public skill_costs table<string, any> @ 存放skill.cost_data用 ---@field public skill_costs table<string, any> @ 存放skill.cost_data用
---@field public card_marks table<integer, any> @ 存放card.mark之用
local Room = class("Room") local Room = class("Room")
-- load classes used by the game -- load classes used by the game
@ -88,6 +89,8 @@ function Room:initialize(_room)
self.request_queue = {} self.request_queue = {}
self.request_self = {} self.request_self = {}
self.skill_costs = {} self.skill_costs = {}
self.card_marks = {}
self.filtered_cards = {}
self.settings = json.decode(self.room:settings()) self.settings = json.decode(self.room:settings())
self.disabled_packs = self.settings.disabledPack self.disabled_packs = self.settings.disabledPack
@ -465,11 +468,13 @@ end
---@param value integer @ 要设为的值,其实也可以设为字符串 ---@param value integer @ 要设为的值,其实也可以设为字符串
function Room:setCardMark(card, mark, value) function Room:setCardMark(card, mark, value)
card:setMark(mark, value) card:setMark(mark, value)
self:doBroadcastNotify("SetCardMark", json.encode{ if not card:isVirtual() then
card.id, self:doBroadcastNotify("SetCardMark", json.encode{
mark, card.id,
value mark,
}) value
})
end
end end
--- 将一张卡牌的mark标记增加count个。 --- 将一张卡牌的mark标记增加count个。
@ -1168,7 +1173,7 @@ function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancel
if includeEquip then if includeEquip then
table.insertTable(hands, player:getCardIds(Player.Equip)) table.insertTable(hands, player:getCardIds(Player.Equip))
end end
for i = 1, minNum do for _ = 1, minNum do
local randomId = hands[math.random(1, #hands)] local randomId = hands[math.random(1, #hands)]
table.insert(chosenCards, randomId) table.insert(chosenCards, randomId)
table.removeOne(hands, randomId) table.removeOne(hands, randomId)
@ -1371,7 +1376,7 @@ end
---@param prompt string|null @ 观星框的标题(暂时雪藏) ---@param prompt string|null @ 观星框的标题(暂时雪藏)
---@param noPut boolean|null @ 是否进行放置牌操作 ---@param noPut boolean|null @ 是否进行放置牌操作
---@param areaNames string[]|null @ 左侧提示信息 ---@param areaNames string[]|null @ 左侧提示信息
---@return table<top|bottom, cardId[]> ---@return table<"top"|"bottom", integer[]>
function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotify, noPut, areaNames) function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotify, noPut, areaNames)
-- 这一大堆都是来提前报错的 -- 这一大堆都是来提前报错的
top_limit = top_limit or Util.DummyTable top_limit = top_limit or Util.DummyTable
@ -1441,10 +1446,10 @@ end
--- 询问玩家任意交换几堆牌堆。 --- 询问玩家任意交换几堆牌堆。
--- ---
---@param player ServerPlayer @ 要询问的玩家 ---@param player ServerPlayer @ 要询问的玩家
---@param piles table<cardIds, cardId[]> @ 卡牌id列表的列表也就是……几堆牌堆的集合 ---@param piles table<cardIds, integer[]> @ 卡牌id列表的列表也就是……几堆牌堆的集合
---@param piles_name string[] @ 牌堆名必须一一对应否则统一替换为“牌堆X” ---@param piles_name string[] @ 牌堆名必须一一对应否则统一替换为“牌堆X”
---@param customNotify string|null @ 自定义读条操作提示 ---@param customNotify string|null @ 自定义读条操作提示
---@return table<cardIds, cardId[]> ---@return table<cardIds, integer[]>
function Room:askForExchange(player, piles, piles_name, customNotify) function Room:askForExchange(player, piles, piles_name, customNotify)
local command = "AskForExchange" local command = "AskForExchange"
piles_name = piles_name or Util.DummyTable piles_name = piles_name or Util.DummyTable
@ -1775,7 +1780,7 @@ end
---@param skillName string @ 技能名 ---@param skillName string @ 技能名
---@param flag string|null @ 限定可移动的区域值为nil装备区和判定区ej ---@param flag string|null @ 限定可移动的区域值为nil装备区和判定区ej
---@param moveFrom ServerPlayer|null @ 是否只是目标1移动给目标2 ---@param moveFrom ServerPlayer|null @ 是否只是目标1移动给目标2
---@return cardId ---@return integer
function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName, flag, moveFrom) function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName, flag, moveFrom)
if flag then if flag then
assert(flag == "e" or flag == "j") assert(flag == "e" or flag == "j")

23
test/lua/lib/fk.lua Normal file
View File

@ -0,0 +1,23 @@
-- 为纯lua的测试环境捏一个虚拟的fk以便于测试
local fk = {}
local os, io = os, io
-- 这下Linux专用了
function fk.QmlBackend_ls(dir)
local f = io.popen("ls " .. dir)
return f:read("*a"):split("\n")
end
function fk.QmlBackend_isDir(dir)
local f = io.popen("if [ -d " .. dir .. " ]; then echo OK; fi")
return f:read("*a"):startsWith("OK")
end
function fk.QmlBackend_exists(dir)
local f = io.popen("if [ -e " .. dir .. " ]; then echo OK; fi")
return f:read("*a"):startsWith("OK")
end
return fk

3454
test/lua/lib/luaunit.lua Normal file

File diff suppressed because it is too large Load Diff

22
test/lua/pattern.lua Normal file
View File

@ -0,0 +1,22 @@
local exp1 = Exppattern:Parse("slash,jink")
local exp2 = Exppattern:Parse("peach,jink")
local exp3 = Exppattern:Parse(".|.|.|.|.|trick")
local exp4 = Exppattern:Parse("peach,ex_nihilo")
local slash = Fk:cloneCard("slash")
TestExppattern = {
testMatchExp = function()
assert(exp1:matchExp(exp2))
end,
testEasyMatchCard = function()
assert(exp1:match(slash))
assert(not exp2:match(slash))
end,
testMatchWithType = function()
assert(not exp3:matchExp(exp1))
assert(exp3:matchExp(exp4))
end,
}

14
test/lua/run.lua Normal file
View File

@ -0,0 +1,14 @@
-- Run tests with `lua5.4 test/lua/run.lua`
---@diagnostic disable: lowercase-global
package.path = package.path .. ";./test/lua/lib/?.lua"
lu = require('luaunit')
local os = os
fk = require('fk')
dofile 'lua/freekill.lua'
dofile 'test/lua/pattern.lua'
os.exit( lu.LuaUnit.run() )