//Snake\\
/* Made by: D1000
Version: 1.0
//Requirements\\
Systems:
TimerUtils (Vexorian)
xebasic (Vexorian)
xefx (Vexorian)
xedamage (Vexorian)
xepreload (Vexorian)
IsTerrainWalkable (Anitarf)
Models:
dummy.mdx (Vexorian)
//Import\\
1) Copy this trigger and everything mentioned in "Requirements" into your map
2) Create an ability with point- or unit-target
and change "AbilityID" (Global in this trigger) to its ID (press Ctrl+D in the Object-Editor to get the ID)
3) Follow the import-instructions in the "xebasic" and the "BoundSentinel"-trigger
4) Have fun (-:
*/scope Snake
//Changing Area:
globals
/*Basics*/
private constant integer AbilityID = 'SNAK' //The ID of the dummy-spell
private constant integer MAX_INSTANCES = 8190 //How many instances of the spell can exist at once
//ATTENTION: Each segment of the Snake uses its own instance!
private constant real Interval = XE_ANIMATION_PERIOD //Self explanatory....
private constant real PushDistance = 8 //How far targeted units will get pushed away. You can also use negative numbers
private constant real PushDistanceAdd = 0 //The increase of the push-distance each level
private constant real Duration = 40 //The duration of the spell
private constant real DurationAdd = 8 //...
/*Head and Movement*/
private constant real Speed = 300 //How fast the snake moves. Uses the same values as the Object-Editor, but you can also use values higher than 522
private constant real SpeedAdd = 75 //...
private constant string HeadModel = "Abilities\\Weapons\\WardenMissile\\WardenMissile.mdl" //The model of the snakes head
private constant real HeadSize = 7.5 //The scale of the head
private constant real HeadSizeAdd = 0 //...
private constant real HeadZ = 60 //The flying-height of the head
private constant real HeadZAdd = 0 //...
private constant real HeadAngle = 0 //The angle of the the head in radians. If you use bj_PI, the head will move backwards
private constant real HeadZAngle = 0 //The pitch angle in radians
//The color of the head (0-255)
private constant integer HeadColorR = 0 //Red
private constant integer HeadColorG = 60 //Green
private constant integer HeadColorB = 0 //Blue
private constant integer HeadColorA = 255 //Alpha
private constant string HeadCreationEffectModel = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" //An effect, which appears when the head gets created
//The following values are the same as by the model of the head.
private constant real HeadCreationEffectSize= 1.5
private constant real HeadCreationEffectSizeAdd= 0
private constant real HeadCreationEffectZ = 60
private constant real HeadCreationEffectZAdd = 0
private constant real HeadCreationEffectAngle = 0
private constant real HeadCreationEffectZAngle = -bj_PI
private constant integer HeadCreationEffectColorR = 255
private constant integer HeadCreationEffectColorG = 255
private constant integer HeadCreationEffectColorB = 255
private constant integer HeadCreationEffectColorA = 255
private constant string HeadDeathEffectModel ="Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" //An effect, which appears when the head gets destroyed
//The following values are the same as by the model of the head.
private constant real HeadDeathEffectSize= 1.5
private constant real HeadDeathEffectSizeAdd= 0
private constant real HeadDeathEffectZ = 60
private constant real HeadDeathEffectZAdd = 0
private constant real HeadDeathEffectAngle = 0
private constant real HeadDeathEffectZAngle = -bj_PI
private constant integer HeadDeathEffectColorR = 255
private constant integer HeadDeathEffectColorG = 255
private constant integer HeadDeathEffectColorB = 255
private constant integer HeadDeathEffectColorA = 255
private constant real DistanceToFirstSegment = 80 //The distance between the head and the first segment
private constant real DistanceToFirstSegmentAdd = 0 //...
private constant real DetectionRange = 800 //The distance within which the head can detect enemy units
private constant real DetectionRangeAdd = 0 //...
private constant real RandomMovement = 100 //Just try what it does^^
private constant boolean IgnorePathing = true //Defines, if the head can ignore obstacles (the segments do this anyway)
private constant real DistractionTime = 2 //After this time the head gets distracted from its target and follows another
private constant real DistractionTimeAdd = 0.5 //...
/*Segments*/
private constant real SegmentSpeed = 400 //The speed of the segments. It´s recommend to set it to a higher speed than the head has, but don´t use to high values ´cause it´ll look weird
private constant real SegmentSpeedAdd = 75 //...
//The next three "blocks" of constant are just the same as for the head, but for the segments
private constant string SegmentModel = "Abilities\\Weapons\\WardenMissile\\WardenMissile.mdl"
private constant real SegmentSize = 5.5
private constant real SegmentSizeAdd = 0
private constant real SegmentZ = 60
private constant real SegmentZAdd = 0
private constant real SegmentAngle = 0
private constant real SegmentZAngle = 0
private constant integer SegmentColorR = 0
private constant integer SegmentColorG = 255
private constant integer SegmentColorB = 100
private constant integer SegmentColorA = 255
private constant string SegmentCreationEffectModel ="Objects\\Spawnmodels\\NightElf\\EntBirthTarget\\EntBirthTarget.mdl"
private constant real SegmentCreationEffectSize= 1
private constant real SegmentCreationEffectSizeAdd= 0
private constant real SegmentCreationEffectZ = 0
private constant real SegmentCreationEffectZAdd = 0
private constant real SegmentCreationEffectAngle = 0
private constant real SegmentCreationEffectZAngle = 0
private constant integer SegmentCreationEffectColorR = 255
private constant integer SegmentCreationEffectColorG = 150
private constant integer SegmentCreationEffectColorB = 255
private constant integer SegmentCreationEffectColorA = 255
private constant string SegmentDeathEffectModel ="Objects\\Spawnmodels\\NightElf\\EntBirthTarget\\EntBirthTarget.mdl"
private constant real SegmentDeathEffectSize= 1
private constant real SegmentDeathEffectSizeAdd= 0
private constant real SegmentDeathEffectZ = 0
private constant real SegmentDeathEffectZAdd = 0
private constant real SegmentDeathEffectAngle = 0
private constant real SegmentDeathEffectZAngle = 0
private constant integer SegmentDeathEffectColorR = 255
private constant integer SegmentDeathEffectColorG = 150
private constant integer SegmentDeathEffectColorB = 255
private constant integer SegmentDeathEffectColorA = 255
//End of the three blocks^^
private constant real FoodNeeded = 250 //The damage which is needed to get a new segment.
private constant real FoodNeededAdd = 0 //...
private constant boolean SegmentOnKill = true //Defines, if the head gets a new segment for killing a unit
private constant real DistanceBetweenSegments = 60 //The distance between the segments
private constant real DistanceBetweenSegmentsAdd = 0 //...
private constant integer StartSegments = 5 //The number of segments the snake has when it gets created
private constant integer StartSegmentsAdd = 0 //...
private constant integer MaxSegments = 12 //The maximal number of segments the snake can have
private constant integer MaxSegmentsAdd = 3 //...
/*Damage and targets*/
// Hero you can specify the target options
// Uses "xedamage", you can find the xedamage-readme in the "Systems"-Folder
private constant boolean TargetMagicImmune = false //If you set this to "true", make sure that the damage-type is NOT Magic (else magic immune units will get pushed back but won´t receive damage)
//! textmacro Snake_DamageSettings
set .exception = UNIT_TYPE_FLYING
set .dtype = DAMAGE_TYPE_MAGIC
set .atype = ATTACK_TYPE_MAGIC
set .damageNeutral = false
//! endtextmacro
private constant real HeadDPS = 30 //The damage per second the head does
private constant real HeadDPSAdd = 6 //...
private constant real HeadAoE = DistanceToFirstSegment //The AoE of the damage the head does
private constant real HeadAoEAdd = DistanceToFirstSegmentAdd //...
private constant real DPS = 25 //The damage per second the segments do
private constant real DPSAdd = 5 //...
private constant real AoE = DistanceBetweenSegments //The AoE of the damage the segments do
private constant real AoEAdd = DistanceBetweenSegmentsAdd //...
/*===============*/
//Do not Change anything below//
/*===============*/
endglobals
private struct Snake[MAX_INSTANCES]
timer tim
real time
real DistractionTimer
unit caster
integer level
real NeededFood
integer segments = 0
unit target
Snake next
Snake previous
delegate xefx fx
static delegate xedamage dmg
static group TempGroup = CreateGroup()
static Snake TempSnake
static boolexpr IsAllowed
static boolexpr IsAllowed2
static method StartSpell takes nothing returns boolean
local Snake this
local Snake head
if GetSpellAbilityId() == AbilityID then
set head = Snake.allocate()
set head.tim = NewTimer()
set head.caster = GetTriggerUnit()
set head.level = GetUnitAbilityLevel(head.caster,AbilityID) - 1
set head.NeededFood = FoodNeeded + FoodNeededAdd*head.level
call SetTimerData(head.tim,head)
if GetSpellTargetUnit() != null then
set head.DistractionTimer = DistractionTime + DistractionTimeAdd*head.level
set head.target = GetSpellTargetUnit()
else
set head.DistractionTimer = 0
endif
set head.time = Atan2(GetSpellTargetY() - GetUnitY(head.caster), GetSpellTargetX() - GetUnitX(head.caster))
if HeadCreationEffectModel != "" then
set head.fx = xefx.create(GetSpellTargetX(),GetSpellTargetY(),HeadCreationEffectAngle + head.time)
set head.fxpath = HeadCreationEffectModel
set head.scale = HeadCreationEffectSize + HeadCreationEffectSizeAdd*head.level
call head.recolor(HeadCreationEffectColorR,HeadCreationEffectColorG,HeadCreationEffectColorB,HeadCreationEffectColorA)
set head.zangle = HeadCreationEffectZAngle
set head.z = HeadCreationEffectZ + HeadCreationEffectZAdd*head.level
call head.fx.destroy()
endif
set head.fx = xefx.create(GetSpellTargetX(),GetSpellTargetY(),HeadAngle + head.time)
set head.time = Duration + DurationAdd*head.level
set head.fxpath = HeadModel
set head.scale = HeadSize + HeadSizeAdd*head.level
call head.recolor(HeadColorR,HeadColorG,HeadColorB,HeadColorA)
set head.zangle = HeadZAngle
set head.z = HeadZ + HeadZAdd*head.level
set this = Snake.allocate()
set head.next = this
set .previous = head
call .NewSegment(head.level,head.x - (DistanceToFirstSegment + DistanceToFirstSegmentAdd*head.level)*Cos(head.xyangle),head.y - (DistanceToFirstSegment + DistanceToFirstSegmentAdd*head.level)*Sin(head.xyangle),head.xyangle - HeadAngle)
loop
set .fxpath = SegmentModel
set .scale = SegmentSize + SegmentSizeAdd*head.level
call .recolor(SegmentColorR,SegmentColorG,SegmentColorB,SegmentColorA)
set .zangle = SegmentZAngle
set .z = SegmentZ + SegmentZAdd*head.level
set head.segments = head.segments + 1
exitwhen head.segments >= StartSegments + StartSegmentsAdd*head.level
set .next = Snake.allocate()
set .next.previous = this
set this = .next
call .NewSegment(head.level,.previous.x - (DistanceBetweenSegments + DistanceBetweenSegmentsAdd*head.level)*Cos(.previous.xyangle),.previous.y - (DistanceBetweenSegments + DistanceBetweenSegmentsAdd*head.level)*Sin(.previous.xyangle),.previous.xyangle - SegmentAngle)
endloop
set .next = 0
call TimerStart(head.tim,Interval,true,function Snake.Loop)
endif
return false
endmethod
static method Loop takes nothing returns nothing
local Snake head = GetTimerData(GetExpiredTimer())
local Snake this = head.next
local unit u
local real damage
local real x
local real y
set head.time = head.time - Interval
set head.DistractionTimer = head.DistractionTimer - Interval
if head.time <= 0 then
set damage = head.level
call ReleaseTimer(head.tim)
set head.NeededFood = head.xyangle
set x = head.x
set y = head.y
call head.fx.destroy()
if HeadDeathEffectModel != "" then
set head.fx = xefx.create(x,y,HeadDeathEffectAngle + head.NeededFood)
set head.fxpath = HeadDeathEffectModel
set head.scale = HeadDeathEffectSize + HeadDeathEffectSizeAdd*damage
call head.recolor(HeadDeathEffectColorR,HeadDeathEffectColorG,HeadDeathEffectColorB,HeadDeathEffectColorA)
set head.zangle = HeadDeathEffectZAngle
set head.z = HeadDeathEffectZ + HeadDeathEffectZAdd*damage
call head.fx.destroy()
endif
call head.destroy()
loop
set head = .next
call .DestroySegment(R2I(damage),.x,.y,.xyangle-SegmentAngle)
exitwhen head == 0
set this = head
endloop
return
endif
if (head.DistractionTimer <= 0 and DistractionTime + DistractionTimeAdd*head.level >= 0) or head.target == null or GetUnitState(head.target,UNIT_STATE_LIFE) < 0.405 then
set .TempSnake = head
call GroupEnumUnitsInRangeCounted(.TempGroup,head.x,head.y,DetectionRange + DetectionRangeAdd,.IsAllowed2,1)
set head.target = FirstOfGroup(.TempGroup)
set head.DistractionTimer = DistractionTime + DistractionTimeAdd*head.level
endif
if head.target == null then
set head.xyangle = HeadAngle + Atan2(head.y + 10*Sin(head.xyangle-HeadAngle) + GetRandomReal(-RandomMovement,RandomMovement) - head.y,head.x + 10*Cos(head.xyangle-HeadAngle) + GetRandomReal(-RandomMovement,RandomMovement) - head.x)
else
set head.xyangle = HeadAngle + Atan2(GetUnitY(head.target) + GetRandomReal(-RandomMovement,RandomMovement) - head.y,GetUnitX(head.target) + GetRandomReal(-RandomMovement,RandomMovement) - head.x)
endif
set x = head.x + (Speed + SpeedAdd*head.level)*Interval * Cos(head.xyangle-HeadAngle)
set y = head.y + (Speed + SpeedAdd*head.level)*Interval * Sin(head.xyangle-HeadAngle)
if (not IgnorePathing and IsTerrainWalkable(x,y)) or (IgnorePathing and RectContainsCoords(bj_mapInitialPlayableArea,x,y)) then
set head.x = x
set head.y = y
else
set head.xyangle = head.xyangle + GetRandomReal(-bj_PI/2*3,-bj_PI/2)
endif
call GroupEnumUnitsInRange(.TempGroup,head.x,head.y,HeadAoE+HeadAoEAdd*head.level,.IsAllowed)
loop
set u = FirstOfGroup(.TempGroup)
exitwhen u == null
call GroupRemoveUnit(.TempGroup,u)
set damage = GetUnitState(u,UNIT_STATE_LIFE)
call .damageTarget(head.caster,u,(HeadDPS + HeadDPSAdd*head.level)*Interval)
set head.NeededFood = head.NeededFood + GetUnitState(u,UNIT_STATE_LIFE) - damage
if SegmentOnKill and GetUnitState(u,UNIT_STATE_LIFE) < 0.405 then
set head.NeededFood = head.NeededFood - FoodNeeded - FoodNeededAdd*head.level
endif
if PushDistance + PushDistanceAdd*head.level != 0 then
set x = GetUnitX(u)+(PushDistance + PushDistanceAdd*head.level)*Cos(damage)
set y = GetUnitY(u)+(PushDistance + PushDistanceAdd*head.level)*Sin(damage)
if IsTerrainWalkable(x,y) or IsUnitType(u,UNIT_TYPE_FLYING) then
set damage = Atan2(GetUnitY(u) - head.y, GetUnitX(u) - head.x)
call SetUnitX(u,x)
call SetUnitY(u,y)
endif
endif
endloop
if SquareRoot((head.x-.x)*(head.x-.x) + (head.y-.y)*(head.y-.y)) > DistanceToFirstSegment + DistanceToFirstSegmentAdd*head.level then
set .xyangle = SegmentAngle + Atan2(head.y - .y, head.x - .x)
set .x = .x + (SegmentSpeed + SegmentSpeedAdd*head.level)*Interval*Cos(.xyangle-SegmentAngle)
set .y = .y + (SegmentSpeed + SegmentSpeedAdd*head.level)*Interval*Sin(.xyangle-SegmentAngle)
endif
call GroupEnumUnitsInRange(.TempGroup,.x,.y,AoE+AoEAdd*head.level,.IsAllowed)
loop
set u = FirstOfGroup(.TempGroup)
exitwhen u == null
call GroupRemoveUnit(.TempGroup,u)
set damage = GetUnitState(u,UNIT_STATE_LIFE)
call .damageTarget(head.caster,u,(DPS + DPSAdd*head.level)*Interval)
set head.NeededFood = head.NeededFood + GetUnitState(u,UNIT_STATE_LIFE) - damage
if SegmentOnKill and GetUnitState(u,UNIT_STATE_LIFE) < 0.405 then
set head.NeededFood = head.NeededFood - FoodNeeded - FoodNeededAdd*head.level
endif
if PushDistance + PushDistanceAdd*head.level != 0 then
set x = GetUnitX(u)+(PushDistance + PushDistanceAdd*head.level)*Cos(damage)
set y = GetUnitY(u)+(PushDistance + PushDistanceAdd*head.level)*Sin(damage)
if IsTerrainWalkable(x,y) or IsUnitType(u,UNIT_TYPE_FLYING) then
set damage = Atan2(GetUnitY(u) - head.y, GetUnitX(u) - head.x)
call SetUnitX(u,x)
call SetUnitY(u,y)
endif
endif
endloop
set this = .next
loop
if SquareRoot((.previous.x-.x)*(.previous.x-.x) + (.previous.y-.y)*(.previous.y-.y)) > DistanceBetweenSegments + DistanceBetweenSegmentsAdd*head.level then
set .xyangle = SegmentAngle + Atan2(.previous.y - .y, .previous.x - .x)
set .x = .x + (SegmentSpeed + SegmentSpeedAdd*head.level)*Interval*Cos(.xyangle-SegmentAngle)
set .y = .y + (SegmentSpeed + SegmentSpeedAdd*head.level)*Interval*Sin(.xyangle-SegmentAngle)
endif
call GroupEnumUnitsInRange(.TempGroup,.x,.y,AoE+AoEAdd*head.level,.IsAllowed)
loop
set u = FirstOfGroup(.TempGroup)
exitwhen u == null
call GroupRemoveUnit(.TempGroup,u)
set damage = GetUnitState(u,UNIT_STATE_LIFE)
call .damageTarget(head.caster,u,(DPS + DPSAdd*head.level)*Interval)
set head.NeededFood = head.NeededFood + GetUnitState(u,UNIT_STATE_LIFE) - damage
if SegmentOnKill and GetUnitState(u,UNIT_STATE_LIFE) < 0.405 then
set head.NeededFood = head.NeededFood - FoodNeeded + FoodNeededAdd*head.level
endif
if PushDistance + PushDistanceAdd*head.level != 0 then
set x = GetUnitX(u)+(PushDistance + PushDistanceAdd*head.level)*Cos(damage)
set y = GetUnitY(u)+(PushDistance + PushDistanceAdd*head.level)*Sin(damage)
if IsTerrainWalkable(x,y) or IsUnitType(u,UNIT_TYPE_FLYING) then
set damage = Atan2(GetUnitY(u) - head.y, GetUnitX(u) - head.x)
call SetUnitX(u,x)
call SetUnitY(u,y)
endif
endif
endloop
exitwhen .next == 0
set this = .next
endloop
if head.NeededFood <= 0 and (MaxSegments + MaxSegmentsAdd*head.level <= 0 or MaxSegments + MaxSegmentsAdd*head.level > head.segments) then
set head.NeededFood = head.NeededFood + FoodNeeded + FoodNeededAdd*head.level
set head.segments = head.segments + 1
set .next = Snake.allocate()
set .next.previous = this
set this = .next
set .next = 0
call .NewSegment(head.level,.previous.x,.previous.y,.previous.xyangle - SegmentAngle)
set .fxpath = SegmentModel
set .scale = SegmentSize + SegmentSizeAdd*head.level
call .recolor(SegmentColorR,SegmentColorG,SegmentColorB,SegmentColorA)
set .zangle = SegmentZAngle
set .z = SegmentZ + SegmentZAdd*head.level
endif
endmethod
static method IsTargetAllowed takes nothing returns boolean
return .allowedTarget(.TempSnake.caster,GetFilterUnit()) and (IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) == false or TargetMagicImmune) and IsUnitVisible(GetFilterUnit(),GetOwningPlayer(.TempSnake.caster))
endmethod
static method IsTargetAllowed2 takes nothing returns boolean
return .allowedTarget(.TempSnake.caster,GetFilterUnit()) and (IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) == false or TargetMagicImmune) and IsUnitVisible(GetFilterUnit(),GetOwningPlayer(.TempSnake.caster)) and GetFilterUnit() != .TempSnake.target
endmethod
method NewSegment takes integer level, real x, real y, real angle returns nothing
if SegmentCreationEffectModel != "" then
set .fx = xefx.create(x,y,SegmentCreationEffectAngle + angle)
set .fxpath = SegmentCreationEffectModel
set .scale = SegmentCreationEffectSize + SegmentCreationEffectSizeAdd*level
call .recolor(SegmentCreationEffectColorR,SegmentCreationEffectColorG,SegmentCreationEffectColorB,SegmentCreationEffectColorA)
set .zangle = SegmentCreationEffectZAngle
set .z = SegmentCreationEffectZ + SegmentCreationEffectZAdd*level
call .fx.destroy()
endif
set .fx = xefx.create(x,y,SegmentAngle + angle)
endmethod
method DestroySegment takes integer level, real x, real y, real angle returns nothing
call .fx.destroy()
if SegmentDeathEffectModel != "" then
set .fx = xefx.create(x,y,SegmentDeathEffectAngle + angle)
set .fxpath = SegmentDeathEffectModel
set .scale = SegmentDeathEffectSize + SegmentDeathEffectSizeAdd*level
call .recolor(SegmentDeathEffectColorR,SegmentDeathEffectColorG,SegmentDeathEffectColorB,SegmentDeathEffectColorA)
set .zangle = SegmentDeathEffectZAngle
set .z = SegmentDeathEffectZ + SegmentDeathEffectZAdd*level
call .fx.destroy()
endif
call .destroy()
endmethod
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
set Snake.IsAllowed = Condition(function Snake.IsTargetAllowed)
set Snake.IsAllowed2 = Condition(function Snake.IsTargetAllowed2)
set .dmg = xedamage.create()
//! runtextmacro Snake_DamageSettings()
call XE_PreloadAbility(AbilityID)
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(t,Condition(function Snake.StartSpell))
set t = null
endmethod
endstruct
endscope