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