local CollectionService = game:GetService("CollectionService")
local Debris = game:GetService("Debris")
local ServerStorage = game:GetService("ServerStorage")

local FORCE_GRANULARITY = 2
local allowTeamDamage = false

local teamDamage = ServerStorage:FindFirstChild("TeamDamage")

if teamDamage then
	allowTeamDamage = teamDamage.Value
end

local function processExplosion(explosion)
	local BLAST_RADIUS = explosion.BlastRadius
	local BLAST_PRESSURE = explosion.BlastPressure
	
	if explosion:FindFirstChild("BLAST_PRESSURE") then
		BLAST_PRESSURE = explosion.BLAST_PRESSURE.Value
	end
	
	if BLAST_PRESSURE > 0 then
		local damagedPlayerSet = {}
		local blastCenter = explosion.Position
		
		local function onExplosionHit(p, dist)
			if explosion:FindFirstChild("Owner") then
				local player = explosion.Owner.Value
				if player then
					local char = player.Character
					if char and p:IsDescendantOf(char) then
						return
					end
				end
			end
			
			local isInCharacter = false
			
			if p.Size.Magnitude / 2 < 20 then
				--world->ticklePrimitive(p, true);
				
				local doBreakjoints = true
				local hitCharacter = p:FindFirstAncestorWhichIsA("Model")
				local hitHumanoid = hitCharacter:FindFirstChild("Humanoid")
				
				if hitCharacter and hitHumanoid then
					-- flag as character
					isInCharacter = true					
					
					-- don't breakjoints characters
					doBreakjoints = false
					
					-- work out what damage to do					
					local hitPlayer = game.Players:GetPlayerFromCharacter(hitCharacter)
					local creatorTag = explosion:FindFirstChild("creator")
					local myPlayer
					
					if creatorTag then
						myPlayer = creatorTag.Value
					end
					
					if hitPlayer and not damagedPlayerSet[hitPlayer] then
						local doDamage = true
						
						if not allowTeamDamage then
							if myPlayer and hitPlayer ~= myPlayer then
								if hitPlayer.Team and myPlayer.Team and hitPlayer.Team == myPlayer.Team then
									doDamage = false
								end
							end
						end
						
						if doDamage then
							-- flag as damaged
							damagedPlayerSet[hitPlayer] = true
							
							-- assume the torso is a massless frictionless unit ball in a perfect vaccum
							dist = math.min(math.max(dist - 0.8, 0), 1)
							
							-- damage to do
							local frac = (dist / BLAST_RADIUS)	
		
							-- do damage. See how much damage to do
							if myPlayer == hitPlayer then
								hitHumanoid:TakeDamage((BLAST_RADIUS * 20) - (frac * 38))
								hitHumanoid:ChangeState("Ragdoll")
							else
								hitHumanoid:TakeDamage(100)
							end
						end
					end
				end		
				
				-- breakjoints stuff
				if doBreakjoints then
					if not hitHumanoid and p:CanSetNetworkOwnership() then
						p:SetNetworkOwner(nil)
					end
					
					for _,joint in pairs(p:GetJoints()) do
						if not CollectionService:HasTag(joint, "GorillaGlue") then
							joint:Destroy()
						end
					end
				end
				
				--Vector3 delta = (p->getCoordinateFrame().translation - position);
				local delta = (p.Position - blastCenter)
				
				--Vector3 normal = 
				--	(delta == Vector3::zero())
				--	? Vector3::unitY()
				--	: delta.direction();
				local normal = (delta == Vector3.new(0, 0, 0))
				               and Vector3.new(0, 1, 0)
				               or  delta.unit
				
				--float radius = p->getRadius();
				local radius = p.Size.magnitude / 2				
				
				--float surfaceArea = radius * radius;
				local surfaceArea = radius * radius
				
				--Vector3 impulse = normal * blastPressure * surfaceArea * (1.0f / 4560.0f); // normalizing factor
				local impulse = normal * BLAST_PRESSURE * surfaceArea * (1.0 / 4560.0)
				
				-- How much force to apply (for characters, ramp it down towards the edge)
				local frac;
				
				if isInCharacter then
					frac = 1 - math.max(0, math.min(1, (dist - 2) / BLAST_RADIUS))
				else
					frac = 1
				end				
				
				--p->getBody()->accumulateLinearImpulse(impulse, p->getCoordinateFrame().translation);
				local currentVelocity = p.Velocity
				local deltaVelocity = impulse / p:GetMass() -- m * del-v = F * del-t = Impulse
				local forceNeeded = workspace.Gravity * p:GetMass() -- F = ma
				
				local bodyV = Instance.new('BodyVelocity')
				bodyV.Velocity = currentVelocity + deltaVelocity
				bodyV.MaxForce = Vector3.new(forceNeeded, forceNeeded, forceNeeded) * 10 * frac
				bodyV.Parent = p
				
				Debris:AddItem(bodyV, 0.2 / FORCE_GRANULARITY)
				
				--p->getBody()->accumulateRotationalImpulse(impulse * 0.5 * radius); // a somewhat arbitrary, but nice torque
				local rotImpulse = impulse * 0.5 * radius
				local currentRotVelocity = p.RotVelocity
				
				local momentOfInertia = (2 * p:GetMass() * radius * radius / 5) -- moment of inertia = 2/5*m*r^2 (assuming roughly spherical)
				local deltaRotVelocity = rotImpulse / momentOfInertia 
				local torqueNeeded = 20 * momentOfInertia -- torque = r x F, want about alpha = 20 rad/s^2, alpha * P = torque
					
				local rot = Instance.new('BodyAngularVelocity')
				rot.MaxTorque = Vector3.new(torqueNeeded, torqueNeeded, torqueNeeded) * 10 * frac
				rot.AngularVelocity = currentRotVelocity + deltaRotVelocity
				rot.Parent = p
				
				Debris:AddItem(rot, 0.2 / FORCE_GRANULARITY)
			end
		end
		
		explosion.Hit:Connect(onExplosionHit)
	end
end

local function onDescendantAdded(desc)
	if desc:IsA("Explosion") then
		local pressure = desc.BlastPressure
		
		if pressure > 0 then
			local blastPressure = Instance.new("NumberValue")
			blastPressure.Name = "BLAST_PRESSURE"
			blastPressure.Value = pressure
			blastPressure.Parent = desc
			
			desc.BlastPressure = 0
		end
		
		processExplosion(desc)
	end
end

workspace.DescendantAdded:Connect(onDescendantAdded)