• 🏆 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!

[vJASS] DummyCaster

JASS:
library DummyCaster/*1.0
*************************************************************************************
*
*   Creates dummy casters for casting spells
*
*   Spells must have 0 manacost. Cast Range and cooldown is not necessary.
*   Supports Channeling spells as well as locking dummy channels to units.
*   Channeling spells, however, cannot be cancelled forcefully.
*   Channeling dummies are locked to their owning unit's position
*
*************************************************************************************
*
*   */ uses/*
*        */ MissileRecycler /* hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
*        */ RegisterPlayerUnitEvent /*  hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*        */ Table/*            hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
*   Recommended
*       OrderIds
*
*************************************************************************************
*
*   API
*
*   static method U2P takes integer abil, integer level,  integer order, unit origin, real x, real y returns nothing
*   - Creates a dummy unit on origin's position, targeting point (x, y)
*   - If the dummy is channeling, it will be locked to its owning unit 
*
*   static method U2W takes integer abil, integer level, integer order, unit origin, widget target returns nothing
*   - Creates a dummy unit on origin's position, targeting widgets
*   - If the dummy is channeling ,it will be locked to its owning unit
*
*   static method P2P takes integer abil, integer level, integer order, player p_owner, real x, real y, real tx, real ty returns nothing
*   - Creates a dummy unit on point (x, y), targeting point (tx, ty)
*
*   static method P2W takes integer abil, integer level, integer order, player p_owner, real x, real y, widget target returns nothing
*   - Creates a dummy unit on point (x, y), targeting widget
*
*   static method PImmediate takes integer abil, integer level, integer order, player p_owner, real x, real y, real facing returns nothing
*   - Creates a dummy on point and issues immediate order
*
*   static method UImmediate takes integer abil, integer level, integer order, unit origin returns nothing
*   - Creates a dummy locked on unit origin, issued immediate order
*
**************************************************************************************
*
*   Credits
*       Bribe
*       Magtheridon96
*
*************************************************************************************/
    globals
        private constant real TIMEOUT = 0.031250000
        private Table tb
    endglobals
    
    //native UnitAlive takes unit id returns boolean
    
    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule
    
    struct DummyCaster
        private unit u
        private unit owner
        private boolean caster
        
        private static constant timer t = CreateTimer()
        
        private static thistype array n
        private static thistype array p
        
        private static method period takes nothing returns nothing
            local thistype this = n[0]
            loop
                exitwhen this == 0
                if UnitAlive(owner) then
                    call SetUnitX(u, GetUnitX(owner))
                    call SetUnitY(u, GetUnitY(owner))
                endif
                set this = n[this]
            endloop
        endmethod
        
        private method insert takes nothing returns nothing
            set n[this] = 0
            set p[this] = p[0]
            set n[p[0]] = this
            set p[0] = this
            
            if p[this] == 0 then
                call TimerStart(t, TIMEOUT, true, function thistype.period)
            endif
        endmethod
        
        private static method new takes real x, real y, real z, real facing, player owner, integer abil, integer level returns thistype
            local thistype this = allocate()
            set u = GetRecycledMissile(x, y, z, facing)
            set tb[GetHandleId(u)] = this
            call PauseUnit(u, false)
            if abil > 0 then
                call UnitAddAbility(u, abil)
            endif
            if level < 1 then
                set level = 1
            endif
            call SetUnitAbilityLevel(u, abil, level)
            if owner != null then
                call SetUnitOwner(u, owner, false)
            endif
            set caster = true
            return this
        endmethod
        
        static method U2P takes integer abil, integer level,  integer order, unit origin, real x, real y returns nothing
            local real ox
            local real oy
            local thistype this
            if UnitAlive(origin) then
                set ox = GetUnitX(origin)
                set oy = GetUnitY(origin)
                set this = new(ox, oy, GetUnitFlyHeight(origin), Atan2(y - oy, x - ox)*bj_RADTODEG, GetOwningPlayer(origin), abil, level)
                call insert()
                set owner = origin
                call IssuePointOrderById(u, order, x, y)
            endif
        endmethod
        
        static method U2W takes integer abil, integer level, integer order, unit origin, widget target returns nothing
            local real ox
            local real oy
            local real tx
            local real ty
            local thistype this
            if UnitAlive(origin) then
                set ox = GetUnitX(origin)
                set oy = GetUnitY(origin)
                set this = new(ox, oy, GetUnitFlyHeight(origin), Atan2(GetWidgetY(target) - oy, GetWidgetX(target) - ox)*bj_RADTODEG, GetOwningPlayer(origin), abil, level)
                call insert()
                set owner = origin
                call IssueTargetOrderById(u, order, target)
            endif
        endmethod
        
        static method P2P takes integer abil, integer level, integer order, player p_owner, real x, real y, real tx, real ty returns nothing
            call IssuePointOrderById(new(x, y, 0, Atan2(ty - y, tx - x)*bj_RADTODEG, p_owner, abil, level).u, order, tx, ty)
        endmethod
        
        static method P2W takes integer abil, integer level, integer order, player p_owner, real x, real y, widget target returns nothing
            call IssueTargetOrderById(new(x, y, 0, Atan2(GetWidgetY(target) - y, GetWidgetX(target) - x)*bj_RADTODEG, p_owner, abil, level).u, order, target)
        endmethod

        static method PImmediate takes integer abil, integer level, integer order, player p_owner, real x, real y, real facing returns nothing
            call IssueImmediateOrderById(new(x, y, 0, facing, p_owner, abil, level).u, order)
        endmethod
        
        static method UImmediate takes integer abil, integer level, integer order, unit origin returns nothing
            local thistype this = new(GetUnitX(origin), GetUnitY(origin), GetUnitFlyHeight(origin), GetUnitFacing(origin), GetOwningPlayer(origin), abil, level)
            call insert()
            set owner = origin
            call IssueImmediateOrderById(u, order)
        endmethod
        
        private static method onFinish takes nothing returns boolean
            local unit dc = GetTriggerUnit()
            local thistype this = tb[GetHandleId(dc)]
            if caster then
                set n[p[this]] = n[this]
                set p[n[this]] = p[this]
                if n[0] == 0 then
                    call PauseTimer(t)
                endif
                call UnitRemoveAbility(dc, GetSpellAbilityId())
                call PauseUnit(dc, true)
                call RecycleMissile(dc)
                set caster = false
            endif
            return false
        endmethod
        
        private static method init takes nothing returns nothing
            set tb = Table.create()
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onFinish)
        endmethod
        implement Init
    endstruct
endlibrary
Example:
JASS:
struct Test extends array
    private static method onCast takes nothing returns boolean
        local integer id = GetSpellAbilityId()
        local unit u = GetTriggerUnit()
        local real i = 36
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        if id == 'oblz' then
            call DummyCaster.U2P('bliz', 1, ORDER_blizzard, u, GetSpellTargetX(), GetSpellTargetY())
            loop
                exitwhen i > 360
                call DummyCaster.P2P('wave', 1, ORDER_shockwave, Player(0), x, y, x + 500*Cos(i*bj_DEGTORAD), y + 500*Sin(i*bj_DEGTORAD))
                set i = i + 36
            endloop
        elseif id == 'stun' then
            call DummyCaster.UImmediate('trun', 1, ORDER_tranquility, u)
            call DummyCaster.PImmediate('star', 1, ORDER_starfall, Player(0), x, y, 0)
        endif
        set u = null
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
        call SetHeroLevel(gg_unit_Hamg_0001, 10, false)
    endmethod
endstruct
 

Attachments

  • Dummy Caster.w3x
    56 KB · Views: 98
Is there a special reason for locking a dummy to a caster?

Some players want to achieve effects like Drain abilities or Leash abilities while moving. It some sort of gives some additional graphic effect.

If it is not necessary then I will remove the API

Why not? Simply grant access to the dummy or via something like .this.cancel()
I know, but i want to remain DummyCaster as a static lib
 
Level 13
Joined
Nov 7, 2014
Messages
571
What are dummy casters usually used for? I think it's mainly for effects like stun (firebolt), revealing (dust of appearance), illusions, etc. that you can't
trigger because there are no natives for those effects. And those spells can be casted instantly so one can get away with only 1 dummy caster and would
not need to recycle dummies.

Some players want to achieve effects like Drain abilities or Leash abilities while moving. It some sort of gives some additional graphic effect.

There are natives for those (AddLightning, AddLightningEx).

But anyway, suppose you really wanted to use some of blizzard's channeled spells (like in the demo map: starfall, tranquility), why are you using a recycling library
intended for a specific kind of projectiles (arrow like, it even has missile in it's name: MissileRecycler), when dummy caster units don't use models and don't need to have a specific facing.
This seems to me like an unnecessary overhead, it's not that hard to write a recycling library and you can make your own dummy unit that has 10000000+ mana
and mana regenration and you can then drop this requirement:
Spells must have 0 manacost.

And about moving the dummy caster constanly to his "owner"'s position, it doesn't seem to work as intended, i.e in the demo map
even though the dummy caster's position is set to that of the archmage the startfall and tranquility's effects are not so what's the point of moving the dummy caster?
Or are those just not good examples maybe?

Also the function names are really hard to remember, what was P2P again? Was it "Peer to Peer", "Pay to Play" or "Point to Point", Idk, can't remember...
 
What are dummy casters usually used for? I think it's mainly for effects like stun (firebolt), revealing (dust of appearance), illusions, etc. that you can't
trigger because there are no natives for those effects. And those spells can be casted instantly so one can get away with only 1 dummy caster and would
not need to recycle dummies.



There are natives for those (AddLightning, AddLightningEx).

But anyway, suppose you really wanted to use some of blizzard's channeled spells (like in the demo map: starfall, tranquility), why are you using a recycling library
intended for a specific kind of projectiles (arrow like, it even has missile in it's name: MissileRecycler), when dummy caster units don't use models and don't need to have a specific facing.
This seems to me like an unnecessary overhead, it's not that hard to write a recycling library and you can make your own dummy unit that has 10000000+ mana
and mana regenration and you can then drop this requirement:


And about moving the dummy caster constanly to his "owner"'s position, it doesn't seem to work as intended, i.e in the demo map
even though the dummy caster's position is set to that of the archmage the startfall and tranquility's effects are not so what's the point of moving the dummy caster?
Or are those just not good examples maybe?

Also the function names are really hard to remember, what was P2P again? Was it "Peer to Peer", "Pay to Play" or "Point to Point", Idk, can't remember...

I thought the system is bugged as well, but it was normal. I remember that even if a unit channels an ability that is instant cast, the effect of the ability remains on it's previous position. I have observed this so many times in Dota (e.g. by using the item Force Staff, which can be proven).

You can also try debugging the position or creating effects on the dummies current position to know if it is bugged :)

The reason for me to use MissileRecycler is that :
1) Recycling(obviously), channeling spells makes me want to use so many dummies.
2) because I have to follow reason #1, It must have instant facing so that it won't have any delays.

Why would you think of other terms besides Point to Point if the library tells you the details itself :) I just did what Maker thinks (he also has the same methods name for is TimedLightning lib, can't blame that)
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Function naming is questionable to say the least.

Honestly, I prefer to code channeled spells standalone and keep dummycaster in simple form.
I'm using even simpler version: call IssueOrder(90000).Immediate(<args>)

Provide demo which compiles - not everyone uses huge orderId global table.
local real tx & local real ty seem useless in U2W.
 
Use proper names:

U2P, P2P, etc is not intuitive enough for function names here.

Don't support channel if it bugs when user aborts it. It needs to support it.

0 mana costs required. This is not compatible with like any normal spells in most maps that are used.

System should not handle it to move dummy unit permanently if not asked for. It should mimic default behaviour. Move -> Abort, etc.

I basicly agree with Aniki's dummy usage critique, he made a good and important point.
 
Top