Jump to content

teleporting and then resurrecting NPC - scriping help needed


Elleh

Recommended Posts

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

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

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 by steve40
Link to comment
Share on other sites

 

 

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

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

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

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

  • 4 weeks later...

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...