edit | blame | history | raw

战斗与刺杀系统

assassination.lua

local CONST     = require 'config.const'
local Balance   = require 'config.balance'
local Suspicion = require 'systems.suspicion'

local M = {}

-----------------------------------------------------------
-- 刺杀入口
-----------------------------------------------------------
function M.attempt(attacker, target)
    -- 冷却检查
    local cd = attacker:storage_get('kill_cd') or 0
    if cd > 0 then return false end

    -- 距离检查
    if not attacker:is_in_radius(target, Balance.KILL_RANGE) then return false end

    -- 正在换装/互动不能刺杀
    if attacker:storage_get('is_disguising') then return false end
    if attacker:storage_get('is_dragging') then return false end

    -- 准备期不可攻击
    local GameMgr = require 'core.game_manager'
    if GameMgr.get_state() == 'prepare' then return false end

    -- 背刺判定
    local backstab = M.is_backstab(attacker, target)
    local windup   = backstab and Balance.BACKSTAB_WINDUP or Balance.KILL_WINDUP
    local damage   = backstab and Balance.BACKSTAB_DAMAGE or Balance.KILL_DAMAGE

    -- 前摇动画
    attacker:play_animation('attack', 1.0, 0, -1, false, true)
    attacker:storage_set('is_attacking', true)

    y3.timer.wait(windup, function()
        attacker:storage_set('is_attacking', false)
        attacker:stop_cur_animation()

        -- 前摇结束后距离再次检查
        if not attacker:is_in_radius(target, Balance.KILL_RANGE + 0.5) then
            M.on_miss(attacker)
            return
        end

        M.on_hit(attacker, target, damage)
    end)

    return true
end

-----------------------------------------------------------
-- 命中处理
-----------------------------------------------------------
function M.on_hit(attacker, target, damage)
    -- 护盾检查
    if target:has_buff_by_key(CONST.BUFF_SHIELD) then
        target:remove_buffs_by_key(CONST.BUFF_SHIELD)
        -- 护盾抵挡,不造成伤害
        M.set_cooldown(attacker, Balance.KILL_CD_HIT)
        return
    end

    -- 造成伤害
    attacker:damage({
        target = target,
        damage = damage,
        type   = CONST.DAMAGE_TYPE_ASSASSINATE,
        no_miss = true,
    })

    -- 怀疑值处理:目击者检查
    local witness_count = M.count_witnesses(attacker, 10)
    if witness_count > 0 then
        Suspicion.add(attacker, Balance.SUS_KILL_WITNESSED)
    else
        Suspicion.add(attacker, Balance.SUS_KILL_UNWITNESSED)
    end

    -- 是否击杀
    if not target:is_alive() then
        M.set_cooldown(attacker, Balance.KILL_CD_KILL)
        M.on_kill(attacker, target)
    else
        M.set_cooldown(attacker, Balance.KILL_CD_HIT)
    end
end

-----------------------------------------------------------
-- 未命中处理
-----------------------------------------------------------
function M.on_miss(attacker)
    Suspicion.add(attacker, Balance.SUS_KILL_MISS)
    M.set_cooldown(attacker, Balance.KILL_CD_MISS)
end

-----------------------------------------------------------
-- 击杀后处理
-----------------------------------------------------------
function M.on_kill(killer, victim)
    -- NPC死亡
    if victim:has_tag('npc') then
        victim:add_tag('corpse')
        victim:set_recycle_on_remove(false)

        -- 触发区域恐慌
        local NpcMgr = require 'core.npc_manager'
        NpcMgr.trigger_panic(victim:get_point(), Balance.PANIC_RADIUS)

        -- 30秒后尸体消失
        y3.timer.wait(Balance.CORPSE_DECAY_TIME, function()
            if victim:has_tag('corpse') and not victim:has_tag('hidden') then
                victim:remove()
            end
        end)
    end

    -- 玩家死亡
    if victim:has_tag('player_controlled') then
        local PlayerMgr = require 'core.player_manager'
        PlayerMgr.on_player_killed(killer, victim)
    end
end

-----------------------------------------------------------
-- 背刺判定
-----------------------------------------------------------
function M.is_backstab(attacker, target)
    local atk_point = attacker:get_point()
    local tgt_point = target:get_point()
    local tgt_face  = target:get_facing()

    -- 计算攻击者相对于目标的方向角
    local dx = atk_point:get_x() - tgt_point:get_x()
    local dy = atk_point:get_y() - tgt_point:get_y()
    local angle_to_attacker = math.deg(math.atan(dy, dx))

    -- 目标正面方向与攻击者方向的夹角
    local diff = math.abs(angle_to_attacker - tgt_face)
    if diff > 180 then diff = 360 - diff end

    -- 夹角 > 120 度 = 攻击者在目标背后
    return diff > 120
end

-----------------------------------------------------------
-- 目击者计数
-----------------------------------------------------------
function M.count_witnesses(attacker, radius)
    local area  = y3.area.create_circle_area(attacker:get_point(), radius)
    local units = area:get_all_unit_in_area()
    area:remove()

    local count = 0
    for _, u in ipairs(units) do
        if u ~= attacker and u:is_alive() then
            if u:has_tag('player_controlled') or u:has_tag('npc') then
                count = count + 1
            end
        end
    end
    return count
end

-----------------------------------------------------------
-- 冷却
-----------------------------------------------------------
function M.set_cooldown(unit, duration)
    unit:storage_set('kill_cd', duration)
    y3.timer.wait(duration, function()
        unit:storage_set('kill_cd', 0)
    end)
end

return M

消声器物品联动

当玩家持有消声器buff时,击杀不触发恐慌、不发送全局通知:

function M.on_kill(killer, victim)
    local is_silent = killer:has_buff_by_key(CONST.BUFF_SILENCER)

    if victim:has_tag('npc') then
        victim:add_tag('corpse')
        victim:set_recycle_on_remove(false)

        if not is_silent then
            NpcMgr.trigger_panic(victim:get_point(), Balance.PANIC_RADIUS)
        end

        if is_silent then
            killer:remove_buffs_by_key(CONST.BUFF_SILENCER)
        end

        y3.timer.wait(Balance.CORPSE_DECAY_TIME, function()
            if victim:has_tag('corpse') and not victim:has_tag('hidden') then
                victim:remove()
            end
        end)
    end
    -- ...
end