@@ -19,7 +19,8 @@ BUILDER:StartStorableVars()
1919 }})
2020 :GetSet (" Length" , 50 , {editor_onchange = function (self ,num ) return math.floor (math .Clamp (num ,- 32768 ,32767 )) end })
2121 :GetSet (" Radius" , 50 , {editor_onchange = function (self ,num ) return math.floor (math .Clamp (num ,- 32768 ,32767 )) end })
22- :GetSet (" Preview" ,false )
22+ :GetSet (" Preview" ,false , {description = " preview target selection boxes" })
23+ :GetSet (" PreviewForces" ,false , {description = " preview the predicted forces" })
2324
2425 :SetPropertyGroup (" BaseForces" )
2526 :GetSet (" BaseForce" , 0 )
@@ -57,7 +58,7 @@ Radial gets the base directions from the targets to the force part]]})
5758 :GetSet (" AccountMass" , false , {description = " Apply acceleration according to mass." })
5859 :GetSet (" Falloff" , false , {description = " Whether the force to apply should fade with distance" })
5960 :GetSet (" ReverseFalloff" , false , {description = " The reverse of the falloff means the force fades when getting closer." })
60- :GetSet (" Levitation" , false , {description = " Tries to stabilize the force to levitate targets at a certain height relative to the part" })
61+ :GetSet (" Levitation" , false , {description = " Tries to stabilize the force to levitate targets at a certain height relative to the part. \n Requires vertical forces. Easiest way is to enter 0 0 500 in 'added vector force' with the Global vector mode which is already there by default. " })
6162 :GetSet (" LevitationHeight" , 0 )
6263
6364 :SetPropertyGroup (" Damping" )
@@ -102,12 +103,325 @@ function PART:OnRemove()
102103end
103104
104105
106+ local white = Color (255 ,255 ,255 )
107+ local red = Color (255 ,0 ,0 )
108+ local green = Color (0 ,255 ,0 )
109+ local blue = Color (0 ,0 ,255 )
110+ local red2 = Color (255 ,100 ,100 )
111+ local red3 = Color (255 ,200 ,200 )
112+ local function draw_force_line (pos , amount )
113+ local length = amount :Length ()
114+ local magnitude = length / 20
115+ amount :Normalize ()
116+ local x = amount .x
117+ local y = amount .y
118+ local z = amount .z
119+ local dir = amount :Angle ()
120+ render .DrawLine ( pos , 9 * magnitude * x * Vector (1 ,0 ,0 ) + pos , red , false )
121+ render .DrawLine ( pos , 9 * magnitude * y * Vector (0 ,1 ,0 ) + pos , green , false )
122+ render .DrawLine ( pos , 9 * magnitude * z * Vector (0 ,0 ,1 ) + pos , blue , false )
123+ cam .IgnoreZ ( true )
124+ for i = 0 ,8 ,1 do
125+ local scrolling = - i + math.floor ((CurTime () % 1 ) * 8 ) + 2
126+ if scrolling == 0 then
127+ render .DrawLine ( (i ) * magnitude * amount + pos , (i + 1 ) * magnitude * amount + pos , red , false )
128+ elseif scrolling == 1 then
129+ render .DrawLine ( (i ) * magnitude * amount + pos , (i + 1 ) * magnitude * amount + pos , red2 , false )
130+ elseif scrolling == 2 then
131+ render .DrawLine ( (i ) * magnitude * amount + pos , (i + 1 ) * magnitude * amount + pos , red3 , false )
132+ else
133+ render .DrawLine ( (i ) * magnitude * amount + pos , (i + 1 ) * magnitude * amount + pos , white , false )
134+ end
135+
136+ end
137+ cam .IgnoreZ ( false )
138+ end
139+
140+
141+ -- convenience functions and tables from net_combat
142+
143+ local pre_excluded_ent_classes = {
144+ [" info_player_start" ] = true ,
145+ [" aoc_spawnpoint" ] = true ,
146+ [" info_player_teamspawn" ] = true ,
147+ [" env_tonemap_controller" ] = true ,
148+ [" env_fog_controller" ] = true ,
149+ [" env_skypaint" ] = true ,
150+ [" shadow_control" ] = true ,
151+ [" env_sun" ] = true ,
152+ [" predicted_viewmodel" ] = true ,
153+ [" physgun_beam" ] = true ,
154+ [" ambient_generic" ] = true ,
155+ [" trigger_once" ] = true ,
156+ [" trigger_multiple" ] = true ,
157+ [" trigger_hurt" ] = true ,
158+ [" info_ladder_dismount" ] = true ,
159+ [" info_particle_system" ] = true ,
160+ [" env_sprite" ] = true ,
161+ [" env_fire" ] = true ,
162+ [" env_soundscape" ] = true ,
163+ [" env_smokestack" ] = true ,
164+ [" light" ] = true ,
165+ [" move_rope" ] = true ,
166+ [" keyframe_rope" ] = true ,
167+ [" env_soundscape_proxy" ] = true ,
168+ [" gmod_hands" ] = true ,
169+ [" env_lightglow" ] = true ,
170+ [" point_spotlight" ] = true ,
171+ [" spotlight_end" ] = true ,
172+ [" beam" ] = true ,
173+ [" info_target" ] = true ,
174+ [" func_lod" ] = true ,
175+ [" func_brush" ] = true ,
176+ [" phys_bone_follower" ] = true ,
177+ }
178+
179+ local physics_point_ent_classes = {
180+ [" prop_physics" ] = true ,
181+ [" prop_physics_multiplayer" ] = true ,
182+ [" prop_ragdoll" ] = true ,
183+ [" weapon_striderbuster" ] = true ,
184+ [" item_item_crate" ] = true ,
185+ [" func_breakable_surf" ] = true ,
186+ [" func_breakable" ] = true ,
187+ [" physics_cannister" ] = true
188+ }
189+
190+ local function MergeTargetsByID (tbl1 , tbl2 )
191+ for i ,v in ipairs (tbl2 ) do
192+ tbl1 [v :EntIndex ()] = v
193+ end
194+ end
195+
196+ local function Is_NPC (ent )
197+ return ent :IsNPC () or ent :IsNextBot () or ent .IsDrGEntity or ent .IsVJBaseSNPC
198+ end
199+
200+ local function ProcessForcesList (ents_hits , tbl , pos , ang , ply )
201+ for i ,v in pairs (ents_hits ) do
202+ if pre_excluded_ent_classes [v :GetClass ()] then ents_hits [i ] = nil end
203+ end
204+ local ftime = 0.016 -- approximate tick duration
205+ local BASEFORCE = 0
206+ local VECFORCE = Vector (0 ,0 ,0 )
207+ if tbl .Continuous then
208+ BASEFORCE = tbl .BaseForce * ftime * 3.3333 -- weird value to equalize how 600 cancels out gravity
209+ VECFORCE = tbl .AddedVectorForce * ftime * 3.3333
210+ else
211+ BASEFORCE = tbl .BaseForce
212+ VECFORCE = tbl .AddedVectorForce
213+ end
214+ for _ ,ent in pairs (ents_hits ) do
215+ if ent :IsWeapon () or ent :GetClass () == " viewmodel" or ent :GetClass () == " func_physbox_multiplayer" then continue end
216+ if ent :GetPos ():Distance (ply :GetPos ()) < 300 then
217+ print (ent )
218+ end
219+ local phys_ent
220+ local is_player = ent :IsPlayer ()
221+ local is_physics = (physics_point_ent_classes [ent :GetClass ()] or string.find (ent :GetClass ()," item_" ) or string.find (ent :GetClass ()," ammo_" ) or (ent :IsWeapon () and not IsValid (ent :GetOwner ())))
222+ local is_npc = Is_NPC (ent )
223+
224+ if is_npc and not tbl .NPC then continue end
225+ if is_player and not (tbl .Players or ent == tbl :GetPlayerOwner ()) then continue end
226+ if is_player and not tbl .AffectSelf and ent == tbl :GetPlayerOwner () then continue end
227+ if is_physics and not tbl .PhysicsProps then continue end
228+ if not is_npc and not is_player and not is_physics then
229+ if not tbl .PointEntities then continue end
230+ end
231+
232+ local is_phys = true
233+ phys_ent = ent
234+ is_phys = false
235+
236+ local oldvel
237+
238+ if IsValid (phys_ent ) then
239+ oldvel = phys_ent :GetVelocity ()
240+ else
241+ oldvel = Vector (0 ,0 ,0 )
242+ end
243+
244+
245+ local addvel = Vector (0 ,0 ,0 )
246+ local add_angvel = Vector (0 ,0 ,0 )
247+
248+ local ent_center = ent :WorldSpaceCenter () or ent :GetPos ()
249+
250+ local dir = ent_center - pos -- part
251+ local locus_pos = pos
252+ if tbl .Locus ~= nil then
253+ if tbl .Locus :IsValid () then
254+ locus_pos = tbl .Locus :GetWorldPosition ()
255+ end
256+ end
257+ local dir2 = ent_center - locus_pos
258+
259+ local dist_multiplier = 1
260+ local damping_dist_mult = 1
261+ local up_mult = 1
262+ local distance = (ent_center - pos ):Length ()
263+ local height_delta = pos .z + tbl .LevitationHeight - ent_center .z
264+
265+ -- what it do
266+ -- if delta is -100 (ent is lower than the desired height), that means +100 adjustment direction
267+ -- height decides how much to knee the force until it equalizes at 0
268+ -- clamp the delta to the ratio levitation height
269+
270+ if tbl .Levitation then
271+ up_mult = math .Clamp (height_delta / (5 + math.abs (tbl .LevitationHeight )),- 1 ,1 )
272+ end
273+
274+ if tbl .BaseForceAngleMode == " Radial" then -- radial on self
275+ addvel = dir :GetNormalized () * tbl .BaseForce
276+ elseif tbl .BaseForceAngleMode == " Locus" then -- radial on locus
277+ addvel = dir2 :GetNormalized () * tbl .BaseForce
278+ elseif tbl .BaseForceAngleMode == " Local" then -- forward on self
279+ addvel = ang :Forward () * tbl .BaseForce
280+ end
281+
282+ if tbl .VectorForceAngleMode == " Global" then -- global
283+ addvel = addvel + tbl .AddedVectorForce
284+ elseif tbl .VectorForceAngleMode == " Local" then -- local on self
285+ addvel = addvel
286+ + ang :Forward ()* tbl .AddedVectorForce .x
287+ + ang :Right ()* tbl .AddedVectorForce .y
288+ + ang :Up ()* tbl .AddedVectorForce .z
289+
290+ elseif tbl .VectorForceAngleMode == " Radial" then -- relative to locus or self
291+ ang2 = dir :Angle ()
292+ addvel = addvel
293+ + ang2 :Forward ()* tbl .AddedVectorForce .x
294+ + ang2 :Right ()* tbl .AddedVectorForce .y
295+ + ang2 :Up ()* tbl .AddedVectorForce .z
296+ elseif tbl .VectorForceAngleMode == " RadialNoPitch" then -- relative to locus or self
297+ dir .z = 0
298+ ang2 = dir :Angle ()
299+ addvel = addvel
300+ + ang2 :Forward ()* tbl .AddedVectorForce .x
301+ + ang2 :Right ()* tbl .AddedVectorForce .y
302+ + ang2 :Up ()* tbl .AddedVectorForce .z
303+ end
304+
305+ --[[ if tbl.TorqueMode == "Global" then
306+ add_angvel = tbl.Torque
307+ elseif tbl.TorqueMode == "Local" then
308+ add_angvel = ang:Forward()*tbl.Torque.x + ang:Right()*tbl.Torque.y + ang:Up()*tbl.Torque.z
309+ elseif tbl.TorqueMode == "TargetLocal" then
310+ add_angvel = tbl.Torque
311+ elseif tbl.TorqueMode == "Radial" then
312+ ang2 = dir:Angle()
313+ addvel = ang2:Forward()*tbl.Torque.x + ang2:Right()*tbl.Torque.y + ang2:Up()*tbl.Torque.z
314+ end]]
315+
316+ local mass = 1
317+ if IsValid (phys_ent ) then
318+ if phys_ent .GetMass then
319+ phys_ent :GetMass ()
320+ end
321+ end
322+ if is_phys and tbl .AccountMass then
323+ if not is_npc then
324+ addvel = addvel * (1 / math.max (mass ,0.1 ))
325+ else
326+ addvel = addvel
327+ end
328+ add_angvel = add_angvel * (1 / math.max (mass ,0.1 ))
329+ end
330+
331+ if tbl .Falloff then
332+ dist_multiplier = math .Clamp (1 - distance / math.max (tbl .Radius , tbl .Length ),0 ,1 )
333+ end
334+ if tbl .ReverseFalloff then
335+ dist_multiplier = 1 - math .Clamp (1 - distance / math.max (tbl .Radius , tbl .Length ),0 ,1 )
336+ end
337+
338+ if tbl .DampingFalloff then
339+ damping_dist_mult = math .Clamp (1 - distance / math.max (tbl .Radius , tbl .Length ),0 ,1 )
340+ end
341+ if tbl .DampingReverseFalloff then
342+ damping_dist_mult = 1 - math .Clamp (1 - distance / math.max (tbl .Radius , tbl .Length ),0 ,1 )
343+ end
344+ damping_dist_mult = damping_dist_mult
345+ local final_damping = 1 - (tbl .Damping * damping_dist_mult )
346+
347+ if tbl .Levitation then
348+ addvel .z = addvel .z * up_mult
349+ end
350+
351+ addvel = addvel * dist_multiplier
352+ draw_force_line (ent :WorldSpaceCenter (), addvel )
353+
354+ end
355+ end
356+
357+ local function preview_process_ents (tbl )
358+ ply = tbl :GetPlayerOwner ()
359+ local pos = tbl .pos
360+ local ang = tbl .ang
361+
362+ if tbl .HitboxMode == " Sphere" then
363+ local ents_hits = ents .FindInSphere (pos , tbl .Radius )
364+ ProcessForcesList (ents_hits , tbl , pos , ang , ply )
365+ elseif tbl .HitboxMode == " Box" then
366+ local mins
367+ local maxs
368+ if tbl .HitboxMode == " Box" then
369+ mins = pos - Vector (tbl .Radius , tbl .Radius , tbl .Length )
370+ maxs = pos + Vector (tbl .Radius , tbl .Radius , tbl .Length )
371+ end
372+
373+ local ents_hits = ents .FindInBox (mins , maxs )
374+ ProcessForcesList (ents_hits , tbl , pos , ang , ply )
375+ elseif tbl .HitboxMode == " Cylinder" then
376+ local ents_hits = {}
377+ if tbl .Length ~= 0 and tbl .Radius ~= 0 then
378+ local counter = 0
379+ MergeTargetsByID (ents_hits ,ents .FindInSphere (pos , tbl .Radius ))
380+ for i = 0 ,1 ,1 / (math.abs (tbl .Length / tbl .Radius )) do
381+ MergeTargetsByID (ents_hits ,ents .FindInSphere (pos + ang :Forward ()* tbl .Length * i , tbl .Radius ))
382+ if counter == 200 then break end
383+ counter = counter + 1
384+ end
385+ MergeTargetsByID (ents_hits ,ents .FindInSphere (pos + ang :Forward ()* tbl .Length , tbl .Radius ))
386+ -- render.DrawWireframeSphere( self:GetWorldPosition() + self:GetWorldAngles():Forward()*(self.Length - 0.5*self.Radius), 0.5*self.Radius, 10, 10, Color( 255, 255, 255 ) )
387+ elseif tbl .Radius == 0 then MergeTargetsByID (ents_hits ,ents .FindAlongRay (pos , pos + ang :Forward ()* tbl .Length )) end
388+ ProcessForcesList (ents_hits , tbl , pos , ang , ply )
389+ elseif tbl .HitboxMode == " Cone" then
390+ local ents_hits = {}
391+ local steps
392+ steps = math .Clamp (4 * math.ceil (tbl .Length / (tbl .Radius or 1 )),1 ,50 )
393+ for i = 1 ,0 ,- 1 / steps do
394+ MergeTargetsByID (ents_hits ,ents .FindInSphere (pos + ang :Forward ()* tbl .Length * i , i * tbl .Radius ))
395+ end
396+
397+ steps = math .Clamp (math.ceil (tbl .Length / (tbl .Radius or 1 )),1 ,4 )
398+
399+ if tbl .Radius == 0 then MergeTargetsByID (ents_hits ,ents .FindAlongRay (pos , pos + ang :Forward ()* tbl .Length )) end
400+ ProcessForcesList (ents_hits , tbl , pos , ang , ply )
401+ elseif tbl .HitboxMode == " Ray" then
402+ local startpos = pos + Vector (0 ,0 ,0 )
403+ local endpos = pos + ang :Forward ()* tbl .Length
404+ ents_hits = ents .FindAlongRay (startpos , endpos )
405+ ProcessForcesList (ents_hits , tbl , pos , ang , ply )
406+ end
407+ end
408+
105409function PART :OnDraw ()
106410 self .pos ,self .ang = self :GetDrawPosition ()
107- if not self .Preview then pac .RemoveHook (" PostDrawOpaqueRenderables" , " pac_force_Draw" .. self .UniqueID ) end
411+ if not self .Preview and not self . PreviewForces then pac .RemoveHook (" PostDrawOpaqueRenderables" , " pac_force_Draw" .. self .UniqueID ) end
108412
109- if self .Preview then
413+ if self .Preview or self . PreviewForces then
110414 pac .AddHook (" PostDrawOpaqueRenderables" , " pac_force_Draw" .. self .UniqueID , function ()
415+ if self .PreviewForces then
416+ -- recalculating forces every drawframe is cringe for other players
417+ if self :GetPlayerOwner () == pac .LocalPlayer then
418+ if self .NPC or self .Players or self .AffectSelf or self .PhysicsProps or self .PointEntities then
419+ preview_process_ents (self )
420+ end
421+ end
422+ end
423+ if not self .Preview then return end
424+
111425 if self .HitboxMode == " Box" then
112426 local mins = Vector (- self .Radius , - self .Radius , - self .Length )
113427 local maxs = Vector (self .Radius , self .Radius , self .Length )
@@ -204,6 +518,7 @@ function PART:Impulse(on)
204518 end
205519 end
206520
521+ if not self .NPC and not self .Players and not self .AffectSelf and not self .PhysicsProps and not self .PointEntities then return end
207522 net .Start (" pac_request_force" , true )
208523 net .WriteVector (self :GetWorldPosition ())
209524 net .WriteAngle (self :GetWorldAngles ())
0 commit comments