Spell PowerBlitz

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
Power Blitz

BTNSacrificialSkull.gif


Created by Darthfett, Concept by ReVolver
As seen in Undead Outbreak by ReVolver

Death Spell
AoE

Screenshot:
powerblitzsmallmy4.jpg


Description:
While channeled, the caster will draw the souls of all nearby corpses to him. Then, once their energy has been channeled to him, he releases them upon his enemies in explosive fury.
Deals 10*(Number of Corpses) to all enemy units in the targeted area. Steals the souls of all units in a 300, 400, 500 etc range, and damages all units in a 300, 400, 500, etc range of the target point.

-MPI
-MUI
-Leak-less
-Lag-less

-Created in vJASS


Requires:
PUI
KT2
Dummy unit - with model "Abilities\Weapons\ZigguratMissile\ZigguratMissile.mdl" (Included in Demo Map)
AoE or Point Targeting ability (Included in Demo Map)

JASS:
scope PowerBlitz initializer Init

//==================================================||
//                                                  ||
//           Spell Created by Darthfett             ||
//               Concept by ReVolver                ||
//                                                  ||
//                 Uses PUI and KT2                 ||
//                                                  ||
//             Easy to end spell, use:              ||
//                                                  ||
//    call PowerBlitz_Data[UNIT].release()          ||
//      - to finish a spell abruptly                ||
//                                                  ||
//==================================================||
//You can take out the above if you like. <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />

globals
    private constant integer AID_PowerBlitz         = &#039;A001&#039;
    //The ability which starts the spell
    
    private constant string CHANNEL                 = &quot;shockwave&quot;
    //The order string, so that it can stop the corpses if the hero stops channeling.
    
    private constant integer MAX_CORPSES            = 10
    //The maximum number of corpses that can have their souls stolen.
    
    private constant real TIMEOUT                   = 0.03125 
    //The timer timeout.  (FPS is ~32)
    
    private constant integer UID_DUMMY              = &#039;visi&#039; 
    //The &quot;souls/ghosts&quot; that are being slid as dummy units
    
    private constant real OFFSET                    = 10
    //How far to slide each tick
    
    private constant integer GHOST_TICK_COUNT_DELAY = 10
    //Number of timer ticks in between each corpse spirit spaw
    
    private constant sound SND_Start                = CreateSound(&quot;Abilities\\Spells\\Demon\\SoulPreservation\\SoulPreservation.wav&quot;,false,true,false,10,10,&quot;&quot;)
    //Sound that plays during at start.  Change the string to whatever you need it to be.
    
    private constant sound SND_Damage               = CreateSound(&quot;Abilities\\Spells\\Other\\Drain\\LifeDrain.wav&quot;,false,true,false,10,10,&quot;&quot;)
    //Sound that plays when damage starts.  Change the string to whatever you need it to be.

    private constant attacktype ATK_TYPE            = ATTACK_TYPE_CHAOS
    //The attack type the unit uses to damage enemies
    
    private constant weapontype WPN_TYPE            = WEAPON_TYPE_WHOKNOWS
    //The weapon type the unit uses to damage enemies
    
    private constant damagetype DMG_TYPE            = DAMAGE_TYPE_UNIVERSAL
    //The damage type the unit uses to damage enemies
endglobals

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == AID_PowerBlitz
endfunction

private constant function DamageAmount takes real lvl, real count returns real
    return 10 * count //Count is the number of corpses.  
    //This is the damage to all units in the area over all the damage time.
endfunction

private constant function CorpseRange takes integer lvl returns integer
    return 200 + (100 * lvl) //Range from which corpses can be picked.
endfunction

private constant function DamageRange takes integer lvl returns integer
    return 200 + (100 * lvl) //Range from which / damage is dealt / units slide to from target point
endfunction

//===================DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING====================\\

globals
    private player tempown
    private integer gbl_groupCount
    private group tempgrp = CreateGroup()
endglobals

//What happens:

//The Caster picks a corpse every GHOST_TICK_COUNT_DELAY * TIMEOUT seconds and starts sliding it toward the caster.
//One there are either:
//   1. MAX_CORPSES corpses slid to the caster
//       OR
//   2. No Corpses left in range
//It will wait 2 seconds and then the dummies will start sliding outward from the targetted point.
//The DAMAGE_PER_CORPSES is dealt over the time that the units slide.

//Features:
//    Corpses will stop being picked when unit ends spell or dies, but spell will continue.
//    Spirits from corpses are created at intervals, and slid to the caster
//    Leakless, Lagless, MUI, MPI, etc
//    Sound! <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite7" alt=":p" title="Stick Out Tongue    :p" loading="lazy" data-shortname=":p" />

private function Remove takes nothing returns nothing
    call KillUnit(GetEnumUnit())
endfunction

private function FiltFunc takes nothing returns boolean
    if GetWidgetLife(GetFilterUnit()) &lt; .405 and IsUnitType(GetFilterUnit(),UNIT_TYPE_MECHANICAL) == false then
        set bj_groupCountUnits = bj_groupCountUnits + 1
        return true
    endif
    return false
endfunction

private function FiltFuncDamage takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) &gt;= .405 and IsUnitType(GetFilterUnit(),UNIT_TYPE_MECHANICAL) == false and IsUnitEnemy(GetFilterUnit(),tempown)
endfunction

private function GroupCountEnum takes nothing returns nothing
    set gbl_groupCount = gbl_groupCount + 1
endfunction

private function GroupCount takes group g returns integer //Non-BJ version of CountUnitsInGroup.
    set gbl_groupCount = 0
    call ForGroup(g,function GroupCountEnum)
    return gbl_groupCount
endfunction

public struct Data
    //! runtextmacro PUI()
    unit c
    player p
    group sliders
    group slid2
    integer i
    real cx
    real cy
    real tx
    real ty
    real angle
    integer lvl
    integer ticks
    integer units
    
    static method create takes nothing returns Data
        local Data d = Data.allocate()
        set d.ticks = 0
        set d.i = 0
        set d.units = 0
        set d.sliders = CreateGroup()
        set d.slid2 = CreateGroup()
        return d
    endmethod
        
    method onDestroy takes nothing returns nothing
//All the actual cleanup is done in .release/.destroy methods, 
//so that it is possible to stop a spell whenever.
//This makes the spell very flexible
        call ForGroup(this.slid2,function Remove)
        call DestroyGroup(this.sliders)
        call DestroyGroup(this.slid2)
        set this.sliders = null
        set this.slid2 = null
        set this.c = null
    endmethod
endstruct


private function Callback_Damage takes nothing returns boolean
    local Data d = KT_GetData()
    local unit fog
    local real x
    local real y
    local real a
    local real newx
    local real newy
    local integer i = 0
    set tempown = d.p
    call GroupEnumUnitsInRange(tempgrp,d.tx,d.ty,DamageRange(d.lvl),Filter(function FiltFuncDamage))
    
    set bj_groupCountUnits = 0
    call ForGroup(d.slid2,function CountUnitsInGroupEnum)
    
    loop //Damage targets
        set fog = FirstOfGroup(tempgrp)
        exitwhen fog == null
        call GroupRemoveUnit(tempgrp,fog)
        call UnitDamageTarget(d.c,fog,(DamageAmount(d.lvl,bj_groupCountUnits) / (DamageRange(d.lvl) / OFFSET)) ,false,false,ATK_TYPE,DMG_TYPE,WPN_TYPE)
    endloop
    if d.ticks * OFFSET &lt; DamageRange(d.lvl) then
        set bj_groupAddGroupDest = tempgrp
        call ForGroup(d.slid2,function GroupAddGroupEnum)
        loop
            set fog = FirstOfGroup(tempgrp)
            exitwhen fog == null
            call GroupRemoveUnit(tempgrp,fog)
            set a = bj_DEGTORAD * ((360 / bj_groupCountUnits) * i)
            set newx = d.tx + (OFFSET * d.ticks ) * Cos(a)
            set newy = d.ty + (OFFSET * d.ticks ) * Sin(a)
            call SetUnitX(fog,newx)
            call SetUnitY(fog,newy)
            set i = i + 1
        endloop
    else
        call d.release()
        return true
    endif
    set d.ticks = d.ticks + 1
    return false
endfunction

private function DamageWait takes nothing returns boolean
    local Data d = KT_GetData()
    call AttachSoundToUnit(SND_Damage,d.c)
    call SetSoundVolume(SND_Damage,127)
    call StartSound(SND_Damage)
    call KT_Add(function Callback_Damage,d,TIMEOUT)
    return true
endfunction

private function Slide2Caster_Callback takes nothing returns boolean
    local Data d = KT_GetData()
    local unit fog
    local unit newdummy
    local real x
    local real y
    local real a
    local real newx
    local real newy
    set bj_groupCountUnits = 0
    call GroupEnumUnitsInRange(tempgrp,d.cx,d.cy,CorpseRange(d.lvl),Filter(function FiltFunc))
    
    if GetWidgetLife(d.c) &lt; .405 or GetUnitCurrentOrder(d.c) != OrderId(CHANNEL) then
        set bj_groupCountUnits = 0
    endif
    
    set d.ticks = d.ticks + 1
    if d.units * GHOST_TICK_COUNT_DELAY &lt;= d.ticks and bj_groupCountUnits &gt; 0 and d.units &lt; MAX_CORPSES then //Every GHOST_TICK_COUNT_DELAY ticks it will start a new slider
        set fog = FirstOfGroup(tempgrp)
        set x = GetUnitX(fog)
        set y = GetUnitY(fog)
        set a = bj_RADTODEG * Atan2((d.cy - y), (d.cx - x)) //Angle between dummy and caster
        set newdummy = CreateUnit(d.p,UID_DUMMY,x,y,a)
        call GroupAddUnit(d.sliders,newdummy)
        call SetUnitPathing(newdummy,false)
        call RemoveUnit(fog)
        set d.units = d.units + 1
    endif
    set bj_groupAddGroupDest = tempgrp
    call ForGroup(d.sliders,function GroupAddGroupEnum)
    loop
        set fog = FirstOfGroup(tempgrp)
        exitwhen fog == null
        call GroupRemoveUnit(tempgrp,fog)
        set x = GetUnitX(fog)
        set y = GetUnitY(fog)
        set a = Atan2((d.cy - y), (d.cx - x))
        set newx = x + OFFSET * Cos(a)
        set newy = y + OFFSET * Sin(a)
        call SetUnitX(fog,newx)
        call SetUnitY(fog,newy)
        if SquareRoot((newx - d.cx)*(newx - d.cx) + (newy - d.cy)*(newy - d.cy)) &lt;= 15 then //Distance Formula
            call GroupRemoveUnit(d.sliders,fog)
            call GroupAddUnit(d.slid2,fog)
        endif
    endloop
    if (d.units &gt;= MAX_CORPSES or bj_groupCountUnits == 0) and GroupCount(d.sliders) == 0 then
        call KT_Add(function DamageWait,d,2)
        set d.ticks = 0
        return true
    endif
    set newdummy = null
    return false
endfunction

private function Actions takes nothing returns nothing
    local Data d = Data[GetTriggerUnit()]
    local location temp = GetSpellTargetLoc()
    if d == 0 then
        set d = Data.create()
        set Data[GetTriggerUnit()] = d
    endif
    
    //Set data
    set d.c = GetTriggerUnit()
    set d.p = GetOwningPlayer(d.c)
    set d.cx = GetUnitX(d.c)
    set d.cy = GetUnitY(d.c)
    set d.tx = GetLocationX(temp)
    set d.ty = GetLocationY(temp)
    call RemoveLocation(temp)
    set temp = null
    set d.lvl = GetUnitAbilityLevel(d.c,AID_PowerBlitz)
    call AttachSoundToUnit(SND_Start,d.c)
    call SetSoundVolume(SND_Start,127)
    call StartSound(SND_Start)
    
    call KT_Add(function Slide2Caster_Callback,d,TIMEOUT)
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Conditions))
    call TriggerAddAction(t,function Actions)
endfunction

endscope


Attached map has the spell, dummy unit, and ability.

===========
The screenshot is not really all that great. You have to see it in action to actually see the spell.
 

Attachments

  • PowerBlitz.w3x
    56.6 KB · Views: 412

Flare

Stops copies me!
Reaction score
662

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
JASS:
    call KillUnit(GetEnumUnit())
    call UnitApplyTimedLife(GetEnumUnit(),&#039;BTLF&#039;,1)

:confused: Ahm... I fail to see the point in that - care to enlighten me? :p

Aha, sorry. :eek: Fix'd. I think I was originally thinking to play the death animation and then the unit would be removed after one second.


Configurable attack/damage/weapon types would be nice (someone may not want to do Chaos damage)

Added them in. I originally made the spell to make it easy for ReVolver to customize, so I left a few things out. ;)

EDIT:

Updated the post with a better screenshot.
 

Romek

Super Moderator
Reaction score
963
JASS:
private function GroupCountEnum takes nothing returns nothing
    set gbl_groupCount = gbl_groupCount + 1
endfunction

private function GroupCount takes group g returns integer //Non-BJ version of CountUnitsInGroup.
    set gbl_groupCount = 0
    call ForGroup(g,function GroupCountEnum)
    return gbl_groupCount
endfunction

There really is no point in recreating the BJ like that. You might as well use the BJ and have the same effect, but less code. :D

Also, maybe you could make 'valid' a global group.
It's only used for an instance, and this could make the code slightly more efficient.

Just set it to CreateGroup() once, and the use GroupClear(). And never destroy it :D
 

Tom_Kazansky

--- wraith it ! ---
Reaction score
157
cool ! +rep :D
If I use this spell in my map, I should credit for Darthfett or ReVolver ? or both ( I think both)

a little off topic: Is a "Grave Keeper" hero suitable for this spell ? :)
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
JASS:
private function GroupCountEnum takes nothing returns nothing
    set gbl_groupCount = gbl_groupCount + 1
endfunction

private function GroupCount takes group g returns integer //Non-BJ version of CountUnitsInGroup.
    set gbl_groupCount = 0
    call ForGroup(g,function GroupCountEnum)
    return gbl_groupCount
endfunction

There really is no point in recreating the BJ like that. You might as well use the BJ and have the same effect, but less code. :D

The BJ has a little bit of fluff:

JASS:
function CountUnitsInGroup takes group g returns integer
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_groupCountUnits = 0
    call ForGroup(g, function CountUnitsInGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(g)
    endif
    return bj_groupCountUnits
endfunction


No need to create the local boolean, no need to check if the group needs to be destroyed.

I did use the original in another section, just so I could inline a function.

Also, maybe you could make 'valid' a global group.
It's only used for an instance, and this could make the code slightly more efficient.

Just set it to CreateGroup() once, and the use GroupClear(). And never destroy it :D

Good idea. I'll update it when I get a chance.

cool ! +rep :D
If I use this spell in my map, I should credit for Darthfett or ReVolver ? or both ( I think both)

a little off topic: Is a "Grave Keeper" hero suitable for this spell ? :)

Well he asked me not to credit him, but I decided to give him credit for the concept anyways. I basically coded what he asked me to. I say credit us both, but he might say just to credit me. Credit us both. :cool:

Grave Keeper sounds like the theme matches (Death, souls, corpses).
 

Romek

Super Moderator
Reaction score
963
There's no need to do this: (In 2 places)
JASS:
call GroupClear(tempgrp)


The group is being cleared every time it's used anyway (FOG Loops).
 

Romek

Super Moderator
Reaction score
963
I don't see any other problems from a quick glance.
Approved

A well made, original spell. :thup:
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top