Elleh Posted March 20, 2013 Author Share Posted March 20, 2013 Absolutely stumped. :blink: So I ended up making a completely seperate "timer" script attacted to the actor, used that global variable ect..... And it worked!...the first time. I threw in a few notifications to make sure everything was running. I summoned him, killed him, saw that my timer was doing it's job, waited 24 hrs, and got my "Recovered!" message. I also tested the spell itself, and it returned the "Still recovering!" message and would not do anyting until the timer stopped. Good. I double-checked my global variable in the console to make sure it was toggling on/off - it was. So everyting seems like it is working as it should. But if I summon him and then kill him a second time, nothing happens. At all. I didn't even see the "I died!" notification, which leads me to believe that the script wasn't firing at all for some reason. But why? And it puzzles me further, because the resurrection script works every time on dying without fail. Am I missing something here? Is putting two seperate scripts on an actor bad somehow? Could it be caused by something not script-related? timer Scriptname DremoraThrallDeathTimerScript extends Actor {Tracks the amount of time since Dremora Thrall's "death".} Float Property DaysToWait Auto Float Property DayOfDeath auto Hidden String Property RecoveryMessage auto GlobalVariable Property DremoraIsDead auto Auto State InitialDeath Event OnDying(Actor akKiller) DremoraIsDead.SetValue(1) DayOfDeath = utility.getCurrentGameTime() debug.Notification("I died!") GoToState("WaitingToRecover") EndEvent EndState State WaitingToRecover Event OnBeginState() If (DayOfDeath + DaystoWait) <= utility.getCurrentGameTime() GoToState("ReadyToSummon") Else GoToState("NotReadyToSummon") EndIf EndEvent EndState State NotReadyToSummon Event OnBeginState() Utility.Wait(5.0) debug.Notification("I am still dead.") GoToState("WaitingToRecover") EndEvent EndState State ReadyToSummon Event OnBeginState() DremoraIsDead.SetValue(0) debug.Notification(RecoveryMessage) EndEvent EndState summon Scriptname SummonDremoraThrallScript extends activemagiceffect {Summons Dremora Thrall in front of player.} Actor Property DremoraThrallRef Auto GlobalVariable Property DremoraIsDead Auto String Property FailureMessage Auto Event OnEffectStart(Actor akTarget, Actor akCaster) If (DremoraIsDead.GetValue()) debug.Notification(FailureMessage) Else Summon(akCaster, DremoraThrallRef) EndIf EndEvent ; GetFormFromFile below to enable 'Global' flag Function Summon(ObjectReference akSummoner = None, ObjectReference akSummon = None, Float afDistance = 150.0, Float afZOffset = 0.0, ObjectReference arPortal = None, Int aiStage = 0) Global While aiStage < 6 aiStage += 1 If aiStage == 1 ; Shroud summon with portal arPortal = akSummon.PlaceAtMe(Game.GetFormFromFile(0x0007CD55, "Skyrim.ESM")) ; SummonTargetFXActivator disables and deletes itself shortly after stage 5 ElseIf aiStage == 2 ; Disable Summon akSummon.Disable() ElseIf aiStage == 3 ; Move portal in front of summoner arPortal.MoveTo(akSummoner, Math.Sin(akSummoner.GetAngleZ()) * afDistance, Math.Cos(akSummoner.GetAngleZ()) * afDistance, afZOffset) ElseIf aiStage == 4 ; Move summon to portal akSummon.MoveTo(arPortal) ElseIf aiStage == 5 ; Enable summon as the portal dissipates akSummon.Enable() EndIf Utility.Wait(0.6) EndWhile EndFunction Link to comment Share on other sites More sharing options...
IsharaMeradin Posted March 20, 2013 Share Posted March 20, 2013 I'm stumped as much as you. Shot in the dark, drop the auto state on the timer script and leave it in the empty state. Maybe it's getting hung up in one of the states...? Link to comment Share on other sites More sharing options...
Elleh Posted March 20, 2013 Author Share Posted March 20, 2013 By the NINE! Finally. :O Pretty good "shot in the dark". Just taking the auto state out didn't work - resulted in the timer running every time, but he wouldn't teleport or resurrect. (Again, why??). But after trying several variations of mixing the timer script in with the teleport script - with and without auto state - I got one that worked. Whew. For inquiring minds XD : Scriptname DeathDremoraThrallScript extends Actor {Teleports Dremora Thrall to holding cell and resurrects him after death.} FormList Property MasterList auto FormList Property EquippedList auto ObjectReference Property HoldingCellMarker auto ObjectReference Property HoldingCellChest auto Activator Property banishFX auto Float Property DaysToWait Auto Float Property DayOfDeath auto Hidden String Property RecoveryMessage auto GlobalVariable Property DremoraIsDead auto Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) If (MasterList.HasForm(akBaseItem) == false) MasterList.AddForm(akBaseItem) EndIf EndEvent Event OnDying(Actor akKiller) DremoraIsDead.SetValue(1) DayOfDeath = utility.getCurrentGameTime() debug.Notification("I died!") ;move follower dead body to holding cell Utility.Wait(4.0) Self.PlaceAtMe(BanishFX) Utility.Wait(0.6) MoveTo(HoldingCellMarker) ;empty existing EquippedList EquippedList.Revert() ;get new equipped items list int MasterSize = MasterList.GetSize() int index = 0 While index < MasterSize Form Entry = MasterList.GetAt(index) If (Self.GetItemCount(Entry) == 0) ;DO NOTHING ElseIf (Self.GetItemCount(Entry) == 0) && (Self.IsEquipped(Entry) == True) EquippedList.AddForm(Entry) EndIf index += 1 EndWhile Utility.Wait(4.0) ;move all items to container for safe-keeping Self.RemoveAllItems(HoldingCellChest, True, True) Utility.Wait(2.0) ;resurrect follower Self.Resurrect() Utility.Wait(2.0) ;remove all items again so that no original items re-appear and there be duplicates etc... ;but this time the items get banished forever Self.RemoveAllItems() Utility.Wait(2.0) ;add the correct items back to the follower HoldingCellChest.RemoveAllItems(Self, True, True) ;re-equip the equipped items Int EquipSize = EquippedList.GetSize() Int Index2 = 0 While Index2 < EquipSize Form Entry2 = EquippedList.GetAt(Index2) Self.EquipItem(Entry2) Index2 += 1 EndWhile GoToState("WaitingToRecover") EndEvent State WaitingToRecover Event OnBeginState() If (DayOfDeath + DaystoWait) <= utility.getCurrentGameTime() GoToState("ReadyToSummon") Else GoToState("NotReadyToSummon") EndIf EndEvent EndState State NotReadyToSummon Event OnBeginState() Utility.Wait(5.0) debug.Notification("I am still dead.") GoToState("WaitingToRecover") EndEvent EndState State ReadyToSummon Event OnBeginState() DremoraIsDead.SetValue(0) debug.Notification(RecoveryMessage) EndEvent EndState Still have no idea what was going on there. Note to self: states are finicky. So that about does it! Need to deal with "hunting bow" still, but I can put up with just grabbing it out of his inventory for a while. (Note: took those items out of the quest alias, but they haven't dissapeared. 99% sure this is due to another mod later in the load order. Will need to experiment.) But for now..... I reeeeally just want to get back to tweaking textures and building my little dungeon for the book. So much more fun. Heh. And once again - thank you so much, Ishara! No way I would have been able to do this without you and kromey holding my hand. Scripts are haaaaaaaaaard. XD Link to comment Share on other sites More sharing options...
steve40 Posted March 20, 2013 Share Posted March 20, 2013 (edited) Using states like that is a poor way of timing an event. You should consider using RegisterForSingleUpdateGameTime. Every time you switch from one state to another and back you are creating a recursion, that is, your function calls are nesting:WaitingToRecover:OnBeginState->NotReadyToSummon:OnBeginState->WaitingToRecover:OnBeginState->NotReadyToSummon:OnBeginState-> looping over and over and over again every 5 seconds until EITHER the timer expires or you run out of "stack memory" (whichever happens first). Stack dumps are bad (stack buffer overflow), they will cause the game to freeze intermittently or stutter badly (maybe even crash), as well as massive spamming in the log. Whenever you call a function from another function, the script engine needs to "remember" the calling function so that it knows where to return to after a function ends or a "return" call is made. However, if the function keeps nesting (never returns, or makes too many function calls) until the stack memory gets exhausted, then the script engine has to purge (dump) that memory and won't be able to return. It's kinda like the script engine suffering from Alzheimer's, it will cause erratic game behavior.RegisterForSingleUpdateGameTime and RegisterForSingleUpdate both return immediately when called, and a new thread is created when their update event is called, so they do not create this nesting problem. Edited March 21, 2013 by steve40 Link to comment Share on other sites More sharing options...
IsharaMeradin Posted March 20, 2013 Share Posted March 20, 2013 Scriptname DeathDremoraThrallScript extends Actor {Teleports Dremora Thrall to holding cell and resurrects him after death.} FormList Property MasterList auto FormList Property EquippedList auto ObjectReference Property HoldingCellMarker auto ObjectReference Property HoldingCellChest auto Activator Property banishFX auto Float Property DaysToWait Auto Float Property DayOfDeath auto Hidden String Property RecoveryMessage auto GlobalVariable Property DremoraIsDead auto Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) If (MasterList.HasForm(akBaseItem) == false) MasterList.AddForm(akBaseItem) EndIf EndEvent Event OnDying(Actor akKiller) DremoraIsDead.SetValue(1) DayOfDeath = utility.getCurrentGameTime() debug.Notification("I died!") ;move follower dead body to holding cell Utility.Wait(4.0) Self.PlaceAtMe(BanishFX) Utility.Wait(0.6) MoveTo(HoldingCellMarker) ;empty existing EquippedList EquippedList.Revert() ;get new equipped items list int MasterSize = MasterList.GetSize() int index = 0 While index < MasterSize Form Entry = MasterList.GetAt(index) If (Self.GetItemCount(Entry) == 0) ;DO NOTHING ElseIf (Self.GetItemCount(Entry) >= 1) && (Self.IsEquipped(Entry) == True) EquippedList.AddForm(Entry) EndIf index += 1 EndWhile Utility.Wait(4.0) ;move all items to container for safe-keeping Self.RemoveAllItems(HoldingCellChest, True, True) Utility.Wait(2.0) ;resurrect follower Self.Resurrect() Utility.Wait(2.0) ;remove all items again so that no original items re-appear and there be duplicates etc... ;but this time the items get banished forever Self.RemoveAllItems() Utility.Wait(2.0) ;add the correct items back to the follower HoldingCellChest.RemoveAllItems(Self, True, True) ;re-equip the equipped items Int EquipSize = EquippedList.GetSize() Int Index2 = 0 While Index2 < EquipSize Form Entry2 = EquippedList.GetAt(Index2) Self.EquipItem(Entry2) Index2 += 1 EndWhile RegisterForSingleUpdate(5) EndEvent Event OnUpdate() If (DayOfDeath + DaystoWait) <= utility.getCurrentGameTime() Utility.Wait(5.0) debug.Notification("I am still dead.") Else DremoraIsDead.SetValue(0) debug.Notification(RecoveryMessage) EndIf RegisterForSingleUpdate(5) EndEvent Try this out. It combines what steve40 was talking about with update registrations and the game time checks which are more accurate than using a game time update registration for the specific length that you want to wait. Basically what it does is every 5 real world seconds it checks the calculated re-summon time, if enough time has passed, he can be summoned, otherwise he cannot be. Don't know why I didn't think of this before. Maybe cause I was too bent on pointing you to other scripts that have time calculations and let you learn from them. Oh and I fixed a typo where it was checking for items equipped. In your earlier example you had the count for the item being equal to zero & equipped. It can't be equipped if there isn't at least 1. Changed == 0 to >= 1 Link to comment Share on other sites More sharing options...
Elleh Posted March 20, 2013 Author Share Posted March 20, 2013 Stack dumps are bad (stack buffer overflow), they will cause the game to freeze intermittently or stutter badly (maybe even crash), as well as massive spamming in the log Ewwww.....Well okay. That doesn't sound good. Shows what I know....hmmmm.....Thanks for the insight steve40! I had actually looked over the assorted "OnUpdates", but decided - for whatever silly reason - that I couldn't use them. So I never actually tried. ^^; ahaha... bad, bad decision. Ishara, I tried that out as-is but it didn't quite work. A handful of seconds after he died, I kept getting "Ready to summon!" over and over at 5 second intervals. I think that's because if " (DayOfDeath + DaystoWait) <= utility.getCurrentGameTime() " it actually means he's alive. I eventually settled on this: Event OnDying(Actor akKiller) DremoraIsDead.SetValue(1) DayOfDeath = utility.getCurrentGameTime() debug.Notification("I died!") ;Insert stuff here RegisterForUpdate(5) EndEvent Event OnUpdate() If (DayOfDeath + DaystoWait) <= utility.getCurrentGameTime() UnregisterForUpdate() DremoraIsDead.SetValue(0) debug.Notification(RecoveryMessage) EndIF EndEvent Does that look okay? It seems to work fine, at least.I think this could work as well: Event OnDying(Actor akKiller) DremoraIsDead.SetValue(1) DayOfDeath = utility.getCurrentGameTime() debug.Notification("I died!") ;Insert stuff here RegisterForSingleUpdate(5) EndEvent Event OnUpdate() If (DayOfDeath + DaystoWait) <= utility.getCurrentGameTime() DremoraIsDead.SetValue(0) debug.Notification(RecoveryMessage) Else RegisterForSingleUpdate(5) EndIF EndEvent I think they ought to do the same thing, but maybe one has an advantage over the other? I duuno. Link to comment Share on other sites More sharing options...
IsharaMeradin Posted March 21, 2013 Share Posted March 21, 2013 I had my logic off. For some reason I thought that if the calculated time to recovery was less than the current time that it wasn't good to go. It just needs to be flipped about. But to answer your question about the two snippets you posted. Both would work and the second is a better setup than what I had proposed with regards to the placement of the update re-registration. Link to comment Share on other sites More sharing options...
Elleh Posted March 21, 2013 Author Share Posted March 21, 2013 Eh, just threw me for a loop for a while. But it's all good now. Now that I look at it a bit more, the second might be good for a longer event that still needed to be run frequently. The wiki mentions that OnUpdate can "pile up on itself", if the update occurs before the event has a chance to work itself out. I guess re-registering like that keeps the event from jumping the gun. I think I'm pretty safe by giving what I have a 5 second buffer, but it might be wise to use the second option for extra security. Never know. Ahhhh well. It feels good to have that all sorted out, at least. :) Thanks again for all your help! Link to comment Share on other sites More sharing options...
schlusmans Posted April 18, 2013 Share Posted April 18, 2013 Wow, this is so great ! I'v been struggling for DAYS with this "NPC's in underwear" problem.I felt I needed resurrecting powers in Skyrim, after the sweet old couple that lives at Salvius farm accidentally got killed in a dragon attack as I was passing by. I used the console cheat to resurrect them, and they happily trotted off to their garden, peacefully working together again. Bliss... :-)So I thought it would be nice to have a spell to do just that. (what's the use of being the Dragonborn if you can't even save people ?) Actually the spell works quite nicely now, it will even resurrect enemies you had no intention to kill (plus make them run away from you for 30 seconds, so you don't have to kill them again, and can safely get out of the premises).Then I thought it could also be useful to resurrect followers, and that's where I ran into the "underwear" problem. Actually I did manage to have followers keep their complete inventory (thanks to the "switch chest" trick), but they won't equip their outfit, until I walk into a city with them, where they will finally change into more fitting clothing. Somewhere I read I could use Reset() and MoveTo the player coordinates to simulate entering a city, but that doesn't seem to work either (?). So now I feel the solution is at hand, thanks to Formlists for equipped gear!There's just one problem I can't work out: the code that IsharaMeradin proposes here seems meant to be attached to a specific actor. But there are plenty of potential followers in the game. So must I put a script on each of them, to update their formlist with what goes in and out of their inventory ? Right now, my script is attached to the MagicEffect of the spell (Scriptname.... extends ActiveMagicEffect).And to access the NPC I'm using the target of the effect: Event OnEffectStart(Actor target, Actor caster) target.Resurrect() etc. So, would there be way to "read" the equipped items of this target, just before resurrection, and put them in a Formlist? Any tips would be greatly appreciated ! Link to comment Share on other sites More sharing options...
IsharaMeradin Posted April 18, 2013 Share Posted April 18, 2013 The form list / equipment swap thing was intended for a single known actor. It may be possible to use it with multiple actors pulled from the follower quest but I'm not sure how to do that. Link to comment Share on other sites More sharing options...
Recommended Posts