library SplitterWave initializer Init uses IsTerrainWalkable,TNTK
globals
private constant integer SID = 'spws'
//The spell rawcode
private constant integer DID = 'spld'
//The dummy unit rawcode
private constant real MOVE_STEP = 25
//The moving speed of a wave
private constant real COLLISION_RADIUS = 80.
//The collisions radius of a wave
private constant real MIN_DAMAGE = 10.0
//The minimal damage dealt on impact. Because of the damage
//reduction feature this is necessary.
private constant real TIME_OUT = 0.04
//Time buffer which prevents perma multi-splits
private constant string SPLIT_SFX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
//Special effect being displayed when a wave *dies*
private constant string IMPACT_SFX = ""
//Special effect being displayed on a unit which got hit by a wave
//Damage options... should be selfexplaining
private constant attacktype ATTACK = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = WEAPON_TYPE_WHOKNOWS
endglobals
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//The spell privates. PLEASE DO NOT TOUCH !
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
private keyword Control
globals
private Control TEMP
private group TEMP_GROUP
private filterfunc CollisionFilter //Filter callback for the enum group
endglobals
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//The maximal travel distance for one spell instance....Well if you don´t understand here´s an example:
//The maximal travel distance is 5000. The first wave has already travelled 3000. If it splits the new
//waves can reach a maximal distance of 2000 (5000-3000). This is valid for every split
private constant function GetMaxDistance takes integer level returns integer
return level * 1250 + 550
endfunction
//The maximal amount of splits for one spell instance
private constant function GetSplits takes integer level returns integer
return level * 4 - 1
endfunction
//The amount of new waves which spawn when a wave is splitting
private constant function GetNewWaveAmount takes integer level returns integer
return level * 0 + 3
endfunction
//The damage dealt to the nearby enemies
private constant function GetImpactDamage takes integer level returns integer
return level * 30 + 10
endfunction
//A small part of the damage which is decreasing on every split.
private constant function GetDamageReduction takes real level returns real
return 2 / level + 1.5
endfunction
//A single wave instance
private struct Wave extends TNTKnock
//Destructor
method onDestroy takes nothing returns nothing
local Control m = .data
set m.waves = m.waves -1
set m.damage = m.damage - m.reduct
if m.damage < MIN_DAMAGE then
set m.damage = MIN_DAMAGE
endif
call DestroyEffect(AddSpecialEffect(SPLIT_SFX,.x,.y))
call RemoveUnit(.knocker)
endmethod
//Selfexplaining
method OnSplit takes nothing returns nothing
local Control m = .data
local real start = Acos(.velx/MOVE_STEP)
local real end = start + bj_PI
local real step = bj_PI / m.new
local unit u
//Checks the timer buffer, the remaining distance and splits
if m.splits > 0 and .distance > 0 and m.cansplit then
set m.cansplit = false
set m.splits = m.splits - 1 //If this becomes 0 the spell is over
//Creating new waves
loop
exitwhen start > end
set u = CreateUnit(Player(15),DID,.0,.0,start*bj_RADTODEG)
call Wave.create(m,u,.x + 2 * .speed * Cos(start),.y + 2 * .speed * Cos(start),start,.distance - 2* .speed)
set m.waves = m.waves + 1
set start = start + step
endloop
endif
call .destroy()
set u = null
endmethod
//Redefining the onBreak method
method onBreak takes nothing returns boolean
local Control m = .data
//Is the instance about to be destroyed normally
if super.onBreak() then
set m.waves = m.waves -1
return true
endif
set TEMP = m
call GroupClear(TEMP_GROUP)
call GroupEnumUnitsInRange(TEMP_GROUP,.x,.y,COLLISION_RADIUS,CollisionFilter)
if not IsTerrainWalkable(.x,.y) or FirstOfGroup(TEMP_GROUP) != null then
call .OnSplit()
return true
endif
return false
endmethod
//Constructor
static method create takes Control m, unit dummy, real x, real y, real rad, real dist returns thistype
local thistype this = thistype.allocate()
call .StartLinear(null,x,y,rad,dist,MOVE_STEP)
call SetUnitPosition(dummy,.x + .velx,.y + .vely)
call SetUnitFacing(dummy,rad*bj_RADTODEG)
set .knocker = dummy
set .data = m
return this
endmethod
endstruct
//This struct controls the splits and holds some coherent data
private struct Control extends Indexable
real runtime = TIME_OUT
unit caster
//real timeout //The time buffer
real damage
real reduct
integer splits
integer waves
integer new
boolean cansplit
//Checks if there are on splits/waves left
method onBreak takes nothing returns boolean
return .splits <= 0 or .waves <= 0
endmethod
//Updates the the time buffer
method onLoop takes nothing returns nothing
set .cansplit = true
endmethod
//Creator
static method create takes unit ca, unit dummy, location loc returns thistype
local thistype this = thistype.allocate()
local real rad = Atan2(GetLocationY(loc) - GetWidgetY(ca),GetLocationX(loc) - GetWidgetX(ca))
local integer lv = GetUnitAbilityLevel(ca,SID)
call Wave.create(this,dummy,GetWidgetX(ca),GetWidgetY(ca),rad,GetMaxDistance(GetUnitAbilityLevel(ca,SID)))
set .caster = ca
set .new = GetNewWaveAmount(lv)
set .splits = GetSplits(lv)
set .damage = GetImpactDamage(lv)
set .reduct = GetDamageReduction(lv)
set .waves = 1
set .cansplit = true
return this
endmethod
endstruct
//A filter which checks if there are valid-enemy units near a wave
private function CollisionFilterFunc takes nothing returns boolean
local Control m = TEMP
local unit u = GetFilterUnit()
//Checks if a wave has to split
if IsUnitEnemy(u,GetOwningPlayer(m.caster)) and not ( /*
*/ GetUnitTypeId(u) == DID or /*
*/ IsUnitType(u, UNIT_TYPE_DEAD) or /*
*/ IsUnitType(u, UNIT_TYPE_STRUCTURE) or /*
*/ IsUnitType(u, UNIT_TYPE_MECHANICAL) or /*
*/ IsUnitType(u, UNIT_TYPE_FLYING) or /*
*/ IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or /*
*/ IsUnitType(u, UNIT_TYPE_ANCIENT) ) /*
*/then
call UnitDamageTarget(m.caster,u,m.damage,false,false,ATTACK,DAMAGE,WEAPON)
call DestroyEffect(AddSpecialEffect(IMPACT_SFX,GetWidgetX(u),GetWidgetY(u)))
set u = null
return true
endif
set u = null
return false
endfunction
//Starts the spell
private function onCast takes nothing returns nothing
call Control.create(GetTriggerUnit(),CreateUnit(Player(15),DID,.0,.0,.0),GetSpellTargetLoc())
//I know its a bad idea to create the dummy unit at this point of the script
//but this will prevent a weird bug
endfunction
//Checks the rawcode
private function onCheck takes nothing returns boolean
return GetSpellAbilityId() == SID
endfunction
//Initializer
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
set TEMP_GROUP = CreateGroup()
set CollisionFilter = Filter( function CollisionFilterFunc )
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddAction(trig, function onCast)
call TriggerAddCondition(trig, Filter( function onCheck))
call Preload(SPLIT_SFX)
call Preload(IMPACT_SFX)
call PreloadStart()
set trig = null
endfunction
endlibrary