• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Snippet] DummySpellcast

Level 7
Joined
Apr 27, 2011
Messages
272
JASS:
library DummyCasterUtils // v.1.0.3
//===========================================================================
// DummyCasterUtils by Alain.Mark
//
// -Info-
//     -This library's purpose is to make dummycasting easier.
//
//    -Pros and Cons-
//     -Pros-
//    -small code
//    -only uses a minimal amount of if's
//    -only uses one dummy caster when casting a particular instant spell on multiple targets
//       at once
//    -uses a straight forward but safe auto-deallocation system for dummy casters that does
//      not use any timers or any condition checks (therefore fast :D)
//
//     -Cons-
//       -there's a little restriction in the order of casting spells but it's not that
//       bothersome: ALL INSTANT SPELLS MUST BE CASTED FIRST BEFORE CHANNELING SPELLS IF THEY ARE INSIDE A SINGLE FUNCTION.
//
// -API-
//   	-function AllocateCaster takes player pl returns nothing
//        -allocates a dummy caster for use.
//
//   	-function CastOnTargetLvl takes unit u1, unit u2, integer id, integer oid, integer lvl returns nothing
//        -cast a single target spell on a single target.
//
//     -function CastOnAreaLvl takes unit u1, real x, real y, real r, integer id, integer oid, boolean v, integer lvl returns nothing
//        -cast a single target spell on multiple targets within an area. one good feature of this function is that
//         it only uses one dummy caster if the spell was instant, and will only allocate extra dummy casters for channeled spells.
//
//     -function CastOnPointLvl takes unit u1, real x, real y, real r, real a, integer id, integer oid, integer lvl returns nothing
//        -cast a point target spell. this uses the "Polar Coordinate System", x & y are the coordinates of the spell origin.
//        "r" is the radius(range) while "a" is the polar angle. "http://en.wikipedia.org/wiki/Polar_coordinate_system"
//
//     -function CastPointBlankLvl takes unit u1, real x, real y, integer id, integer oid, integer lvl returns nothing
//        -cast an instant spell.
//
//===========================================================================
    //=======================================================================
    // Configurable Stuff
    //=======================================================================
        globals
            private integer DUMMY_CASTER_ID = 'n000'        // the id of your dummy caster
            private player  DEFAULT_OWNER   = Player(15)    // default owner of the dummy after it was released
        endglobals
        
        private function CastOnAreaLvl_CHECK takes unit u returns boolean
            // i made this configurable so you can add your own touch for CastOnAreaLvl function's filter
            // note: keep this function a one-liner so it can be inlined.
            return not IsUnitType(u,UNIT_TYPE_DEAD)
        endfunction
        
    //=======================================================================
    // dummy caster allocator & deallocator
    //=======================================================================
        globals
            private integer w = 0
            private unit cs
            private unit array rcs
            private trigger DEALLOCATOR=CreateTrigger()
        endglobals
        
    //=======================================================================
        function AllocateCaster takes player pl returns unit
            if(w==0)then
                set cs=CreateUnit(pl,DUMMY_CASTER_ID,0,0,0)
                call TriggerRegisterUnitEvent(DEALLOCATOR,cs,EVENT_UNIT_SPELL_ENDCAST)
            else
                set w=w-1
                set cs=rcs[w]
                call SetUnitOwner(cs,pl,false)
            endif
            return cs
        endfunction
        
    //=======================================================================
        private function DeallocationProcess takes nothing returns boolean
            set cs=GetTriggerUnit()
            set rcs[w]=cs
            set w=w+1
            call UnitRemoveAbility(cs,GetSpellAbilityId())
            call SetUnitOwner(cs,DEFAULT_OWNER,false)
            return false
        endfunction
    
    //=======================================================================
        private module moltres
            private static method onInit takes nothing returns nothing
                call TriggerAddCondition(DEALLOCATOR,Condition(function DeallocationProcess))
            endmethod
        endmodule
        
    //=======================================================================
    // support function for CastOnAreaLvl...
    //=======================================================================
        globals
            private group g=CreateGroup()
        endglobals

    //=======================================================================
        private function CreateTargetGroup takes real x, real y, real r returns nothing
            call GroupEnumUnitsInRange(g,x,y,r,null)
        endfunction
        
    //=======================================================================
        //! textmacro DummyCasterUtils__AddSpellLeveled takes UNIT, ID, LVL
            call UnitAddAbility($UNIT$,$ID$)
            call SetUnitAbilityLevel($UNIT$,$ID$,$LVL$)
        //! endtextmacro
        
    //=======================================================================
        //! textmacro DummyCasterUtils__SetUnitXY takes UNIT, X, Y
            call SetUnitX($UNIT$,$X$)
            call SetUnitY($UNIT$,$Y$)
        //! endtextmacro
        
    //=======================================================================
    
//===========================================================================
    function CastOnTargetLvl takes unit u1, unit u2, integer id, integer oid, integer lvl returns nothing
        //! runtextmacro DummyCasterUtils__AddSpellLeveled("u1","id","lvl")
        //! runtextmacro DummyCasterUtils__SetUnitXY("u1","GetUnitX(u2)","GetUnitY(u2)")
        call IssueTargetOrderById(u1,oid,u2)
    endfunction
    //=======================================================================
        function CastOnTarget takes unit u1, unit u2, integer id, integer oid returns nothing
            call CastOnTargetLvl(u1,u2,id,oid,1)
        endfunction
        
    //=======================================================================
    function CastOnAreaLvl takes unit u1, real x, real y, real r, integer id, integer oid, boolean v, integer lvl returns nothing
        local player pl=GetOwningPlayer(u1)
        set rcs[w]=u1
        set w=w+1
        call SetUnitOwner(u1,DEFAULT_OWNER,false)
        call CreateTargetGroup(x,y,r)
        loop
            set u1=FirstOfGroup(g)
            exitwhen u1==null
            exitwhen not CastOnAreaLvl_CHECK(u1)
            if((IsUnitAlly(u1,pl) and v) or (IsUnitEnemy(u1,pl) and not v))then
                call CastOnTargetLvl(AllocateCaster(pl),u1,id,oid,lvl)
            endif
            call GroupRemoveUnit(g,u1)
        endloop
    endfunction
    //=======================================================================
        function CastOnArea takes unit u1, real x, real y, real r, integer id, integer oid, boolean v returns nothing
            call CastOnAreaLvl(u1,x,y,r,id,oid,v,1)
        endfunction
    
    //=======================================================================
    function CastOnPointLvl takes unit u1, real x, real y, real r, real a, integer id, integer oid, integer lvl returns nothing
        //! runtextmacro DummyCasterUtils__AddSpellLeveled("u1","id","lvl")
        //! runtextmacro DummyCasterUtils__SetUnitXY("u1","x","y")
        call IssuePointOrderById(u1,oid,Cos(a)*r,Sin(a)*r)
    endfunction
    //=======================================================================
        function CastOnPoint takes unit u1, real x, real y, real r, real a, integer id, integer oid returns nothing
            call CastOnPointLvl(u1,x,y,r,a,id,oid,1)
        endfunction
        
    //=======================================================================
    function CastPointBlankLvl takes unit u1, real x, real y, integer id, integer oid, integer lvl returns nothing
        //! runtextmacro DummyCasterUtils__AddSpellLeveled("u1","id","lvl")
        //! runtextmacro DummyCasterUtils__SetUnitXY("u1","x","y")
        call IssueImmediateOrderById(u1,oid)
    endfunction
    //=======================================================================
        function CastPointBlank takes unit u1, real x, real y, integer id, integer oid returns nothing
            call CastPointBlankLvl(u1,x,y,id,oid,1)
        endfunction
        
//===========================================================================
    private struct s extends array
        implement moltres
    endstruct
    
//===========================================================================
endlibrary
 
Last edited:
You don't have to update it because this already exists.

This is going to support Channeling dummies (Something Nes' DummyCaster doesn't support), so give it a chance.

Your functions should look like this:

JASS:
function CastSpellOnXXXX takes integer abilityId, integer orderId, widget target /* or coordinates or nothing depending on the function */, boolean instant returns nothing
    get a caster from the stack here
    add the ability
    Issue an order
    if instant then
        remove the ability
        release unit to stack
    else
        attach a boolean (true) to the unit with a Table (hashtable) so you can tell whether you should release him into the stack or not
        also attach abilityId to this unit
    endif
endfunction

function run takes nothing returns nothing
    if (lookup boolean from Table) then
        store boolean (false) in Table
        release unit to stack
        remove (load abilityId from Table) from the unit
    endif
endfunction

//Another function:
function onInit takes nothing returns nothing
    Register a player unit event for EVENT_PLAYER_UNIT_SPELL_ENDCAST and register function run
endfunction

^
This is a pretty good way.

The reason we're adding and removing abilities is because some abilities may have the same order id
and since there are multiple casters, you don't want to go into the trouble of saving abilities in a list
and adding all the abilities to every dummy caster that gets allocated.
 
Last edited:
Level 7
Joined
Apr 27, 2011
Messages
272
@Magtheridon 96
Instead of using a table how about I'll just use the dummy caster's HP value instead... to index them and therefore make it easier to attach values. =D

edit:
instead of attaching abilityId why not just use GetSpellAbilityId()???
i didn't provide a boolean to determine if it is instant or not because the auto-deallocator manages both dummies that finish instant spells and dummies that finish channeling spells. ;)

JASS:
    //=======================================================================
        private function DeallocateCasterX takes unit u, integer spellid returns nothing
            //call DeallocateCaster(u)
            call SetUnitOwner(u,DEFAULT_OWNER,false)   // these three
            set rcs[w]=u                                             // lines releases
            set w=w+1                                               // them
            call UnitRemoveAbility(u,spellid)
        endfunction
        
    //=======================================================================
        private function EndcastFilter takes nothing returns boolean
            call DeallocCasterX(GetTriggerUnit(),GetSpellAbilityId())
            return false
        endfunction
        
    //=======================================================================
 
Last edited:
instead of attaching abilityId why not just use GetSpellAbilityId()???

I don't know, both are an option.
GetSpellAbilityId() should be faster. (No need for saving and loading)

i didn't provide a boolean to determine if it is instant or not because the auto-deallocator manages both dummies that finish instant spells and dummies that finish channeling spells. ;)

If I were to cast 3 spells in the same thread with your method, I'd have 3 units in the stack. With my method, there would still be 1. (I'm talking about instant spells here)
 
Level 7
Joined
Apr 27, 2011
Messages
272
If I were to cast 3 spells in the same thread with your method, I'd have 3 units in the stack. With my method, there would still be 1. (I'm talking about instant spells here)

Nope. there would only be still 1 unit in my method also, here's screenshot:
all executed in one thread and it even uses CastSpellOnArea() (chicken's the dummy caster) :ogre_icwydt:
(the spells cast were Entangle, Unholy Frenzy, and a Custom spell ALL DONE BY A SINGLE DUMMY CASTER ON 30 UNITS)
c0d1c04f35.png

my method is quick enough that the stack doesn't increment if there were 3 spells cast in the same thread. long story short, the stack only goes up when channeled spells are cast.
 
But wouldn't the End cast functions run after the thread completes it's execution?
Wow... cast events append a thread.

edit

Can and should be avoided.
Rly i already coded a system that allows casting / channeling dummies and everyone at the helper complained on how useless that was, including nes

Well, I have a use for them, thus they are all wrong. Can you link me to your system? You have quite a lot of systems on TH ^.^
 
Level 7
Joined
Apr 27, 2011
Messages
272
Wow... cast events append a thread.
im not sure about that :\

btw.... the end cast functions fire instantly after a unit finishes casting a spell. And it's nearly as fast as a "UnitRemoveAbility()" placed in this function below for example.
JASS:
 function CastIntantSpellOnTarget takes unit caster, unit target, integer spellid, string orderstr returns nothing
	call UnitAddAbility(caster,spellid)
	...
	...
	call IssueTargetOrder(caster,os,target)
	call UnitRemoveAbility(caster,spellid)
 endfunction
 
Try this:

JASS:
function test takes nothing returns nothing
    local unit caster = CreateUnit(Player(0), 'uLoc', 0, 0, 0)
    local unit target = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
    call CastInstantSpellOnTarget(caster, target, 'AHhb', OrderId("holybolt"))
    call BJDebugMsg("2")
    set caster = null
    set target = null
endfunction

And add this line: call BJDebugMsg("1") to the function that executes when you finish casting a spell.

If the game displays

Code:
1
2

Then end-cast events suspend threads.
If it displays

Code:
2
1

Then they don't.

Note: I meant to say suspend. To append is to execute when the thread's done.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
integers doesnt leak also & its more efficient than strings...
and why do you need orderid convertion anyway?...
object mergers is pretty much customizable, instead of making a dummy for every user,
just paste the whole scipt, save and exit then the dummy will be automatically created...

sample;
JASS:
//! external ObjectMerger w3u ushd dumy unam "Dummy Caster" uabi Aloc ucbs 0 ucpt 0 umvs 0 ushu "" umvh 0 umdl "Dummy.mdl" umpi 100000 umpm 100000 umpr 1000 ufoo 0
 
Using the integers is much more efficient and it doesn't matter if it seems unreadable to you because everyone sets the order ids into globals, so it won't matter.

Also, you can include both types of function wrappers so you're not forcing users to work in a specific way.

I myself only use the integers. To assist me is this library: http://www.hiveworkshop.com/forums/jass-resources-412/repo-order-ids-197002/
"unholyfrenzy" would be ORDER_unholyfrenzy.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Talking about orders ids vs orders strings :

It's not really a matter of performance issue.

- some special orders have no string equivalent, such as the "stun" order : 851973
- if you use constant integers instead of string, if you make a typo, it will be found on saving, while not with a string.
- on an irrevelant part it reduces the size of the internal string table, which will increase operations speed on strings.

And finally the worst part : OrderId2String always returns null when it's a loaded game (game which was saved and then reloaded).
Like UnitId2String btw.
I have not tested if this bug was fixed or not with the latest patch though.
 
Level 7
Joined
Apr 27, 2011
Messages
272
what's the problem with "CreateTargetGroup"? It's in-line friendly anyway. Creating another global wouldn't hurt anyway and i can assure safety. (look "bj_lasCreatedGroup" 's name it's really long and i have heard someone saying that long global names are a lil bit slow)

and for the textmacros? they make my work easier and plus i made sure that they don't fuck up other systems.

btw thanks for pointing out the naming mistake... >.> (i had a prototype library that uses DEFAULT_PLAYER)

@Magtheridon96
my "CastOnAreaLvl" never ceases to amaze me ( i successfully used it to cast three spells on 300 units with only a minimal lag as a result... just a quick 8-10% drop in fps... hehehe :D )
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
the thing is you can do it directly and eliminate at least 3 lines of your code...
JASS:
function CastOnAreaLvl takes unit u, real x, real y, real r, integer id, integer oid, boolean v, integer lvl returns nothing
    local unit first
    set rcs[w]=u
    set w=w+1
    GroupEnumUnitsInRange(bj_lastCreatedGroup,x,y,r,null) 
    call SetUnitOwner(u,DEFAULT_OWNER,false)
    loop
        set first=FirstOfGroup(bj_lastCreatedGroup)
        exitwhen first==null
        //replace this if((IsUnitAlly(u,pl)) or (IsUnitEnemy(u,pl)))then by...
        if not IsUnitType(first, UNIT_TYPE_DEAD) then
            call CastOnTargetLvl(AllocateCaster(GetOwningPlayer(u)),first,id,oid,lvl)
        endif
        call GroupRemoveUnit(bj_lastCreatedGroup,first)
    endloop
endfunction

textmacros shouldnt if just one line or two coz not all people who knows Jass
'knows' that stuffs...like this one >>> AddSpellLeveled & SetUnitPosition, just
make it a public function...

bj_lastCreatedGroup doesnt need to be created nor destroyed and its really recommended for instant spells...
 
mckill2009 said:
if textmacro isnt necessary, then dont use it...

then don't use these libraries too? they aren't necessary but like using textmacro in this case, makes ur life easier and better...

Alain.Mark said:
my "CastOnAreaLvl" never ceases to amaze me ( i successfully used it to cast three spells on 300 units with only a minimal lag as a result... just a quick 8-10% drop in fps... hehehe :D )
cool
 
Level 7
Joined
Apr 27, 2011
Messages
272
the thing is you can do it directly and eliminate at least 3 lines of your code...
JASS:
function CastOnAreaLvl takes unit u, real x, real y, real r, integer id, integer oid, boolean v, integer lvl returns nothing
    local unit first
    set rcs[w]=u
    set w=w+1
    GroupEnumUnitsInRange(bj_lastCreatedGroup,x,y,r,null) 
    call SetUnitOwner(u,DEFAULT_OWNER,false)
    loop
        set first=FirstOfGroup(bj_lastCreatedGroup)
        exitwhen first==null
        //replace this if((IsUnitAlly(u,pl)) or (IsUnitEnemy(u,pl)))then by...
        if not IsUnitType(first, UNIT_TYPE_DEAD) then
            call CastOnTargetLvl(AllocateCaster(GetOwningPlayer(u)),first,id,oid,lvl)
        endif
        call GroupRemoveUnit(bj_lastCreatedGroup,first)
    endloop
endfunction

nope. no. way. if you did that all units within the area will be targeted, enemies or not.... the "boolean v" argument is for target alliance check (which i somehow forgot in the documentation blame it on me... )
true= target allies only
false= target enemies only
 
but dude,

if((IsUnitAlly(u,pl)) or (IsUnitEnemy(u,pl)))then

means either enemy or ally which is basically the same as if if GetOwningPlayer(u) != pl then

not to say that u don't filter out dead units in ur function which is not a good idea...

I don't see you using the boolean parameter "v" on ur function...

I think the function should be

JASS:
function CastOnAreaLvl takes unit u, real x, real y, real r, integer id, integer oid, boolean v, integer lvl returns nothing
        local player pl=GetOwningPlayer(u)
        set rcs[w]=u
        set w=w+1
                call SetUnitOwner(u,DEFAULT_OWNER,false)
        call CreateTargetGroup(x,y,r)
        loop
            set u=FirstOfGroup(affected)
            exitwhen u==null
            if((IsUnitAlly(u,pl)) and v and not IsUnitType(u,UNIT_TYPE_DEAD) then
                call CastOnTargetLvl(AllocateCaster(pl),u,id,oid,lvl)
            endif
            if((IsUnitEnemy(u,pl)) and not v and not IsUnitType(u,UNIT_TYPE_DEAD) then
                call CastOnTargetLvl(AllocateCaster(pl),u,id,oid,lvl)
            endif
            call GroupRemoveUnit(affected,u)
        endloop
    endfunction

Also, restricting area spells to ally/enemy only is not good... you should consider the possibly of an area spell which affects all...
 
Level 7
Joined
Apr 27, 2011
Messages
272
you didn't see the parameter "v" because mckill2009 didn't use it (his example code is very different from mine... just look at the first post)

btw if really you want to target all units (allied or not)... you can resolve it with a quick dirt:
JASS:
function TargetAll/*Wrapper*/ takes unit u1, real x, real y, real r, integer id, integer oid, integer lvl returns nothing
	call CastOnAreaLvl(u1,x,y,r,id,oid,true,lvl)
	call CastOnAreaLvl(u1,x,y,r,id,oid,false,lvl)
endfunction
 
No, I looked at ur original post on the first page, the parameter v was never used... it was taken as a parameter but never used on the function

take a look at ur original post...
JASS:
//===========================================================================
    function CastOnAreaLvl takes unit u, real x, real y, real r, integer id, integer oid, boolean v, integer lvl returns nothing
        local player pl=GetOwningPlayer(u)
        set rcs[w]=u
        set w=w+1
                call SetUnitOwner(u,DEFAULT_OWNER,false)
        call CreateTargetGroup(x,y,r)
        loop
            set u=FirstOfGroup(affected)
            exitwhen u==null
            if((IsUnitAlly(u,pl)) or (IsUnitEnemy(u,pl)))then
                call CastOnTargetLvl(AllocateCaster(pl),u,id,oid,lvl)
            endif
            call GroupRemoveUnit(affected,u)
        endloop
    endfunction
    //=======================================================================

and also ur function will select all units not owned by player pl...

also, are units owned by the caster counted as allies? coz if not, then ur work around for all unit target will not work...
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
then don't use these libraries too? they aren't necessary but like using
textmacro in this case, makes ur life easier and better...
actually it can be like this...easy to read...
JASS:
public function AddSpellLeveled takes unit u, integer id, integer lvl returns nothing
    call UnitAddAbility(u, id)
    call SetUnitAbilityLevel(u,id,lvl)
endfunction

@Mark
if((IsUnitAlly(u,pl)) or (IsUnitEnemy(u,pl)))then has an OR so it can target ALL units, so better use the one I posted...
Adik is right, the 'v' is not used, so it's useless :) and that's why I made that code...

EDIT:
JASS:
if not IsUnitType(u,UNIT_TYPE_DEAD) then
    if v and ((IsUnitEnemy(u,pl)) then
        //doo    
    elseif ((IsUnitAlly(u,pl)) then
        //doo
    endif
endif
 
@mckill - you should still check v on the elseif since currently, if v was true but unit is an ally, then the first if line will be false, but the second [elseif] will be true so it will still fire... meaning on ur function, it will always fire for an allied unit regardless of the value of "v"


and it should be the reverse since he said if v is true, it's allies only...

JASS:
if not IsUnitType(u,UNIT_TYPE_DEAD) then
    if v and ((IsUnitAlly(u,pl)) then
        //doo
    elseif ((IsUnitEnemy(u,pl)) and not v then
        //doo
    endif
endif
 
Magtheridon96 said:
You SHOULD NOT check whether the target is an ally or an enemy.
Leave that up to the user because he's going to have to do those checks within his spell anyway.
You should cast the spell on ANY target.

its for the automated AOE casting [CastOnAreaLvl] and not for the single target functions, so I think he should check becoz it automates everything... it wouldn't be nice to have the spell casted on units which shouldn't get affected eh?
 
its for the automated AOE casting [CastOnAreaLvl] and not for the single target functions, so I think he should check becoz it automates everything... it wouldn't be nice to have the spell casted on units which shouldn't get affected eh?

AoE? hmm.. Didn't notice that..
I guess it's fine as long as you allow the user to pick whether he wants to cast spells on all units, allies, or enemies.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
and it should be the reverse since he said if v is true, it's allies only...
it doesnt matter anyway, then do the reverse also
false = target allies only
true = target enemies only

@Mark
the new has OR also...
if((IsUnitAlly(u,pl) and v) or (IsUnitEnemy(u,pl)) and not v)then

you should see post 39 & 40...also you should include IsUnitType(u,UNIT_TYPE_DEAD) coz
your Gruop pickes up even dead units...
 
@mckill2009 - actually, that method is fine because either one of them should be true for the system to work... and they can never be both true... just add the not IsUnitType(u,UNIT_TYPE_DEAD)

JASS:
if((IsUnitAlly(u,pl) and v) or (IsUnitEnemy(u,pl)) and not v)then

//will work the same as
//though maybe using the below function would make it better (less checks in the long run I think)
//because the one liner or will make it such that the two conditions are always checked, while if you do it
//with if-elseif, then the second block will only be checked if the first was false

if ((IsUnitAlly(u,pl) and v) then
elseif (IsUnitEnemy(u,pl)) and not v) then
endif

//the keypoint here was the addition of the "and v" and the "not v"

//but you should really add the unittype check so make it

if not IsUnitType(u, UNIT_TYPE_DEAD) then
    if((IsUnitAlly(u,pl) and v) or (IsUnitEnemy(u,pl)) and not v)then
    endif
endif

or 

//I suggest this one due to the above mentioned things
if not IsUnitType(u, UNIT_TYPE_DEAD) then
   if ((IsUnitAlly(u,pl) and v) then
   elseif (IsUnitEnemy(u,pl)) and not v) then
   endif
endif
 
Top