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