神貂蝉 (#138)

控制他人/一控多相关
This commit is contained in:
notify 2023-04-27 14:15:08 +08:00 committed by GitHub
parent b50c94d171
commit fc8be0ad40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 518 additions and 194 deletions

104
docs/dev/shendiaochan.rst Normal file
View File

@ -0,0 +1,104 @@
神貂蝉
======
“本回合改为由你操控。” ——神貂蝉
封装
----
先画好靶子!想一下怎么定义函数才好!
- 你来操控别的角色
- 别的角色重新操控自己
- 你同时控制自己和别人
- 询问无懈
- 老朱然
考虑中:。...
.. code:: lua
ServerPlayer:control(ServerPlayer)
ServerPlayer:observe(ServerPlayer)
两函数差不多了吧。。。
这样一来神貂蝉就是让被控者observe然后自己去control回合结束后被控者开始control。
observe
-------
对机器人:无视。
对人发送observe消息。但这个人依然处于room.players中无论是Lua还是cpp。
只是这个人对应的cpp层面的ServerPlayer进旁观了而已。
control
-------
控制者发送control消息。控制者多了个可以控制的角色仅此而已。
如果已有控制者发送dontcontrol消息不准打架 而这个消息最终还是走到observe消息处理
客户端实现
----------
想好了怎么发消息那消息该怎么处理?
首先dontcontrol 把可控制者-1 没有可控了就进旁观呗
然后是control加一个受控者 要是旁观直接切视角
切视角
------
先给个dashboard向下滑出并淡出消失的动画
Lua 改变 Self
根据服务端的信息填充这个新Self的信息
dashboard手牌清空
dashboard填入新Self的新手牌
dashboard重新出现
根据新Self重新安排所有photo
由于不影响烧条 这个动画效果越快越好
一控多时同时询问
----------------
怎么办呢问题的核心在于分属不同玩家的消息却发送给了同一个人。这个可以通过在req/noti中堆个玩家id的参数搞定。但是然后呢
一个很严重的问题就是目前对所有req的处理函数都是直接操作roomscene的元素的。实际上这些元素对于每个被自己控制的player都要单独有才行。
一个想法就是多创建room页面模拟多开。但这个是做不到的因为很多东西都依赖Self而Self只有一个。所以就得在单页中解决这些问题。解决方案就是把那些跟req有关的分出去几个。
换句话说捏几个元素出来表示各个player的状态可能保存在Lua里面。当qml想要切换视角时从Lua读取信息。
当某个人收到req时候就切视角到他那里。所有人烧条打开。
或者也可以让req处理函数返回一个函数切视角时执行之就行了。
也就是说 Room中元素还是那几个 只是随着视角改变他们的值也变 而改变视角会调用函数
其实可以考虑调用从Lua传来的cmdType和jsonData然后就直接开函数了更舒适。
延迟执行callback
-----------------
考虑在notifyUI的时候如果当前视角对应的玩家id和这个消息的id不同那么就暂存到一个消息队列中等到切视角时候就都拿出来。如果某时候就把队列清空。
主要还是只过滤request吧。doNotify不需要过滤换个视角也知道发生啥了。
也没必要维护队列。就一个单独的值保存最近的command和data即可。
当视角发生切换时如果这个人有cmd和data就执行相应的cb。
当向服务器发回答复后cmd和data就没有了。
notify时只对看着这名角色主视角的玩家发送。

View File

@ -114,7 +114,7 @@ function Client:appendLog(msg)
return ""
end
local p = self:getPlayerById(pid)
local str <const> = '<font color="%s"><b>%s</b></font>'
local str = '<font color="%s"><b>%s</b></font>'
if p.general == "anjiang" and (p.deputyGeneral == "anjiang"
or not p.deputyGeneral) then
local ret = Fk:translate("seat#" .. p.seat)
@ -123,7 +123,7 @@ function Client:appendLog(msg)
local ret = p.general
ret = Fk:translate(ret)
if p.deputyGeneral then
if p.deputyGeneral and p.deputyGeneral ~= "" then
ret = ret .. "/" .. Fk:translate(p.deputyGeneral)
end
ret = string.format(str, color, ret)
@ -672,6 +672,12 @@ fk.client_callback["Heartbeat"] = function()
ClientInstance.client:notifyServer("Heartbeat", "")
end
fk.client_callback["ChangeSelf"] = function(jsonData)
local data = json.decode(jsonData)
ClientInstance:getPlayerById(data.id).player_cards[Player.Hand] = data.handcards
ClientInstance:notifyUI("ChangeSelf", data.id)
end
-- Create ClientInstance (used by Lua)
ClientInstance = Client:new()
dofile "lua/client/client_util.lua"

View File

@ -324,6 +324,7 @@ function ActiveTargetFilter(skill_name, to_select, selected, selected_cards)
local card = skill:viewAs(selected_cards)
if card then
ret = card.skill:targetFilter(to_select, selected, selected_cards)
ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), card)
end
end
end
@ -453,4 +454,16 @@ function SetInteractionDataOfSkill(skill_name, data)
end
end
function ChangeSelf(pid)
local c = ClientInstance
c.client:changeSelf(pid) -- for qml
Self = c:getPlayerById(pid)
end
function GetPlayerHandcards(pid)
local c = ClientInstance
local p = c:getPlayerById(pid)
return json.encode(p.player_cards[Player.Hand])
end
dofile "lua/client/i18n/init.lua"

View File

@ -163,6 +163,7 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["seat#6"] = "六号位",
["seat#7"] = "七号位",
["seat#8"] = "八号位",
["@ControledBy"] = "控制者",
["Trust"] = "托管",
["Sort Cards"] = "牌序",

View File

@ -44,10 +44,34 @@ function GameEvent:findParent(eventType)
end
function GameEvent:clear()
for _, f in ipairs(self.extra_clear_funcs) do
if type(f) == "function" then f(self) end
local clear_co = coroutine.create(function()
for _, f in ipairs(self.extra_clear_funcs) do
if type(f) == "function" then f(self) end
end
self:clear_func()
end)
while true do
local err, yield_result, extra_yield_result = coroutine.resume(clear_co)
if err == false then
-- handle error, then break
if not string.find(yield_result, "__manuallyBreak") then
fk.qCritical(yield_result)
print(debug.traceback(co))
end
coroutine.close(clear_co)
break
end
if yield_result == "__handleRequest" then
-- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result)
else
coroutine.close(clear_co)
break
end
end
self:clear_func()
end
local function breakEvent(self, extra_yield_result)

View File

@ -34,9 +34,11 @@ end
function GameLogic:run()
-- default logic
local room = self.room
table.shuffle(self.room.players)
self:assignRoles()
self.room:adjustSeats()
room:doBroadcastNotify("StartGame", "")
room:adjustSeats()
self:chooseGenerals()

View File

@ -22,6 +22,8 @@
---@field public status_skills Skill[] @ 这个房间中含有的状态技列表
---@field public settings table @ 房间的额外设置差不多是json对象
---@field public logic GameLogic @ 这个房间使用的游戏逻辑,可能根据游戏模式而变动
---@field public request_queue table<userdata, table>
---@field public request_self table<integer, integer>
local Room = class("Room")
-- load classes used by the game
@ -119,6 +121,8 @@ function Room:initialize(_room)
for class, skills in pairs(Fk.global_status_skill) do
self.status_skills[class] = {table.unpack(skills)}
end
self.request_queue = {}
self.request_self = {}
end
--- 正式在这个房间中开始游戏。
@ -529,10 +533,13 @@ end
---@return string | nil @ 收到的答复如果wait为false的话就返回nil
function Room:doRequest(player, command, jsonData, wait)
if wait == nil then wait = true end
self.request_queue = {}
player:doRequest(command, jsonData, self.timeout)
if wait then
return player:waitForReply(self.timeout)
local ret = player:waitForReply(self.timeout)
player.serverplayer:setBusy(false)
return ret
end
end
@ -542,8 +549,9 @@ end
---@param jsonData string @ 请求数据
function Room:doBroadcastRequest(command, players, jsonData)
players = players or self.players
self.request_queue = {}
for _, p in ipairs(players) do
self:doRequest(p, command, jsonData or p.request_data, false)
p:doRequest(command, jsonData or p.request_data)
end
local remainTime = self.timeout
@ -553,6 +561,10 @@ function Room:doBroadcastRequest(command, players, jsonData)
elapsed = os.time() - currentTime
p:waitForReply(remainTime - elapsed)
end
for _, p in ipairs(players) do
p.serverplayer:setBusy(false)
end
end
--- 向多名玩家发出竞争请求。
@ -566,9 +578,12 @@ end
---@return ServerPlayer | nil @ 在这次竞争请求中获胜的角色可能是nil
function Room:doRaceRequest(command, players, jsonData)
players = players or self.players
players = table.simpleClone(players)
local player_len = #players
-- self:notifyMoveFocus(players, command)
self.request_queue = {}
for _, p in ipairs(players) do
self:doRequest(p, command, jsonData or p.request_data, false)
p:doRequest(command, jsonData or p.request_data)
end
local remainTime = self.timeout
@ -576,10 +591,11 @@ function Room:doRaceRequest(command, players, jsonData)
local elapsed = 0
local winner
local canceled_players = {}
local ret
while true do
elapsed = os.time() - currentTime
if remainTime - elapsed <= 0 then
return nil
break
end
for _, p in ipairs(players) do
p:waitForReply(0)
@ -589,21 +605,29 @@ function Room:doRaceRequest(command, players, jsonData)
end
if p.reply_cancel then
table.removeOne(players, p)
table.insertIfNeed(canceled_players, p)
end
end
if winner then
self:doBroadcastNotify("CancelRequest", "")
return winner
ret = winner
break
end
if #players == #canceled_players then
return nil
if player_len == #canceled_players then
break
end
coroutine.yield("__handleRequest", remainTime - elapsed)
fk.QThread_msleep(10)
end
for _, p in ipairs(self.players) do
p.serverplayer:setBusy(false)
end
return ret
end
-- main loop for the request handling coroutine
@ -691,6 +715,25 @@ function Room:requestLoop(rest_time)
removeObserver(id)
elseif command == "prelight" then
self:getPlayerById(id):prelightSkill(reqlist[3], reqlist[4] == "true")
elseif command == "changeself" then
local toId = tonumber(reqlist[3])
local from = self:getPlayerById(id)
local to = self:getPlayerById(toId)
local from_sp = from._splayer
-- 注意发来信息的玩家的主视角可能已经不是自己了
-- 先换成正确的玩家
from = table.find(self.players, function(p)
return table.contains(p._observers, from_sp)
end)
-- 切换视角
table.removeOne(from._observers, from_sp)
table.insert(to._observers, from_sp)
from_sp:doNotify("ChangeSelf", json.encode {
id = toId,
handcards = to:getCardIds(Player.Hand),
})
end
elseif rest_time > 10 then
-- let current thread sleep 10ms
@ -2399,6 +2442,33 @@ function Room:adjustSeats()
self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle))
end
---@param a ServerPlayer
---@param b ServerPlayer
function Room:swapSeat(a, b)
local ai, bi
local players = self.players
for i, v in ipairs(self.players) do
if v == a then ai = i end
if v == b then bi = i end
end
players[ai] = b
players[bi] = a
a.seat, b.seat = b.seat, a.seat
local player_circle = {}
for _, v in ipairs(players) do
table.insert(player_circle, v.id)
end
for i = 1, #players - 1 do
players[i].next = players[i + 1]
end
players[#players].next = players[1]
self:doBroadcastNotify("ArrangeSeats", json.encode(player_circle))
end
--- 洗牌。
function Room:shuffleDrawPile()
if #self.draw_pile + #self.discard_pile == 0 then

View File

@ -20,7 +20,9 @@ local ServerPlayer = Player:subclass("ServerPlayer")
function ServerPlayer:initialize(_self)
Player.initialize(self)
self.serverplayer = _self
self.serverplayer = _self -- 控制者
self._splayer = _self -- 真正在玩的玩家
self._observers = { _self } -- "旁观"中的玩家,然而不包括真正的旁观者
self.id = _self:getId()
self.state = _self:getStateString()
self.room = nil
@ -39,7 +41,10 @@ end
---@param command string
---@param jsonData string
function ServerPlayer:doNotify(command, jsonData)
self.serverplayer:doNotify(command, jsonData)
for _, p in ipairs(self._observers) do
p:doNotify(command, jsonData)
end
local room = self.room
for _, t in ipairs(room.observers) do
local id, p = table.unpack(t)
@ -56,10 +61,24 @@ end
---@param jsonData string
---@param timeout integer
function ServerPlayer:doRequest(command, jsonData, timeout)
timeout = timeout or self.room.timeout
self.client_reply = ""
self.reply_ready = false
self.reply_cancel = false
if self.serverplayer:busy() then
self.room.request_queue[self.serverplayer] = self.room.request_queue[self.serverplayer] or {}
table.insert(self.room.request_queue[self.serverplayer], { self.id, command, jsonData, timeout })
return
end
self.room.request_self[self.serverplayer:getId()] = self.id
if not table.contains(self._observers, self.serverplayer) then
self.serverplayer:doNotify("StartChangeSelf", tostring(self.id))
end
timeout = timeout or self.room.timeout
self.serverplayer:setBusy(true)
self.ai_data = {
command = command,
jsonData = jsonData,
@ -115,13 +134,30 @@ end
---@return string @ JSON data
function ServerPlayer:waitForReply(timeout)
local result = _waitForReply(self, timeout)
local sid = self.serverplayer:getId()
local id = self.id
if self.room.request_self[sid] ~= id then
result = ""
end
self.request_data = ""
self.client_reply = result
if result == "__cancel" then
result = ""
self.reply_cancel = true
self.serverplayer:setBusy(false)
end
if result ~= "" then self.reply_ready = true end
if result ~= "" then
self.reply_ready = true
self.serverplayer:setBusy(false)
end
local queue = self.room.request_queue[self.serverplayer]
if queue and #queue > 0 and not self.serverplayer:busy() then
local i, c, j, t = table.unpack(table.remove(queue, 1))
self.room:getPlayerById(i):doRequest(c, j, t)
end
return result
end
@ -648,4 +684,15 @@ function ServerPlayer:revealBySkillName(skill_name)
end
end
-- 神貂蝉
function ServerPlayer:control(p)
if self == p then
self.room:setPlayerMark(p, "@ControledBy", 0)
else
self.room:setPlayerMark(p, "@ControledBy", "seat#" .. self.seat)
end
p.serverplayer = self._splayer
end
return ServerPlayer

View File

@ -78,7 +78,11 @@ local test_active = fk.CreateActiveSkill{
on_use = function(self, room, effect)
--room:doSuperLightBox("packages/test/qml/Test.qml")
local from = room:getPlayerById(effect.from)
print(self.interaction.data)
--print(self.interaction.data)
local to = room:getPlayerById(effect.tos[1])
-- room:swapSeat(from, to)
from:control(to)
-- from:pindian({to})
-- local result = room:askForCustomDialog(from, "simayi", "packages/test/qml/TestDialog.qml", "Hello, world. FROM LUA")
-- print(result)
@ -92,7 +96,7 @@ local test_active = fk.CreateActiveSkill{
-- from.kingdom = "wei"
-- room:broadcastProperty(from, "kingdom")
-- p(cards)
room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true)
-- room:useVirtualCard("slash", nil, from, room:getOtherPlayers(from), self.name, true)
end,
}
local test_vs = fk.CreateViewAsSkill{

View File

@ -36,21 +36,21 @@ QtObject {
function loadConf() {
conf = JSON.parse(Backend.loadConf());
winX = conf.winX || 100;
winY = conf.winY || 100;
winWidth = conf.winWidth || 960;
winHeight = conf.winHeight || 540;
lastLoginServer = conf.lastLoginServer || "127.0.0.1";
savedPassword = conf.savedPassword || {};
lobbyBg = conf.lobbyBg || AppPath + "/image/background";
roomBg = conf.roomBg || AppPath + "/image/gamebg";
bgmFile = conf.bgmFile || AppPath + "/audio/system/bgm.mp3";
language = conf.language || "zh_CN";
disabledPack = conf.disabledPack || [ "test_p_0" ];
preferedMode = conf.preferedMode || "aaa_role_mode";
preferedPlayerNum = conf.preferedPlayerNum || 2;
preferredGeneralNum = conf.preferredGeneralNum || 3;
ladyImg = conf.ladyImg || AppPath + "/image/lady";
winX = conf.winX ?? 100;
winY = conf.winY ?? 100;
winWidth = conf.winWidth ?? 960;
winHeight = conf.winHeight ?? 540;
lastLoginServer = conf.lastLoginServer ?? "127.0.0.1";
savedPassword = conf.savedPassword ?? {};
lobbyBg = conf.lobbyBg ?? AppPath + "/image/background";
roomBg = conf.roomBg ?? AppPath + "/image/gamebg";
bgmFile = conf.bgmFile ?? AppPath + "/audio/system/bgm.mp3";
language = conf.language ?? "zh_CN";
disabledPack = conf.disabledPack ?? [ "test_p_0" ];
preferedMode = conf.preferedMode ?? "aaa_role_mode";
preferedPlayerNum = conf.preferedPlayerNum ?? 2;
preferredGeneralNum = conf.preferredGeneralNum ?? 3;
ladyImg = conf.ladyImg ?? AppPath + "/image/lady";
}
function saveConf() {

View File

@ -14,7 +14,7 @@ Item {
id: roomScene
property int playerNum: 0
property var dashboardModel
// property var dashboardModel
property bool isOwner: false
property bool isStarted: false
@ -29,6 +29,7 @@ Item {
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
@ -77,7 +78,7 @@ Item {
}
Button {
text: "add robot"
visible: dashboardModel.isOwner && !isStarted
visible: isOwner && !isStarted
anchors.centerIn: parent
onClicked: {
ClientInstance.notifyServer("AddRobot", "[]");
@ -103,6 +104,8 @@ Item {
endPhaseButton.visible = false;
respond_play = false;
extra_data = {};
mainWindow.pending_message = [];
mainWindow.is_pending = false;
if (dashboard.pending_skill !== "")
dashboard.stopPending();
@ -209,6 +212,10 @@ Item {
onSelectedChanged: {
Logic.updateSelectedTargets(playerid, selected);
}
Component.onCompleted: {
if (index === 0) dashboard.self = this;
}
}
}
@ -255,28 +262,6 @@ Item {
anchors.top: roomArea.bottom
anchors.left: dashboardBtn.right
self.playerid: dashboardModel.id
self.general: dashboardModel.general
self.screenName: dashboardModel.screenName
self.deputyGeneral: dashboardModel.deputyGeneral
self.role: dashboardModel.role
self.kingdom: dashboardModel.kingdom
self.netstate: dashboardModel.netstate
self.maxHp: dashboardModel.maxHp
self.shield: dashboardModel.shield
self.hp: dashboardModel.hp
self.seatNumber: dashboardModel.seatNumber
self.dead: dashboardModel.dead
self.dying: dashboardModel.dying
self.faceup: dashboardModel.faceup
self.chained: dashboardModel.chained
self.drank: dashboardModel.drank
self.isOwner: dashboardModel.isOwner
onSelectedChanged: {
Logic.updateSelectedTargets(self.playerid, selected);
}
onCardSelected: function(card) {
Logic.enableTargets(card);
@ -809,37 +794,15 @@ Item {
Component.onCompleted: {
toast.show(Backend.translate("$EnterRoom"));
dashboardModel = {
id: Self.id,
general: Self.avatar,
deputyGeneral: "",
screenName: Self.screenName,
role: "unknown",
kingdom: "unknown",
netstate: "online",
maxHp: 0,
hp: 0,
shield: 0,
seatNumber: 1,
dead: false,
dying: false,
faceup: true,
chained: false,
drank: 0,
isOwner: false
}
playerNum = config.roomCapacity;
let i;
for (i = 1; i < playerNum; i++) {
for (let i = 0; i < playerNum; i++) {
photoModel.append({
id: -1,
index: i - 1, // For animating seat swap
general: "",
id: i ? -1 : Self.id,
index: i, // For animating seat swap
general: i ? "" : Self.avatar,
deputyGeneral: "",
screenName: "",
screenName: i ? "" : Self.screenName,
role: "unknown",
kingdom: "unknown",
netstate: "online",

View File

@ -7,13 +7,8 @@ import Qt5Compat.GraphicalEffects
RowLayout {
id: root
property alias self: selfPhoto
property var self
property alias handcardArea: handcardAreaItem
property alias equipArea: selfPhoto.equipArea
property alias delayedTrickArea: selfPhoto.delayedTrickArea
property alias specialArea: selfPhoto.specialArea
property bool selected: selfPhoto.selected
property string pending_skill: ""
property var pending_card
@ -48,10 +43,11 @@ RowLayout {
id: skillPanel
}
Photo {
id: selfPhoto
Item {
width: 175
height: 233
Layout.rightMargin: -175 / 8 + (roomArea.width - 175 * 0.75 * 7) / 8
handcards: handcardAreaItem.length
// handcards: handcardAreaItem.length
}
Connections {
@ -59,6 +55,9 @@ RowLayout {
function onCardSelected(cardId, selected) {
dashboard.selectCard(cardId, selected);
}
function onLengthChanged() {
self.handcards = handcardAreaItem.length;
}
}
function disableAllCards() {
@ -75,11 +74,11 @@ RowLayout {
return;
let component = Qt.createComponent("CardItem.qml");
let parentPos = roomScene.mapFromItem(selfPhoto, 0, 0);
let parentPos = roomScene.mapFromItem(self, 0, 0);
expanded_piles[pile] = [];
if (pile === "_equip") {
let equips = selfPhoto.equipArea.getAllCards();
let equips = self.equipArea.getAllCards();
equips.forEach(data => {
data.x = parentPos.x;
data.y = parentPos.y;
@ -90,7 +89,7 @@ RowLayout {
})
handcardAreaItem.updateCardPosition();
} else {
let ids = JSON.parse(Backend.callLuaFunction("GetPile", [selfPhoto.playerid, pile]));
let ids = JSON.parse(Backend.callLuaFunction("GetPile", [self.playerid, pile]));
ids.forEach(id => {
let data = JSON.parse(Backend.callLuaFunction("GetCardData", [id]));
data.x = parentPos.x;
@ -109,11 +108,11 @@ RowLayout {
if (expanded_pile_names.indexOf(pile) === -1)
return;
let parentPos = roomScene.mapFromItem(selfPhoto, 0, 0);
let parentPos = roomScene.mapFromItem(self, 0, 0);
delete expanded_piles[pile];
if (pile === "_equip") {
let equips = selfPhoto.equipArea.getAllCards();
let equips = self.equipArea.getAllCards();
equips.forEach(data => {
let card = handcardAreaItem.remove([data.cid])[0];
card.origX = parentPos.x;
@ -123,7 +122,7 @@ RowLayout {
})
handcardAreaItem.updateCardPosition();
} else {
let ids = JSON.parse(Backend.callLuaFunction("GetPile", [selfPhoto.playerid, pile]));
let ids = JSON.parse(Backend.callLuaFunction("GetPile", [self.playerid, pile]));
ids.forEach(id => {
let card = handcardAreaItem.remove([id])[0];
card.origX = parentPos.x;
@ -166,7 +165,7 @@ RowLayout {
ids.push(cards[i].cid);
}
}
cards = selfPhoto.equipArea.getAllCards();
cards = self.equipArea.getAllCards();
cards.forEach(c => {
if (cardValid(c.cid, cname)) {
ids.push(c.cid);
@ -178,7 +177,7 @@ RowLayout {
// Must manually analyze pattern here
let pile_list = cname.split("|")[4];
let pile_data = JSON.parse(Backend.callLuaFunction("GetAllPiles", [selfPhoto.playerid]));
let pile_data = JSON.parse(Backend.callLuaFunction("GetAllPiles", [self.playerid]));
if (pile_list && pile_list !== "." && !(pile_data instanceof Array)) {
pile_list = pile_list.split(",");
for (let pile_name of pile_list) {
@ -254,7 +253,7 @@ RowLayout {
enabled_cards.push(card.cid);
});
let cards = selfPhoto.equipArea.getAllCards();
let cards = self.equipArea.getAllCards();
cards.forEach(c => {
if (JSON.parse(Backend.callLuaFunction(
"ActiveCardFilter",
@ -268,7 +267,7 @@ RowLayout {
})
let pile = Backend.callLuaFunction("GetExpandPileOfSkill", [pending_skill]);
let pile_ids = JSON.parse(Backend.callLuaFunction("GetPile", [selfPhoto.playerid, pile]));
let pile_ids = JSON.parse(Backend.callLuaFunction("GetPile", [self.playerid, pile]));
pile_ids.forEach(cid => {
if (JSON.parse(Backend.callLuaFunction(
"ActiveCardFilter",
@ -370,6 +369,32 @@ RowLayout {
}
function tremble() {
selfPhoto.tremble();
self.tremble();
}
function update() {
unSelectAll();
disableSkills();
let cards = handcardAreaItem.cards;
let toRemove = [];
for (let c of cards) {
toRemove.push(c.cid);
c.origY += 30;
c.origOpacity = 0
c.goBack(true);
c.destroyOnStop();
}
handcardAreaItem.remove(toRemove);
skillPanel.clearSkills();
let skills = JSON.parse(Backend.callLuaFunction("GetPlayerSkills", [Self.id]));
for (let s of skills) {
addSkill(s.name);
}
cards = roomScene.drawPile.remove(JSON.parse(Backend.callLuaFunction("GetPlayerHandcards", [Self.id])));
handcardAreaItem.add(cards);
}
}

View File

@ -20,6 +20,10 @@ Item {
radius: 4
border.color: "white"
border.width: 1
Behavior on height {
NumberAnimation { duration: 300; easing.type: Easing.InOutQuad }
}
}
Repeater {
@ -38,6 +42,14 @@ Item {
textFormat: Text.RichText
}
Behavior on x {
NumberAnimation { duration: 300; easing.type: Easing.InOutQuad }
}
Behavior on y {
NumberAnimation { duration: 300; easing.type: Easing.InOutQuad }
}
TapHandler {
enabled: root.parent.state != "candidate" || !root.parent.selectable
onTapped: {

View File

@ -50,7 +50,7 @@ Flickable {
onPressedChanged: {
if (!pressed) return;
enabled = false;
ClientInstance.notifyServer("PrelightSkill", [
ClientInstance.notifyServer("PushRequest", [
"prelight", orig, (!prelighted).toString()
].join(","));
}
@ -154,4 +154,10 @@ Flickable {
}
}
}
function clearSkills() {
prelight_skills.clear();
active_skills.clear();
not_active_skills.clear();
}
}

View File

@ -17,7 +17,7 @@ function arrangePhotos() {
* +---------------+
* | 6 5 4 3 2 |
* | 7 1 |
* | dashboard |
* | 0 |
* +---------------+
*/
@ -32,6 +32,7 @@ function arrangePhotos() {
let startX = verticalPadding + verticalSpacing;
let padding = photoWidth + verticalSpacing;
let regions = [
{ x: startX + padding * 6, y: roomScene.height - 220 },
{ x: startX + padding * 6, y: roomAreaPadding + horizontalSpacing * 3 },
{ x: startX + padding * 5, y: roomAreaPadding + horizontalSpacing },
{ x: startX + padding * 4, y: roomAreaPadding },
@ -42,24 +43,24 @@ function arrangePhotos() {
];
const regularSeatIndex = [
[4],
[3, 5],
[1, 4, 7],
[1, 3, 5, 7],
[1, 3, 4, 5, 7],
[1, 2, 3, 5, 6, 7],
[1, 2, 3, 4, 5, 6, 7],
[0, 4],
[0, 3, 5],
[0, 1, 4, 7],
[0, 1, 3, 5, 7],
[0, 1, 3, 4, 5, 7],
[0, 1, 2, 3, 5, 6, 7],
[0, 1, 2, 3, 4, 5, 6, 7],
];
let seatIndex = regularSeatIndex[playerNum - 2];
let item, region, i;
for (i = 0; i < playerNum - 1; i++) {
for (i = 0; i < playerNum; i++) {
item = photos.itemAt(i);
if (!item)
continue;
region = regions[seatIndex[photoModel.get(i).index] - 1];
region = regions[seatIndex[photoModel.get(i).index]];
item.x = region.x;
item.y = region.y;
}
@ -106,8 +107,14 @@ function doCancelButton() {
}
function replyToServer(jsonData) {
roomScene.state = "notactive";
ClientInstance.replyToServer("", jsonData);
if (!mainWindow.is_pending) {
roomScene.state = "notactive";
} else {
roomScene.state = "";
let data = mainWindow.fetchMessage();
return mainWindow.handleMessage(data.command, data.jsonData);
}
}
function getPhotoModel(id) {
@ -131,21 +138,9 @@ function getPhoto(id) {
}
function getPhotoOrDashboard(id) {
let photo = getPhoto(id);
if (!photo) {
if (id === Self.id)
return dashboard;
}
return photo;
}
function getPhotoOrSelf(id) {
let photo = getPhoto(id);
if (!photo) {
if (id === Self.id)
return dashboard.self;
}
return photo;
if (id === Self.id)
return dashboard;
return getPhoto(id);
}
function getAreaItem(area, id) {
@ -157,13 +152,13 @@ function getAreaItem(area, id) {
return popupBox.item;
}
let photo = getPhotoOrDashboard(id);
let photo = getPhoto(id);
if (!photo) {
return null;
}
if (area === Card.PlayerHand) {
return photo.handcardArea;
return id === Self.id ? dashboard.handcardArea : photo.handcardArea;
} else if (area === Card.PlayerEquip) {
return photo.equipArea;
} else if (area === Card.PlayerJudge) {
@ -230,11 +225,7 @@ function setEmotion(id, emotion, isCardId) {
} else {
photo = getPhoto(id);
if (!photo) {
if (id === dashboardModel.id) {
photo = dashboard.self;
} else {
return null;
}
return null;
}
}
@ -255,11 +246,7 @@ function setEmotion(id, emotion, isCardId) {
function changeHp(id, delta, losthp) {
let photo = getPhoto(id);
if (!photo) {
if (id === dashboardModel.id) {
photo = dashboard.self;
} else {
return null;
}
return null;
}
if (delta < 0) {
if (!losthp) {
@ -292,6 +279,30 @@ function doIndicate(from, tos) {
line.running = true;
}
function changeSelf(id) {
Backend.callLuaFunction("ChangeSelf", [id]);
// move new selfPhoto to dashboard
let order = new Array(photoModel.count);
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
order[item.seatNumber - 1] = item.id;
if (item.id === Self.id) {
dashboard.self = photos.itemAt(i);
}
}
callbacks["ArrangeSeats"](JSON.stringify(order));
// update dashboard
dashboard.update();
// handle pending messages
if (mainWindow.is_pending) {
let data = mainWindow.fetchMessage();
return mainWindow.handleMessage(data.command, data.jsonData);
}
}
callbacks["AddPlayer"] = function(jsonData) {
// jsonData: int id, string screenName, string avatar
for (let i = 0; i < photoModel.count; i++) {
@ -325,8 +336,8 @@ function enableTargets(card) { // card: int | { skill: string, subcards: int[] }
let i = 0;
let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string";
let all_photos = [dashboard.self];
for (i = 0; i < playerNum - 1; i++) {
let all_photos = [];
for (i = 0; i < playerNum; i++) {
all_photos.push(photos.itemAt(i))
}
selected_targets = [];
@ -383,8 +394,8 @@ function updateSelectedTargets(playerid, selected) {
let i = 0;
let card = dashboard.getSelectedCard();
let candidate = (!isNaN(card) && card !== -1) || typeof(card) === "string";
let all_photos = [dashboard.self]
for (i = 0; i < playerNum - 1; i++) {
let all_photos = [];
for (i = 0; i < playerNum; i++) {
all_photos.push(photos.itemAt(i))
}
@ -451,10 +462,8 @@ callbacks["RoomOwner"] = function(jsonData) {
// jsonData: int uid of the owner
let uid = JSON.parse(jsonData)[0];
if (dashboardModel.id === uid) {
dashboardModel.isOwner = true;
roomScene.dashboardModelChanged();
return;
if (Self.id === uid) {
roomScene.isOwner = true;
}
let model = getPhotoModel(uid);
@ -470,38 +479,35 @@ callbacks["PropertyUpdate"] = function(jsonData) {
let property_name = data[1];
let value = data[2];
if (Self.id === uid) {
dashboardModel[property_name] = value;
roomScene.dashboardModelChanged();
return;
}
let model = getPhotoModel(uid);
if (typeof(model) !== "undefined") {
model[property_name] = value;
}
}
callbacks["ArrangeSeats"] = function(jsonData) {
// jsonData: seat order
let order = JSON.parse(jsonData);
callbacks["StartGame"] = function(jsonData) {
roomScene.isStarted = true;
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
item.seatNumber = order.indexOf(item.id) + 1;
item.general = "";
}
}
dashboardModel.seatNumber = order.indexOf(Self.id) + 1;
dashboardModel.general = "";
roomScene.dashboardModelChanged();
callbacks["ArrangeSeats"] = function(jsonData) {
// jsonData: seat order
let order = JSON.parse(jsonData);
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
item.seatNumber = order.indexOf(item.id) + 1;
}
// make Self to the first of list, then reorder photomodel
let selfIndex = order.indexOf(Self.id);
let after = order.splice(selfIndex);
after.push(...order);
let photoOrder = after.slice(1);
let photoOrder = after;
for (let i = 0; i < photoModel.count; i++) {
let item = photoModel.get(i);
@ -513,7 +519,7 @@ callbacks["ArrangeSeats"] = function(jsonData) {
function cancelAllFocus() {
let item;
for (let i = 0; i < playerNum - 1; i++) {
for (let i = 0; i < playerNum; i++) {
item = photos.itemAt(i);
item.progressBar.visible = false;
item.progressTip = "";
@ -528,7 +534,7 @@ callbacks["MoveFocus"] = function(jsonData) {
let command = data[1];
let item, model;
for (let i = 0; i < playerNum - 1; i++) {
for (let i = 0; i < playerNum; i++) {
model = photoModel.get(i);
if (focuses.indexOf(model.id) != -1) {
item = photos.itemAt(i);
@ -546,14 +552,6 @@ callbacks["MoveFocus"] = function(jsonData) {
}
}
}
if (command === "PlayCard") {
if (focuses.indexOf(Self.id) != -1) {
dashboard.self.playing = true;
} else {
dashboard.self.playing = false;
}
}
}
callbacks["PlayerRunned"] = function(jsonData) {
@ -793,8 +791,8 @@ function processPrompt(prompt) {
let raw = Backend.translate(data[0]);
let src = parseInt(data[1]);
let dest = parseInt(data[2]);
if (raw.match("%src")) raw = raw.replace("%src", Backend.translate(getPhotoOrSelf(src).general));
if (raw.match("%dest")) raw = raw.replace("%dest", Backend.translate(getPhotoOrSelf(dest).general));
if (raw.match("%src")) raw = raw.replace("%src", Backend.translate(getPhoto(src).general));
if (raw.match("%dest")) raw = raw.replace("%dest", Backend.translate(getPhoto(dest).general));
if (raw.match("%arg")) raw = raw.replace("%arg", Backend.translate(data[3]));
if (raw.match("%arg2")) raw = raw.replace("%arg2", Backend.translate(data[4]));
return raw;
@ -878,7 +876,7 @@ callbacks["WaitForNullification"] = function() {
callbacks["SetPlayerMark"] = function(jsonData) {
let data = JSON.parse(jsonData);
let player = getPhotoOrSelf(data[0]);
let player = getPhoto(data[0]);
let mark = data[1];
let value = data[2].toString();
if (value == 0) {
@ -922,11 +920,7 @@ callbacks["Animate"] = function(jsonData) {
let photo = getPhoto(id);
if (!photo) {
if (id === dashboardModel.id) {
photo = dashboard.self;
} else {
return null;
}
return null;
}
let animation = component.createObject(photo, {
@ -974,9 +968,6 @@ callbacks["LogEvent"] = function(jsonData) {
}
case "Death": {
let item = getPhoto(data.to);
if (data.to === dashboardModel.id) {
item = dashboard.self;
}
let extension = JSON.parse(Backend.callLuaFunction("GetGeneralData", [item.general])).extension;
Backend.playSound("./packages/" + extension + "/audio/death/" + item.general);
}
@ -1010,7 +1001,7 @@ callbacks["TakeAG"] = (j) => {
let data = JSON.parse(j);
let pid = data[0];
let cid = data[1];
let item = getPhotoOrSelf(pid);
let item = getPhoto(pid);
let general = Backend.translate(item.general);
// the item should be AG box
@ -1036,7 +1027,7 @@ callbacks["UpdateLimitSkill"] = (j) => {
let skill = data[1];
let time = data[2];
let photo = getPhotoOrSelf(id);
let photo = getPhoto(id);
if (photo) {
photo.updateLimitSkill(skill, time);
}
@ -1051,3 +1042,21 @@ callbacks["UpdateRoundNum"] = (j) => {
let data = parseInt(j);
roomScene.miscStatus.roundNum = data;
}
// 神貂蝉
callbacks["StartChangeSelf"] = (j) => {
let id = parseInt(j);
ClientInstance.notifyServer("PushRequest", "changeself," + j);
}
callbacks["ChangeSelf"] = (j) => {
let data = parseInt(j);
if (Self.id === data) {
let msg = mainWindow.fetchMessage();
if (!msg) return;
mainWindow.handleMessage(msg.command, msg.jsonData);
return;
}
changeSelf(data);
}

View File

@ -24,6 +24,8 @@ Item {
? 540 : 960 * parent.height / parent.width
scale: parent.width / width
anchors.centerIn: parent
property bool is_pending: false
property var pending_message: []
Config {
id: config
@ -183,14 +185,33 @@ Item {
errDialog.open();
return;
}
let cb = callbacks[command]
if (typeof(cb) === "function") {
cb(jsonData);
if (mainWindow.is_pending && command !== "ChangeSelf") {
mainWindow.pending_message.push({ command: command, jsonData: jsonData });
} else {
callbacks["ErrorMsg"]("Unknown command " + command + "!");
if (command === "StartChangeSelf") {
mainWindow.is_pending = true;
}
mainWindow.handleMessage(command, jsonData);
}
}
}
function fetchMessage() {
let ret = pending_message.splice(0, 1)[0];
if (pending_message.length === 0) {
is_pending = false;
}
return ret;
}
function handleMessage(command, jsonData) {
let cb = callbacks[command]
if (typeof(cb) === "function") {
cb(jsonData);
} else {
callbacks["ErrorMsg"]("Unknown command " + command + "!");
}
}
}
Shortcut {

View File

@ -12,6 +12,7 @@ ClientPlayer *Self;
Client::Client(QObject *parent) : QObject(parent), callback(0) {
ClientInstance = this;
Self = new ClientPlayer(0, this);
self = Self;
QQmlApplicationEngine *engine = Backend->getEngine();
engine->rootContext()->setContextProperty("ClientInstance", ClientInstance);
engine->rootContext()->setContextProperty("Self", Self);
@ -67,6 +68,12 @@ void Client::removePlayer(int id) {
void Client::clearPlayers() { players.clear(); }
void Client::changeSelf(int id) {
auto p = players[id];
Self = p ? p : self;
Backend->getEngine()->rootContext()->setContextProperty("Self", Self);
}
lua_State *Client::getLuaState() { return L; }
void Client::installAESKey(const QByteArray &key) { router->installAESKey(key); }

View File

@ -27,6 +27,7 @@ public:
ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar);
void removePlayer(int id);
Q_INVOKABLE void clearPlayers();
void changeSelf(int id);
lua_State *getLuaState();
void installAESKey(const QByteArray &key);
@ -37,6 +38,7 @@ signals:
private:
Router *router;
QMap<int, ClientPlayer *> players;
ClientPlayer *self;
lua_State *L;
};

View File

@ -250,7 +250,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
room->addRobot(player);
} else if (command == "Chat") {
room->chat(player, jsonData);
} else if (command == "PrelightSkill") {
} else if (command == "PushRequest") {
room->pushRequest(QString("%1,").arg(player->getId()) + jsonData);
}
}

View File

@ -15,6 +15,7 @@ ServerPlayer::ServerPlayer(Room *room) {
connect(this, &ServerPlayer::kicked, this, &ServerPlayer::kick);
alive = true;
m_busy = false;
}
ServerPlayer::~ServerPlayer() {

View File

@ -38,6 +38,8 @@ public:
volatile bool alive; // For heartbeat
void kick();
bool busy() const { return m_busy; }
void setBusy(bool busy) { m_busy = busy; }
signals:
void disconnected();
void kicked();
@ -47,6 +49,7 @@ private:
Router *router;
Server *server;
Room *room; // Room that player is in, maybe lobby
bool m_busy;
QString requestCommand;
QString requestData;

View File

@ -26,6 +26,7 @@ public:
ClientPlayer *addPlayer(int id, const QString &name, const QString &avatar);
void removePlayer(int id);
void changeSelf(int id);
};
extern Client *ClientInstance;

View File

@ -118,4 +118,7 @@ public:
void doNotify(const QString &command, const QString &json_data);
void prepareForRequest(const QString &command, const QString &data);
bool busy() const;
void setBusy(bool busy);
};