diff --git a/Fk/PhotoElement/MarkArea.qml b/Fk/PhotoElement/MarkArea.qml index 47ccd764..ebf98e67 100644 --- a/Fk/PhotoElement/MarkArea.qml +++ b/Fk/PhotoElement/MarkArea.qml @@ -54,6 +54,13 @@ Item { enabled: root.parent.state != "candidate" || !root.parent.selectable onTapped: { const params = { name: mark_name }; + + if (mark_name.startsWith('@&')) { + params.cardNames = mark_extra.split(','); + roomScene.startCheat("../RoomElement/ViewGeneralPile", params); + return; + } + if (mark_name.startsWith('@$')) { params.cardNames = mark_extra.split(','); } else { @@ -88,7 +95,7 @@ Item { } let special_value = ''; - if (mark.startsWith('@$')) { + if (mark.startsWith('@$') || mark.startsWith('@&')) { special_value += data.length; data = data.join(','); } else { diff --git a/Fk/RoomElement/Dashboard.qml b/Fk/RoomElement/Dashboard.qml index 748f63b8..5729b96b 100644 --- a/Fk/RoomElement/Dashboard.qml +++ b/Fk/RoomElement/Dashboard.qml @@ -160,9 +160,7 @@ RowLayout { } const pile_data = JSON.parse(Backend.callLuaFunction("GetAllPiles", [self.playerid])); - for (let name in pile_data) { - if (name.endsWith("&")) expandPile(name); - } + extractWoodenOx(); if (cname) { const ids = []; @@ -288,6 +286,15 @@ RowLayout { return raw; } + function extractWoodenOx() { + const pile_data = JSON.parse(Backend.callLuaFunction("GetAllPiles", [self.playerid])); + if (!roomScene.autoPending) { // 先屏蔽AskForUseActiveSkill再说,这下只剩使用打出以及出牌阶段了 + for (let name in pile_data) { + if (name.endsWith("&")) expandPile(name); + } + } + } + function updatePending() { roomScene.resetPrompt(); if (pending_skill === "") return; @@ -358,6 +365,8 @@ RowLayout { pending_skill = skill_name; pendings = []; handcardAreaItem.unselectAll(); + retractAllPiles(); + for (let i = 0; i < skillButtons.count; i++) { const item = skillButtons.itemAt(i); item.enabled = item.pressed; @@ -378,6 +387,9 @@ RowLayout { retractAllPiles(); + if (roomScene.state == "playing") + extractWoodenOx(); + pendings = []; handcardAreaItem.adjustCards(); handcardAreaItem.unselectAll(); diff --git a/Fk/RoomElement/Photo.qml b/Fk/RoomElement/Photo.qml index f9a27683..64de7f40 100644 --- a/Fk/RoomElement/Photo.qml +++ b/Fk/RoomElement/Photo.qml @@ -418,6 +418,22 @@ Item { anchors.bottomMargin: 4 style: Text.Outline } + + TapHandler { + enabled: (root.state != "candidate" || !root.selectable) && root.playerid !== Self.id + onTapped: { + const params = { name: "hand_card" }; + let data = JSON.parse(Backend.callLuaFunction("GetPlayerHandcards", [root.playerid])); + data = data.filter((e) => e !== -1); + if (data.length === 0) + return; + + params.ids = data; + + // Just for using room's right drawer + roomScene.startCheat("../RoomElement/ViewPile", params); + } + } } TapHandler { diff --git a/Fk/RoomElement/ViewGeneralPile.qml b/Fk/RoomElement/ViewGeneralPile.qml new file mode 100644 index 00000000..aead17bd --- /dev/null +++ b/Fk/RoomElement/ViewGeneralPile.qml @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Qt5Compat.GraphicalEffects + +Item { + id: root + anchors.fill: parent + property var extra_data: ({}) + signal finish() + + Rectangle { + anchors.fill: parent + color: "black" + + GlowText { + id: pileName + text: Backend.translate(extra_data.name) + width: parent.width + anchors.topMargin: 10 + horizontalAlignment: Text.AlignHCenter + font.family: fontLibian.name + color: "#E4D5A0" + font.pixelSize: 30 + font.weight: Font.Medium + glow.color: "black" + glow.spread: 0.3 + glow.radius: 5 + } + + LinearGradient { + anchors.fill: pileName + source: pileName + gradient: Gradient { + GradientStop { + position: 0 + color: "#FEF7C2" + } + + GradientStop { + position: 0.5 + color: "#D2AD4A" + } + + GradientStop { + position: 1 + color: "#BE9878" + } + } + } + + Flickable { + id: flickableContainer + ScrollBar.vertical: ScrollBar {} + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 40 + flickableDirection: Flickable.VerticalFlick + width: parent.width - 20 + height: parent.height - 40 + contentWidth: cardsList.width + contentHeight: cardsList.height + clip: true + + ColumnLayout { + id: cardsList + + GridLayout { + columns: Math.floor(flickableContainer.width / 100) + + Repeater { + model: extra_data.ids || extra_data.cardNames + + GeneralCardItem { + id: cardItem + // width: (flickableContainer.width - 15) / 4 + // height: cardItem.width * 1.4 + autoBack: false + name: modelData + } + } + } + } + } + } +} diff --git a/lua/client/client.lua b/lua/client/client.lua index 594b30c7..39a4523b 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -125,7 +125,7 @@ function Client:moveCards(moves) pcardMax = self:getPlayerById(move.to):getMaxCards(), id = move.to, }) - if (move.to ~= Self.id and move.toArea == Card.PlayerHand) or table.contains(ids, -1) then + if (not Self:isBuddy(self:getPlayerById(move.to)) and move.toArea == Card.PlayerHand) or table.contains(ids, -1) then ids = table.map(ids, function() return -1 end) end self:getPlayerById(move.to):addCards(move.toArea, ids, move.specialName) @@ -940,6 +940,23 @@ fk.client_callback["PrintCard"] = function(j) Fk:_addPrintedCard(cd) end +fk.client_callback["AddBuddy"] = function(j) + local c = ClientInstance + local data = json.decode(j) + local id, hand = table.unpack(data) + local to = c:getPlayerById(id) + Self:addBuddy(to) + to.player_cards[Player.Hand] = hand +end + +fk.client_callback["RmBuddy"] = function(j) + local c = ClientInstance + local id = tonumber(j) + local to = c:getPlayerById(id) + Self:removeBuddy(to) + to.player_cards[Player.Hand] = table.map(to.player_cards, function() return -1 end) +end + -- Create ClientInstance (used by Lua) ClientInstance = Client:new() dofile "lua/client/client_util.lua" diff --git a/lua/core/player.lua b/lua/core/player.lua index ebde5eee..fefc3300 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -34,6 +34,7 @@ ---@field public cardUsedHistory table @ 用牌次数历史记录 ---@field public skillUsedHistory table @ 发动技能次数的历史记录 ---@field public fixedDistance table @ 与其他玩家的固定距离列表 +---@field public buddy_list integer[] @ 队友列表,或者说自己可以观看别人手牌的那些玩家的列表 local Player = class("Player") ---@alias Phase integer @@ -62,7 +63,7 @@ Player.HistoryGame = 4 --- 构造函数。总之这不是随便调用的函数 function Player:initialize() - self.id = 114514 + self.id = 0 self.hp = 0 self.maxHp = 0 self.kingdom = "qun" @@ -95,6 +96,7 @@ function Player:initialize() self.cardUsedHistory = {} self.skillUsedHistory = {} self.fixedDistance = {} + self.buddy_list = {} end function Player:__tostring() @@ -163,6 +165,8 @@ end -- 'xxx': invisible mark -- '@mark': mark with extra data (maybe string or number) -- '@@mark': mark without data +-- '@$mark': mark with card_name[] data +-- '@&mark': mark with general_name[] data function Player:addMark(mark, count) count = count or 1 local num = self.mark[mark] @@ -906,4 +910,17 @@ function Player:getQuestSkillState(skillName) return type(questSkillState) == "string" and questSkillState or nil end +function Player:addBuddy(other) + table.insert(self.buddy_list, other.id) +end + +function Player:removeBuddy(other) + table.removeOne(self.buddy_list, other.id) +end + +function Player:isBuddy(other) + local id = type(other) == "number" and other or other.id + return self.id == id or table.contains(self.buddy_list, id) +end + return Player diff --git a/lua/server/event.lua b/lua/server/event.lua index a88deace..e3ee23f7 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -8,11 +8,14 @@ fk.NonTrigger = 1 fk.GamePrepared = 78 fk.GameStart = 2 +fk.BeforeTurnStart = 83 fk.TurnStart = 3 fk.TurnEnd = 73 +fk.AfterTurnEnd = 84 fk.EventPhaseStart = 4 fk.EventPhaseProceeding = 5 fk.EventPhaseEnd = 6 +fk.AfterPhaseEnd = 86 fk.EventPhaseChanging = 7 fk.EventPhaseSkipping = 8 @@ -114,4 +117,9 @@ fk.CardShown = 77 fk.SkillEffect = 81 fk.AfterSkillEffect = 82 +-- 83 = PreTurnStart +-- 84 = AfterTurnEnd +-- 85 = xxx +-- 86 = AfterPhaseEnd + fk.NumOfEvents = 83 diff --git a/lua/server/events/death.lua b/lua/server/events/death.lua index 9a4df4d3..6230bb0b 100644 --- a/lua/server/events/death.lua +++ b/lua/server/events/death.lua @@ -31,6 +31,15 @@ GameEvent.functions[GameEvent.Dying] = function(self) logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct) end +GameEvent.prepare_funcs[GameEvent.Death] = function(self) + local deathStruct = table.unpack(self.data) + local room = self.room + local victim = room:getPlayerById(deathStruct.who) + if victim.dead then + return true + end +end + GameEvent.functions[GameEvent.Death] = function(self) local deathStruct = table.unpack(self.data) local room = self.room diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index 8f50b0ff..1ff12dbb 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -182,9 +182,23 @@ GameEvent.cleaners[GameEvent.Round] = function(self) end end +GameEvent.prepare_funcs[GameEvent.Turn] = function(self) + local room = self.room + local logic = room.logic + local player = room.current + + local ret + + if player.faceup then + ret = logic:trigger(fk.BeforeTurnStart, player) + end + + return ret +end + GameEvent.functions[GameEvent.Turn] = function(self) local room = self.room - room.logic:trigger(fk.TurnStart, room.current) + local logic = room.logic room:sendLog{ type = "$AppendSeparator" } @@ -192,15 +206,35 @@ GameEvent.functions[GameEvent.Turn] = function(self) if not player.faceup then player:turnOver() elseif not player.dead then + logic:trigger(fk.TurnStart, room.current) player:play() + logic:trigger(fk.TurnEnd, room.current) + logic:trigger(fk.AfterTurnEnd, room.current, nil, true) end - - room.logic:trigger(fk.TurnEnd, room.current) end GameEvent.cleaners[GameEvent.Turn] = function(self) local room = self.room + local current = room.current + local logic = room.logic + if self.interrupted then + if current.phase ~= Player.NotActive then + local current_phase = current.phase + current.phase = Player.PhaseNone + logic:trigger(fk.EventPhaseChanging, current, + { from = current_phase, to = Player.NotActive }, true) + current.phase = Player.NotActive + room:broadcastProperty(current, "phase") + logic:trigger(fk.EventPhaseStart, current, nil, true) + end + + current.skipped_phases = {} + + logic:trigger(fk.TurnEnd, current, nil, true) + logic:trigger(fk.AfterTurnEnd, room.current, nil, true) + end + for _, p in ipairs(room.players) do p:setCardUseHistory("", 0, Player.HistoryTurn) p:setSkillUseHistory("", 0, Player.HistoryTurn) @@ -218,24 +252,6 @@ GameEvent.cleaners[GameEvent.Turn] = function(self) end end end - - local current = room.current - local logic = room.logic - if self.interrupted then - current.phase = Player.Finish - logic:trigger(fk.EventPhaseStart, current, nil, true) - logic:trigger(fk.EventPhaseEnd, current, nil, true) - - current.phase = Player.NotActive - room:broadcastProperty(current, "phase") - logic:trigger(fk.EventPhaseChanging, current, - { from = Player.Finish, to = Player.NotActive }, true) - logic:trigger(fk.EventPhaseStart, current, nil, true) - - current.skipped_phases = {} - - logic:trigger(fk.TurnEnd, current, nil, true) - end end GameEvent.functions[GameEvent.Phase] = function(self) @@ -288,6 +304,7 @@ GameEvent.functions[GameEvent.Phase] = function(self) room.logic:trigger(fk.AfterDrawNCards, player, data) end, [Player.Play] = function() + player._play_phase_end = false while not player.dead do room:notifyMoveFocus(player, "PlayCard") local result = room:doRequest(player, "PlayCard", player.id) @@ -297,6 +314,11 @@ GameEvent.functions[GameEvent.Phase] = function(self) if use then room:useCard(use) end + + if player._play_phase_end then + player._play_phase_end = false + break + end end end, [Player.Discard] = function() @@ -330,6 +352,12 @@ GameEvent.cleaners[GameEvent.Phase] = function(self) local room = self.room local player = self.data[1] + --[[ + if self.interrupted then + room.logic:trigger(fk.EventPhaseEnd, player, nil, true) + end + --]] + for _, p in ipairs(room.players) do p:setCardUseHistory("", 0, Player.HistoryPhase) p:setSkillUseHistory("", 0, Player.HistoryPhase) @@ -347,8 +375,4 @@ GameEvent.cleaners[GameEvent.Phase] = function(self) end end end - - if self.interrupted then - room.logic:trigger(fk.EventPhaseEnd, player, nil, true) - end end diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index 1bfd59d2..27f7883d 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -7,6 +7,7 @@ ---@field public event integer @ 该事件对应的EventType ---@field public data any @ 事件的附加数据,视类型而定 ---@field public parent GameEvent @ 事件的父事件(栈中的上一层事件) +---@field public prepare_func fun(self: GameEvent) @ 事件即将开始时执行的函数 ---@field public main_func fun(self: GameEvent) @ 事件的主函数 ---@field public clear_func fun(self: GameEvent) @ 事件结束时执行的函数 ---@field public extra_clear_funcs fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表 @@ -15,6 +16,9 @@ ---@field public interrupted boolean @ 事件是否是因为被强行中断而结束的 local GameEvent = class("GameEvent") +---@type (fun(self: GameEvent): bool)[] +GameEvent.prepare_funcs = {} + ---@type (fun(self: GameEvent): bool)[] GameEvent.functions = {} @@ -37,6 +41,7 @@ function GameEvent:initialize(event, ...) self.room = RoomInstance self.event = event self.data = { ... } + self.prepare_func = GameEvent.prepare_funcs[event] or dummyFunc self.main_func = wrapCoFunc(GameEvent.functions[event], self) or dummyFunc self.clear_func = GameEvent.cleaners[event] or dummyFunc self.extra_clear_funcs = Util.DummyTable @@ -131,7 +136,7 @@ function GameEvent:searchEvents(eventType, n, func, endEvent) local to = endEvent and endEvent.id or self.end_id if to == -1 then to = #logic.all_game_events end n = n or 1 - func = func or function() return true end + func = func or Util.TrueFunc local ret if #events < 6 then @@ -201,6 +206,7 @@ end local function breakEvent(self, extra_yield_result) local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self) + cancelEvent.toId = self.id local notcanceled = cancelEvent:exec() local ret, extra_ret = false, nil if not notcanceled then @@ -218,6 +224,9 @@ function GameEvent:exec() local ret = false -- false or nil means this event is running normally local extra_ret self.parent = logic:getCurrentEvent() + + if self:prepare_func() then return true end + logic.game_event_stack:push(self) logic.current_event_id = logic.current_event_id + 1 diff --git a/lua/server/room.lua b/lua/server/room.lua index a20b7931..6e2c10c2 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -794,7 +794,7 @@ function Room:notifyMoveCards(players, card_moves, forceVisible) local function containArea(area, relevant) --处理区的处理? local areas = relevant - and {Card.PlayerEquip, Card.PlayerJudge, Card.DiscardPile, Card.Processing, Card.PlayerHand} + and {Card.PlayerEquip, Card.PlayerJudge, Card.DiscardPile, Card.Processing, Card.PlayerHand, Card.PlayerSpecial} or {Card.PlayerEquip, Card.PlayerJudge, Card.DiscardPile, Card.Processing} return table.contains(areas, area) end @@ -803,7 +803,7 @@ function Room:notifyMoveCards(players, card_moves, forceVisible) -- if move is relevant to player's hands or equips, it should be open -- cards move from/to equip/judge/discard/processing should be open - if not (move.moveVisible or forceVisible or containArea(move.toArea, move.to == p.id)) then + if not (move.moveVisible or forceVisible or containArea(move.toArea, move.to and p:isBuddy(move.to))) then for _, info in ipairs(move.moveInfo) do if not containArea(info.fromArea, move.from == p.id) then info.cardId = -1 diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index a8745d6f..391c9f98 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -551,6 +551,11 @@ function ServerPlayer:skip(phase) end end +function ServerPlayer:endPlayPhase() + self._play_phase_end = true + -- TODO: send log +end + function ServerPlayer:gainAnExtraTurn(delay) local room = self.room delay = (delay == nil) and true or delay @@ -851,4 +856,22 @@ function ServerPlayer:control(p) p.serverplayer = self._splayer end +-- 22 + +function ServerPlayer:addBuddy(other) + if type(other) == "number" then + other = self.room:getPlayerById(other) + end + Player.addBuddy(self, other) + self:doNotify("AddBuddy", json.encode{ other.id, other.player_cards[Player.Hand] }) +end + +function ServerPlayer:removeBuddy(other) + if type(other) == "number" then + other = self.room:getPlayerById(other) + end + Player.removeBuddy(self, other) + self:doNotify("RmBuddy", tostring(other.id)) +end + return ServerPlayer diff --git a/packages/standard/aux_skills.lua b/packages/standard/aux_skills.lua index e125b1da..76b8a1d5 100644 --- a/packages/standard/aux_skills.lua +++ b/packages/standard/aux_skills.lua @@ -51,7 +51,7 @@ local discardSkill = fk.CreateActiveSkill{ local chooseCardsSkill = fk.CreateActiveSkill{ name = "choose_cards_skill", - expand_pile = function(self) return self.expand_pile end, + -- expand_pile = function(self) return self.expand_pile end, card_filter = function(self, to_select, selected) if #selected >= self.num then return false diff --git a/src/server/server.cpp b/src/server/server.cpp index ea5971a6..3d925f01 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -418,22 +418,25 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, passed = true; } else { obj = result[0].toObject(); - // check if this username already login + + // check ban account int id = obj["id"].toString().toInt(); passed = obj["banned"].toString().toInt() == 0; if (!passed) { error_msg = "you have been banned!"; - } else if (!players.value(id)) { - // check if password is the same - auto salt = obj["salt"].toString().toLatin1(); - decrypted_pw.append(salt); - auto passwordHash = - QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256) - .toHex(); - passed = (passwordHash == obj["password"].toString()); - if (!passed) - error_msg = "username or password error"; - } else { + } + + // check if password is the same + auto salt = obj["salt"].toString().toLatin1(); + decrypted_pw.append(salt); + auto passwordHash = + QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256) + .toHex(); + passed = (passwordHash == obj["password"].toString()); + + if (!passed) { + error_msg = "username or password error"; + } else if (players.value(id)) { auto player = players.value(id); // 顶号机制,如果在线的话就让他变成不在线 if (player->getState() == Player::Online) {