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.
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:
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 = 039;A000039; // 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 = 039;n000039;
private constant integer DUMMY_SPELL = 039;A001039; // 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> > 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) > 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't any enemies nearby or if we do not have any damage to deal back
if c < 1.0 then
call DisplayTextToPlayer(GetOwningPlayer(GetTriggerUnit()), 0, 0, "|cffffcc00No units in range...|r")
call IssueImmediateOrder(GetTriggerUnit(), "stop")
elseif damage[H2I(GetTriggerUnit()) - 0x100000] < c then
call DisplayTextToPlayer(GetOwningPlayer(GetTriggerUnit()), 0, 0, "|cffffcc00Not enough damage accumulated yet...|r")
call IssueImmediateOrder(GetTriggerUnit(), "stop")
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'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 < 1.0 then // Can't happen, but we'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) > d then // If the hit won'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;BTLF039;, 1.0) // Begone, foul shade
else // Otherwise the unit'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)) + "!", 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 "specific unit takes damage"
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: