Hey all! I've been going a little deeper with Papyrus, and have been doing some experiments and add-ons to learn a bit more. I have a bunch of NPC adding mods, and things like Birds of Skyrim (not SkyBirds) that populate the towns, but I was always annoyed that the towns were bustling at 2am, so I made a very simple script to attach to actors to disable them during certain hours.
It effectively uses the "light switch" method that is on the CreationKit wiki, which uses states, and registers for a single update at game time when these NPCs should turn back on. I added a little bit of line-of-sight code, so if I am looking at or interacting with the NPC, they won't just disappear in front of me. It works really well, and it's awesome to see birds go away to roost. Towns are nice and desolate at night like they should be! It even works well when you're running around a town when the time switch hits; you'll walk past a group of NPCs, look around, go back and they're gone. It's fluid and not jarring at all. That LoS code really helped.
Anyhow, it's a very small, simple script, but because I did it quick-and-dirty, it's attached directly to every NPC I want this functionality run on. That means there are something like 75 instances currently. Because these are affixed directly to instanced NPCs, there is a ceiling threshold of probably 120 instances that can ever happen, give or take. Zero performance problems so far but I'm looking for a cleaner method.
METHOD 1: Use markers and parent the NPCs enable/disable state to it, and only run the script on the marker.
PROS: Dramatically reduced script instances (1 or 2 per town)
CONS: Cannot run the line-of-site code, since the marker will be acting like a switch, and doesn't have direct access to the individual NPCs (right?)
METHOD 2: Use a quest, aliases, which I still need to fully understand in the context of filling/running on multiple aliases.
Another idea I had is to use formlists with all of the NPC IDs, run the code on the marker, but loop over the formlist and use the condition checks for line of sight, etc there, that way I have more control.
Anyway, just looking for a better long-term design that is easily re-usable. Right now I can just plop this script onto any NPC in any ESP and it runs perfect, but I'm wary of having 200 instances running.
Here's the code, don't mind my silly self-comments
Thank you!
riptname DisableActorTimeOfDay extends Actor {Generic NPC disabler during specified time of day, basically like a light switch}; The times are piped in as script properties; Default is go away at 10pm, come back at 7amfloat Property OffTime = 22.0 autofloat Property OnTime = 7.0 auto float Function GetCurrentHourOfDay() global float Time = Utility.GetCurrentGameTime() Time -= Math.Floor(Time) ; Remove "previous in-game days passed" bit Time *= 24 ; Convert from fraction of a day to number of hours Return Time EndFunction Function RegisterForSingleUpdateGameTimeAt(float GameTime) float CurrentTime = GetCurrentHourOfDay() If (GameTime < CurrentTime) GameTime += 24 EndIf RegisterForSingleUpdateGameTime(GameTime - CurrentTime) EndFunction;-------- implement later;Function CheckDeadAndUnregisterForUpdate() ;If (Self.IsDead()) ;UnregisterForUpdate(); ;EndIf;EndFunction;--------Event OnInit() If (GetCurrentHourOfDay() > OnTime) GoToState("On") Else GoToState("Off") EndIf EndEvent State Off Event OnBeginState() ;Only disable if the NPC is NOT in light of sight of the player, OR in combat (would be weird if a fighting traveller vanished) ;Problem here is that any player in the line of sight won't be updated until the next update, which means if I'm running through town at 9p on the dot, all those NPCs will stay there all night ;OPTIONS: Can I do a temporary check for when 3D is loaded and disable them then as a temp measure?... And if that's the case doesn't it render the OnUpdate code obsolete? They will appear then vanish, nah. If (!Game.GetPlayer().HasLOS(Self) && !(Self).IsInCombat()) Disable() EndIf ;Don't register for update on dead actors If (!(Self).IsDead()) RegisterForSingleUpdateGameTimeAt(OnTime) EndIf EndEvent Event OnUpdateGameTime() GoToState("On") EndEvent EndState State On Event OnBeginState() ;Enable just in case there's another mod or something that places the body somewhere? Enable() If (!(Self).IsDead()) RegisterForSingleUpdateGameTimeAt(OffTime) EndIf EndEvent Event OnUpdateGameTime() GoToState("Off") EndEvent EndState