• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

(xe)projectile

Level 11
Joined
Feb 22, 2006
Messages
752
XEMissile by Anitarf was developed, is more efficient and makes this deprecated: http://www.wc3c.net/showthread.php?t=109660

(xe)projectile v1.1.0


Hey, remember those projectiles in CS that never made it to xe? Well, some time last year I got tired of waiting for Vex to make an xeprojectile system, so I made one myself. I tried to make it as similar as possible to xecollider so I wouldn't have to remember two completely different APIs, and hopefully this helps other people as well.

I hope I'm not being too presumptious naming this xeprojectile, but this was purely to make it fit with the rest of the xe modules.

Changelog

v1.1.0
  • Removed all the delegate abuse from the code preventing xeprojectile.destroy() from causing bugs and simply used jasshelper's 0.9.Z.* support for .destroy() overriding to make xeprojectile.destroy() private.
v1.0.0
  • No actual changes. Just bumped up version number since v0.9.1 seemed stable enough to push it out of beta.
v0.9.1
  • Refactored internal code to make system more robust when it comes to calling xeprojectile.destroy()
v0.9.0
  • Fixed some coding bugs
  • Fixed divide by 0 bug
  • Changed documentation layout
  • Fixed documentation error where it claimed instantiating xeprojectile itself does nothing. In fact you can still have a moving projectile-like special effect just by using the xeprojectile struct.
v0.2.2
  • Fixed bug where a location was unnecessarily being created in the constructor
  • Fixed bug where .targetLoc was not being removed properly after the projectile is destroyed
v0.2.1
  • Fixed bug where projectiles would not fly toward the correct z-value if the terrain z values of the projectile's location and the target location were different
  • Deleted unnecessary local location variable in xeprojectile.executeMovement()
v0.2.0
  • First public release


Credits
  • Vexorian for making xe and the projectile functions in CS

Requires
  • xebasic by Vexorian
  • xefx by Vexorian
  • jasshelper 0.9.Z.0+

Code Library



JASS:
library xeprojectile requires xebasic, xefx

                            //*********************//
                            //     xeprojectile    //
                            //   By: aznricepuff   //
                            //       v1.1.0        //
                            //*********************//

struct xeprojectile
    private delegate xefx fx

    private location loc = null
    private location targetLoc = null
    private real absZ = 0.0
    private real targX = 0.0
    private real targY = 0.0
    private real targZ = 0.0
    real speed = 0.0
    real arc = 0.0
    unit target = null
    real targetZOffset = 0.0
    boolean destroyOnHit = true
    integer data = 0
    boolean start = false
    
    private boolean toDestroy = false
    
    private static xeprojectile array v
    private static integer n = 0
    private static timer t

    static method create takes real x, real y, real z, real dir returns xeprojectile
        local xeprojectile this = xeprojectile.allocate()
        
        set this.fx = xefx.create(x, y, dir)
        set this.loc = Location(x, y)
        set this.z = z
        set this.targetLoc = Location(x, y)
        set this.targetX = x
        set this.targetY = y
        set this.targetZ = z
        set .v[.n] = this
        if (.n == 0) then
            call TimerStart(.t, XE_ANIMATION_PERIOD, true, function xeprojectile.executeMovement)
        endif
        set .n = .n + 1
        return this
    endmethod
    
    method operator x= takes real x returns nothing
        call MoveLocation(this.loc, x, this.y)
        set this.fx.x = x
        set this.fx.z = this.absZ - GetLocationZ(this.loc)
    endmethod
    
    method operator y= takes real y returns nothing
        call MoveLocation(this.loc, this.x, y)
        set this.fx.y = y
        set this.fx.z = this.absZ - GetLocationZ(this.loc)
    endmethod
    
    method operator z= takes real z returns nothing
        set this.absZ = z + GetLocationZ(this.loc)
        set this.fx.z = z
    endmethod
    
    method operator targetX takes nothing returns real
        return this.targX
    endmethod
    
    method operator targetY takes nothing returns real
        return this.targY
    endmethod
    
    method operator targetZ takes nothing returns real
        call MoveLocation(this.targetLoc, this.targX, this.targY)
        return this.targZ - GetLocationZ(this.targetLoc)
    endmethod
    
    method operator targetX= takes real x returns nothing
        set this.targX = x
        call MoveLocation(this.targetLoc, this.targX, this.targY)
    endmethod
    
    method operator targetY= takes real y returns nothing
        set this.targY = y
        call MoveLocation(this.targetLoc, this.targX, this.targY)
    endmethod
    
    method operator targetZ= takes real z returns nothing
        set this.targZ = z + GetLocationZ(this.targetLoc)
    endmethod
    
    method terminate takes nothing returns nothing
        set this.toDestroy = true
    endmethod
    
    private method destroy takes nothing returns nothing
        call this.fx.destroy()
        call RemoveLocation(this.loc)
        call RemoveLocation(this.targetLoc)
        call this.deallocate()
    endmethod
    
    stub method onHit takes nothing returns nothing
    endmethod
    
    stub method onTick takes nothing returns nothing
    endmethod
    
    static method executeMovement takes nothing returns nothing
        local integer i = 0
        local real curX
        local real curY
        local real curZ
        local real targetX
        local real targetY
        local real targetZ
        local real dx
        local real dy
        local real increment
        local real theta
        local real phi
        local real distanceRemaining
        local real zAccel
        local real zSpeed
        local real time
        local boolean done
        local xeprojectile this
        
        loop
            exitwhen (i >= .n)
            
            set this = .v[i]
            if (this.toDestroy) then
                call this.destroy()
                set .v[i] = .v[.n - 1]
                set .n = .n - 1
                set i = i - 1
                if (.n == 0) then
                    call PauseTimer(.t)
                endif
            elseif (this.start) then
                set done = false
                set curX = this.x
                set curY = this.y
                set curZ = this.absZ
                
                set targetX = this.targX
                set targetY = this.targY
                set targetZ = this.targZ
                if (this.target != null) then
                    if (GetWidgetLife(this.target) < 0.405) then
                        set this.target = null
                    else
                        set targetX = GetUnitX(this.target)
                        set targetY = GetUnitY(this.target)
                        call MoveLocation(.targetLoc, targetX, targetY)
                        set targetZ = GetUnitFlyHeight(this.target) + this.targetZOffset + GetLocationZ(.targetLoc)
                    endif
                endif
                set dx = targetX - curX
                set dy = targetY - curY
                set theta = Atan2(dy, dx)
                set this.xyangle = theta
                if (this.speed > 0) then
                    set increment = this.speed * XE_ANIMATION_PERIOD
                    set distanceRemaining = SquareRoot(dx * dx + dy * dy)
                    if (distanceRemaining <= increment) then
                        set this.x = targetX
                        set this.y = targetY
                        set this.absZ = targetZ
                        set this.fx.z = this.absZ - GetLocationZ(this.loc)
                        set done = true
                    else
                        set this.x = curX + increment * Cos(theta)
                        set this.y = curY + increment * Sin(theta)
                        
                        set zAccel = this.arc * 8000
                        set time = distanceRemaining / this.speed
                        set zSpeed = (targetZ - curZ + 0.5 * zAccel * time * time) / time
                        set phi = Atan2(zSpeed, this.speed)
                        
                        set this.zangle = phi + bj_DEGTORAD
                        set this.absZ = curZ + zSpeed * XE_ANIMATION_PERIOD
                        set this.fx.z = this.absZ - GetLocationZ(this.loc)
                    endif
                endif
                set distanceRemaining = (this.x - targetX) * (this.x - targetX) + (this.y - targetY) * (this.y - targetY)
                if (done or distanceRemaining <= 20.0) then
                    set this.start = false
                    call this.onHit()
                    if (this.destroyOnHit) then
                        set this.toDestroy = true
                    endif
                else
                    call this.onTick()
                endif
            endif
            
            set i = i + 1
        endloop
    endmethod
    
    private static method onInit takes nothing returns nothing
        set .t = CreateTimer()
    endmethod
endstruct

endlibrary


Documentation



Code:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
======================================= xeprojectile API ========================================
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

-------------
Version 1.1.0
-------------

This module requires xebasic and xefx.

To make a projectile, you first need to create a struct that extends xeprojectile. Trying to 
instantiate xeprojectile itself will do very little (it will work, but the most you could do 
with it is create a moving, projectile-like special effect). In your struct you can make as many 
fields and methods as necessary, allowing for lots of customization, as long as they dont 
conflict with the members in xeprojectile.

The xeprojectile struct delegates a bunch of stuff to xefx (like xecollider). So anything xefx 
can do, xeprojectile can also do. Check out xefx's documentation for more info.

====================================== struct xeprojectile ======================================

The xeprojectile struct is the core of this module. By itself, it can simulate a moving special 
effect with a trajectory modeled after normal projectiles in wc3. By extending this struct with 
others and overriding the xeprojectile.onHit() and xeprojectile.onTick() methods, however, you 
can create customized projectiles that execute specific code when they hit their targets and 
every XE_ANIMATION_PERIOD seconds during their flight path.


Instance Fields
===============

------
real arc = 0.00
------
The arc of this projectile's flight path. Has the same effect on this projectile as 
the "Missile - Arc" data field in the Object Editor has on a unit's projectile missile. The 
greater the value, the larger the arc in which the projectile will fly.

------
integer data = 0
------
Theoretically attaching is not necessary since any data you need to save should go to an 
instance field in your projectile struct, but just in case, you can use this field to attach 
structs and such to this projectile.

------
boolean destroyOnHit = true
------
Determines whether this projectile is automatically destroyed when it hits its either its 
target coordinates or unit. Note that if this is set to false, the projectile will have to be 
manually destroyed using terminate().

------
real speed = 0.0
------
The (ground) speed of this projectile in wc3 distance units per second.

------
boolean start = false
------
Determines whether the projectile is moving or not. When this field is set to true, this 
projectile will begin to fly toward its target. If this field is set to false, this projectile 
will not move and will not register the onTick() or onHit() events. This field is automatically 
set to false after the projectile hits its target.

------
unit target = null
------
The target unit of this projectile. If this field is non-null, the projectile will ignore the 
xeprojectile.targetX, xeprojectile.targetY, and xeprojectile.targetZ fields and instead home in 
on the specified target. If this field is null, the projectile will use the 
xeprojectile.targetX, xeprojectile.targetY, and xeprojectile.targetZ fields instead.

------
real targetX
------
The x-coordinate of this projectile's target point. The default value is the x-coordinate of 
the location where the projectile was initially created. Note that this field has no effect if a 
unit target is set.

------
real targetY
------
The y-coordinate of this projectile's target point. The default value is the y-coordinate of 
the location where the projectile was initially created. Note that this field has no effect if a 
unit target is set.

------
real targetZ = 0.0
------
The z-offset of this projectile's target point. Note that this field has no effect if a unit 
target is set.

------
real targetZOffset = 0.0
------
The z offset of this projectile's target point above the target unit. If the field 
xeprojectile.target is null, this field has no effect.


Instance Methods
================

------
stub method onHit takes nothing returns nothing
------
This method is called when this projectile hits its target (either a point or a unit). This 
method is called before xeprojectile.destroyOnHit is evaluated, so if xeprojectile.destroyOnHit 
is set to true, this method can still stop the destruction of this projectile by setting the 
boolean to false. This method is also called after the field xeprojectile.start is set to false, 
so if for any reason you need this projectile to continue moving, simply set xeprojectile.start 
to true and reset the appropriate target data.

------
stub method onTick takes nothing returns nothing
------
This method is called every timer loop AFTER all movement calculations have been done. It 
will not be called if this projectile has hit its target; xeprojectile.onHit() will be called 
instead.

------
method terminate takes nothing returns nothing
------
Destroys this projectile.
 
Last edited by a moderator:
Level 14
Joined
Nov 23, 2008
Messages
187
Hmm, why are you creating locations every time? I think, "static" location should be better and faster.

JASS:
struct xeprojectile
    // . . .
    private static location loc
    // . . .

    // just an example of conversion:
    method operator z= takes real z returns nothing
        call MoveLocation(xeprojectile.loc, .x, .y)
        set .projZ = z + GetLocationZ(xeprojectile.loc)
    endmethod

    // . . .

    static method doInit takes nothing returns nothing
        set .t = CreateTimer()
        set .loc = Location(0, 0)
    endmethod
endstruct

The other tip is to move code, that pauses timer, there:

JASS:
            if (this.toDestroy) then
                call this.destroy()
                set .v[i] = .v[.n - 1]
                set .n = .n - 1
                set i = i - 1
                // here
                if (.n == 0) then
                    call PauseTimer(.t)
                endif
                // because .n changes only in this IF-block.
            elseif ......

Next:

JASS:
set distanceRemaining = (this.x - x2) * (this.x - x2) + (this.y - y2) * (this.y - y2)

Does this needs to be in SquareRoot(), isn't it?
 
Level 11
Joined
Feb 22, 2006
Messages
752
Hmm, why are you creating locations every time? I think, "static" location should be better and faster.

Wtf?... Ok well obviously I wasn't paying attention when I was copy-pasting this thing cuz this is an old version. My current version just saves the location for every projectile in an instance variable (still not a static, but...doesn't require MoveLocation() calls in the method operator getter and setter for .z). Gonna update soon.

The other tip is to move code, that pauses timer

If I move it into the loop, I make a needless boolean comparison operation every time the loop iterates when I really only need for it to happen once.

Does this needs to be in SquareRoot(), isn't it?

No. I don't actually use that variable at that point for anything related to distance other than seeing if the projectile has reached its target, and I take into account that I didn't take the square root when I do the comparison:

JASS:
distanceRemaining <= 20.0

This way I avoid an unnecessary function call.

> Name it differently, this is not a part of xe.

yes it is, it requires xebasic, therefore it as an extension to xe.

Yea, I'll just put the parentheses in the system name (i.e. (xe)projectile) just so people know it's meant to be an xe module but they don't think Vex created it or something.


UPDATE: Fixed the location thing... (didn't bump up version cuz it's not really a new version, just a stupid pasting mistake made by me!), renamed the system (xe)projectile.
 
Level 14
Joined
Nov 23, 2008
Messages
187
0. I don't see the reason for holding local location loc = Location(x, y) in create method.

1. Have not found call RemoveLocation(.targetLoc) in onDestroy method.

2. I still assume to use one location for all projectiles. Currently, two locations per projectile seems as a waste of handles.

aznricepuff said:
If I move it into the loop, I make a needless boolean comparison operation every time the loop iterates when I really only need for it to happen once.
3. No, sir, you're wrong.
Currently, the code (1):

JASS:
if (.n == 0) then
  call PauseTimer(.t)
endif

is executed 1/XE_ANIMATION_PERIOD = 40 times per second. If it's in block (2):

JASS:
            if (this.toDestroy) then
                call this.destroy()
                set .v[i] = .v[.n - 1]
                set .n = .n - 1
                set i = i - 1
                // here
                if (.n == 0) then
                    call PauseTimer(.t)
                endif
            elseif

the code (1) is executed only when this.toDestroy equal to true. Feel the difference?

4. Also, why just not inline
set distanceRemaining = (this.x - x2) * (this.x - x2) + (this.y - y2) * (this.y - y2)

directly into condition
if (done or distanceRemaining <= 20.0) then ?
 
Level 11
Joined
Feb 22, 2006
Messages
752
0. I don't see the reason for holding local location loc = Location(x, y) in create method.

That's not even supposed to be there....gonna fix that.

Have not found call RemoveLocation(.targetLoc) in onDestroy method.

Forgot to add it, thx for pointing it out.

2. I still assume to use one location for all projectiles. Currently, two locations per projectile seems as a waste of handles.

Unless you have hundreds of projectiles instantiated at the same time (and if you do, you have a screwed up map) this isnt a problem. And handle indexes get automatically recycled by wc3 anyway.

No, sir, you're wrong.
Currently, the code (1):

JASS:
if (.n == 0) then
  call PauseTimer(.t)
endif

is executed 1/XE_ANIMATION_PERIOD = 40 times per second. If it's in block (2):

JASS:
            if (this.toDestroy) then
                call this.destroy()
                set .v[i] = .v[.n - 1]
                set .n = .n - 1
                set i = i - 1
                // here
                if (.n == 0) then
                    call PauseTimer(.t)
                endif
            elseif

the code (1) is executed only when this.toDestroy equal to true. Feel the difference?

Yea, I'll change that.

Also, why just not inline
set distanceRemaining = (this.x - x2) * (this.x - x2) + (this.y - y2) * (this.y - y2)
directly into condition
if (done or distanceRemaining <= 20.0) then ?

Cuz I don't feel that the microsecond in processing time that saves from the memory space allocation/read is worth code that ugly.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
The coding is in general good, although there are a few minor pet peeves:

JASS:
(GetWidgetLife(this.target) < 0.405)
Pointless. <= 0.

JASS:
                set x1 = this.x
                set y1 = this.y
                set z1 = this.projZ

                set targetX = .targX
                set targetY = .targY
                set targetZ = .targZ
First, please be consistent with use of this (either always or never use it). Second, it really isn't worth declaring a huge list of locals and just setting the array slots to them--arrays are not that much slower.

Next, I'm not seeing the point in toDestroy.
 
Level 11
Joined
Feb 22, 2006
Messages
752
First, please be consistent with use of this (either always or never use it).

Huh, well there were supposed to be the 'this' identifier in those lines, since the method is static and the 'this' is just a local variable. I'm surprised it still works.

Second, it really isn't worth declaring a huge list of locals and just setting the array slots to them--arrays are not that much slower.

Well, I know at least some of those locals I need, cuz they aren't just copies of the struct instance fields. I'll look thru the code and see if I can weed some out, though.

Next, I'm not seeing the point in toDestroy.

You know, now that you mention it, I don't really see the point either. But I seem to recall a LOOOOONG time ago, when I made something similar to this and didn't use toDestroy and just garbage collected in the same callback, I got a bunch of bugs. I'll leave it in for now while I try to remember why I use it.

EDIT: Update: 0.9.0

Well, about two seconds after I made the original post I remembered why I use toDestroy. It's so that .terminate() works correctly. Otherwise people that call .destroy() outside the system might start destroying projectiles while the system is moving them, etc.

And about those locals, I fixed some of them, I renamed some other ones cuz I was forgetting which variables were supposed to store what (never a good thing). But some of them, like theta, curX, curY, are necessary because even though I could just use .xyangle, .x, .y, respectively, those method operators use function calls, so I thought it would be better to just have to make those function calls once and then use the locals for the rest of the stuff.
 
Last edited:
Level 11
Joined
Feb 22, 2006
Messages
752
I don't see how that's possible. There is no way for me to reliably check to see if a projectile's struct has been destroyed inside that global loop. And I cannot stop a struct from being destroyed and its index recycled by .destroy().

I mention specifically to use .terminate() instead of .destroy() in the documentation so if people can't read that properly they'll likely have way more issues using this system than the .destroy() issue.
 
Level 11
Joined
Feb 22, 2006
Messages
752
The only thing is, I don't even know if that's possible. Maybe if I knew how JASS handles its concurrency issues (or if it even does handle concurrency), I could design something around it. But right now it's just too much work for something tht is already fixed by having people properly read the documentation.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
The only thing is, I don't even know if that's possible. Maybe if I knew how JASS handles its concurrency issues (or if it even does handle concurrency), I could design something around it. But right now it's just too much work for something tht is already fixed by having people properly read the documentation.
Hang all calls to the user (onHit etc) until the end of the projectile's turn.
 
Level 11
Joined
Feb 22, 2006
Messages
752
That doesn't help much, since the user still has access to the struct outside of those methods and can call .destroy() pretty much whenever he feels like it. And there is no way I can block .destroy().

In my opinion, .terminate() is the best option. Dumb users can screw up anything, any code, any system, and for me there's a limit to how far I go to protect those people from themselves. Remembering to call .terminate() instead of .destroy() neatly solves this entire problem completely and isn't even a hard thing to do.

EDIT: Ok, I just came up with a solution. A ridiculously simple one. Update soon.

EDIT: Updated. xeprojectile.destroy() now is completely safe (from the viewpoint of the system anyway...people can still try to access struct pointers after calling .destroy() but that is not my problem).
 
Last edited:
Level 11
Joined
Feb 22, 2006
Messages
752
One arithmetic calculation takes up a fraction of a millisecond (and it doesn't get run every timer loop either; just when a projectile needs to get destroyed). It's easier for me to see that I've accessed index n - 1 and know that I've accessed the last used index than for me to switch around the lines to save myself one arithmetic calculation and have to remember that accessing index n isn't an index out of bounds error because I decremented the array size before I'm technically supposed to.
 
Level 10
Joined
Jun 21, 2007
Messages
643
i'm a terrianer jumping head long into trying to get a working projectile system, now knowing only basics of wc3 i would like to know how i cam implament it, now i know that this system requires another jass trigger for it to work . . but can any one piont me in the right direction of how to get a working projectile trigger . . all it hass to do is be triggerd by a spell and head in a straight line with collision dectection
 
Level 16
Joined
Oct 12, 2008
Messages
1,570
call this.deallocate is this supposed to be in onDestroy Always? Because i have never seen that before, not when using standard onDestroy nor when using custom-made. Or is everybody that is not calling it in a custom onDestroy doing it wrong?
 
Level 11
Joined
Feb 22, 2006
Messages
752
You've never seen it before because it's only been in existence for about 5 days. It's a new thing since jasshelper 0.9.Z.0. You can now override a struct's .destroy() method much like how you could previously override a struct's constructor, .create(). So therefore, just like how you needed to call thistype.allocate() inside an overidden .create() to actually allocate a struct, you need to call this.deallocate() inside an overidden .destroy() to actually deallocate (garbage collect) the struct.
 
Level 9
Joined
Nov 28, 2008
Messages
704
I don't see how that's possible. There is no way for me to reliably check to see if a projectile's struct has been destroyed inside that global loop.

One boolean: "InUse = true" inside the struct declaration. Set it to default to true, and make your destroy method set it to false. It's what I've been doing in my projectile loops anyways.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
It's kind of late, but I've uploaded a test map showing a spell using xeprojectile for those that are interested in this.
It helps having experience in using xefx before using xeprojectile.
It also helps to look at xefx's and xeprojectile's documentation if you don't understand something.

Here's the code:
JASS:
scope Shocker initializer Init
    globals
        private constant integer SPELL_ID = 'A000'     
        private constant string TARGET_SFX = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //SFX played when the unit gets hurt
        private constant string TARGET_ATTACH = "origin"  
        private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
        //Missile Properties
        private constant real HEIGHT = 60. //Missile height on creation
        private constant real ARC = 0.1 //Arc of the missile when reaching the target
        private constant real SPEED = 500. //Speed of missile, in WC3 units         
        private constant real BASE_SCALE = 0.75 //Initial scale of the missile  
        private constant string MISSILE_SFX = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl" //SFX of the missile
        private constant string MISSILE_ATTACH = "chest"
    endglobals
    
    private function Damage takes integer lvl, real time returns real
        return 15.+15*lvl + 30*time
    endfunction  
         
    private struct Data extends xeprojectile //Struct must extend xeprojectile. Consider this struct as the missile itself   
        unit cast
        integer lvl
        real count = 0
        method onHit takes nothing returns nothing //This method deals with when the missile reaches the target.
            call DestroyEffect(AddSpecialEffectTarget(TARGET_SFX,.target,TARGET_ATTACH)) //Note that .target came from xeprojectile
            call UnitDamageTarget(.cast,.target,Damage(.lvl,.count),false,true,ATK_TYPE,DMG_TYPE,null) //Damage is based on .count
            call .terminate() //Use terminate instead of destroy to make sure everything will get cleaned up
        endmethod
        
        method onTick takes nothing returns nothing //Method deals with every interval the missile moves. In this example, it increases .count and makes the missile bigger
            set .count = .count + XE_ANIMATION_PERIOD
            set .scale = BASE_SCALE + .count //Note that xeprojectile allows you to use xefx methods like scale
        endmethod
    endstruct
       
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID     
    endfunction

    private function Actions takes nothing returns nothing
        local unit c = GetTriggerUnit()
        local unit s = GetSpellTargetUnit()
        local real ang = Atan2(GetUnitY(s) - GetUnitY(c),GetUnitX(s) - GetUnitX(c)) 
        local Data D = Data.create(GetUnitX(c),GetUnitY(c),60.,ang) //The create method is the same as xeprojectile. 
        //Setting the missile properties
        set D.fxpath = MISSILE_SFX //Method is from xefx, sets what the missile should look like
        set D.destroyOnHit = true //Determines if you want the missle to be destroyed on impact         
        set D.speed = SPEED        
        set D.target = s //Setting this makes the missile travel to the spell target unit 
        //The following is what it would look like if you wanted the missile to travel to the target's coordinates. In this case, you don't need to set D.target to anything
            //set D.targetX = GetUnitX(s)
            //set D.targetY = GetUnitY(s)
        set D.arc = ARC
        set D.start = true //You should set all of the missile properties before doing this.
        //Sets the other variables in the struct
        set D.cast = c
        set D.lvl = GetUnitAbilityLevel(D.cast,SPELL_ID)        
        set c = null
        set s = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition( t, Condition(function Conditions))
        call TriggerAddAction( t, function Actions ) 
        call Preload(MISSILE_SFX)
        call Preload(TARGET_SFX)
    endfunction
endscope
 

Attachments

  • xeprojectileExample.w3x
    50.7 KB · Views: 121
Top