Andrewgosu
The Silent Pandaren Helper
- Reaction score
- 716
Long time a spell from me!
May I present, Shadow Charge!
The concept behind the spell is simple and not very unique - basic Charge spell with stun and knock back in the end.
However, the fun part comes in when taking a look at the eye-candy:
the spell creates a diablo-like shadows behind the caster and behind the target when it's hit.
It works with all units and no dummies besides one doing the stun casting is required.
Yes, you can edit the amount of "shadows" behind the target and caster.
Yes, you can edit the charge and knockback speed.
Though, there's only one drawback:
The spells creates replicas of the unit itself, meaning, if it is used (or the target) by a hero, it will create heroes which show up an interface icon.
To remove the issue, one has to disable the in-game icon showing for heroes.
Anyway, here are some screenshots!
The actual code itself, quite lengthy, I know I am bad at coding.
Have fun!
Comments, feedback and criticism always appreciated!
Andrewgosu
May I present, Shadow Charge!
The concept behind the spell is simple and not very unique - basic Charge spell with stun and knock back in the end.
However, the fun part comes in when taking a look at the eye-candy:
the spell creates a diablo-like shadows behind the caster and behind the target when it's hit.
It works with all units and no dummies besides one doing the stun casting is required.
Yes, you can edit the amount of "shadows" behind the target and caster.
Yes, you can edit the charge and knockback speed.
Though, there's only one drawback:
The spells creates replicas of the unit itself, meaning, if it is used (or the target) by a hero, it will create heroes which show up an interface icon.
To remove the issue, one has to disable the in-game icon showing for heroes.
Anyway, here are some screenshots!
The actual code itself, quite lengthy, I know I am bad at coding.
JASS:
scope SHADOWCHARGE
globals
private constant integer SHADOW_CHARGE_ID = 039;A000039;
private constant integer DUMMY_STUN_ID = 039;A001039;
private constant string DUMMY_STUN_ORDER = "thunderbolt"
private constant integer DUMMY_UNIT_ID = 039;h000039;
private constant integer CASTER_SHADOW_AMOUNT = 4
private constant integer CASTER_MOVE_ANI_INDEX = 0 // Animation "move" index for the caster
private constant integer TARGET_SHADOW_AMOUNT = 2
private constant real SHADOW_SPACING = 55. // Distance between the "shadows"
private constant real CASTER_CHARGE_SPEED = 1125.
private constant real TARGET_KNOCKBACK_SPEED = 375.
private constant real TARGET_KNOCKBACK_LENGTH = 200.
private constant real CASTER_ANI_SPEED_INCREASE = 250. // % the animation speed increases during charge
private constant real TARGET_RANGE_RADIUS = 125. // The radius from the target which the charge will be ended
private constant real DESTRUCTABLE_KILL_RADIUS = 160. // The radius in which the trees are kill around the caster
endglobals
globals
private constant real TIMER_VAL = .0325
private constant playerstate FOOD = PLAYER_STATE_RESOURCE_FOOD_USED
endglobals
private struct casterdata
unit caster = null
player owner = null
unit target = null
timer expired = null
trigger trig = null
integer foodCost = 0
real facing
real x
real y
real startX
real startY
unit array shadows[CASTER_SHADOW_AMOUNT]
method removeShadows takes nothing returns nothing
local integer index = 1
loop
exitwhen (index > CASTER_SHADOW_AMOUNT)
call RemoveUnit(.shadows[index])
call SetPlayerState(.owner, FOOD, GetPlayerState(.owner, FOOD) + .foodCost)
set index = index + 1
endloop
endmethod
method onDestroy takes nothing returns nothing
call SetUnitPathing(.target, false)
call .removeShadows()
call DestroyTrigger(.trig)
call ReleaseTimer(.expired)
endmethod
endstruct
private function EnumDestructablesInCircle takes real radius, real x, real y, code actionFunc returns nothing
local rect r
if (radius >= 0) then
set r = Rect(x - radius, y - radius, x + radius, y + radius)
call EnumDestructablesInRect(r, null, actionFunc)
call RemoveRect(r)
endif
endfunction
private function ShadowCharge_KillDestructables takes nothing returns nothing
call KillDestructable(GetEnumDestructable())
endfunction
private function ShadowCharge_MoveTarget takes nothing returns nothing
local timer t = GetExpiredTimer()
local casterdata data = GetCSData(t)
local real x = GetUnitX(data.target)
local real y = GetUnitY(data.target)
local real dx = x - data.startX
local real dy = y - data.startY
local real speed = TARGET_KNOCKBACK_SPEED / (1 / TIMER_VAL)
local integer index = 1
if (data.x == x) and (data.y == y) or (IsUnitType(data.target, UNIT_TYPE_DEAD) == true) then
call data.destroy()
else
set data.x = x
set data.y = y
if (SquareRoot(dx * dx + dy * dy) < TARGET_KNOCKBACK_LENGTH) then
set dx = x + speed * Cos(data.facing * bj_DEGTORAD)
set dy = y + speed * Sin(data.facing * bj_DEGTORAD)
call EnumDestructablesInCircle(DESTRUCTABLE_KILL_RADIUS, dx, dy, function ShadowCharge_KillDestructables)
call SetUnitPosition(data.target, dx, dy)
loop
exitwhen (index > TARGET_SHADOW_AMOUNT)
set dx = x + (SHADOW_SPACING * index) * Cos(data.facing * bj_DEGTORAD)
set dy = y + (SHADOW_SPACING * index) * Sin(data.facing * bj_DEGTORAD)
call SetUnitX(data.shadows[index], dx)
call SetUnitY(data.shadows[index], dy)
set index = index + 1
endloop
else
call data.destroy()
endif
endif
endfunction
private function ShadowCharge_ChargeFinished takes nothing returns boolean
local unit trig = GetTriggerUnit()
local casterdata data = GetCSData(trig)
local real sin = Sin((GetUnitFacing(data.target)) * bj_DEGTORAD)
local real cos = Cos((GetUnitFacing(data.target)) * bj_DEGTORAD)
local integer id = GetUnitTypeId(data.target)
local integer index = 1
local real dx
local real dy
local integer alpha
if (trig == data.caster) then
call PauseUnit(data.caster, false)
call SetUnitAnimation(data.caster, "stand")
call SetUnitTimeScale(data.caster, 1)
call data.removeShadows()
set data.startX = GetUnitX(data.target)
set data.startY = GetUnitY(data.target)
set bj_ghoul[2012] = CreateUnit(data.owner, DUMMY_UNIT_ID, data.startX, data.startY, 0)
set data.owner = GetOwningPlayer(data.target)
set data.foodCost = GetUnitFoodUsed(data.target)
call SetUnitFacing(data.target, data.facing + 180)
loop
exitwhen (index > TARGET_SHADOW_AMOUNT)
set dx = data.startX + (SHADOW_SPACING * index) * cos
set dy = data.startY + (SHADOW_SPACING * index) * sin
set data.shadows[index] = CreateUnit(data.owner, id, dx, dy, data.facing + 180)
call SetPlayerState(data.owner, FOOD, GetPlayerState(data.owner, FOOD) - data.foodCost)
call UnitAddAbility(data.shadows[index], 039;Aloc039;)
call SetUnitX(data.shadows[index], dx)
call SetUnitY(data.shadows[index], dy)
set alpha = PercentToInt(100. - (index * 100. / (I2R(TARGET_SHADOW_AMOUNT) + 1.)), 255)
call SetUnitVertexColor(data.shadows[index], 255, 255, 255, alpha)
set index = index + 1
endloop
call SetUnitPathing(data.caster, true)
call SetUnitPathing(data.target, false)
call UnitApplyTimedLife(bj_ghoul[2012], 039;BTLF039;, 3.)
call UnitAddAbility(bj_ghoul[2012], DUMMY_STUN_ID)
call SetUnitAbilityLevel(bj_ghoul[2012], DUMMY_STUN_ID, GetUnitAbilityLevel(data.caster, SHADOW_CHARGE_ID))
call IssueTargetOrder(bj_ghoul[2012], DUMMY_STUN_ORDER, data.target)
call TimerStart(data.expired, TIMER_VAL, true, function ShadowCharge_MoveTarget)
endif
return false
endfunction
private function ShadowCharge_MoveCharger takes nothing returns nothing
local timer expired = GetExpiredTimer()
local casterdata data = GetCSData(expired)
local real x = GetUnitX(data.caster)
local real y = GetUnitY(data.caster)
local real speed = CASTER_CHARGE_SPEED / (1 / TIMER_VAL)
local real dx = x + speed * Cos(data.facing * bj_DEGTORAD)
local real dy = y + speed * Sin(data.facing * bj_DEGTORAD)
local real sin = Sin((data.facing - 180.) * bj_DEGTORAD)
local real cos = Cos((data.facing - 180.) * bj_DEGTORAD)
local integer index = 1
call EnumDestructablesInCircle(DESTRUCTABLE_KILL_RADIUS, dx, dy, function ShadowCharge_KillDestructables)
call SetUnitPosition(data.caster, dx, dy)
loop
exitwhen (index > CASTER_SHADOW_AMOUNT)
set dx = x + (SHADOW_SPACING * index) * cos
set dy = y + (SHADOW_SPACING * index) * sin
call SetUnitX(data.shadows[index], dx)
call SetUnitY(data.shadows[index], dy)
set index = index + 1
endloop
if (data.x == x) and (data.y == y) then
call data.destroy()
else
set data.x = x
set data.y = y
endif
endfunction
private function ShadowCharge_InitializeCharge takes nothing returns nothing
local casterdata data = casterdata.create()
local unit caster = GetTriggerUnit()
local unit target = GetSpellTargetUnit()
local player owner = GetOwningPlayer(caster)
local integer id = GetUnitTypeId(caster)
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local real facing = bj_RADTODEG * Atan2(GetUnitY(target) - y, GetUnitX(target) - x)
local timer t = NewTimer()
local trigger trig = CreateTrigger()
local integer index = 1
local real sin = Sin((facing - 180.) * bj_DEGTORAD)
local real cos = Cos((facing - 180.) * bj_DEGTORAD)
local real dx
local real dy
local integer alpha
call PolledWait(.1)
call PauseUnit(caster, true)
call TriggerRegisterUnitInRange(trig, target, TARGET_RANGE_RADIUS, null)
call TriggerAddCondition(trig, Condition(function ShadowCharge_ChargeFinished))
set data.foodCost = GetUnitFoodUsed(caster)
set data.caster = caster
set data.target = target
set data.facing = facing
set data.owner = owner
set data.expired = t
set data.trig = trig
call SetUnitFacing(caster, facing)
call SetUnitPathing(caster, false)
loop
exitwhen (index > CASTER_SHADOW_AMOUNT)
set dx = x + (SHADOW_SPACING * index) * cos
set dy = y + (SHADOW_SPACING * index) * sin
set data.shadows[index] = CreateUnit(owner, id, dx, dy, facing)
call PauseUnit(data.shadows[index] ,true)
call SetUnitX(data.shadows[index], dx)
call SetUnitY(data.shadows[index], dy)
call UnitAddAbility(data.shadows[index], 039;Aloc039;)
set alpha = PercentToInt(100. - (index * 100. / (CASTER_SHADOW_AMOUNT + 1.)), 255)
call SetUnitVertexColor(data.shadows[index], 255, 255, 255, alpha)
call SetPlayerState(owner, FOOD, GetPlayerState(owner, FOOD) - data.foodCost)
call SetUnitAnimationByIndex(data.shadows[index], CASTER_MOVE_ANI_INDEX)
call SetUnitTimeScale(data.shadows[index], CASTER_ANI_SPEED_INCREASE / 100)
set index = index + 1
endloop
call SetUnitAnimationByIndex(caster, CASTER_MOVE_ANI_INDEX)
call SetUnitTimeScale(caster, CASTER_ANI_SPEED_INCREASE / 100)
call SetCSData(t, data)
call SetCSData(caster, data)
call TimerStart(t, TIMER_VAL, true, function ShadowCharge_MoveCharger)
set owner = null
set t = null
set trig = null
endfunction
private function ShadowCharge_AbilityId takes nothing returns boolean
return GetSpellAbilityId() == SHADOW_CHARGE_ID
endfunction
//===========================================================================
function InitTrig_ShadowCharge takes nothing returns nothing
set gg_trg_ShadowCharge = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_ShadowCharge, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_ShadowCharge, Condition( function ShadowCharge_AbilityId))
call TriggerAddAction(gg_trg_ShadowCharge, function ShadowCharge_InitializeCharge)
endfunction
endscope
Have fun!
Comments, feedback and criticism always appreciated!
Andrewgosu