From 039bb2e0a58d90d0b1dd3876f558910fa587eb53 Mon Sep 17 00:00:00 2001 From: LiddulBOFH <13317534+LiddulBOFH@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:45:45 -0600 Subject: [PATCH] New models, broke up functions and other fixes Added new turret drive models courtesy of ghosteh (horizontal ring and vertical trunnion) These new models are untextured, using debugwhite, as they were made with being tucked out of view in mind Also the start of a new more organized file structure for models specifically, found started as >acf/ Currently only the new turret models are found in this Broke up the turret slewing functions to further differentiate horizontal/vertical drives, now the vertical drive actually pitches instead of yaws so you are no longer required to rotate it on spawn for it to be correct Updated hover info to reflect that (looking at the entity to reveal directions) Damage fixes for motors/gyro Mass rebalance for turret drives Turret drives now also hide for the player, and instead redraw as the matching clientside model that better represents the current angle, very noticeable with the new trunnion model --- lua/acf/entities/turrets/turrets.lua | 108 +++++++--- lua/entities/acf_turret/cl_init.lua | 91 ++++++++- lua/entities/acf_turret/init.lua | 250 +++++++++++------------ lua/entities/acf_turret_gyro/init.lua | 9 +- lua/entities/acf_turret_motor/init.lua | 16 +- lua/entities/acf_turret_rotator/init.lua | 18 +- models/acf/core/t_ring.dx80.vtx | Bin 0 -> 3987 bytes models/acf/core/t_ring.dx90.vtx | Bin 0 -> 3987 bytes models/acf/core/t_ring.mdl | Bin 0 -> 1744 bytes models/acf/core/t_ring.phy | Bin 0 -> 5352 bytes models/acf/core/t_ring.sw.vtx | Bin 0 -> 3979 bytes models/acf/core/t_ring.vvd | Bin 0 -> 16832 bytes models/acf/core/t_trun.dx80.vtx | Bin 0 -> 3267 bytes models/acf/core/t_trun.dx90.vtx | Bin 0 -> 3267 bytes models/acf/core/t_trun.mdl | Bin 0 -> 1744 bytes models/acf/core/t_trun.phy | Bin 0 -> 1088 bytes models/acf/core/t_trun.sw.vtx | Bin 0 -> 3259 bytes models/acf/core/t_trun.vvd | Bin 0 -> 15296 bytes 18 files changed, 310 insertions(+), 182 deletions(-) create mode 100644 models/acf/core/t_ring.dx80.vtx create mode 100644 models/acf/core/t_ring.dx90.vtx create mode 100644 models/acf/core/t_ring.mdl create mode 100644 models/acf/core/t_ring.phy create mode 100644 models/acf/core/t_ring.sw.vtx create mode 100644 models/acf/core/t_ring.vvd create mode 100644 models/acf/core/t_trun.dx80.vtx create mode 100644 models/acf/core/t_trun.dx90.vtx create mode 100644 models/acf/core/t_trun.mdl create mode 100644 models/acf/core/t_trun.phy create mode 100644 models/acf/core/t_trun.sw.vtx create mode 100644 models/acf/core/t_trun.vvd diff --git a/lua/acf/entities/turrets/turrets.lua b/lua/acf/entities/turrets/turrets.lua index 141a1323f..86980f6a9 100644 --- a/lua/acf/entities/turrets/turrets.lua +++ b/lua/acf/entities/turrets/turrets.lua @@ -14,6 +14,16 @@ local InchToMm = ACF.InchToMm -- Bunched all of the definitions together due to some loading issue do -- Turret drives + local function ClampAngle(A,Amin,Amax) + local p,y,r + + if A.p < Amin.p then p = Amin.p elseif A.p > Amax.p then p = Amax.p else p = A.p end + if A.y < Amin.y then y = Amin.y elseif A.y > Amax.y then y = Amax.y else y = A.y end + if A.r < Amin.r then r = Amin.r elseif A.r > Amax.r then r = Amax.r else r = A.r end + + return Angle(p,y,r) + end + Turrets.Register("1-Turret",{ Name = "Turrets", Description = "The turret drives themselves.\nThese have a fallback handcrank that is used automatically if no motor is available.", @@ -25,7 +35,7 @@ do -- Turret drives Text = "Maximum number of ACF turrets a player can create." }, GetMass = function(Data, Size) - return math.Round(math.max(Data.Mass * (Size / Data.Size.Base),5), 1) + return math.Round(math.max(Data.Mass * (Size / Data.Size.Base),5) ^ 1.5, 1) end, GetTeethCount = function(Data, Size) local SizePerc = (Size - Data.Size.Min) / (Data.Size.Max - Data.Size.Min) @@ -126,15 +136,15 @@ do -- Turret drives Turrets.RegisterItem("Turret-H","1-Turret",{ Name = "Horizontal Turret", Description = "The large stable base of a turret.", - Model = "models/props_phx/construct/metal_plate_curve360.mdl", + Model = "models/acf/core/t_ring.mdl", ModelSmall = "models/holograms/hq_cylinder.mdl", -- To be used for diameters < 12u, for RWS or other small turrets - Mass = 200, -- At default size, this is the mass of the turret ring. Will scale up/down with diameter difference + Mass = 25, -- At default size, this is the mass of the turret ring. Will scale up/down with diameter difference Size = { Base = 60, -- The default size for the menu Min = 2, -- To accomodate itty bitty RWS turrets Max = 512, -- To accomodate ship turrets - Ratio = 0.05 -- Height modifier for total size + Ratio = 0.1 -- Height modifier for total size }, Teeth = { -- Used to give a final teeth count with size @@ -142,13 +152,8 @@ do -- Turret drives Max = 3072 }, - MassLimit = { - Min = 50, -- Max amount of mass this component can support at minimum size - Max = 80000 -- Max amount of mass th is component can support at maximum size - }, - Armor = { - Min = 5, + Min = 15, Max = 175 }, @@ -156,7 +161,35 @@ do -- Turret drives local Count = #List List[Count + 1] = "Bearing (Local degrees from home angle)" - end + end, + + SlewFuncs = { + GetStab = function(Turret) + local AngDiff = Turret.Rotator:WorldToLocalAngles(Turret.LastRotatorAngle) + + return (Turret.Stabilized and Turret.Active) and (AngDiff.yaw * Turret.StabilizeAmount) or 0 + end, + + GetTargetBearing = function(Turret,StabAmt) + local Rotator = Turret.Rotator + + if Turret.HasArc then + if Turret.Manual then + return Rotator:WorldToLocalAngles(Turret:LocalToWorldAngles(Angle(0, math.Clamp(-Turret.DesiredDeg,Turret.MinDeg,Turret.MaxDeg), 0))).yaw + else + local LocalDesiredAngle = ClampAngle(Turret:WorldToLocalAngles(Turret.DesiredAngle) - Angle(0,StabAmt,0),Angle(0,-Turret.MaxDeg,0),Angle(0,-Turret.MinDeg,0)) + + return Rotator:WorldToLocalAngles(Turret:LocalToWorldAngles(LocalDesiredAngle)).yaw + end + else + return Turret.Manual and (Rotator:WorldToLocalAngles(Turret:LocalToWorldAngles(Angle(0, -Turret.DesiredDeg, 0))).yaw) or (Rotator:WorldToLocalAngles(Turret.DesiredAngle).yaw - StabAmt) + end + end, + + SetRotatorAngle = function(Turret) + Turret.Rotator:SetAngles(Turret:LocalToWorldAngles(Angle(0, Turret.CurrentAngle, 0))) + end + } }) end @@ -164,36 +197,59 @@ do -- Turret drives Turrets.RegisterItem("Turret-V","1-Turret",{ Name = "Vertical Turret", Description = "The smaller part of a turret, usually has the weapon directly attached to it.\nCan be naturally stabilized up to 25% if there is no motor attached, but the mass must be balanced.", - Model = "models/holograms/hq_cylinder.mdl", - Mass = 100, -- At default size, this is the mass of the turret ring. Will scale up/down with diameter difference + Model = "models/acf/core/t_trun.mdl", + Mass = 25, -- At default size, this is the mass of the turret ring. Will scale up/down with diameter difference Size = { Base = 12, -- The default size for the menu - Min = 1, -- To accomodate itty bitty RWS turrets - Max = 36, -- To accomodate ship turrets - Ratio = 1.5 -- Height modifier for total size + Min = 4, -- To accomodate itty bitty RWS turrets + Max = 48, -- To accomodate ship turrets + Ratio = 1 -- Height modifier for total size }, Teeth = { -- Used to give a final teeth count with size Min = 8, - Max = 288 - }, - - MassLimit = { - Min = 20, -- Max amount of mass this component can support at minimum size - Max = 40000 -- Max amount of mass th is component can support at maximum size + Max = 384 }, Armor = { Min = 5, - Max = 175 + Max = 305 }, SetupInputs = function(_,List) local Count = #List List[Count + 1] = "Elevation (Local degrees from home angle)" - end + end, + + SlewFuncs = { + GetStab = function(Turret) + local AngDiff = Turret.Rotator:WorldToLocalAngles(Turret.LastRotatorAngle) + + return (Turret.Stabilized and Turret.Active) and (AngDiff.pitch * Turret.StabilizeAmount) or 0 + end, + + GetTargetBearing = function(Turret,StabAmt) + local Rotator = Turret.Rotator + + if Turret.HasArc then + if Turret.Manual then + return Rotator:WorldToLocalAngles(Turret:LocalToWorldAngles(Angle(math.Clamp(-Turret.DesiredDeg,Turret.MinDeg,Turret.MaxDeg), 0, 0))).pitch + else + local LocalDesiredAngle = ClampAngle(Turret:WorldToLocalAngles(Turret.DesiredAngle) - Angle(StabAmt,0,0),Angle(-Turret.MaxDeg,0,0),Angle(-Turret.MinDeg,0,0)) + + return Rotator:WorldToLocalAngles(Turret:LocalToWorldAngles(LocalDesiredAngle)).pitch + end + else + return Turret.Manual and (Rotator:WorldToLocalAngles(Turret:LocalToWorldAngles(Angle(-Turret.DesiredDeg, 0, 0))).pitch) or (Rotator:WorldToLocalAngles(Turret.DesiredAngle).pitch - StabAmt) + end + end, + + SetRotatorAngle = function(Turret) + Turret.Rotator:SetAngles(Turret:LocalToWorldAngles(Angle(Turret.CurrentAngle, 0, 0))) + end + } }) end end @@ -245,7 +301,7 @@ do -- Turret motors Base = 12, Min = 8, - Max = 24, + Max = 32, }, Torque = { @@ -276,7 +332,7 @@ do -- Turret motors Base = 12, Min = 8, - Max = 24, + Max = 32, }, Torque = { diff --git a/lua/entities/acf_turret/cl_init.lua b/lua/entities/acf_turret/cl_init.lua index ac33ba3ea..49f9c927f 100644 --- a/lua/entities/acf_turret/cl_init.lua +++ b/lua/entities/acf_turret/cl_init.lua @@ -24,6 +24,7 @@ do -- NET SURFER Entity.MinDeg = Data.MinDeg Entity.MaxDeg = Data.MaxDeg Entity.CoMDist = Data.CoMDist + Entity.Type = Data.Type Entity.HasData = true Entity.Age = Clock.CurTime + 5 @@ -42,6 +43,66 @@ do -- NET SURFER end end +do -- Turret drive drawing + local DrawDist = 1024 ^ 2 + local HideInfo = ACF.HideInfoBubble + + local function CSModel(Ent) + if not IsValid(Ent) then return end + + if IsValid(Ent.CSModel) then + if Ent.CSModel:GetModel() ~= Ent:GetModel() then Ent.CSModel:Remove() return end + + return Ent.CSModel + end + + if not Ent.Matrix then return end + + local CSModel = ClientsideModel(Ent:GetModel()) + CSModel:SetParent(Ent) + CSModel:SetPos(Ent:GetPos()) + CSModel:SetAngles(Ent:GetAngles()) + CSModel:SetMaterial(Ent:GetMaterial()) + CSModel:SetColor(Ent:GetColor()) + + CSModel.Material = Ent:GetMaterial() + CSModel.Matrix = Ent.Matrix + CSModel:EnableMatrix("RenderMultiply", CSModel.Matrix) + + Ent.CSModel = CSModel + + return Ent.CSModel + end + + function ENT:Draw() + + -- Partial from base_wire_entity, need the tooltip but without the model drawing since we're drawing our own + local looked_at = self:BeingLookedAtByLocalPlayer() + + if looked_at then + self:DrawEntityOutline() + if not HideInfo() then self:AddWorldTip() end + end + + local Rotator = self:GetNWEntity("ACF.Rotator") + + if (not IsValid(Rotator)) or ((EyePos()):DistToSqr(self:GetPos()) > DrawDist) then self:DrawModel() return end + + local CSM = CSModel(self) + if not IsValid(CSM) then self:DrawModel() return end + + if CSM.Material ~= self:GetMaterial() then CSM:Remove() return end + if CSM:GetColor() ~= self:GetColor() then CSM:Remove() return end + if CSM.Matrix ~= self.Matrix then CSM:Remove() return end + + CSM:SetAngles(Rotator:GetAngles()) + end + + function ENT:OnRemove() + if IsValid(self.CSModel) then self.CSModel:Remove() end + end +end + do -- Overlay local red = Color(255,0,0) local green = Color(0,255,0) @@ -61,13 +122,28 @@ do -- Overlay local X = self:OBBMaxs().x local Pos = self:LocalToWorld(self:OBBCenter()) + if self.Type == "Turret-V" then + UX = self:GetRight() * 0.5 + end + render.DrawLine(Pos + UX,Pos + (self:GetForward() * X) + UX,orange,true) if IsValid(self.Rotator) then render.DrawLine(Pos,Pos + self.Rotator:GetForward() * X,green,true) end local LocPos = self:WorldToLocal(Trace.HitPos) + local AimAng = 0 + local CurAng = 0 local LocDir = Vector(LocPos.x,LocPos.y,0):GetNormalized() + if self.Type == "Turret-V" then + LocDir = Vector(LocPos.x,0,LocPos.z):GetNormalized() + AimAng = -math.Round(self:WorldToLocalAngles(self:LocalToWorldAngles(LocDir:Angle())).pitch,2) + CurAng = -math.Round(self:WorldToLocalAngles(self.Rotator:GetAngles()).pitch,2) + else + AimAng = -math.Round(self:WorldToLocalAngles(self:LocalToWorldAngles(LocDir:Angle())).yaw,2) + CurAng = -math.Round(self:WorldToLocalAngles(self.Rotator:GetAngles()).yaw,2) + end + render.DrawLine(Pos - UX,self:LocalToWorld(self:OBBCenter() + LocDir * X * 2) - UX,magenta,true) render.DrawLine(self:LocalToWorld(self:OBBCenter()),self.Rotator:LocalToWorld(self.LocalCoM),red,true) @@ -78,9 +154,16 @@ do -- Overlay if not ((self.MinDeg == -180) and (self.MaxDeg == 180)) then local MinDir = Vector(X,0,0) - MinDir:Rotate(Angle(0,-self.MinDeg,0)) local MaxDir = Vector(X,0,0) - MaxDir:Rotate(Angle(0,-self.MaxDeg,0)) + + if self.Type == "Turret-V" then + MinDir:Rotate(Angle(-self.MinDeg,0,0)) + MaxDir:Rotate(Angle(-self.MaxDeg,0,0)) + else + MinDir:Rotate(Angle(0,-self.MinDeg,0)) + MaxDir:Rotate(Angle(0,-self.MaxDeg,0)) + end + render.DrawLine(Pos - UX,self:LocalToWorld(self:OBBCenter() + MinDir) - UX,red,true) render.DrawLine(Pos - UX,self:LocalToWorld(self:OBBCenter() + MaxDir) - UX,green,true) end @@ -93,8 +176,8 @@ do -- Overlay cam.Start2D() draw.SimpleTextOutlined("Zero","DermaDefault",HomePos.x,HomePos.y,orange,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Current: " .. -math.Round(self:WorldToLocalAngles(self.Rotator:GetAngles()).yaw,2),"DermaDefault",CurPos.x,CurPos.y,green,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) - draw.SimpleTextOutlined("Aim: " .. -math.Round(self:WorldToLocalAngles(self:LocalToWorldAngles(LocDir:Angle())).yaw,2),"DermaDefault",AimPos.x,AimPos.y,magenta,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Current: " .. CurAng,"DermaDefault",CurPos.x,CurPos.y,green,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) + draw.SimpleTextOutlined("Aim: " .. AimAng,"DermaDefault",AimPos.x,AimPos.y,magenta,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) draw.SimpleTextOutlined("Mass: " .. self.Mass .. "kg","DermaDefault",CoMPos.x,CoMPos.y,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) draw.SimpleTextOutlined("Lateral Distance: " .. self.CoMDist .. "u","DermaDefault",CoMPos.x,CoMPos.y + 16,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER,1,color_black) diff --git a/lua/entities/acf_turret/init.lua b/lua/entities/acf_turret/init.lua index 664f99342..3d5da9dbc 100644 --- a/lua/entities/acf_turret/init.lua +++ b/lua/entities/acf_turret/init.lua @@ -6,6 +6,7 @@ include("shared.lua") -- Local Vars local ACF = ACF + local Contraption = ACF.Contraption local Classes = ACF.Classes local Utilities = ACF.Utilities @@ -60,7 +61,7 @@ do -- Spawn and Update funcs ------------------ local function GetMass(Turret,Data) - return math.Round(math.max(Turret.Mass * (Data.RingSize / Turret.Size.Base),5), 1) + return math.Round(math.max(Turret.Mass * (Data.RingSize / Turret.Size.Base),5) ^ 1.5, 1) end local function UpdateTurret(Entity, Data, Class, Turret) @@ -75,7 +76,11 @@ do -- Spawn and Update funcs local RingHeight = Class.GetRingHeight({Type = Data.Turret, Ratio = Turret.Size.Ratio}, Size) - Entity:SetSize(Vector(Size,Size,RingHeight)) + if Data.Turret == "Turret-H" then + Entity:SetSize(Vector(Size,Size,RingHeight)) + else + Entity:SetScale(Size / 20) + end Entity.ACF.Model = Model Entity.Name = math.Round(Size,2) .. "\" " .. Turret.Name @@ -94,8 +99,11 @@ do -- Spawn and Update funcs LocalCoM = Vector() } + -- Type-specific functions that differ between horizontal and vertical turret components + Entity.SlewFuncs = Turret.SlewFuncs + Entity.DesiredAngle = Entity.DesiredAngle or Angle(0,0,0) - Entity.CurrentAngle = Entity.CurrentAngle or Angle(0,0,0) + Entity.CurrentAngle = Entity.CurrentAngle or 0 -- This is TRUE whenever the last used angle input is Elevation/Bearing -- Otherwise this is FALSE and will attempt to rotate to the Angle input @@ -193,7 +201,8 @@ do -- Spawn and Update funcs Mass = math.Round(Entity.TurretData.TotalMass,1), MinDeg = Entity.MinDeg, MaxDeg = Entity.MaxDeg, - CoMDist = math.Round(CoM:Length2D(),2) + CoMDist = math.Round(CoM:Length2D(),2), + Type = Entity.Turret } local DataString = util.TableToJSON(Data) @@ -256,6 +265,8 @@ do -- Spawn and Update funcs Entity.HandGear = Class.HandGear Entity.Disconnect = false + Entity:SetNWEntity("ACF.Rotator", Rotator) + Rotator:SetPos(Entity:GetPos()) Rotator:SetAngles(Entity:GetAngles()) Rotator:SetParent(Entity) @@ -263,6 +274,7 @@ do -- Spawn and Update funcs Rotator:Spawn() Entity.Rotator = Rotator + Rotator.Turret = Entity UpdateTurret(Entity, Data, Class, Turret) @@ -342,12 +354,39 @@ do -- Spawn and Update funcs if Parent == NULL then continue end -- somehow this shit is still a problem List[V] = V - if V:GetClass() ~= FilterClass then GetFilteredChildren(V, List) end + + if V:GetClass() == FilterClass then continue end + + GetFilteredChildren(V, List, FilterClass) end return List end + local function Proxy_ACF_OnParent(self, _, _) + if (not IsValid(self.ACF_TurretAncestor)) or (not Contraption.HasAncestor(self,self.ACF_TurretAncestor)) then self.ACF_OnParented = nil self.ACF_TurretAncestor = nil return end + + self.ACF_TurretAncestor:UpdateTurretMass(false) + end + + local function Proxy_ACF_OnMassChange(self) + if (not IsValid(self.ACF_TurretAncestor)) or (not Contraption.HasAncestor(self,self.ACF_TurretAncestor)) then self.ACF_OnMassChange = nil self.ACF_TurretAncestor = nil return end + + self.ACF_TurretAncestor:UpdateTurretMass(false) + end + + local function ParentLink(Turret, Entity, Connect) + if Connect then + Entity.ACF_OnParented = Proxy_ACF_OnParent + Entity.ACF_OnMassChange = Proxy_ACF_OnMassChange + Entity.ACF_TurretAncestor = Turret + else + Entity.ACF_OnParented = nil + Entity.ACF_OnMassChange = nil + Entity.ACF_TurretAncestor = nil + end + end + local function BuildWatchlist(Entity) -- Potentially hot and heavy, should only be triggered after a (maximum) delay to catch large changes and not every single new entity if not IsValid(Entity) then return end @@ -366,12 +405,19 @@ do -- Spawn and Update funcs for k in pairs(ChildList) do local Class = k:GetClass() + k.ACF_TurretAncestor = nil if Class == "acf_turret" then Entity.SubTurrets[k] = true + + k.ACF_TurretAncestor = Entity elseif DynamicMassTypes[Class] then Entity.DynamicEntities[k] = true + + ParentLink(Entity,k,true) else if not IsValid(k) then continue end + ParentLink(Entity,k,true) + local PO = k:GetPhysicsObject() if not IsValid(PO) then continue end @@ -389,9 +435,6 @@ do -- Spawn and Update funcs end Entity.StaticCoM = CoM - - debugoverlay.Line(Entity:GetPos(),Entity:LocalToWorld(Entity.StaticCoM),5,Color(194,55,0),true) - debugoverlay.Cross(Entity:LocalToWorld(Entity.StaticCoM),3,5,Color(194,55,0),true) end local function GetDynamicMass(Entity) -- Returns mass center (local to rotator) and amount from all "dynamic" entities, should be triggered after a resettable delay (only delayable by so long) in order to reduce spammed calls @@ -422,9 +465,6 @@ do -- Spawn and Update funcs Entity.DynamicCoM = CoM - debugoverlay.Line(Entity.Rotator:GetPos(),Entity.Rotator:LocalToWorld(Entity.DynamicCoM),5,Color(3,0,194),true) - debugoverlay.Cross(Entity.Rotator:LocalToWorld(Entity.DynamicCoM),3,5,Color(3,0,194),true) - return CoM, Mass end @@ -454,9 +494,6 @@ do -- Spawn and Update funcs Entity.SubTurretCoM = CoM - debugoverlay.Line(Entity.Rotator:GetPos(),Entity.Rotator:LocalToWorld(Entity.SubTurretCoM),5,Color(0,211,81),true) - debugoverlay.Cross(Entity.Rotator:LocalToWorld(Entity.SubTurretCoM),3,5,Color(0,211,81),true) - return CoM, Mass end @@ -550,9 +587,6 @@ do -- Spawn and Update funcs self:CheckCoM(Force) self:UpdateOverlay() - - debugoverlay.Line(self:GetPos(),self:LocalToWorld(self.TurretData.LocalCoM),5,Color(134,134,134),true) - debugoverlay.Cross(self:LocalToWorld(self.TurretData.LocalCoM),3,5,Color(134,134,134),true) end) end end @@ -708,20 +742,11 @@ do -- Metamethods end do -- Think - local function ClampAngle(A,Amin,Amax) - local p,y,r - - if A.p < Amin.p then p = Amin.p elseif A.p > Amax.p then p = Amax.p else p = A.p end - if A.y < Amin.y then y = Amin.y elseif A.y > Amax.y then y = Amax.y else y = A.y end - if A.r < Amin.r then r = Amin.r elseif A.r > Amax.r then r = Amax.r else r = A.r end - - return Angle(p,y,r) - end - function ENT:SetSoundState(State) if State ~= self.SoundPlaying then if State == true then Sounds.CreateAdjustableSound(self,self.SoundPath,0,0) + self.CurrentSound = self.SoundPath else Sounds.SendAdjustableSound(self,true) end @@ -730,16 +755,54 @@ do -- Metamethods self.SoundPlaying = State end + function ENT:InputDirection(Direction) + if self.Disabled then return end + + self.Manual = true + self.UseVector = false + + if isnumber(Direction) then + self.DesiredDeg = Direction + return + end + + self.Manual = false + + if isangle(Direction) then + Direction:Normalize() + self.DesiredAng = Direction + + return + end + if isvector(Direction) then + self.UseVector = true + self.DesiredVector = Direction + + return + end + end + function ENT:Think() -- The meat and POE-TAE-TOES of the turret working + if self.Disabled then + self:SetSoundState(false) + self:NextThink(Clock.CurTime + 0.1) + + return true + end + self:CheckCoM(false) local Tick = Clock.DeltaTime local Rotator = self.Rotator + if not IsValid(Rotator) then self:Remove() return end + local Scale = self.DamageScale * Tick local SlewMax = self.MaxSlewRate * Scale local SlewAccel = self.SlewAccel * Scale local MaxImpulse = math.min(SlewMax, SlewAccel) + local AngleChange = self.CurrentAngle + -- Something or another has caused the turret to be unable to rotate, so don't waste the extra processing time if MaxImpulse == 0 then self.LastRotatorAngle = Rotator:GetAngles() @@ -754,21 +817,9 @@ do -- Metamethods if self.UseVector and (self.Manual == false) then self.DesiredAngle = (self.DesiredVector - Rotator:GetPos()):GetNormalized():Angle() end - local AngDiff = Rotator:WorldToLocalAngles(self.LastRotatorAngle) --+ Angle(0,-self.SlewRate / 2,0) - local StabAmt = math.Clamp((self.Stabilized and self.Active) and (AngDiff.yaw * self.StabilizeAmount) or 0,-SlewMax,SlewMax) - - local TargetBearing = 0 - if self.HasArc then - if self.Manual then - TargetBearing = Rotator:WorldToLocalAngles(self:LocalToWorldAngles(Angle(0, math.Clamp(-self.DesiredDeg,self.MinDeg,self.MaxDeg), 0))).yaw - else - local LocalDesiredAngle = ClampAngle(self:WorldToLocalAngles(self.DesiredAngle) - Angle(0,StabAmt,0),Angle(0,-self.MaxDeg,0),Angle(0,-self.MinDeg,0)) + local StabAmt = math.Clamp(self.SlewFuncs.GetStab(self), -SlewMax, SlewMax) - TargetBearing = Rotator:WorldToLocalAngles(self:LocalToWorldAngles(LocalDesiredAngle)).yaw - end - else - TargetBearing = self.Manual and (Rotator:WorldToLocalAngles(self:LocalToWorldAngles(Angle(0, -self.DesiredDeg, 0))).yaw) or (Rotator:WorldToLocalAngles(self.DesiredAngle).yaw - StabAmt) - end + local TargetBearing = self.SlewFuncs.GetTargetBearing(self,StabAmt) local sign = TargetBearing < 0 and -1 or 1 local Dist = math.abs(TargetBearing) @@ -780,25 +831,25 @@ do -- Metamethods if self.SlewRate ~= 0 and (Dist <= math.abs(FinalAccel)) and (self.SlewRate <= FinalAccel) then self.SlewRate = 0 - self.CurrentAngle = self.CurrentAngle + Angle(0, TargetBearing / 2, 0) + self.CurrentAngle = self.CurrentAngle + TargetBearing / 2 end elseif not self.Active and self.SlewRate ~= 0 then self.SlewRate = self.SlewRate - (math.min(SlewAccel, math.abs(self.SlewRate)) * (self.SlewRate >= 0 and 1 or -1)) end - self.CurrentAngle = self.CurrentAngle + Angle(0, math.Clamp(self.SlewRate + StabAmt,-SlewMax,SlewMax), 0) + self.CurrentAngle = self.CurrentAngle + math.Clamp(self.SlewRate + StabAmt,-SlewMax,SlewMax) if self.HasArc then - self.CurrentAngle = Angle(0,math.Clamp(self.CurrentAngle.yaw,-self.MaxDeg,-self.MinDeg),0) + self.CurrentAngle = math.Clamp(self.CurrentAngle,-self.MaxDeg,-self.MinDeg) end - self.CurrentAngle:Normalize() + self.CurrentAngle = math.NormalizeAngle(self.CurrentAngle) - WireLib.TriggerOutput(self, "Degrees", -self.CurrentAngle.yaw) + WireLib.TriggerOutput(self, "Degrees", -self.CurrentAngle) - Rotator:SetAngles(self:LocalToWorldAngles(self.CurrentAngle)) + self.SlewFuncs.SetRotatorAngle(self) - local MotorSpeed = math.Clamp(math.abs(self.SlewRate + StabAmt),0,SlewMax) / Tick + local MotorSpeed = math.Clamp(math.abs(self.CurrentAngle - AngleChange),0,SlewMax) / Tick local MotorSpeedPerc = MotorSpeed / self.MotorMaxSpeed if MotorSpeedPerc > 0.1 and (self.SoundPlaying == false) then @@ -807,9 +858,13 @@ do -- Metamethods self:SetSoundState(false) end - if self.SoundPlaying == true then Sounds.SendAdjustableSound(self,false, 70 + math.ceil(MotorSpeedPerc * 30), 0.1 + (self.EffortScale * 0.9)) end - - debugoverlay.Line(Rotator:GetPos(), Rotator:GetPos() + Rotator:GetForward() * 16384, 0.05, Color(255,0,0), false) + if self.SoundPlaying == true then + if self.SoundPath ~= (self.CurrentSound or "") then -- should only get set off if the motor is enabled/disabled while the sound is playing + self:SetSoundState(false) + else + Sounds.SendAdjustableSound(self,false, 70 + math.ceil(MotorSpeedPerc * 30), 0.1 + (self.EffortScale * 0.9)) + end + end self.LastRotatorAngle = Rotator:GetAngles() @@ -820,32 +875,33 @@ do -- Metamethods do -- Input/Outputs/Eventually linking ACF.AddInputAction("acf_turret", "Active", function(Entity,Value) + if Entity.Disabled then return end + Entity.Active = tobool(Value) end) ACF.AddInputAction("acf_turret", "Angle", function(Entity,Value) local Ang = isangle(Value) and Value or Angle(0,0,0) - Entity.Manual = false - Entity.DesiredAngle = Ang + + Entity:InputDirection(Ang) end) ACF.AddInputAction("acf_turret", "Vector", function(Entity,Value) local Pos = isvector(Value) and Value or Vector(0,0,0) - Entity.Manual = false - Entity.UseVector = true - Entity.DesiredVector = Pos + + Entity:InputDirection(Pos) end) ACF.AddInputAction("acf_turret", "Bearing", function(Entity,Value) -- Only on horizontal drives if not isnumber(Value) then return end - Entity.Manual = true - Entity.DesiredDeg = Value + + Entity:InputDirection(Value) end) ACF.AddInputAction("acf_turret", "Elevation", function(Entity,Value) -- Only on vertical drives if not isnumber(Value) then return end - Entity.Manual = true - Entity.DesiredDeg = Value + + Entity:InputDirection(Value) end) end @@ -929,85 +985,15 @@ do -- Metamethods self:UpdateOverlay() end - local function ProxyACF_OnParented(self,Entity,Connected) - if not IsValid(Entity) then return end - if Entity:GetClass() ~= "acf_turret" then - if not (IsValid(self.ACF_TurretAncestor) or (Contraption.HasAncestor(self,self.ACF_TurretAncestor))) then - self.ACF_OnParented = nil - self.ACF_OnMassChange = nil - self.ACF_TurretAncestor = nil - return - end - - self.ACF_TurretAncestor:UpdateTurretMass() - - if Connected == true then - Entity.ACF_OnParented = self.ACF_OnParented - Entity.ACF_OnMassChange = self.ACF_OnMassChange - Entity.ACF_TurretAncestor = self.ACF_TurretAncestor - - for k in pairs(Entity:GetChildren()) do - if k:GetClass() == "acf_turret" then continue end - if not IsValid(k) then continue end - - ProxyACF_OnParented(Entity,k,true) - end - else - Entity.ACF_OnParented = nil - Entity.ACF_OnMassChange = nil - Entity.ACF_TurretAncestor = nil - end - end - end - - function ENT:ACF_OnParented(Entity, Connected) -- Potentially called many times a second, so we won't force mass to update + function ENT:ACF_OnParented(Entity, _) -- Potentially called many times a second, so we won't force mass to update if Entity:GetClass() == "acf_turret_rotator" then return end - self:UpdateTurretMass() - - if Entity:GetClass() == "acf_turret" then - if self:GetClass() == "acf_turret" then - if Connected == true then - Entity.ACF_TurretAncestor = self - self:UpdateTurretMass() - else - Entity.ACF_TurretAncestor = nil - if IsValid(Entity) then self:UpdateTurretMass() end - end - end - return - elseif IsValid(self.ACF_TurretAncestor) then - self.ACF_TurretAncestor:UpdateTurretMass() - end + self:UpdateTurretMass(false) -- Should only be called when parenting, checks the position of the motor relative to the ring -- Shooouuld be using ACF_OnParented as it was made with this in mind, but turret entities will overwrite it with the above function to ensure everything is captured if Entity:GetClass() == "acf_turret_motor" then Entity:ValidatePlacement() end if IsValid(self.Motor) then self.Motor:ValidatePlacement() end - - if Connected then - Entity.ACF_TurretAncestor = self - - Entity.ACF_OnMassChange = function(self) - if not IsValid(self.ACF_TurretAncestor) then self.ACF_OnMassChange = nil return end - if not IsValid(Entity) then return end - - self.ACF_TurretAncestor:UpdateTurretMass() - end - - Entity.ACF_OnParented = ProxyACF_OnParented - - for k in pairs(Entity:GetChildren()) do - if k:GetClass() == "acf_turret" then continue end - if not IsValid(k) then continue end - - ProxyACF_OnParented(Entity,k,true) - end - else - Entity.ACF_OnMassChange = nil - Entity.ACF_OnParented = nil - Entity.ACF_TurretAncestor = nil - end end function ENT:OnRemove() diff --git a/lua/entities/acf_turret_gyro/init.lua b/lua/entities/acf_turret_gyro/init.lua index 1a944a69b..1cd6978d3 100644 --- a/lua/entities/acf_turret_gyro/init.lua +++ b/lua/entities/acf_turret_gyro/init.lua @@ -6,6 +6,7 @@ include("shared.lua") -- Local Vars local ACF = ACF +local Damage = ACF.Damage local Classes = ACF.Classes local Utilities = ACF.Utilities local HookRun = hook.Run @@ -223,12 +224,8 @@ do -- Metamethods and other important stuff self:UpdateOverlay() end - function ENT:ACF_OnDamage(DmgResult, DmgInfo) - local HitRes = Damage.doPropDamage(self, DmgResult, DmgInfo) - + function ENT:ACF_PostDamage() self.DamageScale = math.max((self.ACF.Health / (self.ACF.MaxHealth * 0.75)) - 0.25 / 0.75,0) - - return HitRes end function ENT:ACF_OnRepaired() -- Normally has OldArmor, OldHealth, Armor, and Health passed @@ -249,6 +246,8 @@ do -- Metamethods and other important stuff end function ENT:IsActive() + if self.Disabled then return false end + if not (IsValid(self.Turret) or IsValid(self["Turret-H"]) or IsValid(self["Turret-V"])) then self:SetActive(false,"") return false end if (self.ACF.Health / self.ACF.MaxHealth) <= 0.25 then diff --git a/lua/entities/acf_turret_motor/init.lua b/lua/entities/acf_turret_motor/init.lua index 40e729183..2bb1d527f 100644 --- a/lua/entities/acf_turret_motor/init.lua +++ b/lua/entities/acf_turret_motor/init.lua @@ -6,6 +6,7 @@ include("shared.lua") -- Local Vars local ACF = ACF +local Damage = ACF.Damage local Classes = ACF.Classes local Utilities = ACF.Utilities local HookRun = hook.Run @@ -219,16 +220,16 @@ do -- Metamethods and other important stuff self:UpdateOverlay() end - function ENT:ACF_OnDamage(DmgResult, DmgInfo) - local HitRes = Damage.doPropDamage(self, DmgResult, DmgInfo) - + function ENT:ACF_PostDamage() self.DamageScale = math.max((self.ACF.Health / (self.ACF.MaxHealth * 0.75)) - 0.25 / 0.75,0) - return HitRes + if self.Turret then self.Turret:UpdateTurretSlew() end end function ENT:ACF_OnRepaired() -- Normally has OldArmor, OldHealth, Armor, and Health passed self.DamageScale = math.max((self.ACF.Health / (self.ACF.MaxHealth * 0.75)) - 0.25 / 0.75,0) + + if self.Turret then self.Turret:UpdateTurretSlew() end end function ENT:ACF_Activate(Recalc) @@ -254,8 +255,6 @@ do -- Metamethods and other important stuff self.Active = Active self.InactiveReason = Reason - if IsValid(self.Turret) then self.Turret:UpdateTurretSlew() end - self:UpdateOverlay(true) end @@ -295,9 +294,10 @@ do -- Metamethods and other important stuff end function ENT:IsActive() + if self.Disabled then return false end if self.ValidPlacement == false then return false end - if (self.ACF.Health / self.ACF.MaxHealth) <= 0.5 then + if (self.ACF.Health / self.ACF.MaxHealth) <= 0.25 then self:SetActive(false,"Too damaged!") return false end @@ -307,7 +307,7 @@ do -- Metamethods and other important stuff end function ENT:GetInfo() - return {Teeth = self.Teeth, Speed = self.Speed, Torque = self.Torque, Efficiency = self.Efficiency, Accel = self.Accel} + return {Teeth = self.Teeth, Speed = self.Speed, Torque = self.Torque * self.DamageScale, Efficiency = self.Efficiency, Accel = self.Accel} end end end \ No newline at end of file diff --git a/lua/entities/acf_turret_rotator/init.lua b/lua/entities/acf_turret_rotator/init.lua index 10a3411cf..37d9b7cdd 100644 --- a/lua/entities/acf_turret_rotator/init.lua +++ b/lua/entities/acf_turret_rotator/init.lua @@ -1,17 +1,21 @@ include("shared.lua") -ACF.Contraption.AddParentDetour("acf_turret_rotator", "turret") - -function ENT:Initialize(turret) - self.turret = turret or self:GetParent() -end +ACF.Contraption.AddParentDetour("acf_turret_rotator", "Turret") +-- One can't exist without the other function ENT:OnRemove() - if IsValid(self.turret) then - self.turret:Remove() + if IsValid(self.Turret) then + self.Turret:Remove() end end +-- This shouldn't be called usually due to parent detouring, but in the offchance that this is ever directly unparented from the turret ring, destroy it and the turret entity since it is no longer a valid turret +function ENT:ACF_OnParented(Entity, Connected) + if not IsValid(Entity) then return end + + if Connected == false and Entity == self.Turret then self:Remove() end +end + function ENT:UpdateTransmitState() return TRANSMIT_PVS end \ No newline at end of file diff --git a/models/acf/core/t_ring.dx80.vtx b/models/acf/core/t_ring.dx80.vtx new file mode 100644 index 0000000000000000000000000000000000000000..4cd890359c98f8ac54b56b84322ebdc1a129160e GIT binary patch literal 3987 zcmZA3cUaAB0LJnAq-5kJGP3vHd+$voJCVIdc0^=EMr345viHi~dn6&*du1m{vfleS z-us`o`}%#KPv^L~T92$0Rtd|66~apDoDmKQ+k`E`7-76H zPM9G02)@EZVX`nuSR?oe-okkym2g4WBy12i3XwvDa8-yBt_wGXvqHFVPFOFb5pD_A zx0HFOSl>Se2lUi6;fiosxFnnwP6_tHUSXfGN3au)2*(BM+upp4lX~Qj5G{lWw!%8$ zm~d1$ETj~62|I<|!d79kU~4hr>#-AJ^i&ExVk2zVQ(c5^LTAB6XfAXTnh1@Bv4WQ{ zM;Imy6b1+*g}%ZRVYc8e%oSz{0YX>7Luf5@6g~&qAE=Nr)9*3$KKi!VBTK@Jx6rJP{rXkA#Q91L3}KPq-^I z5Ui*9fo!BlY6vxj_Ci~sjZi_TEL0In3TXvLA)}C0$R^|xatj57f4Z#zli)057xD;sg+fAMp}0^&C@qu`$_sUbidOI!femNj4Vw)wTP_9cSUaTT>|oE? z@s`b=S8Aj|S~#!{NXI$gAI<^k+4Q{a%fLFaj>yP4A`|BbC)Np>IVWV{oRF2x%3H;3 ztTXG3?3^=laL#-!Ijp~)av~RUBPXvs$jj3-5Aq>DPt$xTz+1}v))qh^6y|59g-`@V zd72hMF%;)%S_~ynlBa12ltO8qrln8@WqF#GK{=F11>Sv@=T!-nd74&26;$PES_Rcm zou_Fv)Id$1rZrFtwRxJ>LLJoQX<7&MP@ku1Jv2Z=o~8}Z2#tA~HbN6LMKd&Ko1+ES zl4{A*g>^wI&IPSG7r3&nXv4X}jdep?&JFF*o|hZj!Rn4Y+oKaYa-Gnb>jZb!9bGtg zc(5Mm%6Xt0=K)XF6Wuva^gvHuo@_6kz0n7K*?v5Gqd(69Y;SfT&jA?3a}dwL>|hMR zPz-|?>xJQ*7e-(tFE4f!&rv)_V+^m6>{y;-d3v+n7{_^IJm(D`)&~_#o=Lp=U^0I){a8Ot;ruX_^TRZD8m4p8;LrMF2Ir5NoIe8C0L#>pDh)vu^Y-TrO3%40t*{#^dZN+wWJ9co} zv6J12UEEIWW_R;zFK1WqD~4bX7lOU)UhL!cBAgA!el8pb*aJAo9l#;>5Ds&PaD+XA zqudc3V~^oDcMK=k6FAA8z$x|=|2}KjP@KjYoMq4A9CsGy+4H!-oySG?A}(Q`-$J|Z~WnSw`AdcF>l&=+s@x?7G5bh8;dP#``^v#-~ZSD_Fo_z_f`M^ literal 0 HcmV?d00001 diff --git a/models/acf/core/t_ring.dx90.vtx b/models/acf/core/t_ring.dx90.vtx new file mode 100644 index 0000000000000000000000000000000000000000..726750b47ae9fc9fb6f5d94f3a5da2558024487c GIT binary patch literal 3987 zcmZA3cUaAB0LJnAq-5kxWMpUWz4zW3*@^5uvLhlAk;us2d(Uh#l924Zcao6o_1@3% z-v7Ma*YEp$I>&W&q@$lbfULYKAT?6(ywa_(y@lsYN5Gu000a1ldBTO4jd{R)m_J}XEU*~4@(aLXW9ereGw3b!5~c_N!c<|J;2}&Gx(Uq$ zFQJLhSZF0Q7e)$$gki!!p{LMS=q~gSMhQcN;lf~HrSSJ##Bah1J+e$#E-Vq23X6or z!Wv=bqjTZPSnt;LA1$4MV2>ItgwA`UsPSnL?m2OPC=930(wFp{3A4NEF@*3Bns8UU)6s5^f8>gphgp^{KsNGmuA8H6lCRw1X5OUNhW7YYkSgsMUbp|ns& zs4dhL>In6P8bW)arqEWfzB|o#tc4zN7b*x1%!e^QK6VnQYa;q6>16Pt>7;L8_vQTHXB~HTngB+c1X$D!Jf0@ zEt@^B)JTK0a9|yfj&s02oC6$LN2KQ*;lw&21LuT{oD-Z`XJq1>k(qNw7B&lS6|=G~ ztP8SnF38Tg@U>*O{(8!RoXCY7ymBKCPt)9RMP8n!uE@t*%DmR*Lje@zXQl;E2!(l? z7D5pej<=G4Uc=l&|u>*Ma$3UI~c@AO+VK9bZ zD7;y34CA~p93yynvm<$q^0Od|6-kv3{7y z`SJBk<|+`eQQZk13o#0@wgd`v_Bc49ZXn_qhgyOdvXCH8PDv6tP8ecWDzv*Fm!h2sEw00+4PIK&>pVeSx)ut#u| zJAz~EF&yWPA%cy-2`&OB*^~VHtYTN=6iy?Ojl>x)5>adv&T>(RW}|VAi^h5OJT7qO z5yQsdA{T>LHWrt-SX^c=;|g~fSJ|t$#$CmA_Bw8G*Kw1*iCf%F+-7g%4tE=O*}J&M z-Nk+OJ|1xQ@sNFpN8Cd^W*_4T_ZUyvr+9|vc!8JfOT6MmPPS$JQ}n|9u|^EaD?R|?L?V$0h8ceDEU|MkE97hh5LdjJ3c literal 0 HcmV?d00001 diff --git a/models/acf/core/t_ring.mdl b/models/acf/core/t_ring.mdl new file mode 100644 index 0000000000000000000000000000000000000000..aac20e6002e0c655efce28e0688be97db6c419e5 GIT binary patch literal 1744 zcmds2Jxc>Y5S_%&!XjzK!otEzEd&Kayabzoot6iB<}UHzE|KJd78U{V2M9*enKFMs zY>!~6pkQHPC-!MY@tnEcHQDnnB&`Fpv-9T7%x-RXliS+aT>*fn{lzIQUr_VqilI7t z6|+=avULk1!^`7jB=-g&(_z|n(Po*pm8gsu21uK6#yA>dzOZ-PSH^o4h`)+!AnR~T z^AkRwn_yh3K|;R3Tpk<7USPdbR3R*g;L?RN9jd9n$BbjGHc z`{{W$_D=cz**7Dc(*Y#gDV~k}p0thb8lHznvzPzyS{RX8jq7dc6+d%OQ3S zu5eMbE>hev@Mp1V>oR*(kLq2bPSPYqrV;O+HehQH;dXVaR+HI zC+MFAfCXJ2>1FDOw=?VyC$E*vs$*23QZ7578wIUqIiQ%jWq?1}npr~ppjJFSG#vx% MvTj&a)kjqL0aY2L2><{9 literal 0 HcmV?d00001 diff --git a/models/acf/core/t_ring.phy b/models/acf/core/t_ring.phy new file mode 100644 index 0000000000000000000000000000000000000000..0c67aa3f7593b04a63184cc701becd7f757fd3b0 GIT binary patch literal 5352 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 Tj^1?~<8{1=J&Q13_2d5rH%GWL literal 0 HcmV?d00001 diff --git a/models/acf/core/t_trun.dx80.vtx b/models/acf/core/t_trun.dx80.vtx new file mode 100644 index 0000000000000000000000000000000000000000..757262dcf13065b55d72cfdc206f4b485e8cc127 GIT binary patch literal 3267 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 literal 0 HcmV?d00001 diff --git a/models/acf/core/t_trun.sw.vtx b/models/acf/core/t_trun.sw.vtx new file mode 100644 index 0000000000000000000000000000000000000000..5bbf1e06a7e33107f6f2bf2b53d27515a1a621ea GIT binary patch literal 3259 zcmZA3Wp`6)7=_^t6p9yz!QGv~VW7Abx8m;Z?(P(Kcc-|!ySqEg`~_yGnfuFRt+&sW zu@sm&y#h1-|E8agxdom}5YRtfe|r7$BT+s& z@WdZpF%JZ0;DEp_R?Jgs6xbnF>{tb)-@gMW42pn)pb*Fh@`F4eFYva(4ReEBpcJS8 zih+`#IH(N#u7Vqu0A)Z~;CCB5rW~jQ+JJW8FW~bY{H^k!G?)M;gDM~#)C9FbT~H0w z0o6f0;M~E#9sz2BNnku^0_uZ?AOti3jX-nI48#R3KuZt_;(=D6HE0hyg0`Ro=ma{0 zP%s2^0sf>0PeM1)1M~&GKzA?@3&4QvOgz;3V?>;(J40gxIT0tZ1Fa1@$HfT!RY$O0aNtl$NB4zht);5En& z-hj8@9e58ufR7*t_yoR#FW@Km3ci8AK~C@k6a^JQRZs&o22DXv&>IW|Bf&(l2rLDw zz;dt}tOJ|CX0QY70(-zd5DkuiB~}uzqzdy&rsQ5Jl+sH|rPNBJv`XhInDok^jLM|U%A%~w z=6l(d!2cN_m&kDr2QnS(Q_HRZvC! zrAn&o8E%zR6;)L=RaXtwR4vt39o1Dm&j_oA>Z^eos*xJ2iJGdJS97&cOCKMpR%)#_ zYO8i?uMX;{PU@^K>Z)#@-K`FOO-0nhuRfc4`uNo9rQY7FiTbFo`l-JLXrKmZu!d-; zhH1D)XrxAIw8m(x#%a7JXrd-*vZiRNrfIrnXr^Xqw&rNA=4rkbXrZFCNQ<>ZOSMeP zwL&YkN~^U-Yqd`6wLu%TNt?ArTeVHwwL?3#OS`p4d$mvdbwCFdtwTDjBRcAJOvk-W z=%h~Rw9e?P&gr}^cwW>c&&&Sl50C4na^DcoIIbJwzVSUnT{ph_Ceao54f9Oux?%2{ z(ldpBC#BCqTAzn>u3OecGr4JI*S+d)v+X>h2opx_3O=`F?xXz3aYR+_kIgcJl1tM+^DY6n5X9eg~0$SFOBL z6Ytf>b?>?FK;3uWA$s7t5B11vm`jgv}cf}XnaWIc1`X?pI))Ahn@re3=B z9KCYu`FicvQF`OnOZ3*Qm+PHNuh4t1Rr=uKYxU8^H|Ud#Z`NlQ-=;4vzEfY_`1J#elR6p+Or``Ru=NVW3`j&-7m^ts{h^o1xg1RumAu6 literal 0 HcmV?d00001 diff --git a/models/acf/core/t_trun.vvd b/models/acf/core/t_trun.vvd new file mode 100644 index 0000000000000000000000000000000000000000..3f9888b668f90391e7ad0ee1d268cc49e1761877 GIT binary patch literal 15296 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 literal 0 HcmV?d00001