scope FiryStones initializer init
//*************************************************************************************************************//
// Firy Stones //
// by //
// cedi //
// //
// needs: TimerUtils by Vexorian //
// Bound Sentinel by Vexorian //
// IsTerrainWalkable by Antitarf //
// Dummy Model by //
// Knockback by Rising_Dusk //
// LastOrder by Rising_Dusk //
// UnitIndexingUtils by Rising_Dusk //
//*************************************************************************************************************//
//For use, copy the trigger to your map, copy the dummy create a spell and adjust the values below.
globals
//ID of the dummy spell
private constant integer SPELL_ID = 'A000'
//ID of the DoT spell
private constant integer DOT_ID = 'A001'
//ID of the enstrenghten spell
private constant integer ENSTRENGHT_ID = 'A002'
//ID of the dummy
private constant integer DUMMY_ID = 'h000'
//How many levels does your spell have?
private constant integer SPELL_LEVEL_COUNT = 4
//Amount of missiles
private constant integer MISSILE_COUNT = 8
//How much armor should each missile increase? Beware Max of 40 / MISSILE COUNT.
private constant integer ARMOR_PER_MISSILE = 1
//The move timer interval
private constant real TIMER_INTERVAL = 0.02
//Interval of the unit check
private constant real PICK_INTERVAL = 0.10
//Missile speed in ms
private constant real SPEED = 700.00
//Damage decrease per collision in percent
private constant real DAMAGE_LOOSE = 0.15
//Range decrease per collision in percent
private constant real RANGE_LOOSE = 0.15
//Knockback speed decrease per collision in percent
private constant real KNOCKBACK_LOOSE = 0.15
//Start turn speed.
private constant real SPEED_ANGLE = 3.00
//Increase of the turn speed.
private constant real SPEED_ANGLE_INC = 0.01
//Maximal turn speed.
private constant real MAX_SPEED_ANGLE = 6.00
//Distance between missiles and caster.
private constant real START_DISTANCE = 150.00
//Delay of the missiles.
private constant real MAX_SHOOT_DELAY = 5.00
//Size of the missiles 1.00 == 100%
private constant real SIZE = 1.00
//Collisions size of the missiles.
private constant real COLLISIONS_SIZE = 75.00
//Multipler of the damage for the end aoe damage.
private constant real AOE_DAMAGE_INC = 3.00
//Decrement of the knockback speed.
private constant real DECREMENT = 2.00
//Fly height of the missiles.
private constant real HEIGHT = 50.00
//Model of the missiles.
private constant string STONE_MODEL = "Abilities\\Weapons\\BallsOfFireMissile\\BallsOfFireMissile.mdl"
//Order string of the DoT spell.
private constant string DOT_ORDER_STRING = "acidbomb"
//Model when a missile is created.
private constant string CREATE_EFFECT = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
//Model when a missile dies.
private constant string DESTROY_EFFECT = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
//model when the missiles flies away.
private constant string SHOOT_EFFECT = ""
//Model when the missiles deal damage
private constant string DAMAGE_EFFECT = ""
//Should the missiles turn to when they fly away?
private constant boolean TURN = false
// SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM //
private integer array MAX_UNIT_HITTED[SPELL_LEVEL_COUNT]
private real array RANGE[SPELL_LEVEL_COUNT]
private real array AOE[SPELL_LEVEL_COUNT]
private real array DAMAGE[SPELL_LEVEL_COUNT]
private real array KNOCKBACK_RANGE[SPELL_LEVEL_COUNT]
private real array ROTATE_TIME[SPELL_LEVEL_COUNT]
private real REAL_SPEED = SPEED * TIMER_INTERVAL
private real TEMP_REAL
private group TEMP_GROUP = CreateGroup()
private player TEMP_PLAYER
endglobals
//Max amount of units hit by one missile.
private function SET_MAX_UNIT_HITTED takes nothing returns nothing
set MAX_UNIT_HITTED[1] = 2
set MAX_UNIT_HITTED[2] = 3
set MAX_UNIT_HITTED[3] = 4
set MAX_UNIT_HITTED[4] = 5
endfunction
//Max range.
private function SET_RANGE takes nothing returns nothing
set RANGE[1] = 700.00
set RANGE[2] = 750.00
set RANGE[3] = 800.00
set RANGE[4] = 850.00
endfunction
//AoE of the end damage
private function SET_AOE takes nothing returns nothing
set AOE[1] = 150.00
set AOE[2] = 200.00
set AOE[3] = 250.00
set AOE[4] = 300.00
endfunction
//Damage dealt by the missiles.
private function SET_DAMAGE takes nothing returns nothing
set DAMAGE[1] = 150.00
set DAMAGE[2] = 200.00
set DAMAGE[3] = 250.00
set DAMAGE[4] = 300.00
endfunction
//Not real, its the knockback speed in ms.
private function SET_KNOCKBACK_RANGE takes nothing returns nothing
set KNOCKBACK_RANGE[1] = 350.00
set KNOCKBACK_RANGE[2] = 400.00
set KNOCKBACK_RANGE[3] = 550.00
set KNOCKBACK_RANGE[4] = 600.00
endfunction
//The missiles rotate x seconds around the caster.
private function SET_ROTATE_TIME takes nothing returns nothing
set ROTATE_TIME[1] = 5.00
set ROTATE_TIME[2] = 6.00
set ROTATE_TIME[3] = 7.00
set ROTATE_TIME[4] = 8.00
endfunction
//*************************************************************************************************************//
// SYSTEM //
//*************************************************************************************************************//
private function IsPossibleTarget takes nothing returns boolean
return GetWidgetLife( GetFilterUnit() ) > 0.405 and IsUnitEnemy( GetFilterUnit(), TEMP_PLAYER ) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false
endfunction
struct Stone
unit missile = null
effect model = null
integer hitcounter = 0
integer place = 0
real dist = START_DISTANCE
real rotatetime = 0.00
real range = 0.00
real angle = 0.00
real speedangle = SPEED_ANGLE
real vx = 0.00
real vy = 0.00
real dmg = 0.00
real kbrange = 0.00
real ptime = 0.00
group hits = null
boolean rotate = true
FiryStone root = 0
private method Cast takes unit u returns nothing
local unit dummy = CreateUnit( GetOwningPlayer( .missile ), DUMMY_ID, GetUnitX( u ), GetUnitY( u ), 0.00 )
call UnitAddAbility( dummy, DOT_ID )
call SetUnitAbilityLevel( dummy, DOT_ID, .root.level )
call IssueTargetOrder( dummy, DOT_ORDER_STRING, u )
call UnitApplyTimedLife( dummy, 'BTLF', 2.00 )
set dummy = null
set u = null
endmethod
private method Dealdamage takes unit u, real x, real y returns nothing
local real angle = Atan2(GetUnitY( u ) - y, GetUnitX( u ) - x)
call .Cast( u )
call UnitDamageTarget( .missile, u, .dmg, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null )
call KnockbackTarget( .missile, u, angle, .kbrange, DECREMENT )
call DestroyEffect( AddSpecialEffect( DAMAGE_EFFECT, x, y ) )
set .dmg = .dmg * ( 1.00 - DAMAGE_LOOSE )
set .range = .range * ( 1.00 - RANGE_LOOSE )
set .kbrange = .kbrange * ( 1.00 - KNOCKBACK_LOOSE )
set .hitcounter = .hitcounter - 1
if .hitcounter <= 0 then
call .destroy()
endif
set u = null
endmethod
static method create takes FiryStone root, real angle, integer i returns Stone
local Stone data = Stone.allocate()
local real x = GetUnitX( root.caster ) + Cos( angle * bj_DEGTORAD ) * START_DISTANCE
local real y = GetUnitY( root.caster ) + Sin( angle * bj_DEGTORAD ) * START_DISTANCE
set data.root = root
set data.angle = angle
set data.missile = CreateUnit( GetOwningPlayer( root.caster ), DUMMY_ID, x, y, angle )
set data.model = AddSpecialEffectTarget( STONE_MODEL, data.missile, "origin" )
set data.hits = CreateGroup()
set data.rotatetime = ROTATE_TIME[root.level] + GetRandomReal( -MAX_SHOOT_DELAY, MAX_SHOOT_DELAY )
set data.range = RANGE[root.level]
set data.dmg = DAMAGE[root.level]
set data.kbrange = KNOCKBACK_RANGE[root.level]
set data.hitcounter = MAX_UNIT_HITTED[root.level]
set data.place = i
call SetUnitFlyHeight( data.missile, HEIGHT, 0.00 )
call SetUnitScale( data.missile, SIZE, SIZE, SIZE )
call DestroyEffect( AddSpecialEffect( CREATE_EFFECT, x, y ) )
return data
endmethod
method onDestroy takes nothing returns nothing
local unit u
local real x = GetUnitX( .missile )
local real y = GetUnitY( .missile )
local integer i = 0
set TEMP_PLAYER = GetOwningPlayer( .missile )
call GroupEnumUnitsInRange( TEMP_GROUP, x, y, AOE[.root.level], Condition( function IsPossibleTarget ) )
loop
set u = FirstOfGroup( TEMP_GROUP )
exitwhen u == null
call .Cast( u )
set TEMP_REAL = Atan2(GetUnitY( u ) - y, GetUnitX( u ) - x)
call UnitDamageTarget( .missile, u, .dmg * AOE_DAMAGE_INC, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null )
call KnockbackTarget( .missile, u, TEMP_REAL, .kbrange, DECREMENT )
call DestroyEffect( AddSpecialEffect( DAMAGE_EFFECT, x, y ) )
call GroupRemoveUnit( TEMP_GROUP, u )
endloop
call GroupClear( TEMP_GROUP )
call DestroyEffect( AddSpecialEffect( DESTROY_EFFECT, x, y ) )
call DestroyEffect( .model )
call KillUnit( .missile )
call GroupClear( .hits )
call DestroyGroup( .hits )
set i = GetUnitAbilityLevel( .root.caster, ENSTRENGHT_ID )
if i <= ARMOR_PER_MISSILE then
call UnitRemoveAbility( .root.caster, ENSTRENGHT_ID )
else
call SetUnitAbilityLevel( .root.caster, ENSTRENGHT_ID, i - ARMOR_PER_MISSILE )
endif
set .root.There[.place] = false
set .root.stonecount = .root.stonecount - 1
if .root.stonecount <= 0 then
set .root.destroyMe = true
endif
endmethod
private method Move takes nothing returns nothing
local real x = GetUnitX( .missile ) + .vx
local real y = GetUnitY( .missile ) + .vy
set .dist = .dist + REAL_SPEED
set .range = .range - REAL_SPEED
call SetUnitX( .missile, x )
call SetUnitY( .missile, y )
if IsTerrainWalkable( x, y ) == false or .range <= 0.00 then
call .destroy()
endif
endmethod
private method Rotate takes nothing returns nothing
if .speedangle >= MAX_SPEED_ANGLE then
set .speedangle = MAX_SPEED_ANGLE
else
set .speedangle = .speedangle + SPEED_ANGLE_INC
endif
set .angle = .angle + .speedangle
if .angle > 360.00 then
set .angle = .angle - 360.00
elseif .angle < 0.00 then
set .angle = .angle + 360.00
endif
call SetUnitX( .missile, GetUnitX( .root.caster ) + Cos( .angle * bj_DEGTORAD ) * START_DISTANCE )
call SetUnitY( .missile, GetUnitY( .root.caster ) + Sin( .angle * bj_DEGTORAD ) * START_DISTANCE )
endmethod
private method RotateEx takes nothing returns nothing
set .speedangle = .speedangle - SPEED_ANGLE_INC * 3.00
set .angle = .angle + .speedangle
if .angle > 360.00 then
set .angle = .angle - 360.00
elseif .angle < 0.00 then
set .angle = .angle + 360.00
endif
call SetUnitX( .missile, GetUnitX( .root.caster ) + Cos( .angle * bj_DEGTORAD ) * .dist )
call SetUnitY( .missile, GetUnitY( .root.caster ) + Sin( .angle * bj_DEGTORAD ) * .dist )
endmethod
method Control takes nothing returns nothing
local unit u
local real x
local real y
if GetWidgetLife( .root.caster ) <= 0.405 then
call .destroy()
endif
if .rotate == true then
set .rotatetime = .rotatetime - TIMER_INTERVAL
if .rotatetime <= 0.00 then
call DestroyEffect( AddSpecialEffect( SHOOT_EFFECT, GetUnitX( .missile ), GetUnitY( .missile ) ) )
set .rotate = false
set TEMP_REAL = Atan2(GetUnitY( .missile ) - GetUnitY( .root.caster ), GetUnitX( .missile ) - GetUnitX( .root.caster ) )
set .vx = Cos( TEMP_REAL ) * REAL_SPEED
set .vy = Sin( TEMP_REAL ) * REAL_SPEED
else
call .Rotate()
endif
else
call .Move()
if TURN then
call .RotateEx()
endif
endif
set .ptime = .ptime + TIMER_INTERVAL
if .ptime >= PICK_INTERVAL then
set .ptime = 0.00
set x = GetUnitX( .missile )
set y = GetUnitY( .missile )
set TEMP_PLAYER = GetOwningPlayer( .missile )
call GroupEnumUnitsInRange( TEMP_GROUP, x, y, COLLISIONS_SIZE, Condition( function IsPossibleTarget ) )
loop
set u = FirstOfGroup( TEMP_GROUP )
exitwhen u == null
if IsUnitInGroup( u, .hits ) == false then
call GroupAddUnit( .hits, u )
call .Dealdamage( u, x, y )
endif
call GroupRemoveUnit( TEMP_GROUP, u )
endloop
call GroupClear( TEMP_GROUP )
endif
endmethod
endstruct
private function OutControl takes nothing returns nothing
local timer t = GetExpiredTimer()
local FiryStone data = GetTimerData( t )
set t = null
call data.Main()
endfunction
struct FiryStone
unit caster = null
timer time = null
integer stonecount = MISSILE_COUNT
integer level = 0
boolean destroyMe = false
boolean array There[MISSILE_COUNT]
Stone array stones[MISSILE_COUNT]
method onDestroy takes nothing returns nothing
call PauseTimer( .time )
call ReleaseTimer( .time )
endmethod
method Main takes nothing returns nothing
local integer i = 0
if .destroyMe == true then
call .destroy()
endif
loop
exitwhen i == MISSILE_COUNT
if .There[i] == true then
call .stones[i].Control()
endif
set i = i + 1
endloop
endmethod
static method create takes unit caster, integer level returns FiryStone
local FiryStone data = FiryStone.allocate()
local integer i = 0
local real angle = 360.00 / I2R( MISSILE_COUNT )
set data.caster = caster
set data.time = NewTimer()
set data.level = level
call UnitAddAbility( caster, ENSTRENGHT_ID )
call SetUnitAbilityLevel( caster, ENSTRENGHT_ID, MISSILE_COUNT * ARMOR_PER_MISSILE )
loop
exitwhen i == MISSILE_COUNT
set data.There[i] = true
set data.stones[i] = Stone.create( data, angle * I2R( i ), i )
set i = i + 1
endloop
call SetTimerData( data.time, data )
call TimerStart( data.time, TIMER_INTERVAL, true, function OutControl )
return data
endmethod
endstruct
private function Action takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel( u, SPELL_ID )
local FiryStone data = FiryStone.create( u, lvl )
set u = null
endfunction
private function IsSpell takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
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 IsSpell ) )
call TriggerAddAction( t, function Action )
call SET_MAX_UNIT_HITTED()
call SET_RANGE()
call SET_AOE()
call SET_DAMAGE()
call SET_KNOCKBACK_RANGE()
call SET_ROTATE_TIME()
set t = null
endfunction
endscope