Moderator
M
Moderator
11 Nov 2011
Bribe: Very good resource. Nice configurables and clean code.
Bribe: Very good resource. Nice configurables and clean code.
(1 ratings)
// ********************************************************************************* //
// This spells requires the CTL library //
// ********************************************************************************* //
// ********************************************************************************* //
// HOW TO IMPLEMENT //
// ********************************************************************************* //
// //
// 1. Copy the Maledict Ability (object editor) //
// 2. Copy the Maledict Buff (object editor) //
// 3. Copy this trigger //
// 4. Enjoy :), but remember, this spell requires CTL //
// //
// ********************************************************************************* //
library Maledict requires CTL
globals
// ***************************************************************************** //
// CONFIGURABLES //
// ***************************************************************************** //
// Rawcode of the curse and the curse buff
private constant integer MALEDICT_RAWCODE = 'A001'
private constant integer MALEDICT_BUFF_RAWCODE = 'B000'
// @SPECIAL_EFFECT = Path of the special effect
// @EFFECT_ATTACHMENT = Position of the special effect
private constant string SPECIAL_EFFECT = "Abilities\\Spells\\Undead\\DeathPact\\DeathPactCaster.mdl"
private constant string EFFECT_ATTACHMENT = "overhead"
// @ONLY_HERO = If it is true, MALEDICT will only affect
// HERO type units
private constant boolean ONLY_HERO = false
// @DAMAGE_TYPE_EVERY_SECOND = Here you can choose what kind of damage
// the curse will do every second
private constant damagetype DAMAGE_TYPE_EVERY_SECOND = DAMAGE_TYPE_UNIVERSAL
// @DAMAGE_TYPE = Type of damage that the spell will do
// in the extra damage
// @ATTACK_TYPE = Type of attack that the spell will do
// in the extra damage and normal damage
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// Area of effect of the curse (do not touch the +50)
private constant real AoE = 165. + 50.
// @EXTRA_DAMAGE_PERIOD = Seconds that needs to pass to make the extra damage
private constant real EXTRA_DAMAGE_PERIOD = 4.
// @PERIODIC_DAMAGE = Seconds that needs to pass to make the periodic damage
private constant real PERIODIC_DAMAGE = 1.
// @PRELOAD = To preload the special effect
private constant boolean PRELOAD = true
// ***************************************************************************** //
// END CONFIGURABLES //
// ***************************************************************************** //
// @G = "Units" affected by the curse - DO NOT TOUCH THIS!
private constant group G = bj_lastCreatedGroup
endglobals
// ********************************************************************************* //
// CONFIGURABLE //
// ********************************************************************************* //
// Returns the extra damage
// ((HP OF THE TARGET WHEN ADQUIERE THE CURSE - ACTUAL LIFE OF TARGET) / 100) * (10 * LEVEL OF THE CURSE)
private function getExtraDamage takes unit target, real hp, integer level returns real
local real damage
set damage = ((hp - GetWidgetLife(target)) / 100) * (10. * level)
if damage < 0 then
set damage = 0
endif
return damage
endfunction
// Returns the periodic damage
private function getPeriodicDamage takes integer level returns real
return 5. * level
endfunction
// Returns the unit that can be affected by the curse
private function affectedUnit takes unit target, player p returns boolean
static if ONLY_HERO then
return IsUnitType(target, UNIT_TYPE_HERO) and IsUnitEnemy(target, p) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE))
else
return IsUnitEnemy(target, p) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE))
endif
endfunction
// ********************************************************************************* //
private keyword Init
private struct Spell extends array
private static integer instanceCount = 0
private static thistype recycle = 0
private thistype recycleNext
private unit caster // Caster
private unit target // Curse target
private real hp // HP of target
private integer level // Spell level
private real period // @period will serve to know if PERIODIC_DAMAGE seconds has passed
private real period_extraDamage // Same as period, but for the extra damage
implement CTL
implement CTLExpire
// If the target has the curse and is not dead...
if 0 < GetUnitAbilityLevel(this.target, MALEDICT_BUFF_RAWCODE) and not(IsUnitType(this.target, UNIT_TYPE_DEAD)) then
// Let's see if PERIODIC_DAMAGE seconds has passed
if PERIODIC_DAMAGE < this.period then
// If was, reset period to 0
set this.period = 0.
// Making the periodic damage (only if the unit
// is not inmmune to magic)
if not(IsUnitType(this.target, UNIT_TYPE_MAGIC_IMMUNE)) then
call UnitDamageTarget(this.caster, this.target, getPeriodicDamage(this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE_EVERY_SECOND, null)
endif
else
// If was not, add the expired seconds to period (seconds
// of the CTL timer)
set this.period = this.period + 0.031250000
endif
// Let's see if the EXTRA_DAMAGE_PERIOD seconds has passed
if EXTRA_DAMAGE_PERIOD < this.period_extraDamage then
// If was, reset period_extraDamage to 0
set this.period_extraDamage = 0.
// Making the damage and special effect only if the
// damage is greater than 0
if getExtraDamage(this.target, this.hp, this.level) > 0 then
// Only make the damage if the target is not inmmune to magic
if not(IsUnitType(this.target, UNIT_TYPE_MAGIC_IMMUNE)) then
call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
call UnitDamageTarget(this.caster, this.target, getExtraDamage(this.target, this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endif
else
// If was not (the seconds has not passed)...
set this.period_extraDamage = this.period_extraDamage + 0.031250000
endif
else
// If the unit is dead or not has
// the buff...
set this.caster = null
set this.target = null
call this.destroy()
set recycleNext = recycle
set recycle = this
endif
implement CTLNull
implement CTLEnd
private static method run takes nothing returns boolean
local unit j
local thistype this
// If the ability being cast is the Rupture and the target can be affected
// (units that can be affected can be changed in the affectedUnit function)
if GetSpellAbilityId() == MALEDICT_RAWCODE then
call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE, null)
loop
set j = FirstOfGroup(G)
exitwhen j == null
call GroupRemoveUnit(G, j)
if affectedUnit(j, GetTriggerPlayer()) then
set this = thistype.create()
if (recycle == 0) then
set instanceCount = instanceCount + 1
set this = instanceCount
else
set this = recycle
set recycle = recycle.recycleNext
endif
set this.caster = GetTriggerUnit()
set this.level = GetUnitAbilityLevel(this.caster, MALEDICT_RAWCODE)
set this.target = j
set this.hp = GetWidgetLife(j)
endif
endloop
endif
return false
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.run))
// Preload the special effect...
static if PRELOAD then
call Preload(SPECIAL_EFFECT)
endif
endmethod
endmodule
endlibrary
if GetSpellAbilityId() == MALEDICT_RAWCODE then
set caster = GetTriggerUnit()
set level = GetUnitAbilityLevel(caster, MALEDICT_RAWCODE)
set x = GetSpellTargetX()
set y = GetSpellTargetY()
set g = CreateGroup()
// Picking every unit in MALEDICT AoE (default: 165.)
call GroupEnumUnitsInRange(g, x, y, AoE, Condition(function filter))
loop
set target = FirstOfGroup(g)
exitwhen target == null
call GroupRemoveUnit(g, target)
set data = Spell.create(caster, level, target)
call SetHandleInt(data.t, "data", data)
call TimerStart(data.t, 1., true, function tim)
endloop
call DestroyGroup(g)
endif
if ONLY_HERO then
return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer()) and IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO)
endif
return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())
static if ONLY_HERO then
return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer()) and IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO)
else
return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())
endif
Why are you creating a group then destroying it?
You could create a global group and clear it at the end instead of destroying it.
OkUsing static ifs will delete the unneeded code.
One last thing: Why are you still using Hashtables?
Since you're only using hashtables for Timers, I'd suggest using TimerUtils
With the group, I can pick the units that are in the aoe of the spell. Which would be the difference using a global variable?
EDIT : Mmm, the static if throws me an error (syntax error)
Efficiency.
private group G = CreateGroup()
call GroupEnumUnitsInRange(G, x, y, AoE, Condition(function filter))
GroupClear(G)
GroupEnumUnitsInRange
, you could store them into globals call SetTimerData(data.t, data)
call TimerStart(data.t, 1., true, function tim)
call SetWidgetLife(this.target, GetWidgetLife(this.target) - (5. * this.level))
if this.period >= 4 then
private method onDestroy takes nothing returns nothing
call ReleaseTimer(this.t)
//call PauseTimer(this.t)
//call DestroyTimer(this.t)
//set this.t = null
set this.caster = null
set this.target = null
endmethod
set this.t = null
method action takes nothing returns nothing
local real damage
// Making the damage every second...
call SetWidgetLife(this.target, GetWidgetLife(this.target) - (5. * this.level))
// If four seconds was pass, make the extra damage
if this.period >= 4 then
set this.period = 0
set damage = ((this.hp - GetWidgetLife(this.target)) / 100) * (10 * this.level)
// Special effect...
call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
// Extra damage...
call UnitDamageTarget(this.caster, this.target, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
else
// If wasn't, add 1 second to period
set this.period = this.period + 1
endif
endmethod
DAMAGE_TYPE_UNIVERSAL
this.hp
variable and the level variable.call UnitDamageTarget(this.caster, this.target, GetDamage(this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
if GetUnitAbilityLevel(data.target, MALEDICT_BUFF_RAWCODE) > 0 then
if 0 < GetUnitAbilityLevel(data.target, MALEDICT_BUFF_RAWCODE) then
private method onDestroy takes nothing returns nothing
private struct Spell extends array
private constant integer ic = 0
private constant integer ir = 0
private thistype rn
local thistype this
if 0==ir then
set ic=ic+1
set this=ic
else
set this=ir
set ir=.rn
endif
set .rn=ir
set ir=this
EDIT
@baassee:
Y U NO WAIT 6 MINUTES?
-The locals in the condition function are useless. GetSpellTargetX/Y are only called once anyway.
-The constant AoE should be configurable in a private function/m operator.
-In the filter function you set a local into filtered units but you don't use it in the if checks?
-These actions can be called from the create method instead.
-Why are you using hpreduction? Change it to damage as if the target dies of this you cannot retrieve the killing unit nor the bounty.
-This should also be in the configurables, the 4 seconds.
-Why an onDestroy method? It's useless and sucks and besides that, use .deallocate() instead of .destroy()
And I don't get why you try to get people into effiecient struct usage
The speed gain is equivalent to losing:
2 function calls, 2 locals, a useless evaluation, and double free protection
It's only double free protection and a global array actually. You are confusing it with extending structs?
scope Maledict initializer init
globals
private constant integer MALEDICT_RAWCODE = 'A001'
private constant integer MALEDICT_BUFF_RAWCODE = 'B000'
private constant string SPECIAL_EFFECT = "Abilities\\Spells\\Undead\\DeathPact\\DeathPactCaster.mdl"
private constant string EFFECT_ATTACHMENT = "overhead"
// @ONLY_HERO = If it is true, MALEDICT will only affect
// on HERO type units
private constant boolean ONLY_HERO = false
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant real AoE = 165.
// @G = "Units" affected by the curse
private constant group G = CreateGroup()
private constant integer EXTRA_DAMAGE_PERIOD = 4
endglobals
private function getDamage takes unit target, real hp, integer level returns real
return ((hp - GetWidgetLife(target)) / 100) * (10 * level)
endfunction
private struct Spell
// @t = Timer
timer t
// @caster = Caster unit
unit caster
// @level = Level of the ability
integer level
// @target = Target of the cursed
unit target
// @hp = HP of the target unit
real hp
// @period = This variable will help us later
// to make the extra damage every 4 seconds
integer period
static method action takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if 0 < GetUnitAbilityLevel(this.target, MALEDICT_BUFF_RAWCODE) then
// Making the damage every second...
call UnitDamageTarget(this.caster, this.target, (5. * this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE_UNIVERSAL, null)
// If four seconds was pass, make the extra damage
if this.period >= EXTRA_DAMAGE_PERIOD then
set this.period = 0
// Special effect...
call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
// Extra damage...
call UnitDamageTarget(this.caster, this.target, getDamage(this.target, this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
else
// If wasn't, add 1 second to period
set this.period = this.period + 1
endif
else
call this.destroy()
endif
endmethod
static method create takes unit caster, integer level, unit target returns thistype
local thistype this = thistype.allocate()
set this.t = NewTimer()
set this.caster = caster
set this.level = level
set this.target = target
set this.hp = GetWidgetLife(target)
set this.period = 0
call SetTimerData(this.t, this)
call TimerStart(this.t, 1., true, function thistype.action)
return this
endmethod
method destroy takes nothing returns nothing
call ReleaseTimer(this.t)
set this.t = null
set this.caster = null
set this.target = null
call this.deallocate()
endmethod
endstruct
private function filter takes nothing returns boolean
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, MALEDICT_RAWCODE)
local unit target = GetFilterUnit()
local Spell data
if ONLY_HERO then
if IsUnitEnemy(target, GetTriggerPlayer()) and IsUnitType(target, UNIT_TYPE_HERO) then
set data = Spell.create(caster, level, target)
endif
else
if IsUnitEnemy(target, GetTriggerPlayer()) then
set data = Spell.create(caster, level, target)
endif
endif
set caster = null
set target = null
return false
endfunction
private function condition takes nothing returns boolean
if GetSpellAbilityId() == MALEDICT_RAWCODE then
// Picking every unit in MALEDICT AoE (default: 165.)
call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE, Condition(function filter))
endif
return false
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 condition))
set t = null
// Preload
call Preload(SPECIAL_EFFECT)
endfunction
endscope
-You still loop thru the filter function and not the action method.
-It lacks of documentation.
You just have to move the destroy method above the action method
call TimerStart(this.t, 1., true, function thistype.action)
// Determines which units are affected by the spell.
function AffectedTargets takes unit target, player cOwner returns boolean
return IsUnitEnemy(target, cOwner) and not IsUnitType(target, UNIT_TYPE_DEAD)
endfunction
Spell.create
directly.
- Your spell targets dead units and magic-immune units.
- You can allow the user to configure the targets easier by putting a function at the top for the user to determine valid targets.
Ex:
If you don't like this advice, you should at least make the if for ONLY_HERO a static if.JASS:// Determines which units are affected by the spell. function AffectedTargets takes unit target, player cOwner returns boolean return IsUnitEnemy(target, cOwner) and not IsUnitType(target, UNIT_TYPE_DEAD) endfunction
- Your getDamage function is confusing. You should explain more about how the spell calculates the extra damage.
- Periodic damage should be configurable by the user.
- It doesn't seem right that the spell will heal the unit if the target's hit points increases more than the hit points it had.
- There is no point for the data variable in the filter function, just call
Spell.create
directly.- Your code documentation should state that TimerUtils needs to be implemented.
- It's a good practice to separate globals that aren't actually configurable to avoid confusing the user. I'm talking about G.
[*]It doesn't seem right that the spell will heal the unit if the target's hit points increases more than the hit points it had.
It can if the target gets more hit points than the hit points it had when it got Maledict. You should check to see if the extra damage isn't negative since negative damage heals the target.What?, the spell doesn't heal the unit in any case.
- convert your scope into a library and make it requires TimerUtils...
- this >>> (5. * this.level) should be set at the create
It can if the target gets more hit points than the hit points it had when it got Maledict. You should check to see if the extra damage isn't negative since negative damage heals the target.
Your test map should make the spell have more than one level to show that the ability scales with the level.
Just to say, if you want to rip it out of DOTA, it should be able to heal units in theory and practially
You should actually provide a function like getPeriodicDamage so that the user can edit it easier without going through the entire spell code.Mmm, no, because the damage is calculated in the getDamage function (so that the user can edit it without complications).
You should actually provide a function like getPeriodicDamage so that the user can edit it easier without going through the entire spell code.
Mmm, no, because the damage is calculated in the getDamage function (so that the user can edit it without complications)
I do not understand
As this spell idea originated from DOTA it should act like in DOTA?
library Maledict requires CTL
globals
// Rawcode of the curse and the curse buff
private constant integer MALEDICT_RAWCODE = 'A001'
private constant integer MALEDICT_BUFF_RAWCODE = 'B000'
// @SPECIAL_EFFECT = Path of the special effect
// @EFFECT_ATTACHMENT = Position of the special effect
private constant string SPECIAL_EFFECT = "Abilities\\Spells\\Undead\\DeathPact\\DeathPactCaster.mdl"
private constant string EFFECT_ATTACHMENT = "overhead"
// @ONLY_HERO = If it is true, MALEDICT will only affect
// on HERO type units
private constant boolean ONLY_HERO = false
// @DAMAGE_TYPE_EVERY_SECOND = Here you can choose what kind of damage
// the curse will do every second
private constant damagetype DAMAGE_TYPE_EVERY_SECOND = DAMAGE_TYPE_UNIVERSAL
// @DAMAGE_TYPE = Type of damage that the spell will do
// in the extra damage
// @ATTACK_TYPE = Type of attack that the spell will do
// in the extra damage and normal damage
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// Area of effect of the curse (do not touch the +50)
private constant real AoE = 165. + 50.
// @EXTRA_DAMAGE_PERIOD = Seconds that needs to pass to make the extra damage
private constant real EXTRA_DAMAGE_PERIOD = 4.
// @PERIODIC_DAMAGE = Seconds that needs to pass to make periodic damage
private constant real PERIODIC_DAMAGE = 1.
// @PRELOAD = To preload the special effect
private constant boolean PRELOAD = true
// @G = "Units" affected by the curse
private constant group G = CreateGroup()
endglobals
private function getExtraDamage takes unit target, real hp, integer level returns real
return ((hp - GetWidgetLife(target)) / 100) * (10. * level)
endfunction
private function getPeriodicDamage takes integer level returns real
return 5. * level
endfunction
private function affectedUnit takes unit target returns boolean
return true
endfunction
private keyword Init
private struct Spell extends array
unit caster
integer level
unit target
real hp
real period
real period_extraDamage
implement CTL
implement CTLExpire
if 0 < GetUnitAbilityLevel(this.target, MALEDICT_BUFF_RAWCODE) and not(IsUnitType(this.target, UNIT_TYPE_DEAD)) then
if PERIODIC_DAMAGE < this.period then
set this.period = 0.
call UnitDamageTarget(this.caster, this.target, getPeriodicDamage(this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE_EVERY_SECOND, null)
else
set this.period = this.period + 0.031250000
endif
if EXTRA_DAMAGE_PERIOD < this.period_extraDamage then
set this.period_extraDamage = 0.
if getExtraDamage(this.target, this.hp, this.level) > 0 then
call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
call UnitDamageTarget(this.caster, this.target, getExtraDamage(this.target, this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
else
set this.period_extraDamage = this.period_extraDamage + 0.031250000
endif
else
set this.caster = null
set this.target = null
call this.destroy()
endif
implement CTLNull
implement CTLEnd
private static method getUnits takes nothing returns boolean
local thistype this
if affectedUnit(GetFilterUnit()) then
set this = thistype.create()
set this.caster = GetTriggerUnit()
set this.level = GetUnitAbilityLevel(this.caster, MALEDICT_RAWCODE)
set this.target = GetFilterUnit()
set this.hp = GetWidgetLife(this.target)
endif
return false
endmethod
private static method run takes nothing returns boolean
// If the ability being cast is the Rupture and the target can be affected
// (units that can be affected can be changed in the affectedUnit function)
if GetSpellAbilityId() == MALEDICT_RAWCODE then
call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE, Condition(function thistype.getUnits))
endif
return false
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.run))
// Preload the special effect...
static if PRELOAD then
call Preload(SPECIAL_EFFECT)
endif
set t = null
endmethod
endmodule
endlibrary
if PERIODIC_DAMAGE < this.period then
if PERIODIC_DAMAGE <= this.period then
if EXTRA_DAMAGE_PERIOD < this.period_extraDamage then
if EXTRA_DAMAGE_PERIOD <= this.period_extraDamage then
T32 doesn't even pause the timer when no code is running xD
32 useless TriggerEvaluations per second