scriptname critterBird extends critter
{behavior script for basic bird};[USKP 2.0.1]
;!{behavior script for basic fish}
;======================================================================================;
;	IMPORTS     /
;=============/
; include scripts for brevity
import utility
import form
import math
import debug
;======================================================================================;
;	PROPERTIES  /
;=============/
FormList property perchTypeList auto
{formlist of perches I look for}
float property fMinScale = 0.5 auto
{how small can this thing get? Default=0.5}
float property fMaxScale = 1.5 auto
{how big can it get? Default = 1.5}
float property fMinHop = 8.0 auto
{minimum distance to hop.  Default: 8}
float property fMaxHop = 32.0 auto
{max distance to hop.  Default:32}
float property leashLength = 256.0 auto
{how far from home can I roam?  Treat like a radius. Default = 256.0}
float property fSpeed = 150.0 auto
{Speed of base movement. Default= 150}
string property sState auto hidden
{current behavior state of the bird.  NOT a papyrus state}
; Valid State Values:
; 		inFlight
; 		onPerch	; perch basically anywhere I've landed but cannot hop along.
;		onGround
int property iEnergy = 50 auto hidden
{internal energy value 1-100.  Default at spawn: 50}
objectReference property goalPerch auto hidden
{Internally track reserved perch reference}

;======================================================================================;
;	VARIABLES   /
;=============/
objectReference player
; save player reference for later brevity.
int iDice
; integer we'll use and re-use for quick random decision rolls

;======================================================================================;
;	EVENTS	    /
;=============/
EVENT onStart()  	; critter event called when spawned
	;trace("startup bird: " + self)
	; scale a little bit for visual variance
	setScale((randomFloat(fMinScale, fMaxScale)))
	
	; enable after scale, matching other critters [USKP 2.0.1]
	Enable()

	; need 3D before keyframed [USKP 2.0.1]
	if !CheckFor3D(self)
		DisableAndDelete(false)
		return
	endif

	; use keyframed motion
	setMotionType(Motion_Keyframed, false)
	
	;check for Spawner override of leash length
	if spawner.fLeashOverride != 0
		leashLength = spawner.fLeashOverride
	endif
	
	;save off the player reference for brevity
	player = game.getPlayer()
	
	; go to idle swim state and poll
	sState = "onGround"
	if sState == ""
		;trace(self + " has sState of " + sState + ", something is wrong")
	endif
	gotoState("idling")
	registerForSingleUpdate(0.0)
endEVENT

STATE idling
	EVENT onUpdate()		
	; begin main loop of bird behavior
	if CheckViableDistance()
		if (spawner && spawner.isActiveTime())
			; spawner is active this time of day
			; first things first - call home if too far away.
			if getDistance(spawner) > leashLength
				;trace(self + " is too far from spawner.  Fly Home")
				if sState == "inFlight"
					gotoState("Flying")
					flyAwayHome()
				else
					takeFlight()
				endif
			endif
			
			; if we were trying to reach a perch point, land at it now
			if goalPerch != NONE
				;trace(self + " has a goal perch: " + goalPerch)
				if getDistance(goalPerch) < 32
					gotoState("Flying")
					landAtPerch(goalPerch)
				endif
			endif
			
			; if nothing higher priority, then proceed with regular behavior decision
			if sState == "inFlight"
				;trace(self + " is in flight, choosing relevant behavior")
				; choose from in-flight behaviors
				iDice == randomInt(1,2)
				if iDice == 1
					; look for a perch object/node
					ObjectReference goal = findPerch()
					if goal != NONE
						flyToPerch(goal)
					endif
				elseif iDice == 2
					;trace(self + " is in flight and wants to fly more")
					; fly some more
					flyTo(player)
				endif
			elseif sState == "perched"
				;trace(self + " is perched, choosing relevant behavior")
				; choose from perch behaviors
				iDice = randomInt(1,3)
				if iDice == 1
					playIdle()
				elseif iDice == 2
					takeFlight()
				elseif iDice == 3
					groundHop()
				endif
			elseif sState == "onGround"
				;trace(self + " is on the ground, choosing relevant behavior")
				; choose from ground behaviors
				iDice = randomInt(1,3)
				if iDice == 1
					playIdle()
				elseif iDice == 2
					takeFlight()
				elseif iDice == 3
					groundHop()
				endif
			endif
		endif
		bCalculating = False; [USKP 2.0.4]
	endif
	endEVENT
endSTATE

STATE Flying
	EVENT onTranslationComplete()
		;trace(self + " done with translation.  Idle now.")
		gotoState("idling")
		registerForSingleUpdate(0.0)
	endEVENT
endSTATE

;======================================================================================;
;	FUNCTIONS   /
;=============/
FUNCTION groundHop()	
	;trace(self + " beginning groundHop()")
;/// [USKP]
	; Set up variables to solve for right triangle ABC
	; c is the hypotenuse - think of it as the path along which the bird hops
	; the a and c values are going to be added to current XY to determine goal XY
	float aA = getAngleZ()		;        B
	float aB						;      /|
	float aC = 90				;   c/  |b			; we know angle C is 90 because it's a right triangleukhj/p-
	float a						; A/____|C
	float b						;    a	
	float c = randomFloat(fMinHop, fMaxHop)

	; use an int to track the quadrant into which we're hopping (global)
	; I:(+,+)   II:(-,+)   III:(-,-)   IV:(+,-)
	int quadrant
	if (aA > 0 && aA <= 90) || aA > 360			
		quadrant = 1
	elseif aA > 90 && aA <= 180
		quadrant = 4
	elseif aA > 180 && aA <= 270
		quadrant = 3
	elseif aA > 270 && aA <= 360
		quadrant = 2
	endif

	a = c*(cos(aB))
	b = c*(cos(aA))

	float newX
	float newY

	if quadrant == 1
		newX = (self.x + a)
		newY = (self.y + b)
	elseif quadrant == 2
		newX = (self.x - a)
		newY = (self.y + b)
	elseif quadrant == 3
		newX = (self.x - a)
		newY = (self.y - b)
	elseif quadrant == 4
		newX = (self.x + a)
		newY = (self.y - b)
	endif
///;
	; Sclerocephalus replacement [USKP 2.0.3]
	Float AngleZ = self.GetAngleZ()
	Float HopDistance = RandomFloat(fMinHop, fMaxHop)

	Float NewX = self.X + HopDistance * Cos(AngleZ)
	Float NewY = self.Y + HopDistance * Sin(AngleZ)

	if CheckViability()
		return
	endIf

	; now use a spline to "hop" there quicky.
;!	SplineTranslateTo(newX, newY, self.z, 0, 0, self.getAngleZ(), 300, fSpeed)
	SplineTranslateTo(newX, newY, self.Z, 0, 0, AngleZ, 300, fSpeed)

	;trace(self + "performing a short hop to: " + newX + ", " + newY + ".")
	; test, but a little pause between each seems proper.
	wait(randomFloat(0.1, 2.0))
endFUNCTION

FUNCTION takeFlight()
	if CheckViability()
		return
	endIf

	;trace(self + " is taking flight")
	playAnimationAndWait("takeOff","end")
	sState = "inFlight"
	SplinetranslateTo(self.X, self.Y, self.Z + 64, 0, 0, self.getAngleZ(), 50, fSpeed/2)
endFUNCTION

FUNCTION FlyTo(objectReference goal)
	if CheckViability()
		return
	endIf

	;trace(self + " is flying to point: " + goal)
	; quick and dirty - just go right to the point.
	SplinetranslateTo(goal.X, goal.Y, goal.Z + 64, 0, 0, self.getAngleZ(), 200, fSpeed)
endFUNCTION

FUNCTION playIdle()
	; just have to be aware of the valid behaviors we can choose from. 
	; long-term may want to use an array/list and be able to choose dynamically
	iDice = randomInt(1,2)
	if iDice == 1
		playAnimationAndWait("StartGrndPeck","StartGrndLook")
	elseif iDice == 2
		playAnimationAndWait("startGrndFlap","StartGrndLook")
	endif
endFUNCTION

FUNCTION landAtPerch(objectReference goal)
	if CheckViability()
		return
	endIf

	SplineTranslateTo(goal.x, goal.y, goal.z, 0, 0, randomFloat(0.0, 360.0), 300, fSpeed/2)
	playAnimationAndWait("startGrndFlap","StartGrndLook")
endFUNCTION

objectReference FUNCTION findPerch()
	int size = perchTypeList.getSize()
	int i = 0
	bool checking = true
	while i < size && checking == true
		form perchType = perchTypeList.getAt(i)
		i += 1
		objectReference goal = game.FindRandomReferenceOfTypeFromRef(perchType, spawner, leashLength)
		if goal != NONE
			checking = false
			;trace(self + "found a perch: " + goal)
			critterPerch perchScript = goal as critterPerch
			if perchScript.reserved != true
				perchScript.reserved = true
				perchScript.incoming = self
				return goal
			else
				;trace(self + " found a perch, but it was reserved!")
				return NONE
			endif
			;trace(self + " couldn't find a perch")
			return NONE
		endif
	endWhile
endFUNCTION

FUNCTION flyToPerch(objectReference goal)
	; quick and dirty
	flyTo(goal)
endFUNCTION

FUNCTION flyAwayHome()
	flyTo(spawner)
endFUNCTION