1
Fork 0

Added CritterSpawn Congestion Fix 1.52 by Excinerus

development
Eddoursul 7 months ago
parent ea53444dbe
commit 278c4cfefd
  1. 1
      Enderal Credits.txt
  2. BIN
      scripts/critterspawn.pex
  3. 384
      source/scripts/critterspawn.psc

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

@ -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 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
; Do initial stuff when my 3D has loaded up. float extraWaitTime = 0.0 ; extra time in seconds before trying again
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? float property fCheckPlayerDistanceTime = 2.0 auto hidden
; GotoState("WaitingForPlayer") int property iSpawnedCritterCount = 0 auto hidden
; - JOEL REFACTOR - also no longer need to update int property iDeadCritterCount = 0 auto hidden
; RegisterForSingleUpdate(fCheckPlayerDistanceTime) int property iRespawnDelay = 6 auto hidden
if bPrintDebug == TRUE
; debug.trace("spawner " + self + " loaded.") bool property isSpawning = false auto hidden
recursions = 0 bool property shouldTryAgain = false auto hidden
endif actor property PlayerRef auto
; set our control bool to start the loop
bLooping = TRUE bool bPrintDebug = FALSE
Cell _ParentCell
while bLooping
if bPrintDebug Cell property ParentCell
recursions += 1 Cell function get()
; debug.trace("spawner " + self + " while loop #" + recursions) if !_ParentCell
_ParentCell = self.GetParentCell()
endif 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() Debug.Trace("CritterSpawn : Runaway Spawner warning :" + self);
; wait a bit, then see if the player is close again. return true
; Removing this to eliminate some TPLOG spam endif
; ; debug.TraceConditional("player not yet near spawner " + self, bPrintDebug) return false
utility.wait(fCheckPlayerDistanceTime) EndFunction
else Function SpawnInitialCritterBatch()
; ; debug.TraceConditional("spawner " + self + " ready to spawn!!!", bPrintDebug) VanillaLoopBreak()
; player must be nearby - spawn our initial critters endFunction
; don't follow up as we no longer wish to re-generate new critters until the player leaves entirely
spawnInitialCritterBatch()
bLooping = FALSE
endif
endWhile
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 endEVENT
EVENT onUnload() event OnUpdate()
; when our 3D unloads, stop looping until loaded again.
bLooping = FALSE VanillaLoopBreak()
; ; debug.TraceConditional("spawner " + self + " unloading due to onUnload() EVENT.", bPrintDebug) if (iSpawnedCritterCount < iMaxCritterCount*10)
if shouldSpawn()
SpawnABatchOfCritters()
elseif shouldTryAgain
RegisterForSingleUpdate(fCheckPlayerDistanceTime + extraWaitTime )
endif
endif
endEvent
EVENT onUnload()
shouldTryAgain = false
UnregisterForUpdate()
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() ;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)
; Wait a bit before the next spawn iDeadCritterCount = iSpawnedCritterCount
;Wait(fFastSpawnInterval) endIf
int spawnAttempts = iMaxCritterCount - iSpawnedCritterCount + iDeadCritterCount
; Next ;limiting amount of respawns by distance
i = i + 1 if (iDeadCritterCount>0 && bReducedRespawn)
endWhile 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 endFunction
; Called by critters when they die
Event OnCritterDied() function QueueAdditionalSpawns()
; Decrement current critter count, next time OnUpdate VanillaLoopBreak()
; gets called, we'll spawn a new one if (!isSpawning && iSpawnedCritterCount < iMaxCritterCount*10)
if iCurrentCritterCount > 0 UnregisterForUpdate()
iCurrentCritterCount -= 1; faster [USKP 2.0.1] RegisterForSingleUpdate(1+ (1+iSpawnedCritterCount) * iRespawnDelay )
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]
endif endif
endEvent endFunction
; Spawns one Critter ; Called by critters when they die
bool Function SpawnCritter() Event OnCritterDied()
if (iCurrentCritterCount < iMaxCritterCount) && (iCurrentCritterCount >= 0) VanillaLoopBreak()
; Go ahead with the actual spawn if iDeadCritterCount < iSpawnedCritterCount
return SpawnCritterAtRef(self) iDeadCritterCount += 1
;/// [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 else
; debug.trace("("+self+") SpawnCritter() failed because iCurrentCritterCount is "+iCurrentCritterCount) ;iDeadCritterCount is overflowing for some reason
; ;debug.trace("("+self+") iMaxCritterCount: "+iMaxCritterCount) ;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 endif
; a critter died, if we're not currently spawning, try to refresh the queue or push back the delay
return False ; [USKP 1.3.1] to help eliminate log spam and potential long term save bloating. QueueAdditionalSpawns()
endFunction endEvent
; 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)
; Pick a random critter type
Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator
if critterType == none if VanillaLoopBreak()
; STEVE40+USKP extra check
return false return false
endif endif
; Pick a random critter type
; Create the critter and cast it to the critter base class Activator critterType = CritterTypes.GetAt(RandomInt(0, CritterTypes.GetSize() - 1)) as Activator
ObjectReference critterRef = arSpawnRef.PlaceAtMe(critterType, 1, false, true)
Critter thecritter = critterRef as Critter
if thecritter == 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
return false
endFunction
; Set initial variables on the critter
; ; Debug.TraceConditional("Spawner " + self + " is creating Critter " + thecritter, bPrintDebug);
thecritter.SetInitialSpawnerProperties(fLeashLength, fLeashHeight, fLeashDepth, fMaxPlayerDistance + fLeashLength, self)
; Increment count [USKP 2.0.1] float Function GetPlayerDistance() ; Caches player reference too
iCurrentCritterCount += 1
return true
endFunction
; Utility method that returns the player's distance if VanillaLoopBreak()
float Function GetPlayerDistance() return fMaxPlayerDistance + 1
return Game.GetPlayer().GetDistance(self) endif
if !PlayerRef
PlayerRef = Game.GetPlayer()
endif
return PlayerRef.GetDistance(self)
endFunction endFunction
; Utility method that tells the spawner whether it should spawn critters
bool Function ShouldSpawn() 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 if VanillaLoopBreak()
; NOTE - is3dLoaded dumps an error when the 3d is not loaded, but the function still returns false which should return false
; set bLooping to false and jump out of the bLooping While, at which point no additional errors will be thrown endif
; GetParentCell check first helps avoid error log in exteriors? [USKP 2.0.1] if (iSpawnedCritterCount >= iMaxCritterCount*10)
if self.GetParentCell() && self.is3dLoaded() shouldTryAgain = false
if !(GetPlayerDistance() <= fMaxPlayerDistance) 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 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 endIf
; Otherwise, base value on time of day (no handling of wrap around though...) return IsActiveTime() && CustomCheck()
return IsActiveTime() else
else ;if the 3d is not loaded jump out of the looped state. Just an extra safety measure. shouldTryAgain = false
; ;debug.Trace(self + ": should be setting bLooping to False")
bLooping = FALSE
return false return false
endif endif
endFunction endFunction
bool Function IsActiveTime() function SetExtraWaitTime(float t)
bool binTimeRange = false extraWaitTime = t
endFunction
function ClearExtraWaitTime()
extraWaitTime = 0
endFunction
; 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) ;Custom method to override in custom scripts
binTimeRange = (GameHour.GetValue() >= fStartSpawnTime) && (GameHour.GetValue() < fEndSpawnTime) bool Function CustomCheck()
else return true
binTimeRange = (GameHour.GetValue() >= fStartSpawnTime) || (GameHour.GetValue() < fEndSpawnTime) endFunction
endIf
return binTimeRange && ((Weather.GetCurrentWeather() == none) || \ bool Function IsActiveTime()
(Weather.GetCurrentWeather().GetClassification() < 2) || \ if VanillaLoopBreak()
(bSpawnInPrecipitation == TRUE)) 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 endFunction

Loading…
Cancel
Save