edit | blame | history | raw
local CONST = require 'config.const'

local M = {}

local npc_list = {}

-- ---------------------------------------------------------
-- 初始化所有NPC
-- ---------------------------------------------------------
function M.init()
    local model_pool  = CONST.NPC_MODEL_POOL
    local spawn_areas = clicli.area.get_circle_areas_by_tag('npc_spawn')
    local roads       = M.collect_roads()

    for i = 1, CONST.NPC_COUNT do
        local npc_type = M.pick_npc_type()
        local spawn_area = spawn_areas[math.random(math.max(1, #spawn_areas))]
        local spawn = spawn_area and spawn_area:random_point() or clicli.point.create(0, 0)
        local model = #model_pool > 0 and model_pool[math.random(#model_pool)] or nil

        local unit_key = CONST.NPC_UNIT_KEYS[npc_type] or CONST.NPC_UNIT_KEYS.civilian
        local npc = clicli.unit.create_unit(CONST.NPC_PLAYER, unit_key, spawn, math.random(0, 360))

        if model then
            npc:replace_model(model)
        end
        npc:set_attr('最大生命', 1, '基础')
        npc:set_hp(1)

        npc:add_tag('npc')
        npc:add_tag('npc_' .. npc_type)
        npc:storage_set('npc_type',    npc_type)
        npc:storage_set('model_key',   model)
        npc:storage_set('is_panicked', false)
        npc:storage_set('ai_state',    'idle')

        if (npc_type == 'patrol' or npc_type == 'civilian') and #roads > 0 then
            local road = roads[math.random(#roads)]
            npc:move_along_road(road, 1, false, true, true)
            npc:storage_set('assigned_road', road)
        end

        if npc_type == 'vendor' then
            npc:stop()
        end

        if npc_type == 'wanderer' then
            M.start_wander(npc, spawn)
        end

        table.insert(npc_list, npc)
    end
end

-- ---------------------------------------------------------
-- NPC类型权重
-- ---------------------------------------------------------
function M.pick_npc_type()
    local r = math.random(100)
    if r <= 50 then return 'civilian'
    elseif r <= 70 then return 'patrol'
    elseif r <= 85 then return 'vendor'
    else return 'wanderer'
    end
end

-- ---------------------------------------------------------
-- 收集地图中的Road
-- ---------------------------------------------------------
function M.collect_roads()
    local roads = {}
    local tags = { 'road_main_loop', 'road_patrol', 'road_alley', 'road_square' }
    for _, tag in ipairs(tags) do
        local found = clicli.road.get_path_areas_by_tag(tag)
        for _, r in ipairs(found) do
            table.insert(roads, r)
        end
    end
    if #roads == 0 then
        roads = CONST.ROAD_LIST
    end
    return roads
end

-- ---------------------------------------------------------
-- 闲逛AI:在区域内随机移动
-- ---------------------------------------------------------
function M.start_wander(npc, center_point)
    local function wander_step()
        if not npc:is_alive() then return end
        if npc:storage_get('is_panicked') then return end

        local target = center_point:get_random_point(15)
        npc:move_to_pos(target)

        clicli.timer.wait(math.random(5, 12), function()
            wander_step()
        end)
    end
    wander_step()
end

-- ---------------------------------------------------------
-- 恐慌触发
-- ---------------------------------------------------------
function M.trigger_panic(source_point, radius)
    local panic_area = clicli.area.create_circle_area(source_point, radius)
    local units_in   = panic_area:get_all_unit_in_area()
    panic_area:remove()

    for _, u in ipairs(units_in) do
        if u:has_tag('npc') and u:is_alive() then
            u:storage_set('is_panicked', true)

            local flee_point = M.calc_flee_point(u:get_point(), source_point, 20)
            u:move_to_pos(flee_point)

            local recover_time = math.random(8, 12)
            clicli.timer.wait(recover_time, function()
                if not u:is_alive() then return end
                u:storage_set('is_panicked', false)

                local road = u:storage_get('assigned_road')
                if road then
                    u:move_along_road(road, 1, false, true, true)
                else
                    local npc_type = u:storage_get('npc_type')
                    if npc_type == 'vendor' then
                        u:stop()
                    end
                end
            end)
        end
    end
end

function M.calc_flee_point(npc_pos, danger_pos, distance)
    local dx = npc_pos:get_x() - danger_pos:get_x()
    local dy = npc_pos:get_y() - danger_pos:get_y()
    local len = math.sqrt(dx * dx + dy * dy)
    if len < 0.01 then dx, dy = 1, 0; len = 1 end
    local nx = dx / len * distance
    local ny = dy / len * distance
    return clicli.point.create(npc_pos:get_x() + nx, npc_pos:get_y() + ny)
end

-- ---------------------------------------------------------
-- 缩减NPC数量(紧迫期)
-- ---------------------------------------------------------
function M.reduce_npc_count(ratio)
    local remove_count = math.floor(#npc_list * ratio)
    for _ = 1, remove_count do
        if #npc_list == 0 then break end
        local idx = math.random(#npc_list)
        local npc = npc_list[idx]
        if npc:is_alive() then
            npc:remove()
        end
        table.remove(npc_list, idx)
    end
end

function M.get_npc_list()
    return npc_list
end

return M