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