diff --git a/Fk/Config.qml b/Fk/Config.qml index 1391182f..a48454d7 100644 --- a/Fk/Config.qml +++ b/Fk/Config.qml @@ -43,6 +43,7 @@ QtObject { property bool serverEnableBot: true property int roomCapacity: 0 property int roomTimeout: 0 + property bool heg: false property bool enableFreeAssign: false property bool observing: false property bool replaying: false diff --git a/Fk/LobbyElement/RoomGeneralSettings.qml b/Fk/LobbyElement/RoomGeneralSettings.qml index 73f332be..b6e61668 100644 --- a/Fk/LobbyElement/RoomGeneralSettings.qml +++ b/Fk/LobbyElement/RoomGeneralSettings.qml @@ -34,7 +34,7 @@ Flickable { SpinBox { id: playerNum from: 2 - to: 8 + to: 12 value: config.preferedPlayerNum onValueChanged: { diff --git a/Fk/Logic.js b/Fk/Logic.js index e80404b2..aa7d1835 100644 --- a/Fk/Logic.js +++ b/Fk/Logic.js @@ -134,6 +134,7 @@ callbacks["EnterRoom"] = (jsonData) => { config.roomTimeout = data[1] - 1; const roomSettings = data[2]; config.enableFreeAssign = roomSettings.enableFreeAssign; + config.heg = roomSettings.gameMode.includes('heg_mode'); mainStack.push(room); mainWindow.busy = false; } diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index a9d50b69..35bb4c5c 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -12,7 +12,73 @@ const Card = { Void : 8 } +function arrangeManyPhotos() { + /* Layout of photos: + * +----------------+ + * | -2 ... 2 | + * | -1 1 | + * | 0 | + * +----------------+ + */ + + const photoBaseWidth = 175; + const photoMaxWidth = 175 * 0.75; + const verticalSpacing = 32; + // Padding is negative, because photos are scaled. + const roomAreaPadding = -16; + + let horizontalSpacing = 8; + let photoWidth = (roomArea.width - horizontalSpacing * playerNum) / (playerNum - 1); + let photoScale = 0.75; + if (photoWidth > photoMaxWidth) { + photoWidth = photoMaxWidth; + horizontalSpacing = (roomArea.width - photoWidth * (playerNum - 1)) / playerNum; + } else { + photoScale = photoWidth / photoBaseWidth; + } + + const horizontalPadding = (photoWidth - photoBaseWidth) / 2; + const startX = horizontalPadding + horizontalSpacing; + const padding = photoWidth + horizontalSpacing; + let regions = [ + { + x: startX + padding * (playerNum - 2), + y: roomScene.height - 220, + scale: photoScale + }, + ]; + let i; + for (i = 0; i < playerNum - 1; i++) { + regions.push({ + x: startX + padding * (playerNum - 2 - i), + y: roomAreaPadding, + scale: photoScale, + }); + } + regions[1].y += verticalSpacing * 3; + regions[regions.length - 1].y += verticalSpacing * 3; + regions[2].y += verticalSpacing; + regions[regions.length - 2].y += verticalSpacing; + + let item, region; + + for (i = 0; i < playerNum; i++) { + item = photos.itemAt(i); + if (!item) + continue; + + region = regions[photoModel.get(i).index]; + item.x = region.x; + item.y = region.y; + item.scale = region.scale; + } +} + function arrangePhotos() { + if (playerNum > 8) { + return arrangeManyPhotos(); + } + /* Layout of photos: * +---------------+ * | 6 5 4 3 2 | diff --git a/Fk/PhotoElement/Magatama.qml b/Fk/PhotoElement/Magatama.qml index b917b792..5db754ec 100644 --- a/Fk/PhotoElement/Magatama.qml +++ b/Fk/PhotoElement/Magatama.qml @@ -4,15 +4,16 @@ import QtQuick import Fk Image { - source: SkinBank.MAGATAMA_DIR + "0" + source: SkinBank.MAGATAMA_DIR + "0" + (config.heg ? '-heg' : '') state: "3" + height: 19; fillMode: Image.PreserveAspectFit states: [ State { name: "3" PropertyChanges { target: main - source: SkinBank.MAGATAMA_DIR + "3" + source: SkinBank.MAGATAMA_DIR + "3" + (config.heg ? '-heg' : '') opacity: 1 scale: 1 } @@ -21,7 +22,7 @@ Image { name: "2" PropertyChanges { target: main - source: SkinBank.MAGATAMA_DIR + "2" + source: SkinBank.MAGATAMA_DIR + "2" + (config.heg ? '-heg' : '') opacity: 1 scale: 1 } @@ -30,7 +31,7 @@ Image { name: "1" PropertyChanges { target: main - source: SkinBank.MAGATAMA_DIR + "1" + source: SkinBank.MAGATAMA_DIR + "1" + (config.heg ? '-heg' : '') opacity: 1 scale: 1 } @@ -39,7 +40,7 @@ Image { name: "0" PropertyChanges { target: main - source: SkinBank.MAGATAMA_DIR + "0" + source: SkinBank.MAGATAMA_DIR + "0" + (config.heg ? '-heg' : '') opacity: 0 scale: 4 } @@ -55,5 +56,6 @@ Image { Image { id: main anchors.centerIn: parent + height: 19; fillMode: Image.PreserveAspectFit } } diff --git a/Fk/RoomElement/ChooseGeneralBox.qml b/Fk/RoomElement/ChooseGeneralBox.qml index 99f1faae..34825c95 100644 --- a/Fk/RoomElement/ChooseGeneralBox.qml +++ b/Fk/RoomElement/ChooseGeneralBox.qml @@ -174,6 +174,43 @@ GraphicsBox { updatePosition(); } + /* + 主副将的主势力和副势力至少有一个相同; + 副将不可野 主将可野 + */ + function isHegPair(gcard1, gcard2) { + if (!gcard1 || gcard1 === gcard2) { + return true; + } + + if (gcard2.kingdom == "wild") { + return false; + } + + if (gcard1.kingdom == "wild") { + return true; + } + + const k1 = gcard1.kingdom; + const k2 = gcard2.kingdom; + const sub1 = gcard1.subkingdom; + const sub2 = gcard2.subkingdom; + + if (k1 == k2) { + return true; + } + + if (sub1 && (sub1 == k2 || sub1 == sub2)) { + return true; + } + + if (sub2 && sub2 == k1) { + return true; + } + + return false; + } + function updatePosition() { choices = []; @@ -195,7 +232,7 @@ GraphicsBox { for (i = 0; i < generalCardList.count; i++) { item = generalCardList.itemAt(i); - item.selectable = needSameKingdom ? !(selectedItem[0] && (selectedItem[0].kingdom !== item.kingdom)) : true; + item.selectable = needSameKingdom ? isHegPair(selectedItem[0], item) : true; if (selectedItem.indexOf(item) != -1) continue; diff --git a/Fk/RoomElement/GeneralCardItem.qml b/Fk/RoomElement/GeneralCardItem.qml index 78ae990a..123a05fd 100644 --- a/Fk/RoomElement/GeneralCardItem.qml +++ b/Fk/RoomElement/GeneralCardItem.qml @@ -32,12 +32,16 @@ CardItem { card.source: SkinBank.getGeneralPicture(name) glow.color: "white" //Engine.kingdomColor[kingdom] + // FIXME: 藕!! + property bool heg: name.startsWith('hs__') || name.startsWith('ld__') || name.includes('heg__') + Image { source: SkinBank.GENERALCARD_DIR + "border" } Image { scale: subkingdom ? 0.6 : 1 + width: 34; fillMode: Image.PreserveAspectFit transformOrigin: Item.TopLeft source: SkinBank.getGeneralCardDir(kingdom) + kingdom visible: detailed @@ -46,6 +50,7 @@ CardItem { Image { scale: 0.6; x: 9; y: 12 transformOrigin: Item.TopLeft + width: 34; fillMode: Image.PreserveAspectFit source: subkingdom ? SkinBank.getGeneralCardDir(subkingdom) + subkingdom : "" visible: detailed } @@ -54,10 +59,10 @@ CardItem { x: 34 y: 4 spacing: 1 - visible: detailed + visible: detailed && !heg Repeater { id: hpRepeater - model: (hp > 5 || hp !== maxHp) ? 1 : hp + model: (!heg) ? ((hp > 5 || hp !== maxHp) ? 1 : hp) : 0 Item { width: childrenRect.width height: childrenRect.height @@ -97,6 +102,43 @@ CardItem { } } + Row { + x: 34 + y: 3 + spacing: 0 + visible: detailed && heg + Repeater { + id: hegHpRepeater + model: heg ? ((hp > 7 || hp !== maxHp) ? 1 : Math.ceil(hp / 2)) : 0 + Item { + width: childrenRect.width + height: childrenRect.height + Image { + height: 12; fillMode: Image.PreserveAspectFit + source: SkinBank.getGeneralCardDir(kingdom) + kingdom + "-magatama-l" + } + Image { + x: 4.4 + opacity: (index + 1) * 2 <= hp ? 1 : 0 + height: 12; fillMode: Image.PreserveAspectFit + source: { + const k = subkingdom ? subkingdom : kingdom; + SkinBank.getGeneralCardDir(k) + k + "-magatama-r" + } + } + } + } + + Text { + visible: hp > 7 || hp !== maxHp + text: hp === maxHp ? ("x" + hp / 2) : (" " + hp / 2 + "/" + maxHp / 2) + color: "white" + font.pixelSize: 14 + style: Text.Outline + y: -4 + } + } + Shield { visible: shieldNum > 0 && detailed anchors.right: parent.right diff --git a/Fk/RoomElement/Photo.qml b/Fk/RoomElement/Photo.qml index e845de9f..eb1bb15d 100644 --- a/Fk/RoomElement/Photo.qml +++ b/Fk/RoomElement/Photo.qml @@ -326,7 +326,7 @@ Item { Image { id: turnedOver visible: !root.faceup - source: SkinBank.PHOTO_DIR + "faceturned" + source: SkinBank.PHOTO_DIR + "faceturned" + (config.heg ? '-heg' : '') x: 29; y: 5 } @@ -505,7 +505,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: -32 - property var seatChr: ["一", "二", "三", "四", "五", "六", "七", "八"] + property var seatChr: ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"] font.family: fontLi2.name font.pixelSize: 32 text: seatChr[seatNumber - 1] diff --git a/image/card/general/border.png b/image/card/general/border.png index ec886f3d..9a886189 100644 Binary files a/image/card/general/border.png and b/image/card/general/border.png differ diff --git a/image/card/general/qun-magatama-l.png b/image/card/general/qun-magatama-l.png new file mode 100644 index 00000000..fef1f615 Binary files /dev/null and b/image/card/general/qun-magatama-l.png differ diff --git a/image/card/general/qun-magatama-r.png b/image/card/general/qun-magatama-r.png new file mode 100644 index 00000000..f11dff24 Binary files /dev/null and b/image/card/general/qun-magatama-r.png differ diff --git a/image/card/general/shu-magatama-l.png b/image/card/general/shu-magatama-l.png new file mode 100644 index 00000000..cd168ba6 Binary files /dev/null and b/image/card/general/shu-magatama-l.png differ diff --git a/image/card/general/shu-magatama-r.png b/image/card/general/shu-magatama-r.png new file mode 100644 index 00000000..e22eb549 Binary files /dev/null and b/image/card/general/shu-magatama-r.png differ diff --git a/image/card/general/wei-magatama-l.png b/image/card/general/wei-magatama-l.png new file mode 100644 index 00000000..1499067e Binary files /dev/null and b/image/card/general/wei-magatama-l.png differ diff --git a/image/card/general/wei-magatama-r.png b/image/card/general/wei-magatama-r.png new file mode 100644 index 00000000..38de7b8d Binary files /dev/null and b/image/card/general/wei-magatama-r.png differ diff --git a/image/card/general/wild-magatama-l.png b/image/card/general/wild-magatama-l.png new file mode 100644 index 00000000..f55338ed Binary files /dev/null and b/image/card/general/wild-magatama-l.png differ diff --git a/image/card/general/wild-magatama-r.png b/image/card/general/wild-magatama-r.png new file mode 100644 index 00000000..6cbfdc03 Binary files /dev/null and b/image/card/general/wild-magatama-r.png differ diff --git a/image/card/general/wild.png b/image/card/general/wild.png new file mode 100644 index 00000000..c0be5ad2 Binary files /dev/null and b/image/card/general/wild.png differ diff --git a/image/card/general/wu-magatama-l.png b/image/card/general/wu-magatama-l.png new file mode 100644 index 00000000..ea539cae Binary files /dev/null and b/image/card/general/wu-magatama-l.png differ diff --git a/image/card/general/wu-magatama-r.png b/image/card/general/wu-magatama-r.png new file mode 100644 index 00000000..627d0008 Binary files /dev/null and b/image/card/general/wu-magatama-r.png differ diff --git a/image/photo/faceturned-heg.png b/image/photo/faceturned-heg.png new file mode 100644 index 00000000..c8baee3f Binary files /dev/null and b/image/photo/faceturned-heg.png differ diff --git a/image/photo/magatama/0-heg.png b/image/photo/magatama/0-heg.png new file mode 100644 index 00000000..38407e70 Binary files /dev/null and b/image/photo/magatama/0-heg.png differ diff --git a/image/photo/magatama/1-heg.png b/image/photo/magatama/1-heg.png new file mode 100644 index 00000000..ecfe2884 Binary files /dev/null and b/image/photo/magatama/1-heg.png differ diff --git a/image/photo/magatama/2-heg.png b/image/photo/magatama/2-heg.png new file mode 100644 index 00000000..10e04245 Binary files /dev/null and b/image/photo/magatama/2-heg.png differ diff --git a/image/photo/magatama/3-heg.png b/image/photo/magatama/3-heg.png new file mode 100644 index 00000000..3c31c602 Binary files /dev/null and b/image/photo/magatama/3-heg.png differ diff --git a/lua/client/i18n/en_US.lua b/lua/client/i18n/en_US.lua index ee7ab1e3..983c4489 100644 --- a/lua/client/i18n/en_US.lua +++ b/lua/client/i18n/en_US.lua @@ -99,7 +99,7 @@ Fk:loadTranslationTable({ ["#AskForNullificationWithoutTo"] = "是否对 %src 使用的 %arg 使用无懈可击?", ["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张", - ["#askForPindian"] = "请选择一张手牌作为拼点牌", + ["#askForPindian"] = "%arg:请选择一张手牌作为拼点牌", -- ["Trust"] = "托管", -- ["Sort Cards"] = "牌序", diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 2ce0369c..a6568bf3 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -235,7 +235,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张", ["#AskForCard"] = "请选择 %arg 张牌,最少 %arg2 张", - ["#askForPindian"] = "请选择一张手牌作为拼点牌", + ["#askForPindian"] = "%arg:请选择一张手牌作为拼点牌", ["#StartPindianReason"] = "%from 由于 %arg 而发起拼点", ["#ShowPindianCard"] = "%from 的拼点牌是 %card", ["#ShowPindianResult"] = "%from 在 %from 和 %to 之间的拼点中 %arg", @@ -255,6 +255,10 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["seat#6"] = "六号位", ["seat#7"] = "七号位", ["seat#8"] = "八号位", + ["seat#9"] = "九号位", + ["seat#10"] = "十号位", + ["seat#11"] = "十一号位", + ["seat#12"] = "十二号位", ["@ControledBy"] = "控制者", ["Menu"] = "菜单", diff --git a/lua/core/game_mode.lua b/lua/core/game_mode.lua index c297d678..658821f7 100644 --- a/lua/core/game_mode.lua +++ b/lua/core/game_mode.lua @@ -21,7 +21,7 @@ local GameMode = class("GameMode") function GameMode:initialize(name, min, max) self.name = name self.minPlayer = math.max(min, 2) - self.maxPlayer = math.min(max, 8) + self.maxPlayer = math.min(max, 12) end ---@param victim ServerPlayer @ 死者 diff --git a/lua/core/skill_type/trigger.lua b/lua/core/skill_type/trigger.lua index 78df43e4..89ff283c 100644 --- a/lua/core/skill_type/trigger.lua +++ b/lua/core/skill_type/trigger.lua @@ -62,7 +62,8 @@ function TriggerSkill:doCost(event, target, player, data) local room = player.room -- 对于那种cost直接返回true的锁定技,如果是预亮技,那么还是询问一下好 - if ret and player:isFakeSkill(self) and end_time - start_time < 10000 then + if ret and player:isFakeSkill(self) and end_time - start_time < 10000 and + (self.main_skill and self.main_skill or self).visible then ret = room:askForSkillInvoke(player, self.name) end diff --git a/lua/core/skill_type/view_as.lua b/lua/core/skill_type/view_as.lua index c8253b7f..cd59f8f3 100644 --- a/lua/core/skill_type/view_as.lua +++ b/lua/core/skill_type/view_as.lua @@ -27,12 +27,12 @@ end ---@param player Player function ViewAsSkill:enabledAtPlay(player) - return player:hasSkill(self) + return self:isEffectable(player) end ---@param player Player function ViewAsSkill:enabledAtResponse(player, cardResponsing) - return player:hasSkill(self) + return self:isEffectable(player) end ---@param player Player diff --git a/lua/server/events/pindian.lua b/lua/server/events/pindian.lua index c1ee17ba..826a7b20 100644 --- a/lua/server/events/pindian.lua +++ b/lua/server/events/pindian.lua @@ -21,8 +21,8 @@ GameEvent.functions[GameEvent.Pindian] = function(self) pattern = ".", reason = pindianData.reason, } - local prompt = "#askForPindian" - local data = { "choose_cards_skill", prompt, true, json.encode(extraData) } + local prompt = "#askForPindian:::" .. pindianData.reason + local data = { "choose_cards_skill", prompt, false, json.encode(extraData) } local targets = {} local moveInfos = {} diff --git a/lua/server/request.lua b/lua/server/request.lua index aa234d9f..ecb421d6 100644 --- a/lua/server/request.lua +++ b/lua/server/request.lua @@ -36,6 +36,20 @@ local function tellRoomToObserver(self, player) player:doNotify("UpdateDrawPile", #self.draw_pile) player:doNotify("UpdateRoundNum", self:getTag("RoundCount") or 0) + -- send printed_cards + for i = -2, -math.huge, -1 do + local c = Fk.printed_cards[i] + if not c then break end + player:doNotify("PrintCard", json.encode{ c.name, c.suit, c.number }) + end + + -- send card marks + for id, marks in pairs(room.card_marks) do + for k, v in pairs(marks) do + player:doNotify("SetCardMark", json.encode{ id, k, v }) + end + end + table.insert(self.observers, {observee.id, player, player:getId()}) end diff --git a/lua/server/room.lua b/lua/server/room.lua index 77603486..65cd0791 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -2480,7 +2480,7 @@ function Room:handleCardEffect(event, cardEffectEvent) end if not table.contains(players, p) then Self = p -- for enabledAtResponse - for _, s in ipairs(p.player_skills) do + for _, s in ipairs(table.connect(p.player_skills, p._fake_skills)) do if s.pattern and Exppattern:Parse("nullification"):matchExp(s.pattern) and diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index ab670d4c..0fde879f 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -15,6 +15,7 @@ ---@field public phase_index integer ---@field public role_shown boolean ---@field private _fake_skills Skill[] +---@field private _manually_fake_skills Skill[] ---@field public prelighted_skills Skill[] ---@field private _timewaste_count integer ---@field public ai AI @@ -39,6 +40,7 @@ function ServerPlayer:initialize(_self) self.skipped_phases = {} self._fake_skills = {} + self._manually_fake_skills = {} self.prelighted_skills = {} self._prelighted_skills = {} @@ -353,6 +355,28 @@ function ServerPlayer:reconnect() self:doNotify("UpdateDrawPile", #room.draw_pile) self:doNotify("UpdateRoundNum", room:getTag("RoundCount") or 0) + -- send printed_cards + for i = -2, -math.huge, -1 do + local c = Fk.printed_cards[i] + if not c then break end + self:doNotify("PrintCard", json.encode{ c.name, c.suit, c.number }) + end + + -- send card marks + for id, marks in pairs(room.card_marks) do + for k, v in pairs(marks) do + self:doNotify("SetCardMark", json.encode{ id, k, v }) + end + end + + -- send fake skills + for _, s in ipairs(self._manually_fake_skills) do + self:doNotify("AddSkill", json.encode{ self.id, s.name, true }) + if table.contains(self.prelighted_skills, s) then + self:doNotify("PrelightSkill", json.encode{ s.name, true }) + end + end + room:broadcastProperty(self, "state") end @@ -702,11 +726,12 @@ function ServerPlayer:reset() from = self.id, arg = "reset-general" } - self:setChainState(false) + if self.chained then self:setChainState(false) end if not self.faceup then self:turnOver() end end ---@param from ServerPlayer +--- 进行拼点。 +---@param from ServerPlayer ---@param tos ServerPlayer[] ---@param skillName string ---@param initialCard Card|nil @@ -740,6 +765,8 @@ function ServerPlayer:addFakeSkill(skill) end if table.contains(self._fake_skills, skill) then return end + table.insertIfNeed(self._manually_fake_skills, skill) + table.insert(self._fake_skills, skill) for _, s in ipairs(skill.related_skills) do -- if s.main_skill == skill then -- TODO: need more detailed @@ -759,6 +786,8 @@ function ServerPlayer:loseFakeSkill(skill) end if not table.contains(self._fake_skills, skill) then return end + table.removeOne(self._manually_fake_skills, skill) + table.removeOne(self._fake_skills, skill) for _, s in ipairs(skill.related_skills) do table.removeOne(self._fake_skills, s) @@ -839,7 +868,7 @@ function ServerPlayer:revealGeneral(isDeputy, no_trigger) end local oldKingdom = self.kingdom - room:changeHero(self, generalName, false, isDeputy) + room:changeHero(self, generalName, false, isDeputy, false, false) if oldKingdom ~= "wild" then local kingdom = general.kingdom self.kingdom = kingdom