scriptName CritterFish extends Critter
{Main Behavior script for fish schools}
import Utility
import form
import debug
; Constants
float Property fActorDetectionDistance = 300.0 auto
{The Distance at which an actor will trigger a flee behavior}
float Property fTranslationSpeedMean = 40.0 auto
{The movement speed when going from plant to plant, mean value}
float Property fTranslationSpeedVariance = 20.0 auto
{The movement speed when going from plant to plant, variance}
float Property fFleeTranslationSpeed = 70.0 auto
{The movement speed when fleeing from the player}
float Property fMinScale = 0.1 auto
{Minimum initial scale of the Fish}
float Property fMaxScale = 0.2 auto
{Maximum initial scale of the Fish}
float Property fMinDepth = 10.0 auto
{Minimum fish depth}
float Property fSplineCurvature = 200.0 auto
{Spline curvature}
float Property fMinTimeNotMoving = 1.0 auto
float Property fMaxTimeNotMoving = 5.0 auto
float Property fSchoolingDistanceX = 25.0 auto
float Property fSchoolingDistanceY = 35.0 auto
int Property iPercentChanceSchooling = 50 auto
int Property iPercentChanceStopSchooling = 5 auto
float property fMaxRotationSpeed = 360.0 auto
{Max rotation speed while mocing, default = 360 deg/s}
; Hidden property for schooling
bool Property bMoving = false auto hidden
float Property fMoving = 0.0 auto hidden; [USKP 2.0.4]
; Variables
Actor closestActor = none
CritterFish TargetFish = none
;/ clear TargetObject [USKP 2.0.1]
/;
Function FollowClear()
Float delay = 0.367879; 1/e
while TargetFish && bCalculating && delay < 2.943; 3 times
; avoid clearing during calculations [USKP 2.0.4]
Wait(delay)
delay += delay
endWhile
TargetFish = none
endFunction
;/ clear Leader and Follower and Target [USKP 2.0.1]
/;
Function TargetClear()
if TargetFish
TargetFish.Follower = none
endIf
TargetFish = none
endFunction
; Called by the spawner to kick off the processing on this Fish
Event OnStart()
; Vary size a bit
SetScale(RandomFloat(fMinScale, fMaxScale))
; Start in the random swimming state
GotoState("RandomSwimming")
; test moved to Critter [indent retained] [USKP 2.0.1]
WarpToRandomPoint()
; ; Debug.TraceConditional("Fish " + self + " registering for update", bCritterDebug)
; Enable the critter
Enable()
if CheckFor3D(self)
; Switch to keyframe state
SetMotionType(Motion_Keyframed, false)
; Get ready to start moving
RegisterForSingleUpdate(0.0)
else
DisableAndDelete(false)
endIf
endEvent
State RandomSwimming
Event OnUpdate()
; Is the player too far?
if CheckViableDistance()
; Good to update, check for close actors
;! Actor closestActor = Game.FindClosestActorFromRef(self, fActorDetectionDistance)
; Check whether we should flee and move faster
;! float fspeed = 0.0
float fspeed = fFleeTranslationSpeed
if (closestActor != none)
; ;Debug.Trace(self + " Oh noes! there is an Actor " + closestActor + " nearby, Flee")
; Move fast
;! fspeed = fFleeTranslationSpeed
else
; Move at regular speed
fspeed = RandomFloat(fTranslationSpeedMean - fTranslationSpeedVariance, fTranslationSpeedMean + fTranslationSpeedVariance)
endIf
; Time to take off for another plant
if (RandomInt(0, 100) < iPercentChanceSchooling)
if PickTargetFishForSchooling() && PickRandomPointBehindTargetFish()
GotoState("Schooling")
SchoolWithOtherFish(fspeed)
else
GoToNewPoint(fspeed)
endIf
else
GoToNewPoint(fspeed)
endIf
bCalculating = False; [USKP 2.0.4]
endIf
closestActor = none; [USKP 2.0.4]
endEvent
Event OnCritterGoalAlmostReached()
; traceConditional(self + " reached goal", bCritterDebug)
fMoving = 0.0
if PlayerRef; interlock, but never delete during Translation [USKP 2.0.1]
; pre-find for speed, match other critters [USKP 2.0.4]
closestActor = Game.FindClosestActorFromRef(self, fActorDetectionDistance)
; ; Debug.TraceConditional("Fish " + self + " registering for immediate update", bCritterDebug)
RegisterForSingleUpdate(0.0)
endIf
EndEvent
endState
State Schooling
Event OnUpdate()
if CheckViableDistance()
;! if ((RandomInt(0, 100) < iPercentChanceStopSchooling) || TargetFish == none || TargetFish.IsDisabled() || !TargetFish.bMoving)
if (closestActor != none); [USKP 2.0.4]
; ;Debug.Trace(self + " Oh noes! there is an Actor " + closestActor + " nearby, Flee")
GotoState("RandomSwimming")
TargetClear()
GoToNewPoint(fFleeTranslationSpeed)
elseif (RandomInt(0, 100) >= iPercentChanceStopSchooling) && (TargetFish as CritterFish) && TargetFish.fMoving && PickRandomPointBehindTargetFish()
; If the target fish is moving, follow it
;! SchoolWithOtherFish(fFleeTranslationSpeed)
SchoolWithOtherFish(TargetFish.fMoving); reduce twitching [USKP 2.0.4]
else
GotoState("RandomSwimming")
TargetClear()
GoToNewPoint(RandomFloat(fTranslationSpeedMean - fTranslationSpeedVariance, fTranslationSpeedMean + fTranslationSpeedVariance))
endIf
bCalculating = False; [USKP 2.0.4]
endIf
closestActor = none; [USKP 2.0.4]
endEvent
Event OnCritterGoalAlmostReached()
; traceConditional(self + " about to reach goal", bCritterDebug)
if PlayerRef; interlock, and match RandomSwimming [USKP 2.0.1]
; pre-find for speed, match other critters [USKP 2.0.4]
closestActor = Game.FindClosestActorFromRef(self, fActorDetectionDistance)
RegisterForSingleUpdate(0.0)
endIf
EndEvent
endState
float fTargetX
float fTargetY
float fTargetZ
float fTargetAngleZ
float fTargetAngleX
;/
; returns true on success [USKP 2.0.1]
/;
bool Function PickRandomPoint()
; Pick a point inside the upside down cone of height fWaterDepth and radius fLeashLength
; Since most ponds will look like a bowl, we don't want fish close to the edge to clip through the ground
; make sure our spawner is loaded.
if PlayerRef ;/&& CheckCellAttached(self) && Spawner && CheckCellAttached(Spawner)/;
; First find a random point within the radius
;/// [USKP 2.0.4] ///;
Float fLength = RandomFloat(0.0, fLeashLength)
fTargetAngleZ = RandomFloat(-180.0, 180.0)
fTargetX = fSpawnerX + fLength * Math.Cos(fTargetAngleZ)
fTargetY = fSpawnerY + fLength * Math.Sin(fTargetAngleZ)
;*** [USKP 2.0.4] ***
; Now pick a random Z based on the length.
; If flength == fleashLength, then it must be -fMinDepth
; If length == 0, then it can be -fDepth
; fixed [USKP 2.0.4]
if fMinDepth < fDepth
fTargetZ = fSpawnerZ - RandomFloat(fMinDepth, (fDepth - ((flength * (fDepth - fMinDepth)) / fLeashLength)))
else
fTargetZ = fSpawnerZ
endIf
; Pick random target angle
;! fTargetAngleZ = RandomFloat(-180.0, 180.0)
fTargetAngleX = 0.0
return (PlayerRef != None); check again after lengthy calculations [USKP 2.0.1]
else
; debug.trace("Tried to run PickRandomPoint, but no valid TargetFish. "+self)
return false
endif
endFunction
bool Function PickTargetFishForSchooling()
if Spawner && CheckCellAttached(Spawner)
;! TargetFish = Game.FindRandomReferenceOfAnyTypeInList(Spawner.CritterTypes, fSpawnerX, fSpawnerY, fSpawnerZ, fLeashLength - fSchoolingDistanceX) as CritterFish
TargetFish = Game.FindRandomReferenceOfAnyTypeInList(Spawner.CritterTypes, fSpawnerX, fSpawnerY, fSpawnerZ, fLeashLength - fSchoolingDistanceY) as CritterFish
; record TargetFish.Follower [USKP 2.0.1]
if (TargetFish && TargetFish != self && TargetFish.FollowSet(self as ObjectReference))
return true
endif
TargetFish = none
endif
return false
endFunction
;/
; returns true on success [USKP 2.0.1]
/;
bool Function PickRandomPointBehindTargetFish()
if PlayerRef && TargetFish && !TargetFish.IsDisabled() && CheckFor3D(TargetFish)
; moved unchanged variables outside loop [USKP 2.0.1]
float ftargetFishX = targetFish.X - fSpawnerX
float ftargetFishY = targetFish.Y - fSpawnerY
fTargetZ = targetFish.Z; default same as leader [USKP 2.0.4]
fTargetAngleZ = targetFish.GetAngleZ()
;/// [USKP 2.0.4] ///;
; vanilla formula dithers X and Y directly. Reduce calculations by
; dithering angle vector instead.
; reuse fSchoolingDistance[X|Y] as min and max distance from leader.
float fDistance = RandomFloat(fSchoolingDistanceX, fSchoolingDistanceY)
float fDeltaAngle = fTargetAngleZ + RandomFloat(-fAngleVarianceZ, fAngleVarianceZ)
fTargetX = ftargetFishX - fDistance * Math.cos(fDeltaAngle)
fTargetY = ftargetFishY - fDistance * Math.sin(fDeltaAngle)
; variables above are 0,0 spawner relative
float flength = Math.sqrt(fTargetX * fTargetX + fTargetY * fTargetY)
fTargetX += fSpawnerX
fTargetY += fSpawnerY
;*** [USKP 2.0.4] ***
; Now pick a random Z
; If flength == fleashLength, then it must be -fMinDepth
; If length == 0, then it can be -fDepth
; fixed [USKP 2.0.4]
if flength < fLeashLength && fMinDepth < fDepth
fTargetZ = fSpawnerZ - RandomFloat(fMinDepth, (fDepth - ((flength * (fDepth - fMinDepth)) / fLeashLength)))
endIf
;! fTargetAngleZ = targetFish.GetAngleZ()
fTargetAngleX = 0.0
return (PlayerRef != None); check again after lengthy calculations [USKP 2.0.1]
else
; debug.trace("Tried to run PickRandomPointBehindTargetFish, but no valid TargetFish. "+self)
return false
endif
endFunction
Function WarpToRandomPoint()
; Pick a random point
PickRandomPoint()
; And warp to it
SetPosition(fTargetX, fTargetY, fTargetZ)
SetAngle(fTargetAngleX, 0.0, fTargetAngleZ)
endFunction
Function GoToNewPoint(float afSpeed)
; Pick a random point
if !PickRandomPoint()
; ; debug.trace("Tried to run GoToNewPoint, but PickRandomPoint failed. "+self)
bCalculating = False; [USKP 2.0.3]
disableAndDelete()
elseif CheckViability()
return
endif
; And travel to it
fMoving = afSpeed; [USKP 2.0.4]
SplineTranslateTo(fTargetX, ftargetY, ftargetZ, fTargetAngleX, 0.0, fTargetAngleZ, fSplineCurvature, afSpeed, fMaxRotationSpeed)
endFunction
Function SchoolWithOtherFish(float afSpeed)
if !CheckViability()
fMoving = afSpeed; [USKP 2.0.4]
SplineTranslateTo(fTargetX, ftargetY, ftargetZ, fTargetAngleX, 0.0, fTargetAngleZ, fSplineCurvature, afSpeed, fMaxRotationSpeed)
endif
endFunction
EVENT onCellDetach()
; Safety measure - when my cell is detached, for whatever reason, kill me.
; ;debug.trace("Killing self due to onCellDetach() - "+self)
;! DisableAndDelete()
; kick the OnUpdate in hopes it will clean up. [USKP 2.0.1]
bMoving = false
fMoving = 0.0
parent.onCellDetach()
endEVENT