Reference Variables for Nearby Actors

Revision as of 17:20, 6 May 2006 by imported>DragoonWraith
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Originally posted on Elder Scrolls Forums by Tegid

I know there have been several people who have wanted to get references for the various enemies and actors in their area. Here are the steps to do this with the current set of Script commands.

First we need to create a new spell. It should be a Script Effect Spell (I named it TargetDetection) with an area of effect of 4-6000 (in practice 4000 is on the edge of fading out on low end systems), 0 damage and check to ignore immunity/resistance, LOS etc. Basically we want this thing to hit no matter what. Oh, and uncheck hostile and auto-calc costs. (This is probably spread across two dialogs)

Now we need our script effect. This one is definitely not perfect (I don't seem to ever hit my ScriptFinishEffect, but it isn't necessary anyhow.)

CODE

scriptname EntityFinderEffect

short NotReported ref self

float timer

begin ScriptEffectStart

   set self to GetSelf
message "Casting EntityFinderEffect", 20
   set timer to 0
   if (self != player)
       set NotReported to 1
   else
       set NotReported to 0
   endif

end

begin ScriptEffectUpdate

   if (timer > 0)
       set timer to (timer - GetSecondsPassed)
       return
   else
       set timer to 0.5
   endif
   if (NotReported == 1)
       if (EnemyFinderRef1.RefsRequested == 0)
           set NotReported to 0
       else
           if (EnemyFinderRef1.RefsRequested == 1)
             if (EnemyFinderRef1.ref1full == 0)
               set EnemyFinderRef1.ref1full to 1
               set EnemyFinderRef1.reference1 to self
Message "Reported to Reference1",1
               set NotReported to 0
             elseif (EnemyFinderRef1.ref2full == 0)
               set EnemyFinderRef1.ref2full to 1
               set EnemyFinderRef1.reference2 to self
               set NotReported to 0
Message "Reported to Reference2",1
             elseif (EnemyFinderRef1.ref3full == 0)
               set EnemyFinderRef1.ref3full to 1
               set EnemyFinderRef1.reference3 to self
               set NotReported to 0
Message "Reported to Reference3",1
             elseif (EnemyFinderRef1.ref4full == 0)
               set EnemyFinderRef1.ref4full to 1
               set EnemyFinderRef1.reference4 to self
               set NotReported to 0
Message "Reported to Reference4",1
             elseif (EnemyFinderRef1.ref5full == 0)
               set EnemyFinderRef1.ref5full to 1
               set EnemyFinderRef1.reference5 to self
               set NotReported to 0
Message "Reported to Reference5",1
             elseif (EnemyFinderRef1.ref6full == 0)
               set EnemyFinderRef1.ref6full to 1
               set EnemyFinderRef1.reference6 to self
               set NotReported to 0
Message "Reported to Reference6",1
             elseif (EnemyFinderRef1.ref7full == 0)
               set EnemyFinderRef1.ref7full to 1
               set EnemyFinderRef1.reference7 to self
               set NotReported to 0
Message "Reported to Reference7",1
             elseif (EnemyFinderRef1.ref8full == 0)
               set EnemyFinderRef1.ref8full to 1
               set EnemyFinderRef1.reference8 to self
               set NotReported to 0
Message "Reported to Reference8",1
             elseif (EnemyFinderRef1.ref9full == 0)
               set EnemyFinderRef1.ref9full to 1
               set EnemyFinderRef1.reference9 to self
               set NotReported to 0
Message "Reported to Reference9",1
             elseif (EnemyFinderRef1.ref10full == 0)
               set EnemyFinderRef1.ref10full to 1
               set EnemyFinderRef1.reference10 to self
               set NotReported to 0
Message "Reported to Reference10",1
             endif
           endif
       endif
   endif

end

begin ScriptEffectFinish

if (NotReported == 1)
return
else
I have never seen the following code execute. It isn't necessary anyhow, but it was an attempt to directly
return the reference
       MessageBox "Activating the EnemyFinder"
       EnemyFinderRef1.Activate self 1
endif

end

Clearly I have some debug code in there you can uncomment etc. Now, you can see it references variables in an EnemyFinderRef1 that we don't know about. Well first, let's define the script that defines those variables.

CODE

scriptname EnemyFinder

ref AskingRef short RefsRequested ref reference1 ref reference2 ref reference3 ref reference4 ref reference5 ref reference6 ref reference7 ref reference8 ref reference9 ref reference10

short ref1full short ref2full short ref3full short ref4full short ref5full short ref6full short ref7full short ref8full short ref9full short ref10full

ref temp ref self

short doonce

begin OnActivate

   if (doonce == 0)
       set self to EnemyFinderRef1
       set AskingRef to GetActionRef
       set doonce to 1
   endif
The code until the sets is supposed to deal with the ScriptEffectFinish code
   set temp to GetActionRef
   if (temp != AskingRef)
       if (temp != player)
           AskingRef.Activate temp 1
       endif
   endif
   set ref1full to 0
   set ref2full to 0
   set ref3full to 0
   set ref4full to 0
   set ref5full to 0
   set ref6full to 0
   set ref7full to 0
   set ref8full to 0
   set ref9full to 0
   set ref10full to 0
   set RefsRequested to 1
   float xval
   float yval
   float zval
   self.moveto player, 0.0, 0.0, 100.0
set xval to (self.getpos x)
set yval to (self.getpos y)
set zval to (self.getpos z)
MessageBox "Moved to (%.2f, %.2f, %.2f)", xval, yval, zval
   cast TargetDetection Player

end


Again I have some debug code there. Basically this is a script that will store the references and the checks to see if they are full. It goes on an object (an Activator is a great choice) and each time it is activated, it moves to roughly the actors location and casts the spell at the actor(it does no damage and isn't hostile, I just needed a target for it). Right now you can still hear the spell go off, but I didn't worry too much about that for this Proof of Concept (I named the spell TargetDetection remember?).

Now, I make a new activator with the TrigZone01.nif model (to create a new Activator you need to unpack your Oblivion - Meshes.bsa file to have access to the individual files.) Then I add the EnemyFinder script to the object.

At this point I add a copy of it to the world map outside of the Sewers at the beginning of the game. I make that copy a persistent reference named EnemyFinderRef1 (hence the name in the file above).

Now all that must be done is to Activate EnemyFinderRef1. I added a script to an Iron Bow to test it, but you could put it on anything, or you could make the EnemyFinderRef1 run in GameMode instead of on activate and go off every X seconds as you decide. I will include the script I have which is object agnostic (it isn't bow specific) and you can take the code and modify it as you see fit. I've included printouts that aren't perfect but work to the script code. You'll get the first two message boxes (if there are two nearby references) right now since I didn't bother to fix the MessageBox chain. I tried to print the reference values but that failed.

CODE

scriptname RangeTest

float timer short doonce short player_owned

ref actorfinder ref myself

begin OnActivate

   if (IsActionRef player == 1)
       set player_owned to 1
       set actorfinder to enemyfinderref1
       set myself to this
       Activate
   else
       long distance
       ref actor
       set actor to GetActionRef
       set distance to (GetDistance actor)
       MessageBox "I am %.0f distance from %.0f", distance, actor
   endif

end

begin OnDrop

   set player_owned to 0

end

begin GameMode

   if (player_owned)
       if (doonce == 0)
           set timer to 10
           set doonce to 1
       endif
       if (timer > 0)
           set timer to timer - GetSecondsPassed
           long distance
           short button
           ref actor
           if (timer < 5)
               set EnemyFinderRef1.RefsRequested to 0
           endif
           if (EnemyFinderRef1.ref1full == 1)
               set actor to EnemyFinderRef1.reference1
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 1 %.0f", distance, actor
               set EnemyFinderRef1.ref1full to 0
           endif
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref2full == 1)
               set actor to EnemyFinderRef1.reference2
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 2 %.0f", distance, actor
               set EnemyFinderRef1.ref2full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref3full == 1)
               set actor to EnemyFinderRef1.reference3
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 3 %.0f", distance, actor
               set EnemyFinderRef1.ref3full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref4full == 1)
               set actor to EnemyFinderRef1.reference4
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 4 %.0f", distance, actor
               set EnemyFinderRef1.ref4full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref5full == 1)
               set actor to EnemyFinderRef1.reference5
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 5 %.0f", distance, actor
               set EnemyFinderRef1.ref5full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref6full == 1)
               set actor to EnemyFinderRef1.reference6
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 6 %.0f", distance, actor
               set EnemyFinderRef1.ref6full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref7full == 1)
               set actor to EnemyFinderRef1.reference7
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 7 %.0f", distance, actor
               set EnemyFinderRef1.ref7full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref8full == 1)
               set actor to EnemyFinderRef1.reference8
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 8 %.0f", distance, actor
               set EnemyFinderRef1.ref8full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref9full == 1)
               set actor to EnemyFinderRef1.reference9
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 9 %.0f", distance, actor
               set EnemyFinderRef1.ref9full to 0
           endif
           set button to -2
           set button to getbuttonpressed
           if (button > -1 && EnemyFinderRef1.ref10full == 1)
               set actor to EnemyFinderRef1.reference10
               set distance to (GetDistance actor)
               MessageBox "I am %.0f distance from Reference 10 %.0f", distance, actor
               set EnemyFinderRef1.ref10full to 0
           endif
       else            
           Message "Looking for Actor Range", 1
           actorfinder.Activate  myself 1
           set timer to 10
       endif
   endif

end


Clearly you could make your timeout value a global or set it to something different based on the situation. Also you want to pull the references out from your script as soon as possible so that you can make sure you don't miss the one you want due to a one not being able to report in time because the registers are full.

While having this go off every ten seconds had no appreciable effect on frame rate, having it go off on activate allows you to use it from all kinds of scripts and on command rather than having to wait for it to go off on it's own. For instance. Assume you are scripting a quest with several NPCs and they are preparing to attack all the other people in the area. You could activate the EnemyFinderRef1, identify the references that aren't your party and then have the NPCs start combat with those references, effectively picking targets at random and starting to attack. By leaving it on activate you give yourself extra flexibility.

I hope this is helpful to someone and that you haven't all figured this out already or I'll feel dumb for having spent a few late nights working on it.

See Also

Dynamic Storage - may be useful in keeping track of the list of actors