| 09-22-2008, 08:51 PM | #1 | |
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 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. // //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 // //@version 2.2 //=========================================================================== 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" 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 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 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 //here we add the caster to the picked group, just in case call GroupAddUnit(data.picked, data.vic) 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, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null) call DestroyEffect(AddSpecialEffectTarget(DRAIN_EFFECT, .vic, "origin")) call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT, .caster, "origin")) //Heal the caster call SetWidgetLife(.caster, GetWidgetLife(.caster) + .lastDamage) if (GetWidgetLife(.vic) < 0.405) and (IsUnitType(.vic, UNIT_TYPE_HERO) == false) and (IsUnitType(.vic, UNIT_TYPE_SUMMONED) == 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 unit 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 local real minDist = 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 set d = SquareRoot((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 return ret 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 ! set .vic = .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 ) ) set LightningTrg = null //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(0), MISSILE_ID, 0, 0, 0)) endfunction endscope |
| 09-24-2008, 01:00 PM | #2 |
In the SpellData struct, why do you create&destroy the group when you could reuse it? The struct SpellData doesn't really need the targMaxNum member since you can always get that value based on the spell's level. The ChainEffect function should take a SpellData parameter, instead of getting it from the expired timer again. If you're so keen on modularization, then why didn't you make this code a seperate function, since you use it twice? JASS:set a = GetUnitX(data.missile) - GetUnitX(data.vic) set b = GetUnitY(data.missile) - GetUnitY(data.vic) set d = SquareRoot(a*a + b*b) //the distance between "a" and "b" set data.wait = d / SPEED //start moving the damn missile call SetTimerData(data.t, integer(data)) call TimerStart(data.t, TIMER_CICLE, true, function MoveMissile) Why do functions TargetEffect and NextTarget take an integer parameter, why can't they just take a SpellData parameter? Why do you have the hp local variable in the function TargetEffect? You could just use data.lastDamage instead of it. You don't give an expiration timer to your dummy casters, also, shouldn't you check if the victim was a summoned unit or a hero before creating them? I think the calibration function for targets would look better if instead of using GetFilterUnit() it took parameters caster and target, it would look cleaner this way. This spell could benefit a lot from using xe. |
| 09-24-2008, 09:04 PM | #3 | |||||||||
Quote:
Quote:
Quote:
Quote:
However if you really want me to do it, I will do it. Main reason is that I didn't have time to change it, and now I really can't find a strong reason to do it =P Quote:
However (once again) if I really need to change this to see my spell approved you know I will... Quote:
Quote:
Well, that would be just complicating the spell... nothing will really change, I will create a dummy unit for nothing, so what ? ... Bah, Anyway, I know that if I don't change this you are going to be mad at me or just think I am lazy so, I fixed that too lol... Quote:
However, if you know of away that doesn't change the spell's core drastically, I may implement it (as long as it also doesn't reduce efficiency a lot too xD). And don't use that modular thing as an excuse for your evil reasons, I believe there efficiency and code easy to understand like hell DO have a balance between xD Quote:
So, who would teach me ? Vexorian ? Let's be realistic, he wouldn't do it, he hates noobs who can't understand his "easy commented" code... Pyrogasm ? I haven't see him for ages I fear this is the price of honesty, but tonight I will sleep with my conscience clean, at least I don't give illusions to people. I will not implement xe, at least not for now. I updated the code Anitarf, I hope you like it good enough to approve it now. |
| 09-24-2008, 09:36 PM | #4 | ||||||
Quote:
You understand that structs are just parallel arrays where each index can be a struct, right? Well, when you destroy a struct instance it's index gets released and may be reused the next time you create a struct of that type. Therefore, if you don't destroy a group that is stored in a struct's member when you destroy the struct, that group will still be there when a new struct gets the old struct's index. So it's simple. Quote:
Quote:
Quote:
Quote:
Quote:
|
| 09-24-2008, 11:09 PM | #5 |
I'm here, lulz. My advice: read the documentation. xe isn't really that hard to understand and it's DAMN useful. |
| 09-25-2008, 10:33 AM | #6 | ||||||
Quote:
Quote:
Quote:
Quote:
Quote:
Quote:
I may use xe, but for now I just want to see my pending spells approved. |
| 09-25-2008, 11:05 AM | #7 | ||
Quote:
Quote:
|
| 09-25-2008, 08:27 PM | #8 | ||
Quote:
I am even most surprised this works at all with that ! Quote:
I find it weird, you guys are also so keen with super efficient spells and codes, and now you want me to make something slower lol. Anyway, I don't give speed that much importance, and I kinda like the idea, so it's done. Ok guys, the new version of code is now released. I corrected everything as asked (except the "xe"thing, but I hope you all understand, I can't do it right now. Maybe later I will transform this into a template, and there I will use that thing). Please just see my recycle group thing in the structure, I think it works because I remember seeing a code made by Moycak that looks like that, but I am not sure. Be kind on comments as usual, and I hope now I changed the spell enough to please you. |
| 10-04-2008, 08:58 AM | #9 |
48 hours passed: - bumb for justice ! - |
| 10-04-2008, 02:19 PM | #10 |
Stop bumping your thread. It will get looked at in due course. The more impatient you are, the more towards the back of the queue you will go. Now stop being impatient and stop insulting the mods. |
| 10-04-2008, 04:41 PM | #11 | |
Quote:
1 - bumb is allowed after 48 hours. If you want to punish me for bumb, you will have to punish Vestras (per example) as well, he also does it and many other people do it. Besides this is the 1st time I successfully bumb this thread. 2 - I am not impatient. I just noticed mods were forgetting this section due their long time of inactivity. I don't want this to become THW spell submission thread. 3 - I insulted no one here. I only spoke the truth of what I was observing. If you don't like the truth don't blame me, I didn't caused it. Besides, saying the truth was never an insult. Anyway, post deleted, I understand your position as a mod. |
| 10-04-2008, 05:06 PM | #12 |
Mods are not forgetting this section, their priorities are just different. This is a busy period for various mods for various reasons. Mods are not "lazy" as you alledge - they put in far more work than can actually be reasonably expected of anyone. |
| 10-04-2008, 09:39 PM | #13 | |
Quote:
Besides, when you say "mods work very hard", as far as I know, I only see Anitarf working hard, all other mods either seem to disappear for long periods of time or they just don't help much ... I face I don't know all mods from Wc3c, but if you give a quick look to the spell submissions, you will in fact conclude that it is Anitarf who is doing most of the job .. alone... You don't even need to go that far, just look at this thread.... who has been the only mod to helping me ?? Take a wild guess - Anitarf. |
| 10-04-2008, 09:43 PM | #14 |
I don't think you've quite gotten it, yet, have you? Mods work for the site and its users for free; if you ever actually want this to get reviewed, I'd suggest you stop whining about the staff. |
| 10-04-2008, 09:45 PM | #15 | |||
Quote:
Allow me to quote myself: Quote:
Quote:
Anyway, I feel this is getting a lot off-topic, I believe both of us should stop now ... |
