retired coder | real ilfe
Join Date: Mar 2007
Posts: 2,208
|
Dark Lightning
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:
Hidden information:
History
Versions 1.0, 1.2, 1.3, 1.4:
- This versions all had different approaches to try solving the same problem "How to make a lightning spell ?" but they all proved either to be inefficient or to fail
- Yes, I actually remade from zero the spell 3 times (without counting with version 1.0)
Version 1.5:
- First release to the spell Olympics
- Math formula improved
- Removed a useless timer
- Cleaned and commented the code
Version 1.6:
- The Math formula for damage reduction was still, wrong, so now it is corrected and now most things work as they should
Version 1.7:
- Code optimizations
- Fixed a glitch with the dummy unit, now it actually dies
Version 1.8:
- Made the function Targets easier to use
- Now the code has its own algorithm and re uses groups
- Added new function SetProjectile
- Many other minor code fixes and updates were done
- Added Anitarf to the credits
Version 1.9
- Changed the model of the missile to make it look better and now it works with flying units by changing its flying height properly
- Now the missile always faces the direction of the victim
- Improved the map and the terrain
- Changed the algorithm for picking units, now he picks close units to the target
- Now the effects appear on the units
- Now I also preload the units missile and the dummy
Version 2.0
- Now the abilities for the dummy unit are also preloaded
- Replaced SetUnitPosition with SetUnitX and SetUnitY, now the spell is faster!
Version 2.1
- Replaced some variables in order to make the preloading of the dummy unit faster.
- Cleaned the code and deleted old comments and code fragments as well as eliminated the "data = this" laziness
Version 2.2
- Now the code really works well with 1 single timer. Thx Pyrogasm and Anitarf!
- This spell would have not been possible without the people on the credits, Anitarf, Daelin, Deaod and Pyrogasm, thx to you all!
Version 2.3:
- Fixed a leak in method NextTarget
- Moved the timer code to the struct
- Transformed some of the SETUP functions into constants
- Fixed a spelling mistake , replaced TIMER_CICLE, by TIMER_CYCLE
- Added JESP document
Version 2.31:
- Fixed the tooltips of the spell
Version 2.32:
- Updated for patch 1.24
|
 JASS:
scope DarkLightning initializer Init
globals
private constant integer AID = 'A000'
private constant real SPEED = 700.
private constant integer MISSILE_ID = 'h000'
private constant integer DUM_ID = 'h001'
private constant integer DUM_AB = 'A001'
private constant string DUM_ORDER = "animatedead"
private constant real TIMER_CYCLE = 0.03
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
private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL
endglobals
private constant function Range takes integer level returns real
return 500. + (level * 0)
endfunction
private constant function Damage takes integer level returns real
return 100. * level
endfunction
private constant function Heal takes integer level, real damage returns real
return level * 0.33 * damage
endfunction
private constant function Reduction takes integer level returns real
return 0.15 + (level * 0)
endfunction
private constant function TargetsNumber takes integer level returns integer
return 4 + (level * 1)
endfunction
private function Targets takes unit caster, unit target returns boolean
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
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
unit vic
integer level
group picked
unit missile
real wait
integer targNum
real lastDamage
boolean done
static method create takes unit caster, unit vic returns SpellData
local SpellData data = SpellData.allocate()
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
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)
set .wait = d / SPEED
call SetUnitFlyHeight(.missile, GetUnitFlyHeight(.vic), (GetUnitFlyHeight(.missile) - GetUnitFlyHeight(.vic)) / .wait)
endmethod
method TargetEffect takes nothing returns nothing
local unit dum
if (.targNum == 0) then
set .lastDamage = Damage(.level)
else
set .lastDamage = .lastDamage - (.lastDamage * Reduction(.level))
endif
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"))
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
local real cX = GetUnitX(.vic)
local real cY = GetUnitY(.vic)
local real nX
local real nY
local real minDist = Range(.level)*Range(.level)
local real d
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)
set d = (nX - cX)*(nX - cX) + (nY - cY)*(nY - cY)
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
call GroupAddUnit(.picked, .vic)
call .TargetEffect()
set .targNum = .targNum + 1
if (.targNum < TargetsNumber(.level)) then
call .NextTarget()
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)
call SetUnitFacing(.missile, bj_RADTODEG * Atan2(y2 - y1, x2 - x1))
set .wait = .wait - TIMER_CYCLE
if .wait < TIMER_CYCLE then
call SetUnitX(.missile, x2)
call SetUnitY(.missile, y2)
call .ChainEffect()
endif
endmethod
method onDestroy takes nothing returns nothing
call GroupClear(.picked)
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 (currentInstance.done) then
set instancesCount = instancesCount - 1
if (instancesCount > 0) then
set datas[currentIndex] = datas[instancesCount]
set currentIndex = currentIndex - 1
else
call ReleaseTimer(t)
endif
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 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 ) )
set b = Condition(function AceptedTargets)
set g = CreateGroup()
set instancesCount = 0
call Preload(DRAIN_EFFECT)
call Preload(BLOOD_EFFECT)
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
__________________
|