I just started to code recently, and I would really appreciate some feedback on my code. So far, everything seems to work as intended.
Spell Description:
Hero dashes to the target location and uses one of his shunpo stacks. Hero has (1 * spell level) shunpo stacks. The shunpo requires 1 stack If there is an enemy in radius of Y of the target location. Otherwise, it requires 2 stacks. After dash, the hero is turned to the closest enemy unit (hero has priority over normal unit)
If hero target unit, he will shunpo to the unit and attack him, causing T damage (requires 1 stack). If hero kills a minion from this attack, he gains 1 stack.
Note: Shunpo will not work if her shnpo location is the same as her current location.
Do not bother with the stacks numbers. I have them different in some places compared to the description for testing purposes.
Spell Description:
Hero dashes to the target location and uses one of his shunpo stacks. Hero has (1 * spell level) shunpo stacks. The shunpo requires 1 stack If there is an enemy in radius of Y of the target location. Otherwise, it requires 2 stacks. After dash, the hero is turned to the closest enemy unit (hero has priority over normal unit)
If hero target unit, he will shunpo to the unit and attack him, causing T damage (requires 1 stack). If hero kills a minion from this attack, he gains 1 stack.
Note: Shunpo will not work if her shnpo location is the same as her current location.
Do not bother with the stacks numbers. I have them different in some places compared to the description for testing purposes.
JASS:
library Shunpo /*
*/ uses /*
*/ SpellFramework /*
*/ UnitDex /*
*/ BaseFunctions /*
*/ MemoryHackTestAbilityChargesHook /*
*/ GetClosestWidget /*
*/ IsRemaningDistMoreThanObject
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* SPELL CONFIGURATION *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
private module SpellConfiguration
static constant integer SPELL_ABILITY_ID = 'A00E'
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
static constant integer STACK_COST_MIN = 1
static constant integer STACK_COST_MAX = 2
static constant real SPELL_PERIOD = 1.00 / 32.00
static constant real SFX_DEATH_TIME = 1.50
static constant real MIN_SHUNPO_DIST = 60
static constant real SHUNPO_TARGET_OFFSET = 100
static constant real MIN_SHUNPO_SPEED = 100
static constant real STACK_AOE_CHECK = 150
static constant string CAST_SFX = "n/a"
static constant string SHUNPO_SFX = "SoiFon.mdl"
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
static method TargetsShunpoAoeCheck takes unit target, unit caster returns boolean
return GetWidgetLife(target) > 0.405 and /*
*/ IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
*/ (IsUnitVisible(target, GetOwningPlayer(caster))) // is caster in enemy line of sight
endmethod
static method HeroShunpoAoeCheck takes unit target, unit caster returns boolean
return GetWidgetLife(target) > 0.405 and /*
*/ IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
*/ (IsUnitVisible(target, GetOwningPlayer(caster))) and /*
*/ IsUnitType(target, UNIT_TYPE_HERO)
endmethod
static method NonHeroShunpoAoeCheck takes unit target, unit caster returns boolean
return GetWidgetLife(target) > 0.405 and /*
*/ IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
*/ (IsUnitVisible(target, GetOwningPlayer(caster))) and /*
*/ not IsUnitType(target, UNIT_TYPE_HERO)
endmethod
static method DMG takes integer level, unit caster returns real
return 500. + 0. * level + 0.1 * GetHeroStr(caster, true)
endmethod
static method StackCdTime takes integer level returns real
return 3.0
endmethod
endmodule
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* END OF SPELL CONFIGURATION *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* == == == == == == == == == == == == = SPELL CODE == == == == == == == == == == == == = */
globals
private group eG = CreateGroup()
private group hG = CreateGroup()
private group nonHG = CreateGroup()
endglobals
public struct Shunpo extends array
implement SpellConfiguration
private static unit caster
private static unit u
private integer maxStacks
private integer stacks
private integer shunpoCounter
private boolean isTargetNull
private boolean isStackTimerOn
private real finalPointX
private real finalPointY
private real speed
private real dist
private real cX
private real cY
private real angle
private real dmg
private real curDist
private unit target
private timer t
private CastBuff cb
private boolean active
private method onSpellStart takes nothing returns thistype
local real angle
local unit closestUnit
local real targetShunpoX
local real targetShunpoY
local real cX = GetUnitX(Spell.triggerUnit)
local real cY = GetUnitY(Spell.triggerUnit)
local boolean eInRange = false
set this = GetUnitId(Spell.triggerUnit)
debug call BJDebugMsg("[Shunpo] Spell Start Activated")
//Double Cast Protection
if this.active then
debug call BJDebugMsg("[Shunpo] the spell is casting already")
return 0
endif
if Spell.targetUnit == null then
call GroupEnumUnitsInRange(eG, Spell.targetX, Spell.targetY, STACK_AOE_CHECK, null)
loop
set u = FirstOfGroup(eG)
exitwhen u == null or eInRange
if TargetsShunpoAoeCheck(u, Spell.triggerUnit) then
set eInRange = true
debug call BJDebugMsg("[Shunpo] Cast: Enemy Hero In Range")
endif
call GroupRemoveUnit(eG, u)
endloop
call GroupClear(eG)
if (eInRange and this.stacks < STACK_COST_MIN) or /*
*/ (not eInRange and this.stacks < STACK_COST_MAX) then
debug call BJDebugMsg("[Shunpo] Spell Point: Not Enough Stacks")
return 0
endif
if GetDist(cX, Spell.targetX, cY, Spell.targetY) < MIN_SHUNPO_DIST then
debug call BJDebugMsg("[Shunpo] The target loc is too close to the caster's cur loc - spell cancelled")
return 0
else
set this.isTargetNull = true
set this.finalPointX = Spell.targetX
set this.finalPointY = Spell.targetY
if not eInRange then
set this.stacks = this.stacks - STACK_COST_MAX
else
set this.stacks = this.stacks - STACK_COST_MIN
endif
endif
else
if this.stacks >= STACK_COST_MIN then
set angle = GetAngle(cX, Spell.targetX, cY, Spell.targetY)
set targetShunpoX = GetXWithOffset(Spell.targetX, SHUNPO_TARGET_OFFSET, angle - 180)
set targetShunpoY = GetYWithOffset(Spell.targetY, SHUNPO_TARGET_OFFSET, angle - 180)
if GetDist(cX, targetShunpoX, cY, targetShunpoY) < MIN_SHUNPO_DIST then
debug call BJDebugMsg("[Shunpo] The target loc is too close to the caster's cur loc - spell cancelled")
return 0
else
set this.isTargetNull = false
set this.finalPointX = targetShunpoX
set this.finalPointY = targetShunpoY
set this.target = Spell.targetUnit
set this.stacks = this.stacks - STACK_COST_MIN
endif
else
debug call BJDebugMsg("[Shunpo] Spell Target: Not Enough Stacks")
return 0
endif
endif
debug call BJDebugMsg("[Shunpo] Number of Stacks Before Reduction " + I2S(this.stacks))
call DestroyEffect(AddSpecialEffect("Objects\\InventoryItems\\HumanCaptureFlag\\HumanCaptureFlag.mdl", this.finalPointX, this.finalPointY))
call SetCustomUnitAbilityCharges(Spell.triggerUnit, SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Shunpo] Number of Stacks After Reduction " + I2S(this.stacks))
if not this.isStackTimerOn then
set this.t = NewTimerEx(this)
call TimerStart(this.t, StackCdTime(Spell.level), true, function thistype.StacksCd)
set this.isStackTimerOn = true
endif
set this.active = true
set this.cX = cX
set this.cY = cY
set this.dist = GetDist(cX, this.finalPointX, cY, this.finalPointY)
set this.angle = GetAngle(cX, this.finalPointX, cY, this.finalPointY)
set this.dmg = DMG(Spell.level, Spell.triggerUnit)
set this.shunpoCounter = 0
set this.curDist = 0
debug call BJDebugMsg("[Shunpo] Dist: " + R2S(this.dist))
if this.dist / MIN_SHUNPO_SPEED < 2 then
set this.speed = this.dist
else
set this.speed = this.dist / R2I(this.dist / MIN_SHUNPO_SPEED + 0.5)
endif
debug call BJDebugMsg("[Shunpo] Speed: " + R2S(this.speed))
set this.cb = CastBuff.add(null, Spell.triggerUnit)
set this.cb.duration = 1.0
call SetUnitFacingInstant(Spell.triggerUnit, this.angle)
call SetUnitPathing(Spell.triggerUnit, false)
call UnitAddAbility(Spell.triggerUnit, 'Agho')
return this
endmethod
private method onSpellPeriodic takes nothing returns boolean
local real moveX
local real moveY
local real cX
local real cY
local real tempAngle
local unit closestUnit
local effect moveSFX
set caster = GetUnitById(this)
set cX = GetUnitX(caster)
set cY = GetUnitY(caster)
set moveX = GetXWithOffset(cX, this.speed, this.angle)
set moveY = GetYWithOffset(cY, this.speed, this.angle)
call SetUnitAnimationByIndex(caster, 7)
call SetUnitTimeScale(caster, 2)
if this.curDist == this.dist or IsRemaningDistMoreThanObject(moveX, moveY, this.angle, this.speed, (this.dist - this.curDist)) then
debug call BJDebugMsg("[Shunpo] Caster Reached End Point ")
if not this.isTargetNull then
if GetDist(cX, GetUnitX(this.target), cY, GetUnitY(this.target)) < SHUNPO_TARGET_OFFSET + 20 then
debug call BJDebugMsg("[Shunpo] The Point was Target and He is still in Range ")
call SetUnitAnimationByIndex(caster, 12)
call UnitDamageTarget(caster, this.target, this.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
if GetWidgetLife(this.target) < 0.405 then
set this.stacks = this.stacks + 1
call SetCustomUnitAbilityCharges(caster, SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Shunpo] The Target Died: Reset 1 stack. Total Stack: " + I2S(this.stacks))
endif
endif
else
call GroupEnumUnitsInRange(eG, cX, cY, STACK_AOE_CHECK, null)
loop
set u = FirstOfGroup(eG)
exitwhen u == null
if HeroShunpoAoeCheck(u, caster) then
call GroupAddUnit(hG, u)
elseif NonHeroShunpoAoeCheck(u, caster) then
call GroupAddUnit(nonHG, u)
endif
call GroupRemoveUnit(eG, u)
endloop
call GroupClear(eG)
if GetGroupUnitCount(hG) > 0 then
set closestUnit = GetClosestUnitInGroup(cX, cY, hG)
set tempAngle = GetAngle(cX, GetUnitX(closestUnit), cY, GetUnitY(closestUnit))
call SetUnitFacingInstant(caster, tempAngle)
call IssueTargetOrder(caster, "attack", closestUnit)
set closestUnit = null
elseif GetGroupUnitCount(nonHG) > 0 then
set closestUnit = GetClosestUnitInGroup(cX, cY, nonHG)
set tempAngle = GetAngle(cX, GetUnitX(closestUnit), cY, GetUnitY(closestUnit))
call SetUnitFacingInstant(caster, tempAngle)
call IssueTargetOrder(caster, "attack", closestUnit)
set closestUnit = null
endif
call GroupClear(hG)
call GroupClear(nonHG)
endif
return true
else
set this.shunpoCounter = this.shunpoCounter + 1
if this.shunpoCounter > 1 then
set moveSFX = AddSpecialEffect(SHUNPO_SFX, cX, cY)
call DestroyEffectTimed(moveSFX, 1.0)
call SetEffectTimeScale(moveSFX, 2.0)
call SetEffectAnimationByIndex(moveSFX, 7)
call SetEffectAlpha(moveSFX, 30)
call SetEffectFacing(moveSFX, this.angle)
endif
call SetUnitX(caster, moveX)
call SetUnitY(caster, moveY)
set this.curDist = this.curDist + this.speed
return false
endif
endmethod
private method onSpellEnd takes nothing returns nothing
set this.active = false
call SetUnitPathing(caster, true)
call UnitRemoveAbility(caster, 'Agho')
if this.isTargetNull then
debug call BJDebugMsg("[Shunpo] Non Target = Stand Animation")
call SetUnitTimeScale(caster, 1.0)
call SetUnitAnimationByIndex(caster, 1)
call this.cb.remove()
else
debug call BJDebugMsg("[Shunpo] Target = Timer")
call TimerStart(NewTimerEx(this), 0.25 , false, function thistype.SpellEndAnimation)
endif
debug call BJDebugMsg("[Shunpo] Dist: " + R2S(this.curDist))
debug call BJDebugMsg("[Shunpo] curDist: " + R2S(this.dist))
if R2I(this.curDist) != R2I(this.dist) then
debug call BJDebugMsg("[Shunpo] Got Interrpued: Reset Animation and Remove Stun")
call SetUnitTimeScale(caster, 1.0)
call SetUnitAnimationByIndex(caster, 1)
call this.cb.remove()
endif
if this.stacks == this.maxStacks then
set this.isStackTimerOn = false
call ReleaseTimer(this.t)
set this.t = null
elseif this.stacks == 0 then
call StartUnitAbilityCooldown(caster, SPELL_ABILITY_ID, TimerGetRemaining(this.t))
endif
set this.target = null
set caster = null
endmethod
implement SpellEvent
static method StacksCd takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if this.stacks < this.maxStacks then
set this.stacks = this.stacks + 5
call SetCustomUnitAbilityCharges(GetUnitById(this), SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Shunpo] CD Finished: 1 Shunpo Stack Added")
else
debug call BJDebugMsg("[Shunpo] CD Finished: 1 Shunpo Stack Added")
set this.isStackTimerOn = false
call ReleaseTimer(this.t)
set this.t = null
endif
endmethod
static method SpellEndAnimation takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call SetUnitTimeScale(GetUnitById(this), 1.0)
call SetUnitAnimationByIndex(GetUnitById(this), 1)
call this.cb.remove()
call ReleaseTimer(GetExpiredTimer())
endmethod
//===========================================================================
private static method GiveShunpoStacks takes nothing returns nothing
local unit learner
local thistype this
if GetLearnedSkill() == SPELL_ABILITY_ID then
set learner = GetTriggerUnit()
if GetUnitAbilityLevel(learner, SPELL_ABILITY_ID) == 1 then
call SetUnitAbilityCastpoint(learner, SPELL_ABILITY_ID, 0.1)
call InitCustomAbilityAddressChargesHook( GetUnitAbility(learner, SPELL_ABILITY_ID))
call EnableCustomUnitAbilityCharges(learner, SPELL_ABILITY_ID)
debug call BJDebugMsg("[Shunpo] Setup Finished")
endif
set this = GetUnitId(learner)
set this.maxStacks = 5 * GetUnitAbilityLevel(learner, SPELL_ABILITY_ID)
set this.stacks = this.stacks + 5
call SetCustomUnitAbilityCharges(learner, SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Shunpo] Max Stacks " + I2S(this.maxStacks))
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function thistype.GiveShunpoStacks)
endmethod
endstruct
endlibrary
Last edited: