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 Better Dialogue Controls by ecirbaf
Unofficial Enderal Port (fs.dll) by Hishutup and FelesNoctis Unofficial Enderal Port (fs.dll) by Hishutup and FelesNoctis
Shorter Grass by Fhaarkas Shorter Grass by Fhaarkas
CritterSpawn Congestion Fix by Excinerus
Ghost Item Bug Fix for SkyUI by EdmanSA Ghost Item Bug Fix for SkyUI by EdmanSA
Drunk Sinking Head Idle Fix by KnightRangersGuild Drunk Sinking Head Idle Fix by KnightRangersGuild
Book Covers Skyrim by DanielCoffey Book Covers Skyrim by DanielCoffey

Binary file not shown.

View File

@ -1,5 +1,4 @@
scriptName CritterSpawn extends ObjectReference scriptName CritterSpawn extends ObjectReference
{MODIFIED BY STEVE40 and USKP}
import Critter import Critter
import Utility import Utility
@ -42,189 +41,292 @@ float property fLeashOverride auto
bool property bSpawnInPrecipitation auto bool property bSpawnInPrecipitation auto
{Should this critter spawn in rain/snow? DEFAULT: FALSE} {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) ; Constants (shouldn't need to modify these)
;---------------------------------------------- ;----------------------------------------------
float fCheckPlayerDistanceTime = 2.0
;---------------------------------------------- ;----------------------------------------------
; Variables to keep track of spawned critters ; 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.") int property iCurrentCritterCount = 0 auto hidden ; not used, set to -1 to break vanilla spawner while loops
recursions = 0 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 endif
return _ParentCell
endFunction
endproperty
; set our control bool to start the loop
bLooping = TRUE
while bLooping bool Function VanillaLoopBreak()
if bPrintDebug
recursions += 1 if (bLooping || iCurrentCritterCount>0)
; debug.trace("spawner " + self + " while loop #" + recursions) ; 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 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))
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 endif
endWhile
endEVENT endEVENT
event OnUpdate()
VanillaLoopBreak()
if (iSpawnedCritterCount < iMaxCritterCount*10)
if shouldSpawn()
SpawnABatchOfCritters()
elseif shouldTryAgain
RegisterForSingleUpdate(fCheckPlayerDistanceTime + extraWaitTime )
endif
endif
endEvent
EVENT onUnload() EVENT onUnload()
; when our 3D unloads, stop looping until loaded again. shouldTryAgain = false
bLooping = FALSE UnregisterForUpdate()
; ; debug.TraceConditional("spawner " + self + " unloading due to onUnload() EVENT.", bPrintDebug)
endEVENT endEVENT
EVENT onCellDetach() EVENT onCellDetach()
bLooping = FALSE shouldTryAgain = false
; ; debug.TraceConditional("spawner " + self + " unloading due to onCellDetach() EVENT.", bPrintDebug) UnregisterForUpdate()
endEVENT endEVENT
Function SpawnInitialCritterBatch() Function SpawnABatchOfCritters()
; How many do we need to spawn? VanillaLoopBreak()
int icrittersToSpawn = iMaxCritterCount - iCurrentCritterCount ; 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 if (! isSpawning && iSpawnedCritterCount < iMaxCritterCount*10)
int i = 0; isSpawning = true
while (i < icrittersToSpawn)
; Create one critter at a time
SpawnCritter()
; Wait a bit before the next spawn
;Wait(fFastSpawnInterval)
; Next ;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
i = i + 1 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 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 endFunction
; Called by critters when they die ; Called by critters when they die
Event OnCritterDied() Event OnCritterDied()
; Decrement current critter count, next time OnUpdate VanillaLoopBreak()
; gets called, we'll spawn a new one if iDeadCritterCount < iSpawnedCritterCount
if iCurrentCritterCount > 0 iDeadCritterCount += 1
iCurrentCritterCount -= 1; faster [USKP 2.0.1] else
elseif iCurrentCritterCount < 0 ;iDeadCritterCount is overflowing for some reason
; iCurrentCritterCount must be in the negatives. Something is up, but for now increment towards zero ;increase the spawned Critter count instead so the queue keeps getting postponed until the script shuts down
iCurrentCritterCount = 0; earler saves with failed deletions [USKP 2.0.1] iSpawnedCritterCount = iDeadCritterCount
Debug.Trace("CritterSpawn : iDeadCritterCount overflowing " + iDeadCritterCount);
endif endif
; a critter died, if we're not currently spawning, try to refresh the queue or push back the delay
QueueAdditionalSpawns()
endEvent 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) bool Function SpawnCritterAtRef(ObjectReference arSpawnRef)
if VanillaLoopBreak()
return false
endif
; Pick a random critter type ; Pick a random critter type
Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator
if critterType == none if critterType
; STEVE40+USKP extra check 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 return false
endif endif
; Create the critter and cast it to the critter base class return false
ObjectReference critterRef = arSpawnRef.PlaceAtMe(critterType, 1, false, true)
Critter thecritter = critterRef as Critter
if thecritter == none endFunction
; STEVE40+USKP extra check
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 return false
endif endif
; Set initial variables on the critter if (!bAllowRespawn && (iSpawnedCritterCount >= iMaxCritterCount))
; ; Debug.TraceConditional("Spawner " + self + " is creating Critter " + thecritter, bPrintDebug); shouldTryAgain = false
thecritter.SetInitialSpawnerProperties(fLeashLength, fLeashHeight, fLeashDepth, fMaxPlayerDistance + fLeashLength, self) return false
endif
; Increment count [USKP 2.0.1] if ParentCell != none
iCurrentCritterCount += 1 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 return true
endFunction endFunction
; Utility method that returns the player's distance bool Function IsActiveTime()
float Function GetPlayerDistance() if VanillaLoopBreak()
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 return false
endif endif
endFunction bool binTimeRange = (fEndSpawnTime != fStartSpawnTime) ;dont bother reading or checking for Gamehour if not timeframe is set
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 (binTimeRange)
if GameHour
float GameHourf = GameHour.GetValue()
if (fEndSpawnTime >= fStartSpawnTime) if (fEndSpawnTime >= fStartSpawnTime)
binTimeRange = (GameHour.GetValue() >= fStartSpawnTime) && (GameHour.GetValue() < fEndSpawnTime) binTimeRange = (GameHourf >= fStartSpawnTime) && (GameHourf < fEndSpawnTime)
else else
binTimeRange = (GameHour.GetValue() >= fStartSpawnTime) || (GameHour.GetValue() < fEndSpawnTime) binTimeRange = (GameHourf >= fStartSpawnTime) || (GameHourf < fEndSpawnTime)
endIf endIf
return binTimeRange && ((Weather.GetCurrentWeather() == none) || \ else
(Weather.GetCurrentWeather().GetClassification() < 2) || \ shouldTryAgain = false ;spawner not set up properly, stop it
(bSpawnInPrecipitation == TRUE)) 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 endFunction