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
|