· 2 years ago · Aug 09, 2023, 09:45 AM
1--[[
2 MainSystem
3
4 In this Script, this consists of a lot of codes, and it's gun functionality.
5
6 Made by DryOfficial, used by Arrowhead Corporation
7]]
8
9--[[ Services ]]--
10local Players = game:GetService('Players')
11local Replicated = game:GetService('ReplicatedStorage')
12local CollectionService = game:GetService('CollectionService')
13
14-- Get the Resources, it consists of Remotes and more
15local Resources = Replicated:WaitForChild('AGS_Resources')
16local Root = script.Parent.Parent -- Get the directory
17
18local Client = script.Parent:WaitForChild('Client')
19local Server = script.Parent:WaitForChild('Server')
20-- Core Scripts Container for preload
21
22-- Modules --
23local WeldModule = require(Resources.Modules.WeldModule)
24local FastCast = require(Resources.Modules.FastCastRedux) -- For animated bullets ;)
25local CastTypes = require(Resources.Modules.FastCastRedux.TypeDefinitions) -- For Types
26
27local AmmoFolder = Resources:WaitForChild('Ammo')
28local Fragments = Root:WaitForChild('Fragments')
29-- Modules Container for preload
30
31--[[ Variables ]]
32local ScriptName = '(.*)<(.*)>' -- ScriptName<Parent>
33local DebugHitTagName = 'AGS_DebugHit'
34--[[
35 Attachment's Tag so where it gets all the attachment of where it goes.
36 Basically the attachment itself are parented to a part when hit.
37-- So give him a tag so it gets deletable when someone wants to clear the ray hits.
38]]
39local DebugEnabled = false
40-- If Debug is enabled, Features like Rayhit will be shown.
41
42local Setupped_Ammo = {}
43--[[
44 Table consists of Ammo Data,
45 so it shoots out a specific ammunition what gun is using.
46 So since this exists, any types of gun can be implemented.
47 For example, we can have Flamethrower, it shoots out fire
48]]
49
50--[[
51 Deployable Scripts are dictionary that it's own table
52 consists scripts that is loaded and ready to be deployed.
53 Basically this is just advanced way to load scripts
54]]
55local DeployableScripts = {
56 ['Character'] = {}, -- LocalScripts deployed in Character
57 ['PlayerScripts'] = {}, -- LocalScripts deployed in Player Scripts
58}
59
60--[[
61 Create folder named "IgnoreContainer" which
62 should contain bullets and more stuff that should be ignored in raycasting.
63]]
64local IgnoreContainer = nil
65
66if workspace:FindFirstChild('IgnoreContainer') then
67 IgnoreContainer = workspace['IgnoreContainer']
68 -- It exists, no need for a FindFirstChild for it.
69else
70 --[[
71 It doesnt exist, create a folder.
72 Put them in workspace.
73 Name it IgnoreContainer. So client can find it.
74 ]]
75 IgnoreContainer = Instance.new('Folder')
76 IgnoreContainer.Parent = workspace
77 IgnoreContainer.Name = 'IgnoreContainer'
78end
79
80-- Load up the Client based Components
81-- It detects the script if its LocalScript.
82-- If its not, it gets ignored.
83local function preloadClientComponent()
84 for i,v in Client:GetChildren() do
85 local Name,Parent = string.match(v.Name, ScriptName)
86 if v:IsA('LocalScript') and Parent then
87 local Table = DeployableScripts[Parent]
88 if Table then
89 v.Name = Name
90 table.insert(Table, v)
91 end
92 end
93 end
94
95 -- Its done, print it.
96 print('[GunSystem] Client Components successfully loaded')
97end
98
99-- Load up the Server based Components
100-- It detects the script if its Server script (actually ModuleScript),
101-- If its not, it gets ignored.
102local function preloadServerComponent()
103 for i,v in Server:GetChildren() do
104 local Name,Parent = string.match(v.Name, ScriptName)
105
106 --[[
107 I know it's not checking Server scripts,
108 But I wanted to make it ModuleScript.
109
110 I dont want it to be executed automatically.
111 Or I need to implement a thing that should disable the components.
112 ]]
113 if v:IsA('ModuleScript') and Parent then
114 local Table = DeployableScripts[Parent]
115
116 if Table then
117 v.Name = Name
118 table.insert(Table, v)
119 end
120 end
121 end
122
123 -- Its done, print it.
124 print('[GunSystem] Server Components successfully loaded')
125end
126
127--[[
128 OnRayUpdated, basically if Ray wants to be updated,
129 the visualization should be also updated.
130]]
131local function OnRayUpdated(cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject)
132 --[[
133 I didnt create this code,
134 I just straight up stealing it.
135 Heheehehehehehehhehehehehehe
136
137 ~ DryOfficial
138 ]]
139
140 if cosmeticBulletObject == nil then
141 -- It doesnt exist, stop it.
142 return
143 end
144
145 -- Create a new position of where ray goes,
146 local bulletLength = cosmeticBulletObject.Size.Z / 2
147 local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
148
149 -- Update it
150 cosmeticBulletObject.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
151end
152
153--[[
154 The Cast is terminated, start deleting bullet object.
155 This isnt for if ray is hit,
156 this is for when ray is done in 2 scenarios:
157 1. The Ray is hit, activeCast is terminated
158 2. The Ray is out of bounds, activeCast is terminated
159 - other scenarios -
160]]
161local function castTerminating(activeCast: CastTypes.ActiveCast)
162 --[[
163 Deletes/Destroys the Bullet Object once activeCast is terminated.
164 If you dont know, the BulletObject is for ray tracer.
165 ]]
166 activeCast.RayInfo.CosmeticBulletObject:Destroy()
167end
168
169--[[
170 This is a function that get called if cast are hit.
171 This is to tell if a person shot a another person or a object.
172]]
173local function rayHit(activeCast: CastTypes.ActiveCast, rayResult, velocity, bullet)
174 -- Get the result and change it's type.
175 local result = rayResult :: RaycastResult
176
177 if DebugEnabled then
178 --[[
179 If debug is enabled, start creating things like where it shot.
180 Creates a unused attachment that is visible,
181 so you can tell where it got shot.
182 ]]
183
184 -- Create a attachment.
185 local attachment = Instance.new('Attachment')
186 attachment.Parent = result.Instance
187 attachment.WorldPosition = result.Position
188 attachment.Visible = true
189
190 -- Give a attachment to a tag so it can be cleared
191 -- without the code that should be getting advanced
192 CollectionService:AddTag(attachment, DebugHitTagName)
193 end
194
195 -- Fire the rayhit event founded in Resources,
196 -- also add some variables
197 Resources.RayHit:Fire(result.Instance, result.Position)
198end
199
200--[[
201 Loads up the main components such as Ammo Folder,
202 We dont call them fragments, fragments is a kind of extension.
203
204 So we make a another function separated.
205]]
206local function preloadMainComponents()
207 -- Gets the ammo folder
208 -- So we can have types of ammo that gun can be fired.
209 for i,__module in AmmoFolder:GetChildren() do
210 local ok,module = pcall(require, __module)
211 -- Creates a safety function,
212 -- basically calls the function without any error.
213
214 if ok then
215 -- It works, it's time to setup the ammo.
216
217 if not Setupped_Ammo[__module.Name] then
218 -- It doesnt exist in the Setupped Ammo
219
220 local CastObject = FastCast.new()
221 -- Creates Caster using FastCastRedux
222
223 -- Creates a Data
224 Setupped_Ammo[__module.Name] = {
225 set = module,
226 forUsage = {
227 cast = CastObject
228 }
229 }
230
231 -- Connect the events.
232 CastObject.LengthChanged:Connect(OnRayUpdated)
233 CastObject.RayHit:Connect(rayHit)
234 CastObject.CastTerminating:Connect(castTerminating)
235 else
236 --[[
237 It exists!
238 I dont think we can make a ammo that has the same name to others.
239 So we start throwing out the warning.
240 ]]
241 warn('[GunSystem] "'..__module.Name..'" already exists in Ammos Folder, skipping new one.')
242 end
243 end
244 end
245
246 -- Main Components is completed, print it.
247 print(Setupped_Ammo)
248 print('[GunSystem] Main Components successfully loaded')
249end
250
251--[[
252 Loads the Fragments. What is fragments!
253 It is a ModuleScript act as a extension of a gun system
254]]
255local function preloadFragments()
256 -- Gets every modules inside Fragments
257 for _,v in Fragments:GetChildren() do
258 if v:IsA('ModuleScript') then
259 -- Its a module script, start require it.
260 local ok,content = pcall(require, v)
261 -- Always safety call.
262
263 if ok then
264 --[[
265 It is a module, it can be required without errors.
266 Print it.
267 ]]
268 print(string.format('[GunSystem] Fragment %s successfully loaded', v.Name))
269 else
270 -- It's failed, throw the warning.
271 warn(string.format('[GunSystem] Failed to load Fragment, error:\n%s', content))
272 end
273 else
274 -- Throw the warning since it's not a module script.
275 warn(string.format('[GunSystem] Instance %s is not fragment', v.Name))
276 end
277 end
278
279 -- Fragments are done. Print it.
280 print('[GunSystem] Fragments successfully loaded')
281end
282
283local YAFFCIR_ATTEMPTS = 50
284-- YAFFCIR stands for YieldAndFindFirstChildInRecursive
285-- How many attempts until it returns nothing
286
287--[[
288 This is a special function. Gets the instance by:
289 - A name
290 - Find it in every single descendants of instance
291 - Attempts for every function is called.
292]]
293local function YieldAndFindFirstChildInRecursive(instance, name, i)
294 if i and i > YAFFCIR_ATTEMPTS then
295 -- It got the many attempts. Return nothing.
296 return
297 end
298
299 --[[
300 task.wait(-1), yields a bit.
301 Find a instance with a recursive on.
302 If it does not exist, call the function itself.
303 ]]
304 return
305 task.wait(-1) and instance:FindFirstChild(name, true) or
306 YieldAndFindFirstChildInRecursive(instance, name, i and i + 1 or 1)
307end
308
309--[[
310 If character is added, this function should be called.
311 Which deploys the scripts inside the Character.
312
313 And also manipulates the Waist
314]]
315local function characterAdded(character)
316 for i,v in DeployableScripts.Character do
317 -- Start deploying Scripts inside a DeployableScripts > Character
318 -- Get a script, clone it, parent it inside Character
319 v:Clone().Parent = character
320 end
321
322 -- Gets waist which can be only found in R15 I believe
323 local Waist = YieldAndFindFirstChildInRecursive(character, 'Waist')
324
325 if Waist then
326 -- It exists, parent it in character so it doesnt act the arms have physics.
327 -- If waist is in the specific bodypart, it acts like a semi ragdoll or something.
328 Waist.Parent = character
329 end
330end
331
332--[[
333 Clears the RayHit.
334]]
335local function clearRayHits()
336 -- Get everything that exists and tagged in the Collection.
337 -- Then destroy it.
338 for i,v in CollectionService:GetTagged(DebugHitTagName) do
339 v:Destroy()
340 end
341end
342
343-- Get Remote Event, get a Function that fires the client.
344local SendNotify = Resources.SendNotification.FireClient
345
346--[[
347 Fires this function if player joins the game.
348]]
349local function playerAdded(player: Player)
350 if player.Character then
351 -- If Player and Character is loaded at the same time
352 -- prcoeed to call a function to deploy the scripts
353 characterAdded(player.Character)
354 end
355
356 --[[
357 Connect the signal "Chatted"
358 which detects if player chats.
359 ]]
360 player.Chatted:Connect(function(message)
361 --[[
362 In this function, we gonna detect if player wants to do a command.
363 So all we want to do is to tell if player got a prefix on.
364 ]]
365 if string.sub(message, 1, 1) == '!' then
366 local command = string.match(message, '.(.*)')
367 -- Tells what command uses. If it doesnt exist, it gets ignored.
368
369 if command then
370 -- It exists, now let's put some conditions
371 -- what player is trying to call a command.
372
373 if command:lower() == 'clear' then
374 if DebugEnabled then
375 -- Clears the Rayhits
376 clearRayHits()
377 SendNotify(Resources.SendNotification, player, 'Ray Hits are now cleared')
378 else
379 SendNotify(Resources.SendNotification, player, 'Debug is not enabled')
380 end
381 elseif command:lower() == 'debug' then
382 -- Toggle the DebugEnabled.
383 DebugEnabled = not DebugEnabled
384
385 if DebugEnabled then
386 -- Notify as it is now enabled.
387 SendNotify(Resources.SendNotification, player, 'Debug is now ENABLED, every ray hits shows green dots will sends to it')
388 else
389 -- Notify as it is now enabled,
390 -- if player wants to disable a debug
391 -- and Rayhits are present. Call clearRayHits()
392
393 clearRayHits()
394 SendNotify(Resources.SendNotification, player, 'Debug is now DISABLED, every ray hits will be removed')
395 end
396 end
397 end
398 end
399 end)
400
401 -- Deploy every scripts inside DeployableScripts > PlayerScripts
402 for i,lscript in DeployableScripts.PlayerScripts do
403 -- We cant access through Player.PlayerScripts, use ReplicatedFirst.
404 lscript:Clone().Parent = game:GetService('ReplicatedFirst')
405 end
406
407 -- Add a connection if character is spawned.
408 player.CharacterAdded:Connect(characterAdded)
409end
410
411--[[
412 A remote's function. It's fired if client wants to setup a weapon.
413]]
414local function setupWeapon(user: Player, tool)
415 if not tool then
416 -- We cant tell if it's exploiter. We gonna just assume it because it doesnt exist.
417 user:Kick('Tool must be present in a remote event, kicked for possibly Exploit')
418 return
419 end
420
421 local Nodes = tool:FindFirstChild('Nodes') -- Get nodes
422 local GunModel = tool:FindFirstChild('GunModel') -- Get gun model
423 Nodes.Parent = GunModel -- Parent Nodes to gun model
424
425 local Character = user.Character
426
427 -- Torso or UpperTorso depending if Rig is R6 or R15
428 local HandleParent =
429 Character:FindFirstChild('Torso') or
430 Character:FindFirstChild('UpperTorso')
431
432 if not (Nodes or GunModel) then
433 -- Assume it, they dont exist so they get kicked.
434 -- Client mostly does not lie about telling the event to setup a weapon.
435 user:Kick('Nodes or GunModel doesnt exist. Kicked for possibly exploit.')
436 return
437 end
438
439 -- Get Grip (aka Handle) from Nodes
440 local Grip = Nodes:FindFirstChild('Grip')
441
442 if not Grip then
443 -- It doesnt exist, kick the player
444 user:Kick('Grip doesnt exist in nodes. Kicked for possibly exploit.')
445 return
446 end
447
448 -- Weld it
449 WeldModule.weld(Grip, Nodes:GetChildren())
450 WeldModule.weld(Grip, GunModel:GetChildren())
451
452 -- Make nodes transparent
453 for i,v in Nodes:GetChildren() do
454 if v:IsA('BasePart') then
455 v.Transparency = 1
456 end
457 end
458
459 -- The main weld, welded to Torso and Grip
460 local MainWeld = Instance.new('Motor6D')
461 MainWeld.Parent = Grip
462 MainWeld.Part0 = HandleParent
463 MainWeld.Part1 = Grip
464
465 -- If tool is being equipped, put model to Character
466 tool.Equipped:Connect(function()
467 GunModel.Parent = Character
468 end)
469
470 -- If it's unequipped, put it inside tool.
471 tool.Unequipped:Connect(function()
472 GunModel.Parent = tool
473 end)
474
475 -- Setup is done, print it.
476 print('[GunSystem] Weapon "'..tool.Name..'" is loaded!')
477end
478
479--[[
480 Plays the sound without using sound:Play()
481]]
482local function playSound(player, parent, sound)
483 if sound and parent then
484 local newsound = sound:Clone()
485 newsound.Parent = parent
486
487 --[[
488 If player sucessfully examined the remote arguments,
489 they can play a sound with loop on
490
491 So for this reason, we turn off the Loop to make
492 exploiter have limited access through remotes like this
493 ]]
494 newsound.Looped = false -- So in that case we turn it off
495
496 -- Turns ON the PlayOnRemove, when sound is destroyed
497 -- The sound will play, but not looped
498 newsound.PlayOnRemove = true
499
500 -- Destroy the cloned sound
501 newsound:Destroy()
502 else
503 -- They dont exist, kick the player for not meeting the conditions.
504 -- And assume them that they exploit.
505 player:Kick('Argument does not meet the conditions, kicked for possibly exploit.')
506 end
507end
508
509--[[
510 fireWeapon gets called when player is shooting the gun.
511 so for now we must provide Origin, Direction, Settings, Behavior, and more.
512
513 Origin = is a starter point or act as a muzzle where bullet will spawned,
514 Direction = is a end point where bullet will go
515 Settings = Gun's Settings
516 Behavior = Gun's Behavior
517]]
518local function fireWeapon(Player, Origin, Direction, Settings, Behavior: CastTypes.FastCastBehavior, Params)
519
520 -- Get AmmoName and Get the AmmoTable with the AmmoName
521 local AmmoName = Settings.Ammunition
522 local AmmoTable = Setupped_Ammo[AmmoName]
523
524 if AmmoTable then
525 -- Ammo exists, lets see if character also exists.
526
527 local Character = Player.Character
528
529 if Character then
530 -- It exists! Now let's start casting.
531
532 local AGSServer = require(Character.AGS_Server)
533 -- TODO: Dont require Server every player fires the gun
534
535 -- Ehh this look dumb.
536 local Params = AGSServer.getParams()
537
538 -- Get the Caster from AmmoTable
539 local Caster = AmmoTable.forUsage.cast
540
541 -- Change the Setting of Behavior
542 Behavior.CosmeticBulletTemplate = AmmoTable.set.Model
543 Behavior.CosmeticBulletContainer = IgnoreContainer
544 Behavior.Acceleration = Vector3.new(0,-workspace.Gravity/4,0)
545 Behavior.HighFidelityBehavior = FastCast.HighFidelityBehavior.Always
546 Behavior.HighFidelitySegmentSize = 1
547 Behavior.RaycastParams = Params
548
549 -- Get the Velocity, no need to convert them to studs.
550 local Velocity = Settings.MuzzleVelocity
551
552 -- And finally Fire the Caster then we get the Active Cast, known as bullet.
553 local ActiveCast = Caster:Fire(Origin, Direction, Velocity, Behavior)
554
555 -- Put the UserData on the ActiveCast
556 ActiveCast.UserData = {
557 -- So now we know who shot the gun.
558 Player = Player
559 }
560 end
561 end
562end
563
564--[[
565 Start the other things like connect the events and do something.
566]]
567local function startOthers()
568
569 --[[
570 If players are already joined before the setup, we do this.
571 ]]
572 for i,v in Players:GetPlayers() do
573 -- Call the playerAdded with a argument of v as Player
574 playerAdded(v)
575 end
576
577 -- Connect the Events
578 Players.PlayerAdded:Connect(playerAdded)
579 Resources.SetupWeapon.OnServerEvent:Connect(setupWeapon)
580 Resources.FireWeapon.OnServerEvent:Connect(fireWeapon)
581 Resources.PlaySound.OnServerEvent:Connect(playSound)
582end
583
584return function()
585 -- Start
586 local Tick = tick()
587 _G['AGS_ACTIVE'] = newproxy()
588
589 -- Start Print
590 print('[GunSystem] System currently loading')
591
592 -- Fire Every functions to load
593 preloadFragments() -- Load the Fragments
594 preloadMainComponents() -- Load the Main Components
595 preloadServerComponent() -- Load the Server based Components
596 preloadClientComponent() -- Load the Client based Components
597 startOthers()
598
599 -- End Print
600 print('[GunSystem] System successfully loaded ('..tostring(tick()-Tick)..')')
601end