mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-16 03:32:34 +08:00
Rewrite event (#60)
* doc for gameevent * init game event * correct other yield call * hp event * dying event * move card event * use/response event * judge event * remove space
This commit is contained in:
parent
b0cc1afa02
commit
3f077a6d69
173
doc/dev/gameevent.md
Normal file
173
doc/dev/gameevent.md
Normal file
|
@ -0,0 +1,173 @@
|
|||
# Fk的游戏事件
|
||||
|
||||
___
|
||||
|
||||
在Fk中,“事件”指的大约是像`room:judge`, `room:damage`之类的操作。这些操作一般和某个游戏术语挂钩(“判定”、“伤害”),然后其中包含着一系列操作,比如伤害事件包含了与伤害事件有关的各种触发时机、以及扣减等实际的动作等。
|
||||
|
||||
之所以要把事件单独挑出来聊聊,是因为有以下几点需求:
|
||||
|
||||
* 事件要能够被半路中止。
|
||||
* 对于被中止的事件,要能判断它能不能中止事件栈中的更低层事件。
|
||||
* 对于被中止的事件,需要做“垃圾回收”(例如将处于处理区的相关卡牌移动到弃牌堆等等)
|
||||
|
||||
___
|
||||
|
||||
## 对于如何实现的构想
|
||||
|
||||
(施工完成后再来修改这一节)
|
||||
|
||||
首先是如何实现事件类。初步构想一下,应该有以下的属性/方法:
|
||||
|
||||
* 事件名(也可以是枚举值)
|
||||
* 事件数据(一个表,内含所有要用到的数据)
|
||||
* 事件id(在一局游戏中唯一标记某个事件)
|
||||
* 事件的函数体,也就是具体要做的事情
|
||||
* 事件被中止或者正常结束时,用来清理现场的函数
|
||||
|
||||
问题来了,既然事件本质上还是个函数体,那要怎么才能中止呢?
|
||||
|
||||
比较容易想到的是利用协程。协程差不多就是一种特殊的函数调用方式,但是在协程体里面可以调用yield函数立刻返回到resume调用者,然后还可能可以继续调用(如果“中止事件”这一事件也被中止的话,之类的奇怪情况),总之可以纳入实现“事件”这种复杂概念的考虑。
|
||||
|
||||
事件必然会发生嵌套。所以对此更要慎重考虑。
|
||||
|
||||
现在只是设想!假设以`room:judge`入手:
|
||||
|
||||
```lua
|
||||
function Room:judge(judgeStruct)
|
||||
local judgeEvent = GameEvent:new(GameEvent.Judge, judgeStruct)
|
||||
judgeEvent:exec()
|
||||
end
|
||||
```
|
||||
|
||||
总之可能是这样的吧。exec()就是实际执行事件,可能如下:
|
||||
|
||||
```lua
|
||||
function GameEvent:exec()
|
||||
local event_f = self.event_f
|
||||
local co = coroutine.create(event_f)
|
||||
while true do
|
||||
local yield_result = coroutine.resume(co)
|
||||
if yield_result == "__handleRequest" then
|
||||
-- 正常yield掉,如此层层yield最后到Room的主循环,然后进requestLoop协程处理事务
|
||||
-- 最后返回这里继续resume
|
||||
coroutine.yield(yield_result)
|
||||
else
|
||||
-- 事件被中止,考虑做点什么
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
现在考虑嵌套的情况,event1和event2嵌套,也就是event1的体里面又创建了event2并exec,此时的协程调用关系如下:
|
||||
|
||||
RoomLogic -> event1 -> event2 | RequestLoop
|
||||
|
||||
此时event2中调用了某个耗时的函数,比如room:delay或者各种request,这时候就触发了yield。然后在上面的函数中就获取了yield返回值,然后判断是正常yield后,就进一步yield,此时协程到了event1中。event1继续yield,于是到了Room主协程,主协程其实也调用了exec,所以被yield切回到真正的主线程,然后执行requestloop的协程。乍一看似乎没问题,除了这个跳跃链有够长的。
|
||||
|
||||
这种开销看起来应该不大吧,而且在AI Random Play这种非常需要性能的场所也根本不会发生这种类型的yield(画饼),总之先不考虑这块。
|
||||
|
||||
如果事件被中止(按目前的实现来说就是在特定的时机return true了),那么事件的本体也应该调用yield(这就如同在目前--v0.0.1实现中,那些函数常见的遇到true就直接返回了一样),这样相当于从函数返回了。这种yield就会进入那个else分支,进行这个事件类的有关清扫工作。
|
||||
|
||||
___
|
||||
|
||||
## 构想2 类似无懈可击的事件
|
||||
|
||||
考虑一类特殊的事件:“取消其他事件的事件”。它和普通事件一样,能被中止之类的,而它的作用在于取消掉其他事件。
|
||||
|
||||
接着上面的else分支继续考虑:
|
||||
|
||||
```lua
|
||||
else
|
||||
local cancelEvent = GameEvent:new(GameEvent.CancelEvent, self)
|
||||
local ret = cancelEvent:exec()
|
||||
if ret then break end
|
||||
```
|
||||
|
||||
似乎也没什么非常特殊的内容啊。
|
||||
|
||||
exec()的返回值哪里来?这好像真的是个问题呢。可以考虑返回布尔值表示事件是否中止了?或者更详细的,返回一个状态码,毕竟本质上身为协程自然能有协程该有的种种状态。这里只是初步考虑而已,就考虑前者好了。
|
||||
|
||||
___
|
||||
|
||||
## 落实 - 手杀皇甫嵩
|
||||
|
||||
手杀皇甫嵩是重构整个事件体系的罪魁祸首(雾)。其技能为:若blah blah,你可以终止本次判定,然后blah blah。
|
||||
|
||||
而终止本次判定是目前的体系做不到的。
|
||||
|
||||
考虑如下技能片段:
|
||||
|
||||
```lua
|
||||
on_effect = function(xxx)
|
||||
local judge = {}
|
||||
room:judge(judge)
|
||||
if judge.card.number > 5 then xxx end
|
||||
end
|
||||
```
|
||||
|
||||
皇甫嵩能终止判定,就算他在fk.Judge时机返回true算了。前文已经考虑过judge了,他创建了新event并执行之。而今judge事件遭到打断,room:judge可能可以返回一个返回值来告诉玩家已经被中断之类的。但是Luaer,特别是像我这样的Luaer,懒得考虑事件的合法性之类的,而既然judge已经被终止,那么judge.card就不应该被使用才行。
|
||||
|
||||
为此可以为judge表添加__index元方法,当对key="card"进行取值时,就直接yield掉,除此之外的就rawget。
|
||||
|
||||
还有更复杂的情况呢。当皇甫嵩判乐的时候,如果是黑桃,那么他发动技能终止了判定,然后像个无事人一样出牌呢。乐都还贴在他头上。考察一下Fk里面的乐是怎么写的,哦,原来是on_effect的末尾才移走啊,那没事了。也就是说,如果对judge.card的非法访问使得事件被中止了,那么照这个逻辑,乐是下不来的,符合手刹了这下。
|
||||
|
||||
___
|
||||
|
||||
## 考虑 事件为何中止?
|
||||
|
||||
事件是协程,因此协程中止的方法就是事件中止的方法。有这两种:
|
||||
|
||||
* yield, 落实到Fk就是触发技的各种返回true
|
||||
* error, 这不就是我经常发生的事情吗
|
||||
|
||||
前面也提到过发生yield的时候会有cancelEvent产生,方便玩家反悔中止这次事件,但因为error而中断事件是无法恢复的。试图resume一个报错的协程的话,他会立刻因为error而自动yield。这个可以在exec函数里面多加考虑,如果resume函数返回了true和特定值,那就是正常情况。否则就是报错,输出错误信息并返回。
|
||||
|
||||
那前文那个judge.card怎么办呢?这种严格来说得算在error的范畴,因为不是人为中止本次effect的。但是error的话势必要输出到屏幕,而我个人聚德直接拿judge.card算是合法行为。这种情况或许可以定一个约定好的特殊错误信息,在处理错误的时候如果是这个错误的话就不输出。
|
||||
|
||||
___
|
||||
|
||||
## 考虑 有哪些事件
|
||||
|
||||
在最开始的时候,“依赖关系”这个现象的存在使得触发技多了个on_cost(消耗),但是现在on_cost已经成为界定skill是否发动了的标准。而在skill的effect环节,依然存在着一环扣一环的关系,比如前面举的room:judge例子。
|
||||
|
||||
假设Room.lua里面返回void的都算事件好了,或者再细一点,在函数体里面用了logic:trigger的void函数是事件?算了,这个也不好定义,反正公道自在人心(雾)。但毫无疑问,最为复杂的两个事件就是——使用牌和移动牌。
|
||||
|
||||
真是令人头大啊,这俩可不是好惹的。不过看到它们可能从room.lua分家出去,我其实还是有一丝欣慰(?)
|
||||
|
||||
总之,事件不止room.lua里面那些。就拿前面的考虑来说,由于要中断on_effect,所以on_effect肯定会算成一个事件,可能叫SkillEffect事件吧。
|
||||
再考虑万恶之源武将——老朱然,直接结束你的回合。(他只要回合内造成了伤害就能结束回合,但没说在谁的回合造成了伤害)所以进行回合也理应算是个事件。
|
||||
|
||||
___
|
||||
|
||||
## 考虑 老朱然
|
||||
|
||||
对于老朱然这种人而言,他想要杀掉的是回合事件,而能发动这个技能的时候,事件栈想必已经很深了,稍微模拟一下这个情景:老朱然杀界徐盛并打掉他一滴血,此时事件栈大概如下(还没正式设计各种事件,所以可能不妥):
|
||||
|
||||
* 伤害事件 - room:damage - 询问技能:是否发动胆守,点确定
|
||||
* 技能生效事件 - activeskill:onEffect - 【杀】的effect
|
||||
* 使用牌事件 - room:useCard - 出杀
|
||||
* 进行阶段事件 - ? - 在出牌阶段
|
||||
* 回合事件 - ? - 在回合
|
||||
|
||||
我们的限制条件:无法获得room:damage的返回值,或者说根本没想去获得,其他同理。
|
||||
|
||||
coroutine.yield的功能也只有挂起协程并让相应的resume调用返回而已,那么该怎么办呢?由于以上种种限制的存在(主要还是想把Luaer惯着),我们不能对杀的onEffect下手,其他函数都是核心函数,改改也无妨咯。
|
||||
|
||||
还是结合情景考虑吧。胆守点了确定,此时最直接的感受应该是return true。但是return true的意思是防止伤害(都已经是“造成伤害后”了,怎么防止哦,return true也不会有人管你的),所以这里要另辟蹊径。考虑直接yield:此时会处于DamageEvent的exec()中,也就是处于room:damage中,他在处理中止信息。正常的中止的话,会使用break跳出循环;那么如果我访问调用栈,直接让他一路yield到我们想要的那个事件,如同yield到requestLoop那样呢?
|
||||
|
||||
没错,访问事件栈确实是个解决办法的可能方案。(这时候用id指示事件的重要性就出来了,可以传一个id表示事件,不过话说回来传那个事件本身也没有任何关系就是了咯)如果yield函数返回了一个GameEvent类的实例,那么就在处理环节将其和self进行比较,如果不同,就继续yield,直到退到相应的事件中。
|
||||
|
||||
这种跨越很多级事件的东西怎么取消呢?差不多得了,懒得考虑了,天天防止这防止那,随便逮到个东西就想把他防止掉/取消掉,三国杀的游戏逻辑就是被你们这群人毁掉的
|
||||
|
||||
总之这不考虑如何防止这种直接结束回合了,毕竟这种不断yield的方式无法用事件进行描述。
|
||||
|
||||
___
|
||||
|
||||
## 考虑 内存泄漏的应对
|
||||
|
||||
首先声明,Lua没有内存泄漏。但是如果有些东西用户不想要,但是又不告诉lua的话,lua就会觉得用户想要,然后一直保存着它,这在某种意义上也相当于内存泄漏了。拿实例来说,如果事件被中止了,那么在很多情况下确实就不需要了,但Lua会认为协程是挂起的,用户可能想要恢复,于是一直保存着。
|
||||
|
||||
当然了,对于这情况,Lua提供了coroutine.close用来关掉一个协程。不过我们想要让这个事件彻底删掉,该怎么办呢?
|
||||
|
||||
方法很简单,将它出栈不就行了。照这么说的话,事件在exec开始的时候就入栈,然后等待exec结束就出栈,但对于老朱然这种人,他把函数直接yield掉了,因此有必要手动出栈。
|
59
lua/server/events/death.lua
Normal file
59
lua/server/events/death.lua
Normal file
|
@ -0,0 +1,59 @@
|
|||
GameEvent.functions[GameEvent.Dying] = function(self)
|
||||
local dyingStruct = table.unpack(self.data)
|
||||
local self = self.room
|
||||
local dyingPlayer = self:getPlayerById(dyingStruct.who)
|
||||
dyingPlayer.dying = true
|
||||
self:broadcastProperty(dyingPlayer, "dying")
|
||||
self:sendLog{
|
||||
type = "#EnterDying",
|
||||
from = dyingPlayer.id,
|
||||
}
|
||||
self.logic:trigger(fk.EnterDying, dyingPlayer, dyingStruct)
|
||||
|
||||
if dyingPlayer.hp < 1 then
|
||||
self.logic:trigger(fk.Dying, dyingPlayer, dyingStruct)
|
||||
self.logic:trigger(fk.AskForPeaches, dyingPlayer, dyingStruct)
|
||||
self.logic:trigger(fk.AskForPeachesDone, dyingPlayer, dyingStruct)
|
||||
end
|
||||
|
||||
if not dyingPlayer.dead then
|
||||
dyingPlayer.dying = false
|
||||
self:broadcastProperty(dyingPlayer, "dying")
|
||||
end
|
||||
self.logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct)
|
||||
end
|
||||
|
||||
GameEvent.functions[GameEvent.Death] = function(self)
|
||||
local deathStruct = table.unpack(self.data)
|
||||
local self = self.room
|
||||
local victim = self:getPlayerById(deathStruct.who)
|
||||
victim.dead = true
|
||||
table.removeOne(self.alive_players, victim)
|
||||
|
||||
local logic = self.logic
|
||||
logic:trigger(fk.BeforeGameOverJudge, victim, deathStruct)
|
||||
|
||||
local killer = deathStruct.damage and deathStruct.damage.from or nil
|
||||
if killer then
|
||||
self:sendLog{
|
||||
type = "#KillPlayer",
|
||||
to = {killer.id},
|
||||
from = victim.id,
|
||||
arg = victim.role,
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#KillPlayerWithNoKiller",
|
||||
from = victim.id,
|
||||
arg = victim.role,
|
||||
}
|
||||
end
|
||||
self:sendLogEvent("Death", {to = victim.id})
|
||||
|
||||
self:broadcastProperty(victim, "role")
|
||||
self:broadcastProperty(victim, "dead")
|
||||
|
||||
logic:trigger(fk.GameOverJudge, victim, deathStruct)
|
||||
logic:trigger(fk.Death, victim, deathStruct)
|
||||
logic:trigger(fk.BuryVictim, victim, deathStruct)
|
||||
end
|
210
lua/server/events/hp.lua
Normal file
210
lua/server/events/hp.lua
Normal file
|
@ -0,0 +1,210 @@
|
|||
GameEvent.functions[GameEvent.ChangeHp] = function(self)
|
||||
local player, num, reason, skillName, damageStruct = table.unpack(self.data)
|
||||
local self = self.room
|
||||
if num == 0 then
|
||||
return false
|
||||
end
|
||||
assert(reason == nil or table.contains({ "loseHp", "damage", "recover" }, reason))
|
||||
|
||||
---@type HpChangedData
|
||||
local data = {
|
||||
num = num,
|
||||
reason = reason,
|
||||
skillName = skillName,
|
||||
}
|
||||
|
||||
if self.logic:trigger(fk.BeforeHpChanged, player, data) then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
assert(not (data.reason == "recover" and data.num < 0))
|
||||
player.hp = math.min(player.hp + data.num, player.maxHp)
|
||||
self:broadcastProperty(player, "hp")
|
||||
|
||||
if reason == "damage" then
|
||||
local damage_nature_table = {
|
||||
[fk.NormalDamage] = "normal_damage",
|
||||
[fk.FireDamage] = "fire_damage",
|
||||
[fk.ThunderDamage] = "thunder_damage",
|
||||
}
|
||||
if damageStruct.from then
|
||||
self:sendLog{
|
||||
type = "#Damage",
|
||||
to = {damageStruct.from.id},
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
arg2 = damage_nature_table[damageStruct.damageType],
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#DamageWithNoFrom",
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
arg2 = damage_nature_table[damageStruct.damageType],
|
||||
}
|
||||
end
|
||||
self:sendLogEvent("Damage", {
|
||||
to = player.id,
|
||||
damageType = damage_nature_table[damageStruct.damageType],
|
||||
damageNum = damageStruct.damage,
|
||||
})
|
||||
elseif reason == "loseHp" then
|
||||
self:sendLog{
|
||||
type = "#LoseHP",
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
}
|
||||
self:sendLogEvent("LoseHP", {})
|
||||
elseif reason == "recover" then
|
||||
self:sendLog{
|
||||
type = "#HealHP",
|
||||
from = player.id,
|
||||
arg = num,
|
||||
}
|
||||
end
|
||||
|
||||
self:sendLog{
|
||||
type = "#ShowHPAndMaxHP",
|
||||
from = player.id,
|
||||
arg = player.hp,
|
||||
arg2 = player.maxHp,
|
||||
}
|
||||
|
||||
self.logic:trigger(fk.HpChanged, player, data)
|
||||
|
||||
if player.hp < 1 then
|
||||
if num < 0 then
|
||||
---@type DyingStruct
|
||||
local dyingStruct = {
|
||||
who = player.id,
|
||||
damage = damageStruct,
|
||||
}
|
||||
self:enterDying(dyingStruct)
|
||||
end
|
||||
elseif player.dying then
|
||||
player.dying = false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
GameEvent.functions[GameEvent.Damage] = function(self)
|
||||
local damageStruct = table.unpack(self.data)
|
||||
local self = self.room
|
||||
if damageStruct.damage < 1 then
|
||||
return false
|
||||
end
|
||||
damageStruct.damageType = damageStruct.damageType or fk.NormalDamage
|
||||
|
||||
if damageStruct.from and not damageStruct.from:isAlive() then
|
||||
damageStruct.from = nil
|
||||
end
|
||||
|
||||
assert(damageStruct.to:isInstanceOf(ServerPlayer))
|
||||
|
||||
local stages = {
|
||||
{fk.PreDamage, damageStruct.from},
|
||||
{fk.DamageCaused, damageStruct.from},
|
||||
{fk.DamageInflicted, damageStruct.to},
|
||||
}
|
||||
|
||||
for _, struct in ipairs(stages) do
|
||||
local event, player = table.unpack(struct)
|
||||
if self.logic:trigger(event, player, damageStruct) or damageStruct.damage < 1 then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
assert(damageStruct.to:isInstanceOf(ServerPlayer))
|
||||
end
|
||||
|
||||
if not damageStruct.to:isAlive() then
|
||||
return false
|
||||
end
|
||||
|
||||
if not self:changeHp(damageStruct.to, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
stages = {
|
||||
{fk.Damage, damageStruct.from},
|
||||
{fk.Damaged, damageStruct.to},
|
||||
{fk.DamageFinished, damageStruct.from},
|
||||
}
|
||||
|
||||
for _, struct in ipairs(stages) do
|
||||
local event, player = table.unpack(struct)
|
||||
self.logic:trigger(event, player, damageStruct)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
GameEvent.functions[GameEvent.LoseHp] = function(self)
|
||||
local player, num, skillName = table.unpack(self.data)
|
||||
local self = self.room
|
||||
if num == nil then
|
||||
num = 1
|
||||
elseif num < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
---@type HpLostData
|
||||
local data = {
|
||||
num = num,
|
||||
skillName = skillName,
|
||||
}
|
||||
if self.logic:trigger(fk.PreHpLost, player, data) or data.num < 1 then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
if not self:changeHp(player, -num, "loseHp", skillName) then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.HpLost, player, data)
|
||||
return true
|
||||
end
|
||||
|
||||
GameEvent.functions[GameEvent.Recover] = function(self)
|
||||
local recoverStruct = table.unpack(self.data)
|
||||
local self = self.room
|
||||
if recoverStruct.num < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
local who = recoverStruct.who
|
||||
if self.logic:trigger(fk.PreHpRecover, who, recoverStruct) or recoverStruct.num < 1 then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
if not self:changeHp(who, recoverStruct.num, "recover", recoverStruct.skillName) then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.HpRecover, who, recoverStruct)
|
||||
return true
|
||||
end
|
||||
|
||||
GameEvent.functions[GameEvent.ChangeMaxHp] = function(self)
|
||||
local player, num = table.unpack(self.data)
|
||||
local self = self.room
|
||||
if num == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
player.maxHp = math.max(player.maxHp + num, 0)
|
||||
self:broadcastProperty(player, "maxHp")
|
||||
local diff = player.hp - player.maxHp
|
||||
if diff > 0 then
|
||||
if not self:changeHp(player, -diff) then
|
||||
player.hp = player.hp - diff
|
||||
end
|
||||
end
|
||||
|
||||
if player.maxHp == 0 then
|
||||
self:killPlayer({ who = player.id })
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.MaxHpChanged, player, { num = num })
|
||||
return true
|
||||
end
|
31
lua/server/events/init.lua
Normal file
31
lua/server/events/init.lua
Normal file
|
@ -0,0 +1,31 @@
|
|||
-- Definitions of game events
|
||||
|
||||
GameEvent.ChangeHp = 1
|
||||
GameEvent.Damage = 2
|
||||
GameEvent.LoseHp = 3
|
||||
GameEvent.Recover = 4
|
||||
GameEvent.ChangeMaxHp = 5
|
||||
dofile "lua/server/events/hp.lua"
|
||||
|
||||
GameEvent.Dying = 6
|
||||
GameEvent.Death = 7
|
||||
dofile "lua/server/events/death.lua"
|
||||
|
||||
GameEvent.MoveCards = 8
|
||||
dofile "lua/server/events/movecard.lua"
|
||||
|
||||
GameEvent.UseCard = 9
|
||||
GameEvent.RespondCard = 10
|
||||
dofile "lua/server/events/usecard.lua"
|
||||
|
||||
GameEvent.SkillEffect = 11
|
||||
-- GameEvent.AddSkill = 12
|
||||
-- GameEvent.LoseSkill = 13
|
||||
dofile "lua/server/events/skill.lua"
|
||||
|
||||
GameEvent.Judge = 14
|
||||
dofile "lua/server/events/judge.lua"
|
||||
|
||||
-- TODO: fix this
|
||||
GameEvent.BreakEvent = 999
|
||||
|
62
lua/server/events/judge.lua
Normal file
62
lua/server/events/judge.lua
Normal file
|
@ -0,0 +1,62 @@
|
|||
GameEvent.functions[GameEvent.Judge] = function(self)
|
||||
local data = table.unpack(self.data)
|
||||
local self = self.room
|
||||
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.id},
|
||||
}
|
||||
self:moveCardTo(data.card, Card.Processing, nil, fk.ReasonPrey)
|
||||
|
||||
self.logic:trigger(fk.AskForRetrial, who, data)
|
||||
self.logic:trigger(fk.FinishRetrial, who, data)
|
||||
Fk:filterCard(data.card.id, who, data)
|
||||
self:sendLog{
|
||||
type = "#JudgeResult",
|
||||
from = who.id,
|
||||
card = {data.card.id},
|
||||
}
|
||||
|
||||
if data.pattern then
|
||||
self:delay(400);
|
||||
self:setCardEmotion(data.card.id, data.card:matchPattern(data.pattern) and "judgegood" or "judgebad")
|
||||
self:delay(900);
|
||||
end
|
||||
|
||||
if self.logic:trigger(fk.FinishJudge, who, data) then
|
||||
self.logic:breakEvent()
|
||||
end
|
||||
if self:getCardArea(data.card.id) == Card.Processing then
|
||||
self:moveCardTo(data.card, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile)
|
||||
end
|
||||
end
|
||||
|
||||
GameEvent.cleaners[GameEvent.Judge] = function(self)
|
||||
local data = table.unpack(self.data)
|
||||
local self = self.room
|
||||
if self:getCardArea(data.card.id) == Card.Processing then
|
||||
self:moveCardTo(data.card, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile)
|
||||
end
|
||||
|
||||
-- prohibit access to judge.card
|
||||
setmetatable(data, {
|
||||
__index = function(self, key)
|
||||
if key == "card" then
|
||||
error("__manuallyBreak")
|
||||
end
|
||||
return rawget(self, key)
|
||||
end
|
||||
})
|
||||
end
|
117
lua/server/events/movecard.lua
Normal file
117
lua/server/events/movecard.lua
Normal file
|
@ -0,0 +1,117 @@
|
|||
GameEvent.functions[GameEvent.MoveCards] = function(self)
|
||||
local args = self.data
|
||||
local self = self.room
|
||||
---@type CardsMoveStruct[]
|
||||
local cardsMoveStructs = {}
|
||||
local infoCheck = function(info)
|
||||
assert(table.contains({ Card.PlayerHand, Card.PlayerEquip, Card.PlayerJudge, Card.PlayerSpecial, Card.Processing, Card.DrawPile, Card.DiscardPile, Card.Void }, info.toArea))
|
||||
assert(info.toArea ~= Card.PlayerSpecial or type(info.specialName) == "string")
|
||||
assert(type(info.moveReason) == "number")
|
||||
end
|
||||
|
||||
for _, cardsMoveInfo in ipairs(args) do
|
||||
if #cardsMoveInfo.ids > 0 then
|
||||
infoCheck(cardsMoveInfo)
|
||||
|
||||
---@type MoveInfo[]
|
||||
local infos = {}
|
||||
for _, id in ipairs(cardsMoveInfo.ids) do
|
||||
table.insert(infos, {
|
||||
cardId = id,
|
||||
fromArea = self:getCardArea(id),
|
||||
fromSpecialName = cardsMoveInfo.from and self:getPlayerById(cardsMoveInfo.from):getPileNameOfId(id),
|
||||
})
|
||||
end
|
||||
|
||||
---@type CardsMoveStruct
|
||||
local cardsMoveStruct = {
|
||||
moveInfo = infos,
|
||||
from = cardsMoveInfo.from,
|
||||
to = cardsMoveInfo.to,
|
||||
toArea = cardsMoveInfo.toArea,
|
||||
moveReason = cardsMoveInfo.moveReason,
|
||||
proposer = cardsMoveInfo.proposer,
|
||||
skillName = cardsMoveInfo.skillName,
|
||||
moveVisible = cardsMoveInfo.moveVisible,
|
||||
specialName = cardsMoveInfo.specialName,
|
||||
specialVisible = cardsMoveInfo.specialVisible,
|
||||
}
|
||||
|
||||
table.insert(cardsMoveStructs, cardsMoveStruct)
|
||||
end
|
||||
end
|
||||
|
||||
if #cardsMoveStructs < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
if self.logic:trigger(fk.BeforeCardsMove, nil, cardsMoveStructs) then
|
||||
self.logic:breakEvent(false)
|
||||
end
|
||||
|
||||
self:notifyMoveCards(nil, cardsMoveStructs)
|
||||
|
||||
for _, data in ipairs(cardsMoveStructs) do
|
||||
if #data.moveInfo > 0 then
|
||||
infoCheck(data)
|
||||
|
||||
---@param info MoveInfo
|
||||
for _, info in ipairs(data.moveInfo) do
|
||||
local realFromArea = self:getCardArea(info.cardId)
|
||||
local playerAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special }
|
||||
|
||||
if table.contains(playerAreas, realFromArea) and data.from then
|
||||
self:getPlayerById(data.from):removeCards(realFromArea, { info.cardId }, info.fromSpecialName)
|
||||
elseif realFromArea ~= Card.Unknown then
|
||||
local fromAreaIds = {}
|
||||
if realFromArea == Card.Processing then
|
||||
fromAreaIds = self.processing_area
|
||||
elseif realFromArea == Card.DrawPile then
|
||||
fromAreaIds = self.draw_pile
|
||||
elseif realFromArea == Card.DiscardPile then
|
||||
fromAreaIds = self.discard_pile
|
||||
elseif realFromArea == Card.Void then
|
||||
fromAreaIds = self.void
|
||||
end
|
||||
|
||||
table.removeOne(fromAreaIds, info.cardId)
|
||||
end
|
||||
|
||||
if table.contains(playerAreas, data.toArea) and data.to then
|
||||
self:getPlayerById(data.to):addCards(data.toArea, { info.cardId }, data.specialName)
|
||||
else
|
||||
local toAreaIds = {}
|
||||
if data.toArea == Card.Processing then
|
||||
toAreaIds = self.processing_area
|
||||
elseif data.toArea == Card.DrawPile then
|
||||
toAreaIds = self.draw_pile
|
||||
elseif data.toArea == Card.DiscardPile then
|
||||
toAreaIds = self.discard_pile
|
||||
elseif data.toArea == Card.Void then
|
||||
toAreaIds = self.void
|
||||
end
|
||||
|
||||
table.insert(toAreaIds, toAreaIds == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId)
|
||||
end
|
||||
self:setCardArea(info.cardId, data.toArea, data.to)
|
||||
Fk:filterCard(info.cardId, self:getPlayerById(data.to))
|
||||
|
||||
local currentCard = Fk:getCardById(info.cardId)
|
||||
if
|
||||
data.toArea == Player.Equip and
|
||||
currentCard.type == Card.TypeEquip and
|
||||
data.to ~= nil and
|
||||
self:getPlayerById(data.to):isAlive() and
|
||||
currentCard.equip_skill
|
||||
then
|
||||
currentCard:onInstall(self, self:getPlayerById(data.to))
|
||||
elseif realFromArea == Player.Equip and currentCard.type == Card.TypeEquip and data.from ~= nil and currentCard.equip_skill then
|
||||
currentCard:onUninstall(self, self:getPlayerById(data.from))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs)
|
||||
return true
|
||||
end
|
4
lua/server/events/skill.lua
Normal file
4
lua/server/events/skill.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
GameEvent.functions[GameEvent.SkillEffect] = function(self)
|
||||
local effect_cb = table.unpack(self.data)
|
||||
return effect_cb()
|
||||
end
|
253
lua/server/events/usecard.lua
Normal file
253
lua/server/events/usecard.lua
Normal file
|
@ -0,0 +1,253 @@
|
|||
local playCardEmotionAndSound = function(room, player, card)
|
||||
if card.type ~= Card.TypeEquip then
|
||||
room:setEmotion(player, "./packages/" ..
|
||||
card.package.extensionName .. "/image/anim/" .. card.name)
|
||||
end
|
||||
|
||||
local soundName
|
||||
if card.type == Card.TypeEquip then
|
||||
local subTypeStr
|
||||
if card.sub_type == Card.SubtypeDefensiveRide or card.sub_type == Card.SubtypeOffensiveRide then
|
||||
subTypeStr = "horse"
|
||||
elseif card.sub_type == Card.SubtypeWeapon then
|
||||
subTypeStr = "weapon"
|
||||
else
|
||||
subTypeStr = "armor"
|
||||
end
|
||||
|
||||
soundName = "./audio/card/common/" .. subTypeStr
|
||||
else
|
||||
soundName = "./packages/" .. card.package.extensionName .. "/audio/card/"
|
||||
.. (player.gender == General.Male and "male/" or "female/") .. card.name
|
||||
end
|
||||
room:broadcastPlaySound(soundName)
|
||||
end
|
||||
|
||||
---@param room Room
|
||||
---@param cardUseEvent CardUseStruct
|
||||
local sendCardEmotionAndLog = function(room, cardUseEvent)
|
||||
local from = cardUseEvent.from
|
||||
local _card = cardUseEvent.card
|
||||
|
||||
-- when this function is called, card is already in PlaceTable and no filter skill is applied.
|
||||
-- So filter this card manually here to get 'real' use.card
|
||||
local card = _card
|
||||
if not _card:isVirtual() then
|
||||
local temp = { card = _card }
|
||||
Fk:filterCard(_card.id, room:getPlayerById(from), temp)
|
||||
card = temp.card
|
||||
end
|
||||
|
||||
playCardEmotionAndSound(room, room:getPlayerById(from), card)
|
||||
room:doAnimate("Indicate", {
|
||||
from = from,
|
||||
to = cardUseEvent.tos or {},
|
||||
})
|
||||
|
||||
local useCardIds = card:isVirtual() and card.subcards or { card.id }
|
||||
if cardUseEvent.tos and #cardUseEvent.tos > 0 then
|
||||
local to = {}
|
||||
for _, t in ipairs(cardUseEvent.tos) do
|
||||
table.insert(to, t[1])
|
||||
end
|
||||
|
||||
if card:isVirtual() or (card ~= _card) then
|
||||
if #useCardIds == 0 then
|
||||
room:sendLog{
|
||||
type = "#UseV0CardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseVCardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
card = useCardIds,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseCardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
card = useCardIds
|
||||
}
|
||||
end
|
||||
|
||||
for _, t in ipairs(cardUseEvent.tos) do
|
||||
if t[2] then
|
||||
local temp = {table.unpack(t)}
|
||||
table.remove(temp, 1)
|
||||
room:sendLog{
|
||||
type = "#CardUseCollaborator",
|
||||
from = t[1],
|
||||
to = temp,
|
||||
arg = card.name,
|
||||
}
|
||||
end
|
||||
end
|
||||
elseif cardUseEvent.toCard then
|
||||
if card:isVirtual() or (card ~= _card) then
|
||||
if #useCardIds == 0 then
|
||||
room:sendLog{
|
||||
type = "#UseV0CardToCard",
|
||||
from = from,
|
||||
arg = cardUseEvent.toCard.name,
|
||||
arg2 = card:toLogString(),
|
||||
}
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseVCardToCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
arg = cardUseEvent.toCard.name,
|
||||
arg2 = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseCardToCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
arg = cardUseEvent.toCard.name,
|
||||
}
|
||||
end
|
||||
else
|
||||
if card:isVirtual() or (card ~= _card) then
|
||||
if #useCardIds == 0 then
|
||||
room:sendLog{
|
||||
type = "#UseV0Card",
|
||||
from = from,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseVCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param self GameEvent
|
||||
GameEvent.functions[GameEvent.UseCard] = function(self)
|
||||
local cardUseEvent = table.unpack(self.data)
|
||||
local self = self.room
|
||||
local from = cardUseEvent.from
|
||||
self:moveCards({
|
||||
ids = self:getSubcardsByRule(cardUseEvent.card),
|
||||
from = from,
|
||||
toArea = Card.Processing,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
|
||||
if cardUseEvent.card.skill then
|
||||
cardUseEvent.card.skill:onUse(self, cardUseEvent)
|
||||
end
|
||||
|
||||
sendCardEmotionAndLog(self, cardUseEvent)
|
||||
|
||||
if self.logic:trigger(fk.PreCardUse, self:getPlayerById(cardUseEvent.from), cardUseEvent) then
|
||||
self.logic:breakEvent()
|
||||
end
|
||||
|
||||
if not cardUseEvent.extraUse then
|
||||
self:getPlayerById(cardUseEvent.from):addCardUseHistory(cardUseEvent.card.trueName, 1)
|
||||
end
|
||||
|
||||
if cardUseEvent.responseToEvent then
|
||||
cardUseEvent.responseToEvent.cardsResponded = cardUseEvent.responseToEvent.cardsResponded or {}
|
||||
table.insert(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card)
|
||||
end
|
||||
|
||||
for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do
|
||||
if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
self.logic:trigger(event, self:getPlayerById(cardUseEvent.from), cardUseEvent)
|
||||
if event == fk.CardUsing then
|
||||
self:doCardUseEffect(cardUseEvent)
|
||||
end
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.CardUseFinished, self:getPlayerById(cardUseEvent.from), cardUseEvent)
|
||||
end
|
||||
|
||||
GameEvent.cleaners[GameEvent.UseCard] = function(self)
|
||||
local cardUseEvent = table.unpack(self.data)
|
||||
local self = self.room
|
||||
|
||||
local leftRealCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
|
||||
if #leftRealCardIds > 0 then
|
||||
self:moveCards({
|
||||
ids = leftRealCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
GameEvent.functions[GameEvent.RespondCard] = function(self)
|
||||
local cardResponseEvent = table.unpack(self.data)
|
||||
local self = self.room
|
||||
local from = cardResponseEvent.customFrom or cardResponseEvent.from
|
||||
local card = cardResponseEvent.card
|
||||
local cardIds = self:getSubcardsByRule(card)
|
||||
|
||||
if card:isVirtual() then
|
||||
if #cardIds == 0 then
|
||||
self:sendLog{
|
||||
type = "#ResponsePlayV0Card",
|
||||
from = from,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#ResponsePlayVCard",
|
||||
from = from,
|
||||
card = cardIds,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#ResponsePlayCard",
|
||||
from = from,
|
||||
card = cardIds,
|
||||
}
|
||||
end
|
||||
self:moveCards({
|
||||
ids = cardIds,
|
||||
from = from,
|
||||
toArea = Card.Processing,
|
||||
moveReason = fk.ReasonResonpse,
|
||||
})
|
||||
|
||||
playCardEmotionAndSound(self, self:getPlayerById(from), card)
|
||||
|
||||
for _, event in ipairs({ fk.PreCardRespond, fk.CardResponding, fk.CardRespondFinished }) do
|
||||
self.logic:trigger(event, self:getPlayerById(cardResponseEvent.from), cardResponseEvent)
|
||||
end
|
||||
|
||||
local realCardIds = self:getSubcardsByRule(cardResponseEvent.card, { Card.Processing })
|
||||
if #realCardIds > 0 and not cardResponseEvent.skipDrop then
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
end
|
||||
end
|
81
lua/server/gameevent.lua
Normal file
81
lua/server/gameevent.lua
Normal file
|
@ -0,0 +1,81 @@
|
|||
---@class GameEvent: Object
|
||||
---@field room Room
|
||||
---@field event integer
|
||||
---@field data any
|
||||
local GameEvent = class("GameEvent")
|
||||
|
||||
GameEvent.functions = {}
|
||||
GameEvent.cleaners = {}
|
||||
local function wrapCoFunc(f, ...)
|
||||
if not f then return nil end
|
||||
local args = {...}
|
||||
return function() return f(table.unpack(args)) end
|
||||
end
|
||||
local function dummyFunc() end
|
||||
|
||||
function GameEvent:initialize(event, ...)
|
||||
self.room = RoomInstance
|
||||
self.event = event
|
||||
self.data = { ... }
|
||||
self.main_func = wrapCoFunc(GameEvent.functions[event], self) or dummyFunc
|
||||
self.clear_func = wrapCoFunc(GameEvent.cleaners[event], self) or dummyFunc
|
||||
end
|
||||
|
||||
function GameEvent:exec()
|
||||
local room = self.room
|
||||
local logic = room.logic
|
||||
local ret = false -- false or nil means this event is running normally
|
||||
local extra_ret
|
||||
logic.game_event_stack:push(self)
|
||||
|
||||
local co = coroutine.create(self.main_func)
|
||||
while true do
|
||||
local err, yield_result, extra_yield_result = coroutine.resume(co)
|
||||
|
||||
if err == false then
|
||||
-- handle error, then break
|
||||
if not string.find(yield_result, "__manuallyBreak") then
|
||||
fk.qCritical(yield_result)
|
||||
print(debug.traceback(co))
|
||||
end
|
||||
self.clear_func()
|
||||
ret = true
|
||||
break
|
||||
end
|
||||
|
||||
if yield_result == "__handleRequest" then
|
||||
-- yield to requestLoop
|
||||
coroutine.yield(yield_result, extra_yield_result)
|
||||
|
||||
elseif type(yield_result) == "table" and yield_result.class
|
||||
and yield_result:isInstanceOf(GameEvent) then
|
||||
-- yield to corresponding GameEvent, first pop self from stack
|
||||
self.clear_func()
|
||||
logic.game_event_stack:pop(self)
|
||||
|
||||
-- then, call yield
|
||||
coroutine.yield(yield_result)
|
||||
|
||||
elseif yield_result == "__breakEvent" then
|
||||
-- try to break this event
|
||||
local cancelEvent = GameEvent:new(GameEvent.BreakEvent, self)
|
||||
local notcanceled = cancelEvent:exec()
|
||||
if not notcanceled then
|
||||
self.clear_func()
|
||||
ret = true
|
||||
extra_ret = extra_yield_result
|
||||
break
|
||||
end
|
||||
|
||||
else
|
||||
-- normally exit, simply break the loop
|
||||
extra_ret = yield_result
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
logic.game_event_stack:pop(self)
|
||||
return ret, extra_ret
|
||||
end
|
||||
|
||||
return GameEvent
|
|
@ -4,6 +4,7 @@
|
|||
---@field refresh_skill_table table<Event, TriggerSkill[]>
|
||||
---@field skills string[]
|
||||
---@field event_stack Stack
|
||||
---@field game_event_stack Stack
|
||||
---@field role_table string[][]
|
||||
local GameLogic = class("GameLogic")
|
||||
|
||||
|
@ -13,6 +14,7 @@ function GameLogic:initialize(room)
|
|||
self.refresh_skill_table = {}
|
||||
self.skills = {} -- skillName[]
|
||||
self.event_stack = Stack:new()
|
||||
self.game_event_stack = Stack:new()
|
||||
|
||||
self.role_table = {
|
||||
{ "lord" },
|
||||
|
@ -275,4 +277,8 @@ function GameLogic:trigger(event, target, data)
|
|||
return broken
|
||||
end
|
||||
|
||||
function GameLogic:breakEvent(ret)
|
||||
coroutine.yield("__breakEvent", false)
|
||||
end
|
||||
|
||||
return GameLogic
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
local Room = class("Room")
|
||||
|
||||
-- load classes used by the game
|
||||
GameEvent = require "server.gameevent"
|
||||
dofile "lua/server/events/init.lua"
|
||||
GameLogic = require "server.gamelogic"
|
||||
ServerPlayer = require "server.serverplayer"
|
||||
|
||||
|
@ -64,7 +66,7 @@ function Room:initialize(_room)
|
|||
end)
|
||||
local ret, err_msg = true, true
|
||||
while not self.game_finished do
|
||||
ret, err_msg = coroutine.resume(main_co, err_msg)
|
||||
ret, _, err_msg = coroutine.resume(main_co, err_msg)
|
||||
|
||||
-- handle error
|
||||
if ret == false then
|
||||
|
@ -512,7 +514,7 @@ function Room:delay(ms)
|
|||
if rest <= 0 then
|
||||
break
|
||||
end
|
||||
coroutine.yield(rest)
|
||||
coroutine.yield("__handleRequest", rest)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1075,147 +1077,16 @@ end
|
|||
-- use card logic, and wrappers
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local playCardEmotionAndSound = function(room, player, card)
|
||||
if card.type ~= Card.TypeEquip then
|
||||
room:setEmotion(player, "./packages/" ..
|
||||
card.package.extensionName .. "/image/anim/" .. card.name)
|
||||
end
|
||||
|
||||
local soundName
|
||||
if card.type == Card.TypeEquip then
|
||||
local subTypeStr
|
||||
if card.sub_type == Card.SubtypeDefensiveRide or card.sub_type == Card.SubtypeOffensiveRide then
|
||||
subTypeStr = "horse"
|
||||
elseif card.sub_type == Card.SubtypeWeapon then
|
||||
subTypeStr = "weapon"
|
||||
else
|
||||
subTypeStr = "armor"
|
||||
end
|
||||
|
||||
soundName = "./audio/card/common/" .. subTypeStr
|
||||
else
|
||||
soundName = "./packages/" .. card.package.extensionName .. "/audio/card/"
|
||||
.. (player.gender == General.Male and "male/" or "female/") .. card.name
|
||||
end
|
||||
room:broadcastPlaySound(soundName)
|
||||
local function execGameEvent(type, ...)
|
||||
local event = GameEvent:new(type, ...)
|
||||
local _, ret = event:exec()
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param room Room
|
||||
---@param cardUseEvent CardUseStruct
|
||||
local sendCardEmotionAndLog = function(room, cardUseEvent)
|
||||
local from = cardUseEvent.from
|
||||
local _card = cardUseEvent.card
|
||||
|
||||
-- when this function is called, card is already in PlaceTable and no filter skill is applied.
|
||||
-- So filter this card manually here to get 'real' use.card
|
||||
local card = _card
|
||||
if not _card:isVirtual() then
|
||||
local temp = { card = _card }
|
||||
Fk:filterCard(_card.id, room:getPlayerById(from), temp)
|
||||
card = temp.card
|
||||
end
|
||||
|
||||
playCardEmotionAndSound(room, room:getPlayerById(from), card)
|
||||
room:doAnimate("Indicate", {
|
||||
from = from,
|
||||
to = cardUseEvent.tos or {},
|
||||
})
|
||||
|
||||
local useCardIds = card:isVirtual() and card.subcards or { card.id }
|
||||
if cardUseEvent.tos and #cardUseEvent.tos > 0 then
|
||||
local to = {}
|
||||
for _, t in ipairs(cardUseEvent.tos) do
|
||||
table.insert(to, t[1])
|
||||
end
|
||||
|
||||
if card:isVirtual() or (card ~= _card) then
|
||||
if #useCardIds == 0 then
|
||||
room:sendLog{
|
||||
type = "#UseV0CardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseVCardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
card = useCardIds,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseCardToTargets",
|
||||
from = from,
|
||||
to = to,
|
||||
card = useCardIds
|
||||
}
|
||||
end
|
||||
|
||||
for _, t in ipairs(cardUseEvent.tos) do
|
||||
if t[2] then
|
||||
local temp = {table.unpack(t)}
|
||||
table.remove(temp, 1)
|
||||
room:sendLog{
|
||||
type = "#CardUseCollaborator",
|
||||
from = t[1],
|
||||
to = temp,
|
||||
arg = card.name,
|
||||
}
|
||||
end
|
||||
end
|
||||
elseif cardUseEvent.toCard then
|
||||
if card:isVirtual() or (card ~= _card) then
|
||||
if #useCardIds == 0 then
|
||||
room:sendLog{
|
||||
type = "#UseV0CardToCard",
|
||||
from = from,
|
||||
arg = cardUseEvent.toCard.name,
|
||||
arg2 = card:toLogString(),
|
||||
}
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseVCardToCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
arg = cardUseEvent.toCard.name,
|
||||
arg2 = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseCardToCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
arg = cardUseEvent.toCard.name,
|
||||
}
|
||||
end
|
||||
else
|
||||
if card:isVirtual() or (card ~= _card) then
|
||||
if #useCardIds == 0 then
|
||||
room:sendLog{
|
||||
type = "#UseV0Card",
|
||||
from = from,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseVCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
room:sendLog{
|
||||
type = "#UseCard",
|
||||
from = from,
|
||||
card = useCardIds,
|
||||
}
|
||||
end
|
||||
end
|
||||
---@return boolean
|
||||
function Room:useCard(cardUseEvent)
|
||||
return execGameEvent(GameEvent.UseCard, cardUseEvent)
|
||||
end
|
||||
|
||||
---@param room Room
|
||||
|
@ -1327,60 +1198,6 @@ local onAim = function(room, cardUseEvent, aimEventCollaborators)
|
|||
return true
|
||||
end
|
||||
|
||||
---@param cardUseEvent CardUseStruct
|
||||
---@return boolean
|
||||
function Room:useCard(cardUseEvent)
|
||||
local from = cardUseEvent.from
|
||||
self:moveCards({
|
||||
ids = self:getSubcardsByRule(cardUseEvent.card),
|
||||
from = from,
|
||||
toArea = Card.Processing,
|
||||
moveReason = fk.ReasonUse,
|
||||
})
|
||||
|
||||
if cardUseEvent.card.skill then
|
||||
cardUseEvent.card.skill:onUse(self, cardUseEvent)
|
||||
end
|
||||
|
||||
sendCardEmotionAndLog(self, cardUseEvent)
|
||||
|
||||
if self.logic:trigger(fk.PreCardUse, self:getPlayerById(cardUseEvent.from), cardUseEvent) then
|
||||
goto clean
|
||||
end
|
||||
|
||||
if not cardUseEvent.extraUse then
|
||||
self:getPlayerById(cardUseEvent.from):addCardUseHistory(cardUseEvent.card.trueName, 1)
|
||||
end
|
||||
|
||||
if cardUseEvent.responseToEvent then
|
||||
cardUseEvent.responseToEvent.cardsResponded = cardUseEvent.responseToEvent.cardsResponded or {}
|
||||
table.insert(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card)
|
||||
end
|
||||
|
||||
for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.BeforeCardUseEffect, fk.CardUsing }) do
|
||||
if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
|
||||
break
|
||||
end
|
||||
|
||||
self.logic:trigger(event, self:getPlayerById(cardUseEvent.from), cardUseEvent)
|
||||
if event == fk.CardUsing then
|
||||
self:doCardUseEffect(cardUseEvent)
|
||||
end
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.CardUseFinished, self:getPlayerById(cardUseEvent.from), cardUseEvent)
|
||||
|
||||
::clean::
|
||||
local leftRealCardIds = self:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
|
||||
if #leftRealCardIds > 0 then
|
||||
self:moveCards({
|
||||
ids = leftRealCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
---@param cardUseEvent CardUseStruct
|
||||
function Room:doCardUseEffect(cardUseEvent)
|
||||
---@type table<string, AimStruct>
|
||||
|
@ -1607,7 +1424,9 @@ function Room:doCardEffect(cardEffectEvent)
|
|||
|
||||
if event == fk.CardEffecting then
|
||||
if cardEffectEvent.card.skill then
|
||||
cardEffectEvent.card.skill:onEffect(self, cardEffectEvent)
|
||||
execGameEvent(GameEvent.SkillEffect, function ()
|
||||
cardEffectEvent.card.skill:onEffect(self, cardEffectEvent)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1615,53 +1434,7 @@ end
|
|||
|
||||
---@param cardResponseEvent CardResponseEvent
|
||||
function Room:responseCard(cardResponseEvent)
|
||||
local from = cardResponseEvent.customFrom or cardResponseEvent.from
|
||||
local card = cardResponseEvent.card
|
||||
local cardIds = self:getSubcardsByRule(card)
|
||||
|
||||
if card:isVirtual() then
|
||||
if #cardIds == 0 then
|
||||
self:sendLog{
|
||||
type = "#ResponsePlayV0Card",
|
||||
from = from,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#ResponsePlayVCard",
|
||||
from = from,
|
||||
card = cardIds,
|
||||
arg = card:toLogString(),
|
||||
}
|
||||
end
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#ResponsePlayCard",
|
||||
from = from,
|
||||
card = cardIds,
|
||||
}
|
||||
end
|
||||
self:moveCards({
|
||||
ids = cardIds,
|
||||
from = from,
|
||||
toArea = Card.Processing,
|
||||
moveReason = fk.ReasonResonpse,
|
||||
})
|
||||
|
||||
playCardEmotionAndSound(self, self:getPlayerById(from), card)
|
||||
|
||||
for _, event in ipairs({ fk.PreCardRespond, fk.CardResponding, fk.CardRespondFinished }) do
|
||||
self.logic:trigger(event, self:getPlayerById(cardResponseEvent.from), cardResponseEvent)
|
||||
end
|
||||
|
||||
local realCardIds = self:getSubcardsByRule(cardResponseEvent.card, { Card.Processing })
|
||||
if #realCardIds > 0 and not cardResponseEvent.skipDrop then
|
||||
self:moveCards({
|
||||
ids = realCardIds,
|
||||
toArea = Card.DiscardPile,
|
||||
moveReason = fk.ReasonPutIntoDiscardPile,
|
||||
})
|
||||
end
|
||||
return execGameEvent(GameEvent.RespondCard, cardResponseEvent)
|
||||
end
|
||||
------------------------------------------------------------------------
|
||||
-- move cards, and wrappers
|
||||
|
@ -1670,119 +1443,7 @@ end
|
|||
---@vararg CardsMoveInfo
|
||||
---@return boolean
|
||||
function Room:moveCards(...)
|
||||
---@type CardsMoveStruct[]
|
||||
local cardsMoveStructs = {}
|
||||
local infoCheck = function(info)
|
||||
assert(table.contains({ Card.PlayerHand, Card.PlayerEquip, Card.PlayerJudge, Card.PlayerSpecial, Card.Processing, Card.DrawPile, Card.DiscardPile, Card.Void }, info.toArea))
|
||||
assert(info.toArea ~= Card.PlayerSpecial or type(info.specialName) == "string")
|
||||
assert(type(info.moveReason) == "number")
|
||||
end
|
||||
|
||||
for _, cardsMoveInfo in ipairs({...}) do
|
||||
if #cardsMoveInfo.ids > 0 then
|
||||
infoCheck(cardsMoveInfo)
|
||||
|
||||
---@type MoveInfo[]
|
||||
local infos = {}
|
||||
for _, id in ipairs(cardsMoveInfo.ids) do
|
||||
table.insert(infos, {
|
||||
cardId = id,
|
||||
fromArea = self:getCardArea(id),
|
||||
fromSpecialName = cardsMoveInfo.from and self:getPlayerById(cardsMoveInfo.from):getPileNameOfId(id),
|
||||
})
|
||||
end
|
||||
|
||||
---@type CardsMoveStruct
|
||||
local cardsMoveStruct = {
|
||||
moveInfo = infos,
|
||||
from = cardsMoveInfo.from,
|
||||
to = cardsMoveInfo.to,
|
||||
toArea = cardsMoveInfo.toArea,
|
||||
moveReason = cardsMoveInfo.moveReason,
|
||||
proposer = cardsMoveInfo.proposer,
|
||||
skillName = cardsMoveInfo.skillName,
|
||||
moveVisible = cardsMoveInfo.moveVisible,
|
||||
specialName = cardsMoveInfo.specialName,
|
||||
specialVisible = cardsMoveInfo.specialVisible,
|
||||
}
|
||||
|
||||
table.insert(cardsMoveStructs, cardsMoveStruct)
|
||||
end
|
||||
end
|
||||
|
||||
if #cardsMoveStructs < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
if self.logic:trigger(fk.BeforeCardsMove, nil, cardsMoveStructs) then
|
||||
return false
|
||||
end
|
||||
|
||||
self:notifyMoveCards(nil, cardsMoveStructs)
|
||||
|
||||
for _, data in ipairs(cardsMoveStructs) do
|
||||
if #data.moveInfo > 0 then
|
||||
infoCheck(data)
|
||||
|
||||
---@param info MoveInfo
|
||||
for _, info in ipairs(data.moveInfo) do
|
||||
local realFromArea = self:getCardArea(info.cardId)
|
||||
local playerAreas = { Player.Hand, Player.Equip, Player.Judge, Player.Special }
|
||||
|
||||
if table.contains(playerAreas, realFromArea) and data.from then
|
||||
self:getPlayerById(data.from):removeCards(realFromArea, { info.cardId }, info.fromSpecialName)
|
||||
elseif realFromArea ~= Card.Unknown then
|
||||
local fromAreaIds = {}
|
||||
if realFromArea == Card.Processing then
|
||||
fromAreaIds = self.processing_area
|
||||
elseif realFromArea == Card.DrawPile then
|
||||
fromAreaIds = self.draw_pile
|
||||
elseif realFromArea == Card.DiscardPile then
|
||||
fromAreaIds = self.discard_pile
|
||||
elseif realFromArea == Card.Void then
|
||||
fromAreaIds = self.void
|
||||
end
|
||||
|
||||
table.removeOne(fromAreaIds, info.cardId)
|
||||
end
|
||||
|
||||
if table.contains(playerAreas, data.toArea) and data.to then
|
||||
self:getPlayerById(data.to):addCards(data.toArea, { info.cardId }, data.specialName)
|
||||
else
|
||||
local toAreaIds = {}
|
||||
if data.toArea == Card.Processing then
|
||||
toAreaIds = self.processing_area
|
||||
elseif data.toArea == Card.DrawPile then
|
||||
toAreaIds = self.draw_pile
|
||||
elseif data.toArea == Card.DiscardPile then
|
||||
toAreaIds = self.discard_pile
|
||||
elseif data.toArea == Card.Void then
|
||||
toAreaIds = self.void
|
||||
end
|
||||
|
||||
table.insert(toAreaIds, toAreaIds == Card.DrawPile and 1 or #toAreaIds + 1, info.cardId)
|
||||
end
|
||||
self:setCardArea(info.cardId, data.toArea, data.to)
|
||||
Fk:filterCard(info.cardId, self:getPlayerById(data.to))
|
||||
|
||||
local currentCard = Fk:getCardById(info.cardId)
|
||||
if
|
||||
data.toArea == Player.Equip and
|
||||
currentCard.type == Card.TypeEquip and
|
||||
data.to ~= nil and
|
||||
self:getPlayerById(data.to):isAlive() and
|
||||
currentCard.equip_skill
|
||||
then
|
||||
currentCard:onInstall(self, self:getPlayerById(data.to))
|
||||
elseif realFromArea == Player.Equip and currentCard.type == Card.TypeEquip and data.from ~= nil and currentCard.equip_skill then
|
||||
currentCard:onUninstall(self, self:getPlayerById(data.from))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.AfterCardsMove, nil, cardsMoveStructs)
|
||||
return true
|
||||
return execGameEvent(GameEvent.MoveCards, ...)
|
||||
end
|
||||
|
||||
---@param player integer
|
||||
|
@ -1872,91 +1533,7 @@ end
|
|||
---@param damageStruct DamageStruct|null
|
||||
---@return boolean
|
||||
function Room:changeHp(player, num, reason, skillName, damageStruct)
|
||||
if num == 0 then
|
||||
return false
|
||||
end
|
||||
assert(reason == nil or table.contains({ "loseHp", "damage", "recover" }, reason))
|
||||
|
||||
---@type HpChangedData
|
||||
local data = {
|
||||
num = num,
|
||||
reason = reason,
|
||||
skillName = skillName,
|
||||
}
|
||||
|
||||
if self.logic:trigger(fk.BeforeHpChanged, player, data) then
|
||||
return false
|
||||
end
|
||||
|
||||
assert(not (data.reason == "recover" and data.num < 0))
|
||||
player.hp = math.min(player.hp + data.num, player.maxHp)
|
||||
self:broadcastProperty(player, "hp")
|
||||
|
||||
if reason == "damage" then
|
||||
local damage_nature_table = {
|
||||
[fk.NormalDamage] = "normal_damage",
|
||||
[fk.FireDamage] = "fire_damage",
|
||||
[fk.ThunderDamage] = "thunder_damage",
|
||||
}
|
||||
if damageStruct.from then
|
||||
self:sendLog{
|
||||
type = "#Damage",
|
||||
to = {damageStruct.from.id},
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
arg2 = damage_nature_table[damageStruct.damageType],
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#DamageWithNoFrom",
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
arg2 = damage_nature_table[damageStruct.damageType],
|
||||
}
|
||||
end
|
||||
self:sendLogEvent("Damage", {
|
||||
to = player.id,
|
||||
damageType = damage_nature_table[damageStruct.damageType],
|
||||
damageNum = damageStruct.damage,
|
||||
})
|
||||
elseif reason == "loseHp" then
|
||||
self:sendLog{
|
||||
type = "#LoseHP",
|
||||
from = player.id,
|
||||
arg = 0 - num,
|
||||
}
|
||||
self:sendLogEvent("LoseHP", {})
|
||||
elseif reason == "recover" then
|
||||
self:sendLog{
|
||||
type = "#HealHP",
|
||||
from = player.id,
|
||||
arg = num,
|
||||
}
|
||||
end
|
||||
|
||||
self:sendLog{
|
||||
type = "#ShowHPAndMaxHP",
|
||||
from = player.id,
|
||||
arg = player.hp,
|
||||
arg2 = player.maxHp,
|
||||
}
|
||||
|
||||
self.logic:trigger(fk.HpChanged, player, data)
|
||||
|
||||
if player.hp < 1 then
|
||||
if num < 0 then
|
||||
---@type DyingStruct
|
||||
local dyingStruct = {
|
||||
who = player.id,
|
||||
damage = damageStruct,
|
||||
}
|
||||
self:enterDying(dyingStruct)
|
||||
end
|
||||
elseif player.dying then
|
||||
player.dying = false
|
||||
end
|
||||
|
||||
return true
|
||||
return execGameEvent(GameEvent.ChangeHp, player, num, reason, skillName, damageStruct)
|
||||
end
|
||||
|
||||
---@param player ServerPlayer
|
||||
|
@ -1964,181 +1541,36 @@ end
|
|||
---@param skillName string
|
||||
---@return boolean
|
||||
function Room:loseHp(player, num, skillName)
|
||||
if num == nil then
|
||||
num = 1
|
||||
elseif num < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
---@type HpLostData
|
||||
local data = {
|
||||
num = num,
|
||||
skillName = skillName,
|
||||
}
|
||||
if self.logic:trigger(fk.PreHpLost, player, data) or data.num < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
if not self:changeHp(player, -num, "loseHp", skillName) then
|
||||
return false
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.HpLost, player, data)
|
||||
return true
|
||||
return execGameEvent(GameEvent.LoseHp, player, num, skillName)
|
||||
end
|
||||
|
||||
---@param player ServerPlayer
|
||||
---@param num integer
|
||||
---@return boolean
|
||||
function Room:changeMaxHp(player, num)
|
||||
if num == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
player.maxHp = math.max(player.maxHp + num, 0)
|
||||
self:broadcastProperty(player, "maxHp")
|
||||
local diff = player.hp - player.maxHp
|
||||
if diff > 0 then
|
||||
if not self:changeHp(player, -diff) then
|
||||
player.hp = player.hp - diff
|
||||
end
|
||||
end
|
||||
|
||||
if player.maxHp == 0 then
|
||||
self:killPlayer({ who = player.id })
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.MaxHpChanged, player, { num = num })
|
||||
return true
|
||||
return execGameEvent(GameEvent.ChangeMaxHp, player, num)
|
||||
end
|
||||
|
||||
---@param damageStruct DamageStruct
|
||||
---@return boolean
|
||||
function Room:damage(damageStruct)
|
||||
if damageStruct.damage < 1 then
|
||||
return false
|
||||
end
|
||||
damageStruct.damageType = damageStruct.damageType or fk.NormalDamage
|
||||
|
||||
if damageStruct.from and not damageStruct.from:isAlive() then
|
||||
damageStruct.from = nil
|
||||
end
|
||||
|
||||
assert(damageStruct.to:isInstanceOf(ServerPlayer))
|
||||
|
||||
local stages = {
|
||||
{fk.PreDamage, damageStruct.from},
|
||||
{fk.DamageCaused, damageStruct.from},
|
||||
{fk.DamageInflicted, damageStruct.to},
|
||||
}
|
||||
|
||||
for _, struct in ipairs(stages) do
|
||||
local event, player = table.unpack(struct)
|
||||
if self.logic:trigger(event, player, damageStruct) or damageStruct.damage < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
assert(damageStruct.to:isInstanceOf(ServerPlayer))
|
||||
end
|
||||
|
||||
if not damageStruct.to:isAlive() then
|
||||
return false
|
||||
end
|
||||
|
||||
if not self:changeHp(damageStruct.to, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then
|
||||
return false
|
||||
end
|
||||
|
||||
stages = {
|
||||
{fk.Damage, damageStruct.from},
|
||||
{fk.Damaged, damageStruct.to},
|
||||
{fk.DamageFinished, damageStruct.from},
|
||||
}
|
||||
|
||||
for _, struct in ipairs(stages) do
|
||||
local event, player = table.unpack(struct)
|
||||
self.logic:trigger(event, player, damageStruct)
|
||||
end
|
||||
|
||||
return true
|
||||
return execGameEvent(GameEvent.Damage, damageStruct)
|
||||
end
|
||||
|
||||
---@param recoverStruct RecoverStruct
|
||||
---@return boolean
|
||||
function Room:recover(recoverStruct)
|
||||
if recoverStruct.num < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
local who = recoverStruct.who
|
||||
if self.logic:trigger(fk.PreHpRecover, who, recoverStruct) or recoverStruct.num < 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
if not self:changeHp(who, recoverStruct.num, "recover", recoverStruct.skillName) then
|
||||
return false
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.HpRecover, who, recoverStruct)
|
||||
return true
|
||||
return execGameEvent(GameEvent.Recover, recoverStruct)
|
||||
end
|
||||
|
||||
---@param dyingStruct DyingStruct
|
||||
function Room:enterDying(dyingStruct)
|
||||
local dyingPlayer = self:getPlayerById(dyingStruct.who)
|
||||
dyingPlayer.dying = true
|
||||
self:broadcastProperty(dyingPlayer, "dying")
|
||||
self:sendLog{
|
||||
type = "#EnterDying",
|
||||
from = dyingPlayer.id,
|
||||
}
|
||||
self.logic:trigger(fk.EnterDying, dyingPlayer, dyingStruct)
|
||||
|
||||
if dyingPlayer.hp < 1 then
|
||||
self.logic:trigger(fk.Dying, dyingPlayer, dyingStruct)
|
||||
self.logic:trigger(fk.AskForPeaches, dyingPlayer, dyingStruct)
|
||||
self.logic:trigger(fk.AskForPeachesDone, dyingPlayer, dyingStruct)
|
||||
end
|
||||
|
||||
if not dyingPlayer.dead then
|
||||
dyingPlayer.dying = false
|
||||
self:broadcastProperty(dyingPlayer, "dying")
|
||||
end
|
||||
self.logic:trigger(fk.AfterDying, dyingPlayer, dyingStruct)
|
||||
return execGameEvent(GameEvent.Dying, dyingStruct)
|
||||
end
|
||||
|
||||
---@param deathStruct DeathStruct
|
||||
function Room:killPlayer(deathStruct)
|
||||
local victim = self:getPlayerById(deathStruct.who)
|
||||
victim.dead = true
|
||||
table.removeOne(self.alive_players, victim)
|
||||
|
||||
local logic = self.logic
|
||||
logic:trigger(fk.BeforeGameOverJudge, victim, deathStruct)
|
||||
|
||||
local killer = deathStruct.damage and deathStruct.damage.from or nil
|
||||
if killer then
|
||||
self:sendLog{
|
||||
type = "#KillPlayer",
|
||||
to = {killer.id},
|
||||
from = victim.id,
|
||||
arg = victim.role,
|
||||
}
|
||||
else
|
||||
self:sendLog{
|
||||
type = "#KillPlayerWithNoKiller",
|
||||
from = victim.id,
|
||||
arg = victim.role,
|
||||
}
|
||||
end
|
||||
self:sendLogEvent("Death", {to = victim.id})
|
||||
|
||||
self:broadcastProperty(victim, "role")
|
||||
self:broadcastProperty(victim, "dead")
|
||||
|
||||
logic:trigger(fk.GameOverJudge, victim, deathStruct)
|
||||
logic:trigger(fk.Death, victim, deathStruct)
|
||||
logic:trigger(fk.BuryVictim, victim, deathStruct)
|
||||
return execGameEvent(GameEvent.Death, deathStruct)
|
||||
end
|
||||
|
||||
-- lose/acquire skill actions
|
||||
|
@ -2219,46 +1651,8 @@ end
|
|||
-- judge
|
||||
|
||||
---@param data JudgeStruct
|
||||
---@return Card
|
||||
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.id},
|
||||
}
|
||||
self:moveCardTo(data.card, Card.Processing, nil, fk.ReasonPrey)
|
||||
|
||||
self.logic:trigger(fk.AskForRetrial, who, data)
|
||||
self.logic:trigger(fk.FinishRetrial, who, data)
|
||||
Fk:filterCard(data.card.id, who, data)
|
||||
self:sendLog{
|
||||
type = "#JudgeResult",
|
||||
from = who.id,
|
||||
card = {data.card.id},
|
||||
}
|
||||
|
||||
if data.pattern then
|
||||
self:delay(400);
|
||||
self:setCardEmotion(data.card.id, data.card:matchPattern(data.pattern) and "judgegood" or "judgebad")
|
||||
self:delay(900);
|
||||
end
|
||||
|
||||
self.logic:trigger(fk.FinishJudge, who, data)
|
||||
if self:getCardArea(data.card.id) == Card.Processing then
|
||||
self:moveCardTo(data.card, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile)
|
||||
end
|
||||
return execGameEvent(GameEvent.Judge, data)
|
||||
end
|
||||
|
||||
---@param card Card
|
||||
|
@ -2398,7 +1792,7 @@ function Room:useSkill(player, skill, effect_cb)
|
|||
end
|
||||
player:addSkillUseHistory(skill.name)
|
||||
if effect_cb then
|
||||
return effect_cb()
|
||||
return execGameEvent(GameEvent.SkillEffect, effect_cb)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2413,7 +1807,7 @@ function Room:gameOver(winner)
|
|||
self:doBroadcastNotify("GameOver", winner)
|
||||
|
||||
self.room:gameOver()
|
||||
coroutine.yield()
|
||||
coroutine.yield("__handleRequest")
|
||||
end
|
||||
|
||||
---@param card Card
|
||||
|
|
|
@ -85,7 +85,7 @@ local function _waitForReply(player, timeout)
|
|||
local ret_msg = true
|
||||
while ret_msg do
|
||||
-- when ret_msg is false, that means there is no request in the queue
|
||||
ret_msg = coroutine.yield(1)
|
||||
ret_msg = coroutine.yield("__handleRequest", 1)
|
||||
end
|
||||
|
||||
checkNoHuman(player.room)
|
||||
|
@ -102,7 +102,7 @@ local function _waitForReply(player, timeout)
|
|||
if timeout and rest <= 0 then
|
||||
return ""
|
||||
end
|
||||
coroutine.yield(rest)
|
||||
coroutine.yield("__handleRequest", rest)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user