This commit is contained in:
Ho-spair 2023-07-02 20:48:00 +08:00
commit d5bee59f2a
13 changed files with 120 additions and 44 deletions

View File

@ -2,6 +2,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts import QtQuick.Layouts
import QtMultimedia import QtMultimedia
import Fk import Fk
@ -71,15 +72,15 @@ Item {
} }
// tmp // tmp
DelayButton { Button {
id: quitButton id: menuButton
text: "quit"
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
delay: Debugging ? 10 : 1000 anchors.rightMargin: 10
onActivated: { text: Backend.translate("Menu")
// ClientInstance.clearPlayers(); z: 2
ClientInstance.notifyServer("QuitRoom", "[]"); onClicked: {
menuContainer.visible || menuContainer.open();
} }
} }
@ -345,6 +346,7 @@ Item {
drank: model.drank drank: model.drank
isOwner: model.isOwner isOwner: model.isOwner
ready: model.ready ready: model.ready
surrendered: model.surrendered
onSelectedChanged: { onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected); Logic.updateSelectedTargets(playerid, selected);
@ -395,6 +397,7 @@ Item {
MetroButton { MetroButton {
text: Backend.translate("Sort Cards") text: Backend.translate("Sort Cards")
textFont.pixelSize: 28 textFont.pixelSize: 28
onClicked: Logic.resortHandcards();
} }
MetroButton { MetroButton {
text: Backend.translate("Chat") text: Backend.translate("Chat")
@ -790,7 +793,7 @@ Item {
MiscStatus { MiscStatus {
id: miscStatus id: miscStatus
anchors.right: quitButton.left anchors.right: menuButton.left
anchors.top: parent.top anchors.top: parent.top
anchors.rightMargin: 16 anchors.rightMargin: 16
anchors.topMargin: 8 anchors.topMargin: 8
@ -1067,6 +1070,7 @@ Item {
drank: 0, drank: 0,
isOwner: false, isOwner: false,
ready: false, ready: false,
surrendered: false,
}); });
} }

View File

@ -204,6 +204,47 @@ function moveCards(moves) {
} }
} }
function resortHandcards() {
if (!dashboard.handcardArea.cards.length) {
return;
}
const subtypeString2Number = {
["none"]: Card.SubtypeNone,
["delayed_trick"]: Card.SubtypeDelayedTrick,
["weapon"]: Card.SubtypeWeapon,
["armor"]: Card.SubtypeArmor,
["defensive_horse"]: Card.SubtypeDefensiveRide,
["offensive_horse"]: Card.SubtypeOffensiveRide,
["treasure"]: Card.SubtypeTreasure,
}
dashboard.handcardArea.cards.sort((prev, next) => {
if (prev.type === next.type) {
const prevSubtypeNumber = subtypeString2Number[prev.subtype];
const nextSubtypeNumber = subtypeString2Number[next.subtype];
if (prevSubtypeNumber === nextSubtypeNumber) {
const splitedPrevName = prev.name.split('__');
const prevTrueName = splitedPrevName[splitedPrevName.length - 1];
const splitedNextName = next.name.split('__');
const nextTrueName = splitedNextName[splitedNextName.length - 1];
if (prevTrueName === nextTrueName) {
return prev.cid - next.cid;
} else {
return prevTrueName > nextTrueName ? -1 : 1;
}
} else {
return prevSubtypeNumber - nextSubtypeNumber;
}
} else {
return prev.type - next.type;
}
});
dashboard.handcardArea.updateCardPosition(true);
}
function setEmotion(id, emotion, isCardId) { function setEmotion(id, emotion, isCardId) {
let path; let path;
if (OS === "Win") { if (OS === "Win") {
@ -1336,3 +1377,7 @@ callbacks["AskForLuckCard"] = (j) => {
roomScene.okButton.enabled = true; roomScene.okButton.enabled = true;
roomScene.cancelButton.enabled = true; roomScene.cancelButton.enabled = true;
} }
callbacks["CancelRequest"] = (jsonData) => {
ClientInstance.replyToServer("", "__cancel")
}

View File

@ -62,6 +62,9 @@ Item {
card = cards[i]; card = cards[i];
card.origX = i * spacing; card.origX = i * spacing;
card.origY = 0; card.origY = 0;
card.z = i + 1;
card.initialZ = i + 1;
card.maxZ = cards.length;
} }
} }

View File

@ -26,6 +26,7 @@ Item {
property string name: "slash" property string name: "slash"
property string extension: "" property string extension: ""
property string virt_name: "" property string virt_name: ""
property int type: 0
property string subtype: "" property string subtype: ""
property string color: "" // only use when suit is empty property string color: "" // only use when suit is empty
property string footnote: "" // footnote, e.g. "A use card to B" property string footnote: "" // footnote, e.g. "A use card to B"
@ -51,6 +52,8 @@ Item {
property bool showDetail: false property bool showDetail: false
property int origX: 0 property int origX: 0
property int origY: 0 property int origY: 0
property int initialZ: 0
property int maxZ: 0
property real origOpacity: 1 property real origOpacity: 1
// property bool isClicked: false // property bool isClicked: false
property bool moveAborted: false property bool moveAborted: false
@ -259,10 +262,12 @@ Item {
if (!draggable) return; if (!draggable) return;
if (hovered) { if (hovered) {
glow.visible = true; glow.visible = true;
root.z++;
root.z = root.maxZ ? root.maxZ + 1 : root.z + 1;
} else { } else {
glow.visible = false; glow.visible = false;
root.z--;
root.z = root.initialZ ? root.initialZ : root.z - 1
} }
} }
} }
@ -317,6 +322,7 @@ Item {
suit = data.suit; suit = data.suit;
number = data.number; number = data.number;
color = data.color; color = data.color;
type = data.type ? data.type : 0
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 ?? {}; mark = data.mark ?? {};
@ -330,6 +336,7 @@ Item {
suit: suit, suit: suit,
number: number, number: number,
color: color, color: color,
type: type,
subtype: subtype, subtype: subtype,
virt_name: virt_name, virt_name: virt_name,
mark: mark, mark: mark,

View File

@ -9,7 +9,8 @@ Item {
visible: roundNum || pileNum visible: roundNum || pileNum
function getTimeString(time) { function getTimeString(time) {
const s = time % 60; let s = time % 60;
s < 10 && (s = '0' + s);
const m = (time - s) / 60; const m = (time - s) / 60;
const h = (time - s - m * 60) / 3600; const h = (time - s - m * 60) / 3600;
return h ? `${h}:${m}:${s}` : `${m}:${s}`; return h ? `${h}:${m}:${s}` : `${m}:${s}`;

View File

@ -52,6 +52,7 @@ Item {
property bool selected: false property bool selected: false
property bool playing: false property bool playing: false
property bool surrendered: false
onPlayingChanged: { onPlayingChanged: {
if (playing) { if (playing) {
animPlaying.start(); animPlaying.start();
@ -235,7 +236,7 @@ Item {
anchors.fill: photoMask anchors.fill: photoMask
source: generalImgItem source: generalImgItem
saturation: 0 saturation: 0
visible: root.dead visible: root.dead || root.surrendered
} }
Rectangle { Rectangle {
@ -370,8 +371,8 @@ Item {
Image { Image {
// id: saveme // id: saveme
visible: root.dead || root.dying visible: root.dead || root.dying || root.surrendered
source: SkinBank.DEATH_DIR + (root.dead ? root.role : "saveme") source: SkinBank.DEATH_DIR + (root.dead ? root.role : root.surrendered ? "surrender" : "saveme")
anchors.centerIn: photoMask anchors.centerIn: photoMask
} }

View File

@ -98,6 +98,7 @@ function GetCardData(id)
suit = card:getSuitString(), suit = card:getSuitString(),
color = card:getColorString(), color = card:getColorString(),
mark = mark, mark = mark,
type = card.type,
subtype = cardSubtypeStrings[card.sub_type] subtype = cardSubtypeStrings[card.sub_type]
} }
if card.skillName ~= "" then if card.skillName ~= "" then
@ -625,4 +626,9 @@ function SetObserving(o)
ClientInstance.observing = o ClientInstance.observing = o
end end
function CheckSurrenderAvailable(playedTime)
local curMode = ClientInstance.room_settings.gameMode
return json.encode(Fk.game_modes[curMode]:surrenderFunc(playedTime))
end
dofile "lua/client/i18n/init.lua" dofile "lua/client/i18n/init.lua"

View File

@ -10,6 +10,7 @@
---@field public maxPlayer integer @ 最大玩家数 ---@field public maxPlayer integer @ 最大玩家数
---@field public rule TriggerSkill @ 规则(通过技能完成,通常用来为特定角色及特定时机提供触发事件) ---@field public rule TriggerSkill @ 规则(通过技能完成,通常用来为特定角色及特定时机提供触发事件)
---@field public logic fun() @ 逻辑通过function完成通常用来初始化、分配身份及座次 ---@field public logic fun() @ 逻辑通过function完成通常用来初始化、分配身份及座次
---@field public surrenderFunc fun()
local GameMode = class("GameMode") local GameMode = class("GameMode")
--- 构造函数,不可随意调用。 --- 构造函数,不可随意调用。

View File

@ -540,5 +540,14 @@ function fk.CreateGameMode(spec)
local ret = GameMode:new(spec.name, spec.minPlayer, spec.maxPlayer) local ret = GameMode:new(spec.name, spec.minPlayer, spec.maxPlayer)
ret.rule = spec.rule ret.rule = spec.rule
ret.logic = spec.logic ret.logic = spec.logic
if spec.winner_getter then
assert(type(spec.winner_getter) == "function")
ret.getWinner = spec.winner_getter
end
if spec.surrender_func then
assert(type(spec.surrender_func) == "function")
ret.surrenderFunc = spec.surrender_func
end
return ret return ret
end end

View File

@ -135,6 +135,23 @@ request_handlers["changeself"] = function(room, id, reqlist)
}) })
end end
request_handlers["surrender"] = function(room, id, reqlist)
local logic = room.logic
local curEvent = logic:getCurrentEvent()
if curEvent then
curEvent:addExitFunc(
function()
local player = room:getPlayerById(id)
player.surrendered = true
room:broadcastProperty(player, "surrendered")
room:gameOver(Fk.game_modes[room.settings.gameMode]:getWinner(player))
end
)
room.hasSurrendered = true
room:doBroadcastNotify("CancelRequest", "")
end
end
request_handlers["newroom"] = function(s, id) request_handlers["newroom"] = function(s, id)
s:registerRoom(id) s:registerRoom(id)
end end

View File

@ -2950,7 +2950,7 @@ function Room:getCardsFromPileByRule(pattern, num, fromPile)
if fromPile == "discardPile" then if fromPile == "discardPile" then
pileToSearch = self.discard_pile pileToSearch = self.discard_pile
elseif fromPile == "allPiles" then elseif fromPile == "allPiles" then
pileToSearch = table.clone(self.draw_pile) pileToSearch = table.simpleClone(self.draw_pile)
table.insertTable(pileToSearch, self.discard_pile) table.insertTable(pileToSearch, self.discard_pile)
end end

View File

@ -92,6 +92,10 @@ local function _waitForReply(player, timeout)
player.request_timeout = timeout player.request_timeout = timeout
player.request_start = start player.request_start = start
if state ~= fk.Player_Online then if state ~= fk.Player_Online then
if player.room.hasSurrendered then
return "__cancel"
end
if state ~= fk.Player_Robot then if state ~= fk.Player_Robot then
player.room:checkNoHuman() player.room:checkNoHuman()
player.room:delay(500) player.room:delay(500)
@ -117,6 +121,12 @@ local function _waitForReply(player, timeout)
player.serverplayer:setThinking(false) player.serverplayer:setThinking(false)
return "" return ""
end end
if player.room.hasSurrendered then
player.serverplayer:setThinking(false)
return ""
end
coroutine.yield("__handleRequest", rest) coroutine.yield("__handleRequest", rest)
end end
end end

View File

@ -1,33 +1,5 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
---@param victim ServerPlayer
local function getWinner(victim)
local room = victim.room
local winner = ""
local alive = room.alive_players
if victim.role == "lord" then
if #alive == 1 and alive[1].role == "renegade" then
winner = "renegade"
else
winner = "rebel"
end
elseif victim.role ~= "loyalist" then
local lord_win = true
for _, p in ipairs(alive) do
if p.role == "rebel" or p.role == "renegade" then
lord_win = false
break
end
end
if lord_win then
winner = "lord+loyalist"
end
end
return winner
end
---@param killer ServerPlayer ---@param killer ServerPlayer
local function rewardAndPunish(killer, victim) local function rewardAndPunish(killer, victim)
if killer.dead then return end if killer.dead then return end
@ -104,7 +76,7 @@ GameRule = fk.CreateTriggerSkill{
end end
end, end,
[fk.GameOverJudge] = function() [fk.GameOverJudge] = function()
local winner = getWinner(player) local winner = Fk.game_modes[room.settings.gameMode]:getWinner(player)
if winner ~= "" then if winner ~= "" then
room:gameOver(winner) room:gameOver(winner)
return true return true