761 lines
16 KiB
QML
761 lines
16 KiB
QML
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import QtQuick
|
|
import Qt5Compat.GraphicalEffects
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import Fk
|
|
import Fk.PhotoElement
|
|
|
|
Item {
|
|
id: root
|
|
width: 175
|
|
height: 233
|
|
scale: 0.75
|
|
property int playerid: 0
|
|
property string general: ""
|
|
property string avatar: ""
|
|
property string deputyGeneral: ""
|
|
property string screenName: ""
|
|
property string role: "unknown"
|
|
property string kingdom: "qun"
|
|
property string netstate: "online"
|
|
property alias handcards: handcardAreaItem.length
|
|
property int maxHp: 0
|
|
property int hp: 0
|
|
property int shield: 0
|
|
property int seatNumber: 1
|
|
property bool dead: false
|
|
property bool dying: false
|
|
property bool faceup: true
|
|
property bool chained: false
|
|
property int drank: 0
|
|
property int rest: 0
|
|
property bool isOwner: false
|
|
property bool ready: false
|
|
property int winGame: 0
|
|
property int runGame: 0
|
|
property int totalGame: 0
|
|
property list<string> sealedSlots: []
|
|
|
|
property int distance: -1
|
|
property string status: "normal"
|
|
property int maxCard: 0
|
|
|
|
property alias handcardArea: handcardAreaItem
|
|
property alias equipArea: equipAreaItem
|
|
property alias areasSealed: equipAreaItem
|
|
property alias markArea: markAreaItem
|
|
property alias picMarkArea: picMarkAreaItem
|
|
property alias delayedTrickArea: delayedTrickAreaItem
|
|
property alias specialArea: specialAreaItem
|
|
|
|
property alias progressBar: progressBar
|
|
property alias progressTip: progressTip.text
|
|
|
|
property bool selectable: false
|
|
property bool selected: false
|
|
|
|
property bool playing: false
|
|
property bool surrendered: false
|
|
onPlayingChanged: {
|
|
if (playing) {
|
|
animPlaying.start();
|
|
} else {
|
|
animPlaying.stop();
|
|
}
|
|
}
|
|
|
|
Behavior on x {
|
|
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
Behavior on y {
|
|
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
|
|
}
|
|
|
|
states: [
|
|
State { name: "normal" },
|
|
State { name: "candidate" }
|
|
//State { name: "playing" }
|
|
//State { name: "responding" },
|
|
//State { name: "sos" }
|
|
]
|
|
|
|
state: "normal"
|
|
transitions: [
|
|
Transition {
|
|
from: "*"; to: "normal"
|
|
ScriptAction {
|
|
script: {
|
|
animSelectable.stop();
|
|
animSelected.stop();
|
|
}
|
|
}
|
|
},
|
|
|
|
Transition {
|
|
from: "*"; to: "candidate"
|
|
ScriptAction {
|
|
script: {
|
|
animSelectable.start();
|
|
animSelected.start();
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
PixmapAnimation {
|
|
id: animPlaying
|
|
source: SkinBank.PIXANIM_DIR + "playing"
|
|
anchors.centerIn: parent
|
|
loop: true
|
|
scale: 1.1
|
|
visible: root.playing
|
|
}
|
|
|
|
PixmapAnimation {
|
|
id: animSelected
|
|
source: SkinBank.PIXANIM_DIR + "selected"
|
|
anchors.centerIn: parent
|
|
loop: true
|
|
scale: 1.1
|
|
visible: root.state === "candidate" && selected
|
|
}
|
|
|
|
Image {
|
|
id: back
|
|
source: SkinBank.getPhotoBack(root.kingdom)
|
|
}
|
|
|
|
Text {
|
|
id: generalName
|
|
x: 5
|
|
y: 28
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 22
|
|
opacity: 0.9
|
|
horizontalAlignment: Text.AlignHCenter
|
|
lineHeight: 18
|
|
lineHeightMode: Text.FixedHeight
|
|
color: "white"
|
|
width: 24
|
|
wrapMode: Text.WrapAnywhere
|
|
text: ""
|
|
}
|
|
|
|
Text {
|
|
id: longGeneralName
|
|
x: 5
|
|
y: 6
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 22
|
|
rotation: 90
|
|
transformOrigin: Item.BottomLeft
|
|
opacity: 0.9
|
|
horizontalAlignment: Text.AlignHCenter
|
|
lineHeight: 18
|
|
lineHeightMode: Text.FixedHeight
|
|
color: "white"
|
|
text: ""
|
|
}
|
|
|
|
HpBar {
|
|
id: hp
|
|
x: 8
|
|
value: root.hp
|
|
maxValue: root.maxHp
|
|
shieldNum: root.shield
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 36
|
|
}
|
|
|
|
Item {
|
|
width: 138
|
|
height: 222
|
|
visible: false
|
|
id: generalImgItem
|
|
|
|
Image {
|
|
id: generalImage
|
|
width: deputyGeneral ? parent.width / 2 : parent.width
|
|
Behavior on width { NumberAnimation { duration: 100 } }
|
|
height: parent.height
|
|
smooth: true
|
|
fillMode: Image.PreserveAspectCrop
|
|
source: {
|
|
if (general === "") {
|
|
return "";
|
|
}
|
|
if (deputyGeneral) {
|
|
return SkinBank.getGeneralExtraPic(general, "dual/")
|
|
?? SkinBank.getGeneralPicture(general);
|
|
} else {
|
|
return SkinBank.getGeneralPicture(general)
|
|
}
|
|
}
|
|
}
|
|
|
|
Image {
|
|
id: deputyGeneralImage
|
|
anchors.left: generalImage.right
|
|
width: parent.width / 2
|
|
height: parent.height
|
|
smooth: true
|
|
fillMode: Image.PreserveAspectCrop
|
|
source: {
|
|
const general = deputyGeneral;
|
|
if (deputyGeneral != "") {
|
|
return SkinBank.getGeneralExtraPic(general, "dual/")
|
|
?? SkinBank.getGeneralPicture(general);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
Image {
|
|
id: deputySplit
|
|
source: SkinBank.PHOTO_DIR + "deputy-split"
|
|
opacity: deputyGeneral ? 1 : 0
|
|
}
|
|
|
|
Text {
|
|
id: deputyGeneralName
|
|
anchors.left: generalImage.right
|
|
anchors.leftMargin: -14
|
|
y: 23
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 22
|
|
opacity: 0.9
|
|
horizontalAlignment: Text.AlignHCenter
|
|
lineHeight: 18
|
|
lineHeightMode: Text.FixedHeight
|
|
color: "white"
|
|
width: 24
|
|
wrapMode: Text.WrapAnywhere
|
|
text: luatr(deputyGeneral)
|
|
style: Text.Outline
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: photoMask
|
|
x: 31
|
|
y: 5
|
|
width: 138
|
|
height: 222
|
|
radius: 8
|
|
visible: false
|
|
}
|
|
|
|
OpacityMask {
|
|
id: photoMaskEffect
|
|
anchors.fill: photoMask
|
|
source: generalImgItem
|
|
maskSource: photoMask
|
|
}
|
|
|
|
Colorize {
|
|
anchors.fill: photoMaskEffect
|
|
source: photoMaskEffect
|
|
saturation: 0
|
|
opacity: (root.dead || root.surrendered) ? 1 : 0
|
|
Behavior on opacity { NumberAnimation { duration: 300 } }
|
|
}
|
|
|
|
Rectangle {
|
|
x: 31
|
|
y: 5
|
|
width: 138
|
|
height: 222
|
|
radius: 8
|
|
|
|
// visible: root.drank > 0
|
|
color: "red"
|
|
opacity: (root.drank <= 0 ? 0 : 0.4) + Math.log(root.drank) * 0.12
|
|
Behavior on opacity { NumberAnimation { duration: 300 } }
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: restRect
|
|
anchors.centerIn: photoMask
|
|
anchors.leftMargin: 20
|
|
visible: root.rest > 0
|
|
|
|
GlowText {
|
|
Layout.alignment: Qt.AlignCenter
|
|
text: luatr("resting...")
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 40
|
|
font.bold: true
|
|
color: "#FEF7D6"
|
|
glow.color: "#845422"
|
|
glow.spread: 0.8
|
|
}
|
|
|
|
GlowText {
|
|
Layout.alignment: Qt.AlignCenter
|
|
visible: root.rest > 0 && root.rest < 999
|
|
text: root.rest
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 34
|
|
font.bold: true
|
|
color: "#DBCC69"
|
|
glow.color: "#2E200F"
|
|
glow.spread: 0.6
|
|
}
|
|
|
|
GlowText {
|
|
Layout.alignment: Qt.AlignCenter
|
|
visible: root.rest > 0 && root.rest < 999
|
|
text: luatr("rest round num")
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 28
|
|
color: "#F0E5D6"
|
|
glow.color: "#2E200F"
|
|
glow.spread: 0.6
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: winRateRect
|
|
width: 138; x: 31
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 6
|
|
height: childrenRect.height + 8
|
|
color: "#CC3C3229"
|
|
radius: 8
|
|
border.color: "white"
|
|
border.width: 1
|
|
visible: screenName != "" && !roomScene.isStarted
|
|
|
|
Text {
|
|
y: 4
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
font.pixelSize: 20
|
|
font.family: fontLibian.name
|
|
color: (totalGame > 0 && runGame / totalGame > 0.2) ? "red" : "white"
|
|
style: Text.Outline
|
|
text: {
|
|
if (totalGame === 0) {
|
|
return luatr("Newbie");
|
|
}
|
|
const winRate = (winGame / totalGame) * 100;
|
|
const runRate = (runGame / totalGame) * 100;
|
|
return luatr("Win=%1\nRun=%2\nTotal=%3")
|
|
.arg(winRate.toFixed(2))
|
|
.arg(runRate.toFixed(2))
|
|
.arg(totalGame);
|
|
}
|
|
}
|
|
}
|
|
|
|
Image {
|
|
anchors.bottom: winRateRect.top
|
|
anchors.right: parent.right
|
|
anchors.bottomMargin: -8
|
|
anchors.rightMargin: 4
|
|
source: SkinBank.PHOTO_DIR +
|
|
(isOwner ? "owner" : (ready ? "ready" : "notready"))
|
|
visible: screenName != "" && !roomScene.isStarted
|
|
}
|
|
|
|
Image {
|
|
visible: equipAreaItem.length > 0
|
|
source: SkinBank.PHOTO_DIR + "equipbg"
|
|
x: 31
|
|
y: 121
|
|
}
|
|
|
|
Image {
|
|
source: root.status != "normal" ? SkinBank.STATUS_DIR + root.status : ""
|
|
x: -6
|
|
}
|
|
|
|
Image {
|
|
id: turnedOver
|
|
visible: !root.faceup
|
|
source: SkinBank.PHOTO_DIR + "faceturned" + (config.heg ? '-heg' : '')
|
|
x: 29; y: 5
|
|
}
|
|
|
|
EquipArea {
|
|
id: equipAreaItem
|
|
|
|
x: 31
|
|
y: 157
|
|
}
|
|
|
|
Item {
|
|
id: specialAreaItem
|
|
|
|
x: 31
|
|
y: 139
|
|
|
|
InvisibleCardArea {
|
|
id: specialContainer
|
|
// checkExisting: true
|
|
}
|
|
|
|
function updatePileInfo(areaName) {
|
|
if (areaName.startsWith('#')) return;
|
|
const data = lcall("GetPile", root.playerid, areaName);
|
|
if (data.length === 0) {
|
|
root.markArea.removeMark(areaName);
|
|
} else {
|
|
root.markArea.setMark(areaName, data.length.toString());
|
|
}
|
|
}
|
|
|
|
function add(inputs, areaName) {
|
|
updatePileInfo(areaName);
|
|
specialContainer.add(inputs);
|
|
}
|
|
|
|
function remove(inputs, areaName) {
|
|
updatePileInfo(areaName);
|
|
return specialContainer.remove(inputs);
|
|
}
|
|
|
|
function updateCardPosition(a) {
|
|
specialContainer.updateCardPosition(a);
|
|
}
|
|
}
|
|
|
|
MarkArea {
|
|
id: markAreaItem
|
|
|
|
anchors.bottom: equipAreaItem.top
|
|
x: 31
|
|
}
|
|
|
|
Image {
|
|
id: chain
|
|
visible: root.chained
|
|
source: SkinBank.PHOTO_DIR + "chain"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
y: 72
|
|
}
|
|
|
|
Image {
|
|
// id: saveme
|
|
visible: (root.dead && !root.rest) || root.dying || root.surrendered
|
|
source: {
|
|
if (root.surrendered) {
|
|
return SkinBank.DEATH_DIR + "surrender";
|
|
} else if (root.dead) {
|
|
return SkinBank.getRoleDeathPic(root.role);
|
|
}
|
|
return SkinBank.DEATH_DIR + "saveme";
|
|
}
|
|
anchors.centerIn: photoMask
|
|
}
|
|
|
|
Image {
|
|
id: netstat
|
|
source: SkinBank.STATE_DIR + root.netstate
|
|
x: photoMask.x
|
|
y: photoMask.y
|
|
scale: 0.9
|
|
transformOrigin: Item.TopLeft
|
|
}
|
|
|
|
Image {
|
|
id: handcardNum
|
|
source: SkinBank.PHOTO_DIR + "handcard"
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: -6
|
|
x: -6
|
|
|
|
Text {
|
|
text: {
|
|
if (root.maxCard === root.hp || root.hp < 0) {
|
|
return root.handcards;
|
|
} else {
|
|
const maxCard = root.maxCard < 900 ? root.maxCard : "∞";
|
|
return root.handcards + "/" + maxCard;
|
|
}
|
|
}
|
|
font.family: fontLibian.name
|
|
font.pixelSize: (root.maxCard === root.hp || root.hp < 0 ) ? 32 : 27
|
|
//font.weight: 30
|
|
color: "white"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 4
|
|
style: Text.Outline
|
|
}
|
|
|
|
TapHandler {
|
|
enabled: (root.state != "candidate" || !root.selectable)
|
|
&& root.playerid !== Self.id
|
|
onTapped: {
|
|
const params = { name: "hand_card" };
|
|
let data = lcall("GetPlayerHandcards", root.playerid);
|
|
data = data.filter((e) => e !== -1);
|
|
if (data.length === 0)
|
|
return;
|
|
|
|
params.ids = data;
|
|
|
|
// Just for using room's right drawer
|
|
roomScene.startCheat("../RoomElement/ViewPile", params);
|
|
}
|
|
}
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
|
|
gesturePolicy: TapHandler.WithinBounds
|
|
|
|
onTapped: (p, btn) => {
|
|
if (btn === Qt.LeftButton || btn === Qt.NoButton) {
|
|
if (parent.state != "candidate" || !parent.selectable) {
|
|
return;
|
|
}
|
|
parent.selected = !parent.selected;
|
|
} else if (btn === Qt.RightButton) {
|
|
parent.showDetail();
|
|
}
|
|
}
|
|
|
|
onLongPressed: {
|
|
parent.showDetail();
|
|
}
|
|
}
|
|
|
|
RoleComboBox {
|
|
id: role
|
|
value: root.role
|
|
anchors.top: parent.top
|
|
anchors.topMargin: -4
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: -4
|
|
}
|
|
|
|
LimitSkillArea {
|
|
id: limitSkills
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.topMargin: role.height + 2
|
|
anchors.rightMargin: 30
|
|
}
|
|
|
|
GlowText {
|
|
id: playerName
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 2
|
|
|
|
font.pixelSize: 16
|
|
text: {
|
|
let ret = screenName;
|
|
if (config.blockedUsers?.includes(screenName))
|
|
ret = luatr("<Blocked> ") + ret;
|
|
return ret;
|
|
}
|
|
|
|
glow.radius: 8
|
|
}
|
|
|
|
Image {
|
|
visible: root.state === "candidate" && !selectable && !selected
|
|
source: SkinBank.PHOTO_DIR + "disable"
|
|
x: 31; y: -21
|
|
}
|
|
|
|
GlowText {
|
|
id: seatNum
|
|
visible: !progressBar.visible
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: -32
|
|
property var seatChr: [
|
|
"一", "二", "三", "四", "五", "六",
|
|
"七", "八", "九", "十", "十一", "十二",
|
|
]
|
|
font.family: fontLi2.name
|
|
font.pixelSize: 32
|
|
text: seatChr[seatNumber - 1]
|
|
|
|
glow.color: "brown"
|
|
glow.spread: 0.2
|
|
glow.radius: 8
|
|
//glow.samples: 12
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: trembleAnimation
|
|
running: false
|
|
PropertyAnimation {
|
|
target: root
|
|
property: "x"
|
|
to: root.x - 20
|
|
easing.type: Easing.InQuad
|
|
duration: 100
|
|
}
|
|
PropertyAnimation {
|
|
target: root
|
|
property: "x"
|
|
to: root.x
|
|
easing.type: Easing.OutQuad
|
|
duration: 100
|
|
}
|
|
}
|
|
|
|
function tremble() {
|
|
trembleAnimation.start()
|
|
}
|
|
|
|
ProgressBar {
|
|
id: progressBar
|
|
width: parent.width
|
|
height: 4
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: -4
|
|
from: 0.0
|
|
to: 100.0
|
|
|
|
visible: false
|
|
NumberAnimation on value {
|
|
running: progressBar.visible
|
|
from: 100.0
|
|
to: 0.0
|
|
duration: config.roomTimeout * 1000
|
|
|
|
onFinished: {
|
|
progressBar.visible = false;
|
|
root.progressTip = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
Image {
|
|
anchors.top: progressBar.bottom
|
|
anchors.topMargin: 1
|
|
source: SkinBank.PHOTO_DIR + "control/tip"
|
|
visible: progressTip.text != ""
|
|
Text {
|
|
id: progressTip
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 18
|
|
x: 18
|
|
color: "white"
|
|
text: ""
|
|
}
|
|
}
|
|
|
|
PixmapAnimation {
|
|
id: animSelectable
|
|
source: SkinBank.PIXANIM_DIR + "selectable"
|
|
anchors.centerIn: parent
|
|
loop: true
|
|
visible: root.state === "candidate" && selectable
|
|
}
|
|
|
|
InvisibleCardArea {
|
|
id: handcardAreaItem
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
DelayedTrickArea {
|
|
id: delayedTrickAreaItem
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 8
|
|
}
|
|
|
|
PicMarkArea {
|
|
id: picMarkAreaItem
|
|
|
|
anchors.top: parent.bottom
|
|
anchors.right: parent.right
|
|
anchors.topMargin: -4
|
|
}
|
|
|
|
InvisibleCardArea {
|
|
id: defaultArea
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
Rectangle {
|
|
id: chat
|
|
color: "#F2ECD7"
|
|
radius: 4
|
|
opacity: 0
|
|
width: parent.width
|
|
height: childrenRect.height + 8
|
|
property string text: ""
|
|
visible: false
|
|
Text {
|
|
width: parent.width - 8
|
|
x: 4
|
|
y: 4
|
|
text: parent.text
|
|
wrapMode: Text.WrapAnywhere
|
|
font.family: fontLibian.name
|
|
font.pixelSize: 20
|
|
}
|
|
SequentialAnimation {
|
|
id: chatAnim
|
|
PropertyAnimation {
|
|
target: chat
|
|
property: "opacity"
|
|
to: 0.9
|
|
duration: 200
|
|
}
|
|
NumberAnimation {
|
|
duration: 2500
|
|
}
|
|
PropertyAnimation {
|
|
target: chat
|
|
property: "opacity"
|
|
to: 0
|
|
duration: 150
|
|
}
|
|
onFinished: chat.visible = false;
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
color: "white"
|
|
height: 20
|
|
width: 20
|
|
visible: distance != -1
|
|
Text {
|
|
text: distance
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
|
|
onGeneralChanged: {
|
|
if (!roomScene.isStarted) return;
|
|
const text = luatr(general);
|
|
if (text.length > 6) {
|
|
generalName.text = "";
|
|
longGeneralName.text = text;
|
|
} else {
|
|
generalName.text = text;
|
|
longGeneralName.text = "";
|
|
}
|
|
}
|
|
|
|
function chat(msg) {
|
|
chat.text = msg;
|
|
chat.visible = true;
|
|
chatAnim.restart();
|
|
}
|
|
|
|
function updateLimitSkill(skill, time) {
|
|
limitSkills.update(skill, time);
|
|
}
|
|
|
|
function showDetail() {
|
|
if (playerid === 0 || playerid === -1) {
|
|
return;
|
|
}
|
|
|
|
roomScene.startCheat("PlayerDetail", { photo: this });
|
|
}
|
|
}
|