//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
R_Locations | location | Yes | |
T_Integer | integer | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
//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
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=34
//TESH.alwaysfold=0
library IsTerrainWalkable initializer Init
//*****************************************************************
//* IsTerrainWalkable
//*
//* rewritten in vJass by: Anitarf
//* original implementation: Vexorian
//*
//* A function for checking if a point is pathable for ground
//* units (it does so by attempting to move an item there and
//* checking where it ended up), typically used to stop sliding
//* units before they end up stuck in trees. If the point is not
//* pathable, the function will also determine the nearest point
//* that is (the point where the item ends up).
//*****************************************************************
globals
// this value is how far from a point the item may end up for the point to be considered pathable
private constant real MAX_RANGE = 11
// the following two variables are set to the position of the item after each pathing check
// that way, if a point isn't pathable, these will be the coordinates of the nearest point that is
public real X = 0.0
public real Y = 0.0
// END OF CALIBRATION SECTION
// ================================================================
private rect r
private item check
private item array hidden
private integer hiddenMax = 0
endglobals
private function HideBothersomeItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set hidden[hiddenMax]=GetEnumItem()
call SetItemVisible(hidden[hiddenMax],false)
set hiddenMax=hiddenMax+1
endif
endfunction
// ================================================================
function IsTerrainWalkable takes real x, real y returns boolean
// first, hide any items in the area so they don't get in the way of our item
call MoveRectTo(r, x,y)
call EnumItemsInRect(r,null,function HideBothersomeItem)
// try to move the check item and get it's coordinates
call SetItemPosition(check,x,y)//this unhides the item...
set X = GetItemX(check)
set Y = GetItemY(check)
call SetItemVisible(check,false)//...so we must hide it again
// before returning, unhide any items that got hidden at the start
loop
exitwhen hiddenMax<=0
set hiddenMax=hiddenMax-1
call SetItemVisible(hidden[hiddenMax],true)
set hidden[hiddenMax]=null
endloop
// return pathability status
return (x-X)*(x-X)+(y-Y)*(y-Y) < MAX_RANGE*MAX_RANGE
endfunction
private function Init takes nothing returns nothing
set check = CreateItem('ciri',0,0)
call SetItemVisible(check,false)
set r = Rect(0.0,0.0,128.0,128.0)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
xe
--
Q. Why xe?
A. As the caster system grew bigger vJass also appeared, there are also a lot
of things in the caster system that could have been done better but cannot
be fixed without dropping the caster system's function interface.
Q. No, really, WHY IS IT NAMED XE?
A. I have no idea.
Q. What's wrong with the caster system?
A. Instead of answering that I will list what's right with xe:
* It is modular. You can make a whole spell with just xebasic which is just
like a "constant package" that comes with the dummy model and unit. All the
other parts are disposable or replaceable.
Still, I made them and will probably keep making new modules as I advance,
right now xe can only do some basic functions, still cannot 100% replace
the caster system in functionality, certain parts like the parabolic
projectiles most notably, don't have a xe module yet, maybe later...
xebasic is so minimal I personally hope people could use it on their spells
and systems as a way to make it common to use these constants as constants of
that kind are often required to make things work.
* It is quite OOP, this is more related to the modules themselves, I wanted it
to exploit OOP for two reasons: 1) It prevents the 'ultra long function calls'
disease that has plagued the caster system since the beginning of time. 2) I
personally think it is easier this way instead of memorizing function names
and their argument lists.
xebasic
-------
xebasic is the nucleous of all xe, in order to use a xe module you would most
likely need xebasic. It is also meant to be the only part of xe that most users
would really need to tweak for their map.
This section is supposed to be a rapid guide on copying xebasic. The rest of
the xe modules should be rather easy to implement (just copy the 'trigger' that
contains it to the map).
1) Make a backup of your map.
2) Get vJass support. The most usual way would be using the newgen pack. There
are plenty of other ways. I for example do my vJass coding on Linux using just
jasshelper.exe, WINE and some editor tricks using a tool called Warcity.
Getting vJass to work is a wide area, if just installing and using the
"Jass newgen pack" doesn't work to you, please request/search help somehow. As
of now there really is no vJass support in OS/X, so you would need virtualization
and things of that style.
-- Note: You can have two maps open in the editor, and it is the only way to
make copy and paste work.
3) Copy the model, in this map's import manager you may find dummy.mdx, select
it and export it to some temp folder, then go to your map and import it, use
war3mapimported\dummy.mdx for path.
Please save the map immediatelly after importing the model to prevent it from
getting unimported due to a rare WE bug.
4) Copy the dummy unit: In the object editor under neutral passive, you may
find the dummy unit, select it, go to 'edit' then click copy. Now switch to
your map's object editor and use 'paste'.
5) Write down the rawcode: While you are in the object editor select your map's
recently pasted dummy unit, then go to the 'view' menu and click the option to
show values as raw data. Now take a look to the selected unit type. It will now
come with a code like "ewsp:e001", the last four characters of the code are
what matter, write them down.
6) Copy this map's xebasic trigger to your map.
7) Update your map's xebasic: change the value assigned to XE_DUMMY_UNITID to
'XXXX' where XXXX is the four character code you wrote down in step 5.
8) Save your map and pray, if it compiles correctly then it is done. Make sure
to test (after implementing) whatever needed you to install xebasic. If it
doesn't work correctly then it is likely you made a mistake when copying the
unit or the model or updating the rawcode.
9) Update the other values if you think it is necessary.
Using xebasic
-------------
Well, once you state your trigger/spell/system requires xebasic, just use
its constants, on a map that has implemented xebasic XE_DUMMY_UNITID will be
your key to creating dummy units, just remember to add it 'Aloc' ...
Contact
-------
I got a forum that's supposed to hold questions related to my systems at:
http://wc3campaigns.net/vexorian
I'd really appreciate that you used that forum for your questions, for starters
it is a lot more likely I would actually find the questions in that case.
Changelog
---------
0.5:
- xecollider.terminate now prevents events from firing.
- fixed a unit handle index kidnap in inRangeEnum (xecollider).
- fixed a small documentation bug about xecollider.terminate
- xecast's anti-AI protection now pauses the unit instead of removing the
ability (since that seemed to have bad side effects)
- xecast is now able to deal with units not visible to the player by setting
a constant to true.
- Due to technical split, xecast.castOnTarget now only takes unit and not
widget, old castOnTarget that takes widget has been renamed castOnWidgetTarget.
- xecollider now will not miss the detection of certain units that get inside
the range too fast anymore, however now creates a group per collider (damn) I
hope to find a better solution.
- Added a notice in xepreload's documentation about order events firing during
the preload.
- Added gem of double green fire
0.4:
- More documentation fixes.
- xecast no longer has double frees when using create/Basic/A and the
AOE/group methods.
- damageDestructablesInAOE now actually works and does not leak a xedamage
reference.
- damageTarget now returns true if it was succesful and false if it wasn't.
- xefx's recycle bin now extends array.
- added rune of illusions sample.
0.3:
- More documentation fixes.
- xefx requires xebasic in the library declaration (as it was supposed to)
- xefx now creates the effects at the correct place (used to consider pathing
during creation for some reason).
- xebasic sample uses 197.0 for max collision size since that's the default in
warcraft 3 (The default + 1)
- xebasic now includes an explanation for max collision size and its effects.
- added useSpecialEffect to xedamage
- included BoundSentinel
- added xecollider
- xedamage's required unittype field is not ignored anymore.
- Added the fireNovaStrike example.
0.2:
- Fixed a bug with xecast.castInPoint basically ignoring the arguments.
- Fixed a bug with xecast.createBasic ignoring the order id.
- Fixed documentation bugs.
- Added xedamage.
- Added sheep staff sample.
0.1 : Initial release
//TESH.scrollpos=25
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
xepreload
---------
xepreload attacks the ability preloading issue. It is a good idea to preload
abilities you are going to add to units during the game to avoid the typical
"first-cast freeze". xepreload exploits jasshelper's inline and a timer to
minimize the time spent preloading each ability.
Install
-------
Copy the xepreload trigger to your map.
Usage
-----
_____________________________________________________________________________
function XE_PreloadAbility takes integer abilid returns nothing
----
Preloads the ability, pass it an ability id (rawcode). Notice that this
function may only work during map init. In order to use it in a library's
initializer, make sure the library requires xepreload.
* Please notice that the ability removal might trigger certain order events, try
ignoring xe dummy units in those events if necessary.
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xepreload initializer init requires xebasic
//************************************************************************
// xepreload 0.4
// ---------
// Ah, the joy of preloading abilities, it is such a necessary evil...
// Notice you are not supposed to use this system in places outside map init
//
// This one does the preloading and tries to minimize the hit on loading time
// for example, it only needs one single native call per ability preloaded.
//
//************************************************************************
//===========================================================================================================
globals
private unit dum=null
endglobals
//inline friendly
function XE_PreloadAbility takes integer abilid returns nothing
call UnitAddAbility(dum, abilid)
endfunction
private function kill takes nothing returns nothing
call RemoveUnit(dum)
set dum=null
call DestroyTimer(GetExpiredTimer()) //I do hope this doesn't trigger the apocallypse. I didn't think
// it was a great idea to make this require a whole timer recycling
// system, so, just destroy. Anyone got a better idea on how to make
// this execute after all the other calls at init?
endfunction
private function init takes nothing returns nothing
set dum=CreateUnit(Player(15),XE_DUMMY_UNITID, 0,0,0)
call TimerStart(CreateTimer(),0.0,false,function kill)
endfunction
endlibrary
//TESH.scrollpos=91
//TESH.alwaysfold=0
xefx
----
This module just allows you to have movable special effects, they are actually
dummy units, you can do plenty of things like changing their position, their
height, their rotation (in the xy and in the z axis as well), color, and things
like that. It is all about assigning attributes, the only two important methods
xefx objects have are create and destroy. There are other accessory methods.
implementation
--------------
Just copy the xecast trigger to your map.
xefx object
-------------
__________________________________________________________________________________________________
static method create takes real x, real y, real facing returns xefx
--
This is the create method, it will make a new xefx for you to use, there are
initial values you must specify, like the x,y coordinate and the facing angle.
facing is in radians.
Eg. set myfx = xefx.create()
__________________________________________________________________________________________________
method destroy takes nothing returns nothing
--
This just destroys your xefx object. (call myfx.destroy() )
* List of attributes *
________________________________________________________________________________
string fxpath
----
Determines the model of the special effect, yes, you may change it after
assigning it if necessary to change the model path.
Example: set myfx.path = "abilities\thisisamadeup\modelpath.mdl"
________________________________________________________________________________
real x
real y
real z
----
Determine the position of your special effect, you can keep moving the
effect in a periodic loop, etc.
Example: set myfx.x=myfx.x + 65.0
set myfx.y=GetUnitY(u)
set myfx.z=JumpParabola(t)
________________________________________________________________________________
real xyangle
----
The angle in the xy plane, also called 'facing' angle. (Note: uses radians)
Example: set myfx.xyangle = AngleBetweenPoints(target, source)*bj_DEGTORAD
________________________________________________________________________________
real zangle
----
The angle in the z-axis (inclination?), (Note: uses radians)
Example: set myfx.zangle = bj_PI/2 //Now the model will look towards the sky
________________________________________________________________________________
integer r
integer g
integer b
integer a
----
Well, the model's vertex coloring in RGB , a is the opacity value (use
values from 0 to 255 here)
Example: set myfx.r=255
set myfx.g=0
set myfx.b=255
set myfx.a=128
______________________________________________________________________________________
method recolor takes integer r, integer g , integer b, integer a returns nothing
----
This one assigns all the color values in one pass.
________________________________________________________________________________
real scale (write-only)
----
Allows you to resize the xefx object, the default scale is 1.
Example: set myfx.scale=2.0 //double size (in fact 8x)
set myfx.scale=0.5 //half size (in fact 1/8x)
________________________________________________________________________________
player owner
----
For some reason you might want to change ownership of the effect, for
example, if you use abilityid (see bellow) and the ability does damage.
Example: set myfx.owner = GetOwningPlayer(GetTriggerUnit() )
________________________________________________________________________________
integer abilityid
----
Well, you may use a xefx object to grab a passive ability, perhaps you need
it for ye candy reasons or you want to use it as a damage dealer.
Example: set myfx.abilityid = 'Hphf'
________________________________________________________________________________
playercolor teamcolor
----
The team color to use for the model.
Example: set somevar.teamcolor=PLAYER_COLOR_RED
set somevar.teamcolor=GetPlayerColor(GetOwningPlayer(u))
________________________________________________________________________________
method flash takes string modelpath returns nothing
----
It shows the dead animation of the model specified by modelpath. This is
in case you need this sort of eye candy.
________________________________________________________________________________
method ARGBrecolor takes ARGB color returns nothing
----
If you got the ARGB library in your map, xefx then acquires the ARGBrecolor
method, that you can use to use an ARGB object to recolor the fx's model,
in a way similar to how recolor() works.
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xefx initializer init requires xebasic
//**************************************************
// xefx 0.6
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
private constant real RECYCLE_DELAY = 4.0
//recycling, in order to show the effect correctly, must wait some time before
//removing the unit.
private timer recycler
private timer NOW
endglobals
private struct recyclebin extends array
unit u
real schedule
static recyclebin end=0
static recyclebin begin=0
static method Recycle takes nothing returns nothing
call RemoveUnit(.begin.u) //this unit is private, systems shouldn't mess with it.
set .begin.u=null
set .begin=recyclebin(integer(.begin)+1)
if(.begin==.end) then
set .begin=0
set .end=0
else
call TimerStart(recycler, .begin.schedule-TimerGetElapsed(NOW), false, function recyclebin.Recycle)
endif
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
set NOW=CreateTimer()
call TimerStart(NOW,43200,true,null)
endfunction
struct xefx[MAX_INSTANCES]
public integer tag=0
private unit dummy
private effect fx=null
private real zang=0.0
private integer r=255
private integer g=255
private integer b=255
private integer a=255
private integer abil=0
static method create takes real x, real y, real facing returns xefx
local xefx this=xefx.allocate()
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, facing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
return this
endmethod
method operator owner takes nothing returns player
return GetOwningPlayer(this.dummy)
endmethod
method operator owner= takes player p returns nothing
call SetUnitOwner(this.dummy,p,false)
endmethod
method operator teamcolor= takes playercolor c returns nothing
call SetUnitColor(this.dummy,c)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(this.dummy,value,value,value)
endmethod
//! textmacro XEFX_colorstuff takes colorname, colorvar
method operator $colorname$ takes nothing returns integer
return this.$colorvar$
endmethod
method operator $colorname$= takes integer value returns nothing
set this.$colorvar$=value
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
//! endtextmacro
//! runtextmacro XEFX_colorstuff("red","r")
//! runtextmacro XEFX_colorstuff("green","g")
//! runtextmacro XEFX_colorstuff("blue","b")
//! runtextmacro XEFX_colorstuff("alpha","a")
method recolor takes integer r, integer g , integer b, integer a returns nothing
set this.r=r
set this.g=g
set this.b=b
set this.a=a
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
implement optional ARGBrecolor
method operator abilityid takes nothing returns integer
return this.abil
endmethod
method operator abilityid= takes integer a returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(a!=0) then
call UnitAddAbility(this.dummy,a)
endif
set this.abil=a
endmethod
method flash takes string fx returns nothing
call DestroyEffect(AddSpecialEffectTarget(fx,this.dummy,"origin"))
endmethod
method operator xyangle takes nothing returns real
return GetUnitFacing(this.dummy)*bj_DEGTORAD
endmethod
method operator xyangle= takes real value returns nothing
call SetUnitFacing(this.dummy,value*bj_RADTODEG)
endmethod
method operator zangle takes nothing returns real
return this.zang
endmethod
method operator zangle= takes real value returns nothing
local integer i=R2I(value*bj_RADTODEG+90.5)
set this.zang=value
if(i>=180) then
set i=179
elseif(i<0) then
set i=0
endif
call SetUnitAnimationByIndex(this.dummy, i )
endmethod
method operator x takes nothing returns real
return GetUnitX(this.dummy)
endmethod
method operator y takes nothing returns real
return GetUnitY(this.dummy)
endmethod
method operator z takes nothing returns real
return GetUnitFlyHeight(this.dummy)
endmethod
method operator z= takes real value returns nothing
call SetUnitFlyHeight(this.dummy,value,0)
endmethod
method operator x= takes real value returns nothing
call SetUnitX(this.dummy,value)
endmethod
method operator y= takes real value returns nothing
call SetUnitY(this.dummy,value)
endmethod
method operator fxpath= takes string newpath returns nothing
if (this.fx!=null) then
call DestroyEffect(this.fx)
endif
if (newpath=="") then
set this.fx=null
else
set this.fx=AddSpecialEffectTarget(newpath,this.dummy,"origin")
endif
endmethod
private method onDestroy takes nothing returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(this.fx!=null) then
call DestroyEffect(this.fx)
set this.fx=null
endif
if (recyclebin.end==MAX_INSTANCES) then
call TimerStart(recycler,0,false,function recyclebin.Recycle)
call ExplodeUnitBJ(this.dummy)
else
set recyclebin.end.u=this.dummy
set recyclebin.end.schedule=TimerGetElapsed(NOW)+RECYCLE_DELAY
set recyclebin.end= recyclebin( integer(recyclebin.end)+1)
if( recyclebin.end==1) then
call TimerStart(recycler, RECYCLE_DELAY, false, function recyclebin.Recycle)
endif
call SetUnitOwner(this.dummy,Player(15),false)
endif
set this.dummy=null
endmethod
endstruct
endlibrary
//TESH.scrollpos=153
//TESH.alwaysfold=0
xedamage
------
When blizzard released UnitDamageTarget and UnitDamagePoint there were some
issue, both had a lot of parameters that were undocumented, but more importantly
DamagePoint was not a good enough solution, UnitDamagePoint causes issues with
apple players and it also misses ways to specify what sort of unit to target.
What many people missed was a way to specify these things in a similar way
to targets allowed in the object editor.
Determining and configuring valid targets and things like damage factors is
always a hassle, xedamage can automatize that process in a nice way.
xedamage is the successor of damageoptions it is also a little less messy.
bitflags are not used anymore, instead xedamage uses struct members to specify
most of it. So, when using xedamage, you may end up feeling like feeling a
table of fields. An example is worth a thousand of words:
local xedamage d=xedamage.create()
set d.damageAllies=true // also harm allies
set d.exception = UNIT_TYPE_FLYING // don't harm fliers
call d.factor ( UNIT_TYPE_STRUCTURE, 0.5) // half damage to buildings
set d.dtype = DAMAGE_TYPE_UNIVERSAL // Do universal damage.
//Execute AOE damage using those options:
call d.damageAOE(GetTriggerUnit(), point_x, point_y, 250.0, 450)
But there is more, xedamage also has a couple of members like isInUse() that
would add some event responses for the damaged unit event. That will allow you
to recognize when xedamage was in use to inflict the damage, the damagetype and
attacktype used, and even a custom tag that you could specify as a xedamage
field, this would allow you to have a bridge between xedamage and certain damage
detection systems that rely on such things.
implementation
--------------
Just copy the xedamage trigger to your map.
xedamage object
----------------
xedamage fields include a bunch of boolean fields that you can set to
true/false, they hopefully got self-explaining names, I just list them and
their default values, remember I got a whole forum in wc3c, if you got doubts
don't forget to ask questions there:
boolean damageSelf = false
boolean damageAllies = false
boolean damageEnemies = true
boolean damageNeutral = true
boolean visibleOnly = false
boolean deadOnly = false
boolean alsoDead = false
boolean damageTrees = false
Something to notice is that damageTrees is probably only considered by AOE
damage and perhaps by some spells using xedamage to specify targets.
_______________________
boolean ranged = true
-----------------------
This is a special boolean field, it doesn't really determine valid targets
like the ones above, it merely determines if the damage should be considered
ranged, this merely determines how the AI reacts to the damage, a lot of people
don't care that much and just use true, that's the default.
___________________________________________________
damagetype dtype = DAMAGE_TYPE_UNIVERSAL
attacktype atype = ATTACK_TYPE_NORMAL
weapontype wtype = WEAPON_TYPE_WHOKNOWS
---------------------------------------------------
These fields determine the types to use in the damage native, the damage type
usually determines if the damage would be magical, universal (ultimate) or
physical, attacktype determines armor stuff, and weapon type determines sound.
There's some work on knowing what each combination does, for example:
http://www.wc3campaigns.net/showthread.php?t=100752
Some basic knowledge: For spells it is fine to use ATTACK_TYPE_NORMAL, and
that is default in xedamage, wtype usually doesn't need to be updated . dtype
is the important one, _UNIVERSAL makes the damage behave as a ultimate, there
is also _UNKNOWN which seems to ignore a lot of things, _FIRE, _LIGHTNING and
similar damagetypes are all magical, while DAMAGE_TYPE_NORMAL is physical.
Ultimate damage can harm both ethereal and spell immune units, magical damage
harms ethereal (with bonus) but cannot harm spell immune, physical damage can't
hurt ethereal units.
___________________________
integer tag = 0
---------------------------
This little field allows you to have a custom damage response CurrentDamageTag
(see bellow for event responses) basically, you can use whatever you want here
it all depends on what the thing that uses the event responses will do about it.
____________________________
unittype exception
unittype required
----------------------------
Learn a little about unittype, it is what blizzard calls unit classifications,
basically a unit can be a building, a flier, etc. exception specifies a
unittype that is required for a unit to receive damage. required, does the
opposite, for example:
set d.exception = UNIT_TYPE_FLYING
set d.required = UNIT_TYPE_SUMMONED
This xedamage instance can only hit ground summoned units.
* factor stuff:
Factor options in xedamage, specify some rules, if those rules are matched,
the damage will be multiplied by the specified factor, you can use negative
factor, half factor, etc. Notice that when using negative factors, these
things stack, so if you make a xedamage instance that does negative damage to
undead and negative damage to allies, it might do possitive damage to undead
allies.
If the total damage factor is 0.0 it is the same as adding an exception.
_____________________________
real allyfactor = 1.0
-----------------------------
If the xedamage can affect allies (damageAllies is true), then the damage
will be multiplied by allyfactor, for example, you can make a spell that does
half damage to allies. Or one that heals allies while hurting enemies.
_____________________________________________________________
method factor takes unittype ut, real fc returns nothing
-------------------------------------------------------------
This method allows you to add a specific factor for a unit type, by default
a xedamage instance allows up to three of these rules, you can increase this
cap by increasing MAX_SUB_OPTIONS in the top of the library.
For example:
call d.factor(UNIT_TYPE_STRUCTURE, 0.5)
call d.factor(UNIT_TYPE_SUMMONED, 2.0)
This instance of xedamage would do half damage to structures and double
damage to summoned units. Notice these things stack, so if for some reason
there was a "summoned building" in your map, it would do 100% damage.
_________________________________________________________________________
method abilityFactor takes integer abilityId, real fc returns nothing
-------------------------------------------------------------------------
Let's say you want a passive ability that makes you receive half damage
from fire spells, a way to do this is to make a whole damage detection system
and use xedamage's event responses to find out fire was used in the spell.
Then block the damage somehow... another way is to just change the spell so
when a unit has such passive ability, the damage is multiplied by 0.5 .
The 3 rules cap also works with abilityFactor and can as well be increased
by changing MAX_SUB_OPTIONS.
call d.abilityFactor( 'A000', 0.5)
____________________________________________________________________________
method useSpecialEffect takes string path, string attach returns nothing
----------------------------------------------------------------------------
This will make a special effect show up whenever succesful damage is done
using the xedamage object.
call d.useSpecialEffect("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl","origin")
xedamage methods
----------------
There would be little point in using all those fields without the methods that
make use of them.
____________________________________________________________________________________
method damageTarget takes unit source, unit target, real damage returns boolean
------------------------------------------------------------------------------------
A single unit targetting method, it will consider all the rules we just
reviewed when doing the damage, if the damage would get a factor of 0.0 it will
not perform any damage.
local unit u = GetTriggerUnit()
local unit t = GetSpellTargetUnit()
local xedamage d= xedamage.create()
set d.dtype = DAMAGE_TYPE_FIRE
set d.damageAllies = true
set d.allyfactor = -1.0
call d.damageTarget(u,t, 100)
call d.destroy()
This would be a simple spell that does 100.0 fire damage on enemy units or
heals for 100 hitpoints to allies.
This method returns true if non zero damage was done, and false otherwise.
____________________________________________________________________________________________
method damageTargetForceValue takes unit source, unit target, real damage returns nothing
---------------------------------------------------------------------------------------------
This is an analogue for damageTarget, but it will IGNORE every specified
field, and try to do the specified damage, no matter the circumstances, however
it will use the xedamage instances' dtype, atype and tag for the xedamage event
responses (see bellow).
This could be useful when you already know the factor
(you have previously used getTargetFactor)
__________________________________________________________________________
method allowedTarget takes unit source, unit target returns boolean
--------------------------------------------------------------------------
Returns true if xedamage would do a damage different than 0.0 in this case.
It is useful if you intend to use xedamage to configure a spell's allowed
targets.
if ( d.allowedTarget(u,t) ) then
//...
____________________________________________________________________________
method getTargetFactor takes unit source, unit target returns real
----------------------------------------------------------------------------
For applications similar to allowedTarget, this returns the whole damage
factor, so you can decide what to do based on it.
set fc = d.getTargetFactor(u, t)
________________________________________________________________________________________
method damageGroup takes unit source, group targetGroup, real damage returns integer
----------------------------------------------------------------------------------------
This method does what damageTarget does, but it executes it on a whole unit
group, probably faster than calling damageTarget on every unit in the group.
Notice this method will empty the provided group.
_________________________________________________________________________________________________
method damageAOE takes unit source, real x, real y, real radius, real damage returns integer
method damageAOELoc takes unit source, location loc, real radius, real damage returns integer
-------------------------------------------------------------------------------------------------
It will perform damage on units and destructables (if damageTrees is true)
that are inside the circle, notice collision sizes are considered. The returned
value is the number of targets that were affected by the function, the loc
version allows you to use a location instead of the more sane x,y coordinates.
______________________________________________________________________________________________________________
method damageDestructablesAOE takes unit source, real x, real y, real radius, real damage returns integer
method damageDestructablesAOELoc takes unit source, location loc, real radius, real damage returns integer
-----------------------------------------------------------------------------------------------------------
This one damages all the destructables in a circle, regardless of damageTrees
being true or not.
xedamage static method
-----------------------
________________________________________________________________________________________________
static method getDamageTypeFactor takes unit u, attacktype a, damagetype d returns real
------------------------------------------------------------------------------------------------
This method is used by one of the factor methods up there, thought it would be
useful to make it available as a public method, it just returns the factor that
a specific attacktype/damagetype couple would do on a certain unit u:
set fc = xedamage.getDamageTypeFactor( GetTriggerUnit(), ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE)
Would return 1.0 if the unit is a normal unit, 1.66 if it is ethereal and 0 if it is spell immune.
xedamage event responses
------------------------
______________________________________________________________________
static method isInUse takes nothing returns boolean
----------------------------------------------------------------------
This method will return true if xedamage was in use during a damaged event,
this would help you determine if the damage was inflicted by a xedamage call.
_______________________________________________________________________
readonly static damagetype CurrentDamageType=null
readonly static attacktype CurrentAttackType=null
readonly static integer CurrentDamageTag =0
-----------------------------------------------------------------------
When isInUse() returns true, you can use these event responses to determine
how was the damage inflicted, you can get the damage type, the attack type and
the tag (specified by the field tag in the xedamage object) of the call.
if (xedamage.isInUse() ) then
if(xedamage.CurrentDamageType == DAMAGE_TYPE_FIRE ) then
call BJDebugMsg(R2S(GetEventDamage())+" fire damage was inflicted to "+GetUnitName(GetTriggerUnit() ) )
endif
call BJDebugMsg("tag used: "+I2S(xedamage.CurrentDamageTag) )
endif
--
The FireNovaStrike sample attempts to be a quick example on
how to use xedamage.
//TESH.scrollpos=409
//TESH.alwaysfold=0
library xedamage initializer init// requires xebasic
//************************************************************************
// xedamage 0.6
// --------
// For all your damage and targetting needs.
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAX_SUB_OPTIONS = 3
//=======================================================
private constant real EPSILON = 0.000000001
private unit dmger
private constant integer MAX_SPACE = 8190 // MAX_SPACE/MAX_SUB_OPTIONS is the instance limit for xedamage, usually big enough...
endglobals
private keyword structInit
struct xedamage[MAX_SPACE]
//----
// fields and methods for a xedamage object, they aid determining valid targets and special
// damage factor conditions.
//
// Notice the default values.
//
boolean damageSelf = false // the damage and factor methods usually have a source unit parameter
// xedamage would consider this unit as immune unless you set damageSelf to true
boolean damageAllies = false // Alliance dependent target options.
boolean damageEnemies = true // *
boolean damageNeutral = true // *
boolean ranged = true // Is the attack ranged? This has some effect on the AI of the affected units
// true by default, you may not really need to modify this.
boolean visibleOnly = false // Should only units that are visible for source unit's owner be affected?
boolean deadOnly = false // Should only corpses be affected by "the damage"? (useful when using xedamage as a target selector)
boolean alsoDead = false // Should even corpses and alive units be considered?
boolean damageTrees = false //Also damage destructables? Notice this is used only in certain methods.
//AOE for example targets a circle, so it can affect the destructables
//in that circle, a custom spell using xedamage for targetting configuration
//could also have an if-then-else implemented so it can verify if it is true
//then affect trees manually.
//
// Damage type stuff:
// .dtype : the "damagetype" , determines if the spell is physical, magical or ultimate.
// .atype : the "attacktype" , deals with armor.
// .wtype : the "weapontype" , determines the sound effect to be played when damage is done.
//
// Please use common.j/blizzard.j/ some guide to know what damage/attack/weapon types can be used
//
damagetype dtype = DAMAGE_TYPE_UNIVERSAL
attacktype atype = ATTACK_TYPE_NORMAL
weapontype wtype = WEAPON_TYPE_WHOKNOWS
//
// Damage type 'tag' people might use xedamage.isInUse() to detect xedamage usage, there are other static
// variables like xedamage.CurrentDamageType and xedamage.CurrentDamageTag. The tag allows you to specify
// a custom id for the damage type ** Notice the tag would aid you for some spell stuff, for example,
// you can use it in a way similar to Rising_Dusk's damage system.
//
integer tag = 0
//
// if true, forceDamage will make xedamage ignore dtype and atype and try as hard as possible to deal 100%
// damage.
boolean forceDamage = false
//
// Ally factor! Certain spells probably have double purposes and heal allies while harming enemies. This
// field allows you to do such thing.
//
real allyfactor = 1.0
//
// field: .exception = SOME_UNIT_TYPE
// This field adds an exception unittype (classification), if the unit belongs to this unittype it will
// be ignored.
//
method operator exception= takes unittype ut returns nothing
set this.use_ex=true
set this.ex_ut=ut
endmethod
//
// field: .required = SOME_UNIT_TYPE
// This field adds a required unittype (classification), if the unit does not belong to this unittype
// it will be ignored.
//
method operator required= takes unittype ut returns nothing
set this.use_req=true
set this.req_ut=ut
endmethod
private boolean use_ex = false
private unittype ex_ut = null
private boolean use_req = false
private unittype req_ut = null
private unittype array fct[MAX_SUB_OPTIONS]
private real array fc[MAX_SUB_OPTIONS]
private integer fcn=0
//
// method .factor(SOME_UNIT_TYPE, factor)
// You might call factor() if you wish to specify a special damage factor for a certain classification,
// for example call d.factor(UNIT_TYPE_STRUCTURE, 0.5) makes xedamage do half damage to structures.
//
method factor takes unittype ut, real fc returns nothing
if(this.fcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to factor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of factor() calls")
return
endif
set this.fct[this.fcn] = ut
set this.fc[this.fcn] = fc
set this.fcn = this.fcn+1
endmethod
private integer array abifct[MAX_SUB_OPTIONS]
private real array abifc[MAX_SUB_OPTIONS]
private integer abifcn=0
//
// method .abilityFactor('abil', factor)
// You might call abilityFactor() if you wish to specify a special damage factor for units that have a
// certain ability/buff.
// for example call d.abilityFactor('A000', 1.5 ) makes units that have the A000 ability take 50% more
// damage than usual.
//
method abilityFactor takes integer abilityId, real fc returns nothing
if(this.abifcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to abilityFactor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of abilityFactor() calls")
return
endif
set this.abifct[this.abifcn] = abilityId
set this.abifc[this.abifcn] = fc
set this.abifcn = this.abifcn+1
endmethod
private boolean usefx = false
private string fxpath
private string fxattach
//
// method .useSpecialEffect("effect\\path.mdl", "origin")
// Makes it add (and destroy) an effect when damage is performed.
//
method useSpecialEffect takes string path, string attach returns nothing
set this.usefx = true
set this.fxpath=path
set this.fxattach=attach
endmethod
//********************************************************************
//* Now, the usage stuff:
//*
//================================================================================
// static method xedamage.isInUse() will return true during a unit damaged
// event in case this damage was caused by xedamage, in this case, you can
// read variables like CurrentDamageType, CurrentAttackType and CurrentDamageTag
// to be able to recognize what sort of damage was done.
//
readonly static damagetype CurrentDamageType=null
readonly static attacktype CurrentAttackType=null
readonly static integer CurrentDamageTag =0
private static integer inUse = 0
static method isInUse takes nothing returns boolean
return (.inUse>0) //inline friendly.
endmethod
//========================================================================================================
// This function calculates the damage factor caused by a certain attack and damage
// type, it is static : xedamage.getDamageTypeFactor(someunit, ATTAcK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, 100)
//
static method getDamageTypeFactor takes unit u, attacktype a, damagetype d returns real
local real hp=GetWidgetLife(u)
local real mana=GetUnitState(u,UNIT_STATE_MANA)
local real r
//Since a unit is in that point, we don't need checks.
call SetUnitX(dmger,GetUnitX(u))
call SetUnitY(dmger,GetUnitY(u))
call SetUnitOwner(dmger,GetOwningPlayer(u),false)
set r=hp
if (hp<1) then
call SetWidgetLife(u,1)
set r=1
endif
call UnitDamageTarget(dmger,u,0.01,false,false,a,d,null)
call SetUnitOwner(dmger,Player(15),false)
if (mana>GetUnitState(u,UNIT_STATE_MANA)) then
//Unit had mana shield, return 1 and restore mana too.
call SetUnitState(u,UNIT_STATE_MANA,mana)
set r=1
else
set r= (r-GetWidgetLife(u))*100
endif
call SetWidgetLife(u,hp)
return r
endmethod
private method getTargetFactorCore takes unit source, unit target, boolean usetypes returns real
local player p=GetOwningPlayer(source)
local boolean allied=IsUnitAlly(target,p)
local boolean enemy =IsUnitEnemy(target,p)
local boolean neutral=allied
local real f
local real negf=1.0
local integer i
if(this.damageAllies != this.damageNeutral) then
set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_REQUEST ))
//I thought accuracy was not as important as speed , I think that REQUEST is false is enough to consider
// it neutral.
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_RESPONSE ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_XP ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_SPELLS ))
set allied= allied and not(neutral)
endif
if (not this.damageAllies) and allied then
return 0.0
elseif (not this.damageEnemies) and enemy then
return 0.0
elseif( (not this.damageSelf) and (source==target) ) then
return 0.0
elseif (not this.damageNeutral) and neutral then
return 0.0
elseif( this.use_ex and IsUnitType(target, this.ex_ut) ) then
return 0.0
elseif( this.visibleOnly and not IsUnitVisible(target,p) ) then
return 0.0
elseif ( this.deadOnly and not IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
elseif ( not(this.alsoDead) and IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
endif
set f=1.0
if ( IsUnitAlly(target,p) ) then
set f=f*this.allyfactor
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
if (this.use_req and not IsUnitType(target,this.req_ut)) then
return 0.0
endif
set i=.fcn-1
loop
exitwhen (i<0)
if( IsUnitType(target, this.fct[i] ) ) then
set f=f*this.fc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set i=.abifcn-1
loop
exitwhen (i<0)
if( GetUnitAbilityLevel(target,this.abifct[i] )>0 ) then
set f=f*this.abifc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set f=f*negf
if ( f<=EPSILON) and (f>=-EPSILON) then
return 0.0
endif
if( this.forceDamage or not usetypes ) then
return f
endif
set f=f*xedamage.getDamageTypeFactor(target,this.atype,this.dtype)
if ( f<=EPSILON) and (f>=-EPSILON) then
return 0.0
endif
return f
endmethod
//====================================================================
// With this you might decide if a unit is a valid target for a spell.
//
method getTargetFactor takes unit source, unit target returns real
return this.getTargetFactorCore(source,target,true)
endmethod
//======================================================================
// a little better, I guess
//
method allowedTarget takes unit source, unit target returns boolean
return (this.getTargetFactorCore(source,target,false)!=0.0)
endmethod
//=======================================================================
// performs damage to the target unit, for unit 'source'.
//
method damageTarget takes unit source, unit target, real damage returns boolean
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local real f = this.getTargetFactorCore(source,target,false)
local real pl
if(f!=0.0) then
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
set pl=GetWidgetLife(target)
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return (pl!=GetWidgetLife(target))
endif
return false
endmethod
//=======================================================================================
// The same as damageTarget, but it forces a specific damage value, good if you already
// know the target.
//
method damageTargetForceValue takes unit source, unit target, real damage returns nothing
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
call UnitDamageTarget(source,target, damage, true, .ranged, null, null, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
endmethod
//=====================================================================================
// Notice: this will not Destroy the group, but it will certainly empty the group.
//
method damageGroup takes unit source, group targetGroup, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local unit target
local real f
local integer count=0
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
loop
set target=FirstOfGroup(targetGroup)
exitwhen (target==null)
call GroupRemoveUnit(targetGroup,target)
set f= this.getTargetFactorCore(source,target,false)
if (f!=0.0) then
set count=count+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
endif
endloop
set .inUse = .inUse -1
set .CurrentDamageTag=tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return count
endmethod
private static xedamage instance
private integer countAOE
private unit sourceAOE
private real AOEx
private real AOEy
private real AOEradius
private real AOEdamage
private static boolexpr filterAOE
private static boolexpr filterDestAOE
private static group enumgroup
private static rect AOERect
private static method damageAOE_Enum takes nothing returns boolean
local unit target=GetFilterUnit()
local xedamage this=.instance //adopting a instance.
local real f
if( not IsUnitInRangeXY(target,.AOEx, .AOEy, .AOEradius) ) then
set target=null
return false
endif
set f=.getTargetFactorCore(.sourceAOE, target, false)
if(f!=0.0) then
set .countAOE=.countAOE+1
call UnitDamageTarget(.sourceAOE,target, f*this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
endif
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
private static method damageAOE_DestructablesEnum takes nothing returns boolean
local destructable target=GetFilterDestructable()
local xedamage this=.instance //adopting a instance.
local real dx=.AOEx-GetDestructableX(target)
local real dy=.AOEy-GetDestructableY(target)
if( dx*dx + dy*dy >= .AOEradius+EPSILON ) then
set target=null
return false
endif
set .countAOE=.countAOE+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(.sourceAOE,target, this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
//==========================================================================================
// will affect trees if damageTrees is true!
//
method damageAOE takes unit source, real x, real y, real radius, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius
set .AOEy=y
set .AOEdamage=damage
call GroupEnumUnitsInRange(.enumgroup,x,y,radius+XE_MAX_COLLISION_SIZE, .filterAOE)
if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
set .AOEradius=.AOEradius*.AOEradius
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
endif
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return .countAOE
endmethod
method damageAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageAOE(source, GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//==========================================================================================
// only affects trees, ignores damageTrees
//
method damageDestructablesAOE takes unit source, real x, real y, real radius, real damage returns integer
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius*radius
set .AOEy=y
set .AOEdamage=damage
//if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
//endif
return .countAOE
endmethod
method damageDestructablesAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageDestructablesAOE(source,GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//'friend' with the library init
static method structInit takes nothing returns nothing
set .AOERect= Rect(0,0,0,0)
set .filterAOE= Condition(function xedamage.damageAOE_Enum)
set .filterDestAOE = Condition( function xedamage.damageAOE_DestructablesEnum)
set .enumgroup = CreateGroup()
endmethod
endstruct
private function init takes nothing returns nothing
set dmger=CreateUnit(Player(15), XE_DUMMY_UNITID , 0.,0.,0.)
call UnitAddAbility(dmger,'Aloc')
call xedamage.structInit()
endfunction
endlibrary
//TESH.scrollpos=20
//TESH.alwaysfold=0
function Trig_Ini_Func004001002 takes nothing returns boolean
return ( IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO) == false )
endfunction
function Trig_Ini_Func004A takes nothing returns nothing
call SetUnitUserData( GetEnumUnit(), udg_T_Integer )
set udg_R_Locations[udg_T_Integer] = GetUnitLoc(GetEnumUnit())
set udg_T_Integer = ( udg_T_Integer + 1 )
endfunction
function Trig_Ini_Actions takes nothing returns nothing
local real TX = 0
local real TY = 0
local integer i = 0
local integer r = 0
loop
exitwhen i > 299
set i = i + 1
set TX = GetRandomReal(-5376,5344)
set TY = GetRandomReal(-5632,5088)
set r = GetRandomInt(0, 3)
if r == 0 then
call SetTerrainType(TX,TY,'Adrd',-1,GetRandomInt(1, 5),0)
elseif r == 1 then
call SetTerrainType(TX,TY,'Adrg',-1,GetRandomInt(1, 5),0)
elseif r == 2 then
call SetTerrainType(TX,TY,'Alvd',-1,GetRandomInt(1, 5),0)
elseif r == 3 then
call SetTerrainType(TX,TY,'Adrt',-1,GetRandomInt(1, 5),0)
endif
endloop
call ForGroup( GetUnitsInRectMatching(GetWorldBounds(), Condition(function Trig_Ini_Func004001002)), function Trig_Ini_Func004A )
call BJDebugMsg("TRIGSTR_250")
call BJDebugMsg("
Spell made by: D1000
Thanks to Vexorian for JassHelper, XE, BoundSentinel and TimeUtils")
call SetTimeOfDay( 12.00 )
call Cheat("iseedeadpeople")
call Cheat("thereisnospoon")
endfunction
//===========================================================================
function InitTrig_Ini takes nothing returns nothing
set gg_trg_Ini = CreateTrigger()
call TriggerAddAction( gg_trg_Ini, function Trig_Ini_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Heal takes nothing returns nothing
call SetUnitState(GetEnumUnit(), UNIT_STATE_LIFE, GetUnitState(GetEnumUnit(), UNIT_STATE_MAX_LIFE))
call SetUnitState(GetEnumUnit(), UNIT_STATE_MANA, GetUnitState(GetEnumUnit(), UNIT_STATE_MAX_MANA))
call UnitResetCooldown(GetEnumUnit())
endfunction
function Trig_Heal_Actions takes nothing returns nothing
local group g = CreateGroup()
call SyncSelections()
call GroupEnumUnitsSelected(g,Player(0),null)
call ForGroup(g, function Heal )
call DestroyGroup (g)
endfunction
//===========================================================================
function InitTrig_Heal takes nothing returns nothing
set gg_trg_Heal = CreateTrigger()
call TriggerRegisterPlayerEventEndCinematic( gg_trg_Heal, Player(0) )
call TriggerAddAction( gg_trg_Heal, function Trig_Heal_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Time_Actions takes nothing returns nothing
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, S2R(GetEventPlayerChatString()))
endfunction
//===========================================================================
function InitTrig_Time takes nothing returns nothing
set gg_trg_Time = CreateTrigger()
call TriggerRegisterPlayerChatEvent(gg_trg_Time,Player(0),"", false)
call TriggerAddAction(gg_trg_Time,function Trig_Time_Actions)
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function startunits takes nothing returns boolean
if GetUnitTypeId(GetDyingUnit()) == 'earc' then
return true
endif
return GetUnitTypeId(GetDyingUnit()) == 'edry'
endfunction
function Trig_Revive_Actions takes nothing returns nothing
local unit u = GetDyingUnit()
local unit uu = null
if GetOwningPlayer(u) == Player(0) then
if IsUnitType(u, UNIT_TYPE_HERO) then
call TriggerSleepAction( 3.00 )
call ReviveHero( u, GetStartLocationX(GetPlayerStartLocation(GetOwningPlayer(u))),GetStartLocationX(GetPlayerStartLocation(GetOwningPlayer(u))), false )
elseif startunits() then
set uu = CreateUnitAtLoc(Player(0),GetUnitTypeId(u), udg_R_Locations[GetUnitUserData(u)], GetUnitFacing(GetEnumUnit()) )
call SetUnitUserData( uu, GetUnitUserData(u) )
endif
elseif IsUnitType(u, UNIT_TYPE_HERO) == true then
call TriggerSleepAction( 20.00 )
call ReviveHeroLoc( u, GetUnitLoc(u), false )
else
call TriggerSleepAction( 40.00 )
set uu = CreateUnitAtLoc(GetOwningPlayer(u), GetUnitTypeId(u),udg_R_Locations[GetUnitUserData(u)], GetUnitFacing(GetEnumUnit()) )
call SetUnitUserData( uu, GetUnitUserData(u) )
endif
set u = null
set uu = null
endfunction
//===========================================================================
function InitTrig_Revive takes nothing returns nothing
set gg_trg_Revive = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Revive,EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(gg_trg_Revive,function Trig_Revive_Actions)
endfunction