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

Dummy Unit - Submission

Status
Not open for further replies.

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
JASS:
scope Dummy initializer Init

    globals
        private constant integer DUMMY_ID = 'h000'
        private constant player PLAYER = Player(0)
        private constant integer ABILITY_ID = 'A000'
    endglobals
  
    globals
        private unit tauren
        private Shockwave array waves
        private integer waveCount
        private hashtable hash
    endglobals

    struct Shockwave
  
        real direction
        real speed
        unit dummy
        real maxDist
        real dist
        integer playerNumber
        unit owner
      
        public method Setup takes integer playerNumber, unit owner, integer unitId, real x, real y, real direction returns nothing
            set this.dummy = CreateUnit(Player( PLAYER_NEUTRAL_PASSIVE ), unitId, x, y, direction)
            set this.direction = direction
            set this.playerNumber = playerNumber
            set this.owner = owner
            set this.dist = 0
        endmethod
      
        public method SetMovement takes real maxDist, real direction, real speed returns nothing
            set this.maxDist = maxDist
            set this.direction = direction
            set this.speed = speed
        endmethod
      
        public method Move takes nothing returns nothing
            local real x = GetUnitX(this.dummy)
            local real y = GetUnitY(this.dummy)
            local group g
            local unit u
            call SetUnitFacing(this.dummy, this.direction)
            set x = x + Cos(this.direction * bj_DEGTORAD)*speed
            set y = y + Sin(this.direction *bj_DEGTORAD)*speed
            call SetUnitX(this.dummy, x)
            call SetUnitY(this.dummy, y)
            set this.dist = this.dist + this.speed
            set g = CreateGroup()
            call GroupEnumUnitsInRange(g, x, y, 100, null)
            loop
                set u = FirstOfGroup(g)
                exitwhen u == null
                call GroupRemoveUnit(g, u)
                if(IsPlayerEnemy(GetOwningPlayer(u), Player(this. playerNumber)) and GetWidgetLife(u)>0.405) then
                    call UnitDamageTarget(this.owner, u, 100*0.03, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
                endif
            endloop
            call DestroyGroup(g)
            set g = null
            set u = null
        endmethod
      
        method destroy takes nothing returns nothing
        // null your variables
        call RemoveUnit(this.dummy)
        set this.dummy = null
        call this.deallocate()
endmethod
    endstruct
  
    private function Periodic takes nothing returns nothing
        local integer h = 1
        local Shockwave wave
        local timer t = GetExpiredTimer()
        set h = GetHandleId(t)
        set wave = LoadInteger(hash, h, 0)
        call wave.Move()
        if(wave.dist >= wave. maxDist) then
            call wave.destroy()
            call FlushChildHashtable(hash, h)
            call PauseTimer(t)
            call DestroyTimer(t)
        endif
        set t = null
    endfunction
  
    private function EscButton takes nothing returns nothing
        local real x = GetUnitX(tauren)
        local real y = GetUnitY(tauren)
        local real degrees = GetUnitFacing(tauren)
        local integer i = 0
        local timer t
        local Shockwave wave
        loop
            exitwhen i>=8
            set wave = Shockwave.create()
            call wave.Setup(GetPlayerId(PLAYER),tauren , DUMMY_ID, x, y, degrees+45*i)
            call wave.SetMovement(1000, degrees+45*i, 25)
          
            set t = CreateTimer()
            call TimerStart(t,0.03,true, function Periodic)
            call SaveInteger(hash, GetHandleId(t), 0, wave)
            set i = i + 1
        endloop
        set t = null
    endfunction
  
    private function Init takes nothing returns nothing
  
      
        local real x = GetRectCenterX( bj_mapInitialPlayableArea )
        local real y = GetRectCenterY( bj_mapInitialPlayableArea )
        local trigger trg = CreateTrigger()
        local timer t
        set tauren = CreateUnit(PLAYER, 'Otch', x, y, 0)
        call TriggerRegisterPlayerEvent(trg, PLAYER, EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(trg, function EscButton)
        set t = CreateTimer()
        set hash = InitHashtable()
        set t = null
        set waveCount = 0
        set trg = null
    endfunction



endscope

Feedback would be appreciated. This is my first time using structs. :)
 

Attachments

  • JASS Class Mission 4.w3x
    27.4 KB · Views: 48
I can only test it today evening, but some structs feedback:

I would encapsulate as much as information as possible that are logicaly binded into the struct. For example, the periodic callback, and all timer creation/destruction, is exclusivly used by one Shockwave instance, and that's why it should be part of the Shockwave create/destroy function respectivly.
A static method inside struct can be called by a timer, too, with "function thistype.callback".

I would recommend to enforce a create (static) method, which defines player, angle, speed, max distance - as arguments. Such things are essential for every create, and so it makes sense not to outsource the settings into further functions. From outside, when called, I would call it like: create(Player(0), 270, 500, 1000) ; and it would internaly handle everything.

A very important tip; a struct instance is internaly only an integer, so we can attach a struct instance to a timer. When you do it in combination with a internal callback, then you get rid off the extra global Shockwave array, and replace it by a pretty smart solution.

==

Could you also provide a demo code which covered the eariler part, where one unit casts all spells? One trigger sheet might be just disabled, but I would like to see both working, since they work a bit different.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
I would encapsulate as much as information as possible that are logicaly binded into the struct. For example, the periodic callback, and all timer creation/destruction, is exclusivly used by one Shockwave instance, and that's why it should be part of the Shockwave create/destroy function respectivly.
A static method inside struct can be called by a timer, too, with "function thistype.callback".
That was what I planned to do, but I had problems using methods as callbacks.
I will try using a static method then.
I would recommend to enforce a create (static) method, which defines player, angle, speed, max distance - as arguments. Such things are essential for every create, and so it makes sense not to outsource the settings into further functions. From outside, when called, I would call it like: create(Player(0), 270, 500, 1000) ; and it would internaly handle everything.
I already though about this, but I decided it would be too many arguments, so the line gets very long.
I think I can put Setup into create. SetMovement is an extrta method, so the line is shorter and it can make sense to change direction.
A very important tip; a struct instance is internaly only an integer, so we can attach a struct instance to a timer. When you do it in combination with a internal callback, then you get rid off the extra global Shockwave array, and replace it by a pretty smart solution.
I already changed it to attaching it to the timer. I just forgot to remove the global. :D
Anyway thanks for the tip. I had a lot of problems and thought I made mistakes, so I changed the code several times until I realized, that a unit must have at least 1 movement speed to get moved by SetUnitX/Y.
Could you also provide a demo code which covered the eariler part, where one unit casts all spells? One trigger sheet might be just disabled, but I would like to see both working, since they work a bit different.
I don't have the code anymore, but it wasn't much anyway.

Thanks for the suggestions. I will update it in the evening.

=======================================================================

Update:

create method:
I definitely need the unit type, of the created missile and it's coordinates and most likely also the direction, because I will call CreateUnit(...) in the create method.

The other members can be adjusted after creating the instance, so I could make methods to set them. I could also put everything in the create method, if you think it's better.

I am trying to encapsulate the timer into the struct. I am having problems though since static methods obviously do not have a "this".
My idea would be to have the timer run a static method, that derives the instances from a hashtable attached to the timer.
This seems rather complicated though, so I wonder how I could do it better. I am very new to vJASS, so I don't how this is usually done. :)

=======================================================================

The object data is very tricky. One dummy to cast multiple spells at the same time seems to only work, if its movement speed is set to 0. However the missile dummy must not have 0 to be able to get moved by SetUnitX/Y.
 
Last edited:
I definitely need the unit type, of the created missile and it's coordinates and most likely also the direction, because I will call CreateUnit(...) in the create method.
I guess you might or give the cooridnates, or the tauren unit as parameter, too; would seems ok.

The other members can be adjusted after creating the instance, so I could make methods to set them. I could also put everything in the create method, if you think it's better.
As rule of thump I make it like: Everything that is essential for a instance to work properly, I define as argument in the create method; everything optional is outsourced into methods. (me personaly)

I am trying to encapsulate the timer into the struct. I am having problems though since static methods obviously do not have a "this".
My idea would be to have the timer run a static method, that derives the instances from a hashtable attached to the timer.
Attaching "this" to a timer is the perfect solution. There are TimerUtils which internaly (also) do this attaching directly for you, but from background thinking it's the very same logics. No worries, it's a simple, clean, and fast enough solution, to attach an instance to the timer.

The object data is very tricky. One dummy to cast multiple spells at the same time seems to only work, if its movement speed is set to 0. However the missile dummy must not have 0 to be able to get moved by SetUnitX/Y.
I believe it's maybe do-able with one object editor dummy (there is I think a way to remove the "move" ability from a unit), but it would be also acceptable with using one dummy type for caster, and one for movement for this exercise, I guess.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
I guess you might or give the cooridnates, or the tauren unit as parameter, too; would seems ok.
I prefer coordinates, so you can create missiles wherever you want.

As rule of thump I make it like: Everything that is essential for a instance to work properly, I define as argument in the create method; everything optional is outsourced into methods. (me personaly)
Makes sense. I would say the same. I guess I can make call the SetMovement method in the create method, so you still can change movmement later on.

I also prefer to create the missile for neutral passive because of the score screen to not increase a player's unit count.
I added an owner of the missile, that deals the damage, so the right player gets credit for a kill and the damage event has the correct source.

Do you think it would be a good idea, to have a unit as paramter?
Like this: Shockwave.create(CreateUnit('wave', x, y, ...))

Attaching "this" to a timer is the perfect solution. There are TimerUtils which internaly (also) do this attaching directly for you, but from background thinking it's the very same logics. No worries, it's a simple, clean, and fast enough solution, to attach an instance to the timer.
Ok thank you. I was just worrying that there might be an easier solution that I am not aware of.

I believe it's maybe do-able with one object editor dummy (there is I think a way to remove the "move" ability from a unit), but it would be also acceptable with using one dummy type for caster, and one for movement for this exercise, I guess.
Considering the wave has a model and the dummy not this is essential I guess.
Do you know why the dummy that casts multiple spells at the same time must not have movement? It should not move, as the target point is in its range, but maybe there is a move order when using abilities that somehow blocks the other orders.

=======================================================

Update:

JASS:
scope DummyMissile initializer Init

    globals
        private constant integer DUMMY_ID = 'h001'
        private constant player PLAYER = Player(0)
        private constant integer ABILITY_ID = 'A000'
    endglobals
   
    globals
        private unit tauren
        private hashtable hash
    endglobals

    struct Shockwave
   
        real direction
        real speed
        unit dummy
        real maxDist
        real dist
        unit owner
        timer periodic
       
        public static method create takes unit owner, integer unitId, real x, real y, real direction, real maxDist, real speed returns Shockwave
            local Shockwave this = Shockwave.allocate()
            set this.periodic = CreateTimer()
            set this.dummy = CreateUnit(Player( PLAYER_NEUTRAL_PASSIVE ), unitId, x, y, direction)
            call this.SetMovement(maxDist, direction, speed)
            set this.owner = owner
            set this.dist = 0
            call TimerStart(periodic, 0.03, true, function Shockwave.OnTimer)
            call SaveInteger(hash, GetHandleId(this.periodic), 0, this)
            return this
        endmethod
       
        private static method OnTimer takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local integer h = GetHandleId(t)
            local Shockwave wave = LoadInteger(hash, h, 0)
            call wave.Move()
            set t = null
        endmethod
       
        public method SetMovement takes real maxDist, real direction, real speed returns nothing
            set this.maxDist = maxDist
            set this.direction = direction
            set this.speed = speed
        endmethod
       
       
        private method Move takes nothing returns nothing
            local real x = GetUnitX(this.dummy)
            local real y = GetUnitY(this.dummy)
            local group g
            local unit u
            call SetUnitFacing(this.dummy, this.direction)
            set x = x + Cos(this.direction * bj_DEGTORAD)*speed
            set y = y + Sin(this.direction *bj_DEGTORAD)*speed
            call SetUnitX(this.dummy, x)
            call SetUnitY(this.dummy, y)
            set this.dist = this.dist + this.speed
            if(this.dist > this.maxDist) then
                call this.destroy()
            else
                set g = CreateGroup()
                call GroupEnumUnitsInRange(g, x, y, 100, null)
                loop
                    set u = FirstOfGroup(g)
                    exitwhen u == null
                    call GroupRemoveUnit(g, u)
                    if(IsPlayerEnemy(GetOwningPlayer(u), GetOwningPlayer(this.owner)) and GetWidgetLife(u)>0.405) then
                        call UnitDamageTarget(this.owner, u, 100*0.03, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
                    endif
                endloop
                call DestroyGroup(g)
            endif
            set g = null
            set u = null
        endmethod
       
        public method destroy takes nothing returns nothing
            call RemoveUnit(this.dummy)
            call FlushChildHashtable(hash, GetHandleId(this.periodic))
            call PauseTimer(this.periodic)
            call DestroyTimer(this.periodic)
            set this.dummy = null
            set this.owner = null
            set this.periodic = null
            call this.deallocate()
        endmethod
    endstruct
   
    private function EscButton takes nothing returns nothing
        local real x = GetUnitX(tauren)
        local real y = GetUnitY(tauren)
        local real degrees = GetUnitFacing(tauren)
        local integer i = 0
        local Shockwave wave
        loop
            exitwhen i>=8
            set wave = Shockwave.create(tauren , DUMMY_ID, x, y, degrees+45*i, 1000, 25)
            set i = i + 1
        endloop
    endfunction
   
    private function Init takes nothing returns nothing
   
       
        local real x = GetRectCenterX( bj_mapInitialPlayableArea )
        local real y = GetRectCenterY( bj_mapInitialPlayableArea )
        local trigger trg = CreateTrigger()
        set tauren = CreateUnit(PLAYER, 'Otch', x, y, 0)
        call TriggerRegisterPlayerEvent(trg, PLAYER, EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(trg, function EscButton)
        set hash = InitHashtable()
        set trg = null
    endfunction



endscope

Now the code looks fine I think. Most of the code is within the struct. It is only created once with all its parameters and the rest is handled internally.
Thank you for helping me again.:)

In the attached map I have also added the dummy caster, but the dummy caster trigger is disabled (both triggers work with Esc).
 

Attachments

  • JASS Class Mission 4.w3x
    29.2 KB · Views: 44
prefer coordinates
I think I meant to use unit as parameter, and then to use GetUnitX/Y; result would be same.

I also prefer to create the missile for neutral passive because of the score screen to not increase a player's unit count.
I added an owner of the missile, that deals the damage, so the right player gets credit for a kill and the damage event has the correct source.
In this mission the goal was that the dummy units will deal out the damage, but it is okay, too, if the Tauren gets a member of each instance, as "caster" or so, and deals out the damage. Though, in case the caster is not in game anymore, no damage would be dealt, so I just chose the other way. A combination of caster damage; and if removed then dummy damage, would maybe best but is not really required.
Btw can't you set some things in object editor, so dummy unit won't be accounted for last screen? Iirc you can.

Do you know why the dummy that casts multiple spells at the same time must not have movement?
Units with movement are considred being able to 'turn', and it takes turning time. Without the movement it won't consider any turning rate, and can cast instantly in any direction, and without any duration required to get the correct angle.

====

I believe it should pretty much work, I hope I can test it today evening. Some things I can note though:

  • JASS:
    local Shockwave wave = LoadInteger(hash, h, 0)
    call wave.Move()
    ^Such things is okay, but it's agood habit also always to use the internal nicer syntax, without 'thinking' of the current structname, using 'thistype'.
    JASS:
    local thistype this = LoadInteger(hash, h, 0)
    call this.Move() // or just call .Move()
    .. also something like "return thistype" works for the create. : )

  • It's good habit to make members private, that should not be touched from outside; like "unit" or "timer" ; some others that you would allow to be configured directly might stay public, and then maybe even no extra function is needed for to set new movement.

  • Instead of using one group creation per function call, you could use one static global group variable, and just always use the GroupEnum function on it to get correct units.
    You would never need to destroy the group variable ever again.
I believe it's completed, but will say so after test!
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
Btw can't you set some things in object editor, so dummy unit won't be accounted for last screen? Iirc you can.
Would be nice, if it was possible.
Units with movement are considred being able to 'turn', and it takes turning time. Without the movement it won't consider any turning rate, and can cast instantly in any direction, and without any duration required to get the correct angle.
That explains it, thank you.
I believe it should pretty much work, I hope I can test it today evening. Some things I can note though:
I will keep them in mind. Thanks for telling me.
 
full
 
Status
Not open for further replies.
Top