Name | Type | is_array | initial_value |
//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
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=251
//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=87
//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
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=6
//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=225
//TESH.alwaysfold=0
library xecollider initializer init requires xefx, xebasic
//****************************************************************
//*
//* xecollider 0.5
//* --------------
//* A xecollider object is a special effect that has a collision
//* size that can trigger a hit event and also many options to
//* configure its automatic movement.
//*
//* Please use .terminate() instead of .destroy() this ensures
//* that it will be safe to destroy it (else you would have to
//* worry about destroying it during the animation loop/etc.)
//*
//* To use this struct is a little different than the other
//* current parts of xe. Instead of just creating the xecollider
//* (which works, but it would only be a xefx that can have speed)
//* you probably need to make it do something special on the
//* unit hit event... For this reason, you need to make a new
//* struct extending xecollider that declares an onUnitHit method
//* you may also declare a loopControl method, very useful, can
//* help you reduce 'attaching'.
//*
//****************************************************************
//================================================================
globals
private constant real DEFAULT_COLLISION_SIZE = 50.0 // These are defaults, on one hand you can change them
private constant real DEFAULT_MAX_SPEED = 1500.0 // on the other hand, if a spell relies on the defaults
private constant real DEFAULT_EXPIRATION_TIME = 5.0 // changing them would make the behavior vary...
private constant real PI2 = 6.28318 //It might not be wise to change this
endglobals
//===========================================================================
// So, this exists merely so you can declare your own event handler methods
// if interfaces make your brain blow out, please skip the next four lines.
//
private interface eventHandler
method onUnitHit takes unit hitTarget returns nothing defaults nothing
method loopControl takes nothing returns nothing defaults nothing
endinterface
//===========================================================================
struct xecollider extends eventHandler
// use terminate() instead of .destroy() to "kill" the collider.
// don't worry, terminate will call destroy automatically.
//============================================================================
// delegates:
// We are delegating a xefx object so that people call all the xefx methods
// and member from a xecollider object. This means that from a user
// perspective a xecollider is also an instance of xefx.
//
// Notable ones are: .x , .y , .fxpath and .z ,
// check out xefx's documentation for more info.
//
private delegate xefx fx
//##==========================================================================
// public variables:
//
public real expirationTime = DEFAULT_EXPIRATION_TIME
// Movement speed for the missile.
public real speed = 0.0
// Speed added per second (notice you can use a negative value here)
public real acceleration = 0.0
// If there is acceleration, it is wise to have a cap...
public real maxSpeed = DEFAULT_MAX_SPEED
public real minSpeed = 0.0
public real angleSpeed = 0.0 //The increment in radians per second to the
// direction angle, allows curved movement.
private static integer lastSeen = 0
private group seen
//##==========================================================================
// public methods:
//
//----
// Well, it is a good idea to actually create the missiles.
// notice that if your custom missile struct needs to declare its own create
// method, you can call this as allocate(x,y,dir).
//
// Sorry, no Loc version.
//
public static method create takes real x, real y, real dir returns xecollider
local xecollider xc= xecollider.allocate()
set xc.fx = xefx.create(x,y,dir)
set xc.dir=dir
set xecollider.V[xecollider.N]=xc
set xecollider.N=xecollider.N+1
if(xecollider.N==1) then
call TimerStart(xecollider.T, XE_ANIMATION_PERIOD, true, xecollider.timerLoopFunction )
endif
if(.lastSeen < integer(xc)) then //with this I do group recycling
set .lastSeen = integer(xc)
set xc.seen = CreateGroup()
endif
return xc
endmethod
//----
// The direction is just the angle in radians to which the missile's model faces
// and the automatic movement uses.
//
method operator direction takes nothing returns real
return this.dir
endmethod
method operator direction= takes real v returns nothing
set this.dir=v
set this.fx.xyangle=v
endmethod
//----
// The collisionSize
//
method operator collisionSize takes nothing returns real
return this.csize
endmethod
method operator collisionSize= takes real value returns nothing
set this.csize = value
//good long attribute name, but we use csize in the loop
//don't worry this gets inlined, it would also be helpful if
//I ever decide to add a control for assignment.
endmethod
//---
// targetUnit is a unit to follow (or try to follow), notice that homing
// options require an angleSpeed different to 0.0
//
public method operator targetUnit takes nothing returns unit
return this.homingTargetUnit
endmethod
public method operator targetUnit= takes unit u returns nothing
if(u==null) then
set this.homingMode=0
else
set this.homingMode= .HOMING_UNIT
endif
set this.homingTargetUnit=u
endmethod
//----
// targetPoint is a point to reach (or try to reach), notice that homing
// options require an angleSpeed different to 0.0
//
public method setTargetPoint takes real x, real y returns nothing
set this.homingMode= .HOMING_POINT
set this.homingTargetX=x
set this.homingTargetY=y
endmethod
public method setTargetPointLoc takes location loc returns nothing
set this.homingMode= .HOMING_POINT
set this.homingTargetX=GetLocationX(loc)
set this.homingTargetY=GetLocationY(loc)
endmethod
//----
// Call this in case you used targetUnit or TargetPoint so the missile
// forgets the order to home that target.
//
public method forgetTarget takes nothing returns nothing
set this.homingMode = 0
endmethod
method terminate takes nothing returns nothing
set this.dead=true
set this.fxpath=""
endmethod
//--------
private static timer T
private static integer N=0
private static xecollider array V
private static code timerLoopFunction //I use a code var so create can be above the timerloop function, more readable
private boolean dead=false
private real csize = DEFAULT_COLLISION_SIZE
private real dir
private static constant integer HOMING_UNIT =1
private static constant integer HOMING_POINT=2
private integer homingMode =0
private unit homingTargetUnit = null
private real homingTargetX
private real homingTargetY
private method onDestroy takes nothing returns nothing
call GroupClear(this.seen)
call this.fx.destroy()
endmethod
private static xecollider cinstance
private static real newx
private static real newy
private static group enumGroup
private static boolexpr enumBoolexpr
private static unit array picked
private static integer pickedN
static method timerLoop takes nothing returns nothing
local integer i=0
local integer j=0
local integer c=0
local xecollider this
local real d
local real ns
local real wa
local real df1
local real df2
local unit u
loop
exitwhen (i== xecollider.N )
set this=.V[i] //adopt-a-instance
set this.expirationTime = this.expirationTime - XE_ANIMATION_PERIOD
if(.dead or (this.expirationTime <=0.0) ) then
call this.destroy()
else
set ns=this.angleSpeed*XE_ANIMATION_PERIOD
if (ns!=0.0) then
if(this.homingMode== .HOMING_UNIT ) then
set u=this.homingTargetUnit
if(GetUnitTypeId(u)==0) then
set this.homingMode=.HOMING_POINT
else
set this.homingTargetX=GetUnitX(u)
set this.homingTargetY=GetUnitY(u)
endif
set u=null
endif
if (this.homingMode != 0) then
if(ns<=0) then
set ns=-ns
endif
set wa=Atan2(this.homingTargetY - this.y , this.homingTargetX-this.x)
//if(wa<0.0) then
// set wa=wa+PI2
//endif
set df1=wa-this.dir
set df2=(PI2+wa)-this.dir
if (df1<=0) then
if(df2<=0) then
if(df2>=df1) then
set df1=df2
endif
else
if(-df1>=df2) then
set df1=df2
endif
endif
else
if(df2<=0) then
if(-df2<=df1) then
set df1=df2
endif
else
if(df2<=df1) then
set df1=df2
endif
endif
endif
if(df1<=0) then
if(-df1>=ns) then
set ns=-ns
else
set ns=df1
endif
else
if(df1<=ns) then
set ns=df1
endif
endif
endif
set d=this.dir
set d = d + ns
if(d>=PI2) then
set d=d - PI2
elseif(d<0) then
set d=d + PI2
endif
set this.dir = d
set this.xyangle = d
endif
// function calls are expensive, damned we are, long code inside of loop
// correct software dev. tells us this should go to another function,
// but this is Jass, not real life.
set .cinstance = this
set ns = this.speed + this.acceleration*XE_ANIMATION_PERIOD
if ( ns<this.minSpeed) then
set ns=this.minSpeed
elseif (ns>this.maxSpeed) then
set ns=this.maxSpeed
endif
set d=((this.speed+ns)/2) * XE_ANIMATION_PERIOD
set this.speed=ns
set .newx= .x+d*Cos(this.dir)
set .newy= .y+d*Sin(this.dir)
set .x=.newx
set .y=.newy
set xecollider.pickedN = 0
call GroupEnumUnitsInRange( .enumGroup, .newx, .newy, .csize + XE_MAX_COLLISION_SIZE, .enumBoolexpr)
call GroupClear(this.seen)
set j=0
loop
exitwhen (j==xecollider.pickedN)
call GroupAddUnit( this.seen, xecollider.picked[j])
set j=j+1
endloop
set .V[c]=this
set c=c+1
if( this.loopControl.exists and not this.dead ) then
call this.loopControl()
endif
endif
set i=i+1
endloop
//call BJDebugMsg("}")
set xecollider.N=c
if(c==0) then
call PauseTimer(xecollider.T)
endif
endmethod
private static method inRangeEnum takes nothing returns boolean
local xecollider this= .cinstance //adopt-a-instance
local unit u=GetFilterUnit()
if not(this.dead) and (GetUnitTypeId(u)!=XE_DUMMY_UNITID) and IsUnitInRangeXY(u, .newx, .newy, .csize) then
// ah, the advantages of a standardized unit id...
set xecollider.picked[xecollider.pickedN] = u
set xecollider.pickedN=xecollider.pickedN + 1
if not IsUnitInGroup (u, this.seen ) then
call this.onUnitHit(u)
endif
endif
set u=null
return false
endmethod
//============================================================================
// you aren't supposed to call doInit yourself, try not to do it.
//
static method doInit takes nothing returns nothing
set xecollider.enumGroup = CreateGroup()
set xecollider.enumBoolexpr = Condition( function xecollider.inRangeEnum)
set xecollider.timerLoopFunction = (function xecollider.timerLoop)
set xecollider.T=CreateTimer()
endmethod
endstruct
private function init takes nothing returns nothing
call xecollider.doInit()
endfunction
endlibrary
//TESH.scrollpos=30
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//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=0
//TESH.alwaysfold=0
library IntuitiveDamageSystem initializer Init requires Table
//******************************************************************************
//* BY: Rising_Dusk
//* (Intuitive) Damage Detection System 1.12
//*
//* This library is the core for what has come to be known as the Intuitive
//* Damage Detection System, or IDDS for short. Simply by copying this library
//* into your map somewhere, you will have access to all of its features and
//* options. Below this documentation are some global variables that can be
//* edited to make the system more useful for your map, whatever it might be.
//* Please note that you should only change those globals listed under
//* configuration constants and damage type constants.
//*
//* An important note for the system is that all non-attack damage in your map
//* MUST BE TRIGGERED using the special function call included in this system,
//* UnitDamageTargetEx. This is how the system works to detect attacks, because
//* if the only non-triggered damage in your map originates from attacks, you
//* clearly know which damage packets are attacks. This allows users to use
//* orb abilities in their maps for whatever they want.
//*
//* function UnitDamageTargetEx takes unit source, unit target, real damage, ...
//* ...attacktype attackType, integer damageType, boolean ConsiderArmor returns boolean
//*
//* This is the function with which you will deal all triggered damage in your
//* map. The damageType argument is one of the predefined integer constants that
//* you can edit or add below. Default values for this with the system are
//* DAMAGE_TYPE_ATTACK, DAMAGE_TYPE_SPELL, and DAMAGE_TYPE_EXTRA. You can
//* trigger the system to treat triggered damage like it is an attack if you
//* want to by using DAMAGE_TYPE_ATTACK. It is very easy to make new damage
//* types in the system, just follow the instructions in the configuration
//* constants area. In addition, be sure that the constants you define do not
//* conflict with the predefined Blizzard constants. (ie. DAMAGE_TYPE_FIRE) If
//* these conflicts do exist, you will encounter multiply defined syntax errors.
//* The AttackType argument is the same as in the regular UnitDamageTarget
//* native. Also, the system allows you to consider armor when dealing damage or
//* not. Set the ConsiderArmor boolean argument to false if you want to ignore
//* armor for that damage, or true if you want to factor it in.
//*
//* function TriggerUnregisterDamageEvent takes trigger trg returns boolean
//* function TriggerRegisterDamageEvent takes trigger trg, integer priority returns boolean
//*
//* The TriggerRegisterDamageEvent function is used when initializing a damage
//* detection response trigger. By using this, it allows you to use a syntax
//* structure nigh-identical to the standard JASS2. It returns a boolean for
//* your convenience that is false if you pass it a null trigger. The system
//* also allows you to pass a positive, zero-inclusive integer to it as that
//* trigger's priority. The higher the number you pass, the later on in the
//* trigger executions it will fire. This is useful if you want shield
//* abilities, as you will want their priorities low so that they can block the
//* damage before it gets to other things. You are also allowed to unregister
//* a trigger from the system at any time if you want; this will likely never
//* have to be done for most maps.
//*
//* function SetDamage takes real dmg returns nothing
//*
//* With this function, you can modify the damage the system interprets for its
//* triggers. This function DOES NOT ACTUALLY CHANGE THE DAMAGE BEING DEALT, it
//* is merely a tool for users to use to change the internal variables. The user
//* will need to modify the damage himself by some other means.
//*
//* function SetTriggerPriority takes trigger trg, integer priority returns boolean
//* function GetTriggerPriority takes trigger trg returns integer
//*
//* These functions let you set or get a given trigger's priority at will. These
//* functions both require that the trigger being passed to it is registered to
//* the IDDS system. If you pass an unregistered trigger to GetTriggerPriority,
//* it will return -1. If you pass a similar trigger to SetTriggerPriority, it
//* will return false.
//*
//* function IgnoreHigherPriority takes nothing returns boolean
//*
//* This function is one of the most important reasons for priorities to exist.
//* With it, you can tell the system to ignore higher priority triggers. This
//* is useful, for instance, if you have a triggered evasion ability and don't
//* want anything else to be done with that damage because it was dodged. Other,
//* similar damage-preventing routines will also find this function useful.
//*
//* function RegisterDamageType takes nothing returns integer
//*
//* This function is useful for declaring your own DAMAGE_TYPE_ETC constants
//* external to the system. By declaring your global variable and then calling
//* this on it as follows, you can register new damage types on the fly. This
//* is very useful if you want other systems or spells to introduce new damage
//* types that are either ignored or do special things for that application.
//*
//* function GetTriggerDamageType takes nothing returns integer
//* function GetTriggerDamageSource takes nothing returns unit
//* function GetTriggerDamageTarget takes nothing returns unit
//* function GetTriggerDamageBase takes nothing returns real
//* function GetTriggerDamage takes nothing returns real
//*
//* Like normal WC3 damage detection, the system has event responses for the
//* damage source, the target of the damage, the amount of damage dealt, and
//* other things. It also permits the detection of damage type, which is
//* something standard WC3 does not have. This lets you create on-attack spells
//* very easily whereas without the system it would be very difficult and
//* computationally costly. GetTriggerDamageBase returns the amount of damage
//* the unit was dealt at the beginning of a given trigger series, whereas
//* GetTriggerDamage returns whatever damage the unit has left to receive, if
//* it has been modified in any way with the SetDamage function mentioned
//* earlier.
//*
//* Once you understand all of the aforementioned aspects of the system, you're
//* ready to put it to use. I know it can be tricky to require all spells be
//* triggered, but this is the way of many great maps anyways, so such a
//* requirement is not so unreasonable. If you have any questions regarding the
//* system, please go to www.wc3c.net and send a private message to the account
//* Rising_Dusk and I will respond as soon as I can. This system may only be
//* released at www.wc3c.net and its existence on any other website is against
//* the author's will.
//*
//* Enjoy!
//*
globals
//* Configuration constants
private integer DamageTypeCount = 3
//* These are the damagetype constant globals for ease of use
constant integer DAMAGE_TYPE_ATTACK = 0
constant integer DAMAGE_TYPE_SPELL = 1
constant integer DAMAGE_TYPE_EXTRA = 2
//* To add new constants, simply follow the naming convention and increment
//* the number accordingly. You shouldn't change or remove the first of the
//* damage types, since it is a special case, but you can change the others
//* however you'd like.
//* These are static constants used by the system and shouldn't be changed
private trigger RunTrigger = CreateTrigger()
private trigger AddTrigger = CreateTrigger()
private integer Count = 0
private Table TrigTable = 0
private Table RegiTable = 0
private boolean IgnPrior = false
private real array NewDamage
private trigger array Trg
private integer array Priority
//* Temporary variables used by the system
private unit DamageSource = null
private unit DamageTarget = null
private integer DamageType = 0
private integer DamageId = 0
private real DamageBase = 0.
private real Damage = 0.
endglobals
//******************************************************************************
//******************************************************************************
//* Returns the integer index of a given handle object (Updated for 1.24)
private function H2I takes handle h returns integer
return GetHandleId(h)
endfunction
//* Use an insertion sort algorithm to sort the trigger stack based on priority
private function TriggerSort takes nothing returns boolean
local integer i = 1
local integer j = 0
local integer p = 0
local trigger t = null
loop
exitwhen i >= Count
set t = Trg[i]
set p = Priority[i]
set j = i-1
loop
exitwhen j < 0 or Priority[j] <= p
set Priority[j+1] = Priority[j]
set Trg[j+1] = Trg[j]
set TrigTable[H2I(Trg[j])] = j+1
set j = j - 1
endloop
set Priority[j+1] = p
set Trg[j+1] = t
set TrigTable[H2I(t)] = j+1
set i = i + 1
endloop
set t = null
return true
endfunction
//******************************************************************************
//******************************************************************************
//* The function to call when you want to end a damage's trigger series
function IgnoreHigherPriority takes nothing returns boolean
if DamageSource != null then
//Make sure it was called in the right place
set IgnPrior = true
endif
return IgnPrior
endfunction
//* Changes the base damage for a trigger series on the fly
function SetDamage takes real dmg returns boolean
if DamageSource != null and dmg >= 0 then
//Make sure it was called in the right place
set NewDamage[DamageId] = dmg
set Damage = dmg
return true
endif
return false
endfunction
//* Returns the given trigger's priority if it's loaded to the system
function GetTriggerPriority takes trigger trg returns integer
if RegiTable[H2I(trg)] == 0 then
return -1
endif
return Priority[TrigTable[H2I(trg)]]
endfunction
//* Sets the given trigger's priority if it's loaded to the system
function SetTriggerPriority takes trigger trg, integer priority returns boolean
if RegiTable[H2I(trg)] == 0 or priority < 0 then
return false
endif
set Priority[TrigTable[H2I(trg)]] = priority
return TriggerSort()
endfunction
//******************************************************************************
//******************************************************************************
//* The new damage function used by the system
function UnitDamageTargetEx takes unit source, unit target, real damage, attacktype attackType, integer damageType, boolean ConsiderArmor returns boolean
local boolean b = false
set DamageType = damageType
set DamageSource = source
if ConsiderArmor then
set b = UnitDamageTarget(source, target, damage, false, false, attackType, DAMAGE_TYPE_NORMAL, null)
else
set b = UnitDamageTarget(source, target, damage, false, false, attackType, DAMAGE_TYPE_UNIVERSAL, null)
endif
if not b then
set DamageType = 0
set DamageSource = null
endif
return b
endfunction
//* The method by which one registers a trigger with the system
function TriggerRegisterDamageEvent takes trigger trg, integer priority returns boolean
if trg == null or priority < 0 then
return false
endif
if RegiTable[H2I(trg)] == 0 then
set RegiTable[H2I(trg)] = 1
endif
set Trg[Count] = trg
set Priority[Count] = priority
set TrigTable[H2I(trg)] = Count
set Count = Count + 1
return TriggerSort()
endfunction
//* The method by which one unregisters a trigger from the system
function TriggerUnregisterDamageEvent takes trigger trg returns boolean
local integer i = 0
if trg == null then
return false
endif
set i = TrigTable[H2I(trg)]
if trg != Trg[i] then
return false
endif
set Trg[i] = Trg[Count]
set Priority[i] = Priority[Count]
set TrigTable[H2I(Trg[i])] = i
set RegiTable[H2I(trg)] = 0
set Count = Count - 1
return TriggerSort()
endfunction
//* Initialization shorthand to register a new damage type externally
function RegisterDamageType takes nothing returns integer
local integer i = DamageTypeCount
set DamageTypeCount = DamageTypeCount + 1
return i
endfunction
//******************************************************************************
//******************************************************************************
//* Wrappers for the system that can get inlined anyways
function GetTriggerDamageType takes nothing returns integer
return DamageType
endfunction
function GetTriggerDamageSource takes nothing returns unit
return DamageSource
endfunction
function GetTriggerDamageTarget takes nothing returns unit
return DamageTarget
endfunction
function GetTriggerDamageBase takes nothing returns real
return DamageBase
endfunction
function GetTriggerDamage takes nothing returns real
return Damage
endfunction
//******************************************************************************
//******************************************************************************
private function RunConditions takes nothing returns boolean
//* The conditions for what must be true for damage detection to run
return GetEventDamage() >= 0.0001
endfunction
private function AddConditions takes nothing returns boolean
//* The conditions for registering a unit with the damage system
return true
endfunction
private function PreloadConditions takes unit u returns boolean
//* The conditions for preloading a unit to the damage system
return true
endfunction
//******************************************************************************
//******************************************************************************
globals
private integer array IDStack
private integer IDC = 0
private integer IDN = 0
endglobals
private function Run takes nothing returns nothing
local unit u = GetEventDamageSource()
local unit s = DamageSource
local unit t = GetTriggerUnit()
local integer i = 0
local integer id = 0
local integer d = DamageType
local real r = GetEventDamage()
local real b = r
//Allocate an id for this damage packet
if IDN > 0 then
set id = IDStack[IDN]
set IDN = IDN - 1
else
set id = IDC
set IDC = IDC + 1
endif
if DamageSource == null then
//Damage is of type attack
set d = DAMAGE_TYPE_ATTACK
set s = u
endif
loop
exitwhen i > Count or IgnPrior
//Ensure all variables are correct for nesting
set Damage = r
set DamageBase = b
set DamageTarget = t
set DamageSource = s
set DamageType = d
set DamageId = id
set NewDamage[id] = 0.
if IsTriggerEnabled(Trg[i]) and TriggerEvaluate(Trg[i]) then
call TriggerExecute(Trg[i])
endif
if NewDamage[id] > 0. then
//Update damage if it was changed
set r = NewDamage[id]
endif
set i = i + 1
endloop
set Damage = 0.
set DamageBase = 0.
set DamageTarget = null
set DamageSource = null
set DamageType = 0
set DamageId = 0
set IgnPrior = false
set NewDamage[id] = 0.
//Return id to the stack
set IDN = IDN + 1
set IDStack[IDN] = id
set u = null
set s = null
set t = null
endfunction
private function Load takes nothing returns nothing
call TriggerRegisterUnitEvent(RunTrigger, GetEnteringUnit(), EVENT_UNIT_DAMAGED)
endfunction
//******************************************************************************
//******************************************************************************
private function PreloadUnits takes nothing returns boolean
if PreloadConditions(GetFilterUnit()) then
call TriggerRegisterUnitEvent(RunTrigger, GetFilterUnit(), EVENT_UNIT_DAMAGED)
endif
return false
endfunction
private function Init takes nothing returns nothing
local rect r = GetWorldBounds()
local region re = CreateRegion()
local boolexpr b = Condition(function PreloadUnits)
local group g = CreateGroup()
//* Create the tables for use with the system
set TrigTable = Table.create()
set RegiTable = Table.create()
call TriggerAddAction(RunTrigger, function Run)
call TriggerAddCondition(RunTrigger, Condition(function RunConditions))
call GroupEnumUnitsInRect(g, r, b)
call RegionAddRect(re, r)
call TriggerRegisterEnterRegion(AddTrigger, re, null)
call TriggerAddAction(AddTrigger, function Load)
call TriggerAddCondition(AddTrigger, Condition(function AddConditions))
call RemoveRect(r)
call DestroyGroup(g)
call DestroyBoolExpr(b)
set re = null
set g = null
set b = null
set r = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
The purpose of this readme is to make implementing the spellpack easier, if something is not clear
feel free to send me a PM at the hive (www.hiveworkshop.com).
1 - Implementing XE:
The spellpack requires a system made by vexorian called XE, I included the system's readme under this
one, the instructions for implementing are found under the "xe readme" trigger.
2 - Implementing other required systems:
The spellpack also requires other 3 smaller systems: Table, TimerUtils, and Rising_Dusk's Intuitive
Damage System.
To implement, simply copy the 3 triggers.
3 - Implementing the spellpack:
First, copy all object editor spells and buffs to your map.
Then copy the spell triggers found under the Spells section.
Adjust the rawcodes in each spell trigger so they match the rawcodes of the spells in your map.
Adjust any other vaules you want in the setup section of each spell.
Now, test your map, if the spellpack is working, congratulations! If it's not, check if you followed
each step correctly, if that doesn't work, send me a PM and I'll see what I can do.
- Darkyvm
//TESH.scrollpos=18
//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=9
//TESH.alwaysfold=0
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=21
//TESH.alwaysfold=0
xecast
------
This one solves typical problems that require dummy casters. Things like the
AOE sleep, targetted warstomp, etc. It is object oriented, this just means that
you'll actually not just call functions but deal with xecast objects, change
their attributes and then order them to cast. It deals with the dirty things
like recycling and dealing with timing, etc.
implementation
--------------
Just copy the xecast trigger to your map.
xecast object
-------------
__________________________________________________________________________________________________
static method create takes nothing returns nothing
- ------
This is the create method, it will make a new xecast object for you to use:
set somevariable = xecast.create()
_________________________________________________________________________________________________
static method createBasic takes integer abilityID, integer orderid, player owner returns xecast
- - - - - - - - - - - -
An abbreviated constructor, allows you to quickly set the basic attributes
abilityID: the rawcode of the ability to cast.
Example: 'AHbz'
orderid : the orderid (integer) of the ability to cast.
Example: OrderId("blizzard")
owner : the owning player for this cast object (The one that gets credit for damage)
Example: ( GetOwningPlayer(GetTriggerUnit() ))
_________________________________________________________________________________________________
method destroy takes nothing returns nothing
-
Call destroy() on instances you are not going to use anymore, to prevent struct leaks that would
break your map. A simple use for xecast is to just keep one instance per dummy spell to prevent
having to care about destroying them. Another possibility is to use the A constructors.
Example: call somevariable.destroy()
__________________________________________________________________________________________________
static method createA takes nothing returns nothing
static method createBasicA takes integer abilityID, integer orderid, player owner returns xecast
-
These do the same as create and createBasic , the only difference is that the
object is destroyed automatically after every call to a cast method (See bellow).
_____________________________________________________________________________________________________
method castOnTarget takes widget target returns nothing
-
Tells the xecast object to cast its spell on the target. target may be unit, item or destructable.
_____________________________________________________________________________________________________
method castOnPoint takes real x, real y returns nothing
method castOnLoc takes location loc returns nothing
method castInPoint takes real x, real y returns nothing
method castInLoc takes location loc returns nothing
- -----------------------------------------------
Instead of casting on a target unit/item/destructable these ones cast on a target point. OnPoint is
used for point-targeteable spells, while InPoint is used for spells that have no target. The Loc
versions allow you to use locations. Locations are useless most of the times, but if you want to use
them you can use the Loc versions.
Example: call somevar.castOnPoint( spellx, spelly )
_____________________________________________________________________________________________________
method castOnAOE takes real x, real y, real radius returns nothing
method castOnAOELoc takes location loc,real radius returns nothing
method castOnGroup takes group g returns nothing
- ---------------------------------------------------
Methods to cast the spell on multiple units, AOE takes a circle's center and radius, while Group
takes a unit group, notice that the unit group will get cleaned after calling this function, which
means it will be an empty unit group, no, it does not destroy the group automatically, just empties it
* List of attributes *
________________________________________________________________________________
integer abilityid
----
This one holds the ability to cast's ability Id.
Example: set somevar.abilityid='AHbz'
________________________________________________________________________________
integer level
----
The level of the ability to cast.
Example: set somevar.level = GetUnitAbilityLevel(u, spellid)
________________________________________________________________________________
real recycledelay
----
The recycle delay is the time to wait before recycling the dummy caster, if
it is 0.0 the ability will be considered instant.
A proper recycle delay is important since when a dummy caster is recycled
its owner becomes player passive. Every damage done by the casted spell will
not credit the correct player.
Some other spells need some time in order to cast correctly. Not to mention
the channeling ones that require the caster to last during that situation.
Example: set somevar.recycledelay=10.0
________________________________________________________________________________
player owningplayer
----
The player that owns the spell (Who gets credited for it)
Example: set somevar.owningplayer = Player(2)
________________________________________________________________________________
integer orderid (write-only)
----
The ability to cast's order id. eg 858029 or OrderId("blizzard")
________________________________________________________________________________
string orderstring (write-only)
----
The ability to cast's order string (eg "blizzard")
________________________________________________________________________________
boolean customsource
----
false by default, determines if you want the dummy caster to be placed at
a specific point when casting, this allows you to exploit blizz spell's eye
candy. Once customsource is true, you need to set sourcex,sourcey and
sourcez.
________________________________________________________________________________
real sourcex, sourcey, sourcez
----
The coordinates where you want to place the dummy caster, z is height and
is 0.0 by default. These are ignored if customsource is set to false.
________________________________________________________________________________
method setSourcePoint takes real x, real y, real z returns nothing
method setSourceLoc takes location loc, real z returns nothing
----
In case setting all that stuff manually takes too much lines for your taste
you can use these methods to set those values, they will automatically set
customsource to true.
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=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=45
//TESH.alwaysfold=0
xecollider
----------
A specialization xefx, aids at creating 2D missiles with collision and speed
and that sort of stuff, notice you'd usually have to make a new struct and
extend xecollider from it so you can declare your own methods for handling
the hit event.
implementation
--------------
First of all, you need xefx, after you implement xefx, you may just copy
the xecollider trigger to your map.
I'd also recommend you to implement the BoundSentinel library to prevent
crashes related to the xecolliders moving too down in the map, you can find the
BoundSentinel in the "Extras" trigger category.
Notice this needs at least jasshelper 0.9.E.0 to compile.
xecollider object
-----------------
__________________________________________________________________________________________________
static method create takes real x, real y, real dir returns xecollider
- - - ----------
This is the create method, it will make a new xecollider object for you to
use, If you are extending xecollider and wish to have a custom create method
on your struct, remember that you will need to call allocate(x,y,dir)
set somevariable = myxecollider.create( GetUnitX(u), GetUnitY(u), angl )
_________________________________________________________________________________________________
method terminate takes nothing returns nothing
- - --------------
Call terminate() when you wish to 'kill' the missile, it will call .destroy()
automatically, I recommend you not to call .destroy() manually. Use onDestroy
to detect when it was called. You can use .x and .y on onDestroy...
Example: call somevariable.terminate()
_____________________________________________________________________________________________________
delegate xefx
-
A xecollider object is practically also a xefx object, you may call all of
the members in xefx for your disposal, including x,y,z (to change the position)
and height of the missile) and fxpath (the model used by the missile) please,
read the xefx documentation as it includes a lot of members used by xecollider.
________________________________________________________________________________
real expirationTime
- ------------------------------------------------------------
The missile will die after expirationTime seconds, notice this is just a
variable so you can modify and read it as you will, when the expirationTime
ends, the missile is 'killed' so onDestroy will be called. If you do not assign
this manually, the expirationTime will be 100.0 by default
Example: set somevar.expirationTime = 3.0 // Die after three seconds.
________________________________________________________________________________
real direction
- -----------------------------------------------------------------
Direction determines two things, the facing angle of the missile and the
direction at which the missile will move (if you enable speed, etc) Since
just about everything else uses radians this does as well.
Example: set somevar.direction = 0.5 * bj_PI // face north...
________________________________________________________________________________
real collisionSize
- -------------------------------------------------------------
xecollider wouldn't have that name if it wasn't for collision, if a unit's
collision circle collides with the xecollider's collisionSize, then your
onUnitHit method will be called. Notice that you can change collisionSize
dynamically.
Example: set somevar.collisionSize = 50.0
________________________________________________________________________________
real speed = 0.0
real acceleration = 0.0
real maxSpeed = DEFAULT_MAX_SPEED
real minSpeed = 0.0
- ------------------------------------------------------
The automatic movement is just 2D over the xy axis, so is the collision, simple
spells don't usually mess a lot with 3D... Speed would be the distance a
xecollider can move in a second, the acceleration is an increment of speed
per second, notice acceleration can be negative.
maxSpeed and minSpeed are caps for the speed in case acceleration was used.
Initially a missile has zero speed and zero acceleration , the minimum speed
is 0, which means a negative acceleration can never make speed go negative
(which would probably make the missile look as it is going backwards) The
default max speed is 1500, but you can tweak that by changing the constant
at the top of the xecollider library.
________________________________________________________________________________
real angleSpeed = 0.0
- ------------------------------------------------
Although it lacks a more formal name, this is the increment to the direction
angle during a second, this like the other variables, is in radians. It will
allow you to have missiles with curved movement, angleSpeed is also used by
homing (see bellow)
xecollider event methods
------------------------
These are methods you may declare on your custom struct in order to listen to
the xecollider events.
________________________________________________________________________________
method onUnitHit takes unit hitTarget returns nothing defaults nothing
- - --------- -
The onUnitHit method is called whenever the missile hits a unit (the collision
circles intersect) it is similar to the UnitInRange event. hitTarget is the
unit that just collided with the missile.
________________________________________________________________________________
method loopControl takes nothing returns nothing defaults nothing
- ------- --------
This is a method that, if declared will be called every XE_ANIMATION_PERIOD
seconds, may allow you to save a timer loop.
The FireNovaStrike sample attempts to be a well explained example on how to
use xecollider, get used to this whole 'extends' stuff, as you'd see in the
example it makes things easier since you will not need to attach things anymore.
//TESH.scrollpos=48
//TESH.alwaysfold=0
//**************************************************************************
//A vJASS spell that creates a missile that follows your target, dealing *
//damage to enemy units it hits. *
// *
//Included in Arcane Mage spellpack 1.3 *
// *
// Now working on Patch 1.24! *
// *
// - Darkyvm *
//**************************************************************************
scope ArcaneMissile initializer init
//**************************************************************************
//*************************** SETUP START **********************************
//**************************************************************************
globals
private constant integer SPELL_ID = 'A000'//spell ID of ArcaneMissiles
private constant real damage = 50.0 //damage dealt by the missile, multiplied by the level
private constant real duration = 5.0 //duration of the missile, multiplied by level
private constant string missile = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareMissile.mdl"//model of the missile
private constant string SFX = "Abilities\\Weapons\\AncestralGuardianMissile\\AncestralGuardianMissile.mdl"//SFX that appears when a target takes damage
private constant string deathSFX = "Abilities\\Spells\\Items\\AIlb\\AIlbSpecialArt.mdl"//effect that appears when the missile dies
private constant string flashSFX = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareBoltImpact.mdl"//effect displayed on the missile while it's flying around
endglobals
private function setupDamageOptions takes xedamage d returns nothing
//setup of the damage options
set d.dtype = DAMAGE_TYPE_MAGIC //deals magic damage
set d.atype = ATTACK_TYPE_NORMAL //normal attack type
set d.damageEnemies = true //hits enemies
set d.damageAllies = false //doesn't hit allies
set d.damageNeutral = true //hits neutral units
set d.damageSelf = false //doesn't hit self
set d.required = UNIT_TYPE_GROUND //doesn't hit flying units
set d.exception = UNIT_TYPE_STRUCTURE //doesn't hit structures
endfunction
//**************************************************************************
//*************************** SETUP END ************************************
//**************************************************************************
globals
private xedamage damageOptions
endglobals
//**************************************************************************
private struct ArcaneMissile extends xecollider
unit castingHero
integer level
method onUnitHit takes unit hitunit returns nothing
if (damageOptions.allowedTarget( this.castingHero , hitunit ) ) then
call DestroyEffect( AddSpecialEffectTarget(SFX,hitunit, "origin") )
call damageOptions.damageTarget(this.castingHero , hitunit, this.level * damage)
endif
endmethod
method onDestroy takes nothing returns nothing
set this.castingHero = null
call DestroyEffect( AddSpecialEffect(deathSFX, this.x , this.y ) )
endmethod
method loopControl takes nothing returns nothing
if(GetRandomReal(0,1)<0.25) then
call DestroyEffect( AddSpecialEffect(flashSFX, this.x , this.y ) )
endif
endmethod
endstruct
//**************************************************************************
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//**************************************************************************
private function onSpellEffect takes nothing returns nothing
local unit tar = GetSpellTargetUnit()
local unit hero = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(hero, SPELL_ID)
local real x
local real y
local ArcaneMissile am
set x = GetUnitX(hero)
set y = GetUnitY(hero)
set am = ArcaneMissile.create( x, y, bj_RADTODEG * Atan2(GetUnitY(tar) - y, GetUnitX(tar) - x))
set am.fxpath = missile
set am.speed = 500.0
set am.acceleration = 1500.0
set am.maxSpeed = 2000.0
set am.z = 30.0
set am.angleSpeed = 5.0
set am.targetUnit = tar
set am.expirationTime = ( level * duration )
set am.castingHero = hero
set am.level = GetUnitAbilityLevel(hero, SPELL_ID)
set tar = null
set hero = null
endfunction
//**************************************************************************
private function init takes nothing returns nothing
local trigger ArcaneMissileTrg = CreateTrigger()
call XE_PreloadAbility(SPELL_ID)
set damageOptions=xedamage.create()
call setupDamageOptions(damageOptions)
call TriggerRegisterAnyUnitEventBJ(ArcaneMissileTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(ArcaneMissileTrg, Condition(function Conditions))
call TriggerAddAction(ArcaneMissileTrg, function onSpellEffect)
set ArcaneMissileTrg = null
call Preload(missile)
call Preload(SFX)
call Preload(flashSFX)
call Preload(deathSFX)
endfunction
endscope
//TESH.scrollpos=33
//TESH.alwaysfold=0
//**************************************************************************
//A vJASS passive spell that creates a shield around the caster, the shield*
//has a chance to reflect attacks back to the attackers. *
// *
//Included in Arcane Mage spellpack 1.3 *
// *
// Now working on Patch 1.24! *
// *
// - Darkyvm *
//**************************************************************************
scope MageArmor initializer init
//**************************************************************************
//*************************** SETUP START **********************************
//**************************************************************************
globals
private constant integer SPELL_ID = 'A002'//spell ID of mage armor
private constant string heroSFX = "Abilities\\Spells\\Items\\AIre\\AIreTarget.mdl"//effect that appears at the hero
private constant string tarSFX = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"//effect that appears at the target
endglobals
private function chance takes integer level returns real
//chance for the trigger to activate
return level * 0.15
endfunction
//**************************************************************************
//*************************** SETUP END ************************************
//**************************************************************************
private function Conditions takes nothing returns boolean
return GetTriggerDamageType() == DAMAGE_TYPE_ATTACK
endfunction
//**************************************************************************
private function Actions takes nothing returns nothing
local unit attacked = GetTriggerDamageTarget()
local unit attacker = GetTriggerDamageSource()
local real damage = GetTriggerDamage()
local integer level
if (GetUnitAbilityLevel(attacked, SPELL_ID) > 0 ) then
set level = GetUnitAbilityLevel(attacked, SPELL_ID)
if (GetRandomReal(0.00, 1.00) < chance(level)) then
call SetWidgetLife(attacked, (GetWidgetLife(attacked) + damage))
call UnitDamageTarget(attacked, attacker, damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
call DestroyEffect(AddSpecialEffectTarget(heroSFX, attacked, "origin"))
call DestroyEffect(AddSpecialEffectTarget(tarSFX, attacker, "origin"))
endif
endif
set attacked = null
set attacker = null
endfunction
//******************************************************************************
private function init takes nothing returns nothing
local trigger MageArmorTrg = CreateTrigger()
call TriggerRegisterDamageEvent(MageArmorTrg, 2)
call TriggerAddCondition(MageArmorTrg, Condition(function Conditions))
call TriggerAddAction(MageArmorTrg, function Actions)
set MageArmorTrg = null
call XE_PreloadAbility(SPELL_ID)
call Preload(heroSFX)
call Preload(tarSFX)
endfunction
endscope
//TESH.scrollpos=81
//TESH.alwaysfold=0
//**************************************************************************
//A vJASS spell that burns away the mana of the target, and makes target *
//detonate if it's mana reaches 0. *
// *
//Included in Arcane Mage spellpack 1.3 *
// *
// Now working on Patch 1.24! *
// *
// - Darkyvm *
//**************************************************************************
scope SpiritBurn initializer Init
//**************************************************************************
//*************************** SETUP START **********************************
//**************************************************************************
globals
private constant integer SPELL_ID = 'A001'//spell ID of the spirit burn spell
private constant integer BUFF_ID = 'B000'//ID of the spirit burn buff
private constant string burnEffect = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"//effect that appears when a unit's mana is drained
private constant string boomEffect = "Units\\NightElf\\Wisp\\WispExplode.mdl"//effect that appears when a unit goes boom (mana = 0 and is under effect of spell)
private constant real mana_base = 20.0//base mana burned by second
private constant real mana_inc = 10.0//mana burn increase per level
private constant real manaTimer = 1.0//timer period between each mana burn
private constant real explodeDmg = 0.50//percentage of health that detonates if the unit's mana = 0
private constant real heroExplodeDmg = 0.10//percentage of health that detonates when hero's mana = 0
private constant real radius = 350.0//radius of the explosion
private constant real duration = 10.0//duration of the spell, used to control buff stacking
endglobals
private function setupDamageOptions takes xedamage d returns nothing
//setup of the damage options
set d.dtype = DAMAGE_TYPE_MAGIC //deals magic damage
set d.atype = ATTACK_TYPE_NORMAL //normal attack type
set d.damageEnemies = true //hits enemies
set d.damageAllies = false //doesn't hit allies
set d.damageNeutral = true //hits neutral units
set d.damageSelf = false //doesn't hit self
set d.exception = UNIT_TYPE_STRUCTURE //doesn't hit structures
endfunction
//**************************************************************************
//*************************** SETUP END ************************************
//**************************************************************************
globals
private group all
private boolexpr b
private xedamage damageOptions
endglobals
private function Pick takes nothing returns boolean
return true
endfunction
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private struct Data
unit caster
unit target
boolean hasbuff = false
real counter
static method create takes unit c, unit t returns Data
local Data D = Data.allocate()
set D.caster = c
set D.target = t
set D.counter = 0.0
return D
endmethod
method onDestroy takes nothing returns nothing
set .hasbuff = false
endmethod
endstruct
private function Loop takes nothing returns nothing
local real dmg
local timer t = GetExpiredTimer()
local Data D = Data(GetTimerData(t))
local real ManaBurn = (mana_base + mana_inc*(I2R(GetUnitAbilityLevel(D.caster, SPELL_ID)) - 1))
local real dur = duration
if not D.hasbuff and GetUnitAbilityLevel(D.target, BUFF_ID) > 0 then
set D.hasbuff = true
endif
if D.hasbuff and GetUnitAbilityLevel(D.target, BUFF_ID) > 0 then
set D.counter = D.counter + manaTimer
if GetUnitState(D.target, UNIT_STATE_MANA) > 1 then
call SetUnitState(D.target, UNIT_STATE_MANA, GetUnitState(D.target, UNIT_STATE_MANA) - ManaBurn)
call DestroyEffect(AddSpecialEffectTarget(burnEffect, D.target, "origin"))
else
call GroupEnumUnitsInRange(all, GetUnitX(D.target), GetUnitY(D.target), radius, b)
if not IsUnitType(D.target, UNIT_TYPE_HERO) then
set dmg = GetWidgetLife(D.target) * explodeDmg
else
set dmg = GetWidgetLife(D.target) * heroExplodeDmg
endif
call damageOptions.damageGroup(D.caster, all, dmg)
call DestroyEffect(AddSpecialEffectTarget(boomEffect, D.target, "origin"))
call UnitRemoveAbility(D.target, BUFF_ID)
endif
endif
if D.hasbuff and (D.counter > dur or GetUnitAbilityLevel(D.target, BUFF_ID) < 1) then
call D.destroy()
call ReleaseTimer(t)
endif
set t = null
endfunction
private function Actions takes nothing returns nothing
local Data D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
local timer t = NewTimer()
call SetTimerData(t, integer(D))
call TimerStart(t, manaTimer, true, function Loop)
set t = null
endfunction
private function Init takes nothing returns nothing
local trigger SpiritBurnTrg = CreateTrigger()
call XE_PreloadAbility(SPELL_ID)
call XE_PreloadAbility(BUFF_ID)
set damageOptions=xedamage.create()
call setupDamageOptions(damageOptions)
call TriggerRegisterAnyUnitEventBJ(SpiritBurnTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(SpiritBurnTrg, Condition(function Conditions))
call TriggerAddAction(SpiritBurnTrg, function Actions)
set all = CreateGroup()
set b = Condition(function Pick)
set SpiritBurnTrg = null
call Preload(burnEffect)
call Preload(boomEffect)
endfunction
endscope
//TESH.scrollpos=63
//TESH.alwaysfold=0
//**************************************************************************
//A mean vJASS ultimate spell that creates a barrage of missiles that fly *
//towards the target location and deal AOE damage when they collide with a *
//enemy. *
// *
//Included in Arcane Mage spellpack 1.3 *
// *
// Now working on Patch 1.24! *
// *
// - Darkyvm *
//**************************************************************************
scope ArcaneBarrage initializer Init
//**************************************************************************
//*************************** SETUP START **********************************
//**************************************************************************
globals
private constant integer SPELL_ID = 'A003'//spell ID of the arcane barrage spell
private constant string boomEffect = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"//effect that appears when a missile collides with a unit
private constant string missile = "Abilities\\Spells\\Undead\\OrbOfDeath\\AnnihilationMissile.mdl"//missile path
private constant real expTime = 2.0//the missiles' expiration time
private constant real damage = 30.0//damage per missile
private constant real damageInc = 30.0//damage increment per level
private constant real radius = 150.0//radius of the explosion
private constant real mTimer = 0.05//timer period between each missile
private constant integer maxM = 30//maximum number of missiles
endglobals
private function setupDamageOptions takes xedamage d returns nothing
//setup of the damage options
set d.dtype = DAMAGE_TYPE_MAGIC //deals magic damage
set d.atype = ATTACK_TYPE_NORMAL //normal attack type
set d.damageEnemies = true //hits enemies
set d.damageAllies = false //doesn't hit allies
set d.damageNeutral = true //hits neutral units
set d.damageSelf = false //doesn't hit self
set d.required = UNIT_TYPE_GROUND //doesn't hit flying units
set d.exception = UNIT_TYPE_STRUCTURE //doesn't hit structures
endfunction
//**************************************************************************
//*************************** SETUP END ************************************
//**************************************************************************
globals
private xedamage damageOptions
endglobals
private struct Data
unit caster
unit target
integer intg
static method create takes unit c, unit t, integer i returns Data
local Data D = Data.allocate()
set D.caster = c
set D.target = t
set D.intg = i
return D
endmethod
method onDestroy takes nothing returns nothing
set this.caster = null
set this.target = null
endmethod
endstruct
private struct Missile extends xecollider
unit caster
real dmg
method onUnitHit takes unit hitunit returns nothing
if (damageOptions.allowedTarget( this.caster, hitunit)) then
call DestroyEffect(AddSpecialEffectTarget(boomEffect, hitunit, "origin"))
call damageOptions.damageAOE(this.caster, GetUnitX(hitunit), GetUnitY(hitunit), radius, this.dmg)
call this.terminate()
endif
endmethod
method onDestroy takes nothing returns nothing
set this.caster = null
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function Loop takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data D = GetTimerData(t)
local Missile m
local real finalDmg = damage + damageInc*(I2R(GetUnitAbilityLevel(D.caster, SPELL_ID) - 1))
if D.intg < maxM then
set m = Missile.create(GetUnitX(D.caster), GetUnitY(D.caster), D.intg*(2*bj_PI)/maxM)
set m.fxpath = missile
set m.speed = 500.0
set m.acceleration = 1000.0
set m.maxSpeed = 1500.0
set m.z = 30.0
set m.angleSpeed = 3.0
set m.targetUnit = D.target
set m.expirationTime = expTime
set m.caster = D.caster
set m.dmg = finalDmg
set D.intg = D.intg + 1
else
call D.destroy()
call ReleaseTimer(t)
endif
set t = null
endfunction
private function Actions takes nothing returns nothing
local integer i=0
local unit hero = GetTriggerUnit()
local location tarLoc = GetSpellTargetLoc()
local unit tarUnit = CreateUnitAtLoc(GetOwningPlayer(hero), XE_DUMMY_UNITID, tarLoc, bj_UNIT_FACING)
local Data D = Data.create(hero, tarUnit, i)
local timer t = NewTimer()
call SetTimerData(t, integer(D))
call TimerStart(t, mTimer, true, function Loop)
call KillUnit(tarUnit)
set t = null
set hero = null
set tarLoc = null
set tarUnit = null
endfunction
private function Init takes nothing returns nothing
local trigger ArcaneBarrageTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(ArcaneBarrageTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(ArcaneBarrageTrg, Condition(function Conditions))
call TriggerAddAction(ArcaneBarrageTrg, function Actions)
call XE_PreloadAbility(SPELL_ID)
set damageOptions=xedamage.create()
call setupDamageOptions(damageOptions)
set ArcaneBarrageTrg = null
endfunction
endscope
//TESH.scrollpos=171
//TESH.alwaysfold=0
//**************************************************************************
//A vJASS spell that charges the target with arcane energies, making him *
//release shock bursts if there are enemy units nearby or be banished if *
//there are no enemy units nearby *
// *
//Included in Arcane Mage spellpack 1.3 *
// *
// Now working on Patch 1.24! *
// *
// - Darkyvm *
//**************************************************************************
scope NetherCharge initializer init
//**************************************************************************
//*************************** SETUP START **********************************
//**************************************************************************
globals
private constant integer SPELL_ID = 'A004'//spell ID of nether charge
private constant integer BANISH_ID = 'A005'//spell ID of the banish spell
private constant integer BUFF_ID = 'B001'//ID of the nether charge buff
private constant integer B_BUFF_ID = 'B002'//ID of the phase out buff
private constant string chargeEffect = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"//effect that appears when a unit is hit by the spell
private constant string dmgEffect = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"//effect that appears when a unit is damaged
private constant string lightSfx = "CLPB"//model path of the lightning effects
private constant real radius = 350.0 //radius of the discharges
private constant real radiusInc = 50.0 //radius increment per level
private constant real damage = 75.0 //damage of the lightning bolts
private constant real damageInc = 25.0 //damage increment per level
private constant real dt = 1.0//timer between each pulse
private constant real lt = 0.2//duration of each lightning effect
private constant boolean L_checkVis = false//false if you don't want the lightning effects to be affected by fog, true otherwise
endglobals
private function setupDamageOptions takes xedamage d returns nothing
//setup of the damage options
set d.dtype = DAMAGE_TYPE_MAGIC //deals magic damage
set d.atype = ATTACK_TYPE_NORMAL //normal attack type
set d.damageEnemies = true //hits enemies
set d.damageAllies = false //doesn't hit allies
set d.damageNeutral = true //hits neutral units
set d.damageSelf = false //doesn't hit self
set d.exception = UNIT_TYPE_STRUCTURE //doesn't hit structures
set d.damageTrees = false
call d.useSpecialEffect(dmgEffect, "origin")
endfunction
//**************************************************************************
//*************************** SETUP END ************************************
//**************************************************************************
globals
private xedamage damageOptions
private group unitGrp
private group EG
private boolexpr b
endglobals
private struct Data
static group isAffected
static integer index = 0
unit caster
unit target
boolean hasbuff = false
private static method onInit takes nothing returns nothing
set Data.isAffected = CreateGroup()
endmethod
static method create takes unit c, unit t returns Data
local Data D = Data.allocate()
set D.caster = c
set D.target = t
call GroupAddUnit(Data.isAffected, t)
if integer(D) > Data.index then
set Data.index = integer(D)
endif
return D
endmethod
method onDestroy takes nothing returns nothing
call GroupRemoveUnit(Data.isAffected, .target)
set .hasbuff = false
if integer(this) == Data.index then
set Data.index = Data.index - 1
endif
endmethod
static method SetCaster takes unit caster, unit target returns nothing
local integer i = 0
local Data D
loop
exitwhen i > Data.index
set D = Data(i)
if D.target == target and D.hasbuff then
set D.caster = caster
return
endif
set i = i + 1
endloop
endmethod
endstruct
private struct LData
lightning sfx
endstruct
private function LDestroy takes nothing returns nothing
local timer t = GetExpiredTimer()
local LData LD = LData(GetTimerData(t))
call DestroyLightning(LD.sfx)
call ReleaseTimer(t)
call LD.destroy()
set t = null
endfunction
private function TimedLightning takes string codeName, boolean checkVis, location loc1, real z1, location loc2, real z2, real timeout returns nothing
local real x1 = GetLocationX(loc1)
local real y1 = GetLocationY(loc1)
local real x2 = GetLocationX(loc2)
local real y2 = GetLocationY(loc2)
local timer t = NewTimer()
local LData LD = LData.create()
set LD.sfx = AddLightningEx(codeName, checkVis, x1, y1, z1, x2, y2, z2)
call SetTimerData(t, integer(LD))
call TimerStart(t, timeout, true, function LDestroy)
set t = null
endfunction
private function targets takes nothing returns boolean
local unit target = GetFilterUnit()
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function Loop takes nothing returns nothing
local timer t = GetExpiredTimer()
local xecast xc = xecast.createA()
local real GUx
local real GUy
local integer GC = 0
local unit GU
local Data D = Data(GetTimerData(t))
local real Damage = damage + (damageInc * (GetUnitAbilityLevel(D.caster, SPELL_ID) - 1))
local real Radius = radius + (radiusInc * (GetUnitAbilityLevel(D.caster, SPELL_ID) - 1))
if not D.hasbuff and GetUnitAbilityLevel(D.target, BUFF_ID) > 0 then
set D.hasbuff = true
endif
if D.hasbuff and GetUnitAbilityLevel(D.target, BUFF_ID) > 0 then
call GroupEnumUnitsInRange(unitGrp, GetUnitX(D.target), GetUnitY(D.target), Radius, b)
loop
set GU = FirstOfGroup(unitGrp)
exitwhen (GU == null)
call GroupRemoveUnit(unitGrp, GU)
if IsUnitEnemy(GU, GetOwningPlayer(D.caster)) then
set GC = GC + 1
call GroupAddUnit(EG, GU)
endif
endloop
if GC > 1 then
call damageOptions.damageAOE(D.caster, GetUnitX(D.target), GetUnitY(D.target), Radius, Damage)
loop
set GU = FirstOfGroup(EG)
exitwhen (GU == null)
call GroupRemoveUnit(EG, GU)
call xc.destroy()
call TimedLightning(lightSfx, L_checkVis, GetUnitLoc(D.target), 50.0, GetUnitLoc(GU), 50.0, lt)
endloop
if GetUnitAbilityLevel(D.target, B_BUFF_ID) > 0 then
call UnitRemoveAbility(D.target, B_BUFF_ID)
endif
else
set xc.abilityid = BANISH_ID
set xc.level = GetUnitAbilityLevel(D.caster, SPELL_ID)
set xc.orderstring = "banish"
set xc.owningplayer = GetOwningPlayer(D.caster)
call xc.castOnTarget(D.target)
endif
endif
if D.hasbuff and GetUnitAbilityLevel(D.target, BUFF_ID) < 1 then
call D.destroy()
call ReleaseTimer(t)
endif
set GU = null
set t = null
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local unit tar = GetSpellTargetUnit()
local Data D
local timer t
if not IsUnitInGroup(tar, Data.isAffected) then
set D = Data.create(u, tar)
set t = NewTimer()
call SetTimerData(t, integer(D))
call TimerStart(t, dt, true, function Loop)
else
call Data.SetCaster(u, tar)
endif
set t = null
call DestroyEffect(AddSpecialEffectTarget(chargeEffect, tar, "origin"))
set tar = null
set u = null
endfunction
private function init takes nothing returns nothing
local trigger trg = CreateTrigger()
call XE_PreloadAbility(SPELL_ID)
call XE_PreloadAbility(BANISH_ID)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trg, Condition(function Conditions))
call TriggerAddAction(trg, function Actions)
set damageOptions=xedamage.create()
call setupDamageOptions(damageOptions)
set unitGrp = CreateGroup()
set EG = CreateGroup()
set b = Condition(function targets)
endfunction
endscope
//TESH.scrollpos=93
//TESH.alwaysfold=0
//************************************************************************
//* *
//* Paladon's General System *
//* [PGS] *
//* Version 1.0 *
//* *
//* This system's purpose is only the support of the featured test map *
//* and does not affect the presented spell(s) or system(s). If you want *
//* to import any of the presented spell(s) or system(s) you don't have *
//* to import this trigger and script. *
//************************************************************************
library PGS
globals
private constant string WMessage = "Arcane mage spellpack made by Darkyvm. Credits to Paladon for his general system, used in this testmap, Vexorian for his XE0.6, table and timer utils systems and Rising_Dusk for his damage detection system. Press ESC to refresh cooldown and restore health and mana to the hero." //returns the message displayed after starting the map.
private constant integer EnemyPlayer = 1
//returns which player controls your enemies. 0 is red, 1 is blue, etc.
private constant string ReviveEffect = "Abilities\\Spells\\Undead\\RaiseSkeletonWarrior\\RaiseSkeleton.mdl"
//returns the path of the effect displayed upon the rivival of enemies.
private constant real WaitForRevive = 4.00
//returns the time in seconds before the enemy revives again.
private constant boolean FogOn = false
//returns whether the Black Mask and Fog of War is enabled (true) or disabled (false).
// private constant real TreeTimer = 10.00
//returns the time in seconds until a fallen tree regrows.
// --- END OF SETTINGS --- DO NOT MODIFY ANYTHING BELOW THIS LINE ---
private unit groupunit
private real array EnemyX
private real array EnemyY
private real array EnemyF
private integer array EnemyI
private filterfunc filter
private destructable Tree
// private trigger PGS_TR = CreateTrigger()
private trigger PGS_UR = CreateTrigger()
endglobals
private function TrueFilter takes nothing returns boolean
return true
endfunction
// private function W2D takes widget w returns destructable
// return w
// return null
// endfunction
function Message takes string msg returns nothing
local integer i = 0
loop
call DisplayTimedTextToPlayer(Player(i),0,0,60,msg)
set i = i + 1
exitwhen i == bj_MAX_PLAYERS
endloop
endfunction
function PGS_URevive takes nothing returns nothing
local unit u = GetTriggerUnit()
if(not(GetUnitUserData(u)<=0))then
call TriggerSleepAction(WaitForRevive)
set groupunit = CreateUnit(Player(EnemyPlayer),EnemyI[GetUnitUserData(u)],EnemyX[GetUnitUserData(u)],EnemyY[GetUnitUserData(u)],EnemyF[GetUnitUserData(u)])
call SetUnitUserData(groupunit, GetUnitUserData(u) )
call DestroyEffect(AddSpecialEffect(ReviveEffect,GetUnitX(groupunit),GetUnitY(groupunit)))
endif
set u = null
endfunction
// function PGS_TRevive takes nothing returns nothing
// set Tree = W2D(GetTriggerWidget())
// call TriggerSleepAction(TreeTimer)
// call DestructableRestoreLife(Tree,GetWidgetLife(Tree), true)
// set Tree = null
// endfunction
// function PGS_TR_Events takes nothing returns nothing
// set Tree = GetEnumDestructable()
// call TriggerRegisterDeathEvent(PGS_TR,Tree)
// set Tree = null
// endfunction
function InitTrig_PGSystem takes nothing returns nothing
local integer i
local group gro = CreateGroup()
set filter=Filter(function TrueFilter)
call GroupEnumUnitsOfPlayer(gro, Player(EnemyPlayer),filter)
set i = 0
loop
set groupunit = FirstOfGroup(gro)
exitwhen groupunit==null
call GroupRemoveUnit(gro,groupunit)
set i = (i+1)
call SetUnitUserData(groupunit,i)
set EnemyX[i] = GetUnitX(groupunit)
set EnemyY[i] = GetUnitY(groupunit)
set EnemyF[i] = GetUnitFacing(groupunit)
set EnemyI[i] = GetUnitTypeId(groupunit)
endloop
call FogEnable(FogOn)
call FogMaskEnable(FogOn)
call Message(WMessage)
call Message("Now working with patch 1.24!")
call TriggerRegisterPlayerUnitEvent(PGS_UR,Player(EnemyPlayer), EVENT_PLAYER_UNIT_DEATH,filter)
call TriggerAddAction(PGS_UR, function PGS_URevive)
// call TriggerAddAction(PGS_TR, function PGS_TRevive)
set bj_wantDestroyGroup = true
// call EnumDestructablesInRect(bj_mapInitialPlayableArea,filter, function PGS_TR_Events)
set PGS_UR = null
call DestroyGroup(gro)
endfunction
endlibrary