Interaction (#61)

* AG (WIP)

* fixbug: setemotion in windows

* room:askForCardsChosen

* fixbug: askForCardChosen
This commit is contained in:
notify 2023-03-01 21:41:16 +08:00 committed by GitHub
parent 3f077a6d69
commit 3e78466947
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 350 additions and 19 deletions

View File

@ -281,6 +281,27 @@ fk.client_callback["AskForCardChosen"] = function(jsonData)
ClientInstance:notifyUI("AskForCardChosen", json.encode(ui_data))
end
fk.client_callback["AskForCardsChosen"] = function(jsonData)
-- jsonData: [ int target_id, int min, int max, string flag, int reason ]
local data = json.decode(jsonData)
local id, min, max, flag, reason = data[1], data[2], data[3], data[4], data[5]
local target = ClientInstance:getPlayerById(id)
local hand = target.player_cards[Player.Hand]
local equip = target.player_cards[Player.Equip]
local judge = target.player_cards[Player.Judge]
if not string.find(flag, "h") then
hand = {}
end
if not string.find(flag, "e") then
equip = {}
end
if not string.find(flag, "j") then
judge = {}
end
local ui_data = {hand, equip, judge, min, max, reason}
ClientInstance:notifyUI("AskForCardsChosen", json.encode(ui_data))
end
--- separated moves to many moves(one card per move)
---@param moves CardsMoveStruct[]
local function separateMoves(moves)

View File

@ -236,7 +236,7 @@ function Card.static:getIdList(c)
-- array
local ret = {}
for _, c2 in ipairs(c) do
table.insertTable(ret, Card:getIdList(c))
table.insertTable(ret, Card:getIdList(c2))
end
return ret
end

View File

@ -142,12 +142,12 @@ function table.random(tab, n)
if #tab == 0 then return nil end
local tmp = {table.unpack(tab)}
local ret = {}
while n > 0 do
while n > 0 and #tmp > 0 do
local i = math.random(1, #tmp)
table.insert(ret, table.remove(tmp, i))
n = n - 1
end
return #ret == 1 and ret[1] or ret
return n == 1 and ret[1] or ret
end
---@param delimiter string

View File

@ -842,6 +842,7 @@ end
---@param target ServerPlayer
---@param flag string @ "hej", h for handcard, e for equip, j for judge
---@param reason string
---@return integer
function Room:askForCardChosen(chooser, target, flag, reason)
local command = "AskForCardChosen"
self:notifyMoveFocus(chooser, command)
@ -849,12 +850,6 @@ function Room:askForCardChosen(chooser, target, flag, reason)
local result = self:doRequest(chooser, command, json.encode(data))
if result == "" then
result = -1
else
result = tonumber(result)
end
if result == -1 then
local areas = {}
if string.find(flag, "h") then table.insert(areas, Player.Hand) end
if string.find(flag, "e") then table.insert(areas, Player.Equip) end
@ -862,11 +857,55 @@ function Room:askForCardChosen(chooser, target, flag, reason)
local handcards = target:getCardIds(areas)
if #handcards == 0 then return end
result = handcards[math.random(1, #handcards)]
else
result = tonumber(result)
end
if result == -1 then
local handcards = target:getCardIds(Player.Hand)
if #handcards == 0 then return end
result = table.random(handcards)
end
return result
end
---@param chooser ServerPlayer
---@param target ServerPlayer
---@param min integer
---@param max integer
---@param flag string @ "hej", h for handcard, e for equip, j for judge
---@param reason string
---@return integer[]
function Room:askForCardsChosen(chooser, target, min, max, flag, reason)
if min == 1 and max == 1 then
return { self:askForCardChosen(chooser, target, flag, reason) }
end
local command = "AskForCardsChosen"
self:notifyMoveFocus(chooser, command)
local data = {target.id, min, max, flag, reason}
local result = self:doRequest(chooser, command, json.encode(data))
local ret
if result ~= "" then
ret = json.decode(result)
else
local areas = {}
if string.find(flag, "h") then table.insert(areas, Player.Hand) end
if string.find(flag, "e") then table.insert(areas, Player.Equip) end
if string.find(flag, "j") then table.insert(areas, Player.Judge) end
local handcards = target:getCardIds(areas)
if #handcards == 0 then return {} end
ret = table.random(handcards, math.min(min, #handcards))
end
local new_ret = table.filter(ret, function(id) return id ~= -1 end)
table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), #ret - #new_ret))
return new_ret
end
---@param player ServerPlayer
---@param choices string[]
---@param skill_name string
@ -1056,6 +1095,51 @@ function Room:askForNullification(players, card_name, pattern, prompt, cancelabl
return nil
end
-- AG(a.k.a. Amazing Grace) functions
-- Popup a box that contains many cards, then ask player to choose one
---@param player ServerPlayer
---@param id_list integer[] | Card[]
---@param cancelable boolean
---@param reason string
---@return integer
function Room:askForAG(player, id_list, cancelable, reason)
id_list = Card:getIdList(id_list)
if #id_list == 1 and not cancelable then
return id_list[1]
end
local command = "AskForAG"
self:notifyMoveFocus(player, reason or command)
local data = { id_list, cancelable, reason }
local ret = self:doRequest(player, command, json.encode(data))
if ret == "" and not cancelable then
ret = table.random(id_list)
end
return tonumber(ret)
end
---@param player ServerPlayer
---@param id_list integer[] | Card[]
---@param disable_ids integer[] | Card[]
function Room:fillAG(player, id_list, disable_ids)
id_list = Card:getIdList(id_list)
-- disable_ids = Card:getIdList(disable_ids)
player:doNotify("FillAG", json.encode{ id_list, disable_ids })
end
---@param player ServerPlayer
---@param id integer
function Room:takeAG(taker, id, notify_list)
self:doBroadcastNotify("TakeAG", json.encode{ taker.id, id }, notify_list)
end
---@param player ServerPlayer
function Room:closeAG(player)
if player then player:doNotify("CloseAG", "")
else self:doBroadcastNotify("CloseAG", "") end
end
-- Show a qml dialog and return qml's ClientInstance.replyToServer
-- Do anything you like through this function

View File

@ -60,11 +60,20 @@ local test_active = fk.CreateActiveSkill{
can_use = function(self, player)
return true
end,
target_filter = function() return true end,
on_use = function(self, room, effect)
--room:doSuperLightBox("packages/test/qml/Test.qml")
local from = room:getPlayerById(effect.from)
local result = room:askForCustomDialog(from, "simayi", "packages/test/qml/TestDialog.qml", "Hello, world. FROM LUA")
print(result)
-- local result = room:askForCustomDialog(from, "simayi", "packages/test/qml/TestDialog.qml", "Hello, world. FROM LUA")
-- print(result)
-- room:fillAG(from, { 1, 43, 77 })
-- local id = room:askForAG(from, { 1, 43, 77 })
-- room:takeAG(from, id)
-- room:delay(2000)
-- room:closeAG(from)
local cards = room:askForCardsChosen(from, room:getPlayerById(effect.tos[1]), 2, 3, "hej", "")
p(cards)
end,
}
local test2 = General(extension, "mouxusheng", "wu", 4, 4, General.Female)

View File

@ -18,6 +18,7 @@ Item {
property bool isStarted: false
property alias popupBox: popupBox
property alias manualBox: manualBox
property alias bigAnim: bigAnim
property alias promptText: prompt.text
property alias okCancel: okCancel
@ -388,6 +389,26 @@ Item {
}
}
// manualBox: same as popupBox, but must be closed manually
Loader {
id: manualBox
z: 999
onSourceChanged: {
if (item === null)
return;
item.finished.connect(() => source = "");
item.widthChanged.connect(() => manualBox.moveToCenter());
item.heightChanged.connect(() => manualBox.moveToCenter());
moveToCenter();
}
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

View File

@ -0,0 +1,64 @@
import QtQuick
GraphicsBox {
property int spacing: 5
property string currentPlayerName: ""
property bool interactive: false
id: root
title.text: Backend.translate("Please choose cards")
width: cards.count * 100 + spacing * (cards.count - 1) + 25
height: 180
ListModel {
id: cards
}
Row {
x: 20
y: 35
spacing: root.spacing
Repeater {
model: cards
CardItem {
cid: model.cid
name: model.name
suit: model.suit
number: model.number
autoBack: false
selectable: model.selectable
footnote: model.footnote
footnoteVisible: true
onClicked: {
if (root.interactive && selectable) {
root.interactive = false;
roomScene.state = "notactive";
ClientInstance.replyToServer("", cid.toString());
}
}
}
}
}
function addIds(ids) {
ids.forEach((id) => {
let data = Backend.callLuaFunction("GetCardData", [id]);
data = JSON.parse(data);
data.selectable = true;
data.footnote = "";
cards.append(data);
});
}
function takeAG(g, cid) {
for (let i = 0; i < cards.count; i++) {
let item = cards.get(i);
if (item.cid !== cid) continue;
item.footnote = g;
item.selectable = false;
break;
}
}
}

View File

@ -1,16 +1,21 @@
import QtQuick
import QtQuick.Layouts
import ".."
GraphicsBox {
signal cardSelected(int cid)
id: root
title.text: Backend.translate("$ChooseCard")
//@to-do: Adjust the UI design in case there are more than 7 cards
// TODO: Adjust the UI design in case there are more than 7 cards
width: 70 + Math.min(7, Math.max(1, handcards.count, equips.count, delayedTricks.count)) * 100
height: 50 + (handcards.count > 0 ? 150 : 0) + (equips.count > 0 ? 150 : 0) + (delayedTricks.count > 0 ? 150 : 0)
signal cardSelected(int cid)
signal cardsSelected(var ids)
property bool multiChoose: false
property int min: 0
property int max: 1
property var selected_ids: []
ListModel {
id: handcards
}
@ -66,7 +71,23 @@ GraphicsBox {
autoBack: false
known: false
selectable: true
onClicked: root.cardSelected(cid);
onClicked: {
if (!root.multiChoose) {
root.cardSelected(cid);
}
}
onSelectedChanged: {
if (selected) {
origY = origY - 20;
root.selected_ids.push(cid);
} else {
origY = origY + 20;
root.selected_ids.splice(root.selected_ids.indexOf(cid), 1);
}
origX = x;
goBack(true);
root.selected_ids = root.selected_ids;
}
}
}
}
@ -107,7 +128,23 @@ GraphicsBox {
number: model.number
autoBack: false
selectable: true
onClicked: root.cardSelected(cid);
onClicked: {
if (!root.multiChoose) {
root.cardSelected(cid);
}
}
onSelectedChanged: {
if (selected) {
origY = origY - 20;
root.selected_ids.push(cid);
} else {
origY = origY + 20;
root.selected_ids.splice(root.selected_ids.indexOf(cid));
}
origX = x;
goBack(true);
root.selected_ids = root.selected_ids;
}
}
}
}
@ -148,11 +185,34 @@ GraphicsBox {
number: model.number
autoBack: false
selectable: true
onClicked: root.cardSelected(cid);
onClicked: {
if (!root.multiChoose) {
root.cardSelected(cid);
}
}
onSelectedChanged: {
if (selected) {
origY = origY - 20;
root.selected_ids.push(cid);
} else {
origY = origY + 20;
root.selected_ids.splice(root.selected_ids.indexOf(cid));
}
origX = x;
goBack(true);
root.selected_ids = root.selected_ids;
}
}
}
}
}
MetroButton {
text: Backend.translate("OK")
visible: root.multiChoose
enabled: root.selected_ids.length <= root.max && root.selected_ids.length >= root.min
onClicked: root.cardsSelected(root.selected_ids)
}
}
onCardSelected: finished();

View File

@ -228,7 +228,7 @@ function setEmotion(id, emotion, isCardId) {
}
}
let animation = component.createObject(photo, {source: path});
let animation = component.createObject(photo, {source: (OS === "Win" ? "file:///" : "") + path});
animation.anchors.centerIn = photo;
if (isCardId) {
animation.started.connect(() => photo.busy = true);
@ -669,6 +669,52 @@ callbacks["AskForCardChosen"] = function(jsonData) {
});
}
callbacks["AskForCardsChosen"] = function(jsonData) {
// jsonData: [ int[] handcards, int[] equips, int[] delayedtricks,
// int min, int max, string reason ]
let data = JSON.parse(jsonData);
let handcard_ids = data[0];
let equip_ids = data[1];
let delayedTrick_ids = data[2];
let min = data[3];
let max = data[4];
let reason = data[5];
let handcards = [];
let equips = [];
let delayedTricks = [];
handcard_ids.forEach(id => {
let card_data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
handcards.push(card_data);
});
equip_ids.forEach(id => {
let card_data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
equips.push(card_data);
});
delayedTrick_ids.forEach(id => {
let card_data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
delayedTricks.push(card_data);
});
roomScene.promptText = Backend.translate("#AskForChooseCard")
.arg(Backend.translate(reason));
roomScene.state = "replying";
roomScene.popupBox.source = "RoomElement/PlayerCardBox.qml";
let box = roomScene.popupBox.item;
box.multiChoose = true;
box.min = min;
box.max = max;
box.addHandcards(handcards);
box.addEquips(equips);
box.addDelayedTricks(delayedTricks);
roomScene.popupBox.moveToCenter();
box.cardsSelected.connect((ids) => {
replyToServer(JSON.stringify(ids));
});
}
callbacks["MoveCards"] = function(jsonData) {
// jsonData: merged moves
let moves = JSON.parse(jsonData);
@ -910,6 +956,32 @@ callbacks["GameOver"] = function(jsonData) {
roomScene.isStarted = false;
}
callbacks["FillAG"] = (j) => {
let data = JSON.parse(j);
let ids = data[0];
roomScene.manualBox.source = "RoomElement/AG.qml";
roomScene.manualBox.item.addIds(ids);
}
callbacks["AskForAG"] = (j) => {
roomScene.state = "replying";
roomScene.manualBox.item.interactive = true;
}
callbacks["TakeAG"] = (j) => {
if (!roomScene.manualBox.item) return;
let data = JSON.parse(j);
let pid = data[0];
let cid = data[1];
let item = getPhotoOrSelf(pid);
let general = Backend.translate(item.general);
// the item should be AG box
roomScene.manualBox.item.takeAG(general, cid);
}
callbacks["CloseAG"] = () => roomScene.manualBox.item.close();
callbacks["CustomDialog"] = (j) => {
let data = JSON.parse(j);
let path = data.path;