Added CritterSpawn Congestion Fix 1.52 by Excinerus

This commit is contained in:
Eddoursul 2023-10-25 23:34:09 +02:00
parent ea53444dbe
commit 278c4cfefd
3 changed files with 249 additions and 146 deletions

View File

@ -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.

View File

@ -1,5 +1,4 @@
scriptName CritterSpawn extends ObjectReference
{MODIFIED BY STEVE40 and USKP}
scriptName CritterSpawn extends ObjectReference
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
endif
; set our control bool to start the loop
bLooping = TRUE
while bLooping
if bPrintDebug
recursions += 1
; debug.trace("spawner " + self + " while loop #" + recursions)
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
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
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 onUnload()
; when our 3D unloads, stop looping until loaded again.
bLooping = FALSE
; ; debug.TraceConditional("spawner " + self + " unloading due to onUnload() EVENT.", bPrintDebug)
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()
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()
; Wait a bit before the next spawn
;Wait(fFastSpawnInterval)
if (! isSpawning && iSpawnedCritterCount < iMaxCritterCount*10)
isSpawning = true
; 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]
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
; 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
if thecritter == none
; STEVE40+USKP extra check
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
; Set initial variables on the critter
; ; Debug.TraceConditional("Spawner " + self + " is creating Critter " + thecritter, bPrintDebug);
thecritter.SetInitialSpawnerProperties(fLeashLength, fLeashHeight, fLeashDepth, fMaxPlayerDistance + fLeashLength, self)
function SetExtraWaitTime(float t)
extraWaitTime = t
endFunction
function ClearExtraWaitTime()
extraWaitTime = 0
endFunction
; Increment count [USKP 2.0.1]
iCurrentCritterCount += 1
;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 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
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
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