Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xepreload requires xebasic, optional TimerUtils
//******************************************************************************
// xepreload 0.9
// ---------
// 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
private keyword DebugIdInteger2IdString
//inline friendly (when debug mode is off..)
function XE_PreloadAbility takes integer abilid returns nothing
call UnitAddAbility(dum, abilid)
static if DEBUG_MODE then
if(dum==null) then
call BJDebugMsg("XE_PreloadAbility: do not load abilities after map init or during modules' onInit")
elseif GetUnitAbilityLevel(dum, abilid) == 0 then
call BJDebugMsg("XE_PreloadAbility: Ability "+DebugIdInteger2IdString.evaluate(abilid)+" does not exist.")
endif
endif
endfunction
// ................................................................................
//================================================================================
// Convert a integer id value into a 4-letter id code.
// * Taken from cheats.j so I don't have to code it again.
// * Used only on debug so making a whole library for it seemed silly
// * Private so people don't begin using xepreload just to call this function....
// * It will not work correctly if you paste this code in the custom script section
// due to the infamous % bug. Then again, if you do that then you probably
// deserve it....
//
private function DebugIdInteger2IdString takes integer value returns string
local string charMap = ".................................!.#$%&'()*+,-./0123456789:;<=>.@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~................................................................................................................................."
local string result = ""
local integer remainingValue = value
local integer charValue
local integer byteno
set byteno = 0
loop
set charValue = ModuloInteger(remainingValue, 256)
set remainingValue = remainingValue / 256
set result = SubString(charMap, charValue, charValue + 1) + result
set byteno = byteno + 1
exitwhen byteno == 4
endloop
return result
endfunction
//--------------------------------
private function kill takes nothing returns nothing
call RemoveUnit(dum)
set dum=null
static if (LIBRARY_TimerUtils ) then
call ReleaseTimer( GetExpiredTimer() )
else
call DestroyTimer(GetExpiredTimer())
endif
endfunction
private struct init extends array
private static method onInit takes nothing returns nothing
local timer t
set dum = CreateUnit( Player(15), XE_DUMMY_UNITID, 0,0,0)
if( dum == null) then
debug call BJDebugMsg("xePreload : XE_DUMMY_UNITID ("+DebugIdInteger2IdString.evaluate(XE_DUMMY_UNITID)+") not added correctly to the map.")
endif
static if (LIBRARY_TimerUtils) then
set t=NewTimer()
else
set t=CreateTimer()
endif
call TimerStart(t,0.0,false,function kill)
set t=null
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xedummy requires xebasic
//******************************************************************************
// xedummy 0.9
// ------
// For all your xe dummy recycling needs.
//
//******************************************************************************
//==============================================================================
globals
// The number of different angles at which the dummy units will be stored.
private constant integer ANGLE_RESOLUTION = 12
// The total number of xe dummy units that will be preloaded on map initialization.
private constant integer INITIAL_DUMMY_COUNT = 36
// Don't allow to keep more than DUMMY_STACK_LIMIT inactive dummy units.
private constant integer DUMMY_STACK_LIMIT = 240
endglobals
// END OF CALIBRATION SECTION
// ================================================================
private keyword xedummy
private struct recycleQueue extends array
recycleQueue next
recycleQueue prev
real angle
integer size
xedummy first
xedummy last
static method onInit takes nothing returns nothing
local integer i=0
loop
exitwhen i==ANGLE_RESOLUTION
set i=i+1
set recycleQueue(i).prev=recycleQueue(i-1)
set recycleQueue(i).next=recycleQueue(i+1)
set recycleQueue(i).angle=(i-0.5)*(360.0/ANGLE_RESOLUTION)
endloop
set recycleQueue(1).prev=recycleQueue(i)
set recycleQueue(i).next=recycleQueue(1)
endmethod
static method get takes real angle returns recycleQueue
return recycleQueue(R2I(angle/360.0*ANGLE_RESOLUTION)+1)
endmethod
endstruct
// ================================================================
struct xedummy
private static group g=CreateGroup()
private unit u
// ----------------------------------------------------------------
private xedummy next
private method queueInsert takes recycleQueue q returns nothing
call SetUnitFacing(.u, q.angle)
if q.size==0 then
set q.first=this
else
set q.last.next=this
endif
set q.last=this
set .next=0
// Recursively check adajcent queues and migrate xedummies as needed.
if q.size>q.next.size then
set this=q.first
set q.first=.next
call .queueInsert(q.next)
elseif q.size>q.prev.size then
set this=q.first
set q.first=.next
call .queueInsert(q.prev)
else
set q.size=q.size+1
endif
endmethod
private static method queueRemove takes recycleQueue q returns xedummy
// Recursively check adajcent queues and migrate xedummies as needed.
local xedummy this
if q.size<q.next.size then
set this=q.last
set q.last=.queueRemove(q.next)
set .next=q.last
call SetUnitFacing(q.last.u, q.angle)
elseif q.size<q.prev.size then
set this=q.last
set q.last=.queueRemove(q.prev)
set .next=q.last
call SetUnitFacing(q.last.u, q.angle)
else
set q.size=q.size-1
if q.size==0 then
set q.last=0
endif
endif
set this=q.first
set q.first=.next
set .next=0
return this
endmethod
// ----------------------------------------------------------------
private static method create takes unit u returns xedummy
local xedummy this
if GetUnitTypeId(u)!=XE_DUMMY_UNITID then
debug call BJDebugMsg("ReleaseXEDummy error: Method called on a unit of an incorrect type.")
elseif IsUnitInGroup(u, .g) then
debug call BJDebugMsg("ReleaseXEDummy error: Method called on an already released unit.")
else
set this=.allocate()
if integer(this)>DUMMY_STACK_LIMIT then
call RemoveUnit(u)
call .deallocate()
return 0
endif
set .u=u
call GroupAddUnit(.g, u)
call .queueInsert(recycleQueue.get(GetUnitFacing(u)))
call SetUnitAnimationByIndex(u, 90)
call SetUnitScale(u, 1, 0, 0)
call SetUnitVertexColor(u, 255, 255, 255, 255)
// call ShowUnit(u, false) // Do not hide the unit, it is rather costly and not needed.
return this
endif
return 0
endmethod
private method destroy takes nothing returns nothing
call GroupRemoveUnit(.g, .u)
call ShowUnit(.u, true) // Show the unit in case it was hidden before being recycled.
set .u=null
call .deallocate()
endmethod
// ----------------------------------------------------------------
private static unit dummy
private static method onInit takes nothing returns nothing
local integer i=INITIAL_DUMMY_COUNT
local recycleQueue q=recycleQueue(1)
if i>DUMMY_STACK_LIMIT then
debug call BJDebugMsg("xedummy error: INITIAL_DUMMY_COUNT can not be larger than DUMMY_STACK_LIMIT.")
set i=DUMMY_STACK_LIMIT
endif
loop
exitwhen i==0
set .dummy = CreateUnit(Player(15), XE_DUMMY_UNITID, 0.0,0.0,q.angle)
call UnitAddAbility(.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(.dummy,'Aloc')
call UnitRemoveAbility(.dummy,XE_HEIGHT_ENABLER)
call .create(.dummy)
set i=i-1
set q=q.next
endloop
endmethod
// ----------------------------------------------------------------
static method new takes player p, real x, real y, real face returns unit
local recycleQueue q
local xedummy this
loop
exitwhen face>0.0
set face=face+360.0
endloop
loop
exitwhen face<360.0
set face=face-360.0
endloop
set q=recycleQueue.get(face)
if q.size==0 then
set .dummy = CreateUnit(p, XE_DUMMY_UNITID, x,y,face)
call UnitAddAbility(.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(.dummy,'Aloc')
call UnitRemoveAbility(.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(.dummy, x)
call SetUnitY(.dummy, y)
else
set this=.queueRemove(q)
set .dummy=.u
call .destroy()
call SetUnitX(.dummy, x)
call SetUnitY(.dummy, y)
call SetUnitFacing(.dummy, face)
call SetUnitOwner(.dummy, p, true)
endif
return .dummy
endmethod
static method release takes unit u returns nothing
call .create(u)
endmethod
endstruct
// ================================================================
function XE_NewDummyUnit takes player p, real x, real y, real face returns unit
return xedummy.new( p,x,y,face )
endfunction
function XE_ReleaseDummyUnit takes unit u returns nothing
call xedummy.release( u )
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xecast initializer init requires xebasic
//******************************************************************************
// xecast 0.9
// ------
// Because dummy casters REALLY ARE this complicated!
//
//******************************************************************************
//==============================================================================
globals
private constant integer MAXINSTANCES = 8190
//this is a lot, unless you leak xecast objects (which is a bad thing)
private constant integer INITIAL_DUMMY_COUNT = 12
//don't allow to keep more than DUMMY_STACK_LIMIT innactive dummy units :
private constant integer DUMMY_STACK_LIMIT = 50
// 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.
private constant boolean FORCE_INVISIBLE_CAST = false
//When AUTO_RESET_MANA_COOLDOWN is set to true, xecast will reset
// the dummy unit's cooldown and mana before casting every spell.
// it is a performance penalty, so if you are sure that all dummy spells
// in your map got 0 mana and cooldown cost, you may set it to false.
private constant boolean AUTO_RESET_MANA_COOLDOWN = true
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(u) : If you want to hit a unit u with the ability, supports FORCE_INVISIBLE_CAST.
// .castOnWidgetTarget(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 IssueImmediateOrder(u,"stop")// bugfix, see: http://www.wc3c.net/showpost.php?p=1131163&postcount=5
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)
static if AUTO_RESET_MANA_COOLDOWN then
call UnitResetCooldown(dummy)
call SetUnitState(dummy, UNIT_STATE_MANA, 10000.0)
endif
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
static 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=0
//TESH.alwaysfold=0
library xefx initializer init requires xebasic, optional xedummy
//**************************************************
// xefx 0.9
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
//Delay in order to show the death animation of the effect correctly when xefx is destroyed.
//You may need to increase this if you are using effects with longer death animations.
private constant real MIN_RECYCLE_DELAY = 4.0
//The delay does not need to be exact so we do cleanup in batches instead of individually.
//This determines how often the recycler runs, should be less than MIN_RECYCLE_DELAY.
private constant real RECYCLE_INTERVAL = 0.5
//if this is true and the xedummy library is present, units will be recycled instead of removed.
private constant boolean RECYCLE_DUMMY_UNITS = true
private timer recycler
endglobals
private struct recyclebin
unit u
private recyclebin next=0
private static recyclebin array list
private static integer readindex=1
private static integer writeindex=0
private static integer count=0
static method Recycle takes nothing returns nothing
local recyclebin this = .list[readindex]
loop
exitwhen this==0
static if RECYCLE_DUMMY_UNITS and LIBRARY_xedummy then
call XE_ReleaseDummyUnit(this.u)
else
call RemoveUnit(this.u)
endif
set this.u=null
set .count=.count-1
call this.destroy()
set this=this.next
endloop
set .list[readindex]=0
set .writeindex=.readindex
set .readindex=.readindex+1
if .readindex>R2I(MIN_RECYCLE_DELAY/RECYCLE_INTERVAL+1.0) then
set .readindex=0
endif
if count!=0 then
call TimerStart(recycler, RECYCLE_INTERVAL, false, function recyclebin.Recycle)
endif
endmethod
static method create takes unit u returns recyclebin
local recyclebin this=recyclebin.allocate()
if .count==0 then
call TimerStart(recycler, RECYCLE_INTERVAL, false, function recyclebin.Recycle)
endif
set .count=.count+1
set .u=u
call SetUnitOwner(u,Player(15),false)
set .next=.list[.writeindex]
set .list[.writeindex]=this
return this
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
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()
static if RECYCLE_DUMMY_UNITS and LIBRARY_xedummy then
set this.dummy= XE_NewDummyUnit(Player(15), x,y, facing*bj_RADTODEG)
else
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)
endif
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 operator abilityLevel takes nothing returns integer
return GetUnitAbilityLevel( this.dummy, this.abil)
endmethod
method operator abilityLevel= takes integer newLevel returns nothing
call SetUnitAbilityLevel(this.dummy, this.abil, newLevel)
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
method hiddenReset takes string newfxpath, real newfacing returns nothing
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
local real z = this.z
local real za = this.zangle
local integer level = this.abilityLevel
set .fxpath=null
static if RECYCLE_DUMMY_UNITS and LIBRARY_xedummy then
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
call recyclebin.create(this.dummy)
set this.dummy=XE_NewDummyUnit(Player(15), x,y, newfacing*bj_RADTODEG)
else
call RemoveUnit(this.dummy)
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, newfacing*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)
endif
set .fxpath=newfxpath
if(level != 0) then
call UnitAddAbility(this.dummy, this.abil)
call SetUnitAbilityLevel(this.dummy, this.abil, level)
endif
set this.z = z
set zangle = za
endmethod
method destroy 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
call recyclebin.create(this.dummy)
set this.dummy=null
call this.deallocate()
endmethod
method hiddenDestroy takes nothing returns nothing
call ShowUnit(dummy,false)
call destroy()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xedamage initializer init requires xebasic
//************************************************************************
// xedamage 0.8
// --------
// For all your damage and targetting needs.
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAX_SUB_OPTIONS = 3
private constant real FACTOR_TEST_DAMAGE = 0.01
// a low damage to do on units to test their damage factors for specific
// attacktype/damagetype combos.
// You'll need something as high as 20.0 to make it work well with armor resistances.
// (Yes, you read it correctly, 20 ...
//
// If you use such a large value, there may be conflicts with some things relying on damage
// (ie they are not away of the isDummyDamage tag that xedamage posseses.) which may be quite bad if you think about it...
// then maybe it is better to change it to 0.01 ... then will work fine, just fine - but it will generally ignore armor -
// I am leaving it as 0.01 by default, because honestly, I'd rather make it ignore armor than have a lot of people sending me
// rants about how they detect 20.0 damage where none is visible...
private constant real MAX_DAMAGE_FACTOR = 3.00
// The maximum damage factor in the map. I think 3 is fine.
//=======================================================
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
readonly static boolean isDummyDamage = false
//========================================================================================================
// 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
local real fc = FACTOR_TEST_DAMAGE
//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< FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR) then
call SetWidgetLife(u, hp + FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR )
set r = hp + FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR
set fc = GetWidgetLife(u)-hp + EPSILON
endif
set isDummyDamage = true
call UnitDamageTarget(dmger,u, fc ,false,false,a,d,null)
static if DEBUG_MODE then
if IsUnitType(u, UNIT_TYPE_DEAD) and (hp>0.405) then
call BJDebugMsg("xedamage: For some reason, the unit being tested by getDamageTypeFactor has died. Verify MAX_DAMAGE_FACTOR is set to a correct value. ")
endif
endif
set isDummyDamage = false
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)) / fc
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
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
if(pl != GetWidgetLife(target) ) then
if(usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
return true
endif
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
local real hp
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
set hp = GetWidgetLife(target)
endif
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
if(usefx and (hp > GetWidgetLife(target)) ) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
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
local real hp
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
if(this.usefx) then
set hp =GetWidgetLife(target)
endif
call UnitDamageTarget(.sourceAOE,target, f*this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
if(this.usefx and (hp > GetWidgetLife(target) ) ) 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=0
//TESH.alwaysfold=0
library xecollider initializer init requires xefx, xebasic
//****************************************************************
//*
//* xecollider 0.9
//* --------------
//* 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.angleMode= ANGLE_NO_MOVEMENT
else
set this.angleMode= ANGLE_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.angleMode= ANGLE_HOMING_POINT
set this.homingTargetX=x
set this.homingTargetY=y
endmethod
public method setTargetPointLoc takes location loc returns nothing
set this.angleMode= ANGLE_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.angleMode = ANGLE_NO_MOVEMENT
endmethod
public method operator rotating takes nothing returns boolean
return (angleMode ==ANGLE_ROTATING)
endmethod
public method operator rotating= takes boolean val returns nothing
if(val) then
set angleMode = ANGLE_ROTATING
elseif (angleMode == ANGLE_ROTATING) then
set angleMode = ANGLE_NO_MOVEMENT
endif
endmethod
private boolean silent = false
private method destroy takes nothing returns nothing
call GroupClear(this.seen)
if(this.silent) then
call this.fx.hiddenDestroy()
else
call this.fx.destroy()
endif
call .deallocate()
endmethod
method terminate takes nothing returns nothing
set this.dead=true
set this.fxpath=""
endmethod
// declare hiddenDestroy so people don't call directly on the delegate xefx
method hiddenDestroy takes nothing returns nothing
set silent = true
call terminate()
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 ANGLE_HOMING_UNIT =1
private static constant integer ANGLE_HOMING_POINT=2
private static constant integer ANGLE_NO_MOVEMENT=0
private static constant integer ANGLE_ROTATING=3
private integer angleMode =0
private unit homingTargetUnit = null
private real homingTargetX
private real homingTargetY
private static real newx
private static real newy
private static group enumGroup
private static group tempGroup
private static unit array picked
private static integer pickedN
static method timerLoop takes nothing returns nothing
local integer i=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
local group g
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.angleMode== ANGLE_HOMING_UNIT ) then
set u=this.homingTargetUnit
if ( (GetUnitTypeId(u)==0) or IsUnitType(u, UNIT_TYPE_DEAD) ) then
set this.angleMode= ANGLE_NO_MOVEMENT
set this.homingTargetUnit = null
else
set this.homingTargetX=GetUnitX(u)
set this.homingTargetY=GetUnitY(u)
endif
set u=null
endif
if (this.angleMode == ANGLE_ROTATING) then
//nothing (ns is already ns)
elseif( this.angleMode != ANGLE_NO_MOVEMENT) 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
else
set ns = 0
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 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
call GroupEnumUnitsInRange( .enumGroup, .newx, .newy, .csize + XE_MAX_COLLISION_SIZE, null)
loop
set u=FirstOfGroup(.enumGroup)
exitwhen u==null
call GroupRemoveUnit(.enumGroup, u)
if not IsUnitType(u, UNIT_TYPE_DEAD) and not(this.dead) and (GetUnitTypeId(u)!=XE_DUMMY_UNITID) and IsUnitInRangeXY(u, .newx, .newy, .csize) then
call GroupAddUnit(.tempGroup, u)
if not IsUnitInGroup (u, this.seen ) then
call this.onUnitHit(u)
endif
endif
endloop
set g=.tempGroup
call GroupClear(this.seen)
set .tempGroup=this.seen
set this.seen=g
set g=null
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
//============================================================================
// 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.tempGroup = CreateGroup()
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=0
//TESH.alwaysfold=0
library xemissile requires xefx, xebasic
//****************************************************************
//*
//* xemissile 0.9
//* -------------
//* A xemissile object is a special effect that moves like a
//* WC3's attack or spell missile.
//*
//* 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.)
//*
//* This struct is used similarly to xecollider. Instead of just
//* creating the xemissile (which works, but it would only be a
//* xefx that can move like a missile) you probably need to make
//* it do something special when it reaches its target...
//* For this reason, you need to make a new struct extending
//* xemissile that declares an onHit method, you may also declare
//* a loopControl method.
//*
//****************************************************************
//===========================================================================
// 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 onHit takes nothing returns nothing defaults nothing
method loopControl takes nothing returns nothing defaults nothing
endinterface
//===========================================================================
private struct missile extends eventHandler
private delegate xefx fx
// movement duration parameter.
private real time
// xy movement parameters.
private real mx
private real my
private real mvx
private real mvy
private real mvxy = 1.0
// z movement parameters.
private real mz
private real mvz
private real maz
private static location l = Location(0.0,0.0)
// target parameters.
private real tx = 0.0
private real ty = 0.0
private real tz = 0.0
private unit tu = null
public real zoffset = 0.0
private boolean update = true
private boolean launched = false
private boolean dead = false
public method operator x takes nothing returns real
return .mx
endmethod
public method operator y takes nothing returns real
return .my
endmethod
public method operator z takes nothing returns real
call MoveLocation(.l, .mx,.my)
return mz-GetLocationZ(.l)
endmethod
public method operator x= takes real r returns nothing
set .update=true
set .mx=r
endmethod
public method operator y= takes real r returns nothing
set .update=true
set .my=r
endmethod
public method operator z= takes real r returns nothing
set .update=true
call MoveLocation(.l, .mx,.my)
set .mz=r+GetLocationZ(.l)
endmethod
public method operator speed takes nothing returns real
return .mvxy/XE_ANIMATION_PERIOD
endmethod
public method operator speed= takes real newspeed returns nothing
local real factor=newspeed*XE_ANIMATION_PERIOD/.mvxy
if newspeed<=0.0 then
debug call BJDebugMsg("xemissile speed error: speed must be a non-zero positive value.")
return
endif
set .mvxy=newspeed*XE_ANIMATION_PERIOD
if .launched then
set .time=.time/factor
set .mvx=.mvx*factor
set .mvy=.mvy*factor
set .mvz=.mvz*factor
set .maz=.maz*factor*factor
endif
endmethod
public method operator targetUnit takes nothing returns unit
return this.tu
endmethod
public method operator targetUnit= takes unit u returns nothing
set .update=true
set .tu=u
set .tx=GetUnitX(u)
set .ty=GetUnitY(u)
call MoveLocation(.l, .tx,.ty)
set .tz=GetUnitFlyHeight(u)+GetLocationZ(.l)+.zoffset
endmethod
public method setTargetPoint takes real x, real y, real z returns nothing
set .update=true
set .tu=null
set .tx=x
set .ty=y
call MoveLocation(.l, .tx,.ty)
set .tz=z+GetLocationZ(.l)
endmethod
//-------- Missile launcher
public method launch takes real speed, real arc returns nothing
local real dx=.tx-.mx
local real dy=.ty-.my
local real d=SquareRoot(dx*dx+dy*dy)
local real a=Atan2(dy, dx)
local real dz=.tz-.mz
set .mvxy=speed*XE_ANIMATION_PERIOD
if speed<=0.0 then
debug call BJDebugMsg("xemissile launch error: speed must be a non-zero positive value.")
return
elseif d>0.0 then
set .time=d/speed
else
set .time=XE_ANIMATION_PERIOD
endif
set .mvz=( (d*arc)/(.time/4.0) + dz/.time )*XE_ANIMATION_PERIOD // Do some mathemagics to get a proper arc.
set .dead=.dead and not(.launched) // In case this is called from the onHit method to bounce the missile.
if not .dead and not .launched then
set .launched=true
set .V[.N]=this
set .N=.N+1
if(.N==1) then
call TimerStart(.T, XE_ANIMATION_PERIOD, true, xemissile.timerLoopFunction )
endif
endif
endmethod
//-------- Constructors and destructors
static method create takes real x, real y, real z, real a returns missile
local missile this=missile.allocate()
set .mx=x
set .my=y
call MoveLocation(.l, x,y)
set .mz=z+GetLocationZ(.l)
set .fx = xefx.create(x,y,a)
set .fx.z = z
return this
endmethod
private boolean silent=false
private method destroy takes nothing returns nothing
if(this.silent) then
call this.fx.hiddenDestroy()
else
call this.fx.destroy()
endif
call .deallocate()
endmethod
method terminate takes nothing returns nothing
set this.dead=true
set this.fx.zangle=0.0
set this.fxpath=""
endmethod
// declare hiddenDestroy so people don't call directly on the delegate xefx
method hiddenDestroy takes nothing returns nothing
set silent = true
call terminate()
endmethod
//-------- Main engine
private static timer T
private static integer N=0
private static xemissile array V
private static code timerLoopFunction //I use a code var so create can be above the timerloop function, more readable
private static method timerLoop takes nothing returns nothing
local integer i=0
local integer c=0
local thistype this
local real dx
local real dy
local real d
local real a
loop
exitwhen (i==.N )
set this=.V[i] //adopt-a-instance
if .dead then
set .launched=false
set this.fx.zangle=0.0
call .destroy()
else
if .tu!=null and GetUnitTypeId(.tu)!=0 then
set .update=true
set .tx=GetUnitX(.tu)
set .ty=GetUnitY(.tu)
call MoveLocation(.l, .tx,.ty)
set .tz=GetUnitFlyHeight(.tu)+GetLocationZ(.l)+.zoffset
endif
if .update then
set .update=false
set dx=.tx-.mx
set dy=.ty-.my
set d=SquareRoot(dx*dx+dy*dy)
set a=Atan2(dy,dx)
if d>0.0 then
set .time=d/.mvxy*XE_ANIMATION_PERIOD
else
set .time=XE_ANIMATION_PERIOD
endif
set .mvx=Cos(a)*.mvxy
set .mvy=Sin(a)*.mvxy
set .fx.xyangle=a
set .maz=2*((.tz-.mz)/.time/.time*XE_ANIMATION_PERIOD*XE_ANIMATION_PERIOD-(.mvz*XE_ANIMATION_PERIOD)/.time)
endif
set .mx=.mx+.mvx
set .my=.my+.mvy
set a=.maz/2.0
set .mvz=.mvz+a
set .mz=.mz+.mvz
set .mvz=.mvz+a
set .fx.x=.mx
set .fx.y=.my
call MoveLocation(.l, .mx,.my)
set .fx.z=.mz-GetLocationZ(.l)
set .fx.zangle=Atan2(.mvz, .mvxy)
set .time=.time-XE_ANIMATION_PERIOD
if .time<=0.0 then
set .dead=true
call this.onHit()
if .dead then
set .fxpath=""
endif
endif
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
set .N=c
if(c==0) then
call PauseTimer(.T)
endif
endmethod
static method onInit takes nothing returns nothing
set .timerLoopFunction = (function missile.timerLoop)
set .T=CreateTimer()
endmethod
endstruct
//===========================================================================
struct xemissile extends missile
public static method create takes real x,real y,real z, real tx,real ty,real tz returns xemissile
local xemissile xm = xemissile.allocate(x,y,z, Atan2(ty-y,tx-x))
call xm.setTargetPoint(tx,ty,tz)
return xm
endmethod
endstruct
struct xehomingmissile extends xemissile
public static method create takes real x,real y,real z, unit target,real zoffset returns xehomingmissile
local xehomingmissile xm = xehomingmissile.allocate(x,y,z, GetUnitX(target), GetUnitY(target), 0.0)
set xm.zoffset=zoffset
set xm.targetUnit=target
return xm
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
xe
--
Q. Why xe?
A. As the caster system grew bigger vJass also appeared, there are also a lot
of things in the caster system that could have been done better but cannot
be fixed without dropping the caster system's function interface.
Q. No, really, WHY IS IT NAMED XE?
A. I have no idea.
Q. What's wrong with the caster system?
A. Instead of answering that I will list what's right with xe:
* It is modular. You can make a whole spell with just xebasic which is just
like a "constant package" that comes with the dummy model and unit. All the
other parts are disposable or replaceable.
Still, I made them and will probably keep making new modules as I advance,
right now xe can only do some basic functions, still cannot 100% replace
the caster system in functionality, certain parts like the parabolic
projectiles most notably, don't have a xe module yet, maybe later...
xebasic is so minimal I personally hope people could use it on their spells
and systems as a way to make it common to use these constants as constants of
that kind are often required to make things work.
* It is quite OOP, this is more related to the modules themselves, I wanted it
to exploit OOP for two reasons: 1) It prevents the 'ultra long function calls'
disease that has plagued the caster system since the beginning of time. 2) I
personally think it is easier this way instead of memorizing function names
and their argument lists.
xebasic
-------
xebasic is the nucleous of all xe, in order to use a xe module you would most
likely need xebasic. It is also meant to be the only part of xe that most users
would really need to tweak for their map.
This section is supposed to be a rapid guide on copying xebasic. The rest of
the xe modules should be rather easy to implement (just copy the 'trigger' that
contains it to the map).
1) Make a backup of your map.
2) Get vJass support. The most usual way would be using the newgen pack. There
are plenty of other ways. I for example do my vJass coding on Linux using just
jasshelper.exe, WINE and some editor tricks using a tool called Warcity.
Getting vJass to work is a wide area, if just installing and using the
"Jass newgen pack" doesn't work to you, please request/search help somehow. As
of now there really is no vJass support in OS/X, so you would need virtualization
and things of that style.
-- Note: You can have two maps open in the editor, and it is the only way to
make copy and paste work.
3) Copy the model, in this map's import manager you may find dummy.mdx, select
it and export it to some temp folder, then go to your map and import it, use
war3mapimported\dummy.mdx for path.
Please save the map immediatelly after importing the model to prevent it from
getting unimported due to a rare WE bug.
4) Copy the dummy unit: In the object editor under neutral passive, you may
find the dummy unit, select it, go to 'edit' then click copy. Now switch to
your map's object editor and use 'paste'.
5) Write down the rawcode: While you are in the object editor select your map's
recently pasted dummy unit, then go to the 'view' menu and click the option to
show values as raw data. Now take a look to the selected unit type. It will now
come with a code like "ewsp:e001", the last four characters of the code are
what matter, write them down.
6) Copy this map's xebasic trigger to your map.
7) Update your map's xebasic: change the value assigned to XE_DUMMY_UNITID to
'XXXX' where XXXX is the four character code you wrote down in step 5.
8) Save your map and pray, if it compiles correctly then it is done. Make sure
to test (after implementing) whatever needed you to install xebasic. If it
doesn't work correctly then it is likely you made a mistake when copying the
unit or the model or updating the rawcode.
9) Update the other values if you think it is necessary.
Using xebasic
-------------
Well, once you state your trigger/spell/system requires xebasic, just use
its constants, on a map that has implemented xebasic XE_DUMMY_UNITID will be
your key to creating dummy units, just remember to add it 'Aloc' ...
Contact
-------
I got a forum that's supposed to hold questions related to my systems at:
http://wc3campaigns.net/vexorian
I'd really appreciate that you used that forum for your questions, for starters
it is a lot more likely I would actually find the questions in that case.
Changelog
---------
0.9:
- Added xemissile.
- Added xedummy.
- xefx can now use xedummy to recycle dummy units.
- xepreload can now be used from struct initializers.
- Fixed a bug in xecast that would occur if recycledelay was shorter than
the duration of the spell cast by the dummy caster.
- Fixed a bug in xefx where hiddenReset did not properly assign the new fxpath.
- Optimized the xecollider collision detection.
- Fixed some errors in xefx and xecast documentation.
- new sample: Firestrike.
- Removed an xecast leak in the IllusionRune sample.
- Removed a handle id leak in SheepStaff sample.
0.8:
- No longer let xecollider hit corpses...
- When xecollider misses targetUnit (the unit dies, it will stop homing and
keep going on its current direction).
- angleSpeed no longer implicitly makes rotation automatic unless there is homing,
If you want rotation, set the boolean member rotation to true.
- xedamage: Added MAX_DAMAGE_FACTOR to prevent some deads, a debug message will
appear if the unit dies when being tested for a damage factor.
- xepreload: debug message when preloading an ability at a wrong time.
0.7:
- Requires jasshelper 0.A.2.4 or greater.
- xepreload will now show an useful error when the ability does not exist
(in debug mode).
- xepreload may now use TimerUtils to recycle its timer if you happen to have
TimerUtils in the map.
- xecast automatically resets mana and cooldown for the dummy before making it
cast stuff. If this behaviour concerns you, you may disable it through a
constant.
- Added targetUnit, setTargetPoint and forgetTarget to xecollider's
documentation.
- Added abilityLevel to xefx.
- Fixed a mistake in xefx's documentation that said "copy the xecast trigger"
- xedamage: Added a constant FACTOR_TEST_DAMAGE, which allows you to set the
amount of damage to do when testing for damage...
- xedamage: Added an isDummyDamage event response boolean to detect test damages.
- xedamage: Added a lot of things to documentation about the issues with armor.
- xedamage: fx is for sure not shown when there is no damage.
- xefx: added hiddenDestroy and hiddenReset
- new sample: Chains of fire.
0.6:
- The demo map is playable in patch 1.24.
- Only the samples and demomap system needed to be updated, xe's libraries
themselves have not changed.
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=0
//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=0
//TESH.alwaysfold=0
xedummy
-------
xedummy attacks the dummy unit recycling issue. Creating units is a costly
operation and due to their versatility, xe dummy units can end up being
created and destroyed very frequently. In such situations, recycling them
makes sense.
Install
-------
Copy the xedummy trigger to your map.
Usage
-----
_____________________________________________________________________________
function XE_NewDummyUnit takes player p, real x, real y, real face returns unit
----
Gives you a new xe dummy unit. If a previously preloaded or released unit
is available for the given facing angle, it will be reused, else a new xe
dummy unit will be created. The unit is automatically given the 'aloc'
ability to make it unselectable and the XE_HEIGHT_ENABLER ability so you can
modify its fly height.
There is no way to change the facing angle of a unit instantly, this is
why xedummy stores recycled units at various facing angles so that when a
new unit is requested, the system may already have a unit with a similar
facing angle available. This way, the non-instant change of facing when
reusing a recycled unit is made less noticeable.
The number of angles at which dummy units are stored is configurable,
however keep in mind that more angles mean higher overhead. With the current
implementation, the maximum theoretical overhead is N*N/4 units, so with the
default 12 directions the map will use at most 36 more units than it would
without this library.
_____________________________________________________________________________
function XE_ReleaseDummyUnit takes unit u returns nothing
----
Makes an xe dummy unit available for recycling. Please only call this on
xe dummy units that were obtained with the NewXEDummy function and make sure
to destroy any special effects attached to them and remove any abilities
they may have.
You can limit how many units the system will keep available for reuse.
If this limit is reached then released dummy units will be removed. This
way, if at some point your map needs a number of dummy units that greatly
exceeds your average use, you won't be stuck with that many dummy units for
the rest of the game. However, this limit should not be set too low or the
map will end up creating and removing dummy units more often than recycling
them.
_____________________________________________________________________________
//TESH.scrollpos=0
//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 unit target returns nothing
-
Tells the xecast object to cast its spell on the target unit. Can cast on units that are invisible
to the owner of the xecast if FORCE_INVISIBLE_CAST is set to true.
_____________________________________________________________________________________________________
method castOnWidgetTarget 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.
//TESH.scrollpos=0
//TESH.alwaysfold=0
xefx
----
This module just allows you to have movable special effects, they are actually
dummy units, you can do plenty of things like changing their position, their
height, their rotation (in the xy and in the z axis as well), color, and things
like that. It is all about assigning attributes, the only two important methods
xefx objects have are create and destroy. There are other accessory methods.
implementation
--------------
Just copy the xefx 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"
________________________________________________________________________________
method hiddenReset takes string newfxpath, real newfacing returns nothing
----
Resets the xefx with a new effect path and a new facing angle (radians).
Avoids playing the dead animation of the previous model, notice that it is
impossible to do this withouth playing the birth animation of the new model
and without playing the sound of the dead animation of the previous model.
Example: call myfx.hiddenReset("abilities\thisisamadeup\modelpath.mdl", 0.4)
________________________________________________________________________________
method hiddenDestroy takes nothing returns nothing
----
Destroys the xefx without playing the death animation of the model. Notice
that it is impossible to do this without playing the sound of the
dead animation.
Example: call myfx.hiddenDestroy()
________________________________________________________________________________
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 red
integer green
integer blue
integer alpha
----
Well, the model's vertex coloring in RGB , a is the opacity value (use
values from 0 to 255 here)
Example: set myfx.red=255
set myfx.green=0
set myfx.blue=255
set myfx.alpha=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'
________________________________________________________________________________
integer abilityLevel
----
And with this one, you change the level of that ability.
Example: set myfx.abilityLevel = GetUnitAbilityLevel( u, s)
________________________________________________________________________________
playercolor teamcolor
----
The team color to use for the model.
Example: set somevar.teamcolor=PLAYER_COLOR_RED
set somevar.teamcolor=GetPlayerColor(GetOwningPlayer(u))
________________________________________________________________________________
method flash takes string modelpath returns nothing
----
It shows the dead animation of the model specified by modelpath. This is
in case you need this sort of eye candy.
________________________________________________________________________________
method ARGBrecolor takes ARGB color returns nothing
----
If you got the ARGB library in your map, xefx then acquires the ARGBrecolor
method, that you can use to use an ARGB object to recolor the fx's model,
in a way similar to how recolor() works.
//TESH.scrollpos=0
//TESH.alwaysfold=0
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.
About damage factors and dummy damages
----------------------------------------
Typically xedamage does some dummy damage to test the damage factor of a unit
because blizzard has not provided us with such a native... This damage is
invisible to the player, but not invisible to spells and damage detect systems
which could be a problem.
By default, the dummy damage is 0.01 and it is a common practice in these
spells and systems to ignore such low damage values. However, a bug from
blizzard makes it so we require a HUGE damage of 20.0 to be actually able to
detect all armor resistances. Something as low as 0.01 will not even detect
hero resistance. This blows. So if you want xedamage to consider resistance
correctly, you should use a test damage of 1/(armor reduction you wish to detect)
(change the FACTOR_TEST_DAMAGE constant in the xedamage trigger).
Of course, if you do this change, then you'll confuse spells/systems that
detect damage, that's the reason I have added a variable isDummyDamage to
xedamage (call it xedamage.isDummyDamage ) that you may read during a damage
event to ignore this dummy damage.
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
------------------------
______________________________________________________________________
boolean isDummyDamage
----------------------------------------------------------------------
This variable is true when xedamage has performed "dummy damage" see
above for more details.
______________________________________________________________________
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.
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)
________________________________________________________________________________
unit targetUnit = null
- ---------------------------------------------------------
By assigning this member to a living unit, the xecollider will home towards
the unit's position. Notice that for this to work it will require an angleSpeed
higher than 0.0.
________________________________________________________________________________
method setTargetPoint takes real x, real y returns nothing
method setTargetPointLoc takes real x, real y returns nothing
- -----------------
This one makes the missile home towards a point. Same as targetUnit , it will
need an angleSpeed higher than 0.
________________________________________________________________________________
method forgetTargetPoint takes nothing returns nothing
- ------------------------
This method makes the xecollider forget it was homing towards a unit or point
then it becomes a normal missile, you may like to change angleSpeed as well
though.
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=0
//TESH.alwaysfold=0
xemissile
----------
A specialization xefx, aids at creating targeted missiles with arc and
speed and that sort of stuff, notice you'd usually have to make a new
struct and extend xemissile 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 xemissile trigger to your map.
I'd also recommend you to implement the BoundSentinel library to prevent
crashes related to the xemissiles 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.
This library has two objects you can extend, xemissile and xehomingmissile.
xehomingmissile extends xemissile and is only there to provide an alternate
constructor for your convenience. You could also create a normal xemissile
and then give it a homing target and it would work the same as if you created
a xehmoingmissile.
xemissile object
-----------------
__________________________________________________________________________________________________
static method create takes real x, real y, real z, real tx, real ty, real tz returns xemissile
- - - ----------
This is the create method, it will make a new xemissile object for you to
launch, If you are extending xemissile and wish to have a custom create method
on your struct, remember that you will need to call allocate(x,y,z, tx,ty,tz)
set somevariable = myxemissile.create( GetUnitX(u),GetUnitY(u),GetUnitFlyHeight(u)+LAUNCH_OFFSET, targetx,targety,0.0 )
xehomingmissile object
-----------------
__________________________________________________________________________________________________
static method create takes real x, real y, real z, unit target, real zoffset returns xehomingmissile
- - - ----------
Same as the xemissile create method, except that the target now is not a
point, but a unit, which will be hit at zoffset from its origin point.
set somevariable = myxehomingmissile.create( GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u)+LAUNCH_OFFSET, target, HIT_OFFSET )
both objects
-----------------
_________________________________________________________________________________________________
method launch takes real speed, real arc returns nothing
- - -----------------
This launches the missile, it is a seperate method from .create in order to
keep method argument lists reasonably short, the xemissile create method
already takes 6 arguments. You will probably never create an xemissile without
launching it immediately afterwards.
The speed value determines how fast the missile will move and can later be
changed mid-flight, the arc value determines at what angle the missile will be
launched and works the same way as object editor missile arc values.
Example: call somevariable.launch(1000.0, 0.25)
_________________________________________________________________________________________________
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()
_________________________________________________________________________________________________
method setTargetPoint takes real tx, real ty, real tz returns nothing
- - ---------
This method sets the point towards which the xemissile will fly. This point
is already automatically set by the create method, if the xemissile has a
targetUnit set then this point will be constantly updated to the position of
that unit. You can also manually set the target point with this method to
redirect the missile, if you do the missile will forget any homing target
it may have had.
Example: call somevariable.setTargetPoint(GetSpellTargetX(),GetSpellTargetY(),0.0)
_____________________________________________________________________________________________________
delegate xefx
-
A xemissile 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 xemissile.
________________________________________________________________________________
real speed = 0.0
- ------------------------------------------------------
The automatic movement is just a vertical arc that homes in on the target,
just like WC3 attack and spell missiles.
Speed would be the distance a xemissile can move in a second and should be
a positive value or the missile will not cooperate. Speed is automatically
set by the launch method but you can also set it manually after launch.
unit targetUnit = null
real zoffset = 0.0
- ------------------------------------------------------
By setting these two values you can make an xemissile home in on a unit.
These values are automatically set by the xehomingmissile's create method,
but you can also set them manually. If you set the unit to null or if the
unit is removed, the missile will break its homing and land at the unit's
last known coordinates.
xemissile event methods
------------------------
These are methods you may declare on your custom struct in order to listen to
the xemissile events.
________________________________________________________________________________
method onHit takes nothing returns nothing defaults nothing
- - ------------- --------
The onHit method is called when the missile hits its target. The missile will
be destroyed afterwards unless you re-launch it.
________________________________________________________________________________
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.
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
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
//=========================================================================================
globals
private constant boolean ALLOW_OUTSIDE_PLAYABLE_MAP_AREA = false
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns boolean
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local rect map
local rect rc
if ALLOW_OUTSIDE_PLAYABLE_MAP_AREA then
set map = GetWorldBounds()
else
set map = bj_mapInitialPlayableArea
endif
set minx = GetRectMinX(map)
set miny = GetRectMinY(map)
set maxx = GetRectMaxX(map)
set maxy = GetRectMaxY(map)
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
if ALLOW_OUTSIDE_PLAYABLE_MAP_AREA then
call RemoveRect(map)
endif
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddCondition(t, Condition(function dis))
set rc=null
set map = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//**********************************************
//* Rune of Illusions
//*
//* A quick sample for xecast
//*
//*
//I003 : rune of illusion
//A009 : Illusions (dummy)
//----------------------------------------------
scope IllusionRune initializer init
private function itemIdMatch takes nothing returns boolean //If the id of the pickup item
return ( GetItemTypeId(GetManipulatedItem()) == 'I003' ) //matches I003 ...
endfunction //
//====================================================
//: This time we'll use a single xecast instance for
//: all the cases the rune is used. It is recommended
//: to do this since it is more efficient.
//
globals // :We begin by actually declaring our cast global
private xecast cast // variable
endglobals //
private function setItUp takes nothing returns nothing //: In this function,
set cast = xecast.create() // we do some basic setup
set cast.abilityid = 'A009' //The ability Id for our dummy illusions ability
set cast.orderid = 852274 //The order id.
//: The illusions ability is a special case since it does not have an orderstring
// so we are just going to use its order id, every active ability has one,
// I got the id using a detector trigger, but most people would prefer to use
// the order id guide: http://www.wc3campaigns.net/tools/weu/ORDERIDS.xls
//
//
//
endfunction
private function onItemPick takes nothing returns nothing
local unit u = GetTriggerUnit()
set cast.owningplayer = GetOwningPlayer(u) //* The owning player, determines who will own the illusion.
call cast.castOnTarget( u ) //Let's call the castOnTarget method on the Triggering unit.
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger() //The usual Trigger registering
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM) //: Unit pickups item
call TriggerAddCondition(t, Condition(function itemIdMatch)) //:
call TriggerAddAction(t, function onItemPick)
call setItUp() //call our xecast setup function...
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
//**********************************************
//* Sheep staff.
//*
//* A quick sample for xecast.AOEloc
//*
//*
//----------------------------------------------
scope SheepStaff initializer init
private function spellIdMatch takes nothing returns boolean
return (GetSpellAbilityId()=='A006')
endfunction
private function onSpellCast takes nothing returns nothing
local xecast xc = xecast.createA() //CreateA so we don't have to destroy the object after the cast.
local unit u = GetTriggerUnit()
local location loc = GetSpellTargetLoc() //The target point
call SetUnitAnimation(u, "attack") // Let's focus on the look of the item cast for a second...
//==============================================
// Here, we do assignments:
//
set xc.abilityid = 'A005' //* Cast ability A005
set xc.level = GetUnitAbilityLevel(u, 'A006' ) //* Same level as trigger ability
set xc.orderstring = "polymorph" //* The order is polymorph
set xc.owningplayer = GetOwningPlayer(u) //* The owning player, determines targets and bounty credits
call xc.castOnAOELoc( loc, 200.0 ) //* Our castOnAOE call, using the location version
//* A radius of 200.0
// Since createA was used, no need to destroy the xecast object
call RemoveLocation(loc) //We need to clean the point.
set loc=null
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerAddCondition(t, Condition( function spellIdMatch) )
call TriggerAddAction(t, function onSpellCast)
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
set t=null
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
//**********************************************
//* Double gem of green fire!
//* --------------------------
//* A quick sample for xecollider
//*
//*
//----------------------------------------------
scope BiGreenFire initializer init
globals
private constant integer FIRE_BALL_COUNT = 2 //number of fire balls
private constant real DISTANCE = 75.0 //distance between each two fire balls.
endglobals
//
// Yes, usually you'll need to make a struct to use xecollider.
// basically, xecolliders need to know what to do when they hit
// something, when you make your own struct that extends xecollider
// you can declare your own onUnitHit method that will be executed
// everytime the collider hits a unit.
//
// The good thing is that you can use this struct to store
// the missile's information as well.
//
private struct greenMissile extends xecollider
//
// For example, I am storing the owner of the green missile here.
// owner would be the hero that used the item.
//
unit owner
//
// So, the onUnitHit will fire whenever there is a collision
// between the collider and some unit, notice you can use the
// struct's members in this method, this is actualyl a xecollider
// as well, so you can use all xecollider's members and methods in it.
//
method onUnitHit takes unit target returns nothing
// Don't explode if the owner is "hit", often when creating the missile
// it would think it hit the unit that created it...
if (this.owner != target) then
// perform the damage stuff, credit the owner.
call UnitDamageTarget( this.owner, target, 100.0 , true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE , WEAPON_TYPE_WHOKNOWS)
// No, do not call .destroy(), terminate is better
call this.terminate()
endif
endmethod
endstruct
//
// This is the function we set to run whenever the triggerer ability is
// casted.
//
private function onSpellCast takes nothing returns nothing
local unit u = GetTriggerUnit() //The hero casting the spell
local location loc = GetSpellTargetLoc() //The target point
local real x = GetUnitX(u) // The position of the hero
local real y = GetUnitY(u) //
local real ang = Atan2( GetLocationY(loc) - y, GetLocationX(loc) - x) //the angle between the target and the unit
local real ang2
local integer i
local real mx
local real my
local greenMissile xc //notice our variable uses the greenMissile type
call SetUnitAnimation(u, "attack") // Let's focus on the look of the item cast for a second...
call TriggerSleepAction(0.0)
// ang2 would be perpendicular to the other angle, we are using radians so to get
// ang2 we add PI/2 which is equivalent to 90 degrees.
set ang2 = ang + bj_PI/2
set i=0 //
loop // we are creating two missiles but that's too much hassle and repetitive
exitwhen (i==FIRE_BALL_COUNT) // code, just use a loop and one trick for the angle you shall see bellow
if ( (i==FIRE_BALL_COUNT-1) and ( ModuloInteger(FIRE_BALL_COUNT,2)==1) ) then
set mx = x // When FIRE_BALL_COUNT is odd, create the last fire ball
set my = y // right in the middle.
else
set mx = x + (DISTANCE*(i/2+1))*Cos(ang2) //Getting the position for the missile using a polar
set my = y + (DISTANCE*(i/2+1))*Sin(ang2) //projections. Increase distance by 75 every 2 steps.
endif
set xc = greenMissile.create(mx, my , ang) //create the missile, notice we are using greenMissile for create as well.
// set the model path:
set xc.fxpath = "Abilities\\Weapons\\GreenDragonMissile\\GreenDragonMissile.mdl"
// a flash, see xefx's readme, it just plays a dead animation.
call xc.flash( "Abilities\\Weapons\\GreenDragonMissile\\GreenDragonMissile.mdl" )
set xc.acceleration = 1000.0 // The missile starts with a speed of 0, acceleration of 1000
set xc.maxSpeed = 500.0 // and a speed cap of 500.0,
set xc.expirationTime = 20000.0 // it expires after 2 seconds.
// So, try visualizing those things
set xc.z = 50.0 // some height, since missiles often fly...
set xc.owner = u // important stuff, see how our collider is using the greenMissile struct
// so we can assign the owner, as you saw above the owner determines
// what can hit the missile and who to credit the damage for.
set ang2= bj_PI + ang2 //this is the trick, the second angle will be the opposite to the
// current one.
set i=i+1
endloop
call RemoveLocation(loc) //We need to clean the point.
set u=null
set loc=null // good idea to null handle local variables.
endfunction
//:
//: Now some usual stuff, conditions, events... This is normal Jass, nothing to do with xecollider.
//: So I think I can get away without commenting it...
//:
private function spellIdMatch takes nothing returns boolean
return (GetSpellAbilityId()=='A00A')
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerAddCondition(t, Condition( function spellIdMatch) )
call TriggerAddAction(t, function onSpellCast)
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
set t=null
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library FireNovaStrike initializer init requires xecollider, xedamage
//**************************************************************************************
//* FireNovaStrike
//* ---------------
//* A simple xedamage + xecollider demo.
//* Requirements:
//* - xecollider, xedamage
//* - A triggerer ability, with a unit target. An example would be
//* the FireNovaStrike ability you can find in this map's object editor.
//*
//*************************************************************************************
//==================================================================================
// Config:
//
//
globals
private constant integer SPELL_ID = 'A007' //The triggerer spell id.
private constant integer FIREBALL_COUNT = 20 //How many fire balls to show
endglobals
//==================================================================================
// Let's set the damage options up, this function takes a xedamage object d
// which something else probably instantiated and we would like to configure.
//
private function setupDamageOptions takes xedamage d returns nothing
set d.dtype = DAMAGE_TYPE_FIRE // Do spell, fire (magic) damage
set d.atype = ATTACK_TYPE_NORMAL //
call d.factor( UNIT_TYPE_STRUCTURE , 0.5) //half damage to buildings.
set d.damageEnemies = true // This evil spell pretty much hits
set d.damageAllies = true // anything that is not invulnerable or
set d.damageNeutral = true // the casting hero himSelf... it would
set d.damageSelf = false // be quite dumb if it was able to hit the hero...
endfunction
//**********************************************************************************
globals
private xedamage damageOptions
endglobals
//==================================================================================
// FireBolt struct: to make your xecolliders (in case you wish to detect events...
// you need to extend your own struct, see it as a convenience cause it also saves
// you the problem of attaching stuff (see how I am saving the casting unit in
// the missile's struct so I can do damage correctly)
//
//
private struct FireBolt extends xecollider //* the "extends xecollider" basically means
//* your FireBolt struct represents a xecollider as well
unit castingHero // Accept it, you most likely had to use a struct anyway,
integer level // to at least attach things like these. the extends approach
// helps us adding fields to the missile like this
//==============================================================================
// The missile's onUnitHit method is called everytime it hits a unit.
// So this method is handling the unitHit event, nice?
//
method onUnitHit takes unit hitunit returns nothing
// We want the missile to 'die' automatically on impact, but we don't
// want it to die on any impact, for example, when the missile is created
// it will count as an impact with the casting hero... We are using xedamage's
// damageSelf option to prevent the hero from being targeteable...
// xedamage.allowedTarget returns true if under those options a unit would
// be a valid target for the source, in this case, the source is the casting
// hero (which we are retrieving from the struct's members) while the target
// is the unit that was just hit (which we are retrieving from the method's
// argument)
//
if (damageOptions.allowedTarget( this.castingHero , hitunit ) ) then
//Let's make the unit look as if it is taking fire, how? Let's make a fireball effect explode on them:
call DestroyEffect( AddSpecialEffectTarget("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl",hitunit, "origin") )
// We know it is a valid target, let's damage it?
// we are retrieving the level from the struct's members, and it is used in
// a simple damage formula, for every level the damage increases by 25.
call damageOptions.damageTarget(this.castingHero , hitunit, this.level * 25.0 )
// We wish the missile to explode
call this.terminate()
endif
endmethod
method onDestroy takes nothing returns nothing
set this.castingHero = null //perhaps it is a good idea to null it,
//well, it isn't, but I am making a sample
//of how to use onDestroy here!
// call DestroyEffect( AddSpecialEffect(GetAbilityEffectById('AOcl',EFFECT_TYPE_TARGET,0) , this.x , this.y ) )
// uncomment this and pretty lightning effects will appear whenever one of our fire bolts
// dies, just so you notice how onDestroy is called both after someone calls terminate()
// and after the expiration time ends...
endmethod
//===============================================================================
// just for a sample will use the loopControl event, all right, will make the
// missile 'flash' randomly.
//
method loopControl takes nothing returns nothing
if(GetRandomReal(0,1)<0.02) then //a random condition, 2% chance to flash
//Please notice, a xecollider object also counts as a xefx, so all xefx's
//methods and attributes count, in this case we are calling flash
//
//It makes a special effect's death animation appear on the missile.
//
call this.flash("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl")
endif
//Notice loopControl gets called for the missile every XE_ANIMATION_PERIOD seconds
endmethod
endstruct
private function spellIdMatch takes nothing returns boolean
return (GetSpellAbilityId()==SPELL_ID)
endfunction
private function onSpellEffect takes nothing returns nothing
local integer i=0
local unit tar = GetSpellTargetUnit() //The spell's target
local unit hero = GetTriggerUnit() //The spell's caster
local real x
local real y
local FireBolt fb
local effect fx = AddSpecialEffectTarget("Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl", tar, "origin")
// A warning effect, will destroy this later.
loop
exitwhen i==FIREBALL_COUNT
set x=GetUnitX(hero)
set y=GetUnitY(hero)
// We have pretty much most of the work up there with the fire bolt struct
// and its fields and event handler methods.
//
// Let's finally capitalize that:
//
// The creation method for a xecollider object is (x,y, facing)
// in this case, we wish to create the fire bolt at the hero's position
// facing? Since there are 20 missiles, we want to devide all those 2PI
// radians in 20 equal parts...
//
set fb = FireBolt.create( x, y, i*(2*bj_PI)/ FIREBALL_COUNT )
set fb.fxpath = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
// It is often a good idea to give them missiles a model... Let's use the
// red dragon's missile model.
set fb.speed = 100.0 // We'll use acceleration for these bolts
set fb.acceleration = 250.0 // let them begin with a 100.0 speed, accelerate
set fb.maxSpeed = 1000.0 // them at a ratio of 250 per second, and cap the
// speed at 1000.0
set fb.z = 20.0
set fb.angleSpeed = 1.5 // We'll like the firebolts to home to the spell's target
// we'll first need to give it an angleSpeed, a value of
// 1.5 puts it close to 85 degrees per second (remember
// everything is in radians)
// The angle speed is just how much can the direction
// vary in a second, it determines the homing's accuracy.
//
// Notice how large values here turn the spell into some sort
// of giant homing fire ball, while low values make it
// much closer to a traditional nova.
set fb.targetUnit = tar // As I said, we want the firebolts to home towards
// so, we use the targetUnit field for that.
//
// Try commenting that line out.
set fb.expirationTime = GetRandomReal(2.0,3.0)
// expirationTime determines the duration of the missile, I made things interesting
// by adding a call to random, so not all the fire missiles will end at the same time
// looks less fabricated this way.
// Now, this is good stuff, remember the FireBolt struct is not just a xecollider
// it is also a custom struct we just declared, we added these fields since they
// are useful for the missile's damage:
//
set fb.castingHero = hero
set fb.level = GetUnitAbilityLevel( hero, SPELL_ID)
set i=i+1
endloop
call TriggerSleepAction(0.0)
call SetUnitAnimation( hero, "attack" )
call DestroyEffect(fx)
set fx=null
set tar=null // it is good to null handle variables at the end of a function...
set hero=null
endfunction
//=====================================================================================================
// Init stuff:
//
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
// Initializing the damage options:
set damageOptions=xedamage.create() // first instanciate a xeobject.
call setupDamageOptions(damageOptions) // now call the function we saw before.
//Setting up the spell's trigger:
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function spellIdMatch))
call TriggerAddAction(t, function onSpellEffect)
set t=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library StormyDreams initializer init requires xebasic, xepreload, xefx, xecast, xedamage, Table
//**************************************************************************************
//* StormyDreams
//* ------------
//* Author: Vexorian.
//* Requirements:
//* - xecast, xefx, xepreload
//* - The Table library
//* - A triggerer ability, with a point target. An example would be
//* the StormyDreams ability you can find in this map's object editor.
//* * If you want the synergy/enable bonus casting you need:
//* - An ability to cast, for example the default is a parasite based
//* ability called
//* * Synergy is based on the level of an ability specified by BONUS_SPELL_ID
//* it could then be a passive/active ability, or a buff.
//*
//*************************************************************************************
//==================================================================================
// Config:
//
//
globals
private constant integer SPELL_ID ='A00B' //The triggerer spell id.
private constant integer BONUS_SPELL_ID ='AIt6' //An ability for synergy, the level of this ability affects the bonuslevel argument.
//Right now it is item damage bonus +6 , you can use any ability you want here.
//You may also leave a 0 to disable the synergy
//Having BONUS_SPELL_ID also alters the eye candy (see bellow)
private constant integer CAST_ABILITY_ID ='A00D' //An ability to cast on valid targets when the next conditions are met:
private constant integer CAST_REQUIRED_LEVEL = 0 //Minimum ability level before using the cast ability
private constant integer CAST_REQUIRED_BONUSLEVEL = 1 //Minimum bonus ability level before using the cast ability
//
// MODEL_PATH_(BONUS_)_MISSILE: The missile's model path
// MODEL_PATH_(BONUS_)_FLASH : The flash effect, it gets played constantly on the missile.
//
private constant string MODEL_PATH_MISSILE = "Abilities\\Spells\\Other\\Incinerate\\IncinerateBuff.mdl"
private constant string MODEL_PATH_BONUS_MISSILE = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
private constant string MODEL_PATH_BONUS_FLASH = "Abilities\\Weapons\\VengeanceMissile\\VengeanceMissile.mdx"
private constant string MODEL_PATH_FLASH = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdx"
endglobals
private function castOrderId takes nothing returns integer
return OrderId("parasite") //sorry, but it turns out these things can't be declared on global init
endfunction
////
// Some config functions:
// -
// radius: "Radius", somehow determines the curve
// speed : Missile speed
// maxBolts: Max number of missiles.
// missileCollision: Collision size of the missile
// damage: Damage done by the missile during contact.
// missileDistance: Initial target distance (for the first missile)
// distanceIncrement: How much the distance is incremented per missile.
// castRecycleDelay: How much to wait before recycling the caster.
private function radius takes real level returns real
return level*0.0 + 200.0
endfunction
private function speed takes integer level returns integer
return level*0+500
endfunction
private function maxBolts takes integer level returns integer
return 6+level*0
endfunction
private function missileCollision takes real level, real bonuslevel returns real
return 0.0*level+0.0*bonuslevel + 100.0 //65.0
endfunction
private function damage takes real level, real bonuslevel returns real
return 11*level+5*bonuslevel+4.0
endfunction
private function missileScale takes real level, real bonuslevel returns real
return 1.0+level*0.0 + bonuslevel*1.1
endfunction
private function initialDistance takes real level, real bonuslevel returns real
return 500.0 + 0*level+0*bonuslevel
endfunction
private function distanceIncrement takes real level, real bonuslevel returns real
return 100.0 + 0*level+0*bonuslevel
endfunction
private function castRecycleDelay takes real level, real bonuslevel returns real
return 5.0 * level + 0.0*bonuslevel + 0.0
endfunction
globals
private constant integer ALPHA_STARTING_VALUE = 0//255
private constant real ALPHA_INCREMENT_PER_SECOND = 150.0
private constant real FLASH_FX_PERIOD = 0.25 // better as a multiple of CLOCK_TICK
private constant real INITIAL_THROW = 0.05 // Time to wait before beginning to throw missiles
private constant real THROW_PERIOD = 0.35 // Period of time between every consecutive throw.
private constant real THROW_ANIMATION_SPEED = 4.00 //Speed factor during throw animation
private constant real RESTORE_ANIMATION_SPEED = 1.00 //'normal' animation speed
private constant real INITIAL_OFFSET = 60.0
private constant real BEZIER_Z1 = 60.0 //This is equivalent to the throw height
private constant real BEZIER_Z2 = 30.0
private constant real BEZIER_Z3 = 180.0
private constant real BEZIER_Z4 = 0.0 //This is equivalent to the final height
private constant real CLOCK_TICK = XE_ANIMATION_PERIOD //animation timer delay
// Change if necessary:
private constant integer MAX_SPELL_LEVELS = 3
private constant integer MAX_BONUS_LEVELS = 3
endglobals
private function configDamage takes integer level, integer bonuslevel, xedamage d returns nothing
//Read xedamage's documentation to know how to configure this.
//does not support damagetrees
call d.useSpecialEffect(GetAbilityEffectById('AOcl', EFFECT_TYPE_TARGET, 0), "origin")
set d.tag=666
set d.damageAllies = true //True if you want it to hurt allies.
set d.atype = ATTACK_TYPE_NORMAL // NORMAL (spell) attacktype
set d.exception = UNIT_TYPE_FLYING // Don't hit fliers
if(bonuslevel>0) then //* The bonus version:
set d.dtype = DAMAGE_TYPE_UNIVERSAL // UNIVERSAL damage, no building damage reduction
else //* The normal version:
set d.dtype = DAMAGE_TYPE_LIGHTNING // LIGHTNING (magical) damage,
call d.factor(UNIT_TYPE_STRUCTURE, 0.33) // does 1/3 damage to buildings.
endif
endfunction
//===============================================================================
// The code. Sample begins
//
private struct missile
group targetlog
integer level
integer bonuslevel
unit u
real x1
real x2
real x3
real x4
real y1
real y2
real y3
real y4
real s=0
real inc=0.01
xefx fx
real flashtime=0
static integer lastid=0
endstruct
private struct throw
integer level
integer bonuslevel
integer donetimes=0
real remaining=0
integer tog=0
boolean stop=false
real tx
real ty
unit u
endstruct
globals
private missile array V
private throw array VT
private integer N=0
private integer NT=0
private timer T
private Table Tab
private group enumgroup
private boolexpr fetchunits
private unit array U
private integer Un
private xecast Cast
private xedamage array damageconf
endglobals
private function fetchFilterUnit takes nothing returns boolean
set U[Un]=GetFilterUnit()
set Un=Un+1
return false
endfunction
private function processThrow takes throw th returns boolean
local missile m=missile.create()
local integer l=th.level
local real xk
local real yk
local real ang
local real ang2
local real dist
local real ca
local real sa
if(integer(m)>missile.lastid) then
set missile.lastid=integer(m)
set m.targetlog=CreateGroup()
else
call GroupClear(m.targetlog)
endif
call SetUnitTimeScale(th.u,THROW_ANIMATION_SPEED)
set m.x1=GetUnitX(th.u)
set m.y1=GetUnitY(th.u)
set ang=Atan2(th.ty-m.y1,th.tx-m.x1)
set m.x1=m.x1+INITIAL_OFFSET*Cos(ang)
set m.y1=m.y1+INITIAL_OFFSET*Sin(ang)
if(th.tog==0) then
set th.tog=1
set ang2=ang+bj_PI/2
else
set th.tog=0
set ang2=ang-bj_PI/2
endif
set m.fx=xefx.create(m.x1,m.y1,ang2)
set m.fx.x=m.x1
set m.fx.y=m.y1
set m.bonuslevel=th.bonuslevel
set m.level=l
if(th.bonuslevel>0) then
set m.fx.fxpath=MODEL_PATH_MISSILE
else
set m.fx.fxpath=MODEL_PATH_BONUS_MISSILE
endif
set m.fx.scale=missileScale(l,th.bonuslevel)
set m.fx.z=BEZIER_Z1
set dist=initialDistance(l,th.bonuslevel)+th.donetimes*distanceIncrement(l,th.bonuslevel)
set m.x4=m.x1+dist*Cos(ang)
set m.y4=m.y1+dist*Sin(ang)
set ca=radius(l)*Cos(ang2)
set sa=radius(l)*Sin(ang2)
set m.x3=m.x4-4*ca
set m.y3=m.y4-4*sa
set m.x2=m.x1+3*ca
set m.y2=m.y1+3*sa
set m.fx.alpha=ALPHA_STARTING_VALUE
set dist=SquareRoot( (m.x4-m.x1)*(m.x4-m.x1)+(m.y4-m.y1)*(m.y4-m.y1) ) //sqrt is necessary
set m.inc=(speed(l)/dist)*CLOCK_TICK
set m.u=th.u
set V[N]=m
set N=N+1
set th.donetimes=th.donetimes+1
if (th.donetimes>=maxBolts(l)) then
call SetUnitTimeScale(th.u,RESTORE_ANIMATION_SPEED)
set Tab[GetHandleId(th.u)]=0
return true
else
set th.remaining=THROW_PERIOD
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
// Having to type unnecessary returns makes vexorian a dull boy
return false
endif
return false
endfunction
private function onExpire takes nothing returns nothing
local integer i=0
local integer n=N
local missile m
local throw th
local real s
local real x
local real y
local real z
local boolean keep
local integer j
local integer ab
local real fc
local integer k
//missile code
set N=0
loop
exitwhen i==n
set m=V[i]
set s=m.s
set s=s+m.inc
set keep=true
if(s>=1.000000001) then
set s=1
set keep=false
endif
set x=s*s*s*m.x4 + 3*s*s*(1-s)*m.x3 + 3*s*(1-s)*(1-s)*m.x2+(1-s)*(1-s)*(1-s)*m.x1
set y=s*s*s*m.y4 + 3*s*s*(1-s)*m.y3 + 3*s*(1-s)*(1-s)*m.y2+(1-s)*(1-s)*(1-s)*m.y1
set z=s*s*s*BEZIER_Z4 + 3*s*s*(1-s)*BEZIER_Z3 + 3*s*(1-s)*(1-s)*BEZIER_Z2+(1-s)*(1-s)*(1-s)*BEZIER_Z1
set m.fx.xyangle=Atan2(y-m.fx.y,x-m.fx.x)
set m.flashtime=m.flashtime+CLOCK_TICK
set m.fx.alpha=m.fx.alpha+R2I(ALPHA_INCREMENT_PER_SECOND*CLOCK_TICK+0.5)
if (m.flashtime>=FLASH_FX_PERIOD) then
if(m.bonuslevel>0) then
call m.fx.flash(MODEL_PATH_BONUS_FLASH)
else
call m.fx.flash(MODEL_PATH_FLASH)
endif
set m.flashtime=0
endif
set m.fx.x=x
set m.fx.y=y
set Un=0
call GroupEnumUnitsInRange(enumgroup,x,y, missileCollision(m.level,m.bonuslevel)+XE_MAX_COLLISION_SIZE, fetchunits)
set j=0
set k=m.level*(MAX_BONUS_LEVELS+1)+m.bonuslevel //let's save this array index for later.
loop
exitwhen (j==Un)
if (U[j]!=m.u) and (IsUnitInRangeXY(U[j],x,y,missileCollision(m.level,m.bonuslevel))) and not IsUnitInGroup(U[j],m.targetlog) then
set fc=damageconf[k].getTargetFactor(m.u, U[j])
if(fc!=0.0) then
if(m.level>=CAST_REQUIRED_LEVEL) and (m.bonuslevel>=CAST_REQUIRED_BONUSLEVEL) then
set Cast.owningplayer=GetOwningPlayer(m.u)
set Cast.level=m.level
set Cast.recycledelay=castRecycleDelay(m.level,m.bonuslevel)
call Cast.castOnTarget(U[j])
endif
call GroupAddUnit(m.targetlog,U[j])
//notice we are usign forceValue, since we already know the factor, it is also possible to
// call damageTarget directly, but that means the system will calculate the factor twice for no reason.
call damageconf[k].damageTargetForceValue(m.u, U[j] , fc* damage(m.level,m.bonuslevel) )
endif
endif
set j=j+1
endloop
if(not keep) then
call m.fx.destroy()
call GroupClear(m.targetlog)
call m.destroy()
else
set m.s=s
set V[N]=m
set N=N+1
endif
set i=i+1
endloop
//now call throw code, supposedly this is not called as many times as missile code, so
// we might as well use a function call...
set n=NT
set NT=0
set i=0
loop
exitwhen(i==n)
set th=VT[i]
set th.remaining = th.remaining - CLOCK_TICK
if th.stop or ( (th.remaining<=-0.001) and processThrow(th) ) then
call SetUnitTimeScale(th.u,RESTORE_ANIMATION_SPEED)
call th.destroy()
else
set VT[NT]=th
set NT=NT+1
endif
set i=i+1
endloop
if(N==0) and (NT==0) then
call PauseTimer(T)
endif
endfunction
private function onSpellEffect takes nothing returns nothing
local location loc =GetSpellTargetLoc()
local unit u=GetTriggerUnit()
local throw th=throw.create()
set Tab[GetHandleId(u)]=integer(th)
set VT[NT]=th
set th.level=GetUnitAbilityLevel(u,SPELL_ID)
set th.bonuslevel=GetUnitAbilityLevel(u,BONUS_SPELL_ID)
set th.remaining=INITIAL_THROW
set th.tx=GetLocationX(loc)
set th.ty=GetLocationY(loc)
set th.u=u
set NT=NT+1
if(NT==1) then
call TimerStart(T,CLOCK_TICK,true, function onExpire)
endif
set loc=null
set u=null
endfunction
private function onSpellEnd takes nothing returns nothing
local integer key=GetHandleId(GetTriggerUnit())
local throw th=throw(Tab[key])
if(th!=0) then
set th.stop=true
call Tab.flush(key)
endif
endfunction
private function spellIdMatch takes nothing returns boolean
return (SPELL_ID==GetSpellAbilityId())
endfunction
//=====================================================================================================
// Init stuff:
//
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local integer i
local integer j
call XE_PreloadAbility(CAST_ABILITY_ID)
set Cast=xecast.create()
set Cast.abilityid=CAST_ABILITY_ID
set Cast.orderid=castOrderId()
set enumgroup=CreateGroup()
set fetchunits=Condition(function fetchFilterUnit)
set Tab=Table.create()
set T=CreateTimer()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function spellIdMatch))
call TriggerAddAction(t, function onSpellEffect)
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t, Condition(function spellIdMatch))
call TriggerAddAction(t, function onSpellEnd)
set i=1
loop
exitwhen (i>MAX_SPELL_LEVELS)
set j=0
loop
exitwhen (j>MAX_BONUS_LEVELS)
set damageconf[i*(MAX_BONUS_LEVELS+1) +j]=xedamage.create()
call configDamage(i,j, damageconf[i*(MAX_BONUS_LEVELS+1)+j] )
set j=j+1
endloop
set i=i+1
endloop
set t=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//! zinc
/*
* ChainInferno
* ------------
* This is a Zinc sample of xecollider and xedamage usage.
* The spell has many missiles each of them is a xecollider
*
* Each xecollider will do logic similar to a chain spell...
*
*******************************************************************************/
library ChainInferno requires xecollider, xedamage
{
// config stuff:
// The triggerer spell's rawcode:
constant integer SPELL_ID = 'A00C';
// A random off-set for the creation position of each missile.
constant real NOISE = 50.0;
function missileCount(integer level)->integer {
return 3+level;
}
function missileMaxSpeed(integer level)->real {
return 700.0 + level * 0.0;
}
function missileMinSpeed(integer level)->real {
return 100.0 + level * 0.0;
}
function maxTargets(integer level) -> integer {
return 2 + 1 * level;
}
/* The following functions are used when setting up the missiles
As a matter of fact, it is not necessary to use functions like this
you could easily just assign the stuff in the missiles, but these
functions are a common way to have a configuration header
in a spell...
*/
function missileDamage(integer level) -> real {
return 15.0 + level * 3.0;
}
function detectDistance(integer level) -> real {
return 1000.0 + 0.0 * level;
}
function configMissile(xecollider m, integer level) {
//asorted settings for the missiles.
// notice a xecollider object is used, and we can mess with these fields
// in the config section like this thanks to OOPness.
// In the case of this spell, maxSpeed and minSpeed are treated in a
// special way, so they do not get assigned by this function.
// The movement height of each missile:
m.z = 60.0;
//the duration of each missile
m.expirationTime = 5.0 + 0.0 * level ;
// The model used by the missile:
m.fxpath = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl";
m.collisionSize = 50.0 + level * 0.0;
m.acceleration = 1200.0 + level * 0.0;
m.angleSpeed = bj_PI * 4;
}
//likewise, we can configure the xedamage:
function setupDamage(xedamage xd, integer level ) {
xd.damageAllies =false; //
xd.damageEnemies =true; // Hit enemies exclusively
xd.damageNeutral = false; //
xd.exception = UNIT_TYPE_STRUCTURE; //do not hit buildings
xd.dtype = DAMAGE_TYPE_FIRE; // fire (magical) normal damage
xd.atype = ATTACK_TYPE_NORMAL;
}
// -------------------------------------------------------------------------
// code stuff:
//
xedamage xdam[];
struct missile extends xecollider
{
integer level;
integer targetCount = 0;
unit owner;
real time = 0;
group targetLog;
//Follows an example on how to have a custom create on
// a xecollider struct...
//
static method create(real x, real y, real dir)->thistype {
missile this = allocate(x,y,dir); /* allocate of xecollider children has that argument list */
if ( targetLog == null) {
targetLog = CreateGroup();
}
GroupClear(targetLog);
return this;
}
static real targetdist = 0.0; //current target's distance
static missile instance; /* to pass the current instance to the enum function */
method onUnitHit(unit hitUnit) {
if( (hitUnit!=owner) && ! IsUnitInGroup(hitUnit, targetLog ) ) {
//when a new unit is hit:
// Try doing damage, if damage gets done, the target is a valid
// one and we can contnue and show the effect and get a new target
if(! xdam[level].damageTarget(owner, hitUnit, missileDamage(level) ) )
return;
flash("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl");
GroupAddUnit(targetLog, hitUnit) ;
targetCount = targetCount + 1;
if( targetCount == maxTargets(level) ) {
//too many targets, terminate this missile.
terminate();
return;
}
if( ( targetUnit != hitUnit ) && (targetUnit!=null) ) {
//The assigned targetUnit was not hit yet, so continue
// moving against it.
return;
}
instance = this;
targetUnit = null; //Pick a new target, start by setting target to null.
GroupEnumUnitsInRange(enumgroup, x,y , detectDistance(level), function()->boolean{
thistype this = instance; //adopt-a-instance
unit picked = GetFilterUnit();
real dx = x - GetUnitX(picked), dy = y - GetUnitY(picked);
real dis = SquareRoot( dx*dx + dy*dy );
//Pick the best new target.
if ( !IsUnitInGroup(picked,targetLog) && (picked!=owner)
&& ( (targetUnit == null) || (dis < targetdist) )
&& xdam[level].allowedTarget(owner, picked)
)
{
targetdist = dis;
targetUnit = picked;
}
picked = null;
return false;
});
if( targetUnit == null) {
angleSpeed = 0;
forgetTarget();
}
}
}
}
group enumgroup;
function onSpell() {
unit u = GetTriggerUnit();
integer level = GetUnitAbilityLevel(u, SPELL_ID );
integer i;
real tx = GetSpellTargetX(), ty=GetSpellTargetY();
real x = GetUnitX(u), y=GetUnitY(u);
real f = Atan2( ty-y, tx-x );
missile m;
if(xdam[level]==0) {
//all array elements start at 0
//the trick here is to init the xedamage instance for each level only when necessary.
xdam[level] = xedamage.create();
setupDamage(xdam[level], level);
}
for ( 0 <= i < missileCount(level) ) {
//create a new missile:
m = missile.create(x + GetRandomReal(-NOISE, NOISE), y + GetRandomReal(-NOISE, NOISE), f);
// NOISE works just as a random off-set for the missile position, it makes the spell look better.
m.owner = u;
m.targetUnit = GetSpellTargetUnit();
m.level = level;
// call configMissile :
configMissile(m,level);
//This one is important, the secret to the looks of the spell is with doing these tricks, each
// missile starts at a speed slower than the previous one, then the movement is accelerated
// by missileAcceleration(level) until all the missiles have the same speed.
m.maxSpeed =missileMaxSpeed(level);
m.speed = m.maxSpeed - (m.maxSpeed - missileMinSpeed(level) )* (i+1) / missileCount(level);
}
u=null;
}
function onInit() {
//let's init the trigger :
// "When any unit casts SPELL_ID, call onSpelll"
trigger t = CreateTrigger();
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerAddAction(t, function onSpell);
TriggerAddCondition(t, function()->boolean{
return GetSpellAbilityId() == SPELL_ID;
});
//let's init the enumgroup variable:
enumgroup = CreateGroup();
}
}
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Fireblast initializer Init requires xedamage, xemissile
//**************************************************************************************
//* Fireblast
//* ---------------
//* A simple xedamage + xemissile demo.
//* Launches a large projectile which fires smaller projectiles at nearby enemy units
//* as it moves. When the large projectile lands, it will fire one last wave of smaller
//* projectiles at all nearby enemy units.
//* Requirements:
//* - xemissile, xedamage
//* - A triggerer ability, with a point target. An example would be
//* the Fireblast ability you can find in this map's object editor.
//*
//*************************************************************************************
//==================================================================================
// Config:
//
//
globals
private constant integer SPELL_ID = 'A00E' //The triggerer spell id.
private constant real TARGET_RADIUS = 750.0 // In what range to look for child missile targets?
private constant real LAUNCH_PERIOD = 0.2 // How often to launch child missiles in flight?
// Main missile visual settings:
private constant string MAIN_MODEL = "Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl"
private constant real MAIN_SCALE = 2.0
private constant real MAIN_LAUNCH_OFFSET = 60.0
private constant real MAIN_SPEED = 500.0
private constant real MAIN_ARC = 0.25
// Child missile visual settings:
private constant string CHILD_MODEL = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
private constant real CHILD_SCALE = 0.5
private constant real CHILD_SPEED = 1000.0
private constant real CHILD_ARC = 0.15
endglobals
private constant function Damage takes integer level returns real
return 20.0 * level // How much damage each child missile deals.
endfunction
//==================================================================================
// Let's set the damage options up, this function takes a xedamage object d
// which something else probably instantiated and we would like to configure.
//
private function setupDamageOptions takes xedamage d returns nothing
set d.dtype = DAMAGE_TYPE_FIRE // Do spell, fire (magic) damage
set d.atype = ATTACK_TYPE_NORMAL //
set d.exception = UNIT_TYPE_STRUCTURE // Do not hit buildings.
// For everything else, the default xedamage settings should do fine.
endfunction
//**********************************************************************************
globals
private xedamage damageOptions
endglobals
//==================================================================================
// FireblastChild struct: these are our damage-dealing missiles that deal damage.
// If you have used xecollider before, this should look familiar: by extending an
// xehomingmissile we can create our own custom homing missile that includes custom
// data and code.
//
//
struct FireblastChild extends xehomingmissile
private unit caster
private integer level
//==============================================================================
// We can declare a custom create method for our missile, note the parameters used
// when calling .allocate, they must match those of xehomingmissile's create method.
//
static method create takes unit caster, integer level, real x, real y, real z, unit target returns FireblastChild
local FireblastChild this = FireblastChild.allocate(x,y,z, target,60.0)
// Let us configure our projectile's custom data:
set this.caster = caster
set this.level = level
// Time to configure the delegate xefx properties:
set this.fxpath = CHILD_MODEL
set this.scale = CHILD_SCALE
// Launch the newly created missile:
call this.launch(CHILD_SPEED, CHILD_ARC)
return this
endmethod
//==============================================================================
// The onHit method will be called when the missile reaches its target.
// In this case, we use it to deal damage to the targeted unit.
//
method onHit takes nothing returns nothing
call damageOptions.damageTarget(.caster, .targetUnit, Damage(.level))
endmethod
endstruct
//==================================================================================
// FireblastMain struct: the main spell missile. Since it is not a homing missile,
// it extends xemissile instead of xehomingmissile. Again, it contains custom data
// and code that make it a different missile from the FireblastChild one.
//
//
struct FireblastMain extends xemissile
private unit caster
private integer level
//==============================================================================
// We can declare a custom create method for our missile, note the parameters used
// when calling .allocate, they must match those of xemissile's create method.
//
static method create takes unit caster, integer level, real tx, real ty returns FireblastMain
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local real z = GetUnitFlyHeight(caster)+MAIN_LAUNCH_OFFSET
local FireblastMain this = FireblastMain.allocate(x,y,z, tx,ty,0.0)
// Let us configure our projectile's custom data:
set this.caster = caster
set this.level = level
// Time to configure the delegate xefx properties:
set this.fxpath = MAIN_MODEL
set this.scale = MAIN_SCALE
// Launch the newly created missile:
call this.launch(MAIN_SPEED, MAIN_ARC)
return this
endmethod
// Used for launching child missiles periodically:
private real launchtime = 0.0
// Used for obtaining the closest target:
private static group g = CreateGroup()
private static unit closestTarget
private static real closestDistance
private static FireblastMain current
private static method targetEnum takes nothing returns boolean
local unit u = GetFilterUnit()
local real dx = GetUnitX(u) - current.x
local real dy = GetUnitY(u) - current.y
local real distance = dx * dx + dy * dy
if distance < .closestDistance and (damageOptions.allowedTarget( current.caster, u ) ) then
set .closestDistance = distance
set .closestTarget = u
endif
set u = null
return false
endmethod
//===============================================================================
// Let us use the loopControl event to spawn the child missiles periodically.
// We will also use this method to tweak the missile's trajectory.
//
method loopControl takes nothing returns nothing
// Notice loopControl gets called for the missile every XE_ANIMATION_PERIOD seconds
set launchtime = launchtime + XE_ANIMATION_PERIOD
// Fire a new child missile every LAUNCH_PERIOD seconds:
// I use a loop in case LAUNCH_PERIOD is less than XE_ANIMATION_PERIOD,
// in which case multiple missiles may get launched per update.
loop
exitwhen launchtime < LAUNCH_PERIOD
set launchtime = launchtime - LAUNCH_PERIOD
// Find the nearest enemy unit:
set .current = this
set .closestTarget = null
set .closestDistance = TARGET_RADIUS*TARGET_RADIUS
call GroupEnumUnitsInRange(.g, .x,.y, TARGET_RADIUS, Condition(function FireblastMain.targetEnum))
if .closestTarget!=null then
// Create the child missile, its create method should handle the rest.
call FireblastChild.create(.caster, .level,.x,.y,.z, .closestTarget)
endif
endloop
// Also, constantly re-launch the missile to get a more unique movement arc
call this.launch(MAIN_SPEED, MAIN_ARC)
endmethod
//==============================================================================
private static method finalEnum takes nothing returns boolean
local unit u = GetFilterUnit()
local FireblastMain this = .current //Don't feel like typing "current" everywhere.
if (damageOptions.allowedTarget( .caster, u ) ) then
call FireblastChild.create(.caster, .level,.x,.y,.z, u)
endif
set u = null
return false
endmethod
//==============================================================================
// The onHit method will be called when the missile reaches its target.
// In this case, we use it to spawn one last wave of child missiles.
// We could also do this in the onDestroy method, in that case the last
// wave of missiles would be launched even if the main missile was
// prematurely terminated.
//
method onHit takes nothing returns nothing
set .current = this
call GroupEnumUnitsInRange(.g, .x,.y, TARGET_RADIUS, Condition(function FireblastMain.finalEnum))
endmethod
endstruct
//=====================================================================================================
private function spellIdMatch takes nothing returns boolean
return (GetSpellAbilityId()==SPELL_ID)
endfunction
private function onSpellEffect takes nothing returns nothing
local real tx = GetSpellTargetX() //The spell's target, since a recent patch
local real ty = GetSpellTargetY() //we no longer need to use locations for this.
local unit hero = GetTriggerUnit() //The spell's caster
local integer level = GetUnitAbilityLevel(hero, SPELL_ID) //The level of the spell
local FireblastMain fb = FireblastMain.create(hero, level, tx,ty)
set hero=null // it is good to null handle variables at the end of a function...
endfunction
//=====================================================================================================
// Init stuff:
//
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
// Initializing the damage options:
set damageOptions=xedamage.create() // first instanciate a xeobject.
call setupDamageOptions(damageOptions) // now call the function we saw before.
//Setting up the spell's trigger:
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function spellIdMatch))
call TriggerAddAction(t, function onSpellEffect)
set t=null
endfunction
endlibrary
//! zinc
// I use this trigger for experiments
// TODO: remove experiments before release
library xeSandBox {}
//TODO: remove the TODO comment on top
//TODO: remove this comment
//! endzinc
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BoolexprUtils initializer init
globals
boolexpr BOOLEXPR_TRUE=null
boolexpr BOOLEXPR_FALSE=null
endglobals
private function rettrue takes nothing returns boolean
return true
endfunction
private function retfalse takes nothing returns boolean
return false
endfunction
private function init takes nothing returns nothing
set BOOLEXPR_TRUE=Condition(function rettrue)
set BOOLEXPR_FALSE=Condition(function retfalse)
endfunction
endlibrary
//library DemoMap initializer init requires CasterSystem, BoolexprUtils
library DemoMap initializer init requires BoolexprUtils, Table
function Credit takes nothing returns string
return "
|cffffff10XE 0.9
|cff00cccc*** Spells by: |cff0000ffVexorian.|r
|cff00cccc*** Turtle Rock Map by: |cff0000ffBlizzard|r
Give credit if you use it in your map.
"
endfunction
private function GetItemUserData takes item it returns integer
return HandleTable["meh"][it]
endfunction
private function SetItemUserData takes item it, integer x returns nothing
set HandleTable["meh"][it] = x
endfunction
globals
private constant boolean AUTO = true
private constant integer UNITID_TESTHERO = 'N000'
private constant integer UNITID_SOLDIER = 'nsnp'
private constant integer ITEMID_ASSISTANT = 'I000'
private constant integer SPELLID_LEVELUP = 'A001'
private constant integer SPELLID_RESET = 'A000'
private constant integer SPELLID_RAISEFOOTMEN = 'A002'
private constant integer COLORID_PLAYER = 5
private constant integer COLORID_CREEPS = 12
private constant integer COLORID_COMP = 1
private constant string STRING_COMPNAME = "Evil guys"
private constant string MODELPATH_OUT = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportTarget.mdl"
private constant string MODELPATH_IN = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportCaster.mdl"
private constant string MODELPATH_RETRAINING = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
private constant string MSG_SUPERMODE = "|cffffff10CoolDown/ManaCost Disabled|r"
private group enumgroup
private real MapSurface
private real START_LOCATION_X
private real START_LOCATION_Y
private integer array CREATE_ITEMS
private integer CREATE_ITEMS_N=0
endglobals
private function setupItems takes nothing returns nothing
set CREATE_ITEMS[0]='I001'
set CREATE_ITEMS[1]='I002'
set CREATE_ITEMS[2]='I003'
set CREATE_ITEMS[3]='I004'
set CREATE_ITEMS_N=4
endfunction
function DemoMapFilter_IsHero takes nothing returns boolean
return IsUnitType(GetFilterUnit(),UNIT_TYPE_HERO)
endfunction
function DemoMap_H2I takes handle h returns integer
return GetHandleId(h)
endfunction
//Demo map is err, a demo map, so we should be freely use UserDatas on creeps since no decent system would use them on public objects
private struct itemdata
real sx
real sy
boolean sb
endstruct
private function SetupEnumItem takes nothing returns nothing
local item e=GetEnumItem()
local itemdata id = itemdata.create()
set id.sx=GetItemX(e)
set id.sy=GetItemY(e)
set id.sb=true
call SetItemUserData(e, integer(id))
set e=null
endfunction
private function RestoreItemAfterUse takes nothing returns nothing
local item it=GetManipulatedItem()
local itemdata k = itemdata(GetItemUserData(it) )
local real x= k.sx
local real y= k.sy
local integer i=GetItemTypeId(it)
call TriggerSleepAction(0)
if not(k.sb) or (k==0) then
elseif not IsItemOwned(it) and GetWidgetLife(it)<=0 and (StringLength(GetItemName(it))>0 or GetTriggerEventId()==EVENT_PLAYER_UNIT_USE_ITEM)then
call PolledWait(3)
call SetItemUserData(CreateItem(i,x,y), integer(k))
endif
set it=null
endfunction
globals
private unit array footman
endglobals
function SpawnFootmen takes real x,real y,real angle,boolean showeyecandy returns nothing
local integer a=0
loop
exitwhen a>5
if (GetWidgetLife(footman[a])<0.405) or (GetOwningPlayer( footman[a])!=Player(0)) then
set footman[a]=CreateUnit(Player(0),UNITID_SOLDIER,x,y,angle)
else
call DestroyEffect(AddSpecialEffect( MODELPATH_OUT ,GetUnitX(footman[a]),GetUnitY(footman[a])))
call SetUnitPosition(footman[a],x,y)
endif
if showeyecandy then
call DestroyEffect(AddSpecialEffectTarget( MODELPATH_IN, footman[a], "origin") )
endif
set a=a+1
endloop
endfunction
globals
private trigger array trig
endglobals
private function LevelUpDialog takes unit u returns nothing
local dialog prompt=DialogCreate()
local integer n=0
local integer i=0
local integer result=0
local integer T=2
call DialogSetMessage(prompt,"Choose Level")
set trig[0]=CreateTrigger()
call TriggerRegisterDialogEvent(trig[0],prompt)
loop
exitwhen n>=6
set n=n+1
set trig[n]=CreateTrigger()
call TriggerRegisterDialogButtonEvent(trig[n],DialogAddButton(prompt,I2S(IMaxBJ(1,(n-1)*T)),0))
endloop
call DialogDisplay(Player(0),prompt,true)
loop
exitwhen GetTriggerEvalCount(trig[0])>0
call TriggerSleepAction(0)
endloop
set i=0
call DialogClear(prompt)
call DialogDestroy(prompt)
set prompt=null
loop
exitwhen i>n
if GetTriggerEvalCount(trig[i])>0 then
set result=i
endif
call DestroyTrigger(trig[i])
set trig[i]=null
set i=i+1
endloop
set result=IMaxBJ(1,(result-1)*T)
if result<GetHeroLevel(u) then
call DestroyEffect(AddSpecialEffectTarget( MODELPATH_RETRAINING,u,"origin"))
call SetHeroLevelBJ(u,result,true)
call ModifyHeroSkillPoints(u,bj_MODIFYMETHOD_SET,result)
else
call SetHeroLevelBJ(u,result,true)
endif
endfunction
globals
private boolean SuperMode=false
private integer Restores=0
endglobals
function DemoMapSpell takes nothing returns nothing
local unit u=GetTriggerUnit()
local integer s=GetSpellAbilityId()
if (GetTriggerEventId()==EVENT_PLAYER_UNIT_SPELL_ENDCAST) then
if(SuperMode)then
call SetUnitManaPercentBJ(u,100)
call UnitResetCooldown(u)
endif
elseif (s==SPELLID_RESET ) then
set Restores=Restores+1
if (Restores==2) then
set SuperMode=true
call BJDebugMsg(MSG_SUPERMODE)
endif
call SetUnitManaPercentBJ(u,100)
call UnitResetCooldown(u)
call SetUnitLifePercentBJ(u,100)
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Items\\AIre\\AIreTarget.mdl",u,"origin"))
call TriggerSleepAction(2.0)
set Restores=Restores-1
elseif (s==SPELLID_LEVELUP) then
call LevelUpDialog(u)
elseif (s==SPELLID_RAISEFOOTMEN) then
call SpawnFootmen(GetUnitX(u)+300*CosBJ(GetUnitFacing(u)),GetUnitY(u)+300*SinBJ(GetUnitFacing(u)),GetUnitFacing(u),true)
endif
set u=null
endfunction
private function AutoRevive takes nothing returns nothing
local group g=GetUnitsSelectedAll(Player(0))
call ReviveHero(GetTriggerUnit(),START_LOCATION_X,START_LOCATION_Y,true)
call SetUnitColor(GetTriggerUnit(),ConvertPlayerColor( COLORID_PLAYER))
if FirstOfGroup(g)==null then
call SetCameraPosition(START_LOCATION_X,START_LOCATION_Y)
call SelectUnit(GetTriggerUnit(),true)
endif
call DestroyGroup(g)
set g=null
endfunction
private struct unitdata
real sx
real sy
real sf
group camp
integer array items [6]
boolean stored=false
endstruct
private function RespawnCreep takes nothing returns nothing
endfunction
private function DemoMapIsCreep takes nothing returns boolean
return IsUnitOwnedByPlayer(GetFilterUnit(),Player(12))
endfunction
private function InitCreeps takes nothing returns nothing
local group aux=CreateGroup()
local group aux2=CreateGroup()
local group c
local unit u
local unitdata k
local integer a=0
call GroupClear(enumgroup)
call GroupEnumUnitsOfPlayer(enumgroup,Player(12), BOOLEXPR_TRUE )
loop
set u=FirstOfGroup(enumgroup)
exitwhen u==null
call GroupRemoveUnit(enumgroup,u)
set c=CreateGroup()
call GroupClear(aux)
loop
call GroupRemoveUnit(enumgroup, u)
call GroupRemoveUnit(aux, u)
if (GetUnitUserData(u)==0) then
call GroupClear(aux2)
call GroupEnumUnitsInRange(aux2,GetUnitX(u),GetUnitY(u), 500.0 , BOOLEXPR_TRUE )
call GroupAddGroup(aux2,aux)
call GroupAddUnit(c,u)
set k= unitdata.create()
set k.sx=GetUnitX(u)
set k.sy=GetUnitY(u)
set k.sf=GetUnitFacing(u)
set k.stored=true
set k.camp=c
call SetUnitUserData(u, integer(k))
if IsUnitType(u,UNIT_TYPE_HERO) then
set a=0
loop
exitwhen a>5
set k.items[a]= GetItemTypeId(UnitItemInSlot(u,a))
set a=a+1
endloop
endif
endif
set u=FirstOfGroup(aux)
exitwhen u==null
endloop
endloop
call DestroyGroup(aux)
call DestroyGroup(aux2)
set aux=null
set aux2=null
endfunction
private function doNudge takes nothing returns nothing
call NudgeObjectsInRect( Rect(START_LOCATION_X-500.0,START_LOCATION_Y-500.0, START_LOCATION_X+500.0,START_LOCATION_Y+500.0 ) )
endfunction
private function init2 takes nothing returns nothing
local trigger t
local unit u
local integer cx
local integer i
local real dx
local real dy
set cx=3
loop
call CreateUnit(Player(12),'ngrk',0,0,0)
set cx=cx-1
exitwhen (cx==0)
endloop
set cx=1
loop
call CreateUnit(Player(12),'ngst',0,0,0)
set cx=cx-1
exitwhen (cx==0)
endloop
set cx=1
loop
call CreateUnit(Player(12),'nggr',0,0,0)
set cx=cx-1
exitwhen (cx==0)
endloop
call SetCameraPosition(START_LOCATION_X,START_LOCATION_Y)
set enumgroup=CreateGroup()
set MapSurface = (GetRectMaxX(bj_mapInitialPlayableArea)-GetRectMinX(bj_mapInitialPlayableArea)) * (GetRectMaxY(bj_mapInitialPlayableArea)-GetRectMinY(bj_mapInitialPlayableArea))
set u= CreateUnit(Player(0),UNITID_TESTHERO,START_LOCATION_X,START_LOCATION_Y,0)
set dx=GetRandomReal(-100,100)
set dy=GetRandomReal(-100,100)
set i=CREATE_ITEMS_N-1
loop
exitwhen (i<0)
call CreateItem( CREATE_ITEMS[i],START_LOCATION_X+(i+3)*dx,START_LOCATION_Y+(i+3)*dy )
set i=i-1
endloop
call UnitAddItemToSlotById( u, ITEMID_ASSISTANT, 1)
set u= CreateUnit(Player(0),UNITID_TESTHERO,START_LOCATION_X,START_LOCATION_Y,0)
call UnitAddItemToSlotById( u, ITEMID_ASSISTANT, 1)
// call GroupEnumUnitsOfPlayer(enumgroup, Player(0), Condition(function SetupFilterPlayerUnit) )
//call CreateCasters(12)
set bj_lastCreatedGameCache=InitGameCache("demomap.c")
set t=CreateTrigger()
call EnumItemsInRect(bj_mapInitialPlayableArea, BOOLEXPR_TRUE, function SetupEnumItem )
call TriggerRegisterPlayerUnitEvent(t,Player(0),EVENT_PLAYER_UNIT_PICKUP_ITEM,null)
call TriggerRegisterPlayerUnitEvent(t,Player(0),EVENT_PLAYER_UNIT_USE_ITEM,null)
call TriggerAddAction(t,function RestoreItemAfterUse)
set t=CreateTrigger()
call InitCreeps()
call TriggerRegisterPlayerUnitEvent(t,Player(12),EVENT_PLAYER_UNIT_DEATH,null)
call TriggerAddAction(t,function RespawnCreep)
set t=CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t,Player(0),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
call TriggerRegisterPlayerUnitEvent(t,Player(0),EVENT_PLAYER_UNIT_SPELL_ENDCAST,null)
call TriggerAddAction(t,function DemoMapSpell)
set t=CreateTrigger()
call TriggerRegisterPlayerUnitEventSimple(t,Player(0),EVENT_PLAYER_HERO_REVIVABLE)
call TriggerAddAction(t,function AutoRevive)
set t=CreateTrigger()
call TriggerAddAction(t,function Credit)
call SpawnFootmen(START_LOCATION_X+100,START_LOCATION_Y+100,0,false)
call SetPlayerColorBJ(Player(0),ConvertPlayerColor( COLORID_PLAYER) ,true)
call SetPlayerColorBJ(Player(12),ConvertPlayerColor( COLORID_CREEPS),true)
call SetPlayerColorBJ(Player(1), ConvertPlayerColor(COLORID_COMP),true)
call SetPlayerName(Player(1), STRING_COMPNAME)
call SetPlayerAllianceStateBJ(Player(1),Player(0),bj_ALLIANCE_UNALLIED)
call SetPlayerAllianceStateBJ(Player(0),Player(1),bj_ALLIANCE_UNALLIED)
call EnableCreepSleepBJ(false)
call SetTimeOfDay(12)
call SetTimeOfDayScalePercentBJ(0.00)
//call FogMaskEnableOff()
//call FogEnableOff()
//call TimerStart(CreateTimer(), 0, false, function doNudge)
set t=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local integer i=0
local integer c=0
call setupItems()
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,100000,Credit())
loop
exitwhen (i==12)
if (GetPlayerController(Player(i))==MAP_CONTROL_USER) and (GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING) then
set c=c+1
if(c==2) then
call BJDebugMsg("Sorry, no testing mode enabled")
return
endif
endif
set i=i+1
endloop
set START_LOCATION_X=GetStartLocationX(GetPlayerStartLocation(Player(0)))
set START_LOCATION_Y=GetStartLocationY(GetPlayerStartLocation(Player(0)))
call TriggerAddAction(t, function init2)
if(AUTO) then
call TriggerExecute(t)
else
call TriggerRegisterPlayerChatEvent(t,Player(0),"iddqd",true)
endif
set t=null
endfunction
endlibrary