Spell Phantasmal Barrage

Flare

Stops copies me!
Reaction score
662
Finally got this b*st*rd of a spell working :D

Technical Details:
Requires NewGen WE
Made in vJASS
Fully* MUI
Leakless*
Lagless (for me, even with a significant number of instances)
System-independent

*I think

Description:
Launches a barrage of spirits towards the target point - upon contact with the ground, the spirits will release a burst of unholy energy, damaging enemy units nearby.

At longer distances, the targeting accuracy is reduced.

Deals 25/35/45 damage, damage is reduced with increased distance from epicenter

Screenshots:
Projectiles coming down from a ~2000-distance cast
pb1sn8.jpg
Projectiles at ~800 distance cast
pb2tp2.jpg

Importing:
Code:
1) Copy the PB trigger into your map
2) Copy the dummy.mdx model (if you do not already have it) into your map
3) Copy the Projectile dummy (h000) and the Launcher dummy (h001) into your map
4) Copy the Phantasmal Barrage (A000)
5) Modify the SID (Ability ID), LID (Launcher dummy ID) and PID (Projectile dummy ID) to match the rawcodes of the dummies and ability you copied
6) Add the spell to a hero
7) Enjoy!

Code:
JASS:
//Phantasmal barrage
//By Flare
//Requires: Projectile dummy (h000)
//          Launcher Dummy (h001)
//          Phantasmal Barrage ability (A000)
//          NewGen WE
//          dummy.mdx


//Thanks to:
//Shadow1500 - Parabola function
//Vexorian       - dummy.mdx
//Please let me know who the creator of the dummy.mdx model is, so I can credit them <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />

struct PB
//Configurables

//Ability rawcode
    private static constant integer SID = &#039;A000&#039;
//Launcher Dummy rawcode
    private static constant integer LID = &#039;h001&#039;
//Projectile Dummy rawcode
    private static constant integer PID = &#039;h000&#039;
//Timer interval
    private static constant real TIMER_INT = 0.03125
//Number of projectiles used
    private static constant integer PROJCOUNT = 5
//Projectile effect
    private static constant string PROJ_FX = &quot;Abilities\\Spells\\Other\\BlackArrow\\BlackArrowMissile.mdl&quot;
//Explosion effect
    private static constant string END_FX = &quot;Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl&quot;
//Number of effects spawned per ring
    private static constant integer FX_COUNT = 1
//Number of rings spawned
    private static constant integer RING_COUNT = 1
//Ring offset
    private static constant real RING_OFFSET = 0.
//Spawn offset
    private static constant real SPAWN_OFFSET = 300.
//Attachment point for projectile
    private static constant string ATTACH_PT = &quot;origin&quot;
//Low bound for projectile speed - projectile speed will be somewhere between SPEED_LOW and SPEED_HIGH
    private static constant real SPEED_LOW = 300
//High bound for projectile speed
    private static constant real SPEED_HIGH = 1000
//Projectile&#039;s target offset - base value for how far from the target point the projectile will land
    private static constant real TARGET_OFFSET = 0.
//Intensity of curve - lower values result in a &quot;sharper&quot; curve, higher values result in a bow-shaped curve
    private static constant real CURVE = 2.5
//Accurate range
    private static constant real ACC_RANGE = 1000.
//Accuracy discrepancy - higher values result in a less accurate barrage at longer distance
//Discrepancy only takes effect if the target point if more than ACC_RANGE units away from the caster
    private static constant real ACC_DISCREP_MULTI = 200.
//For every ACC_DISCREP_MULTI units beyond ACC_RANGE, the projectiles&#039; targeting will be off by up to ACC_DISCREP units
//e.g. (using current values) if you cast the spell 1400 units away
//the projectiles will land up to ((1400-1000)/200)*125 (250) units away from their original target
//NOTE: Projectile&#039;s original target is the target point offset by TARGET_OFFSET towards a random angle
    private static constant real ACC_DISCREP = 125.
//Minimum damage percentage - percentage of whole damage that is dealt at the edge of damage radius
    private static constant real MIN_DMG_PERCENT = 0.25
//Attack type, damage type, weapon type
    private static constant attacktype ATYPE = ATTACK_TYPE_MAGIC
    private static constant damagetype DTYPE = DAMAGE_TYPE_NORMAL
    private static constant weapontype WTYPE = WEAPON_TYPE_WHOKNOWS
//Animation played by the launcher dummies
    private static constant string ANIM = &quot;attack&quot;
//Lifespan of launcher dummies
    private static constant real LIFESPAN = 1.
//Determines whether the projectile dummies should be set back to normal before being killed
    private static constant boolean RESET_DUMMY = true
//Minimum projectile targeting range (thanks to Artificial for pointing out this issue)
    private static constant real MIN_RANGE = 250.
    
//RGBA and scaling values
//Launcher RGBA and scaling
    private static constant integer LR = 255
    private static constant integer LG = 255
    private static constant integer LB = 255
    private static constant integer LA = 127
    
    private static constant real LS = 1.
    
//Projectile RGBA and scaling
    private static constant integer PR = 255
    private static constant integer PG = 255
    private static constant integer PB = 255
    private static constant integer PA = 255
    
    private static constant real PS = 1.25
    
    
//Damage radius calculation
    private static method DamageRadius takes unit u returns real
        local real base = 150.
        local real m1 = I2R (GetUnitAbilityLevel (u, .SID))
        local real m2 = 50.
        return base + m1*m2
    endmethod
    
    
//Damage calculation
    private static method Damage takes unit u returns real
        local real base = 15
        local real m1 = GetUnitAbilityLevel (u, .SID)
        local real m2 = 5
        return base + m1*m2
    endmethod
    
    
//Required globals    
    private static location tempLoc = Location (0,0)
    private static group genGroup = CreateGroup ()
    private static timer spellTimer = CreateTimer ()
    private static PB array data
    private static PB gdata = 0
    private static integer N = 0
    private static real groupX = 0
    private static real groupY = 0
    private static real doublePi = bj_PI * 2
    private static boolean timerStarted = false
    
    
//Required struct members
    unit caster
    unit proj
    real speed
    real pcos
    real psin
    real curdist
    real maxdist
    effect pfx
    
    
    
//Required helper functions
    private static method RingEffect takes string fxString, real ringCount, real effectCount, real offset, real x, real y returns nothing
        local integer i1 = 1
        local integer i2 = 1
        local real angle = GetRandomReal (0, .doublePi)
        local real x2
        local real y2
        loop
        exitwhen i1 &gt; ringCount
            loop
            exitwhen i2 &gt; effectCount
                set angle = angle + .doublePi/effectCount
                set x2 = x + Cos (angle) * (i1 * offset)
                set y2 = y + Sin (angle) * (i1 * offset)
                call DestroyEffect(AddSpecialEffect (fxString, x2, y2))
                set i2 = i2 + 1
            endloop
            set i1 = i1 + 1
        endloop
    endmethod


    private static constant method JumpParabola takes real dist, real maxdist,real curve returns real
        local real t = (dist*2)/maxdist-1
        return (-t*t+1)*(maxdist/curve)
    endmethod
    
    
    private static method SetUnitXY takes unit u,real x,real y returns nothing
        if x&lt;GetRectMaxX(bj_mapInitialPlayableArea) and x&gt;GetRectMinX(bj_mapInitialPlayableArea) and y&lt;GetRectMaxY(bj_mapInitialPlayableArea) and y&gt;GetRectMinY(bj_mapInitialPlayableArea) then
            call SetUnitX(u,x)
            call SetUnitY(u,y)
        endif
    endmethod
    
    
    private static method GetCoordZ takes real x, real y returns real
        call MoveLocation (.tempLoc, x, y)
        return GetLocationZ (.tempLoc)
    endmethod

    
    private static method SetUnitZ takes unit u, real h returns boolean
        local real z = h - .GetCoordZ (GetUnitX (u), GetUnitY (u))
        if z &gt; 0 then
            call SetUnitFlyHeight (u, z, 0)
            return false
        else
            return true
        endif
    endmethod
    
    private static method GetUnitZ takes unit u returns real
        return .GetCoordZ (GetUnitX (u), GetUnitY (u)) + GetUnitFlyHeight (u)
    endmethod
    
    
    private static method GetRandOffsetX takes real x, real dist returns real
        local real discrepdist = ((dist - .ACC_RANGE)/.ACC_DISCREP_MULTI) * .ACC_DISCREP
        local real angle = Cos (GetRandomReal (0, .doublePi))
        return x + angle * discrepdist
    endmethod
    
    
    private static method GetRandOffsetY takes real y, real dist returns real
        local real discrepdist = ((dist - .ACC_RANGE)/.ACC_DISCREP_MULTI) * .ACC_DISCREP
        local real angle = Sin (GetRandomReal (0, .doublePi))
        return y + angle * discrepdist
    endmethod
    
    
//Spell functions
    private static method GroupFunc takes nothing returns boolean
        local PB a = .gdata
        local unit u = GetFilterUnit ()
        local real ux = GetUnitX (u)
        local real uy = GetUnitY (u)
        local real x = .groupX - ux
        local real y = .groupY - uy
        local real dist = SquareRoot (x*x + y*y)
        local real percent = (.DamageRadius (a.caster) - dist)/.DamageRadius (a.caster)
        local real dmg
        if percent &lt; .MIN_DMG_PERCENT then
            set percent = .MIN_DMG_PERCENT
        endif
        set dmg = .Damage (a.caster) * percent
        if IsUnitEnemy (u, GetOwningPlayer (a.caster)) then
            call UnitDamageTarget (a.caster, u, dmg, false, true, .ATYPE, .DTYPE, .WTYPE)
        endif
        set u = null
        return false
    endmethod

    
    private static method Move takes nothing returns nothing
        local integer i = 1
        local integer i2 = 0
        local real fxAngle = 0
        local real x = 0
        local real y = 0
        local real x2 = 0
        local real y2 = 0
        local real z = 0
        local real uz = 0
        local real ex = 0
        local real ey = 0
        local boolean b = false
        local real hDist = 0
        local real zDif = 0
        local integer animindex = 0
        local PB a
        loop
        exitwhen i &gt; .N
            set a = .data<i>
            set x = GetUnitX (a.proj)
            set y = GetUnitY (a.proj)
            set x2 = x + a.pcos*a.speed
            set y2 = y + a.psin*a.speed
            set z = .JumpParabola (a.curdist, a.maxdist, .CURVE)
            set uz = .GetUnitZ (a.proj)
            set hDist = SquareRoot ((x2-x)*(x2-x) + (y2-y)*(y2-y))
            set zDif = z - uz
            set animindex = R2I (Atan2 (zDif, hDist) * bj_RADTODEG + 90)
            
            if animindex &gt;= 0 and animindex &lt;= 180 then
                call SetUnitAnimationByIndex (a.proj, animindex)
            endif
            
            call .SetUnitXY (a.proj, x2, y2)
            set b = .SetUnitZ (a.proj, z)
            
            if b and a.curdist &gt; 0 then
                if .RESET_DUMMY then
                    call SetUnitAnimationByIndex (a.proj, 0)
                endif
                call DestroyEffect (a.pfx)
                set a.curdist = 0
                call KillUnit (a.proj)
                set .gdata = a
                call GroupEnumUnitsInRange (.genGroup, x, y, .DamageRadius (a.caster), Condition (function PB.GroupFunc))
                call .RingEffect (.END_FX, .RING_COUNT, .FX_COUNT, .RING_OFFSET, x, y)
                call a.destroy ()
                set .data<i> = .data[.N]
                set .N = .N - 1
                set i = i - 1
                
                if .N == 0 and .timerStarted then
                    call PauseTimer (.spellTimer)
                    set .timerStarted = false
                endif
            endif
            
            set a.curdist = a.curdist + a.speed
            set i = i + 1
        endloop
    endmethod

    
    private static method SpellCond takes nothing returns boolean
        return GetSpellAbilityId () == .SID
    endmethod
    
    
    private static method SpellActions takes nothing returns nothing
        local PB a
        local integer i = 0
        local unit u
        local unit c = GetTriggerUnit ()
        local real cx = GetUnitX (c)
        local real cy = GetUnitY (c)
        local real sx
        local real sy
        local location l = GetSpellTargetLoc ()
        local real tx = GetLocationX (l)
        local real ty = GetLocationY (l)
        local real x = tx - cx
        local real y = ty - cy
        local real rx
        local real ry
        local real angle = Atan2 (y, x)
        local real dist = SquareRoot (x*x + y*y)
        set rx = cx + Cos (angle) * dist
        set ry = cy + Sin (angle) * dist
        call RemoveLocation (l)
        set l = null
        
        if .timerStarted == false then
            call TimerStart (.spellTimer, .TIMER_INT, true, function PB.Move)
        endif
        
        loop
        exitwhen i == .PROJCOUNT
           if dist &lt; .MIN_RANGE then
                set angle = GetRandomReal (0, .doublePi)
           endif
           set a = PB.create ()
           set a.caster = c
           set sx = cx + Cos (GetRandomReal (0, .doublePi)) * GetRandomReal (0, .SPAWN_OFFSET)
           set sy = cy + Sin (GetRandomReal (0, .doublePi)) * GetRandomReal (0, .SPAWN_OFFSET)
           set u = CreateUnit (GetOwningPlayer (a.caster), .LID, sx, sy, angle * bj_RADTODEG)
           call SetUnitVertexColor (u, .LR, .LG, .LB, .LA)
           call SetUnitScale (u, .LS, .LS, .LS)
           call SetUnitAnimation (u, .ANIM)
           call QueueUnitAnimation (u, &quot;stand&quot;)
           call UnitApplyTimedLife (u, &#039;BTLF&#039;, .LIFESPAN)
           if dist &lt;= .ACC_RANGE then
                set rx = rx + Cos (GetRandomReal (0, .doublePi)) * GetRandomReal (0, .TARGET_OFFSET)
                set ry = ry + Sin(GetRandomReal (0, .doublePi)) * GetRandomReal (0, .TARGET_OFFSET)
           else
                set rx = .GetRandOffsetX (rx, dist)
                set ry = .GetRandOffsetY (ry, dist)
           endif
           set x = rx-cx
           set y = ry-cy
           set angle = Atan2 (y, x)
           set a.pcos = Cos (angle)
           set a.psin = Sin (angle)
           set a.maxdist = SquareRoot (x*x + y*y)
           if a.maxdist &lt; .MIN_RANGE then
                set angle = GetUnitFacing (u) * bj_DEGTORAD
                set a.maxdist = a.maxdist + .MIN_RANGE
                set a.pcos = Cos (angle)
                set a.psin = Sin (angle)
           endif
           set a.proj = CreateUnit (GetOwningPlayer (a.caster), .PID, sx, sy, angle * bj_RADTODEG)
           call SetUnitVertexColor (a.proj, .PR, .PG, .PB, .PA)
           call UnitAddAbility (a.proj, &#039;Arav&#039;)
           call UnitRemoveAbility (a.proj, &#039;Arav&#039;)
           set a.curdist = 0
           set a.speed = GetRandomReal (.SPEED_LOW, .SPEED_HIGH) * .TIMER_INT
           set a.pfx = AddSpecialEffectTarget (.PROJ_FX, a.proj, .ATTACH_PT)
           set i = i + 1
           set .N = .N + 1
           set .data[.N] = a
        endloop
        
        set c = null
        set u = null
    endmethod
    
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger ()
        call TriggerRegisterAnyUnitEventBJ (t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition (t, Condition (function PB.SpellCond))
        call TriggerAddAction (t, function PB.SpellActions)
    endmethod
endstruct</i></i>


Thanks to:
Shadow1500 - Parabola function
Artificial - helping me figure out what I was doing wrong, then commencing to break stuff :p
Vexorian - dummy.mdx [del](can someone tell me who the author of dummy.mdx is so I can properly credit them)[/del]
Gwypaas - giving me the idea to enclose everything within a struct :D

Credits:
If you use this spell, please credit me for making it because it took quite some time to complete :p
 

Attachments

  • Phantasmal Barrage R4.w3x
    34.3 KB · Views: 527

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Deals 25/35/45 damage, damage is reduced with increased distane from epicenter

Typo.

Nice spell, now if you could actually do some good and start making spells for Twilight Crusade that would be great.
 

Vestras

Retired
Reaction score
248
How the hax do you come up with such names? Argh...

Offtopic: are you guys still working on Twilight Crusade? If so, I got a hero waiting for ya.
 

Flare

Stops copies me!
Reaction score
662
How the hax do you come up with such names? Argh...
Well, I don't really know - just sounded cool :)

Offtopic: are you guys still working on Twilight Crusade? If so, I got a hero waiting for ya.
I don't know about Larcenist/TheDamien/WastedSavior, but at the moment, I'm not (due to lack of ideas of stuff I could do :() - might help out again (if Larcenist doesn't turn me away :p) if I think of some ideas

Oh ye, be sure to spam the spell (if your computer can handle it) at maximum range - personally, I think it's a much cooler way to demonstrate the spell if you have a massive AoE bombardment :D As you can see from the first screenshot, there's a pretty big gap between target points and some of the explosions :D
 

Artificial

Without Intelligence
Reaction score
326
Nice spell. :)
It can also be used to create a neat dance floor:
2qiv4zq.jpg

He's probably dancing to the beat of Ashlee Simpson - Dancing Alone. :(
 

Draphoelix

It's not the wintercold that's killing me
Reaction score
132
Nice spell.

Dunno if this is a bug, but the missile thingy got stuck there.

bug1ai4.png


(I thought you was going to stop making spells :p)
 

Flare

Stops copies me!
Reaction score
662
Nice spell. :)
It can also be used to create a neat dance floor:
2qiv4zq.jpg

He's probably dancing to the beat of Ashlee Simpson - Dancing Alone. :(
Fixed that problem (I think) - there's now a minimum range threshold, so if you cast below 100 range (default), the spell will fire projectiles at random around the hero

Nice spell.

Dunno if this is a bug, but the missile thingy got stuck there.

bug1ai4.png


(I thought you was going to stop making spells :p)
1) Please refrain from breaking my stuff in future, the extra work is alot of hassle :D

2) Circumstances? Rough guess at the distance, where did you target, were you spamming, did it happen in first/last few casts, or was it mixed in with the rest? I can't seem to repeat that >_< Maybe it was unknowingly fixed in R2

And I did, but then I made that Force of Nature spell, then I needed something else to do :p

Also:
R3 is up, fixed an issue where the projectiles would go straight down on elevated terrain
 

emjlr3

Change can be a good thing
Reaction score
395
dummy.mdx is Vexs
 

Draphoelix

It's not the wintercold that's killing me
Reaction score
132
I were spamming, and I targeted on the cliff.

I tried to repeat that and spammed, but it was harder. but one projectile got stuck in the air when I spammed.
 

GoGo-Boy

You can change this now in User CP
Reaction score
40
Looks interesting. I can't start that map as well though. :-(
 

Forty

New Member
Reaction score
6
you dont need the private keyword for structmembers it is just more text. but you should make the struct private, what if someone has a struct called PB? 90% of my structs are called PB
 

Flare

Stops copies me!
Reaction score
662
you dont need the private keyword for structmembers
I don't want people f*cking around with the struct members when they shouldn't be (even though I forgot to make the dynamic members private I think)

but you should make the struct private
That means putting it in a scope and, to be honest, that's not needed - since all the members are private, all they can do is .create () and you aren't doing yourself much good by doing that

If you are so dead-set against it, there's nothing stopping you doing it yourself

90% of my structs are called PB
And you've just shown that I don't need to make the struct private - if 90% of your structs are called PB, they are obviously public or private members, so my spell's struct doesn't need to be made private since there isn't any conflict (due to your structs being private)
 

Weep

Godspeed to the sound of the pounding
Reaction score
401
The demo map won't open in WC3 because it has not been compiled with JassHelper (although it compiles fine and works after that).
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      • Ghan
        Administrator - Servers are fun

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top