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

Frostfire Bolt [custom swirly bolt] v1.0.2

Fires two homing projectiles barrel rolling around eachother
towards the target dealing damage on hit, slowing movement and attackspeed
and setting enemy on fire cousing overtime damage.
[the effects (slow,burn) caused by the bolts are depends on the dummy abilities you set.
those could be anything like a stun a daze or even another triggered ability]

highly customizable,well commented

contains "how to import"

The spell is MUI/MPI.

The effects are customizable and so you can set any type of debuff for the two bolts
So basically "FrostFire" is only a random prefix:)

Enjoy!


1.0.2: optimizations and customizable sound

1.0.1:
-static caster for all spells
-Cleared unneeded variables
-Upgraded list inheriting.
-Allowed configuring attack weapon and damage types.
-Separated the damage of the two bolts.



JASS:
scope FrostFireBolt

    globals
        
        private         constant        real                PERIOD                  = 0.03125
        
        private         constant        string              SPELL_SOUND             = "Units\\Creeps\\LavaSpawn\\LavaSpawnMorphBirth.wav"
        
        private         constant        string              EFFECT_ONE              = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
        private         constant        string              EFFECT_TWO              = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
        private         constant        string              IMPACTEFFECT_ONE        = "Abilities\\Spells\\Other\\Volcano\\VolcanoDeath.mdl"//"Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
        private         constant        string              IMPACTEFFECT_TWO        = "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl"//"Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
        
        private         constant        real                BOLT_SCALE_ONE          = 0.70  // 70% scale of effect one
        private         constant        real                BOLT_SCALE_TWO          = 0.70  // 70% scale of effect two
        private         constant        real                BOLT_SPEED              = 900.  // 800/sec
        private         constant        real                AMPLITUDE_VERTICAL      = 40.   
        private         constant        real                AMPLITUDE_HORISONTAL    = 35.
        private         constant        real                BOLT_SPIN_SPEED         = 500.  
        private         constant        real                BOLT_OFFSET             = 50.   // the overall height of the spinning bolts
        
        private         constant        integer             SPELL_ID                = 'ffbs'    // id of the main spell
        private         constant        integer             DUMMYUNIT_ID            = 'ffbd'        //dummy units id
        private         constant        integer             CASTERDUMMY_ID          = 'ffbc'
        private         constant        integer             SLOWSPELL_ORDER         = 852075        //slowing spells order (slow)
        private         constant        integer             BURNSPELL_ORDER         = 852209        //burning spells order (unholyfrenzy)
        private         constant        integer             SLOWSPELL_ID            = 'cosd'        //slowing spells id
        private         constant        integer             BURNSPELL_ID            = 'fisd'        //burning spells id
        
        private         constant        real                DAMAGEPERLEVEL_FIRE     = 60.          // base damage of the spell per level of ability
        private         constant        real                DAMAGEPERLEVEL_FROST    = 60.
        
        private         constant        attacktype          ATTACKTYPE_ONE          = ATTACK_TYPE_MAGIC 
        private         constant        damagetype          DAMAGETYPE_ONE          = DAMAGE_TYPE_FIRE
        private         constant        weapontype          WEAPONTYPE_ONE          = null
        
        private         constant        attacktype          ATTACKTYPE_TWO          = ATTACK_TYPE_MAGIC 
        private         constant        damagetype          DAMAGETYPE_TWO          = DAMAGE_TYPE_COLD
        private         constant        weapontype          WEAPONTYPE_TWO          = null
                                                                                
                                                                                // for raw code's (ability id, unit id) press ctrl+d in object editor
        
    endglobals

    private struct FrostFireBolt

                        effect              effOne                      = null
                        effect              effTwo                      = null
                        unit                utarg                       = null
                        widget              target                      = null
                        unit                caster                      = null
                        boolean             active                      = true
                        integer             sign                        = 1
                        integer             lvl                         = 0
                        real                angle                       = 0.
        static          unit                spellDummy                  = null
                        unit                frostDummy                  = null
                        unit                fireDummy                   = null
                        real                targetX                     = 0.
                        real                targetY                     = 0.
                        real                newX                        = 0.
                        real                newY                        = 0.
                        real                ampX                        = 0.
                        real                ampY                        = 0.
                        real                ampZ                        = 0.
                        thistype            next                        = 0
                        thistype            prev                        = 0
        static          integer             count                       = 0
        static          timer               t                           = CreateTimer()

        private method destroy takes nothing returns nothing
            set .next.prev = .prev
            set .prev.next = .next
            set thistype.count = thistype.count -1
            if thistype.count <= 0 then
                set thistype.count = 0
                call PauseTimer(thistype.t)
            endif
            call .deallocate()
        endmethod
        
    //--------------------------------------------------------------------
    //--------- POSITIONING ----------------------------------------------
    //--------------------------------------------------------------------

        private static method positioning takes nothing returns nothing
            local real velocity
            local real amppower
            local thistype this = thistype(0)
            loop
                set this = .next
                exitwhen this == thistype(0)
                
                if .active then
                
                    if not IsUnitType(.utarg,UNIT_TYPE_DEAD) and GetUnitTypeId(.utarg) != 0 then
                        set .targetX = GetWidgetX(.target)
                        set .targetY = GetWidgetY(.target)
                        set .angle = Atan2(.targetY-.newY,.targetX-.newX)
                        call SetUnitFacing(.frostDummy,.angle*57.29577951)
                        call SetUnitFacing(.fireDummy,.angle*57.29577951)
                    endif
            
            //ooooooooooooooooooooooooooooooooooooooooooooooo//
            //----Placing the unit to the next point---------//
                    
                    set velocity = BOLT_SPEED * PERIOD
                    set .newX = .newX+(velocity * Cos(.angle))
                    set .newY = .newY+(velocity * Sin(.angle))
                    set amppower = 0.
                    if AMPLITUDE_VERTICAL != 0 then
                        set velocity = BOLT_SPIN_SPEED * PERIOD
                        set .ampX = .ampX + velocity * Cos(.angle+1.57079632 )*.sign
                        set .ampY = .ampY + velocity * Sin(.angle+1.57079632 )*.sign
                        set amppower = SquareRoot(.ampX*.ampX+.ampY*.ampY)
                    endif
                    if AMPLITUDE_HORISONTAL != 0. and AMPLITUDE_VERTICAL != 0. then
                        if .sign == 1 then
                            set .ampZ = (BOLT_OFFSET-AMPLITUDE_HORISONTAL)+(amppower*amppower)/(AMPLITUDE_VERTICAL*AMPLITUDE_VERTICAL/AMPLITUDE_HORISONTAL)
                        else
                            set .ampZ = (BOLT_OFFSET+AMPLITUDE_HORISONTAL)-(amppower*amppower)/(AMPLITUDE_VERTICAL*AMPLITUDE_VERTICAL/AMPLITUDE_HORISONTAL)
                        endif
                        call SetUnitFlyHeight(.frostDummy,.ampZ,0.)
                        if .sign == (-1) then
                            set .ampZ = (BOLT_OFFSET-AMPLITUDE_HORISONTAL)+(amppower*amppower)/(AMPLITUDE_VERTICAL*AMPLITUDE_VERTICAL/AMPLITUDE_HORISONTAL)
                        else
                            set .ampZ = (BOLT_OFFSET+AMPLITUDE_HORISONTAL)-(amppower*amppower)/(AMPLITUDE_VERTICAL*AMPLITUDE_VERTICAL/AMPLITUDE_HORISONTAL)
                        endif
                        call SetUnitFlyHeight(.fireDummy,.ampZ,0.)
                    elseif AMPLITUDE_HORISONTAL != 0. then
                        set velocity = BOLT_SPIN_SPEED * PERIOD
                        set .ampZ = .ampZ + velocity * .sign
                        call SetUnitFlyHeight(.frostDummy,BOLT_OFFSET+.ampZ,0.)
                        call SetUnitFlyHeight(.fireDummy,BOLT_OFFSET-.ampZ,0.)
                    endif
                    call SetUnitPosition(.frostDummy,.newX+.ampX,.newY+.ampY)
                    call SetUnitPosition(.fireDummy,.newX-.ampX,.newY-.ampY)
                    if AMPLITUDE_HORISONTAL != 0 or AMPLITUDE_VERTICAL != 0 then
                        if AMPLITUDE_HORISONTAL != 0 and AMPLITUDE_VERTICAL == 0 then 
                            if .ampZ*.ampZ>=AMPLITUDE_HORISONTAL*AMPLITUDE_HORISONTAL then
                                set .sign = .sign*(-1)
                            endif
                        else
                            if amppower>=AMPLITUDE_VERTICAL then
                                set .sign = .sign*(-1)
                            endif
                        endif
                    endif
            //ooooooooooooooooooooooooooooooooooooooooooooooo//
            //----Checks if the unit reached its destination-//
                    if (.targetX-.newX)*(.targetX-.newX)+(.targetY-.newY)*(.targetY-.newY) <= 400.  then
                        set .active = false
                        call DestroyEffect(.effOne)
                        call DestroyEffect(.effTwo)
                        call RemoveUnit(.frostDummy)
                        call RemoveUnit(.fireDummy)
                        set .frostDummy = null
                        set .fireDummy = null
                
                        if not IsUnitType(.utarg,UNIT_TYPE_DEAD) and GetUnitTypeId(.utarg) != 0 then
                            call DestroyEffect(AddSpecialEffect(IMPACTEFFECT_ONE,GetWidgetX(.target),GetWidgetY(.target)))
                            call DestroyEffect(AddSpecialEffect(IMPACTEFFECT_TWO,GetWidgetX(.target),GetWidgetY(.target)))
                            call UnitDamageTarget(.caster,.target,.lvl*DAMAGEPERLEVEL_FIRE,false,false,ATTACKTYPE_ONE,DAMAGETYPE_ONE,WEAPONTYPE_ONE)
                            call UnitDamageTarget(.caster,.target,.lvl*DAMAGEPERLEVEL_FROST,false,false,ATTACKTYPE_TWO,DAMAGETYPE_TWO,WEAPONTYPE_TWO)
                            call SetUnitPosition(thistype.spellDummy,GetWidgetX(.target),GetWidgetY(.target))
                            call SetUnitAbilityLevel(thistype.spellDummy,SLOWSPELL_ID,.lvl)
                            call SetUnitAbilityLevel(thistype.spellDummy,BURNSPELL_ID,.lvl)
                            call IssueTargetOrderById( thistype.spellDummy,SLOWSPELL_ORDER, .target)
                            call IssueTargetOrderById( thistype.spellDummy,BURNSPELL_ORDER, .target)
                        endif
                        set .target = null
                        set .caster = null
                    endif
                endif
                if not .active then
                    call .destroy()
                    set this = .prev
                endif
            endloop
        endmethod


        
    //--------------------------------------------------------------------
    //STRUCT CREATION , REGISTRATION , INSTANCE HANDLING FOR MULTI STRUCTS
    //--------------------------------------------------------------------
        
        private static method create takes nothing returns thistype
            local sound snd = CreateSound(SPELL_SOUND,false,true,false,10000,10000,"")
            local thistype this = thistype.allocate()
            local player pler
            set .target = GetSpellTargetUnit()
            set .utarg = GetSpellTargetUnit()
            set .caster = GetSpellAbilityUnit()
            set pler = GetOwningPlayer(.caster)
            call SetUnitOwner(thistype.spellDummy,pler,false)
            set .lvl = GetUnitAbilityLevel(.caster,SPELL_ID)
            set .newX = GetUnitX(.caster)
            set .newY = GetUnitY(.caster)
            set .targetX = GetWidgetX(.target)
            set .targetY = GetWidgetY(.target)
            set .angle = Atan2(.targetY-.newY,.targetX-.newX)*57.29577951
            call AttachSoundToUnit(snd,.caster)
            call SetSoundVolume(snd,75)
            call StartSound(snd)
            call KillSoundWhenDone(snd)
            set .frostDummy = CreateUnit(pler,DUMMYUNIT_ID,.newX,.newY,.angle)
            set .fireDummy = CreateUnit(pler,DUMMYUNIT_ID,.newX,.newY,.angle)
            call SetUnitScale(.frostDummy,BOLT_SCALE_TWO,BOLT_SCALE_TWO,BOLT_SCALE_TWO)
            call SetUnitScale(.fireDummy,BOLT_SCALE_ONE,BOLT_SCALE_ONE,BOLT_SCALE_ONE)
            call SetUnitPathing(.frostDummy,false)
            call SetUnitPathing(.fireDummy,false)
            call PauseUnit(.frostDummy,true)
            call PauseUnit(.fireDummy,true)
            set .effOne = AddSpecialEffectTarget(EFFECT_ONE,.fireDummy,"chest")
            set .effTwo = AddSpecialEffectTarget(EFFECT_TWO,.frostDummy,"chest")
            call UnitAddAbility(.frostDummy,'Amrf')
            call UnitAddAbility(.fireDummy,'Amrf')
            call UnitRemoveAbility(.frostDummy,'Amrf')
            call UnitRemoveAbility(.fireDummy,'Amrf')
            
            set .active = true
            //this goes here and there and that goes there and here...
            set .prev = thistype(0).prev
            set .next = thistype(0)
            set .prev.next = this
            set thistype(0).prev = this
            set thistype.count = thistype.count + 1
            if thistype.count == 1 then
                call TimerStart(thistype.t, PERIOD, true, function thistype.positioning)
            endif
            set snd = null
            return this
        endmethod
        
        private static method filt takes nothing returns boolean
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.create()
            endif
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            local integer i = 0
            local trigger t = CreateTrigger()
            set thistype.spellDummy = CreateUnit(Player(0),CASTERDUMMY_ID,0.,0.,0.)
            call UnitAddAbility(thistype.spellDummy,'Amrf')
            call UnitRemoveAbility(thistype.spellDummy,'Amrf')
            call UnitAddAbility(thistype.spellDummy,SLOWSPELL_ID)
            call UnitAddAbility(thistype.spellDummy,BURNSPELL_ID)
            call TriggerAddCondition(t, Condition(function thistype.filt))
            loop
                exitwhen i >= bj_MAX_PLAYER_SLOTS
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
                set i = i +1
            endloop
        endmethod

    endstruct
endscope

Keywords:
bolt, fire, frost, frostfire, projectile, spell, missile, spinning, rolling, homing, slow, burn
Contents

Just another Warcraft III map (Map)

Reviews
18:23, 14th Oct 2013 PurgeandFire: Approved! This spell is great for whatever swirly projectile abilities you desire. (old) Review: http://www.hiveworkshop.com/forums/2431630-post20.html

Moderator

M

Moderator

18:23, 14th Oct 2013
PurgeandFire: Approved! This spell is great for whatever swirly projectile abilities you desire.

(old) Review:
http://www.hiveworkshop.com/forums/2431630-post20.html
 
Level 9
Joined
Dec 3, 2010
Messages
162
The resource should be encapsulated within a scope or a library.


JASS:
private                     real                boltScale                   = FFB_BOLT_SCALE
private                     real                boltSpeed                   = FFB_BOLT_SPEED
private                     real                spinSpeed                   = FFB_BOLT_SPIN_SPEED
private                     real                verAmp                      = FFB_AMPLITUDE_VERTICAL
private                     real                horAmp                      = FFB_AMPLITUDE_HORISONTAL
private                     real                boltOffset                  = FFB_BOLT_OFFSET
Do these members remain constant throughout the duration of the spell? If so, you don't need these members, just use the constants directly.


JASS:
readonly                    widget              target                      = null
readonly                    unit                caster                      = null
readonly                    boolean             active                      = true
Any reason why these members are readonly?


I see that you're using a linked list of some sort to iterate through the instances for your periodic effect? You have unneeded static members for your linked list.

JASS:
struct Test
    private thistype next
    private thistype prev

    // looping through the list
    private static method periodic takes nothing returns nothing
        local thistype this = thistype(0)
        
        loop
            set this = this.next
            exitwhen this == 0

            ...
        endloop
    endmethod

    // destroy an instance
    private method destroy takes nothing returns nothing
        set this.next.prev = this.prev
        set this.prev.next = this.next

        // count check, to determine if timer needs to be paused
        if thistype(0).next == 0 then
            // pause timer
        endif

        call this.deallocate()
    endmethod

    // create an instance and insert into list
    private static method create takes nothing returns thistype
        local thistype this = thistype.allocate()

        set this.next = thistype(0)
        set this.prev = thistype(0).prev
        set thistype(0).prev.next = this
        set thistype(0).prev = this
        
        return this
    endmethod

    // init method
    private static method onInit takes nothing returns nothing
        // this is probably unneeded as next and prev already points to thistype(0) by default, but just doing it anyways
        set thistype(0).next = thistype(0)
        set thistype(0).prev = thistype(0)
    endmethod
endstruct
You won't need the variables thiss, first, last and count. And actually, rather than coding your own list from scratch, you could actually use CTL for periodic timers with a timeout of 0.03125.


You should allow ATTACK_TYPE and DAMAGE_TYPE to be configurable, rather than null. WEAPON_TYPE as well if you want.


You don't need to dynamically create caster dummies. Simply have 1 static dummy created at init to perform all the necessary dummy casts.
 
Level 12
Joined
Oct 16, 2010
Messages
680
The resource should be encapsulated within a scope or a library.

sorry i was in a hurry, I just missed that

Do these members remain constant throughout the duration of the spell? If so, you don't need these members, just use the constants directly.

you got the point

Any reason why these members are readonly?

I was thinking of adding events but changed my mind and forgot to rename them

You won't need the variables thiss, first, last and count. And actually, rather than coding your own list from scratch, you could actually use CTL for periodic timers with a timeout of 0.03125.

Since I already scratched it up I don't realy want to rewrote that part but will take your advice and get rid of thiss, first and last. I always thinked of linked lists as a line not a circle :D thanks

will update the map as soon as I can. +rep for the great feedback iAyanami :).
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Private struct are private all members inside and the named, even using static, you can't call it outside, that weird if you use private members twice :D, i never seen what happen if private twice, i'll do it.
If you using method "destroy" inside struct, it will cause an error, i think "onDestroy" is right or you missing some thing.

I shouldnt, JassHelper is not very intelligent but he is good enough to find if user uses his own destroy/create methods, if he does, it implements them, if he doesnt it inlines them to .allocate()/.deallocate()

fix the indentation in onInit method please

Also instead of looping through all players inside onInit, you should use http://www.hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/

JASS:
if SquareRoot(.ampX*.ampX+.ampY*.ampY)>=.verAmp then
into
JASS:
if (this.ampX*this.ampX+this.ampY*this.ampX) >= this.verAmp*this.verAmp) then

Your method of checking if unit is alive is not reliable, if I use tranquility it will heal dead units, so your condition will pass, use either if IsUnitType(unit, UNIT_TYPE_DEAD) then or if IsUnitType(unit, UNIT_TYPE_DEAD) or GetWidgetLife(widget) >= 0.405 then

If you can, order units by Id instead of string, its faster(recommandation, not like Im pushing you into it)

JASS:
call SetUnitFacingTimed(.frostDummy,.angle*57.29577951,0.01)
call SetUnitFacingTimed(.fireDummy,.angle*57.29577951,0.01)
is irrelevant, just use SetUnitFacing, because there is hardcoded time that it takes the unit to turn around, alternativelly you can call SetUnitMoveSpeed(unit, 0.0), which makes unit unmovable, untrunable but the unit will cast and attack instantly under any angle I believe
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Blizzard hide the method "destroy" inside any struct, that why it cause an error, use onDestroy to access that method :)

blizzard does not, blizzard has no control over vJass, because it was not blizzard that made it but Vexorian, and he is former admin of wc3c

Believe me, if you implement your destroy method, it shouldnt cause compile error, onDestroy is only used in case with inheritance when you destroy object of type A inheriting type B, then B's onDestroy will be called.

Destroy and create cant be private tho, and if you dont provide any version of method called create and/or destroy, JassHelper will generate one(which inlines to .allocate()/.deallocate() call
 
Level 9
Joined
Dec 3, 2010
Messages
162
Nope .. Checked everything no double declaration no moduls and alikes :/
Only can think it's my we but I use jngp 5d with a standard (not patched) game so that should be ok

Try re-downloading JNGP in that case. It should work. Get this version if you haven't btw.

Blizzard hide the method "destroy" inside any struct, that why it cause an error, use onDestroy to access that method :)

Err no. Like mentioned above, declaring your own destroy method works perfectly fine. In fact, onDestroy is slower than using destroy. onDestroy really should only be used when working with inheritance.
 
Review:
  • You can initialize the timer "t" with CreateTimer() in the struct:
    JASS:
    struct Example
        private static timer t = CreateTimer()
    endstruct
    Basically, you don't have to do it in the onInit.
  • Your indenting is a bit weird in "onInit". You indent the TriggerAddCondition() and the loop even though they aren't in a block.
  • You may want to register on SPELL_EFFECT rather than SPELL_CAST. Spell effect will fire after mana is used and stuff (probably won't be an issue for this spell, but for future spells you may want to use SPELL_EFFECT)
  • This condition check:
    JASS:
    if GetSpellAbilityId() == FFB_SPELL_ID and GetSpellTargetUnit() != GetSpellAbilityUnit() and not IsUnitType(GetSpellTargetUnit(),UNIT_TYPE_DEAD) then
    Seems a bit odd. Shouldn't you just check the targets in the object editor? E.g. targets allowed: Enemy, Alive
  • In the method create, the sound path should be a constant.
  • For future reference, GetTriggerPlayer() is the same as GetOwningPlayer(GetTriggerPlayer()) for spell casts**, at least in the condition/actions part (not in a separate timer or group call though).
  • In create, you should null snd.
  • For SetUnitScale, you can safely put in 0 for Y and Z (if i recall correctly). It isn't necessary but it might look nicer. :) Just another tip for future spells.
  • JASS:
    if not IsUnitType(.utarg,UNIT_TYPE_DEAD) then
    In general, you should add a check to make sure the unit wasn't removed:
    JASS:
    if not IsUnitType(.utarg, UNIT_TYPE_DEAD) and GetUnitTypeId(.utarg) != 0 then
  • Anything that you don't need to store over the timer's period should be local. For example, preX and preY can simply be inlined since you don't need really *need* to save those values. Once you use them for that loop iteration, you're done with them. You can either make them locals or just inline them. (this is in the method "positioning")
  • Unless I'm mistaken, I don't think you use "boolean b" in the method "positioning". You can remove it.
  • Btw, why aren't you using private for the constants? It would allow you to eliminate prefixing it with FFB and the code might look a bit neater.
  • About this:
    JASS:
    set current = this.prev
    call .destroy()
    set this = current
    Technically, when you call .destroy(), this.prev is still maintained in memory (we don't bother to assign it to 0 because it'll usually be overwritten anyway by a different spell cast). So technically, you could do:
    JASS:
    call this.destroy()
    set this = this.prev
    While this's prev and next no longer point to "this", "this" will still point to its prev and next links. For example, you have a list of members A B C:
    A <-> B <-> C
    You remove B through B.destroy():
    A <-> C; A <- B -> C;
    Note that A and C are now linked (to allow for proper looping), but B still maintains some linking to A and C. Neat, huh? :)

That's about it. Cool spell otherwise. :) I was thinking about making a similar spell while I was in math. It reminds me of arcane barrage (the way they swirl):
http://www.wowhead.com/spell=44425
 
Top