//***********************************************************************************************************
//***********************************************************************************************************
//* Kraken Shell v1.0 by Kingz *
//* Made for Zephyr Contest #9 Assassination *
//* *
//* Used Libraries: TimerUtils,Dummy,RegisterAnyUnitEvent, DummyCast, TerrainPathability, World Bounds * *
//* *
//***********************************************************************************************************
//***********************************************************************************************************
native UnitAlive takes unit u returns boolean
scope Spell initializer onInit
//***********************************************************************************************************
//* Configuration *
//***********************************************************************************************************
globals
private constant integer SPELL_ID = 'A000' // spell id, change if needed
private constant integer STUN_ID = 'A001' // dummy stun id
private constant integer CRIPPLE_ID = 'A002' // dummy cripple id
private constant string MISSILE = "Abilities\\Weapons\\BoatMissile\\BoatMissile.mdl" // model of the actual missile
private constant string HOOK = "abilities\\weapons\\WyvernSpear\\WyvernSpearMissile.mdl" // model of the chain piece
private constant string BLEED_EFF = "Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodPeasant.mdl" // model of the drag effect
private constant integer CHAIN_COUNT = 4 // number of units pulled upon explosion
private constant real AoE = 420.00 // AoE within who are the units pulled, changing this means you need to alter MAX_H_COUNT
private constant real ZOFFSET = 65.00 // Flying height of the chain pieces
private constant integer MAX_H_COUNT = 20 // should be a division of AoE, preferably AoE/H_COLISION_SIZE or higher, has to be set up manually
// max number of chain pieces, in case the target goes away too far from the chain it will retract
// private constant real ELASTIC_FACTOR = 0.45 // not implemented, yet.
private constant real H_COLISION_SIZE = 32.00 // colision size of chain pieces, used for spacing between pieces
private constant real MISSILE_SPEED = 72.00 // missile speed
private constant real COLISION_SIZE = MISSILE_SPEED + 4.0 // CHANGE ONLY IF THE MISSILE IS MISSING THE TARGET
//colision size of the unit, should be greater than move speed of the projectile
endglobals
// function used to get damage based on spell level
private function GetHitDamage takes integer i returns real
return 120.00 + 40*(i-1)
endfunction
// function used to get drag damage based on spell level
private function GetDragDamage takes integer i returns real
return 60.00 + 20.00*(i-1)
endfunction
// function used to get distance between coords, makes the code easier to read
private function GetDistance takes real x1, real x2, real y1, real y2 returns real
return SquareRoot( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))
endfunction
//***********************************************************************************************************
//* End of configuration *
//***********************************************************************************************************
globals
private group ENUM_GROUP = CreateGroup() // group used for enumeration
private constant real TIMEOUT = 0.03 // timeout of 1 cycle of operations executed
endglobals
//***********************************************************************************************************
private struct chain
unit caster // caster
unit target // target
unit array hook[MAX_H_COUNT] // dummy array for chain pieces
effect array model[MAX_H_COUNT] // effect array for dummy models
real rad // direction of the chain in radians
real x // starting x coordinate of the chain
real y // starting y coordinate of the chain
real dmg // damage the chain deals periodically
boolean retract // if true, retract the chain
boolean hooked // if true, deal damage
integer count // counter, used for creating chain pieces with offset and chain movement
// below is standard struct stuff used for looping through struct instances
private integer loopIndex
private static thistype array loopInstances
private static integer loopInstancesCount=0
private static timer LOOP_TIMER=CreateTimer()
//***********************************************************************************************************
// callback function, executed every TIMEOUT seconds, loops through loopInstancesCount struct instances
private static method callback takes nothing returns nothing
local integer i=loopInstancesCount-1
local thistype this
loop
exitwhen i<0
set this=loopInstances[i]
// if retract is false, create more chain pieces, move then, increase count
if not .retract then
// increase the count of units, set the facing of the chain
set .count = .count +1
set .rad = Atan2( GetUnitY(.target) - .y , GetUnitX(.target)- .x)
// create the dummy at the needed X,Y,Z coordinates
set .hook[.count] = Dummy.create(.x, .y, .rad*bj_RADTODEG).unit
call SetUnitFlyHeight(.hook[.count], GetUnitFlyHeight(.caster)+ZOFFSET, 0.0)
// create dummy model
set .model[.count ] = AddSpecialEffectTarget(HOOK, .hook[.count], "origin")
// set dummy coordinates
call SetUnitX(.hook[.count], .x + H_COLISION_SIZE*.count * Cos(.rad))
call SetUnitY(.hook[.count], .y + H_COLISION_SIZE*count * Sin(.rad))
// check whether the chain did hit the target
if GetDistance(GetUnitX(.hook[.count]), GetUnitX(.target), GetUnitY(.hook[.count]), GetUnitY(.target)) <= COLISION_SIZE then
set .hooked = true
set .retract = true
set .dmg = .dmg / (.count+1) // redistribute damage to every count instance to make sure exact damage is dealt
endif
// secondary evaluation, checks whether the chain failled to get to the target
if .count >= MAX_H_COUNT or .target==null then
set .retract = true
endif
else
// the chain is retracting
// if the target was hit move the target, apply damage, create drag effect
if .target!= null and .hooked then
// if the terrain is walkable, move target
if IsTerrainWalkable(GetUnitX(.hook[.count]),GetUnitY(.hook[.count])) then
call SetUnitX(.target, GetUnitX(.hook[.count]))
call SetUnitY(.target, GetUnitY(.hook[.count]))
endif
// display drag effect
call DestroyEffect(AddSpecialEffectTarget(BLEED_EFF, .target, "chest"))
// deal damage to target
call UnitDamageTarget(.caster, .target, .dmg, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
// destroy the last chain piece, and it's model
call Dummy[.hook[.count]].destroy()
call DestroyEffect(.model[.count])
set .model[.count] = null
set .hook[.count]=null
// decrease the count of existing pieces by 1
set .count = .count -1
// if the count below 0 is reached, all pieces are gonne, the chain has done it's thing and it's time to clean up
if .count < 0 then
// apply dummy slow effect to target, if it was hit
if .target != null and .hooked then
call DummyCastTarget(.target, CRIPPLE_ID, "cripple")
endif
// reset variables, destroy instance
set .count = 0
set .retract = false
set .hooked =false
set .target = null
call .destroy()
endif
endif
set i=i-1
endloop
endmethod
// handles the indexes and struct instances, pauses the timer when needed
method destroy takes nothing returns nothing
set loopInstancesCount=loopInstancesCount-1
set loopInstances[loopIndex]=loopInstances[loopInstancesCount]
set loopInstances[loopIndex].loopIndex=loopIndex
if loopInstancesCount==0 then
call PauseTimer(LOOP_TIMER)
endif
call this.deallocate()
endmethod
// sets all the starting values, runs the chain effect
static method run takes unit cast, real xorigin, real yorigin, unit targ2, real damage returns nothing
local thistype this= thistype.allocate()
set loopInstances[loopInstancesCount]=this
set loopIndex=loopInstancesCount
set .caster = cast
set .target = targ2
set .x = xorigin
set .y = yorigin
set .dmg = damage
set .count = -1
if loopInstancesCount==0 then
call TimerStart(LOOP_TIMER, TIMEOUT, true, function thistype.callback)
endif
set loopInstancesCount=loopInstancesCount+1
endmethod
endstruct
//***********************************************************************************************************
//***********************************************************************************************************
private struct spell
unit caster // caster unit
unit target // target unit
unit missile // dummy unit
effect model // model of the dummy
integer count // ??
real dmg // damage on impact
real dragDmg // drag damage
real rad // facing of the dummy
real x // dummy x
real y // dummy y
// used for group filter
private static thistype tmp
// below is standard struct stuff used for looping through struct instances
private integer loopIndex
private static thistype array loopInstances
private static integer loopInstancesCount=0
private static timer LOOP_TIMER=CreateTimer()
//**********************************************************************************************************
// group filter
static method FilterGroup takes nothing returns boolean
local unit f = GetFilterUnit() // filtering unit
// the actual filter
if IsUnitEnemy(f, GetOwningPlayer(thistype.tmp.caster)) and UnitAlive(f) and IsUnitType(f, UNIT_TYPE_STRUCTURE)==false and IsUnitType(f, UNIT_TYPE_MECHANICAL)==false and IsUnitType(f, UNIT_TYPE_FLYING)==false and thistype.tmp.count < CHAIN_COUNT and f!=thistype.tmp.target then
// unit passed the filter, run chain effect
call chain.run(thistype.tmp.caster, GetUnitX(thistype.tmp.target), GetUnitY(thistype.tmp.target), f, thistype.tmp.dragDmg)
set thistype.tmp.count = thistype.tmp.count + 1
endif
// null the filter unit, return false always making no units added to the group
set f = null
return false
endmethod
// callback function, executed every TIMEOUT seconds, loops through loopInstancesCount struct instances
private static method callback takes nothing returns nothing
local integer i=loopInstancesCount-1
local thistype this
loop
exitwhen i<0
set this=loopInstances[i]
// missile movement adjustments
set .rad = Atan2( GetUnitY(.target) - GetUnitY(.caster), GetUnitX(.target) - GetUnitX(.caster))
set .x = .x + MISSILE_SPEED * Cos(.rad)
set .y = .y + MISSILE_SPEED * Sin(.rad)
call SetUnitX(.missile, .x)
call SetUnitY(.missile, .y)
// prevents the missile going outside map bounds
if .x >= WorldBounds.maxX or .x <= WorldBounds.minX or .y >= WorldBounds.maxY or .y <= WorldBounds.minY then
set .target = null
endif
// if the distance between the unit and the missile is less than unit's colision size or the target is null, run effect
if GetDistance(.x, GetUnitX(.target), .y, GetUnitY(.target)) <= COLISION_SIZE or .target==null then
// if the target exists, do the chain effect
if .target!= null then
call UnitDamageTarget(.caster, .target, .dmg, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
set thistype.tmp = this
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), AoE, function thistype.FilterGroup)
call DummyCastTarget(.target, STUN_ID, "thunderbolt")
endif
// destroys the dummy model, destroys the dummy instance, nulls the needed variables and destroys this spell instance
call DestroyEffect(.model)
call Dummy[.missile].destroy()
set .caster = null
set .target = null
set .missile = null
call .destroy()
endif
set i=i-1
endloop
endmethod
// handles the indexes and struct instances, pauses the timer when needed
method destroy takes nothing returns nothing
set loopInstancesCount=loopInstancesCount-1
set loopInstances[loopIndex]=loopInstances[loopInstancesCount]
set loopInstances[loopIndex].loopIndex=loopIndex
if loopInstancesCount==0 then
call PauseTimer(LOOP_TIMER)
endif
call this.deallocate()
endmethod
// sets all the starting values, runs the spell
static method run takes unit cast, unit targ returns nothing
local thistype this= thistype.allocate()
set loopInstances[loopInstancesCount]=this
set loopIndex=loopInstancesCount
set .caster = cast
set .target = targ
set .count = 0
set .dmg = GetHitDamage(GetUnitAbilityLevel(cast, SPELL_ID))
set .dragDmg = GetDragDamage(GetUnitAbilityLevel(cast, SPELL_ID))
set .rad = Atan2( GetUnitY(.target) - GetUnitY(.caster), GetUnitX(.target) - GetUnitX(.caster))
set .x = GetUnitX(.caster)
set .y = GetUnitY(.caster)
set .missile = Dummy.create(.x, .y, .rad*bj_RADTODEG).unit
call SetUnitFlyHeight(.missile, GetUnitFlyHeight(.caster)+ZOFFSET, 0.0)
set .model = AddSpecialEffectTarget(MISSILE, .missile, "origin")
if loopInstancesCount==0 then
call TimerStart(LOOP_TIMER, TIMEOUT, true, function thistype.callback)
endif
set loopInstancesCount=loopInstancesCount+1
endmethod
endstruct
//***********************************************************************************************************
//***********************************************************************************************************
// condition for the spell to fire
private function runCheck takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call spell.run(GetTriggerUnit(), GetSpellTargetUnit())
endif
return false
endfunction
//***********************************************************************************************************
//***********************************************************************************************************
// register the spell event, add the needed condition
private function onInit takes nothing returns nothing
call RegisterAnyUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function runCheck, null)
endfunction
//***********************************************************************************************************
endscope