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

Rush v1.05

  • Like
Reactions: deepstrasz
I don't know how useful you'll find this but this is a dash spell template. It was originally a request that I accepted to practice some documentation and configuration. I tried to make it as configurable as possible.

JASS:
    //Configurables
    integer Rush = 'A000' //This is the ability id the spell uses. Change it after copying the trigger.
    real Speed = 1150 //This is the speed of the caster when he uses the spell.
    real Interval = .03 //How often you want the timer to run.
    real Range = 175 //How close units should be to be damaged by the spell.
    boolean UnitOrSfx = false //This decides if you want to use a dummy unit or sfx as the well...sfx of the spell. False for sfx, true for dummy unit.
    integer Transparency = 80 //How transparent you want the dummy to be.
    integer RushDummy = 'e001' // This is the id of the dummy unit created.
    real Lifespan = 0.5 // Life span of the dummy.
    string Sfx = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl" //The sfx that appears when the caster dashes.
    string Animation = "stand ready" //What animation plays when the caster dashes.
    real DamageBase = 75 //This is the base damage which will be multiplied by the ability level.
    integer R = 255 //This is the red value for the dummy unit.
    integer G = 255 //This is the blue value for the dummy unit.
    integer B = 255 //This is the green value for the dummy unit.
    real AbilDist = 200 //This is the distance that scales per level of ability. Set BonusDist to 0 if you want a pure level based distance.
    real BonusDist = 200 // This is the bonus distance. Set AbilDist to 0 if you want constant distant.
    attacktype AttackType = ATTACK_TYPE_CHAOS //The attack type.
    damagetype DamageType = DAMAGE_TYPE_UNIVERSAL //The damage type. They're both currently set to these configurations to allow easy testing.


v1.04 - The issues Doomlord and watermelon_1234 pointed out have been addressed.
v1.05 - Uses IsUnitType and GetUnitTypeId for checking dead units now.





-----------------------------------
-Importing Instructions-
-----------------------------------

1. Copy the script found in the map header, Mikagami Spells.
2. Paste it into your own map header.
3. Copy the hashtable variable Rush_Hash in the variable editor or make your own and name it Rush_Hash.
4. Copy the trigger Rush. Copy the trigger Rush Dummy Dies.
5. Either copy the dummy ability and dummy units or make your own.
6. Press ctrl + d and look at the id of the dummies.
7. Replace 'A000' with the correct id.
8. Replace 'e001' with the correct id.
8. Save your map and test it.

Note: You need Jass NewGen Pack for this to work.
Note: It is EXTREMELY important that you copy the script in the map header. That script prevents any bugs and is essential for this spell to work.
Note: You might want to enable the DebugMsg to see for yourself if the spell is MUI. Just remove the "//".



[TD] Rush [/TD]
Dash forward with an ungodly speed, dealing damage to enemy units in a line. Distance moved increases with each level of the ability.

LEVELS
Level 1: Deals 75 damage.
Level 2: Deals 150 damage.
Level 3: Deals 225 damage.

[TD] SAMPLE IMAGES [/TD]

Sample One

Sample Two


Rush_zpsb4ce5e39.png


Rush_Alternate_zps9c878152.png


[TD] CHANGELOGS: [/TD][TD]
v1.00 - Initial Release
v1.01 - Destroys g at the end, Doesn't save g every time, Lifespan is now configurable,
removed == false and true, converted facing to radians at the start, rempved cx, cy, tx, and ty using the natives of cos and sin.
v1.02 - Distance and damage are now configurable. Removed the variables count and duration. You stay in your current position if you try to pass through impassable terrain. Made configurable filter, damage and distance functions.
v1.03 - Fixed more of the issues Doomlord pointed out.
v1.04 - The issues Doomlord and watermelon_1234 pointed out have been addressed.
v1.05 - Uses IsUnitType and GetUnitTypeId for checking dead units now.
[/TD]
[TD] Credits: [/TD][TD]
doomhammer99 - Spell description template.
Rising_Dusk - Terrain pathability check.
Weep - For GDD which is used to test this spell.
Hive - For teaching how to make spells.
[/TD]

TerrainPathability by Rising_Dusk

Cast

Rush Dummy Dies

JASS:
library TerrainPathability initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//* 
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//* 
//******************************************************************************
//* 
//*    > function IsTerrainDeepWater    takes real x, real y returns boolean
//*    > function IsTerrainShallowWater takes real x, real y returns boolean
//*    > function IsTerrainLand         takes real x, real y returns boolean
//*    > function IsTerrainPlatform     takes real x, real y returns boolean
//*    > function IsTerrainWalkable     takes real x, real y returns boolean
//* 
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//* 
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//* 
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//* 
//* Variables that can be used from the library:
//*     [real]    TerrainPathability_X
//*     [real]    TerrainPathability_Y
//* 
globals
    private constant real    MAX_RANGE     = 10.
    private constant integer DUMMY_ITEM_ID = 'wolg'
endglobals

globals    
    private item       Item   = null
    private rect       Find   = null
    private item array Hid
    private integer    HidMax = 0
    public  real       X      = 0.
    public  real       Y      = 0.
endglobals

function IsTerrainDeepWater takes real x, real y returns boolean
    return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
    return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
function IsTerrainLand takes real x, real y returns boolean
    return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
endfunction
function IsTerrainPlatform takes real x, real y returns boolean
    return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction

private function HideItem takes nothing returns nothing
    if IsItemVisible(GetEnumItem()) then
        set Hid[HidMax] = GetEnumItem()
        call SetItemVisible(Hid[HidMax], false)
        set HidMax = HidMax + 1
    endif
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
    //Hide any items in the area to avoid conflicts with our item
    call MoveRectTo(Find, x, y)
    call EnumItemsInRect(Find ,null, function HideItem)
    //Try to move the test item and get its coords
    call SetItemPosition(Item, x, y) //Unhides the item
    set X = GetItemX(Item)
    set Y = GetItemY(Item)
    static if LIBRARY_IsTerrainWalkable then
        //This is for compatibility with the IsTerrainWalkable library
        set IsTerrainWalkable_X = X
        set IsTerrainWalkable_Y = Y
    endif
    call SetItemVisible(Item, false)//Hide it again
    //Unhide any items hidden at the start
    loop
        exitwhen HidMax <= 0
        set HidMax = HidMax - 1
        call SetItemVisible(Hid[HidMax], true)
        set Hid[HidMax] = null
    endloop
    //Return walkability
    return (X-x)*(X-x)+(Y-y)*(Y-y) <= MAX_RANGE*MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction

private function Init takes nothing returns nothing
    set Find = Rect(0., 0., 128., 128.)
    set Item = CreateItem(DUMMY_ITEM_ID, 0, 0)
    call SetItemVisible(Item, false)
endfunction
endlibrary
JASS:
//This part is where you can configure the spell.
globals
    //Configurables
    integer Rush = 'A000' //This is the ability id the spell uses. Change it after copying the trigger.
    real Speed = 1150 //This is the speed of the caster when he uses the spell.
    real Interval = .03 //How often you want the timer to run.
    real Range = 175 //How close units should be to be damaged by the spell.
    boolean UnitOrSfx = false //This decides if you want to use a dummy unit or sfx as the well...sfx of the spell. False for sfx, true for dummy unit.
    integer Transparency = 80 //How transparent you want the dummy to be.
    integer RushDummy = 'e001' // This is the id of the dummy unit created.
    real Lifespan = 0.5 // Life span of the dummy.
    string Sfx = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl" //The sfx that appears when the caster dashes.
    string Animation = "stand ready" //What animation plays when the caster dashes.
    real DamageBase = 75 //This is the base damage which will be multiplied by the ability level.
    integer R = 255 //This is the red value for the dummy unit.
    integer G = 255 //This is the blue value for the dummy unit.
    integer B = 255 //This is the green value for the dummy unit.
    real AbilDist = 200 //This is the distance that scales per level of ability. Set BonusDist to 0 if you want a pure level based distance.
    real BonusDist = 200 // This is the bonus distance. Set AbilDist to 0 if you want constant distant.
    attacktype AttackType = ATTACK_TYPE_CHAOS //The attack type.
    damagetype DamageType = DAMAGE_TYPE_UNIVERSAL //The damage type. They're both currently set to these configurations to allow easy testing.
    
    //Non-configurables
    group RushGroup = CreateGroup() //Don't touch this. Don't null or destroy this.
    real Offset = Speed * Interval //How far the caster moves per interval.
endglobals

//------Configurable functions------//

//Configure the conditions for group check here
function UnitCheck takes unit caster, unit u returns boolean
    return IsUnitEnemy(u, GetOwningPlayer(caster)) and not (IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId != 0) and IsUnitType(u, UNIT_TYPE_GROUND) and not IsUnitType(u, UNIT_TYPE_STRUCTURE)
endfunction

//The Damage formula for the ability.
function Damage takes unit caster, integer Rush, real BaseDamage returns real
    return GetUnitAbilityLevel(caster, Rush) * BaseDamage
endfunction

//Formula for the maximum distance. If you only want the ability to affect distance, set BonusDist to 0.
function MaximumDistance takes unit caster, integer Rush, real AbilDist, real BonusDist returns real
    return GetUnitAbilityLevel(caster, Rush) * AbilDist + BonusDist
endfunction
//---End of Configurable functions---//
 

function Rush_Periodic takes nothing returns nothing
    //Local Variable Setup
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit caster = LoadUnitHandle(udg_Rush_Hash, id, 0)
    local real facing = LoadReal(udg_Rush_Hash, id, 1)
    local real cur_dist = LoadReal(udg_Rush_Hash, id, 2)
    local group g = LoadGroupHandle(udg_Rush_Hash, id, 3)
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local real x1 = x + Offset * Cos(facing)
    local real y1 = y + Offset * Sin(facing)
    local player owner = GetOwningPlayer(caster)
    local unit dummy
    local unit u
    local real damage = Damage(caster, Rush, DamageBase) //You can change this to whatever you want.
    local real MaxDistance = MaximumDistance(caster, Rush, AbilDist, BonusDist) //This makes the distance you dash scale with the level.
    
    
    if cur_dist < MaxDistance then
        call SetUnitAnimation(caster, Animation)
        if not UnitOrSfx then
            call DestroyEffect(AddSpecialEffect(Sfx, x, y))
        else
            set dummy = CreateUnit(owner, RushDummy, x, y, facing)
            call SetUnitAnimation(dummy, Animation)
            call SetUnitVertexColor(dummy, R, G, B, Transparency)
            call UnitApplyTimedLife(dummy, 'BTLF', Lifespan)
            set dummy = null
        endif
        call GroupEnumUnitsInRange(RushGroup, x, y, Range, null)
        loop //What we do here is check if the target is in g. If not, then we damage it and add it to g to prevent it from being damaged again.
            set u = FirstOfGroup(RushGroup)
            exitwhen u == null
            if not IsUnitInGroup(u, g) and UnitCheck(caster, u) then
                call UnitDamageTarget(caster, u, damage, false, false, AttackType, DamageType, null)
                call GroupAddUnit(g, u)
            endif
            call GroupRemoveUnit(RushGroup, u)
        endloop
        if IsTerrainWalkable(x1, y1) then //If the terrain is passable the you'll dash, if not then trigger ends.
            call SetUnitX(caster, x1)
            call SetUnitY(caster, y1)
            set cur_dist = cur_dist + Offset //This counts how many times you've moved.
            call SaveReal(udg_Rush_Hash, id, 2, cur_dist)
        else
            call PauseTimer(t)
            call DestroyTimer(t)
            call DestroyGroup(g)
            call SetUnitAnimation(caster, "stand") //Resets the animation.
            call FlushChildHashtable(udg_Rush_Hash, id)
        endif
    else
        call PauseTimer(t)
        call DestroyTimer(t)
        call DestroyGroup(g)
        call SetUnitAnimation(caster, "stand") //Resets the animation.
        call FlushChildHashtable(udg_Rush_Hash, id)
    endif
    
    //Nulling
    set t = null
    set caster = null
    set g = null
endfunction

function Rush_Actions takes nothing returns nothing
    //Local Variable Setup
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
    local unit caster = GetTriggerUnit()
    local real dx = GetSpellTargetX() - GetUnitX(caster)
    local real dy = GetSpellTargetY() - GetUnitY(caster)
    local real dist_check = (dx*dx) + (dy*dy)
    local real facing
    
    //A little distance check to avoid bugs when you cast the spell in your current position.
    if dist_check <= 100 * 100 then
        set facing = GetUnitFacing(caster) * bj_DEGTORAD
    else
        set facing = (Atan2(dy, dx))
    endif
    
    //Hashtable Setup
    call SaveUnitHandle(udg_Rush_Hash, id, 0, caster)
    call SaveReal(udg_Rush_Hash, id, 1, facing)
    call SaveReal(udg_Rush_Hash, id, 2, 0)
    call SaveGroupHandle(udg_Rush_Hash, id, 3, CreateGroup())
    //End Hashtable Setup
    
    call TimerStart(t, Interval, true, function Rush_Periodic)
    
    //Nulling
    set t = null
    set caster = null
endfunction

function Rush_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == Rush then
        call Rush_Actions()
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Rush takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Rush_Conditions) )
    set udg_Rush_Hash = InitHashtable()
    set t = null
endfunction
  • Rush Dummy Dies
    • Events
      • Unit - A unit Dies
    • Conditions
      • (Unit-type of (Triggering unit)) Equal to Rush Dummy
    • Actions
      • Unit - Remove (Triggering unit) from the game

Keywords:
Dash, Spell, Template, MUI, Leakless??, JASS, vJASS, Simple, Will, be, rejected, probably, updated
Contents

Dash Spell Template (Map)

Reviews
Rush v1.05 | Reviewed by Maker | 17th Aug 2013 APPROVED The spell is leakless an MUI but not very original [tr] Your globals should be private constant Take the pathability library out of the header Get rid of Rush...

Moderator

M

Moderator


Rush v1.05 | Reviewed by Maker | 17th Aug 2013
APPROVED


126248-albums6177-picture66521.png


  • The spell is leakless an MUI but not very original
126248-albums6177-picture66523.png


  • Your globals should be private constant
  • Take the pathability library out of the header
  • Get rid of Rush Dummy Dies trigger
  • You could precalculate Offset * Cos(facing) and the Sin also
  • You can't pick units with GetUnitTypeId(u) != 0 so no need to check it
  • You could use SetUnitPropWindow to lock the facing
  • Use the ability level at the time of casting
  • Try making this all vjass
[tr]
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Use the Update button.

Review:
  1. Local group g is never destroyed.
  2. You don't need to save the group handle with every iteration. The thread at that time will complete the loop first before being iterated again. Reconsider your logic there.
  3. There isn't a code block for impassable terrain encounter.
  4. You can use a real value to keep track of the distance moved and compare it to the max distance instead of having to do excessive calculations.
  5. Put the damage and max distance into a global/constant function for easy configuration. It is ill-advised to leave a configurable value in the midst of your code.
  6. Make dummy life span configurable.
  7. The Offset global is unnecessary.
  8. Remove == true and == false. They are unnecessary. You can inline them with
    JASS:
    if condition then // Instead of == true
    
    // or
    
    if not condition then // Instead of == false
  9. This code block can be improved
    JASS:
            if UnitOrSfx == false then
                call DestroyEffect(AddSpecialEffect(Sfx, x, y))
            else
                // Code
            endif
    and
    JASS:
        if count < Duration then
            // Code
        else
            // Code
        endif

    Since a boolean can only return either true or false, you don't need to use elseif.
  10. JASS:
        if dist_check <= 100 then
            set facing = GetUnitFacing(caster)
        elseif dist_check > 100 then
            set facing = bj_RADTODEG*(Atan2(dy, dx))
        endif

    should be
    JASS:
        if dist_check <= 100 then
            set facing = GetUnitFacing(caster)*bj_DEGTORAD
        elseif dist_check > 100 then
            set facing = (Atan2(dy, dx))
        endif

    This helps you avoid having to reconvert the values in the set x and y of the periodic function.
  11. Duration is a wrong name. Use something like distance per iteration.
  12. cx, cy, tx, ty are unnecessary. You don't have to set a local for every single thing. It is not efficient, it is the opposite of that. Either remove them or remove dx, dy and use them in dist_check.
  13. cos, sin in the periodic func is also unnecessary. Just use the natives there directly.
  14. It is called Nulling, not Leak removal.
  15. Create a filter function like this
    JASS:
        function filterUnit takes unit caster, unit u returns boolean
            return /*
            
            */ IsUnitEnemy(u, GetOwningPlayer(caster)) and /* Target is an enemy of caster
            */ not IsUnitType(u, UNIT_TYPE_DEAD) and /* Target is alive
            */ GetUnitAbilityLevel(u, 'Avul') == 0 and /* Target is not invulnerable
            */ not IsUnitType(u, UNIT_TYPE_STRUCTURE) /* Target is not a structure
            */
        endfunction
    for configuration.
  16. You may want to use Maker's Shadow Trail system to awesome-ize (I made this up) the spell.

That is all for now. Ask if you feel like I misread the code :)
 
Last edited:
Level 3
Joined
Sep 9, 2009
Messages
658
1. Okay. So I just have to remove DestroyGroup(g) then?

2. So what do you suggest I do here?

3. The script at the map header doesn't check that?

4. Can you point out these excessive calculations?

5. I tried to make them as globals before but then they move at the same distance even if they have different levels of abilities.

6. You mean the expiration timer?

7. I'll remove it.

8. Okay.

9. So I should do it the way you showed me?

10. Will do.

11. Lol. Sure.

12. If I remove, dx and dy what do I use in the distance check? And when should I set something to a local?

13. What natives?

14. Okay.

15. Okay. But why check for invulnerability though? It's not like they'll get damaged.

EDIT: By the way, were you waiting for my reply? I went out for a bit.

EDIT: Didn't see 16. Can I not do that? I have nothiing against Maker's systems and spells. I'm just...not very fond of vJASS. If I can get it to work I'll try to make use of it.

EDIT: I'm curious, suppose every issue except 11 and 14 were gone. Would this still get a Rejected or Needs Fix status?
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
1. Okay. So I just have to remove DestroyGroup(g) then?

2. So what do you suggest I do here?

3. The script at the map header doesn't check that?

4. Can you point out these excessive calculations?

5. I tried to make them as globals before but then they move at the same distance even if they have different levels of abilities.

6. You mean the expiration timer?

7. I'll remove it.

8. Okay.

9. So I should do it the way you showed me?

10. Will do.

11. Lol. Sure.

12. If I remove, dx and dy what do I use in the distance check? And when should I set something to a local?

13. What natives?

14. Okay.

15. Okay. But why check for invulnerability though? It's not like they'll get damaged.

EDIT: By the way, were you waiting for my reply? I went out for a bit.

EDIT: Didn't see 16. Can I not do that? I have nothiing against Maker's systems and spells. I'm just...not very fond of vJASS. If I can get it to work I'll try to make use of it.

EDIT: I'm curious, suppose every issue except 11 and 14 were gone. Would this still get a Rejected or Needs Fix status?

  1. You never add it in the first place. Just put it in the timer destroy block.
  2. Remove that line :)
  3. No. You can debug a draft code execution and see what happens.
  4. Set local damage and max distance, calculate duration. Everything will be simplified if you do it like this:
    JASS:
    if currentDist >= maxDist then 
    // execute end code 
    else
    // execute move code
    // save currentDist = currentDist + intervalDist
  5. Global is just a suggestion. You can simply go with a function like this:
    JASS:
        function Rush_GetMaxDistance takes integer level returns real
            return 1000 + level*200
        endfunction
  6. Indeed.
  7. Nice :)
  8. Nice :)
  9. Yeh.
  10. Nice :)
  11. Nice :)
  12. I recommend this:
    JASS:
        local real dx = GetSpellTargetX() - GetUnitX(caster)
        local real dy = GetSpellTargetY() - GetUnitY(caster)
    since you will be using dx, dy again. On a side note, SquareRoot is computationally expensive, you may want to do it like this:
    JASS:
        local real dist_check = (dx*dx) + (dy*dy)
    
        // Square the compare value here
        if dist_check <= 100*100 then
        else
        endif
  13. Do it like this:
    JASS:
        local real x1 = x + Offset * Cos(facing)
        local real y1 = y + Offset * Sin(facing)
    The Sin() and Cos() natives are what I am talking about.
  14. Nice :)
  15. It is just an example. You can do it the way you want. Oh and filter out invulnerable units helps reduce computing effort for your spell :)
  16. Be my guest. It is just a suggestion.

I am not an actual moderator so I cannot answer your last question. Sorry =/

edit

Whoops. Forgot to tell you that you can just set the dummy's death type to Can't raise, Does not decay and get rid of the Dummy removal trigger :p
 
Level 3
Joined
Sep 9, 2009
Messages
658
I'll get to it when I'm in front of my desktop.

Where's the timer destroy block? Where I flushed the childtable? And you still have to destroy the group even if you save it in a hashtable and flush it?

For number 2. Will the spell still work fine even if I don't save it? Won't bug or anything?

I'll do something about the impassable terrain but what kind of impassable terrain? Cliffs? Trees?

Didn't think you were referring to the variables sin and cos. I'll never use them as variable names again.

And...never use squareroot and pow? Ever?

And the dummies play their death sound even with Can't Raise, Does not Decay. The Dummy removal trigger prevents that from happening. Should I still remove it?

EDIT: Map updated.
 
Last edited:
Level 16
Joined
Dec 15, 2011
Messages
1,423
3 - What do you mean impassable terrain?
4 - Can you show me a way to get current distance? All I can think of is saving 0 and adding offset to it every iteration.
5 - Didn't have enough time. Will work on this soon.
7 Thought about it. If I'm using it more than once wouldn't it be better to store it in a variable?
15 Haven't started yet. Would having a filter function really make it faster?

3/
JASS:
        if IsTerrainWalkable(x1, y1) then
            call SetUnitX(caster, x1)
            call SetUnitY(caster, y1)
            else
            // This is the code to be executed when the hero encounters impassable terrain.
            // Add the end code here
        endif

4/ Indeed it is done the way you said. Add offset to a saved real with every iteration.

7/ The Offset variable is unnecessary because it is only a single calculation, it is not worthy of a global. You can put it into a local for use in periodic though.

15/ It is not about efficiency. It is about readability and configuration. The filter func allows easy configuration on the user's end.
 
Level 3
Joined
Sep 9, 2009
Messages
658
The spell is updated now. Though since Offset is now used in these equations I thought it should stay. Is that alright?

JASS:
    local real x1 = x + Offset * Cos(facing)
    local real y1 = y + Offset * Sin(facing)
    set cur_dist = cur_dist + Offset
On another note, I couldn't do that thing you did with \* in the filter function. So my filter function ended up looking like this.
JASS:
function UnitCheck takes unit caster, unit u returns boolean
    return IsUnitEnemy(u, GetOwningPlayer(caster)) and not IsUnitType(u, UNIT_TYPE_DEAD) and IsUnitType(u, UNIT_TYPE_GROUND) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and  GetUnitAbilityLevel(u, 'Avul') == 0
endfunction
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Yeh. It is a vJASS thing. Don't worry.

Anyway, nice job on improving this :)

edit

Wait. You are supposed to execute the spell end code if the caster encounters impassable terrain, not like that dude :/

Just copy the end code and replace the "stay in same position".

Another note, you should increase then save cur_dist after you moved the unit. It is the correct logic :)
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
The code in the test-map doesn't match the code you put in the description. I'm assuming the code in the description is the updated one.

Short Review

  • GetUnitAbilityLevel(u, 'Avul') == 0is not really a good check for invulnerable units since it won't be true for all the ways a unit can become invulnerable in WC3. I would suggest just removing it.
  • The I2R calls are useless since the calculations in your functions involves reals.
  • You still have some SomeBoolean == false instead of not SomeBoolean
  • I'd suggest a configurable string for setting the animation for the caster when it finishes the spell. Since this is a template, it should be as configurable as possible.
    In general, most literal or constants you put in the code should be configurable, except your hashtable accesses. Even having the 100 * 100 configurable isn't a bad idea in case the user wants more precision (though it seems better off as a template constant).
  • JASS:
    call SaveGroupHandle(udg_Rush_Hash, id, 3, g)
    The local variable g is rather useless. You can just do this:
    JASS:
    call SaveGroupHandle(udg_Rush_Hash, id, 3, CreateGroup())

If you have the time, you could strive to make this an actual vJass template. For instance, making the template into a module that can be imported into a struct.
You can check the tutorials section for getting started with vJass.
 
Level 3
Joined
Sep 9, 2009
Messages
658
The code in the test map is the updated one. I'm just too lazy to update the one in the spell description.

I only see I2R in the top functions where ability level is involved. Since ability level is an integer, don't I have to convert it to a real?

SomeBoolean == false...are you talking about UnitOrSfx == false? Then what do you suggest? This is the only way I could think of to make it swtichable.

100*100 is better off as a constant.

You mean local group at the start?
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
The code in the test map is the updated one. I'm just too lazy to update the one in the spell description.
I've redownloaded your test map just to make sure and this is what the code looks like:
JASS:
//This part is where you can configure the spell.
globals
    //Configurables
    integer Rush = 'A000' //This is the ability id the spell uses. Change it after copying the trigger.
    real Speed = 1150 //This is the speed of the caster when he uses the spell.
    real Interval = .03 //How often you want the timer to run.
    real Offset = Speed * Interval //How far the caster moves per interval.
    real Range = 175 //How close units should be to be damaged by the spell.
    boolean UnitOrSfx = false //This decides if you want to use a dummy unit or sfx as the well...sfx of the spell.
    integer Transparency = 80 //How transparent you want the dummy to be.
    integer RushDummy = 'e001' // This is the id of the dummy unit created.
    string Sfx = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl" //The sfx that appears when the caster dashes.
    string Animation = "stand ready" //What animation plays when the caster dashes.
    attacktype AttackType = ATTACK_TYPE_CHAOS //The attack type.
    damagetype DamageType = DAMAGE_TYPE_UNIVERSAL //The damage type. They're both currently set to these configurations to allow easy testing.
    
    //Non-configurables
    group RushGroup = CreateGroup() //Don't touch this. Don't null or destroy this.
endglobals

function Rush_Periodic takes nothing returns nothing
    //Local Variable Setup
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit caster = LoadUnitHandle(udg_Rush_Hash, id, 0)
    local real facing = LoadReal(udg_Rush_Hash, id, 1)
    local integer count = LoadInteger(udg_Rush_Hash, id, 2)
    local group g = LoadGroupHandle(udg_Rush_Hash, id, 3)
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local real cos = Cos(Deg2Rad(facing))
    local real sin = Sin(Deg2Rad(facing))
    local real x1 = x + Offset * cos
    local real y1 = y + Offset * sin
    local player owner = GetOwningPlayer(caster)
    local unit dummy
    local unit u
    
    //Configurable Locals
    local real damage = (GetUnitAbilityLevel(caster, Rush) * 75)//You can change this to whatever you want.
    local real MaxDistance = (GetUnitAbilityLevel(caster, Rush) * 200) + 200 //This makes the distance you dash scale with the level.
    //End of Configurable Locals
    
    local real Duration = MaxDistance * Interval //This determines how long you dash. Don't touch this unless you know what to do.
    //End Setup
    
    set count = count + 1//This counts how many times you've moved.
    call SaveInteger(udg_Rush_Hash, id, 2, count)
    
    if count < Duration then
        call SetUnitAnimation(caster, Animation)
        if UnitOrSfx == false then
            call DestroyEffect(AddSpecialEffect(Sfx, x, y))
        elseif UnitOrSfx == true then
            set dummy = CreateUnit(owner, RushDummy, x, y, facing)
            call SetUnitAnimation(dummy, Animation)
            call SetUnitVertexColor(dummy, 255, 255, 255, Transparency)
            call UnitApplyTimedLife(dummy, 'BTLF', .5)
            set dummy = null
        endif
        call GroupEnumUnitsInRange(RushGroup, x, y, Range, null)
        loop //What we do here is check if the target is in g. If not, then we damage it and add it to g to prevent it from being damaged again.
            set u = FirstOfGroup(RushGroup)
            exitwhen u == null
            if IsUnitInGroup(u, g) == false and IsUnitEnemy(u, owner) and GetUnitState(u, UNIT_STATE_LIFE) > .405 and IsUnitType(u, UNIT_TYPE_GROUND) and IsUnitType(u, UNIT_TYPE_STRUCTURE) == false then
                call UnitDamageTarget(caster, u, damage, false, false, AttackType, DamageType, null)
                call GroupAddUnit(g, u)
            endif
            call GroupRemoveUnit(RushGroup, u)
            call SaveGroupHandle(udg_Rush_Hash, id, 3, g)
        endloop
        if IsTerrainWalkable(x1, y1) then
            call SetUnitX(caster, x1)
            call SetUnitY(caster, y1)
        endif
        
    elseif count > Duration then
        call GroupClear(g)
        call PauseTimer(t)
        call DestroyTimer(t)
        call SetUnitAnimation(caster, "stand") //Resets the animation.
        call FlushChildHashtable(udg_Rush_Hash, id)
    endif
    
    //Leak Removal
    set t = null
    set caster = null
    set g = null
endfunction

function Rush_Actions takes nothing returns nothing
    //Local Variable Setup
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
    local unit caster = GetTriggerUnit()
    local real cx = GetUnitX(caster)
    local real cy = GetUnitY(caster)
    local real tx = GetSpellTargetX()
    local real ty = GetSpellTargetY()
    local real dx = tx - cx
    local real dy = ty - cy
    local real dist_check = SquareRoot((dx*dx) + (dy*dy))
    local real facing
    local group g = CreateGroup()
    
    //A little distance check to avoid bugs when you cast the spell in your current position.
    if dist_check <= 100 then
        set facing = GetUnitFacing(caster)
    elseif dist_check > 100 then
        set facing = bj_RADTODEG*(Atan2(dy, dx))
    endif
    
    //Hashtable Setup
    call SaveUnitHandle(udg_Rush_Hash, id, 0, caster)
    call SaveReal(udg_Rush_Hash, id, 1, facing)
    call SaveInteger(udg_Rush_Hash, id, 2, 0)
    call SaveGroupHandle(udg_Rush_Hash, id, 3, g)
    //End Hashtable Setup
    
    call TimerStart(t, Interval, true, function Rush_Periodic)
    
    //Leak Removal
    set t = null
    set caster = null
    set g = null
endfunction

function Rush_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == Rush then
        call Rush_Actions()
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Rush takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Rush_Conditions) )
    set udg_Rush_Hash = InitHashtable()
    set t = null
endfunction
Since ability level is an integer, don't I have to convert it to a real?
The integers would be implicitly converted to reals as long as you have reals in the same calculation. Just try removing both I2R calls; you'll see that your map still compiles fine.

Then what do you suggest?
I already suggested an alternative: You still have some SomeBoolean == false instead of not SomeBoolean

You mean local group at the start?
I should have been more clear. I'm talking about the local group g in Rush_Actions.
 
Level 3
Joined
Sep 9, 2009
Messages
658
Updated! And I have to apologize, the map that was uploaded before was actually the wrong one. NOW, I've made sure the correct one is uploaded and I've corrected the issues watermelon1234 pointed out.

Lastly, if you're going to tell me to do if not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0 then you're going to have to tell me how to bypass the error because every time I try to place it in the filter function, cannot convert integer to boolean and cannot compare variables of primitive types blah blah error pops up. I tried doing this to no avail.

JASS:
if blah blah then
   return true
endif
   return false

EDIT: Also updated the script in the spell description.
EDIT: I've decided to keep 100*100 and 'stand' animation after the spell as constants.
 
Level 3
Joined
Sep 9, 2009
Messages
658
Your globals should be private constant
Take the pathability library out of the header
Get rid of Rush Dummy Dies trigger
You could precalculate Offset * Cos(facing) and the Sin also
You could use SetUnitPropWindow to lock the facing

Isn't private constant a vjass thing?
Do I use SetUnitPropWindow to precalculate Offset * Cos(facing)? Otherwise, what would happen if I use SetUnitPropWindow? I haven't tried it out yet.
 
Level 6
Joined
Jul 23, 2018
Messages
243
Why do I get error with globals? And I'm not very good at vJass so please be simple and tender
This did not happen when I first tested the map. And I'm using patch 1.29
Edit: The globals worked fine now, now I got error "Identifier already in used: Damage"
Edit 2: For the above Edit, I'm still trying to figure to fix that, not sure what triggers interfere with it. Anyway, when I set boolean UnitorSfx to true, the dummy unit faced 0 degree? Hope you can fix that.
 
Last edited:
Top