mirror of
				https://github.com/chev2/gmod-addons.git
				synced 2025-10-30 06:31:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			248 lines
		
	
	
	
		
			7.6 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
	
		
			7.6 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| AddCSLuaFile("cl_init.lua")
 | |
| AddCSLuaFile("shared.lua")
 | |
| 
 | |
| include("shared.lua")
 | |
| 
 | |
| -- Internal size of the ball--do not touch this
 | |
| ENT.BallSize = 18
 | |
| 
 | |
| -- If ball is being initially served or not
 | |
| ENT.ServingPitch = false
 | |
| 
 | |
| util.AddNetworkString("llb_baseball.DrawInvertedColors")
 | |
| util.AddNetworkString("llb_baseball.ChangeBallColor")
 | |
| util.AddNetworkString("llb_baseball.ScreenShake")
 | |
| 
 | |
| local NEW_VEL_VECTOR = Vector()
 | |
| 
 | |
| function ENT:Initialize()
 | |
|     self:SetModel("models/llb/baseball.mdl")
 | |
|     self:SetModelScale(0.55)
 | |
| 
 | |
|     self:SetCollisionGroup(COLLISION_GROUP_PASSABLE_DOOR)
 | |
|     self:PhysicsInit(SOLID_VPHYSICS)
 | |
|     self:SetMoveType(MOVETYPE_VPHYSICS)
 | |
|     self:SetSolid(SOLID_VPHYSICS)
 | |
| 
 | |
|     self:SetTrigger(true)
 | |
| 
 | |
|     self.ServingPitch = true
 | |
| 
 | |
|     local size = self.BallSize / 2.1
 | |
|     self:PhysicsInitSphere(size, "metal_bouncy")
 | |
|     self:SetCollisionBounds(Vector(-size, -size, -size), Vector(size, size, size))
 | |
| 
 | |
|     local trailTexture = "trails/smoke"
 | |
|     if IsMounted("tf") then trailTexture = "effects/beam_generic01" end
 | |
|     self.BallTrail = util.SpriteTrail(self, 0, Color(255, 0, 0), false, 17, 17, 0.03, 1 / 17, trailTexture)
 | |
| 
 | |
|     local phys = self:GetPhysicsObject()
 | |
|     if phys:IsValid() then
 | |
|         phys:Wake()
 | |
|         phys:EnableGravity(false)
 | |
|     end
 | |
| 
 | |
|     hook.Add("KeyPress", self, function(ent, ply, key)
 | |
|         if not IsValid(ply) or not IsValid(ent) or not IsFirstTimePredicted() then return end
 | |
| 
 | |
|         local plyPos = ply:GetPos()
 | |
|         local entPos = ent:GetPos()
 | |
| 
 | |
|         -- The player will hit the ball if they're within 120 hammer units.
 | |
|         -- They don't need to look directly at the ball
 | |
|         if key == IN_ATTACK and plyPos:Distance(entPos) < 120 then
 | |
|             local dmginfo = DamageInfo()
 | |
|             dmginfo:SetAttacker(ply)
 | |
|             ent:OnTakeDamage(dmginfo)
 | |
|         end
 | |
|     end)
 | |
| end
 | |
| 
 | |
| function ENT:Think()
 | |
|     local phys = self:GetPhysicsObject()
 | |
|     if not IsValid(phys) then return end
 | |
| 
 | |
|     local vel = self:GetVelocity()
 | |
|     vel.x = self:GetLockXAxis() and 0 or vel.x
 | |
|     vel.y = self:GetLockYAxis() and 0 or vel.y
 | |
|     vel.z = self:GetLockZAxis() and 0 or vel.z
 | |
| 
 | |
|     -- Use a preinitialized vector so we don't create a vector every frame
 | |
|     NEW_VEL_VECTOR:SetUnpacked(vel.x, vel.y, vel.z)
 | |
|     phys:SetVelocity(NEW_VEL_VECTOR)
 | |
| 
 | |
|     self:NextThink(CurTime())
 | |
| 
 | |
|     return true
 | |
| end
 | |
| 
 | |
| function ENT:PhysicsCollide(data, physobj)
 | |
|     -- Play sound and screen shake on bounce
 | |
|     if data.DeltaTime > 0.02 then
 | |
|         sound.Play(self.BounceSound, self:GetPos(), 80, 100, 1)
 | |
| 
 | |
|         self:ScreenShake(2, 4, 0.2, 1500)
 | |
|     end
 | |
| end
 | |
| 
 | |
| function ENT:SpawnFunction(ply, tr, ClassName)
 | |
|     if not tr.Hit then return end
 | |
| 
 | |
|     local ent = ents.Create(ClassName)
 | |
|     ent:SetPos(tr.HitPos + tr.HitNormal * self.BallSize * 2)
 | |
|     ent:Spawn()
 | |
|     ent:Activate()
 | |
| 
 | |
|     local phys = ent:GetPhysicsObject()
 | |
|     phys:SetVelocity(Vector(0, 0, 0))
 | |
|     phys:SetAngles(Angle(0, 0, 0))
 | |
|     phys:AddAngleVelocity(Vector(256, 0, 0))
 | |
| 
 | |
|     return ent
 | |
| end
 | |
| 
 | |
| function ENT:OnTakeDamage(dmginfo)
 | |
|     -- Prevents multiple damage events from overlapping and causing issues
 | |
|     if self.IsPaused then return end
 | |
| 
 | |
|     -- Attacker must be an alive player
 | |
|     local att = dmginfo:GetAttacker()
 | |
|     if not att:IsPlayer() or not att:Alive() then return end
 | |
| 
 | |
|     -- Ball needs valid physics, otherwise we can't temporarily freeze it
 | |
|     local phys = self:GetPhysicsObject()
 | |
|     if not IsValid(phys) then return end
 | |
| 
 | |
|     local newvel = self:GetVelocity() * 1.5
 | |
|     local speed = newvel:Length() ^ 0.8
 | |
| 
 | |
|     -- Freeze the ball
 | |
|     phys:EnableMotion(false)
 | |
|     self.IsPaused = true
 | |
| 
 | |
|     -- Set new ball owner
 | |
|     self.BallOwner = att
 | |
| 
 | |
|     -- Set the color of the ball trail
 | |
|     local plycol = att:GetPlayerColor():ToColor()
 | |
|     local newplycol = math.Round(plycol.r) .. " " .. math.Round(plycol.g) .. " " .. math.Round(plycol.b)
 | |
|     self.BallTrail:Fire("Color", newplycol)
 | |
| 
 | |
|     -- Let clients know about the ball's new color
 | |
|     net.Start("llb_baseball.ChangeBallColor")
 | |
|         net.WriteEntity(self)
 | |
|         net.WriteEntity(self.BallOwner)
 | |
|         net.Broadcast()
 | |
| 
 | |
|     -- Fixes issues where velocity gets nulled out if ball is locked to an axis and player fires perpendicular to it
 | |
|     local aimAng = att:GetAimVector():Angle()
 | |
|     aimAng:Normalize()
 | |
| 
 | |
|     if self:GetLockXAxis() then
 | |
|         aimAng.y = aimAng.y > 0 and 90 or -90
 | |
|     end
 | |
| 
 | |
|     if self:GetLockYAxis() then
 | |
|         aimAng.y = math.abs(aimAng.y) > 90 and 180 or 0
 | |
|     end
 | |
| 
 | |
|     if self:GetLockZAxis() then
 | |
|         aimAng.p = 0
 | |
|     end
 | |
| 
 | |
|     local aimVecFixed = aimAng:Forward()
 | |
| 
 | |
|     local ballPauseTime = math.Clamp(0.00205 * speed, 0, 1.2)
 | |
| 
 | |
|     -- Play different sounds based on ball speed
 | |
|     if speed > 1000 then
 | |
|         -- Tell clients in PVS to invert their screen colors
 | |
|         net.Start("llb_baseball.DrawInvertedColors")
 | |
|             net.WriteEntity(self)
 | |
|             net.WriteFloat(math.Clamp(0.00205 * speed, 0, 1.2))
 | |
|             net.SendPVS(self:GetPos())
 | |
| 
 | |
|         sound.Play(self.StrongHitSound, self:GetPos(), 80, 100, 1)
 | |
|         self:ScreenShake(25, 4, ballPauseTime, 300)
 | |
|     elseif speed > 500 then
 | |
|         sound.Play(self.StrongHitSound, self:GetPos(), 80, 100, 1)
 | |
|         self:ScreenShake(20, 4, ballPauseTime, 300)
 | |
|     else
 | |
|         sound.Play(self.HitSound, self:GetPos(), 80, speed * 0.0295 + 100, 1)
 | |
|         self:ScreenShake(2, 4, ballPauseTime, 300)
 | |
|     end
 | |
| 
 | |
|     -- Lock the player until ball unfreezes.
 | |
|     att:Lock()
 | |
|     timer.Simple(ballPauseTime, function()
 | |
|         if not IsValid(att) then return end
 | |
| 
 | |
|         att:UnLock()
 | |
|     end)
 | |
| 
 | |
|     -- Unfreeze the ball after it was temporarily frozen.
 | |
|     timer.Simple(ballPauseTime, function()
 | |
|         if not IsValid(self) or not IsValid(phys) then return end
 | |
|         self.IsPaused = false
 | |
| 
 | |
|         phys:EnableMotion(true)
 | |
| 
 | |
|         if self.ServingPitch then
 | |
|             phys:SetVelocity(aimVecFixed * 50 * 3.4)
 | |
| 
 | |
|             self.ServingPitch = false
 | |
|         else
 | |
|             if speed == 0 then return end
 | |
| 
 | |
|             -- *May need some more testing
 | |
|             phys:SetVelocity(aimVecFixed * speed * 3.8)
 | |
|         end
 | |
| 
 | |
|         phys:AddAngleVelocity(Vector(-512, 0, 0))
 | |
|     end)
 | |
| end
 | |
| 
 | |
| function ENT:StartTouch(ent)
 | |
|     local speed = self:GetVelocity():Length() ^ 0.8
 | |
|     -- Damage a player if they touch the ball
 | |
|     if ent:IsPlayer() and self.BallOwner != ent and speed > 16 then
 | |
|         if not self:GetDamagePlayers() then return end
 | |
|         local dmginf = DamageInfo()
 | |
|         dmginf:SetDamage(speed / 8)
 | |
|         dmginf:SetDamageType(DMG_GENERIC)
 | |
|         dmginf:SetAttacker(self)
 | |
|         dmginf:SetInflictor(self)
 | |
|         dmginf:SetDamageForce(Vector(0, 0, 1))
 | |
|         ent:TakeDamageInfo(dmginf)
 | |
| 
 | |
|         self:ScreenShake(2, 30, 0.5, 300)
 | |
| 
 | |
|         -- If player is still alive after taking damage
 | |
|         if ent:Health() > 0 then
 | |
|             sound.Play("llb/takedmg_" .. math.random(1, 4) .. ".ogg", self:GetPos(), 80, 100, 1)
 | |
|         -- Reset ball speed when a player dies
 | |
|         else
 | |
|             local phys = self:GetPhysicsObject()
 | |
|             phys:SetVelocity(Vector(0, 0, 0))
 | |
|             phys:SetAngles(Angle(90, 0, 0))
 | |
|             phys:AddAngleVelocity(-phys:GetAngleVelocity() + Vector(256, 0, 0))
 | |
| 
 | |
|             self.ServingPitch = true
 | |
| 
 | |
|             sound.Play(self.KnockoutSound, self:GetPos(), 80, 100, 1)
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function ENT:ScreenShake(amplitude, frequency, duration, radius)
 | |
|     --util.ScreenShake(self:GetPos(), amplitude, frequency, duration, radius, true)
 | |
| 
 | |
|     -- New AirShake parameter only works on clientside at the moment, so we have to network it
 | |
|     net.Start("llb_baseball.ScreenShake")
 | |
|         net.WriteVector(self:GetPos())
 | |
|         net.WriteUInt(amplitude, 8)
 | |
|         net.WriteUInt(frequency, 8)
 | |
|         net.WriteFloat(duration)
 | |
|         net.WriteFloat(radius)
 | |
|         net.SendPVS(self:GetPos())
 | |
| end
 |