scriptName CritterSpawn extends ObjectReference {MODIFIED BY STEVE40 and USKP} import Critter import Utility ;---------------------------------------------- ; Properties to be set for this Critter spawn ;---------------------------------------------- ; The type of critter (base object) to create FormList property CritterTypes auto { The base object to create references of to spawn critters} ; The distance from this spawner that Moths are allowed to be float property fLeashLength = 500.0 auto { The distance that moths are allowed to be from this spawner} float property fLeashHeight = 50.0 auto { The distance that dragonflies are allowed to be from above spawner} float property fLeashDepth = 50.0 auto { The distance that fish are allowed to be from below spawner} float property fMaxPlayerDistance = 2000.0 auto { The distance from the player before the Spawner stops spawning critters} int property iMaxCritterCount = 10 auto { The maximum number of critters this spawner can generate} float property fFastSpawnInterval = 0.1 auto { When spawning critters, the interval between spawns} float property fSlowSpawnInterval = 5.0 auto { When spawning critters, the interval between spawns} GlobalVariable property GameHour auto { Make this point to the GameHour global } float property fStartSpawnTime = 6.0 auto { The Time after which this spawner can be active} float property fEndSpawnTime = 11.0 auto { The Time before which this spawner can be active} float property fLeashOverride auto {Optional: Manually set roaming radius for critters spawned} bool property bSpawnInPrecipitation auto {Should this critter spawn in rain/snow? DEFAULT: FALSE} ;---------------------------------------------- ; Constants (shouldn't need to modify these) ;---------------------------------------------- float fCheckPlayerDistanceTime = 2.0 ;---------------------------------------------- ; Variables to keep track of spawned critters ;---------------------------------------------- int property iCurrentCritterCount = 0 auto hidden bool bLooping bool bPrintDebug = FALSE ; should usually be set to false. int recursions ; Do initial stuff when my 3D has loaded up. EVENT OnCellAttach() ; USKP 1.3.3 - Changed from OnLoad() to be more reliable, especially in interior cells. ; The Spawner will register for update and periodically check whether the player is close or not ; - JOEL REFACTOR - Going to use onLoad() & onUnload() instead of states? ; GotoState("WaitingForPlayer") ; - JOEL REFACTOR - also no longer need to update ; RegisterForSingleUpdate(fCheckPlayerDistanceTime) if bPrintDebug == TRUE ; debug.trace("spawner " + self + " loaded.") recursions = 0 endif ; set our control bool to start the loop bLooping = TRUE while bLooping if bPrintDebug recursions += 1 ; debug.trace("spawner " + self + " while loop #" + recursions) endif if !shouldSpawn() ; wait a bit, then see if the player is close again. ; Removing this to eliminate some TPLOG spam ; ; debug.TraceConditional("player not yet near spawner " + self, bPrintDebug) utility.wait(fCheckPlayerDistanceTime) else ; ; debug.TraceConditional("spawner " + self + " ready to spawn!!!", bPrintDebug) ; player must be nearby - spawn our initial critters ; don't follow up as we no longer wish to re-generate new critters until the player leaves entirely spawnInitialCritterBatch() bLooping = FALSE endif endWhile endEVENT EVENT onUnload() ; when our 3D unloads, stop looping until loaded again. bLooping = FALSE ; ; debug.TraceConditional("spawner " + self + " unloading due to onUnload() EVENT.", bPrintDebug) endEVENT EVENT onCellDetach() bLooping = FALSE ; ; debug.TraceConditional("spawner " + self + " unloading due to onCellDetach() EVENT.", bPrintDebug) endEVENT Function SpawnInitialCritterBatch() ; How many do we need to spawn? int icrittersToSpawn = iMaxCritterCount - iCurrentCritterCount ; Create that many critters int i = 0; while (i < icrittersToSpawn) ; Create one critter at a time SpawnCritter() ; Wait a bit before the next spawn ;Wait(fFastSpawnInterval) ; Next i = i + 1 endWhile endFunction ; Called by critters when they die Event OnCritterDied() ; Decrement current critter count, next time OnUpdate ; gets called, we'll spawn a new one if iCurrentCritterCount > 0 iCurrentCritterCount -= 1; faster [USKP 2.0.1] elseif iCurrentCritterCount < 0 ; iCurrentCritterCount must be in the negatives. Something is up, but for now increment towards zero iCurrentCritterCount = 0; earler saves with failed deletions [USKP 2.0.1] endif endEvent ; Spawns one Critter bool Function SpawnCritter() if (iCurrentCritterCount < iMaxCritterCount) && (iCurrentCritterCount >= 0) ; Go ahead with the actual spawn return SpawnCritterAtRef(self) ;/// [USKP 2.0.1] moved into SpawnCritterAtRef ; Increment count iCurrentCritterCount = iCurrentCritterCount + 1 return true ///; elseif iCurrentCritterCount < 0 ; debug.trace("("+self+") has invalid iCurrentCritterCount of "+iCurrentCritterCount+", abort!") ; turn off loop bLooping = FALSE else ; debug.trace("("+self+") SpawnCritter() failed because iCurrentCritterCount is "+iCurrentCritterCount) ; ;debug.trace("("+self+") iMaxCritterCount: "+iMaxCritterCount) endif return False ; [USKP 1.3.1] to help eliminate log spam and potential long term save bloating. endFunction ; Spawns one Critter at a specific location ; returns true on success [USKP 2.0.1] ; bool Function SpawnCritterAtRef(ObjectReference arSpawnRef) ; Pick a random critter type Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator if critterType == none ; STEVE40+USKP extra check return false endif ; Create the critter and cast it to the critter base class ObjectReference critterRef = arSpawnRef.PlaceAtMe(critterType, 1, false, true) Critter thecritter = critterRef as Critter if thecritter == none ; STEVE40+USKP extra check return false endif ; Set initial variables on the critter ; ; Debug.TraceConditional("Spawner " + self + " is creating Critter " + thecritter, bPrintDebug); thecritter.SetInitialSpawnerProperties(fLeashLength, fLeashHeight, fLeashDepth, fMaxPlayerDistance + fLeashLength, self) ; Increment count [USKP 2.0.1] iCurrentCritterCount += 1 return true endFunction ; Utility method that returns the player's distance float Function GetPlayerDistance() return Game.GetPlayer().GetDistance(self) endFunction ; Utility method that tells the spawner whether it should spawn critters bool Function ShouldSpawn() ;DREW - Added an extra safety measure for if the object is stuck in the spawn check loop while the 3d is not loaded ; NOTE - is3dLoaded dumps an error when the 3d is not loaded, but the function still returns false which should ; set bLooping to false and jump out of the bLooping While, at which point no additional errors will be thrown ; GetParentCell check first helps avoid error log in exteriors? [USKP 2.0.1] if self.GetParentCell() && self.is3dLoaded() if !(GetPlayerDistance() <= fMaxPlayerDistance) return false endIf ; Otherwise, base value on time of day (no handling of wrap around though...) return IsActiveTime() else ;if the 3d is not loaded jump out of the looped state. Just an extra safety measure. ; ;debug.Trace(self + ": should be setting bLooping to False") bLooping = FALSE return false endif endFunction bool Function IsActiveTime() bool binTimeRange = false ; BUGFIX BY STEVE40 - I found one case in Hearthfire where Bethesda didn't set the GameHour property on the script, causing it to spam the logs if GameHour == none bLooping = False ; kill the script return false endIf if (fEndSpawnTime >= fStartSpawnTime) binTimeRange = (GameHour.GetValue() >= fStartSpawnTime) && (GameHour.GetValue() < fEndSpawnTime) else binTimeRange = (GameHour.GetValue() >= fStartSpawnTime) || (GameHour.GetValue() < fEndSpawnTime) endIf return binTimeRange && ((Weather.GetCurrentWeather() == none) || \ (Weather.GetCurrentWeather().GetClassification() < 2) || \ (bSpawnInPrecipitation == TRUE)) endFunction