Delaytrick (#34)

* skip phase (not tested)

* Keep alive

* fix peach

* update

* fix move card

* indulgence

* lightning

* complete skip phase
This commit is contained in:
notify 2022-12-20 18:40:17 +08:00 committed by GitHub
parent 0029949a40
commit dfa88df214
9 changed files with 197 additions and 12 deletions

View File

@ -290,6 +290,11 @@ Fk:loadTranslationTable{
["normal_damage"] = "无属性",
["fire_damage"] = "火属性",
["thunder_damage"] = "雷属性",
["phase_judge"] = "判定阶段",
["phase_draw"] = "摸牌阶段",
["phase_play"] = "出牌阶段",
["phase_discard"] = "弃牌阶段",
}
-- related to sendLog
@ -319,6 +324,9 @@ Fk:loadTranslationTable{
["$DrawCards"] = "%from 摸了 %arg 张牌 %card",
["$DiscardCards"] = "%from 弃置了 %arg 张牌 %card",
-- phase
["#PhaseSkipped"] = "%from 跳过了 %arg",
-- useCard
["#UseCard"] = "%from 使用了牌 %card",
["#UseCardToTargets"] = "%from 使用了牌 %card目标是 %to",
@ -327,6 +335,7 @@ Fk:loadTranslationTable{
["#ResponsePlayCard"] = "%from 打出了牌 %card",
-- judge
["#StartJudgeReason"] = "%from 开始了 %arg 的判定",
["#InitialJudge"] = "%from 的判定牌为 %card",
["#ChangedJudge"] = "%from 发动“%arg”把 %to 的判定牌改为 %card",
["#JudgeResult"] = "%from 的判定结果为 %card",

View File

@ -54,4 +54,9 @@ function ActiveSkill:onUse(room, cardUseEvent) end
---@param cardEffectEvent CardEffectEvent | SkillEffectEvent
function ActiveSkill:onEffect(room, cardEffectEvent) end
-- Delayed Trick Only
---@param room Room
---@param cardEffectEvent CardEffectEvent | SkillEffectEvent
function ActiveSkill:onNullified(room, cardEffectEvent) end
return ActiveSkill

View File

@ -90,6 +90,7 @@ end
---@field feasible fun(self: ActiveSkill, selected: integer[], selected_cards: integer[]): boolean
---@field on_use fun(self: ActiveSkill, room: Room, cardUseEvent: CardUseStruct): boolean
---@field on_effect fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean
---@field on_nullified fun(self: ActiveSkill, room: Room, cardEffectEvent: CardEffectEvent): boolean
---@param spec ActiveSkillSpec
---@return ActiveSkill
@ -102,6 +103,7 @@ function fk.CreateActiveSkill(spec)
if spec.feasible then skill.feasible = spec.feasible end
if spec.on_use then skill.onUse = spec.on_use end
if spec.on_effect then skill.onEffect = spec.on_effect end
if spec.on_nullified then skill.onNullified = spec.on_nullified end
return skill
end

View File

@ -812,7 +812,7 @@ function Room:useCard(cardUseEvent)
from = from,
to = cardUseEvent.tos or {},
})
if cardUseEvent.tos then
if cardUseEvent.tos and #cardUseEvent.tos > 0 then
local to = {}
for _, t in ipairs(cardUseEvent.tos) do
table.insert(to, t[1])
@ -927,7 +927,7 @@ function Room:useCard(cardUseEvent)
local target = TargetGroup:getRealTargets(cardUseEvent.tos)[1]
if not self:getPlayerById(target).dead then
local findSameCard = false
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Equip)) do
for _, cardId in ipairs(self:getPlayerById(target):getCardIds(Player.Judge)) do
if Fk:getCardById(cardId).trueName == Fk:getCardById(cardUseEvent.cardId) then
findSameCard = true
end
@ -1026,7 +1026,9 @@ end
function Room:doCardEffect(cardEffectEvent)
for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do
if cardEffectEvent.isCancellOut then
self.logic:trigger(fk.CardEffectCancelledOut, self:getPlayerById(cardEffectEvent.from), cardEffectEvent)
if cardEffectEvent.from then
self.logic:trigger(fk.CardEffectCancelledOut, self:getPlayerById(cardEffectEvent.from), cardEffectEvent)
end
break
end
@ -1038,7 +1040,7 @@ function Room:doCardEffect(cardEffectEvent)
break
end
if self.logic:trigger(event, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) then
if cardEffectEvent.from and self.logic:trigger(event, self:getPlayerById(cardEffectEvent.from), cardEffectEvent) then
return
end
@ -1252,7 +1254,7 @@ function Room:moveCardTo(card, to_place, target, reason, skill_name, special_nam
to = target.id
end
self.moveCards{
self:moveCards{
ids = ids,
from = self.owner_map[ids[1]],
to = to,
@ -1620,10 +1622,19 @@ function Room:judge(data)
local who = data.who
self.logic:trigger(fk.StartJudge, who, data)
data.card = Fk:getCardById(self:getNCards(1)[1])
if data.reason ~= "" then
self:sendLog{
type = "#StartJudgeReason",
from = who.id,
arg = data.reason,
}
end
self:sendLog{
type = "#InitialJudge",
from = who.id,
card = {data.card},
card = {data.card.id},
}
self:moveCardTo(data.card, Card.Processing, nil, fk.ReasonPrey)
@ -1632,7 +1643,7 @@ function Room:judge(data)
self:sendLog{
type = "#JudgeResult",
from = who.id,
card = {data.card},
card = {data.card.id},
}
self.logic:trigger(fk.FinishJudge, who, data)

View File

@ -8,6 +8,7 @@
---@field reply_ready boolean
---@field reply_cancel boolean
---@field phases Phase[]
---@field skipped_phases Phase[]
---@field phase_state table[]
---@field phase_index integer
local ServerPlayer = Player:subclass("ServerPlayer")
@ -26,6 +27,7 @@ function ServerPlayer:initialize(_self)
self.reply_ready = false
self.reply_cancel = false
self.phases = {}
self.skipped_phases = {}
end
---@param command string
@ -136,6 +138,13 @@ function ServerPlayer:changePhase(from_phase, to_phase)
return false
end
local phase_name_table = {
[Player.Judge] = "phase_judge",
[Player.Draw] = "phase_draw",
[Player.Play] = "phase_play",
[Player.Discard] = "phase_discard",
}
---@param phase_table Phase[]
function ServerPlayer:play(phase_table)
phase_table = phase_table or {}
@ -161,7 +170,7 @@ function ServerPlayer:play(phase_table)
for i = 1, #phases do
phase_state[i] = {
phase = phases[i],
skipped = false
skipped = self.skipped_phases[phases[i]] or false
}
end
@ -180,7 +189,10 @@ function ServerPlayer:play(phase_table)
local logic = self.room.logic
self.phase = Player.PhaseNone
local skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
local skip = phase_state[i].skipped
if not skip then
skip = logic:trigger(fk.EventPhaseChanging, self, phase_change)
end
phases[i] = phase_change.to
phase_state[i].phase = phases[i]
@ -188,7 +200,7 @@ function ServerPlayer:play(phase_table)
room:notifyProperty(self, self, "phase")
local cancel_skip = true
if phases[i] ~= Player.NotActive and (phase_state[i].skipped or skip) then
if phases[i] ~= Player.NotActive and (skip) then
cancel_skip = logic:trigger(fk.EventPhaseSkipping, self)
end
@ -201,7 +213,33 @@ function ServerPlayer:play(phase_table)
if self.phase ~= Player.NotActive then
logic:trigger(fk.EventPhaseEnd, self)
else break end
else
self.skipped_phases = {}
end
else
room:sendLog{
type = "#PhaseSkipped",
from = self.id,
arg = phase_name_table[self.phase],
}
end
end
end
---@param phase Phase
function ServerPlayer:skip(phase)
if not table.contains({
Player.Judge,
Player.Draw,
Player.Play,
Player.Discard
}, phase) then
return
end
self.skipped_phases[phase] = true
for _, t in ipairs(self.phase_state) do
if t.phase == phase then
t.skipped = true
end
end
end

View File

@ -15,6 +15,8 @@
---@alias CardEffectEvent { from: integer, tos: TargetGroup, cardId: integer, toCardId: integer|null, responseToEvent: CardUseStruct|null, nullifiedTargets: interger[]|null, extraUse: boolean|null, disresponsiveList: integer[]|null, unoffsetableList: integer[]|null, addtionalDamage: integer|null, customFrom: integer|null, cardIdsResponded: integer[]|null }
---@alias SkillEffectEvent { from: integer, tos: integer[], cards: integer[] }
---@alias JudgeStruct { who: ServerPlayer, card: Card, reason: string }
---@alias CardMoveReason integer
fk.ReasonJustMove = 1

View File

@ -118,7 +118,22 @@ GameRule = fk.CreateTriggerSkill{
end,
[Player.Judge] = function()
local cards = player:getCardIds(Player.Judge)
for i = #cards, 1, -1 do
local card = Fk:getCardById(cards[i])
room:moveCardTo(card, Card.Processing, nil, fk.ReasonPut, self.name)
---@type CardEffectEvent
local effect_data = {
cardId = cards[i],
to = player.id,
tos = { {player.id} },
}
room:doCardEffect(effect_data)
if effect_data.isCancellOut and card.skill then
card.skill:onNullified(room, effect_data)
end
end
end,
[Player.Draw] = function()
room:drawCards(player, 2, self.name)

View File

@ -125,6 +125,11 @@ local peachSkill = fk.CreateActiveSkill{
can_use = function(self, player)
return player:isWounded()
end,
on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
use.tos = { { use.from } }
end
end,
on_effect = function(self, room, effect)
local to = effect.to
local from = effect.from
@ -367,10 +372,64 @@ extension:addCards({
amazingGrace:clone(Card.Heart, 4),
})
local lightningSkill = fk.CreateActiveSkill{
name = "lightning_skill",
can_use = function(self, player)
local judge = player:getCardIds(Player.Judge)
for _, id in ipairs(judge) do
local cd = Fk:getCardById(id)
if cd.name == "lightning" then
return false
end
end
return true
end,
on_use = function(self, room, use)
if not use.tos or #TargetGroup:getRealTargets(use.tos) == 0 then
use.tos = { { use.from } }
end
end,
on_effect = function(self, room, effect)
local to = room:getPlayerById(effect.to)
local judge = {
who = to,
reason = "lightning",
}
room:judge(judge)
local result = judge.card
if result.suit == Card.Spade and result.number >= 2 and result.number <= 9 then
room:damage{
to = to.id,
damage = 3,
damageType = fk.ThunderDamage,
skillName = self.name,
}
room:moveCards{
ids = { effect.cardId },
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile
}
else
self:onNullified(room, effect)
end
end,
on_nullified = function(self, room, effect)
local to = room:getPlayerById(effect.to)
local nextp = to:getNextAlive()
room:moveCards{
ids = { effect.cardId },
to = nextp.id,
toArea = Card.PlayerJudge,
moveReason = fk.ReasonPut
}
end,
}
local lightning = fk.CreateDelayedTrickCard{
name = "lightning",
suit = Card.Spade,
number = 1,
skill = lightningSkill,
}
Fk:loadTranslationTable{
["lightning"] = "闪电",
@ -381,10 +440,53 @@ extension:addCards({
lightning:clone(Card.Heart, 12),
})
local indulgenceSkill = fk.CreateActiveSkill{
name = "indulgence_skill",
target_filter = function(self, to_select, selected)
if #selected == 0 then
local player = Fk:currentRoom():getPlayerById(to_select)
if Self ~= player then
local judge = player:getCardIds(Player.Judge)
for _, id in ipairs(judge) do
local cd = Fk:getCardById(id)
if cd.name == "indulgence" then
return false
end
end
return true
end
end
return false
end,
feasible = function(self, selected)
return #selected == 1
end,
on_effect = function(self, room, effect)
local to = room:getPlayerById(effect.to)
local judge = {
who = to,
reason = "indulgence",
}
room:judge(judge)
local result = judge.card
if result.suit ~= Card.Heart then
to:skip(Player.Play)
end
self:onNullified(room, effect)
end,
on_nullified = function(self, room, effect)
room:moveCards{
ids = { effect.cardId },
toArea = Card.DiscardPile,
moveReason = fk.ReasonPutIntoDiscardPile
}
end,
}
local indulgence = fk.CreateDelayedTrickCard{
name = "indulgence",
suit = Card.Spade,
number = 6,
skill = indulgenceSkill,
}
Fk:loadTranslationTable{
["indulgence"] = "乐不思蜀",

View File

@ -24,6 +24,7 @@ void ClientSocket::init()
this, &ClientSocket::getMessage);
connect(socket, &QTcpSocket::errorOccurred,
this, &ClientSocket::raiseError);
socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
}
void ClientSocket::connectToHost(const QString &address, ushort port)