- 复活角色
- 将cancelable全改为默认true
- move私有牌堆的未知牌时不再显示错误
- 处理区牌增加大多数脚注
- 装备栏有宝物时压缩间距
- 使用虚拟牌时处理区有虚拟名字
- 带详细描述的选择框
- 武将一览界面显示技能语音、胜利语音、死亡语音
This commit is contained in:
notify 2023-06-10 02:18:51 +08:00 committed by GitHub
parent 04f1009075
commit 7f718503bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 390 additions and 57 deletions

View File

@ -17,19 +17,19 @@ ColumnLayout {
font.pixelSize: 20 font.pixelSize: 20
font.bold: true font.bold: true
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WrapAnywhere wrapMode: Text.WordWrap
} }
Text { Text {
text: qsTr(hint) text: qsTr(hint)
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WrapAnywhere wrapMode: Text.WordWrap
} }
Text { Text {
text: qsTr("validator_hint") text: qsTr("validator_hint")
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WrapAnywhere wrapMode: Text.WordWrap
} }
TextField { TextField {

View File

@ -12,7 +12,7 @@ ColumnLayout {
Text { Text {
text: qsTr("help_text") text: qsTr("help_text")
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WrapAnywhere wrapMode: Text.WordWrap
} }
RowLayout { RowLayout {
@ -54,7 +54,7 @@ ColumnLayout {
Text { Text {
text: qsTr("key_help_text") text: qsTr("key_help_text")
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WrapAnywhere wrapMode: Text.WordWrap
textFormat: Text.RichText textFormat: Text.RichText
onLinkActivated: Qt.openUrlExternally(link); onLinkActivated: Qt.openUrlExternally(link);
} }

View File

@ -148,16 +148,42 @@ Item {
radius: 8 radius: 8
property string general: "caocao" property string general: "caocao"
function addSkillAudio(skill) {
const skilldata = JSON.parse(Backend.callLuaFunction("GetSkillData", [skill]));
if (!skilldata) return;
const extension = skilldata.extension;
for (let i = 0; i < 999; i++) {
let fname = AppPath + "/packages/" + extension + "/audio/skill/" +
skill + (i !== 0 ? i.toString() : "") + ".mp3";
if (Backend.exists(fname)) {
audioModel.append({ name: skill, idx: i });
} else {
if (i > 0) break;
}
}
}
function updateGeneral() { function updateGeneral() {
detailGeneralCard.name = general; detailGeneralCard.name = general;
const data = JSON.parse(Backend.callLuaFunction("GetGeneralDetail", [general])); const data = JSON.parse(Backend.callLuaFunction("GetGeneralDetail", [general]));
generalText.clear(); generalText.clear();
audioModel.clear();
data.skill.forEach(t => { data.skill.forEach(t => {
generalText.append("<b>" + Backend.translate(t.name) + "</b>: " + t.description) generalText.append("<b>" + Backend.translate(t.name) +
"</b>: " + t.description);
addSkillAudio(t.name);
}); });
data.related_skill.forEach(t => { data.related_skill.forEach(t => {
generalText.append("<font color=\"purple\"><b>" + Backend.translate(t.name) + "</b>: " + t.description + "</font>") generalText.append("<font color=\"purple\"><b>" + Backend.translate(t.name) +
"</b>: " + t.description + "</font>");
addSkillAudio(t.name);
}); });
addSkillAudio(general + "_win_audio");
} }
Flickable { Flickable {
@ -190,6 +216,58 @@ Item {
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
font.pixelSize: 16 font.pixelSize: 16
} }
Repeater {
model: ListModel {
id: audioModel
}
Button {
Layout.fillWidth: true
contentItem: ColumnLayout {
Text {
Layout.fillWidth: true
text: Backend.translate(name) + (idx ? " (" + idx.toString() + ")" : "")
font.bold: true
font.pixelSize: 14
}
Text {
Layout.fillWidth: true
text: Backend.translate("$" + name + (idx ? idx.toString() : ""))
wrapMode: Text.WordWrap
}
}
onClicked: {
const skilldata = JSON.parse(Backend.callLuaFunction("GetSkillData", [name]));
const extension = skilldata.extension;
Backend.playSound("./packages/" + extension +
"/audio/skill/" + name, idx);
}
}
}
Button {
Layout.fillWidth: true
contentItem: ColumnLayout {
Text {
Layout.fillWidth: true
text: Backend.translate("Death audio")
font.bold: true
font.pixelSize: 14
}
Text {
Layout.fillWidth: true
text: Backend.translate("~" + generalDetail.general)
wrapMode: Text.WordWrap
}
}
onClicked: {
const general = generalDetail.general
const extension = JSON.parse(Backend.callLuaFunction("GetGeneralData", [general])).extension;
Backend.playSound("./packages/" + extension + "/audio/death/" + general);
}
}
} }
} }
} }

View File

@ -39,7 +39,7 @@ Item {
Text { Text {
id: bulletin_info id: bulletin_info
width: parent.width width: parent.width
wrapMode: TextEdit.WrapAnywhere wrapMode: TextEdit.WordWrap
textFormat: Text.MarkdownText textFormat: Text.MarkdownText
text: Backend.translate('Bulletin Info') text: Backend.translate('Bulletin Info')
} }

View File

@ -263,6 +263,53 @@ function setEmotion(id, emotion, isCardId) {
animation.start(); animation.start();
} }
function setCardFootnote(id, footnote) {
let card;
roomScene.tableCards.forEach((v) => {
if (v.cid === id) {
card = v;
return;
}
});
if (!card) {
return;
}
card.footnote = footnote;
card.footnoteVisible = true;
}
callbacks["SetCardFootnote"] = (j) => {
const data = JSON.parse(j);
const id = data[0];
const note = data[1];
setCardFootnote(id, note);
}
function setCardVirtName(id, name) {
let card;
roomScene.tableCards.forEach((v) => {
if (v.cid === id) {
card = v;
return;
}
});
if (!card) {
return;
}
card.virt_name = name;
}
callbacks["SetCardVirtName"] = (j) => {
const data = JSON.parse(j);
const ids = data[0];
const note = data[1];
ids.forEach(id => setCardVirtName(id, note));
}
function changeHp(id, delta, losthp) { function changeHp(id, delta, losthp) {
const photo = getPhoto(id); const photo = getPhoto(id);
if (!photo) { if (!photo) {
@ -754,6 +801,7 @@ callbacks["AskForChoice"] = (jsonData) => {
const choices = data[0]; const choices = data[0];
const skill_name = data[1]; const skill_name = data[1];
const prompt = data[2]; const prompt = data[2];
const detailed = data[3];
if (prompt === "") { if (prompt === "") {
roomScene.promptText = Backend.translate("#AskForChoice") roomScene.promptText = Backend.translate("#AskForChoice")
.arg(Backend.translate(skill_name)); .arg(Backend.translate(skill_name));
@ -761,7 +809,13 @@ callbacks["AskForChoice"] = (jsonData) => {
roomScene.promptText = processPrompt(prompt); roomScene.promptText = processPrompt(prompt);
} }
roomScene.state = "replying"; roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChoiceBox.qml"); let qmlSrc;
if (!detailed) {
qmlSrc = "../RoomElement/ChoiceBox.qml";
} else {
qmlSrc = "../RoomElement/DetailedChoiceBox.qml";
}
roomScene.popupBox.sourceComponent = Qt.createComponent(qmlSrc);
const box = roomScene.popupBox.item; const box = roomScene.popupBox.item;
box.options = choices; box.options = choices;
box.skill_name = skill_name; box.skill_name = skill_name;

View File

@ -13,9 +13,9 @@ import Fk.RoomElement
*/ */
Column { Column {
height: 88 height: 70
width: 138 width: 138
property int itemHeight: Math.floor(height / 4) property int itemHeight: treasureItem.name === "" ? height / 3 : height / 4
property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem] property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem]
property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"] property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"]
property int length: area.length property int length: area.length
@ -29,7 +29,7 @@ Column {
EquipItem { EquipItem {
id: treasureItem id: treasureItem
width: parent.width width: parent.width
height: itemHeight height: name === "" ? 0 : itemHeight
opacity: 0 opacity: 0
} }

View File

@ -120,31 +120,44 @@ Item {
} }
Rectangle { Rectangle {
id: virt_rect
visible: root.virt_name !== "" visible: root.virt_name !== ""
width: parent.width width: parent.width
height: 14 height: 20
anchors.verticalCenter: parent.verticalCenter y: 40
Text { color: "snow"
anchors.centerIn: parent opacity: 0.8
text: Backend.translate(root.virt_name) radius: 4
} border.color: "black"
border.width: 1
} }
GlowText { Text {
visible: virt_rect.visible
anchors.centerIn: virt_rect
font.pixelSize: 16
font.family: fontLibian.name
font.letterSpacing: -0.6
text: Backend.translate(root.virt_name)
}
Text {
id: footnoteItem id: footnoteItem
text: footnote text: footnote
x: 6 x: 0
y: parent.height - height - 6 y: parent.height - height - 10
width: root.width - x * 2 width: root.width - x * 2
color: "#E4D5A0" color: "#E4D5A0"
// color: "white"
visible: footnoteVisible visible: footnoteVisible
style: Text.Outline
wrapMode: Text.WrapAnywhere wrapMode: Text.WrapAnywhere
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.family: fontLibian.name font.family: fontLibian.name
font.pixelSize: 14 font.pixelSize: 14
glow.color: "black" // glow.color: "black"
glow.spread: 1 // glow.spread: 1
glow.radius: 1 // glow.radius: 1
//glow.samples: 12 //glow.samples: 12
} }

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Fk.Pages
GraphicsBox {
property var options: []
property string skill_name: ""
property int result
id: root
title.text: Backend.translate("$Choice").arg(Backend.translate(skill_name))
width: Math.max(140, body.width + 20)
height: body.height + title.height + 20
ListView {
id: body
x: 10
y: title.height + 5
width: Math.min(700, 220 * model.length)
height: 300
orientation: ListView.Horizontal
clip: true
spacing: 20
model: options
delegate: Item {
width: 200
height: 290
MetroButton {
id: choicetitle
width: parent.width
text: Backend.translate(modelData)
textFont.pixelSize: 24
anchors.top: choiceDetail.bottom
anchors.topMargin: 8
onClicked: {
result = index;
root.close();
}
}
Flickable {
id: choiceDetail
x: 4
height: parent.height - choicetitle.height
contentHeight: detail.height
width: parent.width
clip: true
Text {
id: detail
width: parent.width
text: Backend.translate(":" + modelData)
color: "white"
wrapMode: Text.WordWrap
font.pixelSize: 16
textFormat: TextEdit.RichText
}
}
}
}
}

View File

@ -45,7 +45,7 @@ GraphicsBox {
font.family: fontLibian.name font.family: fontLibian.name
font.pixelSize: 18 font.pixelSize: 18
style: Text.Outline style: Text.Outline
wrapMode: Text.WrapAnywhere wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }

View File

@ -130,7 +130,7 @@ Item {
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
color: "white" color: "white"
width: 24 width: 24
wrapMode: Text.WordWrap wrapMode: Text.WrapAnywhere
text: "" text: ""
} }
@ -205,7 +205,7 @@ Item {
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
color: "white" color: "white"
width: 24 width: 24
wrapMode: Text.WordWrap wrapMode: Text.WrapAnywhere
text: Backend.translate(deputyGeneral) text: Backend.translate(deputyGeneral)
style: Text.Outline style: Text.Outline
} }
@ -278,7 +278,7 @@ Item {
id: equipAreaItem id: equipAreaItem
x: 31 x: 31
y: 139 y: 157
} }
Item { Item {

View File

@ -66,7 +66,7 @@ Item {
c.selectable = true; c.selectable = true;
c.height = c.height * 0.8; c.height = c.height * 0.8;
c.width = c.width * 0.8; c.width = c.width * 0.8;
c.rotation = (Math.random() - 0.5) * 5; // c.rotation = (Math.random() - 0.5) * 5;
} }
} }
@ -81,7 +81,7 @@ Item {
c.selectable = false; c.selectable = false;
c.height = c.height / 0.8; c.height = c.height / 0.8;
c.width = c.width / 0.8; c.width = c.width / 0.8;
c.rotation = 0; // c.rotation = 0;
} }
const vanished = []; const vanished = [];
if (result.length < outputs.length) { if (result.length < outputs.length) {

View File

@ -117,9 +117,11 @@ function Client:moveCards(moves)
end end
---@param msg LogMessage ---@param msg LogMessage
function Client:appendLog(msg) local function parseMsg(msg, nocolor)
local self = ClientInstance
local data = msg local data = msg
local function getPlayerStr(pid, color) local function getPlayerStr(pid, color)
if nocolor then color = "white" end
if not pid then if not pid then
return "" return ""
end end
@ -177,7 +179,7 @@ function Client:appendLog(msg)
local function parseArg(arg) local function parseArg(arg)
arg = arg or "" arg = arg or ""
arg = Fk:translate(arg) arg = Fk:translate(arg)
arg = string.format('<font color="#0598BC"><b>%s</b></font>', arg) arg = string.format('<font color="%s"><b>%s</b></font>', nocolor and "white" or "#0598BC", arg)
return arg return arg
end end
@ -192,7 +194,26 @@ function Client:appendLog(msg)
log = string.gsub(log, "%%arg2", arg2) log = string.gsub(log, "%%arg2", arg2)
log = string.gsub(log, "%%arg3", arg3) log = string.gsub(log, "%%arg3", arg3)
log = string.gsub(log, "%%arg", arg) log = string.gsub(log, "%%arg", arg)
self:notifyUI("GameLog", log) return log
end
---@param msg LogMessage
function Client:appendLog(msg)
self:notifyUI("GameLog", parseMsg(msg))
end
---@param msg LogMessage
function Client:setCardNote(ids, msg)
for _, id in ipairs(ids) do
if id ~= -1 then
self:notifyUI("SetCardFootnote", json.encode{ id, parseMsg(msg, true) })
end
end
end
fk.client_callback["SetCardFootnote"] = function(jsonData)
local data = json.decode(jsonData)
ClientInstance:setCardNote(data[1], data[2]);
end end
fk.client_callback["Setup"] = function(jsonData) fk.client_callback["Setup"] = function(jsonData)
@ -412,28 +433,33 @@ local function mergeMoves(moves)
end end
local function sendMoveCardLog(move) local function sendMoveCardLog(move)
local client = ClientInstance
if #move.ids == 0 then return end if #move.ids == 0 then return end
local hidden = table.contains(move.ids, -1) local hidden = table.contains(move.ids, -1)
local msgtype local msgtype
if move.from and move.toArea == Card.DrawPile then if move.from and move.toArea == Card.DrawPile then
msgtype = hidden and "$PutCard" or "$PutKnownCard" msgtype = hidden and "$PutCard" or "$PutKnownCard"
ClientInstance:appendLog{ client:appendLog{
type = msgtype, type = msgtype,
from = move.from, from = move.from,
card = move.ids, card = move.ids,
arg = #move.ids, arg = #move.ids,
} }
client:setCardNote(move.ids, {
type = "$$PutCard",
from = move.from,
})
elseif move.toArea == Card.PlayerSpecial then elseif move.toArea == Card.PlayerSpecial then
msgtype = hidden and "$RemoveCardFromGame" or "$AddToPile" msgtype = hidden and "$RemoveCardFromGame" or "$AddToPile"
ClientInstance:appendLog{ client:appendLog{
type = msgtype, type = msgtype,
arg = move.specialName, arg = move.specialName,
arg2 = #move.ids, arg2 = #move.ids,
card = move.ids, card = move.ids,
} }
elseif move.fromArea == Card.PlayerSpecial and move.to then elseif move.fromArea == Card.PlayerSpecial and move.to then
ClientInstance:appendLog{ client:appendLog{
type = "$GetCardsFromPile", type = "$GetCardsFromPile",
from = move.to, from = move.to,
arg = move.fromSpecialName, arg = move.fromSpecialName,
@ -441,7 +467,7 @@ local function sendMoveCardLog(move)
card = move.ids, card = move.ids,
} }
elseif move.moveReason == fk.ReasonDraw then elseif move.moveReason == fk.ReasonDraw then
ClientInstance:appendLog{ client:appendLog{
type = "$DrawCards", type = "$DrawCards",
from = move.to, from = move.to,
card = move.ids, card = move.ids,
@ -449,7 +475,7 @@ local function sendMoveCardLog(move)
} }
elseif (move.fromArea == Card.DrawPile or move.fromArea == Card.DiscardPile) elseif (move.fromArea == Card.DrawPile or move.fromArea == Card.DiscardPile)
and move.moveReason == fk.ReasonPrey then and move.moveReason == fk.ReasonPrey then
ClientInstance:appendLog{ client:appendLog{
type = "$PreyCardsFromPile", type = "$PreyCardsFromPile",
from = move.to, from = move.to,
card = move.ids, card = move.ids,
@ -457,14 +483,14 @@ local function sendMoveCardLog(move)
} }
elseif (move.fromArea == Card.Processing or move.fromArea == Card.PlayerJudge) elseif (move.fromArea == Card.Processing or move.fromArea == Card.PlayerJudge)
and move.toArea == Card.PlayerHand then and move.toArea == Card.PlayerHand then
ClientInstance:appendLog{ client:appendLog{
type = "$GotCardBack", type = "$GotCardBack",
from = move.to, from = move.to,
card = move.ids, card = move.ids,
arg = #move.ids, arg = #move.ids,
} }
elseif move.fromArea == Card.DiscardPile and move.toArea == Card.PlayerHand then elseif move.fromArea == Card.DiscardPile and move.toArea == Card.PlayerHand then
ClientInstance:appendLog{ client:appendLog{
type = "$RecycleCard", type = "$RecycleCard",
from = move.to, from = move.to,
card = move.ids, card = move.ids,
@ -472,7 +498,7 @@ local function sendMoveCardLog(move)
} }
elseif move.from and move.fromArea ~= Card.PlayerJudge and elseif move.from and move.fromArea ~= Card.PlayerJudge and
move.toArea ~= Card.PlayerJudge and move.to and move.from ~= move.to then move.toArea ~= Card.PlayerJudge and move.to and move.from ~= move.to then
ClientInstance:appendLog{ client:appendLog{
type = "$MoveCards", type = "$MoveCards",
from = move.from, from = move.from,
to = { move.to }, to = { move.to },
@ -486,7 +512,7 @@ local function sendMoveCardLog(move)
msgtype = "$PasteCard" msgtype = "$PasteCard"
end end
if msgtype then if msgtype then
ClientInstance:appendLog{ client:appendLog{
type = msgtype, type = msgtype,
from = move.from, from = move.from,
to = { move.to }, to = { move.to },
@ -497,12 +523,16 @@ local function sendMoveCardLog(move)
-- TODO ... -- TODO ...
if move.moveReason == fk.ReasonDiscard then if move.moveReason == fk.ReasonDiscard then
ClientInstance:appendLog{ client:appendLog{
type = "$DiscardCards", type = "$DiscardCards",
from = move.from, from = move.from,
card = move.ids, card = move.ids,
arg = #move.ids, arg = #move.ids,
} }
client:setCardNote(move.ids, {
type = "$$DiscardCards",
from = move.from
})
end end
end end
@ -512,10 +542,10 @@ fk.client_callback["MoveCards"] = function(jsonData)
local separated = separateMoves(raw_moves) local separated = separateMoves(raw_moves)
ClientInstance:moveCards(separated) ClientInstance:moveCards(separated)
local merged = mergeMoves(separated) local merged = mergeMoves(separated)
ClientInstance:notifyUI("MoveCards", json.encode(merged))
for _, move in ipairs(merged) do for _, move in ipairs(merged) do
sendMoveCardLog(move) sendMoveCardLog(move)
end end
ClientInstance:notifyUI("MoveCards", json.encode(merged))
end end
fk.client_callback["ShowCard"] = function(jsonData) fk.client_callback["ShowCard"] = function(jsonData)

View File

@ -130,6 +130,7 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["Quit"] = "退出", ["Quit"] = "退出",
["BanGeneral"] = "禁将", ["BanGeneral"] = "禁将",
["ResumeGeneral"] = "解禁", ["ResumeGeneral"] = "解禁",
["Death audio"] = "阵亡",
["$WelcomeToLobby"] = "欢迎进入新月杀游戏大厅!", ["$WelcomeToLobby"] = "欢迎进入新月杀游戏大厅!",
@ -350,9 +351,22 @@ Fk:loadTranslationTable{
["#EnterDying"] = "%from 进入了濒死阶段", ["#EnterDying"] = "%from 进入了濒死阶段",
["#KillPlayer"] = "%from [%arg] 阵亡,凶手是 %to", ["#KillPlayer"] = "%from [%arg] 阵亡,凶手是 %to",
["#KillPlayerWithNoKiller"] = "%from [%arg] 阵亡,无伤害来源", ["#KillPlayerWithNoKiller"] = "%from [%arg] 阵亡,无伤害来源",
["#Revive"] = "%from 竟然复活了",
-- misc -- misc
["#GuanxingResult"] = "%from 的观星结果为 %arg 上 %arg2 下", ["#GuanxingResult"] = "%from 的观星结果为 %arg 上 %arg2 下",
["#ChainStateChange"] = "%from %arg 了武将牌", ["#ChainStateChange"] = "%from %arg 了武将牌",
["#ChainDamage"] = "%from 处于连环状态,将受到传导的伤害", ["#ChainDamage"] = "%from 处于连环状态,将受到传导的伤害",
} }
-- card footnote
Fk:loadTranslationTable{
["$$DiscardCards"] = "%from弃置",
["$$PutCard"] = "%from置于",
["##UseCard"] = "%from使用",
["##UseCardTo"] = "%from对%to",
["##ResponsePlayCard"] = "%from打出",
["##ShowCard"] = "%from展示",
["##JudgeCard"] = "%arg判定",
}

View File

@ -244,7 +244,14 @@ function Player:removeCards(playerArea, cardIds, specialName)
break break
end end
if table.contains(fromAreaIds, id) then
table.removeOne(fromAreaIds, id) table.removeOne(fromAreaIds, id)
-- FIXME: 为客户端移动id为-1的牌考虑但总感觉有地方需要商讨啊
elseif table.every(fromAreaIds, function(e) return e == -1 end) then
table.remove(fromAreaIds, 1)
elseif id == -1 then
table.remove(fromAreaIds, 1)
end
end end
end end
end end

View File

@ -21,6 +21,10 @@ GameEvent.functions[GameEvent.Judge] = function(self)
card = {data.card.id}, card = {data.card.id},
} }
self:moveCardTo(data.card, Card.Processing, nil, fk.ReasonJudge) self:moveCardTo(data.card, Card.Processing, nil, fk.ReasonJudge)
self:sendFootnote({ data.card.id }, {
type = "##JudgeCard",
arg = data.reason,
})
self.logic:trigger(fk.AskForRetrial, who, data) self.logic:trigger(fk.AskForRetrial, who, data)
self.logic:trigger(fk.FinishRetrial, who, data) self.logic:trigger(fk.FinishRetrial, who, data)
@ -30,6 +34,10 @@ GameEvent.functions[GameEvent.Judge] = function(self)
from = who.id, from = who.id,
card = {data.card.id}, card = {data.card.id},
} }
self:sendFootnote({ data.card.id }, {
type = "##JudgeCard",
arg = data.reason,
})
if data.pattern then if data.pattern then
self:delay(400); self:delay(400);

View File

@ -154,6 +154,27 @@ local sendCardEmotionAndLog = function(room, cardUseEvent)
} }
end end
end end
if #useCardIds == 0 then return end
if cardUseEvent.tos and #cardUseEvent.tos > 0 and #cardUseEvent.tos <= 2 then
local tos = table.map(cardUseEvent.tos, function(e) return e[1] end)
room:sendFootnote(useCardIds, {
type = "##UseCardTo",
from = from,
to = tos,
})
if card:isVirtual() then
room:sendCardVirtName(useCardIds, card.name)
end
else
room:sendFootnote(useCardIds, {
type = "##UseCard",
from = from,
})
if card:isVirtual() then
room:sendCardVirtName(useCardIds, card.name)
end
end
end end
---@param self GameEvent ---@param self GameEvent
@ -250,6 +271,15 @@ GameEvent.functions[GameEvent.RespondCard] = function(self)
toArea = Card.Processing, toArea = Card.Processing,
moveReason = fk.ReasonResonpse, moveReason = fk.ReasonResonpse,
}) })
if #cardIds > 0 then
self:sendFootnote(cardIds, {
type = "##ResponsePlayCard",
from = from,
})
if card:isVirtual() then
self:sendCardVirtName(cardIds, card.name)
end
end
if self.logic:trigger(fk.PreCardRespond, self:getPlayerById(cardResponseEvent.from), cardResponseEvent) then if self.logic:trigger(fk.PreCardRespond, self:getPlayerById(cardResponseEvent.from), cardResponseEvent) then
self.logic:breakEvent() self.logic:breakEvent()

View File

@ -419,6 +419,11 @@ function Room:removeCardMark(card, mark, count)
self:setCardMark(card, mark, math.max(num - count, 0)) self:setCardMark(card, mark, math.max(num - count, 0))
end end
---@param player ServerPlayer
function Room:setPlayerProperty(player, property, value)
player[property] = value
self:broadcastProperty(player, property)
end
--- 将房间中某个tag设为特定值。 --- 将房间中某个tag设为特定值。
--- ---
@ -742,6 +747,14 @@ function Room:sendLog(log)
self:doBroadcastNotify("GameLog", json.encode(log)) self:doBroadcastNotify("GameLog", json.encode(log))
end end
function Room:sendFootnote(ids, log)
self:doBroadcastNotify("SetCardFootnote", json.encode{ ids, log })
end
function Room:sendCardVirtName(ids, name)
self:doBroadcastNotify("SetCardVirtName", json.encode{ ids, name })
end
--- 播放某种动画效果给players看。 --- 播放某种动画效果给players看。
---@param type string @ 动画名字 ---@param type string @ 动画名字
---@param data any @ 这个动画附加的额外信息在这个函数将会被转成json字符串 ---@param data any @ 这个动画附加的额外信息在这个函数将会被转成json字符串
@ -868,7 +881,7 @@ end
---@return boolean, table ---@return boolean, table
function Room:askForUseActiveSkill(player, skill_name, prompt, cancelable, extra_data) function Room:askForUseActiveSkill(player, skill_name, prompt, cancelable, extra_data)
prompt = prompt or "" prompt = prompt or ""
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
extra_data = extra_data or {} extra_data = extra_data or {}
local skill = Fk.skills[skill_name] local skill = Fk.skills[skill_name]
if not (skill and (skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill))) then if not (skill and (skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill))) then
@ -929,7 +942,7 @@ Room.askForUseViewAsSkill = Room.askForUseActiveSkill
---@param skipDiscard boolean @ 是否跳过弃牌(即只询问选择可以弃置的牌) ---@param skipDiscard boolean @ 是否跳过弃牌(即只询问选择可以弃置的牌)
---@return integer[] @ 弃掉的牌的id列表可能是空的 ---@return integer[] @ 弃掉的牌的id列表可能是空的
function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt, skipDiscard) function Room:askForDiscard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt, skipDiscard)
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
pattern = pattern or "" pattern = pattern or ""
local canDiscards = table.filter( local canDiscards = table.filter(
@ -1004,7 +1017,7 @@ function Room:askForChoosePlayers(player, targets, minNum, maxNum, prompt, skill
if maxNum < 1 then if maxNum < 1 then
return {} return {}
end end
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
local data = { local data = {
targets = targets, targets = targets,
@ -1042,7 +1055,7 @@ function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancel
if minNum < 1 then if minNum < 1 then
return nil return nil
end end
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
pattern = pattern or "" pattern = pattern or ""
local chosenCards = {} local chosenCards = {}
@ -1089,7 +1102,7 @@ function Room:askForChooseCardAndPlayers(player, targets, minNum, maxNum, patter
if maxNum < 1 then if maxNum < 1 then
return {} return {}
end end
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
pattern = pattern or "." pattern = pattern or "."
local pcards = table.filter(player:getCardIds({ Player.Hand, Player.Equip }), function(id) local pcards = table.filter(player:getCardIds({ Player.Hand, Player.Equip }), function(id)
@ -1224,15 +1237,15 @@ end
---@param choices string[] @ 可选选项列表 ---@param choices string[] @ 可选选项列表
---@param skill_name string @ 技能名 ---@param skill_name string @ 技能名
---@param prompt string @ 提示信息 ---@param prompt string @ 提示信息
---@param data any @ 暂未使用 ---@param detailed boolean @ 暂未使用
---@return string @ 选择的选项 ---@return string @ 选择的选项
function Room:askForChoice(player, choices, skill_name, prompt, data) function Room:askForChoice(player, choices, skill_name, prompt, detailed)
if #choices == 1 then return choices[1] end if #choices == 1 then return choices[1] end
local command = "AskForChoice" local command = "AskForChoice"
prompt = prompt or "" prompt = prompt or ""
self:notifyMoveFocus(player, skill_name) self:notifyMoveFocus(player, skill_name)
local result = self:doRequest(player, command, json.encode{ local result = self:doRequest(player, command, json.encode{
choices, skill_name, prompt choices, skill_name, prompt, detailed
}) })
if result == "" then result = choices[1] end if result == "" then result = choices[1] end
return result return result
@ -1459,7 +1472,7 @@ function Room:askForUseCard(player, card_name, pattern, prompt, cancelable, extr
local command = "AskForUseCard" local command = "AskForUseCard"
self:notifyMoveFocus(player, card_name) self:notifyMoveFocus(player, card_name)
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
extra_data = extra_data or {} extra_data = extra_data or {}
pattern = pattern or card_name pattern = pattern or card_name
prompt = prompt or "" prompt = prompt or ""
@ -1505,7 +1518,7 @@ function Room:askForResponse(player, card_name, pattern, prompt, cancelable, ext
local command = "AskForResponseCard" local command = "AskForResponseCard"
self:notifyMoveFocus(player, card_name) self:notifyMoveFocus(player, card_name)
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
extra_data = extra_data or {} extra_data = extra_data or {}
pattern = pattern or card_name pattern = pattern or card_name
prompt = prompt or "" prompt = prompt or ""
@ -1554,7 +1567,7 @@ function Room:askForNullification(players, card_name, pattern, prompt, cancelabl
local command = "AskForUseCard" local command = "AskForUseCard"
card_name = card_name or "nullification" card_name = card_name or "nullification"
cancelable = cancelable or false cancelable = (cancelable == nil) and true or cancelable
extra_data = extra_data or {} extra_data = extra_data or {}
prompt = prompt or "" prompt = prompt or ""
pattern = pattern or card_name pattern = pattern or card_name
@ -1750,7 +1763,7 @@ function Room:askForChooseToMoveCardInBoard(player, prompt, skillName, cancelabl
if flag then if flag then
assert(flag == "e" or flag == "j") assert(flag == "e" or flag == "j")
end end
cancelable = (not cancelable) and false or true cancelable = (cancelable == nil) and true or cancelable
local data = { local data = {
flag = flag, flag = flag,
@ -2694,6 +2707,21 @@ function Room:useSkill(player, skill, effect_cb)
end end
end end
---@param player ServerPlayer
---@param sendLog boolean|nil
function Room:revivePlayer(player, sendLog)
if not player.dead then return end
self:setPlayerProperty(player, "dead", false)
self:setPlayerProperty(player, "dying", false)
self:setPlayerProperty(player, "hp", player.maxHp)
table.insertIfNeed(self.alive_players, player)
sendLog = (sendLog == nil) and true or sendLog
if sendLog then
self:sendLog { type = "#Revive", from = player.id }
end
end
---@param room Room ---@param room Room
local function shouldUpdateWinRate(room) local function shouldUpdateWinRate(room)
if room.settings.gameMode == "heg_mode" then return false end if room.settings.gameMode == "heg_mode" then return false end

View File

@ -351,6 +351,10 @@ function ServerPlayer:showCards(cards)
from = self.id, from = self.id,
cards = cards, cards = cards,
}) })
room:sendFootnote(cards, {
type = "##ShowCard",
from = self.id,
})
room.logic:trigger(fk.CardShown, self, { cardIds = cards }) room.logic:trigger(fk.CardShown, self, { cardIds = cards })
end end