UI modify (#158)

- 实现移动场上一张牌;
- 实现用作记录牌名并可查看的mark;
- 将askForChoice和interaction的文本解析方式改为prompt;
- 新增属性将牌移至牌堆指定索引位置;
- 修改时机“游戏开始时”至正确位置;
- 优化衍生牌逻辑;
- 新增“卡牌展示后”时机。
This commit is contained in:
Ho-spair 2023-05-20 16:00:03 +08:00 committed by GitHub
parent c222728a98
commit 1c6304f0f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 460 additions and 78 deletions

View File

@ -34,7 +34,7 @@ Rectangle {
Layout.preferredHeight: 120
cellHeight: 48
cellWidth: 48
model: 49
model: 50
visible: false
delegate: ItemDelegate {
Image {

View File

@ -49,7 +49,7 @@ Item {
TapHandler {
onTapped: {
lobby_dialog.sourceComponent = Qt.createComponent("Fk.LobbyElement", "EditProfile");
lobby_dialog.sourceComponent = Qt.createComponent("EditProfile.qml");
lobby_drawer.open();
}
}

View File

@ -154,7 +154,7 @@ Item {
icon.name: "media-playback-start"
text: Backend.translate("Create Room")
onClicked: {
lobby_dialog.sourceComponent = Qt.createComponent("Fk.LobbyElement", "CreateRoom");
lobby_dialog.sourceComponent = Qt.createComponent("../LobbyElement/CreateRoom.qml");
lobby_drawer.open();
config.observing = false;
}

View File

@ -487,14 +487,14 @@ Item {
Backend.callLuaFunction("SetInteractionDataOfSkill", [skill_name, "null"]);
switch (data.type) {
case "combo":
skillInteraction.sourceComponent = Qt.createComponent("Fk.SkillInteraction", "SkillCombo");
skillInteraction.sourceComponent = Qt.createComponent("../SkillInteraction/SkillCombo.qml");
skillInteraction.item.skill = skill_name;
skillInteraction.item.default_choice = data["default"];
skillInteraction.item.choices = data.choices;
// skillInteraction.item.clicked();
break;
case "spin":
skillInteraction.sourceComponent = Qt.createComponent("Fk.SkillInteraction", "SkillSpin");
skillInteraction.sourceComponent = Qt.createComponent("../SkillInteraction/SkillSpin.qml");
skillInteraction.item.skill = skill_name;
skillInteraction.item.from = data.from;
skillInteraction.item.to = data.to;
@ -816,7 +816,7 @@ Item {
}
function startCheat(type, data) {
cheatLoader.sourceComponent = Qt.createComponent("Fk.Cheat", type);
cheatLoader.sourceComponent = Qt.createComponent(`../Cheat/${type}.qml`);
cheatLoader.item.extra_data = data;
cheatDrawer.open();
}

View File

@ -228,7 +228,7 @@ function setEmotion(id, emotion, isCardId) {
// TODO: set picture emotion
return;
}
let component = Qt.createComponent("Fk.RoomElement", "PixmapAnimation");
let component = Qt.createComponent("../RoomElement/PixmapAnimation.qml");
if (component.status !== Component.Ready)
return;
@ -277,7 +277,7 @@ function changeHp(id, delta, losthp) {
}
function doIndicate(from, tos) {
let component = Qt.createComponent("Fk.RoomElement", "IndicatorLine");
let component = Qt.createComponent("../RoomElement/IndicatorLine.qml");
if (component.status !== Component.Ready)
return;
@ -594,7 +594,7 @@ callbacks["AskForGeneral"] = function(jsonData) {
let heg = data[2];
roomScene.promptText = Backend.translate("#AskForGeneral");
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "ChooseGeneralBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChooseGeneralBox.qml");
let box = roomScene.popupBox.item;
box.accepted.connect(() => {
replyToServer(JSON.stringify(box.choices));
@ -627,7 +627,7 @@ callbacks["AskForGuanxing"] = function(jsonData) {
let min_bottom_cards = data.min_bottom_cards;
let max_bottom_cards = data.max_bottom_cards;
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "GuanxingBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GuanxingBox.qml");
data.cards.forEach(id => {
let d = Backend.callLuaFunction("GetCardData", [id]);
cards.push(JSON.parse(d));
@ -663,7 +663,7 @@ callbacks["AskForChoice"] = function(jsonData) {
roomScene.promptText = processPrompt(prompt);
}
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "ChoiceBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChoiceBox.qml");
let box = roomScene.popupBox.item;
box.options = choices;
box.skill_name = skill_name;
@ -702,7 +702,7 @@ callbacks["AskForCardChosen"] = function(jsonData) {
roomScene.promptText = Backend.translate("#AskForChooseCard")
.arg(Backend.translate(reason));
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "PlayerCardBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PlayerCardBox.qml");
let box = roomScene.popupBox.item;
box.addHandcards(handcards);
box.addEquips(equips);
@ -745,7 +745,7 @@ callbacks["AskForCardsChosen"] = function(jsonData) {
roomScene.promptText = Backend.translate("#AskForChooseCard")
.arg(Backend.translate(reason));
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "PlayerCardBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/PlayerCardBox.qml");
let box = roomScene.popupBox.item;
box.multiChoose = true;
box.min = min;
@ -759,6 +759,33 @@ callbacks["AskForCardsChosen"] = function(jsonData) {
});
}
callbacks["AskForMoveCardInBoard"] = function(jsonData) {
const data = JSON.parse(jsonData);
const { cards, cardsPosition, generalNames } = data;
roomScene.state = "replying";
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/MoveCardInBoardBox.qml");
const boxCards = [];
cards.forEach(id => {
let d = Backend.callLuaFunction("GetCardData", [id]);
boxCards.push(JSON.parse(d));
});
const box = roomScene.popupBox.item;
box.cards = boxCards;
box.cardsPosition = cardsPosition;
box.generalNames = generalNames.map(name => {
const namesSplited = name.split('/');
return namesSplited.length > 1 ? namesSplited.map(nameSplited => Backend.translate(nameSplited)).join('/') : Backend.translate(name)
});
box.arrangeCards();
box.accepted.connect(() => {
replyToServer(JSON.stringify(box.getResult()));
});
}
callbacks["MoveCards"] = function(jsonData) {
// jsonData: merged moves
let moves = JSON.parse(jsonData);
@ -938,7 +965,7 @@ callbacks["Animate"] = function(jsonData) {
}
case "InvokeSkill": {
let id = data.player;
let component = Qt.createComponent("Fk.RoomElement", "SkillInvokeAnimation");
let component = Qt.createComponent("../RoomElement/SkillInvokeAnimation.qml");
if (component.status !== Component.Ready)
return;
@ -1002,7 +1029,7 @@ callbacks["LogEvent"] = function(jsonData) {
callbacks["GameOver"] = function(jsonData) {
roomScene.state = "notactive";
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "GameOverBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/GameOverBox.qml");
let box = roomScene.popupBox.item;
box.winner = jsonData;
roomScene.isStarted = false;
@ -1011,7 +1038,7 @@ callbacks["GameOver"] = function(jsonData) {
callbacks["FillAG"] = (j) => {
let data = JSON.parse(j);
let ids = data[0];
roomScene.manualBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "AG");
roomScene.manualBox.sourceComponent = Qt.createComponent("../RoomElement/AG.qml");
roomScene.manualBox.item.addIds(ids);
}

View File

@ -33,7 +33,7 @@ Item {
width: childrenRect.width
height: 22
Text {
text: Backend.translate(mark_name) + ' ' + mark_extra
text: Backend.translate(mark_name) + ' ' + (special_value !== '' ? special_value : mark_extra)
font.family: fontLibian.name
font.pixelSize: 22
font.letterSpacing: -0.6
@ -53,16 +53,20 @@ Item {
TapHandler {
enabled: root.parent.state != "candidate" || !root.parent.selectable
onTapped: {
let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name]));
data = data.filter((e) => e !== -1);
if (data.length === 0)
return;
const params = { name: mark_name };
if (mark_name.startsWith('@$')) {
params.cardNames = mark_extra.split(',');
} else {
let data = JSON.parse(Backend.callLuaFunction("GetPile", [root.parent.playerid, mark_name]));
data = data.filter((e) => e !== -1);
if (data.length === 0)
return;
params.ids = data;
}
// Just for using room's right drawer
roomScene.startCheat("ViewPile", {
name: mark_name,
ids: data
});
roomScene.startCheat("../RoomElement/ViewPile", params);
}
}
}
@ -83,11 +87,18 @@ Item {
}
}
data = (data instanceof Array ? data.map((markText) => Backend.translate(markText)).join(' ') : Backend.translate(data));
let special_value = '';
if (mark.startsWith('@$')) {
special_value = data.length;
data = data.join(',');
} else {
data = data instanceof Array ? data.map((markText) => Backend.translate(markText)).join(' ') : Backend.translate(data);
}
if (modelItem)
modelItem.mark_extra = data;
else
markList.append({ mark_name: mark, mark_extra: data });
markList.append({ mark_name: mark, mark_extra: data, special_value });
arrangeMarks();
}

View File

@ -85,7 +85,7 @@ Item {
Image {
id: cardItem
source: known ? SkinBank.getCardPicture(cid)
source: known ? SkinBank.getCardPicture(cid || name)
: (SkinBank.CARD_DIR + "card-back")
anchors.fill: parent
fillMode: Image.PreserveAspectCrop

View File

@ -14,6 +14,18 @@ GraphicsBox {
width: Math.max(140, body.width + 20)
height: body.height + title.height + 20
function processPrompt(prompt) {
const data = prompt.split(":");
let raw = Backend.translate(data[0]);
const src = parseInt(data[1]);
const dest = parseInt(data[2]);
if (raw.match("%src")) raw = raw.replace("%src", Backend.translate(getPhoto(src).general));
if (raw.match("%dest")) raw = raw.replace("%dest", Backend.translate(getPhoto(dest).general));
if (raw.match("%arg")) raw = raw.replace("%arg", Backend.translate(data[3]));
if (raw.match("%arg2")) raw = raw.replace("%arg2", Backend.translate(data[4]));
return raw;
}
GridLayout {
id: body
x: 10
@ -27,7 +39,7 @@ GraphicsBox {
MetroButton {
Layout.fillWidth: true
text: Backend.translate(modelData)
text: processPrompt(modelData)
onClicked: {
result = index;

View File

@ -73,7 +73,7 @@ RowLayout {
if (expanded_pile_names.indexOf(pile) !== -1)
return;
let component = Qt.createComponent("Fk.RoomElement", "CardItem");
let component = Qt.createComponent("../RoomElement/CardItem.qml");
let parentPos = roomScene.mapFromItem(self, 0, 0);
expanded_piles[pile] = [];

View File

@ -50,7 +50,7 @@ Item {
function remove(outputs)
{
let component = Qt.createComponent("Fk.RoomElement", "CardItem");
let component = Qt.createComponent("CardItem.qml");
if (component.status !== Component.Ready)
return [];

View File

@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Layouts
import Fk.Pages
GraphicsBox {
id: root
property var cards: []
property var cardsPosition: []
property var generalNames: []
property var result
property int padding: 25
title.text: Backend.translate("Please click to move card")
width: body.width + padding * 2
height: title.height + body.height + padding * 2
ColumnLayout {
id: body
x: padding
y: parent.height - padding - height
spacing: 20
Repeater {
id: areaRepeater
model: generalNames
Row {
spacing: 5
Rectangle {
anchors.verticalCenter: parent.verticalCenter
color: "#6B5D42"
width: 20
height: 100
radius: 5
Text {
anchors.fill: parent
width: 20
height: 100
text: modelData
color: "white"
font.family: fontLibian.name
font.pixelSize: 18
style: Text.Outline
wrapMode: Text.WrapAnywhere
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
Repeater {
id: cardRepeater
model: cards
Rectangle {
color: "#4A4139"
width: 93
height: 130
opacity: 0.5
Text {
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
text: Backend.translate(JSON.parse(Backend.callLuaFunction("GetCardData", [modelData.cid])).subtype)
color: "#90765F"
font.family: fontLibian.name
font.pixelSize: 16
width: parent.width * 0.8
wrapMode: Text.WordWrap
}
}
}
property alias cardRepeater: cardRepeater
}
}
MetroButton {
Layout.alignment: Qt.AlignHCenter
id: buttonConfirm
text: Backend.translate("OK")
width: 120
height: 35
enabled: false
onClicked: close();
}
}
Repeater {
id: cardItem
model: cards
CardItem {
x: index
y: -1
cid: modelData.cid
name: modelData.name
suit: modelData.suit
number: modelData.number
selectable: !result || result.item === this
onClicked: {
if (!selectable) return;
if ((result || {}).item === this) {
result = undefined;
} else {
result = { item: this };
}
updatePosition(this);
}
}
}
function arrangeCards() {
for (let i = 0; i < cards.length; i++) {
const curCard = cardItem.itemAt(i);
curCard.origX = i * 98 + 50;
curCard.origY = cardsPosition[i] * 150 + body.y;
curCard.goBack();
}
}
function updatePosition(item) {
for (let i = 0; i < 2; i++) {
const index = cards.findIndex(data => item.cid === data.cid);
result && (result.pos = cardsPosition[index]);
const cardPos = cardsPosition[index] === 0 ? (result ? 1 : 0) : (result ? 0 : 1);
const curArea = areaRepeater.itemAt(cardPos);
const curBox = curArea.cardRepeater.itemAt(index);
const curPos = mapFromItem(curArea, curBox.x, curBox.y);
item.origX = curPos.x;
item.origY = curPos.y;
item.goBack(true);
buttonConfirm.enabled = !!result;
}
}
function getResult() {
return result ? { cardId: result.item.cid, pos: result.pos } : '';
}
}

View File

@ -39,27 +39,41 @@ Item {
Text {
anchors.centerIn: parent
topPadding: 5
id: skill
font.family: fontLi2.name
font.pixelSize: Math.max(26 - text.length, 18)
visible: false
font.bold: true
}
Glow {
id: glowItem
source: skill
anchors.fill: skill
radius: 6
//samples: 8
color: "grey"
color: "black"
spread: 0.3
radius: 5
}
LinearGradient {
anchors.fill: skill
source: skill
gradient: Gradient {
GradientStop { position: 0; color: "#FFE07C" }
GradientStop { position: 1; color: "#B79A5F" }
GradientStop {
position: 0
color: "#FEF7C2"
}
GradientStop {
position: 0.5
color: "#D2AD4A"
}
GradientStop {
position: 1
color: "#BE9878"
}
}
}

View File

@ -71,7 +71,7 @@ Item {
columns: 4
Repeater {
model: extra_data.ids
model: extra_data.ids || extra_data.cardNames
CardItem {
id: cardItem
@ -79,7 +79,16 @@ Item {
height: cardItem.width * 1.4
autoBack: false
Component.onCompleted: {
let data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData]));
let data = {}
if (extra_data.ids) {
data = JSON.parse(Backend.callLuaFunction("GetCardData", [modelData]));
} else {
data.cid = 0;
data.name = modelData;
data.suit = '';
data.number = 0;
data.color = '';
}
setData(data);
}
}

View File

@ -9,7 +9,20 @@ MetroButton {
property var choices: []
property string default_choice
property string answer: default_choice
text: Backend.translate(answer)
function processPrompt(prompt) {
const data = prompt.split(":");
let raw = Backend.translate(data[0]);
const src = parseInt(data[1]);
const dest = parseInt(data[2]);
if (raw.match("%src")) raw = raw.replace("%src", Backend.translate(getPhoto(src).general));
if (raw.match("%dest")) raw = raw.replace("%dest", Backend.translate(getPhoto(dest).general));
if (raw.match("%arg")) raw = raw.replace("%arg", Backend.translate(data[3]));
if (raw.match("%arg2")) raw = raw.replace("%arg2", Backend.translate(data[4]));
return raw;
}
text: processPrompt(answer)
onAnswerChanged: {
if (!answer) return;
@ -21,7 +34,7 @@ MetroButton {
}
onClicked: {
roomScene.popupBox.sourceComponent = Qt.createComponent("Fk.RoomElement", "ChoiceBox");
roomScene.popupBox.sourceComponent = Qt.createComponent("../RoomElement/ChoiceBox.qml");
let box = roomScene.popupBox.item;
box.options = choices;
box.accepted.connect(() => {

View File

@ -29,10 +29,18 @@ function getGeneralPicture(name) {
return GENERAL_DIR + "0.jpg";
}
function getCardPicture(cid) {
let data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid]));
let extension = data.extension;
let name = data.name;
function getCardPicture(cidOrName) {
let extension = "";
let name = "unknown";
if (typeof cidOrName === 'string') {
name = cidOrName;
extension = Backend.callLuaFunction("GetCardExtensionByName", [cidOrName]);
} else {
const data = JSON.parse(Backend.callLuaFunction("GetCardData", [cid]));
extension = data.extension;
name = data.name;
}
let path = AppPath + "/packages/" + extension + "/image/card/" + name + ".png";
if (Backend.exists(path)) {
return path;

View File

@ -98,6 +98,14 @@ function GetCardData(id)
return json.encode(ret)
end
function GetCardExtensionByName(cardName)
local card = table.find(Fk.cards, function(card)
return card.name == cardName
end)
return card and card.package.extensionName or ""
end
function GetAllGeneralPack()
local ret = {}
for _, name in ipairs(Fk.package_names) do

View File

@ -143,6 +143,7 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["#choose-trigger"] = "请选择一项技能发动",
["trigger"] = "选择技能",
["Please arrange cards"] = "请拖拽移动卡牌",
["Please click to move card"] = "请点击移动卡牌",
[" thinking..."] = " 思考中...",
["AskForGeneral"] = "选择武将",
@ -203,10 +204,6 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["$NoWinner"] = "平局!",
["Back To Lobby"] = "返回大厅",
["basic_char"] = "",
["trick_char"] = "",
["equip_char"] = "",
["Bulletin Info"] = "<h2>更新说明</h2>\
1. 退/<br>\
2. <br>\

View File

@ -22,6 +22,7 @@
---@field public skill Skill
---@field public special_skills string[] | nil
---@field public is_damage_card boolean
---@field public is_derived boolean|null
local Card = class("Card")
---@alias Suit integer
@ -96,6 +97,11 @@ function Card:initialize(name, suit, number, color)
self.skillNames = {}
self.mark = {}
if string.sub(name, 1, 1) == "&" then
self.name = string.sub(name, 2, #name)
self.is_derived = true
end
local mt = table.simpleClone(getmetatable(self))
local newidx = mt.__newindex or rawset
mt.__newindex = function(t, k, v)
@ -139,6 +145,7 @@ function Card:clone(suit, number)
newCard.equip_skill = self.equip_skill
newCard.attack_range = self.attack_range
newCard.is_damage_card = self.is_damage_card
newCard.is_derived = self.is_derived
return newCard
end

View File

@ -742,4 +742,17 @@ function Player:getSwitchSkillState(skillName, afterUse)
end
end
function Player:canMoveCardInBoardTo(to, id)
local card = Fk:getCardById(id)
assert(card.type == Card.TypeEquip or card.sub_type == Card.SubtypeDelayedTrick)
if card.type == Card.TypeEquip then
return not to:getEquipment(card.sub_type)
else
return not table.find(to:getCardIds(Player.Judge), function(cardId)
return Fk:getCardById(cardId).name == card.name
end)
end
end
return Player

View File

@ -208,6 +208,20 @@ function table:slice(begin, _end)
return ret
end
function table:assign(targetTbl)
for key, value in pairs(targetTbl) do
if self[key] then
if type(value) == "table" then
table.insertTable(self[key], value)
else
table.insert(self[key], value)
end
else
self[key] = value
end
end
end
-- allow a = "Hello"; a[1] == "H"
local str_mt = getmetatable("")
str_mt.__index = function(str, k)

View File

@ -6,6 +6,7 @@
---@alias Event integer
fk.NonTrigger = 1
fk.GamePrepared = 78
fk.GameStart = 2
fk.TurnStart = 3
fk.TurnEnd = 73
@ -101,4 +102,8 @@ fk.BeforeTriggerSkillUse = 75
fk.BeforeDrawCard = 76
fk.NumOfEvents = 77
fk.CardShown = 77
-- 78 = GamePrepared
fk.NumOfEvents = 79

View File

@ -92,7 +92,7 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self)
room:notifyMoveFocus(room.alive_players, "AskForLuckCard")
room:doBroadcastNotify("AskForLuckCard", room.settings.luckTime or 4)
local remainTime = room.timeout
local remainTime = room.timeout + 1
local currentTime = os.time()
local elapsed = 0
@ -127,12 +127,17 @@ GameEvent.functions[GameEvent.Round] = function(self)
local logic = room.logic
local p
if room:getTag("FirstRound") then
local isFirstRound = room:getTag("FirstRound")
if isFirstRound then
room:setTag("FirstRound", false)
end
room:setTag("RoundCount", room:getTag("RoundCount") + 1)
room:doBroadcastNotify("UpdateRoundNum", room:getTag("RoundCount"))
if isFirstRound then
logic:trigger(fk.GameStart, room.current)
end
logic:trigger(fk.RoundStart, room.current)
repeat

View File

@ -37,6 +37,7 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
moveVisible = cardsMoveInfo.moveVisible,
specialName = cardsMoveInfo.specialName,
specialVisible = cardsMoveInfo.specialVisible,
drawPilePosition = cardsMoveInfo.drawPilePosition,
}
table.insert(cardsMoveStructs, cardsMoveStruct)
@ -100,7 +101,18 @@ GameEvent.functions[GameEvent.MoveCards] = function(self)
toAreaIds = self.void
end
table.insert(toAreaIds, data.toArea == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId)
if data.toArea == Card.DrawPile then
local putIndex = data.drawPilePosition or 1
if putIndex == -1 then
putIndex = #self.draw_pile + 1
elseif putIndex < 1 or putIndex > #self.draw_pile + 1 then
putIndex = 1
end
table.insert(toAreaIds, putIndex, info.cardId)
else
table.insert(toAreaIds, info.cardId)
end
end
self:setCardArea(info.cardId, data.toArea, data.to)
if data.toArea == Card.DrawPile or realFromArea == Card.DrawPile then

View File

@ -187,7 +187,7 @@ GameEvent.functions[GameEvent.UseCard] = function(self)
table.insert(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card)
end
for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do
for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.CardUsing }) do
if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
break
end

View File

@ -233,6 +233,16 @@ end
function GameLogic:prepareDrawPile()
local room = self.room
local allCardIds = Fk:getAllCardIds()
for i = #allCardIds, 1, -1 do
if Fk:getCardById(allCardIds[i]).is_derived then
local id = allCardIds[i]
table.removeOne(allCardIds, id)
table.insert(room.void, id)
room:setCardArea(id, Card.Void, nil)
end
end
table.shuffle(allCardIds)
room.draw_pile = allCardIds
for _, id in ipairs(room.draw_pile) do
@ -291,7 +301,7 @@ function GameLogic:prepareForStart()
end
function GameLogic:action()
self:trigger(fk.GameStart)
self:trigger(fk.GamePrepared)
local room = self.room
execGameEvent(GameEvent.DrawInitial)

View File

@ -1572,6 +1572,82 @@ function Room:askForCustomDialog(player, focustxt, qmlPath, extra_data)
})
end
---@field player ServerPlayer
---@field targetOne ServerPlayer
---@field targetTwo ServerPlayer
---@field skillName string
---@return cardId
function Room:askForMoveCardInBoard(player, targetOne, targetTwo, skillName)
local cards = {}
local cardsPosition = {}
for _, equipId in ipairs(targetOne:getCardIds(Player.Equip)) do
if targetOne:canMoveCardInBoardTo(targetTwo, equipId) then
table.insert(cards, equipId)
end
end
for _, equipId in ipairs(targetTwo:getCardIds(Player.Equip)) do
if targetTwo:canMoveCardInBoardTo(targetOne, equipId) then
table.insert(cards, equipId)
end
end
if #cards > 0 then
table.sort(cards, function(prev, next)
local prevSubType = Fk:getCardById(prev).sub_type
local nextSubType = Fk:getCardById(next).sub_type
return prevSubType < nextSubType
end)
for _, id in ipairs(cards) do
table.insert(cardsPosition, self:getCardOwner(id) == targetOne and 0 or 1)
end
end
for _, trickId in ipairs(targetOne:getCardIds(Player.Judge)) do
if targetOne:canMoveCardInBoardTo(targetTwo, trickId) then
table.insert(cards, trickId)
table.insert(cardsPosition, 0)
end
end
for _, trickId in ipairs(targetTwo:getCardIds(Player.Judge)) do
if targetTwo:canMoveCardInBoardTo(targetOne, trickId) then
table.insert(cards, trickId)
table.insert(cardsPosition, 1)
end
end
if #cards == 0 then
return
end
local firstGeneralName = targetOne.general + (targetOne.deputyGeneral ~= "" and ("/" .. targetOne.deputyGeneral) or "")
local secGeneralName = targetTwo.general + (targetTwo.deputyGeneral ~= "" and ("/" .. targetTwo.deputyGeneral) or "")
local data = { cards = cards, cardsPosition = cardsPosition, generalNames = { firstGeneralName, secGeneralName } }
local command = "AskForMoveCardInBoard"
self:notifyMoveFocus(player, command)
local result = self:doRequest(player, command, json.encode(data))
if result == "" then
local randomIndex = math.random(1, #cards)
result = { cardId = cards[randomIndex], pos = cardsPosition[randomIndex] }
else
result = json.decode(result)
end
local cardToMove = Fk:getCardById(result.cardId)
self:moveCardTo(
cardToMove,
cardToMove.type == Card.TypeEquip and Player.Equip or Player.Judge,
result.pos == 0 and targetTwo or targetOne,
fk.ReasonPut,
skillName,
nil,
true
)
end
------------------------------------------------------------------------
-- 使用牌
------------------------------------------------------------------------
@ -1712,6 +1788,7 @@ function Room:doCardUseEffect(cardUseEvent)
local realCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
self.logic:trigger(fk.BeforeCardUseEffect, cardUseEvent.from, cardUseEvent)
-- If using Equip or Delayed trick, move them to the area and return
if cardUseEvent.card.type == Card.TypeEquip then
if #realCardIds == 0 then

View File

@ -343,6 +343,8 @@ function ServerPlayer:showCards(cards)
from = self.id,
cards = cards,
})
room.logic:trigger(fk.CardShown, self, { cardIds = cards })
end
---@param from_phase Phase

View File

@ -28,6 +28,7 @@
---@field public moveVisible boolean|null
---@field public specialName string|null
---@field public specialVisible boolean|null
---@field public drawPilePosition number|null @ 移至牌堆的索引位置,值为-1代表置入牌堆底或者牌堆牌数+1也为牌堆底
---@class PindianResult
---@field public toCard Card

View File

@ -78,31 +78,9 @@ local maxCardsSkill = fk.CreateMaxCardsSkill{
end,
}
local moveTokenSkill = fk.CreateTriggerSkill{
name = "move_token_skill",
global = true,
refresh_events = {fk.GameStart}, --refresh优先于on_use不要在正常的游戏开始发牌技能refresh中拿牌
can_refresh = function(self, event, target, player, data)
return player.seat == 1
end,
on_refresh = function(self, event, target, player, data)
local room = player.room
for i = #room.draw_pile, 1, -1 do
if Fk:getCardById(room.draw_pile[i]).name[1] == "&" then
local id = room.draw_pile[i]
table.removeOne(room.draw_pile, id)
table.insert(room.void, id)
room:setCardArea(id, Card.Void, nil)
end
end
end,
}
AuxSkills = {
discardSkill,
chooseCardsSkill,
choosePlayersSkill,
maxCardsSkill,
moveTokenSkill,
}

View File

@ -41,7 +41,7 @@ end
GameRule = fk.CreateTriggerSkill{
name = "game_rule",
events = {
fk.GameStart,
fk.GamePrepared,
fk.AskForPeaches, fk.AskForPeachesDone,
fk.GameOverJudge, fk.BuryVictim,
},
@ -58,7 +58,7 @@ GameRule = fk.CreateTriggerSkill{
return false
end
if event == fk.GameStart then
if event == fk.GamePrepared then
room:setTag("FirstRound", true)
room:setTag("RoundCount", 0)
return false

View File

@ -15,6 +15,17 @@ Fk:loadTranslationTable{
["club"] = "梅花",
["diamond"] = "方块",
["basic_char"] = "",
["trick_char"] = "",
["equip_char"] = "",
["weapon"] = "武器牌",
["armor"] = "防具牌",
["defensive_horse"] = "防御坐骑牌",
["offensive_horse"] = "进攻坐骑牌",
["treasure"] = "宝物牌",
["delayed_trick"] = "延时类锦囊牌",
["slash"] = "",
[":slash"] = "基本牌<br /><b>时机</b>:出牌阶段<br /><b>目标</b>:攻击范围内的一名其他角色<br /><b>效果</b>对目标角色造成1点伤害。",
["#slash-jink"] = "%src 对你使用了杀,请使用 %arg 张闪",