scope SpiralingTrails initializer init
globals
private constant integer ABILITY_ID = 'A000'
private constant integer DUMMY_ID = 'e000'
private constant string DUMMY_MODEL = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdx"
private constant real DT = 1.0 / 32.0
private constant real Z_OFFSET = 80.0
// the target point is considered reached if we are this many units away from it
private constant real TARGET_DISTANCE_THRESHOLD = 16.0
// if we are this many or less units from the terrain we stop
private constant real TERRAIN_DISTANCE_THRESHOLD = 16.0
// UnitDamageTarget arguments
private constant boolean MELEE_ATTACK = false
private constant boolean RANGE_ATTACK = true
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL // spell
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL // ignore armor value
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
private function unit_filter takes unit u returns boolean
// e.g: return false if the unit is invulnerable
return true
endfunction
private function linear_speed takes integer level returns real
return 500.0
endfunction
private function distance takes integer level returns real
return 900.0
endfunction
private function dummy_scale takes integer level returns real
return 0.5 + 0.5 * level
endfunction
private function spiral_radius takes integer level returns real
return 64.0
endfunction
private function damage takes integer level returns real
return 100.0 * level
endfunction
// NOTE: you can rewrite these functions to use your dummy recycling scripts!
globals
private unit created_dummy
endglobals
private function dummy_create takes player p returns unit
set created_dummy = CreateUnit(p, DUMMY_ID, 0.0, 0.0, 270.0)
call UnitAddAbility(created_dummy, 'Amrf')
call UnitRemoveAbility(created_dummy, 'Amrf')
call UnitAddAbility(created_dummy, 'Aloc')
call UnitRemoveAbility(created_dummy, 'Aloc')
return created_dummy
endfunction
private function dummy_destroy takes unit u returns nothing
call RemoveUnit(u)
endfunction
globals
private location loc = Location(0.0, 0.0)
endglobals
private function GetTerrainZ takes real x, real y returns real
call MoveLocation(loc, x, y)
return GetLocationZ(loc)
endfunction
private function GetUnitZ takes unit u returns real
call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
return GetLocationZ(loc) + GetUnitFlyHeight(u)
endfunction
private function SetUnitZ takes unit u, real z returns nothing
call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
call SetUnitFlyHeight(u, z - GetLocationZ(loc), 0.0)
endfunction
native UnitAlive takes unit u returns boolean
globals
private constant real TADT_POW_2 = TARGET_DISTANCE_THRESHOLD * TARGET_DISTANCE_THRESHOLD
private constant real TEDT = TERRAIN_DISTANCE_THRESHOLD
private real dx
private real dy
private real dz
private real v_len
private real ax
private real ay
private real az
private group G = CreateGroup()
private unit U
endglobals
private struct SpiralingTrail
static timer tmr = CreateTimer()
static thistype array update_list
static integer update_count = 0
player caster_owner
unit caster
unit dummy
effect dummy_model
real dummy_scale
real damage
// current position
real px
real py
real pz
// linear velocity
real dpx
real dpy
real dpz
// target point
real tx
real ty
real tz
real bvx_x
real bvx_y
real bvx_z
real bvy_x
real bvy_y
real bvy_z
real bvz_x
real bvz_y
real bvz_z
real spiral_radius
real ang_xy
real ang_xz
real ang_xz_inc
real x
real y
real z
static method create takes unit caster, string dummy_model, real px, real py, real pz, real tx, real ty, real tz, real ang_xz_initial, integer inc_sign, real speed, real scale, real p_spiral_radius, real p_damage returns thistype
local thistype this = allocate()
local real ang
local real y_axis_deg
local real bvz_ang_xy
local real bvz_ang_xz
set this.caster_owner = GetOwningPlayer(caster)
set this.caster = caster
set this.dummy = dummy_create(this.caster_owner)
set this.dummy_model = AddSpecialEffectTarget(dummy_model, this.dummy, "origin")
set this.dummy_scale = scale
call SetUnitScale(this.dummy, scale, scale, scale)
set this.damage = p_damage
set this.px = px
set this.py = py
set this.pz = pz
set this.tx = tx
set this.ty = ty
set this.tz = tz
set dx = this.tx - this.px
set dy = this.ty - this.py
set dz = this.tz - this.pz
set v_len = SquareRoot(dx * dx + dy * dy + dz * dz)
set dx = dx / v_len
set dy = dy / v_len
set dz = dz / v_len
set this.dpx = dx * speed * DT
set this.dpy = dy * speed * DT
set this.dpz = dz * speed * DT
set ang = 2.0 * bj_PI - Atan2(dy, dx)
set bvx_x = Cos(ang - bj_PI / 2.0)
set bvx_y = Sin(ang - bj_PI / 2.0)
set bvx_z = 0.0
set bvy_x = Cos(ang)
set bvy_y = Sin(ang)
set bvy_z = 0.0
set bvz_x = 0.0
set bvz_y = 0.0
set bvz_z = 1.0
set this.ang_xy = 0.0
set this.ang_xz = ang_xz_initial
set this.spiral_radius = p_spiral_radius
set this.ang_xz_inc = inc_sign * 2.0 * bj_PI / 15.0
call SetUnitX(this.dummy, this.px)
call SetUnitY(this.dummy, this.py)
call SetUnitZ(this.dummy, this.pz)
call SetUnitFacing(this.dummy, Atan2(dy, dx) * bj_RADTODEG)
call SetUnitAnimationByIndex(this.dummy, R2I(Atan2(dz, SquareRoot(dx * dx + dy * dy)) * bj_RADTODEG + 90.0))
return this
endmethod
method destroy2 takes integer i returns nothing
set update_list[i] = update_list[update_count]
set update_count = update_count - 1
call DestroyEffect(this.dummy_model)
call dummy_destroy(this.dummy)
set this.caster = null
set this.dummy = null
call this.destroy()
endmethod
static method move takes nothing returns nothing
local integer i
local thistype this
local real terrain_z
local boolean hit_unit
set i = 1
loop
exitwhen i > update_count
set this = update_list[i]
set dx = this.tx - this.px
set dy = this.ty - this.py
set dz = this.tz - this.pz
if dx * dx + dy * dy + dz * dz <= TADT_POW_2 then
// we reached the target point
//
set this.px = this.tx
set this.py = this.ty
set this.pz = this.tz
call SetUnitX(this.dummy, this.px)
call SetUnitY(this.dummy, this.py)
call SetUnitZ(this.dummy, this.pz)
call this.destroy2(i)
set i = i - 1
else
set this.px = this.px + this.dpx
set this.py = this.py + this.dpy
set this.pz = this.pz + this.dpz
set ax = this.spiral_radius * Sin(this.ang_xz) * Cos(this.ang_xy)
set ay = this.spiral_radius * Sin(this.ang_xz) * Sin(this.ang_xy)
set az = this.spiral_radius * Cos(this.ang_xz)
set this.x = this.px + this.bvx_x * ax + this.bvx_y * ay + this.bvx_z * az
set this.y = this.py + this.bvy_x * ax + this.bvy_y * ay + this.bvy_z * az
set this.z = this.pz + this.bvz_x * ax + this.bvz_y * ay + this.bvz_z * az
call SetUnitX(this.dummy, this.x)
call SetUnitY(this.dummy, this.y)
call SetUnitZ(this.dummy, this.z)
set this.ang_xz = this.ang_xz + this.ang_xz_inc
// check for enemy units
set hit_unit = false
call GroupEnumUnitsInRange(G, this.x, this.y, this.dummy_scale * 64.0, null)
loop
set U = FirstOfGroup(G)
exitwhen U == null
call GroupRemoveUnit(G, U)
if UnitAlive(U) and IsUnitEnemy(U, this.caster_owner) and not hit_unit and unit_filter(U) then
set hit_unit = true
if GetWidgetLife(U) - this.damage <= 0.405 then
call SetUnitExploded(U, true)
call UnitDamageTarget(this.caster, U, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
else
call UnitDamageTarget(this.caster, U, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endif
call this.destroy2(i)
set i = i - 1
exitwhen true
endif
endloop
if not hit_unit then
// check for terrain/cliffs
call MoveLocation(loc, this.x, this.y)
set terrain_z = GetLocationZ(loc)
if this.pz - terrain_z <= TEDT then
call this.destroy2(i)
set i = i - 1
endif
endif
endif
set i = i + 1
endloop
endmethod
method start_moving takes nothing returns nothing
set update_count = update_count + 1
set update_list[update_count] = this
if update_count == 1 then
call TimerStart(tmr, DT, true, function thistype.move)
endif
endmethod
endstruct
private struct SpiralingTrails extends array
static method cast takes nothing returns nothing
local SpiralingTrail st
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, ABILITY_ID)
local real dist = distance(level)
local real speed = linear_speed(level)
local real scale = dummy_scale(level)
local real p_spiral_radius = spiral_radius(level)
local real p_damage = damage(level)
local real cx = GetUnitX(caster)
local real cy = GetUnitY(caster)
local real cz = GetUnitZ(caster) + Z_OFFSET
local real tx = GetSpellTargetX()
local real ty = GetSpellTargetY()
local real tz = cz // GetTerrainZ(tx, ty)
// call MoveLocation(loc, tx, ty)
// set tz = GetLocationZ(loc)
set dx = tx - cx
set dy = ty - cy
set dz = tz - cz
set v_len = SquareRoot(dx * dx + dy * dy + dz * dz)
set dx = dx / v_len
set dy = dy / v_len
set dz = dz / v_len
set tx = cx + dx * dist
set ty = cy + dy * dist
set tz = cz + dz * dist
set st = SpiralingTrail.create(caster, DUMMY_MODEL, cx, cy, cz, tx, ty, tz, 0.0, 1, speed, scale, p_spiral_radius, p_damage)
call st.start_moving()
set st = SpiralingTrail.create(caster, DUMMY_MODEL, cx, cy, cz, tx, ty, tz, bj_PI / 4.0, -1, speed, scale, p_spiral_radius, p_damage)
call st.start_moving()
set caster = null
endmethod
endstruct
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddAction(t, function SpiralingTrails.cast)
endfunction
endscope