Missile Spells/System + StatusStack System 1.5

-This is a pack of custom missile spells.
-The system can also be used for your own custom missiles
-I've added a how to inside the trigger folder and also a template which you can use to make your own missile
-Requires JNGP to edit/save because of the use of library
-The Status Stacking System is an optional add-on, it causes the duration of certain buffs that you put in the trigger to stack instead of overwriting each other (In this map I used stun and freeze(slow))
-For simplicity I set the damage of every missile to a multiplier of the caster's int(SkillLevel*Int) but you can change it (if you use the template or modify the movement code)
-The basic aspects which influence the missiles can be configured at the start of the library except damage.

Default Spell Damage and Range
Damage - SkillLevel*IntofCaster
Range - 1000 + 100*SkillLevel

Spells
Piercing Arrow - Fires an arrow that damages units it passes through.
Arrow Storm - Same as piercing arrow but fires 12 arrows around you
Explosive Arrow - Fires an arrow which explodes upon contact
Prismatic Missile - Fires a piercing missile which causes either stun of freeze
Power Arrow - A Piercing arrow that causes knockback
Shoot Arrow - A single hit arrow


Codes:

JASS:
library MissileLibrary initializer Init requires GroupRecycler
///////////////////////////////////////
//       Missile Spells/System       //
//         by: Adiktuz               //
//         Version 1.5               //
///////////////////////////////////////
//Description:                       //
//A simple system made for the custom//
//missile spells included in the map //
//but can also be used as a base for //
//your own custom missile system     //
///////////////////////////////////////
//            Credits                //
// Vexorian for the dummy.mdx        //
//        vJASS                      //
//Element of Water, Deaod, Itachi009 //
//The_Reborn_Devil, hell_gate        //
//CBX_Entertainment                  //
///////////////////////////////////////


//List of Included Actions
//Arrow_Create
//Arrow_Storm_Create (A massive version of Arrow_Create)
//Arrow_Create_Custom (Enables creation of missiles that do not use the predefined variables of this system)
//Missile_Movement
//Knockback



//Don't forget to import dummy.mdx

//You can configure this things
globals
    constant real RANGE_BASE = 1000 //This is the base range of the missile spells
   
    constant real RANGE_INCREMENT = 100 //This is the range increment for each level of the spells
   
    constant integer MISSILE = 'h001' //This is the raw code for the missile dummy
   
    constant integer DUMMY = 'h003' //This is the raw code for the dummy caster used by Prismatic Missile
   
    constant real RADIUS = 300 //This is the damage radius for the explosive missile
   
    constant real RADIUS_FILTER = 100 //This is the radius when a missile will hit a unit, this is also the activation radius for the explosion of an explosive missile
   
    constant real DISTANCE = 20 //This is the distance travelled by the missile every .03 seconds
   
    constant real KNOCK_BACK_RANGE = 200 //This is the distance of the knockback
   
    constant real KB_DIST = 10 //This is the distance at which the unit is knockbacked every .03 seconds
   
    constant integer TIME = 20 //This is the time on how long the status of Prismatic Missile will lasts per hit (10 = 1 second)
   
    constant integer FREEZE = 'A00H' //This is the raw code of the freeze spell
   
    constant integer STUN = 'A00G' //This is the raw code of the stun spell
   
    constant boolean CUSTOM_STATUS = true //When this is true, Prismatic Missile will use the custom status effect system
    //If you will use the Status Effect System, the buffs from Prismatic Missile will stack for every hit
    //If you set CUSTOM_STATUS to false be sure to modify the duration for the status effects in the object editor
   
    group DAMAGE_GROUP = CreateGroup() //No need to configure this
   
    group TEMP_GROUP = CreateGroup() //No need to configure this
   
    group TEMP_GROUP_E = CreateGroup() //No need to configure this
   
    //The following are paths for the model files to be used by the missiles.
    //If you are going to change the paths here be sure to change all \ in the path to \\
   
    constant string EXPLOSION_EFFECT = "Objects\\Spawnmodels\\Human\\FragmentationShards\\FragBoomSpawn.mdl" //The path for the effect for the explosion
   
    constant string PIERCING_MISSILE = "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl" //This is the path for the model for the piercing missile
   
    constant string EXPLOSION_MISSILE = "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl" //This is the path for the model for the explosion missile
   
    constant string PRISMATIC_MISSILE = "Abilities\\Weapons\\ProcMissile\\ProcMissile.mdl" //This is the path for the model for the prismatic missile
   
    constant string SH_MISSILE = "Abilities\\Weapons\\ColdArrow\\ColdArrowMissile.mdl" //This is the path for the model for the single hit missile
   
    constant string KNOCKBACK_PIERCING_MISSILE = "Abilities\\Spells\\Other\\BlackArrow\\BlackArrowMissile.mdl" //This is the path for the model for the piercing missile if knockback is enabled
   
    constant string KNOCKBACK_EXPLOSION_MISSILE = null//This is the path for the model for the explosion missile if knockback is enabled
   
    constant string KNOCKBACK_PRISMATIC_MISSILE = null//This is the path for the model for the prismatic missile if knockback is enabled
   
    constant string KNOCKBACK_SH_MISSILE = null//This is the path for the model for the single hit missile if knockback is enabled
   
    constant string KB_EFFECT = null //This is the path of the knockback effect
   
    //End of configurables
   
    real MAP_X_MAX
   
    real MAP_X_MIN
   
    real MAP_Y_MIN
   
    real MAP_Y_MAX
   
    private timer T = CreateTimer()
    private integer array DATA
    private integer TOTAL = 0
   
    private timer TK = CreateTimer()
    private integer array DATAK
    private integer TOTALK = 0
   
endglobals
//Do not edit anything from here unless you are sure what you are doing

private function missilecheck takes nothing returns boolean
    return (GetUnitTypeId(GetFilterUnit()) != MISSILE and GetWidgetLife(GetFilterUnit()) >= .405)
endfunction

private struct Missile
    real x
    real y
    real dx
    real dy
    real dxk
    real dyk
    unit u
    player owner
    real angle
    real distance
    real damage
    boolean kb = false
    effect Effect
    real kb_distance = KNOCK_BACK_RANGE
    group Group //= CreateGroup()
    real speed
    string behavior
    boolean height
    private method onDestroy takes nothing returns nothing
        call KillUnit(this.u)
        set this.u = null
        set this.owner = null
        set this.Group = null
        set this.Effect = null
    endmethod
   
    static method Knockback takes nothing returns nothing
    local Missile dat
    local integer i = 0
    loop
        exitwhen i == TOTALK
        set dat = DATAK[i]
        set dat.x = dat.x + dat.dxk
        set dat.y = dat.y + dat.dyk
        if (dat.x >= MAP_X_MIN and dat.x <= MAP_X_MAX and dat.y >= MAP_Y_MIN and dat.y <= MAP_Y_MAX) then
        call SetUnitPosition(dat.u, dat.x, dat.y)
        call DestroyEffect(AddSpecialEffect(KB_EFFECT, dat.x, dat.y))
        endif
        set dat.kb_distance = dat.kb_distance - KB_DIST
        if dat.kb_distance <= 0 then
            set dat.u = null //We set it to null because the onDestroy method has the action KillUnit and so this line would prevent the unit to be killed
            call dat.destroy()
            set TOTALK = TOTALK - 1
            set DATAK[i] = DATAK[TOTALK]
            set i = i - 1
    endif
    set i = i + 1
    endloop
    if TOTALK == 0 then
        call PauseTimer(TK)
    endif
    endmethod
   
    static method KB_Init takes unit a, real angle returns nothing
    local Missile dat = Missile.allocate()
    set DATAK[TOTALK] = dat
    set dat.u = a
    set dat.owner = GetOwningPlayer(dat.u)
    set dat.angle = angle
    set dat.x = GetUnitX(dat.u)
    set dat.y = GetUnitY(dat.u)
    set dat.dxk = KB_DIST*Cos(angle)
    set dat.dyk = KB_DIST*Sin(angle)
    if TOTALK == 0 then
        call TimerStart(TK, .03, true, function Missile.Knockback)
    endif
    set TOTALK = TOTALK + 1
    endmethod
   
    static method move takes nothing returns nothing
    local unit c
    local unit e
    local real dist = 0
    local location mpoint
    local location tpoint
    local integer i = 0
    local real dista
    local Missile dat
    loop
        exitwhen i == TOTAL
        set dat = DATA[i]
        set tpoint = Location(dat.x, dat.y)
        set dat.x = dat.x + dat.dx
        set dat.y = dat.y + dat.dy
        set DAMAGE_GROUP = dat.Group
        if (dat.x >= MAP_X_MIN and dat.x <= MAP_X_MAX and dat.y >= MAP_Y_MIN and dat.y <= MAP_Y_MAX) then
            call SetUnitPosition(dat.u, dat.x, dat.y)
        else
            set dat.distance = 0
        endif
        set mpoint = Location(dat.x,dat.y)
        if dat.height then
            call SetUnitFlyHeight(dat.u, GetUnitFlyHeight(dat.u) + GetLocationZ(tpoint) - GetLocationZ(mpoint) ,0)
            if GetUnitFlyHeight(dat.u) <= 1 then
                set dat.distance = 0
            endif
        endif
        call RemoveLocation(tpoint)
        call GroupEnumUnitsInRange(TEMP_GROUP, dat.x, dat.y, RADIUS_FILTER, Condition(function missilecheck))
        if FirstOfGroup(TEMP_GROUP) != null then
            if dat.behavior == "PIERCE" then
                loop
                    set c = FirstOfGroup(TEMP_GROUP)
                    exitwhen c == null
                    if (IsPlayerEnemy(GetOwningPlayer(c), dat.owner) and IsUnitInGroup(c, DAMAGE_GROUP) != true) then
                        call GroupAddUnit(DAMAGE_GROUP, c)
                        if dat.kb == true then
                            call Missile.KB_Init(c,dat.angle)
                        endif
                        call UnitDamageTarget(dat.u, c, dat.damage , false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DIVINE, WEAPON_TYPE_WHOKNOWS)
                    endif
                    call GroupRemoveUnit(TEMP_GROUP, c)
                endloop
            endif
            if dat.behavior == "SINGLE" then
                loop
                    set c = FirstOfGroup(TEMP_GROUP)
                    exitwhen c == null
                    if IsPlayerEnemy(GetOwningPlayer(c), dat.owner) then
                        set dista = SquareRoot((dat.x - GetUnitX(c))*(dat.x - GetUnitX(c)) + (dat.y - GetUnitY(c))*(dat.y - GetUnitY(c)))
                        if dist != 0 then
                            if dist > dista  then
                                set e = c
                                set dist = dista
                            endif
                        else
                            set e = c
                            set dist = dista                    
                        endif
                    endif
                    call GroupRemoveUnit(TEMP_GROUP, c)
                endloop
                if e != null then
                    call UnitDamageTarget(dat.u, e, dat.damage , false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DIVINE, WEAPON_TYPE_WHOKNOWS)
                    set dat.distance = 0
                     if dat.kb == true then
                            call Missile.KB_Init(c,dat.angle)
                    endif
                endif
            endif
            if dat.behavior == "EXPLODE" then
                call GroupEnumUnitsInRange(TEMP_GROUP_E, dat.x, dat.y, RADIUS, Condition(function missilecheck))
                loop
                    set c = FirstOfGroup(TEMP_GROUP_E)
                    exitwhen c == null
                    if (IsPlayerEnemy(GetOwningPlayer(c), dat.owner) and IsUnitInGroup(c, DAMAGE_GROUP) != true) then
                        if dat.kb == true then
                            call Missile.KB_Init(c,dat.angle)
                        endif
                            call GroupAddUnit(DAMAGE_GROUP, c)
                            call UnitDamageTarget(dat.u, c, dat.damage , false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DIVINE, WEAPON_TYPE_WHOKNOWS)
                            set dat.distance = 0
                            call DestroyEffect(AddSpecialEffect(EXPLOSION_EFFECT, GetUnitX(c), GetUnitY(c)))
                        endif
                        call GroupRemoveUnit(TEMP_GROUP_E, c)
                    endloop
                endif
                    if dat.behavior == "PRISM" then
                        loop
                            set c = FirstOfGroup(TEMP_GROUP)
                            exitwhen c == null
                            if (IsPlayerEnemy(GetOwningPlayer(c), dat.owner) and IsUnitInGroup(c, DAMAGE_GROUP) != true) then
                                if dat.kb == true then
                                    call Missile.KB_Init(c,dat.angle)
                                endif
                                call GroupAddUnit(DAMAGE_GROUP, c)
                                if GetRandomInt(1,100) >= 50 then
                                    set e = CreateUnit(dat.owner, DUMMY, dat.x, dat.y, 0.00)
                                    call UnitApplyTimedLife(e, 'BTLF', 1.00)
                                    call UnitAddAbility(e, FREEZE)
                                    call IssueTargetOrder(e, "slow", c)
                                if CUSTOM_STATUS == true then
                                    call SaveInteger(udg_StatusHash, GetHandleId(c), StringHash("FreezeTime"), LoadInteger(udg_StatusHash, GetHandleId(c), StringHash("FreezeTime"))+ TIME)
                                    call GroupAddUnit(udg_StatusGroup, c)
                                    call EnableTrigger(gg_trg_Status_Effect_Removal)
                                endif
                            else
                                set e = CreateUnit(dat.owner, DUMMY, dat.x, dat.y, 0.00)
                                call UnitApplyTimedLife(e, 'BTLF', 1.00)
                                call UnitAddAbility(e, STUN)
                                call IssueTargetOrder(e, "firebolt", c)
                                if CUSTOM_STATUS == true then
                                    call SaveInteger(udg_StatusHash, GetHandleId(c), StringHash("StunTime"), LoadInteger(udg_StatusHash, GetHandleId(c), StringHash("FreezeTime")) + TIME)
                                    call GroupAddUnit(udg_StatusGroup, c)
                                    call EnableTrigger(gg_trg_Status_Effect_Removal)
                                endif
                            endif
                            call UnitDamageTarget(dat.u, c, dat.damage , false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_DIVINE, WEAPON_TYPE_WHOKNOWS)
                        endif
                        call GroupRemoveUnit(TEMP_GROUP, c)
                    endloop
                endif
            endif
            set dat.distance = dat.distance - DISTANCE
            if dat.distance <= 0 then
                call DestroyEffect(dat.Effect)
                call ReleaseGroup(dat.Group)
                call dat.destroy()
                set TOTAL = TOTAL - 1
                set DATA[i] = DATA[TOTAL]
                set i = i - 1
            endif
            set i = i + 1
        endloop
        if TOTAL == 0 then
            call PauseTimer(T)
        endif
        call RemoveLocation(mpoint)
        set c = null
        set e = null
        set mpoint = null
        set tpoint = null
    endmethod
endstruct


//The following actions are for the missile creation

function Arrow_Create takes unit a, real b, real c, boolean d, string e, real angle, real height, boolean constantfly returns nothing
    local Missile dat = Missile.create()
    local group grup = NewGroup()
    local real x = GetUnitX(a)
    local real y = GetUnitY(a)
    set DATA[TOTAL] = dat
    set dat.owner = GetOwningPlayer(a)
    set dat.u = CreateUnit(dat.owner, MISSILE, x, y, bj_RADTODEG*angle)
    set dat.x = GetUnitX(dat.u)
    set dat.y = GetUnitY(dat.u)
    if e == "PIERCE" then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(PIERCING_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_PIERCING_MISSILE, dat.u, "origin")
        endif
    endif
    if e == "EXPLODE" then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(EXPLOSION_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_EXPLOSION_MISSILE, dat.u, "origin")
        endif
    endif 
    if e == "PRISM"then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(PRISMATIC_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_PRISMATIC_MISSILE, dat.u, "origin")
        endif
    endif
    if e == "SINGLE"then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(SH_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_SH_MISSILE, dat.u, "origin")
        endif
    endif
    set dat.behavior = e
    set dat.distance = RANGE_BASE + RANGE_INCREMENT*c
    set dat.damage = b
    set dat.angle = angle
    set dat.Group = grup
    set dat.kb = d
    set dat.dx = DISTANCE*Cos(angle)
    set dat.dy = DISTANCE*Sin(angle)
    set grup = null
    call UnitAddAbility(dat.u, 'Amrf')
    call UnitRemoveAbility(dat.u, 'Amrf')
    call SetUnitFlyHeight(dat.u, height, 0)
    set dat.height = constantfly
    set TOTAL = TOTAL + 1
    if TOTAL == 1 then
        call TimerStart(T, .03, true, function Missile.move)
    endif
endfunction

function Arrow_Create_Custom takes unit a, real b, real c, boolean d, string e, real angle, string f, string g, real h, real i, real height ,boolean constantfly  returns nothing
    local Missile dat = Missile.create()
    local group grup = CreateGroup()
    local real x = GetUnitX(a)
    local real y = GetUnitY(a)
    set dat.owner = GetOwningPlayer(a)
    set dat.u = CreateUnit(dat.owner, MISSILE, x, y, bj_RADTODEG*angle)
    set dat.x = GetUnitX(dat.u)
    set dat.y = GetUnitY(dat.u)
    if e == "PIERCE" then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(PIERCING_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_PIERCING_MISSILE, dat.u, "origin")
        endif
    endif
    if e == "EXPLODE" then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(EXPLOSION_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_EXPLOSION_MISSILE, dat.u, "origin")
        endif
    endif 
    if e == "PRISM"then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(PRISMATIC_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_PRISMATIC_MISSILE, dat.u, "origin")
        endif
    endif
    if e == "SINGLE"then
        if d == false then
            set dat.Effect = AddSpecialEffectTarget(SH_MISSILE, dat.u, "origin")
        else
            set dat.Effect = AddSpecialEffectTarget(KNOCKBACK_SH_MISSILE, dat.u, "origin")
        endif
    endif
    set dat.behavior = e
    set dat.distance = h
    set dat.damage = b
    set dat.angle = angle
    set dat.Group = grup
    set dat.kb = d
    set dat.speed = i
    set dat.dx = dat.speed*Cos(angle)
    set dat.dy = dat.speed*Sin(angle)
    set grup = null
    call UnitAddAbility(dat.u, 'Amrf')
    call UnitRemoveAbility(dat.u, 'Amrf')
    call SetUnitFlyHeight(dat.u, height, 0)
    set dat.height = constantfly
    if TOTAL == 0 then
        call TimerStart(T, .03, true, function Missile.move)
    endif
    set TOTAL = TOTAL + 1
endfunction

private function Init takes nothing returns nothing
    set MAP_X_MAX = GetRectMaxX(bj_mapInitialPlayableArea)
    set MAP_X_MIN = GetRectMinX(bj_mapInitialPlayableArea)
    set MAP_Y_MAX = GetRectMaxY(bj_mapInitialPlayableArea)
    set MAP_Y_MIN = GetRectMinY(bj_mapInitialPlayableArea)
endfunction

endlibrary


  • Variable Config
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set StatusHash = (Last created hashtable)
  • Status Effect Removal
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in StatusGroup and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) has buff Stun ) Equal to True
            • Then - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Load (Key StunTime) of (Key (Picked unit)) from StatusHash) Equal to 0
                • Then - Actions
                  • Unit - Remove Stun buff from (Picked unit)
                • Else - Actions
                  • Hashtable - Save ((Load (Key StunTime) of (Key (Picked unit)) from StatusHash) - 1) as (Key StunTime) of (Key (Picked unit)) in StatusHash
            • Else - Actions
              • Hashtable - Save 0 as (Key StunTime) of (Key (Picked unit)) in StatusHash
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) has buff Freezing ) Equal to True
            • Then - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Load (Key FreezeTime) of (Key (Picked unit)) from StatusHash) Equal to 0
                • Then - Actions
                  • Unit - Remove Freezing buff from (Picked unit)
                • Else - Actions
                  • Hashtable - Save ((Load (Key FreezeTime) of (Key (Picked unit)) from StatusHash) - 1) as (Key FreezeTime) of (Key (Picked unit)) in StatusHash
            • Else - Actions
              • Hashtable - Save 0 as (Key FreezeTime) of (Key (Picked unit)) in StatusHash
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) has buff Stun ) Equal to False
              • ((Picked unit) has buff Freezing ) Equal to False
            • Then - Actions
              • Unit Group - Remove (Picked unit) from StatusGroup
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in StatusHash
            • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (StatusGroup is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions


JASS:
//This is on how to make a custom missile using this system.
//A custom missile is any missile that will not use the predefined variables in the library (ie. model, speed, range)
//Just make this function call
call Arrow_Create_Custom(unit, real, real, boolean, string, real, string, string, real, real,real,boolean)
//Defining the parameters(from left to right)
//The caster unit
//The damage that will be dealt by the missile
//The level of the Spell (you must use I2R() to turn it into a real)
//Wheter the missile will do a knockback or not
//The behavior of the missile (PIERCE, EXPLODE, PRISM, SINGLE)
//The angle at which the missile will fly
//The path to the model that will be used by the missile
//The path to the model that will be used by the missile if knockback is enabled
//The distance that will be travelled by the arrow
//The distance travelled by the arrow every .03 seconds (default is 20)
//The height of the missile
//wheter the missile will fly at a constant height(regardless of terrain height) or not(varying with terrain height). If set to true missiles will vanish if it hits the ground

//Example: You want a piercing arrow that has the same properties like the normal but travels 2x faster than the normal
local unit a = GetTriggerUnit()
local integer b = GetUnitAbilityLevel(a, GetSpellAbilityId())
local real angle = Atan2(GetSpellTargetY() - GetUnitY(a), GetSpellTargetX() - GetUnitX(a))
call Arrow_Create_Custom(a, GetHeroInt(a,true)*b ,b, false, "PIERCE", angle, PIERCING_MISSILE, KNOCKBACK_PIERCING_MISSILE, RANGE_BASE + RANGE_INCREMENT*b, 40, 100,false)


-Missile Spells-
by: Adiktuz

Features:
-Different missile behaviors (piercing missiles, exploding missiles, etc)
-Configurable damage
-Each missile behavior has its own group and own handler functions

How to use:

1. Copy all object data
2. Copy MissileLibrary and TimerUtils
3. Configurable data are in the start of the MissileLibrary.
4. Needs JNGP because of the use of vJASS.
5. Just call the appropriate arrow creation function to use the system
6. Don't forget to export/import dummy.mdx

Status Effects System
by: Adiktuz

-->This system was made in GUI because this was made and finished before this spellpack was made<--
-->The purpose of this system is to stack the duration of buffs/status effects instead of new buffs overwriting the duration of the same buff<--
-->For this map, this system causes the duration of the status effects from Prismatic Missile to stack.

-This system is optional
- If you wont use this be sure to modify the duration of the status effect spells in the object editor because they are set to really high values.
-If you use this system be sure to trigger all related status effects [stun, freeze(slow)]
Stun - stops the unit from moving
Freeze - slows target

->To add a new buff you want to stack durations in the system just copy/paste an if-then-else action and make the necessary adjustments. Also make sure to add another condition to the last if-then-else action inside the group loop corresponding to the new buff.
->If you add a buff in the system or use this system be sure to trigger all placements of the buffs you want the system to affect and set their duration in the object editor to the max. (For stun, 0.00 = infinite)


JASS:
//Use the function call below if you want to use the default missile creation in the library

function ArrowStart takes nothing returns nothing
    local unit a = GetTriggerUnit()
    local integer b = GetUnitAbilityLevel(a, GetSpellAbilityId())
    local real i = 1
    local real f = 12//The number of arrows made by the Arrow Storm
    local real angle = Atan2(GetSpellTargetY() - GetUnitY(a), GetSpellTargetX() - GetUnitX(a))
    //In the following actions
    //The first parameter (a) is the caster
    //The second parameter (I2Rblahblah) is the damage that will be dealt by the missile
    //The third parameter I2R(b) is the ability level
    //The boolean parameter are to set if the arrow will cause knockback or not
    //The Fifth parameter is for the behavior of missiles (PIERCE, EXPLODE, PRISM or SINGLE)
    //The sixth parameter is for the facing angle of the unit, the angle at which the arrow will fly to
    //The seventh parameter is the height of the missile (default is 100)
    //The eight parameter is wheter the missile will fly at a constant height(regardless of terrain height) or not(varying with terrain height). If set to true missiles will vanish if it hits the ground
    if GetSpellAbilityId() == 'A000' then
        call Arrow_Create(a, I2R(b)*GetHeroInt(GetTriggerUnit(), true), I2R(b), false, "PIERCE", angle, 100, false)
    endif
    if GetSpellAbilityId() == 'A001' then
        call Arrow_Create(a, I2R(b)*GetHeroInt(a, true), I2R(b), false, "EXPLODE", angle, 100,false)
    endif
    if GetSpellAbilityId() == 'A003' then
        call Arrow_Create(a, I2R(b)*GetHeroInt(a, true), I2R(b), false, "PRISM", angle, 100,false)
    endif
    if GetSpellAbilityId() == 'A004' then
        call Arrow_Create(a, I2R(b)*GetHeroInt(GetTriggerUnit(), true), I2R(b), true, "PIERCE", GetUnitFacing(a), 100, false)
    endif
    if GetSpellAbilityId() == 'A002' then
        loop
            exitwhen i > f
            call Arrow_Create(a, I2R(b)*GetHeroInt(GetTriggerUnit(), true), I2R(b), false, "PIERCE", bj_DEGTORAD*((360*i)/f), 100, false)
            set i = i + 1
        endloop
    endif
    if GetSpellAbilityId() == 'A005' then
        call Arrow_Create(a, I2R(b)*GetHeroInt(GetTriggerUnit(), true), I2R(b), false, "SINGLE", angle, 100, true)
    endif
    set a = null
endfunction

//===========================================================================
function InitTrig_ArrowStart takes nothing returns nothing
    set gg_trg_ArrowStart = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_ArrowStart, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddAction( gg_trg_ArrowStart, function ArrowStart )
endfunction



ChangeLog:

Version 1.0 - Made system which includes, piercing missiles, explosive missiles and prismatic missile

Version 1.1 - Added knockback capability to the missiles which can be set for every missile through a boolean parameter and a single hit missile.
-Also added the ability to change the model of a missile if knockback is enabled

Version 1.2 - Now uses structs and also reduced the creation functions to 2(one for a single missile that uses the predefined variables and one a missile that dont use the predefined variables).
- More user friendly in terms of custom missile creation! Now, users can create their own custom missile with just one function call!
- Added a single hit missile

Version 1.3 - Code optimizations. Transferred the movement and knockback functions into methods. Now requires TimerUtils.
- Reworked the behavior system, I removed the groups and used strings to check the missile behavior.
- Added knockback capability to the single hit arrow (I forgot it when I first created the single hit missile).
- Added a new parameter height which sets the height of the missile.

Version 1.4 - Added an optional action to the movement of the missiles as suggested by hell_gate that will make the missile fly at an almost constant height(regardless of terrain height) that can be toggled thru an added boolean parameter at function call.
- If the new boolean parameter is set to true, missiles that hit the ground will vanish.
- To show it I set the boolean to true for the Shoot Arrow ability while all other abilities are set to false.
- Removed TimerUtils and changed the way the missiles are looped to reduce number of timers used based on The_Reborn_Devil's suggestion.
- Added a GroupRecycler made by The_Reborn_Devil

Version 1.5 - Changed the angle calculation to support every angle (suggestion by CBX_Entertainment


Vexorian
Element of Water
Deaod
Itachi009
The_Reborn_Devil
hell_gate
CBX_Entertainment


Keywords:
JASS, MUI, missiles, hashtable, status, buffs, adiktuz, arrows,
Contents

vJASS - MSSS 1.5 (Map)

Reviews
20:53, 11th Jan 2010 The_Reborn_Devil: The code looks really good now. I don't think there's anything left which should be fixed. Status: Approved Rating: Recommended

Moderator

M

Moderator

20:53, 11th Jan 2010
The_Reborn_Devil:
The code looks really good now. I don't think there's anything left which should be fixed.

Status: Approved
Rating: Recommended
 
I think theres just too many of these. But might be useful anyways.

maybe. At first I was really only supposed to make the spells but then I thought that maybe making a core system might help someone so I used a library. and the status system was just added and is optional, it was for the project I'm working on. anyways thanks for comment!
 
Do not use constant handles, it doesn't make them any faster and optimizers such as vex's will break your system by replacing, for example, every MISSILE_HASH with InitHashtable().

so I should remove that part and then just create the variable in the editor? and what other variables should I change also?
 
No, just remove the constant prefix on every handle-type global, like hashtables and groups.

EDIT: I just looked through the code properly, and it's highly inefficient. Use structs and a stack loop, not unit groups and hashtables. Ouch.

Let me ask you a question: how many missiles can this handle at once? My missile system (search for my name in spells&systems) can handle 200+ missiles at once, and I don't even have a very good machine.
 
Level 14
Joined
Nov 18, 2007
Messages
816
  • You destroy timers (which has been banned long ago)
  • You destroy groups (which leaks)
  • You use hashtables over structs
  • You use hardcoded intervals for your timers (and maybe some other hardcoded values as well, it wouldnt surprise me at least)
  • Parameters named a, b and c? Talk about maintainability
  • Same with locals, give those variables proper names.
  • I suggest you take a look at the private key word.
  • CountUnitsInGroup(b) != 0 could be written as FirstOfGroup(b)!=null
  • Use keys instead of StringHashs (refer to JassHelpers manual to learn about keys)
  • What does this do, that xecollider cant?
 
Level 5
Joined
Dec 8, 2008
Messages
102
- @ Deaod, whats bad about destroying timers?, i do that too and never had any problems
- If you use a global group instead of create / destroy it every interval you will greatly improve your spell performance !
- Use Struct-variables instead of hashtables (faster)
- Inline the CountUnitsInGroup function (its easy, just write: call ForGroup(g, function CountUnitsInGroupEnum) instead, and then check if bj_groupCountUnits > 0, cause the bj will make 6 additional lines of code : D)

If you fix this its a nice job and worth approving
 
one thing to all posters: I'm currently still do not understand that much about vJASS that's why this uses hashtables..

-what's wrong with parameter a,b,c? The user's arent supposed to edit those parts. but if you insist I change them then I would

-about the destroyed timer, whats wrong with it? And I only destroy the timer for the map bounds init.

-And using PRIVATE, why? the variables are supposed to be able to be used by other triggers. The template trigger for the creation uses the variables so they aren't supposed to be private.

-and also what's wrong with hardcoded values?

-And about the last question, I don't know about xe collider but this system/spells can be used with simple JASS knowledge, and I think that one is for vJASS users.

Anyways, thanks for comments. I'll try to do the things that you mentioned but right now I cannot use structs, maybe when I managed to learn structs (that's the thing I'm working on right now)...
 
Level 14
Joined
Nov 18, 2007
Messages
816
whats bad about destroying timers?
Blizzard screwed up and introduced bugs when you set timer variables to null. So you cant fully destroy timers, as a ghost handle would remain because you didnt null all references to the timer.

what's wrong with parameter a,b,c?
Some day, you might want to modify that script. And lets assume you completely forgot what you wrote there. Variable names are helpful to figure out what they do. a,b and c are not that helpful.

And using PRIVATE, why?
anything someone outside of your system should not be able to access (or doesnt need to access), should be private to your script. Expose a minimal interface. That could some day help prevent bugs.

and also what's wrong with hardcoded values?
Well, assume you want to change something about your script and you go searching for all those hardcoded values to modify. The chance to overlook one instance of a hardcoded value is higher the longer your script is. Just having to change one instance (a constant at the top of your script) is significantly less error-prone.
 
Level 1
Joined
Jan 15, 2010
Messages
2
For some reason, when i select this map in warcraft 3, i can't start it. I re-downloaded 3 times and its still the same. May i know what is the problem?
 
Blizzard screwed up and introduced bugs when you set timer variables to null. So you cant fully destroy timers, as a ghost handle would remain because you didnt null all references to the timer.

Some day, you might want to modify that script. And lets assume you completely forgot what you wrote there. Variable names are helpful to figure out what they do. a,b and c are not that helpful.

anything someone outside of your system should not be able to access (or doesnt need to access), should be private to your script. Expose a minimal interface. That could some day help prevent bugs.

Well, assume you want to change something about your script and you go searching for all those hardcoded values to modify. The chance to overlook one instance of a hardcoded value is higher the longer your script is. Just having to change one instance (a constant at the top of your script) is significantly less error-prone.

so I should not destroy the timer?

I think only the knockback system and the movement can be private. I'll try it next update.

only the timer time-out is hardcoded.

anyway, thanks for your comments.

EDIT: I forgot to change the reduction of the distance value to the variable, its still hard coded. I'll change it on the next update..

Updated the system, I added a knockback function that can be used by all missiles and also the ability to change the model of each missile if knockback is enabled.

EDIT 2: Updated! Check the details above.

EDIT 3: Updated! Now requires TimerUtils. Replaced the missile groups by strings. and various changes. see the changelog above.

EDIT 4: Updated again!

EDIT 5: Removed the extra unneeded RemoveLocation that I forgot to remove earlier.
 
Level 8
Joined
May 30, 2009
Messages
266
you really need to change that GetUnitFacing(a) in the ArrowStart trigger
why? simple!
it only allows you to have certain degrees where you can shoot your arrows (somewhat like 16 in total or so.. dunno)
you will notice this, when you spam arrows and just slightly change the position of your cursor

replace it with
set TEMP_POINT = GetUnitLoc(GetTriggerUnit())
set TEMP_POINT2 = GetSpellTargetLoc()
set TEMP_REAL = AngleBetweenPoints(TEMP_POINT, TEMP_POINT2)
and then insert TEMP_REAL as the degree value for the arrows

so now you have 360 possible directions :p
 
you really need to change that GetUnitFacing(a) in the ArrowStart trigger
why? simple!
it only allows you to have certain degrees where you can shoot your arrows (somewhat like 16 in total or so.. dunno)
you will notice this, when you spam arrows and just slightly change the position of your cursor

replace it with
set TEMP_POINT = GetUnitLoc(GetTriggerUnit())
set TEMP_POINT2 = GetSpellTargetLoc()
set TEMP_REAL = AngleBetweenPoints(TEMP_POINT, TEMP_POINT2)
and then insert TEMP_REAL as the degree value for the arrows

so now you have 360 possible directions :p

I noticed it but don't know how to change it... gotta try it... angle between points return in degrees or radians? btw I cannot change it this week...
 
You should save the map again with CTRL + S and then upload it again without changing anything.

You saved the map uncompiled.

oh... okay... maybe because I used "Save As"... I'll fix it later...

EDIT: reuploaded the map... Its compiled now so no need to resave... (IDK why the last updated still show feb 23...)
 
Level 12
Joined
Sep 4, 2007
Messages
407
HELP ME PLZ. I CnP this code for my map (i know a little jass, this is my first contact with vJass) and i got this when i save: It considers GroupRecycler as a scope of the library! =(



What do I do?!

EDIT: sorry forgot to import GroupRecycler ¬¬ now i look like a noob.
 
Last edited:
Level 12
Joined
Sep 4, 2007
Messages
407
FOUND a bug with the function create_arrow_custom: there's a line missing in it:

set DATA[TOTAL] = dat

i suppose putting in it will fix it.

dude, this is a major bug it makes the function simply not to work... o_O (actually it creates the arrow effect but it will not move anywhere, am i right?)

EDIT: FOUND ANOTHER bug, if an arrow hits a target the moment another one of the same tipe is being created, the created one will be destroyed just as the one that hit a target. o_O
 
Last edited:
FOUND a bug with the function create_arrow_custom: there's a line missing in it:

set DATA[TOTAL] = dat

i suppose putting in it will fix it.

dude, this is a major bug it makes the function simply not to work... o_O (actually it creates the arrow effect but it will not move anywhere, am i right?)

EDIT: FOUND ANOTHER bug, if an arrow hits a target the moment another one of the same tipe is being created, the created one will be destroyed just as the one that hit a target. o_O

about the missing line... yeah... for the second one, can you post a screenie? it does not happen to me... btw, I'm not sure if I can update this since I'm pretty busy with my project...
 
Top