From 67d9a27144ef8fe24d34bad1c75bddb92eda78c5 Mon Sep 17 00:00:00 2001 From: Earu <20880060+Earu@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:39:52 +0200 Subject: [PATCH 1/6] resume/finish the work of @Python1320 on pcf parser and making pac3 lazy load pcf particles Co-authored-by: Python1320 --- lua/pac3/core/client/hooks.lua | 5 -- lua/pac3/core/client/init.lua | 1 + lua/pac3/core/client/parts/effect.lua | 85 ++------------------- lua/pac3/core/server/effects.lua | 59 --------------- lua/pac3/core/server/init.lua | 1 - lua/pac3/core/shared/init.lua | 12 +++ lua/pac3/extra/client/init.lua | 1 + lua/pac3/extra/client/map_outfit.lua | 25 ++++++ lua/pac3/extra/server/map_outfit.lua | 11 ++- lua/pac3/libraries/pcfparser.lua | 105 ++++++++++++++++++++++++++ lua/pac3/libraries/pcfprovider.lua | 89 ++++++++++++++++++++++ 11 files changed, 249 insertions(+), 145 deletions(-) delete mode 100644 lua/pac3/core/server/effects.lua create mode 100644 lua/pac3/extra/client/map_outfit.lua create mode 100644 lua/pac3/libraries/pcfparser.lua create mode 100644 lua/pac3/libraries/pcfprovider.lua diff --git a/lua/pac3/core/client/hooks.lua b/lua/pac3/core/client/hooks.lua index 49cb5e2ab..c81f7ab7b 100644 --- a/lua/pac3/core/client/hooks.lua +++ b/lua/pac3/core/client/hooks.lua @@ -195,8 +195,3 @@ pac.AddHook("pac_PlayerFootstep", "events", function(ply, pos, snd, vol) return true end end) - -net.Receive("pac_effect_precached", function() - local name = net.ReadString() - pac.CallHook("EffectPrecached", name) -end) diff --git a/lua/pac3/core/client/init.lua b/lua/pac3/core/client/init.lua index ba7104752..21aaa0264 100644 --- a/lua/pac3/core/client/init.lua +++ b/lua/pac3/core/client/init.lua @@ -48,6 +48,7 @@ pac.animations = include("pac3/libraries/animations.lua") include("pac3/core/shared/init.lua") +pac.pcfprovider = include("pac3/libraries/pcfprovider.lua") pac.urltex = include("pac3/libraries/urltex.lua") include("parts.lua") diff --git a/lua/pac3/core/client/parts/effect.lua b/lua/pac3/core/client/parts/effect.lua index 6e50bd192..b5adb2a44 100644 --- a/lua/pac3/core/client/parts/effect.lua +++ b/lua/pac3/core/client/parts/effect.lua @@ -4,11 +4,11 @@ local ParticleEffect = ParticleEffect local BUILDER, PART = pac.PartTemplate("base_drawable") PART.ClassName = "effect" -PART.Groups = {'effects', 'model', 'entity'} -PART.Icon = 'icon16/wand.png' +PART.Groups = {"effects", "model", "entity"} +PART.Icon = "icon16/wand.png" BUILDER:StartStorableVars() - BUILDER:GetSet("Effect", "default", {enums = function() return pac.particle_list end}) + BUILDER:GetSet("Effect", "default", {enums = function() return pac.pcfprovider.GetParticleList() end}) BUILDER:GetSet("Loop", true) BUILDER:GetSet("Follow", true) BUILDER:GetSet("Rate", 1, {editor_sensitivity = 0.1}) @@ -24,7 +24,7 @@ BUILDER:EndStorableVars() BUILDER:RemoveProperty("Translucent") PART.Translucent = false -- otherwise OnDraw won't be called -local BaseClass_GetOwner = PART.GetOwner +--local BaseClass_GetOwner = PART.GetOwner function PART:GetNiceName() return pac.PrettifyName(self:GetEffect()) @@ -32,89 +32,18 @@ end function PART:Initialize() self:SetEffect(self.Effect) - - if not pac.particle_list then - local found = {} - - for file_name in pairs(pac_loaded_particle_effects) do - local ok, err = pcall(function() - local data = file.Read("particles/"..file_name, "GAME", "b") - if data then - for str in data:gmatch("\3%c([%a_]+)%c") do - if #str > 1 then - found[str] = str - end - end - end - end) - - if not ok then - local msg = "unable to parse particle file " .. file_name .. ": " .. err - self:SetError(msg) - pac.Message(Color(255, 50, 50), msg) - end - end - - pac.particle_list = found - end end PART.last_spew = 0 -if not pac_loaded_particle_effects then - pac_loaded_particle_effects = {} - - for _, file_name in pairs(file.Find("particles/*.pcf", "GAME")) do - if not pac_loaded_particle_effects[file_name] and not pac.BlacklistedParticleSystems[file_name:lower()] then - game.AddParticles("particles/" .. file_name) - end - - pac_loaded_particle_effects[file_name] = true - end -end - -local already = {} -local alreadyServer = {} -local function pac_request_precache(name) - if already[name] then return end - already[name] = true - PrecacheParticleSystem(name) - net.Start("pac_request_precache") - net.WriteString(name) - net.SendToServer() -end - function PART:SetEffect(name) - self.waitingForServer = true self.Effect = name - self.Ready = alreadyServer[name] or false - - if not alreadyServer[name] then - pac_request_precache(name) - else - self.waitingForServer = false - end -end - -pac.AddHook("pac_EffectPrecached", "pac_Effects", function(name) - if alreadyServer[name] then return end - alreadyServer[name] = true - pac.dprint("effect %q precached!", name) - pac.CallRecursiveOnAllParts("OnEffectPrecached", name) -end) - -function PART:OnEffectPrecached(name) - if self.Effect == name then - self.Ready = true - self.waitingForServer = false - end + self.Ready = false end function PART:OnDraw() if not self.Ready then - if not self.waitingForServer then - self:SetEffect(self.Effect) - end + self.Ready = pac.pcfprovider.LoadEffect(self.Effect) return end @@ -143,7 +72,7 @@ function PART:OnHide() end function PART:OnShow(from_rendering) - if from_rendering then + if from_rendering and self.Ready then self:Emit(self:GetDrawPosition()) end end diff --git a/lua/pac3/core/server/effects.lua b/lua/pac3/core/server/effects.lua deleted file mode 100644 index cff75842a..000000000 --- a/lua/pac3/core/server/effects.lua +++ /dev/null @@ -1,59 +0,0 @@ -pac.EffectsBlackList = -{ - "frozen_steam", - "portal_rift_01", - "explosion_silo", - "citadel_shockwave_06", - "citadel_shockwave", - "choreo_launch_rocket_start", - "choreo_launch_rocket_jet", -} - -if not pac_loaded_particle_effects then - pac_loaded_particle_effects = {} - local files = file.Find("particles/*.pcf", "GAME") - - for key, file_name in pairs(files) do - if not pac_loaded_particle_effects[file_name] and not pac.BlacklistedParticleSystems[file_name:lower()] then - game.AddParticles("particles/" .. file_name) - end - - pac_loaded_particle_effects[file_name] = true - end - - pac.Message('Loaded total ', #files, ' particle systems') -end - -util.AddNetworkString("pac_effect_precached") -util.AddNetworkString("pac_request_precache") - -function pac.PrecacheEffect(name) - PrecacheParticleSystem(name) - net.Start("pac_effect_precached") - net.WriteString(name) - net.Broadcast() -end - -local queue = {} -net.Receive("pac_request_precache", function(len, pl) - local name = net.ReadString() - if table.HasValue(pac.EffectsBlackList, name) then return end - - -- Each player gets a 50 length queue - local plqueue = queue[pl] - if plqueue then - if #plqueue<50 then plqueue[#plqueue+1] = name end - else - plqueue = {name} - queue[pl] = plqueue - local function processQueue() - if #plqueue == 0 then - queue[pl] = nil - else - timer.Simple(0.5, processQueue) - pac.PrecacheEffect(table.remove(plqueue,1)) - end - end - processQueue() - end -end) diff --git a/lua/pac3/core/server/init.lua b/lua/pac3/core/server/init.lua index 28f092079..e807965b2 100644 --- a/lua/pac3/core/server/init.lua +++ b/lua/pac3/core/server/init.lua @@ -22,7 +22,6 @@ include("util.lua") include("pac3/core/shared/init.lua") -include("effects.lua") include("event.lua") include("net_messages.lua") include("test_suite_backdoor.lua") diff --git a/lua/pac3/core/shared/init.lua b/lua/pac3/core/shared/init.lua index 382905062..746cfec18 100644 --- a/lua/pac3/core/shared/init.lua +++ b/lua/pac3/core/shared/init.lua @@ -7,6 +7,7 @@ include("entity_mutator.lua") include("hash.lua") pac.StringStream = include("pac3/libraries/string_stream.lua") +pac.pcfparser = include("pac3/libraries/pcfparser.lua") CreateConVar("pac_sv_draw_distance", 0, CLIENT and FCVAR_REPLICATED or bit.bor(FCVAR_REPLICATED, FCVAR_ARCHIVE)) @@ -63,3 +64,14 @@ do pac.BlacklistedParticleSystems[val] = true end end + +pac.EffectsBlackList = +{ + frozen_steam = true, + portal_rift_01 = true, + explosion_silo = true, + citadel_shockwave_06 = true, + citadel_shockwave = true, + choreo_launch_rocket_start = true, + choreo_launch_rocket_jet = true, +} diff --git a/lua/pac3/extra/client/init.lua b/lua/pac3/extra/client/init.lua index e882db6e9..f6fb40085 100644 --- a/lua/pac3/extra/client/init.lua +++ b/lua/pac3/extra/client/init.lua @@ -3,5 +3,6 @@ pacx = pacx or {} include("pac3/extra/shared/init.lua") include("contraption.lua") +include("map_outfit.lua") include("pac2_compat.lua") include("wire_expression_extension.lua") \ No newline at end of file diff --git a/lua/pac3/extra/client/map_outfit.lua b/lua/pac3/extra/client/map_outfit.lua new file mode 100644 index 000000000..08b7bbe5e --- /dev/null +++ b/lua/pac3/extra/client/map_outfit.lua @@ -0,0 +1,25 @@ +local WAIT_INTERVAL = 0.1 +local WAIT_ATTEMPTS = 50 + +local pending = 0 + +net.Receive("pac_map_outfit_effect", function() + local index = net.ReadUInt(16) + local effect = net.ReadString() + + pending = pending + 1 + local timer_name = "pac_map_outfit_effect_" .. pending + + timer.Create(timer_name, WAIT_INTERVAL, WAIT_ATTEMPTS, function() + local ent = Entity(index) + if not ent:IsValid() then return end + + timer.Remove(timer_name) + + pac.pcfprovider.StepIndex() + + if pac.pcfprovider.LoadEffect(effect) then + ParticleEffectAttach(effect, PATTACH_ABSORIGIN_FOLLOW, ent, 0) + end + end) +end) diff --git a/lua/pac3/extra/server/map_outfit.lua b/lua/pac3/extra/server/map_outfit.lua index 9773f4532..85e630687 100644 --- a/lua/pac3/extra/server/map_outfit.lua +++ b/lua/pac3/extra/server/map_outfit.lua @@ -1,5 +1,7 @@ pacx.SpawnedMapEntities = pacx.SpawnedMapEntities or {} +util.AddNetworkString("pac_map_outfit_effect") + local VEC0 = Vector(0, 0, 0) local ANG0 = Angle(0, 0, 0) local VEC1 = Vector(1, 1, 1) @@ -93,8 +95,13 @@ local spawn_handlers = { end, effect = function(part, parent) - if parent:IsValid() and part.data.Effect then - ParticleEffectAttach(part.data.Effect, PATTACH_ABSORIGIN_FOLLOW, parent, 0) + local effect = part.self.Effect + + if parent:IsValid() and effect and effect ~= "" then + net.Start("pac_map_outfit_effect") + net.WriteUInt(parent:EntIndex(), 16) + net.WriteString(effect) + net.Broadcast() end end, } diff --git a/lua/pac3/libraries/pcfparser.lua b/lua/pac3/libraries/pcfparser.lua new file mode 100644 index 000000000..b04f71c65 --- /dev/null +++ b/lua/pac3/libraries/pcfparser.lua @@ -0,0 +1,105 @@ +local pcfparser = {} + +local DEFINITION_TYPE = "DmeParticleSystemDefinition" + +local function read_u16(data, pos) + local a, b = data:byte(pos, pos + 1) + if not b then return end + return a + b * 256, pos + 2 +end + +local function read_u32(data, pos) + local a, b, c, d = data:byte(pos, pos + 3) + if not d then return end + return a + b * 256 + c * 65536 + d * 16777216, pos + 4 +end + +local function read_string(data, pos) + local stop = data:find("\0", pos, true) + if not stop then return end + return data:sub(pos, stop - 1), stop + 1 +end + +function pcfparser.ParseEffectNames(data) + local names = {} + if not data then return names end + + local header_start = data:find("", header_start, true) + if not version or not header_end or version > 5 then return names end + + local pos = header_end + 6 + local dict = {} + + if version >= 2 then + local count + if version <= 3 then + count, pos = read_u16(data, pos) + else + count, pos = read_u32(data, pos) + end + if not count then return names end + + for i = 0, count - 1 do + dict[i], pos = read_string(data, pos) + if not pos then return names end + end + end + + local element_count + element_count, pos = read_u32(data, pos) + if not element_count then return names end + + for _ = 1, element_count do + local type_name, name + + if version < 2 then + type_name, pos = read_string(data, pos) + elseif version <= 4 then + local index + index, pos = read_u16(data, pos) + type_name = index and dict[index] + else + local index + index, pos = read_u32(data, pos) + type_name = index and dict[index] + end + if not pos then return names end + + if version <= 3 then + name, pos = read_string(data, pos) + elseif version == 4 then + local index + index, pos = read_u16(data, pos) + name = index and dict[index] + else + local index + index, pos = read_u32(data, pos) + name = index and dict[index] + end + if not pos then return names end + + pos = pos + 16 + + if type_name == DEFINITION_TYPE and name and name ~= "" then + names[name:lower()] = name + end + end + + return names +end + +function pcfparser.ReadEffectNames(path, game_path) + local f = file.Open(path, "rb", game_path or "GAME") + if not f then return {} end + + local data = f:Read(f:Size()) + f:Close() + + return pcfparser.ParseEffectNames(data) +end + +return pcfparser diff --git a/lua/pac3/libraries/pcfprovider.lua b/lua/pac3/libraries/pcfprovider.lua new file mode 100644 index 000000000..3615d9c75 --- /dev/null +++ b/lua/pac3/libraries/pcfprovider.lua @@ -0,0 +1,89 @@ +local pcfprovider = {} + +local FRAME_BUDGET = 0.002 + +local effect_files = {} +local effect_list = {} +local loaded_files = {} +local precached = {} +local pending_files +local pending_index = 1 +local indexed = false + +local function index_file(file_name) + if pac.BlacklistedParticleSystems[file_name:lower()] then return end + + local ok, names = pcall(pac.pcfparser.ReadEffectNames, "particles/" .. file_name) + + if not ok then + pac.Message(Color(255, 50, 50), "unable to parse particle file ", file_name, ": ", names) + return + end + + for lower_name, name in pairs(names) do + effect_files[lower_name] = file_name + + if not pac.EffectsBlackList[lower_name] then + effect_list[name] = name + end + end +end + +function pcfprovider.StepIndex(budget) + if indexed then return true end + + pending_files = pending_files or file.Find("particles/*.pcf", "GAME") + + local deadline = budget and SysTime() + budget + + while pending_index <= #pending_files do + index_file(pending_files[pending_index]) + pending_index = pending_index + 1 + + if deadline and SysTime() > deadline then return false end + end + + indexed = true + pending_files = nil + + return true +end + +function pcfprovider.IsIndexed() + return indexed +end + +function pcfprovider.GetParticleList() + pcfprovider.StepIndex() + return effect_list +end + +function pcfprovider.LoadEffect(name) + if not indexed then return false end + if not name or name == "" then return false end + + local lower_name = name:lower() + if pac.EffectsBlackList[lower_name] then return false end + + local file_name = effect_files[lower_name] + + if file_name and not loaded_files[file_name] then + loaded_files[file_name] = true + game.AddParticles("particles/" .. file_name) + end + + if not precached[lower_name] then + precached[lower_name] = true + PrecacheParticleSystem(name) + end + + return true +end + +pac.AddHook("Think", "pcfprovider_index", function() + if pcfprovider.StepIndex(FRAME_BUDGET) then + pac.RemoveHook("Think", "pcfprovider_index") + end +end) + +return pcfprovider From 16b23d7c2e152ad729324c88dddb0a0ba54abfbc Mon Sep 17 00:00:00 2001 From: Earu <20880060+Earu@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:48:17 +0200 Subject: [PATCH 2/6] fix the CI complaints --- lua/pac3/core/client/hooks.lua | 4 +- lua/pac3/core/shared/init.lua | 80 ++++++++++++++-------------- lua/pac3/extra/server/map_outfit.lua | 15 +----- 3 files changed, 44 insertions(+), 55 deletions(-) diff --git a/lua/pac3/core/client/hooks.lua b/lua/pac3/core/client/hooks.lua index c81f7ab7b..53e361ad0 100644 --- a/lua/pac3/core/client/hooks.lua +++ b/lua/pac3/core/client/hooks.lua @@ -57,13 +57,13 @@ do if ply.pac_global_animation_rate == 0 then animrate = ply:GetModelScale() * 2 elseif ply.pac_global_animation_rate ~= 1 then - ply:SetCycle((pac.RealTime * ply.pac_global_animation_rate)%1) + ply:SetCycle((pac.RealTime * ply.pac_global_animation_rate) % 1) animrate = ply.pac_global_animation_rate end end if ply.pac_animation_sequences then - local part, thing = next(ply.pac_animation_sequences) + local part = next(ply.pac_animation_sequences) if part and part:IsValid() then diff --git a/lua/pac3/core/shared/init.lua b/lua/pac3/core/shared/init.lua index 746cfec18..875ab28ec 100644 --- a/lua/pac3/core/shared/init.lua +++ b/lua/pac3/core/shared/init.lua @@ -14,48 +14,48 @@ CreateConVar("pac_sv_draw_distance", 0, CLIENT and FCVAR_REPLICATED or bit.bor(F do local tohash = { -- Crash - 'eye_muzzle.pcf', -- E.Y.E: Divine Cybermancy effect that crashes gmod. https://github.com/CapsAdmin/pac3/issues/1410 - 'weapon_unusual_isotope.pcf', + "eye_muzzle.pcf", -- E.Y.E: Divine Cybermancy effect that crashes gmod. https://github.com/CapsAdmin/pac3/issues/1410 + "weapon_unusual_isotope.pcf", -- Invalid - 'blood_fx.pcf', - 'boomer_fx.pcf', - 'charger_fx.pcf', - 'default.pcf', - 'electrical_fx.pcf', - 'environmental_fx.pcf', - 'fire_01l4d.pcf', - 'fire_fx.pcf', - 'fire_infected_fx.pcf', - 'firework_crate_fx.pcf', - 'fireworks_fx.pcf', - 'footstep_fx.pcf', - 'gen_dest_fx.pcf', - 'hunter_fx.pcf', - 'infected_fx.pcf', - 'insect_fx.pcf', - 'item_fx.pcf', - 'locator_fx.pcf', - 'military_artillery_impacts.pcf', - 'rain_fx.pcf', - 'rain_storm_fx.pcf', - 'rope_fx.pcf', - 'screen_fx.pcf', - 'smoker_fx.pcf', - 'speechbubbles.pcf', - 'spitter_fx.pcf', - 'steam_fx.pcf', - 'steamworks.pcf', - 'survivor_fx.pcf', - 'tank_fx.pcf', - 'tanker_explosion.pcf', - 'test_collision.pcf', - 'test_distancealpha.pcf', - 'ui_fx.pcf', - 'vehicle_fx.pcf', - 'water_fx.pcf', - 'weapon_fx.pcf', - 'witch_fx.pcf' + "blood_fx.pcf", + "boomer_fx.pcf", + "charger_fx.pcf", + "default.pcf", + "electrical_fx.pcf", + "environmental_fx.pcf", + "fire_01l4d.pcf", + "fire_fx.pcf", + "fire_infected_fx.pcf", + "firework_crate_fx.pcf", + "fireworks_fx.pcf", + "footstep_fx.pcf", + "gen_dest_fx.pcf", + "hunter_fx.pcf", + "infected_fx.pcf", + "insect_fx.pcf", + "item_fx.pcf", + "locator_fx.pcf", + "military_artillery_impacts.pcf", + "rain_fx.pcf", + "rain_storm_fx.pcf", + "rope_fx.pcf", + "screen_fx.pcf", + "smoker_fx.pcf", + "speechbubbles.pcf", + "spitter_fx.pcf", + "steam_fx.pcf", + "steamworks.pcf", + "survivor_fx.pcf", + "tank_fx.pcf", + "tanker_explosion.pcf", + "test_collision.pcf", + "test_distancealpha.pcf", + "ui_fx.pcf", + "vehicle_fx.pcf", + "water_fx.pcf", + "weapon_fx.pcf", + "witch_fx.pcf" } pac.BlacklistedParticleSystems = {} diff --git a/lua/pac3/extra/server/map_outfit.lua b/lua/pac3/extra/server/map_outfit.lua index 85e630687..8e6a1c4cf 100644 --- a/lua/pac3/extra/server/map_outfit.lua +++ b/lua/pac3/extra/server/map_outfit.lua @@ -4,20 +4,9 @@ util.AddNetworkString("pac_map_outfit_effect") local VEC0 = Vector(0, 0, 0) local ANG0 = Angle(0, 0, 0) -local VEC1 = Vector(1, 1, 1) local function tocolor(v, a) return Color(v.x, v.y, v.z, a) end -local function has_clip(part) - for key, part in pairs(part.children) do - if part.self.ClassName == "clip" then - return true - end - end - - return false -end - local function set_pos(ent, data, parent) local ppos = Vector() local pang = Angle() @@ -27,7 +16,7 @@ local function set_pos(ent, data, parent) pang = parent:GetAngles() end - local pos, ang = LocalToWorld((data.Position or VEC0)+ (data.PositionOffset or VEC0), (data.Angles or ANG0) + (data.AngleOffset or ANG0), ppos, pang) + local pos, ang = LocalToWorld((data.Position or VEC0) + (data.PositionOffset or VEC0), (data.Angles or ANG0) + (data.AngleOffset or ANG0), ppos, pang) ent:SetPos(pos) ent:SetAngles(ang) @@ -144,7 +133,7 @@ concommand.Add("pac_spawn_map", function(ply, _, args) return ply:PrintMessage(HUD_PRINTCONSOLE, "Please enter a pac name") end - for _,v in pairs(pacx.SpawnedMapEntities) do + for _, v in pairs(pacx.SpawnedMapEntities) do SafeRemoveEntity(v) end From 6a0459f9ee6c841abb421cb737c402c2070a4445 Mon Sep 17 00:00:00 2001 From: Earu <20880060+Earu@users.noreply.github.com> Date: Thu, 22 Oct 2026 23:40:17 +0200 Subject: [PATCH 3/6] PR feedback --- lua/pac3/extra/client/map_outfit.lua | 9 +- lua/pac3/libraries/pcfparser.lua | 118 +++++++++++++++------------ lua/pac3/libraries/pcfprovider.lua | 26 +++--- 3 files changed, 78 insertions(+), 75 deletions(-) diff --git a/lua/pac3/extra/client/map_outfit.lua b/lua/pac3/extra/client/map_outfit.lua index 08b7bbe5e..60848a7ed 100644 --- a/lua/pac3/extra/client/map_outfit.lua +++ b/lua/pac3/extra/client/map_outfit.lua @@ -1,5 +1,5 @@ local WAIT_INTERVAL = 0.1 -local WAIT_ATTEMPTS = 50 +local WAIT_ATTEMPTS = 300 local pending = 0 @@ -13,13 +13,10 @@ net.Receive("pac_map_outfit_effect", function() timer.Create(timer_name, WAIT_INTERVAL, WAIT_ATTEMPTS, function() local ent = Entity(index) if not ent:IsValid() then return end + if not pac.pcfprovider.LoadEffect(effect) then return end timer.Remove(timer_name) - pac.pcfprovider.StepIndex() - - if pac.pcfprovider.LoadEffect(effect) then - ParticleEffectAttach(effect, PATTACH_ABSORIGIN_FOLLOW, ent, 0) - end + ParticleEffectAttach(effect, PATTACH_ABSORIGIN_FOLLOW, ent, 0) end) end) diff --git a/lua/pac3/libraries/pcfparser.lua b/lua/pac3/libraries/pcfparser.lua index b04f71c65..03f502340 100644 --- a/lua/pac3/libraries/pcfparser.lua +++ b/lua/pac3/libraries/pcfparser.lua @@ -1,91 +1,101 @@ local pcfparser = {} +local string_find = string.find +local string_sub = string.sub +local string_match = string.match +local string_lower = string.lower +local file_Open = file.Open + local DEFINITION_TYPE = "DmeParticleSystemDefinition" +local HEADER_PREFIX = "", header_start, true) - if not version or not header_end or version > 5 then return names end + local header = read_string(f) + local version = header and tonumber(string_match(header, "^(%d+)")) + if not version or version > 5 then return names end - local pos = header_end + 6 local dict = {} if version >= 2 then - local count - if version <= 3 then - count, pos = read_u16(data, pos) - else - count, pos = read_u32(data, pos) - end + local count = read_index(f, size, version > 3) if not count then return names end for i = 0, count - 1 do - dict[i], pos = read_string(data, pos) - if not pos then return names end + dict[i] = read_string(f) + if not dict[i] then return names end end end - local element_count - element_count, pos = read_u32(data, pos) + local element_count = read_u32(f, size) if not element_count then return names end for _ = 1, element_count do local type_name, name if version < 2 then - type_name, pos = read_string(data, pos) - elseif version <= 4 then - local index - index, pos = read_u16(data, pos) - type_name = index and dict[index] + type_name = read_string(f) + if not type_name then return names end else - local index - index, pos = read_u32(data, pos) - type_name = index and dict[index] + local index = read_index(f, size, version > 4) + if not index then return names end + type_name = dict[index] end - if not pos then return names end if version <= 3 then - name, pos = read_string(data, pos) - elseif version == 4 then - local index - index, pos = read_u16(data, pos) - name = index and dict[index] + name = read_string(f) + if not name then return names end else - local index - index, pos = read_u32(data, pos) - name = index and dict[index] + local index = read_index(f, size, version > 4) + if not index then return names end + name = dict[index] end - if not pos then return names end - pos = pos + 16 + if f:Tell() + GUID_SIZE > size then return names end + f:Skip(GUID_SIZE) if type_name == DEFINITION_TYPE and name and name ~= "" then - names[name:lower()] = name + names[string_lower(name)] = name end end @@ -93,13 +103,15 @@ function pcfparser.ParseEffectNames(data) end function pcfparser.ReadEffectNames(path, game_path) - local f = file.Open(path, "rb", game_path or "GAME") + local f = file_Open(path, "rb", game_path or "GAME") if not f then return {} end - local data = f:Read(f:Size()) + local ok, names = pcall(pcfparser.ParseEffectNames, f) f:Close() - return pcfparser.ParseEffectNames(data) + if not ok then error(names, 0) end + + return names end return pcfparser diff --git a/lua/pac3/libraries/pcfprovider.lua b/lua/pac3/libraries/pcfprovider.lua index 3615d9c75..805506d28 100644 --- a/lua/pac3/libraries/pcfprovider.lua +++ b/lua/pac3/libraries/pcfprovider.lua @@ -6,8 +6,6 @@ local effect_files = {} local effect_list = {} local loaded_files = {} local precached = {} -local pending_files -local pending_index = 1 local indexed = false local function index_file(file_name) @@ -29,32 +27,28 @@ local function index_file(file_name) end end -function pcfprovider.StepIndex(budget) - if indexed then return true end +local step_index = coroutine.wrap(function() + local deadline = SysTime() + FRAME_BUDGET - pending_files = pending_files or file.Find("particles/*.pcf", "GAME") + for _, file_name in ipairs(file.Find("particles/*.pcf", "GAME")) do + index_file(file_name) - local deadline = budget and SysTime() + budget - - while pending_index <= #pending_files do - index_file(pending_files[pending_index]) - pending_index = pending_index + 1 - - if deadline and SysTime() > deadline then return false end + if SysTime() > deadline then + coroutine.yield(false) + deadline = SysTime() + FRAME_BUDGET + end end indexed = true - pending_files = nil return true -end +end) function pcfprovider.IsIndexed() return indexed end function pcfprovider.GetParticleList() - pcfprovider.StepIndex() return effect_list end @@ -81,7 +75,7 @@ function pcfprovider.LoadEffect(name) end pac.AddHook("Think", "pcfprovider_index", function() - if pcfprovider.StepIndex(FRAME_BUDGET) then + if step_index() then pac.RemoveHook("Think", "pcfprovider_index") end end) From 2549018e7d4835ed4b726fbcfe4f12822897b102 Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Sat, 24 Oct 2026 21:45:08 -0400 Subject: [PATCH 4/6] Use table --- lua/pac3/libraries/pcfparser.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/pac3/libraries/pcfparser.lua b/lua/pac3/libraries/pcfparser.lua index 03f502340..09c784998 100644 --- a/lua/pac3/libraries/pcfparser.lua +++ b/lua/pac3/libraries/pcfparser.lua @@ -28,7 +28,7 @@ end local function read_string(f) local start = f:Tell() - local str = "" + local str = {} while true do local chunk = f:Read(STRING_CHUNK) @@ -36,12 +36,12 @@ local function read_string(f) local stop = string_find(chunk, "\0", 1, true) if stop then - str = str .. string_sub(chunk, 1, stop - 1) + table.insert(str, string_sub(chunk, 1, stop - 1)) f:Seek(start + #str + 1) - return str + return table.concat(str) end - str = str .. chunk + table.insert(str, chunk) end end From 0cc608ccc39f83314496ee60fe00fa2227227ad0 Mon Sep 17 00:00:00 2001 From: thegrb93 Date: Sat, 24 Oct 2026 21:46:18 -0400 Subject: [PATCH 5/6] Use lower name in PrecacheParticleSystem --- lua/pac3/libraries/pcfprovider.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/pac3/libraries/pcfprovider.lua b/lua/pac3/libraries/pcfprovider.lua index 805506d28..f21d78cf4 100644 --- a/lua/pac3/libraries/pcfprovider.lua +++ b/lua/pac3/libraries/pcfprovider.lua @@ -68,7 +68,7 @@ function pcfprovider.LoadEffect(name) if not precached[lower_name] then precached[lower_name] = true - PrecacheParticleSystem(name) + PrecacheParticleSystem(lower_name) end return true From 6167226de63979c0c2539a138f2d99b61ac7ce7d Mon Sep 17 00:00:00 2001 From: Ryan <20880060+Earu@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:52:58 +0400 Subject: [PATCH 6/6] fix a bug within the pcf parser --- lua/pac3/libraries/pcfparser.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/pac3/libraries/pcfparser.lua b/lua/pac3/libraries/pcfparser.lua index 09c784998..f2037fe3c 100644 --- a/lua/pac3/libraries/pcfparser.lua +++ b/lua/pac3/libraries/pcfparser.lua @@ -29,6 +29,7 @@ end local function read_string(f) local start = f:Tell() local str = {} + local len = 0 while true do local chunk = f:Read(STRING_CHUNK) @@ -37,11 +38,12 @@ local function read_string(f) local stop = string_find(chunk, "\0", 1, true) if stop then table.insert(str, string_sub(chunk, 1, stop - 1)) - f:Seek(start + #str + 1) + f:Seek(start + len + stop) return table.concat(str) end table.insert(str, chunk) + len = len + #chunk end end