Name | Type | is_array | initial_value |
KB_Angle | real | Yes | |
KB_Casters | unit | Yes | |
KB_CountBuffs | integer | No | |
KB_DestroyTrees | boolean | Yes | |
KB_EffectCounter | integer | Yes | |
KB_EffectCounter2 | integer | Yes | |
KB_Effects_1 | string | Yes | |
KB_Effects_2 | string | Yes | |
KB_GeneralIntegers | integervar | Yes | |
KB_KnockbackedUnits | group | No | |
KB_Levels | integer | Yes | |
KB_MaxDistance | real | Yes | |
KB_ReachedDistance | real | Yes | |
KB_ReducedReal | real | No | |
KB_ReduceSpeedReal | real | Yes | |
KB_SpecificSpeed | real | Yes | |
KB_StartPositions | location | Yes | |
KB_TempPoint | location | Yes | |
KB_TempReal | real | No | |
KB_TotalKnockUnits | integer | No | |
KB_Units | unit | Yes | |
KBA_Caster | unit | No | |
KBA_DestroyTrees | boolean | No | |
KBA_DistancePerLevel | real | No | |
KBA_Level | integer | No | |
KBA_SpecialEffects | string | Yes | |
KBA_Speed | real | No | |
KBA_StartingPosition | location | No | |
KBA_TargetUnit | unit | No | |
Kill_count | integer | Yes | |
List | integer | No | |
Multiboard_Spots | integer | Yes | 0 |
Player_colors | string | Yes | |
Player_count | integer | No | |
Player_Unit | unit | Yes | |
Sample_Group | group | No | |
Sample_Point | location | No | |
SpawnUnit | timer | Yes | |
Timer1 | timer | Yes | |
TimerWindow | timerdialog | Yes |
//TESH.scrollpos=293
//TESH.alwaysfold=0
scope fireWall initializer Init
// Fire Wall spell v1.01
// made by MaskedPoptart
//
// inspired by the spell of the same name in diablo 2
//================== START SETUP SECTION =======================
//If you understand what something does, go ahead and change it as you desire.
globals
private constant integer SPELL_ID = 'A000' //CHANGE THIS. rawcode of your FireWall ability in object editor.
private constant string FLAME_EFFECT = "Environment\\LargeBuildingFire\\LargeBuildingFire2.mdl"
private constant string FLAME_EFFECT2 = "Abilities\\Spells\\Other\\ImmolationRed\\ImmolationREDTarget.mdl"
private constant real FLAME_SPACING = 40.0
private constant real FLAME_SPAWN_INTERVAL = 0.0029*FLAME_SPACING
private constant real DAMAGE_INTERVAL = 0.2 //how often units are damaged by the fire wall, in seconds.
//increase the interval slightly if it causes the game to slow down.
private constant real DAMAGE_RADIUS = 100.0 //the maximum distance a unit can be from the wall and still be
//damaged. if this number is less than 0.5*FLAME_SPACING, units will be able to walk through the wall undamaged.
private constant string FIRE_DAMAGE_UNIT_EFFECT = "Abilities\\Spells\\Other\\ImmolationRed\\ImmolationRedDamage.mdl"
private constant string FIRE_DAMAGE_UNIT_ATTACH = "chest"
private constant real FIRE_DAMAGE_UNIT_EFFECT_LENGTH = 1.2
private constant string FIRE_DAMAGE_DEST_EFFECT = "Environment\\LargeBuildingFire\\LargeBuildingFire2.mdl"
private constant string FIRE_DAMAGE_DEST_ATTACH = "Stump0"
private constant real FIRE_DAMAGE_DEST_EFFECT_LENGTH = 0.967
private constant boolean DAMAGE_DESTRUCTABLES = true
endglobals
//returns the length of time that the wall will burn
private function GetDuration takes integer level returns real
return 10.0+5.0*level
endfunction
//returns how long the wall is
private function GetWallLength takes integer level returns real
return 64.0+192.0*level
endfunction
//returns the damage per second that the wall causes
private function GetDPS takes integer level returns real
return 50.0*level
endfunction
//used by the damage filters
private function IsValidUnitTarget takes unit u, player ownerOfCaster returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and/*
*/ not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and/*
*/ IsUnitType(u, UNIT_TYPE_GROUND) and/*
*/ (IsUnitEnemy(u, ownerOfCaster) or/*
*/ GetOwningPlayer(u) == Player(PLAYER_NEUTRAL_PASSIVE))
endfunction
//used by the damage filters
private function IsValidDestTarget takes destructable d returns boolean
return GetDestructableLife(d) > 0
endfunction
//=================== END SETUP SECTION =======================
// DON'T TOUCH ANY OF THE CRAP AFTER THIS LINE
//returns the number of flames that make up the wall
private function GetNumFlames takes integer level returns integer
return R2I(GetWallLength(level)/FLAME_SPACING)
endfunction
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//each flame of the wall has its own struct instance
struct Flame
//each flame has two effects for optimal eye candy
private effect flameEffect
private effect flameEffect2
//each flame has a position, of course...
readonly real flameX
readonly real flameY
//each flame is spawned at a different time, so it needs a timer to tell it when to burn out.
private timer flameTimer
//returns true if flame is currently burning
readonly boolean isBurning
static method Destroy takes nothing returns nothing
local thistype f = GetTimerData(GetExpiredTimer())
call f.destroy()
endmethod
static method create takes real x, real y, integer level returns thistype
local thistype f = thistype.allocate()
set f.isBurning = true
set f.flameX = x
set f.flameY = y
set f.flameEffect = AddSpecialEffect(FLAME_EFFECT, x, y)
set f.flameEffect2 = AddSpecialEffect(FLAME_EFFECT2, x, y)
set f.flameTimer = NewTimer()
call SetTimerData(f.flameTimer, f)
//destroy f after GetDuration seconds
call TimerStart(f.flameTimer, GetDuration(level), false, function thistype.Destroy)
return f
endmethod
private method onDestroy takes nothing returns nothing
//put out the flame, clean up stuff
set this.isBurning = false
call DestroyEffect(this.flameEffect)
call DestroyEffect(this.flameEffect2)
call ReleaseTimer(this.flameTimer)
set this.flameTimer = null
endmethod
endstruct
private keyword FireWall
globals
//for use in the damage filters (below)
private rect destructableDamageRect
private group tempGroup = CreateGroup()
private FireWall tempFireWall
endglobals
//each "FireWall" instance contains a bunch of Flame instances as well as other properties
struct FireWall
private unit caster //caster of the spell
private player owner //owner of caster
private IntegerList flames //flames is a List of Flame struct objects.
//It's better to use a List than an array in this situation because arrays
//make terrible struct members, etc. See List documentation if you care.
private integer level //level of the spell
private real centerX //target x of casted spell
private real centerY //target y of casted spell
private real normalX //x component of perpendicular normalized vector (????)
private real normalY //y component of perpendicular normalized vector (don't worry about it)
private timer flameSpawnTimer //this is a repeating timer that will gradually form the wall by
//spawning pairs of flames.
private integer totalWidth //how many flames to spawn on each side of the wall
private integer currentWidth//how many flames are currently on each side of the wall (while spawning)
private timer damageTimer //this timer damages units near each flame, every DAMAGE_INTERVAL seconds
private static hashtable damagedTargets //this keeps track of units and/or destructables that were
//already damaged by this wall, during this DAMAGE_INTERVAL
private static boolexpr DamageUnitExpr //stores Filter(function thistype.DamageUnitFilter)
private static boolexpr DamageDestExpr //stores Filter(function thistype.DamageDestFilter)
//this method is used to periodically add one flame to both sides of the wall,
//if the total number of flames in the wall is supposed to be odd.
method addFlamesOdd takes nothing returns nothing
local integer i = this.currentWidth
local real x
local real y
set x = this.centerX + i*FLAME_SPACING*this.normalX //see how we can use vectors to efficiently
set y = this.centerY + i*FLAME_SPACING*this.normalY //form a perpendicular wall of flame objects?
call this.flames.addLast(Flame.create(x, y, this.level)) //create a flame and add it to the
//end of the list.
set x = this.centerX - i*FLAME_SPACING*this.normalX
set y = this.centerY - i*FLAME_SPACING*this.normalY
call this.flames.addFirst(Flame.create(x, y, this.level))//create a flame and add it to the
//start of the list.
//notice that we are able to keep the list sorted according to the order of the flames
// in the wall.
set this.currentWidth = i + 1
if(this.currentWidth>this.totalWidth)then //if we've spawned all the flames,
call ReleaseTimer(this.flameSpawnTimer) //stop the spawn timer
endif
endmethod
//Timers can't execute instance methods, so it executes a static method that redirects
//to an instance method, using the stored TimerData.
static method AddFlamesOdd takes nothing returns nothing
local thistype fw = GetTimerData(GetExpiredTimer())
call fw.addFlamesOdd()
endmethod
//this method is used to periodically add one flame to both sides of the wall,
//if the total number of flames in the wall is supposed to be even.
method addFlamesEven takes nothing returns nothing
local integer i = this.currentWidth
local real x
local real y
set x = this.centerX + (i+.5)*FLAME_SPACING*this.normalX
set y = this.centerY + (i+.5)*FLAME_SPACING*this.normalY
call this.flames.addLast(Flame.create(x, y, this.level))//same deal as addFlamesOdd
set x = this.centerX - (i+.5)*FLAME_SPACING*this.normalX
set y = this.centerY - (i+.5)*FLAME_SPACING*this.normalY
call this.flames.addFirst(Flame.create(x, y, this.level))
set this.currentWidth = i + 1
if(this.currentWidth>this.totalWidth)then
call ReleaseTimer(this.flameSpawnTimer)
endif
endmethod
static method AddFlamesEven takes nothing returns nothing
local thistype fw = GetTimerData(GetExpiredTimer())
call fw.addFlamesEven()
endmethod
//I don't like looping through groups twice, so I filter out units AND damage them
//in the filter.
static method DamageUnitFilter takes nothing returns boolean
local unit u = GetFilterUnit()
local real d = GetDPS(tempFireWall.level)*DAMAGE_INTERVAL
if(IsValidUnitTarget(u, tempFireWall.owner) /*
*/and LoadInteger(.damagedTargets, tempFireWall, GetHandleId(u)) == 0)then
//Usually one unit will be in range of more than one flame of the same wall. To keep
// the unit from being damaged multiple times by the same wall, we attach an integer
// to it in the static hashtable damagedTargets.
// 0 == not damaged yet this interval.
// 1 == already damaged this interval.
call UnitDamageTarget(tempFireWall.caster, u, d, false, true, ATTACK_TYPE_MAGIC,/*
*/ DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
//here we use TimedBuffEffects to make the fire damage effect appear on the unit
// without stacking on previous fire damage effects
call AddBuffEffectTarget(FIRE_DAMAGE_UNIT_EFFECT, u,/*
*/ FIRE_DAMAGE_UNIT_ATTACH, FIRE_DAMAGE_UNIT_EFFECT_LENGTH)
//unit has now been damaged, so save 1 in the hashtable
call SaveInteger(.damagedTargets, tempFireWall, GetHandleId(u), 1)
endif
return false
endmethod
//same as above, but for destructables
static method DamageDestFilter takes nothing returns boolean
local destructable dest = GetFilterDestructable()
local real dmg = GetDPS(tempFireWall.level)*DAMAGE_INTERVAL
if(IsValidDestTarget(dest) /*
*/and LoadInteger(.damagedTargets, tempFireWall, GetHandleId(dest)) == 0)then
call UnitDamageTarget(tempFireWall.caster, dest, dmg, false, true, ATTACK_TYPE_MAGIC,/*
*/DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
call SaveInteger(.damagedTargets, tempFireWall, GetHandleId(dest), 1)
endif
return false
endmethod
static method onInit takes nothing returns nothing
//this isn't completely necessary, but it keeps us from having to do an extra function call every loop.
set .DamageDestExpr = Filter(function thistype.DamageDestFilter)
set .DamageUnitExpr = Filter(function thistype.DamageUnitFilter)
endmethod
//This method loops through all the flames in the list.
//Each flame finds units in range of it, and damages them if they pass the conditions.
//keep in mind the filter checks the conditions and does the damaging.
method damageAllFlames takes nothing returns nothing
local Flame iFlame //current Flame object
local integer i //current index
if(this.flames.size>0)then
call FlushChildHashtable(.damagedTargets, this) //reset hashtable at start of each interval
set i = 0 //start at first index of list
loop
exitwhen i>=this.flames.size //stop after last index of list
set iFlame = Flame(this.flames[i]) //set iFlame to object stored at index i in the flames IntegerList
if(iFlame.isBurning)then //if flame is burning
set tempFireWall = this //store this FireWall in a temp global, damage units near iFlame's position.
call GroupEnumUnitsInRange(tempGroup, iFlame.flameX, iFlame.flameY, DAMAGE_RADIUS, .DamageUnitExpr)
if(DAMAGE_DESTRUCTABLES)then
call SetRect(destructableDamageRect, iFlame.flameX-DAMAGE_RADIUS, /*
*/iFlame.flameY-DAMAGE_RADIUS, iFlame.flameX+DAMAGE_RADIUS, iFlame.flameY+DAMAGE_RADIUS)
call EnumDestructablesInRect(destructableDamageRect, .DamageDestExpr, null)
endif
else
call this.flames.remove(i) //remove flame from list
endif
set i = i + 1
endloop
else
//individual flames burn out automatically because of their timers.
//after they burn out, we remove them from the list (see above).
//if there are no flames left (flames.size == 0), destroy this FireWall.
call this.destroy()
endif
endmethod
static method DamageAllFlames takes nothing returns nothing
local thistype fw = GetTimerData(GetExpiredTimer())
call fw.damageAllFlames()
endmethod
static method create takes unit caster, real targetX, real targetY, integer level returns thistype
local thistype fw = thistype.allocate()
local integer numFlames = GetNumFlames(level)
local real Dx = targetX - GetUnitX(caster) //Dx = x distance from caster to target
local real Dy = targetY - GetUnitY(caster) //Dy = y distance from caster to target
local real D = SquareRoot(Dx*Dx + Dy*Dy) //D = combined distance
local integer i
set fw.caster = caster
set fw.owner = GetOwningPlayer(caster)
set fw.centerX = targetX
set fw.centerY = targetY
set fw.normalX = -Dy/D //remember how to get a perpendicular line you take the negative reciprocal?
set fw.normalY = Dx/D //and to get a vector of length 1, you divide each component by the current length
set fw.level = level
set fw.flames = IntegerList.create() //initialize the IntegerList so it can store stuff.
set fw.totalWidth = numFlames/2 //width is from the center to the edge, remember?
set fw.flameSpawnTimer = NewTimer()
call SetTimerData(fw.flameSpawnTimer, fw)
if(numFlames/2*2 != numFlames)then //if numFlames is 3, for example, 3/2 =1.5 but 1.5 is converted to an
//integer by cutting off the stuff after the decimal. So, 3/2 = 1. 1*2 != 3, so 3 must be odd.
//Basically this checks if numFlames is odd.
call fw.flames.addLast(Flame.create(targetX, targetY, level))//create one flame at the center
set fw.currentWidth = 1
call TimerStart(fw.flameSpawnTimer, FLAME_SPAWN_INTERVAL, true, function thistype.AddFlamesOdd)
else
set fw.currentWidth = 0
call fw.addFlamesEven()//create two flames equally spaced from the center
call TimerStart(fw.flameSpawnTimer, FLAME_SPAWN_INTERVAL, true, function thistype.AddFlamesEven)
endif
set fw.damageTimer = NewTimer()
call SetTimerData(fw.damageTimer, fw)
call TimerStart(fw.damageTimer, DAMAGE_INTERVAL, true, function thistype.DamageAllFlames)
set fw.damagedTargets = InitHashtable()
return fw
endmethod
private method onDestroy takes nothing returns nothing
//clean up crap
call ReleaseTimer(this.damageTimer)
set this.damageTimer = null
call this.flames.destroy()
set this.flames = 0
set this.caster = null
call FlushChildHashtable(this.damagedTargets, this)
set this.damagedTargets = null
endmethod
endstruct
private function Actions takes nothing returns nothing
call FireWall.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY(), GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID))
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
set destructableDamageRect = Rect(0.0, 0.0, 1.0, 1.0)
endfunction
endscope