I posted a video of the twisted fate spellpack from awhile back to reddit and received an overwhelmingly positive response. Everyone voted that Lee Sin should be next, and thus, here it is. Enjoy.
Notes:
I've created this pack with the intention that the abilities matched the real ones in league of legends as close as possible. I have not modified game constants to make level 18 stats match those of league of legends identically, I just aimed for values that were "close enough" by choosing good stats and stats per level, etc.
The real knockback effect created by Lee Sin's Dragon's Rage ability can knock targets through walls *given that the endpoint is in pathable terrain*. I've decided not to implement this to avoid getting units stuck on the test map. If I create another league of legends champion which also needs a league style knockback, I'll create a unifying library for that.
I've chosen not to follow exactly trivial code modification standards for his spells because this is designed to match twisted fate with extreme accuracy, not designed for others to modify.
Yes, this is fully MUI. Try not to drool on your keyboard.
Description of Spells:
Flurry (Innate): After using an ability, Lee Sin's next 2 basic attacks within 3 seconds gain 40% attack speed and return 15 energy each.
Sonic Wave (Active): Lee Sin shoots a wave of sound in a line, dealing physical damage to the first enemy it hits and revealing the enemy for 3 seconds. If Sonic Wave hits, Lee Sin can cast Resonating Strike within the next 3 seconds.
Resonating Strike (Active): Lee Sin dashes to the enemy revealed by Sonic Wave, dealing physical damage including 8% of the target's missing health. Minions and monsters take a maximum of 400 damage.
Safeguard (Active): Lee Sin dashes to target ally, he and the target each gaining a shield that absorbs a certain amount of damage. Each shield dissipates after 5 seconds if not already destroyed. Lee Sin can cast Iron Will within 3 seconds of using Safeguard.
Iron Will (Active): For 5 seconds, Lee Sin gains bonus life steal and spell vamp.
Tempest (Active): Lee Sin smashes the ground, dealing magic damage to all nearby enemies and revealing them for 4 seconds. If Tempest hits an enemy, Lee Sin can cast Cripple within the next 3 seconds.
Cripple (Active): Lee Sin cripples all enemies revealed by Tempest reducing their movement and attack speed. The movement and attack speed recover over 4 seconds.
Dragon's Rage (Active): Lee Sin deals physical damage to a target champion and knocks the target back 1200 units over 1 second. Enemies the target collides with take the same damage and are knocked airborne for 1 second.
privatefunction p takesnothingreturnsnothing localunit FoG callGroupEnumUnitsInRect(grp,bj_mapInitialPlayableArea,null) loop set FoG=FirstOfGroup(grp) exitwhen FoG==null ifGetUnitTypeId(FoG)==LEE_SIN_ID andGetUnitState(FoG,UNIT_STATE_MANA)>200.then callSetUnitState(FoG,UNIT_STATE_MANA,200.) endif callGroupRemoveUnit(grp,FoG) endloop endfunction
publicfunction getBonusAD takesunit u returnsreal localreal damage if realDamage.exists(u)then set damage=I2R(realDamage[u])-(52.8+(3.2*GetHeroLevel(u))) if damage>0.then return damage else return0. endif debugelse debugDisplayTextToPlayer(GetLocalPlayer(),0.,0.,"ERROR: Tried to get Bonus AD from a non-indexed unit!") endif return-1. endfunction
privatefunction preloadAfter takesnothingreturnsnothing localtimer time=GetExpiredTimer() local slop s=preloadDB[time] localsound sd=CreateSound(s.snd,false,false,false,12700,12700,"") callSetSoundVolume(sd,1) callStartSound(sd) callKillSoundWhenDone(sd) call s.destroy() set sd=null callDestroyTimer(time) set time=null endfunction
publicfunction preloadSound takesstring str returnsnothing localtimer time=CreateTimer() local slop s=slop.create() set s.snd=str set preloadDB[time]=s callTimerStart(time,.01,false,function preloadAfter) set time=null endfunction
publicfunction unsilenceUnit takesunit u returnsnothing callUnitRemoveAbility(u,SILENCED_ID) endfunction
privatefunction newDamage takesnothingreturnsnothing localunit dS=GetEventDamageSource() localreal damage=GetEventDamage() ifGetUnitTypeId(dS)==LEE_SIN_ID and DamageType_get(damage)==DamageType_ATTACK then set realDamage[dS]=R2I(damage) endif set dS=null endfunction
privatefunction c takesnothingreturnsboolean localunit tU=GetTriggerUnit() if realDamage.exists(tU)==falsethen set realDamage[tU]=EXPECTED_L1_DAMAGE endif returnfalse endfunction
privatefunction i takesnothingreturnsnothing localunit FoG localtrigger registerNewLees=CreateTrigger() localregion reg=CreateRegion() set dummy=CreateUnit(Player(13),CASTER_ID,0.,0.,0.) callRegionAddRect(reg,bj_mapInitialPlayableArea) callTriggerRegisterEnterRegion(registerNewLees,reg,null) callTriggerAddCondition(registerNewLees,Condition(function c)) callTimerStart(CreateTimer(),ENERGY_FIDELITY,true,function p) callGroupEnumUnitsInRect(grp,bj_mapInitialPlayableArea,null) set realDamage=HandleTable.create() set preloadDB=HandleTable.create() call StructuredDD_ddBucket.addHandler(function newDamage) loop set FoG=FirstOfGroup(grp) exitwhen FoG==null ifGetUnitTypeId(FoG)==LEE_SIN_ID then set realDamage[FoG]=EXPECTED_L1_DAMAGE endif callGroupRemoveUnit(grp,FoG) endloop set registerNewLees=null set reg=null endfunction endlibrary
flurry
Jass:
scope flurry initializer i privatestruct flurDat unit lee timer time integer attackCounter endstruct
privatefunction handler takesnothingreturnsnothing localunit dS=GetEventDamageSource() local flurDat tempDat if DamageType_get(GetEventDamage())==DamageType_ATTACK andGetUnitTypeId(dS)==leeSinConsts_LEE_SIN_ID and fromLee.exists(dS)then set tempDat=fromLee[dS] set tempDat.attackCounter=tempDat.attackCounter+1 callSetUnitState(tempDat.lee,UNIT_STATE_MANA,GetUnitState(tempDat.lee,UNIT_STATE_MANA)+15.) if tempDat.attackCounter==2then callUnitRemoveAbility(dS,leeSinConsts_BONUS_ATTACK_SPEED_ID) callPauseTimer(tempDat.time) endif endif set dS=null endfunction
privatefunction after takesnothingreturnsnothing local flurDat tempDat=fromTime[GetExpiredTimer()] callUnitRemoveAbility(tempDat.lee,leeSinConsts_BONUS_ATTACK_SPEED_ID) endfunction
privatefunction c takesnothingreturnsboolean localunit tU=GetTriggerUnit() local flurDat tempDat ifGetUnitTypeId(tU)==leeSinConsts_LEE_SIN_ID then if fromLee.exists(tU)then set tempDat=fromLee[tU] callPauseTimer(tempDat.time) else set tempDat=flurDat.create() set tempDat.lee=tU set tempDat.time=CreateTimer() set fromLee[tU]=tempDat set fromTime[tempDat.time]=tempDat endif set tempDat.attackCounter=0 callUnitAddAbility(tempDat.lee,leeSinConsts_BONUS_ATTACK_SPEED_ID) callTimerStart(tempDat.time,TIMEOUT,false,function after) endif returnfalse endfunction
privatefunction i takesnothingreturnsnothing localtrigger t=CreateTrigger() callTriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(t,Condition(function c)) call StructuredDD_ddBucket.addHandler(function handler) set fromLee=HandleTable.create() set fromTime=HandleTable.create() set t=null endfunction endscope
sonicWave
Jass:
scope sonicWave initializer i privatestruct struckDat unit hit unit caster real timeleft integer abilityReturnValue endstruct
privatestruct dashDat unit caster unit target real damage endstruct
privatestruct waveDat unit wave unit caster real delX real delY real damage integer stepsLeft player owner endstruct
privatefunction p2 takesnothingreturnsnothing local struckDat tempDat localinteger index=0 localreal cX localreal cY localreal hX localreal hY localreal dist loop exitwhen index>sDBIndex set tempDat=struckDB[index] set tempDat.timeleft=tempDat.timeleft-leeSinConsts_FPS set hX=GetUnitX(tempDat.hit) set hY=GetUnitY(tempDat.hit) set cX=GetUnitX(tempDat.caster) set cY=GetUnitY(tempDat.caster) set dist=SquareRoot((cX-hX)*(cX-hX)+(cY-hY)*(cY-hY)) if tempDat.timeleft<0.orGetUnitAbilityLevel(tempDat.caster,leeSinConsts_RESONATING_STRIKE_ID)<1or UnitAlive(tempDat.hit)==falseor dist>RESONATING_STRIKE_RANGE then callUnitRemoveAbility(tempDat.caster,leeSinConsts_RESONATING_STRIKE_ID) callSetUnitAbilityLevel(tempDat.caster,leeSinConsts_SONIC_WAVE_ID,tempDat.abilityReturnValue) call tempDat.destroy() set struckDB[index]=struckDB[sDBIndex] set sDBIndex=sDBIndex-1 set index=index-1 if sDBIndex==-1then callPauseTimer(secondTimer) endif endif set index=index+1 endloop endfunction
privatefunction p takesnothingreturnsnothing localinteger index=0 local waveDat tempDat local struckDat struck localunit FoG localreal uX localreal uY localboolean hitCheck localsound snd localboolean notWard loop exitwhen index>dbIndex set tempDat=waveDB[index] set uX=GetUnitX(tempDat.wave)+tempDat.delX set uY=GetUnitY(tempDat.wave)+tempDat.delY callSetUnitX(tempDat.wave,uX) callSetUnitY(tempDat.wave,uY) set tempDat.stepsLeft=tempDat.stepsLeft-1 callGroupEnumUnitsInRange(grp,uX,uY,ENUM_RADIUS,null) set hitCheck=false loop set FoG=FirstOfGroup(grp) exitwhen FoG==nullor hitCheck set notWard=true staticif leeSinConsts_IS_TEST_MAP then ifGetUnitTypeId(FoG)==WARD_ID then set notWard=false endif endif ifIsUnitEnemy(FoG,tempDat.owner)and UnitAlive(FoG)andIsUnitType(FoG,UNIT_TYPE_STRUCTURE)==falseandIsUnitType(FoG,UNIT_TYPE_GROUND)and notWard then set hitCheck=true set snd=CreateSound(HIT_SOUND,false,true,true,12700,12700,"") callSetSoundPosition(snd,GetUnitX(FoG),GetUnitY(FoG),50.) callStartSound(snd) callKillSoundWhenDone(snd) call DamageType_dealCodeDamage(tempDat.caster,FoG,tempDat.damage,DAMAGE_TYPE_NORMAL) call leeSinConsts_revealUnit(FoG,tempDat.owner,false) set struck=struckDat.create() set struck.hit=FoG set struck.caster=tempDat.caster set struck.timeleft=STRIKE_LASTING_TIME set struck.abilityReturnValue=GetUnitAbilityLevel(tempDat.caster,leeSinConsts_SONIC_WAVE_ID) set sDBIndex=sDBIndex+1 set struckDB[sDBIndex]=struck set fromLee[tempDat.caster]=struck callSetUnitAbilityLevel(tempDat.caster,leeSinConsts_SONIC_WAVE_ID,5) callIncUnitAbilityLevel(tempDat.caster,leeSinConsts_SONIC_WAVE_ID) callUnitAddAbility(tempDat.caster,leeSinConsts_RESONATING_STRIKE_ID) if sDBIndex==0then //This could easily be reduced to a much slower period to improve performance, but to avoid confusing players I want it as smooth as //possible. To make Lee Sin the way he used to be (unlimited dash range) you can change "leeSinConsts_FPS" to "3." - the same must be //done in function p2 callTimerStart(secondTimer,leeSinConsts_FPS,true,function p2) endif set snd=null endif callGroupRemoveUnit(grp,FoG) endloop if tempDat.stepsLeft==0or hitCheck then callKillUnit(tempDat.wave) call tempDat.destroy() set waveDB[index]=waveDB[dbIndex] set dbIndex=dbIndex-1 set index=index-1 if dbIndex==-1then callPauseTimer(time) endif endif set index=index+1 endloop endfunction
privatefunction c takesnothingreturnsboolean localreal tX localreal tY localreal uX localreal uY localunit tU localreal angle local waveDat tempDat localplayer who localsound snd ifGetSpellAbilityId()==leeSinConsts_SONIC_WAVE_ID then set tempDat=waveDat.create() set tempDat.caster=GetTriggerUnit() set tX=GetSpellTargetX() set tY=GetSpellTargetY() set uX=GetUnitX(tempDat.caster) set uY=GetUnitY(tempDat.caster) set angle=Atan2(tY-uY,tX-uX) set who=GetOwningPlayer(tempDat.caster) set snd=CreateSound(LAUNCH_SOUND,false,true,true,12700,12700,"") callSetSoundVolume(snd,127) callSetSoundPitch(snd,.88) callSetSoundPosition(snd,uX,uY,50.) callStartSound(snd) callKillSoundWhenDone(snd) set snd=null set tempDat.wave=CreateUnit(who,leeSinConsts_SONIC_WAVE_DUMMY_ID,uX+INITIAL_OFFSET*Cos(angle),uY+INITIAL_OFFSET*Sin(angle),angle*bj_RADTODEG) callSetUnitX(tempDat.wave,uX+INITIAL_OFFSET*Cos(angle)) callSetUnitY(tempDat.wave,uY+INITIAL_OFFSET*Sin(angle)) set tempDat.delX=SPEED*leeSinConsts_FPS*Cos(angle) set tempDat.delY=SPEED*leeSinConsts_FPS*Sin(angle) set tempDat.stepsLeft=R2I(DISTANCE/SPEED/leeSinConsts_FPS) set tempDat.damage=20.+30.*GetUnitAbilityLevel(tempDat.caster,leeSinConsts_SONIC_WAVE_ID)+.9*leeSinConsts_getBonusAD(tempDat.caster) set tempDat.owner=who set dbIndex=dbIndex+1 set waveDB[dbIndex]=tempDat if dbIndex==0then callTimerStart(time,leeSinConsts_FPS,true,function p) endif endif returnfalse endfunction
privatefunction dashP takesnothingreturnsnothing localinteger index=0 local dashDat tempDat localreal cX localreal cY localreal tX localreal tY localreal delX localreal delY localreal angle localreal dist localsound snd loop exitwhen index>dDBIndex set tempDat=dashDB[index] set cX=GetUnitX(tempDat.caster) set cY=GetUnitY(tempDat.caster) set tX=GetUnitX(tempDat.target) set tY=GetUnitY(tempDat.target) set angle=Atan2(tY-cY,tX-cX) callSetUnitFacing(tempDat.caster,angle*bj_RADTODEG) set delX=DASH_SPEED*leeSinConsts_FPS*Cos(angle) set delY=DASH_SPEED*leeSinConsts_FPS*Sin(angle) set dist=SquareRoot((tX-(cX+delX))*(tX-(cX+delX))+(tY-(cY+delY))*(tY-(cY+delY))) if dist>DASH_ARRIVE_DISTANCE then callSetUnitX(tempDat.caster,cX+delX) callSetUnitY(tempDat.caster,cY+delY) endif if dist<DASH_ARRIVE_DISTANCE or dist<(2.*DASH_SPEED*leeSinConsts_FPS)then call DamageType_dealCodeDamage(tempDat.caster,tempDat.target,tempDat.damage,DAMAGE_TYPE_NORMAL) callDestroyEffect(AddSpecialEffect(HIT_EFFECT,tX,tY)) set snd=CreateSound(DASH_SOUND,false,true,true,12700,12700,"") callSetSoundVolume(snd,127) callSetSoundPitch(snd,.75) callSetSoundPosition(snd,tX,tY,50.) callStartSound(snd) callKillSoundWhenDone(snd) set snd=null set dashDB[index]=dashDB[dDBIndex] set dDBIndex=dDBIndex-1 set index=index-1 call tempDat.destroy() if dDBIndex==-1then callPauseTimer(dashTimer) endif endif set index=index+1 endloop endfunction
privatefunction resC takesnothingreturnsboolean local struckDat tempDat local dashDat dash ifGetSpellAbilityId()==leeSinConsts_RESONATING_STRIKE_ID then set tempDat=fromLee[GetTriggerUnit()] callUnitRemoveAbility(tempDat.caster,leeSinConsts_RESONATING_STRIKE_ID) set dash=dashDat.create() set dash.caster=tempDat.caster set dash.target=tempDat.hit set dash.damage=20.+30.*tempDat.abilityReturnValue+.9*leeSinConsts_getBonusAD(dash.caster)+.08*(GetUnitState(dash.target,UNIT_STATE_MAX_LIFE)-GetUnitState(dash.target,UNIT_STATE_LIFE)) set dDBIndex=dDBIndex+1 set dashDB[dDBIndex]=dash if dDBIndex==0then callTimerStart(dashTimer,leeSinConsts_FPS,true,function dashP) endif endif returnfalse endfunction
privatefunction i takesnothingreturnsnothing localtrigger t=CreateTrigger() localtrigger resStrike=CreateTrigger() set fromLee=HandleTable.create() call leeSinConsts_preloadSound(HIT_SOUND) call leeSinConsts_preloadSound(LAUNCH_SOUND) call leeSinConsts_preloadSound(DASH_SOUND) callTriggerRegisterAnyUnitEventBJ(resStrike,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(resStrike,Condition(function resC)) callTriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(t,Condition(function c)) set t=null set resStrike=null endfunction endscope
safeguard
Jass:
scope safeguard initializer i privatestruct willDat unit who integer levelReturnAbility real timeLeft endstruct
privatestruct dashDat unit caster unit target integer levelReturnAbility endstruct
privatefunction abilitySwapCheckP takesnothingreturnsnothing localinteger index=0 local willDat tempDat loop exitwhen index>willDBIndex set tempDat=willDB[index] set tempDat.timeLeft=tempDat.timeLeft-leeSinConsts_FPS if tempDat.timeLeft<=0.orGetUnitAbilityLevel(tempDat.who,leeSinConsts_IRON_WILL_ID)<1then callUnitRemoveAbility(tempDat.who,leeSinConsts_IRON_WILL_ID) callSetUnitAbilityLevel(tempDat.who,leeSinConsts_SAFEGUARD_ID,tempDat.levelReturnAbility) call tempDat.destroy() set willDB[index]=willDB[willDBIndex] set willDBIndex=willDBIndex-1 set index=index-1 if willDBIndex==-1then callPauseTimer(ironWillTimer) endif endif set index=index+1 endloop endfunction
privatefunction p takesnothingreturnsnothing localinteger index=0 local dashDat tempDat local willDat will localreal cX localreal cY localreal tX localreal tY localreal angle localreal delX localreal delY localreal distSquared localreal amount localsound snd loop exitwhen index>dbIndex set tempDat=dashDB[index] set cX=GetUnitX(tempDat.caster) set cY=GetUnitY(tempDat.caster) set tX=GetUnitX(tempDat.target) set tY=GetUnitY(tempDat.target) set angle=Atan2(tY-cY,tX-cX) set delX=DASH_SPEED*leeSinConsts_FPS*Cos(angle) set delY=DASH_SPEED*leeSinConsts_FPS*Sin(angle) if tempDat.caster!=tempDat.targetthen callSetUnitX(tempDat.caster,cX+delX) callSetUnitY(tempDat.caster,cY+delY) callSetUnitFacing(tempDat.caster,angle*bj_RADTODEG) endif set distSquared=(tX-cX)*(tX-cX)+(tY-cY)*(tY-cY) if distSquared<TARGET_DISTANCE_SQUARED or distSquared<((DASH_SPEED*leeSinConsts_FPS)*(DASH_SPEED*leeSinConsts_FPS)*2.)then set dashDB[index]=dashDB[dbIndex] set amount=40.*tempDat.levelReturnAbility+.8*GetHeroInt(tempDat.caster,true) call Shield.add(tempDat.caster,amount,SHIELD_DURATION,Shield.DEFAULT_EFFECT) if tempDat.caster!=tempDat.targetthen call Shield.add(tempDat.target,amount,SHIELD_DURATION,Shield.DEFAULT_EFFECT) set snd=CreateSound(DASH_END_SOUND,false,true,true,12700,12700,"") callSetSoundPosition(snd,tX,tY,60.) callSetSoundVolume(snd,127) callStartSound(snd) callKillSoundWhenDone(snd) set snd=null endif callSetUnitAbilityLevel(tempDat.caster,leeSinConsts_SAFEGUARD_ID,5) callIncUnitAbilityLevel(tempDat.caster,leeSinConsts_SAFEGUARD_ID) callUnitAddAbility(tempDat.caster,leeSinConsts_IRON_WILL_ID) set level[tempDat.caster]=tempDat.levelReturnAbility set will=willDat.create() set will.who=tempDat.caster set will.levelReturnAbility=tempDat.levelReturnAbility set will.timeLeft=IRON_WILL_AVAILABLE_DURATION set willDBIndex=willDBIndex+1 set willDB[willDBIndex]=will call tempDat.destroy() if willDBIndex==0then //I know this isn't the ideal use of periodic timers, but it's efficient enough, and will allow me to re-enable safeguard ASAP. callTimerStart(ironWillTimer,leeSinConsts_FPS,true,function abilitySwapCheckP) endif set dbIndex=dbIndex-1 set index=index-1 if dbIndex==-1then callPauseTimer(time) endif endif set index=index+1 endloop endfunction
privatefunction c takesnothingreturnsboolean local dashDat tempDat localsound snd ifGetSpellAbilityId()==leeSinConsts_SAFEGUARD_ID then set tempDat=dashDat.create() set tempDat.caster=GetTriggerUnit() set tempDat.target=GetSpellTargetUnit() set snd=CreateSound(DASH_BEGIN_SOUND,false,true,true,12700,12700,"") callSetSoundPitch(snd,2.) callSetSoundPosition(snd,GetUnitX(tempDat.caster),GetUnitY(tempDat.caster),50.) callSetSoundVolume(snd,127) callStartSound(snd) callKillSoundWhenDone(snd) set snd=null set tempDat.levelReturnAbility=GetUnitAbilityLevel(tempDat.caster,leeSinConsts_SAFEGUARD_ID) set dbIndex=dbIndex+1 set dashDB[dbIndex]=tempDat if dbIndex==0then callTimerStart(time,leeSinConsts_FPS,true,function p) endif endif returnfalse endfunction
privatefunction willEffectAfter takesnothingreturnsnothing localtimer clock=GetExpiredTimer() local willEffectDat tempDat=attachedWillEffect[clock] call levelWithEffect.flush(tempDat.who) call attachedWillEffect.flush(clock) callDestroyTimer(clock) set clock=null endfunction
privatefunction ironWillC takesnothingreturnsboolean localunit tU local willEffectDat tempDat localtimer clock ifGetSpellAbilityId()==leeSinConsts_IRON_WILL_ID then set tU=GetTriggerUnit() callUnitRemoveAbility(tU,leeSinConsts_IRON_WILL_ID) set levelWithEffect[tU]=level[tU] set tempDat=willEffectDat.create() set tempDat.who=tU set clock=CreateTimer() set attachedWillEffect[clock]=tempDat callTimerStart(clock,WILL_EFFECT_DURATION,false,function willEffectAfter) set tU=null set clock=null endif returnfalse endfunction
privatefunction handler takesnothingreturnsnothing localunit dS=GetEventDamageSource() localreal damage=GetEventDamage() if DamageType_get(damage)!=DamageType_SPELL and levelWithEffect.exists(dS)then callSetUnitState(dS,UNIT_STATE_LIFE,GetUnitState(dS,UNIT_STATE_LIFE)+.05*levelWithEffect[dS]*damage) callDestroyEffect(AddSpecialEffectTarget(VAMP_EFFECT,dS,"origin")) endif set dS=null endfunction
privatefunction i takesnothingreturnsnothing localtrigger t=CreateTrigger() localtrigger ironWill=CreateTrigger() call leeSinConsts_preloadSound(DASH_BEGIN_SOUND) call leeSinConsts_preloadSound(DASH_END_SOUND) set level=HandleTable.create() set levelWithEffect=HandleTable.create() set attachedWillEffect=HandleTable.create() callTriggerRegisterAnyUnitEventBJ(ironWill,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(ironWill,Condition(function ironWillC)) callTriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(t,Condition(function c)) call StructuredDD_ddBucket.addHandler(function handler) set t=null set ironWill=null endfunction endscope
privatestaticmethod p takesnothingreturnsnothing localtimer exp=GetExpiredTimer() localthistype tempDat=fromTimer[exp] set tempDat.duration=tempDat.duration-1 if tempDat.duration>0and UnitAlive(tempDat.who)then call tempDat.setSlow() else callDestroyEffect(tempDat.fx) call tempDat.removeSlow() callDestroyTimer(tempDat.clock) call tempDat.destroy() endif set exp=null endmethod
publicstaticmethod setGreater takesunit who,integer level,integer duration returnsnothing localthistype tempDat if fromUnit.exists(who)then set tempDat=fromUnit[who] if level>tempDat.levelthen set tempDat.level=level endif if duration>tempDat.durationthen set tempDat.duration=duration endif else set tempDat=thistype.create() set fromUnit[who]=tempDat set tempDat.who=who set tempDat.fx=AddSpecialEffectTarget(thistype.CRIPPLE_FX,tempDat.who,"origin") set tempDat.level=level set tempDat.duration=duration set tempDat.clock=CreateTimer() setthistype.fromTimer[tempDat.clock]=tempDat callTimerStart(tempDat.clock,1.,true,functionthistype.p) endif call tempDat.setSlow() endmethod
privatestaticmethod onInit takesnothingreturnsnothing set fromUnit=HandleTable.create() set fromTimer=HandleTable.create() setthistype.caster=CreateUnit(Player(13),leeSinConsts_CASTER_ID,0.,0.,0.) endmethod endstruct
privatestruct unitWrapper unit who endstruct
privatestruct crippleLastDat unit who real timeLeft endstruct
privatefunction afterThree takesnothingreturnsnothing localtimer time=GetExpiredTimer() local crippleLastDat tempDat=fromShimTimer[time] set tempDat.timeLeft=tempDat.timeLeft-leeSinConsts_FPS ifGetUnitAbilityLevel(tempDat.who,leeSinConsts_CRIPPLE_ID)<1or tempDat.timeLeft<=0.or UnitAlive(tempDat.who)==falsethen callUnitRemoveAbility(tempDat.who,leeSinConsts_CRIPPLE_ID) callSetUnitAbilityLevel(tempDat.who,leeSinConsts_TEMPEST_ID,tab[tempDat.who]) callDestroyTimer(time) call tempDat.destroy() endif set time=null endfunction
privatefunction shimAfter takesnothingreturnsnothing localtimer time=GetExpiredTimer() local crippleLastDat newDat=crippleLastDat.create() local unitWrapper tempDat=fromShimTimer[time] set newDat.who=tempDat.who set newDat.timeLeft=3. callTimerStart(time,leeSinConsts_FPS,true,function afterThree) set fromShimTimer[time]=newDat set tab[tempDat.who]=GetUnitAbilityLevel(tempDat.who,leeSinConsts_TEMPEST_ID) callSetUnitAbilityLevel(tempDat.who,leeSinConsts_TEMPEST_ID,5) callIncUnitAbilityLevel(tempDat.who,leeSinConsts_TEMPEST_ID) callUnitAddAbility(tempDat.who,leeSinConsts_CRIPPLE_ID) call tempDat.destroy() set time=null endfunction
privatefunction c takesnothingreturnsboolean localunit tU localunit FoG localplayer owner localboolean hit localreal tX localreal tY localtimer shim local unitWrapper tempDat ifGetSpellAbilityId()==leeSinConsts_TEMPEST_ID then set tU=GetTriggerUnit() set tX=GetUnitX(tU) set tY=GetUnitY(tU) set hit=false callGroupEnumUnitsInRange(grp,tX,tY,TEMPEST_RADIUS,null) set owner=GetOwningPlayer(tU) callDestroyEffect(AddSpecialEffect(TEMPEST_FX,tX,tY)) loop set FoG=FirstOfGroup(grp) exitwhen FoG==null ifIsUnitEnemy(FoG,owner)andIsUnitType(FoG,UNIT_TYPE_STRUCTURE)==falseand UnitAlive(FoG)then call DamageType_dealCodeDamage(tU,FoG,25.+35.*GetUnitAbilityLevel(tU,leeSinConsts_TEMPEST_ID)+leeSinConsts_getBonusAD(tU),DAMAGE_TYPE_MAGIC) call leeSinConsts_revealUnit(FoG,owner,true) set hit=true endif callGroupRemoveUnit(grp,FoG) endloop if hit then set shim=CreateTimer() set tempDat=unitWrapper.create() set tempDat.who=tU set fromShimTimer[shim]=tempDat callTimerStart(shim,0.,false,function shimAfter) endif set tU=null endif returnfalse endfunction
privatefunction crippleC takesnothingreturnsboolean localunit tU localunit FoG localplayer owner localinteger returnedLevel ifGetSpellAbilityId()==leeSinConsts_CRIPPLE_ID then set tU=GetTriggerUnit() callUnitRemoveAbility(tU,leeSinConsts_CRIPPLE_ID) set returnedLevel=tab[tU] callGroupEnumUnitsInRange(grp,GetUnitX(tU),GetUnitY(tU),CRIPPLE_RADIUS,null) set owner=GetOwningPlayer(tU) loop set FoG=FirstOfGroup(grp) exitwhen FoG==null ifIsUnitEnemy(FoG,owner)andGetUnitAbilityLevel(FoG,leeSinConsts_REVEALED_ID)>0then call crippleSlow.setGreater(FoG,returnedLevel,4) endif callGroupRemoveUnit(grp,FoG) endloop set tU=null endif returnfalse endfunction
privatefunction i takesnothingreturnsnothing localtrigger t=CreateTrigger() localtrigger cripple=CreateTrigger() set tab=HandleTable.create() set fromShimTimer=HandleTable.create() callTriggerRegisterAnyUnitEventBJ(cripple,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(cripple,Condition(function crippleC)) callTriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(t,Condition(function c)) set cripple=null set t=null endfunction endscope
privatereal duration privateunit who privatereal delZ
privatestaticmethod p takesnothingreturnsnothing localinteger index=0 localthistype tempDat loop exitwhen index>thistype.dbIndex set tempDat=kDB[index] set tempDat.delZ=tempDat.delZ+GRAVITY callSetUnitFlyHeight(tempDat.who,GetUnitFlyHeight(tempDat.who)+tempDat.delZ,0.) ifGetUnitFlyHeight(tempDat.who)<=5.and tempDat.delZ<0.then callSetUnitPropWindow(tempDat.who,GetUnitDefaultPropWindow(tempDat.who)) callSetUnitFlyHeight(tempDat.who,GetUnitDefaultFlyHeight(tempDat.who),0.) call leeSinConsts_unsilenceUnit(tempDat.who) call tempDat.destroy() set kDB[index]=kDB[thistype.dbIndex] setthistype.dbIndex=thistype.dbIndex-1 set index=index-1 ifthistype.dbIndex==-1then callPauseTimer(thistype.clock) endif endif set index=index+1 endloop endmethod
publicstaticmethod add takesunit who,real duration returnsnothing localthistype tempDat=thistype.create() set tempDat.duration=duration set tempDat.who=who set tempDat.delZ=duration/2.*(-GRAVITY)/leeSinConsts_FPS set dbIndex=dbIndex+1 set kDB[dbIndex]=tempDat callSetUnitPropWindow(who,0.) ifUnitAddAbility(who,'Arav')then callUnitRemoveAbility(who,'Arav') endif if dbIndex==0then callTimerStart(thistype.clock,leeSinConsts_FPS,true,functionthistype.p) endif endmethod endstruct
privatestruct knockback unit who unit caster real delX real delY real damage real distance=0 group g endstruct
privatefunction p takesnothingreturnsnothing localinteger index=0 local knockback tempDat localboolean wall localunit FoG localreal newX localreal newY localplayer owner loop exitwhen index>dbIndex set wall=false set tempDat=knockDB[index] set tempDat.distance=tempDat.distance+KNOCKBACK_SPEED*leeSinConsts_FPS set owner=GetOwningPlayer(tempDat.caster) if IsTerrainWalkable(GetUnitX(tempDat.who)+tempDat.delX,GetUnitY(tempDat.who)+tempDat.delY)then set newX=GetUnitX(tempDat.who)+tempDat.delX set newY=GetUnitY(tempDat.who)+tempDat.delY callSetUnitX(tempDat.who,newX) callSetUnitY(tempDat.who,newY) callGroupEnumUnitsInRange(grp,newX,newY,KNOCKUP_RADIUS,null) loop set FoG=FirstOfGroup(grp) exitwhen FoG==null if UnitAlive(FoG)andIsUnitType(FoG,UNIT_TYPE_HERO)andIsUnitEnemy(FoG,owner)and FoG!=tempDat.whoandIsUnitInGroup(FoG,tempDat.g)==falsethen callGroupAddUnit(tempDat.g,FoG) call leeSinConsts_silenceUnit(FoG) call knockup.add(FoG,1.) endif callGroupRemoveUnit(grp,FoG) endloop else set wall=true call leeSinConsts_stunUnit(tempDat.who) endif if wall or tempDat.distance>=KNOCKBACK_DISTANCE then callSetUnitPropWindow(tempDat.who,GetUnitDefaultPropWindow(tempDat.who)) call leeSinConsts_unsilenceUnit(tempDat.who) callDestroyGroup(tempDat.g) call tempDat.destroy() set knockDB[index]=knockDB[dbIndex] set dbIndex=dbIndex-1 set index=index-1 if dbIndex==-1then callPauseTimer(time) endif endif set index=index+1 endloop endfunction
privatefunction c takesnothingreturnsboolean local knockback tempDat localreal targX localreal targY localreal trigX localreal trigY localreal angle localsound s localinteger index=-2 ifGetSpellAbilityId()==leeSinConsts_DRAGONS_RAGE_ID then set tempDat=knockback.create() set tempDat.caster=GetTriggerUnit() set tempDat.who=GetSpellTargetUnit() set tempDat.damage=200.*GetUnitAbilityLevel(tempDat.caster,leeSinConsts_DRAGONS_RAGE_ID)+2.*leeSinConsts_getBonusAD(tempDat.caster) set tempDat.g=CreateGroup() call DamageType_dealCodeDamage(tempDat.caster,tempDat.who,tempDat.damage,DAMAGE_TYPE_NORMAL) set targX=GetUnitX(tempDat.who) set targY=GetUnitY(tempDat.who) call leeSinConsts_silenceUnit(tempDat.who) set s=CreateSound(DRAGONKICK_SOUND,false,true,true,12700,12700,"") callSetSoundPosition(s,targX,targY,50.) callSetSoundVolume(s,127) callStartSound(s) callSetUnitPropWindow(tempDat.who,0.) callKillSoundWhenDone(s) set trigX=GetUnitX(tempDat.caster) set trigY=GetUnitY(tempDat.caster) set angle=Atan2(targY-trigY,targX-trigX) set tempDat.delX=KNOCKBACK_SPEED*leeSinConsts_FPS*Cos(angle) set tempDat.delY=KNOCKBACK_SPEED*leeSinConsts_FPS*Sin(angle) loop exitwhen index>2 callDestroyEffect(AddSpecialEffect(FLAME_EFFECT,trigX+80.*Cos(angle+15.*bj_DEGTORAD*index),trigY+80.*Sin(angle+15.*bj_DEGTORAD*index))) callDestroyEffect(AddSpecialEffect(BLOOD_EFFECT,trigX+100.*Cos(angle+10.*bj_DEGTORAD*index),trigY+100.*Sin(angle+10.*bj_DEGTORAD*index))) set index=index+1 endloop set dbIndex=dbIndex+1 set knockDB[dbIndex]=tempDat if dbIndex==0then callTimerStart(time,leeSinConsts_FPS,true,function p) endif endif returnfalse endfunction
privatefunction i takesnothingreturnsnothing localtrigger t=CreateTrigger() callTriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT) callTriggerAddCondition(t,Condition(function c)) call leeSinConsts_preloadSound(DRAGONKICK_SOUND) set t=null endfunction endscope
Credits:
league of legends/riot games for creating and designing the entirety of lee sin the blind monk (none of this spell pack is original content).
Anitarf for being partially responsible for the IsTerrainWalkable classic snippet.
Vexorian, MindworX, PitzerMike, others for the creation of Table, JassHelper, and JNGP. I would never have created these without them.
Installation:
Please copy all object data in this map to yours.
Update all values in the CUSTOMIZE section of leeSinConsts to match the newly imported data.
Make sure you have copies of Table by Vexorian, IsTerrainWalkable by Anitarf, StructuredDD, and DamageType.
Play single player or online!
Change Log:
2013.02.05 - Initial upload to spells section.
Rating - 0.00 (0 votes)
(Hover and click)
This spell has not been approved in the resource section and is not guaranteed to be working.
It doesn't heal them, it uses my resource Shield to temporarily shield them from damage.
That's not the point. I get that your system is supposed to temporarily shield them from damage. However, the problem is that safeguard doesn't actually shield you from damage but actually heals you for small amount when used (and you lose that hp in end)
This isn't how Lee Sin's W works, neither have I seen this kind of 'shield'
kStiyl, there is no shield functionality in warcraft 3 that behaves like that of league of legends, so I've coded it myself. I can assure you it behaves exactly like in league of legends.
You can test it yourself by shielding, and then taking damage which is greater than the shield's value before its duration is over, and then see what happens after the duration.
All other shield systems I've seen behave like that, and I'm fairly sure that a lot of people are used to having shield that actually 'block' damage instead of showing it through fluctuating HP. Personally, I think it's confusing.
Is there some reason you coded it that way? I'm really curious
Sorry for going off-topic with spell
In the customize section of my Shield resource, there is a line:
privatestaticconstantreal TIMER_PERIOD=1./10.
If you set the value to something like 1./30. or 1./60., the hp bars will not appear to fluctuate anymore.
The reason why there is some "false" hp added to the unit is to better match how health bars look in league of legends. It has the added benefit of reducing the chance of fatal damage killing a unit that should otherwise be blocked.
Here's an example:
Unit has 10/200 hp and uses a shield ability which should block 100 damage. In my system, the unit will temporarily have 110 hp, thus it can block an attack of, say, 50 damage.
However, in the systems you may be used to seeing, this feature does not exist. A unit with 10 hp who uses a shield will die to an attack which should deal 50 damage.
The reason why they work that way is because
Quote:
most people are also used to having shield that actually 'block' damage
That doesn't exist - there is no way to directly block mixed damage.
Unit has 10/200 hp and uses a shield ability which should block 100 damage. In my system, the unit will temporarily have 110 hp, thus it can block an attack of, say, 50 damage.
However, in the systems you may be used to seeing, this feature does not exist. A unit with 10 hp who uses a shield will die to an attack which should deal 50 damage.
In the systems I am seeing (and using) can block fatal damage via 0 second timers + vitality gem ability. In same manner, I don't see how is your system going to block a damage that is over the unit's hp, if the only thing done is increasing current life.
(ex. what if a unit with 100 hp gets a shield of 100,000 via your system? is it going to block 100000 damage as well? increase its hp to 100,000?)
In the systems I am seeing (and using) can block fatal damage via 0 second timers + vitality gem ability. In same manner, I don't see how is your system going to block a damage that is over the unit's hp, if the only thing done is increasing current life.
(ex. what if a unit with 100 hp gets a shield of 100,000 via your system? is it going to block 100000 damage as well? increase its hp to 100,000?)
Hello,
The Periapt of Vitality item just uses "Item Life Bonus" as its ability (which gives some flat HP to whatever unit it is attached to). If you think my Shield resource could benefit from using this method, I'd be interested to hear about it.
Are you suggesting I:
Detect an attack
Add item life bonus temporarily
Use a 0. second timer
Remove the life bonus (logicly)
I'd have to do some tests to see if this can actually block fatal damage as you say.
However, to answer your question,
Quote:
(ex. what if a unit with 100 hp gets a shield of 100,000 via your system? is it going to block 100000 damage as well? increase its hp to 100,000?)
No it won't give the unit 100,000 more hp. It will just fill the void up to the unit's max hp, and then the rest is stored in a separate variable.
To simplify, the system would still block 100,000 hp, but the unit would not appear to have 100,000 hp.
Here are the members of Shield instances:
Jass:
privateunit u privatereal shieldLeft privatereal falseHP privatereal timeLeft privateeffect fx privatethistype prevShield=0 privatethistype nextShield=0
Where falseHP is the amount of hp temporarily added, and shieldLeft is the unallocated shield value - the two together represent the shield's total value.