I'd like to submit a first draft of my generic projectile script from Elemental Magicka. The functionality of it won't really change, but in the future I'd like to clean it up a bit more and document better. Still, I wanted to submit something before it slips my mind again.
There are a number of adjustable constants/functions depending upon what type of motion you are trying to achieve, and what you want the forward sweep to be, but I tried to at least comment those lines. This script would be a lot easier to execute if Morrowind scripting had a few low level math functions...
Begin NMZ_GenericProjectile_Script; -- -- Script Variablesfloat state ; Controls disablefloat timer1 ; Removal timerfloat px ; Caster x position, now calculated rather than being tied to a referencefloat py ; Caster y position, now calculated rather than being tied to a referencefloat xpos ; New x positionfloat ypos ; New y positionfloat zpos ; New z positionfloat dx ; Delta xfloat dy ; Delya yfloat v0 ; Initial velocityfloat vx ; Initial velocity x componentfloat vy ; Initial velocity y componentfloat hyp ; Unit vector from caster position to object positionfloat hypx ; Vector in x direction from caster position to object positionfloat hypy ; Vector in y direction from caster position to object positionfloat dx1 ; New x positionfloat dy1 ; New y positionfloat a0z ; Acceleration due to gravityfloat v0z ; Initial velocity z component (separate from v0, vx, and vy)float dz ; Position + Velocity term for the z direction motionfloat dz1 ; Acceleration term for the z direction motionfloat xpos0 ; Initial x positionfloat ypos0 ; Initial y positionfloat zpos0 ; Initial z positionfloat theta ; (d/dz)(dz + dz1)float ktheta ; Rotation speedfloat tfinal ; Final time after which the object will explode if it hasn't collided yet. This is needed because we don't know the ground height.float pz ; Player z position (projectile will keep going past tfinal so long as it is above the player)float zangle ; Angle in the z direction of the objectfloat xtemp ; Temporary variable for calculating forward vectorfloat ytemp ; Temporary variable for calculating forward vectorfloat circleTimer ; Timer for circular motionfloat circleTimerB ; Timer b for circular motionfloat circle34th ; Unused 34th script variable, results in error on usagefloat circleTime ; Time to complete one revolution of the circular motion componentfloat circleRadius ; Radius function for the circular motion componentfloat circleDelta ; Temp variablefloat circleVec1Offset ; Circle vector 1, z directionfloat circleVec2Offset ; Circle vector 2, xy forward directionfloat circleVec2x ; Circle vector 2 x componentfloat circleVec2y ; Circle vector 2 y componentfloat circleVec2xAlt ; Circle vector 1 x componentfloat circleVec2yAlt ; Circle vector 1 y componentfloat circleNormalize ; Normalization factor for vector resolutionfloat circleOffsetX ; Circle x offsetfloat circleOffsetY ; Circle y offsetfloat circleTime2 ; 0.75 *circleTimefloat circleTime3 ; 0.50 *circleTimefloat circleTime4 ; 0.25 *circleTimefloat circleSeedTime ; Randomized seed timer, 0 < circleSeedTime < circleTime to randomize start position float circleDelta2 ; circleDelta^2float circleDelta3 ; circleDelta^3float circleDelta4 ; circleDelta^4float circleDelta5 ; circleDelta^5float circleDelta6 ; circleDelta^6float circleDelta7 ; circleDelta^7float zangle2 ; zangle^2float zangle3 ; zangle^3float zangle4 ; zangle^4float zangle5 ; zangle^5float zangle6 ; zangle^6float zangle7 ; zangle^7float HeightLimit ; Explosion limiter toggle, 0 = explode at tFinal, 1 = explode at tFinal only if not above player's z position (keeps arrows from looking funny)float angleOffset ; Optional z angle offset; -- -- Menumode Blockif ( MenuMode == 1 ) Returnendif; -- -- Disable Blockif ( GetDisabled == 1 ) if ( state <= 200 ) set state to ( state + 1 ) Return else setdelete 1 DontSaveObject Return endifendif; -- -- Spell Blockif ( state == 0 ) ;Scale object setscale 1.0 ;Height-based explosion limiter set HeightLimit to 0 ;Figure out motion vector set xpos to ( GetPos,x ) set ypos to ( GetPos,y ) set xpos0 to xpos set ypos0 to ypos ;Optional angle offset line for the forward sweep range. Comment out the following line to always move directly forward. set angleOffset to ( Random, 121 ) ;z angle calculations set zangle to ( GetAngle, z ) set zangle to ( zangle + angleOffset ) set zangle to ( zangle - 60 ) setangle, z, zangle ;Convert to radians set zangle to ( zangle * 3.1415 / 180 ); MessageBox "zangle %0.00f xpos %0.00f ypos %0.00f px %0.00f py %0.00f", zangle, xpos, ypos, px, py ;Perform some Taylor series approximations for sin(x) and cos(x), how hard would it have been to include these functions natively? set zangle2 to ( zangle * zangle ) set zangle3 to ( zangle * zangle2 ) set zangle4 to ( zangle2 * zangle2 ) set zangle5 to ( zangle2 * zangle3 ) set zangle6 to ( zangle3 * zangle3 ) set zangle7 to ( zangle3 * zangle4 ) set xtemp to ( zangle - ( zangle3 / 6 ) ) set xtemp to ( xtemp + ( zangle5 / 120 ) ) set xtemp to ( xtemp - ( zangle7 / 5040 ) ) set ytemp to ( 1 - ( zangle2 / 2 ) ) set ytemp to ( ytemp + ( zangle4 / 24 ) ) set ytemp to ( ytemp - ( zangle6 / 720 ) ) set px to ( xpos - xtemp ) set py to ( ypos - ytemp ) set dx to ( xpos - px ) set dy to ( ypos - py ) ;Resolve velocity components ;Default v0 value is 1000 set v0 to 1000 set hypx to ( ( GetPos,x ) - px ) set hypy to ( ( GetPos,y ) - py ) set hypx to ( hypx * hypx ) set hypy to ( hypy * hypy ) set hyp to ( hypx + hypy ) set hyp to ( GetSquareRoot, hyp ) set hyp to ( hyp + 0.01 ) set vx to ( v0 * dx / hyp ) set vy to ( v0 * dy / hyp ) ;Raise off ground set zpos to ( GetPos, z ) set zpos to ( zpos + 80 ) set zpos0 to zpos ; Default values ;set v0z to 200 ;set ktheta to ( -75 / v0z ) set v0z to 200 set ktheta to ( -75 / v0z ) ;Default tfinal value is 2.8 set tfinal to 2.8 setpos,z, zpos ;Next block set state to 1 Returnelseif ( state == 1 ) set timer1 to ( timer1 + GetSecondsPassed ) if ( timer1 < tfinal ) ; Collision check if ( GetCollidingActor == 1 ) set state to 2 Return elseif ( GetCollidingPC == 1 ) set state to 2 Return endif ; The new x, y, and z positions of the object are the linear superposition ; of the forward linear motion (v0 controlled), gravitational motion (v0z controlled), ; and the circular motion around the point generated by the former functions. ; (1) Linear motion component in the forward direction set dx1 to ( ( timer1 * vx ) + xpos0 ) set dy1 to ( ( timer1 * vy ) + ypos0 ) ; (2) Gravity component of the motion in the z direction ;Default value for a0z is 478 set a0z to 478 set dz to ( zpos0 + ( v0z * timer1 ) ) set dz1 to ( -0.5 * a0z * timer1 * timer1 ) set dz1 to ( dz + dz1 ) ; (3) Circular motion component set circleRadius to 20 set circleTime to 1 set circleTime2 to ( 0.75 * circleTime ) set circleTime3 to ( 0.50 * circleTime ) set circleTime4 to ( 0.25 * circleTime ) ; -- Set circle timer to reset after each revolution if ( circleSeedTime == 0 ) set circleSeedTime to ( Random, 101 ) set circleSeedTime to ( circleSeedTime * circleTime / 100 ) endif set circleTimer to ( timer1 + circleSeedTime ) while ( circleTimer > circleTime ) set circleTimer to ( circleTimer - circleTime ) endwhile if ( circleTimer < circleTime4 ) set circleTimerB to ( circleTimer - 0 ) elseif ( circleTimer < circleTime3 ) set circleTimerB to ( circleTimer - circleTime4 ) elseif ( circleTimer < circleTime2 ) set circleTimerB to ( circleTimer - circleTime3 ) elseif ( circleTimer < circleTime ) set circleTimerB to ( circleTimer - circleTime2 ) endif ; -- Calculate offsets for the z vector and forward vector set circleDelta to ( 2 * 3.1415 * circleTimerB / ( circleTime ) ) set circleDelta2 to ( circleDelta * circleDelta ) set circleDelta3 to ( circleDelta * circleDelta2 ) set circleDelta4 to ( circleDelta2 * circleDelta2 ) set circleDelta5 to ( circleDelta2 * circleDelta3 ) set circleDelta6 to ( circleDelta3 * circleDelta3 ) set circleDelta7 to ( circleDelta3 * circleDelta4 ) set circleVec1Offset to ( 1 - ( circleDelta2 / 2 ) ) set circleVec1Offset to ( circleVec1Offset + ( circleDelta4 / 24 ) ) set circleVec1Offset to ( circleVec1Offset - ( circleDelta6 / 720 ) ) set circleVec1Offset to ( circleVec1Offset * circleRadius ) set circleVec2Offset to ( circleDelta - ( circleDelta3 / 6 ) ) set circleVec2Offset to ( circleVec2Offset + ( circleDelta5 / 120 ) ) set circleVec2Offset to ( circleVec2Offset - ( circleDelta7 / 5040 ) ) set circleVec2Offset to ( circleVec2Offset * circleRadius ) ; -- Resolve circleVec2Offset into x and y components ; Note that the circleVec2x/y calcs use the vy/x respectively, which looks reversed, ; but the vector we want is normal to the forward vector and the z vector rather than ; the forward vector itself. set circleNormalize to ( ( vx * vx ) + ( vy * vy ) ) set circleNormalize to ( GetSquareRoot, circleNormalize ) set circleVec2x to ( circleVec2Offset * vy / circleNormalize ) set circleVec2y to ( circleVec2Offset * vx / circleNormalize ) set circleVec2xAlt to ( circleVec1Offset * vy / circleNormalize ) set circleVec2yAlt to ( circleVec1Offset * vx / circleNormalize ) if ( circleTimer < circleTime4 ) set dz1 to ( dz1 - circleVec1Offset ) set dx1 to ( dx1 + circleVec2x ) set dy1 to ( dy1 + circleVec2y ) elseif ( circleTimer < circleTime3 ) set dz1 to ( dz1 + circleVec2Offset ) set dx1 to ( dx1 + circleVec2xAlt ) set dy1 to ( dy1 + circleVec2yAlt ) elseif ( circleTimer < circleTime2 ) set dz1 to ( dz1 + circleVec1Offset ) set dx1 to ( dx1 - circleVec2x ) set dy1 to ( dy1 - circleVec2y ) elseif ( circleTimer < circleTime ) set dz1 to ( dz1 - circleVec2Offset ) set dx1 to ( dx1 - circleVec2xAlt ) set dy1 to ( dy1 - circleVec2yAlt ) endif ; (4) Rotation in x direction to accompany translation set theta to ( v0z + ( -a0z * timer1 ) ) set theta to ( theta * ktheta ) ; ---- Optional rotation limiter (keeps arrows from looking funny) --- ;if ( theta > 90 ) ; set theta to 90 ;elseif ( theta < -90 ) ; set theta to -90 ;endif setangle,x,theta ; (5) Rotate in y direction to refresh collision rotate,y,0.01 ; (6) Set new coordinates for this frame set xpos to dx1 set ypos to dy1 set zpos to dz1 setpos, x, xpos setpos, y, ypos setpos, z, zpos Return else ; This block will allow it to keep arcing if the need arises set pz to ( Player->Getpos,z ) set zpos to ( GetPos, z ) if ( HeightLimit > 0 ) if ( zpos > pz ) set tfinal to ( tfinal + 0.1 ) return endif endif set state to 2 return endifelseif ( state == 2 ) set xpos to ( GetPos, x ) set ypos to ( GetPos, y ) set zpos to ( GetPos, z ) ;Move the projectile downward a little bit. if it just barely collided, then the spell might not hit without this adjustment. set zpos to ( zpos - 100 ) setpos,z,zpos ;Replace the following spell with the desired effect upon impact ExplodeSpell "NMZ_ProjectileSpell_Bomb" DisableendifEnd