#include "script/common.lua"
#include "alicantoscripts/bodyscripts.lua"
#include "alicantoscripts/flightscripts.lua"
#include "alicantoscripts/aiscripts.lua"
#include "alicantoscripts/weaponscripts.lua"
#include "alicantoscripts/utilities.lua"
--head unit and 11 tails
--chilote
pSpeed = GetFloatParam("speed", 10)
--originally 7

--this is the main script for the entity instance, calling the included scripts above for functions

function init()
	--sound
	initBasiliscoSounds()

	--setting locations
	aiSettings = FindLocation("aitags") --location that stores relevant tags to ai function
	aiSettings2 = FindLocation("aitags2")
	flightSettings = FindLocation("flighttags") --stores relevant tags for flight
	--weaponSettings = FindLocation("weapontags") --stores tags for weapon functions
	particleSettings = FindLocation("particletags") --stores tags for particle stuff like color
	
	head = FindBody("alicantohead")
	headTransform = GetBodyTransform(head)
	headVel = Vec()
	headTargetPos = VecAdd(headTransform.pos,Vec(0,0,0))
	headTargetRot = QuatEuler()

	--11 full units, banners optional
	--due to how FindBodies works, pieces must be placed in descending order in editor
	tailTable = FindBodies("alicantotail",false) --tail units in these tables
	--one of the units absolutely requires a "clavicle" tag to make animations work

	--wing placements in the tags of the wing in editor
	--ABSOLUTELY REQUIRED EDITOR TAGS FOR EVERY WING, NO EXCEPTIONS
	--tailNum corresponding to tail unit number
	--sweep for local xyz angle to mount wings at
	--hoverpivot for hovering posture wing tilt
	
	leftWingTable = FindBodies("alicantowingl",false) --left wings
	rightWingTable = FindBodies("alicantowingr",false) --right wings
	
	tailTable = initBody(tailTable)
	leftWingTable = initLeftWings(leftWingTable)
	rightWingTable = initRightWings(rightWingTable) --makes full tables for later use from the handle lists
	
	--large engine pods
	engineL = FindBody("basiliscoenginel")
	engineLtailNum = tonumber(GetTagValue(engineL,"tailNum"))
	engineLLocalTransform = TransformToLocalTransform(tailTable[engineLtailNum].transform,GetBodyTransform(engineL))
	engineLrot = commasplit(GetTagValue(engineL,"rot"))
	for key,value in ipairs(engineLrot) do --converts the value which are still strings to numbers
			value = tonumber(value)
	end
	
	engineR = FindBody("basiliscoenginer")
	engineRtailNum = tonumber(GetTagValue(engineR,"tailNum"))
	engineRLocalTransform = TransformToLocalTransform(tailTable[engineLtailNum].transform,GetBodyTransform(engineR))
	engineRrot = commasplit(GetTagValue(engineR,"rot"))
	for key,value in ipairs(engineRrot) do --converts the value which are still strings to numbers
		value = tonumber(value)
	end
	
	--back launcher?
	launcher = FindBody("basiliscolauncher") --the backmounted launcher
	launcherUnitTransform = tailTable[tonumber(GetTagValue(launcher,"tailNum"))].transform --transform of tail unit launcher is mounted to
	launcherLocalTransform = TransformToLocalTransform(launcherUnitTransform,GetBodyTransform(launcher))
	
	active = true --whether or not thing should be active
	
	initFlightVar()
	
	initAIVar() --initializes required variables for functions in other scripts
	
	initAIMoreVar() --initializes extra required variables
	
	flightMode = "patrol"
	
	initParticleColors() --particles may be changed in editor
	initSearchlightColors() --colors for searchlight, also set in the same location as particle colors

	--searchlight init goes here
	searchLight = FindBody("searchlight")
	lightSpot = FindLight("spotlight")
	lightSource = FindLight("lightindirect")
	searchLightLocalTransform = TransformToLocalTransform(headTransform,GetBodyTransform(searchLight))
	searchLightRot = QuatEuler()
	searchLightTargetRot = QuatEuler()
	
	--init weapons
	specialWeapons = {} --for heavy attacks, only one can be used at a time, the values are booleans to activate them

	equipWeapons()
	
	--Init health check
	fullHealth = getStateLights()
	prevHealth = fullHealth
	firstThrusterCount = getWingThrusters()
	prevThrusterCount = firstThrusterCount
	
	--init world data
	getAllAlicanto()
	getAllChoppers()
end

--it's more flexible to set up the movement modules in functions, off in another script

function tick(dt)
	--projectile operations
	tickRailGunOperation()
	tickAutoCannonOperation()
	tickMultiMissileOperation()
	tickPulseLaserOperation()
	tickMortarOperation()
	tickPoisonCloudOperation(dt)
	tickHeavyShotOperation()
	
	--misc operation 
	tickHangLight(dt)
	
	if not active then return end
	--escape clause
	if escapeTrigger then
		tickEscape(dt)
	end

	--light resets each tick
	idleLight(lightSource)
	idleLight(lightSpot)
	SetLightIntensity(lightSpot, 3888)
	SetLightIntensity(lightSource, 4)
	if flightMode == "patrol" then
		--patrol flight
		turnRate = patrolTurnRate
		distance = 1.9 --separation of tail units
		height = patrolheight
--setup order: position to fly to, speed control, forward or hover, height control, look direction, then tail control, wing control, and then apply head transform
		patrolPointMove(dt)
		--forward motion
		local forward = TransformToParentVec(headTransform,Vec(0,0,-(speed)*dt))--using vectors
		headTargetPos = VecAdd(headTargetPos, forward)--using vectors
		--hover vs forward
		patrolDir()
		forwardFlightTailControl(dt) --control tail movement
		--wing animations
		--oh god it's total tag hell in the editor
		forwardWingMode()
		SetBodyTransform(head,headTransform)
		
		retractLauncher()
		retractRailBarrel()
		
		timeSinceChoosePatrol = timeSinceChoosePatrol + dt
		timeSinceDistraction = timeSinceDistraction + dt
		timeSinceLastSeen = timeSinceLastSeen + dt
		if timeSinceChoosePatrol > patrolTime then
			timeSinceChoosePatrol = 0 
			choosePatrolTarget()
			if hasReconFlare then considerReconFlare(reconFlareChance) end
		end
		if timeSinceDistraction > distractionTime then
			if hearSound() then 
				PlaySound(basiliscoHeard) 
				if not considerStrafing() then 
					if hearAttack then considerNLOSOnHear() end --strafe or engage with subweapons
				end
				if hasReconFlare then considerReconFlare(reconFlareHearChance) end
			end
		end
		if playerSeen then
			timeSinceLastSeen = 0
			timeSinceChoosePatrol = 0 
			timeSinceDistraction = 0
			lookPos = GetPlayerPos()
			playerSeeMeter = math.min(2,playerSeeMeter + dt/playerObscureDivisor)
		end
		if playerSeeMeter >= subWeaponConsiderThreshold then --aim the subweapons at the right spot
			targetPos = GetPlayerPos()
		end
		
		if playerTracked then
			timeSinceDistraction = 0
			targetPos = GetPlayerPos()
			if getVecToPlayer() < math.random(hoverMinDist,hoverMaxDist) and canSeePlayer() then
				flightMode = "alert"
				basiliscoDetectSoundRandomizer()
			end
		end		
	elseif flightMode == "alert" then --hover
		turnRate = hoverTurnRate -- not speed scaled, /4 for tails
		distance = 6
		height = hoverheight
		
		alertMove(dt)
		hoverTailControl(dt)
		hoverWingMode()
		hoverDir(dt)
		hoverHeightControl(dt)
		headTransform = TransformToParentTransform(tailTable[1].transform, Transform(Vec(0,0,-5),QuatEuler()))
		headTransform.rot = headLookRot
		SetBodyTransform (tailTable[1].body,tailTable[1].transform)
		SetBodyTransform(head,headTransform)
		
		extendLauncher()
		extendRailBarrel()
		
		timeSinceLastSeen = timeSinceLastSeen + dt
		timeSinceChoosePatrol = timeSinceChoosePatrol + dt
		
		PlayLoop (basiliscoIdle,headTransform.pos,3,false)
		if not weaponArmed then PlayLoop(basiliscoInterference,targetPos,15,interferencePinger) end --interference sound when not attacking
		interferencePinger = false
		if not retainAlert then alertFlagged = false end --no retainAlert: aggro buildup stops if player not tracked
		if playerSeen then
			timeSinceLastSeen = 0
			timeSinceChoosePatrol = 0
			timeSinceDistraction = 0
			lookPos = GetPlayerPos()
			playerSeeMeter = math.min(2,playerSeeMeter + dt*alertDetectMult/playerObscureDivisor)
		end
		if playerSeeMeter >= subWeaponConsiderThreshold then --aim the subweapons at the right spot
			targetPos = GetPlayerPos()
		end
		if playerTracked then
			timeSinceLastSeen = 0
			timeSinceChoosePatrol = 0
			timeSinceDistraction = 0
			interferencePinger = true
			targetPos = GetPlayerPos()
			alertLight(lightSource)
			alertLight(lightSpot)
			if not playerDead() and not weaponArmed then alertFlagged = true end
		end
		if alertFlagged then --taking a page from the journey guardians to have it attack regardless of whether you hid in time
			timeSinceChoosePatrol = 0
			alertSeenTimer = math.min(alertSeenTimer + dt,maxAlertTime)
			local flashratemod = math.min(1,alertSeenTimer/maxAlertTime)
			--targetPos = GetPlayerPos()
			alertLight(lightSource)
			alertLight(lightSpot)
			SetLightIntensity(lightSpot,(math.sin(alertSeenTimer*19*flashratemod)/2+0.5)*3888) --rapidly flash as it prepares to attack
			SetLightIntensity(lightSource,(math.sin(alertSeenTimer*19*flashratemod)/2+0.5)*4) --rapidly flash as it prepares to attack
			if playerDead() then --cancel if player dies early
				alertFlagged = false
				alertSeenTimer = 0
			end
		end
		if alertSeenTimer >= maxAlertTime then --time to attack
			if math.random() < 0.6 then --0.6
				while not weaponArmed do --loops until a weapon(s) is (are) chosen
					if hasLaser and math.random()<0.3 then laserTriggered = true; weaponArmed = true end
					if hasRailGun and math.random()<0.3 then railGunArmed = true; weaponArmed = true end
					if not hasLaser and not hasRailGun then break end --prevent a while trap
				end
			end
			if not weaponArmed then
				if #specialWeapons > 0 then --don't run an empty table
					_G[specialWeapons[math.random(#specialWeapons)]] = true --using _G for this feels too finicky even if it works
				end
			end
			attackCounter = attackCounter + 1
			alertSeenTimer = 0
			alertFlagged = false
		end
		
		if timeSinceChoosePatrol > distractionTime*1.5 then
			if hearSound() then 
				if hasReconFlare then considerReconFlare(reconFlareHearChance) end
				PlaySound(basiliscoHeard) 
				timeSinceChoosePatrol = 0
				if math.random() < 0.37 then
					basiliscoHoverDodge() --either strafe or flashstep
					if hearAttack then considerNLOSOnHear() end
				elseif considerStrafing() then
					alertSeenTimer = 0
					timeSinceChoosePatrol = 0
					alertFlagged = false
				end
			end	
		end
		if timeSinceChoosePatrol > patrolTime then --resume patrol
			flightMode = "patrol"
			timeSinceChoosePatrol = 0
			timeSinceDistraction = 0
			alertSeenTimer = 0
		end
		hoverAvoidance()
	elseif flightMode == "ram" then --a "critical" attack
		turnRate = 0.13
		distance = 1.9 --separation of tail units
		SetLightIntensity(lightSpot,0)
		SetLightIntensity(lightSource,0)

		if not rammed then speed = math.min(ramFast, speed+dt*333) else speed = math.max(ramSlow,speed-dt*130) end
		--forward motion
		local forward = TransformToParentVec(headTransform,Vec(0,0,-(speed)*dt))--using vectors
		headTargetPos = VecAdd(headTargetPos, forward)--using vectors

		if not rammed then--ram direction
			--look direction determinant
			local rot = QuatLookAt(headTargetPos, targetPos)
			headTargetRot = rot --literally look at the target
			headLookRot = QuatCopy(headTransform.rot)
			alertLight(lightSource)
			alertLight(lightSpot)
			makeBodyUnbreakable() --protect body from holing itself
			makeWingsUnbreakable() --protects wings
		else 
			headTargetRot = QuatLookAt(headTargetPos,recoverVec) --literally look at the target
			headLookRot = QuatCopy(headTransform.rot)
		end
		forwardFlightTailControl(dt) --control tail movement
		--wing animations
		forwardWingMode()
		SetBodyTransform(head,headTransform)
		
		retractLauncher()
		retractRailBarrel()
		
		--ram operation
		if playerSeen then targetPos = GetPlayerPos() end
		
		basiliscoRamOperation(dt)
	elseif flightMode == "strafe" then
		--flight operators
		turnRate = 0.06
		distance = 1.9
		height = 300
		--speed control
		toSpeed = maxSpeed
		if toSpeed > speed then
			speed = clamp(speed + 15*dt, minSpeed, toSpeed)--throttle up
		else
			speed = clamp(speed - 15*dt, minSpeed, maxSpeed)--brakes
		end--]]
		--forward speed
		local forward = TransformToParentVec(headTransform,Vec(0,0,-(speed)*dt))--using vectors
		--headTransform.pos = VecAdd(headTransform.pos,forward) --directly changing the transform, enable to stop drifting
		headTargetPos = VecAdd(headTargetPos, forward)--using vectors
		
		strafeDir()
		forwardFlightTailControl(dt)
		forwardWingMode()
		SetBodyTransform(head,headTransform)
		
		retractLauncher()
		extendRailBarrel()
		
		basiliscoStrafeOperation(dt)
		
		if playerSeen then
			timeSinceLastSeen = 0
			timeSinceChoosePatrol = 0
			timeSinceDistraction = 0
			lookPos = GetPlayerPos()
			playerSeeMeter = math.min(2,playerSeeMeter + dt*1.5)
		end
		if playerSeeMeter >= subWeaponConsiderThreshold then --aim the subweapons at the right spot
			targetPos = GetPlayerPos()
		end
	elseif flightMode == "emplaced" then
		turnRate = hoverTurnRate -- not speed scaled, /4 for tails
		distance = 6
		height = math.max(115,hoverheight*2)
		
		alertMove(dt)
		hoverTailControl(dt)
		hoverWingMode()
		hoverDir(dt)
		hoverHeightControl(dt)
		headTransform = TransformToParentTransform(tailTable[1].transform, Transform(Vec(0,0,-5),QuatEuler()))
		headTransform.rot = headLookRot
		SetBodyTransform (tailTable[1].body,tailTable[1].transform)
		SetBodyTransform(head,headTransform)
		
		extendLauncher()
		extendRailBarrel()
	end
	
	--universal sight code
	playerObscureDivisor = 1
	if playerSpotted() then playerSeen = true end --player is seen in searchlight
	
	if playerSeen then --reduced detection rate if it only sees your head or you crouch
		if InputDown("crouch") then playerObscureDivisor = 2 
		elseif not canSeePlayer(GetPlayerPos()) then
			playerObscureDivisor = 2 
		end
	end
	
	if not canSeePlayer() then playerSeen = false end
	if not playerSeen then --detect meter reduction
		playerSeeMeter = math.max(0,playerSeeMeter - dt) --todo: multiply dt for variable rate of reduction
	end
	if playerSeeMeter >=1 then --aggro threshold
		playerTracked = true
	else
		playerTracked = false
	end
	if timeSinceLastSeen < 2 then --emulating motion tracking
		timeSinceChoosePatrol = 0
		lookPos = GetPlayerPos()
		if playerSeeMeter >=1 then targetPos = GetPlayerPos() end 
	end
	searchLightLook(dt)
	lookPos = VecCopy(targetPos) --reset lookPos to targetPos 
	
	--extra body attachment
	enginePlacement(dt)
	if FindLocation("railguntags") ~= 0 then railGunAttach()
		railGunStatic() 
	end 
	if launcher ~= 0 then launcherAttach() --launcher ~= 0 means it is present
		launcherStatic() 
	end
	
	--weapon operation
	weaponArmed = false --any of the below weapons will set it to true and it will affect the ai code above this line on the next tick
	--lasers
	if laserTriggered then 
		weaponArmed = true
		timeSinceChoosePatrol = 0
		alertSeenTimer = 0 
		alertLight(lightSource)
		alertLight(lightSpot)
		SetLightIntensity(lightSpot,0)
		local aimPos = VecCopy(lookPos)
		if not playerTracked then 
			aimPos= VecAdd(lookPos, TransformToParentVec(laserAxis,Vec(math.sin(GetTime())*3.5,math.cos(GetTime()*1.3)*7,0))) 
		end
		local lightTransform = TransformToParentTransform(headTransform, searchLightLocalTransform) --hijack the normal searchlight control for the laser aim
		searchLightTargetRot = QuatLookAt(lightTransform.pos, aimPos)
		lightTransform.rot = searchLightRot
		SetBodyTransform(searchLight, lightTransform)
	end
	tickLaser(dt)
	
	if railGunArmed then
		weaponArmed =  true
		timeSinceChoosePatrol = 0
		alertSeenTimer = 0 
		alertLight(lightSource)
		alertLight(lightSpot)
	end
	tickRailGun(dt)
	
	if FindLocation("autocannontags") ~= 0 then
		tickAutoCannon(dt)
	end
	
	--subweapons
	if playerSeeMeter >= subWeaponConsiderThreshold and playerSeen then
		considerNLOS()
	end
	if hasMultiMissiles then tickMultiMissile(dt) end
	if hasRCWS then tickRCWS(dt) end
	if hasMortars then tickMortar(dt) end
	if hasReconFlare then
		if reconFlareStart then
			tickReconFlare(dt)
		end
	end
	
	--special weapons
	if hasRam then tickRam() end
	if poisonSequenceStarted then tickPoison(dt) end
	if heavyCannonsArmed then
		tickHeavyCannons(dt)
	end
	if FindLocation("nuketags") ~= 0 then
		if nukeStarted then
			tickNuke(dt)
		end
		tickNukeKill(dt)
		if attackCounter >= nukeThreshold and not nukeAdded then --add nuke attack
			nukeAdded = true
			table.insert(specialWeapons,"nukeStarted")
		end
	end
	
	--throw particles from flight systems
	wingthrusters()
	largeThrusters()
	shedding()
	
	PlayLoop(BasiliscoEngineLoop,tailTable[clavicleNum].transform.pos, 20, false)
	PlayLoop(BasiliscoJetLoop,tailTable[clavicleNum].transform.pos, 40, false)
	
	--forces static for bodies cause idk wtf happened in 0.8.0 to ruin the ram attack
	forceStaticBatch()--]]--uses flyerfp tag to catch all bodies in script
	
	--health handlers
	local health = getStateLights()
	if health < prevHealth then wasHurt = true;wasHurtTimer = 5; prevHealth = health end
	
	armorState()
	
	tickWingHealth()
	tickHurtOperation(dt) --pain response
	escapeCheck() -- if health goes below certain threshold, try to bail
	
	--DrawBodyOutline(head,1)
	--DrawBodyOutline(tailTable[1].body,1)
end

function update(dt)
	if flightMode ~= "alert" and flightMode ~= "emplaced" then
		local acc = VecSub(headTargetPos, headTransform.pos)
		--Rotate smoothly towards target rotation
		headTransform.pos,headTransform.rot = alicantoHeadUpdate(headTransform.pos,headTargetPos,headTransform.rot,headTargetRot,turnRate,dt)
		
		--tail smoothing here
		for i=1,#tailTable do
			local acc = VecSub(tailTable[i].targetPos,tailTable[i].transform.pos)
			tailTable[i].transform.pos = alicantoTailUpdate(tailTable[i].transform.pos, tailTable[i].targetPos, dt)
		end
	else
		basiliscoHoverUpdate(dt)
	end

	searchLightUpdate(dt)
	
	--weapon extras
	poisonPlayerBuff()
end

function draw(dt)
	if hasPoison then drawPoisonHud() end
end

--imma fess that despite using a chilean mythos name, the monster is actually based on the guardian from journey by tgc