Difference between revisions of "Running Scripts On Arrows"

1,482 bytes added ,  14:22, 29 May 2009
m
no edit summary
imported>Scruggs
(some minor additions/clarifications)
imported>Tekuromoto
m
 
(4 intermediate revisions by 4 users not shown)
Line 1: Line 1:
One of the limitations of the Construction Set is that it won't allow you to attach scripts to arrows. This is an obstacle for many mod ideas. A partial workaround is to add a [[Magic_effect_scripts|Script Effect]] enchantment to the arrow; the script will run when the arrow hits an actor. But there are times when you want to detect or control the behavior of an arrow while it is in flight, or trigger some action before the arrow hits an actor. This tutorial will show you how to do that.
One of the limitations of the Construction Set is that it won't allow you to attach scripts to arrows. This is an obstacle for many mod ideas. A partial workaround is to add a [[Magic_effect_scripts|Script Effect]] enchantment to the arrow; the script will run when the arrow hits an actor. But there are times when you want to detect or control the behavior of an arrow while it is in flight, or trigger some action before the arrow hits an actor. This tutorial will show you how to do that.
 
==Getting a reference to an arrow==
The first step is to get a reference to the arrow. This is done by placing a trigger zone at the player's location. When an arrow collides with the trigger zone, an [[OnTriggerMob]] block will run. The reference to the triggering object can then be returned with [[GetActionRef]].
The first step is to get a reference to the arrow. This is done by placing a trigger zone at the player's location. When an arrow collides with the trigger zone, an [[OnTriggerMob]] block will run. The reference to the triggering object can then be returned with [[GetActionRef]].


We'll create a new trigger zone object using the TrigZone01.nif and place a reference to it somewhere in the gameworld. Make sure it is a persistent reference, and has a unique reference ID. We also want to scale it to 0.5 scale. We'll call the reference '''acTrigZoneREF'''.
We'll create a new trigger zone object using the TrigZone01.nif and place a reference to it somewhere in the gameworld. Make sure it is a persistent reference, and has a unique reference ID. We also want to scale it to 0.5 scale. We'll call the reference '''acTrigZoneREF'''.
 
==Moving the trigger zone==
Now, how do we keep the trigger zone glued to the player's location? [[SetPos]] and [[MoveTo]] will be useful here. We will use a quest script to handle the [[MoveTo]] calls for when the trigger zone is not in the same cell as the player, and update its position in the current cell with [[SetPos]].
Now, how do we keep the trigger zone glued to the player's location? [[SetPos]] and [[MoveTo]] will be useful here. We will use a quest script to handle the [[MoveTo]] calls for when the trigger zone is not in the same cell as the player, and update its position in the current cell with [[SetPos]].
<pre>
<pre>
Line 57: Line 57:


Notice that the use of the '''relocate''' variable splits the movement from one cell to the next into four stages. This is to make sure that the trigger zone retains its collision properties after it is moved.
Notice that the use of the '''relocate''' variable splits the movement from one cell to the next into four stages. This is to make sure that the trigger zone retains its collision properties after it is moved.
 
==Adjusting the vertical position==
That script works...sort of. The problem is that the trigger zone is always at the player's feet. We need to adjust the Z position of the trigger zone so that it will always be at the correct height to collide with an arrow fired by the player. Three factors are involved here: the player's height, which is defined by his race; the player's vertical rotation (X angle); and whether or not the player is sneaking. We'll add a section to the script to calculate the necessary adjustment and apply it to the trigger zone's coordinates:
That script works...sort of. The problem is that the trigger zone is always at the player's feet. We need to adjust the Z position of the trigger zone so that it will always be at the correct height to collide with an arrow fired by the player. Three factors are involved here: the player's height, which is defined by his race; the player's vertical rotation (X angle); and whether or not the player is sneaking. We'll add a section to the script to calculate the necessary adjustment and apply it to the trigger zone's coordinates:
<pre>
<pre>
Line 119: Line 119:


Here, [[GetScale]] returns the player's height as a proportion of the default racial height of 128. For the default height, we want the trigger zone placed 115 units above the player's feet when standing, and 95 units above when sneaking. Then we modify the offset to account for the player's vertical facing. Note that it would be more efficient to do some of the scale calculations only once, when the game is first loaded, since that information is not likely to change.
Here, [[GetScale]] returns the player's height as a proportion of the default racial height of 128. For the default height, we want the trigger zone placed 115 units above the player's feet when standing, and 95 units above when sneaking. Then we modify the offset to account for the player's vertical facing. Note that it would be more efficient to do some of the scale calculations only once, when the game is first loaded, since that information is not likely to change.
 
==The fun part==
Great, now we have a trigger zone glued to the player's location, capable of intercepting any arrows he fires. We can attach a demo script to the trigger zone itself to show how this works:
Great, now we have a trigger zone glued to the player's location, capable of intercepting any arrows he fires. We can attach a demo script to the trigger zone itself to show how this works:
<pre>
<pre>
Line 136: Line 136:
</pre>
</pre>
That's fun, but not entirely useful. Also, a serious problem arises: the [[onTriggerMob]] block will run when ''any'' projectile collides with the trigger zone, including arrows and spells fired at the player. What we need is a method to determine whether the projectile is an arrow, and whether it was fired by the player.
That's fun, but not entirely useful. Also, a serious problem arises: the [[onTriggerMob]] block will run when ''any'' projectile collides with the trigger zone, including arrows and spells fired at the player. What we need is a method to determine whether the projectile is an arrow, and whether it was fired by the player.
 
==Validating the reference==
If we know the ID of the arrows we want to run scripts on, this is easy. Just check [[GetIsID]] on the triggering reference:
If we know the ID of the arrows we want to run scripts on, this is easy. Just check [[GetIsID]] on the triggering reference:
<pre>
<pre>
Line 164: Line 164:
   endif
   endif
end</pre>
end</pre>
 
==More fun with OBSE==
Another more useful example results in a bottomless quiver of arrows:
Another more useful example results in a bottomless quiver of arrows:
<pre>
<pre>
Line 192: Line 192:
</pre>
</pre>
We still haven't eliminated the possibility that the arrow was fired by an NPC. We'll tackle that later.
We still haven't eliminated the possibility that the arrow was fired by an NPC. We'll tackle that later.
 
==Tracking the arrow's flight==
At this point, we have a means of obtaining a reference to an arrow, and we can run scripts on that reference. However, it would be useful to know what the arrow is doing. We can figure this out by tracking its position and rotation over the course of its flight:
At this point, we have a means of obtaining a reference to an arrow, and we can run scripts on that reference. However, it would be useful to know what the arrow is doing. We can figure this out by tracking its position and rotation over the course of its flight:
*If the arrow's rotation changes and it continues moving, it has bounced off of a surface;
*If the arrow's rotation changes and it continues moving, it has bounced off of a surface;
Line 251: Line 251:
       endif
       endif
       set triggered to 0
       set triggered to 0
    else
      set ox to xp
      set oy to yp ; set 'previous frame' coordinates to check against next frame
      set oz to zp
     endif
     endif
   endif
   endif
Line 256: Line 260:


Admittedly, that script isn't terribly useful in itself, but it should give you a starting point in setting up your own scripts on arrows.
Admittedly, that script isn't terribly useful in itself, but it should give you a starting point in setting up your own scripts on arrows.
==Determining who fired the arrow==
Remember that issue I said we'd tackle later? A problem may arise if an NPC fires an arrow at the player - if it collides with our trigger zone, the script has no way of recognizing that it wasn't fired by the player. One way to fix that would be to track the arrow's flight over the course of a few frames to determine where it is heading. But there may not be enough time for that, especially in low frame-rate situations.
The [[GetAngle]] function provides a more useful method. At the moment the player fires an arrow, the arrow's angles will match the player's precisely. So a bit of code like this:
<pre>begin onTriggerMob
  set trigRef to getActionRef
  if ( trigRef.isAmmo && trigRef.getAngle z == player.getAngle z && trigRef.getAngle x == player.getAngle x )
    message "The player fired the arrow."
  endif
end</pre>


...would likely do what we want. However, the player might move the mouse before the trigger zone detects the arrow, which would cause the angles to differ. So to be safe, you'd want to compare the arrow's angles to the player's within a reasonable margin of error.
==Tying it all together==
Bear in mind that if two scripts attempt to use this approach at the same time, they will conflict with each other. Therefore, it's a good idea to conditionalize things as specifically as possible. For instance, if you only need to detect a certain type of arrow, then you would enable the trigZone only while the player has that type of arrow equipped.
Bear in mind that if two scripts attempt to use this approach at the same time, they will conflict with each other. Therefore, it's a good idea to conditionalize things as specifically as possible. For instance, if you only need to detect a certain type of arrow, then you would enable the trigZone only while the player has that type of arrow equipped.


Anonymous user