- 为findParent添加深度限制参数(默认无限制)
- 搬运了damageByCardEffect
- 修复了ex__choose_skill
- 修复了华佗、吕蒙和古锭刀
- 添加势力映射,可以指定一个势力必须变成其他几个势力之一(需要神话再临包/OL包自行处理神将变将范围)
- askForCardsChosen界限突破,改成了基于askForPoxi的格式
- 修复了空城虚拟杀可以方天的bug
- 给强制平局添加了原因提醒
- 优化了移动牌的视觉逻辑
This commit is contained in:
YoumuKon 2024-04-02 00:56:04 +08:00 committed by GitHub
parent 75d0ad55c8
commit e840a3b322
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 318 additions and 114 deletions

View File

@ -275,15 +275,30 @@ function moveCards(moves) {
const move = moves[i]; const move = moves[i];
const from = getAreaItem(move.fromArea, move.from); const from = getAreaItem(move.fromArea, move.from);
const to = getAreaItem(move.toArea, move.to); const to = getAreaItem(move.toArea, move.to);
if (!from || !to || from === to) if (!from || !to || (from === to && move.fromArea !== Card.DiscardPile))
continue; continue;
const items = from.remove(move.ids, move.fromSpecialName); const items = from.remove(move.ids, move.fromSpecialName);
if (items.length > 0) if (to === tablePile) {
to.add(items, move.specialName); let vanished = items.filter(c => c.cid === -1);
to.updateCardPosition(true); if (vanished.length > 0) {
drawPile.add(vanished, move.specialName);
drawPile.updateCardPosition(true);
}
vanished = items.filter(c => c.cid !== -1);
if (vanished.length > 0) {
to.add(vanished, move.specialName);
to.updateCardPosition(true);
}
} else {
if (items.length > 0)
to.add(items, move.specialName);
to.updateCardPosition(true);
}
} }
} }
function resortHandcards() { function resortHandcards() {
if (!dashboard.handcardArea.cards.length) { if (!dashboard.handcardArea.cards.length) {
return; return;
@ -1174,9 +1189,9 @@ callbacks["AskForPoxi"] = (jsonData) => {
roomScene.popupBox.sourceComponent = roomScene.popupBox.sourceComponent =
Qt.createComponent("../RoomElement/PoxiBox.qml"); Qt.createComponent("../RoomElement/PoxiBox.qml");
const box = roomScene.popupBox.item; const box = roomScene.popupBox.item;
box.extra_data = JSON.stringify(extra_data);
box.poxi_type = type; box.poxi_type = type;
box.card_data = data; box.card_data = data;
box.extra_data = extra_data;
box.cancelable = cancelable; box.cancelable = cancelable;
for (let d of data) { for (let d of data) {
const arr = []; const arr = [];
@ -1185,6 +1200,7 @@ callbacks["AskForPoxi"] = (jsonData) => {
ids.forEach(id => arr.push(lcall("GetCardData", id))); ids.forEach(id => arr.push(lcall("GetCardData", id)));
box.addCustomCards(d[0], arr); box.addCustomCards(d[0], arr);
} }
box.refreshPrompt();
roomScene.popupBox.moveToCenter(); roomScene.popupBox.moveToCenter();
box.cardsSelected.connect((ids) => { box.cardsSelected.connect((ids) => {

View File

@ -2,12 +2,13 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Fk
import Fk.Pages import Fk.Pages
GraphicsBox { GraphicsBox {
id: root id: root
title.text: lcall("PoxiPrompt", poxi_type, card_data, extra_data) title.text: Util.processPrompt(lcall("PoxiPrompt", poxi_type, card_data, extra_data))
// TODO: Adjust the UI design in case there are more than 7 cards // TODO: Adjust the UI design in case there are more than 7 cards
width: 70 + 700 width: 70 + 700
@ -71,7 +72,7 @@ GraphicsBox {
number: model.number || 0 number: model.number || 0
autoBack: false autoBack: false
known: model.cid !== -1 known: model.cid !== -1
selectable: root.selected_ids.includes(model.cid) || selectable: chosenInBox ||
lcall("PoxiFilter", root.poxi_type, model.cid, root.selected_ids, lcall("PoxiFilter", root.poxi_type, model.cid, root.selected_ids,
root.card_data, root.extra_data); root.card_data, root.extra_data);
@ -84,6 +85,7 @@ GraphicsBox {
root.selected_ids.splice(root.selected_ids.indexOf(cid), 1); root.selected_ids.splice(root.selected_ids.indexOf(cid), 1);
} }
root.selected_ids = root.selected_ids; root.selected_ids = root.selected_ids;
refreshPrompt();
} }
} }
} }
@ -123,7 +125,7 @@ GraphicsBox {
let ret; let ret;
for (let i = 0; i < cardModel.count; i++) { for (let i = 0; i < cardModel.count; i++) {
let item = cardModel.get(i); let item = cardModel.get(i);
if (item.areaName == name) { if (item.areaName === name) {
ret = item; ret = item;
break; break;
} }
@ -149,4 +151,8 @@ GraphicsBox {
area.append(cards); area.append(cards);
} }
} }
function refreshPrompt() {
root.title.text = Util.processPrompt(lcall("PoxiPrompt", poxi_type, card_data, extra_data))
}
} }

View File

@ -744,6 +744,7 @@ end
function PoxiPrompt(poxi_type, data, extra_data) function PoxiPrompt(poxi_type, data, extra_data)
local poxi = Fk.poxi_methods[poxi_type] local poxi = Fk.poxi_methods[poxi_type]
extra_data = extra_data and json.decode(extra_data)
if not poxi or not poxi.prompt then return "" end if not poxi or not poxi.prompt then return "" end
if type(poxi.prompt) == "string" then return Fk:translate(poxi.prompt) end if type(poxi.prompt) == "string" then return Fk:translate(poxi.prompt) end
return Fk:translate(poxi.prompt(data, extra_data)) return Fk:translate(poxi.prompt(data, extra_data))
@ -751,12 +752,14 @@ end
function PoxiFilter(poxi_type, to_select, selected, data, extra_data) function PoxiFilter(poxi_type, to_select, selected, data, extra_data)
local poxi = Fk.poxi_methods[poxi_type] local poxi = Fk.poxi_methods[poxi_type]
extra_data = extra_data and json.decode(extra_data)
if not poxi then return "false" end if not poxi then return "false" end
return json.encode(poxi.card_filter(to_select, selected, data, extra_data)) return json.encode(poxi.card_filter(to_select, selected, data, extra_data))
end end
function PoxiFeasible(poxi_type, selected, data, extra_data) function PoxiFeasible(poxi_type, selected, data, extra_data)
local poxi = Fk.poxi_methods[poxi_type] local poxi = Fk.poxi_methods[poxi_type]
extra_data = extra_data and json.decode(extra_data)
if not poxi then return "false" end if not poxi then return "false" end
return json.encode(poxi.feasible(selected, data, extra_data)) return json.encode(poxi.feasible(selected, data, extra_data))
end end

View File

@ -250,6 +250,12 @@ Fk:loadTranslationTable({
-- ["Trusting ..."] = "托管中 ...", -- ["Trusting ..."] = "托管中 ...",
-- ["Observing ..."] = "旁观中 ...", -- ["Observing ..."] = "旁观中 ...",
["#NoCardDraw"] = "Card Pile is empty",
["#NoGeneralDraw"] = "General Pile is empty",
["#NoEventDraw"] = "All game events terminated",
["#NoEnoughGeneralDraw"] = "No enough generals! (%arg/%arg2)",
["#TimeOutDraw"] = "It's over 9999 Round!",
["$GameOver"] = "Game Over", ["$GameOver"] = "Game Over",
["$Winner"] = "Winner is %1", ["$Winner"] = "Winner is %1",
["$NoWinner"] = "Draw!", ["$NoWinner"] = "Draw!",

View File

@ -307,6 +307,12 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["resting..."] = "休整中", ["resting..."] = "休整中",
["rest round num"] = "轮次", ["rest round num"] = "轮次",
["#NoCardDraw"] = "牌堆被摸空了",
["#NoGeneralDraw"] = "武将牌堆被摸空了",
["#NoEventDraw"] = "没有可执行的事件",
["#NoEnoughGeneralDraw"] = "武将数不足!(%arg/%arg2)",
["#TimeOutDraw"] = "轮数已经突破极限!",
["$GameOver"] = "游戏结束", ["$GameOver"] = "游戏结束",
["$Winner"] = "%1 获胜", ["$Winner"] = "%1 获胜",
["$NoWinner"] = "平局!", ["$NoWinner"] = "平局!",

View File

@ -27,6 +27,8 @@
---@field public currentResponseReason string @ 要求用牌的原因(如濒死,被特定牌指定,使用特定技能···) ---@field public currentResponseReason string @ 要求用牌的原因(如濒死,被特定牌指定,使用特定技能···)
---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌 ---@field public filtered_cards table<integer, Card> @ 被锁视技影响的卡牌
---@field public printed_cards table<integer, Card> @ 被某些房间现场打印的卡牌id都是负数且从-2开始 ---@field public printed_cards table<integer, Card> @ 被某些房间现场打印的卡牌id都是负数且从-2开始
---@field private kingdoms string[] @ 总势力
---@field private kingdom_map table<string, string[]> @ 势力映射表
---@field private _custom_events any[] @ 自定义事件列表 ---@field private _custom_events any[] @ 自定义事件列表
---@field public poxi_methods table<string, PoxiSpec> @ “魄袭”框操作方法表 ---@field public poxi_methods table<string, PoxiSpec> @ “魄袭”框操作方法表
---@field public qml_marks table<string, QmlMarkSpec> @ 自定义Qml标记的表 ---@field public qml_marks table<string, QmlMarkSpec> @ 自定义Qml标记的表
@ -67,6 +69,7 @@ function Engine:initialize()
self.game_modes = {} self.game_modes = {}
self.game_mode_disabled = {} self.game_mode_disabled = {}
self.kingdoms = {} self.kingdoms = {}
self.kingdom_map = {}
self._custom_events = {} self._custom_events = {}
self.poxi_methods = {} self.poxi_methods = {}
self.qml_marks = {} self.qml_marks = {}
@ -271,6 +274,30 @@ function Engine:addGenerals(generals)
end end
end end
--- 为一个势力添加势力映射
---
--- 这意味着原势力登场时必须改变为添加的几个势力之一(须存在)
---@param kingdom string @ 原势力
---@param kingdoms string[] @ 需要映射到的势力
function Engine:appendKingdomMap(kingdom, kingdoms)
local ret = self.kingdom_map[kingdom] or {}
table.insertTableIfNeed(ret, kingdoms)
self.kingdom_map[kingdom] = ret
end
---获得一个势力所映射到的势力,若没有,返回空集
---@param kingdom string @ 原势力
---@return string[] @ 可用势力列表,可能是空的
function Engine:getKingdomMap(kingdom)
local ret = {}
for _, k in ipairs(self.kingdom_map[kingdom] or {}) do
if table.contains(self.kingdoms, k) then
table.insertIfNeed(ret, k)
end
end
return ret
end
--- 判断一个武将是否在本房间可用。 --- 判断一个武将是否在本房间可用。
---@param g string @ 武将名 ---@param g string @ 武将名
function Engine:canUseGeneral(g) function Engine:canUseGeneral(g)

View File

@ -874,7 +874,7 @@ end
---@param card Card @ 特定牌 ---@param card Card @ 特定牌
---@param extra_data? UseExtraData @ 额外数据 ---@param extra_data? UseExtraData @ 额外数据
function Player:canUse(card, extra_data) function Player:canUse(card, extra_data)
return card.skill:canUse(self, card, extra_data) return not self:prohibitUse(card) and card.skill:canUse(self, card, extra_data)
end end
--- 确认玩家是否可以对特定玩家使用特定牌。 --- 确认玩家是否可以对特定玩家使用特定牌。

View File

@ -57,7 +57,7 @@ end
---@param selected? integer[] @ ids of selected targets ---@param selected? integer[] @ ids of selected targets
---@param user? integer @ id of the userdata ---@param user? integer @ id of the userdata
---@param card? Card @ helper ---@param card? Card @ helper
---@param distance_limited boolean @ is limited by distance ---@param distance_limited? boolean @ is limited by distance
function ActiveSkill:modTargetFilter(to_select, selected, user, card, distance_limited) function ActiveSkill:modTargetFilter(to_select, selected, user, card, distance_limited)
return false return false
end end

View File

@ -235,6 +235,7 @@ end
---@field public enabled_at_play? fun(self: ViewAsSkill, player: Player): boolean? ---@field public enabled_at_play? fun(self: ViewAsSkill, player: Player): boolean?
---@field public enabled_at_response? fun(self: ViewAsSkill, player: Player, response: boolean): boolean? ---@field public enabled_at_response? fun(self: ViewAsSkill, player: Player, response: boolean): boolean?
---@field public before_use? fun(self: ViewAsSkill, player: ServerPlayer, use: CardUseStruct): string? ---@field public before_use? fun(self: ViewAsSkill, player: ServerPlayer, use: CardUseStruct): string?
---@field public after_use? fun(self: ViewAsSkill, player: ServerPlayer, use: CardUseStruct): string?
---@field public prompt? string|fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): string ---@field public prompt? string|fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): string
---@param spec ViewAsSkillSpec ---@param spec ViewAsSkillSpec

View File

@ -148,6 +148,10 @@ GameEvent.functions[GameEvent.Round] = function(self)
room:doBroadcastNotify("UpdateRoundNum", roundCount) room:doBroadcastNotify("UpdateRoundNum", roundCount)
-- 强行平局 防止can_trigger报错导致瞬间几十万轮卡炸服务器 -- 强行平局 防止can_trigger报错导致瞬间几十万轮卡炸服务器
if roundCount >= 9999 then if roundCount >= 9999 then
room:sendLog{
type = "#TimeOutDraw",
toast = true,
}
room:gameOver("") room:gameOver("")
end end
@ -354,6 +358,7 @@ GameEvent.functions[GameEvent.Phase] = function(self)
end) end)
end end
) - player:getMaxCards() ) - player:getMaxCards()
room:broadcastProperty(player, "MaxCards")
if discardNum > 0 then if discardNum > 0 then
room:askForDiscard(player, discardNum, discardNum, false, "game_rule", false) room:askForDiscard(player, discardNum, discardNum, false, "game_rule", false)
end end

View File

@ -84,17 +84,32 @@ function GameEvent:prependExitFunc(f)
table.insert(self.extra_exit_funcs, 1, f) table.insert(self.extra_exit_funcs, 1, f)
end end
function GameEvent:findParent(eventType, includeSelf) -- 找第一个与当前事件有继承关系的特定事件
---@param eventType integer @ 事件类型
---@param includeSelf bool @ 是否包括本事件
---@param depth? integer @ 搜索深度
---@return GameEvent?
function GameEvent:findParent(eventType, includeSelf, depth)
if includeSelf and self.event == eventType then return self end if includeSelf and self.event == eventType then return self end
if depth == 0 then return nil end
local e = self.parent local e = self.parent
local l = 1
while e do while e do
if e.event == eventType then return e end if e.event == eventType then return e end
if depth and l >= depth then break end
e = e.parent e = e.parent
l = l + 1
end end
return nil return nil
end end
-- 找n个id介于from和to之间的事件。 -- 找n个id介于from和to之间的事件。
---@param events GameEvent[] @ 事件数组
---@param from integer @ 起始id
---@param to integer @ 终止id
---@param n integer @ 最多找多少个
---@param func fun(e: GameEvent): boolean? @ 过滤用的函数
---@return GameEvent[] @ 找到的符合条件的所有事件最多n个但不保证有n个
local function bin_search(events, from, to, n, func) local function bin_search(events, from, to, n, func)
local left = 1 local left = 1
local right = #events local right = #events

View File

@ -447,6 +447,10 @@ function GameLogic:start()
end end
if not e then -- 没有事件,按理说不应该,平局处理 if not e then -- 没有事件,按理说不应该,平局处理
self.room:sendLog{
type = "#NoEventDraw",
toast = true,
}
self.room:gameOver("") self.room:gameOver("")
end end
@ -744,6 +748,21 @@ function GameLogic:getActualDamageEvents(n, func, scope, end_id)
return ret return ret
end end
--检测最近的伤害事件是否由执行牌的效果触发,即通常描述的使用牌对目标角色造成伤害
---@param is_exact? bool @ 是否进一步判定使用者和来源是否一致默认为true
---@return bool
function GameLogic:damageByCardEffect(is_exact)
is_exact = (is_exact == nil) and true or is_exact
local d_event = self:getCurrentEvent():findParent(GameEvent.Damage, true)
if d_event == nil then return false end
local damage = d_event.data[1]
if damage.chain or damage.card == nil then return false end
local c_event = d_event:findParent(GameEvent.CardEffect, false, 2)
if c_event == nil then return false end
return damage.card == c_event.data[1].card and
(not is_exact or d_event.data[1].from.id == c_event.data[1].from)
end
function GameLogic:dumpEventStack(detailed) function GameLogic:dumpEventStack(detailed)
local top = self:getCurrentEvent() local top = self:getCurrentEvent()
local i = self.game_event_stack.p local i = self.game_event_stack.p

View File

@ -56,6 +56,7 @@ dofile "lua/server/ai/init.lua"
gameevent.lua () gameevent.lua ()
game_rule.lua () game_rule.lua ()
aux_skills.lua (askForUseActiveSkill) aux_skills.lua (askForUseActiveSkill)
aux_poxi.lua (Poxi之后PoxiMethod为基础的交互)
]]---------------------------------------------------------------------- ]]----------------------------------------------------------------------
------------------------------------------------------------------------ ------------------------------------------------------------------------
@ -431,6 +432,10 @@ function Room:getNCards(num, from)
if #self.draw_pile < num then if #self.draw_pile < num then
self:shuffleDrawPile() self:shuffleDrawPile()
if #self.draw_pile < num then if #self.draw_pile < num then
self:sendLog{
type = "#NoCardDraw",
toast = true,
}
self:gameOver("") self:gameOver("")
end end
end end
@ -605,14 +610,16 @@ function Room:changeHero(player, new_general, full, isDeputy, sendLog, maxHpChan
kingdomChange = (kingdomChange == nil) and true or kingdomChange kingdomChange = (kingdomChange == nil) and true or kingdomChange
local kingdom = (isDeputy or not kingdomChange) and player.kingdom or new.kingdom local kingdom = (isDeputy or not kingdomChange) and player.kingdom or new.kingdom
if not isDeputy and kingdomChange and (new.kingdom == "god" or new.subkingdom) then if not isDeputy and kingdomChange then
local allKingdoms = {} local allKingdoms = {}
if new.kingdom == "god" then if new.subkingdom then
allKingdoms = table.filter({"wei", "shu", "wu", "qun", "jin"}, function(k) return table.contains(Fk.kingdoms, k) end)
elseif new.subkingdom then
allKingdoms = { new.kingdom, new.subkingdom } allKingdoms = { new.kingdom, new.subkingdom }
else
allKingdoms = Fk:getKingdomMap(new.kingdom)
end
if #allKingdoms > 0 then
kingdom = self:askForChoice(player, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom")
end end
kingdom = self:askForChoice(player, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom")
end end
execGameEvent(GameEvent.ChangeProperty, execGameEvent(GameEvent.ChangeProperty,
@ -1372,10 +1379,10 @@ function Room:askForChooseCardsAndPlayers(player, minCardNum, maxCardNum, target
local data = { local data = {
targets = targets, targets = targets,
max_target_num = maxTargetNum, max_t_num = maxTargetNum,
min_target_num = minTargetNum, min_t_num = minTargetNum,
max_card_num = maxCardNum, max_c_num = maxCardNum,
min_card_num = minCardNum, min_c_num = minCardNum,
pattern = pattern, pattern = pattern,
skillName = skillName, skillName = skillName,
-- include_equip = includeEquip, -- FIXME: 预定一个破坏性更新 -- include_equip = includeEquip, -- FIXME: 预定一个破坏性更新
@ -1501,6 +1508,10 @@ function Room:getNGenerals(n, position)
end end
if #generals < 1 then if #generals < 1 then
self:sendLog{
type = "#NoGeneralDraw",
toast = true,
}
self:gameOver("") self:gameOver("")
end end
return generals return generals
@ -1594,24 +1605,25 @@ end
function Room:askForChooseKingdom(players) function Room:askForChooseKingdom(players)
players = players or self.alive_players players = players or self.alive_players
local specialKingdomPlayers = table.filter(players, function(p) local specialKingdomPlayers = table.filter(players, function(p)
return p.kingdom == "god" or Fk.generals[p.general].subkingdom return Fk.generals[p.general].subkingdom or #Fk:getKingdomMap(p.kingdom) > 0
end) end)
if #specialKingdomPlayers > 0 then if #specialKingdomPlayers > 0 then
local choiceMap = {} local choiceMap = {}
for _, p in ipairs(specialKingdomPlayers) do for _, p in ipairs(specialKingdomPlayers) do
local allKingdoms = {} local allKingdoms = {}
if p.kingdom == "god" then local curGeneral = Fk.generals[p.general]
allKingdoms = table.filter({"wei", "shu", "wu", "qun", "jin"}, function(k) return table.contains(Fk.kingdoms, k) end) if curGeneral.subkingdom then
else
local curGeneral = Fk.generals[p.general]
allKingdoms = { curGeneral.kingdom, curGeneral.subkingdom } allKingdoms = { curGeneral.kingdom, curGeneral.subkingdom }
else
allKingdoms = Fk:getKingdomMap(p.kingdom)
end end
if #allKingdoms > 0 then
choiceMap[p.id] = allKingdoms
choiceMap[p.id] = allKingdoms local data = json.encode({ allKingdoms, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom" })
p.request_data = data
local data = json.encode({ allKingdoms, allKingdoms, "AskForKingdom", "#ChooseInitialKingdom" }) end
p.request_data = data
end end
self:notifyMoveFocus(players, "AskForKingdom") self:notifyMoveFocus(players, "AskForKingdom")
@ -1674,57 +1686,6 @@ function Room:askForCardChosen(chooser, target, flag, reason, prompt)
return result return result
end end
--- 完全类似askForCardChosen但是可以选择多张牌。
--- 相应的返回的是id的数组而不是单个id。
---@param chooser ServerPlayer @ 要被询问的人
---@param target ServerPlayer @ 被选牌的人
---@param min integer @ 最小选牌数
---@param max integer @ 最大选牌数
---@param flag any @ 用"hej"三个字母的组合表示能选择哪些区域, h 手牌区, e - 装备区, j - 判定区
---@param reason string @ 原因,一般是技能名
---@param prompt? string @ 提示信息
---@return integer[] @ 选择的id
function Room:askForCardsChosen(chooser, target, min, max, flag, reason, prompt)
if min == 1 and max == 1 then
return { self:askForCardChosen(chooser, target, flag, reason) }
end
local command = "AskForCardsChosen"
prompt = prompt or ""
self:notifyMoveFocus(chooser, command)
local data = {target.id, min, max, flag, reason, prompt}
local result = self:doRequest(chooser, command, json.encode(data))
local ret
if result ~= "" then
ret = json.decode(result)
else
local areas = {}
local handcards
if type(flag) == "string" then
if string.find(flag, "h") then table.insert(areas, Player.Hand) end
if string.find(flag, "e") then table.insert(areas, Player.Equip) end
if string.find(flag, "j") then table.insert(areas, Player.Judge) end
handcards = target:getCardIds(areas)
else
handcards = {}
for _, t in ipairs(flag.card_data) do
table.insertTable(handcards, t[2])
end
end
if #handcards == 0 then return {} end
ret = table.random(handcards, math.min(min, #handcards))
end
local new_ret = table.filter(ret, function(id) return id ~= -1 end)
local hand_num = #ret - #new_ret
if hand_num > 0 then
table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), hand_num))
end
return new_ret
end
--- 谋askForCardsChosen需使用Fk:addPoxiMethod定义好方法 --- 谋askForCardsChosen需使用Fk:addPoxiMethod定义好方法
--- ---
--- 选卡规则和返回值啥的全部自己想办法解决data填入所有卡的列表类似ui.card_data --- 选卡规则和返回值啥的全部自己想办法解决data填入所有卡的列表类似ui.card_data
@ -1756,6 +1717,103 @@ function Room:askForPoxi(player, poxi_type, data, extra_data, cancelable)
end end
end end
--- 完全类似askForCardChosen但是可以选择多张牌。
--- 相应的返回的是id的数组而不是单个id。
---@param chooser ServerPlayer @ 要被询问的人
---@param target ServerPlayer @ 被选牌的人
---@param min integer @ 最小选牌数
---@param max integer @ 最大选牌数
---@param flag any @ 用"hej"三个字母的组合表示能选择哪些区域, h 手牌区, e - 装备区, j - 判定区
---可以通过flag.card_data = {{牌堆1名, 牌堆1ID表},...}来定制能选择的牌
---@param reason string @ 原因,一般是技能名
---@param prompt? string @ 提示信息
---@return integer[] @ 选择的id
function Room:askForCardsChosen(chooser, target, min, max, flag, reason, prompt)
if min == 1 and max == 1 then
return { self:askForCardChosen(chooser, target, flag, reason) }
end
-- local command = "AskForCardsChosen"
-- prompt = prompt or ""
-- self:notifyMoveFocus(chooser, command)
-- local data = {target.id, min, max, flag, reason, prompt}
-- local result = self:doRequest(chooser, command, json.encode(data))
-- local ret
-- if result ~= "" then
-- ret = json.decode(result)
-- else
-- local areas = {}
-- local handcards
-- if type(flag) == "string" then
-- if string.find(flag, "h") then table.insert(areas, Player.Hand) end
-- if string.find(flag, "e") then table.insert(areas, Player.Equip) end
-- if string.find(flag, "j") then table.insert(areas, Player.Judge) end
-- handcards = target:getCardIds(areas)
-- else
-- handcards = {}
-- for _, t in ipairs(flag.card_data) do
-- table.insertTable(handcards, t[2])
-- end
-- end
-- if #handcards == 0 then return {} end
-- ret = table.random(handcards, math.min(min, #handcards))
-- end
-- local new_ret = table.filter(ret, function(id) return id ~= -1 end)
-- local hand_num = #ret - #new_ret
-- if hand_num > 0 then
-- table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), hand_num))
-- end
-- return new_ret
local areas = {}
local cards
local data = {
to = target.id,
min = min,
max = max,
skillName = reason,
prompt = prompt,
}
if type(flag) == "string" then
if string.find(flag, "h") then table.insert(areas, Player.Hand) end
if string.find(flag, "e") then table.insert(areas, Player.Equip) end
if string.find(flag, "j") then table.insert(areas, Player.Judge) end
cards = target:getCardIds(areas)
else
cards = {}
for _, t in ipairs(flag.card_data) do
table.insertTable(cards, t[2])
end
end
if #cards <= min then return table.random(cards, math.min(min, #cards)) end
local cards_data = {}
if type(flag) == "string" then
if string.find(flag, "h") and #target:getCardIds(Player.Hand) > 0 then
local handcards = {}
for _, _ in ipairs(target:getCardIds(Player.Hand)) do
table.insert(handcards, -1)
end
table.insert(cards_data, {"$Hand", handcards})
end
if string.find(flag, "e") and #target:getCardIds(Player.Equip) > 0 then table.insert(cards_data, {"$Equip", target:getCardIds(Player.Equip)}) end
if string.find(flag, "j") and #target:getCardIds(Player.Judge) > 0 then table.insert(cards_data, {"$Judge", target:getCardIds(Player.Judge)}) end
local ret = self:askForPoxi(chooser, "AskForCardsChosen", cards_data, data, false)
local new_ret = table.filter(ret, function(id) return id ~= -1 end)
local hidden_num = #ret - #new_ret
if hidden_num > 0 then
table.insertTable(new_ret, table.random(target:getCardIds(Player.Hand), hidden_num))
end
return new_ret
else
for _, t in ipairs(flag.card_data) do
table.insert(cards_data, t)
end
end
return self:askForPoxi(chooser, "AskForCardsChosen", cards_data, data, false)
end
--- 询问一名玩家从众多选项中选择一个。 --- 询问一名玩家从众多选项中选择一个。
---@param player ServerPlayer @ 要询问的玩家 ---@param player ServerPlayer @ 要询问的玩家
---@param choices string[] @ 可选选项列表 ---@param choices string[] @ 可选选项列表
@ -3027,7 +3085,7 @@ end
function Room:obtainCard(player, cid, unhide, reason, proposer) function Room:obtainCard(player, cid, unhide, reason, proposer)
if type(cid) ~= "number" then if type(cid) ~= "number" then
assert(cid and type(cid) == "table") assert(cid and type(cid) == "table")
if cid:isInstanceOf(Card) then if cid[1] == nil then
cid = cid:isVirtual() and cid.subcards or {cid.id} cid = cid:isVirtual() and cid.subcards or {cid.id}
end end
else else

View File

@ -114,7 +114,7 @@ local analepticSkill = fk.CreateActiveSkill{
end) end)
end, end,
can_use = function(self, player, card, extra_data) can_use = function(self, player, card, extra_data)
return ((extra_data and (extra_data.bypass_times or extra_data.analepticRecover)) or return not player:isProhibited(player, card) and ((extra_data and (extra_data.bypass_times or extra_data.analepticRecover)) or
self:withinTimesLimit(player, Player.HistoryTurn, card, "analeptic", player)) self:withinTimesLimit(player, Player.HistoryTurn, card, "analeptic", player))
end, end,
on_use = function(_, _, use) on_use = function(_, _, use)
@ -333,9 +333,17 @@ local gudingSkill = fk.CreateTriggerSkill{
frequency = Skill.Compulsory, frequency = Skill.Compulsory,
events = {fk.DamageCaused}, events = {fk.DamageCaused},
can_trigger = function(self, _, target, player, data) can_trigger = function(self, _, target, player, data)
return target == player and player:hasSkill(self) and local logic = player.room.logic
data.to:isKongcheng() and data.card and data.card.trueName == "slash" and if target == player and player:hasSkill(self) and
not data.chain data.to:isKongcheng() and data.card and data.card.trueName == "slash" and not data.chain then
local event = logic:getCurrentEvent()
if event == nil then return false end
event = event.parent
if event == nil or event.event ~= GameEvent.SkillEffect then return false end
event = event.parent
if event == nil or event.event ~= GameEvent.CardEffect then return false end
return data.card == event.data[1].card and data.from.id == event.data[1].from
end
end, end,
on_use = function(_, _, _, _, data) on_use = function(_, _, _, _, data)
data.damage = data.damage + 1 data.damage = data.damage + 1

View File

@ -0,0 +1,22 @@
-- SPDX-License-Identifier: GPL-3.0-or-later
Fk:addPoxiMethod{
name = "AskForCardsChosen",
card_filter = function(to_select, selected, data, extra_data)
return #selected < extra_data.max
end,
feasible = function(selected, data, extra_data)
return #selected >= extra_data.min and #selected <= extra_data.max
end,
prompt = function(data, extra_data)
if extra_data.prompt then
return extra_data.prompt
else
local ret = Fk:translate("#AskForChooseCards")
ret = ret:gsub("%%1", Fk:translate(extra_data.skillName or "AskForCardsChosen"))
ret = ret:gsub("%%2", Fk:translate(extra_data.min))
ret = ret:gsub("%%3", Fk:translate(extra_data.max))
return ret .. ":" ..extra_data.to
end
end,
}

View File

@ -95,7 +95,7 @@ local choosePlayersSkill = fk.CreateActiveSkill{
local exChooseSkill = fk.CreateActiveSkill{ local exChooseSkill = fk.CreateActiveSkill{
name = "ex__choose_skill", name = "ex__choose_skill",
card_filter = function(self, to_select, selected) card_filter = function(self, to_select, selected)
if #selected >= self.max_card_num then return false end if #selected >= self.max_c_num then return false end
if Fk:currentRoom():getCardArea(to_select) == Card.PlayerSpecial then if Fk:currentRoom():getCardArea(to_select) == Card.PlayerSpecial then
if not string.find(self.pattern or "", self.expand_pile or "") then return false end if not string.find(self.pattern or "", self.expand_pile or "") then return false end
@ -114,11 +114,15 @@ local exChooseSkill = fk.CreateActiveSkill{
return checkpoint return checkpoint
end, end,
target_filter = function(self, to_select, selected, cards) target_filter = function(self, to_select, selected, cards)
if self.pattern ~= "" and #cards < self.min_card_num then return end if #cards < self.min_c_num then return end
if #selected < self.max_target_num then if #selected < self.max_t_num then
return table.contains(self.targets, to_select) return table.contains(self.targets, to_select)
end end
end, end,
min_target_num = function(self) return self.min_t_num end,
max_target_num = function(self) return self.max_t_num end,
min_card_num = function(self) return self.min_c_num end,
max_card_num = function(self) return self.max_c_num end,
} }
local maxCardsSkill = fk.CreateMaxCardsSkill{ local maxCardsSkill = fk.CreateMaxCardsSkill{

View File

@ -4,6 +4,7 @@ local extension = Package:new("standard")
extension.metadata = require "packages.standard.metadata" extension.metadata = require "packages.standard.metadata"
dofile "packages/standard/game_rule.lua" dofile "packages/standard/game_rule.lua"
dofile "packages/standard/aux_skills.lua" dofile "packages/standard/aux_skills.lua"
dofile "packages/standard/aux_poxi.lua"
local jianxiong = fk.CreateTriggerSkill{ local jianxiong = fk.CreateTriggerSkill{
name = "jianxiong", name = "jianxiong",
@ -699,29 +700,26 @@ local keji = fk.CreateTriggerSkill{
can_trigger = function(self, event, target, player, data) can_trigger = function(self, event, target, player, data)
if target == player and player:hasSkill(self) and data.to == Player.Discard then if target == player and player:hasSkill(self) and data.to == Player.Discard then
local room = player.room local room = player.room
local logic = room.logic local play_ids = {}
local e = logic:getCurrentEvent():findParent(GameEvent.Turn, true) player.room.logic:getEventsOfScope(GameEvent.Phase, 1, function (e)
if e == nil then return false end if e.data[2] == Player.Play and e.end_id then
local end_id = e.id table.insert(play_ids, {e.id, e.end_id})
local events = logic.event_recorder[GameEvent.UseCard] or Util.DummyTable
for i = #events, 1, -1 do
e = events[i]
if e.id <= end_id then break end
local use = e.data[1]
if use.from == player.id and use.card.trueName == "slash" then
return false
end end
end return false
events = logic.event_recorder[GameEvent.RespondCard] or Util.DummyTable end, Player.HistoryTurn)
for i = #events, 1, -1 do if #play_ids == 0 then return true end
e = events[i] local function PlayCheck (e)
if e.id <= end_id then break end local in_play = false
local resp = e.data[1] for _, ids in ipairs(play_ids) do
if resp.from == player.id and resp.card.trueName == "slash" then if e.id > ids[1] and e.id < ids[2] then
return false in_play = true
break
end
end end
return in_play and e.data[1].from == player.id and e.data[1].card.trueName == "slash"
end end
return true return #player.room.logic:getEventsOfScope(GameEvent.UseCard, 1, PlayCheck, Player.HistoryTurn) == 0
and #player.room.logic:getEventsOfScope(GameEvent.RespondCard, 1, PlayCheck, Player.HistoryTurn) == 0
end end
end, end,
on_use = function(self, event, target, player, data) on_use = function(self, event, target, player, data)
@ -989,9 +987,10 @@ local qingnang = fk.CreateActiveSkill{
on_use = function(self, room, effect) on_use = function(self, room, effect)
local from = room:getPlayerById(effect.from) local from = room:getPlayerById(effect.from)
room:throwCard(effect.cards, self.name, from, from) room:throwCard(effect.cards, self.name, from, from)
if from:isAlive() and from:isWounded() then local to = room:getPlayerById(effect.tos[1])
if to:isAlive() and to:isWounded() then
room:recover({ room:recover({
who = room:getPlayerById(effect.tos[1]), who = to,
num = 1, num = 1,
recoverBy = from, recoverBy = from,
skillName = self.name skillName = self.name
@ -1117,12 +1116,20 @@ local role_getlogic = function()
if lord ~= nil then if lord ~= nil then
room.current = lord room.current = lord
local a1 = #room.general_pile
local a2 = #room.players * generalNum + lord_num
if a1 < a2 then
room:sendLog{
type = "#NoEnoughGeneralDraw",
arg = a1,
arg2 = a2,
toast = true,
}
room:gameOver("")
end
local generals = table.connect(room:findGenerals(function(g) local generals = table.connect(room:findGenerals(function(g)
return table.find(Fk.generals[g].skills, function(s) return s.lordSkill end) return table.find(Fk.generals[g].skills, function(s) return s.lordSkill end)
end, lord_num), room:getNGenerals(generalNum)) end, lord_num), room:getNGenerals(generalNum))
if #room.general_pile < (#room.players - 1) * generalNum then
room:gameOver("")
end
lord_generals = room:askForGeneral(lord, generals, n) lord_generals = room:askForGeneral(lord, generals, n)
local lord_general, deputy local lord_general, deputy
if type(lord_generals) == "table" then if type(lord_generals) == "table" then

View File

@ -1086,7 +1086,7 @@ local halberdSkill = fk.CreateTargetModSkill{
if player:hasSkill(self) and skill.trueName == "slash_skill" then if player:hasSkill(self) and skill.trueName == "slash_skill" then
local cards = card:isVirtual() and card.subcards or {card.id} local cards = card:isVirtual() and card.subcards or {card.id}
local handcards = player:getCardIds(Player.Hand) local handcards = player:getCardIds(Player.Hand)
if #cards == #handcards and table.every(cards, function(id) return table.contains(handcards, id) end) then if #handcards > 0 and #cards == #handcards and table.every(cards, function(id) return table.contains(handcards, id) end) then
return 2 return 2
end end
end end

View File

@ -83,6 +83,7 @@ local control = fk.CreateActiveSkill{
-- p(room:askForYiji(from, from:getCardIds(Player.Hand), table.map(effect.tos, Util.Id2PlayerMapper), self.name, 2, 10, nil, false, nil, false, 3, true)) -- p(room:askForYiji(from, from:getCardIds(Player.Hand), table.map(effect.tos, Util.Id2PlayerMapper), self.name, 2, 10, nil, false, nil, false, 3, true))
for _, pid in ipairs(effect.tos) do for _, pid in ipairs(effect.tos) do
local to = room:getPlayerById(pid) local to = room:getPlayerById(pid)
-- p(room:askForCardsChosen(from, to, 2, 3, "hej", self.name))
-- p(room:askForPoxi(from, "test", { -- p(room:askForPoxi(from, "test", {
-- { "你自己", from:getCardIds "h" }, -- { "你自己", from:getCardIds "h" },
-- { "对方", to:getCardIds "h" }, -- { "对方", to:getCardIds "h" },