Help with units not being added to groups correctly.

Kenny

Back for now.
Reaction score
202
So basically:

- Unit comes in range of a projectile.
- If the unit is not in the group, it is added to the group and an onUnitImpact() method is run.
- If the unit is in the group, it is ignored.

It is your usual damage group scenario so that a unit does not get damaged twice. I've done it 1,000,000 times before. However, this time it doesn't want to work for some reason.

Here's the script:
JASS:
library Projectile initializer Init requires AIDS, T32, Event, Vector, AutoFly
 
    //-----------------------------------------------------------------------\\
    //   ____            _           _   _ _                                 \\            
    //  |  _ \ _ __ ___ (_) ___  ___| |_(_) | ___                            \\
    //  | |_) | '__/ _ \| |/ _ \/ __| __| | |/ _ \                           \\
    //  |  __/| | | (_) | |  __/ (__| |_| | |  __/                           \\
    //  |_|   |_|  \___// |\___|\___|\__|_|_|\___|                           \\
    //                |__/               By kenny!                    v1.0.0 \\   
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    //                                                                       \\
    //  To do list:                                                          \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯                                                          \\
    //    - Re-write method operators.                 [99% done]            \\
    //    - Attempt to add vertical collision.         [99% done]            \\
    //    - Add method for resetting velocities.       [99% done]            \\
    //    - Re-write ProjInterface.                    [99% done]            \\
    //    - Should global collision event run first?   [50% done]            \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    //                                                                       \\
    //  What is Projectile?                                                  \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                  \\
    //    Projectile is a basic projectile system for simple and advanced    \\
    //    custom spells. This system provides users with a simple interface  \\
    //    that gives great control over projectiles in a map. Users can:     \\
    //                                                                       \\
    //      - Create normal Projectiles using the simplest of methods        \\
    //        available.                                                     \\
    //      - Create more advanced projectiles by extending structs and      \\
    //        using bonus features.                                          \\
    //      - Group Projectiles in range of XYZ coordinates (not available   \\
    //        in most similar systems).                                      \\
    //      - Register triggers to execute when two projectiles collide and  \\
    //        do stuff with them.                                            \\
    //                                                                       \\
    //  How to implement:                                                    \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                    \\
    //    Simply create a new trigger object called Projectile, go to:       \\
    //    'Edit -> Convert to Custom Text', and replace everything that's    \\
    //    there with this script.                                            \\
    //                                                                       \\
    //    Make sure you implement all the required systems for this system.  \\
    //    That includes: AIDS, T32, Event, Vector and AutoFly.               \\
    //                                                                       \\
    //    Save the map, close it, reopen it, then delete the "!" from the    \\
    //    start of the next line (so "external" lines up with this line):    \\
    //    external ObjectMerger w3u ushd proj unam "Projectile Dummy" uabi Aloc uble 0 ucbs 0 ucpt 0 umxp 0 umxr 0 umdl "dummy.mdl" uimz 0 ulpz 0 uprw 1 ushu "" uspa "" umvs 522 umvr 3 ucol 0  ufle 0000 usnd "" ufoo 0  ubba 0 ubdi 0 ubsi 0 uhom 0001 usid 1 usin 1 utyp "" upgr "" utip "" utub ""
    //                                                                       \\
    //    After you have done that, you are ready to use this system. Enjoy. \\
    //                                                                       \\
    //  Projectile API and usage:                                            \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                            \\
    //    Methods available:                                                 \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                 \\
    //    These methods are available to users who wish to use the most      \\
    //    basic of interfaces.                                               \\
    //                                                                       \\
    //    - Projectile.create(xPos,yPos,zPos,facing) --> Projectile          \\
    //                                                                       \\
    //      The above will create a projectile at a desired location. The    \\
    //      first three agruments in the create method refer to the x, y and \\
    //      z coordinates where you want the projectile to be created. The   \\
    //      last of the arguments refers to which direction (in radians) you \\
    //      want the projectile to face initially.                           \\
    //                                                                       \\
    //    - .project(xPos,yPos,zPos,speed) --> Nothing                       \\
    //                                                                       \\
    //      The above will start the projectile moving towards a desired     \\
    //      location. The first three arguments refer to the x, y and z      \\
    //      coordinates of where you would like the projectile to travel to. \\
    //      The last argument refers to the speed at which you want the      \\
    //      projectile to move. The speed is in units per second.            \\
    //                                                                       \\
    //    - .modifyTargeting(xPos,yPos,zPos) --> Nothing                     \\
    //                                                                       \\
    //      The above will determine the new target coordinates of the       \\
    //      projectile, thus changing its trajectory accordingly. The three  \\
    //      arguments refer to the new x, y and z coordinates of the wanted  \\
    //      target location.                                                 \\
    //                                                                       \\
    //    - .modifySpeed(speed) --> Nothing                                  \\
    //                                                                       \\
    //      The above method will modify the speed of the projectile to what \\
    //      the user has specified. The argument is the new speed that has   \\
    //      to be in units per second. Users can use other struct members to \\
    //      retrieve original, previous and current speeds.                  \\
    //                                                                       \\
    //    - .isUnitInHeightRange(whichUnit) --> Boolean                      \\
    //                                                                       \\
    //      The above is somewhat of a bonus feature that users can make use \\
    //      of when the projectile hits a unit. The boolean returned by this \\
    //      method tells us if the unit is within collision distance of the  \\
    //      projectile in regards to unit and projectile height. This method \\
    //      makes simple vertical collision easier for users. The one unit   \\
    //      argument refers to the unit you want to check.                   \\
    //                                                                       \\
    //    - .terminate() --> Nothing                                         \\
    //                                                                       \\
    //      The above method will destroy a projectile and all data that is  \\
    //      associated with it. This can be called at any time while a       \\
    //      projectile is alive, and multiple calls accidently will not      \\
    //      damage anything.                                                 \\
    //                                                                       \\
    //    Bonus methods available:                                           \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                           \\
    //    These methods are available to users who wish to use a more        \\
    //    advanced interface by extending structs.                           \\
    //                                                                       \\
    //    - .onStart() --> Nothing                                           \\
    //                                                                       \\
    //      Users can implement this into a struct and use it to do extra    \\
    //      stuff when the projectile starts to move such as adding effects. \\
    //                                                                       \\
    //    - .onLoop() --> Nothing                                            \\
    //                                                                       \\
    //      Users can implement this into a struct if they wish to do extra  \\
    //      stuff each iteration of the periodic timer.                      \\
    //                                                                       \\
    //    - .onFinish() --> Nothing                                          \\
    //                                                                       \\
    //      Users can implement this into a struct if they want to do stuff  \\
    //      when the projectile is destroyed. This will always fire.         \\
    //                                                                       \\
    //    - .onLandImpact() --> Nothing                                      \\
    //                                                                       \\
    //      Users can implement this into a struct if they want to do stuff  \\
    //      when the projectile hits the ground. This may not fire depending \\
    //      on whether or not a projectile is terminated early or if it is   \\
    //      designed to not hit the ground.                                  \\
    //                                                                       \\
    //    - .onProjImpact() --> Nothing                                      \\
    //                                                                       \\
    //      Users can implement this into a struct if they wish to do stuff  \\
    //      when two projectiles collide. This will only fire if the         \\
    //      projectile is collideable (see below). There is also an event    \\
    //      for projectile collision (also see below).                       \\
    //                                                                       \\
    //    - .onUnitImpact(whichUnit) --> Nothing                             \\
    //                                                                       \\
    //      Users can implement this into a struct if they wish to do stuff  \\
    //      when a projectile hits a unit. The unit argument in this metod   \\
    //      refers to the unit that has been hit by the projectile. This     \\
    //      can be used in conjunction with .isUnitInHeightRange().          \\
    //                                                                       \\
    //    Public members available:                                          \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                          \\
    //    These members are available for users to freely change and modify  \\
    //    at any time throughout the projectiles life span. However, unless  \\
    //    you fully understand how they work, it is recommended that you     \\
    //    set them only after you create the projectile and no where else.   \\
    //                                                                       \\
    //    - .caster        --> The casting unit of a projectile spell. Can   \\
    //                         be null if the projectile is just for effect. \\
    //                         The caster is not automatically the owner.    \\
    //    - .owner         --> The owning player of the projectile, and      \\
    //                         commonly of the casting unit as well. This    \\
    //                         must be set separately to the caster.         \\
    //    - .timedLife     --> Gives the projectile a life span. By          \\
    //                         default projectiles an unlimited life span.   \\
    //                         Timed life should be in seconds.              \\
    //    - .gravity       --> All projectiles are normally under the effect \\
    //                         of default gravity. Users can set a custom    \\
    //                         gravity for individual projectiles if needed. \\
    //    - .scaleSize     --> Sets the scale size of the projectile. Due to \\
    //                         warcraft limitations this can only be set and \\
    //                         not retrieved.                                \\
    //    - .unitCollision --> The radius in which a unit has to be to be    \\
    //                         considered a target. This does not have to be \\
    //                         defined, as it will be set to default.        \\
    //    - .projCollision --> The radius in which a projectile has to be    \\
    //                         to be considered a target. This does not have \\
    //                         to be defined, as it will be set to default.  \\
    //    - .pauseProj     --> Users can pause all movement of a proj.       \\
    //                         This still allows .onLoop() and projectile    \\
    //                         collision. By default this is false.          \\
    //    - .collideable   --> Users can specify if a projectile is able     \\
    //                         to collide with other projectiles. By default \\
    //                         this is false.                                \\
    //    - .effectPath    --> Users can specify what the projectile looks   \\
    //                         like using this member. The effect path can   \\
    //                         even be retrieved for special effect usage.   \\
    //                                                                       \\
    //    Readonly members available:                                        \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                        \\
    //    Unlike the above members, these are not meant to be changed at any \\
    //    time throughout the duration of the projectile. Therefore, they    \\
    //    are readonly, meaning that you can access them but not change      \\
    //    them.                                                              \\
    //                                                                       \\
    //    - .proj          --> Refers to the projectile unit itself. Can be  \\
    //                         useful for attaching effects amongst other    \\
    //                         things.                                       \\
    //    - .theta         --> Refers to the current XY facing angle of the  \\
    //                         projectile. Does not include Z coordinates.   \\
    //                         Can be useful for some projectile spells.     \\
    //    - .phi           --> Refers to the current XYZ facing angle of the \\
    //                         projectile. Includes Z coordinates as well.   \\
    //                         Can be useful for some projectile spells.     \\
    //    - .pos           --> Refers to the current position of the         \\
    //                         projectile. Users can access: .pos.x / .pos.y \\
    //                         / .pos.z. This is a vector struct             \\
    //    - .vel           --> Refers to the current velocity of the         \\
    //                         projectile. Users can access: .vel.x / .vel.y \\
    //                         / .vel.z. This is a vector struct.            \\
    //    - .start         --> Refers to the starting coordinates of the     \\
    //                         projectile. Users can access: .start.x /      \\
    //                         .start.y / .start.z. This is a vector struct. \\
    //    - .target        --> Refers to the target coordinates of the       \\
    //                         projectile. Users can access: .target.x /     \\
    //                         .target.y / .target.z. Again a vector struct. \\
    //    - .currentSpeed  --> Returns the current speed of a given          \\
    //                         projectile. Returned speed is in units per    \\
    //                         second.                                       \\
    //    - .previousSpeed --> Returns the previous speed of a projectile,   \\
    //                         before its speed was modified. Returned speed \\
    //                         is in units per second.                       \\
    //    - .originalSpeed --> Returns the original speed of a projectile,   \\
    //                         even if its speed has been modified multiple  \\
    //                         times. Returned speed is in units per second. \\
    //    - .isTerminated  --> Returns true if the projectile instance has   \\
    //                         been terminated. Can be of use in quite rare  \\
    //                         circumstances.                                \\
    //                                                                       \\
    //    The static method operator:                                        \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                        \\
    //    - Projectile[whichUnit] --> Projectile                             \\
    //                                                                       \\
    //      The static method operator is for unit to Projectile typcasting. \\
    //      Mainly for use when GroupEnumProjectiles... is used (see below). \\
    //      Users specify a unit and it will return the associated           \\
    //      projectile struct, if there is any.                              \\
    //                                                                       \\
    //    Functions available:                                               \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                               \\
    //    - GroupEnumProjectilesInRange(whichGroup,xPos,yPos,zPos,radius)    \\
    //                                                                       \\
    //      Adds all projectiles within a certain radius of a location       \\
    //      specified by the users to a group also specified by the user.    \\
    //      Units added to the group can then be typecasted into a           \\
    //      Projectile struct using the static method operator.              \\
    //                                                                       \\
    //      The first argument is the group which users want to add the      \\
    //      projectiles too. The next three arguments are the x, y and z     \\
    //      coordinates that users want to check. The last argument is the   \\
    //      radius in which units need to be to be added to the group.       \\
    //                                                                       \\
    //    - RegisterProjectileCollisionEvent(whichTrigger)                   \\
    //                                                                       \\
    //      Registers a specified trigger for projectile collision events.   \\
    //      The trigger will then fire whenever a collision between          \\
    //      projectiles occurs. Users are then given other functions to      \\
    //      retrieve both of the projectiles that collided, so that they     \\
    //      can do something special. The argument refers to the trigger     \\
    //      that users want registered.                                      \\
    //                                                                       \\
    //    - GetFirstCollisionProj()                                          \\
    //                                                                       \\
    //      Retrieves the first projectile from the most recent collision    \\
    //      event. Users can then use all the methods, operators and members \\
    //      from the struct to do something with the projectile.             \\
    //                                                                       \\
    //    - GetSecondCollisionProj()                                         \\
    //                                                                       \\
    //      Same as the above function. This will retrieve the other         \\
    //      projectile that collided in the most recent collision event.     \\
    //      Users can then do stuff with the retrieved struct.               \\
    //                                                                       \\
    //  Basic usage:                                                         \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯                                                         \\
    //    function Actions takes nothing returns nothing                     \\
    //        local unit       caster = GetTriggerUnit()                     \\
    //        local real       castx  = GetUnitX(caster)                     \\
    //        local real       casty  = GetUnitY(caster)                     \\
    //        local real       targx  = GetSpellTargetX()                    \\
    //        local real       targy  = GetSpellTargetY()                    \\
    //        local real       angle  = Atan2((targy-casty),(targx-castx))   \\
    //        local Projectile p      = 0                                    \\
    //                                                                       \\
    //        set p = Projectile.create(castx,casty,50.00,angle)             \\
    //                                                                       \\
    //        set p.caster        = caster                                   \\
    //        set p.owner         = GetOwningPlayer(p.caster)                \\
    //        set p.effectPath    = "Something.mdl"                          \\
    //        set p.scaleSize     = 1.00                                     \\
    //                                                                       \\
    //        call p.project(targx,targy,0.00,1000.00)                       \\
    //                                                                       \\
    //        set caster = null                                              \\
    //    endfunction                                                        \\
    //                                                                       \\
    //  More advanced usage:                                                 \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                 \\
    //    private struct Proj extends Projectile                             \\
    //                                                                       \\
    //        unit target = null                                             \\
    //                                                                       \\
    //        method onUnitImpact takes unit whichUnit returns nothing       \\
    //            if IsUnitEnemy(whichUnit,this.owner) then                  \\
    //                if this.isUnitInHeightRange(whichUnit) then            \\
    //                    call UnitDamageTarget(...)                         \\
    //                endif                                                  \\
    //            endif                                                      \\
    //        endmethod                                                      \\
    //                                                                       \\
    //        method onProjImpact takes nothing returns nothing              \\
    //            call this.terminate()                                      \\
    //        endmethod                                                      \\
    //                                                                       \\
    //        method onStart takes nothing returns nothing                   \\
    //            call DestroyEffect(AddSpecialEffect(...))                  \\
    //        endmethod                                                      \\
    //                                                                       \\
    //        method onLoop takes nothing returns nothing                    \\
    //            local real x = GetUnitX(this.target)                       \\
    //            local real y = GetUnitY(this.target)                       \\
    //            local real z = GetUnitFlyHeight(this.target)               \\
    //                                                                       \\
    //            call this.modifyTargeting(x,y,z)                           \\
    //        endmethod                                                      \\
    //                                                                       \\
    //        method onFinish takes nothing returns nothing                  \\
    //            call DestroyEffect(AddSpecialEffect(...))                  \\
    //        endmethod                                                      \\
    //                                                                       \\
    //    endstruct                                                          \\
    //                                                                       \\
    //    function Actions takes nothing returns nothing                     \\
    //        local unit caster = GetTriggerUnit()                           \\
    //        local unit target = GetSpellTargetUnit()                       \\
    //        local real castx  = GetUnitX(caster)                           \\
    //        local real casty  = GetUnitY(caster)                           \\
    //        local real targx  = GetUnitX(target)                           \\
    //        local real targy  = GetUnitY(target)                           \\
    //        local real angle  = Atan2((targy-casty),(targx-castx))         \\
    //        local Proj p      = 0                                          \\
    //                                                                       \\
    //        set p = Proj.create(castx,casty,50.00,angle)                   \\
    //                                                                       \\
    //        set p.caster        = caster                                   \\
    //        set p.target        = target                                   \\
    //        set p.owner         = GetOwningPlayer(p.caster)                \\
    //        set p.effectPath    = "Something.mdl"                          \\
    //        set p.scaleSize     = 1.00                                     \\
    //        set p.gravity       = 0.00 // Assign no gravity to this proj.  \\
    //        set p.projCollision = 64.00                                    \\
    //        set p.unitCollision = 128.00                                   \\
    //        set p.collideable   = true                                     \\
    //                                                                       \\
    //        call p.project(targx,targy,GetUnitFlyHeight(target),1000.00)   \\
    //                                                                       \\
    //        set caster = null                                              \\
    //        set target = null                                              \\
    //    endfunction                                                        \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    //-----------------------------------------------------------------------\\
    //  Configurables:                                                       \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                       \\
    //    - PROJ_DUMMY_ID      --> The raw code of the dummy unit used for   \\
    //                             the projectiles in this system.           \\
    //    - MAX_UNIT_COLLISION --> The maximum unit collision size allowed   \\
    //                             in your map.                              \\
    //    - DEFAULT_GRAVITY    --> The default gravity used for all the      \\
    //                             projectiles in the map.                   \\
    //    - DEFAULT_UNIT_COLK  --> The default unit collision radius for     \\
    //                             projectiles.                              \\
    //    - DEFAULT_PROJ_COLL  --> The default projectile collision radius   \\
    //                             for projectiles.                          \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    globals
        private constant integer PROJ_DUMMY_ID       = 'proj'
        private constant real    MAX_UNIT_COLLISION  = 256.00
        private constant real    DEFAULT_GRAVITY     = -1000.00
        private constant real    DEFAULT_UNIT_COLL   = 128.00
        private constant real    DEFAULT_PROJ_COLL   = 64.00
    endglobals
    
    //-----------------------------------------------------------------------\\
    //                                                                       \\
    //   DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING!   \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    globals    
        private Event      CollisionEvent = 0    
        private Projectile FirstProjData  = 0
        private Projectile SecondProjData = 0
    endglobals
    
    native UnitAlive takes unit id returns boolean
    
    //-----------------------------------------------------------------------\\
    //  ProjInterface:                                                       \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                       \\
    //    Methods available to users if they wish to extend                  \\
    //    a struct to obtain extra functionality.                            \\
    //                                                                       \\
    //    Methods available:                                                 \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                 \\
    //      - .onStart()  --> Fires when the .start method is used.          \\
    //                        Can be used as an onCreate method as well.     \\
    //      - .onLoop()   --> Fires each interval of the periodic timer.     \\
    //                        More movement functionality can be added here. \\
    //      - .onFinish() --> Fires when the proj instance is destroyed.     \\
    //                        Will fire even if the proj did not hit land.   \\
    //                                                                       \\
    //      - .onLandImpact() --> Fires when the projectile hit the ground.  \\
    //                            Only fires if the proj reaches the ground. \\
    //      - .onProjImpact() --> Fires when two projectiles collide.        \\
    //                            Will only fire if a proj is collideable.   \\
    //      - .onUnitImpact() --> Fires when the projectile hits a unit.     \\
    //                            Can be used for collision missiles.        \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    private interface ProjInterface
        method onStart  takes nothing returns nothing defaults nothing
        method onLoop   takes nothing returns nothing defaults nothing
        method onFinish takes nothing returns nothing defaults nothing
        
        method onLandImpact takes nothing returns nothing defaults nothing
        method onProjImpact takes nothing returns nothing defaults nothing
        method onUnitImpact takes unit whichUnit returns nothing defaults nothing
    endinterface
    
    //-----------------------------------------------------------------------\\
    //  ProjData:                                                            \\
    // ¯¯¯¯¯¯¯¯¯¯                                                            \\
    //    Struct that handles and keeps track of Projectile structs for each \\
    //    dummy unit, making if far easier to associate units from           \\
    //    GroupEnumProjectilesInRange() with their allocated structs.        \\
    //                                                                       \\
    //    Makes use of AIDS structs, and will only work for one dummy unit   \\
    //    type. It is only for internal use within the system.               \\
    //                                                                       \\
    //    Methods available:                                                 \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                 \\
    //      - None.                                                          \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    private struct ProjData extends array
        //! runtextmacro AIDS()
        
        Projectile data
        
        private static method AIDS_filter takes unit whichUnit returns boolean
            return GetUnitTypeId(whichUnit) == PROJ_DUMMY_ID
        endmethod
        
        private method AIDS_onCreate takes nothing returns nothing
            set this.data = 0
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            set this.data = 0
        endmethod
        
    endstruct
    
    //-----------------------------------------------------------------------\\
    //  ProjSpeedMod:                                                        \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                        \\
    //    Projectile speed module. Takes care of modify a projectiles speed  \\
    //    and keeps track of original and previous speeds of each proj.      \\
    //                                                                       \\
    //    This is only here to make it easier for me to read through the     \\
    //    script and add extra functionality when needed.                    \\
    //                                                                       \\
    //    Method operators available:                                        \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                        \\
    //      - .currentSpeed  --> Returns the current speed of a projectile.  \\
    //                           Returned speed is in units per second.      \\
    //      - .previousSpeed --> Returns the previous speed of a projectile, \\
    //                           before it's speed was modified.             \\
    //      - .originalSpeed --> Returns the original speed of a projectile, \\
    //                           even if it's speed has been modified lots.  \\
    //                                                                       \\
    //    Methods available:                                                 \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                 \\
    //      - .modifySpeed() --> Modifies a projectiles speed depending on   \\
    //                           the value given by the user.                \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    private module ProjSpeedMod
        
        method operator currentSpeed takes nothing returns real
            return this.speed * T32_FPS
        endmethod
        
        method operator previousSpeed takes nothing returns real
            return this.oldSpeed * T32_FPS
        endmethod
        
        method operator originalSpeed takes nothing returns real
            return this.oriSpeed * T32_FPS
        endmethod
        
        method modifySpeed takes real value returns nothing
            local vector v = vector.sum(this.pos,this.vel)
            
            set this.oldSpeed = this.speed
            set this.theta    = Atan2((v.y - this.pos.y),(v.x - this.pos.x))
            set this.phi      = Atan2(SquareRoot((v.x - this.pos.x) * (v.x - this.pos.x) + (v.y - this.pos.y) * (v.y - this.pos.y)),(v.z - this.pos.z))
            set this.speed    = value * T32_PERIOD
            
            set this.vel.x    = Sin(this.phi) * Cos(this.theta) * this.speed
            set this.vel.y    = Sin(this.phi) * Sin(this.theta) * this.speed
            set this.vel.z    = Cos(this.phi) * this.speed
            
            call v.destroy() 
        endmethod
        
    endmodule
    
    //-----------------------------------------------------------------------\\
    //  ProjOperatorMod:                                                     \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                     \\
    //    Projectile operator module. This module holds all the method       \\
    //    operators available for use with this system. Again, I only have   \\
    //    them in a module so they are easy for me to find and update. They  \\
    //    will be properly added to the Projectile struct when I think that  \\
    //    they are 100% complete.                                            \\
    //                                                                       \\
    //    Method operators available:                                        \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                        \\
    //      - Projectile[]  --> Returns the Projectile instance associated   \\
    //                          with the unit specified by the user.         \\
    //      - .owner=       --> Sets the owner of the projectile. The owner  \\
    //                          is commonly associated with the caster.      \\
    //      - .owner        --> Retrieves the owner of the projectile.       \\
    //                          Useful for filters and damage functions.     \\
    //      - .scaleSize=   --> Sets the scale size of the projectile. Due   \\
    //                          to warcraft limits this cannot be retrieved. \\
    //      - .gravity=     --> Users can set the gravity for individual     \\
    //                          projectiles if needed.                       \\
    //      - .gravity      --> Retrieves the gravity a projectile is using. \\
    //                          Only useful if custom gravity is set.        \\
    //      - .effectPath=  --> Sets the effect path of the projectile. This \\
    //                          is what the projectile will look like.       \\
    //      - .effectPath   --> Returns the string path of what the proj     \\
    //                          looks like.
    //      - .isTerminated --> Returns true if the projectile has been      \\
    //                          terminated. Rarely useful.                   \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    private module ProjOperatorMod
    
        static method operator[] takes unit whichUnit returns thistype
            return ProjData[whichUnit].data
        endmethod
        
        method operator owner= takes player whichPlayer returns nothing
            set this.own = whichPlayer
            call SetUnitOwner(this.proj,whichPlayer,true)
        endmethod
        
        method operator owner takes nothing returns player
            return this.own
        endmethod
        
        method operator scaleSize= takes real value returns nothing
            call SetUnitScale(this.proj,value,0.00,0.00)
        endmethod
        
        method operator gravity= takes real value returns nothing
            set this.grav.z = ((value * T32_PERIOD * T32_PERIOD) / 2.00)
        endmethod
        
        method operator gravity takes nothing returns real
            return ((this.grav.z * 2.00) / T32_PERIOD) / T32_PERIOD
        endmethod
        
        method operator effectPath= takes string whichPath returns nothing
           if this.sfx != null then
               call DestroyEffect(this.sfx)
               set this.path = ""
           endif
           
           if whichPath == "" then
               set this.sfx  = null
               set this.path = ""
           else
               set this.sfx  = AddSpecialEffectTarget(whichPath,this.proj,"origin")
               set this.path = whichPath
           endif
        endmethod
        
        method operator effectPath takes nothing returns string
            return this.path
        endmethod
        
        method operator isTerminated takes nothing returns boolean
            return this.stop
        endmethod
        
    endmodule
        
    //-----------------------------------------------------------------------\\
    //  Projectile:                                                          \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯                                                          \\
    //    Projectile is the main struct of the system that handles the       \\
    //    creation, movement and destruction of projectiles, as well as unit \\
    //    and projectile collision. It makes use of T32 and vectors to make  \\
    //    sure that minimal strain is put on computers while projectiles are \\
    //    running.                                                           \\
    //                                                                       \\
    //    Users can either use Projectile directly for most common needs, or \\
    //    extend a struct with Projectile for more advanced uses.            \\
    //                                                                       \\
    //    Public members available:                                          \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                          \\
    //      These are members that users can freely change and modify at any \\
    //      time throughout the life time of the Projectile. However, it is  \\
    //      best that all changes be made when the projectile is created.    \\
    //                                                                       \\
    //      - .caster        --> The casting unit of a projectile spell. Can \\
    //                           use null if the proj is just for effect.    \\
    //      - .timedLife     --> Gives the projectile a life span. By        \\
    //                           default projectiles an unlimited life span. \\
    //      - .unitCollision --> The radius in which a unit has to be to be  \\
    //                           considered a target.                        \\
    //      - .projCollision --> The radius in which a projectile has to be  \\
    //                           to be considered a target.                  \\
    //      - .pauseProj     --> Users can pause all movement of a proj.     \\
    //                           This still allows .onLoop() and collision.  \\
    //      - .collideable   --> Users can specify if a projectile is able   \\
    //                           to collide with other projectiles.          \\
    //                                                                       \\
    //    Readonly members available:                                        \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                        \\
    //      These are members that users are not meant to change or modify.  \\
    //      However, they are still useful for certain aspects of spells,    \\
    //      therefore users are allowed to access them but not change them.  \\
    //                                                                       \\
    //      - .proj   --> Refers to the projectile unit itself. Can be       \\
    //                    useful for attaching effects amongst other things. \\
    //      - .theta  --> Refers to the current XY facing angle of the proj. \\
    //                    Does not include Z coordinates.                    \\
    //      - .phi    --> Refers to the current XYZ facing angle of the      \\
    //                    projectile. Includes Z coordinates as well.        \\
    //      - .pos    --> Refers to the current position of the projectile.  \\
    //                    Users can access: .pos.x/.pos.y/.pos.z             \\
    //      - .vel    --> Refers to the current velocity of the projectile.  \\
    //                    Users can access: .vel.x/.vel.y/.vel.z             \\
    //      - .start  --> Refers to the starting coordinates of the proj.    \\
    //                    Users can access: .start.x/.start.y/.start.z       \\
    //      - .target --> Refers to the target coordinates of the proj.      \\
    //                    Users can access: .target.x/.target.y/.target.z    \\
    //                                                                       \\
    //    Methods available:                                                 \\
    //   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                 \\
    //      - .create()              --> Creates the projectile at an XYZ    \\
    //                                   location, facing a certain angle.   \\
    //      - .project()             --> Starts the projectile moving to a   \\
    //                                   XYZ location at a certain speed.    \\
    //      - .modifyTargeting()     --> Adjust the target coordinates of a  \\
    //                                   projectile. Changing its direction. \\
    //      - .isUnitInHeightRange() --> Checks to see if a unit is within   \\
    //                                   vertical distance of a projectile.  \\
    //      - .terminate()           --> Terminates a projectile. Will fire  \\
    //                                   .onFinish() method.                 \\
    //                                                                       \\
    //-----------------------------------------------------------------------\\
    
    struct Projectile extends ProjInterface

        unit    caster           = null
        real    timedLife        = -1.00
        real    unitCollision    = DEFAULT_UNIT_COLL
        real    projCollision    = DEFAULT_PROJ_COLL
        boolean pauseProj        = false
        boolean collideable      = false
    
        readonly unit   proj     = null
        readonly real   theta    = 0.00
        readonly real   phi      = 0.00
        readonly vector pos      = 0
        readonly vector vel      = 0
        
        readonly vector start    = 0
        readonly vector target   = 0

        private player  own      = null
        private effect  sfx      = null
        private string  path     = ""
        private real    speed    = 0.00
        private real    oriSpeed = 0.00
        private real    oldSpeed = 0.00
        private vector  grav     = 0
        private boolean stop     = false
        private group   dmged    = null
        
        private static boolexpr tempFilt  = null
        private static group    tempGroup = null
        private static thistype tempData  = 0
        private static vector   tempVect  = 0
        
        private method destroy takes nothing returns nothing
            call this.onFinish()
            
            set ProjData[this.proj].data = 0
            
            call this.pos.destroy()
            call this.vel.destroy()
            call this.grav.destroy()
            call this.start.destroy()
            call this.target.destroy()
            
            if this.sfx != null then
                call DestroyEffect(this.sfx)
            endif
            
            call RemoveUnit(this.proj)
            // call GroupClear(this.dmged)
            call Group.release(this.dmged)
            
            set this.caster = null
            set this.proj   = null
            set this.sfx    = null
            set this.path   = ""
            
            call this.deallocate()
        endmethod
        
        private static method unitFilter takes nothing returns boolean
            local thistype this = thistype.tempData
            local unit     filt = GetFilterUnit()
            
            // if IsUnitInRangeXY(filt,this.pos.x,this.pos.y,this.unitCollision) then
                if /*UnitAlive(filt) == true and*/ IsUnitInGroup(filt,this.dmged) == false then
                    call BJDebugMsg("WTF!")
                    call GroupAddUnit(this.dmged,filt)
                    call this.onUnitImpact(filt)
                endif
            // endif
            
            set filt = null
            
            return false
        endmethod
        
        private method projCollisions takes nothing returns nothing
            local thistype that = thistype(0).next
            
            loop
                exitwhen that == 0
                if this != that and that.collideable then
                    if that.pos.isInSphere(this.pos,this.projCollision) and this.pos.isInSphere(that.pos,that.projCollision) then
                        call this.onProjImpact()
                        call that.onProjImpact()
                        set FirstProjData  = this
                        set SecondProjData = that
                        call CollisionEvent.fire()
                    endif
                endif
                set that = that.next
            endloop
        endmethod
        
        private method periodic takes nothing returns nothing
            if this.stop then
                call this.stopPeriodic()
                call this.destroy()
            endif
            
            if this.collideable then
                call this.projCollisions()
            endif
            
            call this.onLoop()
            
            if not this.pauseProj then
                if this.timedLife > -1.0 then
                    set this.timedLife = this.timedLife - T32_PERIOD
                    if this.timedLife <= 0.00 then
                        set this.stop = true
                    endif
                endif
                
                call this.vel.add(this.grav)
                call this.pos.add(this.vel)
                call this.vel.add(this.grav)
                call thistype.tempVect.getTerrainPoint(this.pos.x,this.pos.y)
                
                call SetUnitX(this.proj,this.pos.x)
                call SetUnitY(this.proj,this.pos.y)
                call SetUnitFlyHeight(this.proj,this.pos.z - thistype.tempVect.z,0.00)
                
                set thistype.tempData = this
                call GroupEnumUnitsInRange(thistype.tempGroup,this.pos.x,this.pos.y,this.unitCollision/* + MAX_UNIT_COLLISION*/,thistype.tempFilt)
                
                if thistype.tempVect.z >= this.pos.z then
                    call this.onLandImpact()
                    set this.stop = true
                endif
            endif
        endmethod
        
        implement T32x
        implement ProjSpeedMod
        implement ProjOperatorMod
        
        method terminate takes nothing returns nothing
            set this.effectPath = ""
            set this.stop = true
        endmethod
        
        method isUnitInHeightRange takes unit whichUnit returns boolean
            return SquareRoot((this.pos.z - GetUnitFlyHeight(whichUnit)) * (this.pos.z - GetUnitFlyHeight(whichUnit))) <= this.unitCollision
        endmethod
        
        method modifyTargeting takes real xPos, real yPos, real zPos returns nothing
            set this.theta    = Atan2((yPos - this.pos.y),(xPos - this.pos.x))
            set this.phi      = Atan2(SquareRoot((xPos - this.pos.x) * (xPos - this.pos.x) + (yPos - this.pos.y) * (yPos - this.pos.y)),(zPos - this.pos.z))
            set this.vel.x    = Sin(this.phi) * Cos(this.theta) * this.speed
            set this.vel.y    = Sin(this.phi) * Sin(this.theta) * this.speed
            set this.vel.z    = Cos(this.phi) * this.speed
            set this.target.x = xPos
            set this.target.y = yPos
            set this.target.z = zPos
        endmethod
        
        method project takes real xPos, real yPos, real zPos, real speed returns nothing
            set this.theta    = Atan2((yPos - this.pos.y),(xPos - this.pos.x))
            set this.phi      = Atan2(SquareRoot((xPos - this.pos.x) * (xPos - this.pos.x) + (yPos - this.pos.y) * (yPos - this.pos.y)),(zPos - this.pos.z))
            set this.speed    = speed * T32_PERIOD
            set this.oriSpeed = this.speed
            set this.vel      = vector.create(Sin(this.phi) * Cos(this.theta) * this.speed,Sin(this.phi) * Sin(this.theta) * this.speed,Cos(this.phi) * this.speed)
            set this.target   = vector.create(xPos,yPos,zPos)
            
            call this.onStart()
            call this.startPeriodic()
        endmethod
        
        static method create takes real xPos, real yPos, real zPos, real facing returns thistype
            local thistype this = thistype.allocate()
            
            set this.proj  = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),PROJ_DUMMY_ID,xPos,yPos,facing * bj_RADTODEG)
            set this.grav  = vector.create(0.00,0.00,(DEFAULT_GRAVITY * T32_PERIOD * T32_PERIOD) / 2.00)
            set this.pos   = vector.create(xPos,yPos,zPos)
            set this.start = vector.create(xPos,yPos,zPos)
            
            call SetUnitX(this.proj,xPos)
            call SetUnitY(this.proj,yPos)
            call thistype.tempVect.getTerrainPoint(xPos,yPos)
            call SetUnitFlyHeight(this.proj,zPos - thistype.tempVect.z,0.00)
            
            // if this.dmged == null then
                set this.dmged = Group.get()
            // endif
            
            set ProjData[this.proj].data = this
            
            return this
        endmethod
        
        private static method onInit takes nothing returns nothing
            set thistype.tempVect  = vector.create(0.00,0.00,0.00)
            set thistype.tempFilt  = Filter(function thistype.unitFilter)
            set thistype.tempGroup = CreateGroup()
        endmethod
        
    endstruct
    
    function GroupEnumProjectilesInRange takes group whichGroup, real x, real y, real z, real radius returns nothing
        local Projectile p = Projectile(0).next
        
        loop
            exitwhen p == 0
            if p.pos.isInSphereEx(x,y,z,radius) then
                call GroupAddUnit(whichGroup,p.proj)
            endif
            set p = p.next
        endloop
    endfunction
    
    function RegisterProjectileCollisionEvent takes trigger whichTrigger returns EventReg
        return CollisionEvent.register(whichTrigger)
    endfunction
    
    function GetFirstCollisionProj takes nothing returns Projectile
        return FirstProjData
    endfunction
    
    function GetSecondCollisionProj takes nothing returns Projectile
        return SecondProjData
    endfunction
    
    private function Init takes nothing returns nothing
        set CollisionEvent = Event.create()
    endfunction
    
endlibrary


The trouble area:
JASS:
...
        private static method unitFilter takes nothing returns boolean
            local thistype this = thistype.tempData
            local unit     filt = GetFilterUnit()
            
            // if IsUnitInRangeXY(filt,this.pos.x,this.pos.y,this.unitCollision) then
                if /*UnitAlive(filt) == true and*/ IsUnitInGroup(filt,this.dmged) == false then
                    call BJDebugMsg("WTF!")
                    call GroupAddUnit(this.dmged,filt)
                    call this.onUnitImpact(filt)
                endif
            // endif
            
            set filt = null
            
            return false
        endmethod
...


The [ljass]"WTF!"[/ljass] debug message runs twice for the same unit.

Can anyone spot the problem?

In the test map, just pick one of the blood mages, and target another unit with Hurl Boulder. You should see three debug messages. One for the caster and two for the target (WHICH SHOULDN'T HAPPEN!).

EDIT:

Sometimes the debug message will show up multiple (two) times for a unit and sometimes it wont show up at all, and rarely it will just work as it should. This is really weird, I am beginning to think I missed something very obvious.

Meh, figured it out, don't worry. It was something stupid after all.
 

Attachments

  • Projectile [v1.0.0].w3x
    89.4 KB · Views: 127
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top