忘了同步lua
Some checks are pending
Check Whitespace and New Line / check (push) Waiting to run
Deploy Doxygen to Pages / build (push) Waiting to run
Deploy Doxygen to Pages / deploy (push) Blocked by required conditions

This commit is contained in:
notify 2024-10-24 21:57:26 +08:00
parent ed6ce84584
commit 29d20089b4
20 changed files with 169 additions and 84 deletions

View File

@ -243,7 +243,7 @@ end
fk.client_callback["EnterRoom"] = function(_data) fk.client_callback["EnterRoom"] = function(_data)
Self = ClientPlayer:new(fk.Self) Self = ClientPlayer:new(fk.Self)
-- 垃圾bug 怎么把这玩意忘了 -- FIXME: 需要改Qml
local ob = ClientInstance.observing local ob = ClientInstance.observing
ClientInstance = Client:new() -- clear old client data ClientInstance = Client:new() -- clear old client data
ClientInstance.observing = ob ClientInstance.observing = ob

View File

@ -902,8 +902,11 @@ function RevertSelection()
local unselectData = { selected = false } local unselectData = { selected = false }
local selectData = { selected = true } local selectData = { selected = true }
local to_select = {} local to_select = {}
local lastcid
local lastselected = false
for cid, cardItem in pairs(h.scene:getAllItems("CardItem")) do for cid, cardItem in pairs(h.scene:getAllItems("CardItem")) do
if table.contains(h.pendings, cid) then if table.contains(h.pendings, cid) then
lastcid = cid
h:selectCard(cid, unselectData) h:selectCard(cid, unselectData)
else else
table.insert(to_select, cardItem) table.insert(to_select, cardItem)
@ -911,9 +914,16 @@ function RevertSelection()
end end
for _, cardItem in ipairs(to_select) do for _, cardItem in ipairs(to_select) do
if cardItem.enabled then if cardItem.enabled then
lastcid = cardItem.id
lastselected = true
h:selectCard(cardItem.id, selectData) h:selectCard(cardItem.id, selectData)
end end
end end
-- 最后模拟一次真实点击卡牌以更新目标和按钮状态
if lastcid then
h:selectCard(lastcid, { selected = not lastselected })
h:update("CardItem", lastcid, "click", { selected = lastselected })
end
h.scene:notifyUI() h.scene:notifyUI()
end end
@ -935,12 +945,11 @@ function FinishRequestUI()
end end
end end
-- TODO 传参带上cardMoveData... function CardVisibility(cardId)
function CardVisibility(cardId, move)
local player = Self local player = Self
local card = Fk:getCardById(cardId) local card = Fk:getCardById(cardId)
if not card then return false end if not card then return false end
return player:cardVisible(cardId, move) return player:cardVisible(cardId)
end end
function RoleVisibility(targetId) function RoleVisibility(targetId)

View File

@ -360,9 +360,9 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["Resume"] = "继续", ["Resume"] = "继续",
["Bulletin Info"] = [==[ ["Bulletin Info"] = [==[
## v0.4.13 ## v0.4.21
UI重构
]==], ]==],
} }

View File

@ -90,6 +90,8 @@ function Card:initialize(name, suit, number, color)
self.color = Card.Red self.color = Card.Red
elseif color ~= nil then elseif color ~= nil then
self.color = color self.color = color
elseif suit == Card.Unknown then
self.color = Card.Unknown
else else
self.color = Card.NoColor self.color = Card.NoColor
end end
@ -242,7 +244,7 @@ end
---@return string @ 描述花色的字符串 ---@return string @ 描述花色的字符串
function Card:getSuitString(symbol) function Card:getSuitString(symbol)
local suit = self.suit local suit = self.suit
local ret local ret = "unknown"
if suit == Card.Spade then if suit == Card.Spade then
ret = "spade" ret = "spade"
elseif suit == Card.Heart then elseif suit == Card.Heart then
@ -251,7 +253,7 @@ function Card:getSuitString(symbol)
ret = "club" ret = "club"
elseif suit == Card.Diamond then elseif suit == Card.Diamond then
ret = "diamond" ret = "diamond"
else elseif suit == Card.NoSuit then
ret = "nosuit" ret = "nosuit"
end end
return symbol and "log_" .. ret or ret return symbol and "log_" .. ret or ret
@ -265,8 +267,10 @@ function Card:getColorString()
return "black" return "black"
elseif color == Card.Red then elseif color == Card.Red then
return "red" return "red"
elseif color == Card.NoColor then
return "nocolor"
end end
return "nocolor" return "unknown"
end end
--- 获取卡牌类型并返回类型描述(例如基本牌/锦囊牌/装备牌)。 --- 获取卡牌类型并返回类型描述(例如基本牌/锦囊牌/装备牌)。
@ -426,8 +430,11 @@ end
--- 比较两张卡牌的花色是否相同 --- 比较两张卡牌的花色是否相同
---@param anotherCard Card @ 另一张卡牌 ---@param anotherCard Card @ 另一张卡牌
---@param diff? boolean @ 比较二者不同 ---@param diff? boolean @ 比较二者不同
---@return boolean 返回比较结果 ---@return boolean @ 返回比较结果
function Card:compareSuitWith(anotherCard, diff) function Card:compareSuitWith(anotherCard, diff)
if table.contains({ self.suit, anotherCard.suit }, Card.Unknown) then
return true
end
if self ~= anotherCard and table.contains({ self.suit, anotherCard.suit }, Card.NoSuit) then if self ~= anotherCard and table.contains({ self.suit, anotherCard.suit }, Card.NoSuit) then
return false return false
end end
@ -444,6 +451,9 @@ end
---@param diff? boolean @ 比较二者不同 ---@param diff? boolean @ 比较二者不同
---@return boolean @ 返回比较结果 ---@return boolean @ 返回比较结果
function Card:compareColorWith(anotherCard, diff) function Card:compareColorWith(anotherCard, diff)
if table.contains({ self.color, anotherCard.color }, Card.Unknown) then
return true
end
if self ~= anotherCard and table.contains({ self.color, anotherCard.color }, Card.NoColor) then if self ~= anotherCard and table.contains({ self.color, anotherCard.color }, Card.NoColor) then
return false return false
end end

View File

@ -33,6 +33,7 @@
---@field public id? integer[] ---@field public id? integer[]
-- v0.2.6改动: cardType会被解析为trueName数组和name数组而不是自己单独成立 -- v0.2.6改动: cardType会被解析为trueName数组和name数组而不是自己单独成立
-- core改动 name数组为空时将根据trueName数组生成对应的name数组
local numbertable = { local numbertable = {
["A"] = 1, ["A"] = 1,
@ -48,6 +49,8 @@ local placetable = {
local card_type_table = {} local card_type_table = {}
local card_truename_table = {}
local function fillCardTypeTable() local function fillCardTypeTable()
local tmp = {} local tmp = {}
for _, cd in ipairs(Fk.cards) do for _, cd in ipairs(Fk.cards) do
@ -66,6 +69,20 @@ local function fillCardTypeTable()
end end
end end
local function fillCardTrueNameTable()
local tmp = {}
for _, cd in ipairs(Fk.cards) do
local tn = cd.trueName
local n = cd.name
if not tmp[n] then
card_truename_table[tn] = card_truename_table[tn] or {}
table.insertIfNeed(card_truename_table[tn], n)
tmp[n] = true
end
end
end
local function matchSingleKey(matcher, card, key) local function matchSingleKey(matcher, card, key)
local match = matcher[key] local match = matcher[key]
if not match then return true end if not match then return true end
@ -302,7 +319,9 @@ local function parseMatcher(str)
ret.suit = not table.contains(t[3], ".") and t[3] or nil ret.suit = not table.contains(t[3], ".") and t[3] or nil
ret.place = not table.contains(t[4], ".") and t[4] or nil ret.place = not table.contains(t[4], ".") and t[4] or nil
ret.name = not table.contains(t[5], ".") and t[5] or nil if table.empty(card_truename_table) then
fillCardTrueNameTable()
end
-- ret.cardType = not table.contains(t[6], ".") and t[6] or nil -- ret.cardType = not table.contains(t[6], ".") and t[6] or nil
if table.empty(card_type_table) then if table.empty(card_type_table) then
fillCardTypeTable() fillCardTypeTable()
@ -325,6 +344,29 @@ local function parseMatcher(str)
table.insert(ret.trueName.neg, temp) table.insert(ret.trueName.neg, temp)
end end
if table.contains(t[5], ".") then
if ret.trueName then
ret.name = {}
for _, tn in ipairs(ret.trueName) do
table.insertTableIfNeed(ret.name, card_truename_table[tn] or Util.DummyTable)
end
for _, neg in ipairs(ret.trueName.neg or Util.DummyTable) do
if type(neg) ~= "table" then neg = { neg } end
if not ret.name.neg then ret.name.neg = {} end
local temp = {}
for _, tn in ipairs(neg) do
table.insertTableIfNeed(temp, card_truename_table[tn] or Util.DummyTable)
end
table.insert(ret.name.neg, temp)
end
else
ret.name = nil
end
else
ret.name = t[5]
end
if not table.contains(t[7], ".") then if not table.contains(t[7], ".") then
ret.id = parseRawNumTable(t[7]) ret.id = parseRawNumTable(t[7])
end end

View File

@ -1,5 +1,21 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
-- 呃 起码要做出以下几种吧:
-- Switch那个开关组件 [boolean model=nil]
-- RadioButton那个多选一圆圈里面打点组件 [string model={ label, value }[]]
-- ComboBox: 那个多选一,下拉一个菜单并选择一个的组件 [string model同上]
-- CheckBox: 那个多选多打钩组件 [string[], model={ choices = {label,value}[], min, max}]
-- Spinner: 选integer的组件吧带加号减号那个 [integer model={min, max}]
--- 定义一套配置项,供玩家在创建房间时配置。配置完成后返回一个表保存配置信息,
--- 下面假设这个配置信息是表`cfg = {}`
---@class GameModeConfigEntry
---@field public name string @ 配置项的内部名cfg的键
---@field public label? string @ 界面上显示的提示信息
---@field public delegate string @ 要显示哪种?
---@field public model any @ 这种delegate需要的model参见注释
---@field public default? any @ 默认值 cfg的value
--- GameMode用来描述一个游戏模式。 --- GameMode用来描述一个游戏模式。
--- ---
--- 可以参考欢乐斗地主。 --- 可以参考欢乐斗地主。
@ -12,6 +28,7 @@
---@field public logic? fun(): GameLogic @ 逻辑通过function完成通常用来初始化、分配身份及座次 ---@field public logic? fun(): GameLogic @ 逻辑通过function完成通常用来初始化、分配身份及座次
---@field public whitelist? string[] @ 白名单 ---@field public whitelist? string[] @ 白名单
---@field public blacklist? string[] @ 黑名单 ---@field public blacklist? string[] @ 黑名单
---@field public config_template? GameModeConfigEntry[] 游戏模式的配置页面,如此一个数组
local GameMode = class("GameMode") local GameMode = class("GameMode")
--- 构造函数,不可随意调用。 --- 构造函数,不可随意调用。
@ -83,4 +100,14 @@ function GameMode:getAdjustedProperty (player)
return list return list
end end
--- 向游戏模式中添加拓展包过滤。
---@param whitelist string[] @ 白名单
---@param blacklist string[] @ 黑名单
function GameMode:addPackageFilter(whitelist, blacklist)
assert(type(whitelist) == "table")
assert(type(blacklist) == "table")
table.insertTable(self.whitelist, whitelist)
table.insertTable(self.blacklist, blacklist)
end
return GameMode return GameMode

View File

@ -1254,7 +1254,7 @@ function Player:roleVisible(target)
end end
end end
if room.observing == false and target == self then return true end if not room.observing and target == self then return true end
return target.role_shown return target.role_shown
end end

View File

@ -100,10 +100,17 @@ function ReqPlayCard:doEndButton()
end end
function ReqPlayCard:selectCard(cid, data) function ReqPlayCard:selectCard(cid, data)
ReqUseCard.selectCard(self, cid, data) if self.skill_name and not self.selected_card then
if self.skill_name and not self.selected_card then return end return ReqActiveSkill.selectCard(self, cid, data)
end
local scene = self.scene
local selected = data.selected
scene:update("CardItem", cid, data)
if self.selected_card then if selected then
self.skill_name = nil
self.selected_card = Fk:getCardById(cid)
scene:unselectOtherCards(cid)
self:setSkillPrompt(self.selected_card.skill, self.selected_card:getEffectiveId()) self:setSkillPrompt(self.selected_card.skill, self.selected_card:getEffectiveId())
local sp_skills = {} local sp_skills = {}
if self.selected_card.special_skills then if self.selected_card.special_skills then
@ -114,11 +121,17 @@ function ReqPlayCard:selectCard(cid, data)
end end
self.scene:update("SpecialSkills", "1", { skills = sp_skills }) self.scene:update("SpecialSkills", "1", { skills = sp_skills })
else else
self.selected_card = nil
self:setPrompt(self.original_prompt) self:setPrompt(self.original_prompt)
self.scene:update("SpecialSkills", "1", { skills = {} }) self.scene:update("SpecialSkills", "1", { skills = {} })
end end
end end
function ReqPlayCard:selectSkill(skill, data)
ReqUseCard.selectSkill(self, skill, data)
self.scene:update("SpecialSkills", "1", { skills = {} })
end
function ReqPlayCard:update(elemType, id, action, data) function ReqPlayCard:update(elemType, id, action, data)
if elemType == "Button" and id == "End" then if elemType == "Button" and id == "End" then
self:doEndButton() self:doEndButton()

View File

@ -4,6 +4,12 @@ local ReqResponseCard = require 'core.request_type.response_card'
---@class ReqUseCard: ReqResponseCard ---@class ReqUseCard: ReqResponseCard
local ReqUseCard = ReqResponseCard:subclass("ReqUseCard") local ReqUseCard = ReqResponseCard:subclass("ReqUseCard")
function ReqUseCard:skillButtonValidity(name)
local player = self.player
local skill = Fk.skills[name]
return skill:isInstanceOf(ViewAsSkill) and skill:enabledAtResponse(player, false)
end
function ReqUseCard:cardValidity(cid) function ReqUseCard:cardValidity(cid)
if self.skill_name then return ReqActiveSkill.cardValidity(self, cid) end if self.skill_name then return ReqActiveSkill.cardValidity(self, cid) end
local card = cid local card = cid
@ -31,6 +37,9 @@ end
function ReqUseCard:feasible() function ReqUseCard:feasible()
local skill = Fk.skills[self.skill_name] local skill = Fk.skills[self.skill_name]
local card = self.selected_card local card = self.selected_card
if skill then
card = skill:viewAs(self.pendings)
end
local ret = false local ret = false
if card and self:cardFeasible(card) then if card and self:cardFeasible(card) then
ret = card.skill:feasible(self.selected_targets, ret = card.skill:feasible(self.selected_targets,

View File

@ -81,6 +81,7 @@ function CardManager:getCardsByArea(area, player, dup, special_name)
assert(player ~= nil) assert(player ~= nil)
if area == Player.Special then if area == Player.Special then
assert(special_name ~= nil) assert(special_name ~= nil)
player.special_cards[special_name] = player.special_cards[special_name] or {}
ret = player.special_cards[special_name] ret = player.special_cards[special_name]
else else
ret = player.player_cards[area] ret = player.player_cards[area]

View File

@ -19,6 +19,7 @@ fk.EventPhaseEnd = 6
fk.AfterPhaseEnd = 86 fk.AfterPhaseEnd = 86
fk.EventPhaseChanging = 7 fk.EventPhaseChanging = 7
fk.EventPhaseSkipping = 8 fk.EventPhaseSkipping = 8
fk.EventPhaseSkipped = 101
fk.BeforeCardsMove = 9 fk.BeforeCardsMove = 9
fk.AfterCardsMove = 10 fk.AfterCardsMove = 10
@ -149,4 +150,4 @@ fk.AfterPlayerRevived = 95
-- 99 = AfterAskForCardResponse -- 99 = AfterAskForCardResponse
-- 100 = AfterAskForNullification -- 100 = AfterAskForNullification
fk.NumOfEvents = 101 fk.NumOfEvents = 102

View File

@ -336,17 +336,16 @@ function MoveEventWrappers:recastCard(card_ids, who, skillName)
end end
--- 将一些卡牌同时分配给一些角色。 --- 将一些卡牌同时分配给一些角色。
---@param room Room @ 房间
---@param list table<integer[]> @ 分配牌和角色的数据表键为角色id值为分配给其的牌id数组 ---@param list table<integer[]> @ 分配牌和角色的数据表键为角色id值为分配给其的牌id数组
---@param proposer? integer @ 操作者的id。默认为空 ---@param proposer? integer @ 操作者的id。默认为空
---@param skillName? string @ 技能名。默认为“分配” ---@param skillName? string @ 技能名。默认为“分配”
---@return table<integer[]> @ 返回成功分配的卡牌 ---@return table<integer[]> @ 返回成功分配的卡牌
function MoveEventWrappers:doYiji(room, list, proposer, skillName) function MoveEventWrappers:doYiji(list, proposer, skillName)
skillName = skillName or "distribution_skill" skillName = skillName or "distribution_skill"
local moveInfos = {} local moveInfos = {}
local move_ids = {} local move_ids = {}
for to, cards in pairs(list) do for to, cards in pairs(list) do
local toP = room:getPlayerById(to) local toP = self:getPlayerById(to)
local handcards = toP:getCardIds("h") local handcards = toP:getCardIds("h")
cards = table.filter(cards, function (id) return not table.contains(handcards, id) end) cards = table.filter(cards, function (id) return not table.contains(handcards, id) end)
if #cards > 0 then if #cards > 0 then
@ -354,7 +353,7 @@ function MoveEventWrappers:doYiji(room, list, proposer, skillName)
local moveMap = {} local moveMap = {}
local noFrom = {} local noFrom = {}
for _, id in ipairs(cards) do for _, id in ipairs(cards) do
local from = room.owner_map[id] local from = self.owner_map[id]
if from then if from then
moveMap[from] = moveMap[from] or {} moveMap[from] = moveMap[from] or {}
table.insert(moveMap[from], id) table.insert(moveMap[from], id)
@ -366,7 +365,7 @@ function MoveEventWrappers:doYiji(room, list, proposer, skillName)
table.insert(moveInfos, { table.insert(moveInfos, {
ids = _cards, ids = _cards,
moveInfo = table.map(_cards, function(id) moveInfo = table.map(_cards, function(id)
return {cardId = id, fromArea = room:getCardArea(id), fromSpecialName = room:getPlayerById(from):getPileNameOfId(id)} return {cardId = id, fromArea = self:getCardArea(id), fromSpecialName = self:getPlayerById(from):getPileNameOfId(id)}
end), end),
from = from, from = from,
to = to, to = to,
@ -389,7 +388,7 @@ function MoveEventWrappers:doYiji(room, list, proposer, skillName)
end end
end end
if #moveInfos > 0 then if #moveInfos > 0 then
room:moveCards(table.unpack(moveInfos)) self:moveCards(table.unpack(moveInfos))
end end
return move_ids return move_ids
end end

View File

@ -491,13 +491,14 @@ end
-- 作用是启动新事件 都是结构差不多的函数 -- 作用是启动新事件 都是结构差不多的函数
---@param event GameEvent ---@param event GameEvent
---@return boolean, GameEvent? ---@return boolean, GameEvent?
function GameLogic:resumeEvent(event, ...) function GameLogic:resumeEvent(event)
local ret, evt local ret, evt
local co = event._co local co = event._co
local resume_reason = "unknown"
while true do while true do
local err, yield_result, extra_yield_result = coroutine.resume(co, ...) local err, yield_result, extra_yield_result = coroutine.resume(co, resume_reason)
if err == false then if err == false then
-- handle error, then break -- handle error, then break
@ -510,7 +511,8 @@ function GameLogic:resumeEvent(event, ...)
if yield_result == "__handleRequest" then if yield_result == "__handleRequest" then
-- yield to requestLoop -- yield to requestLoop
coroutine.yield(yield_result, extra_yield_result) -- handleRequest类的最后被ResumeRoom唤醒接收原因
resume_reason = coroutine.yield(yield_result, extra_yield_result)
elseif type(yield_result) == "table" and yield_result.class elseif type(yield_result) == "table" and yield_result.class
and yield_result:isInstanceOf(GameEvent) then and yield_result:isInstanceOf(GameEvent) then

View File

@ -176,6 +176,7 @@ function Request:ask()
local players = table.simpleClone(self.players) local players = table.simpleClone(self.players)
local currentTime = os.time() local currentTime = os.time()
local resume_reason = "unknown"
-- 1. 向所有人发送询问请求 -- 1. 向所有人发送询问请求
for _, p in ipairs(players) do for _, p in ipairs(players) do
@ -191,7 +192,7 @@ function Request:ask()
-- 判断1若投降则直接结束全部询问若超时则踢掉所有人类玩家这样AI还可计算 -- 判断1若投降则直接结束全部询问若超时则踢掉所有人类玩家这样AI还可计算
if room.hasSurrendered then break end if room.hasSurrendered then break end
local elapsed = os.time() - currentTime local elapsed = os.time() - currentTime
if self.timeout - elapsed <= 0 then if self.timeout - elapsed <= 0 or resume_reason == "request_timer" then
for i = #players, 1, -1 do for i = #players, 1, -1 do
if self.send_success[players[i].serverplayer] then if self.send_success[players[i].serverplayer] then
table.remove(players, i) table.remove(players, i)
@ -246,7 +247,7 @@ function Request:ask()
-- 需要等待呢,等待被唤醒吧 -- 需要等待呢,等待被唤醒吧
if not changed then if not changed then
coroutine.yield("__handleRequest") resume_reason = coroutine.yield("__handleRequest")
end end
end end

View File

@ -89,7 +89,8 @@ function Room:initialize(_room)
end end
-- 供调度器使用的函数。能让房间开始运行/从挂起状态恢复。 -- 供调度器使用的函数。能让房间开始运行/从挂起状态恢复。
function Room:resume() ---@param reason string?
function Room:resume(reason)
-- 如果还没运行的话就先创建自己的主协程 -- 如果还没运行的话就先创建自己的主协程
if not self.main_co then if not self.main_co then
self.main_co = coroutine.create(function() self.main_co = coroutine.create(function()
@ -107,7 +108,7 @@ function Room:resume()
if not self.game_finished then if not self.game_finished then
self.notify_count = 0 self.notify_count = 0
ret, err_msg, rest_time = coroutine.resume(main_co, err_msg) ret, err_msg, rest_time = coroutine.resume(main_co, reason)
-- handle error -- handle error
if ret == false then if ret == false then
@ -600,45 +601,6 @@ function Room:prepareGeneral(player, general, deputy, broadcast)
end end
end end
--- 房间信息摘要,返回房间的大致信息
--- 用于旁观和重连但也可用于debug
function Room:getSummary(player, observe)
local printed_cards = {}
for i = -2, -math.huge, -1 do
local c = Fk.printed_cards[i]
if not c then break end
table.insert(printed_cards, { c.name, c.suit, c.number })
end
local players = {}
for _, p in ipairs(self.players) do
players[tostring(p.id)] = p:getSummary(player, observe)
end
local cmarks = {}
for k, v in pairs(self.card_marks) do
cmarks[tostring(k)] = v
end
return {
you = player.id or player:getId(),
-- data for EnterRoom
d = {
-- #self.players, 留给客户端自己思考
self.timeout,
self.settings,
},
pc = printed_cards,
cm = cmarks,
b = self.banners,
circle = table.map(self.players, Util.IdMapper),
p = players,
rnd = self:getTag("RoundCount") or 0,
dp = #self.draw_pile,
}
end
function Room:toJsonObject(player) function Room:toJsonObject(player)
local o = AbstractRoom.toJsonObject(self) local o = AbstractRoom.toJsonObject(self)
o.round_count = self:getTag("RoundCount") or 0 o.round_count = self:getTag("RoundCount") or 0
@ -1169,7 +1131,7 @@ end
---@param no_indicate? boolean @ 是否不显示指示线 ---@param no_indicate? boolean @ 是否不显示指示线
---@return integer[] @ 选择的牌的id列表可能是空的 ---@return integer[] @ 选择的牌的id列表可能是空的
function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt, expand_pile, no_indicate) function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt, expand_pile, no_indicate)
if minNum < 1 then if maxNum < 1 then
return {} return {}
end end
cancelable = (cancelable == nil) and true or cancelable cancelable = (cancelable == nil) and true or cancelable
@ -1312,7 +1274,7 @@ end
---@param expand_pile? string @ 可选私人牌堆名称,如要分配你武将牌上的牌请填写 ---@param expand_pile? string @ 可选私人牌堆名称,如要分配你武将牌上的牌请填写
---@param skipMove? boolean @ 是否跳过移动。默认不跳过 ---@param skipMove? boolean @ 是否跳过移动。默认不跳过
---@param single_max? integer|table @ 限制每人能获得的最大牌数。输入整数或(以角色id为键以整数为值)的表 ---@param single_max? integer|table @ 限制每人能获得的最大牌数。输入整数或(以角色id为键以整数为值)的表
---@return table<integer[]> @ 返回一个表键为角色id值为分配给其的牌id数组 ---@return table<integer, integer[]> @ 返回一个表键为角色id转字符串值为分配给其的牌id数组
function Room:askForYiji(player, cards, targets, skillName, minNum, maxNum, prompt, expand_pile, skipMove, single_max) function Room:askForYiji(player, cards, targets, skillName, minNum, maxNum, prompt, expand_pile, skipMove, single_max)
targets = targets or self.alive_players targets = targets or self.alive_players
cards = cards or player:getCardIds("he") cards = cards or player:getCardIds("he")
@ -1347,7 +1309,7 @@ function Room:askForYiji(player, cards, targets, skillName, minNum, maxNum, prom
residued_list = residueMap, residued_list = residueMap,
expand_pile = expand_pile expand_pile = expand_pile
} }
p(json.encode(residueMap)) -- p(json.encode(residueMap))
while maxNum > 0 and #_cards > 0 do while maxNum > 0 and #_cards > 0 do
data.max_num = maxNum data.max_num = maxNum
@ -1384,7 +1346,7 @@ function Room:askForYiji(player, cards, targets, skillName, minNum, maxNum, prom
end end
end end
if not skipMove then if not skipMove then
self:doYiji(self, list, player.id, skillName) self:doYiji(list, player.id, skillName)
end end
return list return list
@ -2019,18 +1981,20 @@ end
--- 询问玩家任意交换几堆牌堆。 --- 询问玩家任意交换几堆牌堆。
--- ---
---@param player ServerPlayer @ 要询问的玩家 ---@param player ServerPlayer @ 要询问的玩家
---@param piles table<string, integer[]> @ 卡牌id列表的列表也就是……几堆牌堆的集合 ---@param piles (integer[])[] @ 卡牌id列表的列表也就是……几堆牌堆的集合
---@param piles_name string[] @ 牌堆名,必须一一对应否则统一替换为“牌堆X ---@param piles_name string[] @ 牌堆名,不足部分替换为“牌堆1、牌堆2...
---@param customNotify? string @ 自定义读条操作提示 ---@param customNotify? string @ 自定义读条操作提示
---@return table<string, integer[]> ---@return (integer[])[]
function Room:askForExchange(player, piles, piles_name, customNotify) function Room:askForExchange(player, piles, piles_name, customNotify)
local command = "AskForExchange" local command = "AskForExchange"
piles_name = piles_name or Util.DummyTable piles_name = piles_name or Util.DummyTable
if #piles_name ~= #piles then local x = #piles - #piles_name
piles_name = {} if x > 0 then
for i, _ in ipairs(piles) do for i = 1, x, 1 do
table.insert(piles_name, Fk:translate("Pile") .. i) table.insert(piles_name, Fk:translate("Pile") .. i)
end end
elseif x < 0 then
piles_name = table.slice(piles_name, 1, #piles + 1)
end end
self:notifyMoveFocus(player, customNotify or command) self:notifyMoveFocus(player, customNotify or command)
local data = { local data = {
@ -2884,6 +2848,7 @@ function Room:abortPlayerArea(player, playerSlots)
from = player.id, from = player.id,
toArea = Card.DiscardPile, toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile, moveReason = fk.ReasonPutIntoDiscardPile,
skillName = "gamerule_aborted"
}) })
table.insertTable(player.sealedSlots, slotsToSeal) table.insertTable(player.sealedSlots, slotsToSeal)

View File

@ -46,12 +46,12 @@ function HandleRequest(req)
return true return true
end end
function ResumeRoom(roomId) function ResumeRoom(roomId, reason)
local room = requestRoom:getRoom(roomId) local room = requestRoom:getRoom(roomId)
if not room then return false end if not room then return false end
if not room:isReady() then return false end if not room:isReady() then return false end
RoomInstance = (room ~= requestRoom and room or nil) RoomInstance = (room ~= requestRoom and room or nil)
local over = room:resume() local over = room:resume(reason)
RoomInstance = nil RoomInstance = nil
if over then if over then

View File

@ -238,7 +238,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
local cancel_skip = true local cancel_skip = true
if phase ~= Player.NotActive and (skip) then if phase ~= Player.NotActive and (skip) then
cancel_skip = logic:trigger(fk.EventPhaseSkipping, self) cancel_skip = logic:trigger(fk.EventPhaseSkipping, self, phase)
end end
if (not skip) or (cancel_skip) then if (not skip) or (cancel_skip) then
room:sendLog{ room:sendLog{
@ -260,6 +260,7 @@ function ServerPlayer:gainAnExtraPhase(phase, delay)
from = self.id, from = self.id,
arg = Util.PhaseStrMapper(phase), arg = Util.PhaseStrMapper(phase),
} }
logic:trigger(fk.EventPhaseSkipped, self, phase)
end end
self.phase = current self.phase = current
@ -322,7 +323,7 @@ function ServerPlayer:play(phase_table)
local cancel_skip = true local cancel_skip = true
if phases[i] ~= Player.NotActive and (skip) then if phases[i] ~= Player.NotActive and (skip) then
cancel_skip = logic:trigger(fk.EventPhaseSkipping, self) cancel_skip = logic:trigger(fk.EventPhaseSkipping, self, self.phase)
end end
if (not skip) or (cancel_skip) then if (not skip) or (cancel_skip) then
@ -333,6 +334,7 @@ function ServerPlayer:play(phase_table)
from = self.id, from = self.id,
arg = Util.PhaseStrMapper(self.phase), arg = Util.PhaseStrMapper(self.phase),
} }
logic:trigger(fk.EventPhaseSkipped, self, self.phase)
end end
end end
end end

View File

@ -48,6 +48,7 @@ GameRule = fk.CreateTriggerSkill{
end end
cardNames = table.filter(cardNames, function (cardName) cardNames = table.filter(cardNames, function (cardName)
-- FIXME: 应该印一个“任何情况都适合”的牌,或者说根本不该有这个过滤
local cardCloned = Fk:cloneCard(cardName) local cardCloned = Fk:cloneCard(cardName)
return not (player:prohibitUse(cardCloned) or player:isProhibited(dyingPlayer, cardCloned)) return not (player:prohibitUse(cardCloned) or player:isProhibited(dyingPlayer, cardCloned))
end) end)

View File

@ -8,7 +8,8 @@ Fk:loadTranslationTable({
["log_heart"] = '<font color="#CC3131">♥</font>', ["log_heart"] = '<font color="#CC3131">♥</font>',
["log_club"] = '', ["log_club"] = '',
["log_diamond"] = '<font color="#CC3131">♦</font>', ["log_diamond"] = '<font color="#CC3131">♦</font>',
["log_nosuit"] = "No suit", ["log_nosuit"] = "X",
["log_unknown"] = "?",
-- ["spade"] = "Spade", -- ["spade"] = "Spade",
-- ["heart"] = "Heart", -- ["heart"] = "Heart",
-- ["club"] = "Club", -- ["club"] = "Club",

View File

@ -8,7 +8,9 @@ Fk:loadTranslationTable{
["log_heart"] = '<font color="#CC3131">♥</font>', ["log_heart"] = '<font color="#CC3131">♥</font>',
["log_club"] = '', ["log_club"] = '',
["log_diamond"] = '<font color="#CC3131">♦</font>', ["log_diamond"] = '<font color="#CC3131">♦</font>',
["log_nosuit"] = "无花色", ["log_nosuit"] = "X",
["log_unknown"] = "?",
["unknown"] = "未知",
["spade"] = "黑桃", ["spade"] = "黑桃",
["heart"] = "红桃", ["heart"] = "红桃",
["club"] = "梅花", ["club"] = "梅花",