Added CritterSpawn Congestion Fix 1.52 by Excinerus
This commit is contained in:
parent
ea53444dbe
commit
278c4cfefd
@ -649,6 +649,7 @@ CommonLibSSE-NG by Ryan McKenzie, powerofthree, Charmed Baryon, and others
|
||||
Better Dialogue Controls by ecirbaf
|
||||
Unofficial Enderal Port (fs.dll) by Hishutup and FelesNoctis
|
||||
Shorter Grass by Fhaarkas
|
||||
CritterSpawn Congestion Fix by Excinerus
|
||||
Ghost Item Bug Fix for SkyUI by EdmanSA
|
||||
Drunk Sinking Head Idle Fix by KnightRangersGuild
|
||||
Book Covers Skyrim by DanielCoffey
|
||||
|
Binary file not shown.
@ -1,5 +1,4 @@
|
||||
scriptName CritterSpawn extends ObjectReference
|
||||
{MODIFIED BY STEVE40 and USKP}
|
||||
|
||||
import Critter
|
||||
import Utility
|
||||
@ -42,189 +41,292 @@ float property fLeashOverride auto
|
||||
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)
|
||||
;----------------------------------------------
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
; set our control bool to start the loop
|
||||
bLooping = TRUE
|
||||
|
||||
while bLooping
|
||||
if bPrintDebug
|
||||
recursions += 1
|
||||
; debug.trace("spawner " + self + " while loop #" + recursions)
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
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 OnUpdate()
|
||||
|
||||
VanillaLoopBreak()
|
||||
if (iSpawnedCritterCount < iMaxCritterCount*10)
|
||||
if shouldSpawn()
|
||||
SpawnABatchOfCritters()
|
||||
elseif shouldTryAgain
|
||||
RegisterForSingleUpdate(fCheckPlayerDistanceTime + extraWaitTime )
|
||||
endif
|
||||
endif
|
||||
endEvent
|
||||
|
||||
|
||||
|
||||
|
||||
EVENT onUnload()
|
||||
; when our 3D unloads, stop looping until loaded again.
|
||||
bLooping = FALSE
|
||||
; ; debug.TraceConditional("spawner " + self + " unloading due to onUnload() EVENT.", bPrintDebug)
|
||||
shouldTryAgain = false
|
||||
UnregisterForUpdate()
|
||||
endEVENT
|
||||
|
||||
EVENT onCellDetach()
|
||||
bLooping = FALSE
|
||||
; ; debug.TraceConditional("spawner " + self + " unloading due to onCellDetach() EVENT.", bPrintDebug)
|
||||
shouldTryAgain = false
|
||||
UnregisterForUpdate()
|
||||
endEVENT
|
||||
|
||||
Function SpawnInitialCritterBatch()
|
||||
; How many do we need to spawn?
|
||||
int icrittersToSpawn = iMaxCritterCount - iCurrentCritterCount
|
||||
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
|
||||
|
||||
; Create that many critters
|
||||
int i = 0;
|
||||
while (i < icrittersToSpawn)
|
||||
; Create one critter at a time
|
||||
SpawnCritter()
|
||||
if (! isSpawning && iSpawnedCritterCount < iMaxCritterCount*10)
|
||||
isSpawning = true
|
||||
|
||||
; Wait a bit before the next spawn
|
||||
;Wait(fFastSpawnInterval)
|
||||
|
||||
; Next
|
||||
i = i + 1
|
||||
endWhile
|
||||
;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)
|
||||
if (SpawnCritterAtRef(self))
|
||||
iSpawnedCritterCount += 1
|
||||
endif
|
||||
spawnAttempts -=1
|
||||
endWhile
|
||||
|
||||
isSpawning = false
|
||||
if (iMaxCritterCount - iSpawnedCritterCount + iDeadCritterCount)
|
||||
;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()
|
||||
; 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]
|
||||
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
|
||||
|
||||
; 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)
|
||||
|
||||
if VanillaLoopBreak()
|
||||
return false
|
||||
endif
|
||||
; Pick a random critter type
|
||||
Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator
|
||||
|
||||
if critterType == none
|
||||
; STEVE40+USKP extra check
|
||||
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
|
||||
|
||||
; Create the critter and cast it to the critter base class
|
||||
ObjectReference critterRef = arSpawnRef.PlaceAtMe(critterType, 1, false, true)
|
||||
Critter thecritter = critterRef as Critter
|
||||
return false
|
||||
|
||||
if thecritter == none
|
||||
; STEVE40+USKP extra check
|
||||
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
|
||||
|
||||
; Set initial variables on the critter
|
||||
; ; Debug.TraceConditional("Spawner " + self + " is creating Critter " + thecritter, bPrintDebug);
|
||||
thecritter.SetInitialSpawnerProperties(fLeashLength, fLeashHeight, fLeashDepth, fMaxPlayerDistance + fLeashLength, self)
|
||||
if (!bAllowRespawn && (iSpawnedCritterCount >= iMaxCritterCount))
|
||||
shouldTryAgain = false
|
||||
return false
|
||||
endif
|
||||
|
||||
; Increment count [USKP 2.0.1]
|
||||
iCurrentCritterCount += 1
|
||||
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
|
||||
|
||||
; 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
|
||||
bool Function IsActiveTime()
|
||||
if VanillaLoopBreak()
|
||||
return false
|
||||
endif
|
||||
endFunction
|
||||
bool binTimeRange = (fEndSpawnTime != fStartSpawnTime) ;dont bother reading or checking for Gamehour if not timeframe is set
|
||||
|
||||
bool Function IsActiveTime()
|
||||
bool binTimeRange = false
|
||||
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
|
||||
|
||||
; 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
|
||||
|
Loading…
Reference in New Issue
Block a user