Dinowc
don't expect anything, prepare for everything
- Reaction score
- 223
yeah ofc the idea isn't mine 
I've made this spell since HeX.16 requested it
and I thought I could share it here
any feedback appreciated
fixed some bugs and added counter punching
I've made this spell since HeX.16 requested it
and I thought I could share it here
Falcon Punch
![]()
Charges towards the target knocking any enemy unit along the way. Upon reaching the target, Falcon Assassin deals AoE damage to enemy units and burns them for 7 seconds. Target is then knocked back in the air, dealing additional AoE damage on impact.
Primary Damage: 120 + 50 per lvl
Secondary Damage: 50 + 25 per lvl
Burn Damage: 10 + 5 per lvl
Range: 800 + 50 per lvl
Primary Aoe: 250 + 25 per lvl
Secondary AoE: 200 + 25 per lvl


JASS:
scope punch initializer init // requires Timer Utils, AIDS, Knockback System (Kenny's), Group Utils
// CONFIGURABLE:
globals
private constant integer SPELL_ID = 'A000'
private constant integer PHOENIX_DUMMY = 'h000'
private constant integer STORM_CROW = 'Arav'
private constant real INTERVAL = 0.03125
private constant real MAX_SPEED = 1300.
private constant real START_SPEED = 100.
private constant real ACCELERATION = 800. // speed increase after 1 second
private constant real FLY_SPEED = 900.
private constant boolean INVULNERABLE = false // unit is invulnerable while sliding
private constant real MINIMUM_CAST_RANGE = 0.
private constant boolean COUNTER_PUNCH_DISABLED = false // setting this to true will cause the faster sliding unit to punch the target if both are sliding and targeting each other
private constant boolean CHECK_PATHING = true // for going over cliffs
private constant boolean DESTROYS_TREES = true // if it's set to false, it will stop charging if he hits a tree (or any other destructible)
private constant real TREE_CUT_AOE = 100.
private constant string CAST_SFX = "Abilities\\Spells\\NightElf\\BattleRoar\\RoarCaster.mdl"
private constant string STRIKE_SFX = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
private constant string STRIKE_SFX_ATTACH = "chest"
private constant string IMPACT_SFX = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl"
private constant real BASE_PRIMARY_DAMAGE = 120.
private constant real PRIMARY_DAMAGE_PER_LVL = 50.
private constant real BASE_SECONDARY_DAMAGE = 50.
private constant real SECONDARY_DAMAGE_PER_LVL = 25.
private constant real BASE_PRIMARY_AOE = 250.
private constant real PRIMARY_AOE_PER_LVL = 25.
private constant real BASE_SECONDARY_AOE = 200.
private constant real SECONDARY_AOE_PER_LVL = 25.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant real BASE_BURN_DAMAGE = 10.
private constant real BURN_DAMAGE_PER_LVL = 5.
private constant real BURN_INTERVAL = 0.50
private constant real BASE_BURN_DURATION = 7.
private constant real BURN_DURATION_PER_LVL = 0.
private constant string BURN_SFX = "Environment\\LargeBuildingFire\\LargeBuildingFire2.mdl"
private constant string BURN_SFX_ATTACH = "chest"
private constant attacktype BURN_ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant damagetype BURN_DAMAGE_TYPE = DAMAGE_TYPE_FIRE
private constant real MAX_HEIGHT = 200.
private constant real FLY_DISTANCE = 700.
private constant real FLY_RATE = 800.
private constant real SLIDE_DISTANCE = 300. // distance of the additional sliding once unit falls to the ground
private constant real SLIDE_DURATION = 1.
private constant boolean FLY_OVER_CLIFFS = true
private constant boolean FLIER_INVULNERABLE = false // unit is invulnerable while flying
private constant real KNOCK_SPEED = 750. // speed at which are units being knocked; this is not the speed of knocked units, but of the sliding caster
private constant real KNOCK_DISTANCE = 250.
private constant real KNOCK_DURATION = 0.6
private constant real KNOCK_AOE = 100. // AoE at which are nearby enemies knocked back
private constant real KNOCK_OFFSET = 50. // distance between the center of enumeration rect and the caster
private constant real PHOENIX_OFFSET = -10. // distance between the caster and the phoenix dummy
private constant integer PHOENIX_TRANSPARENCY = 75
private constant real PHOENIX_ANIMATION_SPEED = 1.5
private constant real IMPACT_RANGE = 125. // distance between the caster and the target before the target is hit
private constant integer ANIMATION_ID = 5
private constant real PAUSE_ANIMATION = 0.25 // pauses animation after this many seconds
endglobals
// NOT SO MUCH CONFIGURABLE:
globals
private group G = CreateGroup()
private unit Caster
private unit Target
private player Owner
private integer Lvl
private rect R
private real Slide_Speed = MAX_SPEED * INTERVAL
private real Fly_Speed = FLY_SPEED * INTERVAL
private real X
private real Y
private real Acc = ACCELERATION * INTERVAL * INTERVAL
private real MaxX
private real MaxY
private real MinX
private real MinY
private real Damage
private integer Index
private group Sliders = CreateGroup()
endglobals
private function PolarX takes real dist, real angle returns real
return dist * Cos(angle * bj_DEGTORAD)
endfunction
private function PolarY takes real dist, real angle returns real
return dist * Sin(angle * bj_DEGTORAD)
endfunction
private function GetDistance takes real x1, real y1, real x2, real y2 returns real
local real dx = x2 - x1
local real dy = y2 - y1
return SquareRoot(dx * dx + dy * dy)
endfunction
private function GetAngle takes real x1, real y1, real x2, real y2 returns real
return bj_RADTODEG * Atan2(y2 - y1, x2 - x1)
endfunction
private function GetParabolaZ takes real x returns real
return 4 * MAX_HEIGHT * x * (FLY_DISTANCE - x) / (FLY_DISTANCE * FLY_DISTANCE)
endfunction
private function PrimaryDamage takes integer lvl returns real
return BASE_PRIMARY_DAMAGE + (lvl - 1) * PRIMARY_DAMAGE_PER_LVL
endfunction
private function SecondaryDamage takes integer lvl returns real
return BASE_SECONDARY_DAMAGE + (lvl - 1) * SECONDARY_DAMAGE_PER_LVL
endfunction
private function BurnDamage takes integer lvl returns real
return BASE_BURN_DAMAGE + (lvl - 1) * BURN_DAMAGE_PER_LVL
endfunction
private function PrimaryAoe takes integer lvl returns real
return BASE_PRIMARY_AOE + (lvl - 1) * PRIMARY_AOE_PER_LVL
endfunction
private function SecondaryAoe takes integer lvl returns real
return BASE_SECONDARY_AOE + (lvl - 1) * SECONDARY_AOE_PER_LVL
endfunction
private function BurnDuration takes integer lvl returns real
return BASE_BURN_DURATION + (lvl - 1) * BURN_DURATION_PER_LVL
endfunction
private function SafeX takes real x returns real
if x > MaxX then
return MaxX
endif
if x < MinX then
return MinX
endif
return x
endfunction
private function SafeY takes real y returns real
if y > MaxY then
return MaxY
endif
if y < MinY then
return MinY
endif
return y
endfunction
private struct dot
unit caster
group g
real dmg
timer t
real ticks
real duration
private method onDestroy takes nothing returns nothing
call ReleaseTimer(this.t)
call ReleaseGroup(this.g)
endmethod
endstruct
private struct slide
unit caster
unit target
unit phoenix
real dmg1
real dmg2
real ticks
real dist
real cos
real sin
real acc
real angle
boolean paused
boolean sliding
timer t
static method create takes unit caster, unit target returns thistype
local thistype d = thistype.allocate()
local real angle = GetUnitFacing(caster)
local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
set d.caster = caster
set d.target = target
set d.phoenix = CreateUnit(GetOwningPlayer(d.caster), PHOENIX_DUMMY, GetUnitX(d.caster) + PolarX(PHOENIX_OFFSET, angle), GetUnitY(d.caster) + PolarY(PHOENIX_OFFSET, angle), angle)
call SetUnitVertexColor(d.phoenix, 255, 255, 255, PHOENIX_TRANSPARENCY)
call SetUnitTimeScale(d.phoenix, PHOENIX_ANIMATION_SPEED)
set d.t = NewTimer()
set d.dmg1 = PrimaryDamage(lvl)
set d.dmg2 = SecondaryDamage(lvl)
set d.ticks = 0.
set d.dist = 0.
set d.acc = START_SPEED * INTERVAL
set d.paused = false
set d.sliding = true
call GroupAddUnit(Sliders, caster)
return d
endmethod
private method onDestroy takes nothing returns nothing
call ReleaseTimer(this.t)
set this.caster = null
set this.target = null
set this.phoenix = null
endmethod
endstruct
globals
private slide array D
endglobals
private function PrimaryStrike takes nothing returns boolean
local unit picked = GetFilterUnit()
if IsUnitEnemy(picked, Owner) and GetWidgetLife(picked) > 0. then
call UnitDamageTarget(Caster, picked, Damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
set picked = null
return true
endif
set picked = null
return false
endfunction
private function SecondaryStrike takes nothing returns boolean
local unit picked = GetFilterUnit()
if IsUnitEnemy(picked, Owner) and GetWidgetLife(picked) > 0. then
call UnitDamageTarget(Caster, picked, Damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
endif
set picked = null
return false
endfunction
private function KnockUnits takes nothing returns boolean
local unit picked = GetFilterUnit()
if IsUnitEnemy(picked, Owner) and GetWidgetLife(picked) > 0. and picked != Target and KBS_IsUnitSliding(picked) == false then
call KBS_BeginCommon(picked, KNOCK_DISTANCE, KNOCK_DURATION, GetAngle(X, Y, GetUnitX(picked), GetUnitY(picked)))
endif
set picked = null
return false
endfunction
private function EndSlide takes nothing returns nothing
call PauseUnit(D[Index].caster, false)
call SetUnitPathing(D[Index].caster, true)
call SetUnitTimeScale(D[Index].caster, 1.)
call SetUnitAnimation(D[Index].caster, "stand")
call KillUnit(D[Index].phoenix)
call SetUnitInvulnerable(D[Index].target, false)
call SetUnitInvulnerable(D[Index].caster, false)
call GroupRemoveUnit(Sliders, D[Index].caster)
call PauseTimer(D[Index].t)
call D[Index].destroy()
endfunction
private function DestructibleFilter takes nothing returns nothing
local destructable d = GetEnumDestructable()
static if DESTROYS_TREES then
call KillDestructable(d)
else
call EndSlide()
endif
set d = null
endfunction
private function DoT takes nothing returns nothing
local unit picked = GetEnumUnit()
if GetWidgetLife(picked) > 0. then
call UnitDamageTarget(Caster, picked, Damage, false, false, BURN_ATTACK_TYPE, BURN_DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
call DestroyEffect(AddSpecialEffectTarget(BURN_SFX, picked, BURN_SFX_ATTACH))
endif
set picked = null
endfunction
private function DamageOverTime takes nothing returns nothing
local dot d = GetTimerData(GetExpiredTimer())
set Caster = d.caster
set Damage = d.dmg
call ForGroup(d.g, function DoT)
set d.ticks = d.ticks + BURN_INTERVAL
if d.ticks > d.duration then
call PauseTimer(d.t)
call d.destroy()
endif
endfunction
private function callback takes nothing returns nothing
local slide d = GetTimerData(GetExpiredTimer())
local real x1 = GetUnitX(d.caster)
local real y1 = GetUnitY(d.caster)
local real x2 = GetUnitX(d.target)
local real y2 = GetUnitY(d.target)
local real angle = GetAngle(x1, y1, x2, y2)
local real cos
local real sin
local real dist
local dot a
local real speed
local boolean b = true
set Owner = GetOwningPlayer(d.caster)
set Index = GetUnitIndex(d.caster)
if d.sliding then
if d.acc < Slide_Speed then
set d.acc = d.acc + Acc
else
set d.acc = Slide_Speed
endif
set cos = PolarX(d.acc, angle)
set sin = PolarY(d.acc, angle)
set dist = GetDistance(x1, y1, x2, y2)
call SetUnitX(d.caster, x1 + cos)
call SetUnitY(d.caster, y1 + sin)
call SetUnitX(d.phoenix, GetUnitX(d.phoenix) + cos)
call SetUnitY(d.phoenix, GetUnitY(d.phoenix) + sin)
set X = x1
set Y = y1
set cos = cos + PolarX(KNOCK_OFFSET, angle)
set sin = sin + PolarY(KNOCK_OFFSET, angle)
call MoveRectTo(R, X, Y)
static if CHECK_PATHING then
if IsTerrainPathable(x1 + cos, y1 + sin, PATHING_TYPE_WALKABILITY) == true then
call EndSlide()
endif
endif
call EnumDestructablesInRect(R, null, function DestructibleFilter)
set Target = d.target
if d.acc / INTERVAL > KNOCK_SPEED then
call GroupEnumUnitsInRange(G, x1 + cos, y1 + sin, KNOCK_AOE, Filter(function KnockUnits))
endif
call SetUnitFacingTimed(d.caster, angle, 0.01)
call SetUnitFacingTimed(d.phoenix, angle, 0.01)
if dist <= IMPACT_RANGE + (Slide_Speed * 2.5) then
call SetUnitTimeScale(d.caster, 0.8)
endif
if GetWidgetLife(d.target) <= 0. and IsUnitInGroup(d.caster, Sliders) == true then
call EndSlide()
endif
if dist <= IMPACT_RANGE then
set Index = GetUnitIndex(d.target)
static if COUNTER_PUNCH_DISABLED then
if D[Index].target == d.caster and D[Index].acc > d.acc then
set b = false
endif
endif
if b == true then
call DestroyEffect(AddSpecialEffectTarget(STRIKE_SFX, d.target, STRIKE_SFX_ATTACH))
set Caster = d.caster
set Lvl = GetUnitAbilityLevel(d.caster, SPELL_ID)
set a = dot.create()
set a.caster = d.caster
set a.g = NewGroup()
set a.t = NewTimer()
set a.ticks = 0.
set a.dmg = BurnDamage(Lvl) * BURN_INTERVAL
set a.duration = BurnDuration(Lvl)
set Damage = d.dmg1
call GroupEnumUnitsInRange(a.g, x2, y2, PrimaryAoe(Lvl), Filter(function PrimaryStrike))
call SetTimerData(a.t, a)
call TimerStart(a.t, BURN_INTERVAL, true, function DamageOverTime)
call SetUnitTimeScale(d.caster, 1.)
if COUNTER_PUNCH_DISABLED or D[Index].target != d.caster then
if IsUnitInGroup(d.target, Sliders) then
call EndSlide()
endif
endif
call KillUnit(d.phoenix)
call PauseUnit(d.caster, false)
call PauseUnit(d.target, true)
call SetUnitAnimation(D[Index].caster, "stand")
call IssueImmediateOrder(d.caster, "holdposition")
static if FLIER_INVULNERABLE then
call SetUnitInvulnerable(d.target, true)
endif
call UnitAddAbility(d.target, STORM_CROW)
call UnitRemoveAbility(d.target, STORM_CROW)
call SetUnitPathing(d.target, false)
call SetUnitPathing(d.caster, true)
call GroupRemoveUnit(Sliders, d.caster)
set d.angle = angle
set d.sliding = false
endif
endif
set d.ticks = d.ticks + INTERVAL
if d.ticks > PAUSE_ANIMATION and d.paused == false then
call SetUnitTimeScale(d.caster, 0.)
set d.paused = true
endif
else
set d.cos = PolarX(Fly_Speed, d.angle)
set d.sin = PolarY(Fly_Speed, d.angle)
if d.dist <= FLY_DISTANCE then
set X = x2 + d.cos
set Y = y2 + d.sin
if FLY_OVER_CLIFFS then
call SetUnitX(d.target, SafeX(X))
call SetUnitY(d.target, SafeY(Y))
elseif IsTerrainPathable(X, Y, PATHING_TYPE_WALKABILITY) == false then
call SetUnitX(d.target, SafeX(X))
call SetUnitY(d.target, SafeY(Y))
endif
call SetUnitFlyHeight(d.target, GetParabolaZ(d.dist), FLY_RATE)
elseif KBS_IsUnitSliding(d.target) == false and d.dist < SLIDE_DISTANCE + FLY_DISTANCE then
call SetUnitFlyHeight(d.target, 0., FLY_RATE)
call SetUnitInvulnerable(d.target, false)
call SetUnitInvulnerable(d.caster, false)
call DestroyEffect(AddSpecialEffectTarget(IMPACT_SFX, d.target, "origin"))
call SetUnitPathing(d.target, true)
call KBS_BeginCommon(d.target, SLIDE_DISTANCE, SLIDE_DURATION, d.angle)
set Lvl = GetUnitAbilityLevel(d.caster, SPELL_ID)
set Damage = d.dmg2
call GroupEnumUnitsInRange(G, x2 + d.cos, y2 + d.sin, SecondaryAoe(Lvl), Filter(function SecondaryStrike))
else
call PauseUnit(d.target, false)
call PauseTimer(d.t)
call d.destroy()
endif
set d.dist = d.dist + Fly_Speed
endif
endfunction
private function actions takes nothing returns boolean
local unit caster
local unit target
local slide d
local real angle
if GetSpellAbilityId() == SPELL_ID then
set caster = GetTriggerUnit()
set target = GetSpellTargetUnit()
set d = slide.create(caster, target)
call IssueImmediateOrder(caster, "stop")
call PauseUnit(caster, true)
static if INVULNERABLE then
call SetUnitInvulnerable(caster, true)
endif
call SetUnitPathing(caster, false)
call SetUnitAnimationByIndex(caster, ANIMATION_ID)
set D[GetUnitIndex(caster)] = d
call SetTimerData(d.t, d)
call TimerStart(d.t, INTERVAL, true, function callback)
endif
set caster = null
set target = null
return false
endfunction
private function actions2 takes nothing returns boolean
local unit caster
local real x
local real y
if GetSpellAbilityId() == SPELL_ID then
set caster = GetTriggerUnit()
set x = GetUnitX(caster)
set y = GetUnitY(caster)
if GetDistance(x, y, GetSpellTargetX(), GetSpellTargetY()) >= MINIMUM_CAST_RANGE then
call DestroyEffect(AddSpecialEffect(CAST_SFX, GetUnitX(caster), GetUnitY(caster)))
else
call IssueImmediateOrder(caster, "stop")
endif
endif
set caster = null
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function actions))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddCondition(t, Condition(function actions2))
set R = Rect(-TREE_CUT_AOE, -TREE_CUT_AOE, TREE_CUT_AOE, TREE_CUT_AOE)
set MaxX = GetRectMaxX(bj_mapInitialPlayableArea)
set MaxY = GetRectMaxY(bj_mapInitialPlayableArea)
set MinX = GetRectMinX(bj_mapInitialPlayableArea)
set MinY = GetRectMinY(bj_mapInitialPlayableArea)
endfunction
endscope
any feedback appreciated
fixed some bugs and added counter punching
Attachments
-
105 KB Views: 641