|
|
#1 | |
|
retired coder | real ilfe
|
Ok guys, this is another one of my spell for the spell Olympics. Although I am totally sure I can't win against Anitarf nor Griffen (my freaking Nemesis) I still think this is a spell people should see and I believe some of you may even like it.
The code is well commented, and I feel a system can be explored from the code. Unlike many other spells, I prepared this code for the user, this means the user is not limited only to the SETUP part, I also want the user to enter the code's core so he can also learn from my code and mainly make even better lightning spells. This is mainly why my code has many methods and why it is so divided, so people can actually use an easy and efficient "Divide and Conquer" strategy. This makes sections of the code easier to change. I would really like to see this spell approved, it is my first working lightning spell, and it passed nearly through hell to make it work for Olympics. I just feel bad I actually delivered a not perfect version of this spell... The Math formula is not what I exactly pretended, but it was what I could do with the time I had. Please enjoy and be nice on comments, I put a lot of effort into making this spell for you, the user. Credits: This time I feel forced to tell explicitly the people who helped me making this spell. Note that without them this spell would have not been possible to make. Thx to Anitarf and Pyrogasm, both of them wasted many hours seeing my code, I can never thank them enough for what they did. I would also like to thank Daelin for his outdated, but useful math formulas and to Deaod as well. Description: A JESP spell that allows the user to create lightning spells with any model he desires. In this sample, the caster sends a dark projectile which will damage enemy units and heal the caster by the 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 or a hero. Requirements: - Jass NewGen Pack (uses vJASS) - TimerUtils History:
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.32 //=========================================================================== 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 Last edited by Flame_Phoenix : 08-06-2009 at 09:50 AM. |
|
|
|
|
| Sponsored Links - Login to hide this ad! |
|
|
|
|
#2 |
|
retired coder | real ilfe
|
NOTE: No, I didn't moved Period to inside the structure. Yet Version 2.3 was heavily improved, I changed everything as required! I even did things I didn't need to do, just to please the damn mods !!! This should already be approved by Moyack, but I decided to follow an evil path and to make more modifications so it could be better. I would like a LOT if some one could finally approve this damn spell for once without forcing me to quote Moyack. Please keep in mind the heavy modifications this spell suffered ...
__________________Last edited by Flame_Phoenix : 01-25-2009 at 12:31 PM. |
|
|
|
|
|
#3 |
|
Procrastination Incarnate
Development Director
|
Fine, whatever. Approved. It's still ugly code and I might feel disinclined to approve similar stuff in the future.
__________________Your tooltips are incorrect, by the way. |
|
|
|
|
|
#4 | ||
|
retired coder | real ilfe
|
Quote:
Quote:
EDIT EDIT EDIT Spell was updated, tooltips are now correct. I am going to be as honest as I can be now... Truth is that I look at this spell almost as defeat. I know that Anitarf felt quite forced to approve this, I am definitely not proud of it. Sometimes I just can't have it all I guess. On the other hand, I can now move with my life to a new stage and submit more spells to the community, better spells I hope. I will start by the seal spell, I will fix stuff Pyro asked and re-submit it. After that I have a channel spell in mind that I hope people like. And I don't feel inclined to make all things with only 1 timer so far. Last edited by Flame_Phoenix : 02-09-2009 at 06:57 PM. |
||
|
|
|
|
|
#5 |
|
retired coder | real ilfe
|
Updated for patch 1.24. The script does not need any changes, I only updated TimerUtils.
__________________ |
|
|
|
![]() |
| Thread Tools | Search this Thread |
|
|
|
Donate |