Dinowc
don't expect anything, prepare for everything
- Reaction score
- 223
Energy Field
+
Hellfire Ritual
v2.0
+
Hellfire Ritual
v2.0
I got inspired by HoN's Energy Field and decided to make a Wc3 version of it (although I actually never played HoN, I've made the spell based on it's description xd).
It's:
-MUI
-Lagless (unless you spam)
-Leakless (I think, hard to find any leaks in such huge code :nuts
-Relatively easy configurable
this is the original description, but for the testing purposes, the cooldown, mana cost and cast time are set to 0
Deploying his patented Energy Field Double-Magnetic Modulator v3.0, the Engineer can guarantee that any enemies who enter or exit the active field are purged of all buffs and then silenced and perplexed for a short time.
Takes 5 hits to destroy the gadget.
Type: Magic
Radius: 475
Cast Time: 1.0 Seconds
Mana Cost: 200
Cooldown: 90.0 / 75.0 / 60.0 Seconds
Required Level: 6 / 11 / 16
Level 1 - Lasts 6 seconds, deals 3% of unit's max hp in damage.
Level 2 - Lasts 9 seconds, deals 5% of unit's max hp in damage.
Level 3 - Lasts 12 seconds, deals 7% of unit's max hp in damage.
Code (prepare to get thumped):
JASS:
scope field initializer init
// Energy Field v2.0, by Dinowc
// Credits to HoN
// Requirements:
// NewGen, Timer Utils, Group Utils, AIDS, dummy.mdx (everything included in map)
//How to import:
// Simply create a new trigger, convert it to custom text and replace everything with this code bellow. There are 3 abilities, 1 that casts the EF itself and other 2 are dummy abilities (Purge, Soul Burn). You can find them in Object editor.
//Configurables
globals
private constant integer SPELL_ID = 039;A000039;
private constant integer DUMMY_SPELL_1_ID = 039;A001039; // this is default for purge
private constant integer DUMMY_SPELL_2_ID = 039;A002039; // this is default for soul burn
private constant string DUMMY_SPELL_1_ORDER = "purge"
private constant string DUMMY_SPELL_2_ORDER = "soulburn"
private constant integer DUMMY_CASTER = 039;h002039; // the dummy that casts Purge/Soul Burn
private constant integer GADGET_DUMMY = 039;h004039; // self explanatory
private constant integer TURRET_DUMMY = 039;h001039; // same as above
private constant real TURRET_FACING = 180. // 180. degrees will make the turret face the gadget
private constant integer ARRAY_SIZE = 200
private constant real INTERVAL = 0.05 // make it a multiplier of 1
//Lightning Settings
private constant string LIGHTNING_EFFECT = "CLSB" // since Wc3 doesn't have any pretty lightning effects and I do not want to use imports, I've decided to use this one
private constant real LIGHTNING_HEIGHT_BASE = 10.
private constant real LIGHTNING_HEIGHT_INCREMENT = 40.
private constant integer LIGHTNING_ROWS = 3
private constant boolean LIGHTNING_DIRECTION = true // true for left, false for right
private constant boolean VISIBILITY_CHECK = true // I'm not sure what this means, but I left it at true xd
//Energy Field Settings
private constant real GADGET_OFFSET = 120. // distance between the caster and the gadget
private constant real BOUNDS_SIZE = 50. // // distance between the lightning and entering unit before he get's damaged; it's not really accurate since the field is Xangled, not round
private constant real TREE_CUT_AOE = 130. // AOE around turret where the destructables are destroyed
private constant real BASE_DAMAGE = 0.03 // in percent
private constant real DAMAGE_PER_LEVEL = 0.02 // in percent
private constant integer TURRET_COUNT = 8 // less than 3 and more than 20 is not really recommended
private constant real BASE_DURATION = 6.
private constant real DURATION_PER_LEVEL = 3.
private constant real BASE_AOE = 475.
private constant real AOE_PER_LEVEL = 0.
private constant integer BASE_HITS = 5
private constant integer HITS_PER_LEVEL = 0
private constant string BUILD_EFFECT = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone.mdl" // the effect that appears when you create the gadget, set to "" to display no effect (reduces lag)
private constant string TURRET_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" // the effect that appears on turrets (obelisks), set to "" to display no effect (reduces lag)
private constant string DAMAGE_EFFECT = "Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl"
private constant string DAMAGE_EFFECT_ATTACH = "chest"
private constant string PERIODIC_DAMAGE_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
private constant string PERIODIC_DAMAGE_EFFECT_ATTACH = "origin"
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant boolean TURRET_COLLISION = true //if turret has collision
endglobals
//Don't touch
globals
private rect R
private rect TestRect
private unit DUMMY
private group G = CreateGroup()
private trigger Tr = CreateTrigger()
private real RADIANS = 0.0174
endglobals
private function PolarX takes real dist, real angle returns real
return dist * Cos(angle * RADIANS)
endfunction
private function PolarY takes real dist, real angle returns real
return dist * Sin(angle * RADIANS)
endfunction
private function distance takes real x1, real y1, real x2, real y2 returns real
local real dx = x2 - x1
local real dy = y2 - y1
return SquareRoot(dx * dx + dy * dy)
endfunction
private function LevelIncrease takes real a, real b, integer level returns real
return a + ((I2R(level - 1)) * b)
endfunction
private function GroupClean takes nothing returns nothing
local unit picked = GetEnumUnit()
local real x = GetUnitX(picked)
local real y = GetUnitY(picked)
call SetTerrainPathable(x, y, PATHING_TYPE_WALKABILITY, true)
call KillUnit(picked)
set picked = null
endfunction
private function destructableFilter takes nothing returns boolean
local destructable d = GetFilterDestructable()
if GetDestructableLife(d) > 0 then
call KillDestructable(d)
endif
set d = null
return false
endfunction
private function damage takes unit target, real amount returns real
return GetUnitState(target, UNIT_STATE_MAX_LIFE) * amount
endfunction
private struct data
unit caster
unit gadget
lightning array sfx[ARRAY_SIZE]
integer index
group g
group g2
real damagePercent
real duration
real ticks
integer hits
timer t
static data D
static method AddUnits takes nothing returns boolean
local unit picked = GetFilterUnit()
local player p = GetOwningPlayer(D.caster)
if IsUnitEnemy(picked, p) == true and IsUnitInGroup(picked, D.g2) == false and IsUnitType(picked, UNIT_TYPE_DEAD) == false and GetUnitTypeId(picked) != GADGET_DUMMY and IsUnitType(picked, UNIT_TYPE_STRUCTURE) == false then
call GroupAddUnit(D.g2, picked)
endif
set picked = null
set p = null
return false
endmethod
static method create takes unit whichUnit, real x, real y returns data
local data d = data.allocate()
local player p = GetOwningPlayer(whichUnit)
local real angle = GetUnitFacing(whichUnit)
local real x1 = x + PolarX(GADGET_OFFSET, angle)
local real y1 = y + PolarY(GADGET_OFFSET, angle)
local real x2
local real y2
local real angleInc = 360. / TURRET_COUNT
local integer i = 0
local integer level = GetUnitAbilityLevel(whichUnit, SPELL_ID)
local real AOE = LevelIncrease(BASE_AOE, AOE_PER_LEVEL, level)
local unit dummy
local unit turret
local real height1
local real height2
local location l
local integer a = 0
local real z1
local real z2
set d.caster = whichUnit
set d.damagePercent = LevelIncrease(BASE_DAMAGE, DAMAGE_PER_LEVEL, level)
set d.duration = LevelIncrease(BASE_DURATION, DURATION_PER_LEVEL, level)
call MoveRectTo(R, x1, y1)
call EnumDestructablesInRect(R, Filter(function destructableFilter), null)
if BUILD_EFFECT != "" then
set dummy = CreateUnit(p, DUMMY_CASTER, x1, y1, angle) // this dummy is used only as an effect for more eye candy, it's ok if you delete these 3 lines to reduce lag
call DestroyEffect(AddSpecialEffectTarget(BUILD_EFFECT, dummy, "origin"))
call KillUnit(dummy)
endif
set d.gadget = CreateUnit(p, GADGET_DUMMY, x1, y1, angle)
set d.g = NewGroup()
set d.g2 = NewGroup()
set x = GetUnitX(d.gadget)
set y = GetUnitY(d.gadget)
loop
set x1 = x + PolarX(AOE, angle)
set y1 = y + PolarY(AOE, angle)
call MoveRectTo(R, x1, y1)
call EnumDestructablesInRect(R, Filter(function destructableFilter), null)
if TURRET_EFFECT != "" then
set dummy = CreateUnit(p, DUMMY_CASTER, x1, y1, angle) // this dummy is used only as an effect for more eye candy, it's ok if you delete these 3 lines to reduce lag
call DestroyEffect(AddSpecialEffectTarget(TURRET_EFFECT, dummy, "origin"))
call KillUnit(dummy)
endif
set turret = CreateUnit(p, TURRET_DUMMY, x1, y1, angle + TURRET_FACING)
if TURRET_COLLISION then
call SetTerrainPathable(x1, y1, PATHING_TYPE_WALKABILITY, false)
endif
call GroupAddUnit(d.g, turret)
set l = Location(x1, y1)
set height1 = GetLocationZ(l)
call RemoveLocation(l)
set x2 = x + PolarX(AOE, angle + angleInc)
set y2 = y + PolarY(AOE, angle + angleInc)
set l = Location(x2, y2)
set height2 = GetLocationZ(l)
call RemoveLocation(l)
loop
set z1 = (LIGHTNING_HEIGHT_BASE + a * LIGHTNING_HEIGHT_INCREMENT) + height1
set z2 = (LIGHTNING_HEIGHT_BASE + a * LIGHTNING_HEIGHT_INCREMENT) + height2
static if LIGHTNING_DIRECTION then
set d.sfx[d.index] = AddLightningEx(LIGHTNING_EFFECT, VISIBILITY_CHECK, x1, y1, z1, x2, y2, z2)
set d.index = d.index + 1
else
set d.sfx[d.index] = AddLightningEx(LIGHTNING_EFFECT, VISIBILITY_CHECK, x2, y2, z1, x1, y1, z2)
set d.index = d.index + 1
endif
set a = a + 1
exitwhen a >= LIGHTNING_ROWS
endloop
set a = 0
set angle = angle + angleInc
set i = i + 1
exitwhen i >= TURRET_COUNT
endloop
set D = d
call GroupEnumUnitsInRange(G, x, y, AOE - BOUNDS_SIZE, Filter(function data.AddUnits))
call TriggerRegisterUnitEvent(Tr, d.gadget, EVENT_UNIT_DAMAGED)
set d.ticks = 0.
set d.hits = 0
set d.t = NewTimer()
set turret = null
set dummy = null
set p = null
set l = null
return d
endmethod
static method damageExiters takes nothing returns boolean
local unit picked = GetFilterUnit()
local player p = GetOwningPlayer(D.caster)
local real x = GetUnitX(picked)
local real y = GetUnitY(picked)
local real dist = distance(x, y, GetUnitX(D.gadget), GetUnitY(D.gadget))
local integer level = GetUnitAbilityLevel(D.caster, SPELL_ID)
local real AOE = LevelIncrease(BASE_AOE, AOE_PER_LEVEL, level)
if IsUnitEnemy(picked, p) == true and IsUnitInGroup(picked, D.g2) == true and IsUnitType(picked, UNIT_TYPE_DEAD) == false and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false and GetUnitTypeId(picked) != GADGET_DUMMY and IsUnitType(picked, UNIT_TYPE_STRUCTURE) == false and dist + BOUNDS_SIZE > AOE then
call SetUnitOwner(DUMMY, p, false)
call SetUnitX(DUMMY, x)
call SetUnitY(DUMMY, y)
call SetUnitAbilityLevel(DUMMY, DUMMY_SPELL_1_ID, level)
call SetUnitAbilityLevel(DUMMY, DUMMY_SPELL_2_ID, level)
call IssueTargetOrder(DUMMY, DUMMY_SPELL_1_ORDER, picked)
call IssueTargetOrder(DUMMY, DUMMY_SPELL_2_ORDER, picked)
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, picked, DAMAGE_EFFECT_ATTACH))
call GroupRemoveUnit(D.g2, picked)
endif
set picked = null
set p = null
return false
endmethod
static method filterFunc takes nothing returns boolean
local unit picked = GetFilterUnit()
local player p = GetOwningPlayer(D.caster)
local integer level
if IsUnitEnemy(picked, p) == true and IsUnitType(picked, UNIT_TYPE_DEAD) == false and IsUnitType(picked, UNIT_TYPE_MAGIC_IMMUNE) == false and GetUnitTypeId(picked) != GADGET_DUMMY and IsUnitType(picked, UNIT_TYPE_STRUCTURE) == false then
if IsUnitInGroup(picked, D.g2) == false then
set level = GetUnitAbilityLevel(D.caster, SPELL_ID)
call SetUnitOwner(DUMMY, p, false)
call SetUnitX(DUMMY, GetUnitX(picked))
call SetUnitY(DUMMY, GetUnitY(picked))
call SetUnitAbilityLevel(DUMMY, DUMMY_SPELL_1_ID, level)
call SetUnitAbilityLevel(DUMMY, DUMMY_SPELL_2_ID, level)
call IssueTargetOrder(DUMMY, DUMMY_SPELL_1_ORDER, picked)
call IssueTargetOrder(DUMMY, DUMMY_SPELL_2_ORDER, picked)
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, picked, DAMAGE_EFFECT_ATTACH))
call GroupAddUnit(D.g2, picked)
endif
if ModuloReal(D.ticks, 1) == 1 then
call UnitDamageTarget(D.caster, picked, damage(picked, D.damagePercent), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
if TURRET_EFFECT != "" then
call DestroyEffect(AddSpecialEffectTarget(PERIODIC_DAMAGE_EFFECT, picked, PERIODIC_DAMAGE_EFFECT_ATTACH))
endif
endif
endif
set p = null
set picked = null
return false
endmethod
private method onDestroy takes nothing returns nothing
local integer a = 0
call ForGroup(.g, function GroupClean)
call ReleaseGroup(.g)
loop
call DestroyLightning(.sfx[a])
set .sfx[a] = null
set a = a + 1
exitwhen a > .index
endloop
set .index = 0
call ReleaseGroup(.g2)
call KillUnit(.gadget)
call ReleaseTimer(.t)
endmethod
endstruct
globals
private data array STORE
endglobals
private function callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local data d = GetTimerData(t)
local integer a = 0
local real AOE = LevelIncrease(BASE_AOE, AOE_PER_LEVEL, (GetUnitAbilityLevel(d.caster, SPELL_ID)))
set d.ticks = d.ticks + INTERVAL
if d.ticks >= d.duration then
call d.destroy()
else
set data.D = d
call GroupEnumUnitsInRange(G, GetUnitX(d.gadget), GetUnitY(d.gadget), AOE - BOUNDS_SIZE, function data.filterFunc)
call GroupEnumUnitsInRange(G, GetUnitX(d.gadget), GetUnitY(d.gadget), AOE + BOUNDS_SIZE, function data.damageExiters)
endif
set t = null
endfunction
private function actions takes nothing returns boolean
local unit caster
local data d
if GetSpellAbilityId() == SPELL_ID then
set caster = GetTriggerUnit()
set d = data.create(caster, GetUnitX(caster), GetUnitY(caster))
set STORE[GetUnitIndex(d.gadget)] = d
call SetTimerData(d.t, d)
call TimerStart(d.t, INTERVAL, true, function callback)
set caster = null
endif
return false
endfunction
private function act takes nothing returns boolean
local unit attacked = GetTriggerUnit()
local data d
local integer a
local integer HITS
set d = STORE[GetUnitIndex(attacked)]
if d != null then
set HITS = BASE_HITS + ((GetUnitAbilityLevel(d.caster, SPELL_ID) - 1) * HITS_PER_LEVEL)
set d.hits = d.hits + 1
if d.hits >= HITS then
call d.destroy()
else
set STORE[GetUnitIndex(attacked)] = d
endif
endif
set attacked = null
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
local real aoe = TREE_CUT_AOE / 2
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function actions))
call TriggerAddCondition(Tr, Condition(function act))
set R = Rect(-1 * aoe, -1 * aoe, aoe, aoe)
set TestRect = Rect(-25., -25., 25, 25)
//Preload
call RemoveUnit(CreateUnit(Player(13), GADGET_DUMMY, 0., 0., 0.))
call RemoveUnit(CreateUnit(Player(13), TURRET_DUMMY, 0., 0., 0.))
set DUMMY = CreateUnit(Player(13), DUMMY_CASTER, 0., 0., 0.)
set t = null
endfunction
endscope
after playing a bit with the configurables block (and minor code changes), I've made a spell called Hellfire Ritual
it's actually the same spell, just with different visuals, causes stun instead of purge/silence and it's a point target spell:
Opens a hole to hell itself and summons demons around it who begin a dark ritual. Any unit that enters or leaves the inner field will take damage and be stunned for 2 seconds. Units in the field burn with intense heat, taking damage.
Type: Magic
Radius: 475
Cast Time: 1.0 Seconds
Mana Cost: 200
Cooldown: 90.0 / 75.0 / 60.0 Seconds
Required Level: 6 / 11 / 16
Level 1 - Lasts 6 seconds, deals 3% of unit's max hp in damage, takes 4 hits to destroy the pit.
Level 2 - Lasts 9 seconds, deals 5% of unit's max hp in damage, takes 6 hits to destroy the pit.
Level 3 - Lasts 12 seconds, deals 7% of unit's max hp in damage, takes 8 hits to destroy the pit.
it's to show you how editable this spell actually is
Changelog:
v2.0 - added a point target version of the spell (Hellfire Ritual); added some new configurables; code optimizations
v1.5 - fixed a major visual glitch when creating lightnings on different terrain heights
v1.4 - removed the usage of hashtable and replaced it with arrays instead; Purge and Soul burn effects can now be improved per Energy Field level by changing stats in Object Editor; made some code optimization; added a new configurable
v1.3b - removed some unnecessary lines of code; fixed a minor leak
v1.3 - further improved/optimized the code; the field no longer damages structures nor an enemy gadget; added Group Utils, making the field damage stackable (last thing that made it non-MUI removed); added some additional configurables
v1.2 - made a LOT of changes/improvements (code fixes and optimization, added unit indexing system, new configurables...)
v1.1 - some code fixes, the gadget is destroyed when hit exactly N times, not when attacked
v1.0 - initial post
v1.5 - fixed a major visual glitch when creating lightnings on different terrain heights
v1.4 - removed the usage of hashtable and replaced it with arrays instead; Purge and Soul burn effects can now be improved per Energy Field level by changing stats in Object Editor; made some code optimization; added a new configurable
v1.3b - removed some unnecessary lines of code; fixed a minor leak
v1.3 - further improved/optimized the code; the field no longer damages structures nor an enemy gadget; added Group Utils, making the field damage stackable (last thing that made it non-MUI removed); added some additional configurables
v1.2 - made a LOT of changes/improvements (code fixes and optimization, added unit indexing system, new configurables...)
v1.1 - some code fixes, the gadget is destroyed when hit exactly N times, not when attacked
v1.0 - initial post
Any comment appreciated