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

Grab and Pull [Jass]

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
The skill made by me, sends the spirit, if spirit will face with the enemy,the enemy will be paused and the caster will transport to enemy and deal damage. If you want to take this skill in your map you sould only change the spell id in define SpellForGrabAndPull = 'A000' and your dummy id in define YourDummy = 'h000'

Spell code [jass=My Jass]
function Trig_spell_Conditions takes nothing returns boolean //main condition
return GetSpellAbilityId() == SpellForGrabAndPull //if skill = SpellForGramAndPull
endfunction

function condforcatch takes nothing returns boolean //condition for catch unit
local timer t = GetExpiredTimer() //get timer
local unit dammy = LoadUnitHandle(hash,GetHandleId(t),1) //load dammy
local unit a = LoadUnitHandle(hash,GetHandleId(t),2) //load unit a
local real angle = LoadReal(hash,GetHandleId(t),3) // load real angle
return IsUnitEnemy(GetEnumUnit(), GetOwningPlayer(a)) and not IsUnitType(GetEnumUnit(), UNIT_TYPE_DEAD) //if unit enemy and unit not dead
endfunction

function movetocatched takes nothing returns nothing // move caster to enemy
local timer t2 = GetExpiredTimer() // get timer t2
local timer t = LoadTimerHandle(hash,GetHandleId(t2),3) //load timer t
local unit catched = LoadUnitHandle(hash,GetHandleId(t2),1) //load unit catched
local unit a = LoadUnitHandle(hash,GetHandleId(t2),2) // loadl unit a
local effect e = LoadEffectHandle(hash,GetHandleId(t2),3) // loadl effect e
local real angle2 = bj_RADTODEG * Atan2(GetLocationY(GetUnitLoc(catched)) - GetLocationY(GetUnitLoc(a)), GetLocationX(GetUnitLoc(catched)) - GetLocationX(GetUnitLoc(a))) // angle between unit a and catched
local real dx = GetLocationX(GetUnitLoc(catched)) - GetLocationX(GetUnitLoc(a)) // distance between a and catched
local real dy = GetLocationY(GetUnitLoc(catched)) - GetLocationY(GetUnitLoc(a)) // distance between a and catched
if((SquareRoot(dx * dx + dy * dy) >= 140.1)) then // (if // distance between a and catched >= 140 then)
call SetUnitFacing(a, angle2) // set unit facing a to angle2
call SetUnitAnimation(a, "Walk") // play unit animation
call SetUnitX(a, GetUnitX(a)+20*Cos(angle2*bj_DEGTORAD)) // move unit a
call SetUnitY(a, GetUnitY(a)+20*Sin(angle2*bj_DEGTORAD)) // move unit a
set t2 = null
set t = null
set a = null
set catched = null
else //else
call SetUnitFacing(a, angle2) //set unit facing a to angle2
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl",catched,"origin")) //add & destroy effect
call UnitDamageTarget(a,catched,(90*GetUnitAbilityLevel(a,SpellForGrabAndPull)),false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null) // damage target
call SetUnitPathing(a,true) //set unit a pathing to true
call DestroyEffect(e) // destroy effect e
call PauseUnit(catched, false) // unpause unit catched
call DestroyTimer(t) // destroy timer t
call DestroyTimer(t2) // destroy timer t2
set t2 = null
set t = null
set a = null
set catched = null
call FlushChildHashtable(hash,GetHandleId(t)) //clear hash
call FlushChildHashtable(hash,GetHandleId(t2)) //clear hash
endif
endfunction

function catch2 takes nothing returns nothing // if dammy face enemy in range 75
local timer t = GetExpiredTimer() // get timer t
local timer t2 //create timer t2
local unit dammy = LoadUnitHandle(hash,GetHandleId(t),1) //load unit dammy
local unit a = LoadUnitHandle(hash,GetHandleId(t),2) //load unit a
local real angle = LoadReal(hash,GetHandleId(t),3) // load real angle
local unit catched = LoadUnitHandle(hash,GetHandleId(t),10) // loadl unit catched
local effect e //create effect variable
if(condforcatch()) then //if cond
call RemoveUnit(dammy) //remove dammy from game
set t2 = CreateTimer() // set variable t2 = CreateTimer()
set catched = GetEnumUnit() // set variable unit catched = GetEnumUnit()
call PauseUnit(catched, true) // pause unit
set bj_lastCreatedEffect = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\ReinforcedTrollBurrow\\ReinforcedTrollBurrowTarget.mdl", catched, "origin" ) // add special effect
set e = bj_lastCreatedEffect // set variable effect e = get last created effect
call SaveTimerHandle(hash,GetHandleId(t2),3,t) // save timer t
call SaveUnitHandle(hash,GetHandleId(t2),1,catched) //save unit catched
call SaveUnitHandle(hash,GetHandleId(t2),2,a) // save unit a
call SaveEffectHandle(hash,GetHandleId(t2),3,e) // save effect e
call SetUnitPathing(a,false) // set unit a pathing to false
call TimerStart(t2,0.01,true,function movetocatched) // start timer t2
set t = null
set a = null
set catched = null
call FlushChildHashtable(hash,GetHandleId(t))
else
endif
endfunction

function condfordead takes nothing returns boolean //death condition
local timer t = GetExpiredTimer() // get timer
local unit dammy = LoadUnitHandle(hash,GetHandleId(t),1) //load dammy
if(not( GetUnitState(dammy, UNIT_STATE_LIFE) <= 0 == true)) then // if dammy is dead then
return false
endif
return true
endfunction

function catch takes nothing returns nothing //timer function
local unit key1 = null // for save/load hash
local timer t = GetExpiredTimer() // get timer t
local unit dammy = LoadUnitHandle(hash,GetHandleId(t),1) // load unit dammy
local unit a = LoadUnitHandle(hash,GetHandleId(t),2) // load unit a
local real angle = LoadReal(hash,GetHandleId(t),3) // load real angle
local real x1 = GetLocationX(GetUnitLoc(dammy))+20*Cos(angle*bj_DEGTORAD) // for check "RectContainsLoc"
local real y1 = GetLocationY(GetUnitLoc(dammy))+20*Sin(angle*bj_DEGTORAD) // for check "RectContainsLoc"
call SaveTimerHandle(hash,GetHandleId(key1),1,t) // save timer in key 1
if((RectContainsLoc(bj_mapInitialPlayableArea, Location(x1,y1)) == true)) then // if "our cond" then
call SetUnitX(dammy, GetUnitX(dammy)+20*Cos(angle*bj_DEGTORAD)) //move unit dammy
call SetUnitY(dammy, GetUnitY(dammy)+20*Sin(angle*bj_DEGTORAD)) //move unit dammy
call ForGroup(GetUnitsInRangeOfLocAll(75,GetUnitLoc(dammy)), function catch2) // catch units in range of loc dammy
set a = null
set dammy = null
set t = null
else // else (if rect not containts locaion)
call RemoveUnit(dammy) // remove dammy from game
call DestroyTimer(t) // destroy timer
call FlushChildHashtable(hash,GetHandleId(t)) //clear hash
set a = null
set dammy = null
set t = null
endif
if(condfordead()) then // if / then
set t = LoadTimerHandle(hash,GetHandleId(key1),1) // load timer (for delete)
call DestroyTimer(t) // destroy loaded timer
call FlushChildHashtable(hash,GetHandleId(key1)) // clear hash
call FlushChildHashtable(hash,GetHandleId(t)) // clear hash
else
endif
endfunction

function Trig_spell_Actions takes nothing returns nothing // main function
local unit key1 = null // create null variable for hash save/load
local timer t = CreateTimer() // create timer
local unit a = GetSpellAbilityUnit() // get spell ability unit
local unit catched // create unit variable catched
local unit dammy // create variable dammy (dummy)
local real angle = bj_RADTODEG * Atan2(GetLocationY(GetSpellTargetLoc()) - GetLocationY(GetUnitLoc(a)), GetLocationX(GetSpellTargetLoc()) - GetLocationX(GetUnitLoc(a))) // angle between unit loc a and spell loc
local real x = GetLocationX(GetUnitLoc(a))+60*Cos(angle*bj_DEGTORAD) // real x
local real y = GetLocationY(GetUnitLoc(a))+60*Sin(angle*bj_DEGTORAD) // real y
set bj_lastCreatedUnit = CreateUnitAtLoc(GetOwningPlayer(a), YourDummy, Location(x,y), angle) // create dummy
call UnitApplyTimedLife( bj_lastCreatedUnit, 'BTLF', 1.50 ) // life timer
set dammy = bj_lastCreatedUnit // set dammy = last created unit
call TimerStart(t,0.01,true,function catch) // starting timer
call FlushChildHashtable(hash,GetHandleId(t)) //clear hash
call SaveTimerHandle(hash,GetHandleId(key1),1,t) // save timer t in hash
call SaveUnitHandle(hash,GetHandleId(t),1,dammy) // save unit dammy in hash
call SaveUnitHandle(hash,GetHandleId(t),2,a) // save unit a in hash
call SaveReal(hash,GetHandleId(t),3,angle) // save real angle in hash
call SaveUnitHandle(hash,GetHandleId(t),10,catched) // save variable catched in hash
set a = null
set catched = null
set t = null
endfunction

//===========================================================================
function InitTrig_GrabAndPull takes nothing returns nothing
set gg_trg_GrabAndPull = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_GrabAndPull, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_GrabAndPull, Condition( function Trig_spell_Conditions ) )
call TriggerAddAction( gg_trg_GrabAndPull, function Trig_spell_Actions )
endfunction
[/code]
Tha map's cap [jass=My Jass]
globals
hashtable hash = InitHashtable()
endglobals
define SpellForGrabAndPull = 'A000' // change it on your spell id
define YourDummy = 'h000' // cahnge it on your dummy id
[/code]

Keywords:
Warcraft 3
Contents

Еще одна карта (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 14:35, 14th Sep 2013 Maker: Get rid of pause unit and don't use locations, use coordinates. You have unnulled timer variables so you leak

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

14:35, 14th Sep 2013
Maker: Get rid of pause unit and don't use locations, use coordinates.
You have unnulled timer variables so you leak
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
-Do not use locations
-condforcatch leaks unit and timer, null the variables
JASS:
if ( not ( IsUnitAlly(GetEnumUnit(), GetOwningPlayer(a)) != true ) ) then
        return false
    endif
    if ( not ( GetUnitState(GetEnumUnit(), UNIT_STATE_LIFE) <= 0 != true ) ) then
        return false
    endif
    return true
->
JASS:
return IsUnitEnemy(GetEnumUnit(), GetOwningPlayer(a)) and not IsUnitType(GetEnumUnit(), UNIT_TYPE_DEAD)
 
Don't delete and re-upload your spells, use the Update button.
I work really hard on these reviews:

Magtheridon96 said:
I can see you started out with GUI and did a conversion to JASS, but there are a lot of major flaws with the code.

  1. The identation is pretty off, so it's kind of hard to read. Pass your code through this tool to see what good indentation is like.
  2. You need to have proper documentation at the top that gives a name, author, version, importing instructions and a virgin's tears.
  3. In almost any modern language I can think of, == true is the most redundant thing ever. You can remove it and have the same semantics.
  4. Your Spell Conditions function is both sub-optimal and unreadable. Here's a better alternative:
    JASS:
    function Trig_spell_Conditions takes nothing returns boolean
        return GetSpellAbilityId() == 'A000'
    endfunction
  5. Don't check if a unit is alive or dead based on life, it's terrible. It is perfectly possible to have a dead unit with a million HP.
    A unit is dead if and only if IsUnitType(my_unit, UNIT_TYPE_DEAD) and GetUnitTypeId(my_unit) != 0 is true.
    A unit is alive if the above is false.
  6. Instead of using a heavy squareroot call here:
    SquareRoot(dx * dx + dy * dy) >= 140.1
    Just square both sides:
    dx*dx + dy*dy >= 140*140
    SquareRoot is pretty darn slow.
  7. You have a ton of location, group and special effect memory leaks.
    To resolve them, you need to cache any location or group you use into a variable and you should remove them with RemoveLocation and DestroyGroup respectively. Special effects can be cleaned up instantly like this:
    call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl",catched,"origin"))
    In fact, here, you shouldn't be using locations at all. There are much better alternatives that use coordinates directly.
    For example, this: GetLocationY(GetUnitLoc(catched)) can simply be GetUnitY(catched). It's faster and it avoids creating a location that you need to store and destroy. Avoid locations at all costs in this spell and you'll avoid all the location leaks.
  8. As for groups, create them, use GroupEnumUnitsInRange, iterate over them with the FirstOfGroup-loop method instead of the ForGroup method and then destroy them, or use a global group that you would create once, and reuse that group when you enumerate instead of creating and destroying a local group.


    JASS:
    local group g = CreateGroup()
    local unit u
    
    call GroupEnumUnitsInRange(g, x, y, radius, null)
    loop
        set u = FirstOfGroup(g)
        exitwhen u == null
        call GroupRemoveUnit(g, u)
    
        if UnitFilter(u) then
            // Actions with u here
        endif
    endloop


    This has been proven to be much faster than ForGroup and it gets faster and faster as the number of units gets higher, so it's very scalable as well.
  9. You need to use constant functions that return constant values in order to allow the user to configure your spell without having to dig in the mud and understand your code.


    JASS:
    constant function SpellPrefix_AbilityId takes nothing returns integer
        return 'A000'
    endfunction
    
    constant function SpellPrefix_GetEffectPath takes nothing returns string
        return "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
    endfunction
    
    constant function SpellPrefix_GetDamage takes integer level returns real
        return 60. + level * 40
    endfunction

  10. Your functions need much better names and you need to follow the standard convention that everyone follows. Give your functions a prefix unique to your spell too so the chances of colliding with other functions in the map code aren't high.
    Read this for a detailed reference of how we should name our functions, constants, variables and the like.

There are probably some optimization issues I haven't looked at, but I can get to those when the code is in a better state.
Cheers.
 
Yes, I can see the code is mostly better now, but there are a few things that need to be fixed and I'll list them out in detail eventually.

As for the SquareRoot part, are you 100% sure it's not working?

I mean, it works because:

(√x == y) is a boolean expression exactly equivalent to (x == y^2)

What we're doing is removing the SquareRoot and comparing with the other side squared (140*140)
 
even so, it's bad coding and doesn't compile in the standard world editor as a labelled JASS resource and not vJASS it -should- compile in the standard editor, so even with the understanding that some program may do that, it's not acceptable - not to mention bad practice.

Edit: thought I'd put my comment back in here since this is a delete-reupload, in future, just update it ok?

Tank-Commander said:
First: I'd fix the indentation, 0 indentation is really not that great for readability and general conventions
Secondly: Triggers lack proper names
Thirdly: Lacks configuration
Fourthly: Leaks a special effect
Fifthy: 0 documentation/code commenting
Sixthly: Spell lacks a proper name

only thing there no longer accurate is the special effect leak

and not sure if anybody else pointed this out already

JASS:
set bj_lastCreatedUnit = CreateUnitAtLoc(GetOwningPlayer(a), 'h000', Location(x,y), angle)
call UnitApplyTimedLife( bj_lastCreatedUnit, 'BTLF', 1.50 )
set dammy = bj_lastCreatedUnit

Why not set the variable "dammy" to the created unit (also should probably be "dummy") and scrap the whole bj_lastCreatedUnit

Eidt2: on the subject of being labelled a JASS resource, you have a globals block in the map header, firstly why?, secondly, why? oh and don't double post, use the edit button
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
As for the SquareRoot part, are you 100% sure it's not working?

I mean, it works because:

(√x == y) is a boolean expression exactly equivalent to (x == y^2)

What we're doing is removing the SquareRoot and comparing with the other side squared (140*140)

Negative x values?

Edit:
No, checked negative values don't crash the thread so it should behave the same...
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
REVIEW
TRIGGER FIXES
  • Locations should not be used in jass. Use real values instead.
  • Don't use bj_last created unit. Just do set dummy = CreateUnit()
  • You need to null dummy at end of trigger.
  • This will get overwritten every time.
    JASS:
    SaveTimerHandle(hash,GetHandleId(key1),1,t)
    Also when using null simply declare it in the function don't declare a local variable for it.
  • Don’t use TriggerAddAction() use TriggerAddCondition(). Look at my tutorial converting GUI to efficient jass.
  • This leaks a unit group every time.
    JASS:
    call ForGroup(GetUnitsInRangeOfLocAll(75,GetUnitLoc(dammy)), function catch2) // catch units in range of loc dammy
  • Fix your indentation. Please read the spell submission rules.
  • Don’t use BJs in jass. Use the natives that they call. (all BJs are not bad)
  • This leaks the locals every time it is called. It is also unnecessary and inefficient to store something that is only used once.
    JASS:
        function condfordead takes nothing returns boolean //death condition
            local timer t = GetExpiredTimer() // get timer
            local unit dammy = LoadUnitHandle(hash,GetHandleId(t),1) //load dammy
            if(not( GetUnitState(dammy, UNIT_STATE_LIFE) <= 0 == true)) then // if dammy is dead then
                return false
            endif
            return true
        endfunction
    Here is how it should be.
    JASS:
        function condfordead takes nothing returns boolean //death condition
            if( GetUnitState( LoadUnitHandle( hash, GetHandleId( GetExpiredTimer()), 1), UNIT_STATE_LIFE) <= 0 == true) then 
                // call your action trigger from here
            endif
            return false
        endfunction
  • Don’t use GetUnitState for detecting life of unit use GetWidgetLife().
  • To detect if a unit is dead you can’t check the unit’s health. Units can get healed even when they are dead. Use this to properly detect if a unit is dead.
    JASS:
    if IsUnitType( unit, UNIT_TYPE_DEAD) or GetUnitTypeId( unit) == 0 then
  • That was from a quick glance I may of missed some things.
FINAL RATING
  • Needs Fix
[/TD][/tr]
If you have any questions or problems with my review please tell me. Original Review Template by Doomlord.
 
Just to explain how deathismyfriend's check works:

Alive check:
not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0
is equal to:
not IsUnitType(u, UNIT_TYPE_DEAD) and not GetUnitTypeId(u) == 0

By De Morgan's Law, (¬A ∧ ¬B) ⇔ ¬(A ∨ B)

So it's equivalent to:

not (IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0)

And to negate that and get a death check, we just remove the not operator and get:

IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0

This however, is sub-optimal.

To check if a unit is dead, you do this:
IsUnitType(u, UNIT_TYPE_DEAD)

But do note, if this check returns false, the unit is not necessarily alive.

In fact, doing a life check to detect a dead unit is valid along with the typeId check.
GetWidgetLife(u) <= 0.405 and GetUnitTypeId(u) != 0 is a valid death check.
 
Top