| 01-03-2011, 06:20 AM | #1 |
I was looking through the xe system/modules and really liked how xefx and xecollider worked. However I noticed that it was a bit difficult to get angled/arced motion with the xecollider. I knew there were other systems like custom missile or xemissile but I decided to try to make my own. A lot of base code is heavily inspired/taken from xecollider. This includes the indexing/timer/recycling system. However I chose to use GroupUtils for group recycling because I am used to it. Notes: - The Projectile library uses another library I made called Vector3, as well as xefx, xebasic, BoundSentinel and GroupUtils - Vector3 is a library/struct for 3D vectors and related functionality. - Uses xefx for the effects aspect of the library (delegates like xecollider does) - The test map I am including has a bunch of junk for presentation, such as hero revival, damage numbers, cast bar, effects, etc. - The test map uses JCast for the spell system. Projectile does not require JCast. Pros: - Detailed properties and useful events for fully controllable projectiles. - Easy to use, by just extending and implementing events (like in xecollider) Cons: - Uses multiple Vector3 structs per projectile. - Does not consider terrain height. So a projectile flying over bumpy terrain will follow the ground and might look like it's tripping out. I may change this later. I was hoping to get some comments/suggestions/criticism. If you guys like it enough I can try to submit it to your database. Please try out the test map, and thanks for reading. Here's the library: Projectile:library AdvancedProjectile uses Vector3, xefx, GroupUtils //---------------------------------------------------------------------// // Advanced Projectile //---------------------------------------------------------------------// // // This was inspired heavily by the xecollider part of xe0.8 by Vexorian // You will find A LOT of similarities between the two. // What I was trying to get with this is a smooth path effect that goes // in all directions. This takes advantage of some vector math. //---------------------------------------------------------------------// // // How to use: // // I recommend creating your own struct, and extending Projectile. // This gives you access to events triggered automatically by the system. // // Events: // __________________________________________________________________ // |onUnitHit takes unit hitTarget returns nothing defaults nothing // | - hitTarget: the unit that the projectile passed through. // | // | Implement this if you want to detect when the projectile hit a unit. // | This is the best time to do damage. // +------------------------------------------------------------------ // |onTargetReached takes nothing returns nothing defaults nothing // | // | Implement this if you want to detect when the projectile // | reaches its target (on the ground). By default this will trigger // | only after onUnitHit thanks to collision size/distance checks. // | Note: projectiles will circle around targets if they can't turn // | well enough. This is under your control. // +------------------------------------------------------------------ // |onHitGround takes nothing returns nothing defaults nothing // | // | Implement this if you want to detect when the projectile // | hits the ground. This will trigger if the projectile STARTS // | from the ground (z = 0.0), so to avoid that simply set // | the origins vector's z value to something greater than 0 when // | passing it to the create() function (see below) // +------------------------------------------------------------------ // |onTimerTick takes nothing returns nothing defaults nothing // | // | This is the equivelent of loopControl from xecollider. // | This gets called every update (every DEFAULT_INTERVAL) // +------------------------------------------------------------------ // |onExpired takes nothing returns nothing defaults nothing // | // | This is triggered when the lifetime of the projectile reaches 0 // +------------------------------------------------------------------ // // Creation/Destruction: // __________________________________________________________________ // |static create(Vector3 origin, Vector3 direction, Vector3 target) // | // | - origin: a vector specifying where the projectile starts // | - direction: a vector pointing in the direction the projectile // | will move in at the start // | - target: a vector specifying the initial target that the projectile // | is headed fore. NOTE: this can and probably will change if // | a homingTarget is specified. // | // | This creates the projectile and registers it for updates. At this point, // | its speed, and acceleration is 0. So you have to also set those. // | If you extend Projectile and need to use create, you can call: // | allocate(origin, direction, target) inside your custom create. // +------------------------------------------------------------------ // |Projectile::terminate() // | // | Similarly to xecollider, this is the safest way to destroy. // | Try not to call destroy(), ever. // +------------------------------------------------------------------ // // Useful public properties (case sensitive): // - [source]: set this to a unit to store the creator of the projectile. // - [owner]: the player owner // - [Source]: set this to set source, and owner at the same time // - [homingTarget]: set this to make the projectile home in on someone // - [speed], [maxSpeed], [acceleration]: movement vars you can change. // - [maxTurnXY], [maxTurnZ]: left/right and up/down rotation amounts per second // - [MaxTurn]: set both Z and XY rotation per second // - [x],[y],[z]: position vars, from xefx // - [fxpath]: the string path to the effect of the projectile. // - [checkCollisionHeight]: if true, considers height in hit detection // - [lifeTime]: The amount of time in seconds that this projectile // has left before it terminates (counts down). // - [homingDelay]: The amount of time to delay the homing functionality. // //---------------------------------------------------------------------// globals //the same constants as found in xecollider private constant real DEFAULT_COLLISION_SIZE = 50.0 private constant real DEFAULT_MAX_SPEED = 1500.0 private constant real DEFAULT_EXPIRATION_TIME = 5.0 private constant real DEFAULT_TARGET_DISTANCE = 25.0 //max distance at which onTargetRecahed is triggered //Rotation modes/styles: //NORMAL: The projectile will rotate up/down in a more casual fasion. // This is recommended for projectiles originating for the ground. //AGGRESSIVE: The projectile will rotate up/down more directly, as if it were a missile. // This is recommended for projectils originiating from the air (missiles, firebombs) constant integer ROTATE_NORMAL = 0 constant integer ROTATE_AGGRESSIVE = 1 //similar to the constant in xebasic private constant real DEFAULT_INTERVAL = 0.025 endglobals //---------------------------------------------------------------------// // IProjectile Interface //---------------------------------------------------------------------// // Interface for events that you can use when you extend Projectile. interface IProjectile method onUnitHit takes unit hitTarget returns nothing defaults nothing method onTargetReached takes nothing returns nothing defaults nothing method onHitGround takes nothing returns nothing defaults nothing method onTimerTick takes nothing returns nothing defaults nothing method onExpired takes nothing returns nothing defaults nothing endinterface //---------------------------------------------------------------------// // Projectile Struct - Extend this! //---------------------------------------------------------------------// struct Projectile extends IProjectile //effect carrier private delegate xefx fx //source of any damage unit source = null //unit to home in on, if needed unit homingTarget = null //Vectors for handling correct changes in position. //I suppose I could have just used a bunch of reals, or some arrays of reals. Vector3 forward = 0 //<-- I don't recommend changing this vector directly, use SetProjectileFacing() Vector3 right = 0 //<-- Same as above. Vector3 target = 0 //<-- If the projectile has homingTarget, this vector is updated constantly. Vector3 position = 0 integer rotationMode = ROTATE_NORMAL //basic properties - note these should be considered "per second" real speed = 0.0 real maxSpeed = 0.0 real acceleration = 0.0 real maxTurnZ = 0.0 real maxTurnXY = (2.0 * bj_DEGTORAD) //life time and time before homing starts real lifeTime = 0.0 real homingDelay = 0.0 boolean hiddenExpiration = false boolean checkCollisionHeight = false //Static indexing stuff similar to in xecollider private static timer TIMER private static integer NUM_PROJECTILES = 0 private static Projectile array PROJECTILES private static code timerLoopFunction //collision + data storage static group enumGroup //i made this public so structs that extend this can use it. private static Projectile cinstance private static unit array picked private static integer pickedN private real csize = DEFAULT_COLLISION_SIZE private group seen private boolean dead = false private boolean silent = false //test //AxisDraw ad static method create takes Vector3 origin, Vector3 direction, Vector3 target returns thistype local thistype e = thistype.allocate() set e.position = origin set e.target = target set e.Forward = direction set e.fx = xefx.create(e.position.x, e.position.y, e.forward.xyAngle) set e.lifeTime = DEFAULT_EXPIRATION_TIME set e.maxSpeed = DEFAULT_MAX_SPEED set e.seen = NewGroup() set e.xyangle = e.forward.xyAngle set e.zangle = e.forward.zAngle //test //set e.ad = AxisDraw.create(target.x, target.y, 0) set Projectile.PROJECTILES[Projectile.NUM_PROJECTILES] = e set Projectile.NUM_PROJECTILES = Projectile.NUM_PROJECTILES + 1 if(Projectile.NUM_PROJECTILES==1) then call TimerStart(Projectile.TIMER, DEFAULT_INTERVAL, true, Projectile.timerLoopFunction ) endif return e endmethod static method onInit takes nothing returns nothing set timerLoopFunction = (function thistype.Update) set TIMER = CreateTimer() set enumGroup = NewGroup() endmethod //use this to destroy method terminate takes nothing returns nothing set this.dead=true set this.fxpath="" endmethod method hiddenTerminate takes nothing returns nothing set silent = true call terminate() endmethod method onDestroy takes nothing returns nothing call forward.destroy() call target.destroy() call position.destroy() call right.destroy() if(silent) then call fx.hiddenDestroy() else call fx.destroy() endif call ReleaseGroup(seen) set source = null endmethod static method Update takes nothing returns nothing local Projectile this local integer i = 0 local integer c = 0 local integer j = 0 loop exitwhen i >= Projectile.NUM_PROJECTILES set this = PROJECTILES[i] //Check liftime, simlarly to xecollider set this.lifeTime = this.lifeTime - DEFAULT_INTERVAL set this.homingDelay = RMaxBJ(this.homingDelay - DEFAULT_INTERVAL, 0.0) if(this.dead or (this.lifeTime <= 0.0) ) then if(this.onExpired.exists) then call this.onExpired() endif if(hiddenExpiration) then set silent = true endif call this.destroy() else if(this.HasHomingTarget()) then call target.update(GetUnitX(this.homingTarget), GetUnitY(this.homingTarget), GetUnitZ(this.homingTarget)+50.0) endif //move the projectile forward call this.fwd() //if the homing delay is complete, home in on the target if(this.homingDelay <= 0.0) then call this.steer() endif //Try collision at this point set .cinstance = this set Projectile.pickedN = 0 call GroupEnumUnitsInRange( .enumGroup, x, y, .csize + XE_MAX_COLLISION_SIZE, function thistype.inRangeEnum) call GroupClear(this.seen) set j=0 loop exitwhen (j== Projectile.pickedN) call GroupAddUnit( this.seen, Projectile.picked[j]) set j=j+1 endloop //copy over destroyed structs set PROJECTILES[c]=this set c=c+1 //loop control like in xecollider //call BJDebugMsg("Calling on Timer tick? "+B2S(this.onTimerTick.exists)+" vs "+B2S(this.onHitGround.exists)) if( this.onTimerTick.exists and not this.dead ) then call this.onTimerTick() endif //target was reached, most likely land, not a unit. if( this.onTargetReached.exists and not this.dead ) then if(position.distSqr(target) < DEFAULT_TARGET_DISTANCE * DEFAULT_TARGET_DISTANCE) then call this.onTargetReached() endif endif //projectile hit the ground? if( this.onHitGround.exists and not this.dead ) then if(position.z <= 0) then call this.onHitGround() endif endif endif set i = i + 1 endloop //Pause the timer if there are no more projectiles set Projectile.NUM_PROJECTILES = c if(c==0) then call PauseTimer(Projectile.TIMER) endif endmethod //Collision detection filter //copied and altered from xecollider private static method inRangeEnum takes nothing returns boolean local Projectile this = .cinstance //adopt-a-instance local unit u=GetFilterUnit() if not IsUnitType(u, UNIT_TYPE_DEAD) and not(this.dead) and (GetUnitTypeId(u)!=XE_DUMMY_UNITID) and IsUnitInRangeXY(u, x, y, .csize) then // ah, the advantages of a standardized unit id... if(not this.checkCollisionHeight or (this.checkCollisionHeight and this.HeightCheckSqr(u) <= .csize * .csize)) then //call BJDebugMsg("HIT UNIT: check? "+B2S(this.checkCollisionHeight)+" check: "+this.HeightCheckSqr(u) set Projectile.picked[Projectile.pickedN] = u set Projectile.pickedN=Projectile.pickedN + 1 if not IsUnitInGroup (u, this.seen ) then call this.onUnitHit(u) endif endif endif set u=null return false endmethod //Move the projectile forward in the direction it is facing. //Also calc speed acceleration private method fwd takes nothing returns nothing local real s = speed * DEFAULT_INTERVAL set s = RMinBJ(s + acceleration * DEFAULT_INTERVAL, maxSpeed) call position.update(position.x + forward.x * s, position.y + forward.y * s, position.z + forward.z * s) set x = position.x set y = position.y set z = position.z endmethod //Turn left (radians). This can be done with a global rotate because //the frame of reference never changes for x/y rotation private method turnLeft takes real angle returns nothing call forward.rotate(0, 0, angle) call right.rotate(0, 0, angle) endmethod //Turn right (radians). This can be done with a global rotate because //the frame of reference never changes for x/y rotation private method turnRight takes real angle returns nothing call forward.rotate(0, 0, -angle) call right.rotate(0, 0, -angle) endmethod //Change the direction of the projectil so that it points closer to //the target. private method steer takes nothing returns nothing local real sxy = maxTurnXY * DEFAULT_INTERVAL local real sz = maxTurnZ * DEFAULT_INTERVAL local Vector3 seek = VectorSeek(target, position) local Vector3 up = VectorCrossProduct(right, forward) local Vector3 seek2 local Vector3 targetUp local real dotSide = 0.0 local real dotForward = 0.0 local real dotUp = 0.0 if(rotationMode == ROTATE_AGGRESSIVE) then set targetUp = seek.copy() else set seek2 = VectorCrossProduct(seek, WORLD_UP) set targetUp = VectorCrossProduct(seek2, seek) endif call targetUp.normalize() call right.normalize() call up.normalize() set seek.z = 0 call seek.normalize() set dotSide = right.dot(seek) set dotForward = forward.dot(seek) set dotUp = up.dot(targetUp) //test //call ad.Update(forward, right, up) //Handle left/right steering if(dotForward < 0) then if(dotSide < 0) then call turnLeft(sxy) else call turnRight(sxy) endif else call turnRight(dotSide * sxy) endif set dotForward = forward.dot(targetUp) //handle up/down steering. //This uses local rotation because up/down rotation is relative //to the projectile, and not the world. if(rotationMode == ROTATE_AGGRESSIVE) then //Direct rotation - the projectile will always turn toward the target if(dotUp < 0.01) then call forward.rotateAxis(WORLD_ZERO, right, (1.0-RAbsBJ(dotForward)) * -sz) elseif(dotUp > 0.01) then call forward.rotateAxis(WORLD_ZERO, right, (1.0-RAbsBJ(dotForward)) * sz) endif else //Casual rotation, the projectil will not always turn directly toward the point. if(dotUp < 0.0) then if(dotForward > 0) then call forward.rotateAxis(WORLD_ZERO, right, -sz) else call forward.rotateAxis(WORLD_ZERO, right, sz) endif else call forward.rotateAxis(WORLD_ZERO, right, dotForward * -sz) endif call seek2.destroy() endif //rotate the effect so it lines up set xyangle = forward.xyAngle set zangle = forward.zAngle //cleanup call seek.destroy() call targetUp.destroy() call up.destroy() endmethod method HasHomingTarget takes nothing returns boolean return homingTarget != null and GetUnitState(homingTarget, UNIT_STATE_LIFE) > 0. endmethod method GetUnitZ takes unit u returns real local location xy = Location(GetUnitX(u),GetUnitY(u)) local real z = GetLocationZ(xy) call RemoveLocation(xy) set xy = null return z endmethod //basically distance squared check, but put here //so i wouldn't have other outside library dependencies method HeightCheckSqr takes unit u returns real local real dx = x - GetUnitX(u) local real dy = y - GetUnitY(u) local real dz = z - (GetUnitZ(u)+50.) return dx * dx + dy * dy + dz * dz endmethod //Use this to set the direction the projectile is headed. //This also sets a correct "right" vector, which is essential. method operator Forward= takes Vector3 v returns nothing local Vector3 temp = 0 if forward != 0 then call forward.destroy() endif set forward = v call forward.normalize() if(v.equals(WORLD_UP))then set temp = VectorSeek(target, position) set right = VectorCrossProduct(temp, WORLD_UP) call right.normalize() call temp.destroy() else set right = VectorCrossProduct(forward, WORLD_UP) call right.normalize() endif endmethod //Use this to make the projectile face certain angles (in radians) //However it will still home in on the target. To rotate the target as well, //use SetProjectileFacingEx() method SetProjectileFacing takes real angleXY, real angleZ returns nothing call turnLeft(angleXY - forward.xyAngle) call forward.rotateAxis(WORLD_ZERO, right, angleZ - forward.zAngle) set xyangle = forward.xyAngle set zangle = forward.zAngle endmethod //Rotate everything about this projectile to face a certain direction. //The target location is rotated ournd the projectiles current position //to keep it relatively accurate. method SetProjectileFacingEx takes real angleXY, real angleZ returns nothing local real diffXY = angleXY - forward.xyAngle local real diffZ = angleZ - forward.zAngle call SetProjectileFacing(angleXY, angleZ) call target.rotateAxis(position, WORLD_UP, diffXY) endmethod method operator Source= takes unit u returns nothing set source = u if(u != null) then set owner = GetOwningPlayer(u) endif endmethod method operator MaxTurn= takes real a returns nothing set maxTurnZ = a set maxTurnXY = a endmethod //-------------------------------------------------// //XEFX - overwritten operators for safety method operator z= takes real value returns nothing set position.z = value set fx.z = value endmethod method operator x= takes real value returns nothing set position.x = value set fx.x = value endmethod method operator y= takes real value returns nothing set position.y = value set fx.y = value endmethod endstruct endlibrary And the vector library: Vector3:library Vector3 initializer Init_Vector3 //---------------------------------------------------------------------// // Vector3 //---------------------------------------------------------------------// // // This is a struct that represents an arrow or point in 3D space. // It is pretty similar to most other 3D vector implementations. // // It is easy to get carried away creating vectors so you need to be // sure to destroy them after use. NOTE: some function return brand new // Vector3 structs. These functions/methods are labeled. //---------------------------------------------------------------------// globals Vector3 REAL_UP Vector3 WORLD_UP Vector3 WORLD_BACK Vector3 WORLD_ZERO endglobals struct Vector3 real x = 0.0 real y = 0.0 real z = 0.0 //Constructor //** Returns brand new Vector3! static method create takes real x, real y, real z returns Vector3 local Vector3 v = Vector3.allocate() set v.x = x set v.y = y set v.z = z return v endmethod //Copy this vector //** Returns brand new Vector3! method copy takes nothing returns thistype local Vector3 v = Vector3.allocate() set v.x = x set v.y = y set v.z = z return v endmethod //Creates a vector poiting in these angle "directions" //Angle in paramteres are degrees for simplified use (just here though, radians everywhere else!) //** Returns brand new Vector3! static method fromDegreeAngles takes real xya, real za returns Vector3 local Vector3 v = Vector3.create(1,0,0) call v.rotate(0, -za * bj_DEGTORAD, xya * bj_DEGTORAD) return v endmethod //--------------------------------// //* Basic Properties *// //--------------------------------// //Returns the real length of the vector method length takes nothing returns real return SquareRoot(x*x+y*y+z*z) endmethod //Get the X/Y angle this vector is poiting in method operator xyAngle takes nothing returns real return Atan2(y,x) endmethod //Get the Z (up/down) angle this vector is pointing in. //probably not the most efficient way to do this //but the first one that came to mind method operator zAngle takes nothing returns real local Vector3 c = copy() local Vector3 c2 = copy() local real angle = 0.0 set c2.z = 0.0 set angle = Atan2(c.z, c.dot(c2)) call c.destroy() call c2.destroy() return angle endmethod //--------------------------------// //* Basic Manipulation Functions *// //--------------------------------// method update takes real nx, real ny, real nz returns nothing set x = nx set y = ny set z = nz endmethod method add takes Vector3 v returns nothing set x = x + v.x set y = y + v.y set z = z + v.z endmethod method subtract takes Vector3 v returns nothing set x = x - v.x set y = y - v.y set z = z - v.z endmethod method multiply takes Vector3 v returns nothing set x = x * v.x set y = y * v.y set z = z * v.z endmethod method scale takes real s returns nothing set x = x * s set y = y * s set z = z * s endmethod method translate takes real tx, real ty, real tz returns nothing set x = x + tx set y = y + ty set z = z + tz endmethod //This function forces the vector length = 1 //Useful for many things. method normalize takes nothing returns nothing local real l = length() set x = x / l set y = y / l set z = z / l endmethod //Rotate around GLOBAL AXIS!, this will always rotate around //the global x/y/z axis. Angles in radians. //For local or arbitrary rotation see rotateAxis below. method rotate takes real angleX, real angleY, real angleZ returns nothing local real xp = 0.0 local real yp = 0.0 local real zp = 0.0 set yp = y * Cos(angleX) - z * Sin(angleX) set zp = y * Sin(angleX) + z * Cos(angleX) set xp = x //call BJDebugMsg("Rotate X("+R2S(angleX)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp)) set x = xp set y = yp set z = zp set zp = z * Cos(angleY) - x * Sin(angleY) set xp = z * Sin(angleY) + x * Cos(angleY) set yp = y //call BJDebugMsg("Rotate Y("+R2S(angleY)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp)) set x = xp set y = yp set z = zp set xp = x * Cos(angleZ) - y * Sin(angleZ) set yp = x * Sin(angleZ) + y * Cos(angleZ) set zp = z //call BJDebugMsg("Rotate Z("+R2S(angleZ)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp)) set x = xp set y = yp set z = zp endmethod //rotate the vector about an arbitrary point+axis that is local to this vector, angle in radians //http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html //local variables are extensive in order to show off the usage of the function found in the link above method rotateAxis takes Vector3 p, Vector3 axis, real angle returns nothing local real cos = Cos(angle) local real sin = Sin(angle) //Point around which we are rotating - this can be (0,0,0) for simple local rotation at the origin of the vector. local real a = p.x local real b = p.y local real c = p.z //axis about which we are rotating, helps if normalized local real u = axis.x local real v = axis.y local real w = axis.z //other vars local real u2 = u * u local real v2 = v * v local real w2 = w * w local real d = u2 + v2 + w2 //denominator local real dRoot = SquareRoot(d) local real newX = 0. local real newY = 0. local real newZ = 0. set newX = a*(v2+w2)+u*(-b*v-c*w+u*x+v*y+w*z)+((x-a)*(v2+w2)+u*(b*v+c*w-v*y-w*z))*cos+dRoot*(b*w-c*v-w*y+v*z)*sin set newX = newX / d set newY = b*(u2+w2)+v*(-a*u-c*w+u*x+v*y+w*z)+((y-b)*(u2+w2)+v*(a*u+c*w-u*x-w*z))*cos+dRoot*(-a*w+c*u+w*x-u*z)*sin set newY = newY / d set newZ = c*(u2+v2)+w*(-a*u-b*v+u*x+v*y+w*z)+((z-c)*(u2+v2)+w*(a*u+b*v-u*x-v*y))*cos+dRoot*(a*v-b*u-v*x+u*y)*sin set newZ = newZ / d set x = newX set y = newY set z = newZ endmethod //Dot product another vector onto this one //This is a projection of vector v on this vector. method dot takes Vector3 v returns real return x*v.x + y*v.y + z*v.z endmethod method equals takes Vector3 v returns boolean return x == v.x and y == v.y and z == v.z endmethod method distSqr takes Vector3 v returns real return (v.x-x)*(v.x-x) + (v.y-y)*(v.y-y) + (v.z-z)*(v.z-z) endmethod method printD takes nothing returns nothing debug call BJDebugMsg("Vector["+I2S(this)+"] x: "+R2S(x)+" y:"+R2S(y)+" z:"+R2S(z)) endmethod endstruct //---------------------------------------------------------------------// // External functions //---------------------------------------------------------------------// // // These functions can be used as shortcuts to get a result vector // or value quickly. Note that most of these create brand new vector // structs that you should destroy. //---------------------------------------------------------------------// //remeber to destroy vectors after usage! //** Returns brand new Vector3! function VectorSubtract takes Vector3 v1, Vector3 v2 returns Vector3 return Vector3.create(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z) endfunction //** Returns brand new Vector3! function VectorAdd takes Vector3 v1, Vector3 v2 returns Vector3 return Vector3.create(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z) endfunction //** Returns brand new Vector3! function VectorMultiply takes Vector3 v1, Vector3 v2 returns Vector3 return Vector3.create(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z) endfunction function VectorDotProduct takes Vector3 v1, Vector3 v2 returns real return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z endfunction //** Returns brand new Vector3! function VectorCrossProduct takes Vector3 v1, Vector3 v2 returns Vector3 local Vector3 cross = Vector3.create(0,0,0) if(not v1.equals(v2)) then set cross.x = v1.y*v2.z - v1.z*v2.y set cross.y = v1.z*v2.x - v1.x*v2.z set cross.z = v1.x*v2.y - v1.y*v2.x else //default two parallel vectors to a global right //for whatever reason set cross.x = 1 endif return cross endfunction //Return a normalized desired velocity, make sure to destroy after use //** Returns brand new Vector3! function VectorSeek takes Vector3 target, Vector3 origin returns Vector3 local Vector3 v = VectorSubtract(target, origin) call v.normalize() return v endfunction function Init_Vector3 takes nothing returns nothing set WORLD_UP = Vector3.create(0,0,1)//z axis = up set WORLD_BACK = Vector3.create(0,-1,0)//z axis = up set WORLD_ZERO = Vector3.create(0,0,0) set REAL_UP = Vector3.create(0,1,0) endfunction endlibrary Here is a short example: HOW TO USE://---------------------------------------------------------------------// // Custom Fire Projectile //---------------------------------------------------------------------// struct FireBoltMissile extends Projectile real damage = 0.0 //if the firebolt hits the ground, make it explode? method onHitGround takes nothing returns nothing call this.terminate() endmethod method onUnitHit takes unit target returns nothing if ((HasHomingTarget() and target == homingTarget) or (not HasHomingTarget() and this.source != target and not IsUnitAlly(target, owner) and IsUnitAlive(target))) then //Damage the target unit here! call this.terminate() endif endmethod endstruct function CastFireBolt takes nothing returns nothing local unit caster = [Set this to the caster of the spell] local unit target = [Set this to the target to home in on] local FireBoltMissile p //Where the fireball come out from local Vector3 origin = Vector3.create(GetUnitX(caster), GetUnitY(caster), 50.0) //Where the fireball is headed initially. The system changes this //automatically if you set a homing target (like below) local Vector3 target = Vector3.create(GetUnitX(target), GetUnitY(target), 0) //What direction should the fireball be facing at the start? //In this case, in the direction the caster is facing, and up at 45 degrees. local Vector3 direction = Vector3.fromDegreeAngles(GetUnitFacing(caster), 45.0) //this is important. If the length of the direction vector != 1.0 then it looks bad call direction.normalize() //Create fire projectile here set p = FireBoltMissile.create(origin, direction, target) set p.Source = caster set p.fxpath = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl" set p.speed = 700.0 set p.maxTurnXY = 400.0 * bj_DEGTORAD set p.maxTurnZ = 250.0 * bj_DEGTORAD set p.lifeTime = 5.0 set p.homingTarget = target set p.homingDelay = 0.2 //firebolt specific set p.damage = 50.0 set caster = null set target = null //Don't destroy the vectors. The projectile will do it when it dies. endfunction Extra model resources thanks to: Vexorian (Dummy model), Dan van Ohllus (Missile), s4nji (Icy Spike) |
| 01-04-2011, 05:14 AM | #2 |
| 01-04-2011, 07:57 PM | #3 |
Thanks for the reply. Yes I imagined people would have already this kind of thing many times. I wanted to give it a shot myself and see how it went. Edit: I added a "How to use" section. |
