sevrick

Untitled

Mar 23rd, 2024
5
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 40.91 KB | None | 0 0
  1. local TweenService = game:GetService("TweenService")
  2. local RunService = game:GetService("RunService")
  3. local Players = game:GetService("Players")
  4. local Debris = game:GetService("Debris")
  5. local ContextActionService = game:GetService("ContextActionService")
  6. local CollectionService = game:GetService("CollectionService")
  7. local ContentProvider = game:GetService("ContentProvider")
  8.  
  9. local IsServer = RunService:IsServer()
  10.  
  11. local WeaponsSystemFolder = script.Parent.Parent
  12. local Libraries = WeaponsSystemFolder:WaitForChild("Libraries")
  13. local BaseWeapon = require(Libraries:WaitForChild("BaseWeapon"))
  14. local Parabola = require(Libraries:WaitForChild("Parabola"))
  15. local Roblox = require(Libraries:WaitForChild("Roblox"))
  16.  
  17. local Effects = WeaponsSystemFolder:WaitForChild("Assets"):WaitForChild("Effects")
  18. local ShotsFolder = Effects:WaitForChild("Shots")
  19. local HitMarksFolder = Effects:WaitForChild("HitMarks")
  20. local CasingsFolder = Effects:WaitForChild("Casings")
  21.  
  22. local NO_BULLET_DECALS = false
  23. local NO_BULLET_CASINGS = false
  24.  
  25. --The ignore list will fill up over time. This is how many seconds it will go before
  26. --being refreshed in order to keep it from filling up with instances that aren't in
  27. --the datamodel anymore.
  28. local IGNORE_LIST_LIFETIME = 5
  29.  
  30. local MAX_BULLET_TIME = 10
  31.  
  32. local localRandom = Random.new()
  33. local localPlayer = not IsServer and Players.LocalPlayer
  34.  
  35. local BulletWeapon = {}
  36. BulletWeapon.__index = BulletWeapon
  37. setmetatable(BulletWeapon, BaseWeapon)
  38.  
  39. BulletWeapon.CanAimDownSights = true
  40. BulletWeapon.CanBeFired = true
  41. BulletWeapon.CanBeReloaded = true
  42. BulletWeapon.CanHit = true
  43.  
  44. function BulletWeapon.new(weaponsSystem, instance)
  45. local self = BaseWeapon.new(weaponsSystem, instance)
  46. setmetatable(self, BulletWeapon)
  47.  
  48. self.usesCharging = false
  49. self.charge = 0
  50. self.chargeSoundPitchMin = 0.5
  51. self.chargeSoundPitchMax = 1
  52.  
  53. self.triggerDisconnected = false
  54. self.startupFinished = false -- TODO: make startup time use a configuration value
  55. self.burstFiring = false
  56. self.burstIdx = 0
  57. self.nextFireTime = 0
  58.  
  59. self.recoilIntensity = 0
  60. self.aimPoint = Vector3.new()
  61.  
  62. self:addOptionalDescendant("tipAttach", "TipAttachment")
  63.  
  64. self:addOptionalDescendant("boltMotor", "BoltMotor")
  65. self:addOptionalDescendant("boltMotorStart", "BoltMotorStart")
  66. self:addOptionalDescendant("boltMotorTarget", "BoltMotorTarget")
  67.  
  68. self:addOptionalDescendant("chargeGlowPart", "ChargeGlow")
  69. self:addOptionalDescendant("chargeCompleteParticles", "ChargeCompleteParticles")
  70. self:addOptionalDescendant("dischargeCompleteParticles", "DischargeCompleteParticles")
  71.  
  72. self:addOptionalDescendant("muzzleFlash0", "MuzzleFlash0")
  73. self:addOptionalDescendant("muzzleFlash1", "MuzzleFlash1")
  74. self:addOptionalDescendant("muzzleFlashBeam", "MuzzleFlash")
  75.  
  76. self.hitMarkTemplate = HitMarksFolder:FindFirstChild(self:getConfigValue("HitMarkEffect", "BulletHole"))
  77.  
  78. self.casingTemplate = CasingsFolder:FindFirstChild(self:getConfigValue("CasingEffect", ""))
  79. self:addOptionalDescendant("casingEjectPoint", "CasingEjectPoint")
  80.  
  81. self.ignoreList = {}
  82. self.ignoreListRefreshTime = 0
  83.  
  84. self:addOptionalDescendant("handAttach", "LeftHandAttachment")
  85. self.handAlignPos = nil
  86. self.handAlignRot = nil
  87.  
  88. self.chargingParticles = {}
  89. self.instance.DescendantAdded:Connect(function(descendant)
  90. if descendant.Name == "ChargingParticles" and descendant:IsA("ParticleEmitter") then
  91. table.insert(self.chargingParticles, descendant)
  92. end
  93. end)
  94. for _, v in pairs(self.instance:GetDescendants()) do
  95. if v.Name == "ChargingParticles" and v:IsA("ParticleEmitter") then
  96. table.insert(self.chargingParticles, v)
  97. end
  98. end
  99.  
  100. self:doInitialSetup()
  101.  
  102. return self
  103. end
  104.  
  105. function BulletWeapon:onEquippedChanged()
  106. BaseWeapon.onEquippedChanged(self)
  107.  
  108. if not IsServer then
  109. if self.weaponsSystem.camera then
  110. if self.equipped then
  111. self.startupFinished = false
  112. end
  113. end
  114.  
  115. if self.equipped then
  116. ContextActionService:BindAction("ReloadWeapon", function(...) self:onReloadAction(...) end, false, Enum.KeyCode.R, Enum.KeyCode.ButtonX)
  117. else
  118. ContextActionService:UnbindAction("ReloadWeapon")
  119.  
  120. -- Stop charging/discharging sounds
  121. local chargingSound = self:getSound("Charging")
  122. local dischargingSound = self:getSound("Discharging")
  123. if chargingSound and chargingSound.Playing then
  124. chargingSound:Stop()
  125. end
  126. if dischargingSound and dischargingSound.Playing then
  127. dischargingSound:Stop()
  128. end
  129. end
  130.  
  131. self.triggerDisconnected = false
  132. end
  133. end
  134.  
  135. function BulletWeapon:onReloadAction(actionName, inputState, inputObj)
  136. if inputState == Enum.UserInputState.Begin and not self.reloading then
  137. self:reload()
  138. end
  139. end
  140.  
  141. function BulletWeapon:animateBoltAction(isOpen)
  142. if not self.boltMotor or not self.boltMotorStart or not self.boltMotorTarget then
  143. return
  144. end
  145.  
  146. if isOpen then
  147. self:tryPlaySound("BoltOpenSound")
  148. else
  149. self:tryPlaySound("BoltCloseSound")
  150. end
  151.  
  152. local actionMoveTime = isOpen and self:getConfigValue("ActionOpenTime", 0.025) or self:getConfigValue("ActionCloseTime", 0.075)
  153. local targetCFrame = isOpen and self.boltMotorTarget.CFrame or self.boltMotorStart.CFrame
  154.  
  155. local boltTween = TweenService:Create(self.boltMotor, TweenInfo.new(actionMoveTime, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), { C0 = targetCFrame })
  156. boltTween:Play()
  157. boltTween.Completed:Wait()
  158. end
  159.  
  160. function BulletWeapon:getRandomSeedForId(id)
  161. return id
  162. end
  163.  
  164. -- This function is only called on clients
  165. function BulletWeapon:simulateFire(firingPlayer, fireInfo)
  166. BaseWeapon.simulateFire(self, fireInfo)
  167.  
  168. -- Play "Fired" sound
  169. if self.lastFireSound then
  170. self.lastFireSound:Stop()
  171. end
  172. self.lastFireSound = self:tryPlaySound("Fired", self:getConfigValue("FiredPlaybackSpeedRange", 0.1))
  173.  
  174. -- Simulate each projectile/bullet fired from current weapon
  175. local numProjectiles = self:getConfigValue("NumProjectiles", 1)
  176. local randomGenerator = Random.new(self:getRandomSeedForId(fireInfo.id))
  177. for i = 1, numProjectiles do
  178. self:simulateProjectile(firingPlayer, fireInfo, i, randomGenerator)
  179. end
  180.  
  181. -- Animate the bolt if the current gun has one
  182. local actionOpenTime = self:getConfigValue("ActionOpenTime", 0.025)
  183. if self.boltMotor then
  184. coroutine.wrap(function()
  185. self:animateBoltAction(true)
  186. wait(actionOpenTime)
  187. self:animateBoltAction(false)
  188. end)()
  189. end
  190.  
  191. -- Eject bullet casings and play "CasingHitSound" (child of casing) sound if applicable for current weapon
  192. if not NO_BULLET_CASINGS and self.casingTemplate and self.casingEjectPoint then
  193. local casing = self.casingTemplate:Clone()
  194. casing.Anchored = false
  195. casing.Archivable = false
  196. casing.CFrame = self.casingEjectPoint.WorldCFrame
  197. casing.Velocity = self.casingEjectPoint.Parent.Velocity + (self.casingEjectPoint.WorldAxis * localRandom:NextNumber(self:getConfigValue("CasingEjectSpeedMin", 15), self:getConfigValue("CasingEjectSpeedMax", 18)))
  198. casing.Parent = workspace.CurrentCamera
  199. CollectionService:AddTag(casing, "WeaponsSystemIgnore")
  200.  
  201. local casingHitSound = casing:FindFirstChild("CasingHitSound")
  202. if casingHitSound then
  203. local touchedConn = nil
  204. touchedConn = casing.Touched:Connect(function(hitPart)
  205. if not hitPart:IsDescendantOf(self.instance) then
  206. casingHitSound:Play()
  207. touchedConn:Disconnect()
  208. touchedConn = nil
  209. end
  210. end)
  211. end
  212.  
  213. Debris:AddItem(casing, 2)
  214. end
  215.  
  216. if self.player == Players.LocalPlayer then
  217. coroutine.wrap(function()
  218. -- Wait for "RecoilDelayTime" before adding recoil
  219. local startTime = tick()
  220. local recoilDelayTime = self:getConfigValue("RecoilDelayTime", 0.07)
  221. while tick() < startTime + recoilDelayTime do
  222. RunService.RenderStepped:Wait()
  223. end
  224. RunService.RenderStepped:Wait()
  225.  
  226. -- Add recoil to camera
  227. local recoilMin, recoilMax = self:getConfigValue("RecoilMin", 0.05), self:getConfigValue("RecoilMax", 0.5)
  228. local intensityToAdd = randomGenerator:NextNumber(recoilMin, recoilMax)
  229. local xIntensity = math.sin(tick() * 2) * intensityToAdd * math.rad(0.05)
  230. local yIntensity = intensityToAdd * 0.025
  231. self.weaponsSystem.camera:addRecoil(Vector2.new(xIntensity, yIntensity))
  232.  
  233. if not (self.weaponsSystem.camera:isZoomed() and self:getConfigValue("HasScope", false)) then
  234. self.recoilIntensity = math.clamp(self.recoilIntensity * 1 + (intensityToAdd / 10), 0.005, 1)
  235. end
  236.  
  237. -- Make crosshair reflect recoil/spread amount
  238. local weaponsGui = self.weaponsSystem.gui
  239. if weaponsGui then
  240. weaponsGui:setCrosshairScale(1 + intensityToAdd)
  241. end
  242. end)()
  243. end
  244. end
  245.  
  246. function BulletWeapon:getIgnoreList(includeLocalPlayer)
  247. local now = tick()
  248. local ignoreList = self.ignoreList
  249. if not ignoreList or now - self.ignoreListRefreshTime > IGNORE_LIST_LIFETIME then
  250. ignoreList = {
  251. self.instanceIsTool and self.instance.Parent or self.instance,
  252. workspace.CurrentCamera
  253. }
  254. if not RunService:IsServer() then
  255. if includeLocalPlayer and Players.LocalPlayer and Players.LocalPlayer.Character then
  256. table.insert(ignoreList, Players.LocalPlayer.Character)
  257. end
  258. end
  259. self.ignoreList = ignoreList
  260. end
  261. return ignoreList
  262. end
  263.  
  264. -- This function is only called on clients
  265. function BulletWeapon:simulateProjectile(firingPlayer, fireInfo, projectileIdx, randomGenerator)
  266. local localPlayerInitiatedShot = self.player == Players.LocalPlayer
  267.  
  268. -- Retrieve config values
  269. local bulletSpeed = self:getConfigValue("BulletSpeed", 1000)
  270. local maxDistance = self:getConfigValue("MaxDistance", 2000)
  271. local trailLength = self:getConfigValue("TrailLength", nil)
  272. local trailLengthFactor = self:getConfigValue("TrailLengthFactor", 1)
  273. local showEntireTrailUntilHit = self:getConfigValue("ShowEntireTrailUntilHit", false)
  274. local gravityFactor = self:getConfigValue("GravityFactor", 0)
  275. local minSpread = self:getConfigValue("MinSpread", 0)
  276. local maxSpread = self:getConfigValue("MaxSpread", 0)
  277. local shouldMovePart = self:getConfigValue("ShouldMovePart", false)
  278. local explodeOnImpact = self:getConfigValue("ExplodeOnImpact", false)
  279. local blastRadius = self:getConfigValue("BlastRadius", 8)
  280.  
  281. -- Cheat the origin of the shot back if gun tip in wall/object
  282. if self.tipAttach ~= nil then
  283. local tipCFrame = self.tipAttach.WorldCFrame
  284. local tipPos = tipCFrame.Position
  285. local tipDir = tipCFrame.LookVector
  286. local amountToCheatBack = math.abs((self.instance:FindFirstChild("Handle").Position - tipPos):Dot(tipDir)) + 1
  287. local gunRay = Ray.new(tipPos - tipDir.Unit * amountToCheatBack, tipDir.Unit * amountToCheatBack)
  288. local hitPart, hitPoint = Roblox.penetrateCast(gunRay, self:getIgnoreList(localPlayerInitiatedShot))
  289. if hitPart and math.abs((tipPos - hitPoint).Magnitude) > 0 then
  290. fireInfo.origin = hitPoint - tipDir.Unit * 0.1
  291. fireInfo.dir = tipDir.Unit
  292. end
  293. end
  294.  
  295. local origin, dir = fireInfo.origin, fireInfo.dir
  296.  
  297. dir = Roblox.applySpread(dir, randomGenerator, math.rad(minSpread), math.rad(maxSpread))
  298.  
  299. -- Initialize variables for visuals/particle effects
  300. local bulletEffect = self.bulletEffectTemplate:Clone()
  301. bulletEffect.CFrame = CFrame.new(origin, origin + dir)
  302. bulletEffect.Parent = workspace.CurrentCamera
  303. CollectionService:AddTag(bulletEffect, "WeaponsSystemIgnore")
  304.  
  305. local leadingParticles = bulletEffect:FindFirstChild("LeadingParticles", true)
  306. local attachment0 = bulletEffect:FindFirstChild("Attachment0")
  307. local trailParticles = nil
  308. if attachment0 then
  309. trailParticles = attachment0:FindFirstChild("TrailParticles")
  310. end
  311.  
  312. local hitAttach = bulletEffect:FindFirstChild("HitEffect")
  313. local hitParticles = bulletEffect:FindFirstChild("HitParticles", true)
  314. local numHitParticles = self:getConfigValue("NumHitParticles", 3)
  315. local hitSound = bulletEffect:FindFirstChild("HitSound", true)
  316. local flyingSound = bulletEffect:FindFirstChild("Flying", true)
  317.  
  318. local muzzleFlashTime = self:getConfigValue("MuzzleFlashTime", 0.03)
  319. local muzzleFlashShown = false
  320.  
  321. local beamThickness0 = self:getConfigValue("BeamWidth0", 1.5)
  322. local beamThickness1 = self:getConfigValue("BeamWidth1", 1.8)
  323. local beamFadeTime = self:getConfigValue("BeamFadeTime", nil)
  324.  
  325. -- Enable beam trails for projectile
  326. local beam0 = bulletEffect:FindFirstChild("Beam0")
  327. if beam0 then
  328. beam0.Enabled = true
  329. end
  330. local beam1 = bulletEffect:FindFirstChild("Beam1")
  331. if beam1 then
  332. beam1.Enabled = true
  333. end
  334.  
  335. -- Emit muzzle particles
  336. local muzzleParticles = bulletEffect:FindFirstChild("MuzzleParticles", true)
  337. local numMuzzleParticles = self:getConfigValue("NumMuzzleParticles", 50)
  338. if muzzleParticles then
  339. muzzleParticles.Parent.CFrame = CFrame.new(origin, origin + dir)
  340. local numSteps = 5
  341. for _ = 1, numSteps do
  342. muzzleParticles.Parent.Velocity = Vector3.new(localRandom:NextNumber(-10, 10), localRandom:NextNumber(-10, 10), localRandom:NextNumber(-10, 10))
  343. muzzleParticles:Emit(numMuzzleParticles / numSteps)
  344. end
  345. end
  346.  
  347. -- Show muzzle flash
  348. if self.tipAttach and self.muzzleFlash0 and self.muzzleFlash1 and self.muzzleFlashBeam and projectileIdx == 1 then
  349. local minFlashRotation, maxFlashRotation = self:getConfigValue("MuzzleFlashRotation0", -math.pi), self:getConfigValue("MuzzleFlashRotation1", math.pi)
  350. local minFlashSize, maxFlashSize = self:getConfigValue("MuzzleFlashSize0", 1), self:getConfigValue("MuzzleFlashSize1", 1)
  351. local flashRotation = localRandom:NextNumber(minFlashRotation, maxFlashRotation)
  352. local flashSize = localRandom:NextNumber(minFlashSize, maxFlashSize)
  353. local baseCFrame = self.tipAttach.CFrame * CFrame.Angles(0, 0, flashRotation)
  354. self.muzzleFlash0.CFrame = baseCFrame * CFrame.new(flashSize * -0.5, 0, 0) * CFrame.Angles(0, math.pi, 0)
  355. self.muzzleFlash1.CFrame = baseCFrame * CFrame.new(flashSize * 0.5, 0, 0) * CFrame.Angles(0, math.pi, 0)
  356.  
  357. self.muzzleFlashBeam.Enabled = true
  358. self.muzzleFlashBeam.Width0 = flashSize
  359. self.muzzleFlashBeam.Width1 = flashSize
  360. muzzleFlashShown = true
  361. end
  362.  
  363. -- Play projectile flying sound
  364. if flyingSound then
  365. flyingSound:Play()
  366. end
  367.  
  368. -- Enable trail particles
  369. if trailParticles then
  370. trailParticles.Enabled = true
  371. end
  372.  
  373. -- Set up parabola for projectile path
  374. local parabola = Parabola.new()
  375. parabola:setPhysicsLaunch(origin, dir * bulletSpeed, nil, 35 * -gravityFactor)
  376. -- More samples for higher gravity since path will be more curved but raycasts can only be straight lines
  377. if gravityFactor > 0.66 then
  378. parabola:setNumSamples(3)
  379. elseif gravityFactor > 0.33 then
  380. parabola:setNumSamples(2)
  381. else
  382. parabola:setNumSamples(1)
  383. end
  384.  
  385. -- Set up/initialize variables used in steppedCallback
  386. local stepConn = nil
  387. local pTravelDistance = 0 -- projected travel distance so far if projectile never stops
  388. local startTime = tick()
  389. local didHit = false
  390. local stoppedMotion = false
  391. local stoppedMotionAt = 0
  392. local timeSinceStart = 0
  393. local flyingVisualEffectsFinished = false -- true if all particle effects shown while projectile is flying are done
  394. local visualEffectsFinishTime = math.huge
  395. local visualEffectsLingerTime = 0 -- max time any visual effect needs to finish
  396. if beamFadeTime then
  397. visualEffectsLingerTime = beamFadeTime
  398. end
  399. local hitInfo = {
  400. sid = fireInfo.id,
  401. pid = projectileIdx,
  402. maxDist = maxDistance,
  403. part = nil,
  404. p = nil,
  405. n = nil,
  406. m = Enum.Material.Air,
  407. d = 1e9,
  408. }
  409.  
  410. local steppedCallback = function(dt)
  411. local now = tick()
  412. timeSinceStart = now - startTime
  413.  
  414. local travelDist = bulletSpeed * dt -- distance projectile has travelled since last frame
  415. trailLength = trailLength or travelDist * trailLengthFactor
  416.  
  417. -- Note: the next three variables are all in terms of distance from starting point (which should be tip of current weapon)
  418. local projBack = pTravelDistance - trailLength -- furthest back part of projectile (including the trail effect, so will be the start of the trail effect if any)
  419. local projFront = pTravelDistance -- most forward part of projectile
  420. local maxDist = hitInfo.maxDist or 0 -- before it collides, this is the max distance the projectile can travel. After it collides, this is the hit point
  421.  
  422. -- This will make trailing beams render from tip of gun to wherever projectile is until projectile is destroyed
  423. if showEntireTrailUntilHit then
  424. projBack = 0
  425. end
  426.  
  427. -- Validate projBack and projFront
  428. projBack = math.clamp(projBack, 0, maxDist)
  429. projFront = math.clamp(projFront, 0, maxDist)
  430.  
  431. if not didHit then
  432. -- Check if bullet hit since last frame
  433. local castProjBack, castProjFront = projFront, projFront + travelDist
  434. parabola:setDomain(castProjBack, castProjFront)
  435. local hitPart, hitPoint, hitNormal, hitMaterial, hitT = parabola:findPart(self.ignoreList)
  436.  
  437. if hitPart then
  438. didHit = true
  439. projFront = castProjBack + hitT * (castProjFront - castProjBack) -- set projFront to point along projectile arc where an object was hit
  440. parabola:setDomain(projBack, projFront) -- update parabola domain to match new projFront
  441.  
  442. -- Update hitInfo
  443. hitInfo.part = hitPart
  444. hitInfo.p = hitPoint
  445. hitInfo.n = hitNormal
  446. hitInfo.m = hitMaterial
  447. hitInfo.d = (hitPoint - origin).Magnitude
  448. hitInfo.t = hitT
  449. hitInfo.maxDist = projFront -- since the projectile hit, maxDist is now the hitPoint instead of maxDistance
  450.  
  451. -- Register hit on clients
  452. self:onHit(hitInfo)
  453.  
  454. -- Notify the server that this projectile hit something from client that initiated the shot
  455. -- Show hit indicators on gui of client that shot projectile
  456. if localPlayerInitiatedShot then
  457. local hitInfoClone = {}
  458. for hitInfoKey, value in pairs(hitInfo) do
  459. hitInfoClone[hitInfoKey] = value
  460. end
  461. self.weaponsSystem.getRemoteEvent("WeaponHit"):FireServer(self.instance, hitInfoClone)
  462. end
  463.  
  464.  
  465. -- Deal with all effects that start/stop/change on hit
  466.  
  467. -- Disable trail particles
  468. if trailParticles then
  469. trailParticles.Enabled = false
  470. end
  471.  
  472. -- Stop bullet flying sound
  473. if flyingSound and flyingSound.IsPlaying then
  474. flyingSound:Stop()
  475. end
  476.  
  477. -- Hide the actual projectile model
  478. if bulletEffect then
  479. bulletEffect.Transparency = 1
  480. end
  481.  
  482. -- Stop emitting leading particles
  483. if leadingParticles then
  484. leadingParticles.Rate = 0
  485. visualEffectsLingerTime = math.max(visualEffectsLingerTime, leadingParticles.Lifetime.Max)
  486. end
  487.  
  488. -- Show the explosion on clients for explosive projectiles
  489. if explodeOnImpact then
  490. local explosion = Instance.new("Explosion")
  491. explosion.Position = hitPoint + (hitNormal * 0.5)
  492. explosion.BlastRadius = blastRadius
  493. explosion.BlastPressure = 0 -- no blast pressure because the real explosion happens on server
  494. explosion.ExplosionType = Enum.ExplosionType.NoCraters
  495. explosion.DestroyJointRadiusPercent = 0
  496. explosion.Visible = true
  497. if localPlayerInitiatedShot then
  498. -- Trigger hit indicators on client that initiated the shot if the explosion hit another player/humanoid
  499. explosion.Hit:Connect(function(explodedPart, hitDist)
  500. local humanoid = self.weaponsSystem.getHumanoid(explodedPart)
  501. if humanoid and
  502. explodedPart.Name == "UpperTorso" and
  503. humanoid:GetState() ~= Enum.HumanoidStateType.Dead and
  504. self.weaponsSystem.gui and
  505. explodedPart.Parent ~= self.player.Character and
  506. self.weaponsSystem.playersOnDifferentTeams(self.weaponsSystem.getPlayerFromHumanoid(humanoid), self.player)
  507. then
  508. self.weaponsSystem.gui:OnHitOtherPlayer(self:calculateDamage(hitInfo.d), humanoid)
  509. end
  510. end)
  511. end
  512. explosion.Parent = workspace
  513. end
  514.  
  515. -- Make sure hitAttach is in correct position before showing hit effects
  516. if hitAttach and beam0 and beam0.Attachment1 then
  517. parabola:renderToBeam(beam0)
  518. hitAttach.CFrame = beam0.Attachment1.CFrame * CFrame.Angles(0, math.rad(90), 0)
  519. end
  520.  
  521. -- Show hit particle effect
  522. local hitPartColor = hitPart and hitPart.Color or Color3.fromRGB(255, 255, 255)
  523. if hitPart and hitPart:IsA("Terrain") then
  524. hitPartColor = workspace.Terrain:GetMaterialColor(hitMaterial or Enum.Material.Sand)
  525. end
  526. if hitInfo.h and hitInfo.h:IsA("Humanoid") and hitParticles and numHitParticles > 0 and hitPart then
  527. -- Show particle effect for hitting a player/humanoid
  528. hitParticles.Color = ColorSequence.new(Color3.fromRGB(255, 255, 255))
  529. hitParticles:Emit(numHitParticles)
  530. visualEffectsLingerTime = math.max(visualEffectsLingerTime, hitParticles.Lifetime.Max)
  531. elseif (not hitInfo.h or not hitInfo.h:IsA("Humanoid")) and hitParticles and numHitParticles > 0 then
  532. -- Show particle effect for hitting anything else
  533. if hitPart and self:getConfigValue("HitParticlesUsePartColor", true) then
  534. local existingSeq = hitParticles.Color
  535. local newKeypoints = {}
  536.  
  537. for i, keypoint in pairs(existingSeq.Keypoints) do
  538. local newColor = keypoint.Value
  539. if newColor == Color3.fromRGB(255, 0, 255) then
  540. newColor = hitPartColor
  541. end
  542. newKeypoints[i] = ColorSequenceKeypoint.new(keypoint.Time, newColor)
  543. end
  544.  
  545. hitParticles.Color = ColorSequence.new(newKeypoints)
  546. end
  547.  
  548. hitParticles:Emit(numHitParticles)
  549. visualEffectsLingerTime = math.max(visualEffectsLingerTime, hitParticles.Lifetime.Max)
  550. end
  551.  
  552. -- Play hit sound
  553. if hitSound then
  554. hitSound:Play()
  555. visualEffectsLingerTime = math.max(visualEffectsLingerTime, hitSound.TimeLength)
  556. end
  557.  
  558. -- Manage/show decals, billboards, and models (such as an arrow) that appear where the projectile hit (only if the hit object was not a humanoid/player)
  559. local hitPointObjectSpace = hitPart.CFrame:pointToObjectSpace(hitPoint)
  560. local hitNormalObjectSpace = hitPart.CFrame:vectorToObjectSpace(hitNormal)
  561. if not NO_BULLET_DECALS and
  562. hitPart and
  563. not hitPart.Parent or not hitPart.Parent:FindFirstChildOfClass("Humanoid") and
  564. hitPointObjectSpace and
  565. hitNormalObjectSpace and
  566. self.hitMarkTemplate
  567. then
  568. -- Clone hitMark (this contains all the decals/billboards/models to show on the hit surface)
  569. local hitMark = self.hitMarkTemplate:Clone()
  570. hitMark.Parent = hitPart
  571. CollectionService:AddTag(hitMark, "WeaponsSystemIgnore")
  572.  
  573. -- Move/align hitMark to the hit surface
  574. local incomingVec = parabola:sampleVelocity(1).Unit
  575. if self:getConfigValue("AlignHitMarkToNormal", true) then
  576. -- Make hitMark face straight out from surface where projectile hit (good for decals)
  577. local forward = hitNormalObjectSpace
  578. local up = incomingVec
  579. local right = -forward:Cross(up).Unit
  580. up = forward:Cross(right)
  581. local orientationCFrame = CFrame.fromMatrix(hitPointObjectSpace + hitNormalObjectSpace * 0.05, right, up, -forward)
  582. hitMark.CFrame = hitPart.CFrame:toWorldSpace(orientationCFrame)
  583. else
  584. -- Make hitmark appear stuck in the hit surface from the direction the projectile came from (good for things like arrows)
  585. hitMark.CFrame = hitPart.CFrame * CFrame.new(hitPointObjectSpace, hitPointObjectSpace + hitPart.CFrame:vectorToObjectSpace(incomingVec))
  586. end
  587.  
  588. -- Weld hitMark to the hitPart
  589. local weld = Instance.new("WeldConstraint")
  590. weld.Part0 = hitMark
  591. weld.Part1 = hitPart
  592. weld.Parent = hitMark
  593.  
  594. -- Fade glow decal over time
  595. local glowDecal = hitMark:FindFirstChild("Glow")
  596. if glowDecal then
  597. coroutine.wrap(function()
  598. local heartbeat = RunService.Heartbeat
  599. for i = 0, 1, 1/60 do
  600. heartbeat:Wait()
  601. glowDecal.Transparency = (i ^ 2)
  602. end
  603. end)()
  604. end
  605.  
  606. -- Set bullethole decal color and fade over time
  607. local bulletHole = hitMark:FindFirstChild("BulletHole")
  608. if bulletHole then
  609. bulletHole.Color3 = hitPartColor
  610. TweenService:Create(
  611. bulletHole,
  612. TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, false, 4),
  613. { Transparency = 1 }
  614. ):Play()
  615. end
  616.  
  617. -- Fade impact billboard's size and transparency over time
  618. local impactBillboard = hitMark:FindFirstChild("ImpactBillboard")
  619. if impactBillboard then
  620. local impact = impactBillboard:FindFirstChild("Impact")
  621. local impactTween = TweenService:Create(
  622. impact,
  623. TweenInfo.new(0.1, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0),
  624. { Size = UDim2.new(1, 0, 1, 0) }
  625. )
  626. impactTween.Completed:Connect(function()
  627. TweenService:Create(
  628. impact,
  629. TweenInfo.new(0.1, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0),
  630. { Size = UDim2.new(0.5, 0, 0.5, 0), ImageTransparency = 1 }
  631. ):Play()
  632. end)
  633. impactTween:Play()
  634. end
  635.  
  636. -- Destroy hitMark in 5 seconds
  637. Debris:AddItem(hitMark, 5)
  638. end
  639.  
  640. flyingVisualEffectsFinished = true
  641. visualEffectsFinishTime = now + visualEffectsLingerTime
  642. end
  643. end
  644.  
  645. -- Will enter this if-statement if projectile hit something or maxDistance has been reached
  646. if projFront >= maxDist then
  647. if not stoppedMotion then
  648. stoppedMotion = true
  649. stoppedMotionAt = now
  650. end
  651.  
  652. -- Stop particle effects if projectile didn't hit anything and projBack has reached the end
  653. if projBack >= maxDist and not flyingVisualEffectsFinished then
  654. flyingVisualEffectsFinished = true
  655. visualEffectsFinishTime = now + visualEffectsLingerTime
  656. end
  657. end
  658.  
  659. -- Update parabola domain
  660. parabola:setDomain(projBack, projFront)
  661.  
  662. -- Continue updating pTravelDistance until projBack has reached maxDist (this helps with some visual effects)
  663. if projBack < maxDist then
  664. pTravelDistance = math.max(0, timeSinceStart * bulletSpeed)
  665. end
  666.  
  667.  
  668. -- Update visual effects each frame
  669.  
  670. -- Update CFrame/velocity of projectile if the projectile uses a model (such as rocket or grenade)
  671. if shouldMovePart then
  672. local bulletPos = parabola:samplePoint(1)
  673. local bulletVelocity = parabola:sampleVelocity(1)
  674. bulletEffect.CFrame = CFrame.new(bulletPos, bulletPos + bulletVelocity)
  675. bulletEffect.Velocity = bulletVelocity.Unit * bulletSpeed
  676. end
  677.  
  678. -- Update thickness and render trailing beams
  679. local thickness0 = beamThickness0
  680. local thickness1 = beamThickness1
  681. if beamFadeTime then
  682. -- Fade out trail beams if projectile is no longer moving (hit something or reached max distance)
  683. local timeSinceEnd = stoppedMotion and (now - stoppedMotionAt) or 0
  684. local fadeAlpha = math.clamp(timeSinceEnd / beamFadeTime, 0, 1)
  685. thickness0 = thickness0 * (1 - fadeAlpha)
  686. thickness1 = thickness1 * (1 - fadeAlpha)
  687. end
  688. if beam0 then
  689. beam0.Width0 = thickness0
  690. beam0.Width1 = thickness1
  691. parabola:renderToBeam(beam0)
  692. end
  693. if beam1 then
  694. beam1.Width0 = thickness0
  695. beam1.Width1 = thickness1
  696. parabola:renderToBeam(beam1)
  697. end
  698.  
  699. -- Disable muzzle flash after muzzleFlashTime seconds have passed
  700. if muzzleFlashShown and timeSinceStart > muzzleFlashTime and self.muzzleFlashBeam then
  701. self.muzzleFlashBeam.Enabled = false
  702. muzzleFlashShown = false
  703. end
  704.  
  705. -- Destroy projectile and attached visual effects when visual effects are done showing or max bullet time has been reached
  706. local timeSinceParticleEffectsFinished = now - visualEffectsFinishTime
  707. if (flyingVisualEffectsFinished and timeSinceParticleEffectsFinished > 0) or timeSinceStart > MAX_BULLET_TIME then
  708. if bulletEffect then
  709. bulletEffect:Destroy()
  710. bulletEffect = nil
  711. end
  712.  
  713. stepConn:Disconnect()
  714. end
  715. end
  716.  
  717. stepConn = RunService.Heartbeat:Connect(steppedCallback)
  718.  
  719. -- Get rid of charge on chargeable weapons
  720. if not IsServer and self.usesCharging then
  721. self.charge = math.clamp(self.charge - self:getConfigValue("FireDischarge", 1), 0, 1)
  722. end
  723. end
  724.  
  725. function BulletWeapon:calculateDamage(travelDistance)
  726. local zeroDamageDistance = self:getConfigValue("ZeroDamageDistance", 10000)
  727. local fullDamageDistance = self:getConfigValue("FullDamageDistance", 1000)
  728. local distRange = zeroDamageDistance - fullDamageDistance
  729. local falloff = math.clamp(1 - (math.max(0, travelDistance - fullDamageDistance) / math.max(1, distRange)), 0, 1)
  730. return math.max(self:getConfigValue("HitDamage", 10) * falloff, 0)
  731. end
  732.  
  733. function BulletWeapon:applyDamage(hitInfo)
  734. local damage = self:calculateDamage(hitInfo.d)
  735.  
  736. if damage <= 0 then
  737. return
  738. end
  739.  
  740. self.weaponsSystem.doDamage(hitInfo.h, damage, nil, self.player)
  741. end
  742.  
  743. function BulletWeapon:onHit(hitInfo)
  744. local hitPoint = hitInfo.p
  745. local hitNormal = hitInfo.n
  746. local hitPart = hitInfo.part
  747.  
  748. if hitPart and hitPart.Parent then
  749. local humanoid = self.weaponsSystem.getHumanoid(hitPart)
  750. hitInfo.h = humanoid or hitPart
  751.  
  752. if IsServer and
  753. (not hitInfo.h:IsA("Humanoid") or
  754. self.weaponsSystem.playersOnDifferentTeams(self.weaponsSystem.getPlayerFromHumanoid(hitInfo.h), self.player))
  755. then
  756. self:applyDamage(hitInfo)
  757. elseif hitInfo.h:IsA("Humanoid") and
  758. hitInfo.h:GetState() ~= Enum.HumanoidStateType.Dead and
  759. self.weaponsSystem.gui and
  760. self.player == Players.LocalPlayer and
  761. self.weaponsSystem.playersOnDifferentTeams(self.weaponsSystem.getPlayerFromHumanoid(hitInfo.h), self.player)
  762. then
  763. -- Show hit indicators on gui of client that shot projectile if players are not on same team
  764. self.weaponsSystem.gui:OnHitOtherPlayer(self:calculateDamage(hitInfo.d), hitInfo.h)
  765. end
  766. end
  767.  
  768. -- Create invisible explosion on server that deals damage to anything caught in the explosion
  769. if IsServer and self:getConfigValue("ExplodeOnImpact", false) then
  770. local blastRadius = self:getConfigValue("BlastRadius", 8)
  771. local blastPressure = self:getConfigValue("BlastPressure", 10000)
  772. local blastDamage = self:getConfigValue("BlastDamage", 100)
  773.  
  774. local explosion = Instance.new("Explosion")
  775. explosion.Position = hitPoint + (hitNormal * 0.5)
  776. explosion.BlastRadius = blastRadius
  777. explosion.BlastPressure = blastPressure
  778. explosion.ExplosionType = Enum.ExplosionType.NoCraters
  779. explosion.DestroyJointRadiusPercent = 0
  780. explosion.Visible = false
  781.  
  782. explosion.Hit:Connect(function(explodedPart, hitDist)
  783. local damageMultiplier = (1 - math.clamp((hitDist / blastRadius), 0, 1))
  784. local damageToDeal = blastDamage * damageMultiplier
  785.  
  786. local humanoid = self.weaponsSystem.getHumanoid(explodedPart)
  787. if humanoid then
  788. if explodedPart.Name == "UpperTorso" and
  789. humanoid:GetState() ~= Enum.HumanoidStateType.Dead and
  790. self.weaponsSystem.playersOnDifferentTeams(self.weaponsSystem.getPlayerFromHumanoid(humanoid), self.player)
  791. then
  792. -- Do damage to players/humanoids
  793. self.weaponsSystem.doDamage(humanoid, damageToDeal, nil, self.player)
  794. end
  795. elseif not CollectionService:HasTag(explodedPart, "WeaponsSystemIgnore") then
  796. -- Do damage to a part (sends damage to breaking system)
  797. self.weaponsSystem.doDamage(explodedPart, damageToDeal, nil, self.player)
  798. end
  799. end)
  800.  
  801. explosion.Parent = workspace
  802. end
  803. end
  804.  
  805. function BulletWeapon:fire(origin, dir, charge)
  806. if not self:isCharged() then
  807. return
  808. end
  809.  
  810. BaseWeapon.fire(self, origin, dir, charge)
  811. end
  812.  
  813. function BulletWeapon:onFired(firingPlayer, fireInfo, fromNetwork)
  814. if not IsServer and firingPlayer == Players.LocalPlayer and fromNetwork then
  815. return
  816. end
  817.  
  818. local cooldownTime = self:getConfigValue("ShotCooldown", 0.1)
  819. local fireMode = self:getConfigValue("FireMode", "Semiautomatic")
  820. local isSemiAuto = fireMode == "Semiautomatic"
  821. local isBurst = fireMode == "Burst"
  822.  
  823. if isBurst and not self.burstFiring then
  824. self.burstIdx = 0
  825. self.burstFiring = true
  826. elseif isSemiAuto then
  827. self.triggerDisconnected = true
  828. end
  829.  
  830. -- Calculate cooldown time for burst firing
  831. if self.burstFiring then
  832. self.burstIdx = self.burstIdx + 1
  833. if self.burstIdx >= self:getConfigValue("NumBurstShots", 3) then
  834. self.burstFiring = false
  835. self.triggerDisconnected = true
  836. else
  837. cooldownTime = self:getConfigValue("BurstShotCooldown", nil) or cooldownTime
  838. end
  839. end
  840.  
  841. self.nextFireTime = tick() + cooldownTime
  842.  
  843. BaseWeapon.onFired(self, firingPlayer, fireInfo, fromNetwork)
  844. end
  845.  
  846. function BulletWeapon:onConfigValueChanged(valueName, newValue, oldValue)
  847. BaseWeapon.onConfigValueChanged(self, valueName, newValue, oldValue)
  848. if valueName == "ShotEffect" then
  849. self.bulletEffectTemplate = ShotsFolder:FindFirstChild(self:getConfigValue("ShotEffect", "Bullet"))
  850. if self.bulletEffectTemplate then
  851. local config = self.bulletEffectTemplate:FindFirstChildOfClass("Configuration")
  852. if config then
  853. self:importConfiguration(config)
  854. end
  855.  
  856. local beam0 = self.bulletEffectTemplate:FindFirstChild("Beam0")
  857. if beam0 then
  858. coroutine.wrap(function()
  859. ContentProvider:PreloadAsync({ beam0 })
  860. end)()
  861. end
  862. end
  863. elseif valueName == "HitMarkEffect" then
  864. self.hitMarkTemplate = HitMarksFolder:FindFirstChild(self:getConfigValue("HitMarkEffect", "BulletHole"))
  865. if self.hitMarkTemplate then
  866. local config = self.hitMarkTemplate:FindFirstChildOfClass("Configuration")
  867. if config then
  868. self:importConfiguration(config)
  869. end
  870. end
  871. elseif valueName == "CasingEffect" then
  872. self.casingTemplate = CasingsFolder:FindFirstChild(self:getConfigValue("CasingEffect", ""))
  873. if self.casingTemplate then
  874. local config = self.casingTemplate:FindFirstChildOfClass("Configuration")
  875. if config then
  876. self:importConfiguration(config)
  877. end
  878. end
  879. elseif valueName == "ChargeRate" then
  880. self.usesCharging = newValue ~= nil
  881. end
  882. end
  883.  
  884. function BulletWeapon:onActivatedChanged()
  885. BaseWeapon.onActivatedChanged(self)
  886.  
  887. if not IsServer then
  888. -- Reload if no ammo left in clip
  889. if self.equipped and self:getAmmoInWeapon() <= 0 then
  890. self:reload()
  891. return
  892. end
  893.  
  894. -- Fire weapon
  895. if self.activated and self.player == localPlayer and self:canFire() and tick() > self.nextFireTime then
  896. self:doLocalFire()
  897. end
  898.  
  899. -- Reenable trigger after activated changes to false
  900. if not self.activated and self.triggerDisconnected and not self.burstFiring then
  901. self.triggerDisconnected = false
  902. end
  903. end
  904. end
  905.  
  906. function BulletWeapon:onRenderStepped(dt)
  907. BaseWeapon.onRenderStepped(self, dt)
  908. if not self.tipAttach then return end
  909. if not self.equipped then return end
  910.  
  911. local tipCFrame = self.tipAttach.WorldCFrame
  912.  
  913. if self.player == Players.LocalPlayer then
  914. -- Retrieve aim point from camera and update player's aim animation
  915. local aimTrack = self:getAnimTrack(self:getConfigValue("AimTrack", "RifleAim"))
  916. local aimZoomTrack = self:getAnimTrack(self:getConfigValue("AimZoomTrack", "RifleAimDownSights"))
  917. if aimTrack then
  918. local aimDir = tipCFrame.LookVector
  919.  
  920. local gunLookRay = Ray.new(tipCFrame.p, aimDir * 500)
  921.  
  922. local _, gunHitPoint = Roblox.penetrateCast(gunLookRay, self.ignoreList)
  923.  
  924. if self.weaponsSystem.aimRayCallback then
  925. local _, hitPoint = Roblox.penetrateCast(self.weaponsSystem.aimRayCallback(), self.ignoreList)
  926. self.aimPoint = hitPoint
  927. else
  928. self.aimPoint = gunHitPoint
  929. end
  930.  
  931. if not aimTrack.IsPlaying and not self.reloading then
  932. aimTrack:Play(0.15)
  933. coroutine.wrap(function() -- prevent player from firing until gun is fully out
  934. wait(self:getConfigValue("StartupTime", 0.2))
  935. self.startupFinished = true
  936. end)()
  937. end
  938.  
  939. if aimZoomTrack and not self.reloading then
  940. if not aimZoomTrack.IsPlaying then
  941. aimZoomTrack:Play(0.15)
  942. end
  943. aimZoomTrack:AdjustSpeed(0.001)
  944. if self.weaponsSystem.camera:isZoomed() then
  945. if aimTrack.WeightTarget ~= 0 then
  946. aimZoomTrack:AdjustWeight(1)
  947. aimTrack:AdjustWeight(0)
  948. end
  949. elseif aimTrack.WeightTarget ~= 1 then
  950. aimZoomTrack:AdjustWeight(0)
  951. aimTrack:AdjustWeight(1)
  952. end
  953. end
  954.  
  955. local MIN_ANGLE = -80
  956. local MAX_ANGLE = 80
  957. local aimYAngle = math.deg(self.recoilIntensity)
  958. if self.weaponsSystem.camera.enabled then
  959. -- Gets pitch and recoil from camera to figure out how high/low to aim the gun
  960. aimYAngle = math.deg(self.weaponsSystem.camera:getRelativePitch() + self.weaponsSystem.camera.currentRecoil.Y + self.recoilIntensity)
  961. end
  962. local aimTimePos = 2 * ((aimYAngle - MIN_ANGLE) / (MAX_ANGLE - MIN_ANGLE))
  963.  
  964. aimTrack:AdjustSpeed(0.001)
  965. aimTrack.TimePosition = math.clamp(aimTimePos, 0.001, 1.97)
  966.  
  967. if aimZoomTrack then
  968. aimZoomTrack.TimePosition = math.clamp(aimTimePos, 0.001, 1.97)
  969. end
  970.  
  971. -- Update recoil (decay over time)
  972. local recoilDecay = self:getConfigValue("RecoilDecay", 0.825)
  973. self.recoilIntensity = math.clamp(self.recoilIntensity * recoilDecay, 0, math.huge)
  974. else
  975. warn("no aimTrack")
  976. end
  977. end
  978. end
  979.  
  980. function BulletWeapon:setChargingParticles(charge)
  981. local ratePerCharge = self:getConfigValue("ChargingParticlesRatePerCharge", 20)
  982. local rate = ratePerCharge * charge
  983. for _, v in pairs(self.chargingParticles) do
  984. v.Rate = rate
  985. end
  986. end
  987.  
  988. function BulletWeapon:onStepped(dt)
  989. if not self.tipAttach then return end
  990. if not self.equipped then return end
  991.  
  992. BaseWeapon.onStepped(self, dt)
  993.  
  994. local now = tick()
  995.  
  996. local chargingSound = self:getSound("Charging")
  997. local dischargingSound = self:getSound("Discharging")
  998.  
  999. if self.usesCharging then
  1000. -- Update charge amount
  1001. local chargeBefore = self.charge
  1002. self:handleCharging(dt)
  1003. local chargeDelta = self.charge - chargeBefore
  1004.  
  1005. -- Update charge particles
  1006. if chargeDelta > 0 then
  1007. self:setChargingParticles(self.charge)
  1008. else
  1009. self:setChargingParticles(0)
  1010. end
  1011.  
  1012. -- Play charging sounds
  1013. if chargingSound then
  1014. if chargingSound.Looped then
  1015. if chargeDelta < 0 then
  1016. chargingSound:Stop()
  1017. else
  1018. if not chargingSound.Playing and self.charge < 1 and chargeDelta > 0 then
  1019. chargingSound:Play()
  1020. end
  1021. chargingSound.PlaybackSpeed = self.chargeSoundPitchMin + (self.charge * (self.chargeSoundPitchMax - self.chargeSoundPitchMin))
  1022. end
  1023. else
  1024. if chargeDelta > 0 and self.charge <= 1 and not chargingSound.Playing then
  1025. chargingSound.TimePosition = chargingSound.TimeLength * self.charge
  1026. chargingSound:Play()
  1027. elseif chargeDelta <= 0 and chargingSound.Playing then
  1028. chargingSound:Stop()
  1029. end
  1030. end
  1031. end
  1032. if dischargingSound then
  1033. if dischargingSound.Looped then
  1034. if chargeDelta > 0 then
  1035. dischargingSound:Stop()
  1036. else
  1037. if not dischargingSound.Playing and self.charge > 0 then
  1038. dischargingSound:Play()
  1039. end
  1040. dischargingSound.PlaybackSpeed = self.chargeSoundPitchMin + (self.charge * (self.chargeSoundPitchMax - self.chargeSoundPitchMin))
  1041. end
  1042. else
  1043. if chargeDelta < 0 and self.charge >= 0 and not dischargingSound.Playing then
  1044. dischargingSound.TimePosition = dischargingSound.TimeLength * self.charge
  1045. dischargingSound:Play()
  1046. elseif chargeDelta >= 0 and dischargingSound.Playing then
  1047. dischargingSound:Stop()
  1048. end
  1049. end
  1050. end
  1051.  
  1052. -- Play charge/discharge completed sounds and particle effects
  1053. if chargeBefore < 1 and self.charge >= 1 then
  1054. local chargeCompleteSound = self:getSound("ChargeComplete")
  1055. if chargeCompleteSound then
  1056. chargeCompleteSound:Play()
  1057. end
  1058. if chargingSound and chargingSound.Playing then
  1059. chargingSound:Stop()
  1060. end
  1061. if self.chargeCompleteParticles then
  1062. self.chargeCompleteParticles:Emit(self:getConfigValue("NumChargeCompleteParticles", 25))
  1063. end
  1064. end
  1065. if chargeBefore > 0 and self.charge <= 0 then
  1066. local dischargeCompleteSound = self:getSound("DischargeComplete")
  1067. if dischargeCompleteSound then
  1068. dischargeCompleteSound:Play()
  1069. end
  1070. if dischargingSound and dischargingSound.Playing then
  1071. dischargingSound:Stop()
  1072. end
  1073. if self.dischargeCompleteParticles then
  1074. self.dischargeCompleteParticles:Emit(self:getConfigValue("NumDischargeCompleteParticles", 25))
  1075. end
  1076. end
  1077.  
  1078. self:renderCharge()
  1079. else
  1080. if chargingSound then
  1081. chargingSound:Stop()
  1082. end
  1083. if dischargingSound then
  1084. dischargingSound:Stop()
  1085. end
  1086. end
  1087.  
  1088. if self.usesCharging and self.chargeGlowPart then
  1089. self.chargeGlowPart.Transparency = 1 - self.charge
  1090. end
  1091.  
  1092. -- Fire weapon if it is fully charged
  1093. if self:canFire() and now > self.nextFireTime then
  1094. self:doLocalFire()
  1095. end
  1096. end
  1097.  
  1098. function BulletWeapon:handleCharging(dt)
  1099. local chargeDelta
  1100. local shouldCharge = self.activated or self.burstFiring or self:getConfigValue("ChargePassively", false)
  1101. if self.reloading or self.triggerDisconnected then
  1102. shouldCharge = false
  1103. end
  1104.  
  1105. if shouldCharge then
  1106. chargeDelta = self:getConfigValue("ChargeRate", 0) * dt
  1107. else
  1108. chargeDelta = self:getConfigValue("DischargeRate", 0) * -dt
  1109. end
  1110.  
  1111. self.charge = math.clamp(self.charge + chargeDelta, 0, 1)
  1112. end
  1113.  
  1114. function BulletWeapon:isCharged()
  1115. return not self.usesCharging or self.charge >= 1
  1116. end
  1117.  
  1118. function BulletWeapon:canFire()
  1119. return self.player == Players.LocalPlayer and (self.burstFiring or self.activated) and not self.triggerDisconnected and not self.reloading and self:isCharged() and self.startupFinished
  1120. end
  1121.  
  1122. function BulletWeapon:doLocalFire()
  1123. if self.tipAttach then
  1124. local tipCFrame = self.tipAttach.WorldCFrame
  1125. local tipPos = tipCFrame.Position
  1126. local aimDir = (self.aimPoint - tipPos).Unit
  1127.  
  1128. self:fire(tipPos, aimDir, self.charge)
  1129. end
  1130. end
  1131.  
  1132. return BulletWeapon
  1133.  
Add Comment
Please, Sign In to add comment