//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
Hero | unit | No | |
test | integer | No | 0 |
//TESH.scrollpos=28
//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
//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=0
//TESH.alwaysfold=0
library xecast initializer init requires xebasic
//************************************************************************
// xecast 0.5
// ------
// Because dummy casters REALLY ARE this complicated!
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAXINSTANCES = 8190 //this is a lot, unless you leak xecast objects
private constant integer INITIAL_DUMMY_COUNT = 12
private constant integer DUMMY_STACK_LIMIT = 50 //don't allow to keep more than DUMMY_STACK_LIMIT innactive dummy units
private constant boolean FORCE_INVISIBLE_CAST = false // If your map does not give visibility to all players, or
// for other reasons, you might want xecast to work on
// units that are not visible to the player, in that case
// change this to true, else it is just a performance loss.
endglobals
//=========================================================================
// Please notice all textmacros in this library are considered private.
// in other words: DON'T RUN THOSE TEXTMACROS!
//
private keyword structinit
globals
private real EPSILON=0.001 //noticed in war3 this is the sort of precision we want...
endglobals
struct xecast[MAXINSTANCES]
public integer abilityid = 0 //ID (rawcode) of the ability to cast
public integer level = 1 //Level of the ability to cast
public real recycledelay = 0.0 //Please notice, some spells need a recycle delay
// This is, a time period before they get recycle.
// For example, some spells are not instant, there is
// also the problem with damaging spells, this recycle
// delay must be large enough to contain all the time
// in which the spell can do damage.
public player owningplayer=Player(15) //which player to credit for the ability cast?
//notice this can also affect what units are targeteable
//==================================================================================================
// You need an order id for the ability so the dummy unit is able to cast it, two ways to assign it
// set instance.orderid = 288883 //would assign an integer orderid
// set instance.orderstring = "chainlightning" //would assign an orderstring
// (as those in the object editor)
//
method operator orderid= takes integer v returns nothing
set .oid=v
endmethod
method operator orderstring= takes string s returns nothing
set .oid=OrderId(s)
endmethod
//=================================================================================================
// Finally, you can determine from which point to cast the ability: z is the height coordinate.
//
public boolean customsource=false //Use a custom casting source?
public real sourcex // Determine the casting source for the dummy spell, require customsource =true
public real sourcey // You might prefer to use the setSourcePoint method
public real sourcez=0.0 //
method setSourcePoint takes real x, real y, real z returns nothing
set .sourcex=x
set .sourcey=y
set .sourcez=z
set .customsource=true
endmethod
method setSourceLoc takes location loc, real z returns nothing
set .sourcex=GetLocationX(loc)
set .sourcey=GetLocationY(loc)
set .sourcez=z
set .customsource=true
endmethod
private boolean autodestroy = false
//========================================================================================================
// you are always allowed to use .create() but you can also use createBasic which sets some things that
// are usually necessary up.
//
public static method createBasic takes integer abilityID, integer orderid, player owner returns xecast
local xecast r=xecast.allocate()
if(r==0) then
debug call BJDebugMsg("Warning: unbelievable but you actually used all xecast instances in your map! Please make sure you are not forgetting to destroy those what you create intensively, if that's not the case, then you'll have to increase xecast MAXINSTANCES")
endif
set r.oid=orderid
set r.abilityid=abilityID
set r.owningplayer=owner
return r
endmethod
//========================================================================================================
// Just like the above one, but the instance will self destruct after a call to any cast method
// (recommended)
//
public static method createBasicA takes integer abilityID, integer orderid, player owner returns xecast
local xecast r=xecast.allocate()
if(r==0) then
debug call BJDebugMsg("Warning: unbelievable but you actually used all xecast instances in your map! Please make sure you are not forgetting to destroy those what you create intensively, if that's not the case, then you'll have to increase xecast MAXINSTANCES")
endif
set r.oid=orderid
set r.abilityid=abilityID
set r.owningplayer=owner
set r.autodestroy=true
return r
endmethod
//==========================================================================================================
// Just like create, but the struct instance self destructs after a call to any cast method
// (Recommended)
//
public static method createA takes nothing returns xecast
local xecast r=xecast.allocate()
set r.autodestroy=true
return r
endmethod
//==========================================================================================================
// So, create the dummy, assign options and cast the skill!
// .castOnTarget(w) : If you want to hit a widget w with the ability
// .castOnPoint(x,y) : If you want to hit a point (x,y) with the ability
// .castInPoint(x,y) : For spells like warstomp which do not have a target.
// .castOnAOE(x,y,radius) : Classic area of effect cast. Considers collision size
// .castOnGroup(g) : Cast unit the unit group g, notice it will empty the group yet not destroy it.
//
//**********************************************************************************************************
// The implementation of such methods follows:
private static unit array dummystack
private static integer top=0
private static unit instantdummy
private integer oid=0
private static timer gametime
private static timer T
private static unit array recycle
private static real array expiretime
private static integer rn=0
//==========================================================================================================
// private dorecycle method, sorry but I need this up here.
//
private static method dorecycle takes nothing returns nothing
local unit u =.recycle[0]
local integer l
local integer r
local integer p
local real lt
call UnitRemoveAbility(u,GetUnitUserData(u))
call SetUnitUserData(u,0)
call SetUnitFlyHeight(u,0,0)
call PauseUnit(u,false)
if(.top==DUMMY_STACK_LIMIT) then
call RemoveUnit(u)
else
set .dummystack[.top]=u
set .top=.top+1
endif
set .rn=.rn-1
if(.rn==0) then
return
endif
set p=0
set lt=.expiretime[.rn]
loop
set l=p*2+1
exitwhen l>=.rn
set r=p*2+2
if(r>=.rn)then
if(.expiretime[l]<lt) then
set .expiretime[p]=.expiretime[l]
set .recycle[p]=.recycle[l]
set p=l
else
exitwhen true
endif
elseif (lt<=.expiretime[l]) and (lt<=.expiretime[r]) then
exitwhen true
elseif (.expiretime[l]<.expiretime[r]) then
set .expiretime[p]=.expiretime[l]
set .recycle[p]=.recycle[l]
set p=l
else
set .expiretime[p]=.expiretime[r]
set .recycle[p]=.recycle[r]
set p=r
endif
endloop
set .recycle[p]=.recycle[.rn]
set .expiretime[p]=lt
call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
endmethod
private static trigger abilityRemove
// Repetitive process and no inline implemented for large functions, so for now it is a textmacro:
//! textmacro xecast_allocdummy
if(.recycledelay<EPSILON) then
set dummy=.instantdummy
call SetUnitOwner(dummy,.owningplayer,false)
elseif (.top>0) then
set .top=.top-1
set dummy=.dummystack[.top]
call SetUnitOwner(dummy,.owningplayer,false)
else
set dummy=CreateUnit(.owningplayer,XE_DUMMY_UNITID,0,0,0)
call TriggerRegisterUnitEvent(.abilityRemove,dummy,EVENT_UNIT_SPELL_ENDCAST)
call UnitAddAbility(dummy,'Aloc')
call UnitAddAbility(dummy,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(dummy,XE_HEIGHT_ENABLER)
endif
call UnitAddAbility(dummy,.abilityid)
if(.level>1) then
call SetUnitAbilityLevel(dummy,.abilityid,.level)
endif
//! endtextmacro
private static integer cparent
private static integer current
private static real cexpire
//! textmacro xecast_deallocdummy
if(.recycledelay>=EPSILON) then
set .cexpire=TimerGetElapsed(.gametime)+.recycledelay
set .current=.rn
set .rn=.rn+1
loop
exitwhen (.current==0)
set .cparent=(.current-1)/2
exitwhen (.expiretime[.cparent]<=.cexpire)
set .recycle[.current]=.recycle[.cparent]
set .expiretime[.current]=.expiretime[.cparent]
set .current=.cparent
endloop
set .expiretime[.current]=.cexpire
set .recycle[.current]=dummy
call SetUnitUserData(dummy,.abilityid)
call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
else
call SetUnitUserData(dummy,0)
call SetUnitFlyHeight(dummy,0,0)
call UnitRemoveAbility(dummy,.abilityid)
endif
//! endtextmacro
method castOnTarget takes unit target returns nothing
local unit dummy
local unit tar
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,GetWidgetX(target))
call SetUnitY(dummy,GetWidgetY(target))
endif
if (FORCE_INVISIBLE_CAST) then
call UnitShareVision(target, .owningplayer, true)
call IssueTargetOrderById(dummy,this.oid,target)
call UnitShareVision(target, .owningplayer, false)
else
call IssueTargetOrderById(dummy,this.oid,target)
endif
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
//accepts units, items and destructables, if you know it is
// a unit it is better to use castOnTarget since that would
// be able to use FORCE_INVISIBLE_CAST if necessary.
//
method castOnWidgetTarget takes widget target returns nothing
local unit dummy
local unit tar
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,GetWidgetX(target))
call SetUnitY(dummy,GetWidgetY(target))
endif
call IssueTargetOrderById(dummy,this.oid,target)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castOnPoint takes real x, real y returns nothing
local unit dummy
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,x)
call SetUnitY(dummy,y)
endif
call IssuePointOrderById(dummy,this.oid,x,y)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castOnLoc takes location loc returns nothing
//debug call BJDebugMsg("Warning: Locations are in use")
//nah but I should
call .castOnPoint(GetLocationX(loc),GetLocationY(loc))
endmethod
//ignores custom source x and y (for obvious reasons)
method castInPoint takes real x, real y returns nothing
local unit dummy
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitFlyHeight(dummy,.sourcez,0.0)
endif
call SetUnitX(dummy, x)
call SetUnitY(dummy, y)
call IssueImmediateOrderById(dummy,this.oid)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castInLoc takes location loc returns nothing
//debug call BJDebugMsg("Warning: Locations are in use")
//nah but I should
call .castInPoint(GetLocationX(loc),GetLocationY(loc))
endmethod
//===================================================================================================
// For method castOnAOE:
//
private static group enumgroup
private static real aoex
private static real aoey
private static real aoeradius
private static xecast cinstance
private static boolexpr aoefunc
// Might look wrong, but this is the way to make it consider collision size, a spell that
// got a target circle and uses this method will let the user know which units it will
// hit with the mass cast.
static method filterAOE takes nothing returns boolean
local unit u=GetFilterUnit()
if IsUnitInRangeXY(u, .aoex, .aoey, .aoeradius) then
call .cinstance.castOnTarget(u)
endif
set u=null
return false
endmethod
//
method castOnAOE takes real x, real y, real radius returns nothing
local boolean ad=this.autodestroy
if(ad) then
set this.autodestroy=false
endif
set .aoex=x
set .aoey=y
set .aoeradius=radius
set .cinstance=this
call GroupEnumUnitsInRange(.enumgroup,x,y,radius + XE_MAX_COLLISION_SIZE , .aoefunc)
if(ad) then
call this.destroy()
endif
endmethod
method castOnAOELoc takes location loc,real radius returns nothing
call .castOnAOE(GetLocationX(loc),GetLocationY(loc),radius)
endmethod
//==================================================================================================
// A quick and dirt castOnGroup method, perhaps it'll later have castOntarget inlined, but not now
//
method castOnGroup takes group g returns nothing
local boolean ad=this.autodestroy
local unit t
if(ad) then
set this.autodestroy=false
endif
loop
set t=FirstOfGroup(g)
exitwhen(t==null)
call GroupRemoveUnit(g,t)
call .castOnTarget(t)
endloop
if(ad) then
call this.destroy()
endif
endmethod
private static method removeAbility takes nothing returns boolean
local unit u=GetTriggerUnit()
if(GetUnitUserData(u)!=0) then
call PauseUnit(u,true)
endif
//This is necessary, picture a value for recycle delay that's higher than the casting time,
//for example if the spell does dps, if you leave the dummy caster with the ability and it
//is owned by an AI player it will start casting the ability on player units, so it is
// a good idea to pause it...
set u=null
return true
endmethod
//===================================================================================================
// structinit is a scope private keyword.
//
static method structinit takes nothing returns nothing
local integer i=INITIAL_DUMMY_COUNT+1
local unit u
set .aoefunc=Condition(function xecast.filterAOE)
set .enumgroup=CreateGroup()
set .abilityRemove = CreateTrigger()
loop
exitwhen (i==0)
set u=CreateUnit(Player(15),XE_DUMMY_UNITID,0,0,0)
call TriggerRegisterUnitEvent(.abilityRemove,u,EVENT_UNIT_SPELL_ENDCAST)
call UnitAddAbility(u,'Aloc')
call UnitAddAbility(u,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(u,XE_HEIGHT_ENABLER)
set .dummystack[.top]=u
set .top=.top+1
set i=i-1
endloop
call TriggerAddCondition(.abilityRemove, Condition(function xecast.removeAbility ) )
set .top=.top-1
set .instantdummy=.dummystack[.top]
set .T=CreateTimer()
set .gametime=CreateTimer()
call TimerStart(.gametime,12*60*60,false,null)
endmethod
endstruct
private function init takes nothing returns nothing
call xecast.structinit()
endfunction
endlibrary
//TESH.scrollpos=173
//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 SetUnitExploded(this.dummy, true)
call KillUnit(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=488
//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=12
//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=69
//TESH.alwaysfold=0
/*
SHADOW EXPLOSION
by Kricz
Version:
1.01
Credits:
Vexorian, cedi, peq, D1000, moyack & SkriK
Requires:
TimerUtils, xebasic, xecast, xedamage, xepreload
Discription:
Creates a circle of shadow missiles arround the target area, jumping
to the target point. When they reach the middle, a huge explosion appears,
damaging enemies in the area arround it over time. After the explosion expires,
shadow splitters spawn out of the explosion, flying to random point arround the
area, dealing AoE Damage in their impact area.
*/
scope ShadowExplosion ////needs TimerUtils, xedamage, xepreload
globals
/*MAIN SETUP*/
//The Spell Id of the Spell
private constant integer SPELL_ID = 'A000'
//The Number of Levels for the Spell
private constant integer LEVELS = 3
//Should all destructibles/trees be destroyed by the explosion and the splitters?
private constant boolean DESTROY_TREES = true
/*END OF MAIN SETUP*/
/*MISSILE SETUP*/
//The model of the missiles
private constant string MISSILE_MODEL = "Abilities\\Spells\\Items\\OrbCorruption\\OrbCorruptionMissile.mdl"
//The size of the missiles
private constant real MISSILE_SIZE = 2.25
//The heigh of the missiles
private constant real MISSILE_HEIGH = 55.00
//The duration, unitl all missiles are created at the start
private constant real CREATE_INTERVAL = 1.25
//The number of missiles at the start
private constant integer MISSILE_COUNT = 25 //15 is also good, but 30 looks better for me^^
//The start-speed of the missiles
private constant real MISSILE_START_SPEED = 200.00
//The end-speed of the missiles
private constant real MISSILE_END_SPEED = 700.00
//The distance between the casting point and the created missiles
private constant real MISSILE_DISTANCE = 500.00
/*ONLY 3D MODE SETUPS*/
//If this is true, the missiles will "jump" to the middle ;)
private constant boolean MISSILE_3D_MODE = true
//The maximal heigh of the missiles
private constant real MISSILE_3D_HEIGH = 325.00
//Should the Z-Angle also be changed while jumping? (Looks cooler)
private constant boolean MISSILE_Z_ANGLE = true
/*END OF 3D MODE SETUPS*/
/*END OF MISSILE SETUP*/
/*SPLITTER SETUP*/
//The model of the splitters
private constant string SPLITTER_MODEL = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
//The size of the splitter
private constant real SPLITTER_SIZE = 0.85
//The start heigh of the splitter
private constant real SPLITTER_START_HEIGH = 10.00
//The end heigh of the splitter
private constant real SPLITTER_END_HEIGH = 15.00
//The start-speed of the splitter
private constant real SPLITTER_START_SPEED = 255.00
//The end-speed of the splitter
private constant real SPLITTER_END_SPEED = 485.00
//The minimum distance between explosion and splitter impact point
private constant real SPLITTER_MIN_DISTANCE = 125.00
//The maximum distance between explosion and splitter impact point
private constant real SPLITTER_MAX_DISTANCE = 325.00
//The minimum heigh of the splitters
private constant real SPLITTER_MIN_HEIGH = 200.00
//The maximum heigh of the splitters
private constant real SPLITTER_MAX_HEIGH = 445.00
//Should the Z-Angle also be changed while flying? (Looks cooler)
private constant boolean SPLITTER_Z_ANGLE = true
//How much splitters can be summoned maximum? (Don't make this value smaller then any value of SPLITTERS[X])
private constant integer MAX_SPLITTERS = 25
/*END OF SPLITTER SETUP*/
/*EXPLOSION SETUP*/
//The model of the explosion
private constant string EXPLOSION_MODEL = "Effects\\ShadowExplosion.mdl"
//The duration of the explosion (after that, no damage will be dealed and the splitters will be summoned)
private constant real EXPLOSION_DURATION = 0.5
/*END OF EXPLOSION SETUP*/
//Don't change anything here!
//They are for private using!
private real array EXPLOSION_DMG[LEVELS]
private real array EXPLOSION_AOE[LEVELS]
private real array EXPLOSION_SIZE[LEVELS]
private integer array SPLITTERS[LEVELS]
private real array SPLITTER_AOE[LEVELS]
private real array SPLITTER_DMG[LEVELS]
endglobals
//Setup for the damage dealt over the duration in the circle
private function SETUP_EXPLOSION_DMG takes nothing returns nothing
set EXPLOSION_DMG[1] = 150.00
set EXPLOSION_DMG[2] = 225.00
set EXPLOSION_DMG[3] = 300.00
endfunction
//Setup for the AoE, where units get damaged on the explosion
private function SETUP_EXPLOSION_AOE takes nothing returns nothing
set EXPLOSION_AOE[1] = 275.00
set EXPLOSION_AOE[2] = 325.00
set EXPLOSION_AOE[3] = 375.00
endfunction
//Setup for the model-scale of the explosion dummy
private function SETUP_EXPLOSION_SIZE takes nothing returns nothing
set EXPLOSION_SIZE[1] = 2.00
set EXPLOSION_SIZE[2] = 2.25
set EXPLOSION_SIZE[3] = 2.50
endfunction
//Setup for the summoned splitter each level
private function SETUP_SPLITTERS takes nothing returns nothing
set SPLITTERS[1] = 15
set SPLITTERS[2] = 20
set SPLITTERS[3] = 25
endfunction
//Setup for the damage AoE of each splitter
private function SETUP_SPLITTER_AOE takes nothing returns nothing
set SPLITTER_AOE[1] = 45.00
set SPLITTER_AOE[2] = 60.00
set SPLITTER_AOE[3] = 75.00
endfunction
//Setup for the dealt damage by each splitter
private function SETUP_SPLITTER_DMG takes nothing returns nothing
set SPLITTER_DMG[1] = 30.00
set SPLITTER_DMG[2] = 45.00
set SPLITTER_DMG[3] = 60.00
endfunction
//Setup for the Damage-Options
//! textmacro SE_DAMAGE_OPTIONS
set .dmg.dtype = DAMAGE_TYPE_FIRE
set .dmg.atype = ATTACK_TYPE_HERO
set .dmg.wtype = WEAPON_TYPE_WHOKNOWS
//! endtextmacro
/**************************/
/*END OF USER CONFIGURATION!*/
/**************************/
//Don't change anything below here!!
private function ParabolaZ takes real ZStart, real ZEnd, real High, real MaxDist, real Distance returns real
local real A = ( 2 * ( ZStart + ZEnd ) - 4 * High ) / ( MaxDist * MaxDist )
local real B = ( ZEnd - ZStart - A * MaxDist * MaxDist ) / MaxDist
return A * Distance * Distance + B * Distance + ZStart
endfunction
private function ParabolaZalpha takes real ZStart, real ZEnd, real High, real MaxDist, real Distance returns real
local real A = ( 2 * ( ZStart + ZEnd )- 4 * High ) / ( MaxDist * MaxDist )
local real B = ( ZEnd - ZStart - A * MaxDist * MaxDist ) / MaxDist
return Atan( 2 * A * Distance + B)
endfunction
private keyword missile
private keyword splitter
private struct spell
unit caster = null //The caster
delegate xefx explosionFX = 0 //The explosion xefx
real px = 0.00 //The X of the cast point
real py = 0.00 //The Y of the cast point
integer lvl = 0 //The level of the spell
integer count = 0 //Counts the summoned missiles
real exp_counter = 0.00 //Counts the seconds of the explosion
timer t = null //The used timer
real counter = 0.00 //This real is used to check the time for creating the missiles
integer mode = 0 //The mode for the periodic method (0 = missiles, 1 = explosion, 2 = splitter)
real middlez = 0.00 //The terrain heigh of the middle point
boolean explosion = false //Used to check if it's time for the explosion
//Used to save the actual explosion animation speed
missile array missiles[MISSILE_COUNT] //The missiles at the start
splitter array splitters[MAX_SPLITTERS]
//The splitters created after the explosion
static real interval = CREATE_INTERVAL / MISSILE_COUNT
static group tempgroup = CreateGroup() //Group to detect units each interval
static xedamage dmg //Used to deal damage
static boolexpr filter //filter for tempgroup
static spell temp //required by the filter
static location loc = Location(0., 0.) //Required location to check Terrain Z-Values
static method create takes unit caster, real x, real y, integer lvl returns spell
local spell this = spell.allocate()
set .caster = caster
set .lvl = lvl
set .px = x
set .py = y
set .t = NewTimer()
call MoveLocation(.loc, .px, .py)
set .middlez = GetLocationZ(.loc)
set .explosionFX = xefx.create(.px, .py, 0.00)
set .scale = EXPLOSION_SIZE[.lvl]
debug call BJDebugMsg(GetUnitName(.caster) + " casted Shadow Explosion, creating datas...")
call SetTimerData(.t, this)
call TimerStart(.t, .interval, true, function spell.createMissiles)
return this
endmethod
static method createMissiles takes nothing returns nothing
local timer t = GetExpiredTimer()
local spell this = GetTimerData(t)
if .count >= MISSILE_COUNT then
call PauseTimer(.t)
debug call BJDebugMsg("All missiles are created now. Starting the running the timer with the method periodic.")
call TimerStart(.t, XE_ANIMATION_PERIOD, true, function spell.periodic)
return
else
set .missiles[.count] = missile.create(this)
set .count = .count + 1
endif
endmethod
static method actions takes nothing returns boolean
local unit caster = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
if GetSpellAbilityId() == SPELL_ID then
call spell.create(caster, x, y, lvl)
endif
set caster = null
return false
endmethod
//This method is called the wohle spell over every interval and moves missiles, creates the explosions and so on
static method periodic takes nothing returns nothing
local timer t = GetExpiredTimer()
local spell this = GetTimerData(t)
local integer i = 0
local unit u = null
if .mode <= 0 then
//move missiles and check for explosion
loop
exitwhen i >= MISSILE_COUNT or .explosion
call .missiles[i].move()
set i = i + 1
endloop
set i = .count - 1
// if explosion is true, destroy missiles and create explosion
if .explosion then
loop
exitwhen i < 0
call .missiles[i].destroy()
set .count = .count - 1
set i = i - 1
endloop
//create the explosion dummy, destroy trees if it wanted and set the mode to 1 (explosion mode)
set .fxpath = EXPLOSION_MODEL
call .explosionFX.destroy()
if DESTROY_TREES then
call .dmg.damageDestructablesAOE(.caster, .px, .py, EXPLOSION_AOE[.lvl], 1000000.00) //Found no better way with xedamage^^
endif
debug call BJDebugMsg("Missiles reached the middle, starting explosion now.")
set .mode = 1
return
endif
elseif .mode == 1 then
//checks the counter for the explosion
if .exp_counter < EXPLOSION_DURATION then
set .exp_counter = .exp_counter + XE_ANIMATION_PERIOD
set .temp = this
call GroupEnumUnitsInRange(.tempgroup, .px, .py, EXPLOSION_AOE[.lvl], .filter)
//damage units near the explosion
loop
set u = FirstOfGroup(.tempgroup)
exitwhen u == null
call .dmg.damageTarget(.caster, u, EXPLOSION_DMG[.lvl] / EXPLOSION_DURATION * XE_ANIMATION_PERIOD)
call GroupRemoveUnit(.tempgroup, u)
set u = null
endloop
call GroupClear(.tempgroup)
//if explosion is done, create the spiltters
return
else
debug call BJDebugMsg("Explosion is over, creating splitters now.")
set .mode = 2
loop
exitwhen .count >= SPLITTERS[.lvl]
set .splitters[.count] = splitter.create(this)
set .count = .count + 1
endloop
return
endif
elseif .mode >= 2 then
if .count <= 0 then
call .destroy()
debug call BJDebugMsg("No splitters left, destroying datas.")
return
else
set i = .count - 1
loop
exitwhen i < 0
if not .splitters[i].Control() then
set .count = .count - 1
if .count < 0 then
set .count = 0
else
set .splitters[i] = .splitters[.count]
endif
endif
set i = i - 1
endloop
endif
endif
endmethod
//Destroys the Mainstruct and the Timer
method onDestroy takes nothing returns nothing
call ReleaseTimer(.t)
endmethod
//This is the filter used to check for enemy units and so on...
private static method FilterFunc takes nothing returns boolean
return GetFilterUnit() != spell.temp.caster and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_RESISTANT) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(spell.temp.caster))
endmethod
//Initialization of the Spell, same like the normal initializers, just in a struct
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
set .dmg = xedamage.create()
set .filter = Condition(function spell.FilterFunc)
//adding Events and Actions
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST, null)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(t, Condition(function spell.actions)) //cause conditions are faster than actions ;)
call SETUP_EXPLOSION_DMG()
call SETUP_EXPLOSION_AOE()
call SETUP_EXPLOSION_SIZE()
call SETUP_SPLITTERS()
call SETUP_SPLITTER_AOE()
call SETUP_SPLITTER_DMG()
//! runtextmacro SE_DAMAGE_OPTIONS()
call Preload(MISSILE_MODEL)
call Preload(SPLITTER_MODEL)
set t = null
endmethod
endstruct
//This struct is for the missiles, created in the circle at the start
private struct missile
delegate xefx model = 0 //the missile itself
delegate spell root = 0 //the struct the missiles are from
real angle = 0.00 //the angle of the missile
real speed = MISSILE_START_SPEED //The actual speed of the missiles
real dist = 0.00 //The distance the missile has moved(required by the 3D-Mode)
static real ac = (MISSILE_END_SPEED - MISSILE_START_SPEED) / (MISSILE_DISTANCE / ((MISSILE_END_SPEED + MISSILE_START_SPEED) /2)) * XE_ANIMATION_PERIOD
//The acceleration of the missiles
//Creates a new missile and attaches it to the main struct..
static method create takes spell root returns missile
local missile this = missile.allocate()
local real mx = 0.00
local real my = 0.00
local real angle = 0.00
set .root = root
set angle = I2R(.count) / MISSILE_COUNT * 2 * bj_PI
set mx = .px + MISSILE_DISTANCE * Cos(angle)
set my = .py + MISSILE_DISTANCE * Sin(angle)
set .angle = Atan2(.py - my, .px - mx)
set .model = xefx.create(mx, my, .angle)
debug call BJDebugMsg("Creating new missile with index " + I2S(.count) + " and an angle of " + I2S(R2I(angle * bj_RADTODEG + 0.5)) + ".")
set .fxpath = MISSILE_MODEL
set .z = MISSILE_HEIGH
set .scale = MISSILE_SIZE
return this
endmethod
//moves the missiles each interval
method move takes nothing returns nothing
if .speed < MISSILE_END_SPEED and .dist < MISSILE_DISTANCE then
set .speed = .speed + .ac
set .dist = .dist + .speed * XE_ANIMATION_PERIOD
set .x = .x + .speed * XE_ANIMATION_PERIOD * Cos(.angle)
set .y = .y + .speed * XE_ANIMATION_PERIOD * Sin(.angle)
if MISSILE_3D_MODE then
set .z = ParabolaZ(MISSILE_HEIGH, MISSILE_HEIGH, MISSILE_3D_HEIGH, MISSILE_DISTANCE, .dist)
if MISSILE_Z_ANGLE then
set .zangle = ParabolaZalpha(MISSILE_HEIGH, .middlez + MISSILE_HEIGH, MISSILE_3D_HEIGH, MISSILE_DISTANCE, .dist)
endif
endif
return
elseif .speed >= MISSILE_END_SPEED or .dist >= MISSILE_DISTANCE then
set .explosion = true
endif
endmethod
//Who would believe it? This method destroys the missile
method onDestroy takes nothing returns nothing
call .model.destroy()
endmethod
endstruct
//This is the struct fot the splitter created after the explosion
private struct splitter
delegate xefx model = 0 //The xefx model of the splitter
delegate spell root = 0 //The struct, the splitter are from
real dist = 0.00 //The randomed distance of the splitter
real heigh = 0.00 //The randomed heigh of the splitter
real angle = 0.00 //The angle, the splitter moves
real ac = 0.00 //The acceleration of the splitter
real speed = SPLITTER_START_SPEED //The actual speed of the missile
real moved = 0.00 //The moved distance of the missile
//Just the same as the missiles...
static method create takes spell root returns splitter
local splitter this = splitter.allocate()
set .root = root
set .dist = GetRandomReal(SPLITTER_MIN_DISTANCE, SPLITTER_MAX_DISTANCE)
set .heigh = GetRandomReal(SPLITTER_MIN_HEIGH + SPLITTER_START_HEIGH, SPLITTER_MAX_HEIGH)
set .angle = GetRandomReal(0.00, 360.00)
set .ac = (SPLITTER_END_SPEED - SPLITTER_START_SPEED) / (.dist / ((SPLITTER_END_SPEED + SPLITTER_START_SPEED) / 2)) * XE_ANIMATION_PERIOD
debug call BJDebugMsg("Creating new splitter with index " + I2S(.count) + ".")
set .model = xefx.create(.px, .py, .angle)
set .fxpath = SPLITTER_MODEL
set .scale = SPLITTER_SIZE
set .z = SPLITTER_START_HEIGH
return this
endmethod
//This method is called, when a splitter "lands". It damages enemies arround it.
method damage takes nothing returns nothing
local unit u = null
set .temp = .root
call GroupEnumUnitsInRange(.tempgroup, .x, .y, SPLITTER_AOE[.lvl], .filter)
loop
set u = FirstOfGroup(.tempgroup)
exitwhen u == null
call .dmg.damageTarget(.caster, u, SPLITTER_DMG[.lvl])
call GroupRemoveUnit(.tempgroup, u)
set u = null
endloop
if DESTROY_TREES then
call .dmg.damageDestructablesAOE(.caster, .x, .y, SPLITTER_AOE[.lvl], 100000.)
endif
call GroupClear(.tempgroup)
endmethod
//w00t?? You destroy the poor splitter???
method onDestroy takes nothing returns nothing
call .model.destroy()
endmethod
//moves the splitter and refreshes its angle, blablabla...
method Control takes nothing returns boolean
if .moved < .dist then
set .speed = .speed + .ac
set .moved = .moved + .ac
set .x = .x + .speed * XE_ANIMATION_PERIOD * Cos(.angle)
set .y = .y + .speed * XE_ANIMATION_PERIOD * Sin(.angle)
set .z = ParabolaZ(SPLITTER_START_HEIGH, SPLITTER_START_HEIGH, .heigh, .dist, .moved)
if SPLITTER_Z_ANGLE then
set .zangle = ParabolaZalpha(SPLITTER_START_HEIGH, .middlez + SPLITTER_START_HEIGH, .heigh, .dist, .moved)
endif
return true
else
debug call BJDebugMsg("A splitter reached its target point and will deal damage and destroyed.")
call .damage()
call this.destroy()
return false
endif
return false
endmethod
endstruct
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Testmap initializer init
function init takes nothing returns nothing
call DisplayTimedTextToPlayer(Player(0), 0, 0, 20, "Please give credits to me if you use this spell and credits to the creators of the required systems.")
endfunction
endscope