2023-10-25 21:34:09 +00:00
|
|
|
scriptName CritterSpawn extends ObjectReference
|
2021-10-05 22:22:24 +00:00
|
|
|
|
|
|
|
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}
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
|
|
|
|
Bool property bAllowRespawn = true auto
|
|
|
|
Bool property bReducedRespawn = true auto
|
2021-10-05 22:22:24 +00:00
|
|
|
;----------------------------------------------
|
|
|
|
; Constants (shouldn't need to modify these)
|
|
|
|
;----------------------------------------------
|
|
|
|
|
|
|
|
;----------------------------------------------
|
|
|
|
; Variables to keep track of spawned critters
|
|
|
|
;----------------------------------------------
|
2023-10-25 21:34:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
2021-10-05 22:22:24 +00:00
|
|
|
endif
|
2023-10-25 21:34:09 +00:00
|
|
|
return _ParentCell
|
|
|
|
endFunction
|
|
|
|
endproperty
|
|
|
|
|
|
|
|
|
|
|
|
bool Function VanillaLoopBreak()
|
|
|
|
|
2024-03-27 18:16:13 +00:00
|
|
|
if (bLooping || iCurrentCritterCount != 0)
|
2023-10-25 21:34:09 +00:00
|
|
|
; breaking OnCellAttach runaway loop in baked vanilla functions
|
|
|
|
bLooping = false
|
|
|
|
; breaking SpawnInitialCritterBatch runaway loop in baked vanilla and uskp functions
|
|
|
|
iCurrentCritterCount = 0
|
2021-10-05 22:22:24 +00:00
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
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
|
2021-10-05 22:22:24 +00:00
|
|
|
endEVENT
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
event OnUpdate()
|
|
|
|
|
|
|
|
VanillaLoopBreak()
|
|
|
|
if (iSpawnedCritterCount < iMaxCritterCount*10)
|
|
|
|
if shouldSpawn()
|
|
|
|
SpawnABatchOfCritters()
|
|
|
|
elseif shouldTryAgain
|
|
|
|
RegisterForSingleUpdate(fCheckPlayerDistanceTime + extraWaitTime )
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endEvent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EVENT onUnload()
|
|
|
|
shouldTryAgain = false
|
|
|
|
UnregisterForUpdate()
|
2021-10-05 22:22:24 +00:00
|
|
|
endEVENT
|
|
|
|
|
|
|
|
EVENT onCellDetach()
|
2023-10-25 21:34:09 +00:00
|
|
|
shouldTryAgain = false
|
|
|
|
UnregisterForUpdate()
|
2021-10-05 22:22:24 +00:00
|
|
|
endEVENT
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
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
|
2021-10-05 22:22:24 +00:00
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
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
|
2021-10-05 22:22:24 +00:00
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
;limiting amount of respawns by distance
|
|
|
|
if (iDeadCritterCount>0 && bReducedRespawn)
|
|
|
|
spawnAttempts = 1
|
|
|
|
endif
|
|
|
|
|
2024-03-27 18:16:13 +00:00
|
|
|
while (spawnAttempts>0)
|
2023-10-25 21:34:09 +00:00
|
|
|
if (SpawnCritterAtRef(self))
|
|
|
|
iSpawnedCritterCount += 1
|
|
|
|
endif
|
|
|
|
spawnAttempts -=1
|
|
|
|
endWhile
|
|
|
|
|
|
|
|
isSpawning = false
|
2024-03-27 18:16:13 +00:00
|
|
|
if (iMaxCritterCount - iSpawnedCritterCount + iDeadCritterCount>0)
|
2023-10-25 21:34:09 +00:00
|
|
|
;we couldn't spawn enough critters, or the player is currently killing them : try a bit later
|
|
|
|
QueueAdditionalSpawns()
|
|
|
|
endIf
|
|
|
|
endIf
|
2021-10-05 22:22:24 +00:00
|
|
|
endFunction
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
|
|
|
|
function QueueAdditionalSpawns()
|
|
|
|
VanillaLoopBreak()
|
|
|
|
if (!isSpawning && iSpawnedCritterCount < iMaxCritterCount*10)
|
|
|
|
UnregisterForUpdate()
|
|
|
|
RegisterForSingleUpdate(1+ (1+iSpawnedCritterCount) * iRespawnDelay )
|
2021-10-05 22:22:24 +00:00
|
|
|
endif
|
2023-10-25 21:34:09 +00:00
|
|
|
endFunction
|
2021-10-05 22:22:24 +00:00
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
; Called by critters when they die
|
|
|
|
Event OnCritterDied()
|
|
|
|
VanillaLoopBreak()
|
|
|
|
if iDeadCritterCount < iSpawnedCritterCount
|
|
|
|
iDeadCritterCount += 1
|
2021-10-05 22:22:24 +00:00
|
|
|
else
|
2023-10-25 21:34:09 +00:00
|
|
|
;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);
|
|
|
|
|
2021-10-05 22:22:24 +00:00
|
|
|
endif
|
2023-10-25 21:34:09 +00:00
|
|
|
; a critter died, if we're not currently spawning, try to refresh the queue or push back the delay
|
|
|
|
QueueAdditionalSpawns()
|
|
|
|
endEvent
|
2021-10-05 22:22:24 +00:00
|
|
|
|
|
|
|
bool Function SpawnCritterAtRef(ObjectReference arSpawnRef)
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
if VanillaLoopBreak()
|
2021-10-05 22:22:24 +00:00
|
|
|
return false
|
|
|
|
endif
|
2023-10-25 21:34:09 +00:00
|
|
|
; Pick a random critter type
|
|
|
|
Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator
|
2021-10-05 22:22:24 +00:00
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
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 );
|
2021-10-05 22:22:24 +00:00
|
|
|
return false
|
|
|
|
endif
|
2023-10-25 21:34:09 +00:00
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
endFunction
|
2021-10-05 22:22:24 +00:00
|
|
|
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
float Function GetPlayerDistance() ; Caches player reference too
|
2021-10-05 22:22:24 +00:00
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
if VanillaLoopBreak()
|
|
|
|
return fMaxPlayerDistance + 1
|
|
|
|
endif
|
|
|
|
if !PlayerRef
|
|
|
|
PlayerRef = Game.GetPlayer()
|
|
|
|
endif
|
|
|
|
return PlayerRef.GetDistance(self)
|
2021-10-05 22:22:24 +00:00
|
|
|
endFunction
|
|
|
|
|
|
|
|
bool Function ShouldSpawn()
|
2023-10-25 21:34:09 +00:00
|
|
|
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
|
2021-10-05 22:22:24 +00:00
|
|
|
return false
|
2023-10-25 21:34:09 +00:00
|
|
|
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
|
2021-10-05 22:22:24 +00:00
|
|
|
endIf
|
2023-10-25 21:34:09 +00:00
|
|
|
return IsActiveTime() && CustomCheck()
|
|
|
|
else
|
|
|
|
shouldTryAgain = false
|
2021-10-05 22:22:24 +00:00
|
|
|
return false
|
|
|
|
endif
|
|
|
|
endFunction
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
function SetExtraWaitTime(float t)
|
|
|
|
extraWaitTime = t
|
|
|
|
endFunction
|
|
|
|
function ClearExtraWaitTime()
|
|
|
|
extraWaitTime = 0
|
|
|
|
endFunction
|
2021-10-05 22:22:24 +00:00
|
|
|
|
|
|
|
|
2023-10-25 21:34:09 +00:00
|
|
|
;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
|
2021-10-05 22:22:24 +00:00
|
|
|
endFunction
|