//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
Hero | unit | No | |
test | integer | No | 0 |
//TESH.scrollpos=28
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=16
//TESH.alwaysfold=0
library xepreload initializer init requires xebasic
//************************************************************************
// xepreload 0.4
// ---------
// Ah, the joy of preloading abilities, it is such a necessary evil...
// Notice you are not supposed to use this system in places outside map init
//
// This one does the preloading and tries to minimize the hit on loading time
// for example, it only needs one single native call per ability preloaded.
//
//************************************************************************
//===========================================================================================================
globals
private unit dum=null
endglobals
//inline friendly
function XE_PreloadAbility takes integer abilid returns nothing
call UnitAddAbility(dum, abilid)
endfunction
private function kill takes nothing returns nothing
call RemoveUnit(dum)
set dum=null
call DestroyTimer(GetExpiredTimer()) //I do hope this doesn't trigger the apocallypse. I didn't think
// it was a great idea to make this require a whole timer recycling
// system, so, just destroy. Anyone got a better idea on how to make
// this execute after all the other calls at init?
endfunction
private function init takes nothing returns nothing
set dum=CreateUnit(Player(15),XE_DUMMY_UNITID, 0,0,0)
call TimerStart(CreateTimer(),0.01,false,function kill)
endfunction
endlibrary
//TESH.scrollpos=30
//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.7 //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=189
//TESH.alwaysfold=0
library xefx initializer init requires xebasic
//**************************************************
// xefx 0.6
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
private constant real RECYCLE_DELAY = 4.0
//recycling, in order to show the effect correctly, must wait some time before
//removing the unit.
private timer recycler
private timer NOW
endglobals
private struct recyclebin extends array
unit u
real schedule
static recyclebin end=0
static recyclebin begin=0
static method Recycle takes nothing returns nothing
call RemoveUnit(.begin.u) //this unit is private, systems shouldn't mess with it.
set .begin.u=null
set .begin=recyclebin(integer(.begin)+1)
if(.begin==.end) then
set .begin=0
set .end=0
else
call TimerStart(recycler, .begin.schedule-TimerGetElapsed(NOW), false, function recyclebin.Recycle)
endif
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
set NOW=CreateTimer()
call TimerStart(NOW,43200,true,null)
endfunction
struct xefx[MAX_INSTANCES]
public integer tag=0
private unit dummy
private effect fx=null
private real zang=0.0
private integer r=255
private integer g=255
private integer b=255
private integer a=255
private integer abil=0
static method create takes real x, real y, real facing returns xefx
local xefx this=xefx.allocate()
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, facing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
return this
endmethod
method operator owner takes nothing returns player
return GetOwningPlayer(this.dummy)
endmethod
method operator owner= takes player p returns nothing
call SetUnitOwner(this.dummy,p,false)
endmethod
method operator teamcolor= takes playercolor c returns nothing
call SetUnitColor(this.dummy,c)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(this.dummy,value,value,value)
endmethod
//! textmacro XEFX_colorstuff takes colorname, colorvar
method operator $colorname$ takes nothing returns integer
return this.$colorvar$
endmethod
method operator $colorname$= takes integer value returns nothing
set this.$colorvar$=value
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
//! endtextmacro
//! runtextmacro XEFX_colorstuff("red","r")
//! runtextmacro XEFX_colorstuff("green","g")
//! runtextmacro XEFX_colorstuff("blue","b")
//! runtextmacro XEFX_colorstuff("alpha","a")
method recolor takes integer r, integer g , integer b, integer a returns nothing
set this.r=r
set this.g=g
set this.b=b
set this.a=a
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
implement optional ARGBrecolor
method operator abilityid takes nothing returns integer
return this.abil
endmethod
method operator abilityid= takes integer a returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(a!=0) then
call UnitAddAbility(this.dummy,a)
endif
set this.abil=a
endmethod
method flash takes string fx returns nothing
call DestroyEffect(AddSpecialEffectTarget(fx,this.dummy,"origin"))
endmethod
method operator xyangle takes nothing returns real
return GetUnitFacing(this.dummy)*bj_DEGTORAD
endmethod
method operator xyangle= takes real value returns nothing
call SetUnitFacing(this.dummy,value*bj_RADTODEG)
endmethod
method operator zangle takes nothing returns real
return this.zang
endmethod
method operator zangle= takes real value returns nothing
local integer i=R2I(value*bj_RADTODEG+90.5)
set this.zang=value
if(i>=180) then
set i=179
elseif(i<0) then
set i=0
endif
call SetUnitAnimationByIndex(this.dummy, i )
endmethod
method operator x takes nothing returns real
return GetUnitX(this.dummy)
endmethod
method operator y takes nothing returns real
return GetUnitY(this.dummy)
endmethod
method operator z takes nothing returns real
return GetUnitFlyHeight(this.dummy)
endmethod
method operator z= takes real value returns nothing
call SetUnitFlyHeight(this.dummy,value,0)
endmethod
method operator x= takes real value returns nothing
call SetUnitX(this.dummy,value)
endmethod
method operator y= takes real value returns nothing
call SetUnitY(this.dummy,value)
endmethod
method operator fxpath= takes string newpath returns nothing
if (this.fx!=null) then
call DestroyEffect(this.fx)
endif
if (newpath=="") then
set this.fx=null
else
set this.fx=AddSpecialEffectTarget(newpath,this.dummy,"origin")
endif
endmethod
private method onDestroy takes nothing returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(this.fx!=null) then
call DestroyEffect(this.fx)
set this.fx=null
endif
if (recyclebin.end==MAX_INSTANCES) then
call TimerStart(recycler,0,false,function recyclebin.Recycle)
call SetUnitExploded(this.dummy, true)
call KillUnit(this.dummy)
else
set recyclebin.end.u=this.dummy
set recyclebin.end.schedule=TimerGetElapsed(NOW)+RECYCLE_DELAY
set recyclebin.end= recyclebin( integer(recyclebin.end)+1)
if( recyclebin.end==1) then
call TimerStart(recycler, RECYCLE_DELAY, false, function recyclebin.Recycle)
endif
call SetUnitOwner(this.dummy,Player(15),false)
endif
set this.dummy=null
endmethod
endstruct
endlibrary
//TESH.scrollpos=488
//TESH.alwaysfold=0
library xedamage initializer init requires xebasic
//************************************************************************
// xedamage 0.6
// --------
// For all your damage and targetting needs.
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAX_SUB_OPTIONS = 3
//=======================================================
private constant real EPSILON = 0.000000001
private unit dmger
private constant integer MAX_SPACE = 8190 // MAX_SPACE/MAX_SUB_OPTIONS is the instance limit for xedamage, usually big enough...
endglobals
private keyword structInit
struct xedamage[MAX_SPACE]
//----
// fields and methods for a xedamage object, they aid determining valid targets and special
// damage factor conditions.
//
// Notice the default values.
//
boolean damageSelf = false // the damage and factor methods usually have a source unit parameter
// xedamage would consider this unit as immune unless you set damageSelf to true
boolean damageAllies = false // Alliance dependent target options.
boolean damageEnemies = true // *
boolean damageNeutral = true // *
boolean ranged = true // Is the attack ranged? This has some effect on the AI of the affected units
// true by default, you may not really need to modify this.
boolean visibleOnly = false // Should only units that are visible for source unit's owner be affected?
boolean deadOnly = false // Should only corpses be affected by "the damage"? (useful when using xedamage as a target selector)
boolean alsoDead = false // Should even corpses and alive units be considered?
boolean damageTrees = false //Also damage destructables? Notice this is used only in certain methods.
//AOE for example targets a circle, so it can affect the destructables
//in that circle, a custom spell using xedamage for targetting configuration
//could also have an if-then-else implemented so it can verify if it is true
//then affect trees manually.
//
// Damage type stuff:
// .dtype : the "damagetype" , determines if the spell is physical, magical or ultimate.
// .atype : the "attacktype" , deals with armor.
// .wtype : the "weapontype" , determines the sound effect to be played when damage is done.
//
// Please use common.j/blizzard.j/ some guide to know what damage/attack/weapon types can be used
//
damagetype dtype = DAMAGE_TYPE_UNIVERSAL
attacktype atype = ATTACK_TYPE_NORMAL
weapontype wtype = WEAPON_TYPE_WHOKNOWS
//
// Damage type 'tag' people might use xedamage.isInUse() to detect xedamage usage, there are other static
// variables like xedamage.CurrentDamageType and xedamage.CurrentDamageTag. The tag allows you to specify
// a custom id for the damage type ** Notice the tag would aid you for some spell stuff, for example,
// you can use it in a way similar to Rising_Dusk's damage system.
//
integer tag = 0
//
// if true, forceDamage will make xedamage ignore dtype and atype and try as hard as possible to deal 100%
// damage.
boolean forceDamage = false
//
// Ally factor! Certain spells probably have double purposes and heal allies while harming enemies. This
// field allows you to do such thing.
//
real allyfactor = 1.0
//
// field: .exception = SOME_UNIT_TYPE
// This field adds an exception unittype (classification), if the unit belongs to this unittype it will
// be ignored.
//
method operator exception= takes unittype ut returns nothing
set this.use_ex=true
set this.ex_ut=ut
endmethod
//
// field: .required = SOME_UNIT_TYPE
// This field adds a required unittype (classification), if the unit does not belong to this unittype
// it will be ignored.
//
method operator required= takes unittype ut returns nothing
set this.use_req=true
set this.req_ut=ut
endmethod
private boolean use_ex = false
private unittype ex_ut = null
private boolean use_req = false
private unittype req_ut = null
private unittype array fct[MAX_SUB_OPTIONS]
private real array fc[MAX_SUB_OPTIONS]
private integer fcn=0
//
// method .factor(SOME_UNIT_TYPE, factor)
// You might call factor() if you wish to specify a special damage factor for a certain classification,
// for example call d.factor(UNIT_TYPE_STRUCTURE, 0.5) makes xedamage do half damage to structures.
//
method factor takes unittype ut, real fc returns nothing
if(this.fcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to factor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of factor() calls")
return
endif
set this.fct[this.fcn] = ut
set this.fc[this.fcn] = fc
set this.fcn = this.fcn+1
endmethod
private integer array abifct[MAX_SUB_OPTIONS]
private real array abifc[MAX_SUB_OPTIONS]
private integer abifcn=0
//
// method .abilityFactor('abil', factor)
// You might call abilityFactor() if you wish to specify a special damage factor for units that have a
// certain ability/buff.
// for example call d.abilityFactor('A000', 1.5 ) makes units that have the A000 ability take 50% more
// damage than usual.
//
method abilityFactor takes integer abilityId, real fc returns nothing
if(this.abifcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to abilityFactor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of abilityFactor() calls")
return
endif
set this.abifct[this.abifcn] = abilityId
set this.abifc[this.abifcn] = fc
set this.abifcn = this.abifcn+1
endmethod
private boolean usefx = false
private string fxpath
private string fxattach
//
// method .useSpecialEffect("effect\\path.mdl", "origin")
// Makes it add (and destroy) an effect when damage is performed.
//
method useSpecialEffect takes string path, string attach returns nothing
set this.usefx = true
set this.fxpath=path
set this.fxattach=attach
endmethod
//********************************************************************
//* Now, the usage stuff:
//*
//================================================================================
// static method xedamage.isInUse() will return true during a unit damaged
// event in case this damage was caused by xedamage, in this case, you can
// read variables like CurrentDamageType, CurrentAttackType and CurrentDamageTag
// to be able to recognize what sort of damage was done.
//
readonly static damagetype CurrentDamageType=null
readonly static attacktype CurrentAttackType=null
readonly static integer CurrentDamageTag =0
private static integer inUse = 0
static method isInUse takes nothing returns boolean
return (.inUse>0) //inline friendly.
endmethod
//========================================================================================================
// This function calculates the damage factor caused by a certain attack and damage
// type, it is static : xedamage.getDamageTypeFactor(someunit, ATTAcK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, 100)
//
static method getDamageTypeFactor takes unit u, attacktype a, damagetype d returns real
local real hp=GetWidgetLife(u)
local real mana=GetUnitState(u,UNIT_STATE_MANA)
local real r
//Since a unit is in that point, we don't need checks.
call SetUnitX(dmger,GetUnitX(u))
call SetUnitY(dmger,GetUnitY(u))
call SetUnitOwner(dmger,GetOwningPlayer(u),false)
set r=hp
if (hp<1) then
call SetWidgetLife(u,1)
set r=1
endif
call UnitDamageTarget(dmger,u,0.01,false,false,a,d,null)
call SetUnitOwner(dmger,Player(15),false)
if (mana>GetUnitState(u,UNIT_STATE_MANA)) then
//Unit had mana shield, return 1 and restore mana too.
call SetUnitState(u,UNIT_STATE_MANA,mana)
set r=1
else
set r= (r-GetWidgetLife(u))*100
endif
call SetWidgetLife(u,hp)
return r
endmethod
private method getTargetFactorCore takes unit source, unit target, boolean usetypes returns real
local player p=GetOwningPlayer(source)
local boolean allied=IsUnitAlly(target,p)
local boolean enemy =IsUnitEnemy(target,p)
local boolean neutral=allied
local real f
local real negf=1.0
local integer i
if(this.damageAllies != this.damageNeutral) then
set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_REQUEST ))
//I thought accuracy was not as important as speed , I think that REQUEST is false is enough to consider
// it neutral.
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_RESPONSE ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_XP ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_SPELLS ))
set allied= allied and not(neutral)
endif
if (not this.damageAllies) and allied then
return 0.0
elseif (not this.damageEnemies) and enemy then
return 0.0
elseif( (not this.damageSelf) and (source==target) ) then
return 0.0
elseif (not this.damageNeutral) and neutral then
return 0.0
elseif( this.use_ex and IsUnitType(target, this.ex_ut) ) then
return 0.0
elseif( this.visibleOnly and not IsUnitVisible(target,p) ) then
return 0.0
elseif ( this.deadOnly and not IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
elseif ( not(this.alsoDead) and IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
endif
set f=1.0
if ( IsUnitAlly(target,p) ) then
set f=f*this.allyfactor
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
if (this.use_req and not IsUnitType(target,this.req_ut)) then
return 0.0
endif
set i=.fcn-1
loop
exitwhen (i<0)
if( IsUnitType(target, this.fct[i] ) ) then
set f=f*this.fc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set i=.abifcn-1
loop
exitwhen (i<0)
if( GetUnitAbilityLevel(target,this.abifct[i] )>0 ) then
set f=f*this.abifc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set f=f*negf
if ( f<=EPSILON) and (f>=-EPSILON) then
return 0.0
endif
if( this.forceDamage or not usetypes ) then
return f
endif
set f=f*xedamage.getDamageTypeFactor(target,this.atype,this.dtype)
if ( f<=EPSILON) and (f>=-EPSILON) then
return 0.0
endif
return f
endmethod
//====================================================================
// With this you might decide if a unit is a valid target for a spell.
//
method getTargetFactor takes unit source, unit target returns real
return this.getTargetFactorCore(source,target,true)
endmethod
//======================================================================
// a little better, I guess
//
method allowedTarget takes unit source, unit target returns boolean
return (this.getTargetFactorCore(source,target,false)!=0.0)
endmethod
//=======================================================================
// performs damage to the target unit, for unit 'source'.
//
method damageTarget takes unit source, unit target, real damage returns boolean
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local real f = this.getTargetFactorCore(source,target,false)
local real pl
if(f!=0.0) then
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
set pl=GetWidgetLife(target)
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return (pl!=GetWidgetLife(target))
endif
return false
endmethod
//=======================================================================================
// The same as damageTarget, but it forces a specific damage value, good if you already
// know the target.
//
method damageTargetForceValue takes unit source, unit target, real damage returns nothing
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
call UnitDamageTarget(source,target, damage, true, .ranged, null, null, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
endmethod
//=====================================================================================
// Notice: this will not Destroy the group, but it will certainly empty the group.
//
method damageGroup takes unit source, group targetGroup, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local unit target
local real f
local integer count=0
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
loop
set target=FirstOfGroup(targetGroup)
exitwhen (target==null)
call GroupRemoveUnit(targetGroup,target)
set f= this.getTargetFactorCore(source,target,false)
if (f!=0.0) then
set count=count+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
endif
endloop
set .inUse = .inUse -1
set .CurrentDamageTag=tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return count
endmethod
private static xedamage instance
private integer countAOE
private unit sourceAOE
private real AOEx
private real AOEy
private real AOEradius
private real AOEdamage
private static boolexpr filterAOE
private static boolexpr filterDestAOE
private static group enumgroup
private static rect AOERect
private static method damageAOE_Enum takes nothing returns boolean
local unit target=GetFilterUnit()
local xedamage this=.instance //adopting a instance.
local real f
if( not IsUnitInRangeXY(target,.AOEx, .AOEy, .AOEradius) ) then
set target=null
return false
endif
set f=.getTargetFactorCore(.sourceAOE, target, false)
if(f!=0.0) then
set .countAOE=.countAOE+1
call UnitDamageTarget(.sourceAOE,target, f*this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
endif
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
private static method damageAOE_DestructablesEnum takes nothing returns boolean
local destructable target=GetFilterDestructable()
local xedamage this=.instance //adopting a instance.
local real dx=.AOEx-GetDestructableX(target)
local real dy=.AOEy-GetDestructableY(target)
if( dx*dx + dy*dy >= .AOEradius+EPSILON ) then
set target=null
return false
endif
set .countAOE=.countAOE+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(.sourceAOE,target, this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
//==========================================================================================
// will affect trees if damageTrees is true!
//
method damageAOE takes unit source, real x, real y, real radius, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius
set .AOEy=y
set .AOEdamage=damage
call GroupEnumUnitsInRange(.enumgroup,x,y,radius+XE_MAX_COLLISION_SIZE, .filterAOE)
if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
set .AOEradius=.AOEradius*.AOEradius
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
endif
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return .countAOE
endmethod
method damageAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageAOE(source, GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//==========================================================================================
// only affects trees, ignores damageTrees
//
method damageDestructablesAOE takes unit source, real x, real y, real radius, real damage returns integer
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius*radius
set .AOEy=y
set .AOEdamage=damage
//if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
//endif
return .countAOE
endmethod
method damageDestructablesAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageDestructablesAOE(source,GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//'friend' with the library init
static method structInit takes nothing returns nothing
set .AOERect= Rect(0,0,0,0)
set .filterAOE= Condition(function xedamage.damageAOE_Enum)
set .filterDestAOE = Condition( function xedamage.damageAOE_DestructablesEnum)
set .enumgroup = CreateGroup()
endmethod
endstruct
private function init takes nothing returns nothing
set dmger=CreateUnit(Player(15), XE_DUMMY_UNITID , 0.,0.,0.)
call UnitAddAbility(dmger,'Aloc')
call xedamage.structInit()
endfunction
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=0
//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=484
//TESH.alwaysfold=0
/*
LOST SOULS
by Kricz
Version:
1.01
Credits:
Vexorian & BruZzl3R_17C
Requires:
TimerUtils, xebasic, xecast, xedamage, xepreload, Table
Discription:
Summons a cricle of gravestones arround the target, damaging enemies
in it. Summons lost souls in several intervals, flying to enemies in
the circle and disease them on impact with one of three negative buffs.
*/
scope LostSouls //needs TimerUtils, xecast, xedamage, xepreload, Table, MyFunctions
globals
/*MAIN SETUP*/
//The Spell Id of the Spell
private constant integer SPELL_ID = 'A000'
//The Order-String of the Spell
private constant string ORDER_STRING = "sacrifice"
//The Number of Levels for the Spell
private constant integer LEVELS = 3
//The Number of the effects, the souls can cast
private constant integer DUMMY_SPELLS = 3
//Shouls all destructibles/trees be destroyed in the spell are on start?
private constant boolean DESTROY_TREES = true
//How much auras (effects) should be created in the middle of the circle?
private constant integer AURA_COUNT = 2
/*END OF MAIN SETUP*/
/*CASTER EFFECT SETUP*/
//Should a effect be created at the hero's location?
private constant boolean CASTER_EFFECT = true
//The effect created on the hero while channeling
private constant string CASTER_EFFECT_MODEL = "Abilities\\Spells\\Undead\\Possession\\PossessionCaster.mdl"
//The heigh of the hero effect
private constant real CASTER_EFFECT_HEIGH = 62.50
//The size of the created effect
private constant real CASTER_EFFECT_SIZE = 3.25
/*END OF CASTER EFFECT SETUP*/
/*SETUP FOR THE STONE CIRCLE*/
//The model of the stones
private constant string GRAVE_STONE_MODEL = "Abilities\\Spells\\Undead\\Graveyard\\GraveMarker.mdl"
//How much gravestones should be in the circle?
private constant integer STONE_COUNT = 11
//The size of the stones
private constant real STONE_SIZE = 1.35
/*END OF STONE CIRCLE SETUP*/
/*SETUP FOR THE SOULS*/
//The model of the souls
private constant string LOST_SOUL_MODEL = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
//The distance a soul must have between the target to damage the target and get destroyed
private constant real DISTANCE = 8.5
//The Z-Value of the souls (high)
private constant real HEIGH = 72.50
//The speed of the souls
private constant real SPEED = 500.00
//How much souls can be alive per spell at the same time?
//If there are MAX_SOULS per cast alive, no new will be created until the other reached their target
private constant integer MAX_SOULS = 13
//The size of the souls
private constant real SOUL_SIZE = 1.3
/*END OF SOULS SETUP*/
//Don't change anything here!
//They are for private using!
private real array DAMAGE[LEVELS]
private real array DURATION[LEVELS]
private real array AOE[LEVELS]
private real array INTERVAL[LEVELS]
private string array MODEL_AURA[AURA_COUNT]
private real array AURA_HEIGH[AURA_COUNT]
private real array AURA_SIZE[AURA_COUNT]
private integer array DUMMY_SPELL_ID[DUMMY_SPELLS]
private string array DUMMY_ORDERS[DUMMY_SPELLS]
private integer tempint = 0
private unit tempunit = null
endglobals
//Setup for the damage dealt over the duration in the circle
private function SETUP_DAMAGE takes nothing returns nothing
set DAMAGE[1] = 200
set DAMAGE[2] = 300
set DAMAGE[3] = 400
endfunction
//Setup for the channel duration
private function SETUP_DURATION takes nothing returns nothing
set DURATION[1] = 5.00
set DURATION[2] = 6.00
set DURATION[3] = 7.00
endfunction
//Setup for the AoE each level
private function SETUP_AOE takes nothing returns nothing
set AOE[1] = 350.00
set AOE[2] = 400.00
set AOE[3] = 450.00
endfunction
//Setup for the Intervals between the summons
private function SETUP_INTERVAL takes nothing returns nothing
set INTERVAL[1] = 0.60
set INTERVAL[2] = 0.45
set INTERVAL[3] = 0.30
endfunction
//Setup for the order-id's for the dummys
private function SETUP_DUMMY_IDS takes nothing returns nothing
set DUMMY_SPELL_ID[0] = 'LSD1'
set DUMMY_SPELL_ID[1] = 'LSD2'
set DUMMY_SPELL_ID[2] = 'LSD3'
endfunction
//Setup for the order-strings for the dummys. Note, that the order strings must have the same array number like the ID's!!
private function SETUP_DUMMY_ORDERS takes nothing returns nothing
set DUMMY_ORDERS[0] = "curse"
set DUMMY_ORDERS[1] = "cripple"
set DUMMY_ORDERS[2] = "silence"
endfunction
//Setup for the Auras things...
private function SETUP_AURAS takes nothing returns nothing
set MODEL_AURA[0] = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl"
set AURA_HEIGH[0] = 2.50
set AURA_SIZE[0] = 1.00
set MODEL_AURA[1] = "Abilities\\Spells\\Undead\\Unsummon\\UnsummonTarget.mdl"
set AURA_HEIGH[1] = 0.00
set AURA_SIZE[1] = 1.80
endfunction
//Setup for the Damage-Options
//! textmacro LS_DAMAGE_OPTIONS
set .dmg.dtype = DAMAGE_TYPE_MAGIC
set .dmg.atype = ATTACK_TYPE_MAGIC
set .dmg.wtype = WEAPON_TYPE_WHOKNOWS
//! endtextmacro
/**************************/
/*END OF USER CONFIGURATION!*/
/**************************/
//Don't change anything below here!!
private function GroupGetRandomUnitEnum takes nothing returns nothing
set tempint = tempint + 1
if ( GetRandomInt(1, tempint) == 1 ) then
set tempunit = GetEnumUnit()
endif
endfunction
private function GroupGetRandomUnit takes group g returns unit
set tempint = 0
set tempunit = null
call ForGroup(g, function GroupGetRandomUnitEnum)
return tempunit
endfunction
private keyword soul
//The "Main-Struct". It countains the caster and all required datas, also the active souls
private struct spell
xefx array stone[STONE_COUNT] //The xefx models for the stone circle
xefx array aura[AURA_COUNT] //The xefx models for the auras
xefx cfx = 0 //The xefx effect created at the hero
unit caster = null //The channeling unit
real px = 0.00 //The X of the cast point
real py = 0.00 //The Y of the cast point
integer lvl = 0 //The level of the spell
timer t = null //The used timer
real ticker = 0.00 //The real counts the time
real counter = 0.00 //This real will summon the souls if it has the value of TICK
integer count = 0 //This counter is used to count actual living souls
boolean summon = true //If this is true, more souls will be summoned (also for debuging)
soul array souls[MAX_SOULS] //The souls, which are currently flying
real dist = 0.00 //I use this to store the distance between target point and caster to check if the caster moves during channeling, which is caused through Wc3 itself:
//If you cast the spell while channeling to a point far away from the caster location, he has to move to the new point.
//While moving, his actual oder isn't "move" but ORDER_STRING, and so, the old spell will be channeled further.
static delegate xecast cast = 0 //Used to cast the spells on impact
static group tempgroup = CreateGroup() //Group to detect units each interval
static xedamage dmg //Used to deal damage
static HandleTable table //Uses to detect, if a unit was channeling when casting again
static boolexpr filter //filter for tempgroup
static spell temp //required by the filter
//This is called, when the caster starts the spell. It creates all datas and attachs them to the table
private static method create takes unit caster, integer level, real x, real y returns spell
local spell this = spell.allocate()
local integer i = 0
local real dx = 0.00
local real dy = 0.00
local real angle = 360 / STONE_COUNT
set .caster = caster
set .lvl = level
set .px = x
set .py = y
set .t = NewTimer()
loop
exitwhen i >= AURA_COUNT
set .aura[i] = xefx.create(.px, .py, 0.00)
set .aura[i].fxpath = MODEL_AURA[i]
set .aura[i].scale = AURA_SIZE[i]
set .aura[i].z = AURA_HEIGH[i]
set i = i + 1
endloop
set i = 0
set .dist = SquareRoot((.px - GetUnitX(.caster)) * (.px - GetUnitX(.caster)) + (.py - GetUnitY(.caster)) * (.py - GetUnitY(.caster)))
set .table[.caster] = this
if DESTROY_TREES then
call .dmg.damageDestructablesAOE(.caster, .px, .py, AOE[.lvl], 1000000.00) //Yay i know, it's ugly^^
endif
loop
set angle = I2R(i) / STONE_COUNT * 2 * bj_PI
set dx = x + AOE[.lvl] * Cos(angle)
set dy = y + AOE[.lvl] * Sin(angle)
set angle = Atan2(y - dy, x - dx)
debug call BJDebugMsg("Creating new stone with index: " + I2S(i) + ", angle: " + R2S(angle))
set .stone[i] = xefx.create(dx, dy, angle)
set .stone[i].xyangle = angle
set .stone[i].fxpath = GRAVE_STONE_MODEL
set .stone[i].scale = STONE_SIZE
set i = i + 1
exitwhen i == STONE_COUNT
endloop
if CASTER_EFFECT then
set .cfx = xefx.create(GetUnitX(.caster), GetUnitY(.caster), 0.00)
set .cfx.fxpath = CASTER_EFFECT_MODEL
set .cfx.scale = CASTER_EFFECT_SIZE
set .cfx.z = CASTER_EFFECT_HEIGH
endif
call SetTimerData(.t, this)
call TimerStart(.t, XE_ANIMATION_PERIOD, true, function spell.periodic)
return this
endmethod
//This method will be called every XE_ANIMATION_PERIOD (that means 0.025 seconds) and moves the souls and checks, if the caster is still channeling
private static method periodic takes nothing returns nothing
local timer t = GetExpiredTimer()
local spell this = spell(GetTimerData(t))
local integer i = .count - 1
local unit u = null
local real newdist = SquareRoot((.px - GetUnitX(.caster)) * (.px - GetUnitX(.caster)) + (.py - GetUnitY(.caster)) * (.py - GetUnitY(.caster)))
set .ticker = .ticker + XE_ANIMATION_PERIOD
set .counter = .counter + XE_ANIMATION_PERIOD
if GetWidgetLife(.caster) <= 0.405 or .counter >= DURATION[.lvl] or GetUnitCurrentOrder(.caster) != OrderId(ORDER_STRING) or newdist != .dist then
if CASTER_EFFECT and .cfx != 0 then
call .cfx.destroy()
endif
if .count <= 0 then
debug call BJDebugMsg("Destroying Datas")
call .destroy()
else
debug call BJDebugMsg("Cannot destroy datas yet, because there are living souls, which require these datas.")
set .summon = false
endif
elseif GetWidgetLife(.caster) > 0.405 and .counter < DURATION[.lvl] and GetUnitCurrentOrder(.caster) == OrderId(ORDER_STRING) and newdist == .dist then
call GroupEnumUnitsInRange(.tempgroup, .px, .py, AOE[.lvl], .filter)
loop
set u = FirstOfGroup(.tempgroup)
exitwhen u == null
call SetUnitExploded(u, true)
call .dmg.damageTarget(.caster, u, DAMAGE[.lvl] / DURATION[.lvl] * XE_ANIMATION_PERIOD)
call SetUnitExploded(u, false)
call GroupRemoveUnit(.tempgroup, u)
set u = null
endloop
call GroupClear(.tempgroup)
if .ticker >= INTERVAL[.lvl] and .summon then
set .ticker = 0.00
call .SummonSoul()
endif
endif
loop
exitwhen i < 0
if not .souls[i].Control() then
debug call BJDebugMsg("A Soul reached its target and will be destroyed.")
set .count = .count - 1
if .count < 0 then
set .count = 0
else
set .souls[i] = .souls[.count]
endif
endif
set i = i - 1
endloop
endmethod
//Checks first, if a new soul can be summoned, if true, it creates one and attaches it to the main-struct
method SummonSoul takes nothing returns nothing
local unit u = null
local integer from = 0
if .count < MAX_SOULS - 1 then
set .temp = this
call GroupEnumUnitsInRange(.tempgroup, .px, .py, AOE[.lvl], .filter)
set u = GroupGetRandomUnit(.tempgroup)
call GroupClear(.tempgroup)
if u != null then
set from = GetRandomInt(0, STONE_COUNT -1)
set .souls[.count] = soul.create(this, from, u)
debug call BJDebugMsg("Registring soul in array number '" + I2S(.count) + "'.")
set .count = .count + 1
set u = null
endif
debug else
debug call BJDebugMsg("Soul Storage is full. Cannot create a new soul.")
endif
endmethod
//When the caster dies or the duration over is, the struct need to be destroyed with this method
method onDestroy takes nothing returns nothing
local integer i = 0
call ReleaseTimer(.t)
loop
exitwhen i >= STONE_COUNT
call .stone[i].destroy()
set i = i + 1
endloop
set i = 0
loop
exitwhen i >= .count
set .souls[i].destroyMe = true
set i = i + 1
endloop
set i = 0
loop
exitwhen i >= AURA_COUNT
call .aura[i].destroy()
set i = i + 1
endloop
if CASTER_EFFECT and .cfx != 0 then
call .cfx.destroy()
endif
call .table.flush(.caster)
endmethod
//This method creates the struct for the casting unit
private static method actions takes nothing returns boolean
local unit caster = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
local spell this = spell.table[caster]
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
if GetSpellAbilityId() == SPELL_ID then
if spell.table.exists(caster) and this != null and this != 0 then
call .destroy()
debug call BJDebugMsg("Already channeling, destroying old datas and creating new ones.")
else
debug call BJDebugMsg("Caster wasn't channeling, creating datas.")
endif
call spell.create(caster, lvl, x, y)
endif
set caster = null
return true
endmethod
//This is the filter used to check for enemy units and so on...
private static method FilterFunc takes nothing returns boolean
return GetFilterUnit() != spell.temp.caster and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_RESISTANT) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(spell.temp.caster))
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
set .table = HandleTable.create()
set .dmg = xedamage.create()
set .cast = xecast.create()
set .filter = Condition(function spell.FilterFunc)
//adding Events and Actions
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CHANNEL)
call TriggerAddCondition(t, Condition(function spell.actions)) //cause conditions are faster than actions ;)
//call the Setup functions
call SETUP_DAMAGE()
call SETUP_DURATION()
call SETUP_AOE()
call SETUP_DUMMY_IDS()
call SETUP_DUMMY_ORDERS()
call SETUP_INTERVAL()
call SETUP_AURAS()
//! runtextmacro LS_DAMAGE_OPTIONS()
//Preloading dummy-spells and effects/models
call XE_PreloadAbility(SPELL_ID)
set i = 0
loop
call XE_PreloadAbility(DUMMY_SPELL_ID[i])
exitwhen i >= DUMMY_SPELLS - 1
set i = i + 1
endloop
call Preload(GRAVE_STONE_MODEL)
call Preload(LOST_SOUL_MODEL)
set i = 0
loop
exitwhen i == AURA_COUNT
call Preload(MODEL_AURA[i])
set i = i + 1
endloop
set t = null
endmethod
endstruct
//Each soul has it own struct.
private struct soul
delegate xefx model = 0 //the soul itself
delegate spell root = 0 //the struct the souls are from
unit target = null //The target of the soul
real angle = 0.00 //the angle of the soul
boolean destroyMe = false //If true, the missile will be destroyed in the next timer expiration
//creates the soul...
static method create takes spell root, integer from, unit target returns soul
local soul this = soul.allocate()
set .root = root
set .target = target
set .angle = Atan2(GetUnitY(.target) - .stone[from].y, GetUnitX(.target) - .stone[from].x)
set .model = xefx.create(.stone[from].x, .stone[from].y, .angle)
set .z = HEIGH
set .fxpath = LOST_SOUL_MODEL
set .scale = SOUL_SIZE
return this
endmethod
//Will be called every time for every soul, when the periodic method is called and returns if it should be destroy or not
method Control takes nothing returns boolean
local real dx = GetUnitX(.target) - .x
local real dy = GetUnitY(.target) - .y
local real dist = SquareRoot(dx * dx + dy * dy)
if dist > DISTANCE and .target != null and not .destroyMe and GetWidgetLife(.target) > 0.405 then
set .angle = Atan2(GetUnitY(.target) - .y, GetUnitX(.target) - .x)
set .x = .x + ( SPEED * XE_ANIMATION_PERIOD ) * Cos(.angle)
set .y = .y + ( SPEED * XE_ANIMATION_PERIOD ) * Sin(.angle)
set .xyangle = .angle
return true
elseif dist <= DISTANCE or .target == null or .destroyMe or GetWidgetLife(.target) <= 0.405 then
if GetWidgetLife(.target) > 0.405 and .target != null then
call .cast()
endif
call .destroy()
return false
endif
return false
endmethod
//casts the a random dummy spell on impact
method cast takes nothing returns nothing
local integer id = GetRandomInt(0, DUMMY_SPELLS - 1)
set spell.level = .lvl
set spell.orderstring = DUMMY_ORDERS[id]
set spell.abilityid = DUMMY_SPELL_ID[id]
set spell.owningplayer = GetOwningPlayer(.caster)
call spell.castOnTarget(.target)
debug call BJDebugMsg("Ability |cffffcc00" + GetObjectName(DUMMY_SPELL_ID[id]) + "|r was casted on a |cffffcc00" + GetUnitName(.target) + "|r, with level |cffffcc00" + I2S(.lvl) + "|r.")
endmethod
//destroys the souls
method onDestroy takes nothing returns nothing
call .model.destroy()
endmethod
endstruct
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*LOST SOULS OBJECT MERGER*/
//Use this Merger, to import all dummy abilitys and buffs into your map.
//Import this trigger, save your map, close it and load is again and the abilities are in the map with the correct rawcodes, buff settings and so on...
//After that, you can delate this trigger.
//Buffs
//! external ObjectMerger w3h Bcrs LSB1 ftac 1 fnam "Curse" fube "This unit is cursed and has an increased chance to miss on attacks."
//! external ObjectMerger w3h Bcri LSB2 ftat "Abilities\Spells\Human\Banish\BanishTarget.mdl" fta0 "origin" ftac 1 fnam "Cripple" fube "This unit is crippled and has decreased attack and movement speed."
//! external ObjectMerger w3h BNsi LSB3 ftat "Abilities\Spells\Other\HowlOfTerror\HowlTarget.mdl" fta0 "origin" ftac 1 frac "undead" fnam "Fear" ftip "Fear" fube "This unit scared and cannot attack (only for meele units) or cast spells."
//Abilitys
//! external ObjectMerger w3a Acrs LSD1 auar "" atat "Abilities\Spells\Undead\CarrionSwarm\CarrionSwarmDamage.mdl" ata0 "origin" atac 1 alev 3 achd 0 ansf " | Lost Souls" ahky "" auhk "" aoro "None" aorf "None" Crs 1 0.15 Crs 2 0.30 Crs 3 0.45 abuf 1 LSB1 abuf 2 LSB1 abuf 3 LSB1 aran 1 100 aran 2 100 aran 3 100 acdn 1 0.00 ahdu 1 4.5 ahdu 2 4.5 ahdu 3 4.5 adur 1 4.5 adur 2 4.5 adur 3 4.5 amcs 1 0 atar 1 "air,ground,enemy,organic,neutral" atar 2 "air,ground,enemy,organic,neutral" atar 3 "air,ground,enemy,organic,neutral" atp1 1 "" aub1 1 "" aut1 1 "" auu1 1 "" amcs 2 0
//! external ObjectMerger w3a Acri LSD2 auar "" atat "Abilities\Spells\Undead\AnimateDead\AnimateDeadTarget.mdl" ata0 "origin" atac 1 alev 3 achd 0 ansf " | Lost Souls" ahky "" Cri2 1 0.20 Cri2 2 0.40 Cri2 3 0.60 Cri3 1 0.00 Cri1 1 0.25 Cri1 2 0.50 Cri1 3 0.75 abuf 1 LSB2 abuf 2 LSB2 abuf 3 LSB2 aran 1 100 aran 2 100 aran 3 100 acdn 1 0.00 ahdu 1 4.5 ahdu 2 4.5 ahdu 3 4.5 adur 1 4.5 adur 2 4.5 adur 3 4.5 amcs 1 0 atar 1 "air,ground,enemy,organic,neutral" atar 2 "air,ground,enemy,organic,neutral" atar 3 "air,ground,enemy,organic,neutral" atp1 1 "" aub1 1 "" aut1 1 "" auu1 1 "" areq "" arqa 0 amcs 2 0
//! external ObjectMerger w3a AIse LSD3 anam "Fear" auar "" atat "Abilities\Spells\Undead\DeathCoil\DeathCoilSpecialArt.mdl" ata0 "origin" atac 1 aite 0 alev 3 arac "undead" ansf " | Lost Souls" Nsi1 1 "meele,special,spells" Nsi1 2 "meele,special,spells" Nsi1 3 "meele,special,spells" aart "ReplaceableTextures\CommandButtons\BTNSilence.blp" aare 1 0.00 abuf 1 LSB3 abuf 2 LSB3 abuf 3 LSB3 aran 1 0.00 acdn 1 0.00 ahdu 1 4.50 ahdu 2 4.50 ahdu 3 4.50 adur 1 4.50 adur 2 4.50 adur 3 4.50 atar 1 "air,ground,enemy,organic,neutral" atar 2 "air,ground,enemy,organic,neutral" atar 3 "air,ground,enemy,organic,neutral" amcs 1 0
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Testmap initializer init
function init takes nothing returns nothing
call DisplayTimedTextToPlayer(Player(0), 0, 0, 20, "Please give credits to me if you use this spell and credits to the creators of the required systems.")
endfunction
endscope