• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Solved] Adjust JASS Code to adjust configuration values depending on item hold

Status
Not open for further replies.
Level 12
Joined
May 16, 2020
Messages
660
Hi all,

Can someone please help me adjust a JASS code?

I want to add the following IF statement

  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • UNIT XXX has an item of type Aghanim's Scepter ('schl') Equal to False
    • Then - Actions
      • Set IMPACT_DAMAGE_BASE = 35
      • Set IMPACT_DAMAGE_INC = 45
      • Set ORB_RANGE = 1200
    • Else - Actions
      • Set IMPACT_DAMAGE_BASE = 45
      • Set IMPACT_DAMAGE_INC = 55
      • Set ORB_RANGE = 1600

....to the global configuration of this spell:


So essentially that the DAMAGE_BASE, DAMAGE_INC and ORB_RANGE are adjusted if the caster holds a particular item (in my case 'schl').

Here the JASS code:

JASS:
library FrozenOrb initializer Init requires xebasic
    //------------------------------------------------------<>
    // 'FROZEN ORB'
    //
    // Submitted by Eccho   2010-02-05 (1.0)
    //                      2010-02-15 (1.01)
    // Changelog can be found at the official submission post
    // at
    // http://www.hiveworkshop.com/forums/spells-569/frozen-orb-v1-0-a-158026/
    //
    // Give credits if used!!
    //------------------------------------------------------<>

    //------------------------------------------------------<>
    // 'Introduction'
    //
    // The reason why I did this spell is because I have not
    // seen yet any frozen orb spell which is properly
    // functional, or that I think does not look diablo 2'ish
    // enough (Vexorian made a frozen orb spell, I know, but
    // it isn't working at all anymore. This spell was indeed
    // an interpretation of the sorceress' spell Frozen Orb
    // in Diablo 2.
    //
    // A small note before I continue, due to some
    // complications with vJass I did not manage certain
    // things I had in mind in the beginning. The code may
    // indeed (the callback especially) be written in
    // another way, and I tried that, and failed.
    // Suggestions are welcome.
    //
    // A second note is that, frozen orb by default demands
    // some attention in the memory. Casting this multiply
    // times in a row without a cooldown, is going to have
    // some consequences. This is nothing I can change.
    // Adapt the XE_ANIMATION_PERIOD if needed.
    //
    // Third note is, you will not be able to control the
    // amount of bolts spawned while the orb is alive. These
    // are only depending on the animation period. This is
    // also how Diablo 2 handles Frozen Orb. Each bolt is
    // released each frame until the orb vanishes.
    //
    // 'Implementation instructions'
    //
    // First of all, you will need this library and the
    //  xebasic library. Copy these to your map.
    //
    // Second, you need the Universal Dummy unit found in the
    //  unit editor. Don't forget to import the dummy.mdx and
    //  set the dummy to use it as a model.
    //
    // Important: In order to apply the slow
    //  buff to the affected unit, make sure the universal
    //  dummy has:
    //   A cooldown time > 0
    //   Damage base != 0 (default -1)
    //   Damage die > 0 (default 1)
    //   A fast projectile speed
    //   A decent range
    //   Targets allowed - At least 'ground'
    //   Weapon type - missile
    //   Attack 1 enabled
    //   (if the unit does not have a movement speed, add it)
    //   And the usual stuff
    //
    // Next copy the ability which will cast the spell, and
    // the ability which will apply the buff, and modify the
    // rawcode id's in the constants below. Don't forget to
    // make sure the dummy unit id in the xebasic library is
    // set right as well.
    //
    // 'Credits'
    //
    // Vexorian - JassHelper, xebasic
    // PitzerMike & MindWorX - JNGP
    // Blizzard - Once again for some tooltip inspiration.
    //
    // Give proper credits if used!!
    //------------------------------------------------------<>

    //------------------------------------------------------<>
    // 'Native including'
    //
    // If you have this in your code somewhere else,
    // make sure to not double define it.
    //------------------------------------------------------<>

    native UnitAlive takes unit id returns boolean


    //------------------------------------------------------<>
    // 'Configuration section'
    //
    // Change the spell to fit your needs.
    //------------------------------------------------------<>

    globals
        private constant integer   ABILITY_ID              = 'A000'       //The ability triggering the spell
        private constant integer   FROST_SLOW_ID           = 'A001'       //The ability containing the frost attack. It the duration isn't altered of the already existing one in wc3, use that id instead.
        private constant integer   FROST_SLOW_BUFF         = 'Bfro'       //The buff which is used in the slow attack ability
                                                                       
                                                                          //Art of the main orb unit
        private constant string    ORB_ART                 = "war3mapImported\\FrostOrb.mdx"
                                                                          //"Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
        private constant integer   ORB_SPEED               = 400          //Max travel speed
        private constant integer   ORB_RANGE               = 1200         //Max travel distance
        private constant integer   ORB_RADIUS              = 128          //The radius the orb has. It is required as a polar projection offset when the orb explodes in the end
        private constant integer   ORB_HEIGHT              = 72           //The z-height from the ground the orb will travel (was 48)
        private constant real      ORB_SCALE               = 0.6          //The size/scaling of the orb (I believe the current model have some issues here, but it works with other models)
                                                                          // 2.0

                                                                            //Art of the released bolts. The bolts released when the orb explodes uses this art too.
        private constant string    MISSILE_ART             = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
        private constant integer   MISSILE_SPEED           = 365          //...
                                                                          // 370
        private constant integer   MISSILE_RANGE           = 400          //...
        private constant integer   MISSILE_RADIUS          = 32           //Bolt radius. It is used to check the collision of which enemies the bolts will hit and damage.
        private constant integer   MISSILE_HEIGHT          = 72           //...
        private constant real      MISSILE_SCALE           = 0.5          //...
        private constant real      MISSILE_RAD_OFFSET      = 3*bj_PI/5    //Each bolt is released with a certain angle offset (in radians), and is defined by this constant. Each bolt is added this constant + the previous angle.
        private constant integer   MISSILE_AMOUNT_MAX      = 100          //Keep this constant at a secure amount. The spell could go very wrong if this value is less than the maximum amount of bolts released. 100 is used by default.
   
        private constant integer   ORB_EXPLODE_AMOUNT      = 32           //Amount of bolts released when the orb explodes
        private constant integer   ORB_EXPLODE_SPEED       = 900          //Special bolt travel speed
        private constant integer   ORB_EXPLODE_RANGE       = 900          //Special bolt travel distance
   
                                                                          //If a unit shatters, this effect will be created at it's position
        private constant string    IMPACT_SHATTER_ART      = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        private constant integer   IMPACT_SHATTER_PER_BASE = 0            //Percental base chance to shatter a unit which dies from the spell
        private constant integer   IMPACT_SHATTER_PER_INC  = 0            //Percental increament chance per level to shatter a unit which dies from the spell (example, if base is 8 and incr is 4, each level adds 4 chance)
        private constant integer   IMPACT_DAMAGE_BASE      = 35           //Base damage
        private constant integer   IMPACT_DAMAGE_INC       = 45           //Increamental damage (works in the same way as the shatter base/incr fields)
                                                                       
                                                                         
        private constant attacktype ATTACK_TYPE             = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE             = DAMAGE_TYPE_MAGIC
        private constant weapontype WEAPON_TYPE             = WEAPON_TYPE_WHOKNOWS
    endglobals

    //------------------------------------------------------<>
    // 'Damage filter'
    //
    // Add more options as desired.
    // Some info: It is used as a condition not a boolexpr,
    // thus, not going to give any issue with IsUnitType.
    //------------------------------------------------------<>

    private function TargetFilter takes unit enemy, player caster, real x, real y returns boolean
        return UnitAlive(enemy) and IsUnitEnemy(enemy, caster) and IsUnitInRangeXY(enemy, x, y, MISSILE_RADIUS) and not IsUnitType(enemy, UNIT_TYPE_STRUCTURE)
    endfunction

    //------------------------------------------------------<>
    // 'Shatter filter'
    //
    // Add more options as desired.
    // It determines the units which may shatter
    // The filter does not need to contain the same context
    // as the damage filter. It is only potentially ran after
    // the target filter have became true.
    //------------------------------------------------------<>

    private function ShatterFilter takes unit enemy returns boolean
        return not IsUnitType(enemy, UNIT_TYPE_MECHANICAL) and not IsUnitType(enemy, UNIT_TYPE_HERO) and not IsUnitType(enemy, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(enemy, UNIT_TYPE_STRUCTURE)
    endfunction

    //------------------------------------------------------<>
    // 'Other constants'
    //
    // These should not be altered by default, but if you
    // know what you are doing, or see a way to use other
    // constants instead, feel free.
    //------------------------------------------------------<>

    globals
        private constant real      ORB_TMAX                = 1.0*ORB_RANGE/ORB_SPEED
        private constant real      MISSILE_TMAX            = 1.0*MISSILE_RANGE/MISSILE_SPEED
        private constant real      EXPLODE_TMAX            = 1.0*ORB_EXPLODE_RANGE/ORB_EXPLODE_SPEED
        private constant real      RAD_BETWEEN_EXPL        = bj_PI*2/ORB_EXPLODE_AMOUNT
   
        private constant group     ENUM_GROUP              = CreateGroup()
        private constant timer     ANIM_TIMER              = CreateTimer()
    endglobals

    //------------------------------------------------------<>
    // 'Spell code'
    //
    // If you even think up of an optimize fully working
    // version, tell me about it.
    //------------------------------------------------------<>

    private struct missile
        unit obj
        effect art
        real sx
        real sy
        real xvel
        real yvel
   
        static method create takes string art, player p, real x, real y, real z, real rad, integer speed, real scale returns thistype
            local thistype this = thistype.allocate()
       
            set this.obj = CreateUnit(p, XE_DUMMY_UNITID, x, y, rad*bj_RADTODEG)
            call UnitAddAbility(this.obj, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(this.obj, XE_HEIGHT_ENABLER)
            call SetUnitFlyHeight(this.obj, z, 0)
            call SetUnitScale(this.obj, scale, scale, scale)
            call SetUnitAnimationByIndex(this.obj, 90)
            call UnitRemoveAbility(this.obj, 'Aatk')
       
            set this.art = AddSpecialEffectTarget(art, this.obj, "origin")
            set this.sx = x
            set this.sy = y
            set this.xvel = speed*Cos(rad)
            set this.yvel = speed*Sin(rad)
       
            return this
        endmethod
   
        method clear takes nothing returns nothing
            call DestroyEffect(this.art)
            call KillUnit(this.obj)
            set this.art = null
            set this.obj = null
        endmethod
    endstruct

    private struct spell
        missile orb
        real otick
   
        missile array mis[MISSILE_AMOUNT_MAX]
        real array mtick[MISSILE_AMOUNT_MAX]
        integer mcount
        integer mtot
   
        missile array xpl[ORB_EXPLODE_AMOUNT]
        real xtick //All exploding missiles have the same tickoffset

        unit caster
        player owner
        integer damage
        integer chance
   
        static thistype array tta
        static integer tot = 0
   
        static method callback takes nothing returns nothing
            local thistype this
            local missile m
            local integer i = 0
            local integer j
            local integer k
            local real x
            local real y
            local unit u
            local unit v
       
            loop
                exitwhen (i >= thistype.tot)
                set this = thistype.tta[i]
           
           
                //Bolts goes here
                set j = 0
                loop
                    exitwhen (j >= this.mtot)
               
                    if (this.mis[j].obj != null) then
                        set this.mtick[j] = this.mtick[j]+XE_ANIMATION_PERIOD
                        set x = this.mis[j].sx+this.mis[j].xvel*this.mtick[j]
                        set y = this.mis[j].sy+this.mis[j].yvel*this.mtick[j]
                   
                        call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+MISSILE_RADIUS, null)
                        loop
                            set u = FirstOfGroup(ENUM_GROUP)
                            exitwhen (u == null)
                            call GroupRemoveUnit(ENUM_GROUP, u)
                            exitwhen (TargetFilter(u, this.owner, x, y))
                        endloop
                   
                                                                //A nice BJ, wrapped anyway
                        if (this.mtick[j] <= MISSILE_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null) then
                            call SetUnitX(this.mis[j].obj, x)
                            call SetUnitY(this.mis[j].obj, y)
                   

                        else
                       
                       
                            if (u != null) then
                           
                                call UnitDamageTarget(this.caster, u, this.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                               
                                if (UnitAlive(u)) then
                                    if (GetUnitAbilityLevel(u, FROST_SLOW_BUFF) == 0) then
                               
                                        set v = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0)
                                        call UnitAddAbility(v, FROST_SLOW_ID)
                                        call UnitApplyTimedLife(v, 'BTLF', 1.0)
                                        call IssueTargetOrder(v, "attackonce", u)
                                    endif
                                elseif (GetRandomInt(0, 100) <= this.chance and ShatterFilter(u)) then

                                    call DestroyEffect(AddSpecialEffect(IMPACT_SHATTER_ART, x, y))
                                    call RemoveUnit(u)
                                endif
                            endif
                       
                            //A note - instances are not destroyed until the end of the spell, when all instances are properly cleared.
                            call this.mis[j].clear()
                            set this.mcount = this.mcount-1

                        endif
                    endif
               
                    set j = j+1
                endloop

           
                //Orb goes here
                if (this.orb.obj != null) then
                    set this.otick = this.otick+XE_ANIMATION_PERIOD
                    set x = this.orb.sx+this.orb.xvel*this.otick
                    set y = this.orb.sy+this.orb.yvel*this.otick
               
                    if (this.otick < ORB_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y)) then
                        call SetUnitX(this.orb.obj, x)
                        call SetUnitY(this.orb.obj, y)
                   
                   
                        set this.mis[this.mtot] = missile.create(MISSILE_ART, this.owner, x, y, MISSILE_HEIGHT, this.mtot*MISSILE_RAD_OFFSET, MISSILE_SPEED, MISSILE_SCALE)
                        set this.mtick[this.mtot] = 0
                        set this.mcount = this.mcount+1
                        set this.mtot = this.mtot+1

                   
                    else
                        //Clears the orb
                        call this.orb.clear()
                   
                        //Proceeds with creating special bolts, aka bolts released when the orb vanishes.
                        set j = 0
                        loop
                            exitwhen (j >= ORB_EXPLODE_AMOUNT)
                            set this.xpl[j] = missile.create(MISSILE_ART, this.owner, x+ORB_RADIUS*Cos(j*RAD_BETWEEN_EXPL), y+ORB_RADIUS*Sin(j*RAD_BETWEEN_EXPL), MISSILE_HEIGHT, j*RAD_BETWEEN_EXPL+bj_PI*0.25, ORB_EXPLODE_SPEED, MISSILE_SCALE)
                            set j = j+1
                        endloop
                        set this.xtick = 0
                   
                    endif
                else
           
                    //Special bolt stuff goes here
                    set this.xtick = this.xtick+XE_ANIMATION_PERIOD
               
                    set j = 0
                    loop
                        exitwhen (j >= ORB_EXPLODE_AMOUNT)
                       
                        if (this.xpl[j].obj != null) then
                            set x = this.xpl[j].sx+this.xpl[j].xvel*this.xtick
                            set y = this.xpl[j].sy+this.xpl[j].yvel*this.xtick
                       
                       
                            call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+MISSILE_RADIUS, null)
                            loop
                                set u = FirstOfGroup(ENUM_GROUP)
                                exitwhen (u == null)
                                call GroupRemoveUnit(ENUM_GROUP, u)
                                exitwhen (TargetFilter(u, this.owner, x, y))
                            endloop
                       
                            if (this.xtick < EXPLODE_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null) then
                                call SetUnitX(this.xpl[j].obj, x)
                                call SetUnitY(this.xpl[j].obj, y)
                               
                            else
                           
                                if (u != null) then
                               
                                    call UnitDamageTarget(this.caster, u, this.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                                   
                                    if (UnitAlive(u)) then
                                        if (GetUnitAbilityLevel(u, FROST_SLOW_BUFF) == 0) then

                                            set v = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0)
                                            call UnitAddAbility(v, FROST_SLOW_ID)
                                            call UnitApplyTimedLife(v, 'BTLF', 1.0)
                                            call IssueTargetOrder(v, "attackonce", u)
                                        endif
                                    elseif (GetRandomInt(0, 100) <= this.chance and ShatterFilter(u)) then

                                        call DestroyEffect(AddSpecialEffect(IMPACT_SHATTER_ART, x, y))
                                        call RemoveUnit(u)
                                    endif
                                endif
                           
                                //Clears a special bolt
                                call this.xpl[j].clear()
                            endif
                        endif
                        set j = j+1
                    endloop
               
                endif
           
                //Completely destroys the spell and all instances
                if (this.mcount == 0 and this.orb.obj == null and this.xtick >= ORB_TMAX) then
               
                    set j = 0
                    loop
                        exitwhen (j >= this.mcount)
                        call this.mis[j].destroy()
                        set j = j+1
                    endloop
                    set j = 0
                    loop
                        exitwhen (j >= ORB_EXPLODE_AMOUNT)
                        call this.xpl[j].destroy()
                        set j = j+1
                    endloop
               
                    call this.orb.destroy()
                    call this.destroy()
                    set this.caster = null
                    set thistype.tot = thistype.tot-1
                    set thistype.tta[i] = thistype.tta[thistype.tot]
               
               
   
                    if (thistype.tot == 0) then
                        call PauseTimer(ANIM_TIMER)
                    endif
               
                else
                    set i = i+1
                endif
            endloop
            set v = null
        endmethod
   
        static method create takes unit su, real tx, real ty returns thistype
            local thistype this = thistype.allocate()
            local real x = GetUnitX(su)
            local real y = GetUnitY(su)
            local integer level = GetUnitAbilityLevel(su, ABILITY_ID)
       
            set this.caster = su
            set this.owner = GetOwningPlayer(su)
            set this.orb = missile.create(ORB_ART, this.owner, x, y, ORB_HEIGHT, Atan2(ty-y, tx-x), ORB_SPEED, ORB_SCALE)
            set this.otick = 0
            set this.mcount = 0
            set this.mtot = 0
            set this.damage = IMPACT_DAMAGE_BASE+IMPACT_DAMAGE_INC*(level-1)
            set this.chance = IMPACT_SHATTER_PER_BASE+IMPACT_SHATTER_PER_INC*(level-1)
       
            set thistype.tta[thistype.tot] = this
       
            if (thistype.tot == 0) then
                call TimerStart(ANIM_TIMER, XE_ANIMATION_PERIOD, true, function thistype.callback)
            endif
       
            set thistype.tot = thistype.tot+1
       
            return this
        endmethod
   
    endstruct

    private function Evaluate takes nothing returns boolean
        if (GetSpellAbilityId() == ABILITY_ID) then
            call spell.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
        return false
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function Evaluate))
    endfunction

endlibrary

Many thanks!
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,601
See in the code where it says:
vJASS:
set this.damage = IMPACT_DAMAGE_BASE+IMPACT_DAMAGE_INC*(level-1)
That's where it's setting the damage that will be dealt for THIS particular instance of Frozen Orb.

So you edit it to do this:
vJASS:
if ( UnitHasItemOfTypeBJ(su, 'schl') ) then
    set this.damage = 45+55*(level-1)
else
    set this.damage = 35+45*(level-1)
endif
su is a local variable that's set to the caster in this case.

The range is slightly more complicated. This constant (basically a global variable that can't change after it's been set) determines the maximum number of "ticks" the spell will have, which determines how long the missile lasts and thus how far it will travel.
vJASS:
private constant real      ORB_TMAX                = 1.0*ORB_RANGE/ORB_SPEED

The code used throughout the spell directly references this global variable ORB_TMAX which we don't want. Instead, we want a variable similar to this.damage, which is one that we can give a unique value to depending on whether or not the caster has the item. So let's add a new real variable to the system called "tmax" under "private struct spell" (I knew to add it here because the other this.X variables like damage were stored here as well). Then we need to find all instances of ORB_TMAX that are referenced in the code and replace them with "this.tmax".

Finally, we need to set this.tmax the same way we did with this.damage.
vJASS:
if ( UnitHasItemOfTypeBJ(su, 'schl') ) then
    set this.damage = 45+55*(level-1)
    set this.tmax = 1.0*1600.0/ORB_SPEED
else
    set this.damage = 35+45*(level-1)
    set this.tmax = 1.0*1200.0/ORB_SPEED
endif

And for the sake of keeping the code optimized you can comment out ORB_TMAX, ORB_RANGE, IMPACT_DAMAGE_BASE, and IMPACT_DAMAGE_INC since they're no longer needed.

To comment out code you simply put "//" before it, this turns it into a text comment same as the Comments you can create in the Trigger Editor. You could also just delete them if you'd like.

Here's the final product:
vJASS:
library FrozenOrb initializer Init requires xebasic
    //------------------------------------------------------<>
    // 'FROZEN ORB'
    //
    // Submitted by Eccho   2010-02-05 (1.0)
    //                      2010-02-15 (1.01)
    // Changelog can be found at the official submission post
    // at
    // http://www.hiveworkshop.com/forums/spells-569/frozen-orb-v1-0-a-158026/
    //
    // Give credits if used!!
    //------------------------------------------------------<>
    //------------------------------------------------------<>
    // 'Introduction'
    //
    // The reason why I did this spell is because I have not
    // seen yet any frozen orb spell which is properly
    // functional, or that I think does not look diablo 2'ish
    // enough (Vexorian made a frozen orb spell, I know, but
    // it isn't working at all anymore. This spell was indeed
    // an interpretation of the sorceress' spell Frozen Orb
    // in Diablo 2.
    //
    // A small note before I continue, due to some
    // complications with vJass I did not manage certain
    // things I had in mind in the beginning. The code may
    // indeed (the callback especially) be written in
    // another way, and I tried that, and failed.
    // Suggestions are welcome.
    //
    // A second note is that, frozen orb by default demands
    // some attention in the memory. Casting this multiply
    // times in a row without a cooldown, is going to have
    // some consequences. This is nothing I can change.
    // Adapt the XE_ANIMATION_PERIOD if needed.
    //
    // Third note is, you will not be able to control the
    // amount of bolts spawned while the orb is alive. These
    // are only depending on the animation period. This is
    // also how Diablo 2 handles Frozen Orb. Each bolt is
    // released each frame until the orb vanishes.
    //
    // 'Implementation instructions'
    //
    // First of all, you will need this library and the
    //  xebasic library. Copy these to your map.
    //
    // Second, you need the Universal Dummy unit found in the
    //  unit editor. Don't forget to import the dummy.mdx and
    //  set the dummy to use it as a model.
    //
    // Important: In order to apply the slow
    //  buff to the affected unit, make sure the universal
    //  dummy has:
    //   A cooldown time > 0
    //   Damage base != 0 (default -1)
    //   Damage die > 0 (default 1)
    //   A fast projectile speed
    //   A decent range
    //   Targets allowed - At least 'ground'
    //   Weapon type - missile
    //   Attack 1 enabled
    //   (if the unit does not have a movement speed, add it)
    //   And the usual stuff
    //
    // Next copy the ability which will cast the spell, and
    // the ability which will apply the buff, and modify the
    // rawcode id's in the constants below. Don't forget to
    // make sure the dummy unit id in the xebasic library is
    // set right as well.
    //
    // 'Credits'
    //
    // Vexorian - JassHelper, xebasic
    // PitzerMike & MindWorX - JNGP
    // Blizzard - Once again for some tooltip inspiration.
    //
    // Give proper credits if used!!
    //------------------------------------------------------<>
    //------------------------------------------------------<>
    // 'Native including'
    //
    // If you have this in your code somewhere else,
    // make sure to not double define it.
    //------------------------------------------------------<>
    native UnitAlive takes unit id returns boolean
    //------------------------------------------------------<>
    // 'Configuration section'
    //
    // Change the spell to fit your needs.
    //------------------------------------------------------<>
    globals
        private constant integer   ABILITY_ID              = 'A000'       //The ability triggering the spell
        private constant integer   FROST_SLOW_ID           = 'A001'       //The ability containing the frost attack. It the duration isn't altered of the already existing one in wc3, use that id instead.
        private constant integer   FROST_SLOW_BUFF         = 'Bfro'       //The buff which is used in the slow attack ability
                                                       
                                                                          //Art of the main orb unit
        private constant string    ORB_ART                 = "war3mapImported\\FrostOrb.mdx"
                                                                          //"Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
        private constant integer   ORB_SPEED               = 400          //Max travel speed
        //private constant integer   ORB_RANGE               = 1200         //Max travel distance
        private constant integer   ORB_RADIUS              = 128          //The radius the orb has. It is required as a polar projection offset when the orb explodes in the end
        private constant integer   ORB_HEIGHT              = 72           //The z-height from the ground the orb will travel (was 48)
        private constant real      ORB_SCALE               = 0.6          //The size/scaling of the orb (I believe the current model have some issues here, but it works with other models)
                                                                          // 2.0
                                                                            //Art of the released bolts. The bolts released when the orb explodes uses this art too.
        private constant string    MISSILE_ART             = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
        private constant integer   MISSILE_SPEED           = 365          //...
                                                                          // 370
        private constant integer   MISSILE_RANGE           = 400          //...
        private constant integer   MISSILE_RADIUS          = 32           //Bolt radius. It is used to check the collision of which enemies the bolts will hit and damage.
        private constant integer   MISSILE_HEIGHT          = 72           //...
        private constant real      MISSILE_SCALE           = 0.5          //...
        private constant real      MISSILE_RAD_OFFSET      = 3*bj_PI/5    //Each bolt is released with a certain angle offset (in radians), and is defined by this constant. Each bolt is added this constant + the previous angle.
        private constant integer   MISSILE_AMOUNT_MAX      = 100          //Keep this constant at a secure amount. The spell could go very wrong if this value is less than the maximum amount of bolts released. 100 is used by default.
        private constant integer   ORB_EXPLODE_AMOUNT      = 32           //Amount of bolts released when the orb explodes
        private constant integer   ORB_EXPLODE_SPEED       = 900          //Special bolt travel speed
        private constant integer   ORB_EXPLODE_RANGE       = 900          //Special bolt travel distance
                                                                          //If a unit shatters, this effect will be created at it's position
        private constant string    IMPACT_SHATTER_ART      = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        private constant integer   IMPACT_SHATTER_PER_BASE = 0            //Percental base chance to shatter a unit which dies from the spell
        private constant integer   IMPACT_SHATTER_PER_INC  = 0            //Percental increament chance per level to shatter a unit which dies from the spell (example, if base is 8 and incr is 4, each level adds 4 chance)
        //private constant integer   IMPACT_DAMAGE_BASE      = 35           //Base damage
        //private constant integer   IMPACT_DAMAGE_INC       = 45           //Incremental damage (works in the same way as the shatter base/incr fields)
                                                       
                                                         
        private constant attacktype ATTACK_TYPE             = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE             = DAMAGE_TYPE_MAGIC
        private constant weapontype WEAPON_TYPE             = WEAPON_TYPE_WHOKNOWS
    endglobals
    //------------------------------------------------------<>
    // 'Damage filter'
    //
    // Add more options as desired.
    // Some info: It is used as a condition not a boolexpr,
    // thus, not going to give any issue with IsUnitType.
    //------------------------------------------------------<>
    private function TargetFilter takes unit enemy, player caster, real x, real y returns boolean
        return UnitAlive(enemy) and IsUnitEnemy(enemy, caster) and IsUnitInRangeXY(enemy, x, y, MISSILE_RADIUS) and not IsUnitType(enemy, UNIT_TYPE_STRUCTURE)
    endfunction
    //------------------------------------------------------<>
    // 'Shatter filter'
    //
    // Add more options as desired.
    // It determines the units which may shatter
    // The filter does not need to contain the same context
    // as the damage filter. It is only potentially ran after
    // the target filter have became true.
    //------------------------------------------------------<>
    private function ShatterFilter takes unit enemy returns boolean
        return not IsUnitType(enemy, UNIT_TYPE_MECHANICAL) and not IsUnitType(enemy, UNIT_TYPE_HERO) and not IsUnitType(enemy, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(enemy, UNIT_TYPE_STRUCTURE)
    endfunction
    //------------------------------------------------------<>
    // 'Other constants'
    //
    // These should not be altered by default, but if you
    // know what you are doing, or see a way to use other
    // constants instead, feel free.
    //------------------------------------------------------<>
    globals
        //private constant real      ORB_TMAX                = 1.0*ORB_RANGE/ORB_SPEED
        private constant real      MISSILE_TMAX            = 1.0*MISSILE_RANGE/MISSILE_SPEED
        private constant real      EXPLODE_TMAX            = 1.0*ORB_EXPLODE_RANGE/ORB_EXPLODE_SPEED
        private constant real      RAD_BETWEEN_EXPL        = bj_PI*2/ORB_EXPLODE_AMOUNT
        private constant group     ENUM_GROUP              = CreateGroup()
        private constant timer     ANIM_TIMER              = CreateTimer()
    endglobals
    //------------------------------------------------------<>
    // 'Spell code'
    //
    // If you even think up of an optimize fully working
    // version, tell me about it.
    //------------------------------------------------------<>
    private struct missile
        unit obj
        effect art
        real sx
        real sy
        real xvel
        real yvel
        static method create takes string art, player p, real x, real y, real z, real rad, integer speed, real scale returns thistype
            local thistype this = thistype.allocate()

            set this.obj = CreateUnit(p, XE_DUMMY_UNITID, x, y, rad*bj_RADTODEG)
            call UnitAddAbility(this.obj, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(this.obj, XE_HEIGHT_ENABLER)
            call SetUnitFlyHeight(this.obj, z, 0)
            call SetUnitScale(this.obj, scale, scale, scale)
            call SetUnitAnimationByIndex(this.obj, 90)
            call UnitRemoveAbility(this.obj, 'Aatk')

            set this.art = AddSpecialEffectTarget(art, this.obj, "origin")
            set this.sx = x
            set this.sy = y
            set this.xvel = speed*Cos(rad)
            set this.yvel = speed*Sin(rad)

            return this
        endmethod
        method clear takes nothing returns nothing
            call DestroyEffect(this.art)
            call KillUnit(this.obj)
            set this.art = null
            set this.obj = null
        endmethod
    endstruct
    private struct spell
        missile orb
        real otick
        real tmax
        missile array mis[MISSILE_AMOUNT_MAX]
        real array mtick[MISSILE_AMOUNT_MAX]
        integer mcount
        integer mtot
        missile array xpl[ORB_EXPLODE_AMOUNT]
        real xtick //All exploding missiles have the same tickoffset
        unit caster
        player owner
        integer damage
        integer chance
        static thistype array tta
        static integer tot = 0
        static method callback takes nothing returns nothing
            local thistype this
            local missile m
            local integer i = 0
            local integer j
            local integer k
            local real x
            local real y
            local unit u
            local unit v

            loop
                exitwhen (i >= thistype.tot)
                set this = thistype.tta[i]


                //Bolts goes here
                set j = 0
                loop
                    exitwhen (j >= this.mtot)

                    if (this.mis[j].obj != null) then
                        set this.mtick[j] = this.mtick[j]+XE_ANIMATION_PERIOD
                        set x = this.mis[j].sx+this.mis[j].xvel*this.mtick[j]
                        set y = this.mis[j].sy+this.mis[j].yvel*this.mtick[j]
   
                        call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+MISSILE_RADIUS, null)
                        loop
                            set u = FirstOfGroup(ENUM_GROUP)
                            exitwhen (u == null)
                            call GroupRemoveUnit(ENUM_GROUP, u)
                            exitwhen (TargetFilter(u, this.owner, x, y))
                        endloop
   
                                                                //A nice BJ, wrapped anyway
                        if (this.mtick[j] <= MISSILE_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null) then
                            call SetUnitX(this.mis[j].obj, x)
                            call SetUnitY(this.mis[j].obj, y)
   
                        else
       
       
                            if (u != null) then
           
                                call UnitDamageTarget(this.caster, u, this.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
               
                                if (UnitAlive(u)) then
                                    if (GetUnitAbilityLevel(u, FROST_SLOW_BUFF) == 0) then
               
                                        set v = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0)
                                        call UnitAddAbility(v, FROST_SLOW_ID)
                                        call UnitApplyTimedLife(v, 'BTLF', 1.0)
                                        call IssueTargetOrder(v, "attackonce", u)
                                    endif
                                elseif (GetRandomInt(0, 100) <= this.chance and ShatterFilter(u)) then
                                    call DestroyEffect(AddSpecialEffect(IMPACT_SHATTER_ART, x, y))
                                    call RemoveUnit(u)
                                endif
                            endif
       
                            //A note - instances are not destroyed until the end of the spell, when all instances are properly cleared.
                            call this.mis[j].clear()
                            set this.mcount = this.mcount-1
                        endif
                    endif

                    set j = j+1
                endloop

                //Orb goes here
                if (this.orb.obj != null) then
                    set this.otick = this.otick+XE_ANIMATION_PERIOD
                    set x = this.orb.sx+this.orb.xvel*this.otick
                    set y = this.orb.sy+this.orb.yvel*this.otick

                    if (this.otick < this.tmax and RectContainsCoords(bj_mapInitialPlayableArea, x, y)) then
                        call SetUnitX(this.orb.obj, x)
                        call SetUnitY(this.orb.obj, y)
   
   
                        set this.mis[this.mtot] = missile.create(MISSILE_ART, this.owner, x, y, MISSILE_HEIGHT, this.mtot*MISSILE_RAD_OFFSET, MISSILE_SPEED, MISSILE_SCALE)
                        set this.mtick[this.mtot] = 0
                        set this.mcount = this.mcount+1
                        set this.mtot = this.mtot+1
   
                    else
                        //Clears the orb
                        call this.orb.clear()
   
                        //Proceeds with creating special bolts, aka bolts released when the orb vanishes.
                        set j = 0
                        loop
                            exitwhen (j >= ORB_EXPLODE_AMOUNT)
                            set this.xpl[j] = missile.create(MISSILE_ART, this.owner, x+ORB_RADIUS*Cos(j*RAD_BETWEEN_EXPL), y+ORB_RADIUS*Sin(j*RAD_BETWEEN_EXPL), MISSILE_HEIGHT, j*RAD_BETWEEN_EXPL+bj_PI*0.25, ORB_EXPLODE_SPEED, MISSILE_SCALE)
                            set j = j+1
                        endloop
                        set this.xtick = 0
   
                    endif
                else

                    //Special bolt stuff goes here
                    set this.xtick = this.xtick+XE_ANIMATION_PERIOD

                    set j = 0
                    loop
                        exitwhen (j >= ORB_EXPLODE_AMOUNT)
       
                        if (this.xpl[j].obj != null) then
                            set x = this.xpl[j].sx+this.xpl[j].xvel*this.xtick
                            set y = this.xpl[j].sy+this.xpl[j].yvel*this.xtick
       
       
                            call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+MISSILE_RADIUS, null)
                            loop
                                set u = FirstOfGroup(ENUM_GROUP)
                                exitwhen (u == null)
                                call GroupRemoveUnit(ENUM_GROUP, u)
                                exitwhen (TargetFilter(u, this.owner, x, y))
                            endloop
       
                            if (this.xtick < EXPLODE_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null) then
                                call SetUnitX(this.xpl[j].obj, x)
                                call SetUnitY(this.xpl[j].obj, y)
               
                            else
           
                                if (u != null) then
               
                                    call UnitDamageTarget(this.caster, u, this.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                   
                                    if (UnitAlive(u)) then
                                        if (GetUnitAbilityLevel(u, FROST_SLOW_BUFF) == 0) then
                                            set v = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0)
                                            call UnitAddAbility(v, FROST_SLOW_ID)
                                            call UnitApplyTimedLife(v, 'BTLF', 1.0)
                                            call IssueTargetOrder(v, "attackonce", u)
                                        endif
                                    elseif (GetRandomInt(0, 100) <= this.chance and ShatterFilter(u)) then
                                        call DestroyEffect(AddSpecialEffect(IMPACT_SHATTER_ART, x, y))
                                        call RemoveUnit(u)
                                    endif
                                endif
           
                                //Clears a special bolt
                                call this.xpl[j].clear()
                            endif
                        endif
                        set j = j+1
                    endloop

                endif

                //Completely destroys the spell and all instances
                if (this.mcount == 0 and this.orb.obj == null and this.xtick >= this.tmax) then

                    set j = 0
                    loop
                        exitwhen (j >= this.mcount)
                        call this.mis[j].destroy()
                        set j = j+1
                    endloop
                    set j = 0
                    loop
                        exitwhen (j >= ORB_EXPLODE_AMOUNT)
                        call this.xpl[j].destroy()
                        set j = j+1
                    endloop

                    call this.orb.destroy()
                    call this.destroy()
                    set this.caster = null
                    set thistype.tot = thistype.tot-1
                    set thistype.tta[i] = thistype.tta[thistype.tot]


                    if (thistype.tot == 0) then
                        call PauseTimer(ANIM_TIMER)
                    endif

                else
                    set i = i+1
                endif
            endloop
            set v = null
        endmethod
        static method create takes unit su, real tx, real ty returns thistype
            local thistype this = thistype.allocate()
            local real x = GetUnitX(su)
            local real y = GetUnitY(su)
            local integer level = GetUnitAbilityLevel(su, ABILITY_ID)

            set this.caster = su
            set this.owner = GetOwningPlayer(su)
            set this.orb = missile.create(ORB_ART, this.owner, x, y, ORB_HEIGHT, Atan2(ty-y, tx-x), ORB_SPEED, ORB_SCALE)
            set this.otick = 0
            set this.mcount = 0
            set this.mtot = 0
            if ( UnitHasItemOfTypeBJ(su, 'schl') ) then
                set this.damage = 45+55*(level-1)
                set this.tmax = 1.0*1600.0/ORB_SPEED
            else
                set this.damage = 35+45*(level-1)
                set this.tmax = 1.0*1200.0/ORB_SPEED
            endif
            set this.chance = IMPACT_SHATTER_PER_BASE+IMPACT_SHATTER_PER_INC*(level-1)

            set thistype.tta[thistype.tot] = this

            if (thistype.tot == 0) then
                call TimerStart(ANIM_TIMER, XE_ANIMATION_PERIOD, true, function thistype.callback)
            endif

            set thistype.tot = thistype.tot+1

            return this
        endmethod
    endstruct
    private function Evaluate takes nothing returns boolean
        if (GetSpellAbilityId() == ABILITY_ID) then
            call spell.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
        return false
    endfunction
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function Evaluate))
    endfunction
endlibrary
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,601
I accidentally wrote the wrong variable names. I updated the Final Code with the fix.

Edit: I did it again. Updated again.

Don't forget to update the ability id/buff id and any other settings you may have changed.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,601
Thank you Uncle! Tested and works.

The part with the damage I understood, but the range changes I will need to study some more.
No problem.

Basically, the spell uses a timer that runs every XE_ANIMATION_PERIOD seconds. XE_ANIMATION_PERIOD is defined in the xebasic system that comes with Frozen Orb, so it's equal to whatever it's set in there. I assume it's set to something like 0.03 so that the timer runs ~33 times per second.

Now what happens is that every timer interval a tick variable is increased. This variable keeps track of how much time has elapsed.

This tick variable is then compared to ORB_TMAX, or in our case it's compared to this.tmax, to see if enough time has elapsed that the spell should end.

So this.tmax is set at the start of the spell to be equal to either:
1.0*1200.0/ORB_SPEED
OR with aghanim's scepter:
1.0*1600.0/ORB_SPEED.

ORB_SPEED is a global variable that's set to 400.0 by default.

So if the caster doesn't have aghanim's scepter then this.tmax will be equal to:
1.0*1200.0/400.0 = 3.0.

This means that every timer interval (every ~0.03 seconds) our tick variable is increased by 0.03 and the system checks to see if it's >= 3.0 (tmax). Once this is true the spell ends.

If the caster DOES have aghanim's scepter then this.tmax will be equal to:
1.0*1600.0/400.0 = 4.0.
Resulting in a longer duration and thus traveling farther.

In other words, the spell periodically checks to see if it's reached the end of it's duration. It does this by keeping track of how much time has elapsed and comparing that to the total duration. The total duration of the spell is determined at the start by dividing the range by the speed, or in more mathematical terms: Time = Distance/Velocity.

Speaking on the design methods used in the code...

The whole "this.variable" thing requires a longer explanation but just understand that it's used for creating MUI spells. It allows you to create unique instances of these variables which are linked to a unique instance of the spell. So if you were to cast multiple Frozen Orbs in a row, each individual Frozen Orb would be it's own instance and have it's own versions of the "this.variables". This is useful since it allows you to set different values for each Frozen Orb depending on the situation, which we do in the case of this.damage/this.tmax. If you want to learn more about this then look into Structs.
 
Last edited:
Status
Not open for further replies.
Top