diff --git a/lua/background_npcs_core/classes/actor/sh_actor_base.lua b/lua/background_npcs_core/classes/actor/sh_actor_base.lua index b7522a47..ede11d0f 100644 --- a/lua/background_npcs_core/classes/actor/sh_actor_base.lua +++ b/lua/background_npcs_core/classes/actor/sh_actor_base.lua @@ -710,10 +710,8 @@ function BaseClass:SetState(state, data, forced) end end - if not forced and bgNPC:StateActionExists(state, 'validator') - and not self:CallStateAction(state, 'validator', state, data) - then - return + if not forced and bgNPC:StateActionExists(state, 'validator') and not self:CallStateAction(state, 'validator', state, data) then + return end local new_state, new_data = self:CallStateAction(state, 'pre_start', state, data) diff --git a/lua/background_npcs_core/classes/sh_node_class.lua b/lua/background_npcs_core/classes/sh_node_class.lua index 50b5f5a7..4382c5a7 100644 --- a/lua/background_npcs_core/classes/sh_node_class.lua +++ b/lua/background_npcs_core/classes/sh_node_class.lua @@ -2,16 +2,22 @@ local Vector = Vector local pairs = pairs local ipairs = ipairs local math_floor = math.floor -local math_modf = math.modf +-- local math_modf = math.modf local table_insert = table.insert local table_remove = table.remove +local table_RemoveValueBySeq = table.RemoveValueBySeq local table_Count = table.Count local table_HasValueBySeq = table.HasValueBySeq local util_TraceLine = util.TraceLine local util_TableToJSON = util.TableToJSON local util_JSONToTable = util.JSONToTable +local coroutine_yield = coroutine.yield -- -local SingleChunkSize = 500 +local is_infmap = slib.IsInfinityMap() +if is_infmap then + util_TraceLine = function(...) return util.TraceLine(...) end +end + local LimitPointAxisZ = GetConVar('bgn_point_z_limit'):GetInt() local CheckTraceSuccessToNodeUpperVector = Vector(0, 0, 10) local CheckTraceSuccessToNodeFilter = function(ent) @@ -20,11 +26,31 @@ end BGN_NODE = {} BGN_NODE.Map = {} +BGN_NODE.MapCount = 0 BGN_NODE.Chunks = {} +BGN_NODE.CHUNK_SIZE_X = is_infmap and 10000 or 2000 +BGN_NODE.CHUNK_SIZE_Y = is_infmap and 10000 or 2000 +BGN_NODE.CHUNK_SIZE_Z = is_infmap and 10000 or 2000 + +local CHUNK_CLASS = slib.Component('Chunks') +local MAP_CHUNKS = CHUNK_CLASS:Instance({ + chunk_size_x = BGN_NODE.CHUNK_SIZE_X, + chunk_size_y = BGN_NODE.CHUNK_SIZE_Y, + chunk_size_z = BGN_NODE.CHUNK_SIZE_Z, + no_check_is_in_world = true +}) + +if SERVER then + hook.Add('BGN_PreLoadRoutes', 'BGN_Nodes_ChunkGenerate', function() + if IsValid(MAP_CHUNKS) then return end + -- MAP_CHUNKS:SetConditionChunkTouchesTheWorld() + MAP_CHUNKS:MakeChunks() + end) +end cvars.AddChangeCallback('bgn_point_z_limit', function(convar_name, value_old, value_new) LimitPointAxisZ = value_new -end) +end, 'bgn_point_z_limit_change_callback') function BGN_NODE:Instance(position_value) local obj = {} @@ -37,6 +63,7 @@ function BGN_NODE:Instance(position_value) obj.parents = {} obj.links = {} obj.parent_distance = 250000 + obj.chunk = MAP_CHUNKS:GetChunkByVector(obj.position) function obj:_snet_getdata() local netobj = {} @@ -63,20 +90,22 @@ function BGN_NODE:Instance(position_value) function obj:AddParentNode(node) if self == node or table_HasValueBySeq(self.parents, node) then return end - table_insert(self.parents, node) - + local index = table_insert(self.parents, node) + self.parent_count = index if not node:HasParent(self) then node:AddParentNode(self) end end function obj:RemoveParentNode(node) if self == node then return end - for i = 1, #self.parents do + for i = 1, self.parent_count do local parentNode = self.parents[i] if parentNode == node then self:RemoveLink(parentNode) - table_remove(self.parents, i) + if table_remove(self.parents, i) then + self.parent_count = self.parent_count - 1 + end break end end @@ -87,7 +116,7 @@ function BGN_NODE:Instance(position_value) end function obj:ClearParents() - for i = 1, #BGN_NODE.Map do + for i = 1, BGN_NODE.MapCount do local node = BGN_NODE.Map[i] if node:HasParent(self) then @@ -145,7 +174,7 @@ function BGN_NODE:Instance(position_value) function obj:ClearLinks(linkType) if not self.links[linkType] then return end - for i = 1, #BGN_NODE.Map do + for i = 1, BGN_NODE.MapCount do local node = BGN_NODE.Map[i] if node:HasLink(self, linkType) then @@ -196,53 +225,77 @@ function BGN_NODE:Instance(position_value) function obj:RemoveFromMap() if self.index == -1 then return end - for i = 1, #BGN_NODE.Map do + for i = 1, BGN_NODE.MapCount do local node = BGN_NODE.Map[i] - if node ~= self and node:HasParent(self) then node:RemoveParentNode(self) end end - local chunkId = self:GetChunkID() - - if BGN_NODE.Chunks[chunkId] and table_HasValueBySeq(BGN_NODE.Chunks[chunkId], self.index) then - table_remove(BGN_NODE.Chunks[chunkId], self.index) + do + local node_chunk_id = self:GetChunkID() + local node_chunk = BGN_NODE.Chunks[node_chunk_id] + if node_chunk and table_HasValueBySeq(node_chunk, self.index) then + table_RemoveValueBySeq(node_chunk, self.index) + end end - table_remove(BGN_NODE.Map, self.index) + if table_remove(BGN_NODE.Map, self.index) then + BGN_NODE.MapCount = BGN_NODE.MapCount - 1 - for i = 1, #BGN_NODE.Map do - BGN_NODE.Map[i].index = i + for i = 1, BGN_NODE.MapCount do + local another_node = BGN_NODE.Map[i] + local another_node_chunk_id = another_node:GetChunkID() + local another_node_past_index = another_node.index + another_node.index = i + + if another_node_past_index ~= another_node.index then + local another_chunk = BGN_NODE.Chunks[another_node_chunk_id] + if another_chunk and table_HasValueBySeq(another_chunk, another_node_past_index) then + table_RemoveValueBySeq(another_chunk, another_node_past_index) + table_insert(another_chunk, another_node.index) + end + end + end end end - function obj:GetChunkID(chunkSize) - return BGN_NODE:GetChunkID(self.position, chunkSize) + function obj:GetChunkID() + if not self.chunk then return -1 end + return self.chunk.index end return obj end function BGN_NODE:GetChunkID(pos) - local x = pos.x - local y = pos.y - local xid = math_modf(x / SingleChunkSize) - local yid = math_modf(y / SingleChunkSize) - return xid .. yid + local chunk = MAP_CHUNKS:GetChunkByVector(pos) + if not chunk then return -1 end + return chunk.index +end + +function BGN_NODE:GetChunkManager() + return MAP_CHUNKS +end + +function BGN_NODE:GetChunkNodesCount(pos) + local chunkId = self:GetChunkID(pos) + local chunks = self.Chunks[chunkId] + if not chunks then return 0 end + return #chunks end function BGN_NODE:GetChunkNodes(pos) local chunkId = self:GetChunkID(pos) - local chunk = self.Chunks[chunkId] + local chunks = self.Chunks[chunkId] - if not chunk then return {} end + if not chunks then return {} end local nodes = {} local nodes_count = 0 - for i = 1, #chunk do - local node = self.Map[chunk[i]] + for i = 1, #chunks do + local node = self.Map[chunks[i]] if node then nodes_count = nodes_count + 1 @@ -256,6 +309,12 @@ end function BGN_NODE:AddNodeToMap(node) if not node then return end + local chunkId = node:GetChunkID() + if SERVER and chunkId == -1 then + slib.Warning('Node cannot exist outside of a chunk!') + return + end + local index if node.index ~= -1 then index = node.index @@ -265,7 +324,7 @@ function BGN_NODE:AddNodeToMap(node) node.index = index end - local chunkId = node:GetChunkID() + self.MapCount = self.MapCount + 1 self.Chunks[chunkId] = self.Chunks[chunkId] or {} if not table_HasValueBySeq(self.Chunks[chunkId], index) then @@ -278,17 +337,34 @@ function BGN_NODE:GetNodeByIndex(index) end function BGN_NODE:GetNodeByPos(pos) - for i = 1, #self.Map do + for i = 1, self.MapCount do if self.Map[i]:GetPos() == pos then return self.Map[i] end end end +function BGN_NODE:GetChunkNodesInRadius(pos, radius) + local nodes_in_chunk = BGN_NODE:GetChunkNodes(pos) + local nodes_in_radius = {} + local nodes_count = 0 + radius = radius ^ 2 + + for i = 1, #nodes_in_chunk do + local node = nodes_in_chunk[i] + if node:GetPos():DistToSqr(pos) <= radius then + nodes_count = nodes_count + 1 + nodes_in_radius[nodes_count] = node + end + end + + return nodes_in_radius +end + function BGN_NODE:GetNodesInRadius(pos, radius) local nodes_in_radius = {} local nodes_count = 0 - radius = radius * radius + radius = radius ^ 2 - for i = 1, #self.Map do + for i = 1, self.MapCount do local node = self.Map[i] if node:GetPos():DistToSqr(pos) <= radius then nodes_count = nodes_count + 1 @@ -299,8 +375,38 @@ function BGN_NODE:GetNodesInRadius(pos, radius) return nodes_in_radius end +function BGN_NODE:GetChunkNodesCountInRadius(pos, radius) + local nodes_in_chunk = BGN_NODE:GetChunkNodes(pos) + local nodes_count = 0 + radius = radius ^ 2 + + for i = 1, #nodes_in_chunk do + local node = nodes_in_chunk[i] + if node:GetPos():DistToSqr(pos) <= radius then + nodes_count = nodes_count + 1 + end + end + + return nodes_count +end + +function BGN_NODE:GetNodesCountInRadius(pos, radius) + local nodes_count = 0 + radius = radius ^ 2 + + for i = 1, self.MapCount do + local node = self.Map[i] + if node:GetPos():DistToSqr(pos) <= radius then + nodes_count = nodes_count + 1 + end + end + + return nodes_count +end + function BGN_NODE:ClearNodeMap() self.Map = {} + self.MapCount = 0 self.Chunks = {} end @@ -309,7 +415,7 @@ function BGN_NODE:GetNodeMap() end function BGN_NODE:CountNodesOnMap() - return #self.Map + return self.MapCount end function BGN_NODE:SetMap(map) @@ -322,6 +428,71 @@ function BGN_NODE:SetMap(map) self:FixOutsideMapNodes() end +function BGN_NODE:ExpandMap(map, fix_outside_map_nodes) + for i = 1, #map do + local node = map[i] + if node.index == -1 then + self:AddNodeToMap(node) + end + end + + if not isbool(fix_outside_map_nodes) and fix_outside_map_nodes == nil then + fix_outside_map_nodes = true + end + + if fix_outside_map_nodes then + self:FixOutsideMapNodes() + end +end + +function BGN_NODE:AutoLink(settings, is_async) + settings = settings or {} + + local async_current_pass = 0 + local nodes_count = self.MapCount + local yield + + if is_async then + yield = function() + async_current_pass = async_current_pass + 1 + if async_current_pass > 1 / slib.deltaTime then + async_current_pass = 0 + coroutine_yield() + end + end + end + + for i = 1, nodes_count do + if is_async then yield() end + + local node = self.Map[i] + if not node or (node.single_check and node.single_check_complete) then continue end + if node.single_check then node.single_check_complete = true end + + for k = 1, nodes_count do + if is_async then yield() end + + local another_node = self.Map[k] + if not another_node or node == another_node then continue end + if another_node.single_check and another_node.single_check_complete then continue end + + local another_node_pos = another_node:GetPos() + if node:CheckDistanceLimitToNode(another_node_pos) + and not another_node:HasParent(node) + and node:CheckHeightLimitToNode(another_node_pos) + and node:CheckTraceSuccessToNode(another_node_pos) + then + another_node:AddParentNode(node) + another_node:AddLink(node, 'walk') + end + end + end +end + +function BGN_NODE:AutoLinkAsync(settings) + self:AutoLink(settings, true) +end + function BGN_NODE:GetMap() return self.Map end @@ -332,11 +503,15 @@ function BGN_NODE:FixOutsideMapNodes() local remove_count = 0 local util_IsInWorld = util.IsInWorld - for i = #self.Map, 1, -1 do + for i = self.MapCount, 1, -1 do local node = self.Map[i] + if not node or node.is_in_world then continue end + if not util_IsInWorld(node:GetPos()) then remove_count = remove_count + 1 node:RemoveFromMap() + else + node.is_in_world = true end end diff --git a/lua/background_npcs_core/cvars/cl_cvars.lua b/lua/background_npcs_core/cvars/cl_cvars.lua index 67da204d..5fb9f58d 100644 --- a/lua/background_npcs_core/cvars/cl_cvars.lua +++ b/lua/background_npcs_core/cvars/cl_cvars.lua @@ -16,7 +16,10 @@ CreateConVar('bgn_cl_disable_self_halo_wanted', bgNPC.cvar.bgn_cl_disable_self_h { FCVAR_ARCHIVE }, 'Disable wanted halo only for your player model') CreateConVar('bgn_cl_draw_npc_path', bgNPC.cvar.bgn_cl_draw_npc_path, -{ FCVAR_ARCHIVE }, 'Draw the path of movement of the NPC.') +{ FCVAR_ARCHIVE }, 'Draw the path of movement of the NPC. 1 - enabled, 0 - disabled.') + +CreateConVar('bgn_cl_draw_chunks', bgNPC.cvar.bgn_cl_draw_chunks, +{ FCVAR_ARCHIVE }, 'Receives and displays chunks from the server. 1 - enabled, 0 - disabled. (Use command "bgn_cl_draw_chunks_reload" to retrieve chunks)') CreateConVar('bgn_cl_field_view_optimization', bgNPC.cvar.bgn_cl_field_view_optimization, { FCVAR_ARCHIVE }, 'Enable field of view optimization.') diff --git a/lua/background_npcs_core/cvars/sh_cvars.lua b/lua/background_npcs_core/cvars/sh_cvars.lua index ad61f2f2..7bf03f9b 100644 --- a/lua/background_npcs_core/cvars/sh_cvars.lua +++ b/lua/background_npcs_core/cvars/sh_cvars.lua @@ -8,6 +8,7 @@ bgNPC.cvar.bgn_max_npc = 35 bgNPC.cvar.bgn_enable_dynamic_nodes_only_when_mesh_not_exists = 1 bgNPC.cvar.bgn_dynamic_nodes = 1 bgNPC.cvar.bgn_dynamic_nodes_type = 'grid' +bgNPC.cvar.bgn_dynamic_nodes_save_progress = 0 bgNPC.cvar.bgn_spawn_radius = 3000 bgNPC.cvar.bgn_disable_logic_radius = 500 bgNPC.cvar.bgn_spawn_radius_visibility = 2500 @@ -49,6 +50,7 @@ bgNPC.cvar.bgn_tool_seat_offset_angle_x = 0 bgNPC.cvar.bgn_tool_seat_offset_angle_y = 0 bgNPC.cvar.bgn_tool_seat_offset_angle_z = 0 bgNPC.cvar.bgn_cl_draw_npc_path = 0 +bgNPC.cvar.bgn_cl_draw_chunks = 0 bgNPC.cvar.bgn_cl_field_view_optimization = 1 bgNPC.cvar.bgn_cl_field_view_optimization_range = 500 bgNPC.cvar.bgn_cl_ambient_sound = 1 @@ -101,7 +103,11 @@ scvar.Register('bgn_enable', bgNPC.cvar.bgn_enable, .Access(DefaultAccess) scvar.Register('bgn_debug', bgNPC.cvar.bgn_debug, - FCVAR_ARCHIVE, 'Turns on debug mode and prints additional information to the console.') + FCVAR_ARCHIVE, 'Turns on debug mode and prints additional information to the console. 1 - enabled, 0 - disabled.') + .Access(DefaultAccess) + +scvar.Register('bgn_dynamic_nodes_save_progress', bgNPC.cvar.bgn_dynamic_nodes_save_progress, + FCVAR_ARCHIVE, 'If enabled, the automatically-generated movement mesh will not be deleted with each new generation, 1 - enabled, 0 - disabled.') .Access(DefaultAccess) scvar.Register('bgn_fasted_teleport', bgNPC.cvar.bgn_fasted_teleport, diff --git a/lua/background_npcs_core/global/sh_find_path_service.lua b/lua/background_npcs_core/global/sh_find_path_service.lua index f1844d36..50fa117f 100644 --- a/lua/background_npcs_core/global/sh_find_path_service.lua +++ b/lua/background_npcs_core/global/sh_find_path_service.lua @@ -81,7 +81,7 @@ function bgNPC:FindWalkPath(startPos, endPos, limitIteration, pathType) local currentIteration = 0 local checkedNodes = {} local waitingNodes = {} - local closetNode = bgNPC:GetClosestPointToPointInChunk(startPos, endPos) + local closetNode = bgNPC:GetClosestPointInChunk(startPos) if not closetNode or not IsNotWorld(startPos, closetNode.position) then closetNode = bgNPC:GetClosestPointInRadius(startPos, 500) diff --git a/lua/background_npcs_core/modules/cl_version_checker.lua b/lua/background_npcs_core/modules/cl_version_checker.lua index 812a7883..a8b37f6c 100644 --- a/lua/background_npcs_core/modules/cl_version_checker.lua +++ b/lua/background_npcs_core/modules/cl_version_checker.lua @@ -1,6 +1,11 @@ local text_color_info = Color(61, 206, 217) +local text_color_red = Color(255, 0, 0) local text_command_color = Color(227, 209, 11) local text_version_color = Color(237, 153, 43) +local text_color_mod_title = Color(85, 180, 243) +local text_color_orange = Color(255, 196, 0) +local text_color_green = Color(30, 255, 0) +local text_color_version = Color(135, 196, 211) local function version_check() http.Fetch('https://raw.githubusercontent.com/Shark-vil/background-citizens/master/version.txt', @@ -43,33 +48,36 @@ local function version_check() if v_addon < v_github then - local text_color = Color(255, 196, 0) - chat.AddText(Color(255, 0, 0), '[ADMIN] ', - text_color, lang.msg_outdated, text_version_color, lang.actual_version, + chat.AddText(text_color_red, '[ADMIN] ', + text_color_mod_title, 'Background NPCs:\n', + text_color_orange, lang.msg_outdated, text_version_color, lang.actual_version, text_color_info, lang.update_page_1, - text_command_color, lang.command, text_color_info, lang.update_page_2) + text_command_color, lang.command, text_color_info, lang.update_page_2 .. '\n') elseif v_addon == v_github then - local text_color = Color(30, 255, 0) - chat.AddText(Color(255, 0, 0), '[ADMIN] ', - text_color, lang.msg_latest, text_version_color, lang.actual_version, + chat.AddText(text_color_red, '[ADMIN] ', + text_color_mod_title, 'Background NPCs:\n', + text_color_green, lang.msg_latest, text_version_color, lang.actual_version, text_color_info, lang.update_page_1, - text_command_color, lang.command, text_color_info, lang.update_page_2) + text_command_color, lang.command, text_color_info, lang.update_page_2 .. '\n') elseif v_addon > v_github then - local text_color = Color(30, 255, 0) - chat.AddText(Color(255, 0, 0), '[ADMIN] ', - text_color, lang.msg_dev, text_version_color, lang.actual_version, + chat.AddText(text_color_red, '[ADMIN] ', + text_color_mod_title, 'Background NPCs:\n', + text_color_green, lang.msg_dev, text_version_color, lang.actual_version, text_color_info, lang.update_page_1, - text_command_color, lang.command, text_color_info, lang.update_page_2) + text_command_color, lang.command, text_color_info, lang.update_page_2 .. '\n') end if v_storage ~= bgNPC.VERSION then - local text_color = Color(135, 196, 211) - chat.AddText(Color(255, 0, 0), '[ADMIN] ', text_color, lang.msg_upgrade, text_version_color, v_storage .. ' -> ' .. bgNPC.VERSION) + chat.AddText(text_color_red, '[ADMIN] ', + text_color_mod_title, 'Background NPCs:\n', + text_color_version, lang.msg_upgrade, + text_version_color, v_storage .. ' -> ' .. bgNPC.VERSION .. '\n') + file.Write('background_npcs/version.txt', bgNPC.VERSION) end end, diff --git a/lua/background_npcs_core/modules/debug/sh_draw_chunks.lua b/lua/background_npcs_core/modules/debug/sh_draw_chunks.lua new file mode 100644 index 00000000..b359736c --- /dev/null +++ b/lua/background_npcs_core/modules/debug/sh_draw_chunks.lua @@ -0,0 +1,56 @@ +if SERVER then + snet.Callback('BGN_Nodes_RequestChunksFromTheServer', function(ply, data) + local MAP_CHUNKS = BGN_NODE:GetChunkManager() + snet.Request('BGN_Nodes_SendChunksDataToClient', MAP_CHUNKS:GetChunks()) + .ProgressText('Loading chunks from server') + .Invoke(ply) + end).Protect() +else + local CHUNK_CLASS = slib.Component('Chunks') + local MAP_CHUNKS = CHUNK_CLASS:Instance() + + snet.RegisterCallback('BGN_Nodes_SendChunksDataToClient', function(_, data) + MAP_CHUNKS:SetChunks(data) + end) + + hook.Add('slib.FirstPlayerSpawn', 'BGN_Nodes_RequestChunksFromTheServer', function() + if not GetConVar('bgn_cl_draw_chunks'):GetBool() then return end + snet.InvokeServer('BGN_Nodes_RequestChunksFromTheServer') + end) + + cvars.AddChangeCallback('bgn_cl_draw_chunks', function(_, _, newValue) + if tonumber(newValue) == 1 then + snet.InvokeServer('BGN_Nodes_RequestChunksFromTheServer') + else + MAP_CHUNKS:SetChunks() + end + end, 'bgn_cl_draw_chunks_changed') + + concommand.Add('bgn_cl_draw_chunks_reload', function(ply) + if not ply:IsAdmin() and not ply:IsSuperAdmin() then return end + if not GetConVar('bgn_cl_draw_chunks'):GetBool() then return end + snet.InvokeServer('BGN_Nodes_RequestChunksFromTheServer') + end) + + local angle_zero = Angle() + local color_box_line = Color(255, 255, 255) + local color_box = Color(236, 154, 243, 50) + + hook.Add('PostDrawTranslucentRenderables', 'BGN_Nodes_ChunkRenderer', function() + if not IsValid(MAP_CHUNKS) then return end + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local chunk = MAP_CHUNKS:GetChunkByEntity(ply) + if chunk then + local center = chunk.center_pos + local mins = WorldToLocal(center, angle_zero, chunk.start_pos, angle_zero) + local maxs = WorldToLocal(center, angle_zero, chunk.end_pos, angle_zero) + + render.SetColorMaterial() + render.DrawWireframeBox(center, angle_zero, mins, maxs, color_box_line) + render.DrawBox(center, angle_zero, mins, maxs, color_box) + end + end) +end \ No newline at end of file diff --git a/lua/background_npcs_core/modules/dynamic_movement_mesh/cl_dynamic_movement_mesh.lua b/lua/background_npcs_core/modules/dynamic_movement_mesh/cl_dynamic_movement_mesh.lua new file mode 100644 index 00000000..8acf93b1 --- /dev/null +++ b/lua/background_npcs_core/modules/dynamic_movement_mesh/cl_dynamic_movement_mesh.lua @@ -0,0 +1,23 @@ +snet.RegisterCallback('bgn_dynamic_mevement_mesh_alert_message', function() + local _, lang_code = LocalPlayer():slibGetLanguage() + local text_color_red = Color(255, 0, 0) + local text_color = Color(255, 196, 0) + local text_color_mod_title = Color(85, 180, 243) + local text_color_green = Color(30, 255, 0) + local message, options_message + + if lang_code == 'ru' then + message = 'Dключен генератор сетки перемещения в реальном времени.\n' + message = message .. 'Если у вас возникли проблемы с производительностью, вы можете изменить настройки в спавнменю:\n' + options_message = 'Options > Background NPCs > Генерация\n' + else + message = 'Realtime movement mesh generator is enabled.\n' + message = message .. 'If you have performance problems, you can change the settings in the spawnmenu:\n' + options_message = 'Options > Background NPCs > Generations\n' + end + + chat.AddText(text_color_red, '[ADMIN] ', + text_color_mod_title, 'Background NPCs:\n', + text_color, message, + text_color_green, options_message .. '\n') +end) \ No newline at end of file diff --git a/lua/background_npcs_core/global/sv_dynamic_movement_mesh.lua b/lua/background_npcs_core/modules/dynamic_movement_mesh/sv_dynamic_movement_mesh.lua similarity index 51% rename from lua/background_npcs_core/global/sv_dynamic_movement_mesh.lua rename to lua/background_npcs_core/modules/dynamic_movement_mesh/sv_dynamic_movement_mesh.lua index 640a5071..8cc661ab 100644 --- a/lua/background_npcs_core/global/sv_dynamic_movement_mesh.lua +++ b/lua/background_npcs_core/modules/dynamic_movement_mesh/sv_dynamic_movement_mesh.lua @@ -1,32 +1,61 @@ +local map_name = game.GetMap() +local IsValid = IsValid +local file_Exists = file.Exists +local cvar_bgn_generator_restict = GetConVar('bgn_enable_dynamic_nodes_only_when_mesh_not_exists') +local cvar_bgn_dynamic_nodes = GetConVar('bgn_dynamic_nodes') + +local function MovementMeshExists() + local d, t = 'background_npcs/nodes/', 'DATA' + return file_Exists(d .. map_name .. '.dat', t) or file_Exists(d .. map_name .. '.json', t) +end + +local function IsEnableGenerator() + local restict = cvar_bgn_generator_restict:GetBool() + local enabled = cvar_bgn_dynamic_nodes:GetBool() + return enabled and IsValid(BGN_NODE:GetChunkManager()) and (not restict or not MovementMeshExists()) +end + +hook.Add('slib.FirstPlayerSpawn', 'BGN_MovementMeshGeneratorNotify', function(ply) + if not ply:IsAdmin() and not ply:IsSuperAdmin() then return end + if not IsEnableGenerator() then return end + ply:slibCreateTimer('bgn_dynamic_mevement_mesh_alert_message', 2, 1, function() + if not IsEnableGenerator() then return end + snet.Invoke('bgn_dynamic_mevement_mesh_alert_message', ply) + end) +end) + async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) - local cvar_bgn_generator_restict = GetConVar('bgn_enable_dynamic_nodes_only_when_mesh_not_exists') - local cvar_bgn_dynamic_nodes = GetConVar('bgn_dynamic_nodes') local cvar_bgn_dynamic_nodes_type = GetConVar('bgn_dynamic_nodes_type') local cvar_bgn_spawn_radius = GetConVar('bgn_spawn_radius') local cvar_bgn_runtime_generator_grid_offset = GetConVar('bgn_runtime_generator_grid_offset') + local cvar_bgn_dynamic_nodes_save_progress = GetConVar('bgn_dynamic_nodes_save_progress') local table_Combine = table.Combine - local table_shuffle = table.shuffle + local bit_band = bit.band + local util_PointContents = util.PointContents + local CONTENTS_WATER = CONTENTS_WATER local player_GetAll = player.GetAll local table_remove = table.remove - local map_name = game.GetMap() local math_random = math.random local util_IsInWorld = util.IsInWorld local util_TraceLine = util.TraceLine local math_Clamp = math.Clamp - local file_Exists = file.Exists local Vector = Vector - -- local math_floor = math.floor - local math_modf = math.modf - -- local FrameTime = FrameTime local add_z_axis = Vector(0, 0, 20) local cell_size = 200 + local sqrt_cell_size = cell_size ^ 2 local add_endpos_trace_vector = Vector(0, 0, 1000) - local chunks = {} - local chunk_size = 250 - local max_points_in_chunk = 5 + local map_points = {} + local points_count = 0 local current_pass = 0 + local is_infmap = slib.IsInfinityMap() + local infO_message_for_admins_has_send = true + if is_infmap then + util_IsInWorld = function(...) return util.IsInWorld(...) end + util_TraceLine = function(...) return util.TraceLine(...) end + util_PointContents = function(...) return util.PointContents(...) end + end local trace_filter = function(ent) - if ent:IsWorld() or ent:GetClass() == 'infmap_terrain_collider' then + if IsValid(ent) and (ent:IsWorld() or (is_infmap and select(1, ent:GetClass():find('collider')) ~= nil)) then return true end end @@ -39,57 +68,56 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) end end - local function GetChunkId(pos) - local xid = math_modf(pos.x / chunk_size) - local yid = math_modf(pos.y / chunk_size) - local zid = math_modf(pos.z / chunk_size) - return xid .. yid .. zid - end - - local function MovementMeshExists() - if file_Exists('background_npcs/nodes/' .. map_name .. '.dat', 'DATA') then - return true - elseif file_Exists('background_npcs/nodes/' .. map_name .. '.json', 'DATA') then + local function ChunkHasFull(point_position) + if BGN_NODE:GetChunkNodesCountInRadius(point_position, cell_size) >= 1 then + PassYield() return true end - return false - end - - local function ChunkHasFull(point_position) - local point_chunk_id = GetChunkId(point_position) - local max_points = max_points_in_chunk - if cvar_bgn_dynamic_nodes_type:GetString() ~= 'grid' then - max_points = 2 + local point_chunk_id = BGN_NODE:GetChunkID(point_position) + if point_chunk_id == -1 then + PassYield() + return true end - if chunks[point_chunk_id] and chunks[point_chunk_id] >= max_points then - return true + for i = 1, points_count do + local node = map_points[i] + if node and node.chunk and node.chunk.index == point_chunk_id and node:GetPos():DistToSqr(point_position) <= sqrt_cell_size then + PassYield() + return true + end + PassYield() end - return false - end - local function UpdateChunkPointsCount(point_position) - local point_chunk_id = GetChunkId(point_position) - chunks[point_chunk_id] = chunks[point_chunk_id] or 0 - chunks[point_chunk_id] = chunks[point_chunk_id] + 1 + return false end while true do - local restict = cvar_bgn_generator_restict:GetBool() - local enabled = cvar_bgn_dynamic_nodes:GetBool() - - if not enabled or (restict and MovementMeshExists()) then + if not IsEnableGenerator() then + if infO_message_for_admins_has_send then infO_message_for_admins_has_send = false end wait(1) else - local map_points = {} - local points_count = 0 + if not infO_message_for_admins_has_send then + infO_message_for_admins_has_send = true + local admins = {} + for _, v in ipairs(player_GetAll()) do + if IsValid(v) and (v:IsAdmin() or v:IsSuperAdmin()) then + table.insert(admins, v) + end + end + if #admins ~= 0 then + snet.Invoke('bgn_dynamic_mevement_mesh_alert_message', admins) + end + end + map_points = {} + points_count = 0 + local is_dynamic_nodes_save = cvar_bgn_dynamic_nodes_save_progress:GetBool() local expensive_generator = cvar_bgn_dynamic_nodes_type:GetString() == 'grid' local radius = cvar_bgn_spawn_radius:GetFloat() - local players = table_shuffle(player_GetAll()) - chunks = {} + local players = player_GetAll() current_pass = 0 cell_size = cvar_bgn_runtime_generator_grid_offset:GetInt() + sqrt_cell_size = cell_size ^ 2 for i = #players, 1, -1 do local ply = players[i] @@ -101,17 +129,18 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) yield() + -- print('Start generate new nodes...') + if not expensive_generator then - -- for player_index = 1, math_Clamp(#players, 0, 4) do for player_index = 1, #players do local ply = players[player_index] if not ply then continue end local center = ply:LocalToWorld(ply:OBBCenter()) local current_radius_randomize = cell_size - local z = center.z + 50 + local z = center.z + 100 - for i = 1, radius do + for i = 1, 1000 do current_radius_randomize = math_Clamp(current_radius_randomize, 0, radius) local x = center.x + math_random(-radius, radius) @@ -128,23 +157,32 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) continue end + PassYield() + local new_point_position = tr.HitPos + add_z_axis + if bit_band(util_PointContents(new_point_position), CONTENTS_WATER) == CONTENTS_WATER then + PassYield() + continue + end + + PassYield() if not util_IsInWorld(new_point_position) or ChunkHasFull(new_point_position) then PassYield() continue end + local node = BGN_NODE:Instance(new_point_position) + if is_dynamic_nodes_save then + node.single_check = true + end + points_count = points_count + 1 - map_points[points_count] = BGN_NODE:Instance(new_point_position) + map_points[points_count] = node + current_radius_randomize = current_radius_randomize + cell_size - UpdateChunkPointsCount(new_point_position) PassYield() - - current_radius_randomize = current_radius_randomize + cell_size end - - PassYield() end else local generator_iterations = 0 @@ -157,9 +195,7 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) local center = ply:LocalToWorld(ply:OBBCenter()) local x_offset = 0 - local y = center.y - local z = center.z + 50 - local start_point_vector = Vector(center.x, y, z) + local start_point_vector = center + Vector(0, 0, 100) while start_point_vector:DistToSqr(center) <= sqr_radius do if calc_iterations then @@ -170,11 +206,7 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) local x = k == 1 and center.x + x_offset or center.x - x_offset start_point_vector.x = x - local different_start_point = start_point_vector + Vector(0, 0, math_random(0, 100)) - if not util_IsInWorld(different_start_point) then - different_start_point = start_point_vector - end - + local different_start_point = start_point_vector if not util_IsInWorld(different_start_point) then PassYield() continue @@ -191,17 +223,25 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) continue end - points_count = points_count + 1 + PassYield() local new_point_position = tr.HitPos + add_z_axis + if bit_band(util_PointContents(new_point_position), CONTENTS_WATER) == CONTENTS_WATER then + PassYield() + continue + end + + PassYield() + if ChunkHasFull(new_point_position) then PassYield() continue end - map_points[points_count] = BGN_NODE:Instance(new_point_position) + points_count = points_count + 1 - UpdateChunkPointsCount(new_point_position) + local new_node = BGN_NODE:Instance(new_point_position) + map_points[points_count] = new_node PassYield() end @@ -217,22 +257,21 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) local y_points_count = 0 for node_index = 1, points_count do - local center = map_points[node_index]:GetPos() + local node = map_points[node_index] + if is_dynamic_nodes_save and node_index < points_count - 1 then + node.single_check = true + end + + local center = node:GetPos() local y_offset = 0 - local x = center.x - local z = center.z + 50 - local start_point_vector = Vector(x, 0, z) + local start_point_vector = Vector(center.x, 0, center.z) + Vector(0, 0, 100) for i = 1, generator_iterations do for k = 1, 2 do local y = k == 1 and center.y + y_offset or center.y - y_offset start_point_vector.y = y - local different_start_point = start_point_vector + Vector(0, 0, math_random(0, 100)) - if not util_IsInWorld(different_start_point) then - different_start_point = start_point_vector - end - + local different_start_point = start_point_vector if not util_IsInWorld(different_start_point) then PassYield() continue @@ -249,17 +288,28 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) continue end - y_points_count = y_points_count + 1 + PassYield() local new_point_position = tr.HitPos + add_z_axis + if bit_band(util_PointContents(new_point_position), CONTENTS_WATER) == CONTENTS_WATER then + PassYield() + continue + end + + PassYield() + if ChunkHasFull(new_point_position) then PassYield() continue end - y_axis_points[y_points_count] = BGN_NODE:Instance(new_point_position) + local new_node = BGN_NODE:Instance(new_point_position) + if is_dynamic_nodes_save and i < generator_iterations - 1 then + new_node.single_check = true + end - UpdateChunkPointsCount(new_point_position) + y_points_count = y_points_count + 1 + y_axis_points[y_points_count] = new_node PassYield() end @@ -272,44 +322,15 @@ async.AddDedic('bgNPC_MovementMapDynamicGenerator', function(yield, wait) points_count = #map_points end - local custom_current_pass = 0 - - for point_index = 1, points_count do - local node = map_points[point_index] - if not node then continue end - - for another_point_index = 1, points_count do - local anotherNode = map_points[another_point_index] - if not anotherNode or anotherNode == node then continue end - - local pos = anotherNode:GetPos() - if node:CheckDistanceLimitToNode(pos) - and not anotherNode:HasParent(node) - and node:CheckHeightLimitToNode(pos) - and node:CheckTraceSuccessToNode(pos) - then - anotherNode:AddParentNode(node) - anotherNode:AddLink(node, 'walk') - PassYield() - end - - custom_current_pass = custom_current_pass + 1 - if custom_current_pass >= 2500 then - custom_current_pass = 0 - yield() - end - end - - -- print(point_index, ' / ', points_count) - - -- PassYield() + if cvar_bgn_dynamic_nodes_save_progress:GetBool() then + BGN_NODE:ExpandMap(map_points) + BGN_NODE:AutoLinkAsync() + wait(1) + else + BGN_NODE:SetMap(map_points) + BGN_NODE:AutoLink() + wait(5) end - - -- player.GetAll()[1]:ChatPrint('New mesh generated' .. ' ' .. tostring(CurTime())) - - BGN_NODE:SetMap(map_points) - - wait(5) end end end) \ No newline at end of file diff --git a/lua/background_npcs_core/modules/states/sv_dialogue.lua b/lua/background_npcs_core/modules/states/sv_dialogue.lua index 8d5327be..9f8f3e15 100644 --- a/lua/background_npcs_core/modules/states/sv_dialogue.lua +++ b/lua/background_npcs_core/modules/states/sv_dialogue.lua @@ -78,7 +78,7 @@ function ASSET:SetDialogue(actor1, actor2) end local index = table.insert(dialogue_actors, { - id = tostring(CurTime()) .. tostring(RealTime()) .. actor1:GetType() .. actor2:GetType(), + id = slib.UUID(), interlocutors = { [1] = actor1, [2] = actor2, diff --git a/lua/background_npcs_core/states/sv_walk.lua b/lua/background_npcs_core/states/sv_walk.lua index 09415bf5..d3fc8f6f 100644 --- a/lua/background_npcs_core/states/sv_walk.lua +++ b/lua/background_npcs_core/states/sv_walk.lua @@ -3,13 +3,17 @@ local math_random = math.random local CurTime = CurTime local slib_chance = slib.chance local table_RandomBySeq = table.RandomBySeq +local cvar_bgn_spawn_radius = GetConVar('bgn_spawn_radius') +local math_Clamp = math.Clamp local function GetRandomFoundPointDistance() + local result = 0 if slib_chance(30) then - return math_random(slib_chance(30) and 100 or 500, 2500) + result = math_random(slib_chance(30) and 100 or 500, 2500) else - return math_random(slib_chance(30) and 500 or 1000, 2500) + result = math_random(slib_chance(30) and 500 or 1000, 2500) end + return math_Clamp(result, 0, cvar_bgn_spawn_radius:GetInt()) end local function GetRandomDelayForUpdateWalkTarget() @@ -20,7 +24,7 @@ local function GetNextTargetNode(actor) local dist = GetRandomFoundPointDistance() local points = bgNPC:GetAllPointsInRadius(actor:GetNPC():GetPos(), dist, 'walk') - if not point or #points == 0 then + if not points or #points == 0 then points = bgNPC:GetAllPoints('walk') end diff --git a/lua/background_npcs_core/tool_options/cl_generation_settings.lua b/lua/background_npcs_core/tool_options/cl_generation_settings.lua index 8e34466b..4f2d620a 100644 --- a/lua/background_npcs_core/tool_options/cl_generation_settings.lua +++ b/lua/background_npcs_core/tool_options/cl_generation_settings.lua @@ -11,6 +11,12 @@ local function TOOL_MENU(panel) ['Help'] = true, }) + panel:AddControl('CheckBox', { + ['Label'] = '#bgn.settings.spawn.bgn_dynamic_nodes_save_progress', + ['Command'] = 'bgn_dynamic_nodes_save_progress', + ['Help'] = true, + }) + panel:AddControl('ListBox', { ['Label'] = '#bgn.settings.spawn.bgn_dynamic_nodes_type', ['Command'] = 'bgn_dynamic_nodes_type', diff --git a/lua/background_npcs_core/tool_options/lang/en/cl_spawn.lua b/lua/background_npcs_core/tool_options/lang/en/cl_spawn.lua index 1dd19247..4ca996b0 100644 --- a/lua/background_npcs_core/tool_options/lang/en/cl_spawn.lua +++ b/lua/background_npcs_core/tool_options/lang/en/cl_spawn.lua @@ -4,6 +4,8 @@ return { ['bgn.settings.spawn.bgn_dynamic_nodes'] = 'Dynamic movement mesh', ['bgn.settings.spawn.bgn_dynamic_nodes.help'] = 'Allows you to generate a movement mesh automatically around the players. This is useful when the map does not have a movement mesh file. Keep in mind that this is a resource intensive process. It is recommended to create a movement mesh yourself, or generate one if the map has AI NavMesh.', + ['bgn.settings.spawn.bgn_dynamic_nodes_save_progress'] = 'Save generation progress', + ['bgn.settings.spawn.bgn_dynamic_nodes_save_progress.help'] = 'If enabled, the generator will not clear nodes with each new generation. Instead, the movement mesh will expand as players move around the map. The already generated zones will not change.', ['bgn.settings.spawn.bgn_dynamic_nodes_restict'] = 'Dynamic movement mesh constraint', ['bgn.settings.spawn.bgn_dynamic_nodes_restict.help'] = 'If enabled, the mesh will be generated only when there is no movement mesh file on the map. Otherwise, automatic generation will always be used.', ['bgn.settings.spawn.bgn_dynamic_nodes_type'] = 'Tim dynamic mesh', diff --git a/lua/background_npcs_core/tool_options/lang/ru/cl_spawn.lua b/lua/background_npcs_core/tool_options/lang/ru/cl_spawn.lua index 7fe1bb72..71828ff3 100644 --- a/lua/background_npcs_core/tool_options/lang/ru/cl_spawn.lua +++ b/lua/background_npcs_core/tool_options/lang/ru/cl_spawn.lua @@ -4,6 +4,8 @@ return { ['bgn.settings.spawn.bgn_dynamic_nodes'] = 'Динамическая сетка передвижения', ['bgn.settings.spawn.bgn_dynamic_nodes.help'] = 'Позволяет генерировать сетку передвижения автоматически вокруг игроков. Это полезно, когда у карты нету файла сетки передвижения. Обратите внимание, что это ресурсозатратный процесс. Рекомендуется создать сетку передвижения самому, или сгенерировать, если на карте есть AI NavMesh.', + ['bgn.settings.spawn.bgn_dynamic_nodes_save_progress'] = 'Сохранять прогресс генерации', + ['bgn.settings.spawn.bgn_dynamic_nodes_save_progress.help'] = 'Если включено, то генератор не будет очищать ноды при кажой новой генерации. Вместо этого сетка будет расширяться по мере передвижения игроков по карте. Уже сгенерированные зоны меняться не будут.', ['bgn.settings.spawn.bgn_dynamic_nodes_restict'] = 'Ограничение динамической сетки передвижения', ['bgn.settings.spawn.bgn_dynamic_nodes_restict.help'] = 'Если включено, то сетка будет генерироваться только тогда, когда на карте нету файла сетки передвижения. В противном случае всегда будет использоваться автоматическая генерация.', ['bgn.settings.spawn.bgn_dynamic_nodes_type'] = 'Тим динамической сетки', diff --git a/lua/slib_autoloader/sh_background_npcs.lua b/lua/slib_autoloader/sh_background_npcs.lua index 0052d7fb..d6b6157c 100644 --- a/lua/slib_autoloader/sh_background_npcs.lua +++ b/lua/slib_autoloader/sh_background_npcs.lua @@ -14,7 +14,7 @@ if SERVER then end bgNPC = {} -bgNPC.VERSION = '1.9.9' +bgNPC.VERSION = '1.10.0' -- Do not change ------------- bgNPC.LANGUAGES = {} @@ -93,16 +93,18 @@ local function ExecutableScripts() script:using('global/sh_states.lua') script:using('global/sh_find_path_service.lua') -- script:using('global/sv_pre_spawn_cache.lua') - script:using('global/sv_dynamic_movement_mesh.lua') script:using('global/sv_peaceful_mode.lua') script:using('global/sv_relationship.lua') script:using('modules/cl_updatepage.lua') script:using('modules/cl_render_optimization.lua') + script:using('modules/dynamic_movement_mesh/sv_dynamic_movement_mesh.lua') + script:using('modules/dynamic_movement_mesh/cl_dynamic_movement_mesh.lua') script:using('modules/sv_run_logic_optimization.lua') script:using('modules/debug/cl_render_target_path.lua') script:using('modules/debug/sv_movement_render.lua') script:using('modules/debug/cl_movement_render.lua') + script:using('modules/debug/sh_draw_chunks.lua') script:using('modules/sv_npc_look_at_object.lua') script:using('modules/sv_player_look_at_object.lua') script:using('modules/sv_static_animation_controller.lua') diff --git a/lua/weapons/gmod_tool/stools/bgn_point_editor.lua b/lua/weapons/gmod_tool/stools/bgn_point_editor.lua index 30f515b6..0e2247a3 100644 --- a/lua/weapons/gmod_tool/stools/bgn_point_editor.lua +++ b/lua/weapons/gmod_tool/stools/bgn_point_editor.lua @@ -19,6 +19,10 @@ local ipairs = ipairs local pairs = pairs local util_TraceLine = util.TraceLine local table_insert = table.insert +local is_infmap = slib.IsInfinityMap() +if is_infmap then + util_TraceLine = function(...) return util.TraceLine(...) end +end local render_SetColorMaterial, render_DrawSphere, render_DrawLine, draw_SimpleTextOutlined, cam_Start3D2D, cam_End3D2D, surface_SetFont, surface_SetTextColor, surface_SetTextPos, surface_DrawText diff --git a/version.txt b/version.txt index c70654dd..ed21137e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.9 \ No newline at end of file +1.10.0 \ No newline at end of file