scriptName CritterSpawn extends ObjectReference 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} Bool property bAllowRespawn = true auto Bool property bReducedRespawn = true auto ;---------------------------------------------- ; Constants (shouldn't need to modify these) ;---------------------------------------------- ;---------------------------------------------- ; Variables to keep track of spawned critters ;---------------------------------------------- int property iCurrentCritterCount = 0 auto hidden ; not used, set to -1 to break vanilla spawner while loops bool bLooping = false ; reintroduced to catch baked runaway loops float extraWaitTime = 0.0 ; extra time in seconds before trying again float property fCheckPlayerDistanceTime = 2.0 auto hidden int property iSpawnedCritterCount = 0 auto hidden int property iDeadCritterCount = 0 auto hidden int property iRespawnDelay = 6 auto hidden bool property isSpawning = false auto hidden bool property shouldTryAgain = false auto hidden actor property PlayerRef auto bool bPrintDebug = FALSE Cell _ParentCell Cell property ParentCell Cell function get() if !_ParentCell _ParentCell = self.GetParentCell() endif return _ParentCell endFunction endproperty bool Function VanillaLoopBreak() if (bLooping || iCurrentCritterCount != 0) ; breaking OnCellAttach runaway loop in baked vanilla functions bLooping = false ; breaking SpawnInitialCritterBatch runaway loop in baked vanilla and uskp functions iCurrentCritterCount = 0 Debug.Trace("CritterSpawn : Runaway Spawner warning :" + self); return true endif return false EndFunction Function SpawnInitialCritterBatch() VanillaLoopBreak() endFunction EVENT OnCellAttach() VanillaLoopBreak() ; the spawner will attach to a cell, this can be the cell we just teleported to (door) or one that loaded in the distance ; shouldSpawn() will check if the spawner should start spawning critters, wait a bit or do nothing iSpawnedCritterCount = 0 iDeadCritterCount = 0 isSpawning = false shouldTryAgain = true if shouldSpawn() SpawnABatchOfCritters() elseif shouldTryAgain ;randomize the update time so we're less likely to have synchronized spawners asking for updates RegisterForSingleUpdate(fCheckPlayerDistanceTime * Randomfloat(1.0,1.5)) endif endEVENT event OnUpdate() VanillaLoopBreak() if (iSpawnedCritterCount < iMaxCritterCount*10) if shouldSpawn() SpawnABatchOfCritters() elseif shouldTryAgain RegisterForSingleUpdate(fCheckPlayerDistanceTime + extraWaitTime ) endif endif endEvent EVENT onUnload() shouldTryAgain = false UnregisterForUpdate() endEVENT EVENT onCellDetach() shouldTryAgain = false UnregisterForUpdate() endEVENT Function SpawnABatchOfCritters() VanillaLoopBreak() ; Important note here, even if the instance locking this thread gets dumped midway leaving the thread locked ; this wouldn't leave threads wait()ing in the stacks, they'll just keep reregistering every iSpawnedCritterCount ; and just won't spawn anything, the whole thing will be fixed when the spawner is unloaded and loaded again if (! isSpawning && iSpawnedCritterCount < iMaxCritterCount*10) isSpawning = true ;if not using the Fixed critter script there's no way to prevent over-reported critter deaths, but we'll cap spawns anyway to the max value if (iDeadCritterCount > iSpawnedCritterCount) iDeadCritterCount = iSpawnedCritterCount endIf int spawnAttempts = iMaxCritterCount - iSpawnedCritterCount + iDeadCritterCount ;limiting amount of respawns by distance if (iDeadCritterCount>0 && bReducedRespawn) spawnAttempts = 1 endif while (spawnAttempts>0) if (SpawnCritterAtRef(self)) iSpawnedCritterCount += 1 endif spawnAttempts -=1 endWhile isSpawning = false if (iMaxCritterCount - iSpawnedCritterCount + iDeadCritterCount>0) ;we couldn't spawn enough critters, or the player is currently killing them : try a bit later QueueAdditionalSpawns() endIf endIf endFunction function QueueAdditionalSpawns() VanillaLoopBreak() if (!isSpawning && iSpawnedCritterCount < iMaxCritterCount*10) UnregisterForUpdate() RegisterForSingleUpdate(1+ (1+iSpawnedCritterCount) * iRespawnDelay ) endif endFunction ; Called by critters when they die Event OnCritterDied() VanillaLoopBreak() if iDeadCritterCount < iSpawnedCritterCount iDeadCritterCount += 1 else ;iDeadCritterCount is overflowing for some reason ;increase the spawned Critter count instead so the queue keeps getting postponed until the script shuts down iSpawnedCritterCount = iDeadCritterCount Debug.Trace("CritterSpawn : iDeadCritterCount overflowing " + iDeadCritterCount); endif ; a critter died, if we're not currently spawning, try to refresh the queue or push back the delay QueueAdditionalSpawns() endEvent bool Function SpawnCritterAtRef(ObjectReference arSpawnRef) if VanillaLoopBreak() return false endif ; Pick a random critter type Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator if critterType Critter critty = arSpawnRef.PlaceAtMe(critterType, 1, false, true) as Critter if critty critty.SetInitialSpawnerProperties(fLeashLength, fLeashHeight, fLeashDepth, fMaxPlayerDistance + fLeashLength, self) return true endif else Debug.Trace("CritterSpawn :" + arSpawnRef + " attempted to spawn a bad critter type, check the contents of " + CritterTypes ); return false endif return false endFunction float Function GetPlayerDistance() ; Caches player reference too if VanillaLoopBreak() return fMaxPlayerDistance + 1 endif if !PlayerRef PlayerRef = Game.GetPlayer() endif return PlayerRef.GetDistance(self) endFunction bool Function ShouldSpawn() if VanillaLoopBreak() return false endif if (iSpawnedCritterCount >= iMaxCritterCount*10) shouldTryAgain = false return false endif if (!bAllowRespawn && (iSpawnedCritterCount >= iMaxCritterCount)) shouldTryAgain = false return false endif if ParentCell != none float distance = GetPlayerDistance() if !self.is3dLoaded() || ( distance > fMaxPlayerDistance) extraWaitTime = 2.0 return false else if (bReducedRespawn && iDeadCritterCount > 0 && distance <= fMaxPlayerDistance*0.75 ) if (Randomfloat(1 )>0.99 && !PlayerRef.HasLos(self)) extraWaitTime = 0.0 else extraWaitTime = 2.0 return false endif else extraWaitTime = 0.0 endif endIf return IsActiveTime() && CustomCheck() else shouldTryAgain = false return false endif endFunction function SetExtraWaitTime(float t) extraWaitTime = t endFunction function ClearExtraWaitTime() extraWaitTime = 0 endFunction ;Custom method to override in custom scripts bool Function CustomCheck() return true endFunction bool Function IsActiveTime() if VanillaLoopBreak() return false endif bool binTimeRange = (fEndSpawnTime != fStartSpawnTime) ;dont bother reading or checking for Gamehour if not timeframe is set if (binTimeRange) if GameHour float GameHourf = GameHour.GetValue() if (fEndSpawnTime >= fStartSpawnTime) binTimeRange = (GameHourf >= fStartSpawnTime) && (GameHourf < fEndSpawnTime) else binTimeRange = (GameHourf >= fStartSpawnTime) || (GameHourf < fEndSpawnTime) endIf else shouldTryAgain = false ;spawner not set up properly, stop it Debug.Trace("CritterSpawn :" + self + " spawner not set up properly, has a timerane while missing the GameHourf property"); return false endif endif if (binTimeRange) if (bSpawnInPrecipitation) ;dont check for weather condition if not needed return true; else Weather W = Weather.GetCurrentWeather() return !W || (W.GetClassification() < 2) endif endif return false endFunction