UI commands (#17)

* add cardarea

* move cards

* add util

* delaytrick

* equiparea

* var -> let

* photo loop anim

* setEmotion & damage

* photo status

* doIndicate

* add property to ClientPlayer

* todo: movecards, waiting

* handle moveCards at client

* ui - moveCards

* modify to lua

* move visible

* adjust position of tablepile

Co-authored-by: Notify-ctrl <notify-ctrl@qq.com>
This commit is contained in:
notify 2022-04-14 18:22:00 +08:00 committed by GitHub
parent 09e34cfef6
commit af4924c260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1009 additions and 83 deletions

BIN
image/anim/damage/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
image/anim/damage/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
image/anim/damage/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
image/anim/damage/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
image/anim/damage/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
image/anim/damage/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

View File

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 274 B

View File

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 380 B

View File

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 624 B

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

View File

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

View File

Before

Width:  |  Height:  |  Size: 267 B

After

Width:  |  Height:  |  Size: 267 B

View File

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 406 B

View File

Before

Width:  |  Height:  |  Size: 335 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

View File

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 238 B

View File

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

View File

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

View File

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 500 B

View File

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

View File

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 755 B

View File

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 380 B

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,3 +1,6 @@
---@class Client
---@field client fk.Client
---@field players ClientPlayer[]
Client = class('Client')
-- load client classes
@ -23,6 +26,15 @@ function Client:initialize()
self.players = {} -- ClientPlayer[]
end
---@param id integer
---@return ClientPlayer
function Client:findPlayer(id)
for _, p in ipairs(self.players) do
if p.player:getId() == id then return p end
end
return nil
end
fk.client_callback["Setup"] = function(jsonData)
-- jsonData: [ int id, string screenName, string avatar ]
local data = json.decode(jsonData)
@ -31,6 +43,8 @@ fk.client_callback["Setup"] = function(jsonData)
self:setId(id)
self:setScreenName(name)
self:setAvatar(avatar)
Self = ClientPlayer:new(fk.Self)
table.insert(ClientInstance.players, Self)
end
fk.client_callback["AddPlayer"] = function(jsonData)
@ -47,13 +61,13 @@ fk.client_callback["RemovePlayer"] = function(jsonData)
-- jsonData: [ int id ]
local data = json.decode(jsonData)
local id = data[1]
fk.ClientInstance:removePlayer(id)
for _, p in ipairs(ClientInstance.players) do
if p.player:getId() == id then
table.removeOne(ClientInstance.players, p)
break
end
end
fk.ClientInstance:removePlayer(id)
ClientInstance:notifyUI("RemovePlayer", jsonData)
end
@ -61,20 +75,70 @@ fk.client_callback["ArrangeSeats"] = function(jsonData)
local data = json.decode(jsonData)
local n = #ClientInstance.players
local players = {}
local function findPlayer(id)
for _, p in ipairs(ClientInstance.players) do
if p.player:getId() == id then return p end
end
return nil
end
for i = 1, n do
table.insert(players, findPlayer(data[i]))
table.insert(players, ClientInstance:findPlayer(data[i]))
end
ClientInstance.players = players
ClientInstance:notifyUI("ArrangeSeats", jsonData)
end
fk.client_callback["PropertyUpdate"] = function(jsonData)
-- jsonData: [ int id, string property_name, value ]
local data = json.decode(jsonData)
local id, name, value = data[1], data[2], data[3]
ClientInstance:findPlayer(id)[name] = value
ClientInstance:notifyUI("PropertyUpdate", jsonData)
end
--- separated moves to many moves(one card per move)
---@param moves CardsMoveStruct[]
local function separateMoves(moves)
local ret = {} ---@type CardsMoveInfo[]
for _, move in ipairs(moves) do
for _, info in ipairs(move.moveInfo) do
table.insert(ret, {
ids = {info.cardId},
from = move.from,
to = move.to,
toArea = move.toArea,
fromArea = info.fromArea,
})
end
end
return ret
end
--- merge separated moves (one fromArea per move)
local function mergeMoves(moves)
local ret = {}
local temp = {}
for _, move in ipairs(moves) do
if temp[move.fromArea] == nil then
temp[move.fromArea] = {
ids = {},
from = move.from,
to = move.to,
fromArea = move.fromArea,
toArea = move.toArea
}
end
table.insert(temp[move.fromArea].ids, move.ids[1])
end
for _, v in pairs(temp) do
table.insert(ret, v)
end
return ret
end
fk.client_callback["MoveCards"] = function(jsonData)
-- jsonData: CardsMoveStruct[]
local raw_moves = json.decode(jsonData)
local separated = separateMoves(raw_moves)
local merged = mergeMoves(separated)
ClientInstance:notifyUI("MoveCards", json.encode(merged))
end
-- Create ClientInstance (used by Lua)
ClientInstance = Client:new()

View File

@ -9,3 +9,18 @@ function GetGeneralData(name)
general.kingdom
}
end
function GetCardData(id)
local card = Fk.cards[id]
if card == nil then return json.encode{
cid = id,
known = false
} end
return json.encode{
cid = id,
name = card.name,
number = card.number,
suit = card:getSuitString(),
color = card.color,
}
end

View File

@ -1,7 +1,13 @@
---@class ClientPlayer
---@field player fk.Player
---@field handcardNum integer
---@field known_cards integer[]
local ClientPlayer = Player:subclass("ClientPlayer")
function ClientPlayer:initialize(cp)
self.player = cp
self.handcardNum = 0
self.known_cards = {}
end
return ClientPlayer

View File

@ -74,4 +74,19 @@ function Card:initialize(name, suit, number, color)
self.sub_type = Card.SubTypeNone
end
function Card:getSuitString()
local suit = self.suit
if suit == Card.Spade then
return "spade"
elseif suit == Card.Heart then
return "heart"
elseif suit == Card.Club then
return "club"
elseif suit == Card.Diamond then
return "diamond"
else
return "unknown"
end
end
return Card

View File

@ -17,3 +17,4 @@ end
--debug.sethook(PrintWhenMethodCall, "c")
function p(v) print(inspect(v)) end
function pt(t) for k,v in pairs(t)do print(k,v) end end

View File

@ -49,6 +49,23 @@ function table:removeOne(element)
return false
end
-- Note: only clone key and value, no metatable
-- so dont use for class or instance
---@generic T
---@param self T
---@return T
function table.clone(self)
local ret = {}
for k, v in pairs(self) do
if type(v) == "table" then
ret[k] = table.clone(v)
else
ret[k] = v
end
end
return ret
end
---@class Sql
Sql = {
---@param filename string

View File

@ -219,6 +219,42 @@ function Room:getCardArea(cardId)
return self.card_place[cardId] or Card.Unknown
end
---@param players ServerPlayer[]
---@param card_moves CardsMoveStruct[]
---@param forceVisible boolean
function Room:notifyMoveCards(players, card_moves, forceVisible)
if players == nil or players == {} then players = self.players end
for _, p in ipairs(players) do
local arg = table.clone(card_moves)
for _, move in ipairs(arg) do
-- local to = self:getPlayerById(move.to)
-- forceVisible make the move visible
-- FIXME: move.moveInfo is an array, fix this
move.moveVisible = (forceVisible)
-- if move is relevant to player, it should be open
or ((move.from == p:getId()) or (move.to == p:getId() and move.toArea ~= Card.PlayerSpecial))
-- cards move from/to equip/judge/discard/processing should be open
or move.moveInfo.fromArea == Card.PlayerEquip
or move.toArea == Card.PlayerEquip
or move.moveInfo.fromArea == Card.PlayerJudge
or move.toArea == Card.PlayerJudge
or move.moveInfo.fromArea == Card.DiscardPile
or move.toArea == Card.DiscardPile
or move.moveInfo.fromArea == Card.Processing
or move.toArea == Card.Processing
-- TODO: PlayerSpecial
if not move.moveVisible then
for _, info in ipairs(move.moveInfo) do
info.cardId = -1
end
end
end
p:doNotify("MoveCards", json.encode(arg))
end
end
---@vararg CardsMoveInfo
---@return boolean
function Room:moveCards(...)
@ -266,6 +302,8 @@ function Room:moveCards(...)
return false
end
self:notifyMoveCards(self.players, cardsMoveStructs)
for _, data in ipairs(cardsMoveStructs) do
if #data.moveInfo > 0 then
infoCheck(data)

View File

@ -1,6 +1,6 @@
---@alias CardsMoveInfo {ids: integer[], from: integer|null, to: integer|null, toArea: CardArea, moveReason: CardMoveReason, proposer: integer, skillName: string|null, moveVisible: boolean|null, specialName: string|null, specialVisible: boolean|null }
---@alias MoveInfo {cardId: integer, fromArea: CardArea}
---@alias CardsMoveStruct {moveInfo: {id: integer, fromArea: CardArea}[], from: integer|null, to: integer|null, toArea: CardArea, moveReason: CardMoveReason, proposer: integer|null, skillName: string|null, moveVisible: boolean|null, specialName: string|null, specialVisible: boolean|null, fromSpecialName: string|null }
---@alias CardsMoveStruct {moveInfo: MoveInfo[], from: integer|null, to: integer|null, toArea: CardArea, moveReason: CardMoveReason, proposer: integer|null, skillName: string|null, moveVisible: boolean|null, specialName: string|null, specialVisible: boolean|null, fromSpecialName: string|null }
---@alias HpChangedData { num: integer, reason: string, skillName: string }
---@alias HpLostData { num: integer, skillName: string }

View File

@ -31,6 +31,15 @@ GameRule = fk.CreateTriggerSkill{
-- TODO: need a new function to call the UI
local cardIds = room:getNCards(data.num)
player:addCards(Player.Hand, cardIds)
local move_to_notify = {} ---@type CardsMoveStruct
move_to_notify.toArea = Card.PlayerHand
move_to_notify.to = player:getId()
move_to_notify.moveInfo = {}
for _, id in ipairs(cardIds) do
table.insert(move_to_notify.moveInfo,
{ cardId = id, fromArea = Card.DrawPile })
end
room:notifyMoveCards(room.players, {move_to_notify})
for _, id in ipairs(cardIds) do
room:setCardArea(id, Card.PlayerHand)
@ -62,34 +71,31 @@ GameRule = fk.CreateTriggerSkill{
error("You should never proceed PhaseNone")
end,
[Player.RoundStart] = function()
print("Proceeding RoundStart.")
end,
[Player.Start] = function()
print("Proceeding Start.")
end,
[Player.Judge] = function()
print("Proceeding Judge.")
end,
[Player.Draw] = function()
print("Proceeding Draw.")
room:drawCards(player, 2, self.name)
end,
[Player.Play] = function()
print("Proceeding Play.")
room:askForSkillInvoke(player, "rule")
end,
[Player.Discard] = function()
print("Proceeding Discard.")
local discardNum = #player:getCardIds(Player.Hand) - player:getMaxCards()
if discardNum > 0 then
room:askForDiscard(player, discardNum, discardNum, false, self.name)
end
end,
[Player.Finish] = function()
print("Proceeding Finish.")
end,
[Player.NotActive] = function()
print("Proceeding NotActive.")
end,
})
end,

View File

@ -17,6 +17,22 @@ Item {
property alias promptText: prompt.text
// tmp
Row {
Button{text:"摸1牌"
onClicked:{
Logic.moveCards([{
from:Logic.Player.DrawPile,
to:Logic.Player.PlaceHand,
cards:[1],
}])
}}
Button{text:"弃1牌"
onClicked:{Logic.moveCards([{
to:Logic.Player.DrawPile,
from:Logic.Player.PlaceHand,
cards:[1],
}])}}
}
Button {
text: "quit"
anchors.top: parent.top
@ -145,7 +161,7 @@ Item {
width: parent.width * 0.6
height: 150
x: parent.width * 0.2
y: parent.height * 0.5
y: parent.height * 0.6
}
}

View File

@ -21,8 +21,9 @@ Item {
property string suit: "club"
property int number: 7
property string name: "slash"
property string subtype: ""
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"
property bool footnoteVisible: true
property bool known: true // if false it only show a card back
property bool enabled: true // if false the card will be grey
@ -247,7 +248,7 @@ Item {
function destroyOnStop()
{
root.moveFinished.connect(function(){
destroy();
root.destroy();
});
}
}

View File

@ -6,15 +6,25 @@ RowLayout {
id: root
property alias self: selfPhoto
property alias handcardArea: handcardAreaItem
property alias equipArea: selfPhoto.equipArea
property alias delayedTrickArea: selfPhoto.delayedTrickArea
property alias specialArea: selfPhoto.specialArea
Item {
width: 40
}
HandcardArea {
id: handcardAreaItem
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: 130
Layout.alignment: Qt.AlignVCenter
}
Photo {
id: selfPhoto
handcards: handcardAreaItem.length
}
Item { width: 5 }

View File

@ -1,4 +1,5 @@
import QtQuick 2.15
import "../../util.js" as Utility
Item {
property alias cards: cardArea.cards
@ -68,12 +69,12 @@ Item {
for (i = 0; i < cards.length; i++) {
card = cards[i];
if (card.selected)
card.homeY -= 20;
card.origY -= 20;
}
if (animated) {
for (i = 0; i < cards.length; i++)
roomScene.cardItemGoBack(cards[i], true)
cards[i].goBack(true)
}
}

View File

@ -0,0 +1,101 @@
import QtQuick 2.15
Item {
property point start: Qt.point(0, 0)
property var end: []
property alias running: pointToAnimation.running
property color color: "#96943D"
property real ratio: 0
property int lineWidth: 6
signal finished()
id: root
anchors.fill: parent
Repeater {
model: end
Rectangle {
width: 6
height: Math.sqrt(Math.pow(modelData.x - start.x, 2) + Math.pow(modelData.y - start.y, 2)) * ratio
x: start.x
y: start.y
antialiasing: true
gradient: Gradient {
GradientStop {
position: 0
color: Qt.rgba(255, 255, 255, 0)
}
GradientStop {
position: 1
color: Qt.rgba(200, 200, 200, 0.12)
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 3
height: parent.height
antialiasing: true
gradient: Gradient {
GradientStop {
position: 0
color: Qt.rgba(255, 255, 255, 0)
}
GradientStop {
position: 1
color: Qt.lighter(root.color)
}
}
}
transform: Rotation {
angle: 0
Component.onCompleted: {
var dx = modelData.x - start.x;
var dy = modelData.y - start.y;
if (dx > 0) {
angle = Math.atan2(dy, dx) / Math.PI * 180 - 90;
} else if (dx < 0) {
angle = Math.atan2(dy, dx) / Math.PI * 180 + 270;
} else if (dy < 0) {
angle = 180;
}
}
}
}
}
SequentialAnimation {
id: pointToAnimation
PropertyAnimation {
target: root
property: "ratio"
to: 1
easing.type: Easing.OutCubic
duration: 200
}
PauseAnimation {
duration: 200
}
PropertyAnimation {
target: root
property: "opacity"
to: 0
easing.type: Easing.InQuart
duration: 300
}
onStopped: {
root.visible = false;
root.finished();
}
}
}

View File

@ -57,7 +57,7 @@ Item {
let items = [];
for (let i = 0; i < outputs.length; i++) {
if (_contains(outputs[i])) {
let state = JSON.parse(Sanguosha.getCard4Qml(outputs[i]))
let state = JSON.parse(Backend.getCardData(outputs[i]))
state.x = parentPos.x;
state.y = parentPos.y;
state.opacity = 0;

View File

@ -14,7 +14,7 @@ Item {
property string role: "unknown"
property string kingdom: "qun"
property string netstate: "online"
property int handcards: 0
property alias handcards: handcardAreaItem.length
property int maxHp: 0
property int hp: 0
property int seatNumber: 1
@ -24,10 +24,19 @@ Item {
property bool chained: false
property bool drank: false
property bool isOwner: false
property string status: "normal"
property alias handcardArea: handcardAreaItem
property alias equipArea: equipAreaItem
property alias delayedTrickArea: delayedTrickAreaItem
property alias specialArea: handcardAreaItem
property alias progressBar: progressBar
property alias progressTip: progressTip.text
property bool selectable: false
property bool selected: false
Behavior on x {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
}
@ -36,6 +45,14 @@ Item {
NumberAnimation { duration: 600; easing.type: Easing.InOutQuad }
}
PixmapAnimation {
id: animFrame
source: "selected"
anchors.centerIn: parent
loop: true
scale: 1.1
}
Image {
id: back
source: SkinBank.PHOTO_BACK_DIR + root.kingdom
@ -108,6 +125,18 @@ Item {
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
@ -115,6 +144,13 @@ Item {
x: 29; y: 5
}
EquipArea {
id: equipAreaItem
x: 31
y: 139
}
Image {
id: chain
visible: root.chained
@ -244,6 +280,30 @@ Item {
}
}
PixmapAnimation {
id: animSelectable
source: "selectable"
anchors.centerIn: parent
loop: true
}
InvisibleCardArea {
id: handcardAreaItem
anchors.centerIn: parent
}
DelayedTrickArea {
id: delayedTrickAreaItem
rows: 1
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
}
InvisibleCardArea {
id: defaultArea
anchors.centerIn: parent
}
onGeneralChanged: {
if (!roomScene.isStarted) return;
generalName.text = Backend.translate(general);

View File

@ -1 +1,65 @@
import QtQuick 2.15
import ".."
import "../../skin-bank.js" as SkinBank
Item {
property alias rows: grid.rows
property alias columns: grid.columns
InvisibleCardArea {
id: area
checkExisting: true
}
ListModel {
id: cards
}
Grid {
id: grid
anchors.fill: parent
rows: 100
columns: 100
Repeater {
model: cards
Image {
source: SkinBank.DELAYED_TRICK_DIR + name
}
}
}
function add(inputs)
{
area.add(inputs);
if (inputs instanceof Array) {
cards.append(...inputs);
} else {
cards.append(inputs);
}
}
function remove(outputs)
{
let result = area.remove(outputs);
for (let i = 0; i < result.length; i++) {
let item = result[i];
for (let j = 0; j < cards.count; j++) {
let icon = cards.get(j);
if (icon.cid === item.cid) {
cards.remove(j, 1);
break;
}
}
}
return result;
}
function updateCardPosition(animated)
{
area.updateCardPosition(animated);
}
}

View File

@ -1 +1,120 @@
import QtQuick 2.15
import ".."
import "../../skin-bank.js" as SkinBank
/* Layout of EquipArea:
* | Treasure |
| Weapon |
| Armor |
| +1 | -1 |
+---------------+
*/
Column {
height: 88
width: 138
property int itemHeight: Math.floor(height / 4)
property var items: [treasureItem, weaponItem, armorItem, defensiveHorseItem, offensiveHorseItem]
property var subtypes: ["treasure", "weapon", "armor", "defensive_horse", "offensive_horse"]
property int length: area.length
InvisibleCardArea {
id: area
checkExisting: true
}
EquipItem {
id: treasureItem
width: parent.width
height: itemHeight
opacity: 0
}
EquipItem {
id: weaponItem
width: parent.width
height: itemHeight
opacity: 0
}
EquipItem {
id: armorItem
width: parent.width
height: itemHeight
opacity: 0
}
Row {
width: parent.width
height: itemHeight
Item {
width: Math.ceil(parent.width / 2)
height: itemHeight
EquipItem {
id: defensiveHorseItem
width: parent.width
height: itemHeight
icon: "horse"
opacity: 0
}
}
Item {
width: Math.floor(parent.width / 2)
height: itemHeight
EquipItem {
id: offensiveHorseItem
width: parent.width
height: itemHeight
icon: "horse"
opacity: 0
}
}
}
function add(inputs)
{
area.add(inputs);
let card, item;
if (inputs instanceof Array) {
for (let i = 0; i < inputs.length; i++) {
card = inputs[i];
item = items[subtypes.indexOf(card.subtype)];
item.setCard(card);
item.show();
}
} else {
card = inputs;
item = items[subtypes.indexOf(card.subtype)];
item.setCard(card);
item.show();
}
}
function remove(outputs)
{
let result = area.remove(outputs);
for (let i = 0; i < result.length; i++) {
let card = result[i];
for (let j = 0; j < items.length; j++) {
let item = items[j];
if (item.cid === card.cid) {
item.reset();
item.hide();
}
}
}
return result;
}
function updateCardPosition(animated)
{
area.updateCardPosition(animated);
}
}

View File

@ -0,0 +1,142 @@
import QtQuick 2.15
import ".."
import "../../../util.js" as Utility
import "../../skin-bank.js" as SkinBank
Item {
property int cid: 0
property string name: ""
property string suit: ""
property int number: 0
property string icon: ""
property alias text: textItem.text
id: root
Image {
id: iconItem
anchors.verticalCenter: parent.verticalCenter
x: 3
source: icon ? SkinBank.EQUIP_ICON_DIR + icon : ""
}
Image {
id: suitItem
anchors.right: parent.right
source: suit ? SkinBank.CARD_SUIT_DIR + suit : ""
width: implicitWidth / implicitHeight * height
height: 16
}
GlowText {
id: numberItem
visible: number > 0 && number < 14
text: Utility.convertNumber(number)
color: "white"
font.family: "FZLiBian-S02"
font.pixelSize: 16
glow.color: "black"
glow.spread: 0.75
glow.radius: 2
glow.samples: 4
x: parent.width - 24
y: 1
}
GlowText {
id: textItem
font.family: "FZLiBian-S02"
color: "white"
font.pixelSize: 18
glow.color: "black"
glow.spread: 0.9
glow.radius: 2
glow.samples: 6
anchors.left: iconItem.right
anchors.leftMargin: -8
verticalAlignment: Text.AlignVCenter
}
ParallelAnimation {
id: showAnime
NumberAnimation {
target: root
property: "x"
duration: 200
easing.type: Easing.InOutQuad
from: 10
to: 0
}
NumberAnimation {
target: root
property: "opacity"
duration: 200
easing.type: Easing.InOutQuad
from: 0
to: 1
}
}
ParallelAnimation {
id: hideAnime
NumberAnimation {
target: root
property: "x"
duration: 200
easing.type: Easing.InOutQuad
from: 0
to: 10
}
NumberAnimation {
target: root
property: "opacity"
duration: 200
easing.type: Easing.InOutQuad
from: 1
to: 0
}
}
function reset()
{
cid = 0;
name = "";
suit = "";
number = 0;
text = "";
}
function setCard(card)
{
cid = card.cid;
name = card.name;
suit = card.suit;
number = card.number;
if (card.subtype === "defensive_horse") {
text = "+1";
icon = "horse";
} else if (card.subtype === "offensive_horse") {
text = "-1"
icon = "horse";
} else {
text = name;
icon = name;
}
}
function show()
{
showAnime.start();
}
function hide()
{
hideAnime.start();
}
}

View File

@ -0,0 +1,89 @@
import QtQuick 2.15
import Qt.labs.folderlistmodel 2.15
import "../skin-bank.js" as SkinBank
Item {
property string source: ""
property int currentFrame: 0
property alias interval: timer.interval
property int loadedFrameCount: 0
property bool autoStart: false
property bool loop: false
signal loaded()
signal started()
signal finished()
id: root
width: childrenRect.width
height: childrenRect.height
FolderListModel {
id: fileModel
folder: SkinBank.PIXANIM_DIR + source
nameFilters: ["*.png"]
showDirs: false
}
Repeater {
id: frames
model: fileModel
Image {
source: SkinBank.PIXANIM_DIR + root.source + "/" + index
visible: false
onStatusChanged: {
if (status == Image.Ready) {
loadedFrameCount++;
if (loadedFrameCount == fileModel.count)
root.loaded();
}
}
}
}
onLoaded: {
if (autoStart)
timer.start();
}
Timer {
id: timer
interval: 50
repeat: true
onTriggered: {
if (currentFrame >= fileModel.count) {
frames.itemAt(fileModel.count - 1).visible = false;
if (loop) {
currentFrame = 0;
} else {
timer.stop();
root.finished();
return;
}
}
if (currentFrame > 0)
frames.itemAt(currentFrame - 1).visible = false;
frames.itemAt(currentFrame).visible = true;
currentFrame++;
}
}
function start()
{
if (loadedFrameCount == fileModel.count) {
timer.start();
} else {
root.loaded.connect(function(){
timer.start();
});
}
}
function stop()
{
timer.stop();
}
}

View File

@ -26,9 +26,8 @@ Item {
if (toVanish) {
for (i = 0; i < discardedCards.length; i++) {
card = discardedCards[i];
card.homeOpacity = 0;
// card.goBack(true);
roomScene.cardItemGoBack(card, true)
card.origOpacity = 0;
card.goBack(true);
card.destroyOnStop()
}
@ -101,12 +100,12 @@ Item {
let overflow = false;
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.homeX = i * card.width;
if (card.homeX + card.width >= root.width) {
card.origX = i * card.width;
if (card.origX + card.width >= root.width) {
overflow = true;
break;
}
card.homeY = 0;
card.origY = 0;
}
if (overflow) {
@ -115,8 +114,8 @@ Item {
let spacing = xLimit / (cards.length - 1);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.homeX = i * spacing;
card.homeY = 0;
card.origX = i * spacing;
card.origY = 0;
}
}
@ -124,15 +123,13 @@ Item {
let parentPos = roomScene.mapFromItem(root, 0, 0);
for (i = 0; i < cards.length; i++) {
card = cards[i];
card.homeX += parentPos.x + offsetX;
card.homeY += parentPos.y;
card.origX += parentPos.x + offsetX;
card.origY += parentPos.y;
}
if (animated) {
for (i = 0; i < cards.length; i++)
// cards[i].goBack() // WTF
// console.log(cards[i].homeOpacity)
roomScene.cardItemGoBack(cards[i], true)
cards[i].goBack(true)
}
}
}

View File

@ -1,3 +1,15 @@
var Card = {
Unknown : 0,
PlayerHand : 1,
PlayerEquip : 2,
PlayerJudge : 3,
PlayerSpecial : 4,
Processing : 5,
DrawPile : 6,
DiscardPile : 7,
Void : 8
}
function arrangePhotos() {
/* Layout of photos:
* +---------------+
@ -61,6 +73,134 @@ function replyToServer(jsonData) {
ClientInstance.replyToServer("", jsonData);
}
function getPhotoModel(id) {
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === id) {
return item;
}
}
return undefined;
}
function getPhoto(id) {
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === id) {
return photos.itemAt(i);
}
}
return undefined;
}
function getPhotoOrDashboard(id) {
let photo = getPhoto(id);
if (!photo) {
if (id === Self.id)
return dashboard;
}
return photo;
}
function getAreaItem(area, id) {
if (area === Card.DrawPile) {
return drawPile;
} else if (area === Card.DiscardPile || area === Card.Processing) {
return tablePile;
} else if (area === Card.AG) {
return popupBox.item;
}
let photo = getPhotoOrDashboard(id);
if (!photo) {
return null;
}
if (area === Card.PlayerHand) {
return photo.handcardArea;
} else if (area === Card.PlayerEquip)
return photo.equipArea;
else if (area === Card.PlayerJudge)
return photo.delayedTrickArea;
else if (area === Card.PlayerSpecial)
return photo.specialArea;
return null;
}
function moveCards(moves) {
for (let i = 0; i < moves.length; i++) {
let move = moves[i];
let from = getAreaItem(move.fromArea, move.from);
let to = getAreaItem(move.toArea, move.to);
if (!from || !to || from === to)
continue;
let items = from.remove(move.ids);
if (items.length > 0)
to.add(items);
to.updateCardPosition(true);
}
}
function setEmotion(id, emotion) {
let component = Qt.createComponent("RoomElement/PixmapAnimation.qml");
if (component.status !== Component.Ready)
return;
let photo = getPhoto(id);
if (!photo) {
if (id === dashboardModel.id) {
photo = dashboard.self;
} else {
return null;
}
}
let animation = component.createObject(photo, {source: emotion, anchors: {centerIn: photo}});
animation.finished.connect(() => animation.destroy());
animation.start();
}
function changeHp(id, delta, losthp) {
let photo = getPhoto(id);
if (!photo) {
if (id === dashboardModel.id) {
photo = dashboard.self;
} else {
return null;
}
}
if (delta < 0) {
if (!losthp) {
setEmotion(id, "damage")
photo.tremble()
}
}
}
function doIndicate(from, tos) {
let component = Qt.createComponent("RoomElement/IndicatorLine.qml");
if (component.status !== Component.Ready)
return;
let fromItem = getPhotoOrDashboard(from);
let fromPos = mapFromItem(fromItem, fromItem.width / 2, fromItem.height / 2);
let end = [];
for (let i = 0; i < tos.length; i++) {
if (from === tos[i])
continue;
let toItem = getPhotoOrDashboard(tos[i]);
let toPos = mapFromItem(toItem, toItem.width / 2, toItem.height / 2);
end.push(toPos);
}
let color = "#96943D";
let line = component.createObject(roomScene, {start: fromPos, end: end, color: color});
line.finished.connect(() => line.destroy());
line.running = true;
}
callbacks["AddPlayer"] = function(jsonData) {
// jsonData: int id, string screenName, string avatar
for (let i = 0; i < photoModel.count; i++) {
@ -81,14 +221,11 @@ callbacks["AddPlayer"] = function(jsonData) {
callbacks["RemovePlayer"] = function(jsonData) {
// jsonData: int uid
let uid = JSON.parse(jsonData)[0];
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === uid) {
item.id = -1;
item.screenName = "";
item.general = "";
return;
}
let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") {
model.id = -1;
model.screenName = "";
model.general = "";
}
}
@ -102,12 +239,9 @@ callbacks["RoomOwner"] = function(jsonData) {
return;
}
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === uid) {
item.isOwner = true;
return;
}
let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") {
model.isOwner = true;
}
}
@ -124,12 +258,9 @@ callbacks["PropertyUpdate"] = function(jsonData) {
return;
}
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
if (item.id === uid) {
item[property_name] = value;
return;
}
let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") {
model[property_name] = value;
}
}
@ -193,14 +324,11 @@ callbacks["PlayerRunned"] = function(jsonData) {
let runner = data[0];
let robot = data[1];
let model;
for (let i = 0; i < playerNum - 1; i++) {
model = photoModel.get(i);
if (model.id === runner) {
let model = getPhotoModel(runner);
if (typeof(model) !== "undefined") {
model.id = robot;
}
}
}
callbacks["AskForGeneral"] = function(jsonData) {
// jsonData: string[] Generals
@ -241,3 +369,9 @@ callbacks["AskForChoice"] = function(jsonData) {
replyToServer(choices[box.result]);
});
}
callbacks["MoveCards"] = function(jsonData) {
// jsonData: merged moves
let moves = JSON.parse(jsonData);
moveCards(moves);
}

View File

@ -1,10 +1,14 @@
//var AppPath = "file://home/notify/develop/FreeKill";
//var AppPath = "file:///home/notify/develop/FreeKill";
var PHOTO_BACK_DIR = AppPath + "/image/photo/back/";
var PHOTO_DIR = AppPath + "/image/photo/";
var GENERAL_DIR = AppPath + "/image/generals/";
var STATE_DIR = AppPath + "/image/photo/state/";
var STATUS_DIR = AppPath + "/image/photo/status/";
var ROLE_DIR = AppPath + "/image/photo/role/";
var DEATH_DIR = AppPath + "/image/photo/death/";
var MAGATAMA_DIR = AppPath + "/image/photo/magatama/";
var CARD_DIR = AppPath + "/image/card/";
var CARD_SUIT_DIR = AppPath + "/image/card/suit/";
var DELAYED_TRICK_DIR = AppPath + "/image/card/delayedTrick/";
var EQUIP_ICON_DIR = AppPath + "/image/card/equipIcon/";
var PIXANIM_DIR = AppPath + "/image/anim/"

21
qml/util.js Normal file
View File

@ -0,0 +1,21 @@
.pragma library
function convertNumber(number) {
if (number === 1)
return "A";
if (number >= 2 && number <= 10)
return number;
if (number >= 11 && number <= 13) {
const strs = ["J", "Q", "K"];
return strs[number - 11];
}
return "";
}
Array.prototype.contains = function(element) {
return this.indexOf(element) != -1;
}
Array.prototype.prepend = function() {
this.splice(0, 0, ...arguments);
}

View File

@ -85,20 +85,22 @@ bool QmlBackend::isDir(const QString &file) {
return QFileInfo(file).isDir();
}
#define CALLFUNC int err = lua_pcall(L, 1, 1, 0); \
const char *result = lua_tostring(L, -1); \
if (err) { \
qDebug() << result; \
lua_pop(L, 1); \
return ""; \
} \
lua_pop(L, 1); \
return QString(result); \
QString QmlBackend::translate(const QString &src) {
lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "Translate");
lua_pushstring(L, src.toUtf8().data());
int err = lua_pcall(L, 1, 1, 0);
const char *result = lua_tostring(L, -1);
if (err) {
qDebug() << result;
lua_pop(L, 1);
return "";
}
lua_pop(L, 1);
return QString(result);
CALLFUNC
}
QString QmlBackend::getGeneralData(const QString &general_name) {
@ -106,13 +108,15 @@ QString QmlBackend::getGeneralData(const QString &general_name) {
lua_getglobal(L, "GetGeneralData");
lua_pushstring(L, general_name.toUtf8().data());
int err = lua_pcall(L, 1, 1, 0);
const char *result = lua_tostring(L, -1);
if (err) {
qDebug() << result;
lua_pop(L, 1);
return "";
CALLFUNC
}
lua_pop(L, 1);
return QString(result);
QString QmlBackend::getCardData(int id) {
lua_State *L = ClientInstance->getLuaState();
lua_getglobal(L, "GetCardData");
lua_pushinteger(L, id);
CALLFUNC
}
#undef CALLFUNC

View File

@ -28,6 +28,7 @@ public:
// read data from lua, call lua functions
Q_INVOKABLE QString translate(const QString &src);
Q_INVOKABLE QString getGeneralData(const QString &general_name);
Q_INVOKABLE QString getCardData(int id);
signals:
void notifyUI(const QString &command, const QString &jsonData);