忘了同步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)
Self = ClientPlayer:new(fk.Self)
-- 垃圾bug 怎么把这玩意忘了
-- FIXME: 需要改Qml
local ob = ClientInstance.observing
ClientInstance = Client:new() -- clear old client data
ClientInstance.observing = ob

View File

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

View File

@ -360,9 +360,9 @@ FreeKill使用的是libgit2的C API与此同时使用Git完成拓展包的下
["Resume"] = "继续",
["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
elseif color ~= nil then
self.color = color
elseif suit == Card.Unknown then
self.color = Card.Unknown
else
self.color = Card.NoColor
end
@ -242,7 +244,7 @@ end
---@return string @ 描述花色的字符串
function Card:getSuitString(symbol)
local suit = self.suit
local ret
local ret = "unknown"
if suit == Card.Spade then
ret = "spade"
elseif suit == Card.Heart then
@ -251,7 +253,7 @@ function Card:getSuitString(symbol)
ret = "club"
elseif suit == Card.Diamond then
ret = "diamond"
else
elseif suit == Card.NoSuit then
ret = "nosuit"
end
return symbol and "log_" .. ret or ret
@ -265,9 +267,11 @@ function Card:getColorString()
return "black"
elseif color == Card.Red then
return "red"
end
elseif color == Card.NoColor then
return "nocolor"
end
return "unknown"
end
--- 获取卡牌类型并返回类型描述(例如基本牌/锦囊牌/装备牌)。
function Card:getTypeString()
@ -426,8 +430,11 @@ end
--- 比较两张卡牌的花色是否相同
---@param anotherCard Card @ 另一张卡牌
---@param diff? boolean @ 比较二者不同
---@return boolean 返回比较结果
---@return boolean @ 返回比较结果
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
return false
end
@ -444,6 +451,9 @@ end
---@param diff? boolean @ 比较二者不同
---@return boolean @ 返回比较结果
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
return false
end

View File

@ -33,6 +33,7 @@
---@field public id? integer[]
-- v0.2.6改动: cardType会被解析为trueName数组和name数组而不是自己单独成立
-- core改动 name数组为空时将根据trueName数组生成对应的name数组
local numbertable = {
["A"] = 1,
@ -48,6 +49,8 @@ local placetable = {
local card_type_table = {}
local card_truename_table = {}
local function fillCardTypeTable()
local tmp = {}
for _, cd in ipairs(Fk.cards) do
@ -66,6 +69,20 @@ local function fillCardTypeTable()
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 match = matcher[key]
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.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
if table.empty(card_type_table) then
fillCardTypeTable()
@ -325,6 +344,29 @@ local function parseMatcher(str)
table.insert(ret.trueName.neg, temp)
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
ret.id = parseRawNumTable(t[7])
end

View File

@ -1,5 +1,21 @@
-- 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用来描述一个游戏模式。
---
--- 可以参考欢乐斗地主。
@ -12,6 +28,7 @@
---@field public logic? fun(): GameLogic @ 逻辑通过function完成通常用来初始化、分配身份及座次
---@field public whitelist? string[] @ 白名单
---@field public blacklist? string[] @ 黑名单
---@field public config_template? GameModeConfigEntry[] 游戏模式的配置页面,如此一个数组
local GameMode = class("GameMode")
--- 构造函数,不可随意调用。
@ -83,4 +100,14 @@ function GameMode:getAdjustedProperty (player)
return list
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

View File

@ -1254,7 +1254,7 @@ function Player:roleVisible(target)
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
end

View File

@ -100,10 +100,17 @@ function ReqPlayCard:doEndButton()
end
function ReqPlayCard:selectCard(cid, data)
ReqUseCard.selectCard(self, cid, data)
if self.skill_name and not self.selected_card then return end
if self.skill_name and not self.selected_card then
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())
local sp_skills = {}
if self.selected_card.special_skills then
@ -114,11 +121,17 @@ function ReqPlayCard:selectCard(cid, data)
end
self.scene:update("SpecialSkills", "1", { skills = sp_skills })
else
self.selected_card = nil
self:setPrompt(self.original_prompt)
self.scene:update("SpecialSkills", "1", { skills = {} })
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)
if elemType == "Button" and id == "End" then
self:doEndButton()

View File

@ -4,6 +4,12 @@ local ReqResponseCard = require 'core.request_type.response_card'
---@class ReqUseCard: ReqResponseCard
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)
if self.skill_name then return ReqActiveSkill.cardValidity(self, cid) end
local card = cid
@ -31,6 +37,9 @@ end
function ReqUseCard:feasible()
local skill = Fk.skills[self.skill_name]
local card = self.selected_card
if skill then
card = skill:viewAs(self.pendings)
end
local ret = false
if card and self:cardFeasible(card) then
ret = card.skill:feasible(self.selected_targets,

View File

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

View File

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

View File

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

View File

@ -491,13 +491,14 @@ end
-- 作用是启动新事件 都是结构差不多的函数
---@param event GameEvent
---@return boolean, GameEvent?
function GameLogic:resumeEvent(event, ...)
function GameLogic:resumeEvent(event)
local ret, evt
local co = event._co
local resume_reason = "unknown"
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
-- handle error, then break
@ -510,7 +511,8 @@ function GameLogic:resumeEvent(event, ...)
if yield_result == "__handleRequest" then
-- 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
and yield_result:isInstanceOf(GameEvent) then

View File

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

View File

@ -89,7 +89,8 @@ function Room:initialize(_room)
end
-- 供调度器使用的函数。能让房间开始运行/从挂起状态恢复。
function Room:resume()
---@param reason string?
function Room:resume(reason)
-- 如果还没运行的话就先创建自己的主协程
if not self.main_co then
self.main_co = coroutine.create(function()
@ -107,7 +108,7 @@ function Room:resume()
if not self.game_finished then
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
if ret == false then
@ -600,45 +601,6 @@ function Room:prepareGeneral(player, general, deputy, broadcast)
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)
local o = AbstractRoom.toJsonObject(self)
o.round_count = self:getTag("RoundCount") or 0
@ -1169,7 +1131,7 @@ end
---@param no_indicate? boolean @ 是否不显示指示线
---@return integer[] @ 选择的牌的id列表可能是空的
function Room:askForCard(player, minNum, maxNum, includeEquip, skillName, cancelable, pattern, prompt, expand_pile, no_indicate)
if minNum < 1 then
if maxNum < 1 then
return {}
end
cancelable = (cancelable == nil) and true or cancelable
@ -1312,7 +1274,7 @@ end
---@param expand_pile? string @ 可选私人牌堆名称,如要分配你武将牌上的牌请填写
---@param skipMove? boolean @ 是否跳过移动。默认不跳过
---@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)
targets = targets or self.alive_players
cards = cards or player:getCardIds("he")
@ -1347,7 +1309,7 @@ function Room:askForYiji(player, cards, targets, skillName, minNum, maxNum, prom
residued_list = residueMap,
expand_pile = expand_pile
}
p(json.encode(residueMap))
-- p(json.encode(residueMap))
while maxNum > 0 and #_cards > 0 do
data.max_num = maxNum
@ -1384,7 +1346,7 @@ function Room:askForYiji(player, cards, targets, skillName, minNum, maxNum, prom
end
end
if not skipMove then
self:doYiji(self, list, player.id, skillName)
self:doYiji(list, player.id, skillName)
end
return list
@ -2019,18 +1981,20 @@ end
--- 询问玩家任意交换几堆牌堆。
---
---@param player ServerPlayer @ 要询问的玩家
---@param piles table<string, integer[]> @ 卡牌id列表的列表也就是……几堆牌堆的集合
---@param piles_name string[] @ 牌堆名,必须一一对应否则统一替换为“牌堆X
---@param piles (integer[])[] @ 卡牌id列表的列表也就是……几堆牌堆的集合
---@param piles_name string[] @ 牌堆名,不足部分替换为“牌堆1、牌堆2...
---@param customNotify? string @ 自定义读条操作提示
---@return table<string, integer[]>
---@return (integer[])[]
function Room:askForExchange(player, piles, piles_name, customNotify)
local command = "AskForExchange"
piles_name = piles_name or Util.DummyTable
if #piles_name ~= #piles then
piles_name = {}
for i, _ in ipairs(piles) do
local x = #piles - #piles_name
if x > 0 then
for i = 1, x, 1 do
table.insert(piles_name, Fk:translate("Pile") .. i)
end
elseif x < 0 then
piles_name = table.slice(piles_name, 1, #piles + 1)
end
self:notifyMoveFocus(player, customNotify or command)
local data = {
@ -2884,6 +2848,7 @@ function Room:abortPlayerArea(player, playerSlots)
from = player.id,
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile,
skillName = "gamerule_aborted"
})
table.insertTable(player.sealedSlots, slotsToSeal)

View File

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

View File

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

View File

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

View File

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

View File

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