#include "script/common.lua"
--handler for ai functions such as sight, hearing and targeting

--ai settings 
sightRaycastStep = {step = 0.17, t = 0}

--init functions
function initAIVar()
	patrolTime = tonumber(GetTagValue(aiSettings,"patroltime")) or 10 --max time around a target position before moving to another, determined in editor
	timeSinceLastSeen = 0 --how long since a player was seen
	timeSinceChoosePatrol = 0
	targetPos = GetPlayerPos()
	targetPosOffset = GetPlayerPos()
	lookPos = GetPlayerPos()

	playerSeen = false
	playerTracked = false --fully aware and alert
	playerObscureDivisor = 1 --if player is partly obscured divide detection gain by this
	playerSeeMeter = 0 --1 for alerted, 2 for max for example
	maxRubberBand = tonumber(GetTagValue(aiSettings,"rb")) or 145 --max range the entity will stray from player when choosing random patrol point, taken from editor
	maxHearDist = tonumber(GetTagValue(aiSettings,"hearingdist")) or 157 --maximum hearing range
	maxSeeDist = tonumber(GetTagValue(aiSettings,"seedist")) or 77 --maximum sight range for sight raycast for optimization, currently dummy
	distractionTime = tonumber(GetTagValue(aiSettings,"distractiontime")) or 3
	timeSinceDistraction = 0 --how long since distraction
	searchLightRadius = tonumber(GetTagValue(aiSettings,"lightradius")) or 23
end
function initAIMoreVar()--more specific ai settings for aggressive behaviour
	maxAlertTime = tonumber(GetTagValue(aiSettings2, "maxalert")) or 4
	alertDetectMult = tonumber(GetTagValue(aiSettings2,"alertdetectrate")) or 2.5 --multiplier over dt for detection meter
	alertSeenTimer = 0 --how long it has seen you while alert
	alertFlagged = false
	retainAlert = HasTag(aiSettings2,"stayalert") or false --if true it will still prepare to attack even if you get out of sight
	attackCounter = 0
	interferencePinger = true --whether or not sound gets picked up by sound functions
	wasHurt = false --did something actually break a part of it?
	wasHurtTimer = 0
	wasHurtReactionCD = tonumber(GetTagValue(aiSettings2,"hurtreactioninterval")) or 6 --cooldown for reaction to damage
	wasHurtReactionTimer = 0
	painTolerance = tonumber(GetTagValue(aiSettings2, "paintol")) or 0.92 --0 to 1 probability
	escapeTrigger = false
	escapeHeight = tonumber(GetTagValue(aiSettings2, "escapeheight")) or 75
	weaponArmed = false
	hearAttack = HasTag(aiSettings2,"hearatk") or false --if true, attack on hearSound
	hearAttackChance = tonumber(GetTagValue(aiSettings2, "hearatkodd")) or 0.7
	blindFireTimer = 0
	subWeaponConsiderThreshold = tonumber(GetTagValue(aiSettings2, "subwepconsider")) or 0.5
end

function initBasiliscoSounds()  --basic basilisco sounds
	basiliscoDetect1 = LoadSound("snd/basilisco_detect.ogg")
	basiliscoDetect2 = LoadSound("snd/basilisco_detect_2.ogg")
	basiliscoDetects = {basiliscoDetect1,basiliscoDetect2}
	
	basiliscoHeard = LoadSound("chopper-sound.ogg")
	BasiliscoEngineLoop = LoadLoop("snd/basilisco_idle.ogg")
	BasiliscoJetLoop = LoadLoop("snd/tank2-idle.ogg")
	basiliscoIdle = LoadLoop("snd/basilisco_hum.ogg")
	basiliscoAccelerate = LoadSound("snd/jet_throttleup.ogg")
	flybySound = LoadSound("snd/flyby.ogg")
	basiliscoInterference = LoadLoop("transmission.ogg")
	
	leaveSound = LoadSound("snd/basilisco_leave.ogg")
	basiliscoHurtSound1 = LoadSound("snd/basilisco_hurt1.ogg")
	basiliscoHurtSound2 = LoadSound("snd/basilisco_hurt2.ogg")
	basiliscoHurtSound3 = LoadSound("snd/basilisco_hurt3.ogg")
	basiliscoHurtSound4 = LoadSound("snd/basilisco_hurt4.ogg")
	basiliscoHurts = { basiliscoHurtSound1, basiliscoHurtSound2, basiliscoHurtSound3, basiliscoHurtSound4 }
	basiliscoDeathSound1 = LoadSound("snd/basilisco_death1.ogg")
	basiliscoDeathSound2 = LoadSound("snd/basilisco_death2.ogg")
	basiliscoDeathSound3 = LoadSound("snd/basilisco_death3.ogg")
	basiliscoDeaths = { basiliscoDeathSound1, basiliscoDeathSound2, basiliscoDeathSound3 }
end

function getAllAlicanto()
	--list of all body pieces in map for rejection
	heads = FindBodies("alicantohead",true) --every "head" in the level
	lights = FindBodies("searchlight",true)
	bodies = FindBodies("alicantotail",true) --every "tail" in level
	wingsl = FindBodies("alicantowingl", true)
	wingsr = FindBodies("alicantowingr", true)
	
	railguns = FindBodies("basiliscorailgun", true)
	railgunshrouds = FindBodies("railgunshroud", true)
	railgunbarrels = FindBodies("railgunbarrel",true)
	enginesl = FindBodies("basiliscoenginel",true)
	enginesr = FindBodies("basiliscoenginer",true)
	launchers = FindBodies("basiliscolauncher",true)
	
end

function getAllChoppers()
	choppers = FindBodies("chopper",true)
	tailrotors = FindBodies("tailrotor",true)
	mainrotors = FindBodies("mainrotor",true)
	chopperlights = FindBodies("light",true)
end

--ai functions
function choosePatrolTarget()
	local dir = VecNormalize(Vec(rndFloat(-1,1), 0, rndFloat(-1,1)))
	math.randomseed(math.random(66666)) --idk does this make it even more random?
	local r = rndFloat(15, maxRubberBand) --rubberbanding to the player
	--default is (15,30)
	targetPos = VecAdd(GetPlayerPos(), VecScale(dir, r))--thing should fly here
	if FindLocation("reconflaretags") ~= 0 then --extra eye
		--if HasTag(reconFlareSettings,"active") then considerReconFlare(0) end
	end
end

function canSeePlayer(pos)
	pos = pos or GetPlayerCameraTransform().pos
	local playerPos = pos
	--direction to player
	local lightTransform = TransformToParentTransform(headTransform,searchLightLocalTransform)
	local dir = VecSub (playerPos, lightTransform.pos)
	local dist = VecLength(dir)
	dir = VecNormalize(dir)
	
	rejectSelf()
	QueryRejectVehicle(GetPlayerVehicle())
	return not QueryRaycast(lightTransform.pos, dir, dist, 0, true)

end

function playerSpotted() --is player visible and lit?
	if IsPointAffectedByLight(lightSpot,GetPlayerPos()) and canSeePlayer() then return true end
	return false
end

function hearSound() --did it hear?
	if not hearSens then hearSens = 0.5 end-- hearing sensitivity
	local volume, pos = GetLastSound();
		if volume > hearSens then
			local v = getSoundVolume(pos) * volume
			if v > hearSens then
				targetPos = pos --for distractions
				--targetPos[2] = GetPlayerCameraTransform().pos[2]
				timeSinceChoosePatrol = 0
				timeSinceDistraction = 0
				return true
			end
		end
	return false
end

function basiliscoDetectSoundRandomizer() --plays different sound for each detect
	PlaySound(basiliscoDetects[math.random(#basiliscoDetects)])
end

function basiliscoHurtSoundRandomizer()
	return basiliscoHurts[math.random(#basiliscoHurts)]
end

function basiliscoDeathSoundRandomizer()
	return basiliscoDeaths[math.random(#basiliscoDeaths)]
end

function alertLight(light)
	SetLightColor(light, alertlightR, alertlightG, alertlightB)
end
function idleLight(light)
	SetLightColor(light, idlelightR, idlelightG, idlelightB)
end
function turqLight(light)
	SetLightColor(light, 0.1, 1, 0.7)
end

--tick reliant functions
function searchLightLook(dt)
	local aimPos = VecCopy(lookPos)
	local radius = clamp(timeSinceChoosePatrol*2,0,searchLightRadius)
	if not aimAngle then aimAngle = 0; aimAngleY= 0 end --sorry for needing two aimangles cause i don't want leaks later
	aimAngle = aimAngle%360 + dt
	aimAngleY = aimAngleY%360 + dt*1.7 --one day I can make the look pattern more easily modifiable element
	local x = math.cos(aimAngle) * radius
	local z = math.sin(aimAngleY) * radius
	aimPos = VecAdd(aimPos, Vec(x, 0, z))
	
	local lightTransform = TransformToParentTransform(headTransform, searchLightLocalTransform)
	searchLightTargetRot = QuatLookAt(lightTransform.pos, aimPos)
	lightTransform.rot = searchLightRot
	SetBodyTransform(searchLight, lightTransform)
end

function hoverAvoidance() --pathfinding basically but only to detect other flyers
	rejectSelf() --prepare for raycast
	--QueryRequire("dynamic")
	local hit, p, n, s = QueryClosestPoint(headTargetPos, 15)
	if hit then
		local dir = VecSub(headTransform.pos, p)
		local dist = math.max(1,VecLength(dir))
		dir[2] = 0
		dir = VecNormalize(dir)
		--speed = clamp(17/dist,5,17)
		SetValue("speed",18/dist,"linear",18/dist/0.8)
		tailTable[1].targetPos = VecAdd(tailTable[1].targetPos, VecScale(dir,speed*GetTimeStep())) --remember the neck is the one controlling movement
	end
end


function tickEscape(dt)
	flightMode = "escape"
	activeWeapon = "none"
	retractRailBarrel()
	retractLauncher()
	timeSinceChoosePatrol = 0
	targetPos = escapePoint
	targetPos[2] = toHeight
	if VecLength(VecSub(targetPos,headTransform.pos)) < 40 then active = false end --deactivate monster to free up computation
		patrolPointMove(dt)
		--forward motion
		local forward = TransformToParentVec(headTransform,Vec(0,0,-(speed)*dt))--using vectors
		headTargetPos = VecAdd(headTargetPos, forward)--using vectors

		patrolDir()
		forwardFlightTailControl(dt) --control tail movement
		--wing animations
		--oh god it's total tag hell in the editor
		forwardWingMode()
		SetBodyTransform(head,headTransform)
	turnRate = patrolTurnRate
	distance = 1.9 --separation of tail units
	
	if VecLength(VecSub(targetPos,headTransform.pos)) < 222 then toHeight = -16 else height = escapeHeight end -- dive under close to escape point
	--below will force a height outside of the actual flight function
	local lookLast = VecAdd(headTargetPos,VecNormalize(VecSub(targetPos,headTargetPos)))
	lookLast[2] = toHeight
	headTargetRot = QuatLookAt(headTargetPos, lookLast)
	SetBodyTransform(head,headTransform)
end

function tickWingHealth(dt)
	local enginehealth = getWingThrusters()
	if enginehealth < prevThrusterCount then
		wasHurt = true
		wasHurtTimer = 5
		prevThrusterCount = enginehealth
	end
	
	if enginehealth/firstThrusterCount <= 0 and not enginesDown then
		if considerDying(0.3) then 
			dieInstead() 
			SetString("hud.notification","Great, you killed it!")	
		else
			SetString("hud.notification","It's still not dead!")
		end
		enginesDown = true
	end
end

function tickHurtOperation(dt)
	if wasHurt then
		if math.random() > painTolerance then
			if flightMode == "patrol" and wasHurtReactionTimer <= 0 then
				PlaySound(basiliscoHurtSoundRandomizer(),headTransform.pos,10,false)
				choosePatrolTarget()
				speed = maxSpeed*0.8
				wasHurtReactionTimer = wasHurtReactionCD
				if FindLocation("reconflaretags") ~= 0 then
					if HasTag(reconFlareSettings,"active") then considerReconFlare(reconFlareMaxChance) end
				end
			elseif wasHurtReactionTimer <=0 and flightMode == "alert" then --dodge
				basiliscoHoverDodge(true)
				PlaySound(basiliscoHurtSound3,headTransform.pos,10,false)
				wasHurtReactionTimer = wasHurtReactionCD/2
				if FindLocation("reconflaretags") ~= 0 then
					if HasTag(reconFlareSettings,"active") then considerReconFlare(reconFlareMaxChance) end
				end
			elseif wasHurtReactionTimer <=0 then
				PlaySound(basiliscoHurtSound3,headTransform.pos,10,false)
				wasHurtReactionTimer = wasHurtReactionCD
				if FindLocation("reconflaretags") ~= 0 then
					if HasTag(reconFlareSettings,"active") then considerReconFlare(reconFlareMaxChance) end
				end
			end
		end
		wasHurt = false
	end
	wasHurtReactionTimer = math.max(0,wasHurtReactionTimer-dt)
	wasHurtTimer = math.max(0,wasHurtTimer - GetTimeStep())
end

function escapeCheck()
	if prevHealth/fullHealth < 0.21 and not escapeTrigger then --hp threshold for retreat
		escapeTrigger = true
		local deathchance = 0.04 --0.04
		if enginesDown then deathchance = 0.86 end
		if considerDying(deathchance) then 
			dieInstead()
		else
			PlaySound(leaveSound, headTransform.pos,21, false)
		end
		local dir = VecNormalize(Vec(rndFloat(-1,1), 0, rndFloat(-1,1))) --set a direction to flee in
		escapePoint = VecScale(dir,2222) --until it is long out of sight, surely there won't be greater than 1 km2 maps
	end --run to live another day
end

function considerDying(chance) --nothing personal kId
	if math.random() < chance then
		return true
	end
	return false
end

function dieInstead() --rest in peperoni
	if flightMode == "patrol" then speed = math.min(speed, 17) end
	--kill head
	local headshapes = GetBodyShapes(head)
	for h, headshape in ipairs(headshapes) do
		RemoveTag(headshape,"unbreakable") -- break the corpse to your heart content
		SetShapeEmissiveScale(headshape,0.1)
	end
	SetBodyDynamic(head,true) --fall to the ground
	SetBodyVelocity(head,TransformToParentVec(headTransform,Vec(0,0,-speed)))
	
	--kill body
	for t, tail in ipairs(tailTable) do
		local tailshapes = GetBodyShapes(tail.body)
		for t, tailshape in ipairs(tailshapes) do
			RemoveTag(tailshape,"unbreakable")
			SetTag(tailshape,"collide",true)
			SetShapeEmissiveScale(tailshape,0.1) --shut down all lights
		end
		SetBodyDynamic(tail.body,true)
		SetBodyVelocity(tail.body,TransformToParentVec(tail.transform,Vec(0,0,-speed)))
	end
	
	--kill wings
	for w, wing in ipairs(leftWingTable) do
		wingshapes = GetBodyShapes(wing.body)
		for w,wingshape in ipairs(wingshapes) do
			RemoveTag(wingshape,"unbreakable")
		end
		for w, wingshape in ipairs(wingshapes) do --do it again
			if not HasTag(wingshape,"wingroot") then Explosion(GetShapeWorldTransform(wingshape).pos,rndFloat(1,3.3)) end
		end
		SetBodyDynamic(wing.body,true)
		SetBodyVelocity(wing.body,TransformToParentVec(tailTable[wing.tailNum].transform,Vec(0,0,-speed)))
		--Explosion(GetBodyTransform(wing.body).pos, rndFloat(2,4))
	end
	for w, wing in ipairs(rightWingTable) do
		wingshapes = GetBodyShapes(wing.body)
		for w,wingshape in ipairs(wingshapes) do
			RemoveTag(wingshape,"unbreakable")
		end
		for w, wingshape in ipairs(wingshapes) do --do it again
			if not HasTag(wingshape,"wingroot") then Explosion(GetShapeWorldTransform(wingshape).pos,rndFloat(1,3.3)) end
		end
		SetBodyDynamic(wing.body,true)
		SetBodyVelocity(wing.body,TransformToParentVec(tailTable[wing.tailNum].transform,Vec(0,0,-speed)))
		--Explosion(GetBodyTransform(wing.body).pos, rndFloat(2,4))
	end
	--misc bodies tagged misc
	local miscbodies = FindBodies("misc")
	for m,misc in ipairs(miscbodies) do
		RemoveTag(misc,"unbreakable")
		for s, miscshape in ipairs(GetBodyShapes(misc)) do
			RemoveTag(miscshape,"unbreakable")
		end
		SetBodyDynamic(misc, true)
		SetBodyVelocity(misc,TransformToParentVec(GetBodyTransform(misc),Vec(0,0,-speed)))
	end	
	
	--kill engines
	SetBodyDynamic(engineL,true)
	RemoveTag(engineL,"unbreakable")
	SetBodyDynamic(engineR,true)
	RemoveTag(engineR,"unbreakable")
	SetBodyVelocity(engineL,TransformToParentVec(tailTable[engineLtailNum].transform,Vec(0,0,-speed)))
	SetBodyVelocity(engineR,TransformToParentVec(tailTable[engineRtailNum].transform,Vec(0,0,-speed)))
	Explosion(GetBodyTransform(engineL).pos,4)
	Explosion(GetBodyTransform(engineR).pos,4)
	
	--remove flyerfp tag
	local flyerfps = FindBodies("flyerfp")
	for f,b in ipairs(flyerfps) do
		RemoveTag(b,"flyerfp")
	end
	
	--deactivate
	SetLightEnabled(lightSpot,false)
	PlaySound(basiliscoDeathSoundRandomizer(),headTransform.pos,60)
	active = false
end

--update zone
function searchLightUpdate(dt)
	searchLightRot = QuatSlerp(searchLightRot, searchLightTargetRot, 0.049)
end