System OrderCast

RaiJin

New Member
Reaction score
40
JASS:
//***************************************************************************
//** OD ** OrderCast ** By RaiJin ** Version 1.21 **
//***************************************************************************
//
//  What is Order Cast?
//         - OrderCast is a system that reuses dummies instead of constantly
//           creating new ones and destroy them.
//         - You can register an order cast string to an ability code.
//
//     Pros
//         - Very easy to use
//         - GUI'ers could easily use this 
//         - Faster to code with, doing it manually would take much longer than this
//         - If done right then only one dummy will be needed to cast everything (instant spells)
//
//     Cons
//         - If the dummy is overloaded with a number of abilities it may bug
//
//     Functions:
//         - function OD_CastTarget takes player p, unit target, integer abilicode, integer level returns boolean
//         - function OD_CastImmediate takes player p, real x, real y, integer abilicode, integer level returns boolean
//         - function OD_CastPoint takes player p, real x, real y, real x2, real y2, integer abilicode, integer level returns boolean
//         - function OD_CastChannel takes player p, unit target, integer abilicode, integer level returns boolean
//         - function OD_CastOnGroup takes player p, integer abilicode, integer level, group g returns boolean
//         - function OD_CastOnGroupDamage takes unit p, real dmg, attacktype attackType, damagetype damageType, weapontype weaponType, integer abilicode, integer level, group g returns boolean
//         - function OD_CastOnAOE takes player p, real AOE, real x, real y, boolexpr filter, integer abilicode, integer level returns boolean
//         - function OD_CastOnAOEDamage takes unit p, real AOE, real x, real y, boolexpr filter, real dmg, attacktype attackType, damagetype damageType, weapontype weaponType, integer abilicode, integer level returns boolean
//         - function OD_RegisterOrder takes string s, integer abilicode returns nothing    
//         - function OD_RegisterOrderEx takes string s, integer abilicode, integer level returns nothing
//         - all Above functions can have Ex(EXCEPT FOR CHANNEL) at the end of them, Meaning that it will use the instant dummy registered
//            (below for more details)
//
//      Details:
//         - OD is a system I created because I heard its slow to create units.
//           This system preloads dummies on initialization and then reuses them whenever there not needed
//
//         - function OD_RegisterOrder, Registers strings to the abilicode so the system can call it directly instead of the users
//           manually putting them in
//        
//         - function OD_CastPoint is taking 2 x and y's because there might be a need for angle so I put it in there
//          
//         - function OD_RegisterOrderEx, Works like OD_RegisterOrder except it Registers 1 dummy to the skill
//              this means that all casts will be used by that 1 dummy, meaning it will be faster
//                *NOTE: Only use the "Ex" functions if you know your dummy skills are instant
//            
//
//  Thanks:
//         - Jesus4Lyf for adding in some extra functions and for the idea of Hashing the strings <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />
//
//  How to import:
//         - Create a trigger named OD.
//         - Convert it to custom text and replace the whole trigger text with this.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library OD initializer INIT requires KT

globals
    private constant integer       DUMMY_ID       = &#039;h000&#039;  //DUMMYS ID
    private constant integer       HASH_NEXT      = 53
    private constant integer       MAX_HASH_VALUE = 8191
    private constant real          HOLD           = 0.35   //How long it takes to recycle the dummy after being used
                                                           //Setting it to 0 would be best if you know how to set the dummy
                                                           //ability corrently
    private constant string        STOP           = &quot;stop&quot;
    private          integer       COUNT          = 1      // number of dummy&#039;s you start off with
    private          integer       ORDER
    private          integer array HashedInt
    private          integer array CAST_IDS
    private          unit    array UNITS
    private          unit    array INSTANT_UNITS
endglobals

globals
    //locals
    private group   castgroup = CreateGroup()
    private integer HASH      = 0
    private player  OWNER     = null
    private unit    FIRST     = null
endglobals

private function Hash takes integer int returns integer
    local integer hash=int-(int/MAX_HASH_VALUE)*MAX_HASH_VALUE
    loop
        exitwhen HashedInt[hash]==int
        if HashedInt[hash]==0 then
            set HashedInt[hash]=int
            return hash
        endif
        set hash=hash+HASH_NEXT
        if hash&gt;=MAX_HASH_VALUE then
            set hash=hash-MAX_HASH_VALUE
        endif
    endloop
    return hash
endfunction

private struct Data
    unit dummy
    integer Code
    integer orderID
    
    static method create takes unit which, integer CODE returns Data
        local Data d = Data.allocate()
        set d.dummy = which
        set d.Code = CODE
        set d.orderID = GetUnitCurrentOrder( which )
        return d
    endmethod
    
    private method onDestroy takes nothing returns nothing
        call UnitRemoveAbility( .dummy, .Code )
    endmethod
endstruct

private function RemoveDummy takes nothing returns boolean
    local Data d = KT_GetData()
    local integer i = 0
    loop
        exitwhen i == COUNT
        set i = i + 1
        if d.dummy == UNITS<i> then
            call IssueImmediateOrder( UNITS<i>, STOP )
            exitwhen true
        endif
    endloop
    call d.destroy()
    return true
endfunction

private function ChannelEnd takes nothing returns boolean
    local Data d = KT_GetData()
    if GetUnitCurrentOrder( d.dummy ) != d.orderID then
        call IssueImmediateOrder( d.dummy, STOP )
        call d.destroy()
        return true
    endif
    return false
endfunction

private function NewDummy takes nothing returns unit
    local integer i = 0
    loop
        exitwhen i == COUNT
        set i = i + 1
        if GetUnitCurrentOrder( UNITS<i> ) == ORDER then
            return UNITS<i>
        endif
    endloop
    debug call BJDebugMsg(&quot;SYSTEM WARNING - ORDERDUMMY: Exceeded Max Dummies, Extending the number of Max Dummies!!&quot;)
    set COUNT = COUNT + 1
    set UNITS[COUNT] = CreateUnit( Player(14), DUMMY_ID, 0, 0, bj_UNIT_FACING )
    return UNITS[COUNT]
endfunction

public function CastTarget takes player p, unit Widget, integer abilicode, integer level returns boolean
    local unit d
    if Widget == null then
        debug call BJDebugMsg(&quot;Widget unit is NULL skipping the OrderCast&quot;)
        return false
    endif
    set d = NewDummy()
    call SetUnitOwner( d, p, false )
    call UnitAddAbility( d, abilicode )
    if level &gt; 1 then
        call SetUnitAbilityLevel( d, abilicode, level )
    endif
    call SetUnitX( d, GetUnitX( Widget ) )
    call SetUnitY( d, GetUnitY( Widget ) )
    call IssueTargetOrderById( d, CAST_IDS[Hash(abilicode)], Widget )
    call KT_Add( function RemoveDummy, Data.create( d, abilicode ), HOLD )
    return true
endfunction

public function CastTargetEx takes player p, unit Widget, integer abilicode, integer level returns boolean
    if Widget == null then
        debug call BJDebugMsg(&quot;Widget unit is NULL skipping the OrderCast&quot;)
        return false
    endif
    set HASH = Hash(abilicode)
    if INSTANT_UNITS[HASH] == null then
        debug call BJDebugMsg(&quot;The ability &quot;+I2S(abilicode)+&quot; was not registered as an instant cast skipping actions&quot;)
        return CastTarget(p,Widget,abilicode,level)
    endif
    if level &gt; 1 then
        call SetUnitAbilityLevel(INSTANT_UNITS[HASH],abilicode,level)
    endif
    call SetUnitOwner( INSTANT_UNITS[HASH], p, false )
    call IssueTargetOrderById(INSTANT_UNITS[HASH], CAST_IDS[HASH], Widget)
    return true
endfunction

public function CastPoint takes player p, real x, real y, real x2, real y2, integer abilicode, integer level returns boolean
    local unit d = NewDummy()
    call SetUnitOwner( d, p, false )
    call SetUnitX( d, x2 )
    call SetUnitY( d, y2 )
    call UnitAddAbility( d, abilicode )
    if level &gt; 1 then
        call SetUnitAbilityLevel( d, abilicode, level )
    endif
    call IssuePointOrderById( d, CAST_IDS[Hash(abilicode)], x, y )
    call KT_Add( function RemoveDummy, Data.create( d, abilicode ), HOLD )
    return false
endfunction

public function CastPointEx takes player p, real x, real y, real x2, real y2, integer abilicode, integer level returns boolean
    set HASH = Hash(abilicode)
    if INSTANT_UNITS[HASH] == null then
        debug call BJDebugMsg(&quot;The ability &quot;+I2S(abilicode)+&quot; was not registered as an instant cast skipping actions&quot;)
        return CastPoint(p,x,y,x2,y2,abilicode,level)
    endif
    if level &gt; 1 then
        call SetUnitAbilityLevel(INSTANT_UNITS[HASH],abilicode,level)
    endif
    call SetUnitOwner( INSTANT_UNITS[HASH], p, false )
    call SetUnitX( INSTANT_UNITS[HASH], x2 )
    call SetUnitY( INSTANT_UNITS[HASH], y2 )
    call IssuePointOrderById( INSTANT_UNITS[HASH], CAST_IDS[HASH], x, y )
    return false
endfunction

public function CastImmediate takes player p, real x, real y, integer abilicode, integer level returns boolean
    local unit d = NewDummy()
    call SetUnitOwner( d, p, false )
    call UnitAddAbility( d, abilicode )
    if level &gt; 1 then
        call SetUnitAbilityLevel( d, abilicode, level )
    endif
    call SetUnitX( d, x )
    call SetUnitY( d, y )
    call IssueImmediateOrderById( d, CAST_IDS[Hash(abilicode)] )
    call KT_Add( function RemoveDummy, Data.create( d, abilicode ), HOLD )
    return false
endfunction

public function CastImmediateEx takes player p, real x, real y, integer abilicode, integer level returns boolean
    set HASH = Hash(abilicode)
    if INSTANT_UNITS[HASH] == null then
        debug call BJDebugMsg(&quot;The ability &quot;+I2S(abilicode)+&quot; was not registered as an instant cast skipping actions&quot;)
        return CastImmediate(p,x,y,abilicode,level)
    endif
    if level &gt; 1 then
        call SetUnitAbilityLevel(INSTANT_UNITS[HASH],abilicode,level)
    endif
    call SetUnitOwner(INSTANT_UNITS[HASH],p,false)
    call SetUnitX(INSTANT_UNITS[HASH],x)
    call SetUnitY(INSTANT_UNITS[HASH],y)
    call IssueImmediateOrderById(INSTANT_UNITS[HASH],CAST_IDS[HASH])
    return true
endfunction

public function CastChannel takes player p, unit target, integer abilicode, integer level returns boolean
    local unit d
    if target == null then
        debug call BJDebugMsg(&quot;Requested to cast a spell on a null unit, SKIPPING ACTIONS&quot;)
        return false
    endif
    set d = NewDummy()
    call SetUnitOwner( d, p, false )
    call UnitAddAbility( d, abilicode )
    if level &gt; 1 then
        call SetUnitAbilityLevel( d, abilicode, level )
    endif
    call SetUnitX( d, GetUnitX(target) )
    call SetUnitY( d, GetUnitY(target) )
    call IssueTargetOrderById( d, CAST_IDS[Hash(abilicode)], target )
    call KT_Add( function ChannelEnd, Data.create( d, abilicode ), HOLD )
    return true
endfunction

public function CastOnGroup takes player p, integer abilicode, integer level, group g returns boolean
    if g == null then
        debug call BJDebugMsg(&quot;NULL GROUP&quot;)
        return false
    endif
    loop
        set FIRST=FirstOfGroup(g)
        exitwhen FIRST==null
        call CastTarget(p,FIRST,abilicode,level)
        call GroupRemoveUnit(g,FIRST)
    endloop
    return true
endfunction

public function CastOnGroupEx takes player p, integer abilicode, integer level, group g returns boolean
    if g == null then
        debug call BJDebugMsg(&quot;NULL GROUP&quot;)
        return false
    endif
    loop
        set FIRST=FirstOfGroup(g)
        exitwhen FIRST==null
        call CastTargetEx(p,FIRST,abilicode,level)
        call GroupRemoveUnit(g,FIRST)
    endloop
    return true
endfunction

public function CastOnGroupDamage takes unit p, real dmg, attacktype attackType, damagetype damageType, weapontype weaponType, integer abilicode, integer level, group g returns boolean
    set OWNER = GetOwningPlayer(p)
    if g == null then
        debug call BJDebugMsg(&quot;NULL GROUP&quot;)
        return false
    endif
    loop
        set FIRST=FirstOfGroup(g)
        exitwhen FIRST == null
        call CastTarget(OWNER,FIRST,abilicode,level)
        call UnitDamageTarget(p,FIRST,dmg,false,false,attackType,damageType,weaponType)
        call GroupRemoveUnit(g,FIRST)
    endloop
    return true
endfunction

public function CastOnGroupDamageEx takes unit p, real dmg, attacktype attackType, damagetype damageType, weapontype weaponType, integer abilicode, integer level, group g returns boolean
    set OWNER = GetOwningPlayer(p)
    if g == null then
        debug call BJDebugMsg(&quot;NULL GROUP&quot;)
        return false
    endif
    loop
        set FIRST=FirstOfGroup(g)
        exitwhen FIRST == null
        call CastTargetEx(OWNER,FIRST,abilicode,level)
        call UnitDamageTarget(p,FIRST,dmg,false,false,attackType,damageType,weaponType)
        call GroupRemoveUnit(g,FIRST)
    endloop
    return true
endfunction

public function CastOnAOE takes player p, real AOE, real x, real y, boolexpr filter, integer abilicode, integer level returns boolean
    call GroupEnumUnitsInRange( castgroup, x, y, AOE, filter )
    loop
        set FIRST=FirstOfGroup(castgroup)
        exitwhen FIRST == null
        call CastTarget(p,FIRST,abilicode,level)
        call GroupRemoveUnit(castgroup,FIRST)
    endloop
    return true
endfunction

public function CastOnAOEEx takes player p, real AOE, real x, real y, boolexpr filter, integer abilicode, integer level returns boolean
    call GroupEnumUnitsInRange( castgroup, x, y, AOE, filter )
    loop
        set FIRST=FirstOfGroup(castgroup)
        exitwhen FIRST == null
        call CastTargetEx(p,FIRST,abilicode,level)
        call GroupRemoveUnit(castgroup,FIRST)
    endloop
    return false
endfunction

public function CastOnAOEDamage takes unit p, real AOE, real x, real y, boolexpr filter, real dmg, attacktype attackType, damagetype damageType, weapontype weaponType, integer abilicode, integer level returns boolean
    call GroupEnumUnitsInRange(castgroup,x,y,AOE,filter)
    return CastOnGroupDamage(p,dmg,attackType,damageType,weaponType,abilicode,level,castgroup)
endfunction

public function CastOnAOEDamageEx takes unit p, real AOE, real x, real y, boolexpr filter, real dmg, attacktype attackType, damagetype damageType, weapontype weaponType, integer abilicode, integer level returns boolean
    call GroupEnumUnitsInRange(castgroup,x,y,AOE,filter)
    return CastOnGroupDamageEx(p,dmg,attackType,damageType,weaponType,abilicode,level,castgroup)
endfunction

public function RegisterOrder takes string s, integer abilicode returns nothing
    set CAST_IDS[Hash(abilicode)] = OrderId(s)
endfunction

public function RegisterOrderEx takes string s, integer abilicode, integer level returns boolean
    set HASH = Hash(abilicode)
    set CAST_IDS[HASH] = OrderId(s)
    set INSTANT_UNITS[HASH] = CreateUnit( Player(13), DUMMY_ID, 0, 0, bj_UNIT_FACING )
    call UnitAddAbility( INSTANT_UNITS[HASH], abilicode )
    if level &gt; 1 then
        call SetUnitAbilityLevel( INSTANT_UNITS[HASH], abilicode, level )
    endif
    return false
endfunction

private function INIT takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen i == COUNT
        set i = i + 1
        set UNITS<i> = CreateUnit( Player(14), DUMMY_ID, 0, 0, bj_UNIT_FACING )
        call IssueImmediateOrder( UNITS<i>, &quot;stop&quot; )
    endloop
    set ORDER = GetUnitCurrentOrder( UNITS<i> )
endfunction

endlibrary</i></i></i></i></i></i></i>


GUI Example
*NOTE: Remember to Register the Ability Code first

Trigger:
  • Untitled Trigger 001
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Acid Bomb
    • Actions
      • Set TriggerUnit = (Triggering unit)
      • Set TargetUnit = (Target unit of ability being cast)
      • Custom script: call OD_CastTarget( GetOwningPlayer(udg_TriggerUnit), udg_TargetUnit, &#039;A001&#039;, 1 )


or even

Trigger:
  • Untitled Trigger 001
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Acid Bomb
    • Actions
      • Custom script: call OD_CastTarget( GetOwningPlayer(GetTriggerUnit()), GetSpellTargetUnit(), &#039;A001&#039;, 1 )
JASS demo
JASS:
scope Skill initializer i

private function ACT takes nothing returns nothing
    call OD_CastImmediate( GetOwningPlayer(GetTriggerUnit()), GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), &#039;A001&#039;, 1 )
endfunction

private function i takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddAction( t, function ACT )
    call OD_RegisterOrder( &quot;thunderclap&quot;, &#039;A001&#039; )
endfunction

endscope

CHANGELOG:
Code:
//*******************************
//** ChangeLog
//*******************************

v1.21
-Readjusted Instant Casts

v.1.20
-11 new amazing functions !!
-You can Now Register 1 dummy to a skill(use it for casts that are instant)

v1.02
- The Dummies now Cast by Id's instead of strings ( faster now )

v1.01
-Changed some function names
-Increases Amount of Dummies instead of destroying them
-Added function OD_CastChannel
-Added function OD_CastPoint

v1.00
-Initial Release
 

Jesus4Lyf

Good Idea™
Reaction score
397
Make it so on map init you can specify the order ids for different abil codes, so you can just call with player, target, abil, level...?

What about AoE spells?

Channeling spells? Maybe try waiting til the order id changes?

You don't need max dummies, and you don't need O(n) complexity or removing units. Either of which defeats the point of this. o.o
 

RaiJin

New Member
Reaction score
40
Make it so on map init you can specify the order ids for different abil codes, so you can just call with player, target, abil, level...?

What about AoE spells?

Channeling spells? Maybe try waiting til the order id changes?

You don't need max dummies, and you don't need O(n) complexity or removing units. Either of which defeats the point of this. o.o


Make it so on map init you can specify the order ids for different abil codes, so you can just call with player, target, abil, level...?
> Hm ill do that

What About AoE Spells
> call OrderDummyCastImmediate

Channeling Spells
> yea ill be adding that soon

You don't need max dummies
> hm ok i got it ill fix this up next ver
 

XeNiM666

I lurk for pizza
Reaction score
138
Just a suggestion, maybe since the name of the System is "Reusing Dummies", then why just use only 1 dummy for the functions since it's all instant. Of course you must have a seperate dummy for the Channeling part. So that's 2 dummies instead of creating dummies every function call.

It's just a suggestion. :)
 

RaiJin

New Member
Reaction score
40
Just a suggestion, maybe since the name of the System is "Reusing Dummies", then why just use only 1 dummy for the functions since it's all instant. Of course you must have a seperate dummy for the Channeling part. So that's 2 dummies instead of creating dummies every function call.

It's just a suggestion. :)

ya i already that of that its going to use one dummy then if the system needs to, it'll create a new one and reuse it

thanks for feed back guys + rep

@ Jesus4Lyf
> oh alright ill add that too
 

wraithseeker

Tired.
Reaction score
122
There's something called XE which does all of this. And using 1 dummy for instant spell and XE for the rest is good.
 

Romek

Super Moderator
Reaction score
963
Xe contains a module which allows for very easy dummy casting.
It's called...
...
xecast.

JASS:
library xecast initializer init requires xebasic
//************************************************************************
// xecast 0.5
// ------
//  Because dummy casters REALLY ARE this complicated!
//
//************************************************************************

//===========================================================================================================
 globals
    private constant integer MAXINSTANCES         = 8190 //this is a lot, unless you leak xecast objects
    private constant integer INITIAL_DUMMY_COUNT  = 12
    private constant integer DUMMY_STACK_LIMIT    = 50 //don&#039;t allow to keep more than DUMMY_STACK_LIMIT innactive dummy units

    private constant boolean FORCE_INVISIBLE_CAST = false // If your map does not give visibility to all players, or
                                                          // for other reasons, you might want xecast to work on
                                                          // units that are not visible to the player, in that case
                                                          // change this to true, else it is just a performance loss.
 endglobals

//=========================================================================
// Please notice all textmacros in this library are considered private.
//  in other words: DON&#039;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 = &quot;chainlightning&quot;  //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(&quot;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&#039;s not the case, then you&#039;ll have to increase xecast MAXINSTANCES&quot;)
        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(&quot;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&#039;s not the case, then you&#039;ll have to increase xecast MAXINSTANCES&quot;)
        endif
        set r.oid=orderid
        set r.abilityid=abilityID
        set r.owningplayer=owner
        set r.autodestroy=true
     return r
    endmethod

    //==========================================================================================================
    // Just like create, but the struct instance self destructs after a call to any cast method
    // (Recommended)
    //
    public static method createA takes nothing returns xecast
     local xecast r=xecast.allocate()
        set r.autodestroy=true
     return r
    endmethod

    //==========================================================================================================
    // So, create the dummy, assign options and cast the skill!
    // .castOnTarget(w)       : If you want to hit a widget w with the ability
    // .castOnPoint(x,y)      : If you want to hit a point (x,y) with the ability
    // .castInPoint(x,y)      : For spells like warstomp which do not have a target.
    // .castOnAOE(x,y,radius) : Classic area of effect cast. Considers collision size
    // .castOnGroup(g)        : Cast unit the unit group g, notice it will empty the group yet not destroy it.
    //

    //**********************************************************************************************************
    // The implementation of such methods follows: 
    
    private static  unit array dummystack
    private static  integer    top=0
    private static  unit       instantdummy

    private integer oid=0

    private static timer       gametime
    private static timer       T
    private static unit array  recycle
    private static real array  expiretime
    private static integer     rn=0

    //==========================================================================================================
    // private dorecycle method, sorry but I need this up here.
    //
    private static method dorecycle takes nothing returns nothing
     local unit u =.recycle[0]
     local integer l
     local integer r
     local integer p
     local real    lt
        call UnitRemoveAbility(u,GetUnitUserData(u))
        call SetUnitUserData(u,0)
        call SetUnitFlyHeight(u,0,0)
        call PauseUnit(u,false)
        if(.top==DUMMY_STACK_LIMIT) then
            call RemoveUnit(u)
        else
            set .dummystack[.top]=u
            set .top=.top+1
        endif
        set .rn=.rn-1
        if(.rn==0) then
            return
        endif
        set p=0
        set lt=.expiretime[.rn]
        loop
            set l=p*2+1
            exitwhen l&gt;=.rn
            set r=p*2+2
            if(r&gt;=.rn)then
                if(.expiretime[l]&lt;lt) then
                    set .expiretime[p]=.expiretime[l]
                    set .recycle[p]=.recycle[l]
                    set p=l
                else
                    exitwhen true
                endif
            elseif (lt&lt;=.expiretime[l]) and (lt&lt;=.expiretime[r]) then
                exitwhen true
            elseif (.expiretime[l]&lt;.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&lt;EPSILON) then
            set dummy=.instantdummy
            call SetUnitOwner(dummy,.owningplayer,false)
        elseif (.top&gt;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,&#039;Aloc&#039;)
            call UnitAddAbility(dummy,XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(dummy,XE_HEIGHT_ENABLER)
        endif
        call UnitAddAbility(dummy,.abilityid)
        if(.level&gt;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&gt;=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]&lt;=.cexpire)
                set .recycle[.current]=.recycle[.cparent]
                set .expiretime[.current]=.expiretime[.cparent]
                set .current=.cparent
            endloop
            set .expiretime[.current]=.cexpire
            set .recycle[.current]=dummy
            call SetUnitUserData(dummy,.abilityid)
            call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
        else
            call SetUnitUserData(dummy,0)
            call SetUnitFlyHeight(dummy,0,0)
            call UnitRemoveAbility(dummy,.abilityid)
        endif
    //! endtextmacro

    method castOnTarget takes unit target returns nothing
     local unit dummy
     local unit tar
        //! runtextmacro xecast_allocdummy()
        if (.customsource) then
            call SetUnitX(dummy,.sourcex)
            call SetUnitY(dummy,.sourcey)
            call SetUnitFlyHeight(dummy,.sourcez,0.0)
        else
            call SetUnitX(dummy,GetWidgetX(target))
            call SetUnitY(dummy,GetWidgetY(target))
        endif
        if (FORCE_INVISIBLE_CAST) then
            call UnitShareVision(target, .owningplayer, true)
            call IssueTargetOrderById(dummy,this.oid,target)
            call UnitShareVision(target, .owningplayer, false)
        else
            call IssueTargetOrderById(dummy,this.oid,target)
        endif

        //! runtextmacro xecast_deallocdummy()
        if(.autodestroy ) then
            call this.destroy()
        endif
    endmethod

    //accepts units, items and destructables, if you know it is
    // a unit it is better to use castOnTarget since that would
    // be able to use FORCE_INVISIBLE_CAST if necessary.
    //
    method castOnWidgetTarget takes widget target returns nothing
     local unit dummy
     local unit tar
        //! runtextmacro xecast_allocdummy()
        if (.customsource) then
            call SetUnitX(dummy,.sourcex)
            call SetUnitY(dummy,.sourcey)
            call SetUnitFlyHeight(dummy,.sourcez,0.0)
        else
            call SetUnitX(dummy,GetWidgetX(target))
            call SetUnitY(dummy,GetWidgetY(target))
        endif
        call IssueTargetOrderById(dummy,this.oid,target)

        //! runtextmacro xecast_deallocdummy()
        if(.autodestroy ) then
            call this.destroy()
        endif
    endmethod



    method castOnPoint takes real x, real y returns nothing
     local unit dummy
        //! runtextmacro xecast_allocdummy()
        if (.customsource) then
            call SetUnitX(dummy,.sourcex)
            call SetUnitY(dummy,.sourcey)
            call SetUnitFlyHeight(dummy,.sourcez,0.0)
        else
            call SetUnitX(dummy,x)
            call SetUnitY(dummy,y)
        endif
        call IssuePointOrderById(dummy,this.oid,x,y)
        //! runtextmacro xecast_deallocdummy()
        if(.autodestroy ) then
            call this.destroy()
        endif
    endmethod
    method castOnLoc takes location loc returns nothing
        //debug call BJDebugMsg(&quot;Warning: Locations are in use&quot;)
        //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(&quot;Warning: Locations are in use&quot;)
        //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&#039;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&#039;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,&#039;Aloc&#039;)
            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


Click Me
xebasic can be found in that thread.
The documentation can be found in the map which can be downloaded from that thread.
 

Romek

Super Moderator
Reaction score
963
> Struct interfaces. Bleh.
I prefer them to the usual function syntax.

> What's the point of this when we have Caster System?
What's the point in the Caster System when we have xe?
What's the point in timer attachment systems when we have TimerUtils?
...etc. ;D
 

Viikuna

No Marlo no game.
Reaction score
265
If you do things correctly, casting spells is indeed instant, which means that you only need one dummy to do all the casting. ( Or one dummy per spell, which is maybe lil bit nicer to code in case you do not have any buff system which controlls all the buffing in your map )

But yea, some dummy stack is indeed usefull in case you need your dummies to cast some channeling spells.
 

wraithseeker

Tired.
Reaction score
122
Yes, as long as the spell's entangling roots has no channeling time and dummy's backswing point and such is set to 0.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Liar. (See test map.)

Look, I never want to hear anyone say that again, until it's proven that it's possible. As far as I'm concerned, you either cannot use a single global unit for dummy casting, or you cannot do so reliably.

So give me solid proof. Make my test map work, or from now on I will say it's impossible and that reusing a global dummy is not reliable.
 

Attachments

  • test3.w3x
    11.9 KB · Views: 271

Viikuna

No Marlo no game.
Reaction score
265
Its possible, the trick is to figgure out all the factors that make it impossible and nicely erase them. Just type "AssAssTidies" to your Search thingy and be amazed.


But this isnt really anything new, the knowledge has been there like forever, people just dont get it..

edit. Ive been trying to get people learn this by bitching about creating dummy units during the gameplay, but I was too lazy to keep it going...

Also its funny how my friend Lillerinmuna used global dummy with really badly coded GUI and still many of most skilled Jassers dont get it.. ( Well, its not really a coding thingy, more like general mapping skills or something. )


But yea, your evidence was there all along. If you really wanna know, learn to use Search.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Liar. (See test map.)

Look, I never want to hear anyone say that again, until it's proven that it's possible. As far as I'm concerned, you either cannot use a single global unit for dummy casting, or you cannot do so reliably.

So give me solid proof. Make my test map work, or from now on I will say it's impossible and that reusing a global dummy is not reliable.

In your dummy unit edit the data "spd" as 0, it's done.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top