-- SPDX-License-Identifier: GPL-3.0-or-later local damage_nature_table = { [fk.NormalDamage] = "normal_damage", [fk.FireDamage] = "fire_damage", [fk.ThunderDamage] = "thunder_damage", [fk.IceDamage] = "ice_damage", } local function sendDamageLog(room, damageStruct) if damageStruct.from then room:sendLog{ type = "#Damage", to = {damageStruct.from.id}, from = damageStruct.to.id, arg = damageStruct.damage, arg2 = damage_nature_table[damageStruct.damageType], } else room:sendLog{ type = "#DamageWithNoFrom", from = damageStruct.to.id, arg = damageStruct.damage, arg2 = damage_nature_table[damageStruct.damageType], } end room:sendLogEvent("Damage", { to = damageStruct.to.id, damageType = damage_nature_table[damageStruct.damageType], damageNum = damageStruct.damage, }) end GameEvent.functions[GameEvent.ChangeHp] = function(self) local player, num, reason, skillName, damageStruct = table.unpack(self.data) local room = self.room local logic = room.logic 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, damageEvent = damageStruct, } if reason == "damage" then data.shield_lost = math.min(-num, player.shield) data.num = num + data.shield_lost end if logic:trigger(fk.BeforeHpChanged, player, data) then logic:breakEvent(false) end if reason == "damage" and data.shield_lost > 0 and not damageStruct.isVirtualDMG then room:changeShield(player, -data.shield_lost) end if reason == "damage" then sendDamageLog(room, damageStruct) end if not (reason == "damage" and (data.num == 0 or damageStruct.isVirtualDMG)) then assert(not (data.reason == "recover" and data.num < 0)) player.hp = math.min(player.hp + data.num, player.maxHp) room:broadcastProperty(player, "hp") if reason == "loseHp" then room:sendLog{ type = "#LoseHP", from = player.id, arg = 0 - data.num, } room:sendLogEvent("LoseHP", {}) elseif reason == "recover" then room:sendLog{ type = "#HealHP", from = player.id, arg = data.num, } end room:sendLog{ type = "#ShowHPAndMaxHP", from = player.id, arg = player.hp, arg2 = player.maxHp, } end logic:trigger(fk.HpChanged, player, data) if player.hp < 1 then if num < 0 and not data.preventDying then ---@type DyingStruct local dyingStruct = { who = player.id, damage = damageStruct, } room:enterDying(dyingStruct) end elseif player.dying then player.dying = false room:broadcastProperty(player, "dying") end return true end GameEvent.functions[GameEvent.Damage] = function(self) local damageStruct = table.unpack(self.data) local room = self.room local logic = room.logic if not damageStruct.chain and logic:damageByCardEffect(not not damageStruct.from) then local cardEffectData = logic:getCurrentEvent():findParent(GameEvent.CardEffect) if cardEffectData then local cardEffectEvent = cardEffectData.data[1] damageStruct.damage = damageStruct.damage + (cardEffectEvent.additionalDamage or 0) damageStruct.by_user = true end end if damageStruct.damage < 1 then return false end damageStruct.damageType = damageStruct.damageType or fk.NormalDamage if damageStruct.from and damageStruct.from.dead then damageStruct.from = nil end assert(damageStruct.to:isInstanceOf(ServerPlayer)) local stages = { {fk.PreDamage, "from"}, } if not damageStruct.isVirtualDMG then table.insertTable(stages, { { fk.DamageCaused, "from" }, { fk.DamageInflicted, "to" } }) end for _, struct in ipairs(stages) do local event, player = table.unpack(struct) if logic:trigger(event, damageStruct[player], damageStruct) or damageStruct.damage < 1 then logic:breakEvent(false) end assert(damageStruct.to:isInstanceOf(ServerPlayer)) end if damageStruct.to.dead then return false end damageStruct.dealtRecorderId = room.logic.specific_events_id[GameEvent.Damage] room.logic.specific_events_id[GameEvent.Damage] = room.logic.specific_events_id[GameEvent.Damage] + 1 if damageStruct.card and damageStruct.damage > 0 then local parentUseData = logic:getCurrentEvent():findParent(GameEvent.UseCard) if parentUseData then local cardUseEvent = parentUseData.data[1] cardUseEvent.damageDealt = cardUseEvent.damageDealt or {} cardUseEvent.damageDealt[damageStruct.to.id] = (cardUseEvent.damageDealt[damageStruct.to.id] or 0) + damageStruct.damage end end if damageStruct.damageType ~= fk.NormalDamage and damageStruct.to.chained then if not damageStruct.chain then damageStruct.beginnerOfTheDamage = true end damageStruct.to:setChainState(false) end if not room:changeHp( damageStruct.to, -damageStruct.damage, "damage", damageStruct.skillName, damageStruct) then logic:breakEvent(false) end stages = { {fk.Damage, "from"}, {fk.Damaged, "to"}, } for _, struct in ipairs(stages) do local event, player = table.unpack(struct) logic:trigger(event, damageStruct[player], damageStruct) end return true end GameEvent.exit_funcs[GameEvent.Damage] = function(self) local room = self.room local logic = room.logic local damageStruct = self.data[1] logic:trigger(fk.DamageFinished, damageStruct.to, damageStruct) if damageStruct.beginnerOfTheDamage and not damageStruct.chain then local targets = table.filter(room:getOtherPlayers(damageStruct.to), function(p) return p.chained end) for _, p in ipairs(targets) do room:sendLog{ type = "#ChainDamage", from = p.id } local dmg = { from = damageStruct.from, to = p, damage = damageStruct.damage, damageType = damageStruct.damageType, card = damageStruct.card, skillName = damageStruct.skillName, chain = true, } room:damage(dmg) end end end GameEvent.functions[GameEvent.LoseHp] = function(self) local player, num, skillName = table.unpack(self.data) local room = self.room local logic = room.logic if num == nil then num = 1 elseif num < 1 then return false end ---@type HpLostData local data = { num = num, skillName = skillName, } if logic:trigger(fk.PreHpLost, player, data) or data.num < 1 then logic:breakEvent(false) end if not room:changeHp(player, -data.num, "loseHp", skillName) then logic:breakEvent(false) end logic:trigger(fk.HpLost, player, data) return true end GameEvent.functions[GameEvent.Recover] = function(self) local recoverStruct = table.unpack(self.data) local room = self.room local logic = room.logic if recoverStruct.card then local cardEffectData = logic:getCurrentEvent():findParent(GameEvent.CardEffect) if cardEffectData then local cardEffectEvent = cardEffectData.data[1] recoverStruct.num = recoverStruct.num + (cardEffectEvent.additionalRecover or 0) end end if recoverStruct.num < 1 then return false end local who = recoverStruct.who if logic:trigger(fk.PreHpRecover, who, recoverStruct) or recoverStruct.num < 1 then logic:breakEvent(false) end if not room:changeHp(who, recoverStruct.num, "recover", recoverStruct.skillName) then logic:breakEvent(false) end logic:trigger(fk.HpRecover, who, recoverStruct) return true end GameEvent.functions[GameEvent.ChangeMaxHp] = function(self) local player, num = table.unpack(self.data) local room = self.room ---@type MaxHpChangedData local data = { num = num, } if room.logic:trigger(fk.BeforeMaxHpChanged, player, data) or data.num == 0 then return false end num = data.num room:setPlayerProperty(player, "maxHp", math.max(player.maxHp + num, 0)) room:sendLogEvent("ChangeMaxHp", { player = player.id, num = num, }) room:sendLog{ type = num > 0 and "#HealMaxHP" or "#LoseMaxHP", from = player.id, arg = num > 0 and num or - num, } if player.maxHp == 0 then player.hp = 0 room:broadcastProperty(player, "hp") room:sendLog{ type = "#ShowHPAndMaxHP", from = player.id, arg = 0, arg2 = 0, } room:killPlayer({ who = player.id }) return false end local diff = player.hp - player.maxHp if diff > 0 then if not room:changeHp(player, -diff) then player.hp = player.hp - diff end end room:sendLog{ type = "#ShowHPAndMaxHP", from = player.id, arg = player.hp, arg2 = player.maxHp, } room.logic:trigger(fk.MaxHpChanged, player, { num = num }) return true end