local STATE_READY = 0
local STATE_FIRING = 1
local STATE_WINDING = 2
local STATE_UNWINDING = 3

minigunprojectileHandler = {
	shellNum = 1,
	shells = {},
	defaultShell = {active = false},
}

minigunshellHandler = {
	shellNum = 1,
	shells = {},
	defaultShell = {
		active = false,
		aliveTimer = 0,
		bounces = 0
	},
}

muzzleflash = LoadSprite("gfx/glare.png")

function isItemInTable(item,listing) --for duplicate bodys in tables
	local listed = false
	
	for i,k in ipairs(listing) do
		if k == item then listed = true end --found
	end
	
	if listed then return true else return false end
end


function init()
	RegisterTool("Enforcer", "Enforcer", "MOD/vox/Enforcer.vox", 3)
	SetBool("game.tool.Enforcer.enabled", true)
	SetFloat("game.tool.Enforcer.ammo", 101)

    strength = 100	--Strength of Shotgun Blast
    maxMass = 10000	--The maximum mass for a body to be affected
    maxDist = 50

	state = STATE_READY
	damage = 0.8
	gravity = Vec(0, 0, 0)
	shellGravity = Vec(0, -50, 0)
	velocity = 1.2
	angle = 0
	angVel = 0
        smoke = 0
        explo = GetBool("savegame.mod.explo")

        firstshot = false
	vrec = 0.7
	hrec = 0.5
	Float = 0.4

	shotDelay = 0.000010
	startDelay = 0.6
	bulletsFired = 0
	simulBullets = 0

	for i=1, 200 do
		minigunprojectileHandler.shells[i] = deepcopy(minigunprojectileHandler.defaultShell)
	end

	for i=1, 100 do
		minigunshellHandler.shells[i] = deepcopy(minigunshellHandler.defaultShell)
	end

	shootTimer = 0
	spreadTimer = 0
	smokeTimer = 0
	recoilTimer = 0
	smoke = 0	

	gunsound = LoadLoop("MOD/snd/gunloop.ogg")
	cocksound = LoadSound("MOD/snd/m249_cock.ogg")
	windupsound = LoadSound("MOD/snd/windup.ogg")
	winddownsound = LoadSound("MOD/snd/winddown.ogg")
	shellsound = LoadSound("MOD/snd/shellground.ogg")

	if explo then
		explosive = true
	end	
end

function rndFloat(mi, ma)
	return mi + (ma-mi)*(math.random(0, 1000000)/1000000.0)
end


function deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else
        copy = orig
    end
    return copy
end

function GetAimPos()
	local ct = GetCameraTransform()
	local forwardPos = TransformToParentPoint(ct, Vec(0, 0, -100))
    local direction = VecSub(forwardPos, ct.pos)
    local distance = VecLength(direction)
	local direction = VecNormalize(direction)
        QueryRequire("physical dynamic large")
	local hit, hitDistance = QueryRaycast(ct.pos, direction, distance)
	if hit then
		forwardPos = TransformToParentPoint(ct, Vec(0, 0, -hitDistance))
		distance = hitDistance
	end
	return forwardPos, hit, distance
end

function Shoot()
	if shootTimer > 0 then
		return
	end

	local ct = GetCameraTransform()
	aimpos, hit, distance = GetAimPos()

	local recoildir = TransformToParentVec(ct, Vec(0, 0, 0))
	local vel = GetPlayerVelocity()
	local oldvel = VecCopy(vel)
	vel = VecAdd(vel, recoildir)
	if VecLength(vel) > 6.5 then vel = oldvel end
	SetPlayerVelocity(vel)
	
	local p = toolPos
	local dir = VecSub(aimpos, p)
	local straightDir = VecSub(aimpos, toolPos)
	local maxSpread = 3
---InputDown("ctrl") and 2.5 or 7
	local spread = math.min(spreadTimer, maxSpread) * distance/100
	local damage = math.random(5, 90)
	dir[1] = dir[1] + (math.random()-0.5)*2*spread
	dir[2] = dir[2] + (math.random()-0.5)*2*spread
	dir[3] = dir[3] + (math.random()-0.5)*2*spread

	minigunprojectileHandler.shells[minigunprojectileHandler.shellNum] = deepcopy(minigunprojectileHandler.defaultShell)  
	loadedShell = minigunprojectileHandler.shells[minigunprojectileHandler.shellNum] 
	loadedShell.active = true
        local ct = GetCameraTransform()
	loadedShell.pos = TransformToParentPoint(ct, Vec(0.30, -0.30, -3.3))
        loadedShell.damage = damage/10
	loadedShell.predictedBulletVelocity = VecScale(dir, velocity*(100/distance))
	minigunprojectileHandler.shellNum = (minigunprojectileHandler.shellNum%#minigunprojectileHandler.shells) +1
        
	local espread = 0.5
	local ejectPos = VecAdd(TransformToParentPoint(toolTrans, Vec(0.275, -0.65, -0.7)), VecScale(GetPlayerVelocity(), 4*GetTimeStep()))
	local ejectDirVec = TransformToParentPoint(toolTrans, Vec(1.5, 3, -0.9))
	local shell = Spawn("MOD/akshell.xml", Transform(ejectPos, QuatLookAt(straightDir)))
	ejectDirVec[1] = ejectDirVec[1] + (math.random()-0.5)*2*espread
	ejectDirVec[2] = ejectDirVec[2] + (math.random()-0.5)*2*espread
	ejectDirVec[3] = ejectDirVec[3] + (math.random()-0.5)*2*espread
	local ejectDir = VecAdd(VecSub(ejectDirVec, ejectPos), VecScale(GetPlayerVelocity(), 0.5))

	straightDir[1] = straightDir[1] + (math.random()-0.5)*10*espread
	straightDir[2] = straightDir[2] + (math.random()-0.5)*10*espread
	straightDir[3] = straightDir[3] + (math.random()-0.5)*10*espread

	minigunshellHandler.shells[minigunshellHandler.shellNum] = deepcopy(minigunshellHandler.defaultShell)
	loadedShell = minigunshellHandler.shells[minigunshellHandler.shellNum] 
	loadedShell.active = true
	loadedShell.pos = ejectPos
	loadedShell.targetRot = QuatLookAt(straightDir)
	loadedShell.voxBody = shell[1]
	loadedShell.aliveTimer = 3
	loadedShell.predictedBulletVelocity = VecScale(ejectDir, 2)
	minigunshellHandler.shellNum = (minigunshellHandler.shellNum%#minigunshellHandler.shells) + 1

	shootTimer = shotDelay
	recoilTimer = shotDelay
	spreadTimer = spreadTimer + 0.30
	bulletsFired = bulletsFired + 1
	simulBullets = simulBullets + 1
    vpunch = 0.1

	local vrectemp = vrec
		if firstshot then
			vrectemp = vrec*fsrm
			firstshot = false
		end
		local kickt = GetPlayerTransform(true)
		local playervel = GetPlayerVelocity()
		local qpitch = GetQuatEuler(kickt.rot)
		local unusedpitch, qyaw, qroll = GetQuatEuler(GetPlayerCameraTransform().rot)
		kickt.rot = QuatEuler(qpitch,qyaw,qroll)
		kickt.rot = QuatRotateQuat(kickt.rot,QuatEuler(vrectemp*rndFloat(0.1, 0.33),-hrec*rndFloat(-0.33, 0.11),0))
                if InputDown("rmb") and InputDown("lmb") then
		SetPlayerTransform(kickt,true)
		SetPlayerVelocity(playervel)
                end
        ParticleReset()
	ParticleType("plain")
        ParticleTile(5)
        ParticleColor(1, 0.6, 0.4, 1, 0.3)
        ParticleRadius(rnd(0.1, 0.3))
        ParticleGravity(2)
        ParticleEmissive(3.5, 0.5)
        SpawnParticle(TransformToParentPoint(GetBodyTransform(GetToolBody()), Vec(0.22, -0.6, -3.5)), VecScale(Vec(0, 10, 0), 0.05), 0.4, 0.2)
        local rnd = math.random()*3/4+1
        if InputDown("rmb") and InputDown("lmb") then
        DrawSprite(muzzleflash,{pos=p,rot=GetCameraTransform().rot},3.3*rnd,3.3*rnd,1,0.6,0.3,1,0.3,1,0.1,false,true)
        end
end

function ProjectileOperations(projectile)
	projectile.predictedBulletVelocity = VecAdd(projectile.predictedBulletVelocity, (VecScale(gravity, GetTimeStep())))
	local point2 = VecAdd(projectile.pos, VecScale(projectile.predictedBulletVelocity, GetTimeStep()))
	local dir = VecNormalize(VecSub(point2, projectile.pos))
	local hit, dist, normal, shape = QueryRaycast(projectile.pos, dir, VecLength(VecSub(point2, projectile.pos)))
        local mat, r, g, b, a = GetShapeMaterialAtPosition(shape, hitPos)
    
	
	if hit then
		hitPos = VecAdd(projectile.pos, VecScale(VecNormalize(VecSub(point2, projectile.pos)), dist))
		projectile.active = false
                if not HasTag(shape, "shapeHealth") and not InputDown("rmb") then
                SpawnParticle("smoke", hitPos, Vec(0, 1.0+math.random(1,10)*0.1, 0), 0.5, 0.5)
                end   
		if mat == "metal" or mat == "heavymetal" or mat == "hardmetal" and not InputDown("rmb") then
				ParticleReset()
				ParticleType("plain")
				ParticleTile(6)
				ParticleSticky(0, 0)
				ParticleRadius(0.01, 0.01)
				ParticleAlpha(1)
				ParticleEmissive(10, 10)
				ParticleColor(1, 0.75, 0.25, 1, 0.25, 0.25)
				ParticleGravity(-10, -10)
				for i = 1, 20 do
					SpawnParticle(hitPos, Vec((math.random(-100,100)/100)*5,(math.random(-100,100)/100)*5, (math.random(-100,100)/100)*5), math.random(10,25)/20)
                end
                else
				ParticleReset()
				ParticleType("plain")
				ParticleTile(6)
				ParticleSticky(0, 0)
				ParticleRadius(0.05, 0.05)
				ParticleAlpha(a, 0.7)
				ParticleEmissive(0, 0)
				ParticleColor(r, g, b)
				ParticleGravity(-10, -5)
				for i = 1, math.random (15, 20) do
					SpawnParticle(hitPos, Vec((math.random(-100,100)/100)*5,(math.random(-100,100)/100)*5, (math.random(-100,100)/100)*5), math.random(10,25)/20)
                                end
                                end
                
		if explosive then
			MakeHole(hitPos, 0.20, 0.20*0.8, 0.25*0.11)	
		end

                if not explosive then
			MakeHole(hitPos, damage, damage*0.8, damage*0.11)
                end
				
		if InputDown("lmb") and InputDown("rmb") then
                Explosion(hitPos, 0.02)
                end
		--dima bot support
                local hitcount = 0.4 and 1 or 9
		local dmgMult = 0.6 and 20 or 55
		destructiblerobots(hitPos,damage*dmgMult,hitcount)


                --holed body
		QueryRequire("physical") --dynamic optional
                --dynamic small optional
		local mi = VecAdd(hitPos,Vec(-damage,-damage,-damage))
		local ma = VecAdd(hitPos,Vec(damage,damage,damage))
		local bodies = QueryAabbBodies(mi, ma)	
		local hitbody = GetShapeBody(hitShape)
		--DebugPrint(isItemInTable(hitbody,bodies))
		if not isItemInTable(hitbody, bodies) then 
			table.insert(bodies, GetShapeBody(hitShape))
		end
		for i,body in ipairs(bodies) do
			--compute body center and dist
			local bmi,bma = GetBodyBounds(body)
			local bc = VecLerp(bmi,bma,0.5) --get center
			local dir = VecSub(bc, hitPos)
			local dist = VecLength(dir)
			dir = VecScale(dir,1/dist)
			local mass = GetBodyMass(body)--get mass
			
			--dir forward
			dir = VecLerp(dir,projectile.predictedBulletVelocity,0.8)
			dir = VecNormalize(dir)
			local force = math.min(5000/2.2522,12*mass)
			if HasTag(shape, "shapeHealth") then
				force = math.min(1000/2.2522,12*mass)
			end
                        if explosive then
				force = 0
			end
			local add = VecScale(dir,force)
			
			ApplyBodyImpulse(body,hitPos,add)
			end
	else
		DrawLine(projectile.pos, point2)
	end

	projectile.pos = point2
end

function ShellOperations(shell)
	shell.predictedBulletVelocity = VecAdd(shell.predictedBulletVelocity, (VecScale(shellGravity, GetTimeStep())))
	local point2 = VecAdd(shell.pos, VecScale(shell.predictedBulletVelocity, GetTimeStep()))
	local dir = VecNormalize(VecSub(point2, shell.pos))
	local distance = VecLength(VecSub(point2, shell.pos))
	QueryRejectBody(shell.voxBody)
	QueryRequire("physical")
	local hit, dist, normal = QueryRaycast(shell.pos, dir, distance)
	if hit then
		if shell.bounces == 5 then
			shell.active = false
		elseif shell.bounces == 0 then
			shell.targetRot = QuatLookAt(shell.pos, point2)
			PlaySound(shellsound, shell.pos, 0.4, false)
			local dot = VecDot(normal, shell.predictedBulletVelocity)
			shell.predictedBulletVelocity = VecSub(shell.predictedBulletVelocity, VecScale(normal, dot*1.6))
			shell.bounces = shell.bounces + 1
		else
			shell.targetRot = QuatLookAt(point2, shell.pos)
			local dot = VecDot(normal, shell.predictedBulletVelocity)
			shell.predictedBulletVelocity = VecSub(shell.predictedBulletVelocity, VecScale(normal, dot*1.6))
			shell.bounces = shell.bounces + 1
		end
	end
	shell.rot = QuatSlerp(shell.rot, shell.targetRot, 0.3)
	SetBodyTransform(shell.voxBody, Transform(shell.pos, shell.rot))
	shell.pos = point2
end

function destructiblerobots(pos,damage,hitcounter) --thanks dima
    local hit, point, n, shape = QueryClosestPoint(pos, 0.3)
	if HasTag(shape, "shapeHealth") then
		if tonumber(GetTagValue(shape,"shapeHealth"))-damage < 0 and not HasTag(shape, "body") then 
			RemoveTag(shape,"unbreakable")
		end
	end

    if hit then 
        SetFloat('level.destructible-bot.hitCounter',hitcounter)
        SetInt('level.destructible-bot.hitShape',shape)
        SetString('level.destructible-bot.weapon',"weapon-name")
        SetFloat('level.destructible-bot.damage',damage)
    end 
end

function push() 
	local t = GetCameraTransform()
	local pos = t.pos
	local pos2 = TransformToParentPoint(t, Vec(0, 0, 0))
	local dir1 = TransformToParentVec(t, Vec(0, 0, -1))
	local hit, dist, normal, shape = QueryRaycast(pos, dir1, 50)
	blastpoint = VecAdd(pos, VecScale(dir1, dist))

	local mi = VecAdd(blastpoint, Vec(-strength*.03, -strength*.03, -strength*.03))
	local ma = VecAdd(blastpoint, Vec(strength*.03, strength*.03, strength*.03))
	QueryRequire("physical dynamic")
        QueryRejectBody("physical dynamic small")
	local bodies = QueryAabbBodies(mi, ma)
							
	for i=1,#bodies do
		local b = bodies[i]
		local bmi, bma = GetBodyBounds(b)
		local bc = VecLerp(bmi, bma, 0.5)
		local dir = VecSub(bc, blastpoint)
		local dist1 = VecLength(dir)
		dir = VecScale(dir, 1.0/dist1)
		local mass = GetBodyMass(b)
		if mass < maxMass then
			local massScale = 1 - math.min(mass/maxMass, 1.0)
			local distScale = 1 - math.min(dist/maxDist, 1.0)
			local add = VecScale(dir1, (strength/2) * (massScale/2) * (distScale*2))
			local vel = VecScale(dir1, .5)
			vel = VecAdd(vel, add)
			SetBodyVelocity(b, vel)
		end
	end
end

function tick(dt)
	for key, shell in ipairs(minigunprojectileHandler.shells) do
		if shell.active then
			ProjectileOperations(shell)
		end
	end

	for key, shell in ipairs(minigunshellHandler.shells) do
		if shell.aliveTimer > 0 then
			shell.aliveTimer = shell.aliveTimer - dt
			if shell.aliveTimer < 0.1 then
				shell.active = false
			end
		end
		if shell.active then
			ShellOperations(shell)
		else
			Delete(shell.voxBody)
		end
	end

	if GetString("game.player.tool") == "Enforcer" and GetPlayerVehicle() == 0 then
		SetBool("hud.aimdot", false)
		if InputDown("lmb") then
			if state == STATE_READY or state == STATE_UNWINDING then
				state = STATE_WINDING
				PlaySound(windupsound, GetPlayerTransform().pos, 0.5, false)
			end
			angVel = math.min(500, angVel + dt*1000)
		else
			angVel = math.max(0, angVel - dt*250)
		end

		if InputReleased("lmb") then
			if state == STATE_FIRING or state == STATE_WINDING then
				state = STATE_UNWINDING
				spreadTimer = 0
				if simulBullets > 3 then
                                smokeTimer = 1
				end
				if simulBullets > 100 then
					smokeTimer = 2
				end
				simulBullets = 0
				PlaySound(winddownsound, GetPlayerTransform().pos, 0.5, false)
			end
		end

		if state == STATE_WINDING then
			startDelay = startDelay - dt
			if startDelay <= 0 then
				state = STATE_FIRING
			end
		end

		if state == STATE_UNWINDING then
			startDelay = startDelay + dt
			if startDelay > 0.6 then
				state = STATE_READY
				startDelay = 0.6
			end
		end

		if state == STATE_FIRING then
			Shoot()
			PlayLoop(gunsound, GetPlayerTransform().pos, 0.8, false)
		end

		local b = GetToolBody()
		if b ~= 0 then
			local heightOffset = InputDown("ctrl") and 0.3 or 0.2
			local offset = Transform(Vec(0.1, heightOffset, 0))
			SetToolTransform(offset)
			toolTrans = GetBodyTransform(b)
			toolPos = TransformToParentPoint(toolTrans, Vec(0.20, -0.75, -3.5))

			if recoilTimer > 0 then
                if InputDown("rmb") then
                shakex = math.random(-8, 8)/350
                shakey = math.random(-8, 8)/350
                shakez = math.random(-8, 8)/350
                else
                shakex = math.random(-5, 5)/350
                shakey = math.random(-5, 5)/350
                shakez = math.random(-5, 5)/350
                end
				
				t.pos = Vec(0.1+shakex, heightOffset+shakey, 0.01+shakez)
				t.rot = QuatEuler(3, 0, 0)
				SetToolTransform(t)
                                if math.random(1,3) == 3 then
				PointLight(toolPos, 1, 0.75, 0.25, 1)
                                end
                                
				recoilTimer = recoilTimer - dt
			end

			angle = angle + angVel * dt * 5
			local voxSize = 0.1
			local attach = Transform(Vec(2.8*voxSize, -7.3*voxSize, 0))
			if body ~= b then
				body = b
				local shapes = GetBodyShapes(b)
				barrel = shapes[1]
				barrelTransform = TransformToLocalTransform(attach, GetShapeLocalTransform(barrel))	
			end

			attach.rot = QuatEuler(0, 0, -angle)
			t = TransformToParentTransform(attach, barrelTransform)
			SetShapeLocalTransform(barrel, t)
		end
	else
		state = STATE_READY
		startDelay = 0.6
	end
	
	if shootTimer > 0 then
		shootTimer = shootTimer - dt
	end	

	if smokeTimer >= 0 then
		local smokepos = TransformToParentPoint(GetCameraTransform(), Vec(10.55, -0.65, -1))
		smokeTimer = smokeTimer - dt
	end

	if GetString("game.player.tool") == "Enforcer" and GetPlayerVehicle() == 0 then
	if not InputDown("lmb") and smokeTimer > 0 then
for i=0, 1 do    
        ParticleReset()
        ParticleType("smoke")
	ParticleRadius(0.08, 0.3)
	ParticleAlpha(1*0.4, 0)	
	ParticleGravity(1)
        ParticleEmissive(0)
	SpawnParticle(TransformToParentPoint(GetBodyTransform(GetToolBody()), Vec(0.22, -0.6, -3.5)), VecScale(Vec(0, 15, 0), 0.05), 3, 0.25)	
end
end
end
end

function ParticleEmmit(hitPos)
        ParticleReset()        
	ParticleTile(4)
	ParticleStretch(3)
	ParticleEmissive(10, 0)
	ParticleRadius(0.009)
	ParticleGravity(-10)
	ParticleColor(1, 0.7, 0.3)
	ParticleSticky(0.05)
	for i=1, 10 do
		SpawnParticle(hitPos, Vec(rnd(-2, 2), rnd(-2,2), rnd(-2,2)), rnd(1,4))
	end
end

function rnd(mi, ma)
	return mi + (ma-mi)*(math.random(0, 1000000)/1000000.0)
end

function draw()
	if GetString("game.player.tool") == "Enforcer" and GetPlayerVehicle() == 0 then
		UiPush()
			UiTranslate(UiCenter(), UiHeight()-60)
			UiAlign("center middle")
			UiColor(1, 1, 1)
			UiFont("bold.ttf", 32)
			UiTextOutline(0,0,0,1,0.1)
			UiText(bulletsFired)
		UiPop()
	end

	if GetString("game.player.tool") == "Enforcer" and GetPlayerVehicle() == 0 then
	UiPush()
    	UiAlign("center middle")
    	UiTranslate(UiCenter(), UiMiddle());
    	UiImage("circle/crosshair-dot.png")
  	UiPop()
end
	if GetString("game.player.tool") == "Enforcer" and GetPlayerVehicle() == 0 then
UiPop()
UiPush()
			UiAlign("center")
			UiTextOutline(0, 0, 0, 1, 0.1)
			UiTranslate(220, 1020)
			UiFont("bold.ttf", 20)	
			UiColor(.75, .75, .75)
			UiText("LMB - SHOOT, LMB+RMB - EXPLOSIVE ROUNDS")
end
end