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

March of the Machines v1.12

  • Like
Reactions: Empirean and ILH
Heavily based on Tinker's March of the Machine from Dota 2. See youtube video of March of the Machine in Dota 2.

Preview GIFs

151030d1450108941-march-machines-v1-00-random.gif



151031d1450109233-march-machines-v1-00-normal.gif



151032d1450109533-march-machines-v1-00-zigzag.gif



Code
JASS:
scope MarchOfTheMachines    
    //v1.12
    //by Flux
    //http://www.hiveworkshop.com/forums/members/flux/
    
    /*
    Description:
    Enlists an army of robotic minions to destroy 
    enemy units in an area around Tinker.
    
    NOTES:
    - optionally uses SpellEffectEvent
    - optionally uses MissileRecyler
    
    SPELL NOTES:
    - The spawn radius is centered and fixed around the 
      cast point, not on Tinker's location.
    - The spawn area is a line with a configured length, 
      which is centered in a configured range behind the 
      targeted cast point.
    - Robots do not spawn at points of the spawning 
      line which are outside of the map boundaries
    - When an enemy comes within collision radius of 
      a robot, the robot deals damage in explosion radius 
      around itself and then disappears.

    
    Credits:
      WILL THE ALMIGHTY - Laser Strike Model (edited)
      Bribe             - SpellEffectEvent and MissileRecycler
      Magtheridon96     - RegisterPlayerUnitEvent
      Vexorian          - Attachable Dummy Model
    */
    native UnitAlive takes unit u returns boolean
    
    //=============================================================
    //====================  CONFIGURATION  ========================
    //=============================================================
    globals
        //Rawcode of the Spell
        private constant integer SPELL_ID = 'Amrc'  
        
        //Rawcode of the map dummy unit
        private constant integer DUMMY_ID = 'dumi'  
        
        //If you have MissileRecyler, set owner of the
        //missiles
        private constant player DUMMY_OWNER = Player(15)
        
        //Path to the robot model file
        private constant string ROBOT_PATH = "Units\\Creeps\\HeroTinkerRobot\\HeroTinkerRobot.mdl"
        
        //attack and damage types
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
        
        //if true, robots will spawn at a random location 
        //if false, the spawn location has a pattern
        private constant boolean RANDOM_SPAWN = false  
        
        //if RANDOM_SPAWN = false, this will determine
        //how many robots spawn in the rectangle width
        //it always spawn at the two edge of the rectangle width
        private constant integer ROBOTS_PER_WIDTH = 8
        
        private constant real ROBOTS_SCALE = 1.10
        
        //if RANDOM_SPAWN = false, setting this to false
        //make the spawn pattern zigzag.
        private constant boolean SPAWN_PATTERN_NORMAL = true
        
        //Visual Effects
        private constant string AREA_SFX = "Abilities\\Spells\\Undead\\ReplenishMana\\SpiritTouchTarget.mdl"
        private constant string SPAWN_SFX = "war3mapImported\\LaserEntry.mdx"
        private constant string EXPLODE_SFX = "Objects\\Spawnmodels\\Human\\FragmentationShards\\FragBoomSpawn.mdl"
        
        //How long will AREA_SFX last
        private constant real AREA_SFX_DURATION = 0.8
        
        //Determines how much AREA_SFX is created
        private constant integer SFX_PER_LENGTH = 8
        private constant integer SFX_PER_WIDTH = 8
        
        //If true, your enemies can also see the AREA_SFX
        private constant boolean SFX_VISIBLE = false
        
        //Spell Periodic Timeout
        private constant real TIMEOUT = 0.03125000
    endglobals
    
    //The radius which determines when a robot has collided
    //with an enemy
    private function CollideRadius takes integer lvl returns real
        return lvl*10 + 40.0
    endfunction
    
    //Robots colliding will explode and deals damage to an 
    //area
    private function ExplodeRadius takes integer lvl returns real
        return lvl*50.0 + 50.0
    endfunction
    
    //When robots collide into enemy units, it will deal damage to
    //the unit it collided with
    private function CollideDamage takes integer lvl returns real
        return lvl*5.0 //5, 10, 15
    endfunction
    
    //When robots collide into enemy units, it will explode and deal
    //damage within a certain ExplodeRadius
    private function ExplodeDamage takes integer lvl returns real
        return lvl*5.0 + 25    //30, 35, 40
    endfunction
    
    //Area of Effect Width
    private function SpellWidth takes integer lvl returns real
        return lvl*100.0 + 600.0
    endfunction
    
    //Area of Effect Length
    private function SpellLength takes integer lvl returns real
        return lvl*100.0 + 600.0
    endfunction
    
    //Spawn Rate of robots
    private function RobotsPerSecond takes integer lvl returns integer
        return lvl*2 + 13
    endfunction
    
    //Robot movement speed
    private function RobotSpeed takes integer lvl returns real
        return lvl*50.0 + 300.0
    endfunction
    
    //How long spawning will last
    private function SpawnDuration takes integer lvl returns real
        return lvl*1.0 + 4.0
    endfunction
    
    //Determines what units will get hit
    private function TargetFilter takes unit robot, unit target returns boolean
        return (IsUnitEnemy(target, GetOwningPlayer(robot)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target))
    endfunction
    //=============================================================
    //==================  END CONFIGURATION  ======================
    //=============================================================
    
    private keyword spell
    
    private struct robot
        private real dist
        private unit u
        private effect model
        private spell s
        readonly thistype next
        readonly thistype prev
        
        private static group g = CreateGroup()
        
        method destroy takes nothing returns nothing
            //Remove Unit
            call DestroyEffect(model)
            static if LIBRARY_MissileRecycler then
                call SetUnitOwner(.u, DUMMY_OWNER, true)
                call RecycleMissile(.u)
            else
                call KillUnit(.u)
            endif
            //Remove from the list
            set .next.prev = .prev
            set .prev.next = .next
            set .u = null
            set .model = null
            call .deallocate()
        endmethod
        
        method move takes nothing returns nothing
            local unit u
            local real x
            local real y
            local boolean explode = false
            if .dist < s.length then
                set x = GetUnitX(.u) + s.dx
                set y = GetUnitY(.u) + s.dy
                set .dist = .dist + s.moveSpeed
                call SetUnitX(.u, x)
                call SetUnitY(.u, y)
                call GroupEnumUnitsInRange(g, x, y, s.radCollide, null)
                loop
                    set u = FirstOfGroup(g)
                    exitwhen u == null
                    call GroupRemoveUnit(g, u)
                    if TargetFilter(.u, u) then
                        //deals collision damage
                        call UnitDamageTarget(.u, u, s.dmgCollide, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                        set u = null
                        set explode = true
                        exitwhen true
                    endif
                endloop
                if explode then
                    call GroupEnumUnitsInRange(g, x, y, s.radExplode, null)
                    loop
                        set u = FirstOfGroup(g)
                        exitwhen u == null
                        call GroupRemoveUnit(g, u)
                        if TargetFilter(.u, u) then
                            //deals collision damage
                            call UnitDamageTarget(.u, u, s.dmgExplode, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                        endif
                    endloop
                    //SFX
                    call DestroyEffect(AddSpecialEffect(EXPLODE_SFX, x, y))
                    call .destroy()
                endif
            else
                call .destroy()
            endif
        endmethod
        
        static method allocateHead takes nothing returns thistype
            local thistype this = allocate()
            set .next = 0
            set .prev = 0
            return this
        endmethod
        
        static method create takes real x, real y, real facing, spell instance returns thistype
            local thistype this = allocate()
            set .s = instance
            //insert in the list
            set .next = .s.head.next
            set .prev = .s.head
            set .prev.next = this
            set .next.prev = this
            set .dist = 0
            static if LIBRARY_MissileRecycler then
                set .u = GetRecycledMissile(x, y, 0, facing*bj_RADTODEG)
                set model = AddSpecialEffectTarget(ROBOT_PATH, .u, "origin")
                call SetUnitOwner(.u, s.owner, true)
            else
                set .u = CreateUnit(s.owner, DUMMY_ID, x, y, facing*bj_RADTODEG)
                set model = AddSpecialEffectTarget(ROBOT_PATH, .u, "origin")
                call PauseUnit(.u, true)
            endif
            if ROBOTS_SCALE != 1 then
                call SetUnitScale(.u, ROBOTS_SCALE, ROBOTS_SCALE, ROBOTS_SCALE)
            endif
            return this
        endmethod
    endstruct
    
    globals
        private constant integer RPW = ROBOTS_PER_WIDTH - 1
    endglobals
    
    private struct spell
    
        //center of the rectangle
        private real x  //point of cast
        private real y  //point of cast
        readonly real dx
        readonly real dy
        
        //spawning counter
        private real spawnCtr
        private real spawnTime
        
        //spell data
        private real duration
        readonly player owner
        readonly real dmgCollide
        readonly real dmgExplode
        readonly real radCollide
        readonly real radExplode
        readonly real moveSpeed
        //spell area of effect
        readonly real angle
        readonly real cosA
        readonly real sinA
        readonly real length
        private real width
        
        //UnitGroup head
        robot head

        static if not RANDOM_SPAWN then
            private real lastSpawnWidth
            static if not SPAWN_PATTERN_NORMAL then
                private boolean spawnWidthIncreasing
            endif
        endif
        //Special effects variables
        private real sfxlength
        private real sfxCtr
        
        private static timer t = CreateTimer()
        private static integer array next
        private static integer array prev
        
        private method destroy takes nothing returns nothing
            //Remove from the List
            set next[prev[this]] = next[this]
            set prev[next[this]] = prev[this]
            if next[0] == 0 then
                call PauseTimer(t)
            endif
            set .owner = null
            call .deallocate()
        endmethod
        
        
        private method periodic takes nothing returns nothing
            local robot r = head.next
            static if RANDOM_SPAWN then
                local real rand = GetRandomReal(-.width, .width)
            endif
            local real x
            local real y
            //sfx variables
            local real sfxwidth
            local real x2   //rotated
            local real y2   //rotated
            local string str
            //Move all the robot
            loop
                exitwhen r == 0
                call r.move()
                set r = r.next
            endloop
            //Update duration
            set .duration = .duration - TIMEOUT
            if .duration < 0 then
                if head.next == 0 then
                    call .destroy()
                endif
            else
                set .spawnCtr = .spawnCtr + TIMEOUT
                if .spawnCtr > .spawnTime then
                    set .spawnCtr = .spawnCtr - .spawnTime
                    //Spawn robots
                    static if RANDOM_SPAWN then
                        set x = .x - 0.5*(.length*cosA - rand*sinA)
                        set y = .y - 0.5*(.length*sinA + rand*cosA)
                    else
                        static if SPAWN_PATTERN_NORMAL then
                            set .lastSpawnWidth = .lastSpawnWidth + 2*.width/RPW
                            if .lastSpawnWidth > .width then
                                set .lastSpawnWidth = -.width
                            endif
                        else
                            if .spawnWidthIncreasing then
                                set .lastSpawnWidth = .lastSpawnWidth + 2*.width/RPW
                                if .lastSpawnWidth > .width then
                                    set .spawnWidthIncreasing = false
                                endif
                                
                            else
                                set .lastSpawnWidth = .lastSpawnWidth - 2*.width/RPW
                                if .lastSpawnWidth < -.width then
                                    set .spawnWidthIncreasing = true
                                endif
                            endif
                        endif
                        set x = .x - 0.5*(.length*cosA - .lastSpawnWidth*sinA)
                        set y = .y - 0.5*(.length*sinA + .lastSpawnWidth*cosA)
                    endif
                    //-------- CREATE ROBOT TYPE STRUCT ----------
                    call robot.create(x, y, .angle, this)
                    
                    call DestroyEffect(AddSpecialEffect(SPAWN_SFX, x, y))
                endif
                //Special Effects
                if .sfxlength <= 0.5*.length then
                    set sfxCtr = sfxCtr + TIMEOUT
                    if sfxCtr > AREA_SFX_DURATION/SFX_PER_LENGTH then
                        set sfxCtr = sfxCtr - AREA_SFX_DURATION/SFX_PER_LENGTH
                        set sfxwidth = -0.5*.width
                        loop
                            exitwhen sfxwidth > 0.5*.width
                            //copy
                            set x = .sfxlength
                            set y = sfxwidth
                            //rotate
                            set x2 = x*cosA - y*sinA
                            set y2 = y*cosA + x*sinA
                            //translate
                            set x = x2 + .x
                            set y = y2 + .y
                            set str = AREA_SFX
                            if not IsPlayerAlly(GetLocalPlayer(), .owner) then
                                set str = ""
                            endif
                            //Create SFX
                            call DestroyEffect(AddSpecialEffect(AREA_SFX, x, y))
                            set sfxwidth = sfxwidth + .width/SFX_PER_WIDTH
                        endloop
                        set .sfxlength = .sfxlength + .length/SFX_PER_LENGTH
                    endif
                endif
            endif
        endmethod
        
        private static method pickAll takes nothing returns nothing
            local thistype this = next[0]
            loop
                exitwhen this == 0
                call .periodic()
                set this = next[this]
            endloop
        endmethod
        
        private static method onCast takes nothing returns boolean
            local thistype this = allocate()
            local unit caster = GetTriggerUnit()
            local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
            set .owner = GetTriggerPlayer() 
            set .x = GetSpellTargetX()
            set .y = GetSpellTargetY()
            set .angle = Atan2(.y - GetUnitY(caster), .x - GetUnitX(caster))
            set .cosA = Cos(.angle)
            set .sinA = Sin(.angle)

            //Level-dependent variables
            set .dmgCollide = CollideDamage(lvl)
            set .dmgExplode = ExplodeDamage(lvl)
            set .radCollide = CollideRadius(lvl)
            set .radExplode = ExplodeRadius(lvl)
            set .duration = SpawnDuration(lvl)
            set .spawnCtr = 0
            set .spawnTime = 1.0/RobotsPerSecond(lvl)
            set .width = SpellWidth(lvl)
            set .length = SpellLength(lvl)
            set .moveSpeed = RobotSpeed(lvl)*TIMEOUT
            set .dx = .moveSpeed*.cosA
            set .dy = .moveSpeed*.sinA
            //robots group head node
            set .head = robot.allocateHead()
            static if not RANDOM_SPAWN then
                set lastSpawnWidth = -.width
                static if not SPAWN_PATTERN_NORMAL then
                    set spawnWidthIncreasing = true
                endif
            endif
            //Area Effect
            set sfxlength = -0.5*.length// - .length/SFX_PER_LENGTH
            set sfxCtr = 0
            
            //Insert in the list
            set next[this] = 0
            set prev[this] = prev[0]
            set next[prev[this]] = this
            set prev[0] = this
            if prev[this] == 0 then
                call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
            endif
            set caster = null
            return false
        endmethod
        
        static if not LIBRARY_SpellEffectEvent then
            private static method condition takes nothing returns boolean
                return (GetSpellAbilityId() == SPELL_ID and thistype.onCast() )
            endmethod
        endif
        
        private static method onInit takes nothing returns nothing
            static if LIBRARY_SpellEffectEvent then
                call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.condition))
                set t = null
            endif
        endmethod
        
    endstruct


endscope

//Even though the laboratory has since been sealed off, 
//the ability to radio in robotic drones is still in 
//working order.


Changelog:
v1.00 - [14 December 2015]
- Initial Release

v1.10 - [17 December 2015]
- Spells now uses a temporary single group variable avoiding creating and destroying groups periodically.
- Static values such as Cos(.angle) and Sin(.angle) are stored in variables to avoid re-calculating them
- No longer inline the TriggerRegisterAnyUnitEventBJ
- Fixed erraneous special effects not showing the real area of effect.
- Fixed some minor scripting mistakes
- Fixed ROBOTS_PER_WIDTH bug.

v1.11 - [16 January 2015]
- Damage Source is now Tinker, not the robot machine.

v1.12 - [4 March 2016]
- Optimized robot movement by storing x and y motion in a variable instead of calculating them every time.
- Added a Robot Size Scale configuration.

Keywords:
March of the Machines, March, Machines, Dota, Tinker, Boush,
Contents

March of the Machines (Map)

Reviews
18:52, 20th Dec 2015 BPower: Approved. The code looks good to me. The concept is well known to all DotA players. I was a very good with the Tinker about 4 years before :) Overall I'll rate the spell with 4/5. Good job.

Moderator

M

Moderator

18:52, 20th Dec 2015
BPower: Approved. The code looks good to me.
The concept is well known to all DotA players.
I was a very good with the Tinker about 4 years before :)

Overall I'll rate the spell with 4/5. Good job.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
To do list:
*Upload GIF Previews
*Add Vexorian to the credit list because of his dummy model with attachable origin
*Do Bribe's suggestions except the inlining part
 

Attachments

  • Random.gif
    Random.gif
    2.4 MB · Views: 2,724
  • Normal.gif
    Normal.gif
    2.4 MB · Views: 2,639
  • Zigzag.gif
    Zigzag.gif
    2.8 MB · Views: 2,715
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Hey Flux, nuce system overall. I have some constructive feedback how to improve or simplify a few things, if you have a moment.


- Creating/destroying groups dynamically for just that one instant is unnecessary and can be replaced by using a single global group which never gets destroyed

- I never see you change the .angle value, so you can store Cos and Sin of that angle into arrays to reduce the need for those expensive functions.

- TriggerRegisterAnyUnitEventBJ is fine and there's no reason to inline it.

- Nulling the caster variable is unnecessary as it's a parameter and those don't leak unless you use "set caster = unit" on it.

- You can merge your FoG loops and do an if/else in each one to assess if its range matches the criteria. It spares you from needing to enum and use the filter method twice, while keeping the code a little more organized.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
- Creating/destroying groups dynamically for just that one instant is unnecessary and can be replaced by using a single global group which never gets destroyed
Ok, I get it. I thought it wouldn't matter much.

- I never see you change the .angle value, so you can store Cos and Sin of that angle into arrays to reduce the need for those expensive functions.
Brilliant, never thought of that.

- TriggerRegisterAnyUnitEventBJ is fine and there's no reason to inline it.
I hate the red font color :p

- Nulling the caster variable is unnecessary as it's a parameter and those don't leak unless you use "set caster = unit" on it.
Yes, I forgot to remove that because at first, it wasn't an input argument but a local variable.

- You can merge your FoG loops and do an if/else in each one to assess if its range matches the criteria. It spares you from needing to enum and use the filter method twice, while keeping the code a little more organized.

Make sense. That will also remove the boolean explode.

Thanks Bribe for that wonderful feedback!
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Some things to think about:

1. MarchOfTheArm is placed into a scope, yet the struct "spell" is not declared as private.
Scopes are placed last by the compiler, hence can't be used like a function library.

Solution: Either make your struct "spell" private or remove the scope keyword and replace it with "library".
You should do the second if you want to enable the option to create a march of the arm indipendent from
the EVENT_PLAYER_SPELL_EFFECT event. Otherwise a scope is the better solution.

2. struct spell is not a good name, only because it is not a private struct.
This can result into code conflict in case another struct named "spell" exists in the map.
So either place a "private" keyword infront of struct spell or re-name it.
Which solution you chose depends on if you want to grant users access to the struct or not.
private --> no access
re-name --> access

I have more ideas about the code, which I would like to share with you,
but first decide between the options I mentioned above. I would recommend to
stay with the scope and privatize the struct.
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
The struct was private first, but then struct robot can't access them so I made it public and put the keyword. I thought the
private keyword spell privatizes it.
JassHelper said:
keyword

The keyword statement allows you to declare a replacement directive for an scope without declaring an actual function/variable/etc. It is useful for many reasons, the most important of the reasons being that you cannot use a private/public member in an scope before it is declared, in most cases this limitation is no more than an annoyance requiring you to change the position of declarations, in other situations though this is a limitator of other features.

For example two mutually recursive functions may use .evaluate (keep reading this readme) to call each other, but if you also want the functions to be private it is impossible to do it without using keyword:
Hmm, but from the example, function B is private. I think I got it. I'll make the struct spell private, but struct robot will still have access because of the private keyword spell declared
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
You should add a learn tooltip in the object editor.

I would recommend that the tinker hero deals the damage, instead of the dummy unit.
Otherwise it could mess with damage detection systems, especially when the user
manipulates outgoing damage values. So add a unit member, where you store the caster handle.

The local group could be replaced by a global group for a small performance boots.

Cos & Sin values seem to be static. You could also store them.

When moving dummy units via SetUnitX/Y, you always have to think about map boundaries.
In case you move a dummy out of bounds the game will crash with a fatal error.

The unit variable "caster" in create must not be nulled. That applies for all arguments in any function.
Exception: You re-assign the variable.

It seems not required to assign r - set r = robot.create(x, y, .angle, this), you could also do call robot.create(x, y, .angle, this)

Like Bribe mentioned already mentioned TriggerRegisterAnyUnitEventBJ is a
useful wrapper for player unit events.

You do not need the return value of spell.create(caster). It's nitpicking but I just
present you the way I use in public resources:
JASS:
        private static method onCast takes nothing returns boolean
            local thistype this = thistype.allocate()
            //*  My code
            return false
        endmethod
        
        static if not LIBRARY_SpellEffectEvent then
            private static method condition takes nothing returns boolean
                return (GetSpellAbilityId() == ABILITY_ID) and thistype.onCast()
            endmethod
        endif
    
        private static method onInit takes nothing returns nothing
            static if LIBRARY_SpellEffectEvent then
                call RegisterSpellEffectEvent(ABILITY_ID, function thistype.onCast)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.condition))
                set t = null
            endif
        endif
This spell could use my Missile library for best end-userr configuration
I don't want to advertise my resources and the way you code it is totally fine.
---------------------------------------------------------------------------------------------
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I would recommend that the tinker hero deals the damage, instead of the dummy unit.
Otherwise it could mess with damage detection systems, especially when the user
manipulates outgoing damage values. So add a unit member, where you store the caster handle.
However in Dota/Dota2, the damage source is the robot. Can you give me an example how it could mess with damage detection systems? Also, if you have played Dota/Dota2, there is an item called "BladeMail" which returns all damage taken to their respective source. Making Tinker the damage source will make "BladeMail" a great counter to this spell, which is not the case in Dota/Dota2.

The local group could be replaced by a global group for a small performance boots.
Ok, Bribe also mentioned that :)


Cos & Sin values seem to be static. You could also store them.
Will do, Bribe also mentioned that :)

When moving dummy units via SetUnitX/Y, you always have to think about map boundaries.
In case you move a dummy out of bounds the game will crash with a fatal error.
I was planning to add a boundary check at first, but I tried casting the spell at the corner of the map bounds and it did not crash so I didn't bother.

The unit variable "caster" in create must not be nulled. That applies for all arguments in any function.
Exception: You re-assign the variable.
Ok, I thought it wouldn't matter much. Just OCD kicking in.

It seems not required to assign r - set r = robot.create(x, y, .angle, this), you could also do call robot.create(x, y, .angle, this)
Will do.

Like Bribe mentioned already mentioned TriggerRegisterAnyUnitEventBJ is a
useful wrapper for player unit events.
Fine I'll change it.

You do not need the return value of spell.create(caster). It's nitpicking but I just
present you the way I use in public resources:
JASS:
        private static method onCast takes nothing returns boolean
            local thistype this = thistype.allocate()
            //*  My code
            return false
        endmethod
        
        static if not LIBRARY_SpellEffectEvent then
            private static method condition takes nothing returns boolean
                return (GetSpellAbilityId() == ABILITY_ID) and thistype.onCast()
            endmethod
        endif
    
        private static method onInit takes nothing returns nothing
            static if LIBRARY_SpellEffectEvent then
                call RegisterSpellEffectEvent(ABILITY_ID, function thistype.onCast)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.condition))
                set t = null
            endif
        endif
Ok, I got it.

This spell could use my Missile library for best end-userr configuration
I don't want to advertise my resources and the way you code it is totally fine.
I'll take a look, maybe I'll add it as an optional requirement.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
You can optimize further by storing the product of movespeed and angle
JASS:
set vectorX = movespeed*cos
set vectorY = movespeed*sin
set x = GetUnitX(.u) + vectorX
set y = GetUnitX(.u) + vectorY
 
Level 13
Joined
Aug 19, 2014
Messages
1,111
Nice spell Flux and its really great, the zigzag movement is pretty cool and original. Would be more cooler if we can configure the robot scale.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Nice spell Flux and its really great, the zigzag movement is pretty cool and original. Would be more cooler if we can configure the robot scale.

Nice suggestion, I updated it so now it's possible to scale the robot.

EDIT:
Forgot to mention this,
You can optimize further by storing the product of movespeed and angle
JASS:
set vectorX = movespeed*cos
set vectorY = movespeed*sin
set x = GetUnitX(.u) + vectorX
set y = GetUnitX(.u) + vectorY

Nice suggestion as well. I've included it in the update.
 
Last edited:
Top