diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 1a008c74..38737f87 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -85,8 +85,10 @@ jobs: cd assets/res cp -r /etc/ssl/certs . cp /usr/share/ca-certificates/mozilla/* certs/ - echo ${FKVER%)} > fk_ver - ./genfkver.sh + cd ../.. + echo ${FKVER%)} > ../fk_ver + ../genfkver.sh + cp ../fk_ver assets/res - name: Configure CMake Project working-directory: ${{github.workspace}} diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index c32fb5d2..69c4ea81 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -77,6 +77,7 @@ jobs: cp lib/win/* FreeKill-release cp build/zh_CN.qm FreeKill-release cp build/en_US.qm FreeKill-release + cp fk_ver FreeKill-release cp ../Qt/6.5.3/mingw_64/bin/li*.dll FreeKill-release cp ../Qt/Tools/OpenSSLv3/Win_x64/bin/libcrypto-3-x64.dll FreeKill-release 7z a -t7z FreeKill-release.7z FreeKill-release -r -mx=9 -m0=LZMA2 -ms=10m -mf=on -mhc=on -mmt=on diff --git a/CHANGELOG.md b/CHANGELOG.md index 0072d29c..ba438d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # ChangeLog +## v0.4.16 + +在引入freekill-core之后的第一次版本更新,甚至无法保证这次更新是否正常 + +1. 改进processPrompt,支持双将和暗将 +2. 副将长名旋转 +3. 国战体力上限优化,包括一览和选将框 +4. 空格添加结束出牌阶段,Escape键呼出菜单 +5. 武将一览左栏文本换行 +6. 同名替换影响已选择的武将 +7. 再次排序手牌时按照点数排序 +8. Logic.js翻译 +9. 进入房间翻译删去句号,跟房间内其他toast风格统一 +10. 常见疑问最后一张“下一条”改为“OK!” +11. 录像回放“从文件打开”翻译 +12. interaction自动弹出和关闭,comboBox补技能名 +13. 卡牌音效添加装备效果音效和使用音效,小小重构 +14. activeSkill的prompt的selected_targets实装 +15. 禁用扩展包文本ui限制长度 +16. 右键技能呼出气泡 +17. 搬运了ArrangeCards。 +18. 优化了GuanxingBox的操作 +19. 修复了不能及时更新技能prompt的bug +20. 取消目标后会刷新目标选择 +21. 完备了借刀的牌名 +22. CPP代码进行大的重构,配有少量文档 +23. (底层)调度机制大改 +24. 大厅UI开始调整,但是仍未完工 + +___ + ## v0.4.13 & 14 & 15 - 优化重连逻辑 diff --git a/CMakeLists.txt b/CMakeLists.txt index a55a906d..5139f0b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16) -project(FreeKill VERSION 0.4.15) +project(FreeKill VERSION 0.4.16) add_definitions(-DFK_VERSION=\"${CMAKE_PROJECT_VERSION}\") find_package(Qt6 REQUIRED COMPONENTS diff --git a/Fk/Pages/RoomLogic.js b/Fk/Pages/RoomLogic.js index 708d7a51..00b6f30f 100644 --- a/Fk/Pages/RoomLogic.js +++ b/Fk/Pages/RoomLogic.js @@ -813,10 +813,6 @@ function updateSelectedTargets(playerid, selected) { } } } - const prompt = lcall("CardPrompt", card, selected_targets); - if (prompt !== "") { - roomScene.setPrompt(Util.processPrompt(prompt)); - } } else { all_photos.forEach(photo => { photo.state = "normal"; diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index ceae29e8..5db8de1a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -3,8 +3,8 @@ + android:versionCode="416" + android:versionName="0.4.16"> diff --git a/genfkver.sh b/genfkver.sh index e7809bab..ef293c75 100755 --- a/genfkver.sh +++ b/genfkver.sh @@ -7,7 +7,7 @@ cd $(dirname $0) sed -i '2,$d' ./fk_ver fn() { - for f in $(ls -1 $1); do + for f in $(ls -1 $1 | sort); do if [ -d $1/$f ]; then fn $1/$f else diff --git a/lua/client/client.lua b/lua/client/client.lua index dbbd2d9d..f6296ee7 100644 --- a/lua/client/client.lua +++ b/lua/client/client.lua @@ -9,6 +9,7 @@ ---@field public discard_pile integer[] @ 弃牌堆 ---@field public observing boolean ---@field public record any +---@field public last_update_ui integer @ 上次刷新状态技UI的时间 Client = AbstractRoom:subclass('Client') -- load client classes @@ -65,6 +66,23 @@ function Client:initialize() else self:notifyUI(command, data) end + + if self.recording and command == "GameLog" then + --and os.getms() - self.last_update_ui > 60000 then + -- self.last_update_ui = os.getms() + -- TODO: create a function + -- 刷所有人手牌上限 + for _, p in ipairs(self.alive_players) do + self:notifyUI("MaxCard", { + pcardMax = p:getMaxCards(), + id = p.id, + }) + end + -- 刷自己的手牌 + for _, cid in ipairs(Self:getCardIds("h")) do + self:notifyUI("UpdateCard", cid) + end + end end self.discard_pile = {} @@ -72,6 +90,7 @@ function Client:initialize() self.disabled_packs = {} self.disabled_generals = {} + -- self.last_update_ui = os.getms() self.recording = false end @@ -114,10 +133,6 @@ function Client:moveCards(moves) for _, move in ipairs(moves) do if move.from and move.fromArea then local from = self:getPlayerById(move.from) - self:notifyUI("MaxCard", { - pcardMax = from:getMaxCards(), - id = move.from, - }) if move.fromArea == Card.PlayerHand and not Self:isBuddy(self:getPlayerById(move.from)) then for _ = 1, #move.ids do table.remove(from.player_cards[Player.Hand]) @@ -133,13 +148,11 @@ function Client:moveCards(moves) if move.to and move.toArea then local ids = move.ids - self:notifyUI("MaxCard", { - pcardMax = self:getPlayerById(move.to):getMaxCards(), - id = move.to, - }) - 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) + if (move.toArea == Card.PlayerHand and not Self:isBuddy(self:getPlayerById(move.to))) or + (move.toArea == Card.PlayerSpecial and not move.moveVisible) then + ids = {-1} end + self:getPlayerById(move.to):addCards(move.toArea, ids, move.specialName) elseif move.toArea == Card.DiscardPile then table.insert(self.discard_pile, move.ids[1]) @@ -340,6 +353,7 @@ fk.client_callback["AddObserver"] = function(data) } local p = ClientPlayer:new(player) table.insert(ClientInstance.observers, p) + -- ClientInstance:notifyUI("ServerMessage", string.format(Fk:translate("$AddObserver"), name)) end fk.client_callback["RemoveObserver"] = function(data) @@ -347,6 +361,7 @@ fk.client_callback["RemoveObserver"] = function(data) for _, p in ipairs(ClientInstance.observers) do if p.player:getId() == id then table.removeOne(ClientInstance.observers, p) + -- ClientInstance:notifyUI("ServerMessage", string.format(Fk:translate("$RemoveObserver"), p.player:getScreenName())) break end end @@ -387,10 +402,6 @@ fk.client_callback["PropertyUpdate"] = function(data) end ClientInstance:notifyUI("PropertyUpdate", data) - ClientInstance:notifyUI("MaxCard", { - pcardMax = ClientInstance:getPlayerById(id):getMaxCards(), - id = id, - }) end fk.client_callback["AskForCardChosen"] = function(data) @@ -474,7 +485,38 @@ end ---@param moves CardsMoveStruct[] local function separateMoves(moves) local ret = {} ---@type CardsMoveInfo[] + + local function containArea(area, relevant, defaultVisible) --处理区的处理? + local areas = relevant + and {Card.PlayerEquip, Card.PlayerJudge, Card.PlayerHand} + or {Card.PlayerEquip, Card.PlayerJudge} + return table.contains(areas, area) or (defaultVisible and table.contains({Card.Processing, Card.DiscardPile}, area)) + end + for _, move in ipairs(moves) do + local singleVisible = move.moveVisible + if not singleVisible then + if move.visiblePlayers then + local visiblePlayers = move.visiblePlayers + if type(visiblePlayers) == "number" then + if Self:isBuddy(visiblePlayers) then + singleVisible = true + end + elseif type(visiblePlayers) == "table" then + if table.find(visiblePlayers, function(pid) return Self:isBuddy(pid) end) then + singleVisible = true + end + end + else + if move.to and move.toArea == Card.PlayerSpecial and Self:isBuddy(move.to) then + singleVisible = true + end + end + end + if not singleVisible then + singleVisible = containArea(move.toArea, move.to and Self:isBuddy(move.to), move.moveVisible == nil) + end + for _, info in ipairs(move.moveInfo) do table.insert(ret, { ids = {info.cardId}, @@ -486,6 +528,7 @@ local function separateMoves(moves) specialName = move.specialName, fromSpecialName = info.fromSpecialName, proposer = move.proposer, + moveVisible = singleVisible or containArea(info.fromArea, move.from and Self:isBuddy(move.from), move.moveVisible == nil) }) end end @@ -513,7 +556,7 @@ local function mergeMoves(moves) proposer = move.proposer, } end - table.insert(temp[info].ids, move.ids[1]) + table.insert(temp[info].ids, move.moveVisible and move.ids[1] or -1) end for _, v in pairs(temp) do table.insert(ret, v) @@ -609,8 +652,27 @@ local function sendMoveCardLog(move) from = move.from, card = move.ids, } - -- elseif move.toArea == Card.Processing then - -- nop + elseif move.toArea == Card.Processing then + if move.fromArea == Card.DrawPile and (move.moveReason == fk.ReasonPut or move.moveReason == fk.ReasonJustMove) then + if hidden then + client:appendLog{ + type = "$ViewCardFromDrawPile", + from = move.proposer, + arg = #move.ids, + } + else + client:appendLog{ + type = "$TurnOverCardFromDrawPile", + from = move.proposer, + card = move.ids, + arg = #move.ids, + } + client:setCardNote(move.ids, { + type = "$$TurnOverCard", + from = move.proposer, + }) + end + end elseif move.from and move.toArea == Card.DrawPile then msgtype = hidden and "$PutCard" or "$PutKnownCard" client:appendLog{ @@ -1029,7 +1091,7 @@ end fk.client_callback["EnterLobby"] = function(jsonData) local c = ClientInstance - --[[ + ---[[ if c.recording and not c.observing then c.recording = false c.record[2] = table.concat({ @@ -1120,7 +1182,7 @@ local function loadPlayerSummary(pdata) to = id, toArea = Card.PlayerSpecial, specialName = k, - specialVisible = Self.id == id, + moveVisible = true, } table.insert(card_moves, move) end diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index a0c8dce3..c6f75933 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -16,6 +16,8 @@ function GetGeneralData(name) subkingdom = general.subkingdom, hp = general.hp, maxHp = general.maxHp, + mainMaxHpAdjustedValue = general.mainMaxHpAdjustedValue, + deputyMaxHpAdjustedValue = general.deputyMaxHpAdjustedValue, shield = general.shield, hidden = general.hidden, total_hidden = general.total_hidden, @@ -31,6 +33,8 @@ function GetGeneralDetail(name) kingdom = general.kingdom, hp = general.hp, maxHp = general.maxHp, + mainMaxHp = general.mainMaxHpAdjustedValue, + deputyMaxHp = general.deputyMaxHpAdjustedValue, gender = general.gender, skill = {}, related_skill = {}, @@ -382,6 +386,22 @@ function CardFeasible(card, selected_targets) return ret end +---@param card string | integer +---@param selected_targets integer[] @ ids of selected players +function CardPrompt(card, selected_targets) + local c ---@type Card + local selected_cards + if type(card) == "number" then + c = Fk:getCardById(card) + selected_cards = {card} + else + local t = json.decode(card) + return ActiveSkillPrompt(t.skill, t.subcards, selected_targets) + end + + return ActiveSkillPrompt(c.skill, selected_cards, selected_targets) +end + -- Handle skills function GetSkillData(skill_name) @@ -621,6 +641,7 @@ end function GetInteractionOfSkill(skill_name) local skill = Fk.skills[skill_name] if skill and skill.interaction then + skill.interaction.data = nil return skill:interaction() end return nil @@ -738,10 +759,9 @@ function GetCardProhibitReason(cid, method, pattern) local skillName = s.name local ret = Fk:translate(skillName) if ret ~= skillName then - -- TODO: translate - return ret .. "禁" .. (method == "use" and "使用" or "打出") + return ret .. Fk:translate("prohibit") .. Fk:translate(method == "use" and "method_use" or "method_response_play") elseif skillName:endsWith("_prohibit") and skillName:startsWith("#") then - return Fk:translate(skillName:sub(2, -10)) .. "禁" .. (method == "use" and "使用" or "打出") + return Fk:translate(skillName:sub(2, -10)) .. Fk:translate("prohibit") .. Fk:translate(method == "use" and "method_use" or "method_response_play") else return ret end diff --git a/lua/client/i18n/en_US.lua b/lua/client/i18n/en_US.lua index d83fd798..070b2aee 100644 --- a/lua/client/i18n/en_US.lua +++ b/lua/client/i18n/en_US.lua @@ -12,7 +12,10 @@ Fk:loadTranslationTable({ -- ["Old Password"] = "旧密码", -- ["New Password"] = "新密码", -- ["Update Avatar"] = "更新头像", + -- ["Update avatar done."] = "头像已更新", -- ["Update Password"] = "更新密码", + -- ["Update password done."] = "密码已更新", + -- ["Old password wrong!"] = "旧密码错误!", -- ["Lobby BG"] = "大厅壁纸", -- ["Room BG"] = "房间背景", -- ["Game BGM"] = "游戏BGM", @@ -90,9 +93,14 @@ Fk:loadTranslationTable({ ["Cards Overview"] = "Cards", ["Special card skills:"] = "Special use method:", ["Every suit & number:"] = "All suit and number:", + -- ["Male Audio"] = "男性音效", + -- ["Female Audio"] = "女性音效", + -- ["Equip Effect Audio"] = "效果音效", + -- ["Equip Use Audio"] = "使用音效", ["Scenarios Overview"] = "Game modes", -- ["Replay"] = "录像", -- ["Replay Manager"] = "来欣赏潇洒的录像吧!", + -- ["Replay from File"] = "从文件打开", ["Game Win"] = "Win", ["Game Lose"] = "Lose", ["Play the Replay"] = "Play", @@ -156,7 +164,7 @@ Fk:loadTranslationTable({ ["IncludeDeputy"] = "Deputy character enabled", -- Room - ["$EnterRoom"] = "Successfully entered the room.", + ["$EnterRoom"] = "Successfully entered the room", ["#currentRoundNum"] = "Round #%1", ["$Choice"] = "%1: Please choose", ["$ChooseGeneral"] = "Please choose %1 character(s)", @@ -347,7 +355,7 @@ Fk:loadTranslationTable({ -- get/lose skill ["#AcquireSkill"] = '%from acquired the skill "%arg"', - ["#LoseSkill"] = '%from lost the skill "%arg"', + ["#LoseSkill"] = '%from lost the skill "%arg"', -- moveCards (they are sent by notifyMoveCards) ["$PutCard"] = "%arg card(s) of %from were put into draw pile", @@ -405,8 +413,8 @@ Fk:loadTranslationTable({ -- turnOver ["#TurnOver"] = "%from turned over character card, now his status is %arg", - ["face_up"] = "face up", - ["face_down"] = "face down", + ["face_up"] = "face up", + ["face_down"] = "face down", -- damage, heal and lose HP ["#Damage"] = "%to dealt %arg %arg2 DMG to %from", @@ -443,4 +451,6 @@ Fk:loadTranslationTable({ ["##ResponsePlayCard"] = "%from plays", ["##ShowCard"] = "%from shows", ["##JudgeCard"] = "%arg judge", + ["##PindianCard"] = "%from point fights", + ["##RecastCard"] = "%from recasts", }, "en_US") diff --git a/lua/client/i18n/zh_CN.lua b/lua/client/i18n/zh_CN.lua index 44094276..f6ff38a7 100644 --- a/lua/client/i18n/zh_CN.lua +++ b/lua/client/i18n/zh_CN.lua @@ -12,7 +12,10 @@ Fk:loadTranslationTable{ ["Old Password"] = "旧密码", ["New Password"] = "新密码", ["Update Avatar"] = "更新头像", + ["Update avatar done."] = "头像已更新", ["Update Password"] = "更新密码", + ["Update password done."] = "密码已更新", + ["Old password wrong!"] = "旧密码错误!", ["Lobby BG"] = "大厅壁纸", ["Room BG"] = "房间背景", ["Game BGM"] = "游戏BGM", @@ -31,7 +34,7 @@ Fk:loadTranslationTable{ ["Search"] = "搜索", ["Back"] = "返回", - ["Refresh Room List"] = "刷新房间列表", + ["Refresh Room List"] = "刷新房间列表 (%1个房间)", ["Disable Extension"] = "禁用Lua拓展 (重启后生效)", ["Create Room"] = "创建房间", @@ -100,9 +103,12 @@ Fk:loadTranslationTable{ ["Every suit & number:"] = "所有的花色和点数:", ["Male Audio"] = "男性音效", ["Female Audio"] = "女性音效", + ["Equip Effect Audio"] = "效果音效", + ["Equip Use Audio"] = "使用音效", ["Scenarios Overview"] = "玩法一览", ["Replay"] = "录像", ["Replay Manager"] = "来欣赏潇洒的录像吧!", + ["Replay from File"] = "从文件打开", ["Game Win"] = "胜利", ["Game Lose"] = "失败", ["Play the Replay"] = "重放", @@ -212,7 +218,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["IncludeDeputy"] = "启用副将机制", -- Room - ["$EnterRoom"] = "成功加入房间。", + ["$EnterRoom"] = "成功加入房间", ["#currentRoundNum"] = "第 %1 轮", ["$Choice"] = "%1:请选择", ["$ChooseGeneral"] = "请选择 %1 名武将", @@ -222,7 +228,7 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["#PlayCard"] = "出牌阶段,请使用一张牌", ["#AskForGeneral"] = "请选择 1 名武将", - ["#AskForSkillInvoke"] = "你想发动技能“%1”吗?", + ["#AskForSkillInvoke"] = "你想发动〖%1〗吗?", ["#AskForLuckCard"] = "你想使用手气卡吗?还可以使用 %1 次,剩余手气卡∞张", ["AskForLuckCard"] = "手气卡", ["#AskForChoice"] = "%1:请选择", @@ -254,13 +260,13 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["$Equip"] = "装备区", ["$Judge"] = "判定区", ['$Selected'] = "已选择", - ["#AskForUseActiveSkill"] = "请使用技能 %1", - ["#AskForUseCard"] = "请使用卡牌 %1", - ["#AskForResponseCard"] = "请打出卡牌 %1", - ["#AskForNullification"] = "是否为目标为 %dest 的 %arg 使用无懈可击?", - ["#AskForNullificationWithoutTo"] = "是否对 %src 使用的 %arg 使用无懈可击?", - ["#AskForPeaches"] = "%src 生命危急,需要 %arg 个桃", - ["#AskForPeachesSelf"] = "你生命危急,需要 %arg 个桃或酒", + ["#AskForUseActiveSkill"] = "请发动〖%1〗", + ["#AskForUseCard"] = "请使用【%1】", + ["#AskForResponseCard"] = "请打出【%1】", + ["#AskForNullification"] = "是否为目标为 %dest 的【%arg】使用【无懈可击】?", + ["#AskForNullificationWithoutTo"] = "是否对 %src 使用的【%arg】使用【无懈可击】?", + ["#AskForPeaches"] = "%src 生命危急,需要 %arg 个【桃】", + ["#AskForPeachesSelf"] = "你生命危急,需要 %arg 个【桃】或【酒】", ["#AskForDiscard"] = "请弃置 %arg 张牌,最少 %arg2 张", ["#AskForCard"] = "请选择 %arg 张牌,最少 %arg2 张", @@ -322,6 +328,9 @@ FreeKill使用的是libgit2的C API,与此同时使用Git完成拓展包的下 ["Back To Lobby"] = "返回大厅", ["Save Replay"] = "保存录像", + ["$AddObserver"] = '玩家 %s 开始旁观', + ["$RemoveObserver"] = '旁观者 %s 离开了房间', + ["Speed Resume"] = "匀速", ["Speed Up"] = "加速", ["Speed Down"] = "减速", @@ -373,6 +382,7 @@ Fk:loadTranslationTable{ ["pile_discard"] = "弃牌堆", ["processing_area"] = "处理区", ["Pile"] = "牌堆", + ["toObtain"] = "获得的牌", ["Top"] = "牌堆顶", ["Bottom"] = "牌堆底", ["Shuffle"] = "洗牌", @@ -408,8 +418,8 @@ Fk:loadTranslationTable{ ["$GameEnd"] = "== 游戏结束 ==", -- get/lose skill - ["#AcquireSkill"] = "%from 获得了技能 “%arg”", - ["#LoseSkill"] = "%from 失去了技能 “%arg”", + ["#AcquireSkill"] = "%from 获得了〖%arg〗", + ["#LoseSkill"] = "%from 失去了〖%arg〗", -- moveCards (they are sent by notifyMoveCards) ["$GetCardsFromPile"] = "%from 从 %arg 中获得了 %arg2 张牌 %card", @@ -432,6 +442,8 @@ Fk:loadTranslationTable{ ["$DiscardCards"] = "%from 弃置了 %arg 张牌 %card", ["$DiscardOther"] = "%to 弃置了 %from 的 %arg 张牌 %card", ["$PutToDiscard"] = "%arg 张牌 %card 被置入弃牌堆", + ["$ViewCardFromDrawPile"] = "%from 观看了 %arg 张牌", + ["$TurnOverCardFromDrawPile"] = "%from 亮出了 %arg 张牌 %card", ["#AbortArea"] = "%from 的 %arg 被废除", ["#ResumeArea"] = "%from 的 %arg 被恢复", @@ -464,30 +476,30 @@ Fk:loadTranslationTable{ ["#FilterCard"] = "由于 %arg 的效果,与 %from 相关的 %arg2 被视为了 %arg3", -- skill - ["#InvokeSkill"] = "%from 发动了 “%arg”", + ["#InvokeSkill"] = "%from 发动了〖%arg〗", -- judge ["#StartJudgeReason"] = "%from 开始了 %arg 的判定", ["#InitialJudge"] = "%from 的判定牌为 %arg", - ["#ChangedJudge"] = "%from 发动“%arg”把 %to 的判定牌改为 %arg2", + ["#ChangedJudge"] = "%from 发动了〖%arg〗把 %to 的判定牌改为 %arg2", ["#JudgeResult"] = "%from 的判定结果为 %arg", -- turnOver ["#TurnOver"] = "%from 将武将牌翻面,现在是 %arg", - ["face_up"] = "正面朝上", - ["face_down"] = "背面朝上", + ["face_up"] = "正面朝上", + ["face_down"] = "背面朝上", -- damage, heal and lose HP ["#Damage"] = "%to 对 %from 造成了 %arg 点 %arg2 伤害", ["#DamageWithNoFrom"] = "%from 受到了 %arg 点 %arg2 伤害", ["#LoseHP"] = "%from 失去了 %arg 点体力", ["#HealHP"] = "%from 回复了 %arg 点体力", - ["#ShowHPAndMaxHP"] = "%from 现在的体力值为 %arg,体力上限为 %arg2", + ["#ShowHPAndMaxHP"] = "%from 的体力值为 %arg,体力上限为 %arg2", ["#LoseMaxHP"] = "%from 减了 %arg 点体力上限", ["#HealMaxHP"] = "%from 加了 %arg 点体力上限", -- dying and death - ["#EnterDying"] = "%from 进入了濒死阶段", + ["#EnterDying"] = "%from 进入了濒死状态", ["#KillPlayer"] = "%from [%arg] 阵亡,凶手是 %to", ["#KillPlayerWithNoKiller"] = "%from [%arg] 阵亡,无伤害来源", ["#Revive"] = "%from 竟然复活了", @@ -499,7 +511,7 @@ Fk:loadTranslationTable{ ["#GuanxingResult"] = "%from 的观星结果为 %arg 上 %arg2 下", ["#ChainStateChange"] = "%from %arg 了武将牌", ["#ChainDamage"] = "%from 处于连环状态,将受到传导的伤害", - ["#ChangeKingdom"] = "%from 的国籍从 %arg 变成了 %arg2", + ["#ChangeKingdom"] = "%from 的势力从 %arg 变成了 %arg2", ["#RoomOutdated"] = "服务器更新完毕!该房间已过期,将无法再次游玩", } @@ -507,10 +519,13 @@ Fk:loadTranslationTable{ Fk:loadTranslationTable{ ["$$DiscardCards"] = "%from弃置", ["$$PutCard"] = "%from置于", + ["$$TurnOverCard"] = "%from亮出", ["##UseCard"] = "%from使用", ["##UseCardTo"] = "%from对%to", ["##ResponsePlayCard"] = "%from打出", ["##ShowCard"] = "%from展示", ["##JudgeCard"] = "%arg判定", + ["##PindianCard"] = "%from拼点", + ["##RecastCard"] = "%from重铸", } diff --git a/lua/core/card_type/equip.lua b/lua/core/card_type/equip.lua index 3fdacb4f..e565db52 100644 --- a/lua/core/card_type/equip.lua +++ b/lua/core/card_type/equip.lua @@ -2,33 +2,80 @@ ---@class EquipCard : Card ---@field public equip_skill Skill +---@field public equip_skills Skill[] +---@field public dynamicEquipSkills fun(player: Player): Skill[] local EquipCard = Card:subclass("EquipCard") function EquipCard:initialize(name, suit, number) Card.initialize(self, name, suit, number) self.type = Card.TypeEquip self.equip_skill = nil + self.equip_skills = nil + self.dynamicEquipSkills = nil end ---@param room Room ---@param player Player function EquipCard:onInstall(room, player) - if self.equip_skill then - room:handleAddLoseSkills(player, self.equip_skill.name, nil, false, true) + local equipSkills = self:getEquipSkills(player) + if #equipSkills > 0 then + local noTrigger = table.filter(equipSkills, function(skill) return skill.attached_equip end) + if #noTrigger > 0 then + noTrigger = table.map(noTrigger, function(skill) return skill.name end) + room:handleAddLoseSkills(player, table.concat(noTrigger, "|"), nil, false, true) + end + + local toTrigger = table.filter(equipSkills, function(skill) return not skill.attached_equip end) + if #toTrigger > 0 then + toTrigger = table.map(toTrigger, function(skill) return skill.name end) + room:handleAddLoseSkills(player, table.concat(toTrigger, "|"), nil, false) + end end end ---@param room Room ---@param player Player function EquipCard:onUninstall(room, player) - if self.equip_skill then - room:handleAddLoseSkills(player, "-" .. self.equip_skill.name, nil, false, true) + local equipSkills = self:getEquipSkills(player) + if #equipSkills > 0 then + local noTrigger = table.filter(equipSkills, function(skill) return skill.attached_equip end) + if #noTrigger > 0 then + noTrigger = table.map(noTrigger, function(skill) return '-' .. skill.name end) + room:handleAddLoseSkills(player, table.concat(noTrigger, "|"), nil, false, true) + end + + local toTrigger = table.filter(equipSkills, function(skill) return not skill.attached_equip end) + if #toTrigger > 0 then + toTrigger = table.map(toTrigger, function(skill) return '-' .. skill.name end) + room:handleAddLoseSkills(player, table.concat(toTrigger, "|"), nil, false) + end end end +---@param player Player +---@return Skill[] +function EquipCard:getEquipSkills(player) + if self.dynamicEquipSkills then + local equipSkills = self:dynamicEquipSkills(player) + if equipSkills and #equipSkills > 0 then + return equipSkills + end + end + + if self.equip_skills then + return self.equip_skills + elseif self.equip_skill then + return { self.equip_skill } + end + + return {} +end + function EquipCard:clone(suit, number) local ret = Card.clone(self, suit, number) ret.equip_skill = self.equip_skill + ret.equip_skills = self.equip_skills + ret.dynamicEquipSkills = self.dynamicEquipSkills ret.onInstall = self.onInstall ret.onUninstall = self.onUninstall return ret @@ -36,6 +83,7 @@ end ---@class Weapon : EquipCard ---@field public attack_range integer +---@field public dynamicAttackRange? fun(player: Player): int local Weapon = EquipCard:subclass("Weapon") function Weapon:initialize(name, suit, number, attackRange) @@ -47,9 +95,21 @@ end function Weapon:clone(suit, number) local ret = EquipCard.clone(self, suit, number) ret.attack_range = self.attack_range + ret.dynamicAttackRange = self.dynamicAttackRange return ret end +function Weapon:getAttackRange(player) + if type(self.dynamicAttackRange) == "function" then + local currentAttackRange = self:dynamicAttackRange(player) + if currentAttackRange then + return currentAttackRange + end + end + + return self.attack_range +end + ---@class Armor : EquipCard local Armor = EquipCard:subclass("armor") diff --git a/lua/core/engine.lua b/lua/core/engine.lua index a822c882..17044b41 100644 --- a/lua/core/engine.lua +++ b/lua/core/engine.lua @@ -676,9 +676,10 @@ end --- --- 其实就是翻译了 ":" .. name 罢了 ---@param name string @ 要获得描述的名字 +---@param lang? string @ 要使用的语言,默认读取config ---@return string @ 描述 -function Engine:getDescription(name) - return self:translate(":" .. name) +function Engine:getDescription(name, lang) + return self:translate(":" .. name, lang) end return Engine diff --git a/lua/core/game_mode.lua b/lua/core/game_mode.lua index 2062a1ff..b74cf40d 100644 --- a/lua/core/game_mode.lua +++ b/lua/core/game_mode.lua @@ -71,4 +71,16 @@ function GameMode:countInFunc(room) return true end +-- 修改角色的属性 +---@param player ServerPlayer +---@return table @ 返回表,键为调整的角色属性,值为调整后的属性 +function GameMode:getAdjustedProperty (player) + local list = {} + if player.role == "lord" and player.role_shown and #player.room.players > 4 then + list.hp = player.hp + 1 + list.maxHp = player.maxHp + 1 + end + return list +end + return GameMode diff --git a/lua/core/player.lua b/lua/core/player.lua index 33d674e0..5a39332f 100644 --- a/lua/core/player.lua +++ b/lua/core/player.lua @@ -260,13 +260,7 @@ function Player:removeCards(playerArea, cardIds, specialName) if #fromAreaIds == 0 then break end - - if table.contains(fromAreaIds, id) then - table.removeOne(fromAreaIds, id) - -- FIXME: 为客户端移动id为-1的牌考虑,但总感觉有地方需要商讨啊! - elseif table.every(fromAreaIds, function(e) return e == -1 end) then - table.remove(fromAreaIds, 1) - elseif id == -1 then + if not table.removeOne(fromAreaIds, id) and not table.removeOne(fromAreaIds, -1) then table.remove(fromAreaIds, 1) end end @@ -455,7 +449,7 @@ function Player:getAttackRange() baseValue = 0 for _, id in ipairs(weapons) do local weapon = Fk:getCardById(id) - baseValue = math.max(baseValue, weapon.attack_range or 1) + baseValue = math.max(baseValue, weapon:getAttackRange(self) or 1) end end @@ -563,7 +557,7 @@ end --- 比较距离 ---@param other Player @ 终点角色 ---@param num integer @ 比较基准 ----@param operator string @ 运算符,有 ``"<"`` ``">"`` ``"<="`` ``">="`` ``"=="`` ``"~="`` +---@param operator "<"|">"|"<="|">="|"=="|"~=" @ 运算符 ---@return boolean @ 返回比较结果,不计入距离结果永远为false function Player:compareDistance(other, num, operator) local distance = self:distanceTo(other) @@ -596,6 +590,11 @@ function Player:inMyAttackRange(other, fixLimit) fixLimit = fixLimit or 0 local status_skills = Fk:currentRoom().status_skills[AttackRangeSkill] or Util.DummyTable + for _, skill in ipairs(status_skills) do + if skill:withoutAttackRange(self, other) then + return false + end + end for _, skill in ipairs(status_skills) do if skill:withinAttackRange(self, other) then return true @@ -609,7 +608,8 @@ end --- 获取下家。 ---@param ignoreRemoved? boolean @ 忽略被移除 ---@param num? integer @ 第几个,默认1 ----@return ServerPlayer +---@param ignoreRest? boolean @ 是否忽略休整 +---@return Player function Player:getNextAlive(ignoreRemoved, num, ignoreRest) if #Fk:currentRoom().alive_players == 0 then return self.rest > 0 and self.next.rest > 0 and self.next or self @@ -631,9 +631,9 @@ function Player:getNextAlive(ignoreRemoved, num, ignoreRest) end --- 获取上家。 ----@param ignoreRemoved boolean @ 忽略被移除 +---@param ignoreRemoved? boolean @ 忽略被移除 ---@param num? integer @ 第几个,默认1 ----@return ServerPlayer +---@return Player function Player:getLastAlive(ignoreRemoved, num) num = num or 1 local index = (ignoreRemoved and #Fk:currentRoom().alive_players or #table.filter(Fk:currentRoom().alive_players, function(p) return not p:isRemoved() end)) - num @@ -1019,9 +1019,11 @@ function Player:prohibitReveal(isDeputy) return false end ----@param to Player ----@param ignoreFromKong? boolean ----@param ignoreToKong? boolean +--- 判断能否拼点 +---@param to Player @ 拼点对象 +---@param ignoreFromKong? boolean @ 忽略发起者没有手牌 +---@param ignoreToKong? boolean @ 忽略对象没有手牌 +---@return boolean function Player:canPindian(to, ignoreFromKong, ignoreToKong) if self == to then return false end @@ -1073,6 +1075,10 @@ function Player:getSwitchSkillState(skillName, afterUse, inWord) end end +--- 是否能移动特定牌至特定角色 +---@param to Player @ 移动至的角色 +---@param id integer @ 移动的牌 +---@return boolean function Player:canMoveCardInBoardTo(to, id) if self == to then return false @@ -1094,6 +1100,11 @@ function Player:canMoveCardInBoardTo(to, id) end end +--- 是否能移动特定牌至特定角色 +--- @param to Player @ 移动至的角色 +--- @param flag? string @ 移动的区域,`e`为装备区,`j`为判定区,`nil`为装备区和判定区 +--- @param excludeIds? integer[] @ 排除的牌 +---@return boolean function Player:canMoveCardsInBoardTo(to, flag, excludeIds) if self == to then return false @@ -1120,6 +1131,9 @@ function Player:canMoveCardsInBoardTo(to, flag, excludeIds) return false end +--- 获取使命技状态 +---@param skillName string +---@return string? @ 存在返回`failed` or `succeed`,不存在返回`nil` function Player:getQuestSkillState(skillName) local questSkillState = self:getMark(MarkEnum.QuestSkillPreName .. skillName) return type(questSkillState) == "string" and questSkillState or nil diff --git a/lua/core/skill.lua b/lua/core/skill.lua index 3cc1b9e8..000699d7 100644 --- a/lua/core/skill.lua +++ b/lua/core/skill.lua @@ -90,8 +90,19 @@ function Skill:addRelatedSkill(skill) end --- 确认本技能是否为装备技能。 +---@param player Player ---@return boolean -function Skill:isEquipmentSkill() +function Skill:isEquipmentSkill(player) + if player then + local filterSkills = Fk:currentRoom().status_skills[FilterSkill] + for _, filter in ipairs(filterSkills) do + local result = filter:equipSkillFilter(self, player) + if result then + return true + end + end + end + return self.attached_equip and type(self.attached_equip) == 'string' and self.attached_equip ~= "" end @@ -126,4 +137,11 @@ function Skill:isSwitchSkill() return self.switchSkillName and type(self.switchSkillName) == 'string' and self.switchSkillName ~= "" end +--判断技能是否为角色技能 +---@param player Player +---@return boolean +function Skill:isPlayerSkill(player) + return not (self:isEquipmentSkill(player) or self.name:endsWith("&")) +end + return Skill diff --git a/lua/core/skill_type/active.lua b/lua/core/skill_type/active.lua index 9c5d13c9..0fdc04f9 100644 --- a/lua/core/skill_type/active.lua +++ b/lua/core/skill_type/active.lua @@ -21,47 +21,51 @@ function ActiveSkill:initialize(name, frequency) end --------- --- Note: these functions are used both client and ai +-- 注:客户端函数,AI也会调用以作主动技判断 ------- { ---- Determine whether the skill can be used in playing phase ----@param player Player ----@param card Card @ helper +-- 判断该技能是否可主动发动 +---@param player Player @ 使用者 +---@param card Card @ 牌 +---@param extra_data UseExtraData @ 额外数据 +---@return bool function ActiveSkill:canUse(player, card, extra_data) return self:isEffectable(player) end ---- Determine whether a card can be selected by this skill ---- only used in skill of players ----@param to_select integer @ id of a card not selected ----@param selected integer[] @ ids of selected cards ----@param selected_targets integer[] @ ids of selected players +-- 判断一张牌是否可被此技能选中 +---@param to_select integer @ 待选牌 +---@param selected integer[] @ 已选牌 +---@param selected_targets integer[] @ 已选目标 +---@return bool function ActiveSkill:cardFilter(to_select, selected, selected_targets) return true end ---- Determine whether a target can be selected by this skill ---- only used in skill of players ----@param to_select integer @ id of the target ----@param selected integer[] @ ids of selected targets ----@param selected_cards integer[] @ ids of selected cards ----@param card Card @ helper ----@param extra_data? any @ extra_data +-- 判断一名角色是否可被此技能选中 +---@param to_select integer @ 待选目标 +---@param selected integer[] @ 已选目标 +---@param selected_cards integer[] @ 已选牌 +---@param card Card @ 牌 +---@param extra_data UseExtraData @ 额外数据 +---@return bool function ActiveSkill:targetFilter(to_select, selected, selected_cards, card, extra_data) return false end ---- Determine whether a target can be selected by this skill(in modifying targets) ---- only used in skill of players ----@param to_select integer @ id of the target ----@param selected? integer[] @ ids of selected targets ----@param user? integer @ id of the userdata ----@param card? Card @ helper ----@param distance_limited? boolean @ is limited by distance +-- 判断一名角色是否可成为此技能的目标 +---@param to_select integer @ 待选目标 +---@param selected integer[] @ 已选目标 +---@param user? integer @ 使用者 +---@param card? Card @ 牌 +---@param distance_limited? boolean @ 是否受距离限制 +---@return bool function ActiveSkill:modTargetFilter(to_select, selected, user, card, distance_limited) return false end +-- 获得技能的最小目标数 +---@return number @ 最小目标数 function ActiveSkill:getMinTargetNum() local ret if self.target_num then ret = self.target_num @@ -78,6 +82,10 @@ function ActiveSkill:getMinTargetNum() end end +-- 获得技能的最大目标数 +---@param player Player @ 使用者 +---@param card Card @ 牌 +---@return number @ 最大目标数 function ActiveSkill:getMaxTargetNum(player, card) local ret if self.target_num then ret = self.target_num @@ -100,6 +108,8 @@ function ActiveSkill:getMaxTargetNum(player, card) return ret end +-- 获得技能的最小卡牌数 +---@return number @ 最小卡牌数 function ActiveSkill:getMinCardNum() local ret if self.card_num then ret = self.card_num @@ -116,6 +126,8 @@ function ActiveSkill:getMinCardNum() end end +-- 获得技能的最大卡牌数 +---@return number @ 最大卡牌数 function ActiveSkill:getMaxCardNum() local ret if self.card_num then ret = self.card_num @@ -132,6 +144,11 @@ function ActiveSkill:getMaxCardNum() end end +-- 获得技能的距离限制 +---@param player Player @ 使用者 +---@param card Card @ 使用卡牌 +---@param to Player @ 目标 +---@return number @ 距离限制 function ActiveSkill:getDistanceLimit(player, card, to) local ret = self.distance_limit or 0 local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable @@ -143,8 +160,14 @@ function ActiveSkill:getDistanceLimit(player, card, to) return ret end +-- 判断一个角色是否在技能的距离限制内 +---@param player Player @ 使用者 +---@param isattack bool @ 是否使用攻击距离 +---@param card Card @ 使用卡牌 +---@param to Player @ 目标 +---@return bool function ActiveSkill:withinDistanceLimit(player, isattack, card, to) - if to and to.dead then return false end + if not to or player:distanceTo(to) < 1 then return false end local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable if not card and self.name:endsWith("_skill") then card = Fk:cloneCard(self.name:sub(1, #self.name - 6)) @@ -174,7 +197,7 @@ function ActiveSkill:withinDistanceLimit(player, isattack, card, to) end return (isattack and player:inMyAttackRange(to)) or - (player:distanceTo(to) > 0 and player:distanceTo(to) <= self:getDistanceLimit(player, card, to)) or + (player:distanceTo(to) <= self:getDistanceLimit(player, card, to)) or hasMark(card, MarkEnum.BypassDistancesLimit, card_temp_suf) or hasMark(player, MarkEnum.BypassDistancesLimit, temp_suf) or hasMark(to, MarkEnum.BypassDistancesLimitTo, temp_suf) @@ -189,26 +212,32 @@ function ActiveSkill:withinDistanceLimit(player, isattack, card, to) -- end))) end ---- Determine if selected cards and targets are valid for this skill ---- If returns true, the OK button should be enabled ---- only used in skill of players - --- NOTE: don't reclaim it ----@param selected integer[] @ ids of selected players ----@param selected_cards integer[] @ ids of selected cards +-- 判断一个技能是否可发动(也就是确认键是否可点击) +-- 警告:没啥事别改 +---@param selected integer[] @ 已选目标 +---@param selected_cards integer[] @ 已选牌 +---@param player Player @ 使用者 +---@param card Card @ 牌 +---@return bool function ActiveSkill:feasible(selected, selected_cards, player, card) return #selected >= self:getMinTargetNum() and #selected <= self:getMaxTargetNum(player, card) and #selected_cards >= self:getMinCardNum() and #selected_cards <= self:getMaxCardNum() end +-- 使用技能时默认的烧条提示(一般会在主动使用时出现) +---@param selected_cards integer[] @ 已选牌 +---@param selected_targets integer[] @ 已选目标 +---@return string? +function ActiveSkill:prompt(selected_cards, selected_targets) return "" end + ------- } ---@param room Room ----@param cardUseEvent CardUseStruct +---@param cardUseEvent CardUseStruct | SkillEffectEvent function ActiveSkill:onUse(room, cardUseEvent) end ---@param room Room ----@param cardUseEvent CardUseStruct +---@param cardUseEvent CardUseStruct | SkillEffectEvent ---@param finished? bool function ActiveSkill:onAction(room, cardUseEvent, finished) end @@ -225,8 +254,4 @@ function ActiveSkill:onEffect(room, cardEffectEvent) end ---@param cardEffectEvent CardEffectEvent | SkillEffectEvent function ActiveSkill:onNullified(room, cardEffectEvent) end ----@param selected_cards integer[] @ ids of selected cards ----@param selected_targets integer[] @ ids of selected players -function ActiveSkill:prompt(selected_cards, selected_targets) return "" end - return ActiveSkill diff --git a/lua/core/skill_type/attack_range.lua b/lua/core/skill_type/attack_range.lua index aa48de04..66fe7925 100644 --- a/lua/core/skill_type/attack_range.lua +++ b/lua/core/skill_type/attack_range.lua @@ -15,8 +15,18 @@ function AttackRangeSkill:getFixed(from) return nil end +---@param from Player +---@param to Player +---@return boolean function AttackRangeSkill:withinAttackRange(from, to) return false end +---@param from Player +---@param to Player +---@return boolean +function AttackRangeSkill:withoutAttackRange(from, to) + return false +end + return AttackRangeSkill diff --git a/lua/core/skill_type/filter.lua b/lua/core/skill_type/filter.lua index 2538a2a8..13842e13 100644 --- a/lua/core/skill_type/filter.lua +++ b/lua/core/skill_type/filter.lua @@ -17,4 +17,11 @@ function FilterSkill:viewAs(card, player) return nil end +---@param skill Skill +---@param player Player +---@return string +function FilterSkill:equipSkillFilter(skill, player) + return nil +end + return FilterSkill diff --git a/lua/core/skill_type/usable_skill.lua b/lua/core/skill_type/usable_skill.lua index 7fbbb082..58e544ff 100644 --- a/lua/core/skill_type/usable_skill.lua +++ b/lua/core/skill_type/usable_skill.lua @@ -14,6 +14,12 @@ function UsableSkill:initialize(name, frequency) self.max_use_time = {9999, 9999, 9999, 9999} end +-- 获得技能的最大使用次数 +---@param player Player @ 使用者 +---@param scope integer @ 考察时机(默认为回合) +---@param card Card @ 卡牌 +---@param to Player @ 目标 +---@return number @ 最大使用次数 function UsableSkill:getMaxUseTime(player, scope, card, to) scope = scope or Player.HistoryTurn local ret = self.max_use_time[scope] @@ -26,18 +32,31 @@ function UsableSkill:getMaxUseTime(player, scope, card, to) return ret end +-- 判断一个角色是否在技能的次数限制内 +---@param player Player @ 使用者 +---@param scope integer @ 考察时机(默认为回合) +---@param card? Card @ 牌,若没有牌,则尝试制造一张虚拟牌 +---@param card_name? string @ 牌名 +---@param to any @ 目标 +---@return bool function UsableSkill:withinTimesLimit(player, scope, card, card_name, to) if to and to.dead then return false end scope = scope or Player.HistoryTurn local status_skills = Fk:currentRoom().status_skills[TargetModSkill] or Util.DummyTable - if not card and self.name:endsWith("_skill") then - card = Fk:cloneCard(self.name:sub(1, #self.name - 6)) + if not card then + if card_name then + card = Fk:cloneCard(card_name) + elseif self.name:endsWith("_skill") then + card = Fk:cloneCard(self.name:sub(1, #self.name - 6)) + end + end + if not card_name and card then + card_name = card.trueName end for _, skill in ipairs(status_skills) do if skill:bypassTimesCheck(player, self, scope, card, to) then return true end end - card_name = card_name or card.trueName local temp_suf = table.simpleClone(MarkEnum.TempMarkSuffix) local card_temp_suf = table.simpleClone(MarkEnum.CardTempMarkSuffix) diff --git a/lua/fk_ex.lua b/lua/fk_ex.lua index 12dc2d1e..1c1e6b5a 100644 --- a/lua/fk_ex.lua +++ b/lua/fk_ex.lua @@ -180,11 +180,11 @@ end ---@field public card_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_targets: integer[]): boolean? ---@field public target_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], selected_cards: integer[], card: Card, extra_data: any): boolean? ---@field public feasible? fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): boolean? ----@field public on_use? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct): boolean? ----@field public on_action? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct, finished: boolean): boolean? ----@field public about_to_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean? ----@field public on_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean? ----@field public on_nullified? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean? +---@field public on_use? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct | SkillEffectEvent): boolean? +---@field public on_action? fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct | SkillEffectEvent, finished: boolean): boolean? +---@field public about_to_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent | SkillEffectEvent): boolean? +---@field public on_effect? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent | SkillEffectEvent): boolean? +---@field public on_nullified? fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent | SkillEffectEvent): boolean? ---@field public mod_target_filter? fun(self: ActiveSkill, to_select: integer, selected: integer[], user: integer, card: Card, distance_limited: boolean): boolean? ---@field public prompt? string|fun(self: ActiveSkill, selected_cards: integer[], selected_targets: integer[]): string ---@field public interaction any @@ -335,12 +335,14 @@ end ---@field public correct_func? fun(self: AttackRangeSkill, from: Player, to: Player): number? ---@field public fixed_func? fun(self: AttackRangeSkill, player: Player): number? ---@field public within_func? fun(self: AttackRangeSkill, from: Player, to: Player): boolean? +---@field public without_func? fun(self: AttackRangeSkill, from: Player, to: Player): boolean? ---@param spec AttackRangeSpec ---@return AttackRangeSkill function fk.CreateAttackRangeSkill(spec) assert(type(spec.name) == "string") - assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function" or type(spec.within_func) == "function") + assert(type(spec.correct_func) == "function" or type(spec.fixed_func) == "function" or + type(spec.within_func) == "function" or type(spec.without_func) == "function") local skill = AttackRangeSkill:new(spec.name) readStatusSpecToSkill(skill, spec) @@ -353,6 +355,9 @@ function fk.CreateAttackRangeSkill(spec) if spec.within_func then skill.withinAttackRange = spec.within_func end + if spec.without_func then + skill.withoutAttackRange = spec.without_func + end return skill end @@ -417,6 +422,7 @@ end ---@class FilterSpec: StatusSkillSpec ---@field public card_filter? fun(self: FilterSkill, card: Card, player: Player, isJudgeEvent: boolean): boolean? ---@field public view_as? fun(self: FilterSkill, card: Card, player: Player): Card? +---@field public equip_skill_filter? fun(self: FilterSkill, skill: Skill, player: Player): string? ---@param spec FilterSpec ---@return FilterSkill @@ -427,6 +433,7 @@ function fk.CreateFilterSkill(spec) readStatusSpecToSkill(skill, spec) skill.cardFilter = spec.card_filter skill.viewAs = spec.view_as + skill.equipSkillFilter = spec.equip_skill_filter return skill end @@ -526,7 +533,20 @@ function fk.CreateDelayedTrickCard(spec) end local function readCardSpecToEquip(card, spec) - card.equip_skill = spec.equip_skill + if spec.equip_skill then + if spec.equip_skill.class and spec.equip_skill:isInstanceOf(Skill) then + card.equip_skill = spec.equip_skill + card.equip_skills = { spec.equip_skill } + else + card.equip_skill = spec.equip_skill[1] + card.equip_skills = spec.equip_skill + end + end + + if spec.dynamic_equip_skills then + assert(type(spec.dynamic_equip_skills) == "function") + card.dynamicEquipSkills = spec.dynamic_equip_skills + end if spec.on_install then card.onInstall = spec.on_install end if spec.on_uninstall then card.onUninstall = spec.on_uninstall end @@ -543,6 +563,11 @@ function fk.CreateWeapon(spec) local card = Weapon:new(spec.name, spec.suit, spec.number, spec.attack_range) readCardSpecToCard(card, spec) readCardSpecToEquip(card, spec) + if spec.dynamic_attack_range then + assert(type(spec.dynamic_attack_range) == "function") + card.dynamicAttackRange = spec.dynamic_attack_range + end + return card end @@ -610,6 +635,10 @@ function fk.CreateGameMode(spec) assert(type(spec.is_counted) == "function") ret.countInFunc = spec.is_counted end + if spec.get_adjusted then + assert(type(spec.get_adjusted) == "function") + ret.getAdjustedProperty = spec.get_adjusted + end return ret end diff --git a/lua/freekill.lua b/lua/freekill.lua index c0e04992..1ab4c6e6 100644 --- a/lua/freekill.lua +++ b/lua/freekill.lua @@ -4,8 +4,7 @@ -- 向Lua虚拟机中加载库、游戏中的类,以及加载Mod等等。 -- 加载第三方库 -package.path = package.path .. ";./lua/lib/?.lua" - .. ";./lua/?.lua" +package.path = "./?.lua;./?/init.lua;./lua/lib/?.lua;./lua/?.lua" -- middleclass: 轻量级的面向对象库 class = require "middleclass" @@ -62,7 +61,9 @@ UI = require "ui-util" -- 读取配置文件。 -- 因为io马上就要被禁用了,所以赶紧先在这里读取配置文件。 local function loadConf() - local cfg = io.open("freekill.client.config.json") + local new_core = FileIO.pwd():endsWith("packages/freekill-core") + + local cfg = io.open((new_core and "../../" or "") .. "freekill.client.config.json") local ret if cfg == nil then ret = { diff --git a/lua/server/events/death.lua b/lua/server/events/death.lua index 884b775b..84ff8d5a 100644 --- a/lua/server/events/death.lua +++ b/lua/server/events/death.lua @@ -1,6 +1,8 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -GameEvent.functions[GameEvent.Dying] = function(self) +---@class GameEvent.Dying : GameEvent +local Dying = GameEvent:subclass("GameEvent.Dying") +function Dying:main() local dyingStruct = table.unpack(self.data) local room = self.room local logic = room.logic @@ -27,7 +29,7 @@ GameEvent.functions[GameEvent.Dying] = function(self) end end -GameEvent.exit_funcs[GameEvent.Dying] = function(self) +function Dying:exit() local room = self.room local logic = room.logic local dyingStruct = self.data[1] @@ -41,7 +43,9 @@ GameEvent.exit_funcs[GameEvent.Dying] = function(self) logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct, self.interrupted) end -GameEvent.prepare_funcs[GameEvent.Death] = function(self) +---@class GameEvent.Death : GameEvent +local Death = GameEvent:subclass("GameEvent.Death") +function Death:prepare() local deathStruct = table.unpack(self.data) local room = self.room local victim = room:getPlayerById(deathStruct.who) @@ -50,7 +54,7 @@ GameEvent.prepare_funcs[GameEvent.Death] = function(self) end end -GameEvent.functions[GameEvent.Death] = function(self) +function Death:main() local deathStruct = table.unpack(self.data) local room = self.room local victim = room:getPlayerById(deathStruct.who) @@ -99,7 +103,9 @@ GameEvent.functions[GameEvent.Death] = function(self) logic:trigger(fk.Deathed, victim, deathStruct) end -GameEvent.functions[GameEvent.Revive] = function(self) +---@class GameEvent.Revive : GameEvent +local Revive = GameEvent:subclass("GameEvent.Revive") +function Revive:main() local room = self.room local player, sendLog, reason = table.unpack(self.data) @@ -118,3 +124,5 @@ GameEvent.functions[GameEvent.Revive] = function(self) reason = reason or "" room.logic:trigger(fk.AfterPlayerRevived, player, { reason = reason }) end + +return { Dying, Death, Revive } diff --git a/lua/server/events/gameflow.lua b/lua/server/events/gameflow.lua index de520a9e..7da6960b 100644 --- a/lua/server/events/gameflow.lua +++ b/lua/server/events/gameflow.lua @@ -47,7 +47,9 @@ local function discardInit(room, player) end end -GameEvent.functions[GameEvent.DrawInitial] = function(self) +---@class GameEvent.DrawInitial : GameEvent +local DrawInitial = GameEvent:subclass("GameEvent.DrawInitial") +function DrawInitial:main() local room = self.room local luck_data = { @@ -81,6 +83,7 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self) room:setTag("LuckCardData", luck_data) room:notifyMoveFocus(room.alive_players, "AskForLuckCard") room:doBroadcastNotify("AskForLuckCard", room.settings.luckTime or 4) + room.room:setRequestTimer(room.timeout * 1000 + 1000) local remainTime = room.timeout + 1 local currentTime = os.time() @@ -123,6 +126,8 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self) coroutine.yield("__handleRequest", (remainTime - elapsed) * 1000) end + room.room:destroyRequestTimer() + for _, player in ipairs(room.alive_players) do local draw_data = luck_data[player.id] draw_data.luckTime = nil @@ -132,10 +137,23 @@ GameEvent.functions[GameEvent.DrawInitial] = function(self) room:removeTag("LuckCardData") end -GameEvent.functions[GameEvent.Round] = function(self) +---@class GameEvent.Round : GameEvent +local Round = GameEvent:subclass("GameEvent.Round") + +function Round:action() + local room = self.room + local p + repeat + p = room.current + GameEvent.Turn:create(p):exec() + if room.game_finished then break end + room.current = room.current:getNextAlive(true, nil, true) + until p.seat >= p:getNextAlive(true, nil, true).seat +end + +function Round:main() local room = self.room local logic = room.logic - local p local isFirstRound = room:getTag("FirstRound") if isFirstRound then @@ -160,18 +178,11 @@ GameEvent.functions[GameEvent.Round] = function(self) end logic:trigger(fk.RoundStart, room.current) - - repeat - p = room.current - GameEvent(GameEvent.Turn, p):exec() - if room.game_finished then break end - room.current = room.current:getNextAlive(true, nil, true) - until p.seat >= p:getNextAlive(true, nil, true).seat - + self:action() logic:trigger(fk.RoundEnd, p) end -GameEvent.cleaners[GameEvent.Round] = function(self) +function Round:clear() local room = self.room for _, p in ipairs(room.players) do @@ -198,7 +209,9 @@ GameEvent.cleaners[GameEvent.Round] = function(self) end end -GameEvent.prepare_funcs[GameEvent.Turn] = function(self) +---@class GameEvent.Turn : GameEvent +local Turn = GameEvent:subclass("GameEvent.Turn") +function Turn:prepare() local room = self.room local logic = room.logic local player = room.current @@ -224,7 +237,7 @@ GameEvent.prepare_funcs[GameEvent.Turn] = function(self) return logic:trigger(fk.BeforeTurnStart, player) end -GameEvent.functions[GameEvent.Turn] = function(self) +function Turn:main() local room = self.room room.current.phase = Player.PhaseNone room.logic:trigger(fk.TurnStart, room.current) @@ -232,7 +245,7 @@ GameEvent.functions[GameEvent.Turn] = function(self) room.current:play() end -GameEvent.cleaners[GameEvent.Turn] = function(self) +function Turn:clear() local room = self.room local current = room.current @@ -280,7 +293,9 @@ GameEvent.cleaners[GameEvent.Turn] = function(self) end end -GameEvent.functions[GameEvent.Phase] = function(self) +---@class GameEvent.Phase : GameEvent +local Phase = GameEvent:subclass("GameEvent.Phase") +function Phase:main() local room = self.room local logic = room.logic @@ -373,7 +388,7 @@ GameEvent.functions[GameEvent.Phase] = function(self) end end -GameEvent.cleaners[GameEvent.Phase] = function(self) +function Phase:clear() local room = self.room local player = self.data[1] local logic = room.logic @@ -408,3 +423,5 @@ GameEvent.cleaners[GameEvent.Phase] = function(self) room:broadcastProperty(p, "MaxCards") end end + +return { DrawInitial, Round, Turn, Phase } diff --git a/lua/server/events/hp.lua b/lua/server/events/hp.lua index 15fb92e8..543bbf9d 100644 --- a/lua/server/events/hp.lua +++ b/lua/server/events/hp.lua @@ -31,7 +31,9 @@ local function sendDamageLog(room, damageStruct) }) end -GameEvent.functions[GameEvent.ChangeHp] = function(self) +---@class GameEvent.ChangeHp : GameEvent +local ChangeHp = GameEvent:subclass("GameEvent.ChangeHp") +function ChangeHp:main() local player, num, reason, skillName, damageStruct = table.unpack(self.data) local room = self.room local logic = room.logic @@ -112,17 +114,21 @@ GameEvent.functions[GameEvent.ChangeHp] = function(self) return true end -GameEvent.functions[GameEvent.Damage] = function(self) +---@class GameEvent.Damage : GameEvent +local Damage = GameEvent:subclass("GameEvent.Damage") +function Damage:main() local damageStruct = table.unpack(self.data) local room = self.room local logic = room.logic - if not damageStruct.chain and logic:damageByCardEffect(not not damageStruct.from) then + if not damageStruct.chain and logic:damageByCardEffect(false) then local cardEffectData = logic:getCurrentEvent():findParent(GameEvent.CardEffect) if cardEffectData then local cardEffectEvent = cardEffectData.data[1] damageStruct.damage = damageStruct.damage + (cardEffectEvent.additionalDamage or 0) - damageStruct.by_user = true + if damageStruct.from and cardEffectEvent.from == damageStruct.from.id then + damageStruct.by_user = true + end end end @@ -137,12 +143,14 @@ GameEvent.functions[GameEvent.Damage] = function(self) assert(damageStruct.to:isInstanceOf(ServerPlayer)) - local stages = { - {fk.PreDamage, "from"}, - } + local stages = {} if not damageStruct.isVirtualDMG then - table.insertTable(stages, { { fk.DamageCaused, "from" }, { fk.DamageInflicted, "to" } }) + stages = { + { fk.PreDamage, "from"}, + { fk.DamageCaused, "from" }, + { fk.DamageInflicted, "to" }, + } end for _, struct in ipairs(stages) do @@ -198,7 +206,7 @@ GameEvent.functions[GameEvent.Damage] = function(self) return true end -GameEvent.exit_funcs[GameEvent.Damage] = function(self) +function Damage:exit() local room = self.room local logic = room.logic local damageStruct = self.data[1] @@ -230,7 +238,9 @@ GameEvent.exit_funcs[GameEvent.Damage] = function(self) end end -GameEvent.functions[GameEvent.LoseHp] = function(self) +---@class GameEvent.LoseHp : GameEvent +local LoseHp = GameEvent:subclass("GameEvent.LoseHp") +function LoseHp:main() local player, num, skillName = table.unpack(self.data) local room = self.room local logic = room.logic @@ -258,7 +268,9 @@ GameEvent.functions[GameEvent.LoseHp] = function(self) return true end -GameEvent.functions[GameEvent.Recover] = function(self) +---@class GameEvent.Recover : GameEvent +local Recover = GameEvent:subclass("GameEvent.Recover") +function Recover:main() local recoverStruct = table.unpack(self.data) local room = self.room local logic = room.logic @@ -289,7 +301,9 @@ GameEvent.functions[GameEvent.Recover] = function(self) return true end -GameEvent.functions[GameEvent.ChangeMaxHp] = function(self) +---@class GameEvent.ChangeMaxHp : GameEvent +local ChangeMaxHp = GameEvent:subclass("GameEvent.ChangeMaxHp") +function ChangeMaxHp:main() local player, num = table.unpack(self.data) local room = self.room @@ -344,3 +358,5 @@ GameEvent.functions[GameEvent.ChangeMaxHp] = function(self) room.logic:trigger(fk.MaxHpChanged, player, { num = num }) return true end + +return { ChangeHp, Damage, LoseHp, Recover, ChangeMaxHp } diff --git a/lua/server/events/init.lua b/lua/server/events/init.lua index dbbe92b5..4575b665 100644 --- a/lua/server/events/init.lua +++ b/lua/server/events/init.lua @@ -5,51 +5,46 @@ -- 某类事件对应的结束事件,其id刚好就是那个事件的相反数 -- GameEvent.EventFinish = -1 -GameEvent.Game = 0 +local tmp +tmp = require "server.events.misc" +GameEvent.Game = tmp[1] +GameEvent.ChangeProperty = tmp[2] +GameEvent.ClearEvent = tmp[3] -GameEvent.ChangeHp = 1 -GameEvent.Damage = 2 -GameEvent.LoseHp = 3 -GameEvent.Recover = 4 -GameEvent.ChangeMaxHp = 5 -dofile "lua/server/events/hp.lua" +tmp = require "server.events.hp" +GameEvent.ChangeHp = tmp[1] +GameEvent.Damage = tmp[2] +GameEvent.LoseHp = tmp[3] +GameEvent.Recover = tmp[4] +GameEvent.ChangeMaxHp = tmp[5] -GameEvent.Dying = 6 -GameEvent.Death = 7 -GameEvent.Revive = 22 -dofile "lua/server/events/death.lua" +tmp = require "server.events.death" +GameEvent.Dying = tmp[1] +GameEvent.Death = tmp[2] +GameEvent.Revive = tmp[3] -GameEvent.MoveCards = 8 -dofile "lua/server/events/movecard.lua" +tmp = require "server.events.movecard" +GameEvent.MoveCards = tmp -GameEvent.UseCard = 9 -GameEvent.RespondCard = 10 -GameEvent.CardEffect = 20 -dofile "lua/server/events/usecard.lua" +tmp = require "server.events.usecard" +GameEvent.UseCard = tmp[1] +GameEvent.RespondCard = tmp[2] +GameEvent.CardEffect = tmp[3] -GameEvent.SkillEffect = 11 --- GameEvent.AddSkill = 12 --- GameEvent.LoseSkill = 13 -dofile "lua/server/events/skill.lua" +tmp = require "server.events.skill" +GameEvent.SkillEffect = tmp -GameEvent.Judge = 14 -dofile "lua/server/events/judge.lua" +tmp = require "server.events.judge" +GameEvent.Judge = tmp -GameEvent.DrawInitial = 15 -GameEvent.Round = 16 -GameEvent.Turn = 17 -GameEvent.Phase = 18 -dofile "lua/server/events/gameflow.lua" +tmp = require "server.events.gameflow" +GameEvent.DrawInitial = tmp[1] +GameEvent.Round = tmp[2] +GameEvent.Turn = tmp[3] +GameEvent.Phase = tmp[4] -GameEvent.Pindian = 19 -dofile "lua/server/events/pindian.lua" - --- 20 = CardEffect -GameEvent.ChangeProperty = 21 - --- 新的clear函数专用 -GameEvent.ClearEvent = 9999 -dofile "lua/server/events/misc.lua" +tmp = require "server.events.pindian" +GameEvent.Pindian = tmp for _, l in ipairs(Fk._custom_events) do local name, p, m, c, e = l.name, l.p, l.m, l.c, l.e @@ -58,37 +53,3 @@ for _, l in ipairs(Fk._custom_events) do GameEvent.cleaners[name] = c GameEvent.exit_funcs[name] = e end - -local eventTranslations = { - [GameEvent.Game] = "GameEvent.Game", - - [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.Revive] = "GameEvent.Revive", - [GameEvent.MoveCards] = "GameEvent.MoveCards", - [GameEvent.UseCard] = "GameEvent.UseCard", - [GameEvent.RespondCard] = "GameEvent.RespondCard", - [GameEvent.CardEffect] = "GameEvent.CardEffect", - [GameEvent.SkillEffect] = "GameEvent.SkillEffect", - [GameEvent.Judge] = "GameEvent.Judge", - [GameEvent.DrawInitial] = "GameEvent.DrawInitial", - [GameEvent.Round] = "GameEvent.Round", - [GameEvent.Turn] = "GameEvent.Turn", - [GameEvent.Phase] = "GameEvent.Phase", - [GameEvent.Pindian] = "GameEvent.Pindian", - - [GameEvent.ChangeProperty] = "GameEvent.ChangeProperty", - - [GameEvent.ClearEvent] = "GameEvent.ClearEvent", -} - -function GameEvent.static:translate(id) - local ret = eventTranslations[id] - if not ret then ret = id end - return ret -end diff --git a/lua/server/events/judge.lua b/lua/server/events/judge.lua index 86a59f9f..b561be9b 100644 --- a/lua/server/events/judge.lua +++ b/lua/server/events/judge.lua @@ -1,6 +1,8 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -GameEvent.functions[GameEvent.Judge] = function(self) +---@class GameEvent.Judge : GameEvent +local Judge = GameEvent:subclass("GameEvent.Judge") +function Judge:main() local data = table.unpack(self.data) local room = self.room local logic = room.logic @@ -53,7 +55,7 @@ GameEvent.functions[GameEvent.Judge] = function(self) end end -GameEvent.cleaners[GameEvent.Judge] = function(self) +function Judge:clear() local data = table.unpack(self.data) local room = self.room if (self.interrupted or not data.skipDrop) and room:getCardArea(data.card.id) == Card.Processing then @@ -71,3 +73,5 @@ GameEvent.cleaners[GameEvent.Judge] = function(self) end }) end + +return Judge diff --git a/lua/server/events/misc.lua b/lua/server/events/misc.lua index db18d589..f6e42f16 100644 --- a/lua/server/events/misc.lua +++ b/lua/server/events/misc.lua @@ -1,10 +1,14 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -GameEvent.functions[GameEvent.Game] = function(self) +---@class GameEvent.Game : GameEvent +local Game = GameEvent:subclass("GameEvent.Game") +function Game:main() self.room.logic:run() end -GameEvent.functions[GameEvent.ChangeProperty] = function(self) +---@class GameEvent.ChangeProperty : GameEvent +local ChangeProperty = GameEvent:subclass("GameEvent.Game") +function ChangeProperty:main() local data = table.unpack(self.data) local room = self.room local player = data.from @@ -125,12 +129,14 @@ GameEvent.functions[GameEvent.ChangeProperty] = function(self) logic:trigger(fk.AfterPropertyChange, player, data) end -GameEvent.functions[GameEvent.ClearEvent] = function(self) +---@class GameEvent.ClearEvent : GameEvent +local ClearEvent = GameEvent:subclass("GameEvent.ClearEvent") +function ClearEvent:main() local event = self.data[1] local logic = self.room.logic -- 不可中断 - Pcall(event.clear_func, event) - for _, f in ipairs(event.extra_clear_funcs) do + Pcall(event.clear, event) + for _, f in ipairs(event.extra_clear) do if type(f) == "function" then Pcall(f, event) end end @@ -147,3 +153,5 @@ GameEvent.functions[GameEvent.ClearEvent] = function(self) logic.game_event_stack:pop() logic.cleaner_stack:pop() end + +return { Game, ChangeProperty, ClearEvent } diff --git a/lua/server/events/movecard.lua b/lua/server/events/movecard.lua index 7f03cd2d..e6c4a2c3 100644 --- a/lua/server/events/movecard.lua +++ b/lua/server/events/movecard.lua @@ -1,6 +1,8 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -GameEvent.functions[GameEvent.MoveCards] = function(self) +---@class GameEvent.MoveCards : GameEvent +local MoveCards = GameEvent:subclass("GameEvent.MoveCards") +function MoveCards:main() local args = self.data local room = self.room ---@type CardsMoveStruct[] @@ -57,6 +59,7 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) specialVisible = cardsMoveInfo.specialVisible, drawPilePosition = cardsMoveInfo.drawPilePosition, moveMark = cardsMoveInfo.moveMark, + visiblePlayers = cardsMoveInfo.visiblePlayers, } table.insert(cardsMoveStructs, cardsMoveStruct) @@ -69,10 +72,11 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) from = cardsMoveInfo.from, toArea = Card.DiscardPile, moveReason = fk.ReasonPutIntoDiscardPile, - specialName = cardsMoveInfo.specialName, - specialVisible = cardsMoveInfo.specialVisible, - drawPilePosition = cardsMoveInfo.drawPilePosition, - moveMark = cardsMoveInfo.moveMark, + moveVisible = true, + --specialName = cardsMoveInfo.specialName, + --specialVisible = cardsMoveInfo.specialVisible, + --drawPilePosition = cardsMoveInfo.drawPilePosition, + --moveMark = cardsMoveInfo.moveMark, } table.insert(cardsMoveStructs, cardsMoveStruct) @@ -159,7 +163,7 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) realFromArea == Player.Equip and beforeCard.type == Card.TypeEquip and data.from ~= nil and - beforeCard.equip_skill + #beforeCard:getEquipSkills(room:getPlayerById(data.from)) > 0 then beforeCard:onUninstall(room, room:getPlayerById(data.from)) end @@ -183,15 +187,20 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) end end if data.moveMark then - local mark = table.clone(data.moveMark) or {"", 0} - room:setCardMark(currentCard, mark[1], mark[2]) + local mark = data.moveMark + if type(mark) == "string" then + room:setCardMark(currentCard, mark, 1) + elseif type(mark) == "table" then + mark = table.clone(data.moveMark) or {"", 0} + room:setCardMark(currentCard, mark[1], mark[2]) + end end if data.toArea == Player.Equip and currentCard.type == Card.TypeEquip and data.to ~= nil and room:getPlayerById(data.to):isAlive() and - currentCard.equip_skill + #currentCard:getEquipSkills(room:getPlayerById(data.to)) > 0 then currentCard:onInstall(room, room:getPlayerById(data.to)) end @@ -202,3 +211,5 @@ GameEvent.functions[GameEvent.MoveCards] = function(self) room.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs) return true end + +return MoveCards diff --git a/lua/server/events/pindian.lua b/lua/server/events/pindian.lua index 0982a373..64c1bec1 100644 --- a/lua/server/events/pindian.lua +++ b/lua/server/events/pindian.lua @@ -1,6 +1,8 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -GameEvent.functions[GameEvent.Pindian] = function(self) +---@class GameEvent.Pindian : GameEvent +local Pindian = GameEvent:subclass("GameEvent.Pindian") +function Pindian:main() local pindianData = table.unpack(self.data) local room = self.room local logic = room.logic @@ -35,6 +37,7 @@ GameEvent.functions[GameEvent.Pindian] = function(self) pindianCard:addSubcard(_pindianCard.id) pindianData.fromCard = pindianCard + pindianData._fromCard = _pindianCard table.insert(moveInfos, { ids = { _pindianCard.id }, @@ -53,6 +56,7 @@ GameEvent.functions[GameEvent.Pindian] = function(self) pindianCard:addSubcard(_pindianCard.id) pindianData.results[to.id].toCard = pindianCard + pindianData.results[to.id]._toCard = _pindianCard table.insert(moveInfos, { ids = { _pindianCard.id }, @@ -86,9 +90,11 @@ GameEvent.functions[GameEvent.Pindian] = function(self) if p == pindianData.from then pindianData.fromCard = pindianCard + pindianData._fromCard = _pindianCard else pindianData.results[p.id] = pindianData.results[p.id] or {} pindianData.results[p.id].toCard = pindianCard + pindianData.results[p.id]._toCard = _pindianCard end table.insert(moveInfos, { @@ -109,10 +115,21 @@ GameEvent.functions[GameEvent.Pindian] = function(self) room:moveCards(table.unpack(moveInfos)) + room:sendFootnote({ pindianData._fromCard.id }, { + type = "##PindianCard", + from = pindianData.from.id, + }) + for _, to in ipairs(pindianData.tos) do + room:sendFootnote({ pindianData.results[to.id]._toCard.id }, { + type = "##PindianCard", + from = to.id, + }) + end + logic:trigger(fk.PindianCardsDisplayed, nil, pindianData) - for toId, result in pairs(pindianData.results) do - local to = room:getPlayerById(toId) + for _, to in ipairs(pindianData.tos) do + local result = pindianData.results[to.id] if pindianData.fromCard.number > result.toCard.number then result.winner = pindianData.from elseif pindianData.fromCard.number < result.toCard.number then @@ -131,9 +148,13 @@ GameEvent.functions[GameEvent.Pindian] = function(self) room:sendLog{ type = "#ShowPindianResult", from = pindianData.from.id, - to = { toId }, + to = { to.id }, arg = result.winner == pindianData.from and "pindianwin" or "pindiannotwin" } + + -- room:setCardEmotion(pindianData._fromCard.id, result.winner == pindianData.from and "pindianwin" or "pindiannotwin") + -- room:setCardEmotion(pindianData.results[to.id]._toCard.id, result.winner == to and "pindianwin" or "pindiannotwin") + logic:trigger(fk.PindianResultConfirmed, nil, singlePindianData) end @@ -142,7 +163,7 @@ GameEvent.functions[GameEvent.Pindian] = function(self) end end -GameEvent.cleaners[GameEvent.Pindian] = function(self) +function Pindian:clear() local pindianData = table.unpack(self.data) local room = self.room @@ -168,3 +189,5 @@ GameEvent.cleaners[GameEvent.Pindian] = function(self) end if not self.interrupted then return end end + +return Pindian diff --git a/lua/server/events/skill.lua b/lua/server/events/skill.lua index 1d3c304d..df4ffea4 100644 --- a/lua/server/events/skill.lua +++ b/lua/server/events/skill.lua @@ -1,6 +1,8 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -GameEvent.functions[GameEvent.SkillEffect] = function(self) +---@class GameEvent.SkillEffect : GameEvent +local SkillEffect = GameEvent:subclass("GameEvent.SkillEffect") +function SkillEffect:main() local effect_cb, player, _skill = table.unpack(self.data) local room = self.room local logic = room.logic @@ -19,3 +21,5 @@ GameEvent.functions[GameEvent.SkillEffect] = function(self) logic:trigger(fk.AfterSkillEffect, player, skill) return ret end + +return SkillEffect diff --git a/lua/server/events/usecard.lua b/lua/server/events/usecard.lua index 7796b080..4e79b134 100644 --- a/lua/server/events/usecard.lua +++ b/lua/server/events/usecard.lua @@ -162,7 +162,9 @@ local sendCardEmotionAndLog = function(room, cardUseEvent) return _card end -GameEvent.functions[GameEvent.UseCard] = function(self) +---@class GameEvent.UseCard : GameEvent +local UseCard = GameEvent:subclass("GameEvent.UseCard") +function UseCard:main() local cardUseEvent = table.unpack(self.data) local room = self.room local logic = room.logic @@ -185,6 +187,27 @@ GameEvent.functions[GameEvent.UseCard] = function(self) cardUseEvent.card.skill:onUse(room, cardUseEvent) end + if cardUseEvent.card.type == Card.TypeEquip then + local targets = TargetGroup:getRealTargets(cardUseEvent.tos) + if #targets == 1 then + local target = room:getPlayerById(targets[1]) + local subType = cardUseEvent.card.sub_type + local equipsExist = target:getEquipments(subType) + + if #equipsExist > 0 and not target:hasEmptyEquipSlot(subType) then + local choices = table.map( + equipsExist, + function(id, index) + return "#EquipmentChoice:" .. index .. "::" .. Fk:translate(Fk:getCardById(id).name) end + ) + if target:hasEmptyEquipSlot(subType) then + table.insert(choices, target:getAvailableEquipSlots(subType)[1]) + end + cardUseEvent.toPutSlot = room:askForChoice(target, choices, "replace_equip", "#GameRuleReplaceEquipment") + end + end + end + if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then logic:breakEvent() end @@ -238,7 +261,7 @@ GameEvent.functions[GameEvent.UseCard] = function(self) end end -GameEvent.cleaners[GameEvent.UseCard] = function(self) +function UseCard:clear() local cardUseEvent = table.unpack(self.data) local room = self.room @@ -254,7 +277,9 @@ GameEvent.cleaners[GameEvent.UseCard] = function(self) end end -GameEvent.functions[GameEvent.RespondCard] = function(self) +---@class GameEvent.RespondCard : GameEvent +local RespondCard = GameEvent:subclass("GameEvent.RespondCard") +function RespondCard:main() local cardResponseEvent = table.unpack(self.data) local room = self.room local logic = room.logic @@ -306,7 +331,7 @@ GameEvent.functions[GameEvent.RespondCard] = function(self) logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) end -GameEvent.cleaners[GameEvent.RespondCard] = function(self) +function RespondCard:clear() local cardResponseEvent = table.unpack(self.data) local room = self.room @@ -322,7 +347,9 @@ GameEvent.cleaners[GameEvent.RespondCard] = function(self) end end -GameEvent.functions[GameEvent.CardEffect] = function(self) +---@class GameEvent.CardEffect : GameEvent +local CardEffect = GameEvent:subclass("GameEvent.CardEffect") +function CardEffect:main() local cardEffectEvent = table.unpack(self.data) local room = self.room local logic = room.logic @@ -359,13 +386,16 @@ GameEvent.functions[GameEvent.CardEffect] = function(self) end logic:breakEvent() end - elseif cardEffectEvent.to and logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then - cardEffectEvent.nullifiedTargets = cardEffectEvent.nullifiedTargets or {} - table.insert(cardEffectEvent.nullifiedTargets, cardEffectEvent.to) - + elseif logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then + if cardEffectEvent.to then + cardEffectEvent.nullifiedTargets = cardEffectEvent.nullifiedTargets or {} + table.insert(cardEffectEvent.nullifiedTargets, cardEffectEvent.to) + end logic:breakEvent() end room:handleCardEffect(event, cardEffectEvent) end end + +return { UseCard, RespondCard, CardEffect } diff --git a/lua/server/gameevent.lua b/lua/server/gameevent.lua index 1bc91287..0c9fc5a9 100644 --- a/lua/server/gameevent.lua +++ b/lua/server/gameevent.lua @@ -4,15 +4,11 @@ ---@field public id integer @ 事件的id,随着时间推移自动增加并分配给新事件 ---@field public end_id integer @ 事件的对应结束id,如果整个事件中未插入事件,那么end_id就是自己的id ---@field public room Room @ room实例 ----@field public event integer @ 该事件对应的EventType +---@field public event GameEvent @ 该事件对应的EventType,现已改为对应的class ---@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)[] @ 事件结束时执行的自定义函数列表 ----@field public exit_func fun(self: GameEvent) @ 事件结束后执行的函数 ----@field public extra_exit_funcs fun(self:GameEvent)[] @ 事件结束后执行的自定义函数 +---@field public extra_clear fun(self:GameEvent)[] @ 事件结束时执行的自定义函数列表 +---@field public extra_exit fun(self:GameEvent)[] @ 事件结束后执行的自定义函数 ---@field public exec_ret boolean? @ exec函数的返回值,可能不存在 ---@field public status string @ ready, running, exiting, dead ---@field public interrupted boolean @ 事件是否是因为被中断而结束的,可能是防止事件或者被杀 @@ -31,61 +27,96 @@ GameEvent.cleaners = {} ---@type (fun(self: GameEvent): bool)[] GameEvent.exit_funcs = {} -local function wrapCoFunc(f, ...) - if not f then return nil end - local args = {...} - return function() return f(table.unpack(args)) end -end local dummyFunc = Util.DummyFunc function GameEvent:initialize(event, ...) self.id = -1 self.end_id = -1 self.room = RoomInstance + -- for compat self.event = event + ---@diagnostic disable-next-line + -- self.event = self.class 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 - self.exit_func = GameEvent.exit_funcs[event] or dummyFunc - self.extra_exit_funcs = Util.DummyTable self.status = "ready" self.interrupted = false + + self.extra_clear = Util.DummyTable + self.extra_exit = Util.DummyTable end --- 静态函数,实际定义在events/init.lua -function GameEvent:translate(id) - error('static') +---@generic T +---@param self T +---@return T +function GameEvent.create(self, ...) + if self.class then error('cannot use "create()" by event instances') end + return self:new(self, ...) +end + +-- 获取最接近GameEvent的基类 +---@return GameEvent +function GameEvent.getBaseClass(self, ...) + if self.class then error('cannot use "getBaseClass()" by event instances') end + if self.super == GameEvent or self == GameEvent then + return self + end + return self.super:getBaseClass() +end + +function GameEvent.static:subclassed(subclass) + local mt = getmetatable(subclass) + -- 适配老代码event == GameEvent.Turn之类的奇技淫巧,危险性待评估 + -- 这样若某个模式启用派生类修改逻辑,那么findParent之类的基于父类也能找 + mt.__eq = function(a, b) + if not a.super or not b.super then return false end + return rawequal(a, b) or a:isSubclassOf(b) or b:isSubclassOf(a) + end end function GameEvent:__tostring() - return string.format("<%s #%d>", GameEvent:translate(self.event), self.id) + return string.format("<%s #%d>", + type(self.event == "string") and self.event or self.class.name, self.id) +end + +function GameEvent:prepare() + return (GameEvent.prepare_funcs[self.event] or dummyFunc)(self) +end + +function GameEvent:main() + return (GameEvent.functions[self.event] or dummyFunc)(self) +end + +function GameEvent:clear() + return (GameEvent.cleaners[self.event] or dummyFunc)(self) +end + +function GameEvent:exit() + return (GameEvent.exit_funcs[self.event] or dummyFunc)(self) end function GameEvent:addCleaner(f) - if self.extra_clear_funcs == Util.DummyTable then - self.extra_clear_funcs = {} + if self.extra_clear == Util.DummyTable then + self.extra_clear= {} end - table.insert(self.extra_clear_funcs, f) + table.insert(self.extra_clear, f) end function GameEvent:addExitFunc(f) - if self.extra_exit_funcs == Util.DummyTable then - self.extra_exit_funcs = {} + if self.extra_exit== Util.DummyTable then + self.extra_exit= {} end - table.insert(self.extra_exit_funcs, f) + table.insert(self.extra_exit, f) end function GameEvent:prependExitFunc(f) - if self.extra_exit_funcs == Util.DummyTable then - self.extra_exit_funcs = {} + if self.extra_exit== Util.DummyTable then + self.extra_exit= {} end - table.insert(self.extra_exit_funcs, 1, f) + table.insert(self.extra_exit, 1, f) end -- 找第一个与当前事件有继承关系的特定事件 ----@param eventType integer @ 事件类型 +---@param eventType GameEvent @ 事件类型 ---@param includeSelf bool @ 是否包括本事件 ---@param depth? integer @ 搜索深度 ---@return GameEvent? @@ -187,18 +218,18 @@ function GameEvent:exec() self.parent = logic:getCurrentEvent() - if self:prepare_func() then return true end + if self:prepare() then return true end logic:pushEvent(self) - local co = coroutine.create(self.main_func) + local co = coroutine.create(function() return self:main() end) self._co = co self.status = "running" coroutine.yield(self, "__newEvent") - Pcall(self.exit_func, self) - for _, f in ipairs(self.extra_exit_funcs) do + Pcall(self.exit, self) + for _, f in ipairs(self.extra_exit) do if type(f) == "function" then Pcall(f, self) end diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index d2afa29e..9388b89a 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -10,7 +10,7 @@ ---@field public cleaner_stack Stack ---@field public role_table string[][] ---@field public all_game_events GameEvent[] ----@field public event_recorder table +---@field public event_recorder table ---@field public current_event_id integer local GameLogic = class("GameLogic") @@ -23,7 +23,21 @@ function GameLogic:initialize(room) self.game_event_stack = Stack:new() self.cleaner_stack = Stack:new() self.all_game_events = {} - self.event_recorder = {} + self.event_recorder = setmetatable({}, { + -- 对派生事件而言 共用一个键 键取决于最接近GameEvent类的基类 + __newindex = function(t, k, v) + if type(k) == "table" and k:isSubclassOf(GameEvent) then + k = k:getBaseClass() + end + rawset(t, k, v) + end, + __index = function(t, k) + if type(k) == "table" and k:isSubclassOf(GameEvent) then + k = k:getBaseClass() + end + return rawget(t, k) + end, + }) self.current_event_id = 0 self.specific_events_id = { [GameEvent.Damage] = 1, @@ -65,13 +79,13 @@ function GameLogic:run() self:action() end -local function execGameEvent(type, ...) - local event = GameEvent:new(type, ...) +---@return boolean +local function execGameEvent(tp, ...) + local event = tp:create(...) local _, ret = event:exec() return ret end - function GameLogic:assignRoles() local room = self.room local n = #room.players @@ -113,17 +127,13 @@ function GameLogic:chooseGenerals() generals = table.filter(generals, function(g) return not table.contains(lord_generals, g) end) room:returnToGeneralPile(generals) - room:setPlayerGeneral(lord, lord_general, true) + room:prepareGeneral(lord, lord_general, deputy, true) + room:askForChooseKingdom({lord}) - room:broadcastProperty(lord, "general") - room:broadcastProperty(lord, "kingdom") - room:setDeputyGeneral(lord, deputy) - room:broadcastProperty(lord, "deputyGeneral") end local nonlord = room:getOtherPlayers(lord, true) - local generals = room:getNGenerals(#nonlord * generalNum) - table.shuffle(generals) + local generals = table.random(room.general_pile, #nonlord * generalNum) for i, p in ipairs(nonlord) do local arg = table.slice(generals, (i - 1) * generalNum + 1, i * generalNum + 1) p.request_data = json.encode{ arg, n } @@ -133,25 +143,22 @@ function GameLogic:chooseGenerals() room:notifyMoveFocus(nonlord, "AskForGeneral") room:doBroadcastRequest("AskForGeneral", nonlord) - local selected = {} for _, p in ipairs(nonlord) do + local general, deputy if p.general == "" and p.reply_ready then local general_ret = json.decode(p.client_reply) - local general = general_ret[1] - local deputy = general_ret[2] - table.insertTableIfNeed(selected, general_ret) - room:setPlayerGeneral(p, general, true, true) - room:setDeputyGeneral(p, deputy) + general = general_ret[1] + deputy = general_ret[2] else - room:setPlayerGeneral(p, p.default_reply[1], true, true) - room:setDeputyGeneral(p, p.default_reply[2]) + general = p.default_reply[1] + deputy = p.default_reply[2] end + room:findGeneral(general) + room:findGeneral(deputy) + room:prepareGeneral(p, general, deputy) p.default_reply = "" end - generals = table.filter(generals, function(g) return not table.contains(selected, g) end) - room:returnToGeneralPile(generals) - room:askForChooseKingdom(nonlord) end @@ -178,14 +185,25 @@ function GameLogic:broadcastGeneral() p.shield = math.min(general.shield + (deputy and deputy.shield or 0), 5) -- TODO: setup AI here - if p.role ~= "lord" then - room:broadcastProperty(p, "general") - room:broadcastProperty(p, "kingdom") - room:broadcastProperty(p, "deputyGeneral") - elseif #players >= 5 then - p.maxHp = p.maxHp + 1 - p.hp = p.hp + 1 + local changer = Fk.game_modes[room.settings.gameMode]:getAdjustedProperty(p) + if changer then + for key, value in pairs(changer) do + p[key] = value + end end + local fixMaxHp = Fk.generals[p.general].fixMaxHp + local deputyFix = Fk.generals[p.deputyGeneral] and Fk.generals[p.deputyGeneral].fixMaxHp + if deputyFix then + fixMaxHp = fixMaxHp and math.min(fixMaxHp, deputyFix) or deputyFix + end + if fixMaxHp then + p.maxHp = fixMaxHp + end + p.hp = math.min(p.maxHp, p.hp) + + room:broadcastProperty(p, "general") + room:broadcastProperty(p, "deputyGeneral") + room:broadcastProperty(p, "kingdom") room:broadcastProperty(p, "maxHp") room:broadcastProperty(p, "hp") room:broadcastProperty(p, "shield") @@ -416,7 +434,7 @@ end -- 此为启动事件管理器并启动第一个事件的初始函数 function GameLogic:start() - local root_event = GameEvent:new(GameEvent.Game) + local root_event = GameEvent.Game:create() self:pushEvent(root_event) @@ -424,25 +442,20 @@ function GameLogic:start() -- 事件管理器协程,同时也是Game事件 -- 当新事件想要exec时,就切回此处,由这里负责调度协程 -- 一个事件结束后也切回此处,然后resume - local co = coroutine.create(root_event.main_func) + local co = coroutine.create(function() return root_event:main() end) root_event._co = co - local jump_to -- shutdown函数用 - while true do -- 对于cleaner和正常事件,处理更后面来的 local ne = self:getCurrentEvent() local ce = self:getCurrentCleaner() local e = ce and (ce.id >= ne.id and ce or ne) or ne - -- 如果正在jump的话,判断是否需要继续clean,否则正常继续 - if e == ne and jump_to ~= nil then + if e == ne and e.killed then e.interrupted = true - e.killed = e ~= jump_to self:clearEvent(e) coroutine.close(e._co) e.status = "dead" - if e == jump_to then jump_to = nil end -- shutdown结束了 e = self:getCurrentCleaner() end @@ -467,11 +480,12 @@ function GameLogic:start() coroutine.close(e._co) e.status = "dead" elseif ret == true then - -- 跳到越早发生的事件越好 - if not jump_to then - jump_to = evt - else - jump_to = jump_to.id < evt.id and jump_to or evt + -- 遍历栈,将shutdown图中的事件全标记上killed + -- 被标记killed的事件之后会自动结束并清理 + for i = self.game_event_stack.p, 1, -1 do + local event = self.game_event_stack.t[i] + event.killed = true + if event == evt then break end end end end @@ -551,9 +565,9 @@ function GameLogic:clearEvent(event) if event.event == GameEvent.ClearEvent then return end if event.status == "exiting" then return end event.status = "exiting" - local ce = GameEvent(GameEvent.ClearEvent, event) + local ce = GameEvent.ClearEvent:create(event) ce.id = self.current_event_id - local co = coroutine.create(ce.main_func) + local co = coroutine.create(function() return ce:main() end) ce._co = co self.cleaner_stack:push(ce) end @@ -563,7 +577,7 @@ function GameLogic:getCurrentEvent() return self.game_event_stack.t[self.game_event_stack.p] end ----@param eventType integer +---@param eventType GameEvent function GameLogic:getMostRecentEvent(eventType) return self:getCurrentEvent():findParent(eventType, true) end @@ -581,7 +595,7 @@ function GameLogic:getCurrentSkillName() end -- 在指定历史范围中找至多n个符合条件的事件 ----@param eventType integer @ 要查找的事件类型 +---@param eventType GameEvent @ 要查找的事件类型 ---@param n integer @ 最多找多少个 ---@param func fun(e: GameEvent): boolean @ 过滤用的函数 ---@param scope integer @ 查询历史范围,只能是当前阶段/回合/轮次 diff --git a/lua/server/request.lua b/lua/server/request.lua index 1fead55d..2fb07a25 100644 --- a/lua/server/request.lua +++ b/lua/server/request.lua @@ -2,9 +2,13 @@ local function tellRoomToObserver(self, player) local observee = self.players[1] + local start_time = os.getms() local summary = self:getSummary(observee, true) player:doNotify("Observe", json.encode(summary)) + fk.qInfo(string.format("[Observe] %d, %s, in %.3fms", + self.id, player:getScreenName(), (os.getms() - start_time) / 1000)) + table.insert(self.observers, {observee.id, player, player:getId()}) end @@ -76,6 +80,7 @@ request_handlers["luckcard"] = function(room, id, reqlist) p:doNotify("AskForLuckCard", pdata.luckTime) else p.serverplayer:setThinking(false) + ResumeRoom(room.id) end room:setTag("LuckCardData", luck_data) @@ -111,6 +116,7 @@ request_handlers["surrender"] = function(room, id, reqlist) room.hasSurrendered = true player.surrendered = true room:doBroadcastNotify("CancelRequest", "") + ResumeRoom(room.id) end request_handlers["updatemini"] = function(room, pid, reqlist) @@ -127,6 +133,7 @@ end request_handlers["newroom"] = function(s, id) s:registerRoom(id) + ResumeRoom(id) end request_handlers["reloadpackage"] = function(_, _, reqlist) @@ -135,31 +142,16 @@ request_handlers["reloadpackage"] = function(_, _, reqlist) Fk:reloadPackage(path) end --- 处理异步请求的协程,本身也是个死循环就是了。 --- 为了适应调度器,目前又暂且将请求分为“耗时请求”和不耗时请求。 --- 耗时请求处理后会立刻挂起。不耗时的请求则会不断处理直到请求队列空后再挂起。 -local function requestLoop(self) - while true do - local ret = false - local request = self.thread:fetchRequest() - if request ~= "" then - ret = true - local reqlist = request:split(",") - local roomId = tonumber(table.remove(reqlist, 1)) - local room = self:getRoom(roomId) +return function(self, request) + local reqlist = request:split(",") + local roomId = tonumber(table.remove(reqlist, 1)) + local room = self:getRoom(roomId) - if room then - RoomInstance = room - local id = tonumber(reqlist[1]) - local command = reqlist[2] - Pcall(request_handlers[command], room, id, reqlist) - RoomInstance = nil - end - end - if not ret then - coroutine.yield() - end + if room then + RoomInstance = room + local id = tonumber(reqlist[1]) + local command = reqlist[2] + Pcall(request_handlers[command], room, id, reqlist) + RoomInstance = nil end end - -return requestLoop diff --git a/lua/server/room.lua b/lua/server/room.lua index 6a0f69a9..84888492 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -84,6 +84,11 @@ function Room:initialize(_room) self.request_queue = {} self.request_self = {} + -- doNotify过载保护,每次获得控制权时置为0 + -- 若在yield之前执行了max次doNotify则强制让出 + self.notify_count = 0 + self.notify_max = 500 + self.settings = json.decode(self.room:settings()) self.disabled_packs = self.settings.disabledPack if not Fk.game_modes[self.settings.gameMode] then @@ -108,10 +113,11 @@ function Room:resume() local main_co = self.main_co if self:checkNoHuman() then - return true + goto GAME_OVER end if not self.game_finished then + self.notify_count = 0 ret, err_msg, rest_time = coroutine.resume(main_co, err_msg) -- handle error @@ -162,17 +168,6 @@ function Room:isReady() return true end - -- 因为delay函数而延时:判断延时是否已经结束。 - -- 注意整个delay函数的实现都搬到这来了,delay本身只负责挂起协程了。 - if self.in_delay then - local rest = self.delay_duration - (os.getms() - self.delay_start) / 1000 - if rest <= 0 then - self.in_delay = false - return true - end - return false, rest - end - -- 剩下的就是因为等待应答而未就绪了 -- 检查所有正在等回答的玩家,如果已经过了烧条时间 -- 那么就不认为他还需要时间就绪了 @@ -182,13 +177,14 @@ function Room:isReady() for _, p in ipairs(self.players) do -- 这里判断的话需要用_splayer了,不然一控多的情况下会导致重复判断 if p._splayer:thinking() then - ret = false -- 烧条烧光了的话就把thinking设为false rest = p.request_timeout * 1000 - (os.getms() - p.request_start) / 1000 if rest <= 0 or p.serverplayer:getState() ~= fk.Player_Online then p._splayer:setThinking(false) + else + ret = false end end @@ -244,7 +240,6 @@ function Room:run() local logic = (mode.logic and mode.logic() or GameLogic):new(self) self.logic = logic if mode.rule then logic:addTriggerSkill(mode.rule) end - -- GameEvent(GameEvent.Game):exec() logic:start() end @@ -337,7 +332,7 @@ end --- 获得当前房间中的所有玩家。 --- --- 返回的数组的第一个元素是当前回合玩家,并且按行动顺序进行排序。 ----@param sortBySeat? boolean @ 是否无视按座位排序直接返回 +---@param sortBySeat? boolean @ 是否按座位排序,默认是 ---@return ServerPlayer[] @ 房间中玩家的数组 function Room:getAllPlayers(sortBySeat) if not self.game_started then @@ -359,7 +354,7 @@ function Room:getAllPlayers(sortBySeat) end --- 获得所有存活玩家,参看getAllPlayers ----@param sortBySeat? boolean +---@param sortBySeat? boolean @ 是否按座位排序,默认是 ---@return ServerPlayer[] function Room:getAlivePlayers(sortBySeat) if sortBySeat == nil or sortBySeat then @@ -370,7 +365,7 @@ function Room:getAlivePlayers(sortBySeat) if temp == nil then return { table.unpack(self.players) } end - local ret = {current} + local ret = current.dead and {} or {current} while temp ~= current do if not temp.dead then table.insert(ret, temp) @@ -386,7 +381,7 @@ end --- 获得除一名玩家外的其他玩家。 ---@param player ServerPlayer @ 要排除的玩家 ----@param sortBySeat? boolean @ 是否要按座位排序? +---@param sortBySeat? boolean @ 是否按座位排序,默认是 ---@param include_dead? boolean @ 是否要把死人也算进去? ---@return ServerPlayer[] @ 其他玩家列表 function Room:getOtherPlayers(player, sortBySeat, include_dead) @@ -565,8 +560,8 @@ function Room:setBanner(name, value) end ---@return boolean -local function execGameEvent(type, ...) - local event = GameEvent:new(type, ...) +local function execGameEvent(tp, ...) + local event = tp:create(...) local _, ret = event:exec() return ret end @@ -600,6 +595,41 @@ function Room:setDeputyGeneral(player, general) self:notifyProperty(player, player, "deputyGeneral") end +---@param player ServerPlayer +---@param general string +---@param deputy string +---@param broadcast boolean|nil +function Room:prepareGeneral(player, general, deputy, broadcast) + self:findGeneral(general) + self:findGeneral(deputy) + local skills = Fk.generals[general]:getSkillNameList() + if Fk.generals[deputy] then + table.insertTable(skills, Fk.generals[deputy]:getSkillNameList()) + end + if table.find(skills, function (s) return Fk.skills[s].isHiddenSkill end) then + self:setPlayerMark(player, "__hidden_general", general) + if Fk.generals[deputy] then + self:setPlayerMark(player, "__hidden_deputy", deputy) + deputy = "" + end + general = "hiddenone" + end + player.general = general + player.gender = Fk.generals[general].gender + self:broadcastProperty(player, "gender") + if Fk.generals[deputy] then + player.deputyGeneral = deputy + end + player.kingdom = Fk.generals[general].kingdom + for _, property in ipairs({"general","deputyGeneral","kingdom"}) do + if broadcast then + self:broadcastProperty(player, property) + else + self:notifyProperty(player, player, property) + end + end +end + ---@param player ServerPlayer @ 要换将的玩家 ---@param new_general string @ 要变更的武将,若不存在则变身为孙策,孙策不存在变身为士兵 ---@param full? boolean @ 是否血量满状态变身 @@ -757,6 +787,10 @@ local function surrenderCheck(room) room.hasSurrendered = false end +local function setRequestTimer(room) + room.room:setRequestTimer(room.timeout * 1000 + 500) +end + --- 向某个玩家发起一次Request。 ---@param player ServerPlayer @ 发出这个请求的目标玩家 ---@param command string @ 请求的类型 @@ -770,9 +804,11 @@ function Room:doRequest(player, command, jsonData, wait) player:doRequest(command, jsonData, self.timeout) if wait then + setRequestTimer(self) local ret = player:waitForReply(self.timeout) player.serverplayer:setBusy(false) player.serverplayer:setThinking(false) + self.room:destroyRequestTimer() surrenderCheck(self) return ret end @@ -786,6 +822,7 @@ function Room:doBroadcastRequest(command, players, jsonData) players = players or self.players self.request_queue = {} self.race_request_list = nil + setRequestTimer(self) for _, p in ipairs(players) do p:doRequest(command, jsonData or p.request_data) end @@ -803,6 +840,7 @@ function Room:doBroadcastRequest(command, players, jsonData) p.serverplayer:setThinking(false) end + self.room:destroyRequestTimer() surrenderCheck(self) end @@ -819,6 +857,7 @@ function Room:doRaceRequest(command, players, jsonData) players = players or self.players players = table.simpleClone(players) local player_len = #players + setRequestTimer(self) -- self:notifyMoveFocus(players, command) self.request_queue = {} self.race_request_list = players @@ -837,7 +876,8 @@ function Room:doRaceRequest(command, players, jsonData) if remainTime - elapsed <= 0 then break end - for _, p in ipairs(players) do + for i = #players, 1, -1 do + local p = players[i] p:waitForReply(0) if p.reply_ready == true then winner = p @@ -845,7 +885,7 @@ function Room:doRaceRequest(command, players, jsonData) end if p.reply_cancel then - table.removeOne(players, p) + table.remove(players, i) table.insertIfNeed(canceled_players, p) elseif p.id > 0 then -- 骗过调度器让他以为自己尚未就绪 @@ -871,20 +911,16 @@ function Room:doRaceRequest(command, players, jsonData) p.serverplayer:setThinking(false) end + self.room:destroyRequestTimer() surrenderCheck(self) return ret end --- 延迟一段时间。 ---- ---- 这个函数不应该在请求处理协程中使用。 ---@param ms integer @ 要延迟的毫秒数 function Room:delay(ms) - local start = os.getms() - self.delay_start = start - self.delay_duration = ms - self.in_delay = true + self.room:delay(ms) coroutine.yield("__handleRequest", ms) end @@ -913,24 +949,6 @@ function Room:notifyMoveCards(players, card_moves, forceVisible) end end - local function containArea(area, relevant) --处理区的处理? - local areas = relevant - 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 - - -- forceVisible make the move visible - -- 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 and p.isBuddy and p:isBuddy(move.to))) then - for _, info in ipairs(move.moveInfo) do - if not containArea(info.fromArea, move.from and p.isBuddy and p:isBuddy(move.from)) then - info.cardId = -1 - end - end - end end p:doNotify("MoveCards", json.encode(arg)) end @@ -1058,7 +1076,7 @@ end --- 与此同时,在战报里面发一条“xxx发动了xxx” ---@param player ServerPlayer @ 发动技能的那个玩家 ---@param skill_name string @ 技能名 ----@param skill_type? string @ 技能的动画效果,默认是那个技能的anim_type +---@param skill_type? string | AnimationType @ 技能的动画效果,默认是那个技能的anim_type function Room:notifySkillInvoked(player, skill_name, skill_type) local bigAnim = false if not skill_type then @@ -1842,7 +1860,13 @@ function Room:askForChoice(player, choices, skill_name, prompt, detailed, all_ch local result = self:doRequest(player, command, json.encode{ choices, all_choices, skill_name, prompt, detailed }) - if result == "" then result = choices[1] end + if result == "" then + if table.contains(choices, "Cancel") then + result = "Cancel" + else + result = choices[1] + end + end return result end @@ -1970,6 +1994,93 @@ function Room:askForAddTarget(player, targets, num, can_minus, distance_limited, return {} end +--- 询问玩家在自定义大小的框中排列卡牌(观星、交换、拖拽选牌) +---@param player ServerPlayer @ 要询问的玩家 +---@param skillname string @ 烧条技能名 +---@param cardMap any @ { "牌堆1卡表", "牌堆2卡表", …… } +---@param prompt? string @ 操作提示 +---@param box_size? integer @ 数值对应卡牌平铺张数的最大值,为0则有单个卡位,每张卡占100单位长度,默认为7 +---@param max_limit? integer[] @ 每一行牌上限 { 第一行, 第二行,…… },不填写则不限 +---@param min_limit? integer[] @ 每一行牌下限 { 第一行, 第二行,…… },不填写则不限 +---@param free_arrange? boolean @ 是否允许自由排列第一行卡的位置,默认不能 +---@param pattern? string @ 控制第一行卡牌是否可以操作,不填写默认均可操作 +---@param poxi_type? string @ 控制每张卡牌是否可以操作、确定键是否可以点击,不填写默认均可操作 +---@param default_choice? table[] @ 超时的默认响应值,在带poxi_type时需要填写 +---@return table[] +function Room:askForArrangeCards(player, skillname, cardMap, prompt, free_arrange, box_size, max_limit, min_limit, pattern, poxi_type, default_choice) + prompt = prompt or "" + local areaNames = {} + if type(cardMap[1]) == "number" then + cardMap = {cardMap} + else + for i = #cardMap, 1, -1 do + if type(cardMap[i]) == "string" then + table.insert(areaNames, 1, cardMap[i]) + table.remove(cardMap, i) + end + end + end + if #areaNames == 0 then + areaNames = {skillname, "toObtain"} + end + box_size = box_size or 7 + max_limit = max_limit or {#cardMap[1], #cardMap > 1 and #cardMap[2] or #cardMap[1]} + min_limit = min_limit or {0, 0} + for _ = #cardMap + 1, #min_limit, 1 do + table.insert(cardMap, {}) + end + pattern = pattern or "." + poxi_type = poxi_type or "" + local command = "AskForArrangeCards" + local data = { + cards = cardMap, + names = areaNames, + prompt = prompt, + size = box_size, + capacities = max_limit, + limits = min_limit, + is_free = free_arrange or false, + pattern = pattern or ".", + poxi_type = poxi_type or "", + cancelable = ((pattern ~= "." or poxi_type ~= "") and (default_choice == nil)) + } + local result = self:doRequest(player, command, json.encode(data)) + -- local result = player.room:askForCustomDialog(player, skillname, + -- "RoomElement/ArrangeCardsBox.qml", { + -- cardMap, prompt, box_size, max_limit, min_limit, free_arrange or false, areaNames, + -- pattern or ".", poxi_type or "", ((pattern ~= "." or poxi_type ~= "") and (default_choice == nil)) + -- }) + if result == "" then + if default_choice then return default_choice end + for j = 1, #min_limit, 1 do + if #cardMap[j] < min_limit[j] then + local cards = {table.connect(table.unpack(cardMap))} + if #min_limit > 1 then + for i = 2, #min_limit, 1 do + table.insert(cards, {}) + if #cards[i] < min_limit[i] then + for _ = 1, min_limit[i] - #cards[i], 1 do + table.insert(cards[i], table.remove(cards[1], #cards[1] + #cards[i] - min_limit[i] + 1)) + end + end + end + if #cards[1] > max_limit[1] then + for i = 2, #max_limit, 1 do + while #cards[i] < max_limit[i] do + table.insert(cards[i], table.remove(cards[1], max_limit[1] + 1)) + if #cards[1] == max_limit[1] then return cards end + end + end + end + end + return cards + end + end + return cardMap + end + return json.decode(result) +end + -- TODO: guanxing type --- 询问玩家对若干牌进行观星。 --- @@ -2003,9 +2114,15 @@ function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotif end local command = "AskForGuanxing" self:notifyMoveFocus(player, customNotify or command) + local max_top = top_limit and top_limit[2] or #cards + local card_map = {table.slice(cards, 1, max_top + 1)} + if max_top < #cards then + table.insert(card_map, table.slice(cards, max_top)) + end local data = { prompt = "", - cards = cards, + is_free = true, + cards = card_map, min_top_cards = top_limit and top_limit[1] or 0, max_top_cards = top_limit and top_limit[2] or #cards, min_bottom_cards = bottom_limit and bottom_limit[1] or 0, @@ -2034,7 +2151,7 @@ function Room:askForGuanxing(player, cards, top_limit, bottom_limit, customNotif for i = #top, 1, -1 do table.insert(self.draw_pile, 1, top[i]) end - for i = 1, #bottom, -1 do + for i = 1, #bottom, 1 do table.insert(self.draw_pile, bottom[i]) end @@ -2062,7 +2179,7 @@ function Room:askForExchange(player, piles, piles_name, customNotify) if #piles_name ~= #piles then piles_name = {} for i, _ in ipairs(piles) do - table.insert(piles_name, "Pile" .. i) + table.insert(piles_name, Fk:translate("Pile") .. i) end end self:notifyMoveFocus(player, customNotify or command) @@ -2420,6 +2537,7 @@ end -- Show a qml dialog and return qml's ClientInstance.replyToServer -- Do anything you like through this function +-- 调用一个自定义对话框,须自备loadData方法 ---@param player ServerPlayer ---@param focustxt string ---@param qmlPath string @@ -2434,6 +2552,7 @@ function Room:askForCustomDialog(player, focustxt, qmlPath, extra_data) }) end +--- 询问移动场上的一张牌 ---@param player ServerPlayer @ 移动的操作 ---@param targetOne ServerPlayer @ 移动的目标1玩家 ---@param targetTwo ServerPlayer @ 移动的目标2玩家 @@ -2731,15 +2850,16 @@ function Room:doCardUseEffect(cardUseEvent) return end - if self:getPlayerById(TargetGroup:getRealTargets(cardUseEvent.tos)[1]).dead then - self:moveCards({ - ids = realCardIds, - toArea = Card.DiscardPile, - moveReason = fk.ReasonPutIntoDiscardPile, - }) - else - local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1] - local existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type) + local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1] + if not (self:getPlayerById(target).dead or table.contains((cardUseEvent.nullifiedTargets or Util.DummyTable), target)) then + local existingEquipId + if cardUseEvent.toPutSlot and cardUseEvent.toPutSlot:startsWith("#EquipmentChoice") then + local index = cardUseEvent.toPutSlot:split(":")[2] + existingEquipId = self:getPlayerById(target):getEquipments(cardUseEvent.card.sub_type)[tonumber(index)] + elseif not self:getPlayerById(target):hasEmptyEquipSlot(cardUseEvent.card.sub_type) then + existingEquipId = self:getPlayerById(target):getEquipment(cardUseEvent.card.sub_type) + end + if existingEquipId then self:moveCards( { @@ -2772,7 +2892,7 @@ function Room:doCardUseEffect(cardUseEvent) end local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1] - if not self:getPlayerById(target).dead then + if not (self:getPlayerById(target).dead or table.contains((cardUseEvent.nullifiedTargets or Util.DummyTable), target)) then local findSameCard = false for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do if Fk:getCardById(cardId).trueName == cardUseEvent.card.trueName then @@ -2783,6 +2903,13 @@ function Room:doCardUseEffect(cardUseEvent) if not findSameCard then if cardUseEvent.card:isVirtual() then self:getPlayerById(target):addVirtualEquip(cardUseEvent.card) + elseif cardUseEvent.card.name ~= Fk:getCardById(cardUseEvent.card.id, true).name then + local card = Fk:cloneCard(cardUseEvent.card.name) + card.skillNames = cardUseEvent.card.skillNames + card:addSubcard(cardUseEvent.card.id) + self:getPlayerById(target):addVirtualEquip(card) + else + self:getPlayerById(target):removeVirtualEquip(cardUseEvent.card.id) end self:moveCards({ @@ -2796,12 +2923,6 @@ function Room:doCardUseEffect(cardUseEvent) end end - self:moveCards({ - ids = realCardIds, - toArea = Card.DiscardPile, - moveReason = fk.ReasonPutIntoDiscardPile, - }) - return end @@ -3091,34 +3212,16 @@ end --- 让一名玩家获得一张牌 ---@param player integer|ServerPlayer @ 要拿牌的玩家 ----@param cid integer|Card|integer[] @ 要拿到的卡牌 +---@param card integer|integer[]|Card|Card[] @ 要拿到的卡牌 ---@param unhide? boolean @ 是否明着拿 ---@param reason? CardMoveReason @ 卡牌移动的原因 ---@param proposer? integer @ 移动操作者的id -function Room:obtainCard(player, cid, unhide, reason, proposer) - if type(cid) ~= "number" then - assert(cid and type(cid) == "table") - if cid[1] == nil then - cid = cid:isVirtual() and cid.subcards or {cid.id} - end - else - cid = {cid} - end - if #cid == 0 then return end - - if type(player) == "table" then - player = player.id - end - - self:moveCards({ - ids = cid, - from = self.owner_map[cid[1]], - to = player, - toArea = Card.PlayerHand, - moveReason = reason or fk.ReasonJustMove, - proposer = proposer or player, - moveVisible = unhide or false, - }) +---@param skill_name? string @ 技能名 +---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} +---@param visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效) +function Room:obtainCard(player, card, unhide, reason, proposer, skill_name, moveMark, visiblePlayers) + local pid = type(player) == "number" and player or player.id + self:moveCardTo(card, Card.PlayerHand, player, reason, skill_name, nil, unhide, proposer or pid, moveMark, visiblePlayers) end --- 让玩家摸牌 @@ -3126,8 +3229,9 @@ end ---@param num integer @ 摸牌数 ---@param skillName? string @ 技能名 ---@param fromPlace? string @ 摸牌的位置,"top" 或者 "bottom" +---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} ---@return integer[] @ 摸到的牌 -function Room:drawCards(player, num, skillName, fromPlace) +function Room:drawCards(player, num, skillName, fromPlace, moveMark) local drawData = { who = player, num = num, @@ -3150,6 +3254,7 @@ function Room:drawCards(player, num, skillName, fromPlace) moveReason = fk.ReasonDraw, proposer = player.id, skillName = skillName, + moveMark = moveMark, }) return { table.unpack(topCards) } @@ -3158,13 +3263,15 @@ end --- 将一张或多张牌移动到某处 ---@param card integer | integer[] | Card | Card[] @ 要移动的牌 ---@param to_place integer @ 移动的目标位置 ----@param target? ServerPlayer @ 移动的目标角色 +---@param target? ServerPlayer|integer @ 移动的目标角色 ---@param reason? integer @ 移动时使用的移牌原因 ---@param skill_name? string @ 技能名 ---@param special_name? string @ 私人牌堆名 ---@param visible? boolean @ 是否明置 ---@param proposer? integer @ 移动操作者的id -function Room:moveCardTo(card, to_place, target, reason, skill_name, special_name, visible, proposer) +---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} +---@param visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效) +function Room:moveCardTo(card, to_place, target, reason, skill_name, special_name, visible, proposer, moveMark, visiblePlayers) reason = reason or fk.ReasonJustMove skill_name = skill_name or "" special_name = special_name or "" @@ -3174,7 +3281,12 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam if table.contains( {Card.PlayerEquip, Card.PlayerHand, Card.PlayerJudge, Card.PlayerSpecial}, to_place) then - to = target.id + assert(target) + if type(target) == "number" then + to = target + else + to = target.id + end end local movesSplitedByOwner = {} @@ -3196,6 +3308,8 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam specialName = special_name, moveVisible = visible, proposer = proposer, + moveMark = moveMark, + visiblePlayers = visiblePlayers, }) end end @@ -3559,6 +3673,10 @@ function Room:recastCard(card_ids, who, skillName) moveReason = fk.ReasonRecast, proposer = who.id }) + self:sendFootnote(card_ids, { + type = "##RecastCard", + from = who.id, + }) self:broadcastPlaySound("./audio/system/recast") self:sendLog{ type = skillName == "recast" and "#Recast" or "#RecastBySkill", @@ -3712,6 +3830,7 @@ end ---@param winner string @ 获胜的身份,空字符串表示平局 function Room:gameOver(winner) if not self.game_started then return end + self.room:destroyRequestTimer() if table.contains( { "running", "normal" }, @@ -3727,6 +3846,7 @@ function Room:gameOver(winner) self:broadcastProperty(p, "role") end self:doBroadcastNotify("GameOver", winner) + fk.qInfo(string.format("[GameOver] %d, %s, %s, in %ds", self.id, self.settings.gameMode, winner, os.time() - self.start_time)) if shouldUpdateWinRate(self) then for _, p in ipairs(self.players) do @@ -3990,6 +4110,52 @@ function Room:resumePlayerArea(player, playerSlots) end end +---@param player ServerPlayer +---@param playerSlots string | string[] +function Room:addPlayerEquipSlots(player, playerSlots) + assert(type(playerSlots) == "string" or type(playerSlots) == "table") + + if type(playerSlots) == "string" then + playerSlots = { playerSlots } + end + + for _, slot in ipairs(playerSlots) do + local slotIndex = table.indexOf(player.equipSlots, slot) + if slotIndex > -1 then + table.insert(player.equipSlots, slotIndex, slot) + else + table.insert(player.equipSlots, slot) + end + end + + self:broadcastProperty(player, "equipSlots") +end + +---@param player ServerPlayer +---@param playerSlots string | string[] +function Room:removePlayerEquipSlots(player, playerSlots) + assert(type(playerSlots) == "string" or type(playerSlots) == "table") + + if type(playerSlots) == "string" then + playerSlots = { playerSlots } + end + + for _, slot in ipairs(playerSlots) do + table.removeOne(player.equipSlots, slot) + end + + self:broadcastProperty(player, "equipSlots") +end + +---@param player ServerPlayer +---@param playerSlots string[] +function Room:setPlayerEquipSlots(player, playerSlots) + assert(type(playerSlots) == "table") + player.equipSlots = playerSlots + + self:broadcastProperty(player, "equipSlots") +end + --- 设置休整 ---@param player ServerPlayer ---@param roundNum integer diff --git a/lua/server/scheduler.lua b/lua/server/scheduler.lua index f67a1e87..7ac2398f 100644 --- a/lua/server/scheduler.lua +++ b/lua/server/scheduler.lua @@ -2,49 +2,19 @@ local Room = require "server.room" ---[[ -local verbose = function(...) - printf(...) -end ---]] - -- 所有当前正在运行的房间(即游戏尚未结束的房间) ---@type table local runningRooms = {} --- 所有处于就绪态的房间,以及request协程(如果就绪的话) ----@type Room[] -local readyRooms = {} - -local requestCo = coroutine.create(function(room) - require "server.request"(room) -end) - -- 仿照Room接口编写的request协程处理器 local requestRoom = setmetatable({ id = -1, runningRooms = runningRooms, - -- minDelayTime 是当没有任何就绪房间时,可以睡眠的时间。 - -- 因为这个时间是所有房间预期就绪用时的最小值,故称为minDelayTime。 - minDelayTime = -1, - getRoom = function(_, roomId) return runningRooms[roomId] end, - resume = function(self) - local err, msg = coroutine.resume(requestCo, self) - if err == false then - fk.qCritical(msg .. "\n" .. debug.traceback(requestCo)) - end - return nil, 0 - end, - - isReady = function(self) - return self.thread:hasRequest() - end, - registerRoom = function(self, id) local cRoom = self.thread:getRoom(id) local room = Room:new(cRoom) @@ -59,116 +29,44 @@ local requestRoom = setmetatable({ runningRooms[-1] = requestRoom --- 从所有运行中房间中挑出就绪的房间。 --- 方法暂时就是最简单的遍历。 -local function refreshReadyRooms() - -- verbose '[+] Refreshing ready queue...' - for k, v in pairs(runningRooms) do - local ready, rest = v:isReady() - if ready then - table.insertIfNeed(readyRooms, v) - elseif rest and rest >= 0 then - local time = requestRoom.minDelayTime - time = math.min((time <= 0 and 9999999 or time), rest) - requestRoom.minDelayTime = math.ceil(time) - end - end - -- verbose('[+] now have %d ready rooms...', #readyRooms) -end - --- 主循环。只要线程没有被杀掉,就一直循环下去。 --- 函数每轮循环会从队列中取一个元素并交给控制权, --- 如果没有,则尝试刷新队列,无法刷新则开始睡眠。 -local function mainLoop() - -- request协程的专用特判变量。因为处理request不应当重置睡眠时长 - local rest_sleep_time - - while not requestRoom.thread:isTerminated() do - local room = table.remove(readyRooms, 1) - if room then - -- verbose '============= LOOP ==============' - -- verbose('[*] Switching to %s...', tostring(room)) - - RoomInstance = (room ~= requestRoom and room or nil) - local over, rest = room:resume() - RoomInstance = nil - - if over then - -- verbose('[#] %s is finished, removing ...', tostring(room)) - for _, e in ipairs(room.logic.game_event_stack.t) do - coroutine.close(e._co) - end - for _, e in ipairs(room.logic.cleaner_stack.t) do - coroutine.close(e._co) - end - room.logic = nil - runningRooms[room.id] = nil - else - local time = requestRoom.minDelayTime - if room == requestRoom then - rest = rest_sleep_time - end - - if rest and rest >= 0 then - time = math.min((time <= 0 and 9999999 or time), rest) - else - time = -1 - end - requestRoom.minDelayTime = math.ceil(time) - -- verbose("[+] minDelay is %d ms...", requestRoom.minDelayTime) - -- verbose('[-] %s successfully yielded, %d ready rooms left...', - -- tostring(room), #readyRooms) - end - else - refreshReadyRooms() - if #readyRooms == 0 then - refreshReadyRooms() - if #readyRooms == 0 then - local time = requestRoom.minDelayTime - -- verbose('[.] Sleeping for %d ms...', time) - local cur = os.getms() - - time = math.min((time <= 0 and 9999999 or time), 200) - - -- 调用RoomThread的trySleep函数开始真正的睡眠。会被wakeUp(c++)唤醒。 - requestRoom.thread:trySleep(time) - local runningRoomsCount = -1 -- 必有requestRoom,从-1开始算 - for _ in pairs(runningRooms) do - runningRoomsCount = runningRoomsCount + 1 - if runningRoomsCount > 0 then break end - end - if runningRoomsCount == 0 and requestRoom.thread:isOutdated() then - break - end - - -- verbose('[!] Waked up after %f ms...', (os.getms() - cur) / 1000) - - if time > 0 then - rest_sleep_time = math.floor(time - (os.getms() - cur) / 1000) - else - rest_sleep_time = -1 - end - - requestRoom.minDelayTime = -1 - end - end - end - end - -- verbose '=========== LOOP END ============' - -- verbose '[:)] Goodbye!' -end - -- 当Cpp侧的RoomThread运行时,以下这个函数就是这个线程的主函数。 -- 而这个函数里面又调用了上面的mainLoop。 function InitScheduler(_thread) requestRoom.thread = _thread - Pcall(mainLoop) + -- Pcall(mainLoop) end function IsConsoleStart() return requestRoom.thread:isConsoleStart() end +local Req = require "server.request" +function HandleRequest(req) + Req(requestRoom, req) + return true +end + +function ResumeRoom(roomId) + local room = requestRoom:getRoom(roomId) + if not room then return false end + if not room:isReady() then return false end + RoomInstance = (room ~= requestRoom and room or nil) + local over = room:resume() + RoomInstance = nil + + if over then + for _, e in ipairs(room.logic.game_event_stack.t) do + coroutine.close(e._co) + end + for _, e in ipairs(room.logic.cleaner_stack.t) do + coroutine.close(e._co) + end + room.logic = nil + runningRooms[room.id] = nil + end + return over +end + if FileIO.pwd():endsWith("packages/freekill-core") then FileIO.cd("../..") end diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 0a10edf0..5e35ae52 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -53,17 +53,25 @@ end ---@param command string ---@param jsonData string function ServerPlayer:doNotify(command, jsonData) + local room = self.room for _, p in ipairs(self._observers) do + if p:getState() ~= fk.Player_Robot then + room.notify_count = room.notify_count + 1 + end p:doNotify(command, jsonData) end - local room = self.room for _, t in ipairs(room.observers) do local id, p = table.unpack(t) if id == self.id and room.room:hasObserver(p) then p:doNotify(command, jsonData) end end + + if room.notify_count >= room.notify_max and + coroutine.status(room.main_co) == "normal" then + room:delay(100) + end end --- Send a request to client, and allow client to reply within *timeout* seconds. @@ -155,6 +163,16 @@ local function _waitForReply(player, timeout) end end +--- 发送一句聊天 +---@param msg string +function ServerPlayer:chat(msg) + self.room:doBroadcastNotify("Chat", json.encode { + type = 2, + sender = self.id, + msg = msg, + }) +end + --- Wait for at most *timeout* seconds for reply from client. --- --- If *timeout* is negative or **nil**, the function will wait forever until get reply. @@ -369,7 +387,7 @@ function ServerPlayer:changePhase(from_phase, to_phase) table.remove(self.phases, 1) end - GameEvent(GameEvent.Phase, self, self.phase):exec() + GameEvent.Phase:create(self, self.phase):exec() return false end @@ -412,7 +430,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay) arg = phase_name_table[phase], } - GameEvent(GameEvent.Phase, self, self.phase):exec() + GameEvent.Phase:create(self, self.phase):exec() phase_change = { from = phase, @@ -491,7 +509,7 @@ function ServerPlayer:play(phase_table) end if (not skip) or (cancel_skip) then - GameEvent(GameEvent.Phase, self, self.phase):exec() + GameEvent.Phase:create(self, self.phase):exec() else room:sendLog{ type = "#PhaseSkipped", @@ -554,7 +572,7 @@ function ServerPlayer:gainAnExtraTurn(delay, skillName) local ex_tag = self.tag["_extra_turn_count"] table.insert(ex_tag, skillName) - GameEvent(GameEvent.Turn, self):exec() + GameEvent.Turn:create(self):exec() table.remove(ex_tag) @@ -581,18 +599,21 @@ end ---@param num integer @ 摸牌数 ---@param skillName? string @ 技能名 ---@param fromPlace? string @ 摸牌的位置,"top" 或者 "bottom" +---@param moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} ---@return integer[] @ 摸到的牌 -function ServerPlayer:drawCards(num, skillName, fromPlace) - return self.room:drawCards(self, num, skillName, fromPlace) +function ServerPlayer:drawCards(num, skillName, fromPlace, moveMark) + return self.room:drawCards(self, num, skillName, fromPlace, moveMark) end ---@param pile_name string ----@param card integer|Card +---@param card integer | integer[] | Card | Card[] ---@param visible? boolean ---@param skillName? string -function ServerPlayer:addToPile(pile_name, card, visible, skillName) - local room = self.room - room:moveCardTo(card, Card.PlayerSpecial, self, fk.ReasonJustMove, skillName, pile_name, visible) +---@param proposer? integer +---@param visiblePlayers? integer | integer[] @ 为nil时默认对自己可见 +function ServerPlayer:addToPile(pile_name, card, visible, skillName, proposer, visiblePlayers) + self.room:moveCardTo(card, Card.PlayerSpecial, self, fk.ReasonJustMove, skillName, pile_name, visible, + proposer or self.id, nil, visiblePlayers) end function ServerPlayer:bury() @@ -637,6 +658,7 @@ function ServerPlayer:clearPiles() end function ServerPlayer:addVirtualEquip(card) + self:removeVirtualEquip(card:getEffectiveId()) Player.addVirtualEquip(self, card) self.room:doBroadcastNotify("AddVirtualEquip", json.encode{ player = self.id, @@ -647,10 +669,12 @@ end function ServerPlayer:removeVirtualEquip(cid) local ret = Player.removeVirtualEquip(self, cid) - self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{ - player = self.id, - id = cid, - }) + if ret then + self.room:doBroadcastNotify("RemoveVirtualEquip", json.encode{ + player = self.id, + id = cid, + }) + end return ret end diff --git a/lua/server/system_enum.lua b/lua/server/system_enum.lua index 7e764065..79743909 100644 --- a/lua/server/system_enum.lua +++ b/lua/server/system_enum.lua @@ -15,7 +15,8 @@ ---@field public specialName? string @ 若终点区域为PlayerSpecial,则存至对应私人牌堆内 ---@field public specialVisible? boolean @ 控制上述创建私人牌堆后是否令其可见 ---@field public drawPilePosition? integer @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底 ----@field public moveMark? table @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} +---@field public moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} +---@field public visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效) --- MoveInfo 一张牌的来源信息 ---@class MoveInfo @@ -36,7 +37,8 @@ ---@field public specialName? string @ 若终点区域为PlayerSpecial,则存至对应私人牌堆内 ---@field public specialVisible? boolean @ 控制上述创建私人牌堆后是否令其可见 ---@field public drawPilePosition? integer @ 移至牌堆的索引位置,值为-1代表置入牌堆底,或者牌堆牌数+1也为牌堆底 ----@field public moveMark? table @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} +---@field public moveMark? table|string @ 移动后自动赋予标记,格式:{标记名(支持-inarea后缀,移出值代表区域后清除), 值} +---@field public visiblePlayers? integer|integer[] @ 控制移动对特定角色可见(在moveVisible为false时生效) --- PindianResult 拼点结果 ---@class PindianResult diff --git a/packages/maneuvering/i18n/en_US.lua b/packages/maneuvering/i18n/en_US.lua index 1186e44e..6b0ff49e 100644 --- a/packages/maneuvering/i18n/en_US.lua +++ b/packages/maneuvering/i18n/en_US.lua @@ -2,21 +2,21 @@ return { ["maneuvering"] = "Maneuvering", ["thunder__slash"] = "Thunder Slash", - [":thunder__slash"] = "Thunder Slash (basic card)
Phase: Action phase
Target: Another player within your ATK range
Effect: Deal 1 Thunder DMG to the targets.
Note: You can only use 1 Slash per action phase.", + [":thunder__slash"] = "Thunder Slash (basic card)
Phase: Action phase
Target: Another player within your ATK range
Effect: Deal 1 Thunder DMG to the targets.
Note: You can only use 1 Slash per action phase.", ["#thunder__slash_skill"] = "Choose 1 player within your ATK range, deal 1 Thunder DMG to him", ["#thunder__slash_skill_multi"] = "Choose up to %arg players within your ATK range. Deal 1 Thunder DMG to them", ["fire__slash"] = "Fire Slash", - [":fire__slash"] = "Fire Slash (basic card)
Phase: Action phase
Target: Another player within your ATK range
Effect: Deal 1 Fire DMG to the targets.
Note: You can only use 1 Slash per action phase.", + [":fire__slash"] = "Fire Slash (basic card)
Phase: Action phase
Target: Another player within your ATK range
Effect: Deal 1 Fire DMG to the targets.
Note: You can only use 1 Slash per action phase.", ["#fire__slash_skill"] = "Choose 1 player within your ATK range, deal 1 Fire DMG to him", ["#fire__slash_skill_multi"] = "Choose up to %arg players within your ATK range. Deal 1 Fire DMG to them", ["analeptic"] = "Alcohol", - [":analeptic"] = "Alcohol (basic card)
Phase: 1. Action phase 2. When you are dying
Target: Yourself
Effect: 1. the DMG of the next Slash you use this turn is increased by +1. (This effect can only be used once per turn) 2. You heal 1 HP.", + [":analeptic"] = "Alcohol (basic card)
Phase: 1. Action phase 2. When you are dying
Target: Yourself
Effect: 1. the DMG of the next Slash you use this turn is increased by +1. (This effect can only be used once per turn) 2. You heal 1 HP.", ["#analeptic_skill"] = "the DMG of the next Slash you use this turn is increased by +1", ["iron_chain"] = "Iron Chain", - [":iron_chain"] = "Iron Chain (trick card)
Phase: Action phase
Target: 1~2 players
Effect: Change chain state of the targets.", + [":iron_chain"] = "Iron Chain (trick card)
Phase: Action phase
Target: 1~2 players
Effect: Change chain state of the targets.", ["#iron_chain_skill"] = "Choose 1~2 players. Change their chain states", ["_normal_use"] = "Normally use", ["recast"] = "Recast", @@ -25,29 +25,29 @@ return { ["fire_attack"] = "Fire Attack", ["fire_attack_skill"] = "Fire Attack", - [":fire_attack"] = "Fire Attack (trick card)
Phase: Action phase
Target: A player with hand cards
Effect: The target player shows 1 hand card; then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him.", + [":fire_attack"] = "Fire Attack (trick card)
Phase: Action phase
Target: A player with hand cards
Effect: The target player shows 1 hand card; then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him.", ["#fire_attack-show"] = "%src used Fire Attack to you, please show 1 hand card", ["#fire_attack-discard"] = "You can discard 1 %arg hand card, then deal 1 Fire DMG to %src", ["#fire_attack_skill"] = "Choose a player with hand cards. He shows 1 hand card;
then, if you discard 1 card with the same suit, you deal 1 Fire DMG to him", ["supply_shortage"] = "Supply Shortage", - [":supply_shortage"] = "Supply Shortage (delayed trick card)
Phase: Action phase
Target: Another player at distance 1
Effect: Place this card in target's judgement area. He performs a judgement in his judge phase: if result is not ♣, he skips his draw phase.", + [":supply_shortage"] = "Supply Shortage (delayed trick card)
Phase: Action phase
Target: Another player at distance 1
Effect: Place this card in target's judgement area. He performs a judgement in his judge phase: if result is not ♣, he skips his draw phase.", ["#supply_shortage_skill"] = "Place this card in another player's judgement area. He performs a judgement in his judge phase:
If result is not ♣, he skips his draw phase", ["guding_blade"] = "Ancient Scimitar", - [":guding_blade"] = "Ancient Scimitar (equip card, weapon)
ATK range: 2
Weapon skill: When your used Slash is about to cause DMG, if the target player has no hand cards: the DMG is increased by +1.", + [":guding_blade"] = "Ancient Scimitar (equip card, weapon)
ATK range: 2
Weapon skill: When your used Slash is about to cause DMG, if the target player has no hand cards: the DMG is increased by +1.", ["#guding_blade_skill"] = "Ancient Scimitar", ["fan"] = "Fan", - [":fan"] = "Fan (equip card, weapon)
ATK range: 4
Weapon skill: You can use any basic Slash as Fire Slash.", + [":fan"] = "Fan (equip card, weapon)
ATK range: 4
Weapon skill: You can use any basic Slash as Fire Slash.", ["#fan_skill"] = "Fan", ["vine"] = "Vine", - [":vine"] = "Vine (equip card, armor)
Armor skill: Savage Assault, Archery Attack and basic Slash have no effect on you. When you are about to suffer Fire DMG, the DMG is increased by +1.", + [":vine"] = "Vine (equip card, armor)
Armor skill: Savage Assault, Archery Attack and basic Slash have no effect on you. When you are about to suffer Fire DMG, the DMG is increased by +1.", ["#vine_skill"] = "Vine", ["silver_lion"] = "Sliver Lion", - [":silver_lion"] = "Sliver Lion (equip card, armor)
Armor skill: When you are about to suffer DMG: that DMG is reduced to 1. When you lose this card in your equipment area: you heal 1 HP.", + [":silver_lion"] = "Sliver Lion (equip card, armor)
Armor skill: When you are about to suffer DMG: that DMG is reduced to 1. When you lose this card in your equipment area: you heal 1 HP.", ["#silver_lion_skill"] = "Sliver Lion", ["hualiu"] = "Hua Liu", diff --git a/packages/maneuvering/init.lua b/packages/maneuvering/init.lua index 336f0ffa..7f4698f9 100644 --- a/packages/maneuvering/init.lua +++ b/packages/maneuvering/init.lua @@ -136,7 +136,7 @@ local analepticSkill = fk.CreateActiveSkill{ card = effect.card, }) else - to.drank = to.drank + 1 + to.drank = to.drank + 1 + ((effect.extra_data or {}).additionalDrank or 0) room:broadcastProperty(to, "drank") end end @@ -497,21 +497,21 @@ Fk:loadTranslationTable{ ["maneuvering"] = "军争", ["thunder__slash"] = "雷杀", - [":thunder__slash"] = "基本牌
时机:出牌阶段
目标:攻击范围内的一名角色
效果:对目标角色造成1点雷电伤害。", + [":thunder__slash"] = "基本牌
时机:出牌阶段
目标:攻击范围内的一名角色
效果:对目标角色造成1点雷电伤害。", ["#thunder__slash_skill"] = "选择攻击范围内的一名角色,对其造成1点雷电伤害", ["#thunder__slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点雷电伤害", ["fire__slash"] = "火杀", - [":fire__slash"] = "基本牌
时机:出牌阶段
目标:攻击范围内的一名角色
效果:对目标角色造成1点火焰伤害。", + [":fire__slash"] = "基本牌
时机:出牌阶段
目标:攻击范围内的一名角色
效果:对目标角色造成1点火焰伤害。", ["#fire__slash_skill"] = "选择攻击范围内的一名角色,对其造成1点火焰伤害", ["#fire__slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点火焰伤害", ["analeptic"] = "酒", - [":analeptic"] = "基本牌
时机:出牌阶段/你处于濒死状态时
目标:你
效果:目标角色本回合使用的下一张【杀】将要造成的伤害+1/目标角色回复1点体力。", + [":analeptic"] = "基本牌
时机:出牌阶段/你处于濒死状态时
目标:你
效果:目标角色本回合使用的下一张【杀】将要造成的伤害+1/目标角色回复1点体力。", ["#analeptic_skill"] = "你于此回合内使用的下一张【杀】的伤害值基数+1", ["iron_chain"] = "铁锁连环", - [":iron_chain"] = "锦囊牌
时机:出牌阶段
目标:一至两名角色
效果:横置或重置目标角色的武将牌。", + [":iron_chain"] = "锦囊牌
时机:出牌阶段
目标:一至两名角色
效果:横置或重置目标角色的武将牌。", ["#iron_chain_skill"] = "选择一至两名角色,这些角色横置或重置", ["_normal_use"] = "正常使用", ["recast"] = "重铸", @@ -520,29 +520,29 @@ Fk:loadTranslationTable{ ["fire_attack"] = "火攻", ["fire_attack_skill"] = "火攻", - [":fire_attack"] = "锦囊牌
时机:出牌阶段
目标:一名有手牌的角色
效果:目标角色展示一张手牌,然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害。", + [":fire_attack"] = "锦囊牌
时机:出牌阶段
目标:一名有手牌的角色
效果:目标角色展示一张手牌,然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害。", ["#fire_attack-show"] = "%src 对你使用了火攻,请展示一张手牌", ["#fire_attack-discard"] = "你可弃置一张 %arg 手牌,对 %src 造成1点火属性伤害", ["#fire_attack_skill"] = "选择一名有手牌的角色,令其展示一张手牌,
然后你可以弃置一张与此牌花色相同的手牌对其造成1点火焰伤害", ["supply_shortage"] = "兵粮寸断", - [":supply_shortage"] = "延时锦囊牌
时机:出牌阶段
目标:距离1的一名其他角色
效果:将此牌置于目标角色判定区内。其判定阶段进行判定:若结果不为♣,其跳过摸牌阶段。然后将【兵粮寸断】置入弃牌堆。", + [":supply_shortage"] = "延时锦囊牌
时机:出牌阶段
目标:距离1的一名其他角色
效果:将此牌置于目标角色判定区内。其判定阶段进行判定:若结果不为♣,其跳过摸牌阶段。然后将【兵粮寸断】置入弃牌堆。", ["#supply_shortage_skill"] = "选择距离1的一名角色,将此牌置于其判定区内。其判定阶段判定:
若结果不为♣,其跳过摸牌阶段", ["guding_blade"] = "古锭刀", - [":guding_blade"] = "装备牌·武器
攻击范围:2
武器技能:锁定技。每当你使用【杀】对目标角色造成伤害时,若该角色没有手牌,此伤害+1。", + [":guding_blade"] = "装备牌·武器
攻击范围:2
武器技能:锁定技。每当你使用【杀】对目标角色造成伤害时,若该角色没有手牌,此伤害+1。", ["#guding_blade_skill"] = "古锭刀", ["fan"] = "朱雀羽扇", - [":fan"] = "装备牌·武器
攻击范围:4
武器技能:当你声明使用普【杀】后,你可以将此【杀】改为火【杀】。", + [":fan"] = "装备牌·武器
攻击范围:4
武器技能:当你声明使用普【杀】后,你可以将此【杀】改为火【杀】。", ["#fan_skill"] = "朱雀羽扇", ["vine"] = "藤甲", - [":vine"] = "装备牌·防具
防具技能:锁定技。【南蛮入侵】、【万箭齐发】和普通【杀】对你无效。每当你受到火焰伤害时,此伤害+1。", + [":vine"] = "装备牌·防具
防具技能:锁定技。【南蛮入侵】、【万箭齐发】和普通【杀】对你无效。每当你受到火焰伤害时,此伤害+1。", ["#vine_skill"] = "藤甲", ["silver_lion"] = "白银狮子", - [":silver_lion"] = "装备牌·防具
防具技能:锁定技。每当你受到伤害时,若此伤害大于1点,防止多余的伤害。每当你失去装备区里的【白银狮子】后,你回复1点体力。", + [":silver_lion"] = "装备牌·防具
防具技能:锁定技。每当你受到伤害时,若此伤害大于1点,防止多余的伤害。每当你失去装备区里的【白银狮子】后,你回复1点体力。", ["#silver_lion_skill"] = "白银狮子", ["hualiu"] = "骅骝", diff --git a/packages/standard/aux_skills.lua b/packages/standard/aux_skills.lua index 42b31090..93d7a849 100644 --- a/packages/standard/aux_skills.lua +++ b/packages/standard/aux_skills.lua @@ -213,7 +213,7 @@ local uncompulsoryInvalidity = fk.CreateInvaliditySkill { end return (skill.frequency ~= Skill.Compulsory and skill.frequency ~= Skill.Wake) and - not (skill:isEquipmentSkill() or skill.name:endsWith("&")) and + skill:isPlayerSkill(from) and hasMark(from, MarkEnum.UncompulsoryInvalidity, MarkEnum.TempMarkSuffix) -- ( -- from:getMark(MarkEnum.UncompulsoryInvalidity) ~= 0 or diff --git a/packages/standard/game_rule.lua b/packages/standard/game_rule.lua index e893d2b4..b5ba5a8e 100644 --- a/packages/standard/game_rule.lua +++ b/packages/standard/game_rule.lua @@ -53,7 +53,14 @@ GameRule = fk.CreateTriggerSkill{ end) if #cardNames == 0 then return end - local peach_use = room:askForUseCard(player, "peach", table.concat(cardNames, ",") , prompt, true, {analepticRecover = true}) + local peach_use = room:askForUseCard( + player, + "peach", + table.concat(cardNames, ","), + prompt, + true, + {analepticRecover = true, must_targets = { dyingPlayer.id }} + ) if not peach_use then break end peach_use.tos = { {dyingPlayer.id} } if peach_use.card.trueName == "analeptic" then diff --git a/packages/standard/i18n/en_US.lua b/packages/standard/i18n/en_US.lua index bef7f82d..514843d9 100644 --- a/packages/standard/i18n/en_US.lua +++ b/packages/standard/i18n/en_US.lua @@ -52,6 +52,7 @@ Fk:loadTranslationTable({ ["liubei"] = "Liu Bei", ["rende"] = "Benevolence", [":rende"] = "In your Action Phase: you can give any # of hand cards to other players; then, if you have given a total of 2 or more cards, you heal 1 HP (only once).", + ["#rende-active"] = "Use Benevolence, give any # of hand cards to other players;
then, if you have given a total of 2 or more cards, you heal 1 HP (only once)", ["jijiang"] = "Rouse", [":jijiang"] = "(lord) When you need to use/play Slash: you can ask other Shu characters to play Slash, which is regard as you use/play that.", ["#jijiang-ask"] = "Rouse: you can play a Slash, which is regarded as %src uses/plays", @@ -89,6 +90,7 @@ Fk:loadTranslationTable({ ["sunquan"] = "Sun Quan", ["zhiheng"] = "Balance of Power", [":zhiheng"] = "Once per Action Phase: you can discard any # of cards; then, draw the same # of cards.", + ["#zhiheng-active"] = "Use Balance of Power, discard any # of cards; then, draw the same # of cards", ["jiuyuan"] = "Rescued", [":jiuyuan"] = "(lord, forced) When another Wu character uses Peach to you, you heal +1 HP.", @@ -103,18 +105,20 @@ Fk:loadTranslationTable({ ["huanggai"] = "Huang Gai", ["kurou"] = "Trojan Flesh", [":kurou"] = "In your Action Phase: you can lose 1 HP; then, draw 2 cards.", + ["#kurou-active"] = "Use Trojan Flesh, lose 1 HP; then, draw 2 cards", ["zhouyu"] = "Zhou Yu", ["yingzi"] = "Handsome", [":yingzi"] = "In your Draw Phase: you can draw +1 additional card.", ["fanjian"] = "Sow Dissension", - [":fanjian"] = "Once per Action Phase: you can make another player choose 1 suit; then, that player takes 1 hand card from you and displays it. If the guess was wrong, you cause him 1 DMG.", + [":fanjian"] = "Once per Action Phase: you can make another player choose 1 suit; then, he takes 1 hand card from you and displays it. If the guess was wrong, you cause him 1 DMG.", + ["#fanjian-active"] = "Use Sow Dissension, select another player; he chooses 1 suit;
then he takes 1 hand card from you and displays it. If the guess was wrong, you cause him 1 DMG", ["daqiao"] = "Da Qiao", ["guose"] = "National Beauty", [":guose"] = "You can use any diamond card as Indulgence.", ["liuli"] = "Shirk", - [":liuli"] = "When you become the target of Slash: you can discard 1 card and select another player (except the attacker) within your attack range; then, that player becomes the target of the Slash instead.", + [":liuli"] = "When you become the target of Slash: you can discard 1 card and select another player (except the attacker) within your attack range; then, he becomes the target of the Slash instead.", ["#liuli-target"] = "Shirk: you can discard 1 card and transfer the Slash", ["luxun"] = "Lu Xun", @@ -128,10 +132,12 @@ Fk:loadTranslationTable({ [":xiaoji"] = "After you lose 1 card in your equipment area: you can draw 2 cards.", ["jieyin"] = "Marriage", [":jieyin"] = "Once per Action Phase: you can discard 2 hand cards and select a hurt male character; then, both of you heal 1 HP.", + ["#jieyin-active"] = "Use Marriage, discard 2 hand cards and select a hurt male character; then, both of you heal 1 HP", ["huatuo"] = "Hua Tuo", ["qingnang"] = "Green Salve", [":qingnang"] = "Once per Action Phase: you can discard 1 hand card and select a wounded player; then, he heals 1 HP.", + ["#qingnang-active"] = "Use Green Salve, discard 1 hand card and select a wounded player; then, he heals 1 HP", ["jijiu"] = "First Aid", [":jijiu"] = "Outside of your turn: you can use any red card as Peach.", @@ -142,6 +148,7 @@ Fk:loadTranslationTable({ ["diaochan"] = "Diao Chan", ["lijian"] = "Seed of Animosity", [":lijian"] = "Once per Action Phase: you may discard 1 card and select 2 male characters; then, this is regarded as one of them having used Duel to target the other. This Duel can't be countered by Nullification.", + ["#lijian-active"] = "Use Seed of Animosity, discard 1 card and select 2 male characters;
then, this is regarded as one of them having used Duel to target the other.
This Duel can't be countered by Nullification", ["biyue"] = "Envious by Moon", [":biyue"] = "In your Finish Phase, you can draw 1 card.", diff --git a/packages/standard/i18n/zh_CN.lua b/packages/standard/i18n/zh_CN.lua index b633733c..708c4e14 100644 --- a/packages/standard/i18n/zh_CN.lua +++ b/packages/standard/i18n/zh_CN.lua @@ -100,6 +100,7 @@ Fk:loadTranslationTable{ ["$rende2"] = "唯贤唯德,能服于人。", ["rende"] = "仁德", [":rende"] = "出牌阶段,你可以将至少一张手牌任意分配给其他角色。你于本阶段内以此法给出的手牌首次达到两张或更多后,你回复1点体力。", + ["#rende-active"] = "发动 仁德,将至少一张手牌交给其他角色", ["$jijiang1"] = "蜀将何在?", ["$jijiang2"] = "尔等敢应战否?", ["jijiang"] = "激将", @@ -175,7 +176,8 @@ Fk:loadTranslationTable{ ["$zhiheng1"] = "容我三思。", ["$zhiheng2"] = "且慢。", ["zhiheng"] = "制衡", - [":zhiheng"] = "出牌阶段限一次,你可以弃置至少一张牌然后摸等量的牌。", + [":zhiheng"] = "出牌阶段限一次,你可以弃置任意张牌,然后摸等量的牌。", + ["#zhiheng-active"] = "发动 制衡,弃置任意张牌,然后摸等量的牌", ["$jiuyuan1"] = "有汝辅佐,甚好!", ["$jiuyuan2"] = "好舒服啊。", ["jiuyuan"] = "救援", @@ -206,7 +208,8 @@ Fk:loadTranslationTable{ ["$kurou1"] = "请鞭笞我吧,公瑾!", ["$kurou2"] = "赴汤蹈火,在所不辞!", ["kurou"] = "苦肉", - [":kurou"] = "出牌阶段,你可以失去1点体力然后摸两张牌。", + [":kurou"] = "出牌阶段,你可以失去1点体力,然后摸两张牌。", + ["#kurou-active"] = "发动 苦肉,失去1点体力,然后摸两张牌", ["zhouyu"] = "周瑜", ["#zhouyu"] = "大都督", @@ -219,7 +222,8 @@ Fk:loadTranslationTable{ ["$fanjian1"] = "挣扎吧,在血和暗的深渊里!", ["$fanjian2"] = "痛苦吧,在仇与恨的地狱中!", ["fanjian"] = "反间", - [":fanjian"] = "阶段技。你可以令一名其他角色选择一种花色,然后正面朝上获得你的一张手牌。若此牌花色与该角色所选花色不同,你对其造成1点伤害。", + [":fanjian"] = "出牌阶段限一次,你可以令一名其他角色选择一种花色,然后正面朝上获得你的一张手牌。若此牌花色与其所选花色不同,你对其造成1点伤害。", + ["#fanjian-active"] = "发动 反间,选择一名其他角色,令其选择一种花色,然后正面朝上获得你的一张手牌
若此牌花色与其所选花色不同,你对其造成1点伤害", ["daqiao"] = "大乔", ["#daqiao"] = "矜持之花", @@ -246,7 +250,7 @@ Fk:loadTranslationTable{ ["$lianying1"] = "牌不是万能的,但是没牌是万万不能的。", ["$lianying2"] = "旧的不去,新的不来。", ["lianying"] = "连营", - [":lianying"] = "当你失去最后的手牌后,你可以摸一张牌。", + [":lianying"] = "当你失去手牌后,若你没有手牌,你可以摸一张牌。", ["sunshangxiang"] = "孙尚香", ["#sunshangxiang"] = "弓腰姬", @@ -259,7 +263,8 @@ Fk:loadTranslationTable{ ["$jieyin1"] = "夫君,身体要紧。", ["$jieyin2"] = "他好,我也好。", ["jieyin"] = "结姻", - [":jieyin"] = "出牌阶段限一次,你可以弃置两张手牌并选择一名已受伤的男性角色:若如此做,你和该角色各回复1点体力。", + [":jieyin"] = "出牌阶段限一次,你可以弃置两张手牌并选择一名已受伤的男性角色,然后你与其各回复1点体力。", + ["#jieyin-active"] = "发动 结姻,弃置两张手牌并选择一名已受伤的男性角色,你与其各回复1点体力", ["huatuo"] = "华佗", ["#huatuo"] = "神医", @@ -268,7 +273,8 @@ Fk:loadTranslationTable{ ["$qingnang1"] = "早睡早起,方能养生。", ["$qingnang2"] = "越老越要补啊。", ["qingnang"] = "青囊", - [":qingnang"] = "出牌阶段限一次,你可以弃置一张手牌并选择一名已受伤的角色:若如此做,该角色回复1点体力。", + [":qingnang"] = "出牌阶段限一次,你可以弃置一张手牌并选择一名已受伤的角色,然后其回复1点体力。", + ["#qingnang-active"] = "发动 青囊,弃置一张手牌并选择一名已受伤的角色,其回复1点体力", ["$jijiu1"] = "别紧张,有老夫呢。", ["$jijiu2"] = "救人一命,胜造七级浮屠。", ["jijiu"] = "急救", @@ -291,6 +297,7 @@ Fk:loadTranslationTable{ ["$lijian2"] = "夫君,你要替妾身作主啊……", ["lijian"] = "离间", [":lijian"] = "出牌阶段限一次,你可以弃置一张牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被【无懈可击】的【决斗】。", + ["#lijian-active"] = "发动 离间,弃置一张手牌并选择两名其他男性角色,后选择的角色视为对先选择的角色使用了一张不能被【无懈可击】的【决斗】", ["$biyue1"] = "失礼了~", ["$biyue2"] = "羡慕吧~", ["biyue"] = "闭月", @@ -529,4 +536,7 @@ Fk:loadTranslationTable{ ["revealDeputy"] = "明置副将 %arg", ["game_rule"] = "弃牌阶段", + ["replace_equip"] = "替换装备", + ["#EquipmentChoice"] = "%arg", + ["#GameRuleReplaceEquipment"] = "请选择要置入的区域", } diff --git a/packages/standard/init.lua b/packages/standard/init.lua index cc20fca1..8daa6022 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -13,11 +13,7 @@ local jianxiong = fk.CreateTriggerSkill{ anim_type = "masochism", events = {fk.Damaged}, can_trigger = function(self, event, target, player, data) - if target == player and player:hasSkill(self) and data.card then - local room = player.room - local subcards = data.card:isVirtual() and data.card.subcards or {data.card.id} - return #subcards>0 and table.every(subcards, function(id) return room:getCardArea(id) == Card.Processing end) - end + return target == player and player:hasSkill(self) and data.card and player.room:getCardArea(data.card) == Card.Processing end, on_use = function(self, event, target, player, data) player.room:obtainCard(player.id, data.card, true, fk.ReasonJustMove) @@ -157,11 +153,11 @@ local tuxi = fk.CreateTriggerSkill{ events = {fk.EventPhaseStart}, can_trigger = function(self, event, target, player, data) return target == player and player:hasSkill(self) and player.phase == Player.Draw and - table.find(player.room:getOtherPlayers(player), function(p) return not p:isKongcheng() end) + table.find(player.room:getOtherPlayers(player, false), function(p) return not p:isKongcheng() end) end, on_cost = function(self, event, target, player, data) local room = player.room - local targets = table.map(table.filter(room:getOtherPlayers(player), function(p) + local targets = table.map(table.filter(room:getOtherPlayers(player, false), function(p) return not p:isKongcheng() end), Util.IdMapper) local result = room:askForChoosePlayers(player, targets, 1, 2, "#tuxi-ask", self.name) @@ -273,7 +269,8 @@ local yiji = fk.CreateTriggerSkill{ for _, id in ipairs(ret.cards) do table.removeOne(ids, id) end - room:moveCardTo(ret.cards, Card.PlayerHand, room:getPlayerById(ret.targets[1]), fk.ReasonGive, self.name, nil, false, player.id) + room:moveCardTo(ret.cards, Card.PlayerHand, room:getPlayerById(ret.targets[1]), fk.ReasonGive, + self.name, nil, false, player.id, nil, player.id) if #ids == 0 then break end if player.dead then room:moveCards({ @@ -357,6 +354,7 @@ zhenji:addSkill(qingguo) local rende = fk.CreateActiveSkill{ name = "rende", + prompt = "#rende-active", anim_type = "support", card_filter = function(self, to_select, selected) return Fk:currentRoom():getCardArea(to_select) == Card.PlayerHand @@ -633,6 +631,7 @@ huangyueying:addSkill(qicai) local zhiheng = fk.CreateActiveSkill{ name = "zhiheng", + prompt = "#zhiheng-active", anim_type = "drawcard", can_use = function(self, player) return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0 @@ -737,10 +736,9 @@ lvmeng:addSkill(keji) local kurou = fk.CreateActiveSkill{ name = "kurou", + prompt = "#kurou-active", anim_type = "drawcard", - card_filter = function(self, to_select, selected, selected_targets) - return false - end, + card_filter = Util.FalseFunc, on_use = function(self, room, effect) local from = room:getPlayerById(effect.from) room:loseHp(from, 1, self.name) @@ -762,6 +760,7 @@ local yingzi = fk.CreateTriggerSkill{ } local fanjian = fk.CreateActiveSkill{ name = "fanjian", + prompt = "#fanjian-active", can_use = function(self, player) return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0 end, @@ -937,6 +936,7 @@ local xiaoji = fk.CreateTriggerSkill{ } local jieyin = fk.CreateActiveSkill{ name = "jieyin", + prompt = "#jieyin-active", anim_type = "support", can_use = function(self, player) return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0 @@ -978,6 +978,7 @@ sunshangxiang:addSkill(jieyin) local qingnang = fk.CreateActiveSkill{ name = "qingnang", + prompt = "#qingnang-active", anim_type = "support", can_use = function(self, player) return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0 @@ -993,8 +994,8 @@ local qingnang = fk.CreateActiveSkill{ card_num = 1, on_use = function(self, room, effect) local from = room:getPlayerById(effect.from) - room:throwCard(effect.cards, self.name, from, from) local to = room:getPlayerById(effect.tos[1]) + room:throwCard(effect.cards, self.name, from, from) if to:isAlive() and to:isWounded() then room:recover({ who = to, @@ -1063,6 +1064,7 @@ lvbu:addSkill(wushuang) local lijian = fk.CreateActiveSkill{ name = "lijian", + prompt = "#lijian-active", anim_type = "offensive", can_use = function(self, player) return player:usedSkillTimes(self.name, Player.HistoryPhase) == 0 @@ -1072,7 +1074,9 @@ local lijian = fk.CreateActiveSkill{ end, target_filter = function(self, to_select, selected) if #selected < 2 and to_select ~= Self.id then - return Fk:currentRoom():getPlayerById(to_select):isMale() + local target = Fk:currentRoom():getPlayerById(to_select) + return target:isMale() and (#selected == 0 or + target:canUseTo(Fk:cloneCard("duel"), Fk:currentRoom():getPlayerById(selected[1]))) end end, target_num = 2, @@ -1151,12 +1155,10 @@ local role_getlogic = function() end) room:returnToGeneralPile(generals) - room:setPlayerGeneral(lord, lord_general, true) + room:prepareGeneral(lord, lord_general, deputy, true) + room:askForChooseKingdom({lord}) - room:broadcastProperty(lord, "general") room:broadcastProperty(lord, "kingdom") - room:setDeputyGeneral(lord, deputy) - room:broadcastProperty(lord, "deputyGeneral") -- 显示技能 local canAttachSkill = function(player, skillName) @@ -1210,8 +1212,7 @@ local role_getlogic = function() end local nonlord = room:getOtherPlayers(lord, true) - local generals = room:getNGenerals(#nonlord * generalNum) - table.shuffle(generals) + local generals = table.random(room.general_pile, #nonlord * generalNum) for i, p in ipairs(nonlord) do local arg = table.slice(generals, (i - 1) * generalNum + 1, i * generalNum + 1) p.request_data = json.encode{ arg, n } @@ -1221,30 +1222,22 @@ local role_getlogic = function() room:notifyMoveFocus(nonlord, "AskForGeneral") room:doBroadcastRequest("AskForGeneral", nonlord) - local selected = {} for _, p in ipairs(nonlord) do + local general, deputy if p.general == "" and p.reply_ready then local general_ret = json.decode(p.client_reply) - local general = general_ret[1] - local deputy = general_ret[2] - table.insertTableIfNeed(selected, general_ret) - room:setPlayerGeneral(p, general, true, true) - room:setDeputyGeneral(p, deputy) + general = general_ret[1] + deputy = general_ret[2] else - table.insertTableIfNeed(selected, p.default_reply) - room:setPlayerGeneral(p, p.default_reply[1], true, true) - room:setDeputyGeneral(p, p.default_reply[2]) + general = p.default_reply[1] + deputy = p.default_reply[2] end + room:findGeneral(general) + room:findGeneral(deputy) + room:prepareGeneral(p, general, deputy) p.default_reply = "" end - generals = table.filter(generals, function(g) - return not table.find(selected, function(lg) - return Fk.generals[lg].trueName == Fk.generals[g].trueName - end) - end) - room:returnToGeneralPile(generals) - room:askForChooseKingdom(nonlord) end diff --git a/packages/standard_cards/i18n/en_US.lua b/packages/standard_cards/i18n/en_US.lua index 0c0f9d6c..4a353c52 100644 --- a/packages/standard_cards/i18n/en_US.lua +++ b/packages/standard_cards/i18n/en_US.lua @@ -52,6 +52,8 @@ Fk:loadTranslationTable({ ["method_draw"] = "draw", ["method_discard"] = "discard", + ["prohibit"] = " prohibit ", + ["slash"] = "Slash", [":slash"] = "Slash (basic card)
Phase: Action phase
Target: Another player within your ATK range
Effect: Deal 1 DMG to the targets.
Note: You can only use 1 Slash per action phase.", ["#slash-jink"] = "%src used Slash to you, please use a Dodge", diff --git a/packages/standard_cards/i18n/zh_CN.lua b/packages/standard_cards/i18n/zh_CN.lua index 66f7700f..f5a6afa2 100644 --- a/packages/standard_cards/i18n/zh_CN.lua +++ b/packages/standard_cards/i18n/zh_CN.lua @@ -52,10 +52,12 @@ Fk:loadTranslationTable{ ["method_draw"] = "摸", ["method_discard"] = "弃置", + ["prohibit"] = "禁", + ["slash"] = "杀", [":slash"] = "基本牌
时机:出牌阶段
目标:攻击范围内的一名角色
效果:对目标角色造成1点伤害。", - ["#slash-jink"] = "%src 对你使用了杀,请使用一张闪", - ["#slash-jink-multi"] = "%src 对你使用了杀,请使用一张闪(此为第 %arg 张,共需 %arg2 张)", + ["#slash-jink"] = "%src 对你使用了【杀】,请使用一张【闪】", + ["#slash-jink-multi"] = "%src 对你使用了【杀】,请使用一张【闪】(此为第 %arg 张,共需 %arg2 张)", ["#slash_skill"] = "选择攻击范围内的一名角色,对其造成1点伤害", ["#slash_skill_multi"] = "选择攻击范围内的至多%arg名角色,对这些角色各造成1点伤害", diff --git a/packages/standard_cards/init.lua b/packages/standard_cards/init.lua index 962e3d11..ef594209 100644 --- a/packages/standard_cards/init.lua +++ b/packages/standard_cards/init.lua @@ -622,6 +622,7 @@ local amazingGraceSkill = fk.CreateActiveSkill{ ids = toDisplay, toArea = Card.Processing, moveReason = fk.ReasonPut, + proposer = use.from, }) table.forEach(room.players, function(p) @@ -722,7 +723,17 @@ local lightningSkill = fk.CreateActiveSkill{ local nextp = to repeat nextp = nextp:getNextAlive(true) - if nextp == to then break end + if nextp == to then + if nextp:isProhibited(nextp, effect.card) then + room:moveCards{ + ids = room:getSubcardsByRule(effect.card, { Card.Processing }), + toArea = Card.DiscardPile, + moveReason = fk.ReasonPut + } + return + end + break + end until not nextp:hasDelayedTrick("lightning") and not nextp:isProhibited(nextp, effect.card) @@ -816,10 +827,16 @@ local crossbowAudio = fk.CreateTriggerSkill{ local crossbowSkill = fk.CreateTargetModSkill{ name = "#crossbow_skill", attached_equip = "crossbow", - bypass_times = function(self, player, skill, scope) - if player:hasSkill(self) and skill.trueName == "slash_skill" - and scope == Player.HistoryPhase then - return true + bypass_times = function(self, player, skill, scope, card) + if player:hasSkill(self) and skill.trueName == "slash_skill" and scope == Player.HistoryPhase then + --FIXME: 无法检测到非转化的cost选牌的情况,如活墨等 + local cardIds = Card:getIdList(card) + local crossbows = table.filter(player:getEquipments(Card.SubtypeWeapon), function(id) + return Fk:getCardById(id).equip_skill == self + end) + return #crossbows == 0 or not table.every(crossbows, function(id) + return table.contains(cardIds, id) + end) end end, } @@ -1156,9 +1173,13 @@ local eightDiagramSkill = fk.CreateTriggerSkill{ attached_equip = "eight_diagram", events = {fk.AskForCardUse, fk.AskForCardResponse}, can_trigger = function(self, event, target, player, data) - return target == player and player:hasSkill(self) and - (data.cardName == "jink" or (data.pattern and Exppattern:Parse(data.pattern):matchExp("jink|0|nosuit|none"))) and - (event == fk.AskForCardUse and not player:prohibitUse(Fk:cloneCard("jink")) or not player:prohibitResponse(Fk:cloneCard("jink"))) + if not (target == player and player:hasSkill(self) and + (data.cardName == "jink" or (data.pattern and Exppattern:Parse(data.pattern):matchExp("jink|0|nosuit|none")))) then return end + if event == fk.AskForCardUse then + return not player:prohibitUse(Fk:cloneCard("jink")) + else + return not player:prohibitResponse(Fk:cloneCard("jink")) + end end, on_use = function(self, event, target, player, data) local room = player.room diff --git a/src/core/util.cpp b/src/core/util.cpp index 6c517955..4decfa29 100644 --- a/src/core/util.cpp +++ b/src/core/util.cpp @@ -172,10 +172,16 @@ static void writeDirMD5(QFile &dest, const QString &dir, static void writeFkVerMD5(QFile &dest) { QFile flist("fk_ver"); if (flist.exists() && flist.open(QIODevice::ReadOnly)) { + flist.readLine(); + QStringList allNames; while (true) { QByteArray bytes = flist.readLine().simplified(); if (bytes.isNull()) break; - writeFileMD5(dest, bytes); + allNames << QString::fromLocal8Bit(bytes); + } + allNames.sort(); + foreach(auto s, allNames) { + writeFileMD5(dest, s); } } } diff --git a/src/network/server_socket.cpp b/src/network/server_socket.cpp index 989ab90e..777f2575 100644 --- a/src/network/server_socket.cpp +++ b/src/network/server_socket.cpp @@ -4,6 +4,7 @@ #include "network/client_socket.h" #include "server/server.h" #include "core/util.h" +#include ServerSocket::ServerSocket(QObject *parent) : QObject(parent) { server = new QTcpServer(this); diff --git a/src/server/auth.cpp b/src/server/auth.cpp index 9fc70ff1..fac1cde5 100644 --- a/src/server/auth.cpp +++ b/src/server/auth.cpp @@ -3,6 +3,7 @@ #include "server/serverplayer.h" #include "core/util.h" #include "network/client_socket.h" +#include AuthManager::AuthManager(QObject *parent) : QObject(parent) { rsa = initRSA(); diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 0f821901..db4f363f 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -547,16 +547,16 @@ void QmlBackend::readPendingDatagrams() { } void QmlBackend::showDialog(const QString &type, const QString &text, const QString &orig) { - static const QString title = tr("FreeKill") + " v" + FK_VERSION; + //static const QString title = tr("FreeKill") + " v" + FK_VERSION; QMessageBox *box = nullptr; if (type == "critical") { - box = new QMessageBox(QMessageBox::Critical, title, text, QMessageBox::Ok); + box = new QMessageBox(QMessageBox::Critical, text, text, QMessageBox::Ok); connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater); } else if (type == "info") { - box = new QMessageBox(QMessageBox::Information, title, text, QMessageBox::Ok); + box = new QMessageBox(QMessageBox::Information, text, text, QMessageBox::Ok); connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater); } else if (type == "warning") { - box = new QMessageBox(QMessageBox::Warning, title, text, QMessageBox::Ok); + box = new QMessageBox(QMessageBox::Warning, text, text, QMessageBox::Ok); connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater); }