Blade Barrier by BlackRose
v1.04 - 04/07/2010
v1.04 - 04/07/2010
Screenshot:
Description:
Creates a barrier of blades around the caster, dealing damage to enemy units nearby.
- MUI
- Coded in vJass
Requires:
- AIDS by Jesus4Lyf
- GTrigger by Jesus4Lyf
- KeyTimers2 by Jesus4Lyf
- SimError by Vexorian
- Table by Vexorian
Ability code:
JASS:
library BladeBarrier initializer onInit requires GT, KT, AIDS, SimError, Table
//+----------------------------------------------------------------------------------+
//| v1.04b - 11/07/2010 Sunday
//| Blade Barrier by BlackRose
//|
//+----------------------------------------------------------------------------------+
//|
//| Description: Creates a barrier of blades around the caster unit, damaging enemy
//| units that get in the way.
//|
//| Requires: - JassHelper [latest - <a href="http://www.wc3c.net/showthread.php?t=88142]" target="_blank" class="link link--external" rel="nofollow ugc noopener">http://www.wc3c.net/showthread.php?t=88142]</a>
//| - GTrigger by Jesus4Lyf
//| - KeyTimer2 by Jesus4Lyf
//| - AIDS by Jesus4Lyf
//| - SimError by Vexorian
//| - Table by Vexorian
//|
//| Usage: call BladeBarrier_Create( unit source, unit target, integer level )
//|
//| How to import:
//| -------------
//| 1) Copy this trigger, and all requirements listed above into your map.
//| (You can copy the folder)
//| 2) Go to AIDS system, and follow it's implementation steps.
//| 3) Copy any object with Blade Barrier in it.
//| 4) Import the custom pitchModel into your map.
//| 5) Update all rawcodes.
//|
//+----------------------------------------------------------------------------------+
//|
//| - All credit to Tossrock, creator or Pudge Wars Advanced for the spell concept.
//|
//+----------------------------------------------------------------------------------+
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
//+ CONFIGURABLES SECTION
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
globals
private constant integer ABILITY_ID = 039;A004039; // "Blade Barrier" ability ID. This causes the response.
private constant integer DUMMY_ID = 039;h001039; // Rawcode of the required dummy unit. Must used the PitchDummy.MDX model.
private constant real TIMER_PERIOD = 0.03 // Movement of Blades done every TIMER_PERIOD.
private constant real DAMAGE_PERIOD = 0.10 // Units are damaged every DAMAGE_PERIOD.
private constant boolean ALLOW_MULTI_INSTANCE = false // If enabled, more than one Blade Barrier's can be made per unit.
private constant string ERROR_MSG = "You can only have one Blade Barrier at once."
//=====================================================
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype WEAPON_TYPE = null
private constant real COLLISION_SIZE = 32 // Collision size of units... well Pudge.
private constant real BLADE_SPINSPEED = 8.00
private constant real BLADE_ANGLESPEED = 0.12
//=====================================================
// Where damage effects are made.
private constant boolean DO_DMG_SFX = true
private constant string DMG_SFX = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
private constant string DMG_LOC = "chest"
// When a unit is killed by Blade Barrier, create these effects.
private constant boolean DO_DEATH_SFX = false
private constant string DEATH_SFX = ""
private constant string DEATH_LOC = ""
//=====================================================
// Sounds:
// --------
// They are played every x ticks of the TIMER_PERIOD, which is 0.075 seconds. One of the sounds below is chosen to play.
// Note: Some sounds cannot be played rapidly.
private constant real SOUND_EVERY = 0.09 // It has to be a multiple of TIMER_PERIOD.
private constant integer SOUND_VOLUME = 50 // The volume of the created sounds. Max is 127.
private constant string SOUND_ONE = "Sound\\Units\\Combat\\MetalLightSliceMetal1.wav"
private constant string SOUND_TWO = "Sound\\Units\\Combat\\MetalLightSliceMetal2.wav"
private constant string SOUND_THREE = "Sound\\Units\\Combat\\MetalLightSliceMetal3.wav"
private constant string SOUND_PARAM = "MetalLightSliceMetal"
private constant string SOUND_EAX = "CombatSoundsEAX"
private constant integer SOUND_DUR_1 = 376
private constant integer SOUND_DUR_2 = 480
private constant integer SOUND_DUR_3 = 461
//=====================================================
// Models:
// -------
// What shall your Blade Barrier look like?
private constant boolean FADE = true // Or else its death animation will be played.
private constant real RANGE_OFFSET = 20 // Blade will be created at RADIUS (function) + GetRandomReal( -20, 20 )
// The unit is not created with this colour for effiency.
// You must make sure these match the Object Editor's values unless you want an odd result.
private constant integer RED = 255
private constant integer GREEN = 255
private constant integer BLUE = 255
private constant real HEIGHT = 60.00
private constant string MODEL_ONE = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
private constant string MODEL_TWO = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
private constant string MODEL_THREE = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
// Should the death animation be that of the original Pudge Wars?
private constant boolean ORIGINAL_DEATH_ANIM = false
endglobals
//+------------------------+
//| Don't touch below. |
//+------------------------+
native UnitAlive takes unit id returns boolean
private keyword BladeBarrier
globals
private constant integer INLINED_VOLUME = R2I(SOUND_VOLUME * 127.00 * 0.01)
private BladeBarrier tempData = 0
endglobals
// Now you may start poking numbers again.
//===========================================================\\
// Unit filter:
// - Organic
// - Living
// - Enemy
private function IsTargetValid takes unit whichUnit returns boolean
return IsUnitEnemy( whichUnit, tempData.owner ) and /* Enemy
*/ IsUnitType( whichUnit, UNIT_TYPE_STRUCTURE ) == false and /* Structure
*/ UnitAlive( whichUnit ) and /* Alive
Don't touch this one.
*/ IsUnitInGroup( whichUnit, tempData.damageGroup ) == false
endfunction
//===========================================================\\
// Damage per second.
private function DPS takes integer level returns real
return level * 250.
endfunction
//===========================================================\\
// Blade Barrier duration.
private function DURATION takes integer level returns real
return 20.00
endfunction
//===========================================================\\
// Area of the spell.
private function RADIUS takes integer level returns real
return 200.00
endfunction
//===========================================================\\
// How many Blade effects are created?
private function BLADES takes integer level returns integer
return level * 20
endfunction
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
//+ END CONFIGURABLES
//+ BLADE BARRIER STARTS HERE.
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
static if not ALLOW_MULTI_INSTANCE then // Do not create the group if its true. Waste of variable.
globals
private group ACTIVE_GROUP = CreateGroup() // Required group if you only want one barrier per unit.
endglobals
endif
//----------------------------------------------------
// BladeUnitData: Handles the blade units.
//----------------------------------------------------
private struct BladeUnitData extends array
//! runtextmacro AIDS()
unit blade
effect fxpath
integer pitch
real pitchDir
real rotationDir
real angle
real angleDir
real radius
static method AIDS_filter takes unit u returns boolean
return GetUnitTypeId( u ) == DUMMY_ID
endmethod
method cleanup takes nothing returns nothing
call DestroyEffect( this.fxpath )
static if FADE then
call RemoveUnit( this.blade )
else
call KillUnit( this.blade )
endif
endmethod
endstruct
//------------------------------------------------------------------------------------------
// DAMAGE SECTION
//------------------------------------------------------------------------------------------
private struct DamageData
BladeBarrier originData // Used to keep track of the parent data.
unit target = null // Assigned target.
real dps = 0.00 // Damage per Second.
real radius_45 = 0.00
real radius_80 = 0.00
private static method periodic takes nothing returns boolean
local thistype this = KT_GetData()
local real tx = GetUnitX( this.target )
local real ty = GetUnitY( this.target )
local real tempX = this.originData.cx - tx
local real tempY = this.originData.cy - ty
if this.originData.sec >= this.originData.dur or /* Expired
*/ ( tempX*tempX+tempY*tempY ) > this.radius_45 or /* Exit barrier radius
*/ not UnitAlive( this.originData.caster ) or /* Dead caster
*/ not UnitAlive( this.target ) then // dead target
call GroupRemoveUnit( this.originData.damageGroup, this.target )
call this.destroy()
return true
endif
if ( tempX*tempX+tempY*tempY ) > this.radius_80 then
static if DO_DMG_SFX then
call DestroyEffect( AddSpecialEffectTarget( DMG_SFX, this.target, DMG_LOC ) )
endif
call UnitDamageTarget( this.originData.source, this.target, this.dps, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
if not UnitAlive( this.target ) then
static if DO_DEATH_SFX then
call call DestroyEffect( AddSpecialEffectTarget( DEATH_SFX, this.target, DEATH_SFX_LOC ) )
endif
call this.destroy()
return true
endif
endif
return false
endmethod
static method create takes unit u, BladeBarrier i returns thistype
local thistype this = thistype.allocate()
set this.originData = i
set this.target = u
set this.dps = DPS( i.level ) * DAMAGE_PERIOD // Level is stored, incase you leveled up the skill while the blade is active.
set this.radius_45 = (i.radius+45)*(i.radius+45)
set this.radius_80 = (i.radius-80)*(i.radius-80)
call GroupAddUnit( i.damageGroup, u )
call KT_Add( function thistype.periodic, this, DAMAGE_PERIOD )
return this
endmethod
endstruct
private struct BladeBarrier
boolean early = false // Was the spell cancelled early?
unit source = null
player owner = null
unit caster = null
integer level = 0
real cx = 0.00
real cy = 0.00
real cz = 0.00
real radius = 0.00
real ticks = 0.00
integer alpha = 255
real sec = 0.00
real dur = 0.00
group bladeGroup = null
group damageGroup = null
trigger t = null
static HandleTable data = 0
// Creates yet another blade to add into the barrier.
private method AddBlade takes nothing returns nothing
local unit blade = CreateUnit( this.owner, DUMMY_ID, this.cx, this.cy, 0 )
local BladeUnitData bdata = BladeUnitData[blade]
local integer n = GetRandomInt( 0, 2 )
// Add a bit of variety into the blades, aye?
set bdata.rotationDir = GetRandomReal( 0.40, 1.60 )
set bdata.pitchDir = GetRandomReal( 0.40, 1.60 )
set bdata.angleDir = GetRandomReal( 0.40, 1.60 )
if GetRandomInt( 0, 1 ) == 0 then
set bdata.rotationDir = bdata.rotationDir * -1
set bdata.angleDir = bdata.angleDir * -1
else
set bdata.pitchDir = bdata.pitchDir * -1
endif
set bdata.blade = blade
set bdata.pitch = GetRandomInt( 0 ,180 )
set bdata.radius = this.radius + GetRandomReal( -RANGE_OFFSET, RANGE_OFFSET )
set bdata.angle = GetRandomReal( -bj_PI, bj_PI )
if n == 0 then
set bdata.fxpath = AddSpecialEffectTarget( MODEL_ONE, blade, "origin" )
elseif n == 1 then
set bdata.fxpath = AddSpecialEffectTarget( MODEL_TWO, blade, "origin" )
elseif n == 2 then
set bdata.fxpath = AddSpecialEffectTarget( MODEL_THREE, blade, "origin" )
endif
call GroupAddUnit( this.bladeGroup, blade )
set blade = null
endmethod
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private static method rotateBlade takes nothing returns nothing
local unit bladeUnit = GetEnumUnit()
local BladeUnitData bdata = BladeUnitData[bladeUnit]
local integer newPitch = ModuloInteger( bdata.pitch + R2I( bdata.pitchDir * BLADE_SPINSPEED ), 180 )
local real newFacing = GetUnitFacing( bladeUnit ) + bdata.rotationDir * BLADE_SPINSPEED
local real x = tempData.cx + bdata.radius * Cos( bdata.angle )
local real y = tempData.cy + bdata.radius * Sin( bdata.angle )
call SetUnitAnimationByIndex( bladeUnit, newPitch )
call SetUnitFacing( bladeUnit, newFacing )
call SetUnitX( bladeUnit, x )
call SetUnitY( bladeUnit, y )
set bdata.angle = bdata.angle + bdata.angleDir * BLADE_ANGLESPEED
set bladeUnit = null
endmethod
private method createBladeSound takes nothing returns nothing
local integer r = GetRandomInt( 0, 2 )
local sound s = null
if r == 0 then
set s = CreateSound( SOUND_ONE, false, true, true, 10, 10, SOUND_EAX )
call SetSoundParamsFromLabel( s, SOUND_PARAM )
call SetSoundDuration( s, SOUND_DUR_1 )
elseif r == 1 then
set s = CreateSound( SOUND_TWO, false, true, true, 10, 10, SOUND_EAX )
call SetSoundParamsFromLabel( s, SOUND_PARAM )
call SetSoundDuration( s, SOUND_DUR_2 )
elseif r == 2 then
set s = CreateSound( SOUND_THREE, false, true, true, 10, 10, SOUND_EAX )
call SetSoundParamsFromLabel( s, SOUND_PARAM )
call SetSoundDuration( s, SOUND_DUR_3 )
endif
call SetSoundVolume( s, INLINED_VOLUME )
call SetSoundPosition( s, this.cx, this.cy, GetUnitFlyHeight( this.caster ) )
call StartSound( s )
call KillSoundWhenDone( s )
set this.ticks = 0.00
set s = null
endmethod
private static method adjustHeight takes nothing returns nothing
call SetUnitFlyHeight( GetEnumUnit(), tempData.cz + HEIGHT, 0.00 )
endmethod
private static method periodic takes nothing returns boolean
local thistype this = KT_GetData()
local real z = GetUnitFlyHeight( this.caster )
set this.cx = GetUnitX( this.caster )
set this.cy = GetUnitY( this.caster )
set tempData = this
call ForGroup( this.bladeGroup, function thistype.rotateBlade )
if z != this.cz then
set this.cz = z
call ForGroup( this.bladeGroup, function thistype.adjustHeight )
endif
set this.ticks = this.ticks + TIMER_PERIOD
if ModuloReal( this.ticks, SOUND_EVERY ) == 0.00 then
call this.createBladeSound()
endif
set this.sec = this.sec + TIMER_PERIOD
if this.sec >= this.dur or not UnitAlive( this.caster ) then
static if not ALLOW_MULTI_INSTANCE then
call GroupRemoveUnit( ACTIVE_GROUP, this.caster )
endif
call thistype.data.flush( this.t )
call DestroyTrigger( this.t )
static if ORIGINAL_DEATH_ANIM then
set this.sec = 15.00
endif
set this.ticks = 0
static if FADE then
call KT_Add( function thistype.release, this, TIMER_PERIOD )
else
call this.destroy()
endif
return true
endif
return false
endmethod
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private static method GroupDestroy takes nothing returns nothing
call BladeUnitData[GetEnumUnit()].cleanup()
endmethod
private static method deathBlade takes nothing returns nothing
local unit u = GetEnumUnit()
local BladeUnitData bdata = BladeUnitData<u>
call SetUnitAnimationByIndex( u, ModuloInteger( bdata.pitch + R2I( bdata.pitchDir * BLADE_SPINSPEED ), 180 ) )
call SetUnitFacing( u, GetUnitFacing( u ) + bdata.rotationDir * BLADE_SPINSPEED )
call SetUnitX( u, tempData.cx + bdata.radius * Cos( bdata.angle ) )
call SetUnitY( u, tempData.cy + bdata.radius * Sin( bdata.angle ) )
set bdata.radius = bdata.radius - 18 / ( tempData.sec - 13 )
call SetUnitVertexColor( u, RED, GREEN, BLUE, tempData.alpha )
endmethod
private static method release takes nothing returns boolean
local thistype this = KT_GetData()
set tempData = this
call ForGroup( this.bladeGroup, function thistype.deathBlade )
set this.ticks = this.ticks + 1
set this.sec = this.sec + TIMER_PERIOD
set this.alpha = R2I( 255 - this.ticks * 3 )
if this.alpha <= 0.00 then
call this.destroy()
return true
endif
return false
endmethod
private static method damageCond takes nothing returns boolean
local unit u = GetTriggerUnit()
if IsTargetValid( u ) then
call DamageData.create( u, thistype( thistype.data[GetTriggeringTrigger()] ) )
endif
set u = null
return false
endmethod
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private method onDestroy takes nothing returns nothing
call ForGroup( this.bladeGroup, function thistype.GroupDestroy )
endmethod
static method create takes unit sourceUnit, unit whichUnit, integer level returns thistype
local thistype this = thistype.allocate()
local integer i = 0
// sourceUnit --> Who cast the spell. They will be responsible for damage.
// whichUnit --> Who is the centre of the barrier.
// whichPlayer --> Who owns the Blade Barrier.
static if not ALLOW_MULTI_INSTANCE then
if IsUnitInGroup( whichUnit, ACTIVE_GROUP ) then
call SimError( GetOwningPlayer( sourceUnit ), ERROR_MSG )
set this.early = true
call this.deallocate()
return 0
endif
endif
if this.bladeGroup == null then
set this.bladeGroup = CreateGroup()
else
call GroupClear( this.bladeGroup )
endif
if this.damageGroup == null then
set this.damageGroup = CreateGroup()
else
call GroupClear( this.damageGroup )
endif
set this.source = sourceUnit
set this.caster = whichUnit
set this.cx = GetUnitX( whichUnit )
set this.cy = GetUnitY( whichUnit )
set this.cz = GetUnitFlyHeight( whichUnit )
set this.owner = GetOwningPlayer( sourceUnit )
set this.level = level
set this.dur = DURATION( this.level )
set this.radius = RADIUS ( this.level )
loop
exitwhen i == BLADES( this.level )
call this.AddBlade()
set i = i + 1
endloop
set tempData = this
set this.t = CreateTrigger()
set thistype.data[this.t] = this
call TriggerRegisterUnitInRange( this.t, this.caster, this.radius + ( 45 - COLLISION_SIZE ), null )
call TriggerAddCondition( this.t, function thistype.damageCond )
static if not ALLOW_MULTI_INSTANCE then
call GroupAddUnit( ACTIVE_GROUP, this.caster )
endif
call KT_Add( function thistype.periodic, this, TIMER_PERIOD )
return this
endmethod
endstruct
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Event responses:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private function onCast takes nothing returns boolean
local unit u = GetTriggerUnit()
if IsUnitInGroup( u, ACTIVE_GROUP ) then
call PauseUnit( u, true )
call IssueImmediateOrder( u, "stop" )
call PauseUnit( u, false )
call SimError( GetTriggerPlayer(), ERROR_MSG )
endif
set u = null
return false
endfunction
// onEffect: response to the no target ability, it directly utilizes the struct.
private function onEffect takes nothing returns boolean
local unit u = GetTriggerUnit()
call BladeBarrier.create( u, u, GetUnitAbilityLevel( u, ABILITY_ID ) )
set u = null
return false
endfunction
// BladeBarrier_Create: uses a boolean for determining whether a instance was
// successful or not, eg: powerups.
public function Create takes unit source, unit target, integer level returns boolean
local BladeBarrier barrier = BladeBarrier.create( source, target, level )
if barrier == 0 then
return false
endif
return true
endfunction
private function onInit takes nothing returns nothing
call GT_AddStartsEffectAction( function onEffect, ABILITY_ID )
static if not ALLOW_MULTI_INSTANCE then
call GT_AddBeginsChanellingAction( function onCast, ABILITY_ID )
endif
set BladeBarrier.data = Table.create()
endfunction
endlibrary</u>
CHANGELOG:
v1.04b - 11/07/2010
- Made one more [ljass]IsUnitType( unit, UNIT_TYPE_DEAD )[/ljass] call to [ljass]UnitAlive[/ljass].
v1.04a - 01/07/2010
- Removed dependency on Damage. Added configurable [ljass]attacktype[/ljass], [ljass]damagetype[/ljass], and [ljass]weapontype[/ljass].
- Made one barrier per instance blocked in the create method as well.
- Added a possible death animation if a unit is killed by this ability.
- SOUND_EVERY is now in seconds.
- Blade Barrier will now track it's target's height.
- The number of blades created can now be adjusted by a configurable function which takes a level.
- Changed [ljass]IsUnitType( unit, UNIT_TYPE_DEAD )[/ljass] calls to [ljass]UnitAlive[/ljass].
- DamageData now ends if the damage dealt kills the target rather than on the next timer iteration.
- BladeUnitData now longer checks for a null effect or nulls the unit.
- Cleaned up the BladeBarrier struct a bit.
- Added colouring global configs. for the Blade Barrier units.
- Create method no longer takes a player, but takes a integer level now.
- Inlined the INLINED_VOLUME.
- Added a new function: [ljass]public function Create takes unit source, unit target, integer level returns boolean[/ljass]
- No longer using GroupUtils for recylcing.
v1.03 - 26/06/2010
- It actually was not MUI. Fixed this.
- Privatalized a shitload of members. I don't know why.
- When a unit in the damage group left the radius, they couldn't get back in, fixed this.
v1.02 - 03/06/2010
- Code optimisation
- Added a configurable to have the original death animation
- Fixed the configurables
- Create method takes a player, and a unit - sourceUnit and whichPlayer
- Fixed the target Blade Barrier - still no support for one instance per target.
- Removed the target from the 'damage whirl' when they die.
v1.01 - 16/05/2010
- Code improvement
- Allowed option for instant removal of blades or fade out
- Allowed option for one barrier per unit
- Changed creation method of struct to take a unit, to allow in making a 'target' Blade Barrier (Dinowc)
v1.00 - 13/05/2010
- Initial release