diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 613be5c2..f62e5d76 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -133,6 +133,16 @@ function GetAllPiles(id) return json.encode(ClientInstance:getPlayerById(id).special_cards or {}) end +function GetPlayerSkills(id) + local p = ClientInstance:getPlayerById(id) + return json.encode(table.map(p.player_skills, function(s) + return s.visible and { + name = s.name, + description = Fk:getDescription(s.name), + } or nil + end)) +end + ---@param card string | integer ---@param player integer function CanUseCard(card, player) diff --git a/lua/core/card.lua b/lua/core/card.lua index 5a1bdd90..d984c5ad 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -117,6 +117,10 @@ function Card:initialize(name, suit, number, color) setmetatable(self, mt) end +function Card:__tostring() + return string.format("%s[%s %d]", self.name, self:getSuitString(), self.number) +end + --- 克隆特定卡牌并赋予花色与点数。 --- --- 会将skill/special_skills/equip_skill继承到克隆牌中。 diff --git a/lua/core/player.lua b/lua/core/player.lua index f8593279..4627dcde 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -93,6 +93,10 @@ function Player:initialize() self.fixedDistance = {} end +function Player:__tostring() + return string.format("%s #%d", self.id < 0 and "Bot" or "Player", math.abs(self.id)) +end + --- 设置角色、体力、技能。 ---@param general General @ 角色类型 ---@param setHp boolean @ 是否设置体力 diff --git a/lua/core/skill_type/trigger.lua b/lua/core/skill_type/trigger.lua index 25bc5917..3841f9cd 100644 --- a/lua/core/skill_type/trigger.lua +++ b/lua/core/skill_type/trigger.lua @@ -70,7 +70,7 @@ end ---@return boolean @ returns true if trigger is broken function TriggerSkill:cost(event, target, player, data) local ret = false - if self.frequency == Skill.Compulsory then + if self.frequency == Skill.Compulsory or self.frequency == Skill.Wake then return true end diff --git a/lua/core/util.lua b/lua/core/util.lua index b658767d..1f3e02e8 100644 --- a/lua/core/util.lua +++ b/lua/core/util.lua @@ -131,6 +131,23 @@ function table.simpleClone(self) return ret end +-- similar to table.clone but convert all class/instances to string +function table.cloneWithoutClass(self) + local ret = {} + for k, v in pairs(self) do + if type(v) == "table" then + if v.class or v.super then + ret[k] = tostring(v) + else + ret[k] = table.cloneWithoutClass(v) + end + else + ret[k] = v + end + end + return ret +end + -- if table does not contain the element, we insert it function table:insertIfNeed(element) if not table.contains(self, element) then diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index 44a486cb..96aa9987 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -37,3 +37,25 @@ dofile "lua/server/events/pindian.lua" -- TODO: fix this GameEvent.BreakEvent = 999 +local eventTranslations = { + [GameEvent.ChangeHp] = "GameEvent.ChangeHp", + [GameEvent.Damage] = "GameEvent.Damage", + [GameEvent.LoseHp] = "GameEvent.LoseHp", + [GameEvent.Recover] = "GameEvent.Recover", + [GameEvent.ChangeMaxHp] = "GameEvent.ChangeMaxHp", + [GameEvent.Dying] = "GameEvent.Dying", + [GameEvent.Death] = "GameEvent.Death", + [GameEvent.MoveCards] = "GameEvent.MoveCards", + [GameEvent.UseCard] = "GameEvent.UseCard", + [GameEvent.RespondCard] = "GameEvent.RespondCard", + [GameEvent.SkillEffect] = "GameEvent.SkillEffect", + [GameEvent.Judge] = "GameEvent.Judge", + [GameEvent.DrawInitial] = "GameEvent.DrawInitial", + [GameEvent.Round] = "GameEvent.Round", + [GameEvent.Turn] = "GameEvent.Turn", + [GameEvent.Pindian] = "GameEvent.Pindian", +} + +function GameEvent.static:translate(id) + return eventTranslations[id] +end diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index c77d7fd3..c6794d27 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -2,6 +2,7 @@ ---@field public room Room ---@field public event integer ---@field public data any +---@field public parent GameEvent ---@field public main_func fun(self: GameEvent) ---@field public clear_func fun(self: GameEvent) ---@field public extra_clear_funcs any[] @@ -27,6 +28,15 @@ function GameEvent:initialize(event, ...) self.interrupted = false end +function GameEvent:findParent(eventType) + local e = self.parent + repeat + if e.event == eventType then return e end + e = e.parent + until not e + return nil +end + function GameEvent:clear() for _, f in ipairs(self.extra_clear_funcs) do if type(f) == "function" then f(self) end @@ -39,6 +49,7 @@ function GameEvent:exec() local logic = room.logic local ret = false -- false or nil means this event is running normally local extra_ret + self.parent = logic:getCurrentEvent() logic.game_event_stack:push(self) local co = coroutine.create(self.main_func) diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index b6eadd77..237c0961 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -276,10 +276,45 @@ function GameLogic:trigger(event, target, data) return broken end +---@return GameEvent function GameLogic:getCurrentEvent() return self.game_event_stack.t[self.game_event_stack.p] end +function GameLogic:dumpEventStack(detailed) + local top = self:getCurrentEvent() + local i = self.game_event_stack.p + local inspect = p + if not top then return end + + print("===== Start of event stack dump =====") + if not detailed then print("") end + + repeat + local printable_data + if type(top.data) ~= "table" then + printable_data = top.data + else + printable_data = table.cloneWithoutClass(top.data) + end + + if not detailed then + print("Stack level #" .. i .. ": " .. GameEvent:translate(top.event)) + else + print("\nStack level #" .. i .. ":") + inspect{ + eventId = GameEvent:translate(top.event), + data = printable_data or "nil", + } + end + + top = top.parent + i = i - 1 + until not top + + print("\n===== End of event stack dump =====") +end + function GameLogic:breakEvent(ret) coroutine.yield("__breakEvent", ret) end diff --git a/packages/maneuvering/init.lua b/packages/maneuvering/init.lua index 8aab7ece..62f35163 100644 --- a/packages/maneuvering/init.lua +++ b/packages/maneuvering/init.lua @@ -419,7 +419,7 @@ local silverLion = fk.CreateArmor{ equip_skill = silverLionSkill, on_uninstall = function(self, room, player) Armor.onUninstall(self, room, player) - if player:isWounded() and self.equip_skill:isEffectable(player) then + if player:isAlive() and player:isWounded() and self.equip_skill:isEffectable(player) then room:broadcastPlaySound("./packages/maneuvering/audio/card/silver_lion") room:setEmotion(player, "./packages/maneuvering/image/anim/silver_lion") room:recover{ diff --git a/qml/Pages/RoomElement/CardItem.qml b/qml/Pages/RoomElement/CardItem.qml index 48dbd74c..fdcce2e0 100644 --- a/qml/Pages/RoomElement/CardItem.qml +++ b/qml/Pages/RoomElement/CardItem.qml @@ -167,6 +167,7 @@ Item { DragHandler { enabled: draggable + grabPermissions: PointHandler.TakeOverForbidden xAxis.enabled: true yAxis.enabled: true diff --git a/qml/Pages/RoomElement/Cheat/PlayerDetail.qml b/qml/Pages/RoomElement/Cheat/PlayerDetail.qml new file mode 100644 index 00000000..b557c9b3 --- /dev/null +++ b/qml/Pages/RoomElement/Cheat/PlayerDetail.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Flickable { + id: root + anchors.fill: parent + property var extra_data: ({}) + + signal finish() + + contentHeight: details.height + ScrollBar.vertical: ScrollBar {} + + ColumnLayout { + id: details + width: parent.width - 16 + + // TODO: player details + Text { + id: screenName + Layout.fillWidth: true + font.pixelSize: 18 + } + + TextEdit { + id: skillDesc + + Layout.fillWidth: true + font.pixelSize: 18 + + readOnly: true + selectByKeyboard: true + selectByMouse: false + wrapMode: TextEdit.WordWrap + textFormat: TextEdit.RichText + } + } + + onExtra_dataChanged: { + if (!extra_data.photo) return; + screenName.text = ""; + skillDesc.text = ""; + + let id = extra_data.photo.playerid; + if (id == 0) return; + + let data = JSON.parse(Backend.callLuaFunction("GetPlayerSkills", [id])); + data.forEach(t => { + skillDesc.append("" + Backend.translate(t.name) + ": " + t.description) + }); + } +} diff --git a/qml/Pages/RoomElement/GraphicsBox.qml b/qml/Pages/RoomElement/GraphicsBox.qml index bf7f72b9..a8817940 100644 --- a/qml/Pages/RoomElement/GraphicsBox.qml +++ b/qml/Pages/RoomElement/GraphicsBox.qml @@ -28,6 +28,7 @@ Item { } DragHandler { + grabPermissions: PointHandler.TakeOverForbidden xAxis.enabled: true yAxis.enabled: true } diff --git a/qml/Pages/RoomElement/Photo.qml b/qml/Pages/RoomElement/Photo.qml index 09dd4919..e5bd62dd 100644 --- a/qml/Pages/RoomElement/Photo.qml +++ b/qml/Pages/RoomElement/Photo.qml @@ -9,7 +9,7 @@ Item { width: 175 height: 233 scale: 0.75 - property int playerid + property int playerid: 0 property string general: "" property string screenName: "" property string role: "unknown" @@ -319,11 +319,22 @@ Item { } TapHandler { - onTapped: { - if (parent.state != "candidate" || !parent.selectable) { - return; + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton + gesturePolicy: TapHandler.WithinBounds + + onTapped: (p, btn) => { + if (btn === Qt.LeftButton || btn === Qt.NoButton) { + if (parent.state != "candidate" || !parent.selectable) { + return; + } + parent.selected = !parent.selected; + } else if (btn === Qt.RightButton) { + parent.showDetail(); } - parent.selected = !parent.selected; + } + + onLongPressed: { + parent.showDetail(); } } @@ -538,4 +549,8 @@ Item { function updateLimitSkill(skill, time) { limitSkills.update(skill, time); } + + function showDetail() { + roomScene.startCheat("RoomElement/Cheat/PlayerDetail.qml", { photo: this }); + } }