• 🏆 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!

[System] Missile

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I may have to sometimes rush into approving because otherwise nothing's ever going to get approved and everone cries about their resource for still being pending.

Anyway nice to see some bug fixes. I have another call and this message has been interrupted too many times so I have lost my train of thought, so whatever useful information I was going to provide I can't provide just yet.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Each struct should share the same linked list. There's no way any map will ever have more than 1500 missiles as most machines wouldn't be able to handle that.


There are other things, but I'm too tired to go through them atm. For example, this should be running on a 32x timer library to minimize overhead on the timers. Also, should put some documentation up on open/curve. Comment your code. Say why you are doing the things you are doing and what you are trying to do in the first place ; P.


This is silly
if IsPointInRegion(WorldBounds.worldRegion,tx,ty) then

Just do an event for when a unit leaves the WorldBounds region rather than checking for it every time.


And yea, plenty more, but I'm too tired atm.
 
Level 7
Joined
Dec 3, 2006
Messages
339
This is silly
if IsPointInRegion(WorldBounds.worldRegion,tx,ty) then

Just do an event for when a unit leaves the WorldBounds region rather than checking for it every time.

I think the event would not fire in time in some situations(high missile speed). I think the if then is fine.
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
I'm thinking of moving to this from Kenny's one (my map has a lot of periodic things due to its complexity and it seems to cause lags on certain PC's when the minions are fighting), however it still lacks certain things:
-projectile scaling
-timed missiles (it's not a big deal, but still)
-certain data fields, like "player owner, real damageDealt" and so forth, for easier data attaching; yes, you can implement them to your struct, but they're so commonly used things that you'd almost always have to create those members for the missile struct.
-the dummy unit gets removed instantly; what if my model has a cool death anim?
-it should also have some methods that allows the user to restart the missile with different attributes, rather than creating a new one.

Well, that's all for now.
 
Level 6
Joined
Jun 20, 2011
Messages
249
-projectile scaling
yes
-certain data fields
yes

I've also developed an "unpin" method included inside the module that allows you to deattach the missile from the struct without destroying the missile nor the instance.

Now i've come up with some serious issues regarding the API of the system, for example it would be cool for the missile to always point towards a given "location" or "position" and the user is able to change it's coordinates whenever it wants, currently takes a lot of work to do that (Because of this the use of Nes's Position looks better and better everytime)
Any API-related suggestion is very welcome
 
The simpler the API, the better.
Also, you should think about how the user is going to manage remembering the API.
UnitIndexer's API is really easy. You can easily guess most of the function names because of Jass convention:

GetUnitId
GetUnitById
etc..

Here, CreateMissile is good because I know that whenever I create something in Jass,
I'm going to use the function CreateXXXX (ex: CreateUnit, CreateTrigger, CreateImage, CreateItem, CreateQuest, etc..)
RemoveMissile and DestroyMissile are also acceptable.
MissileStart would be good as an alternate wrapper for the Launch function along with another LaunchMissile function (MissileStart is derived from TimerStart)

edit

JASS:
        method operator angle= takes real value returns nothing
            set cA=value
            set sX=sM*Cos(value)
            set sY=sM*Sin(value)
            set aX=aM*Cos(value)
            set aY=aM*Sin(value)
            if target!=null and distance!=0 then
                call MoveLocation(GetZ,x+(distance-slide)*Cos(value),y+(distance-slide)*Sin(value))
                set fZ=(tZ+GetLocationZ(GetZ)-z)/distance
            endif
        endmethod

Oh and you should cached Sin(value) and Cos(value) here.
They're repeated 3 times, so caching both of them is a plus. (You don't have to null them, so you're gaining speed now)
 
Level 12
Joined
Mar 28, 2005
Messages
160
from method deflect:

Code:
set angle=2*Atan2(ty-y,tx-x)+bj_PI-cA
looks oddly familiar.....

haven't peaked in the map itself, and not expecting a system req., but a shout out would be nice

with that said, from your parabolic function:

Code:
set this.z=4*this.height*s*(d-s)/(d*d)+this.oZ+this.fZ*s
looks strikingly similar as well.....

a few more thoughts:

  1. what if I want to collide with something more than once? hell what if I want to collide two missiles? or terrains?
  2. why do you care so much how much distance we have left to travel to a target? lots of extra maths about these parts too (distance doesn't need square roots and multiplications).
  3. seems a bit excessive for pitch angle (dummy unit animation index) figuring. how about simply as a function of z velocity?

haven't read through this thread - perhaps a few of the aforementioned have been previous talking points...

I seriously need to release for review a little of the alchemy I have been dabbling in
 
Level 6
Joined
Jun 20, 2011
Messages
249
Proper credit hasn't been awarded by the use of both equations (deflect by you and parabola by acehard) i'll make sure to include that in the next update.
-Collide missiles? Possible, you have to search missiles nearby periodically using the MissileList struct and deflect it from the other's missile position.
-Distance left: I'm not following, how are you going to calculate distance without using multiplication or square root? I case you havent noticed those calculations are only done when the missile is created and, in the case the missile has a target, every period (otherwise it won't do the calculations again).
-Pitch angle: The figuring might be complex, but it's perfect, it's the derivative of the parabola function plus the derivative of the z decay. I've seen how other systems try to handle this in a "simpler" way, but end up screwing it up
 
Level 12
Joined
Mar 28, 2005
Messages
160
Well if I am moving with a velocity v/s, my distance traveled will be v*Timeout/s, totaldistance=totaldistance+velocity. no? you take speed as an argument for a reason...

In anycase, I just don't see why we need to know the values accurately every iteration...you don't do anything with them

As for dummy animations,

something I am working on calculates it as a function of z velocity, which makes things a whole lot easier

you can handle these pitches with infinite accuracy because you are controlling the parabolic movement directly. that is fine and dandy. but its overkill IMO. You are constantly solving for the slope of the tangent line for every new height change point, which in your case can be more easily figured. we know that the velocity is positive going up, and negative going down, and we keep track of said value dynamically. select an arbitrary value for a maximum absolute z velocity (this is neither hard nor grossly inaccurate), and simply adjust animations as a function of this maximum, given the current z velocity.

there is only so fast we can go up in WC3 before we literally eject from the screen, anything above, or realistically, anything remotely close to that value could be assumed to approach our maximum - establish this set point, and go from there. in the end the same thing you are doing, just less math.

P.S. - are you positive your derivation is accurate? you seem to be missing too many variables, unless there is some expansion and simplification I'm not following, or a particular derivation rule I've forgot....
 
Last edited:
Level 6
Joined
Jun 20, 2011
Messages
249
I'm 100% sure it's accurate.

>In anycase, I just don't see why we need to know the values accurately every iteration...you don't do anything with them

I already explained in the post before that I only need to know those values if the missile has a unit target. Do a re-check on how the "slide" and "distance" variables work (sM means the speed modulo, i know the documentation is close to non-existant)

You would have to explain further a way to avoid accurate z pitch without making it look bad
 
Level 12
Joined
Mar 28, 2005
Messages
160
lets say, for shits and giggles, that a zvelocity of 50wc3_units/timeout_interval is fast enough to be assumed some terminal zvelocity for missiles. anything above this and things just shoot out of the screen so fast you can't even see them.

our dummy unit has animations from 0-181 (there or about, forget the exact numbers iNfraNe used)

at 50 zvelocity, we would want the maximum pitch up, which is an animation index of 0, when traveling at -50 zvelocity, we would want the maximum pitch angle down, which is an animation index of 181 (I think this is the correct directions). zvelocity fractions within our range of 100 is easy enough to compute, and convert to a specific animation index within its range (0-181).

what this really relies on is the fact that we have a finite number of animations to use, so we don't need infinite accuracy when solving for them. Close enough is perfectly acceptable, since the model can only get so close anyway.

something like:

animationid = R2I(90-zvelocity*1.8)

would work out swimmingly

when zvel=0, animationid is 90
when zvel=50(our max), animationid is 0
when zvel=-50 (max in the other direction), animationid is 181

when zvel=25 (somewhere in the middle), animationid is 45(somewhere in the middle)
when zvel=-10, animationid is 108

and here is the beauty of it - zvelocities above or below 50/-50 don't matter, because trying to set the animation index outside of the acceptable range results in the closest allowable value actually being used :)

having said all that - the parabolic function you employ relies on known values for current and max distance (in your case s and d).

try:

animationid = R2I((currDistance/maxDistance)*181)

on for size.... :) this should provide 100% similar values to your method, theoretically. there in lies one problem, what if its a flat parabola - we still reach our maximum and minimum pitches - which isn't realistic....which is why we should be basing them off zvelocities, and not distance traveled along our arc.

thoughts?

its hard to compare these values to your math, but what you could do is solve for a zvelocity at a given point on your predefined parabola (rise over run in our case), and compare your computed animationid to one of the examples above, assuming a similar zvelocity

pitch should be entirely dependent on how fast or slow the missile is traveling up or down
 
Level 6
Joined
Jun 20, 2011
Messages
249
It would take out some complexity out of the equation i'm currently using.

Atan(-(8*this.height*s-4*d*this.height)/(d*d))
->
R2I((currDistance/maxDistance)*181)

But i wouldnt use "181" because that means that the missile has pitch 0 when it starts and 180 when it lands.
Maybe some kind of calculation to ease the angle, managed by a private real variable called "zF"
when height is assinged then
zF = Atan(-4*this.height/d)
And it would be equal to the angle of the parabola at point 0.
Resulting equation for pitch
R2I( zF-(currDistance/maxDistance)*2*zF )
 
Level 12
Joined
Mar 28, 2005
Messages
160
it would have pitch 181 when it lands, no?

1/1*181=181 ???

why does the angle need any easing??? or what exactly do you mean by easing?

I just don't see why you need all that math, or what this.height has anything to do with what the pitch should be (what if our parabola is flat, but starts at a high height)

I just ran some numbers and seem to get very similar values

where does "Atan(-4*this.height/d)" come from?

when start height is 50 (reasonable), zF=-.19. how is this the start angle (radians), which is -10 degrees, that makes no sense to me.

and why are you doing "zF-(currDistance/maxDistance)*2*zF"???

could you please explain?
 
Level 6
Joined
Jun 20, 2011
Messages
249
zF = Atan(4*this.height/d)
Comes from the derivative function I wrote based on AceHart's parabola... but simplified taking in considerance that at start "s" or "currDist" is equal to 0.

Simplification and result
Original: Atan(-(8*this.height*s-4*d*this.height)/(d*d))
Step 1: Atan(-(8*this.height*s-4*d*this.height)/(d*d)) (s is equal to 0 therefore the whole multiplication is removed)
Step 2: Atan(-(-4*d*this.height)/(d*d)) (simplify the "d" var)
Step 3: Atan(-(-4*this.height)/(d)) (simplify the minus)
Result: Atan(4*this.height/d)

The angle needs easing because it makes no sense that a missile with a very low parabola height ends up looking straight towards the floor (180 pitch angle) when it's pitch should be tangent to the parabola's curve.

Explanation to the "zF-(currDistance/maxDistance)*2*zF" equation
Start (0 currDistance): pitch = zF - 0 (that means the starting parabola angle)
Max parabola point (currDistance is equal half the distance): pitch = zF - zF*2*0.5 (that means 0)
End (currDistance is equal to distance): pitch = zF - zF*2 (that means negative starting parabola angle, and because parabolas end the same way they started its also the angle of the end of the parabola)
 
Level 12
Joined
Mar 28, 2005
Messages
160
we set pitch as a function of z velocity, so a parabola with a very flat curve will never reach anywhere close to a pitch of 0 or 181, it will rarely deviate from 90, because its z velocity will never approach our terminal z velocity. so easing isn't necessary

just don't see why we need all that extra math, for that same exact result...
 
Last edited:
JASS:
        method operator angle= takes real value returns nothing
            set cA=value
            set sX=sM*Cos(value)
            set sY=sM*Sin(value)
            set aX=aM*Cos(value)
            set aY=aM*Sin(value)
            if target!=null and distance!=0 then
                call MoveLocation(GetZ,x+(distance-slide)*Cos(value),y+(distance-slide)*Sin(value))
                set fZ=(tZ+GetLocationZ(GetZ)-z)/distance
            endif
        endmethod

Sin x3
Cos x3

I think you know where this is going.
 
Level 6
Joined
Jun 20, 2011
Messages
249
I'm performing updates to Missile (v2.0.0) in this pastebin. It also includes the newest changes to Loc

Basically what has changed?
-The API, now it uses Loc it decimated the API
-Vector components, removed. They didn't have such a great impact on performance anyways. Reason: Now that it uses Loc it needs to do calculations to the angle between them every period
-Uses CTL
-Added more methods to the interface, and more to come.

Side note to emjlr3: Did a benchmark on the system using the math we discussed before, no performance improvements, I'm still open to do more tests.
 
Level 6
Joined
Jun 20, 2011
Messages
249
Indeed, but in this case because of the less intensive math also some of the accuracy is lost (and it's noticeable, specially when the parabola height is high).

zF - 2*zF*(d-s) is linear (if it starts at X it ends at -X)
 
Level 6
Joined
Jun 20, 2011
Messages
249
It doesn't!
Remember that zF is set to the initial angle of the parabola when the height is set
JASS:
method operator height= takes real h returns nothing
    //... height stuff ...//
    set zF = Atan(4*h/distance)
endmethod
So lets say that zF is equal to 30 degrees,
start: 30
half-way: 0
end: -30
And yes, the derivative function i'm currently using has the same values at those 3 points, but the whole parabola transition looks a lot weirder because the derivative calculates a parabola angle, this function calculates a ciruclar angle.

Now regarding recent updates to the Missile system shown in the pastebin:
First do realize that i took all the extra functionallity from Loc and created a branch out of it called AdvLoc, so Loc would handle the location native replacement and AdvLoc all extra useful functions I made up myself.
Now that Missile always aims to an AdvLoc named "impact" and the user can move it, it had to calculate the vector components every period (which are speed, acceleration and slide, which happen to change depending on the angle the missile has) and making the system ultimately slower, but I came up with a solution.
What if... these vector components are managed by AdvLoc as well?
By running a few optional textmacros on AdvLoc I can fully store all these components within the impact variable, and, if the user moves it, the values would automatically update without trigger evaluations or periodic calculations, just by calling the AdvLoc.move method.
Downside? The vars implemented into AdvLoc would be public, but the user shouldn't access them, Also it would mean some overhead for AdvLocs that are not in need of these vars because it would either add an if check or do unnecesary calculations.
Solutions: Create a somehow complex system of Loc extensios that store their own variables are automatically updated whenever the user moves the Loc (and I don't think that trigger evaluations are the way to go) using keywords or such, i'll work on that
 
Level 6
Joined
Jun 20, 2011
Messages
249
v2.0.0
-Added AdvLoc and CTL as a requirement.
-Uses typeid for struct indexing
-Improved performance
-Added the alternative createLOC method
-Added the origin and impact AdvLoc members to the struct. These are crucial to where the missile is going and to where the missile comes from. If you move one of them the missile changes its course.
-Added the onMissile method to the Missile interface, it's a method that detects missile collision, its extremely slow and currently in a beta stage.

I haven't applied emjlr's suggestion because the efficiency gain doesn't add up for the aesthetics loss, I'm still open to discuss the subject

NOTE: This version does not break compability
 
Level 12
Joined
Mar 28, 2005
Messages
160
the efficiency gain doesn't add up for the aesthetics loss

or lack thereof

good to see you add my onMissile suggestion

would have been silly not to include it, I have not thought of any easier way than O(n), except for maybe some algorithm where you allocate small rects around the map, track in which missiles are, and only loop through missiles in a certain rect containing the forLoop missile
 
Level 6
Joined
Jun 20, 2011
Messages
249
would have been silly not to include it, I have not thought of any easier way than O(n), except for maybe some algorithm where you allocate small rects around the map, track in which missiles are, and only loop through missiles in a certain rect containing the forLoop missile

That would be insane.

And the current complexity is not O(n), but O(n^2), each missile that has runs onMissile has to do O(n)... that means O(n) done n times.
But i've already thought of a better algorithm: If a missile knows what's its distance towards another missile, then the other missile should know that it has the exact same distance towards this one. If applied that concept to enumerating through every missile the complexity should go down to O(n log n).
But before doing that I have to explain some key points on how the system works
-Everys struct has a linked list head called NODE
-Missiles created within the struct are inserted into this head, creating that way a system of linked lists, one per each struct, separated from other lists.
-Because of this there isn't really a way to enumerate through all missiles, reason why I need to create some sort of "global" linked list where are missiles are inserted besides being inserted into their struct head.
-This "global" list was formely known as the MissileList struct
Until I find another way to enumerate through them all, maybe changing how the struct works without dropping efficiency this would stay the same.
 
Level 7
Joined
Apr 30, 2011
Messages
359
can you include some better documentation?

some Q:
1. what value should i put in Arc and Curve
2. what is the difference between Arc and Height, and Curve and Open?
 
Level 10
Joined
May 27, 2009
Messages
494
since nes isn't still finalizing his little own lib

perhaps dirac should update this lib's documentation because the documentation is different from how the script works...

the create method and the test script follows this documentation
JASS:
*       static method create takes real originX, real originY, real originZ...
*                               ...real targetX, real targetY, real targetZ returns Missile

while the lib uses this kind of method
JASS:
static method create takes real ox, real oy, real oz, real a, real d, real iz returns thistype
            local AdvLoc o = AdvLoc.create(ox,oy,oz)
            local AdvLoc i = AdvLoc.create(ox+d*Cos(a),oy+d*Sin(a),iz)
            call AdvLoc.link(o,i)
            static if LIBRARY_MissileRecycler then
                return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
            else
                return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
            endif
        endmethod

So there isn't any targetX nor targetY but instead angle and dist respectively.

Lol.. it took me time to figure out what kind of problem is causing my script to not follow the correct angle and distance

EDIT:
Ok a small problem
i guess you should move the onPeriod calls on top.. 'cause even though the missile is terminated.. the method is still being called
 
Last edited:
I used this system for a simple missile with a location target with the following parameters:
- collision size: 80
- speed: 12
- height: 65

The missile itself works fine and does what it should, but when I added the onCollide method (no compile errors whatsoever), for some reason, the onCollide method is never called when the missile hits a unit. Can someone give an example on how to use this properly? I basicly just edited the demo code and added the onCollide method. Maybe I'm just doing something wrong?
 
Level 6
Joined
Oct 23, 2011
Messages
182
I used this system for a simple missile with a location target with the following parameters:
- collision size: 80
- speed: 12
- height: 65

The missile itself works fine and does what it should, but when I added the onCollide method (no compile errors whatsoever), for some reason, the onCollide method is never called when the missile hits a unit. Can someone give an example on how to use this properly? I basicly just edited the demo code and added the onCollide method. Maybe I'm just doing something wrong?

make sure onCollide method is above the module implementation
 
Your documentation is invalid.

You state that we should pass in origin coords followed by target coords, but you're actually taking origin coords, angle, distance and ... what is iz?

edit
I have fixed your documentation for you because this was causing a lot of confusion for some people in the past few days.
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
If a missile hits two targets at once and the onCollide returns true for both, the loop will permanently stop. (I had to add a check to both the missileTerminate method so it doesnt double-terminate and to the group onCollide checker so it doesn't look for more if one of them returned true)
 
If a missile hits two targets at once and the onCollide returns true for both, the loop will permanently stop. (I had to add a check to both the missileTerminate method so it doesnt double-terminate and to the group onCollide checker so it doesn't look for more if one of them returned true)
Can you post this "fixed" version of the missile system please?
 
Level 19
Joined
Aug 8, 2007
Messages
2,765
Can you post this "fixed" version of the missile system please?

haven't modded in about a month so can't remember how well this works

JASS:
library Missile /* v2.0.1

*/uses/* 
*/  CTL                         /*  hiveworkshop.com/forums/jass-resources-412/snippet-constant-timer-loop-32-a-201381/
*/  AdvLoc                      /*  hiveworkshop.com/forums/submissions-414/snippet-lacking-loc-209322/
*/  optional WorldBounds        /*  hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
*/  optional MissileRecycler    /*  hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/

Credits:
    -   emjlr3 for the deflect angle equation
    -   AceHart for the parabola equation
    
**********************************************************************/
globals
    //***********************************************************************
    //  This dummy must use Vexorian's dummy model and it's movement type
    //  should be "Hover" if you want to correctly move over water, otherwise
    //  use "None". The dummy must also have Crow Form. If you don't have a
    //  dummy unit in your map then use the one MissileRecycler provides.
    private constant integer DUMMY_RAW_CODE = 'e000'
endglobals
/**********************************************************************
*
*   struct Missile
*
*       static method create takes real originX, real originY, real originZ...
*                               ...real angle, real distance, real targetZ returns Missile
*           -   The angle is in radians.
*           -   Missiles have the following values to be set by you.
*               -   real speed
*               -   real turn
*               -   real open / curve
*               -   real height / arc
*               -   real acceleration
*               -   real collision
*               -   unit target
*               -   unit source
*               -   string model
*               -   integer data
*
*       real x
*       real y
*       real z
*           -   The missile's position
*
*       real slide
*           -   The amount of distance it has covered.
*       AdvLoc origin
*           -   The missile's origin Loc
*       AdvLoc impact
*           -   Where the missile will aim to.
*
*       method deflect takes real x, real y returns nothing
*           -   Deflects the missile from the target point, changing
*           -   it's course.
*
*       method bounce takes nothing returns nothing
*           -   Bounces the missile from it's current Z position.
*           -   Useful when assigning targets to missiles that were
*           -   already active, it fixes the rough Z position change.
*
***********************************************************************
*
*   (optional)
*   static method onCollide takes Missile this, unit justHit returns boolean
*       -   Runs every time the missile collides with something.
*       -   If returns true the missile is destroyed.
*   
*   (optional)
*   static method onPeriod takes Missile this returns boolean
*       -   Runs every period.
*       -   If returns true the missile is destroyed.
*
*   (optional)
*   static method onFinish takes Missile this returns boolean
*       -   Runs whenever the missile finishes it's course.
*       -   If returns true the missile is destroyed.
*
*   (optional)
*   static method onMissile takes Missile this, Missile hit, real range returns boolean
*       -   Runs whenever the missile encounters another missile.
*       -   If returns true the missile is destroyed.
*
*   (optional)
*   static method onRemove takes Missile this returns nothing
*       -   Runs whenever the missile is deallocated.
*
*   module MissileStruct
*
*       static method launch takes Missile toLaunch returns nothing
*
***********************************************************************
*
*   DISCLAIMER:
*
*       -   Missile's default owner is Player(15) and this is meant to
*       -   not be modifiable.
*
*       -   For curved missiles the "x" and "y" coordinates are only
*       -   correct in the moment of launch or impact, but during it's
*       -   air time they ignore the curve it has.
*
*       -   This system can support up to 600 proyectiles with complex
*       -   mathematics (curve, arc, collision) before it lags.
*
**********************************************************************/
    
    struct MissileList extends array
        implement LinkedList
    endstruct
    
    struct Missile extends array
    
        //***********************************************************************
        //  Why LinkedList? In order to reach a high level of dynamism i'm basing
        //  this system on iterating through multiple linked lists, each struct
        //  has one and each struct iterates through it.
        implement LinkedList
        boolean terminated
        AdvLoc impact
        AdvLoc origin
        
        real slide
        real damageDealt
        readonly real x
        readonly real y
        readonly real z
        
        private real    cA  //current angle
        
        private effect  fx  //effect
        private string  fP  //model
        
        private real    dS
        
        readonly unit   dummy
        readonly group  unitsHit
        
        boolean recycle
        boolean wantDestroy
    //    real lastCollideX
    //    real lastCollideY
        unit    target
        unit    source
        real    collision
        real    height
        real    turn
        real    open
        real    damage
        real    speed
        real    acceleration
        integer data
        
        method operator model= takes string path returns nothing
            call DestroyEffect(fx)
            set fP=path
            set fx=AddSpecialEffectTarget(path,dummy,"origin")
        endmethod
        
        method operator model takes nothing returns string
            return fP
        endmethod
        
        method operator curve= takes real value returns nothing
            set open=Tan(value)*origin.distance
        endmethod
        
        method operator curve takes nothing returns real
            return Atan(open/origin.distance)
        endmethod
        
        method operator arc= takes real value returns nothing
            set height=Tan(value)*origin.distance/4
        endmethod
        
        method operator arc takes nothing returns real
            return Atan(4*height/origin.distance)
        endmethod
        
        method operator scale= takes real v returns nothing
            call SetUnitScale(dummy,v,v,v)
            set dS=v
        endmethod
        
        method operator scale takes nothing returns real
            return dS
        endmethod
        
        static method createEx takes unit whichUnit, AdvLoc o, AdvLoc i returns thistype
            local thistype this=allocate()
            
            set source = null
            set target = null
            set acceleration = 0
            set height = 0
            set turn = 0
            set open = 0
            set unitsHit = CreateGroup()
            set collision = 0
            set recycle = false
            set wantDestroy = false
            set fP = ""
            set this.terminated = false
            set x = o.x
            set y = o.y
            set z = o.z
            set origin = o
            set impact = i
        //   set lastCollideX = -1
        //    set lastCollideY = -1
            set cA = origin.angle
            set slide=0
            
            set dummy=whichUnit
            call MoveLocation(Loc.global,o.x,o.y)
            call SetUnitFlyHeight(dummy,o.z-GetLocationZ(Loc.global),0)
            
            call MissileList.base.insertNode(this)
            
            return this
        endmethod
        
        static method createLoc takes AdvLoc o, AdvLoc i returns thistype
            call AdvLoc.link(o,i)
            static if LIBRARY_MissileRecycler then
                return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
            else
                return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
            endif
        endmethod
        
        static method create takes real ox, real oy, real oz, real a, real d, real iz returns thistype
            local AdvLoc o = AdvLoc.create(ox,oy,oz)
            local AdvLoc i = AdvLoc.create(ox+d*Cos(a),oy+d*Sin(a),iz)
            call AdvLoc.link(o,i)
            static if LIBRARY_MissileRecycler then
                return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
            else
                return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
            endif
        endmethod
        
        method bounce takes nothing returns nothing
            call origin.move(x,y,z)
            set slide=0
        endmethod
        
        method deflect takes real tx, real ty returns nothing
            local real a = 2*Atan2(ty-y,tx-x)+bj_PI-cA
            call impact.move(x+(origin.distance-slide)*Cos(a),y+(origin.distance-slide)*Sin(a),impact.z)
            call this.bounce()
        endmethod
        
        method destroy takes nothing returns nothing
            set wantDestroy=true
        endmethod
        
        method terminate takes nothing returns nothing
            call DestroyEffect(fx)
            call DestroyGroup(unitsHit)
          //  if lastCollideX + lastCollideY != -2 then
         //       call SetUnitX(dummy, lastCollideX)
         //       call SetUnitY(dummy, lastCollideY)
         //       call DestroyEffect(AddSpecialEffectTarget(fP,dummy,"origin"))
          //  else
            
          //  endif
            set recycle=false
            set fx=null
            
            static if LIBRARY_MissileRecycler then
                call RecycleMissile(dummy)
            else
                call RemoveUnit(dummy)
            endif
            
            call impact.unlock()
            call origin.unlock()
            
            call MissileList(this).removeNode()
            
            call this.removeNode()
            call this.deallocate()
        endmethod
        
        method move takes nothing returns nothing
        
            local real a
            local real d
            local real s
            local real h
            local real tx
            local real ty
            local real ox
            local real oy
            local AdvLoc o
            
            loop
                exitwhen head
                set o = origin
                set ox = o.x
                set oy = o.y
                set h = height
                
                if target!=null and GetUnitTypeId(target)!=0 then
                    call impact.move(GetUnitX(target),GetUnitY(target),GetUnitFlyHeight(target))
                    set a = Atan2(impact.y-y,impact.x-x)
                    set slide = origin.distance-SquareRoot((impact.x-x)*(impact.x-x)+(impact.y-y)*(impact.y-y))
                else
                    set a = o.angle
                    set target = null
                endif
                
                if turn!=0 and not(Cos(cA-a)>=Cos(turn)) then
                    if Sin(a-cA)>=0 then
                        set cA = cA+turn
                    else
                        set cA = cA-turn
                    endif
                else
                    set cA = a
                endif
                
                set d = o.distance
                set s = slide+speed
                set slide = s
                call SetUnitFacing(dummy,cA*bj_RADTODEG)
                
                set tx = x+speed*Cos(cA)
                set ty = y+speed*Sin(cA)
                set speed = speed + acceleration
                set x = tx
                set y = ty

                if h!=0 or o.slope!=0 then
                    call MoveLocation(Loc.global,tx,ty)
                    set z = 4*h*s*(d-s)/(d*d)+o.slope*s+o.z
                    call SetUnitFlyHeight(dummy,z-GetLocationZ(Loc.global),0)
                    call SetUnitAnimationByIndex(dummy,R2I((Atan(origin.slope)-Atan((8*h*s-4*d*h)/(d*d)))*bj_RADTODEG)+90)
                endif
                
                if open!=0 then
                    set a = 4*open*s*(d-s)/(d*d)
                    set tx = tx+a*Cos(cA+1.57)
                    set ty = ty+a*Sin(cA+1.57)
                    call SetUnitFacing(dummy,(cA+Atan(-(8*open*s-4*d*open)/(d*d)))*bj_RADTODEG)
                endif
                
                static if LIBRARY_WorldBounds then
                    if tx>WorldBounds.maxX or tx<WorldBounds.minX or ty>WorldBounds.maxY or ty<WorldBounds.minY then
                        call destroy()
                    else
                        call SetUnitX(dummy,tx)
                        call SetUnitY(dummy,ty)
                    endif
                else
                    call SetUnitX(dummy,tx)
                    call SetUnitY(dummy,ty)
                endif
                
                
                if s>=d then
                    set recycle = true
                endif
                
                set this = next
                
            endloop
        endmethod
    
    endstruct
    
    globals
        private integer                     ACTIVE  =   0
        private integer                     SIZE    =   0
        private trigger                     FIRE    =   CreateTrigger()
        private integer             array   STACK
        private integer             array   INSTANCES
        private TimerGroup32        array   TIMER
        private Missile             array   NODE
    endglobals
    
    //***********************************************************************
    //  This function runs periodically. Can you see the trigger evaluation
    //  at the end? If you've read T32 then you know exactly what it does.
    //  The loop above is for cleaning up, the SIZE variable keeps track of
    //  how many instances have been deallocated by the user, if higher than
    //  0 then some of them need to be removed. STACK[SIZE] stores the value
    //  of the deallocated instances.
    private function Execute takes nothing returns nothing
        loop
            exitwhen SIZE==0
            set ACTIVE=ACTIVE-1
            set SIZE=SIZE-1
            set INSTANCES[STACK[SIZE]]=INSTANCES[STACK[SIZE]]-1
            if INSTANCES[STACK[SIZE]]==0 then
                call TIMER[STACK[SIZE]].stop()
                if ACTIVE==0 then
                    return
                endif
            endif
        endloop
        call TriggerEvaluate(FIRE)
    endfunction
    
    //***********************************************************************
    //  Adds a new instance to the given struct index (This system attaches
    //  indexes to every struct you implement MissileStruct to) If the amount
    //  of INSTANCES[index] was 0 then it adds the struct's iterate method to
    //  the FIRE trigger for it's evaluation. ACTIVE keeps track of all
    //  allocated instances, if it was 0 that means the timer isn't even
    //  running yet, it needs to be started.
    private function StartPeriodic takes integer index returns nothing
        if INSTANCES[index]==0 then
            call TIMER[index].start()
        endif
        set ACTIVE=ACTIVE+1
        set INSTANCES[index]=INSTANCES[index]+1
    endfunction
    
    //***********************************************************************
    //  Adds the struct's index to the stack to clear it in the Execute
    //  function above.
    private function StopPeriodic takes integer index returns nothing
        set STACK[SIZE]=index
        set SIZE=SIZE+1
    endfunction
    
    module MissileStruct
        
        private static method missileTerminate takes Missile this returns nothing
            if this.terminated then
                return
            endif
            set this.terminated = true
            static if thistype.onRemove.exists then
                call onRemove(this)
            endif
            call this.terminate()
            call StopPeriodic(thistype.typeid)
        endmethod
        
        static method unpin takes Missile this returns nothing
            call this.removeNode()
            call StopPeriodic(thistype.typeid)
        endmethod

        static if thistype.onMissile.exists then
            private static method onMissileLoop takes Missile this returns nothing
                local Missile node = MissileList.base
                local real ox = this.x
                local real oy = this.y
                loop
                    set node = node.next
                    exitwhen MissileList(node).head
                    loop
                        exitwhen node.head
                        if node!=this and onMissile(this,node,SquareRoot((ox-node.x)*(ox-node.x)+(oy-node.y)*(oy-node.y))) then
                            call missileTerminate(this)
                        endif
                        set node = MissileList(node).next
                    endloop
                endloop
            endmethod
        endif

        //***********************************************************************
        //  First it takes the struct's first instance, which is the next to the
        //  head (NODE[thistype.typeid].next) and starts iterating through it until it hits
        //  the head again. Then checks if the missile is marked for recycling,
        //  if so it runs the methods and destroys the missile.
        private static method missileIterate takes nothing returns nothing
            local unit u
            local boolean finished = false
            local Missile this = NODE[thistype.typeid].next
            call this.move()
            loop
                exitwhen this.head
                
                if this.wantDestroy then
                    static if thistype.onFinish.exists then
                        call thistype.onFinish(this)
                    endif
                    call missileTerminate(this)
                    
                else
                    if this.recycle then
                        static if thistype.onCollide.exists then
                            static if thistype.onFinish.exists then
                                if this.target==null then
                                    if thistype.onFinish(this) then
                                        call missileTerminate(this)
                                    else
                                        set this.recycle = false
                                    endif
                                elseif thistype.onCollide(this,this.target) then
                                    call missileTerminate(this)
                                else
                                    set this.recycle = false
                                endif
                            else 
                                if this.target==null then
                                    call missileTerminate(this)
                                elseif thistype.onCollide(this,this.target) then
                                    call missileTerminate(this)
                                else
                                    set this.recycle = false
                                endif
                            endif
                        else
                            static if thistype.onFinish.exists then
                                if thistype.onFinish(this) then
                                    call missileTerminate(this)
                                else
                                    set this.recycle = false
                                endif
                            else
                                call missileTerminate(this)
                            endif
                        endif
                    else
                        static if thistype.onCollide.exists then
                            if this.collision!=0 then
                                call GroupEnumUnitsInRange(bj_lastCreatedGroup,this.x,this.y,this.collision,null)
                                loop
                                    set u = FirstOfGroup(bj_lastCreatedGroup)
                                    exitwhen u==null or finished
                                    if not(IsUnitInGroup(u,this.unitsHit)) and u!=this.target and thistype.onCollide(this,u) then
                                        call missileTerminate(this)
                                        set finished = true
                                    endif
                                    call GroupAddUnit(this.unitsHit,u)
                                    call GroupRemoveUnit(bj_lastCreatedGroup,u)
                                endloop
                            endif
                        endif
                    endif
                endif
                
                static if thistype.onMissile.exists then
                    call onMissileLoop(this)
                endif
                
                static if thistype.onPeriod.exists then
                    if thistype.onPeriod(this) then
                        call missileTerminate(this)
                    endif
                endif
                
                set this = this.next
            endloop
            
            set u=null
        endmethod
        
        static method launch takes Missile this returns nothing
            call StartPeriodic(thistype.typeid)
            call NODE[thistype.typeid].insertNode(this)
        endmethod
        
        private static method onInit takes nothing returns nothing
            set NODE[thistype.typeid] = Missile.createNode()
            set TIMER[thistype.typeid] = TimerGroup32.create(function thistype.missileIterate)
        endmethod
        
    endmodule
endlibrary
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Linked List should be listed in the requirements.

There is a good chance to double terminate a missile (see Arhowk's post above).
It happens, if you declare onCollision and two units are hit during one loop.
This causes logical problems on many levels (LinkedList management, double-freeing an unit in case of DummyRecycler usage, allocation, ...)
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
You forgot to mention that this system requires LinkedListModule and AutoFly (I missed the Crow Form thingy). Placing AutoFly as optional is preferred tho.

EDIT:
Oh, have been mentioned above.

EDIT 2:
I tried to display the value of this.slide in static method onPeriod but it prints weird numbers :/
attachment.php


JASS:
*          real slide
*           -   The amount of distance it has covered
If your documentation is right, then .slide should always be increased. As far as I see, the distance starts to be deducted after it missed its target (target unit was attached)

EDIT 3:
Urgh, I encountered a lot of bugs using this library. I used fixed version by Arhowk and now it works just fine. At least the double termination thingy has fixed. :)

EDIT 4:
My spell started to lag hardly after some minutes (5 minutes or more) and the lags happened only when some missiles are launched.
 

Attachments

  • a.jpg
    a.jpg
    275.6 KB · Views: 331
Last edited:
EDIT 3:
Urgh, I encountered a lot of bugs using this library. I used fixed version by Arhowk and now it works just fine. At least the double termination thingy has fixed. :)
Yeah, I wish Dirac would still be here to apply Arhowk's fix into the topic opener.

Or maybe a mod can do this? Aside from the double termination bug, this missile system is the standard go-to with missiles imho.
 
Top