| 01-20-2009, 08:47 AM | #1 |
Hi guys, since Dusk had the unpleasant decision of moving my code here for no good reason at all, and since that thread is huge and contains a lot of crap, I decided to remake the thread, this time for code criticism. I would like to hear the opinion from the user point of view of the code, since I made the code thinking on the user. If you have ideas to upgrade, please make a comment. Please note that this should already be approved (having in mind what Moyack said), but I am just asking another opinion. JASS://=========================================================================== //A JESP spell that allows the user to create lightning spells with any model //he desires. In this sample, the caster sends a purple projectile which will //damage enemy units and heal the caster by an amount of damage they received. //If an enemy unit dies due this ability, it will return as an Undead to aid //the caster, unless it is a summon, a hero or a flying unit. // //Requires TimerUtils // //@author Flame_Phoenix // //@credits //- Deaod, for all hi help in the code, with math formulas and advices //- Anitarf, for math formulas and advices for efficiency //- Pyrogasm, for giving me the algorithm for making the spell with 1 timer only //- Daelin, for the math formulas on his outdated spells //- Vexorian, for the idea of preloading units and abilities and for TimerUtils // //@version 2.3 //=========================================================================== scope DarkLightning initializer Init //=========================================================================== //=============================SETUP START=================================== //=========================================================================== globals private constant integer AID = 'A000' //rw of teh ability private constant real SPEED = 700. //speed of the missile private constant integer MISSILE_ID = 'h000' //rw of the missile private constant integer DUM_ID = 'h001' //rw of the dummy unit private constant integer DUM_AB = 'A001' //Ability of the dumy unit (animated dead) private constant string DUM_ORDER = "animatedead" //string order of the dummy unit private constant real TIMER_CICLE = 0.03 //cicles of the timer private constant string DRAIN_EFFECT = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl" private constant string BLOOD_EFFECT = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl" private constant attacktype A_TYPE = ATTACK_TYPE_MAGIC //the attack type of the spell private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL //the damage type of the spell endglobals private function Range takes integer level returns real //If there is more than one Target, a next target will be picked in a 500 //AOE from the first return 500. + (level * 0) endfunction private function Damage takes integer level returns real //Damage each Target will take return 100. * level endfunction private function Heal takes integer level, real damage returns real //the heal the caster will get when damaging enemies // in this case in level 1 caster gains 33% of damage done, in level //2 he gains 66% of damage done and in level 3 he gains 99% of the //damage deal to the target return level * 0.33 * damage endfunction private function Reduction takes integer level returns real //Damage reduction per Target return 0.15 + (level * 0) endfunction private function TargetsNumber takes integer level returns integer //The number of targets return 4 + (level * 1) endfunction private function Targets takes unit caster, unit target returns boolean //"caster" is the caster of the spell, and "target" is the target being evauated return IsUnitEnemy(target, GetOwningPlayer(caster)) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (GetWidgetLife(target) > 0.405) endfunction //=========================================================================== //=============================SETUP END===================================== //=========================================================================== globals private group g private boolexpr b private unit tmpCaster = null private timer t private integer instancesCount endglobals //=========================================================================== private function AceptedTargets takes nothing returns boolean return Targets(tmpCaster, GetFilterUnit()) endfunction //=========================================================================== private struct SpellData //static SpellData array datas //an array which will contain the instances unit caster //our caster ! unit vic //the current victim integer level //the level of the ability group picked //saves all targeted units so far, so they don't get picked again unit missile //the missile real wait //tells us how much time we must wait integer targNum //Current number of the target real lastDamage //this tells us the last amount of damage a unit received. NOTE: this is NOT the damage a unit is taking. boolean done static method create takes unit caster, unit vic returns SpellData local SpellData data = SpellData.allocate() //setting variables set data.caster = caster set data.vic = vic set data.level = GetUnitAbilityLevel(caster, AID) set data.missile = CreateUnit(GetOwningPlayer(caster), MISSILE_ID, GetUnitX(caster), GetUnitY(caster), 0) set data.wait = 0. set data.targNum = 0 set data.done = false //we recycle the group if data.picked == null then set data.picked = CreateGroup() endif return data endmethod method SetProjectile takes nothing returns nothing local real a = GetUnitX(.missile) - GetUnitX(.vic) local real b = GetUnitY(.missile) - GetUnitY(.vic) local real d = SquareRoot(a*a + b*b) //the distance between "a" and "b" set .wait = d / SPEED //we adapt the fly height of the missile to the height of the target! call SetUnitFlyHeight(.missile, GetUnitFlyHeight(.vic), (GetUnitFlyHeight(.missile) - GetUnitFlyHeight(.vic)) / .wait) endmethod method TargetEffect takes nothing returns nothing local unit dum //the hp the enemies will lose and that the caster will win if (.targNum == 0) then set .lastDamage = Damage(.level) else set .lastDamage = .lastDamage - (.lastDamage * Reduction(.level)) endif //here we damage the bad guy ! Die you bastard !!!! //we also created the effects for both targets and caster call UnitDamageTarget(.caster, .vic, .lastDamage, true, false, A_TYPE, D_TYPE, null) call DestroyEffect(AddSpecialEffectTarget(DRAIN_EFFECT, .vic, "origin")) call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT, .caster, "origin")) //Heal the caster call SetWidgetLife(.caster, GetWidgetLife(.caster) + Heal(.level, .lastDamage)) if (GetWidgetLife(.vic) < 0.405) and (IsUnitType(.vic, UNIT_TYPE_HERO) == false) and (IsUnitType(.vic, UNIT_TYPE_SUMMONED) == false) and (IsUnitType(.vic, UNIT_TYPE_FLYING) == false) then set dum = CreateUnit(GetOwningPlayer(.caster), DUM_ID, GetUnitX(.vic), GetUnitY(.vic), 0) call UnitAddAbility(dum, DUM_AB) call SetUnitAbilityLevel(dum, DUM_AB, .level) call IssueImmediateOrder(dum, DUM_ORDER) call UnitApplyTimedLife(dum, 'BTLF', 1.) endif set dum = null endmethod method NextTarget takes nothing returns nothing local unit f = null local unit ret = null //the position of the current target local real cX = GetUnitX(.vic) local real cY = GetUnitY(.vic) //the position of our new target local real nX local real nY //by saving the minimal distance, this will help us choose //the closest new target to our current target //note we start it with the biggest value possible, so //we can find the minimum after //Also note that I am calculating minDist^2 so I can avoid //the use of a squareroot which will save speed. Know that if //minDist <, >, <=, >= d then minDis^2 <, >, <=, >= d^2 and vice-versa local real minDist = Range(.level)*Range(.level) local real d //this will be a temporary variable for the distances we will calculate //here we pick all units near the last target set tmpCaster = .caster call GroupEnumUnitsInRange(g, GetUnitX(.vic), GetUnitY(.vic), Range(.level), b) loop set f = FirstOfGroup(g) exitwhen(f == null) call GroupRemoveUnit(g, f) if (IsUnitInGroup(f, .picked) == false) then set nX = GetUnitX(f) set nY = GetUnitY(f) //now we calculate the distance between f and our current target //note that: d^2 = (x2-x1)^2 + (y2-y1)^2 //I avoid using a square to make this faster set d = (nX - cX)*(nX - cX) + (nY - cY)*(nY - cY) //if this new distance is the smallest, then we update our target if (d < minDist) then set minDist = d set ret = f endif endif endloop set .vic = ret set ret = null endmethod method ChainEffect takes nothing returns nothing //we add the victim to the victims groups, so we won't pick it twice call GroupAddUnit(.picked, .vic) //here we call the function responsable for the bad things we do to the bad guys xD call .TargetEffect() //now we increase the counter to know how many units we hit set .targNum = .targNum + 1 //if the number of our current target is lower than the maximum amount //of targets we can hit, we continue, else we end everything if (.targNum < TargetsNumber(.level)) then //pick new target ! call .NextTarget() //if the new unit is not null, we repeat this step, else we end if (.vic != null) then call .SetProjectile() else set .done = true endif else set .done = true endif endmethod method MoveMissile takes nothing returns nothing local real x1 = GetUnitX(.missile) local real x2 = GetUnitX(.vic) local real y1 = GetUnitY(.missile) local real y2 = GetUnitY(.vic) local real dx = TIMER_CICLE * (x2 - x1) / .wait local real dy = TIMER_CICLE * (y2 - y1) / .wait call SetUnitX(.missile, x1 + dx) call SetUnitY(.missile, y1 + dy) //here we set the facing of the missile call SetUnitFacing(.missile, bj_RADTODEG * Atan2(y2 - y1, x2 - x1)) set .wait = .wait - TIMER_CICLE //this is when the missile gets to the unit if .wait < TIMER_CICLE then call SetUnitX(.missile, x2) call SetUnitY(.missile, y2) //This runs the ChainEffect function again! call .ChainEffect() endif endmethod method onDestroy takes nothing returns nothing //we clear the gorup so we can use it later call GroupClear(.picked) //we destroy the projectile call ShowUnit(.missile, false) call KillUnit(.missile) endmethod endstruct //=========================================================================== globals private SpellData array datas endglobals //=========================================================================== private function Periodic takes nothing returns nothing local integer currentIndex = 0 local SpellData currentInstance loop set currentInstance = datas[currentIndex] call currentInstance.MoveMissile() //if our instance is done, we decrement the number of total instances //and then we check if there are any more instances being run if (currentInstance.done) then set instancesCount = instancesCount - 1 //if there are, then we update our instance to the next of the array //and we correct the index if (instancesCount > 0) then set datas[currentIndex] = datas[instancesCount] set currentIndex = currentIndex - 1 //else we just release the timer else call ReleaseTimer(t) endif //now before we leave current instance for good, we destroy it! call currentInstance.destroy() endif set currentIndex = currentIndex + 1 exitwhen currentIndex >= instancesCount endloop endfunction //=========================================================================== private function Conditions takes nothing returns boolean if GetSpellAbilityId() == AID then //if there are no instances of the spell then it means the timer does not //exist, so we create it and start it! if instancesCount == 0 then set t = NewTimer() call TimerStart(t, TIMER_CICLE, true, function Periodic) endif set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit()) call datas[instancesCount].SetProjectile() set instancesCount = instancesCount + 1 endif return false endfunction //=========================================================================== private function Init takes nothing returns nothing local trigger LightningTrg = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( LightningTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( LightningTrg, Condition( function Conditions ) ) //setting globals set b = Condition(function AceptedTargets) set g = CreateGroup() set instancesCount = 0 //Preload the effects call Preload(DRAIN_EFFECT) call Preload(BLOOD_EFFECT) //preloading units and spells set bj_lastCreatedUnit = CreateUnit(Player(0), DUM_ID, 0, 0, 0) call UnitAddAbility(bj_lastCreatedUnit, DUM_AB) call KillUnit(bj_lastCreatedUnit) call KillUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), MISSILE_ID, 0, 0, 0)) endfunction endscope This is a lightning spell that uses only 1 timer for all instances. I find the code quite easy to understand, although I admit that the movement of the projectile could be better. Hope you all like it. |
| 01-20-2009, 10:21 AM | #2 |
I think the functions in the setup section should be constant. EDIT: Except Targets, which makes non-constant function calls. |
| 01-20-2009, 10:54 AM | #3 | |
Quote:
jasshelper will inline them so it's okay to keep it that way. |
| 01-20-2009, 10:54 AM | #4 | ||
Quote:
Thx for critic! +rep Off-topic PS: When will you download msn ? xD EDIT Sry, must spread repo =( Quote:
|
| 01-20-2009, 11:08 AM | #5 |
here's what happens: your code:function A takes integer lvl returns integer return 10*lvl endfunction function Sampler takes nothing returns nothing local integer damage=A(GetUnitLevel(GetTriggerUnit())) endfunction after inline:function Sampler takes nothing returns nothing local integer damage=10*GetUnitLevel(GetTriggerUnit()) // INLINE!! endfunction It doesn't matter if it's a constant function or not, if both gets inlined then there's no advantage over the other since they do the same thing. |
| 01-20-2009, 11:36 AM | #6 |
Inlined or not, constant is a helpful convention for designating configurable functions which for all purposes ought to be constant. |
| 01-20-2009, 11:45 AM | #7 | |
Quote:
|
| 01-20-2009, 12:08 PM | #8 |
Mmm, I think constant functions are inlined too, but I am really not sure... Besides this fact, is there anything else to upgrade? |
| 01-20-2009, 12:16 PM | #9 |
I still say you should move the stack and timer manipulation from the Conditions and Periodic function to the create and onDestroy methods. All it should take would be to move the Periodic function and the datas global array above the SpellData struct and slap a private keyword SpellData above them; or make them a static method and a static array member of the struct, respectively, that way the periodic code would be above the create/onDestroy methods but below the MoveMissile method, making calls to it faster; they'd still be unnecessary function calls, though, easily avoided by inlining the MoveMissile code into the Periodic method/function. |
| 01-20-2009, 12:17 PM | #10 |
TIMER_CICLE is a typo, yes? I think you mean either "cycle" or "circle". |
| 01-20-2009, 12:24 PM | #11 |
No, he means "cicle". It's his style, man. :) |
| 01-20-2009, 01:03 PM | #12 |
Then why use function at all, use global constants instead. |
| 01-20-2009, 01:03 PM | #13 | ||||
Quote:
Quote:
PS: this is a joke Ok, I edited some stuff: 1 - now SETUP functions are constant (assuming they are inlined, I decided to change them) 2 - Typo CICLE -> CYCLE is fixed Now to the list of things I can't do, or wont do. I will not change: Quote:
Now about the other stuff you suggest Anitarf, after re-reading your post again (and again [and again]) i finally understand your point, but I still have a problem with parameters (which I will never be able to pass to the onDestroy method). Yet, even If I do succeed in making the modification, my entire code will be inside a single method, which will define nothing ... I really prefer to have things divided, this way my structure defines an instance of a spell, and the functions that loop through all instances are outside the structure, because they are something different, they are not instances of the spell, they loop through instances of the spell. I am really sorry Anitarf, but now that my mind is clear, I am afraid I will have to say no once more. Well, don't be mad at me, see the bright side, I did everything else you requested (except xe, that doesn't count). So you gave like 999 tips and I just refused 2 of them. That is a good statistic =D Quote:
Anyway, since I see some people are interested, I will let this code here a few more days, and then I will move it back to submissions. Than help me God, if they don't approve this I will kill them all xD because according to Moyack this should already be approved. Anyway, thx all for the time! |
| 01-20-2009, 01:03 PM | #14 | |
Quote:
|
| 01-20-2009, 01:08 PM | #15 | |
Quote:
Anyway, for those you want to see the new version of the code: JASS://=========================================================================== //A JESP spell that allows the user to create lightning spells with any model //he desires. In this sample, the caster sends a purple projectile which will //damage enemy units and heal the caster by an amount of damage they received. //If an enemy unit dies due this ability, it will return as an Undead to aid //the caster, unless it is a summon, a hero or a flying unit. // //Requires TimerUtils // //@author Flame_Phoenix // //@credits //- Deaod, for all hi help in the code, with math formulas and advices //- Anitarf, for math formulas and advices for efficiency //- Pyrogasm, for giving me the algorithm for making the spell with 1 timer only //- Daelin, for the math formulas on his outdated spells //- Vexorian, for the idea of preloading units and abilities and for TimerUtils // //@version 2.3 //=========================================================================== scope DarkLightning initializer Init //=========================================================================== //=============================SETUP START=================================== //=========================================================================== globals private constant integer AID = 'A000' //rw of teh ability private constant real SPEED = 700. //speed of the missile private constant integer MISSILE_ID = 'h000' //rw of the missile private constant integer DUM_ID = 'h001' //rw of the dummy unit private constant integer DUM_AB = 'A001' //Ability of the dumy unit (animated dead) private constant string DUM_ORDER = "animatedead" //string order of the dummy unit private constant real TIMER_CYCLE = 0.03 //cicles of the timer private constant string DRAIN_EFFECT = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl" private constant string BLOOD_EFFECT = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl" private constant attacktype A_TYPE = ATTACK_TYPE_MAGIC //the attack type of the spell private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL //the damage type of the spell endglobals private constant function Range takes integer level returns real //If there is more than one Target, a next target will be picked in a 500 //AOE from the first return 500. + (level * 0) endfunction private constant function Damage takes integer level returns real //Damage each Target will take return 100. * level endfunction private constant function Heal takes integer level, real damage returns real //the heal the caster will get when damaging enemies // in this case in level 1 caster gains 33% of damage done, in level //2 he gains 66% of damage done and in level 3 he gains 99% of the //damage deal to the target return level * 0.33 * damage endfunction private constant function Reduction takes integer level returns real //Damage reduction per Target return 0.15 + (level * 0) endfunction private constant function TargetsNumber takes integer level returns integer //The number of targets return 4 + (level * 1) endfunction private function Targets takes unit caster, unit target returns boolean //"caster" is the caster of the spell, and "target" is the target being evauated return IsUnitEnemy(target, GetOwningPlayer(caster)) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (GetWidgetLife(target) > 0.405) endfunction //=========================================================================== //=============================SETUP END===================================== //=========================================================================== globals private group g private boolexpr b private unit tmpCaster = null private timer t private integer instancesCount endglobals //=========================================================================== private function AceptedTargets takes nothing returns boolean return Targets(tmpCaster, GetFilterUnit()) endfunction //=========================================================================== private struct SpellData //static SpellData array datas //an array which will contain the instances unit caster //our caster ! unit vic //the current victim integer level //the level of the ability group picked //saves all targeted units so far, so they don't get picked again unit missile //the missile real wait //tells us how much time we must wait integer targNum //Current number of the target real lastDamage //this tells us the last amount of damage a unit received. NOTE: this is NOT the damage a unit is taking. boolean done static method create takes unit caster, unit vic returns SpellData local SpellData data = SpellData.allocate() //setting variables set data.caster = caster set data.vic = vic set data.level = GetUnitAbilityLevel(caster, AID) set data.missile = CreateUnit(GetOwningPlayer(caster), MISSILE_ID, GetUnitX(caster), GetUnitY(caster), 0) set data.wait = 0. set data.targNum = 0 set data.done = false //we recycle the group if data.picked == null then set data.picked = CreateGroup() endif return data endmethod method SetProjectile takes nothing returns nothing local real a = GetUnitX(.missile) - GetUnitX(.vic) local real b = GetUnitY(.missile) - GetUnitY(.vic) local real d = SquareRoot(a*a + b*b) //the distance between "a" and "b" set .wait = d / SPEED //we adapt the fly height of the missile to the height of the target! call SetUnitFlyHeight(.missile, GetUnitFlyHeight(.vic), (GetUnitFlyHeight(.missile) - GetUnitFlyHeight(.vic)) / .wait) endmethod method TargetEffect takes nothing returns nothing local unit dum //the hp the enemies will lose and that the caster will win if (.targNum == 0) then set .lastDamage = Damage(.level) else set .lastDamage = .lastDamage - (.lastDamage * Reduction(.level)) endif //here we damage the bad guy ! Die you bastard !!!! //we also created the effects for both targets and caster call UnitDamageTarget(.caster, .vic, .lastDamage, true, false, A_TYPE, D_TYPE, null) call DestroyEffect(AddSpecialEffectTarget(DRAIN_EFFECT, .vic, "origin")) call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT, .caster, "origin")) //Heal the caster call SetWidgetLife(.caster, GetWidgetLife(.caster) + Heal(.level, .lastDamage)) if (GetWidgetLife(.vic) < 0.405) and (IsUnitType(.vic, UNIT_TYPE_HERO) == false) and (IsUnitType(.vic, UNIT_TYPE_SUMMONED) == false) and (IsUnitType(.vic, UNIT_TYPE_FLYING) == false) then set dum = CreateUnit(GetOwningPlayer(.caster), DUM_ID, GetUnitX(.vic), GetUnitY(.vic), 0) call UnitAddAbility(dum, DUM_AB) call SetUnitAbilityLevel(dum, DUM_AB, .level) call IssueImmediateOrder(dum, DUM_ORDER) call UnitApplyTimedLife(dum, 'BTLF', 1.) endif set dum = null endmethod method NextTarget takes nothing returns nothing local unit f = null local unit ret = null //the position of the current target local real cX = GetUnitX(.vic) local real cY = GetUnitY(.vic) //the position of our new target local real nX local real nY //by saving the minimal distance, this will help us choose //the closest new target to our current target //note we start it with the biggest value possible, so //we can find the minimum after //Also note that I am calculating minDist^2 so I can avoid //the use of a squareroot which will save speed. Know that if //minDist <, >, <=, >= d then minDis^2 <, >, <=, >= d^2 and vice-versa local real minDist = Range(.level)*Range(.level) local real d //this will be a temporary variable for the distances we will calculate //here we pick all units near the last target set tmpCaster = .caster call GroupEnumUnitsInRange(g, GetUnitX(.vic), GetUnitY(.vic), Range(.level), b) loop set f = FirstOfGroup(g) exitwhen(f == null) call GroupRemoveUnit(g, f) if (IsUnitInGroup(f, .picked) == false) then set nX = GetUnitX(f) set nY = GetUnitY(f) //now we calculate the distance between f and our current target //note that: d^2 = (x2-x1)^2 + (y2-y1)^2 //I avoid using a square to make this faster set d = (nX - cX)*(nX - cX) + (nY - cY)*(nY - cY) //if this new distance is the smallest, then we update our target if (d < minDist) then set minDist = d set ret = f endif endif endloop set .vic = ret set ret = null endmethod method ChainEffect takes nothing returns nothing //we add the victim to the victims groups, so we won't pick it twice call GroupAddUnit(.picked, .vic) //here we call the function responsable for the bad things we do to the bad guys xD call .TargetEffect() //now we increase the counter to know how many units we hit set .targNum = .targNum + 1 //if the number of our current target is lower than the maximum amount //of targets we can hit, we continue, else we end everything if (.targNum < TargetsNumber(.level)) then //pick new target ! call .NextTarget() //if the new unit is not null, we repeat this step, else we end if (.vic != null) then call .SetProjectile() else set .done = true endif else set .done = true endif endmethod method MoveMissile takes nothing returns nothing local real x1 = GetUnitX(.missile) local real x2 = GetUnitX(.vic) local real y1 = GetUnitY(.missile) local real y2 = GetUnitY(.vic) local real dx = TIMER_CYCLE * (x2 - x1) / .wait local real dy = TIMER_CYCLE * (y2 - y1) / .wait call SetUnitX(.missile, x1 + dx) call SetUnitY(.missile, y1 + dy) //here we set the facing of the missile call SetUnitFacing(.missile, bj_RADTODEG * Atan2(y2 - y1, x2 - x1)) set .wait = .wait - TIMER_CYCLE //this is when the missile gets to the unit if .wait < TIMER_CYCLE then call SetUnitX(.missile, x2) call SetUnitY(.missile, y2) //This runs the ChainEffect function again! call .ChainEffect() endif endmethod method onDestroy takes nothing returns nothing //we clear the gorup so we can use it later call GroupClear(.picked) //we destroy the projectile call ShowUnit(.missile, false) call KillUnit(.missile) endmethod endstruct //=========================================================================== globals private SpellData array datas endglobals //=========================================================================== private function Periodic takes nothing returns nothing local integer currentIndex = 0 local SpellData currentInstance loop set currentInstance = datas[currentIndex] call currentInstance.MoveMissile() //if our instance is done, we decrement the number of total instances //and then we check if there are any more instances being run if (currentInstance.done) then set instancesCount = instancesCount - 1 //if there are, then we update our instance to the next of the array //and we correct the index if (instancesCount > 0) then set datas[currentIndex] = datas[instancesCount] set currentIndex = currentIndex - 1 //else we just release the timer else call ReleaseTimer(t) endif //now before we leave current instance for good, we destroy it! call currentInstance.destroy() endif set currentIndex = currentIndex + 1 exitwhen currentIndex >= instancesCount endloop endfunction //=========================================================================== private function Conditions takes nothing returns boolean if GetSpellAbilityId() == AID then //if there are no instances of the spell then it means the timer does not //exist, so we create it and start it! if instancesCount == 0 then set t = NewTimer() call TimerStart(t, TIMER_CYCLE, true, function Periodic) endif set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit()) call datas[instancesCount].SetProjectile() set instancesCount = instancesCount + 1 endif return false endfunction //=========================================================================== private function Init takes nothing returns nothing local trigger LightningTrg = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( LightningTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( LightningTrg, Condition( function Conditions ) ) //setting globals set b = Condition(function AceptedTargets) set g = CreateGroup() set instancesCount = 0 //Preload the effects call Preload(DRAIN_EFFECT) call Preload(BLOOD_EFFECT) //preloading units and spells set bj_lastCreatedUnit = CreateUnit(Player(0), DUM_ID, 0, 0, 0) call UnitAddAbility(bj_lastCreatedUnit, DUM_AB) call KillUnit(bj_lastCreatedUnit) call KillUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), MISSILE_ID, 0, 0, 0)) endfunction endscope I can update the first post though. I also have a test map I can post, if you people find necessary or if you people want to have fun testing an unbalanced spell xD |
