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: 446
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.
 
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
 
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 ? :)
 
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).
 
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).
 
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.
  • Varine Varine:
    I would be there for days, even with my camera set up slides can take a long time, and if they want perfect captures I really need to use my scanners that are professionally made for that. My camera rig works well for what it is, but for enlargements and things it's not as good.
  • Varine Varine:
    I've only had a couple clients with that so far, though. I don't have a website or anything yet though.
  • Varine Varine:
    Console repair can be worthwhile, but it's also not a thing I can do at scale in my house. I just don't have room for the equipment. I need an office that I can segregate out for archival and then electronic restoration.
  • Varine Varine:
    But in order for that to be real, I need more time, and for more time I need to work less, and to work less I need a different job, and for a different job I need more money to fall back on so that I can make enough to just pay like, rent and utilities and use my savings to find these projects
    +1
  • Varine Varine:
    Another couple years. I just need to take it slow and it'll get there.
  • jonas jonas:
    any chance to get that stolen money back?
  • jonas jonas:
    Maybe you can do console repair just as a side thing, especially if there's so much competition business might be slow. Or do you need a lot of special equipment for that?
  • jonas jonas:
    I recently bought a used sauna and the preowner told me some component is broken, I took a look and it was just a burnt fuse, really cheap to fix. I was real proud of my self since I usually have two left hands for this kinda stuff :p
  • tom_mai78101 tom_mai78101:
    I am still playing Shapez 2. What an awful thing to happen, Varine, and hopefully everything has been sorted out soon. Always use multi-factor authentication whenever you have the opportunity to do so.
    +1
  • Varine Varine:
    I think all of the money is accounted for now, and all the cards have been changed out, so I think for the most part it's taken care of now. Just need to go through and make sure all of my accounts are secured again, it's just time consuming.
  • Varine Varine:
    And yeah everything has 2 factor turned on now, or at least everything I can think of at the moment.
  • Varine Varine:
    The consoles don't need too much equipment that I don't already have. I would like to get a reflow oven, but I don't really want to buy one so I'm thinking about modifying a toaster oven I have to make something that will work for what I'm doing.
  • Varine Varine:
    I have the soldering irons and reflow and all that, but without an oven it's kind of hard to build mod chips and things like that. I made a handful of them with a hot air station, but it's a pain.
  • Varine Varine:
    The only thing I'm not really set up for is BGA rework. I've done it before a little bit, but not reliably, and that equipment is wildly expensive. You need X-rays and shit.
  • Varine Varine:
    I also have a couple 3D printers. I'm not super good with those and need to get an enclosure built, but they'll be useful for some aesthetic mods I've been thinking about. At least I can use them to do designs and then just have someone else print out the parts for me once I know they work.
  • Varine Varine:
    I also use them to make adapters for all my camera shit, but that's also an on the side thing for now. Lens adapters get really expensive.
  • Varine Varine:
    I've been trying to do some little art pieces as well, but I'm not much an engineer so they haven't gone great. I got some new things showing up to try and play with
  • Varine Varine:
    I want to build this tesserect kind of thing with mirrors, and I've been trying to make this like black hole diorama. In my head it looks really cool, but I kind of thought I could form polarizing lenses into a sphere but I tend to just destroy them every time I try.
  • Varine Varine:
    So I got a new idea, but I'm not sure how to make it work like I want without being able to get a polarizer curved. I think they are made out of PVA typically, and I thought I could just heat it up a little bit to soften the film, but that clearly isn't working. So I'm going to try a few other things, I'm thinking if I put a mirror film over the polarizing film I might get something cool. I have some polarized LED's as well, and I think if I make a central light source I can use the mirrors combined with the polarizers to make that central light APPEAR black. I have next week off so I'm going to spend some time trying to figure it out
  • Varine Varine:
    The tesserect works, at least. I just need to figure out how to be able to assemble it, but I think I have a pretty good idea of how to go about it. Or at least a prototype of it. I'll post some pictures next week
  • jonas jonas:
    That last bit sounds like the last entry in a scientist's journal in a destroyed research facility in a post-apocalyptic video game
  • Varine Varine:
    lol it's not that exciting
  • Varine Varine:
    Shiny tho
  • Varine Varine:
    Basically it's a cube with a two way mirror on the inside, and then a smaller cube suspended in that with a mirrors on the outside of it. Kind of like those infinity pictures where they use two mirrors to go forever. Only it's twelve mirrors
  • Varine Varine:
    And the tiniest LED strip I could find

      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