- 战报自动滚动
- 长按手牌显示提示
- 选将阶段可查看技能
- 减小延时锦囊的尺寸
- 在转圈界面显示提示文本
- 手气卡机制
This commit is contained in:
notify 2023-05-19 07:45:21 +08:00 committed by GitHub
parent fc3a7dbda7
commit ce2cae0aa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 417 additions and 52 deletions

View File

@ -32,6 +32,7 @@ cp ../server/init.sql assets/res/server
cp ../LICENSE assets/res
cp ../zh_CN.qm assets/res
cp ../fk_ver assets/res
cp ../waiting_tips.txt assets/res
# Due to Qt Android's bug, we need make sure every directory has a subfile (not subdir)
function fixDir() {

View File

@ -31,6 +31,7 @@ Fk:loadTranslationTable{
["Player num"] = "玩家数目",
["Select general num"] = "选将数目",
["Operation timeout"] = "操作时长(秒)",
["Luck Card Times"] = "手气卡次数",
["Game Mode"] = "游戏模式",
["Enable free assign"] = "自由选将",
["Enable deputy general"] = "启用副将机制",
@ -128,10 +129,13 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["$ChooseGeneral"] = "请选择 %1 名武将",
["Same General Convert"] = "替换武将",
["Fight"] = "出战",
["Show General Detail"] = "查看技能",
["#PlayCard"] = "出牌阶段,请使用一张牌",
["#AskForGeneral"] = "请选择 1 名武将",
["#AskForSkillInvoke"] = "你想发动技能“%1”吗",
["#AskForLuckCard"] = "你想使用手气卡吗?还可以使用 %1 次,剩余手气卡∞张",
["AskForLuckCard"] = "手气卡",
["#AskForChoice"] = "%1请选择",
["#choose-trigger"] = "请选择一项技能发动",
["trigger"] = "选择技能",

View File

@ -1,35 +1,113 @@
-- SPDX-License-Identifier: GPL-3.0-or-later
local function drawInit(room, player, n)
-- TODO: need a new function to call the UI
local cardIds = room:getNCards(n)
player:addCards(Player.Hand, cardIds)
for _, id in ipairs(cardIds) do
Fk:filterCard(id, player)
end
local move_to_notify = {} ---@type CardsMoveStruct
move_to_notify.toArea = Card.PlayerHand
move_to_notify.to = player.id
move_to_notify.moveInfo = {}
move_to_notify.moveReason = fk.ReasonDraw
for _, id in ipairs(cardIds) do
table.insert(move_to_notify.moveInfo,
{ cardId = id, fromArea = Card.DrawPile })
end
room:notifyMoveCards(nil, {move_to_notify})
for _, id in ipairs(cardIds) do
room:setCardArea(id, Card.PlayerHand, player.id)
end
end
local function discardInit(room, player)
local cardIds = player:getCardIds(Player.Hand)
player:removeCards(Player.Hand, cardIds)
table.insertTable(room.draw_pile, cardIds)
for _, id in ipairs(cardIds) do
Fk:filterCard(id, nil)
end
local move_to_notify = {} ---@type CardsMoveStruct
move_to_notify.from = player.id
move_to_notify.toArea = Card.DrawPile
move_to_notify.moveInfo = {}
move_to_notify.moveReason = fk.ReasonJustMove
for _, id in ipairs(cardIds) do
table.insert(move_to_notify.moveInfo,
{ cardId = id, fromArea = Card.PlayerHand })
end
room:notifyMoveCards(nil, {move_to_notify})
for _, id in ipairs(cardIds) do
room:setCardArea(id, Card.DrawPile, nil)
end
end
GameEvent.functions[GameEvent.DrawInitial] = function(self)
local room = self.room
local luck_data = {
drawInit = drawInit,
discardInit = discardInit,
playerList = table.map(room.alive_players, Util.IdMapper),
}
for _, player in ipairs(room.alive_players) do
local draw_data = { num = 4 }
room.logic:trigger(fk.DrawInitialCards, player, draw_data)
luck_data[player.id] = draw_data
luck_data[player.id].luckTime = room.settings.luckTime
if player.id < 0 then -- Robot
luck_data[player.id].luckTime = 0
end
if draw_data.num > 0 then
-- TODO: need a new function to call the UI
local cardIds = room:getNCards(draw_data.num)
player:addCards(Player.Hand, cardIds)
for _, id in ipairs(cardIds) do
Fk:filterCard(id, player)
end
local move_to_notify = {} ---@type CardsMoveStruct
move_to_notify.toArea = Card.PlayerHand
move_to_notify.to = player.id
move_to_notify.moveInfo = {}
move_to_notify.moveReason = fk.ReasonDraw
for _, id in ipairs(cardIds) do
table.insert(move_to_notify.moveInfo,
{ cardId = id, fromArea = Card.DrawPile })
end
room:notifyMoveCards(nil, {move_to_notify})
for _, id in ipairs(cardIds) do
room:setCardArea(id, Card.PlayerHand, player.id)
end
room.logic:trigger(fk.AfterDrawInitialCards, player, data)
drawInit(room, player, draw_data.num)
end
end
if room.settings.luckTime <= 0 then
for _, player in ipairs(room.alive_players) do
local draw_data = luck_data[player.id]
draw_data.luckTime = nil
room.logic:trigger(fk.AfterDrawInitialCards, player, data)
end
return
end
room:setTag("LuckCardData", luck_data)
room:notifyMoveFocus(room.alive_players, "AskForLuckCard")
room:doBroadcastNotify("AskForLuckCard", room.settings.luckTime or 4)
local remainTime = room.timeout
local currentTime = os.time()
local elapsed = 0
while true do
elapsed = os.time() - currentTime
if remainTime - elapsed <= 0 then
break
end
if table.every(room:getTag("LuckCardData").playerList, function(id)
return room:getTag("LuckCardData")[id].luckTime == 0
end) then
break
end
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)
end
for _, player in ipairs(room.alive_players) do
local draw_data = luck_data[player.id]
draw_data.luckTime = nil
room.logic:trigger(fk.AfterDrawInitialCards, player, data)
end
room:removeTag("LuckCardData")
end
GameEvent.functions[GameEvent.Round] = function(self)

View File

@ -85,6 +85,27 @@ request_handlers["prelight"] = function(room, id, reqlist)
p:prelightSkill(reqlist[3], reqlist[4] == "true")
end
request_handlers["luckcard"] = function(room, id, reqlist)
local p = room:getPlayerById(id)
local cancel = reqlist[3] == "false"
local luck_data = room:getTag("LuckCardData")
local pdata = luck_data[id]
if not cancel then
pdata.luckTime = pdata.luckTime - 1
luck_data.discardInit(room, p)
luck_data.drawInit(room, p, pdata.num)
else
pdata.luckTime = 0
end
if pdata.luckTime > 0 then
p:doNotify("AskForLuckCard", pdata.luckTime)
end
room:setTag("LuckCardData", luck_data)
end
request_handlers["changeself"] = function(room, id, reqlist)
local p = room:getPlayerById(id)
local toId = tonumber(reqlist[3])
@ -107,7 +128,8 @@ request_handlers["changeself"] = function(room, id, reqlist)
})
end
local function requestLoop(self, rest_time)
local function requestLoop(self)
local rest_time = 0
while true do
local ret = false
local request = self.room:fetchRequest()
@ -122,7 +144,7 @@ local function requestLoop(self, rest_time)
-- otherwise CPU usage will be 100% (infinite yield <-> resume loop)
fk.QThread_msleep(10)
end
coroutine.yield(ret)
rest_time = coroutine.yield(ret)
end
end

View File

@ -71,8 +71,8 @@ function Room:initialize(_room)
local main_co = coroutine.create(function()
self:run()
end)
local request_co = coroutine.create(function(rest)
self:requestLoop(rest)
local request_co = coroutine.create(function()
self:requestLoop()
end)
local ret, err_msg, rest_time = true, true
while not self.game_finished do
@ -642,8 +642,7 @@ function Room:doRaceRequest(command, players, jsonData)
break
end
coroutine.yield("__handleRequest", remainTime - elapsed)
fk.QThread_msleep(10)
coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000)
end
for _, p in ipairs(self.players) do
@ -2525,7 +2524,7 @@ function Room:gameOver(winner)
end
self.room:gameOver()
coroutine.yield("__handleRequest")
coroutine.yield("__handleRequest", 0)
end
---@param card Card

View File

@ -25,6 +25,7 @@ QtObject {
property var disabledGenerals: []
property int preferredTimeout
property int preferredLuckTime
// Player property of client
property string serverAddr
@ -60,6 +61,7 @@ QtObject {
bgmVolume = conf.bgmVolume ?? 50.;
disableMsgAudio = conf.disableMsgAudio ?? false;
preferredTimeout = conf.preferredTimeout ?? 15;
preferredLuckTime = conf.preferredLuckTime ?? 0;
disabledGenerals = conf.disabledGenerals ?? [];
}
@ -83,6 +85,7 @@ QtObject {
conf.bgmVolume = bgmVolume;
conf.disableMsgAudio = disableMsgAudio;
conf.preferredTimeout = preferredTimeout;
conf.preferredLuckTime = preferredLuckTime;
conf.disabledGenerals = disabledGenerals;
Backend.saveConf(JSON.stringify(conf, undefined, 2));

View File

@ -5,15 +5,12 @@ import QtQuick.Controls
ListView {
id: root
//property alias font: textEdit.font
//property alias text: textEdit.text
//property alias color: textEdit.color
//property alias textFormat: textEdit.textFormat
//flickableDirection: Flickable.VerticalFlick
//contentWidth: textEdit.width
//contentHeight: textEdit.height
clip: true
highlight: Rectangle { color: "#EEEEEE"; radius: 5 }
highlightMoveDuration: 500
ScrollBar.vertical: ScrollBar {
parent: root.parent
anchors.top: root.top
@ -30,17 +27,27 @@ ListView {
clip: true
readOnly: true
selectByKeyboard: true
selectByMouse: true
selectByMouse: false
wrapMode: TextEdit.WrapAnywhere
textFormat: TextEdit.RichText
font.pixelSize: 16
TapHandler {
onTapped: root.currentIndex = index;
}
}
Button {
text: "Return to Bottom"
visible: root.currentIndex !== logModel.count - 1
onClicked: root.currentIndex = logModel.count - 1;
}
function append(text) {
let autoScroll = atYEnd;
let autoScroll = root.currentIndex === logModel.count - 1;
logModel.append({ logText: text });
if (autoScroll && contentHeight > contentY + height) {
contentY = contentHeight - height;
if (autoScroll) {
root.currentIndex = logModel.count - 1;
}
}
}

View File

@ -96,6 +96,23 @@ ColumnLayout {
}
}
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: Backend.translate("Luck Card Times")
}
SpinBox {
from: 0
to: 8
value: config.preferredLuckTime
onValueChanged: {
config.preferredLuckTime = value;
}
}
}
Switch {
id: freeAssignCheck
checked: Debugging ? true : false
@ -136,6 +153,7 @@ ColumnLayout {
gameMode: config.preferedMode,
disabledPack: config.disabledPack,
generalNum: config.preferredGeneralNum,
luckTime: config.preferredLuckTime,
disabledGenerals,
}])
);

View File

@ -46,6 +46,7 @@ Item {
property bool selected: false
property bool draggable: false
property bool autoBack: true
property bool showDetail: false
property int origX: 0
property int origY: 0
property real origOpacity: 1
@ -67,6 +68,11 @@ Item {
signal generalChanged() // For choose general freely
signal hoverChanged(bool enter)
onRightClicked: {
if (!showDetail) return;
roomScene.startCheat("RoomElement/Cheat/CardDetail.qml", { card: this });
}
RectangularGlow {
id: glowItem
anchors.fill: parent

View File

@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Flickable {
id: root
anchors.fill: parent
property var extra_data: ({})
signal finish()
contentHeight: details.height
ScrollBar.vertical: ScrollBar {}
ColumnLayout {
id: details
width: parent.width - 40
x: 20
// TODO: player details
Text {
id: screenName
Layout.fillWidth: true
font.pixelSize: 18
}
TextEdit {
id: skillDesc
Layout.fillWidth: true
font.pixelSize: 18
readOnly: true
selectByKeyboard: true
selectByMouse: false
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
}
}
onExtra_dataChanged: {
const card = extra_data.card;
if (!card) return;
const name = card.virt_name ? card.virt_name : card.name;
screenName.text = Backend.translate(name);
skillDesc.text = Backend.translate(":" + name);
}
}

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Flickable {
id: root
anchors.fill: parent
property var extra_data: ({})
signal finish()
contentHeight: details.height
ScrollBar.vertical: ScrollBar {}
ColumnLayout {
id: details
width: parent.width - 40
x: 20
TextEdit {
id: skillDesc
Layout.fillWidth: true
font.pixelSize: 18
readOnly: true
selectByKeyboard: true
selectByMouse: false
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
}
}
onExtra_dataChanged: {
if (!extra_data.generals) return;
skillDesc.text = "";
extra_data.generals.forEach((g) => {
let data = JSON.parse(Backend.callLuaFunction("GetGeneralDetail", [g]));
skillDesc.append(Backend.translate(data.kingdom) + " " + Backend.translate(g) + " " + data.hp + "/" + data.maxHp);
data.skill.forEach(t => {
skillDesc.append("<b>" + Backend.translate(t.name) + "</b>: " + t.description)
});
data.related_skill.forEach(t => {
skillDesc.append("<font color=\"purple\"><b>" + Backend.translate(t.name) + "</b>: " + t.description + "</font>")
});
skillDesc.append("\n");
});
}
}

View File

@ -16,7 +16,8 @@ Flickable {
ColumnLayout {
id: details
width: parent.width - 16
width: parent.width - 40
x: 20
// TODO: player details
Text {

View File

@ -106,6 +106,16 @@ GraphicsBox {
onClicked: close();
}
MetroButton {
id: detailBtn
enabled: choices.length > 0
text: Backend.translate("Show General Detail")
onClicked: roomScene.startCheat(
"RoomElement/Cheat/GeneralDetail.qml",
{ generals: choices }
);
}
}
}
}
@ -179,6 +189,7 @@ GraphicsBox {
item.goBack(true);
}
}
root.choicesChanged();
fightButton.enabled = (choices.length == choiceNum);
@ -201,10 +212,10 @@ GraphicsBox {
if (JSON.parse(Backend.callLuaFunction(
"GetSameGenerals", [generalList.get(i).name])
).length > 0) {
convertBtn.visible = true;
convertBtn.enabled = true;
return;
}
}
convertBtn.visible = false;
convertBtn.enabled = false;
}
}

View File

@ -111,7 +111,7 @@ CardItem {
color: "white"
font.family: fontLibian.name
font.pixelSize: 18
lineHeight: Math.max(1.4 - lineCount / 10, 0.6)
lineHeight: Math.max(1.4 - lineCount / 8, 0.6)
style: Text.Outline
wrapMode: Text.WrapAnywhere
}

View File

@ -34,6 +34,7 @@ Item {
card.autoBack = true;
card.draggable = true;
card.selectable = false;
card.showDetail = true;
card.clicked.connect(adjustCards);
}
@ -45,6 +46,7 @@ Item {
card = result[i];
card.draggable = false;
card.selectable = false;
card.showDetail = false;
card.selectedChanged.disconnect(adjustCards);
}
return result;

View File

@ -511,7 +511,6 @@ Item {
DelayedTrickArea {
id: delayedTrickAreaItem
rows: 1
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
}

View File

@ -5,9 +5,6 @@ import ".."
import "../../skin-bank.js" as SkinBank
Item {
property alias rows: grid.rows
property alias columns: grid.columns
InvisibleCardArea {
id: area
checkExisting: true
@ -17,16 +14,17 @@ Item {
id: cards
}
Grid {
Row {
id: grid
anchors.fill: parent
rows: 100
columns: 100
spacing: -4
Repeater {
model: cards
Image {
height: 55 * 0.8
width: 47 * 0.8
source: SkinBank.DELAYED_TRICK_DIR + name
}
}

View File

@ -78,6 +78,18 @@ function doOkButton() {
));
return;
}
if (roomScene.extra_data.luckCard) {
okButton.enabled = false;
ClientInstance.notifyServer("PushRequest", [
"luckcard", true
].join(","));
if (roomScene.extra_data.time == 1) {
roomScene.state = "notactive";
}
return;
}
replyToServer("1");
}
@ -103,6 +115,13 @@ function doCancelButton() {
return;
}
if (roomScene.extra_data.luckCard) {
ClientInstance.notifyServer("PushRequest", [
"luckcard", false
].join(","));
roomScene.state = "notactive";
return;
}
replyToServer("__cancel");
}
@ -1060,3 +1079,17 @@ callbacks["ChangeSelf"] = (j) => {
}
changeSelf(data);
}
callbacks["AskForLuckCard"] = (j) => {
// jsonData: int time
const time = parseInt(j);
roomScene.promptText = Backend.translate("#AskForLuckCard").arg(time);
roomScene.state = "replying";
roomScene.extra_data = {
luckCard: true,
time: time,
};
roomScene.okCancel.visible = true;
roomScene.okButton.enabled = true;
roomScene.cancelButton.enabled = true;
}

View File

@ -151,10 +151,13 @@ Rectangle {
}
Text {
text: "FK联机交流群531553435"
text: "常用联机IP175.178.66.93\nFK联机交流群531553435"
font.pixelSize: 20
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
anchors.right: parent.right
anchors.rightMargin: 20
horizontalAlignment: Text.AlignRight
}
//--------------------Disappear--------------

View File

@ -15,6 +15,7 @@ Window {
minimumHeight: 90
title: "FreeKill v" + FkVersion
property var callbacks: Logic.callbacks
property var tipList: []
Item {
id: mainWindow
@ -67,10 +68,45 @@ Item {
onBusyChanged: busyText = "";
BusyIndicator {
id: busyIndicator
running: true
anchors.centerIn: parent
visible: mainWindow.busy === true
}
Text {
anchors.top: busyIndicator.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 8
visible: mainWindow.busy === true
property int idx: 1
text: tipList[idx - 1] ?? ""
color: "#F0E5DA"
font.pixelSize: 20
font.family: fontLibian.name
style: Text.Outline
styleColor: "#3D2D1C"
textFormat: Text.RichText
width: parent.width * 0.7
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAnywhere
onVisibleChanged: idx = 0;
Timer {
running: parent.visible
interval: 3600
repeat: true
onTriggered: {
const oldIdx = parent.idx;
while (parent.idx === oldIdx) {
parent.idx = Math.floor(Math.random() * tipList.length) + 1;
}
}
}
}
Item {
visible: mainWindow.busy === true && mainWindow.busyText !== ""
anchors.bottom: parent.bottom
@ -247,6 +283,9 @@ Item {
width = config.winWidth;
height = config.winHeight;
}
const tips = Backend.loadTips();
tipList = tips.trim().split("\n");
}
onClosing: {

View File

@ -231,6 +231,18 @@ QString QmlBackend::loadConf() {
return conf.readAll();
}
QString QmlBackend::loadTips() {
QFile conf("waiting_tips.txt");
if (!conf.exists()) {
conf.open(QIODevice::WriteOnly);
static const char *init_conf = "转啊~ 转啊~";
conf.write(init_conf);
return init_conf;
}
conf.open(QIODevice::ReadOnly);
return conf.readAll();
}
void QmlBackend::saveConf(const QString &conf) {
QFile c("freekill.client.config.json");
c.open(QIODevice::WriteOnly);

View File

@ -38,6 +38,7 @@ public:
Q_INVOKABLE QString pubEncrypt(const QString &key, const QString &data);
Q_INVOKABLE QString loadConf();
Q_INVOKABLE QString loadTips();
Q_INVOKABLE void saveConf(const QString &conf);
Q_INVOKABLE void replyDelayTest(const QString &screenName, const QString &cipher);

26
waiting_tips.txt Normal file
View File

@ -0,0 +1,26 @@
在对局中长按他人武将牌可以查看其技能哦
长按手牌可以查看这张卡牌的详细信息
在大厅界面点击左上角的头像可以进行各种设置
在聊天中输入$zhiheng:1试试看
在聊天中输入$~caocao试试看
不要为了一个战功,放弃你的队友,你不是一个人在战斗。
转啊~ 转啊~
给FreeKill的GitHub仓库点一个star吧
你可以在武将一览界面禁将,在你创建的房间中禁用武将不会出现在选将框。
如果你遇到了bug请截图并反馈给开发者
如果你想要制作FK的mod需要先学习Lua语言
目光所及短寸吾才之间满腹狭目之阿瞒有我见袁本良计初只能窥取冀州便是底竟不从易成略在胸如反掌之良计速出吾才满目光所及腹袁本初竟短寸之间不从之
吔!
想要参与到FK的开发工作中那么就从制作自己的Mod开始吧
在游戏的一些地方,长按可以显示该元素的详情。如果你用的是电脑版的话可以用鼠标右键代替长按。
注意:官方不支持任何形式的账号交易行为,请妥善保管好您的账号密码。请勿与他人共享账号。因账号交易、共享引起的账号争议官方均不受理。
如果牌不好的话就请辱骂GK的发牌员吧
行动前请三思,需要先明确全场所有人的技能与卡牌的效果,再去做出决定。
神曰“MBKS”
正在匹配势均力敌的对手…… 匹配到界徐盛
先喝酒还是先上刀,这是个值得考虑的问题
在抱怨FK只有标准包那么去试着参与联机吧
如果在牌局内想查看技能,可以长按想要看技能的角色
FK的开服很简单只要单机启动就可以当做服务器使用了
用Linux也能搭建FK服务器起始页推荐的联机IP就是跑在Linux服务器上的
我们的游戏正在蒸蒸日上哦~