diff --git a/Fk/Pages/Room.qml b/Fk/Pages/Room.qml index a8622381..8db1f875 100644 --- a/Fk/Pages/Room.qml +++ b/Fk/Pages/Room.qml @@ -747,6 +747,26 @@ Item { } } + // manualBox: same as popupBox, but must be closed manually + Loader { + id: manualBox + z: 999 + onSourceChanged: { + if (item === null) + return; + item.finished.connect(() => sourceComponent = undefined); + item.widthChanged.connect(() => manualBox.moveToCenter()); + item.heightChanged.connect(() => manualBox.moveToCenter()); + moveToCenter(); + } + onSourceComponentChanged: sourceChanged(); + + function moveToCenter() { + item.x = Math.round((roomArea.width - item.width) / 2); + item.y = Math.round(roomArea.height * 0.67 - item.height / 2); + } + } + Loader { id: popupBox z: 999 @@ -772,27 +792,6 @@ Item { } } - // manualBox: same as popupBox, but must be closed manually - Loader { - id: manualBox - z: 999 - onSourceChanged: { - if (item === null) - return; - item.finished.connect(() => sourceComponent = undefined); - item.widthChanged.connect(() => manualBox.moveToCenter()); - item.heightChanged.connect(() => manualBox.moveToCenter()); - moveToCenter(); - } - onSourceComponentChanged: sourceChanged(); - - function moveToCenter() - { - item.x = Math.round((roomArea.width - item.width) / 2); - item.y = Math.round(roomArea.height * 0.67 - item.height / 2); - } - } - Loader { id: bigAnim anchors.fill: parent diff --git a/lua/client/client_util.lua b/lua/client/client_util.lua index 6a3966d7..9f3a4416 100644 --- a/lua/client/client_util.lua +++ b/lua/client/client_util.lua @@ -1,698 +1,748 @@ -- SPDX-License-Identifier: GPL-3.0-or-later -- All functions in this file are used by Qml function Translate(src) - return Fk:translate(src) + return Fk:translate(src) end function GetGeneralData(name) - local general = Fk.generals[name] - if general == nil then - general = Fk.generals["diaochan"] - end - return json.encode { - package = general.package.name, - extension = general.package.extensionName, - kingdom = general.kingdom, - subkingdom = general.subkingdom, - hp = general.hp, - maxHp = general.maxHp, - shield = general.shield, - hidden = general.hidden, - total_hidden = general.total_hidden - } + local general = Fk.generals[name] + if general == nil then + general = Fk.generals["diaochan"] + end + return json.encode { + package = general.package.name, + extension = general.package.extensionName, + kingdom = general.kingdom, + subkingdom = general.subkingdom, + hp = general.hp, + maxHp = general.maxHp, + shield = general.shield, + hidden = general.hidden, + total_hidden = general.total_hidden + } end function GetGeneralDetail(name) - local general = Fk.generals[name] - if general == nil then - general = Fk.generals["diaochan"] + local general = Fk.generals[name] + if general == nil then + general = Fk.generals["diaochan"] + end + local ret = { + package = general.package.name, + extension = general.package.extensionName, + kingdom = general.kingdom, + hp = general.hp, + maxHp = general.maxHp, + gender = general.gender, + skill = {}, + related_skill = {}, + companions = general.companions + } + for _, s in ipairs(general.skills) do + table.insert(ret.skill, { + name = s.name, + description = Fk:getDescription(s.name) + }) + end + for _, s in ipairs(general.other_skills) do + table.insert(ret.skill, { + name = s, + description = Fk:getDescription(s) + }) + end + for _, s in ipairs(general.related_skills) do + table.insert(ret.related_skill, { + name = s.name, + description = Fk:getDescription(s.name) + }) + end + for _, s in ipairs(general.related_other_skills) do + table.insert(ret.related_skill, { + name = s, + description = Fk:getDescription(s) + }) + end + for _, g in pairs(Fk.generals) do + if table.contains(g.companions, general.name) then + table.insertIfNeed(ret.companions, g.name) end - local ret = { - package = general.package.name, - extension = general.package.extensionName, - kingdom = general.kingdom, - hp = general.hp, - maxHp = general.maxHp, - gender = general.gender, - skill = {}, - related_skill = {}, - companions = general.companions - } - for _, s in ipairs(general.skills) do - table.insert(ret.skill, { - name = s.name, - description = Fk:getDescription(s.name) - }) - end - for _, s in ipairs(general.other_skills) do - table.insert(ret.skill, { - name = s, - description = Fk:getDescription(s) - }) - end - for _, s in ipairs(general.related_skills) do - table.insert(ret.related_skill, { - name = s.name, - description = Fk:getDescription(s.name) - }) - end - for _, s in ipairs(general.related_other_skills) do - table.insert(ret.related_skill, { - name = s, - description = Fk:getDescription(s) - }) - end - for _, g in pairs(Fk.generals) do - if table.contains(g.companions, general.name) then - table.insertIfNeed(ret.companions, g.name) - end - end - return json.encode(ret) + end + return json.encode(ret) end function GetSameGenerals(name) - return json.encode(Fk:getSameGenerals(name)) + return json.encode(Fk:getSameGenerals(name)) end local cardSubtypeStrings = { - [Card.SubtypeNone] = "none", - [Card.SubtypeDelayedTrick] = "delayed_trick", - [Card.SubtypeWeapon] = "weapon", - [Card.SubtypeArmor] = "armor", - [Card.SubtypeDefensiveRide] = "defensive_horse", - [Card.SubtypeOffensiveRide] = "offensive_horse", - [Card.SubtypeTreasure] = "treasure" + [Card.SubtypeNone] = "none", + [Card.SubtypeDelayedTrick] = "delayed_trick", + [Card.SubtypeWeapon] = "weapon", + [Card.SubtypeArmor] = "armor", + [Card.SubtypeDefensiveRide] = "defensive_horse", + [Card.SubtypeOffensiveRide] = "offensive_horse", + [Card.SubtypeTreasure] = "treasure" } function GetCardData(id, virtualCardForm) - local card = Fk:getCardById(id) - if card == nil then - return json.encode { - cid = id, - known = false - } - end - local mark = {} - for k, v in pairs(card.mark) do - if k and k:startsWith("@") and v and v ~= 0 then - table.insert(mark, { - k = k, - v = v - }) - end - end - local ret = { - cid = id, - name = card.name, - extension = card.package.extensionName, - number = card.number, - suit = card:getSuitString(), - color = card:getColorString(), - mark = mark, - type = card.type, - subtype = cardSubtypeStrings[card.sub_type] + local card = Fk:getCardById(id) + if card == nil then + return json.encode { + cid = id, + known = false } - if card.skillName ~= "" then - local orig = Fk:getCardById(id, true) - ret.name = orig.name - ret.virt_name = card.name + end + local mark = {} + for k, v in pairs(card.mark) do + if k and k:startsWith("@") and v and v ~= 0 then + table.insert(mark, { + k = k, + v = v + }) end - if virtualCardForm then - local virtualCard = ClientInstance:getPlayerById(virtualCardForm):getVirualEquip(id) - if virtualCard then - ret.virt_name = virtualCard.name - ret.subtype = cardSubtypeStrings[virtualCard.sub_type] - end + end + local ret = { + cid = id, + name = card.name, + extension = card.package.extensionName, + number = card.number, + suit = card:getSuitString(), + color = card:getColorString(), + mark = mark, + type = card.type, + subtype = cardSubtypeStrings[card.sub_type] + } + if card.skillName ~= "" then + local orig = Fk:getCardById(id, true) + ret.name = orig.name + ret.virt_name = card.name + end + if virtualCardForm then + local virtualCard = ClientInstance:getPlayerById(virtualCardForm):getVirualEquip(id) + if virtualCard then + ret.virt_name = virtualCard.name + ret.subtype = cardSubtypeStrings[virtualCard.sub_type] end - return json.encode(ret) + end + return json.encode(ret) end function GetCardExtensionByName(cardName) - local card = table.find(Fk.cards, function(card) - return card.name == cardName - end) + local card = table.find(Fk.cards, function(card) + return card.name == cardName + end) - return card and card.package.extensionName or "" + return card and card.package.extensionName or "" end function GetAllGeneralPack() - local ret = {} - for _, name in ipairs(Fk.package_names) do - if Fk.packages[name].type == Package.GeneralPack then - table.insert(ret, name) - end + local ret = {} + for _, name in ipairs(Fk.package_names) do + if Fk.packages[name].type == Package.GeneralPack then + table.insert(ret, name) end - return json.encode(ret) + end + return json.encode(ret) end function GetGenerals(pack_name) - local ret = {} - for _, g in ipairs(Fk.packages[pack_name].generals) do - if not g.total_hidden then - table.insert(ret, g.name) - end + local ret = {} + for _, g in ipairs(Fk.packages[pack_name].generals) do + if not g.total_hidden then + table.insert(ret, g.name) end - return json.encode(ret) + end + return json.encode(ret) end function SearchAllGenerals(word) - local ret = {} - for _, name in ipairs(Fk.package_names) do - if Fk.packages[name].type == Package.GeneralPack then - table.insertTable(ret, json.decode(SearchGenerals(name, word))) - end + local ret = {} + for _, name in ipairs(Fk.package_names) do + if Fk.packages[name].type == Package.GeneralPack then + table.insertTable(ret, json.decode(SearchGenerals(name, word))) end - return json.encode(ret) + end + return json.encode(ret) end function SearchGenerals(pack_name, word) - local ret = {} - if word == "" then - return GetGenerals(pack_name) + local ret = {} + if word == "" then + return GetGenerals(pack_name) + end + for _, g in ipairs(Fk.packages[pack_name].generals) do + if not g.total_hidden and string.find(Fk:translate(g.name), word) then + table.insert(ret, g.name) end - for _, g in ipairs(Fk.packages[pack_name].generals) do - if not g.total_hidden and string.find(Fk:translate(g.name), word) then - table.insert(ret, g.name) - end - end - return json.encode(ret) + end + return json.encode(ret) end function UpdatePackageEnable(pkg, enabled) - if enabled then - table.removeOne(ClientInstance.disabled_packs, pkg) - else - table.insertIfNeed(ClientInstance.disabled_packs, pkg) - end + if enabled then + table.removeOne(ClientInstance.disabled_packs, pkg) + else + table.insertIfNeed(ClientInstance.disabled_packs, pkg) + end end function GetAvailableGeneralsNum() - local generalPool = Fk:getAllGenerals() - local except = {} - local ret = 0 - for _, g in ipairs(Fk.packages["test_p_0"].generals) do - table.insert(except, g.name) - end + local generalPool = Fk:getAllGenerals() + local except = {} + local ret = 0 + for _, g in ipairs(Fk.packages["test_p_0"].generals) do + table.insert(except, g.name) + end - local availableGenerals = {} - for _, general in pairs(generalPool) do - if not table.contains(except, general.name) then - if (not general.hidden and not general.total_hidden) and #table.filter(availableGenerals, function(g) - return g.trueName == general.trueName - end) == 0 then - ret = ret + 1 - end - end + local availableGenerals = {} + for _, general in pairs(generalPool) do + if not table.contains(except, general.name) then + if (not general.hidden and not general.total_hidden) and #table.filter(availableGenerals, function(g) + return g.trueName == general.trueName + end) == 0 then + ret = ret + 1 + end end + end - return ret + return ret end function GetAllCardPack() - local ret = {} - for _, name in ipairs(Fk.package_names) do - if Fk.packages[name].type == Package.CardPack then - table.insert(ret, name) - end + local ret = {} + for _, name in ipairs(Fk.package_names) do + if Fk.packages[name].type == Package.CardPack then + table.insert(ret, name) end - return json.encode(ret) + end + return json.encode(ret) end function GetCards(pack_name) - local ret = {} - for _, c in ipairs(Fk.packages[pack_name].cards) do - table.insert(ret, c.id) - end - return json.encode(ret) + local ret = {} + for _, c in ipairs(Fk.packages[pack_name].cards) do + table.insert(ret, c.id) + end + return json.encode(ret) end function GetCardSpecialSkills(cid) - return json.encode(Fk:getCardById(cid).special_skills or Util.DummyTable) + return json.encode(Fk:getCardById(cid).special_skills or Util.DummyTable) end function DistanceTo(from, to) - local a = ClientInstance:getPlayerById(from) - local b = ClientInstance:getPlayerById(to) - return a:distanceTo(b) + local a = ClientInstance:getPlayerById(from) + local b = ClientInstance:getPlayerById(to) + return a:distanceTo(b) end function GetPile(id, name) - return json.encode(ClientInstance:getPlayerById(id):getPile(name) or {}) + return json.encode(ClientInstance:getPlayerById(id):getPile(name) or {}) end function GetAllPiles(id) - return json.encode(ClientInstance:getPlayerById(id).special_cards or Util.DummyTable) + return json.encode(ClientInstance:getPlayerById(id).special_cards or Util.DummyTable) end function GetPlayerSkills(id) - local p = ClientInstance:getPlayerById(id) - return json.encode(table.map(p.player_skills, function(s) - return s.visible and { - name = s.name, - description = Fk:getDescription(s.name) - } or nil - end)) + local p = ClientInstance:getPlayerById(id) + return json.encode(table.map(p.player_skills, function(s) + return s.visible and { + name = s.name, + description = Fk:getDescription(s.name) + } or nil + end)) end ---@param card string | integer ---@param player integer function CanUseCard(card, player) - local c ---@type Card - if type(card) == "number" then - c = Fk:getCardById(card) + local c ---@type Card + if type(card) == "number" then + c = Fk:getCardById(card) + else + local data = json.decode(card) + local skill = Fk.skills[data.skill] + local selected_cards = data.subcards + if skill:isInstanceOf(ViewAsSkill) then + c = skill:viewAs(selected_cards) + if not c then + return "false" + end else - local data = json.decode(card) - local skill = Fk.skills[data.skill] - local selected_cards = data.subcards - if skill:isInstanceOf(ViewAsSkill) then - c = skill:viewAs(selected_cards) - if not c then - return "false" - end - else - -- ActiveSkill should return true here - return "true" - end + -- ActiveSkill should return true here + return "true" end + end - player = ClientInstance:getPlayerById(player) - local ret = c.skill:canUse(player, c) - ret = ret and not player:prohibitUse(c) - if ret then - local min_target = c.skill:getMinTargetNum() - if min_target > 0 then - for _, p in ipairs(ClientInstance.players) do - if c.skill:targetFilter(p.id, {}, {}, c) then - return "true" - end - end - return "false" + player = ClientInstance:getPlayerById(player) + local ret = c.skill:canUse(player, c) + ret = ret and not player:prohibitUse(c) + if ret then + local min_target = c.skill:getMinTargetNum() + if min_target > 0 then + for _, p in ipairs(ClientInstance.players) do + if c.skill:targetFilter(p.id, {}, {}, c) then + return "true" end + end + return "false" end - return json.encode(ret) + end + return json.encode(ret) end function CardProhibitedUse(card) - local c ---@type Card - local ret = false - if type(card) == "number" then - c = Fk:getCardById(card) - else - local data = json.decode(card) - local skill = Fk.skills[data.skill] - local selected_cards = data.subcards - if skill:isInstanceOf(ViewAsSkill) then - c = skill:viewAs(selected_cards) - end + local c ---@type Card + local ret = false + if type(card) == "number" then + c = Fk:getCardById(card) + else + local data = json.decode(card) + local skill = Fk.skills[data.skill] + local selected_cards = data.subcards + if skill:isInstanceOf(ViewAsSkill) then + c = skill:viewAs(selected_cards) end - if c == nil then - return "true" - else - ret = Self:prohibitUse(c) - end - return json.encode(ret) + end + if c == nil then + return "true" + else + ret = Self:prohibitUse(c) + end + return json.encode(ret) end ---@param card string | integer ---@param to_select integer @ id of the target ---@param selected integer[] @ ids of selected targets function CanUseCardToTarget(card, to_select, selected) - if ClientInstance:getPlayerById(to_select).dead then - return "false" - end - 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 ActiveTargetFilter(t.skill, to_select, selected, t.subcards) - end + if ClientInstance:getPlayerById(to_select).dead then + return "false" + end + 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 ActiveTargetFilter(t.skill, to_select, selected, t.subcards) + end - local ret = c.skill:targetFilter(to_select, selected, selected_cards, c) - ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), c) - return json.encode(ret) + local ret = c.skill:targetFilter(to_select, selected, selected_cards, c) + ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), c) + return json.encode(ret) end ---@param card string | integer ---@param to_select integer @ id of a card not selected ---@param selected_targets integer[] @ ids of selected players function CanSelectCardForSkill(card, to_select, selected_targets) - local c ---@type Card - local selected_cards - if type(card) == "number" then - c = Fk:getCardById(card) - selected_cards = {card} - else - error() - end + local c ---@type Card + local selected_cards + if type(card) == "number" then + c = Fk:getCardById(card) + selected_cards = { card } + else + error() + end - local ret = c.skill:cardFilter(to_select, selected_cards, selected_targets) - return json.encode(ret) + local ret = c.skill:cardFilter(to_select, selected_cards, selected_targets) + return json.encode(ret) end ---@param card string | integer ---@param selected_targets integer[] @ ids of selected players function CardFeasible(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 ActiveFeasible(t.skill, selected_targets, t.subcards) - end + 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 ActiveFeasible(t.skill, selected_targets, t.subcards) + end - local ret = c.skill:feasible(selected_targets, selected_cards, Self, c) - return json.encode(ret) + local ret = c.skill:feasible(selected_targets, selected_cards, Self, c) + return json.encode(ret) end -- Handle skills function GetSkillData(skill_name) - local skill = Fk.skills[skill_name] - if not skill then - return "null" - end - local freq = "notactive" - if skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill) then - freq = "active" - end - local frequency - if skill.frequency == Skill.Limited then - frequency = "limit" - elseif skill.frequency == Skill.Wake then - frequency = "wake" - elseif skill.frequency == Skill.Quest then - frequency = "quest" - end - return json.encode { - skill = Fk:translate(skill_name), - orig_skill = skill_name, - extension = skill.package.extensionName, - freq = freq, - frequency = frequency, - switchSkillName = skill.switchSkillName, - isViewAsSkill = skill:isInstanceOf(ViewAsSkill) - } + local skill = Fk.skills[skill_name] + if not skill then + return "null" + end + local freq = "notactive" + if skill:isInstanceOf(ActiveSkill) or skill:isInstanceOf(ViewAsSkill) then + freq = "active" + end + local frequency + if skill.frequency == Skill.Limited then + frequency = "limit" + elseif skill.frequency == Skill.Wake then + frequency = "wake" + elseif skill.frequency == Skill.Quest then + frequency = "quest" + end + return json.encode { + skill = Fk:translate(skill_name), + orig_skill = skill_name, + extension = skill.package.extensionName, + freq = freq, + frequency = frequency, + switchSkillName = skill.switchSkillName, + isViewAsSkill = skill:isInstanceOf(ViewAsSkill) + } end function ActiveCanUse(skill_name) - local skill = Fk.skills[skill_name] - local ret = false - if skill then - if skill:isInstanceOf(ActiveSkill) then - ret = skill:canUse(Self) - elseif skill:isInstanceOf(ViewAsSkill) then - ret = skill:enabledAtPlay(Self) - if ret then - local exp = Exppattern:Parse(skill.pattern) - local cnames = {} - for _, m in ipairs(exp.matchers) do - if m.name then - table.insertTable(cnames, m.name) - end - if m.trueName then - table.insertTable(cnames, m.trueName) - end - end - for _, n in ipairs(cnames) do - local c = Fk:cloneCard(n) - c.skillName = skill_name - ret = c.skill:canUse(Self, c) - if ret then - break - end - end - end + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:canUse(Self) + elseif skill:isInstanceOf(ViewAsSkill) then + ret = skill:enabledAtPlay(Self) + if ret then + local exp = Exppattern:Parse(skill.pattern) + local cnames = {} + for _, m in ipairs(exp.matchers) do + if m.name then + table.insertTable(cnames, m.name) + end + if m.trueName then + table.insertTable(cnames, m.trueName) + end end + for _, n in ipairs(cnames) do + local c = Fk:cloneCard(n) + c.skillName = skill_name + ret = c.skill:canUse(Self, c) + if ret then + break + end + end + end end - return json.encode(ret) + end + return json.encode(ret) end function ActiveSkillPrompt(skill_name, selected, selected_targets) - local skill = Fk.skills[skill_name] - local ret = false - if skill then - if type(skill.prompt) == "function" then - ret = skill:prompt(selected, selected_targets) - else - ret = skill.prompt - end + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if type(skill.prompt) == "function" then + ret = skill:prompt(selected, selected_targets) + else + ret = skill.prompt end - return json.encode(ret or "") + end + return json.encode(ret or "") end function ActiveCardFilter(skill_name, to_select, selected, selected_targets) - local skill = Fk.skills[skill_name] - local ret = false - if skill then - if skill:isInstanceOf(ActiveSkill) then - ret = skill:cardFilter(to_select, selected, selected_targets) - elseif skill:isInstanceOf(ViewAsSkill) then - ret = skill:cardFilter(to_select, selected) - end + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:cardFilter(to_select, selected, selected_targets) + elseif skill:isInstanceOf(ViewAsSkill) then + ret = skill:cardFilter(to_select, selected) end - return json.encode(ret) + end + return json.encode(ret) end function ActiveTargetFilter(skill_name, to_select, selected, selected_cards) - local skill = Fk.skills[skill_name] - local ret = false - if skill then - if skill:isInstanceOf(ActiveSkill) then - ret = skill:targetFilter(to_select, selected, selected_cards) - elseif skill:isInstanceOf(ViewAsSkill) then - local card = skill:viewAs(selected_cards) - if card then - ret = card.skill:targetFilter(to_select, selected, selected_cards, card) - ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), card) - end - end + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:targetFilter(to_select, selected, selected_cards) + elseif skill:isInstanceOf(ViewAsSkill) then + local card = skill:viewAs(selected_cards) + if card then + ret = card.skill:targetFilter(to_select, selected, selected_cards, card) + ret = ret and not Self:isProhibited(Fk:currentRoom():getPlayerById(to_select), card) + end end - return json.encode(ret) + end + return json.encode(ret) end function ActiveFeasible(skill_name, selected, selected_cards) - local skill = Fk.skills[skill_name] - local ret = false - if skill then - if skill:isInstanceOf(ActiveSkill) then - ret = skill:feasible(selected, selected_cards, Self, nil) - elseif skill:isInstanceOf(ViewAsSkill) then - local card = skill:viewAs(selected_cards) - if card then - ret = card.skill:feasible(selected, selected_cards, Self, card) - end - end + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if skill:isInstanceOf(ActiveSkill) then + ret = skill:feasible(selected, selected_cards, Self, nil) + elseif skill:isInstanceOf(ViewAsSkill) then + local card = skill:viewAs(selected_cards) + if card then + ret = card.skill:feasible(selected, selected_cards, Self, card) + end end - return json.encode(ret) + end + return json.encode(ret) end function CanViewAs(skill_name, card_ids) - local skill = Fk.skills[skill_name] - local ret = false - if skill then - if skill:isInstanceOf(ViewAsSkill) then - ret = skill:viewAs(card_ids) ~= nil - elseif skill:isInstanceOf(ActiveSkill) then - ret = true - end + local skill = Fk.skills[skill_name] + local ret = false + if skill then + if skill:isInstanceOf(ViewAsSkill) then + ret = skill:viewAs(card_ids) ~= nil + elseif skill:isInstanceOf(ActiveSkill) then + ret = true end - return json.encode(ret) + end + return json.encode(ret) end -- card_name may be id, name of card, or json string function CardFitPattern(card_name, pattern) - local exp = Exppattern:Parse(pattern) - local c - local ret = false - if type(card_name) == "number" then - c = Fk:getCardById(card_name) + local exp = Exppattern:Parse(pattern) + local c + local ret = false + if type(card_name) == "number" then + c = Fk:getCardById(card_name) + ret = exp:match(c) + elseif string.sub(card_name, 1, 1) == "{" then + local data = json.decode(card_name) + local skill = Fk.skills[data.skill] + local selected_cards = data.subcards + if skill:isInstanceOf(ViewAsSkill) then + c = skill:viewAs(selected_cards) + if c then ret = exp:match(c) - elseif string.sub(card_name, 1, 1) == "{" then - local data = json.decode(card_name) - local skill = Fk.skills[data.skill] - local selected_cards = data.subcards - if skill:isInstanceOf(ViewAsSkill) then - c = skill:viewAs(selected_cards) - if c then - ret = exp:match(c) - end - else - return "true" - end + end else - ret = exp:matchExp(card_name) + return "true" end - return json.encode(ret) + else + ret = exp:matchExp(card_name) + end + return json.encode(ret) end function SkillFitPattern(skill_name, pattern) - local skill = Fk.skills[skill_name] - local ret = false - if skill and skill.pattern then - local exp = Exppattern:Parse(pattern) - ret = exp:matchExp(skill.pattern) - end - return json.encode(ret) + local skill = Fk.skills[skill_name] + local ret = false + if skill and skill.pattern then + local exp = Exppattern:Parse(pattern) + ret = exp:matchExp(skill.pattern) + end + return json.encode(ret) end function CardProhibitedResponse(card) - local c ---@type Card - local ret = false - if type(card) == "number" then - c = Fk:getCardById(card) - else - local data = json.decode(card) - local skill = Fk.skills[data.skill] - local selected_cards = data.subcards - if skill:isInstanceOf(ViewAsSkill) then - c = skill:viewAs(selected_cards) - end + local c ---@type Card + local ret = false + if type(card) == "number" then + c = Fk:getCardById(card) + else + local data = json.decode(card) + local skill = Fk.skills[data.skill] + local selected_cards = data.subcards + if skill:isInstanceOf(ViewAsSkill) then + c = skill:viewAs(selected_cards) end - if c == nil then - return "true" - else - ret = Self:prohibitUse(c) - end - return json.encode(ret) + end + if c == nil then + return "true" + else + ret = Self:prohibitUse(c) + end + return json.encode(ret) end function SkillCanResponse(skill_name, cardResponsing) - local skill = Fk.skills[skill_name] - local ret = false - if skill and skill:isInstanceOf(ViewAsSkill) then - ret = skill:enabledAtResponse(Self, cardResponsing) - end - return json.encode(ret) + local skill = Fk.skills[skill_name] + local ret = false + if skill and skill:isInstanceOf(ViewAsSkill) then + ret = skill:enabledAtResponse(Self, cardResponsing) + end + return json.encode(ret) end function GetVirtualEquip(player, cid) - local c = ClientInstance:getPlayerById(player):getVirualEquip(cid) - if not c then - return "null" - end - return json.encode { - name = c.name, - cid = c.subcards[1] - } + local c = ClientInstance:getPlayerById(player):getVirualEquip(cid) + if not c then + return "null" + end + return json.encode { + name = c.name, + cid = c.subcards[1] + } end function GetExpandPileOfSkill(skillName) - local skill = Fk.skills[skillName] - return skill and (skill.expand_pile or "") or "" + local skill = Fk.skills[skillName] + return skill and (skill.expand_pile or "") or "" end function GetGameModes() - local ret = {} - for k, v in pairs(Fk.game_modes) do - table.insert(ret, { - name = Fk:translate(v.name), - orig_name = v.name, - minPlayer = v.minPlayer, - maxPlayer = v.maxPlayer - }) - end - table.sort(ret, function(a, b) - return a.name > b.name - end) - return json.encode(ret) + local ret = {} + for k, v in pairs(Fk.game_modes) do + table.insert(ret, { + name = Fk:translate(v.name), + orig_name = v.name, + minPlayer = v.minPlayer, + maxPlayer = v.maxPlayer + }) + end + table.sort(ret, function(a, b) + return a.name > b.name + end) + return json.encode(ret) end function GetInteractionOfSkill(skill_name) - local skill = Fk.skills[skill_name] - if skill and skill.interaction then - return json.encode(skill:interaction()) - end - return "null" + local skill = Fk.skills[skill_name] + if skill and skill.interaction then + return json.encode(skill:interaction()) + end + return "null" end function SetInteractionDataOfSkill(skill_name, data) - local skill = Fk.skills[skill_name] - if skill and skill.interaction then - skill.interaction.data = json.decode(data) - end + local skill = Fk.skills[skill_name] + if skill and skill.interaction then + skill.interaction.data = json.decode(data) + end end function ChangeSelf(pid) - ClientInstance.client:changeSelf(pid) -- for qml - Self = ClientInstance:getPlayerById(pid) + ClientInstance.client:changeSelf(pid) -- for qml + Self = ClientInstance:getPlayerById(pid) end function GetPlayerHandcards(pid) - local p = ClientInstance:getPlayerById(pid) - return json.encode(p.player_cards[Player.Hand]) + local p = ClientInstance:getPlayerById(pid) + return json.encode(p.player_cards[Player.Hand]) end function GetPlayerEquips(pid) - local p = ClientInstance:getPlayerById(pid) - return json.encode(p.player_cards[Player.Equip]) + local p = ClientInstance:getPlayerById(pid) + return json.encode(p.player_cards[Player.Equip]) end function ResetClientLua() - local _data = ClientInstance.enter_room_data; - local data = ClientInstance.room_settings - Self = ClientPlayer:new(fk.Self) - ClientInstance = Client:new() -- clear old client data - ClientInstance.players = {Self} - ClientInstance.alive_players = {Self} - ClientInstance.discard_pile = {} + local _data = ClientInstance.enter_room_data; + local data = ClientInstance.room_settings + Self = ClientPlayer:new(fk.Self) + ClientInstance = Client:new() -- clear old client data + ClientInstance.players = { Self } + ClientInstance.alive_players = { Self } + ClientInstance.discard_pile = {} - ClientInstance.enter_room_data = _data; - ClientInstance.room_settings = data + ClientInstance.enter_room_data = _data; + ClientInstance.room_settings = data - ClientInstance.disabled_packs = data.disabledPack - ClientInstance.disabled_generals = data.disabledGenerals - -- ClientInstance:notifyUI("EnterRoom", jsonData) + ClientInstance.disabled_packs = data.disabledPack + ClientInstance.disabled_generals = data.disabledGenerals + -- ClientInstance:notifyUI("EnterRoom", jsonData) end function ResetAddPlayer(j) - fk.client_callback["AddPlayer"](j) + fk.client_callback["AddPlayer"](j) end function GetRoomConfig() - return json.encode(ClientInstance.room_settings) + return json.encode(ClientInstance.room_settings) end function GetPlayerGameData(pid) - local p = ClientInstance:getPlayerById(pid) - if not p then - return "[0, 0, 0]" - end - local raw = p.player:getGameData() - local ret = {} - for _, i in fk.qlist(raw) do - table.insert(ret, i) - end - return json.encode(ret) + local p = ClientInstance:getPlayerById(pid) + if not p then + return "[0, 0, 0]" + end + local raw = p.player:getGameData() + local ret = {} + for _, i in fk.qlist(raw) do + table.insert(ret, i) + end + return json.encode(ret) end function SetPlayerGameData(pid, data) - local p = ClientInstance:getPlayerById(pid) - p.player:setGameData(table.unpack(data)) - table.insert(data, 1, pid) - ClientInstance:notifyUI("UpdateGameData", json.encode(data)) + local p = ClientInstance:getPlayerById(pid) + p.player:setGameData(table.unpack(data)) + table.insert(data, 1, pid) + ClientInstance:notifyUI("UpdateGameData", json.encode(data)) end function FilterMyHandcards() - Self:filterHandcards() + Self:filterHandcards() end function SetObserving(o) - ClientInstance.observing = o + ClientInstance.observing = o end function CheckSurrenderAvailable(playedTime) - local curMode = ClientInstance.room_settings.gameMode - return json.encode(Fk.game_modes[curMode]:surrenderFunc(playedTime)) + local curMode = ClientInstance.room_settings.gameMode + return json.encode(Fk.game_modes[curMode]:surrenderFunc(playedTime)) end function SaveRecord() - local c = ClientInstance - c.client:saveRecord(json.encode(c.record), c.record[2]) + local c = ClientInstance + c.client:saveRecord(json.encode(c.record), c.record[2]) +end + +function GetCardProhibitReason(cid, method, pattern) + local card = Fk:getCardById(cid) + if not card then return "" end + if method == "play" and not card.skill:canUse(Self, card) then return "" end + if method ~= "play" and not card:matchPattern(pattern) then return "" end + if method == "play" then method = "use" end + + local status_skills = Fk:currentRoom().status_skills[ProhibitSkill] or Util.DummyTable + local s + for _, skill in ipairs(status_skills) do + local fn = method == "use" and skill.prohibitUse or skill.prohibitResponse + if fn(skill, Self, card) then + s = skill + break + end + end + if not s then return "" end + + -- try to return a translated string + local skillName = s.name + local ret = Fk:translate(skillName) + if ret ~= skillName then + -- TODO: translate + return ret .. "禁" .. (method == "use" and "使用" or "打出") + elseif skillName:endsWith("_prohibit") and skillName:startsWith("#") then + return Fk:translate(skillName:sub(2, -10)) .. "禁" .. (method == "use" and "使用" or "打出") + else + return ret + end +end + +function PoxiPrompt(poxi_type, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi or not poxi.prompt then return "" end + if type(poxi.prompt) == "string" then return Fk:translate(poxi.prompt) end + return poxi.prompt(data) +end + +function PoxiFilter(poxi_type, to_select, selected, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi then return "false" end + return json.encode(poxi.card_filter(to_select, selected, data)) +end + +function PoxiFeasible(poxi_type, selected, data) + local poxi = Fk.poxi_methods[poxi_type] + if not poxi then return "false" end + return json.encode(poxi.feasible(selected, data)) end dofile "lua/client/i18n/init.lua" diff --git a/lua/server/ai/random_ai.lua b/lua/server/ai/random_ai.lua index 7e7e037e..61042410 100644 --- a/lua/server/ai/random_ai.lua +++ b/lua/server/ai/random_ai.lua @@ -6,273 +6,273 @@ local RandomAI = AI:subclass("RandomAI") ---@param skill ActiveSkill ---@param card Card | nil local function useActiveSkill(self, skill, card) - local room = self.room - local player = self.player + local room = self.room + local player = self.player - local filter_func = skill.cardFilter - if card then - filter_func = function() - return false - end + local filter_func = skill.cardFilter + if card then + filter_func = function() + return false end + end - if self.command == "PlayCard" and (not skill:canUse(player, card) or player:prohibitUse(card)) then - return "" - end - - local max_try_times = 100 - local selected_targets = {} - local selected_cards = {} - local min = skill:getMinTargetNum() - local max = skill:getMaxTargetNum(player, card) - local min_card = skill:getMinCardNum() - local max_card = skill:getMaxCardNum() - for _ = 0, max_try_times do - if skill:feasible(selected_targets, selected_cards, self.player, card) then - break - end - local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) - local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard 'zixing') - if ret and card then - if player:prohibitUse(card) then - ret = false - end - end - return ret - end) - avail_targets = table.map(avail_targets, function(p) - return p.id - end) - local avail_cards = table.filter(player:getCardIds{Player.Hand, Player.Equip}, function(id) - return filter_func(skill, id, selected_cards, selected_targets) - end) - - if #avail_targets == 0 and #avail_cards == 0 then - break - end - table.insertIfNeed(selected_targets, table.random(avail_targets)) - table.insertIfNeed(selected_cards, table.random(avail_cards)) - end - if skill:feasible(selected_targets, selected_cards, self.player, card) then - local ret = json.encode { - card = card and card.id or json.encode { - skill = skill.name, - subcards = selected_cards - }, - targets = selected_targets - } - -- print(ret) - return ret - end + if self.command == "PlayCard" and (not skill:canUse(player, card) or player:prohibitUse(card)) then return "" + end + + local max_try_times = 100 + local selected_targets = {} + local selected_cards = {} + local min = skill:getMinTargetNum() + local max = skill:getMaxTargetNum(player, card) + local min_card = skill:getMinCardNum() + local max_card = skill:getMaxCardNum() + for _ = 0, max_try_times do + if skill:feasible(selected_targets, selected_cards, self.player, card) then + break + end + local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) + local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard 'zixing') + if ret and card then + if player:prohibitUse(card) then + ret = false + end + end + return ret + end) + avail_targets = table.map(avail_targets, function(p) + return p.id + end) + local avail_cards = table.filter(player:getCardIds { Player.Hand, Player.Equip }, function(id) + return filter_func(skill, id, selected_cards, selected_targets) + end) + + if #avail_targets == 0 and #avail_cards == 0 then + break + end + table.insertIfNeed(selected_targets, table.random(avail_targets)) + table.insertIfNeed(selected_cards, table.random(avail_cards)) + end + if skill:feasible(selected_targets, selected_cards, self.player, card) then + local ret = json.encode { + card = card and card.id or json.encode { + skill = skill.name, + subcards = selected_cards + }, + targets = selected_targets + } + -- print(ret) + return ret + end + return "" end ---@param self RandomAI ---@param skill ViewAsSkill local function useVSSkill(self, skill, pattern, cancelable, extra_data) - local player = self.player - local room = self.room - local precondition + local player = self.player + local room = self.room + local precondition - if self.command == "PlayCard" then - precondition = skill:enabledAtPlay(player) - if not precondition then - return nil - end - local exp = Exppattern:Parse(skill.pattern) - local cnames = {} - for _, m in ipairs(exp.matchers) do - if m.name then - table.insertTable(cnames, m.name) - end - end - for _, n in ipairs(cnames) do - local c = Fk:cloneCard(n) - precondition = c.skill:canUse(Self, c) - if precondition then - break - end - end - else - precondition = skill:enabledAtResponse(player) - if not precondition then - return nil - end - local exp = Exppattern:Parse(pattern) - precondition = exp:matchExp(skill.pattern) + if self.command == "PlayCard" then + precondition = skill:enabledAtPlay(player) + if not precondition then + return nil end - - if (not precondition) or math.random() < 0.2 then - return nil + local exp = Exppattern:Parse(skill.pattern) + local cnames = {} + for _, m in ipairs(exp.matchers) do + if m.name then + table.insertTable(cnames, m.name) + end end - - local selected_cards = {} - local max_try_time = 100 - - for _ = 0, max_try_time do - local avail_cards = table.filter(player:getCardIds{Player.Hand, Player.Equip}, function(id) - return skill:cardFilter(id, selected_cards) - end) - if #avail_cards == 0 then - break - end - table.insert(selected_cards, table.random(avail_cards)) - if skill:viewAs(selected_cards) then - return { - skill = skill.name, - subcards = selected_cards - } - end + for _, n in ipairs(cnames) do + local c = Fk:cloneCard(n) + precondition = c.skill:canUse(Self, c) + if precondition then + break + end end + else + precondition = skill:enabledAtResponse(player) + if not precondition then + return nil + end + local exp = Exppattern:Parse(pattern) + precondition = exp:matchExp(skill.pattern) + end + + if (not precondition) or math.random() < 0.2 then return nil + end + + local selected_cards = {} + local max_try_time = 100 + + for _ = 0, max_try_time do + local avail_cards = table.filter(player:getCardIds { Player.Hand, Player.Equip }, function(id) + return skill:cardFilter(id, selected_cards) + end) + if #avail_cards == 0 then + break + end + table.insert(selected_cards, table.random(avail_cards)) + if skill:viewAs(selected_cards) then + return { + skill = skill.name, + subcards = selected_cards + } + end + end + return nil end local random_cb = {} random_cb.AskForUseActiveSkill = function(self, jsonData) - local data = json.decode(jsonData) - local skill = Fk.skills[data[1]] - local cancelable = data[3] - if cancelable and math.random() < 0.25 then - return "" - end - local extra_data = json.decode(data[4]) - for k, v in pairs(extra_data) do - skill[k] = v - end - return useActiveSkill(self, skill) + local data = json.decode(jsonData) + local skill = Fk.skills[data[1]] + local cancelable = data[3] + if cancelable and math.random() < 0.25 then + return "" + end + local extra_data = json.decode(data[4]) + for k, v in pairs(extra_data) do + skill[k] = v + end + return useActiveSkill(self, skill) end random_cb.AskForSkillInvoke = function(self, jsonData) - return table.random {"1", ""} + return table.random { "1", "" } end random_cb.AskForUseCard = function(self, jsonData) - local player = self.player - local data = json.decode(jsonData) - local card_name = data[1] - local pattern = data[2] or card_name - local cancelable = data[4] or true - local exp = Exppattern:Parse(pattern) + local player = self.player + local data = json.decode(jsonData) + local card_name = data[1] + local pattern = data[2] or card_name + local cancelable = data[4] or true + local exp = Exppattern:Parse(pattern) - local avail_cards = table.filter(player:getCardIds("he"), function(id) - return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id)) - end) - if #avail_cards > 0 then - if math.random() < 0.25 then - return "" - end - avail_cards = table.map(avail_cards, function(id) - return Fk:getCardById(id) - end) - for _, card in ipairs(avail_cards) do - local skill = card.skill - local max_try_times = 100 - local selected_targets = {} - local min = skill:getMinTargetNum() - local max = skill:getMaxTargetNum(player, card) - local min_card = skill:getMinCardNum() - local max_card = skill:getMaxCardNum() - for _ = 0, max_try_times do - if skill:feasible(selected_targets, {card.id}, self.player, card) then - break - end - local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) - local ret = skill:targetFilter(p.id, selected_targets, {card}, card or Fk:cloneCard 'zixing') - return ret - end) - avail_targets = table.map(avail_targets, function(p) - return p.id - end) - - if #avail_targets + #avail_cards < 1 then - break - end - table.insertIfNeed(selected_targets, table.random(avail_targets)) - end - if skill:feasible(selected_targets, {card.id}, self.player, card) then - return json.encode { - card = table.random(avail_cards), - targets = selected_targets - } - end - end + local avail_cards = table.filter(player:getCardIds("he"), function(id) + return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id)) + end) + if #avail_cards > 0 then + if math.random() < 0.25 then + return "" end - return "" + avail_cards = table.map(avail_cards, function(id) + return Fk:getCardById(id) + end) + for _, card in ipairs(avail_cards) do + local skill = card.skill + local max_try_times = 100 + local selected_targets = {} + local min = skill:getMinTargetNum() + local max = skill:getMaxTargetNum(player, card) + local min_card = skill:getMinCardNum() + local max_card = skill:getMaxCardNum() + for _ = 0, max_try_times do + if skill:feasible(selected_targets, { card.id }, self.player, card) then + break + end + local avail_targets = table.filter(self.room:getAlivePlayers(), function(p) + local ret = skill:targetFilter(p.id, selected_targets, { card }, card or Fk:cloneCard 'zixing') + return ret + end) + avail_targets = table.map(avail_targets, function(p) + return p.id + end) + + if #avail_targets + #avail_cards < 1 then + break + end + table.insertIfNeed(selected_targets, table.random(avail_targets)) + end + if skill:feasible(selected_targets, { card.id }, self.player, card) then + return json.encode { + card = table.random(avail_cards), + targets = selected_targets + } + end + end + end + return "" end ---@param self RandomAI random_cb.AskForResponseCard = function(self, jsonData) - local data = json.decode(jsonData) - local pattern = data[2] - local cancelable = true - local exp = Exppattern:Parse(pattern) - local avail_cards = table.filter(self.player:getCardIds{Player.Hand, Player.Equip}, function(id) - return exp:match(Fk:getCardById(id)) - end) - if #avail_cards > 0 then - return json.encode { - card = table.random(avail_cards), - targets = {} - } - end - -- TODO: vs skill - return "" + local data = json.decode(jsonData) + local pattern = data[2] + local cancelable = true + local exp = Exppattern:Parse(pattern) + local avail_cards = table.filter(self.player:getCardIds { Player.Hand, Player.Equip }, function(id) + return exp:match(Fk:getCardById(id)) + end) + if #avail_cards > 0 then + return json.encode { + card = table.random(avail_cards), + targets = {} + } + end + -- TODO: vs skill + return "" end ---@param self RandomAI random_cb.PlayCard = function(self, jsonData) - local cards = table.map(self.player:getCardIds(Player.Hand), function(id) - return Fk:getCardById(id) - end) - local actives = table.filter(self.player:getAllSkills(), function(s) - return s:isInstanceOf(ActiveSkill) - end) - local vss = table.filter(self.player:getAllSkills(), function(s) - return s:isInstanceOf(ViewAsSkill) - end) - table.insertTable(cards, actives) - table.insertTable(cards, vss) + local cards = table.map(self.player:getCardIds(Player.Hand), function(id) + return Fk:getCardById(id) + end) + local actives = table.filter(self.player:getAllSkills(), function(s) + return s:isInstanceOf(ActiveSkill) + end) + local vss = table.filter(self.player:getAllSkills(), function(s) + return s:isInstanceOf(ViewAsSkill) + end) + table.insertTable(cards, actives) + table.insertTable(cards, vss) - while #cards > 0 do - local sth = table.random(cards) - if sth:isInstanceOf(Card) then - local card = sth - local skill = card.skill ---@type ActiveSkill - if math.random() > 0.15 then - local ret = useActiveSkill(self, skill, card) - if ret ~= "" then - return ret - end - table.removeOne(cards, card) - else - table.removeOne(cards, card) - end - elseif sth:isInstanceOf(ActiveSkill) then - local active = sth - if math.random() > 0.30 then - local ret = useActiveSkill(self, active, nil) - if ret ~= "" then - return ret - end - end - table.removeOne(cards, active) - else - local vs = sth - if math.random() > 0.20 then - local ret = useVSSkill(self, vs) - -- TODO: handle vs result - end - table.removeOne(cards, vs) + while #cards > 0 do + local sth = table.random(cards) + if sth:isInstanceOf(Card) then + local card = sth + local skill = card.skill ---@type ActiveSkill + if math.random() > 0.15 then + local ret = useActiveSkill(self, skill, card) + if ret ~= "" then + return ret end + table.removeOne(cards, card) + else + table.removeOne(cards, card) + end + elseif sth:isInstanceOf(ActiveSkill) then + local active = sth + if math.random() > 0.30 then + local ret = useActiveSkill(self, active, nil) + if ret ~= "" then + return ret + end + end + table.removeOne(cards, active) + else + local vs = sth + if math.random() > 0.20 then + local ret = useVSSkill(self, vs) + -- TODO: handle vs result + end + table.removeOne(cards, vs) end + end - return "" + return "" end function RandomAI:initialize(player) - AI.initialize(self, player) - self.cb_table = random_cb + AI.initialize(self, player) + self.cb_table = random_cb end return RandomAI diff --git a/lua/server/ai/trust_ai.lua b/lua/server/ai/trust_ai.lua index ed577ea4..153eca26 100644 --- a/lua/server/ai/trust_ai.lua +++ b/lua/server/ai/trust_ai.lua @@ -8,328 +8,6 @@ local trust_cb = {} function TrustAI:initialize(player) AI.initialize(self, player) self.cb_table = trust_cb -<<<<<<< HEAD -======= - self.player = player - self.room = RoomInstance or ClientInstance - - fk.ai_role[player.id] = "neutral" - fk.roleValue[player.id] = { - lord = 0, - loyalist = 0, - rebel = 0, - renegade = 0 - } - self:updatePlayers() -end - -function TrustAI:isRolePredictable() - return self.room.settings.gameMode ~= "aaa_role_mode" -end - -local function aliveRoles(room) - fk.alive_roles = { - lord = 0, - loyalist = 0, - rebel = 0, - renegade = 0 - } - for _, ap in ipairs(room:getAllPlayers(false)) do - fk.alive_roles[ap.role] = 0 - end - for _, ap in ipairs(room:getAlivePlayers(false)) do - fk.alive_roles[ap.role] = fk.alive_roles[ap.role] + 1 - end - return fk.alive_roles -end - -function TrustAI:objectiveLevel(to) - if self.player.id == to.id then - return -2 - elseif #self.room:getAlivePlayers(false) < 3 then - return 5 - end - local ars = aliveRoles(self.room) - if self:isRolePredictable() then - fk.ai_role[self.player.id] = self.role - fk.roleValue[self.player.id][self.role] = 666 - if self.role == "renegade" then - fk.explicit_renegade = true - end - for _, p in ipairs(self.room:getAlivePlayers()) do - if - p.role == self.role or p.role == "lord" and self.role == "loyalist" or - p.role == "loyalist" and self.role == "lord" - then - table.insert(self.friends, p) - if p.id ~= self.player.id then - table.insert(self.friends_noself, p) - end - else - table.insert(self.enemies, p) - end - end - elseif self.role == "renegade" then - if to.role == "lord" then - return -1 - elseif ars.rebel < 1 then - return 4 - elseif fk.ai_role[to.id] == "loyalist" then - return ars.lord + ars.loyalist - ars.rebel - elseif fk.ai_role[to.id] == "rebel" then - local r = ars.rebel - ars.lord + ars.loyalist - if r >= 0 then - return 3 - else - return r - end - end - elseif self.role == "lord" or self.role == "loyalist" then - if fk.ai_role[to.id] == "rebel" then - return 5 - elseif to.role == "lord" then - return -2 - elseif ars.rebel < 1 then - if self.role == "lord" then - return fk.explicit_renegade and fk.ai_role[to.id] == "renegade" and 4 or to.hp > 1 and 2 or 0 - elseif fk.explicit_renegade then - return fk.ai_role[to.id] == "renegade" and 4 or -1 - else - return 3 - end - elseif fk.ai_role[to.id] == "loyalist" then - return -2 - elseif fk.ai_role[to.id] == "renegade" then - local r = ars.lord + ars.loyalist - ars.rebel - if r <= 0 then - return r - else - return 3 - end - end - elseif self.role == "rebel" then - if to.role == "lord" then - return 5 - elseif fk.ai_role[to.id] == "loyalist" then - return 4 - elseif fk.ai_role[to.id] == "rebel" then - return -2 - elseif fk.ai_role[to.id] == "renegade" then - local r = ars.rebel - ars.lord + ars.loyalist - if r > 0 then - return 1 - else - return r - end - end - end - return 0 -end - -function TrustAI:updatePlayers(update) - self.role = self.player.role - local neutrality = {} - self.enemies = {} - self.friends = {} - self.friends_noself = {} - - local aps = self.room:getAlivePlayers() - local function compare_func(a, b) - local v1 = fk.roleValue[a.id].rebel - local v2 = fk.roleValue[b.id].rebel - if v1 == v2 then - v1 = fk.roleValue[a.id].renegade - v2 = fk.roleValue[b.id].renegade - end - return v1 > v2 - end - table.sort(aps, compare_func) - fk.explicit_renegade = false - local ars = aliveRoles(self.room) - local rebel, renegade, loyalist = 0, 0, 0 - for _, ap in ipairs(aps) do - if ap.role == "lord" then - fk.ai_role[ap.id] = "loyalist" - elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then - rebel = rebel + 1 - fk.ai_role[ap.id] = "rebel" - elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then - renegade = renegade + 1 - fk.ai_role[ap.id] = "renegade" - fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100 - elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then - loyalist = loyalist + 1 - fk.ai_role[ap.id] = "loyalist" - else - fk.ai_role[ap.id] = "neutral" - end - end - - for n, p in ipairs(self.room:getAlivePlayers(false)) do - n = self:objectiveLevel(p) - if n < 0 then - table.insert(self.friends, p) - if p.id ~= self.player.id then - table.insert(self.friends_noself, p) - end - elseif n > 0 then - table.insert(self.enemies, p) - else - table.insert(neutrality, p) - end - end - self:assignValue() - --[[ - if self.enemies<1 and #neutrality>0 - and#self.toUse<3 and self:getOverflow()>0 - then - function compare_func(a,b) - return sgs.getDefense(a) 0 or fk.roleValue[player.id].rebel > 0 and intention < 0 then - fk.roleValue[player.id].renegade = fk.roleValue[player.id].renegade + - intention * (100 - fk.roleValue[player.id].renegade) / 200 - end - local aps = player.room:getAlivePlayers() - local function compare_func(a, b) - local v1 = fk.roleValue[a.id].rebel - local v2 = fk.roleValue[b.id].rebel - if v1 == v2 then - v1 = fk.roleValue[a.id].renegade - v2 = fk.roleValue[b.id].renegade - end - return v1 > v2 - end - table.sort(aps, compare_func) - fk.explicit_renegade = false - local ars = aliveRoles(player.room) - local rebel, renegade, loyalist = 0, 0, 0 - for _, ap in ipairs(aps) do - if ap.role == "lord" then - fk.ai_role[ap.id] = "loyalist" - elseif fk.roleValue[ap.id].rebel > 50 and ars.rebel > rebel then - rebel = rebel + 1 - fk.ai_role[ap.id] = "rebel" - elseif fk.roleValue[ap.id].renegade > 50 and ars.renegade > renegade then - renegade = renegade + 1 - fk.ai_role[ap.id] = "renegade" - fk.explicit_renegade = fk.roleValue[ap.id].renegade > 100 - elseif fk.roleValue[ap.id].rebel < -50 and ars.loyalist > loyalist then - loyalist = loyalist + 1 - fk.ai_role[ap.id] = "loyalist" - else - fk.ai_role[ap.id] = "neutral" - end - end - fk.qWarning( - player.general .. - " " .. - intention .. - " " .. - fk.ai_role[player.id] .. - " rebelValue:" .. fk.roleValue[player.id].rebel .. " renegadeValue:" .. fk.roleValue[player.id].renegade - ) --]] - end -end - -function TrustAI:filterEvent(event, player, data) - if event == fk.TargetSpecified then - local callback = fk.ai_card[data.card.name] - callback = callback and callback.intention - if type(callback) == "function" then - for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do - p = self.room:getPlayerById(p) - local intention = callback(p.ai, data.card, self.room:getPlayerById(data.from)) - if type(intention) == "number" then - updateIntention(self.room:getPlayerById(data.from), p, intention) - end - end - elseif type(callback) == "number" then - for _, p in ipairs(TargetGroup:getRealTargets(data.tos)) do - p = self.room:getPlayerById(p) - updateIntention(self.room:getPlayerById(data.from), p, callback) - end - end - elseif event == fk.StartJudge then - fk.trick_judge[data.reason] = data.pattern - elseif event == fk.AfterCardsMove then - end -end - -function TrustAI:isWeak(player, getAP) - player = player or self.player - if type(player) == "number" then - player = self.room:getPlayerById(player) - end - return player.hp < 2 or player.hp <= 2 and #player:getCardIds("&h") <= 2 -end - -function TrustAI:isFriend(pid, tid) - if tid then - local bt = self:isFriend(pid) - return bt ~= nil and bt == self:isFriend(tid) - end - if type(pid) == "number" then - pid = self.room:getPlayerById(pid) - end - local ve = self:objectiveLevel(pid) - if ve < 0 then - return true - elseif ve > 0 then - return false - end -end - -function TrustAI:isEnemie(pid, tid) - if tid then - local bt = self:isFriend(pid) - return bt ~= nil and bt ~= self:isFriend(tid) - end - if type(pid) == "number" then - pid = self.room:getPlayerById(pid) - end - local ve = self:objectiveLevel(pid) - if ve > 0 then - return true - elseif ve < 0 then - return false - end -end - -function TrustAI:eventData(game_event) - local event = self.room.logic:getCurrentEvent():findParent(GameEvent[game_event], true) - return event and event.data[1] -end - -for _, n in ipairs(FileIO.ls("packages")) do - if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then - dofile("packages/" .. n .. "/" .. n .. "_ai.lua") - end -end --- 加载两次拓展是为了能够引用,例如属性杀的使用直接套入普通杀的使用 -for _, n in ipairs(FileIO.ls("packages")) do - if FileIO.isDir("packages/" .. n) and FileIO.exists("packages/" .. n .. "/" .. n .. "_ai.lua") then - dofile("packages/" .. n .. "/" .. n .. "_ai.lua") - end ->>>>>>> 79d3213cc0aa996c9072ae5696e387a9bda210d8 end return TrustAI diff --git a/lua/server/event.lua b/lua/server/event.lua index 09f91e0c..78d8efee 100644 --- a/lua/server/event.lua +++ b/lua/server/event.lua @@ -129,3 +129,121 @@ fk.GeneralRevealed = 89 fk.GeneralHidden = 90 fk.NumOfEvents = 91 + +local events = { + "NonTrigger", + "GamePrepared", + "GameStart", + "BeforeTurnStart", + "TurnStart", + "TurnEnd", + "AfterTurnEnd", + "EventPhaseStart", + "EventPhaseProceeding", + "EventPhaseEnd", + "AfterPhaseEnd", + "EventPhaseChanging", + "EventPhaseSkipping", + + "BeforeCardsMove", + "AfterCardsMove", + + "DrawNCards", + "AfterDrawNCards", + "DrawInitialCards", + "AfterDrawInitialCards", + + "PreHpRecover", + "HpRecover", + "PreHpLost", + "HpLost", + "BeforeHpChanged", + "HpChanged", + "MaxHpChanged", + + "EventLoseSkill", + "EventAcquireSkill", + + "StartJudge", + "AskForRetrial", + "FinishRetrial", + "FinishJudge", + + "RoundStart", + "RoundEnd", + "BeforeTurnOver", + "TurnedOver", + "BeforeChainStateChange", + "ChainStateChanged", + + "PreDamage", + "DamageCaused", + "DamageInflicted", + "Damage", + "Damaged", + "DamageFinished", + + "EnterDying", + "Dying", + "AfterDying", + + "PreCardUse", + "AfterCardUseDeclared", + "AfterCardTargetDeclared", + "CardUsing", + "BeforeCardUseEffect", + "TargetSpecifying", + "TargetConfirming", + "TargetSpecified", + "TargetConfirmed", + "CardUseFinished", + + "PreCardRespond", + "CardResponding", + "CardRespondFinished", + + "PreCardEffect", + "BeforeCardEffect", + "CardEffecting", + "CardEffectFinished", + "CardEffectCancelledOut", + + "AskForPeaches", + "AskForPeachesDone", + "Death", + "BuryVictim", + "Deathed", + "BeforeGameOverJudge", + "GameOverJudge", + "GameFinished", + + "AskForCardUse", + "AskForCardResponse", + + "StartPindian", + "PindianCardsDisplayed", + "PindianResultConfirmed", + "PindianFinished", + + "AfterDrawPileShuffle", + + "BeforeTriggerSkillUse", + + "BeforeDrawCard", + + "CardShown", + + "SkillEffect", + "AfterSkillEffect", + + "AreaAborted", + "AreaResumed", + + "GeneralRevealed", + "GeneralHidden", + + "NumOfEvents" +} +for i, event in ipairs(events) do + fk[event] = i +end --]] diff --git a/lua/server/events/usecard.lua b/lua/server/events/usecard.lua index 32613bcd..19b99c01 100644 --- a/lua/server/events/usecard.lua +++ b/lua/server/events/usecard.lua @@ -194,16 +194,15 @@ end GameEvent.functions[GameEvent.UseCard] = function(self) local cardUseEvent = table.unpack(self.data) local room = self.room - local logic = room.logic if cardUseEvent.card.skill then cardUseEvent.card.skill:onUse(room, cardUseEvent) end - if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then + if room.logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then cardUseEvent.breakEvent = true self.data = { cardUseEvent } - logic:breakEvent() + room.logic:breakEvent() end room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse) @@ -223,7 +222,7 @@ GameEvent.functions[GameEvent.UseCard] = function(self) break end - logic:trigger(event, room:getPlayerById(cardUseEvent.from), cardUseEvent) + room.logic:trigger(event, room:getPlayerById(cardUseEvent.from), cardUseEvent) if event == fk.CardUsing then room:doCardUseEffect(cardUseEvent) end @@ -232,13 +231,12 @@ end GameEvent.cleaners[GameEvent.UseCard] = function(self) local cardUseEvent = table.unpack(self.data) - local room = self.room - room.logic:trigger(fk.CardUseFinished, room:getPlayerById(cardUseEvent.from), cardUseEvent) + self.room.logic:trigger(fk.CardUseFinished, self.room:getPlayerById(cardUseEvent.from), cardUseEvent) - local leftRealCardIds = room:getSubcardsByRule(cardUseEvent.card, { Card.Processing }) + local leftRealCardIds = self.room:getSubcardsByRule(cardUseEvent.card, { Card.Processing }) if #leftRealCardIds > 0 then - room:moveCards({ + self.room:moveCards({ ids = leftRealCardIds, toArea = Card.DiscardPile, moveReason = fk.ReasonUse @@ -249,12 +247,11 @@ end GameEvent.functions[GameEvent.RespondCard] = function(self) local cardResponseEvent = table.unpack(self.data) local room = self.room - local logic = room.logic - if logic:trigger(fk.PreCardRespond, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) then + if room.logic:trigger(fk.PreCardRespond, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) then cardResponseEvent.breakEvent = true self.data = { cardResponseEvent } - logic:breakEvent() + room.logic:breakEvent() end local from = cardResponseEvent.customFrom or cardResponseEvent.from @@ -296,19 +293,18 @@ GameEvent.functions[GameEvent.RespondCard] = function(self) if cardResponseEvent.retrial ~= true then playCardEmotionAndSound(room, room:getPlayerById(from), card) end - logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) + room.logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) self.data = { cardResponseEvent } end GameEvent.cleaners[GameEvent.RespondCard] = function(self) local cardResponseEvent = table.unpack(self.data) - local room = self.room - room.logic:trigger(fk.CardRespondFinished, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) + self.room.logic:trigger(fk.CardRespondFinished, self.room:getPlayerById(cardResponseEvent.from), cardResponseEvent) - local realCardIds = room:getSubcardsByRule(cardResponseEvent.card, { Card.Processing }) + local realCardIds = self.room:getSubcardsByRule(cardResponseEvent.card, { Card.Processing }) if #realCardIds > 0 and not cardResponseEvent.skipDrop then - room:moveCards({ + self.room:moveCards({ ids = realCardIds, toArea = Card.DiscardPile, moveReason = fk.ReasonResonpse @@ -319,34 +315,33 @@ end GameEvent.functions[GameEvent.CardEffect] = function(self) local cardEffectEvent = table.unpack(self.data) local room = self.room - local logic = room.logic for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do local user = cardEffectEvent.from and room:getPlayerById(cardEffectEvent.from) or nil if cardEffectEvent.isCancellOut then - if logic:trigger(fk.CardEffectCancelledOut, user, cardEffectEvent) then + if room.logic:trigger(fk.CardEffectCancelledOut, user, cardEffectEvent) then cardEffectEvent.isCancellOut = false else - logic:breakEvent() + room.logic:breakEvent() end end if not cardEffectEvent.toCard and (not (room:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to) or #room:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0) then - logic:breakEvent() + room.logic:breakEvent() end if table.contains((cardEffectEvent.nullifiedTargets or Util.DummyTable), cardEffectEvent.to) then - logic:breakEvent() + room.logic:breakEvent() end if event == fk.PreCardEffect then - if cardEffectEvent.from and logic:trigger(event, room:getPlayerById(cardEffectEvent.from), cardEffectEvent) then - logic:breakEvent() + if cardEffectEvent.from and room.logic:trigger(event, room:getPlayerById(cardEffectEvent.from), cardEffectEvent) then + room.logic:breakEvent() end - elseif cardEffectEvent.to and logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then - logic:breakEvent() + elseif cardEffectEvent.to and room.logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then + room.logic:breakEvent() end room:handleCardEffect(event, cardEffectEvent) diff --git a/lua/server/gamelogic.lua b/lua/server/gamelogic.lua index 429c4ca9..eea38604 100644 --- a/lua/server/gamelogic.lua +++ b/lua/server/gamelogic.lua @@ -13,424 +13,423 @@ local GameLogic = class("GameLogic") function GameLogic:initialize(room) - self.room = room - self.skill_table = {} -- TriggerEvent --> TriggerSkill[] - self.skill_priority_table = {} - self.refresh_skill_table = {} - self.skills = {} -- skillName[] - self.game_event_stack = Stack:new() - self.all_game_events = {} - self.event_recorder = {} - self.current_event_id = 0 + self.room = room + self.skill_table = {} -- TriggerEvent --> TriggerSkill[] + self.skill_priority_table = {} + self.refresh_skill_table = {} + self.skills = {} -- skillName[] + self.game_event_stack = Stack:new() + self.all_game_events = {} + self.event_recorder = {} + self.current_event_id = 0 - self.role_table = {{"lord"}, {"lord", "rebel"}, {"lord", "rebel", "renegade"}, - {"lord", "loyalist", "rebel", "renegade"}, {"lord", "loyalist", "rebel", "rebel", "renegade"}, - {"lord", "loyalist", "rebel", "rebel", "rebel", "renegade"}, - {"lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade"}, - {"lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade"}} + self.role_table = { { "lord" }, { "lord", "rebel" }, { "lord", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "renegade" }, { "lord", "loyalist", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "rebel", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "renegade" }, + { "lord", "loyalist", "loyalist", "rebel", "rebel", "rebel", "rebel", "renegade" } } end function GameLogic:run() - -- default logic - local room = self.room - table.shuffle(self.room.players) - self:assignRoles() - self.room.game_started = true - room:doBroadcastNotify("StartGame", "") - room:adjustSeats() + -- default logic + local room = self.room + table.shuffle(self.room.players) + self:assignRoles() + self.room.game_started = true + room:doBroadcastNotify("StartGame", "") + room:adjustSeats() - self:chooseGenerals() + self:chooseGenerals() - self:buildPlayerCircle() - self:broadcastGeneral() - self:prepareDrawPile() - self:attachSkillToPlayers() - self:prepareForStart() + self:buildPlayerCircle() + self:broadcastGeneral() + self:prepareDrawPile() + self:attachSkillToPlayers() + self:prepareForStart() - self:action() + self:action() end local function execGameEvent(type, ...) - local event = GameEvent:new(type, ...) - local _, ret = event:exec() - return ret + local event = GameEvent:new(type, ...) + local _, ret = event:exec() + return ret end function GameLogic:assignRoles() - local room = self.room - local n = #room.players - local roles = self.role_table[n] - table.shuffle(roles) + local room = self.room + local n = #room.players + local roles = self.role_table[n] + table.shuffle(roles) - for i = 1, n do - local p = room.players[i] - p.role = roles[i] - if p.role == "lord" then - p.role_shown = true - room:broadcastProperty(p, "role") - else - room:notifyProperty(p, p, "role") - end + for i = 1, n do + local p = room.players[i] + p.role = roles[i] + if p.role == "lord" then + p.role_shown = true + room:broadcastProperty(p, "role") + else + room:notifyProperty(p, p, "role") end + end end function GameLogic:chooseGenerals() - local room = self.room - local generalNum = room.settings.generalNum - local n = room.settings.enableDeputy and 2 or 1 - local lord = room:getLord() - local lord_generals = {} + local room = self.room + local generalNum = room.settings.generalNum + local n = room.settings.enableDeputy and 2 or 1 + local lord = room:getLord() + local lord_generals = {} - if lord ~= nil then - room.current = lord - local generals = {} - local lordlist = {} - local lordpools = {} - if room.settings.gameMode == "aaa_role_mode" then - for _, general in pairs(Fk:getAllGenerals()) do - if (not general.hidden and not general.total_hidden) and table.find(general.skills, function(s) - return s.lordSkill - end) and not table.find(lordlist, function(g) - return g.trueName == general.trueName - end) then - table.insert(lordlist, general) - end - end - lordlist = table.random(lordlist, 3) or {} + if lord ~= nil then + room.current = lord + local generals = {} + local lordlist = {} + local lordpools = {} + if room.settings.gameMode == "aaa_role_mode" then + for _, general in pairs(Fk:getAllGenerals()) do + if (not general.hidden and not general.total_hidden) and table.find(general.skills, function(s) + return s.lordSkill + end) and not table.find(lordlist, function(g) + return g.trueName == general.trueName + end) then + table.insert(lordlist, general) end - table.insertTable(generals, Fk:getGeneralsRandomly(generalNum, Fk:getAllGenerals(), nil, function(g) - return table.contains(table.map(lordlist, function(g) - return g.trueName - end), g.trueName) - end)) - for i = 1, #generals do - generals[i] = generals[i].name - end - lordpools = table.simpleClone(generals) - table.insertTable(lordpools, table.map(lordlist, function(g) - return g.name - end)) - lord_generals = room:askForGeneral(lord, lordpools, n) - local lord_general, deputy - if type(lord_generals) == "table" then - deputy = lord_generals[2] - lord_general = lord_generals[1] - else - lord_general = lord_generals - lord_generals = {lord_general} - end - - room:setPlayerGeneral(lord, lord_general, true) - room:askForChooseKingdom({lord}) - room:broadcastProperty(lord, "general") - room:broadcastProperty(lord, "kingdom") - room:setDeputyGeneral(lord, deputy) - room:broadcastProperty(lord, "deputyGeneral") + end + lordlist = table.random(lordlist, 3) or {} + end + table.insertTable(generals, Fk:getGeneralsRandomly(generalNum, Fk:getAllGenerals(), nil, function(g) + return table.contains(table.map(lordlist, function(g) + return g.trueName + end), g.trueName) + end)) + for i = 1, #generals do + generals[i] = generals[i].name + end + lordpools = table.simpleClone(generals) + table.insertTable(lordpools, table.map(lordlist, function(g) + return g.name + end)) + lord_generals = room:askForGeneral(lord, lordpools, n) + local lord_general, deputy + if type(lord_generals) == "table" then + deputy = lord_generals[2] + lord_general = lord_generals[1] + else + lord_general = lord_generals + lord_generals = { lord_general } end - local nonlord = room:getOtherPlayers(lord, true) - local generals = Fk:getGeneralsRandomly(#nonlord * generalNum, nil, lord_generals) - table.shuffle(generals) - for _, p in ipairs(nonlord) do - local arg = {} - for i = 1, generalNum do - table.insert(arg, table.remove(generals, 1).name) - end - p.request_data = json.encode {arg, n} - p.default_reply = table.random(arg, n) + room:setPlayerGeneral(lord, lord_general, 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 = Fk:getGeneralsRandomly(#nonlord * generalNum, nil, lord_generals) + table.shuffle(generals) + for _, p in ipairs(nonlord) do + local arg = {} + for i = 1, generalNum do + table.insert(arg, table.remove(generals, 1).name) end + p.request_data = json.encode { arg, n } + p.default_reply = table.random(arg, n) + end - room:notifyMoveFocus(nonlord, "AskForGeneral") - room:doBroadcastRequest("AskForGeneral", nonlord) + room:notifyMoveFocus(nonlord, "AskForGeneral") + room:doBroadcastRequest("AskForGeneral", nonlord) - for _, p in ipairs(nonlord) do - if p.general == "" and p.reply_ready then - local generals = json.decode(p.client_reply) - local general = generals[1] - local deputy = generals[2] - room:setPlayerGeneral(p, general, true, true) - room:setDeputyGeneral(p, deputy) - else - room:setPlayerGeneral(p, p.default_reply[1], true, true) - room:setDeputyGeneral(p, p.default_reply[2]) - end - p.default_reply = "" + for _, p in ipairs(nonlord) do + if p.general == "" and p.reply_ready then + local generals = json.decode(p.client_reply) + local general = generals[1] + local deputy = generals[2] + room:setPlayerGeneral(p, general, true, true) + room:setDeputyGeneral(p, deputy) + else + room:setPlayerGeneral(p, p.default_reply[1], true, true) + room:setDeputyGeneral(p, p.default_reply[2]) end + p.default_reply = "" + end - room:askForChooseKingdom(nonlord) + room:askForChooseKingdom(nonlord) end function GameLogic:buildPlayerCircle() - local room = self.room - local players = room.players - room.alive_players = {table.unpack(players)} - for i = 1, #players - 1 do - players[i].next = players[i + 1] - end - players[#players].next = players[1] + local room = self.room + local players = room.players + room.alive_players = { table.unpack(players) } + for i = 1, #players - 1 do + players[i].next = players[i + 1] + end + players[#players].next = players[1] end function GameLogic:broadcastGeneral() - local room = self.room - local players = room.players + local room = self.room + local players = room.players - for _, p in ipairs(players) do - assert(p.general ~= "") - local general = Fk.generals[p.general] - local deputy = Fk.generals[p.deputyGeneral] - p.maxHp = p:getGeneralMaxHp() - p.hp = deputy and math.floor((deputy.hp + general.hp) / 2) or general.hp - p.shield = math.min(general.shield + (deputy and deputy.shield or 0), 5) - -- TODO: setup AI here + for _, p in ipairs(players) do + assert(p.general ~= "") + local general = Fk.generals[p.general] + local deputy = Fk.generals[p.deputyGeneral] + p.maxHp = p:getGeneralMaxHp() + p.hp = deputy and math.floor((deputy.hp + general.hp) / 2) or general.hp + 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 - end - room:broadcastProperty(p, "maxHp") - room:broadcastProperty(p, "hp") - room:broadcastProperty(p, "shield") + 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 end + room:broadcastProperty(p, "maxHp") + room:broadcastProperty(p, "hp") + room:broadcastProperty(p, "shield") + end end function GameLogic:prepareDrawPile() - local room = self.room - local allCardIds = Fk:getAllCardIds() + local room = self.room + local allCardIds = Fk:getAllCardIds() - for i = #allCardIds, 1, -1 do - if Fk:getCardById(allCardIds[i]).is_derived then - local id = allCardIds[i] - table.removeOne(allCardIds, id) - table.insert(room.void, id) - room:setCardArea(id, Card.Void, nil) - end + for i = #allCardIds, 1, -1 do + if Fk:getCardById(allCardIds[i]).is_derived then + local id = allCardIds[i] + table.removeOne(allCardIds, id) + table.insert(room.void, id) + room:setCardArea(id, Card.Void, nil) end + end - table.shuffle(allCardIds) - room.draw_pile = allCardIds - for _, id in ipairs(room.draw_pile) do - self.room:setCardArea(id, Card.DrawPile, nil) - end + table.shuffle(allCardIds) + room.draw_pile = allCardIds + for _, id in ipairs(room.draw_pile) do + self.room:setCardArea(id, Card.DrawPile, nil) + end end function GameLogic:attachSkillToPlayers() - local room = self.room - local players = room.players + local room = self.room + local players = room.players - local addRoleModSkills = function(player, skillName) - local skill = Fk.skills[skillName] - if skill.lordSkill and (player.role ~= "lord" or #room.players < 5) then - return - end - - if #skill.attachedKingdom > 0 and not table.contains(skill.attachedKingdom, player.kingdom) then - return - end - - room:handleAddLoseSkills(player, skillName, nil, false) + local addRoleModSkills = function(player, skillName) + local skill = Fk.skills[skillName] + if skill.lordSkill and (player.role ~= "lord" or #room.players < 5) then + return end - for _, p in ipairs(room.alive_players) do - local skills = Fk.generals[p.general].skills - for _, s in ipairs(skills) do - addRoleModSkills(p, s.name) - end - for _, sname in ipairs(Fk.generals[p.general].other_skills) do - addRoleModSkills(p, sname) - end - local deputy = Fk.generals[p.deputyGeneral] - if deputy then - skills = deputy.skills - for _, s in ipairs(skills) do - addRoleModSkills(p, s.name) - end - for _, sname in ipairs(deputy.other_skills) do - addRoleModSkills(p, sname) - end - end + if #skill.attachedKingdom > 0 and not table.contains(skill.attachedKingdom, player.kingdom) then + return end + + room:handleAddLoseSkills(player, skillName, nil, false) + end + for _, p in ipairs(room.alive_players) do + local skills = Fk.generals[p.general].skills + for _, s in ipairs(skills) do + addRoleModSkills(p, s.name) + end + for _, sname in ipairs(Fk.generals[p.general].other_skills) do + addRoleModSkills(p, sname) + end + + local deputy = Fk.generals[p.deputyGeneral] + if deputy then + skills = deputy.skills + for _, s in ipairs(skills) do + addRoleModSkills(p, s.name) + end + for _, sname in ipairs(deputy.other_skills) do + addRoleModSkills(p, sname) + end + end + end end function GameLogic:prepareForStart() - local room = self.room - local players = room.players + local room = self.room + local players = room.players - self:addTriggerSkill(GameRule) - for _, trig in ipairs(Fk.global_trigger) do - self:addTriggerSkill(trig) - end + self:addTriggerSkill(GameRule) + for _, trig in ipairs(Fk.global_trigger) do + self:addTriggerSkill(trig) + end - self.room:sendLog{ - type = "$GameStart" - } + self.room:sendLog { + type = "$GameStart" + } end function GameLogic:action() - self:trigger(fk.GamePrepared) - local room = self.room + self:trigger(fk.GamePrepared) + local room = self.room - execGameEvent(GameEvent.DrawInitial) + execGameEvent(GameEvent.DrawInitial) - while true do - execGameEvent(GameEvent.Round) - if room.game_finished then - break - end + while true do + execGameEvent(GameEvent.Round) + if room.game_finished then + break end + end end ---@param skill TriggerSkill function GameLogic:addTriggerSkill(skill) - if skill == nil or table.contains(self.skills, skill.name) then - return + if skill == nil or table.contains(self.skills, skill.name) then + return + end + + table.insert(self.skills, skill.name) + + for _, event in ipairs(skill.refresh_events) do + if self.refresh_skill_table[event] == nil then + self.refresh_skill_table[event] = {} + end + table.insert(self.refresh_skill_table[event], skill) + end + + for _, event in ipairs(skill.events) do + if self.skill_table[event] == nil then + self.skill_table[event] = {} + end + table.insert(self.skill_table[event], skill) + + if self.skill_priority_table[event] == nil then + self.skill_priority_table[event] = {} end - table.insert(self.skills, skill.name) - - for _, event in ipairs(skill.refresh_events) do - if self.refresh_skill_table[event] == nil then - self.refresh_skill_table[event] = {} + local priority_tab = self.skill_priority_table[event] + local prio = skill.priority_table[event] + if not table.contains(priority_tab, prio) then + for i, v in ipairs(priority_tab) do + if v < prio then + table.insert(priority_tab, i, prio) + break end - table.insert(self.refresh_skill_table[event], skill) + end + + if not table.contains(priority_tab, prio) then + table.insert(priority_tab, prio) + end end - for _, event in ipairs(skill.events) do - if self.skill_table[event] == nil then - self.skill_table[event] = {} - end - table.insert(self.skill_table[event], skill) - - if self.skill_priority_table[event] == nil then - self.skill_priority_table[event] = {} - end - - local priority_tab = self.skill_priority_table[event] - local prio = skill.priority_table[event] - if not table.contains(priority_tab, prio) then - for i, v in ipairs(priority_tab) do - if v < prio then - table.insert(priority_tab, i, prio) - break - end - end - - if not table.contains(priority_tab, prio) then - table.insert(priority_tab, prio) - end - end - - if not table.contains(self.skill_priority_table[event], skill.priority_table[event]) then - - table.insert(self.skill_priority_table[event], skill.priority_table[event]) - end + if not table.contains(self.skill_priority_table[event], skill.priority_table[event]) then + table.insert(self.skill_priority_table[event], skill.priority_table[event]) end + end - if skill.visible then - if (Fk.related_skills[skill.name] == nil) then - return - end - for _, s in ipairs(Fk.related_skills[skill.name]) do - if (s.class == TriggerSkill) then - self:addTriggerSkill(s) - end - end + if skill.visible then + if (Fk.related_skills[skill.name] == nil) then + return end + for _, s in ipairs(Fk.related_skills[skill.name]) do + if (s.class == TriggerSkill) then + self:addTriggerSkill(s) + end + end + end end ---@param event Event ---@param target ServerPlayer|nil ---@param data any|nil function GameLogic:trigger(event, target, data, refresh_only) - local room = self.room - local broken = false - local skills = self.skill_table[event] or {} - local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable - local _target = room.current -- for iteration - local player = _target - if #skills_to_refresh > 0 then - repeat - do - -- refresh skills. This should not be broken - for _, skill in ipairs(skills_to_refresh) do - if skill:canRefresh(event, target, player, data) then - skill:refresh(event, target, player, data) - end - end - player = player.next - end - until player == _target + local room = self.room + local broken = false + local skills = self.skill_table[event] or {} + local skills_to_refresh = self.refresh_skill_table[event] or Util.DummyTable + local _target = room.current -- for iteration + local player = _target + if #skills_to_refresh > 0 then + repeat + do + -- refresh skills. This should not be broken + for _, skill in ipairs(skills_to_refresh) do + if skill:canRefresh(event, target, player, data) then + skill:refresh(event, target, player, data) + end + end + player = player.next + end + until player == _target + end + + if #skills == 0 or refresh_only then + return + end + + local prio_tab = self.skill_priority_table[event] + local prev_prio = math.huge + + for _, prio in ipairs(prio_tab) do + if broken then + break + end + if prio >= prev_prio then + -- continue + goto trigger_loop_continue end - if #skills == 0 or refresh_only then - return - end + repeat + do + local triggerables = table.filter(skills, function(skill) + return skill.priority_table[event] == prio and skill:triggerable(event, target, player, data) + end) - local prio_tab = self.skill_priority_table[event] - local prev_prio = math.huge + local skill_names = table.map(triggerables, function(skill) + return skill.name + end) - for _, prio in ipairs(prio_tab) do - if broken then + while #skill_names > 0 do + local skill_name = prio <= 0 and table.random(skill_names) or + room:askForChoice(player, skill_names, "trigger", "#choose-trigger") + + local skill = skill_name == "game_rule" and GameRule or Fk.skills[skill_name] + + local len = #skills + broken = skill:trigger(event, target, player, data) + + table.insertTable(skill_names, + table.map(table.filter(table.slice(skills, len - #skills), function(s) + return s.priority_table[event] == prio and s:triggerable(event, target, player, data) + end), function(s) + return s.name + end)) + + broken = broken or (event == fk.AskForPeaches and room:getPlayerById(data.who).hp > 0) + + if broken then break + end + table.removeOne(skill_names, skill_name) end - if prio >= prev_prio then - -- continue - goto trigger_loop_continue + if broken then + break end + player = player.next + end + until player == _target - repeat - do - local triggerables = table.filter(skills, function(skill) - return skill.priority_table[event] == prio and skill:triggerable(event, target, player, data) - end) - - local skill_names = table.map(triggerables, function(skill) - return skill.name - end) - - while #skill_names > 0 do - local skill_name = prio <= 0 and table.random(skill_names) or - room:askForChoice(player, skill_names, "trigger", "#choose-trigger") - - local skill = skill_name == "game_rule" and GameRule or Fk.skills[skill_name] - - local len = #skills - broken = skill:trigger(event, target, player, data) - - table.insertTable(skill_names, - table.map(table.filter(table.slice(skills, len - #skills), function(s) - return s.priority_table[event] == prio and s:triggerable(event, target, player, data) - end), function(s) - return s.name - end)) - - broken = broken or (event == fk.AskForPeaches and room:getPlayerById(data.who).hp > 0) - - if broken then - break - end - table.removeOne(skill_names, skill_name) - end - if broken then - break - end - player = player.next - end - until player == _target - - prev_prio = prio - ::trigger_loop_continue:: - end - _target.ai:filterEvent(event, target, data) - return broken + prev_prio = prio + ::trigger_loop_continue:: + end + _target.ai:filterEvent(event, target, data) + return broken end ---@return GameEvent function GameLogic:getCurrentEvent() - return self.game_event_stack.t[self.game_event_stack.p] + return self.game_event_stack.t[self.game_event_stack.p] end -- 在指定历史范围中找至多n个符合条件的事件 @@ -440,90 +439,89 @@ end ---@param scope integer @ 查询历史范围,只能是当前阶段/回合/轮次 ---@return GameEvent[] @ 找到的符合条件的所有事件,最多n个但不保证有n个 function GameLogic:getEventsOfScope(eventType, n, func, scope) - scope = scope or Player.HistoryTurn - local event = self:getCurrentEvent() - local start_event ---@type GameEvent - if scope == Player.HistoryGame then - start_event = self.all_game_events[1] - elseif scope == Player.HistoryRound then - start_event = event:findParent(GameEvent.Round, true) - elseif scope == Player.HistoryTurn then - start_event = event:findParent(GameEvent.Turn, true) - elseif scope == Player.HistoryPhase then - start_event = event:findParent(GameEvent.Phase, true) - end + scope = scope or Player.HistoryTurn + local event = self:getCurrentEvent() + local start_event ---@type GameEvent + if scope == Player.HistoryGame then + start_event = self.all_game_events[1] + elseif scope == Player.HistoryRound then + start_event = event:findParent(GameEvent.Round, true) + elseif scope == Player.HistoryTurn then + start_event = event:findParent(GameEvent.Turn, true) + elseif scope == Player.HistoryPhase then + start_event = event:findParent(GameEvent.Phase, true) + end - return start_event:searchEvents(eventType, n, func) + return start_event:searchEvents(eventType, n, func) end function GameLogic:dumpEventStack(detailed) - local top = self:getCurrentEvent() - local i = self.game_event_stack.p - local inspect = p - if not top then - return + local top = self:getCurrentEvent() + local i = self.game_event_stack.p + local inspect = p + if not top then + return + end + + print("===== Start of event stack dump =====") + if not detailed then + print("") + end + + repeat + local printable_data + if type(top.data) ~= "table" then + printable_data = top.data + else + printable_data = table.cloneWithoutClass(top.data) end - print("===== Start of event stack dump =====") if not detailed then - print("") + print("Stack level #" .. i .. ": " .. tostring(top)) + else + print("\nStack level #" .. i .. ":") + inspect { + eventId = GameEvent:translate(top.event), + data = printable_data or "nil" + } end - repeat - local printable_data - if type(top.data) ~= "table" then - printable_data = top.data - else - printable_data = table.cloneWithoutClass(top.data) - end + top = top.parent + i = i - 1 + until not top - if not detailed then - print("Stack level #" .. i .. ": " .. tostring(top)) - else - print("\nStack level #" .. i .. ":") - inspect { - eventId = GameEvent:translate(top.event), - data = printable_data or "nil" - } - end - - top = top.parent - i = i - 1 - until not top - - print("\n===== End of event stack dump =====") + print("\n===== End of event stack dump =====") end function GameLogic:dumpAllEvents(from, to) - from = from or 1 - to = to or #self.all_game_events - assert(from <= to) - - local indent = 0 - local tab = " " - for i = from, to, 1 do - local v = self.all_game_events[i] - if type(v) ~= "table" then - indent = math.max(indent - 1, 0) - -- v = "End" - -- print(tab:rep(indent) .. string.format("#%d: %s", i, v)) - else - print(tab:rep(indent) .. string.format("%s", tostring(v))) - if v.id ~= v.end_id then - indent = indent + 1 - end - end + from = from or 1 + to = to or #self.all_game_events + assert(from <= to) + local indent = 0 + local tab = " " + for i = from, to, 1 do + local v = self.all_game_events[i] + if type(v) ~= "table" then + indent = math.max(indent - 1, 0) + -- v = "End" + -- print(tab:rep(indent) .. string.format("#%d: %s", i, v)) + else + print(tab:rep(indent) .. string.format("%s", tostring(v))) + if v.id ~= v.end_id then + indent = indent + 1 + end end + end end function GameLogic:breakEvent(ret) - self.room.breakEvent = true - coroutine.yield("__breakEvent", ret) + self.room.breakEvent = true + coroutine.yield("__breakEvent", ret) end function GameLogic:breakTurn() - local event = self:getCurrentEvent():findParent(GameEvent.Turn) - event:shutdown() + local event = self:getCurrentEvent():findParent(GameEvent.Turn) + event:shutdown() end return GameLogic diff --git a/mkfksource.sh b/mkfksource.sh index 51c04416..a2144a9f 100755 --- a/mkfksource.sh +++ b/mkfksource.sh @@ -3,7 +3,7 @@ dir=FreeKill-${FK_VER} mkdir $dir echo Copying -cp -r ./Freekill/.git $dir +cp -r ./FreeKill/.git $dir cd $dir git restore . diff --git a/waiting_tips.txt b/waiting_tips.txt index 4dd7104b..c8ed62e9 100644 --- a/waiting_tips.txt +++ b/waiting_tips.txt @@ -8,10 +8,10 @@ 给FreeKill的GitHub仓库点一个star吧! 你可以在武将一览界面禁将,在你创建的房间中禁用武将不会出现在选将框。 如果你遇到了bug,请截图并反馈给开发者 -如果你想要制作FK的mod,需要先学习Lua语言 +如果你想要制作新月杀的mod,需要先学习Lua语言 目光所及短寸吾才之间满腹狭目之阿瞒有我见袁本良计初只能窥取冀州便是底竟不从易成略在胸如反掌之良计速出吾才满目光所及腹袁本初竟短寸之间不从之 吔! -想要参与到FK的开发工作中?那么就从制作自己的Mod开始吧! +想要参与到新月杀的开发工作中?那么就从制作自己的Mod开始吧! 在游戏的一些地方,长按可以显示该元素的详情。如果你用的是电脑版的话可以用鼠标右键代替长按。 注意:官方不支持任何形式的账号交易行为,请妥善保管好您的账号密码。请勿与他人共享账号。因账号交易、共享引起的账号争议官方均不受理。 如果牌不好的话,就请辱骂GK的发牌员吧 @@ -19,8 +19,9 @@ 神曰:“MBKS” 正在匹配势均力敌的对手…… 匹配到界徐盛 先喝酒还是先上刀,这是个值得考虑的问题 -在抱怨FK只有标准包?那么去试着参与联机吧 +在抱怨新月杀只有标准包?那么去试着参与联机吧 如果在牌局内想查看技能,可以长按想要看技能的角色 -FK的开服很简单,只要单机启动就可以当做服务器使用了 -用Linux也能搭建FK服务器,起始页推荐的联机IP就是跑在Linux服务器上的 +新月杀的开服很简单,只要单机启动就可以当做服务器使用了 +用Linux也能搭建新月杀服务器,起始页推荐的联机IP就是跑在Linux服务器上的 我们的游戏正在蒸蒸日上哦~ +请素质游戏!不要做出烧条之类的举动哦~