FreeKill/Fk/Pages/Room.qml
2024-11-09 19:27:41 +08:00

1449 lines
37 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import QtMultimedia
import Fk
import Fk.Common
import Fk.RoomElement
import Fk.PhotoElement as PhotoElement
import "RoomLogic.js" as Logic
Item {
id: roomScene
property int playerNum: 0
// property var dashboardModel
property bool isOwner: false
property bool isStarted: false
property bool isFull: false
property bool isAllReady: false
property bool isReady: false
property bool canKickOwner: false
property alias popupBox: popupBox
property alias manualBox: manualBox
property alias bigAnim: bigAnim
property alias promptText: prompt.text
property var currentPrompt
property alias okCancel: okCancel
property alias okButton: okButton
property alias cancelButton: cancelButton
property alias dynamicCardArea: dynamicCardArea
property alias tableCards: tablePile.cards
property alias dashboard: dashboard
property alias drawPile: drawPile
property alias skillInteraction: skillInteraction
property alias miscStatus: miscStatus
property alias banner: banner
property var selected_targets: []
property string responding_card
property var extra_data: ({})
property var skippedUseEventId: []
property real replayerSpeed
property int replayerElapsed
property int replayerDuration
Image {
source: config.roomBg
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
}
MediaPlayer {
id: bgm
source: config.bgmFile
loops: MediaPlayer.Infinite
onPlaybackStateChanged: {
if (playbackState == MediaPlayer.StoppedState && roomScene.isStarted)
play();
}
audioOutput: AudioOutput {
volume: config.bgmVolume / 100
}
}
onIsStartedChanged: {
if (isStarted) {
Backend.playSound("./audio/system/gamestart");
bgm.play();
canKickOwner = false;
kickOwnerTimer.stop();
} else {
bgm.stop();
}
}
Button {
id: menuButton
anchors.top: parent.top
anchors.topMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
text: luatr("Menu")
onClicked: {
if (menuContainer.visible){
menuContainer.close();
} else {
menuContainer.open();
}
}
Menu {
id: menuContainer
y: menuButton.height - 12
width: parent.width * 1.8
MenuItem {
id: quitButton
text: luatr("Quit")
icon.source: AppPath + "/image/modmaker/back"
onClicked: {
if (config.replaying) {
Backend.controlReplayer("shutdown");
mainStack.pop();
} else if (config.observing) {
ClientInstance.notifyServer("QuitRoom", "[]");
} else {
quitDialog.open();
}
}
}
MenuItem {
id: volumeButton
text: luatr("Audio Settings")
icon.source: AppPath + "/image/button/tileicon/configure"
onClicked: {
volumeDialog.open();
}
}
Menu {
title: luatr("Overview")
icon.source: AppPath + "/image/button/tileicon/rule_summary"
icon.width: 24
icon.height: 24
icon.color: palette.windowText
MenuItem {
id: generalButton
text: luatr("Generals Overview")
icon.source: AppPath + "/image/button/tileicon/general_overview"
onClicked: {
overviewLoader.overviewType = "Generals";
overviewDialog.open();
overviewLoader.item.loadPackages();
}
}
MenuItem {
id: cardslButton
text: luatr("Cards Overview")
icon.source: AppPath + "/image/button/tileicon/card_overview"
onClicked: {
overviewLoader.overviewType = "Cards";
overviewDialog.open();
overviewLoader.item.loadPackages();
}
}
MenuItem {
id: modesButton
text: luatr("Modes Overview")
icon.source: AppPath + "/image/misc/paper"
onClicked: {
overviewLoader.overviewType = "Modes";
overviewDialog.open();
}
}
}
MenuItem {
id: surrenderButton
enabled: !config.observing && !config.replaying && isStarted
text: luatr("Surrender")
icon.source: AppPath + "/image/misc/surrender"
onClicked: {
const photo = getPhoto(Self.id);
if (isStarted && !(photo.dead && photo.rest <= 0)) {
const surrenderCheck = lcall('CheckSurrenderAvailable', miscStatus.playedTime);
if (!surrenderCheck.length) {
surrenderDialog.informativeText =
luatr('Surrender is disabled in this mode');
} else {
surrenderDialog.informativeText = surrenderCheck
.map(str => `${luatr(str.text)}${str.passed ? '✓' : '✗'}`)
.join('<br>');
}
surrenderDialog.open();
}
}
}
}
}
Button {
text: luatr("Add Robot")
visible: isOwner && !isStarted && !isFull
anchors.centerIn: parent
enabled: config.serverEnableBot
onClicked: {
ClientInstance.notifyServer("AddRobot", "[]");
}
}
Button {
text: luatr("Start Game")
visible: isOwner && !isStarted && isFull
enabled: isAllReady
anchors.centerIn: parent
onClicked: {
ClientInstance.notifyServer("StartGame", "[]");
}
}
Timer {
id: opTimer
interval: 1000
}
Button {
text: isReady ? luatr("Cancel Ready") : luatr("Ready")
visible: !isOwner && !isStarted
enabled: !opTimer.running
anchors.centerIn: parent
onClicked: {
opTimer.start();
ClientInstance.notifyServer("Ready", "");
}
}
Button {
id: kickOwner
anchors.horizontalCenter: parent.horizontalCenter
y: parent.height / 2 + 30
text: "踢出房主"
visible: canKickOwner && !isStarted && isFull && !isOwner
onClicked: {
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.isOwner) {
ClientInstance.notifyServer("KickPlayer", item.id.toString());
}
}
}
}
Timer {
id: kickOwnerTimer
interval: 15000
onTriggered: {
canKickOwner = true;
}
}
onIsAllReadyChanged: {
if (!isAllReady) {
canKickOwner = false;
kickOwnerTimer.stop();
} else {
Backend.playSound("./audio/system/ready");
kickOwnerTimer.start();
}
}
Rectangle {
x: parent.width / 2 + 60
y: parent.height / 2 - 30
color: "snow"
opacity: 0.8
radius: 6
visible: !isStarted
width: 280
height: 280
Flickable {
id: flickableContainer
ScrollBar.vertical: ScrollBar {}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
flickableDirection: Flickable.VerticalFlick
width: parent.width - 10
height: parent.height - 10
contentHeight: roominfo.height
clip: true
Text {
id: roominfo
font.pixelSize: 16
width: parent.width
wrapMode: TextEdit.WordWrap
Component.onCompleted: {
const data = lcall("GetRoomConfig");
let cardpack = lcall("GetAllCardPack");
cardpack = cardpack.filter(p => !data.disabledPack.includes(p));
text = luatr("GameMode") + luatr(data.gameMode) + "<br />"
+ luatr("LuckCardNum") + "<b>" + data.luckTime + "</b><br />"
+ luatr("ResponseTime") + "<b>" + config.roomTimeout + "</b><br />"
+ luatr("GeneralBoxNum") + "<b>" + data.generalNum + "</b>"
+ (data.enableFreeAssign ? "<br />" + luatr("IncludeFreeAssign")
: "")
+ (data.enableDeputy ? " " + luatr("IncludeDeputy") : "")
+ '<br />' + luatr('CardPackages') + cardpack.map(e => {
let ret = luatr(e);
// TODO: 这种东西最好还是变量名规范化= =
if (ret.search(/特殊牌|衍生牌/) === -1) {
ret = "<b>" + ret + "</b>";
}
return ret;
}).join('');
}
}
}
}
states: [
State { name: "notactive" },
State { name: "active" }
]
state: "notactive"
transitions: [
Transition {
from: "*"; to: "notactive"
ScriptAction {
script: {
skillInteraction.sourceComponent = undefined;
promptText = "";
okCancel.visible = false;
okButton.enabled = false;
cancelButton.enabled = false;
endPhaseButton.visible = false;
progress.visible = false;
extra_data = {};
dashboard.disableAllCards();
dashboard.disableSkills();
dashboard.pending_skill = "";
// dashboard.retractAllPiles();
for (let i = 0; i < photoModel.count; i++) {
const item = photos.itemAt(i);
item.state = "normal";
item.selected = false;
// item.selectable = false;
}
if (popupBox.item != null) {
popupBox.item.finished();
}
lcall("FinishRequestUI");
applyChange({});
}
}
},
Transition {
from: "notactive"; to: "active"
ScriptAction {
script: {
const dat = Backend.getRequestData();
const total = dat["timeout"] * 1000;
const now = Date.now(); // ms
const elapsed = now - (dat["timestamp"] ?? now);
if (total <= elapsed) {
roomScene.state = "notactive";
}
progressAnim.from = (1 - elapsed / total) * 100.0;
progressAnim.duration = total - elapsed;
progress.visible = true;
}
}
}
]
/* Layout:
* +---------------------+
* | Photos, get more |
* | in arrangePhotos() |
* | tablePile |
* | progress,prompt,btn |
* +---------------------+
* | dashboard |
* +---------------------+
*/
ListModel {
id: photoModel
}
Item {
id: roomArea
width: roomScene.width
height: roomScene.height - dashboard.height + 20
Repeater {
id: photos
model: photoModel
Photo {
playerid: model.id
general: model.general
avatar: model.avatar
deputyGeneral: model.deputyGeneral
screenName: model.screenName
role: model.role
role_shown: model.role_shown
kingdom: model.kingdom
netstate: model.netstate
maxHp: model.maxHp
hp: model.hp
shield: model.shield
seatNumber: model.seatNumber
dead: model.dead
dying: model.dying
faceup: model.faceup
chained: model.chained
drank: model.drank
rest: model.rest
isOwner: model.isOwner
ready: model.ready
surrendered: model.surrendered
sealedSlots: JSON.parse(model.sealedSlots)
onSelectedChanged: {
if ( state === "candidate" ) lcall("UpdateRequestUI", "Photo", playerid, "click", { selected } );
}
Component.onCompleted: {
if (index === 0) dashboard.self = this;
}
}
}
onWidthChanged: Logic.arrangePhotos();
onHeightChanged: Logic.arrangePhotos();
InvisibleCardArea {
id: drawPile
x: parent.width / 2
y: roomScene.height / 2
}
TablePile {
id: tablePile
width: parent.width * 0.6
height: 150
x: parent.width * 0.2
y: parent.height * 0.6 + 10
}
}
Item {
id: dashboardBtn
width: childrenRect.width
height: childrenRect.height
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.left: parent.left
anchors.leftMargin: 8
ColumnLayout {
MetroButton {
text: luatr("Choose one handcard")
textFont.pixelSize: 28
visible: {
if (roomScene.state === "notactive") return false;
if (dashboard.handcardArea.length <= 15) {
return false;
}
const cards = dashboard.handcardArea.cards;
for (const card of cards) {
if (card.selectable) return true;
}
return false;
}
onClicked: roomScene.startCheat("../RoomElement/ChooseHandcard");
}
MetroButton {
id: revertSelectionBtn
text: luatr("Revert Selection")
textFont.pixelSize: 28
enabled: dashboard.pending_skill !== ""
onClicked: //dashboard.revertSelection();
{
lcall("RevertSelection");
}
}
// MetroButton {
// text: luatr("Trust")
// }
MetroButton {
text: luatr("Sort Cards")
textFont.pixelSize: 28
onClicked: {
if (lcall("CanSortHandcards", Self.id)) {
let sortMethods = [];
for (let index = 0; index < sortMenuRepeater.count; index++) {
var tCheckBox = sortMenuRepeater.itemAt(index)
sortMethods.push(tCheckBox.checked)
}
Logic.sortHandcards(sortMethods);
}
}
onRightClicked: {
if (sortMenu.visible) {
sortMenu.close();
} else {
sortMenu.open();
}
}
Menu {
id: sortMenu
x: parent.width
y: -25
width: parent.width * 2
background: Rectangle {
color: "black"
border.width: 3
border.color: "white"
opacity: 0.8
}
Repeater {
id: sortMenuRepeater
model: ["Sort by Type", "Sort by Number", "Sort by Suit"]
CheckBox {
id: control
text: "<font color='white'>" + luatr(modelData) + "</font>"
checked: modelData === "Sort by Type"
font.pixelSize: 20
indicator: Rectangle {
implicitWidth: 26
implicitHeight: 26
x: control.leftPadding
y: control.height / 2 - height / 2
radius: 3
border.color: "white"
Rectangle {
width: 14
height: 14
x: 6
y: 6
radius: 2
color: control.down ? "#17a81a" : "#21be2b"
visible: control.checked
}
}
}
}
}
}
MetroButton {
text: luatr("Chat")
textFont.pixelSize: 28
onClicked: roomDrawer.open();
}
}
}
Dashboard {
id: dashboard
width: roomScene.width - dashboardBtn.width
anchors.top: roomArea.bottom
anchors.left: dashboardBtn.right
}
Rectangle {
id: replayControls
visible: config.replaying
anchors.bottom: dashboard.top
anchors.bottomMargin: -60
anchors.horizontalCenter: parent.horizontalCenter
width: childrenRect.width + 8
height: childrenRect.height + 8
color: "#88EEEEEE"
radius: 4
RowLayout {
x: 4; y: 4
Text {
font.pixelSize: 20
font.bold: true
text: {
const elapsedMin = Math.floor(replayerElapsed / 60);
const elapsedSec = replayerElapsed % 60;
const totalMin = Math.floor(replayerDuration / 60);
const totalSec = replayerDuration % 60;
return elapsedMin.toString() + ":" + elapsedSec + "/" + totalMin
+ ":" + totalSec;
}
}
Switch {
text: luatr("Show All Cards")
checked: config.replayingShowCards
onCheckedChanged: config.replayingShowCards = checked;
}
Switch {
text: luatr("Speed Resume")
checked: false
onCheckedChanged: Backend.controlReplayer("uniform");
}
Button {
text: luatr("Speed Down")
onClicked: Backend.controlReplayer("slowdown");
}
Text {
font.pixelSize: 20
font.bold: true
text: "x" + replayerSpeed;
}
Button {
text: luatr("Speed Up")
onClicked: Backend.controlReplayer("speedup");
}
Button {
property bool running: true
text: luatr(running ? "Pause" : "Resume")
onClicked: {
running = !running;
Backend.controlReplayer("toggle");
}
}
}
}
Item {
id: controls
anchors.bottom: dashboard.top
anchors.bottomMargin: -60
width: roomScene.width
Text {
id: prompt
visible: progress.visible
anchors.bottom: progress.bottom
z: 1
color: "#F0E5DA"
font.pixelSize: 16
font.family: fontLibian.name
style: Text.Outline
styleColor: "#3D2D1C"
textFormat: TextEdit.RichText
anchors.horizontalCenter: progress.horizontalCenter
}
ProgressBar {
id: progress
width: parent.width * 0.6
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: okCancel.top
anchors.bottomMargin: 4
from: 0.0
to: 100.0
visible: false
background: Rectangle {
implicitWidth: 200
implicitHeight: 12
color: "black"
radius: 6
}
contentItem: Item {
implicitWidth: 196
implicitHeight: 10
Rectangle {
width: progress.visualPosition * parent.width
height: parent.height
radius: 6
gradient: Gradient {
GradientStop { position: 0.0; color: "orange" }
GradientStop { position: 0.3; color: "red" }
GradientStop { position: 0.7; color: "red" }
GradientStop { position: 1.0; color: "orange" }
}
}
}
NumberAnimation on value {
id: progressAnim
running: progress.visible
from: 100.0
to: 0.0
duration: config.roomTimeout * 1000
onFinished: {
roomScene.state = "notactive"
}
}
}
Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.right: okCancel.left
anchors.rightMargin: 20
color: "#88EEEEEE"
radius: 8
visible: {
if (roomScene.state !== "active") {
return false;
}
if (!specialCardSkills) {
return false;
}
if (specialCardSkills.count > 1) {
return true;
}
return (specialCardSkills.model ?? false)
&& specialCardSkills.model[0] !== "_normal_use"
}
width: childrenRect.width
height: childrenRect.height - 20
RowLayout {
y: -10
Repeater {
id: specialCardSkills
RadioButton {
property string orig_text: modelData
text: luatr(modelData)
checked: index === 0
onCheckedChanged: {
lcall("UpdateRequestUI", "SpecialSkills", "1", "click", modelData);
}
}
}
}
}
Loader {
id: skillInteraction
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.right: okCancel.left
anchors.rightMargin: 20
}
Row {
id: okCancel
anchors.bottom: parent.bottom
anchors.horizontalCenter: progress.horizontalCenter
spacing: 20
visible: false
Button {
id: skipNullificationButton
text: luatr("SkipNullification")
visible: !!extra_data.useEventId
&& !skippedUseEventId.find(id => id === extra_data.useEventId)
onClicked: {
skippedUseEventId.push(extra_data.useEventId);
lcall("UpdateRequestUI", "Button", "Cancel");
}
}
Button {
id: okButton
text: luatr("OK")
onClicked: lcall("UpdateRequestUI", "Button", "OK");
}
Button {
id: cancelButton
text: luatr("Cancel")
onClicked: lcall("UpdateRequestUI", "Button", "Cancel");
}
}
Button {
id: endPhaseButton
text: luatr("End")
anchors.bottom: parent.bottom
anchors.bottomMargin: 40
anchors.right: parent.right
anchors.rightMargin: 30
visible: false;
onClicked: lcall("UpdateRequestUI", "Button", "End");
}
}
// manualBox: same as popupBox, but must be closed manually
Loader {
id: manualBox
z: 999
onSourceChanged: {
if (item === null)
return;
item.finished.connect(() => sourceComponent = undefined);
item.widthChanged.connect(() => manualBox.moveToCenter());
item.heightChanged.connect(() => manualBox.moveToCenter());
moveToCenter();
}
onSourceComponentChanged: sourceChanged();
function moveToCenter() {
item.x = Math.round((roomArea.width - item.width) / 2);
item.y = Math.round(roomArea.height * 0.67 - item.height / 2);
}
}
Loader {
id: popupBox
z: 999
onSourceChanged: {
if (item === null)
return;
item.finished.connect(() => {
sourceComponent = undefined;
});
item.widthChanged.connect(() => {
popupBox.moveToCenter();
});
item.heightChanged.connect(() => {
popupBox.moveToCenter();
});
moveToCenter();
}
onSourceComponentChanged: sourceChanged();
function moveToCenter() {
item.x = Math.round((roomArea.width - item.width) / 2);
item.y = Math.round(roomArea.height * 0.67 - item.height / 2);
}
}
Loader {
id: bigAnim
anchors.fill: parent
z: 999
}
function activateSkill(skill_name, selected) {
lcall("UpdateRequestUI", "SkillButton", skill_name, "click", { selected } );
}
Drawer {
id: roomDrawer
width: parent.width * 0.36 / mainWindow.scale
height: parent.height / mainWindow.scale
dim: false
clip: true
dragMargin: 0
scale: mainWindow.scale
transformOrigin: Item.TopLeft
ColumnLayout {
anchors.fill: parent
SwipeView {
Layout.fillWidth: true
Layout.fillHeight: true
interactive: false
currentIndex: drawerBar.currentIndex
Item {
LogEdit {
id: log
anchors.fill: parent
}
}
Item {
visible: !config.replaying
AvatarChatBox {
id: chat
anchors.fill: parent
}
}
}
TabBar {
id: drawerBar
width: roomDrawer.width
TabButton {
width: roomDrawer.width / 2
text: luatr("Log")
}
TabButton {
width: roomDrawer.width / 2
text: luatr("Chat")
}
}
}
}
Popup {
id: cheatDrawer
width: realMainWin.width * 0.60
height: realMainWin.height * 0.8
anchors.centerIn: parent
background: Rectangle {
color: "#CC2E2C27"
radius: 5
border.color: "#A6967A"
border.width: 1
}
Loader {
id: cheatLoader
anchors.centerIn: parent
width: parent.width / mainWindow.scale
height: parent.height / mainWindow.scale
scale: mainWindow.scale
clip: true
onSourceChanged: {
if (item === null)
return;
item.finish.connect(() => {
cheatDrawer.close();
});
}
onSourceComponentChanged: sourceChanged();
}
}
Item {
id: dynamicCardArea
anchors.fill: parent
}
MessageDialog {
id: quitDialog
title: luatr("Quit")
informativeText: luatr("Are you sure to quit?")
buttons: MessageDialog.Ok | MessageDialog.Cancel
onButtonClicked: function (button) {
switch (button) {
case MessageDialog.Ok: {
ClientInstance.notifyServer("QuitRoom", "[]");
break;
}
case MessageDialog.Cancel: {
quitDialog.close();
}
}
}
}
MessageDialog {
id: surrenderDialog
title: luatr("Surrender")
informativeText: ''
buttons: MessageDialog.Ok | MessageDialog.Cancel
onButtonClicked: function (button, role) {
switch (button) {
case MessageDialog.Ok: {
const surrenderCheck =
lcall('CheckSurrenderAvailable', miscStatus.playedTime);
if (surrenderCheck.length &&
!surrenderCheck.find(check => !check.passed)) {
ClientInstance.notifyServer("PushRequest", [
"surrender", true
]);
}
surrenderDialog.close();
break;
}
case MessageDialog.Cancel: {
surrenderDialog.close();
}
}
}
}
Popup {
id: volumeDialog
width: realMainWin.width * 0.5
height: realMainWin.height * 0.5
anchors.centerIn: parent
background: Rectangle {
color: "#EEEEEEEE"
radius: 5
border.color: "#A6967A"
border.width: 1
}
Loader {
anchors.centerIn: parent
width: parent.width / mainWindow.scale
height: parent.height / mainWindow.scale
scale: mainWindow.scale
source: AppPath + "/Fk/LobbyElement/AudioSetting.qml"
}
}
Popup {
id: overviewDialog
width: realMainWin.width * 0.75
height: realMainWin.height * 0.75
anchors.centerIn: parent
background: Rectangle {
color: "#EEEEEEEE"
radius: 5
border.color: "#A6967A"
border.width: 1
}
Loader {
id: overviewLoader
property string overviewType: "Generals"
anchors.centerIn: parent
width: parent.width / mainWindow.scale
height: parent.height / mainWindow.scale
scale: mainWindow.scale
source: AppPath + "/Fk/Pages/" + overviewType + "Overview.qml"
}
}
GlowText {
anchors.centerIn: dashboard
visible: Logic.getPhoto(Self.id).rest > 0 && !config.observing
text: luatr("Resting, don't leave!")
color: "#DBCC69"
font.family: fontLibian.name
font.pixelSize: 28
glow.color: "#2E200F"
glow.spread: 0.6
}
Rectangle {
anchors.fill: dashboard
visible: config.observing && !config.replaying
color: "transparent"
GlowText {
anchors.centerIn: parent
text: luatr("Observing ...")
color: "#4B83CD"
font.family: fontLi2.name
font.pixelSize: 48
}
}
MiscStatus {
id: miscStatus
anchors.right: menuButton.left
anchors.top: parent.top
anchors.rightMargin: 16
anchors.topMargin: 8
}
PhotoElement.MarkArea {
id: banner
x: 12; y: 12
width: (roomScene.width - 175 * 0.75 * 7) / 4 + 175 - 16
transformOrigin: Item.TopLeft
scale: 0.75
bgColor: "#BB838AEA"
}
Danmaku {
id: danmaku
width: parent.width
}
Shortcut {
sequence: "D"
property bool show_distance: false
onActivated: {
show_distance = !show_distance;
showDistance(show_distance);
}
}
Shortcut {
sequence: "T"
onActivated: {
roomDrawer.open();
}
}
Shortcut {
sequence: "Return"
enabled: okButton.enabled
onActivated: lcall("UpdateRequestUI", "Button", "OK");
}
Shortcut {
sequence: "Space"
enabled: cancelButton.enabled || endPhaseButton.visible;
onActivated: if (cancelButton.enabled) {
lcall("UpdateRequestUI", "Button", "Cancel");
} else {
Logic.replyToServer("");
}
}
Shortcut {
sequence: "Escape"
onActivated: menuContainer.open();
}
Timer {
id: statusSkillTimer
interval: 200
running: isStarted
repeat: true
onTriggered: {
lcall("RefreshStatusSkills");
// 刷大家的明置手牌提示框
for (let i = 0; i < photos.count; i++)
photos.itemAt(i).handcardsChanged();
}
}
function addToChat(pid, raw, msg) {
if (raw.type === 1) return;
const photo = Logic.getPhoto(pid);
if (photo === undefined && config.hideObserverChatter)
return;
msg = msg.replace(/\{emoji([0-9]+)\}/g,
'<img src="../../image/emoji/$1.png" height="24" width="24" />');
raw.msg = raw.msg.replace(/\{emoji([0-9]+)\}/g,
'<img src="../../image/emoji/$1.png" height="24" width="24" />');
if (raw.msg.startsWith("$")) {
if (specialChat(pid, raw, raw.msg.slice(1))) return;
}
chat.append(msg, raw);
if (photo === undefined) {
const user = raw.userName;
const m = raw.msg;
danmaku.sendLog(`${user}: ${m}`);
return;
}
photo.chat(raw.msg);
}
function specialChat(pid, data, msg) {
// skill audio: %s%d[%s]
// death audio: ~%s
// something special: !%s:...
const time = data.time;
const userName = data.userName;
const general = luatr(data.general);
if (msg.startsWith("!")) {
if (config.hidePresents)
return true;
const splited = msg.split(":");
const type = splited[0].slice(1);
switch (type) {
case "Egg":
case "GiantEgg":
case "Shoe":
case "Wine":
case "Flower": {
const fromId = pid;
const toId = parseInt(splited[1]);
const component = Qt.createComponent("../ChatAnim/" + type + ".qml");
//if (component.status !== Component.Ready)
// return false;
const fromItem = Logic.getPhotoOrDashboard(fromId);
const fromPos = mapFromItem(fromItem, fromItem.width / 2,
fromItem.height / 2);
const toItem = Logic.getPhoto(toId);
const toPos = mapFromItem(toItem, toItem.width / 2,
toItem.height / 2);
const egg = component.createObject(roomScene, {
start: fromPos,
end: toPos
});
egg.finished.connect(() => egg.destroy());
egg.running = true;
return true;
}
default:
return false;
}
} else if (msg.startsWith("~")) {
const g = msg.slice(1);
const extension = lcall("GetGeneralData", g).extension;
if (!config.disableMsgAudio)
Backend.playSound("./packages/" + extension + "/audio/death/" + g);
const m = luatr("~" + g);
data.msg = m;
if (general === "")
chat.append(`[${time}] ${userName}: ${m}`, data);
else
chat.append(`[${time}] ${userName}(${general}): ${m}`, data);
const photo = Logic.getPhoto(pid);
if (photo === undefined) {
danmaku.sendLog(`${userName}: ${m}`);
return true;
}
photo.chat(m);
return true;
} else {
const splited = msg.split(":");
if (splited.length < 2) return false;
const skill = splited[0];
const idx = parseInt(splited[1]);
const gene = splited[2];
if (!config.disableMsgAudio)
try {
callbacks["LogEvent"]({
type: "PlaySkillSound",
name: skill,
general: gene,
i: idx,
});
} catch (e) {}
const m = luatr("$" + skill + (gene ? "_" + gene : "")
+ (idx ? idx.toString() : ""));
data.msg = m;
if (general === "")
chat.append(`[${time}] ${userName}: ${m}`, data);
else
chat.append(`[${time}] ${userName}(${general}): ${m}`, data)
const photo = Logic.getPhoto(pid);
if (photo === undefined) {
danmaku.sendLog(`${userName}: ${m}`);
return true;
}
photo.chat(m);
return true;
}
return false;
}
function addToLog(msg) {
log.append({ logText: msg });
}
function sendDanmaku(msg) {
danmaku.sendLog(msg);
chat.append(null, {
msg: msg,
general: "__server", // FIXME: 基于默认读取貂蝉的数据
userName: "",
time: "Server",
});
}
function showDistance(show) {
for (let i = 0; i < photoModel.count; i++) {
const item = photos.itemAt(i);
if (show) {
item.distance = lcall("DistanceTo", Self.id, item.playerid);
} else {
item.distance = -1;
}
}
}
function startCheat(type, data) {
cheatLoader.sourceComponent = Qt.createComponent(`../Cheat/${type}.qml`);
cheatLoader.item.extra_data = data;
cheatDrawer.open();
}
function resetToInit() {
const datalist = [];
for (let i = 0; i < photoModel.count; i++) {
const item = photoModel.get(i);
let gameData;
try {
gameData = lcall("GetPlayerGameData", item.id);
} catch (e) {
console.log(e);
gameData = [0, 0, 0, 0];
}
if (item.id > 0) {
datalist.push({
id: item.id,
avatar: item.avatar,
name: item.screenName,
isOwner: item.isOwner,
ready: item.ready,
gameData: gameData,
});
}
}
mainStack.pop();
lcall("ResetClientLua");
mainStack.push(room);
mainStack.currentItem.loadPlayerData(datalist);
}
function setPrompt(text, iscur) {
promptText = text;
if (iscur) currentPrompt = text;
}
function resetPrompt() {
promptText = currentPrompt;
}
function loadPlayerData(datalist) {
datalist.forEach(d => {
if (d.id === Self.id) {
roomScene.isOwner = d.isOwner;
} else {
lcall("ResetAddPlayer",
[d.id, d.name, d.avatar, d.ready, d.gameData[3]]);
}
lcall("SetPlayerGameData", d.id, d.gameData);
Logic.getPhotoModel(d.id).isOwner = d.isOwner;
});
}
function getPhoto(id) {
return Logic.getPhoto(id);
}
function activate() {
if (state === "active") state = "notactive";
state = "active";
}
function applyChange(uiUpdate) {
const sskilldata = uiUpdate["SpecialSkills"]?.[0]
if (sskilldata) {
specialCardSkills.model = sskilldata?.skills ?? [];
}
dashboard.applyChange(uiUpdate);
const pdatas = uiUpdate["Photo"];
pdatas?.forEach(pdata => {
const photo = Logic.getPhoto(pdata.id);
photo.state = pdata.state;
photo.selectable = pdata.enabled;
photo.selected = pdata.selected;
});
for (let i = 0; i < photoModel.count; i++) {
const item = photos.itemAt(i);
item.targetTip = lcall("GetTargetTip", item.playerid);
}
const buttons = uiUpdate["Button"];
if (buttons) {
okCancel.visible = true;
}
buttons?.forEach(bdata => {
switch (bdata.id) {
case "OK":
okButton.enabled = bdata.enabled;
break;
case "Cancel":
cancelButton.enabled = bdata.enabled;
break;
case "End":
endPhaseButton.enabled = bdata.enabled;
endPhaseButton.visible = bdata.enabled;
break;
}
})
// Interaction最后上桌 太给脸了居然插结
uiUpdate["_delete"]?.forEach(data => {
if (data.type == "Interaction") {
skillInteraction.sourceComponent = undefined;
if (roomScene.popupBox.item)
roomScene.popupBox.item.close();
}
});
uiUpdate["_new"]?.forEach(dat => {
if (dat.type == "Interaction") {
const data = dat.data.spec;
const skill_name = dat.data.skill_name;
switch (data.type) {
case "combo":
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.detailed = data.detailed;
skillInteraction.item.all_choices = data.all_choices;
skillInteraction.item.clicked();
break;
case "spin":
skillInteraction.sourceComponent =
Qt.createComponent("../SkillInteraction/SkillSpin.qml");
skillInteraction.item.skill = skill_name;
skillInteraction.item.from = data.from;
skillInteraction.item.to = data.to;
skillInteraction.item?.clicked();
break;
case "custom":
skillInteraction.sourceComponent =
Qt.createComponent(AppPath + "/" + data.qml_path + ".qml");
skillInteraction.item.skill = skill_name;
skillInteraction.item.extra_data = data;
skillInteraction.item?.clicked();
break;
default:
skillInteraction.sourceComponent = undefined;
break;
}
}
});
}
Component.onCompleted: {
toast.show(luatr("$EnterRoom"));
playerNum = config.roomCapacity;
for (let i = 0; i < playerNum; i++) {
photoModel.append({
id: i ? -1 : Self.id,
index: i, // For animating seat swap
general: i ? "" : Self.avatar,
avatar: i ? "" : Self.avatar,
deputyGeneral: "",
screenName: i ? "" : Self.screenName,
role: "unknown",
role_shown: false,
kingdom: "unknown",
netstate: "online",
maxHp: 0,
hp: 0,
shield: 0,
seatNumber: i + 1,
dead: false,
dying: false,
faceup: true,
chained: false,
drank: 0,
rest: 0,
isOwner: false,
ready: false,
surrendered: false,
sealedSlots: "[]",
});
}
Logic.arrangePhotos();
}
}