Spell Damage Accumulator

AceHart

Your Friendly Neighborhood Admin
Reaction score
1,497
What is it?

Damage Accumulator is an area targeted spell that deals the total damage the Hero has been hit with since the last cast to all enemy units in that area.
Up to some maximum total.

I.e. the longer you wait, the stronger it will be.
If you wait too long, the additional damage is lost.
And the more units you hit at once, the less effective it will be.


Looks:

You see the damage in the usual floating text that fades up and away way.
Additionally, some cloud thingy appears over the target units and casts lightning on them.
It looks better than it sounds.


JASS:
library FT
globals
    private constant real vx = 0.071 * 0.5 * Cos(45.0 * bj_DEGTORAD)
    private constant real vy = 0.071 * 0.5 * Sin(45.0 * bj_DEGTORAD)
endglobals
// Floating text is a complete pain in JASS
// Where, for example, GUI uses 10 as default text size, JASS comes with... 0.023?
// Makes all the sense really
// Hence this function
function FloatingTextXY takes string s,real x, real y returns nothing
    set bj_lastCreatedTextTag = CreateTextTag() // New floating text
    call SetTextTagText(bj_lastCreatedTextTag, s, 0.023) // Set the message and the size
    call SetTextTagPos(bj_lastCreatedTextTag, x + GetRandomReal(0, 50.0), y + GetRandomReal(0, 50.0), 0) // Slightly offset from the point
    call SetTextTagColor(bj_lastCreatedTextTag, 255, 0, 0, 255) // Red, no transparency
    call SetTextTagVelocity(bj_lastCreatedTextTag, vx, vy) // The speed it moves with. A direction actually.
    call SetTextTagFadepoint(bj_lastCreatedTextTag, 2.0) // Start fading
    call SetTextTagLifespan(bj_lastCreatedTextTag, 3.0) // Show text this long
    call SetTextTagPermanent(bj_lastCreatedTextTag, false) // Have it remove itself
endfunction
endlibrary


scope DA initializer Init
globals
    // Configuration options
    private constant integer SPELL_ID = 'A000' // Damage Accumulator
    private constant real SPELL_RANGE = 450.0 // Radius of area of effect
    private constant real MAX_DAMAGE = 500.0 // Maximum total damage you can accumulate
    private constant integer DUMMY = 'n000'
    private constant integer DUMMY_SPELL =  'A001' // Single-target, 1 damage Chain Lightning
    private constant string DUMMY_ORDER = "chainlightning" // Order to use to cast it
    private constant string DUMMY_EFFECT = "Abilities\\Spells\\Other\\Doom\\DoomTarget.mdl" // Alternatively, some effect to show
    // Leave as is
    private trigger dt = CreateTrigger() // Trigger for "A unit takes damage"
    private group dg = CreateGroup() // Used to make sure every unit is only ever added once to the above trigger
    private real array damage // To keep track of the damage so far
    private group g = CreateGroup() // Used whenever we need some units
    private real c = 0 // Used to count the number of units we're going to hit
endglobals

private function H2I takes handle h returns integer // Converts a handle to a usable number
    return h
    return 0
endfunction

private function AddCaster takes unit u returns boolean
    // If it has the ability and we never seen it before
    if GetUnitAbilityLevel(u, SPELL_ID) > 0 and IsUnitInGroup(u, dg) == false then
        call GroupAddUnit(dg, u) // Remember unit to prevent duplicate events
        set damage[H2I(u) - 0x100000] = 0.0 // Start its damage count at 0
        call TriggerRegisterUnitEvent(dt, u, EVENT_UNIT_DAMAGED) // add the "This unit takes damage" event
    endif
    return false
endfunction
private function IsStartingCaster takes nothing returns boolean // Called from "elapsed time is 0.0", to register pre-placed units, if any
    return AddCaster(GetFilterUnit())
endfunction
private function IsNewCaster takes nothing returns boolean // Called from "a unit enters map", in case the unit is created or bought or ... later
    return AddCaster(GetTriggerUnit())
endfunction

// Get all units in the map and register events if they have the spell
private function StartingUnits takes nothing returns nothing
    call GroupClear(g)
    call GroupEnumUnitsInRect(g, GetWorldBounds(), Condition(function IsStartingCaster))
endfunction

// Track the total damage
private function Accumulate takes nothing returns nothing
    local integer i = H2I(GetTriggerUnit()) - 0x100000 // Poor man's attachment system, but try to beat this for speed
    set damage<i> = damage<i> + GetEventDamage()
    if damage<i> &gt; MAX_DAMAGE then // only up to the max allowed
        set damage<i> = MAX_DAMAGE
    endif
endfunction

private function GetTargetCount takes nothing returns boolean
    // Enemy, still moving, no structure
    if IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit())) and GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) &gt; 0.5 and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false then
        set c = c + 1.0 // Count them as we find them
        return true
    endif
    return false // Otherwise ignore it
endfunction

private function HasTarget takes nothing returns nothing
    local location l = GetSpellTargetLoc()

    call GroupClear(g)
    set c = 0.0
    call GroupEnumUnitsInRangeOfLoc(g, l, SPELL_RANGE, Condition(function GetTargetCount))
    call RemoveLocation(l)
    set l = null
    // We cancel the cast if there aren&#039;t any enemies nearby or if we do not have any damage to deal back
    if c &lt; 1.0 then
        call DisplayTextToPlayer(GetOwningPlayer(GetTriggerUnit()), 0, 0, &quot;|cffffcc00No units in range...|r&quot;)
        call IssueImmediateOrder(GetTriggerUnit(), &quot;stop&quot;)
    elseif damage[H2I(GetTriggerUnit()) - 0x100000] &lt; c then
        call DisplayTextToPlayer(GetOwningPlayer(GetTriggerUnit()), 0, 0, &quot;|cffffcc00Not enough damage accumulated yet...|r&quot;)
        call IssueImmediateOrder(GetTriggerUnit(), &quot;stop&quot;)
    endif
endfunction

// Every spell needs this
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

// The interesting part
private function Actions takes nothing returns nothing
    local unit u = GetTriggerUnit() // Who casted
    local location l = GetSpellTargetLoc() // Where
    local player p = GetOwningPlayer(u) // What player
    local integer i = H2I(u) - 0x100000 // Index to damage array for casting unit
    local unit dummy
    local unit t // Temporary target unit
    local real d // Damage to deal per unit

    // Get and count all units we&#039;re going to hit
    call GroupClear(g)
    set c = 0.0
    call GroupEnumUnitsInRangeOfLoc(g, l, SPELL_RANGE, Condition(function GetTargetCount))
    call RemoveLocation(l)
    set l = null
    if c &lt; 1.0 then // Can&#039;t happen, but we&#039;re paranoid
        set p = null
        set u = null
        return
    endif
    // Amount of damage to deal to each unit
    set d = 0.5 + damage<i> / c
    // Reset damage count
    set damage<i> = 0.0
    loop // For each target unit
        set t = FirstOfGroup(g)
        exitwhen t == null
        call GroupRemoveUnit(g, t)
        if GetUnitState(t, UNIT_STATE_LIFE) &gt; d then // If the hit won&#039;t kill it
            set dummy = CreateUnit(p, DUMMY, GetUnitX(t), GetUnitY(t), bj_UNIT_FACING) // New dummy caster
            call UnitAddAbility(dummy, DUMMY_SPELL) // Chain Lightning
            call IssueTargetOrder(dummy, DUMMY_ORDER, t) // Cast it
            call UnitApplyTimedLife(dummy, &#039;BTLF&#039;, 1.0) // Begone, foul shade
        else // Otherwise the unit&#039;s nearly dead already: show simple effect
            call DestroyEffect(AddSpecialEffect(DUMMY_EFFECT, GetUnitX(t), GetUnitY(t)))
        endif
        // Deal the damage
        call UnitDamageTarget(u, t, d, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
        // Show damage dealt to this target unit
        call FloatingTextXY(I2S(R2I(d)) + &quot;!&quot;, GetUnitX(t), GetUnitY(t))
    endloop

    set dummy = null // Clean up
    set p = null
    set u = null
endfunction

private function Init takes nothing returns nothing
    local region r = CreateRegion()

    // Elapsed game time is 0.0 seconds
    local trigger t = CreateTrigger()
    call TriggerRegisterTimerEvent(t, 0.0, false)
    call TriggerAddAction(t, function StartingUnits)

    // A unit enters entire map
    set t = CreateTrigger()
    call RegionAddRect(r, GetWorldBounds())
    call TriggerRegisterEnterRegion(t, r, null)
    call TriggerAddCondition(t, Condition(function IsNewCaster))

    // A unit begins casting an ability
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
    call TriggerAddCondition(t, Condition(function Conditions))
    call TriggerAddAction(t, function HasTarget)
    // A unit starts the effect of an ability
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function Conditions))
    call TriggerAddAction(t, function Actions)

    // Events are added dynamically.
    // Once there are some, this function will be called as &quot;specific unit takes damage&quot;
    call TriggerAddAction(dt, function Accumulate)
endfunction
endscope
</i></i></i></i></i></i>



Getting it into your own map:

Create a trigger,
menu: Edit / Convert to custom script,
replace whatever is showing there with the above script.
Edit the configuration options as needed.

You need an area ability.
I used Silence for no particular reason.
With a better effect and, of course, it doesn't silence anything.

A flying dummy.
The usual, no attack, no sound, no corpse, flying, plenty mana, "cloudy" model, ...
A Chain Lighting that only hits one target for only 1 damage.
Or whatever else you want your dummy to cast.

Note:
Requires NewGen.


Enjoy.

Yours,
AceHart



Q: Impressive!
A: Admit it, you started reading here and not even seen it yet

Q: What map could possibly want to use this?
A: The one I'm working on for example

Q: What map is that?
A: Uhm... the one I'm working on?

Q: MUI?
A: This is multi-whatever-the-heck-you-care-for-and-then-some-including-but-not-limited-to-the-kitchen-sink-and-the-cat

Q: It uses a global though to count
A: It's still <see above>

Q: I can has cookie?
A: Noez, phyrex1an ated them all

Q: Can I edit this so it gets a higher max total per level?
A: Yes.

Q: Any chance you might be slightly more specific?
A: Replace the max total constant with an array, set the maxs per level in the init function, compare with array[level]

Q: Same for range then, right?
A: Right.

Q: Why not simply run the actions in the conditions? Much more efficient!
A1: Sure.
A2: Doesn't work, it needs the number of units to hit before it can hit them

Q: Wouldn't it be better to copy AddCaster twice?
A: Maybe... though that's counting micro-seconds. How many new units with that spell do you create per second?

Q: Why count the units again? You already did on "begins casting".
A: Only the paranoid survive

Q: Then why not cast it entirely on "begins"?
A: See previous answer

Q: When's the next BlizzCon?
A: August

Q: 5 triggers for one single spell...
A: I'm open to improvements

Q: Your... uhm... "attachment system" can only handle a couple thousand units!
A: Damn! I knew it!

Q: What's that "Easter Egg" deal on Bnet?
A1: Old news...
A2: http://www.battle.net/ - scroll down to the bottom, see that warp pad in the center? Hover over the middle blue part until some text shows. Click. Don't move.

Q: Set t = null? Remove the region? Set r = null?
A: Removing the region is not that great an idea. And the rest isn't a leak either as the triggers are going to stay around forever.

Q: ...
A: Sorry, in order to keep this below 300 lines, that's it

Q: Damn!
A: There's always another time



The map:
 

Attachments

  • DamageAccumulator_Spell_AceHart.w3x
    15.6 KB · Views: 185

Romek

Super Moderator
Reaction score
963
Cool Spell, and funny FAQ :p


GetWidgetLife() is faster, and IsUnitType(unit, UNIT_TYPE_DEAD) is more 'stable'.
And players don't need nulling.

I also get a lag-spike when I cast it for the first time. Quite possibly a problem on my side though.

Good job anyway. :)
I'm surprised a spell like this hasn't already been made. :p
Well, unique ideas are very rare at the moment.

I bet everybody is eager to find out more about your project now too!
 
Reaction score
91
> I also get a lag-spike when I cast it for the first time. Quite possibly a problem on my side though.
Seriously, almost every custom spell has a lag spike on first cast, even if it was preloaded.

Interesting and nice spell, however couldn't you make the error message like an ordinary game error such as "Not enough mana" (SimError comes to mind, though it could be independant)?
 

Romek

Super Moderator
Reaction score
963
> Seriously, almost every custom spell has a lag spike on first cast, even if it was preloaded.
No they don't. Definitely not like this anyway.
 

emjlr3

Change can be a good thing
Reaction score
395
JASS:
Q: Your... uhm... &quot;attachment system&quot; can only handle a couple thousand units!
A: Damn! I knew it!


this could be remedied...
 

Bloodcount

Starcraft II Moderator
Reaction score
297
Nice spell, although when I read the Q&A I liked to hear that you are preparing a new project.
 

Viikuna

No Marlo no game.
Reaction score
265
GetWidgetLife() is faster, and IsUnitType(unit, UNIT_TYPE_DEAD) is more 'stable'.

GetWidgetLife will fail if you modify dead units hp. IsUnitType(unit, UNIT_TYPE_DEAD) will fail if unit has already decayed. ( since it doesnt exist anymore )

edit. The best method to check if unit is death, would be order some dummy to cast spell on it or something.
But since both of these functions work most of the time, no one really wants to bother.
 

BlackRose

Forum User
Reaction score
239
I played it and............ I don't get it, all I see is a thudnerbolt from a poof :)... which is really smart idea....

Is it the more damage you take, you cast, boom!?
 

emjlr3

Change can be a good thing
Reaction score
395
It could. But is it worth it?


As for the first time cast lag, that's normal.
It uses lightning effects with no pre-loading...

maybe, regardless its a 2s fix, so why not
 
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