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

[Spell] Hook. No hooking, but stunning (JASS)

Status
Not open for further replies.
Level 7
Joined
Feb 9, 2021
Messages
301
Nah, what laziness, also I wanna @maxodors explains me what is that but well,

@maxodors, also I made my best to fix the code and I think is done, because now it can be multicasted without problem
The main things I changed are:
1) I added this function
vJASS:
call UnitRemoveBuffBJ( 'B00C', TG_Stunned_U[TG_Loop_Int] )
to the first if block, just in case, because the stun is not removed automatically if the unit is paused.
2) In the part of
vJASS:
if GetWidgetLife(TG_Target[TG_Loop_Int]) > 0.405 and not IsUnitType(TG_Target[TG_Loop_Int], UNIT_TYPE_MAGIC_IMMUNE) and IsUnitEnemy(TG_Target[TG_Loop_Int], GetOwningPlayer(TG_Caster[TG_Index])) and IsUnitType(TG_Target[TG_Loop_Int], UNIT_TYPE_STRUCTURE) == false then
is a mistake, you wrote "TG_Index" instead of "TG_Loop_Int" in some part.
3) I add to
vJASS:
elseif TG_Unit_Is_Grappled[TG_Loop_Int] and UnitHasBuffBJ(TG_Stunned_U[TG_Loop_Int], 'B00C') and TG_Stun_Dur[TG_Loop_Int] < TG_Stun_Max_Dur then
this part
vJASS:
TG_Stunned_U[TG_Loop_Int] != null
because you can't check if has a buff a no unit
4) I changed
vJASS:
elseif GetUnitCurrentOrder(TG_Caster[TG_Loop_Int]) != String2OrderIdBJ("absorb") or TG_Process[TG_Loop_Int] > TG_Dist or TG_Unit_Is_Grappled[TG_Loop_Int] and not UnitHasBuffBJ(TG_Stunned_U[TG_Loop_Int], 'B00C') then
for
vJASS:
elseif GetUnitCurrentOrder(TG_Caster[TG_Loop_Int]) != String2OrderIdBJ("absorb") or TG_Process[TG_Loop_Int] > TG_Dist or TG_Stun_Finished[TG_Loop_Int] or TG_Stun_Removed[TG_Loop_Int] then
because is better end the spell if some of the unique two variable conditions to end you have are true.
5) I changed
vJASS:
if TG_Loop_Int == 0 then
    call PauseTimer(TG_Timer)
         
endif
for
vJASS:
if TG_Index == 0 then
    call PauseTimer(TG_Timer)
         
endif
because that was a mistake
6) I added
vJASS:
call GroupClear(TG_Group)
to the part of the group loop, just in case.
JASS:
globals
    integer TG_Index = 0
    unit array TG_Caster
    unit array TG_Dummy
    unit array TG_Dummy_Effect
    unit array TG_Grappled_U
    unit array TG_Target
    unit array TG_Stun_Dummy
    unit array TG_Stunned_U
    unit TG_Effect
    real array TG_Angel
    real array TG_Process
    real array TG_Stun_Dur
    real array TG_Max_Targets
    integer array TG_Counter
    timer TG_Timer = CreateTimer()
    timer TG_Stun_Timer = CreateTimer()
    integer TG_Loop_Int = 0
    integer TG_Hash = 0
    group TG_Group = CreateGroup()
    group TG_Effects_Group = CreateGroup()
    boolean array TG_Unit_Is_Grappled
    boolean array TG_Stun_Removed
    boolean array TG_Stun_Finished
 
        //configurations
    real TG_Stun_Max_Dur = 3.0
    real TG_Dmg = 100 * 0.0312500 / TG_Stun_Max_Dur
    real TG_Dist = 800
    real TG_Speed = 1300 * 0.0312500
    real TG_Aoe = 64
 
endglobals

function Trig_Tentacle_Grapple_ActionsV2 takes nothing returns nothing
    set TG_Loop_Int = 1
    loop
        exitwhen TG_Loop_Int > TG_Index
      
        if TG_Stun_Dur[TG_Loop_Int] >= TG_Stun_Max_Dur and not TG_Stun_Finished[TG_Loop_Int] then
            call UnitRemoveBuffBJ( 'B00C', TG_Stunned_U[TG_Loop_Int] )
            call IssueImmediateOrder(TG_Caster[TG_Loop_Int], "stop")
            call BJDebugMsg("StunFinished")
            set TG_Stun_Finished[TG_Loop_Int] = true
     
        elseif GetUnitCurrentOrder(TG_Caster[TG_Loop_Int]) != String2OrderIdBJ("absorb") and TG_Unit_Is_Grappled[TG_Loop_Int] and not TG_Stun_Removed[TG_Loop_Int] and not TG_Stun_Finished[TG_Loop_Int] then
            call UnitRemoveBuffBJ( 'B00C', TG_Stunned_U[TG_Loop_Int] )
            call BJDebugMsg("RemoveStun")
            set TG_Stun_Removed[TG_Loop_Int] = true
         
        elseif GetUnitCurrentOrder(TG_Caster[TG_Loop_Int]) != String2OrderIdBJ("absorb") or TG_Process[TG_Loop_Int] > TG_Dist or TG_Stun_Finished[TG_Loop_Int] or TG_Stun_Removed[TG_Loop_Int] then
            //after the stun finished, return the dummy and delete Effects
             
            set TG_Effect = LoadUnitHandle(Hash, TG_Loop_Int, TG_Counter[TG_Loop_Int])
            set TG_Counter[TG_Loop_Int] = TG_Counter[TG_Loop_Int] - 1
            //set TG_Effect = LoadUnitHandleBJ((TG_Counter[TG_Loop_Int] + 1 ), TG_Loop_Int, Hash)
            call RemoveUnit(TG_Effect)
            call GroupRemoveUnit(TG_Effects_Group,TG_Effect)
             
            call SetUnitX(TG_Dummy[TG_Loop_Int], GetUnitX(TG_Dummy[TG_Loop_Int]) + TG_Speed * Cos((TG_Angel[TG_Loop_Int] - 180) * bj_DEGTORAD))
            call SetUnitY(TG_Dummy[TG_Loop_Int], GetUnitY(TG_Dummy[TG_Loop_Int]) + TG_Speed * Sin((TG_Angel[TG_Loop_Int] - 180) * bj_DEGTORAD))
         
            if TG_Counter[TG_Loop_Int] == 0 then
                call RemoveUnit(TG_Dummy[TG_Loop_Int])
                 
                set TG_Caster[TG_Loop_Int] = TG_Caster[TG_Index]
                set TG_Dummy[TG_Loop_Int] = TG_Dummy[TG_Index]
                set TG_Process[TG_Loop_Int] = TG_Process[TG_Index]
                set TG_Unit_Is_Grappled[TG_Loop_Int] = TG_Unit_Is_Grappled[TG_Index]
                set TG_Stun_Removed[TG_Loop_Int] = TG_Stun_Removed[TG_Index]
                set TG_Stun_Finished[TG_Loop_Int] = TG_Stun_Finished[TG_Index]
                set TG_Dummy_Effect[TG_Loop_Int] = TG_Dummy_Effect[TG_Index]
                set TG_Grappled_U[TG_Loop_Int] = TG_Grappled_U[TG_Index]
                set TG_Target[TG_Loop_Int] = TG_Target[TG_Index]
                set TG_Stun_Dummy[TG_Loop_Int] = TG_Stun_Dummy[TG_Index]
                set TG_Stunned_U[TG_Loop_Int] = TG_Stunned_U[TG_Index]
                set TG_Angel[TG_Loop_Int] = TG_Angel[TG_Index]
                set TG_Process[TG_Loop_Int] = TG_Process[TG_Index]
                set TG_Stun_Dur[TG_Loop_Int] = TG_Stun_Dur[TG_Index]
                set TG_Counter[TG_Loop_Int] = TG_Counter[TG_Index]
                set TG_Max_Targets[TG_Loop_Int] = TG_Max_Targets[TG_Index]
             
                set TG_Hash=1
                loop
                    exitwhen TG_Hash>TG_Counter[TG_Index]
                    call SaveUnitHandle(Hash, TG_Loop_Int, TG_Hash, LoadUnitHandle(Hash, TG_Index, TG_Hash))
                    set TG_Hash=TG_Hash+1
                endloop
         
                set TG_Index = TG_Index - 1
                set TG_Loop_Int = TG_Loop_Int - 1
             
                if TG_Index == 0 then
                    call PauseTimer(TG_Timer)
         
                endif
            endif
        elseif TG_Unit_Is_Grappled[TG_Loop_Int] and TG_Stunned_U[TG_Loop_Int] != null and UnitHasBuffBJ(TG_Stunned_U[TG_Loop_Int], 'B00C') and TG_Stun_Dur[TG_Loop_Int] < TG_Stun_Max_Dur then
            set TG_Stun_Dur[TG_Loop_Int] = TG_Stun_Dur[TG_Loop_Int] + 0.0312500
            call UnitDamageTarget(TG_Caster[TG_Loop_Int], TG_Stunned_U[TG_Loop_Int], TG_Dmg, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
         
        else
                //Move Dummy and Create Effects
            set TG_Counter[TG_Loop_Int] = TG_Counter[TG_Loop_Int] + 1
            set TG_Process[TG_Loop_Int] = TG_Process[TG_Loop_Int] + TG_Speed
         
            set TG_Dummy_Effect[TG_Loop_Int] = CreateUnit(GetOwningPlayer(TG_Caster[TG_Loop_Int]), 'h003', GetUnitX(TG_Dummy[TG_Loop_Int]), GetUnitY(TG_Dummy[TG_Loop_Int]), TG_Angel[TG_Loop_Int])
            call GroupAddUnit(TG_Effects_Group, TG_Dummy_Effect[TG_Loop_Int])
            call SaveUnitHandle(Hash, TG_Loop_Int, TG_Counter[TG_Loop_Int], TG_Dummy_Effect[TG_Loop_Int])
             
            call SetUnitX(TG_Dummy[TG_Loop_Int], GetUnitX(TG_Dummy[TG_Loop_Int]) + TG_Speed * Cos(TG_Angel[TG_Loop_Int] * bj_DEGTORAD))
            call SetUnitY(TG_Dummy[TG_Loop_Int], GetUnitY(TG_Dummy[TG_Loop_Int]) + TG_Speed * Sin(TG_Angel[TG_Loop_Int] * bj_DEGTORAD))
         
             
                //Pick Units Around Dummy
             
            call GroupEnumUnitsInRange(TG_Group, GetUnitX(TG_Dummy[TG_Loop_Int]), GetUnitY(TG_Dummy[TG_Loop_Int]), TG_Aoe, null)
            loop
                set TG_Target[TG_Loop_Int] = FirstOfGroup(TG_Group)
                exitwhen TG_Max_Targets[TG_Loop_Int] == 0 or TG_Target[TG_Loop_Int] == null
                if GetWidgetLife(TG_Target[TG_Loop_Int]) > 0.405 and not IsUnitType(TG_Target[TG_Loop_Int], UNIT_TYPE_MAGIC_IMMUNE) and IsUnitEnemy(TG_Target[TG_Loop_Int], GetOwningPlayer(TG_Caster[TG_Loop_Int])) and not IsUnitType(TG_Target[TG_Loop_Int], UNIT_TYPE_STRUCTURE) then
                    set TG_Stunned_U[TG_Loop_Int] = TG_Target[TG_Loop_Int]
                    set TG_Unit_Is_Grappled[TG_Loop_Int] = true
                 
                    set TG_Max_Targets[TG_Loop_Int] = TG_Max_Targets[TG_Loop_Int] - 1
                 
                    set TG_Stun_Dummy[TG_Loop_Int] = CreateUnit(GetOwningPlayer(TG_Caster[TG_Loop_Int]), 'h001', GetUnitX(TG_Caster[TG_Loop_Int]), GetUnitY(TG_Caster[TG_Loop_Int]), 270)
                    call UnitAddAbilityBJ( 'A00K', TG_Stun_Dummy[TG_Loop_Int])
                    call IssueTargetOrder(TG_Stun_Dummy[TG_Loop_Int], "thunderbolt", TG_Target[TG_Loop_Int])
                     
                    call GroupClear(TG_Group)
                endif
                call GroupRemoveUnit(TG_Group, TG_Target[TG_Loop_Int])
            endloop
        endif
         
        set TG_Loop_Int = TG_Loop_Int + 1
    endloop
 

endfunction


function Trig_Tentacle_Grapple_Actions takes nothing returns nothing
    local real xCaster = GetUnitX(TG_Caster[TG_Index])
    local real yCaster = GetUnitY(TG_Caster[TG_Index])
    local real xTarget = GetSpellTargetX()
    local real yTarget = GetSpellTargetY()
 
    set TG_Angel[TG_Index] = bj_RADTODEG * Atan2(yTarget - yCaster, xTarget - xCaster)
 
    set TG_Dummy[TG_Index] = CreateUnit(GetOwningPlayer(TG_Caster[TG_Index]), 'h003', xCaster + 0 * Cos(TG_Angel[TG_Index] * bj_DEGTORAD), yCaster + 0 * Sin(TG_Angel[TG_Index] * bj_DEGTORAD), TG_Angel[TG_Index])
    call SetUnitPathing(TG_Dummy[TG_Index], false)
    //Animation, Sounds
    call SetUnitAnimationByIndex(TG_Caster[TG_Index], 5)
 
    //Addtional Settings
    set TG_Process[TG_Index] = 0
    set TG_Counter[TG_Index] = 0
    set TG_Unit_Is_Grappled[TG_Index] = false
    set TG_Grappled_U[TG_Index] = null
    set TG_Stun_Removed[TG_Index] = false
    set TG_Stun_Finished[TG_Index] = false
    set TG_Target[TG_Index] = null
    set TG_Dummy_Effect[TG_Index] = null
    set TG_Stun_Dummy[TG_Index] = null
    set TG_Stunned_U[TG_Index] = null
    set TG_Stun_Dur[TG_Index] = 0
    set TG_Max_Targets[TG_Index] = 1
    //Timer
    if TG_Index == 1 then
        call TimerStart(TG_Timer, 0.0312500, true, function Trig_Tentacle_Grapple_ActionsV2)
    endif
 
endfunction


function Trig_Tentacle_Grapple_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A00J' then
        set TG_Index = (TG_Index + 1)
        set TG_Caster[TG_Index] = GetTriggerUnit()
        return true
    endif
    return false
endfunction



//===========================================================================
function InitTrig_Tentacle_Grapple takes nothing returns nothing
    set gg_trg_Tentacle_Grapple = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Tentacle_Grapple, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Tentacle_Grapple, Condition( function Trig_Tentacle_Grapple_Conditions ) )
    call TriggerAddAction( gg_trg_Tentacle_Grapple, function Trig_Tentacle_Grapple_Actions )
endfunction

One problem is if the target dies while is stunned the chains continues the process like anything happens but it can't grab another unit, but I think you can solve it easily.

Also:

For this you have to do A LOT of extra work, I don't know if you wanna, but lets see

For this you just have to comparte the path of the terrain your dummy is gonna move
Thank you. I see that I made some stupid mistakes, which bugged the trigger.

Sorry, I completely forgot about the hashtable. I am planning to utilise cJass in all my future code because it is easier to understand and faster to make.

There are still some problems:
1. For some reason, the dummy is created with a different distance from the unit, depending on the direction of the cast. I want it to look like the hook comes out of his mouth. However, this dislocation of the dummy ruins it.
2. If you cast a spell and instantly stop it, it bugs sometimes. If you do it multiple times, you get a fatal error.

I do want extra work, as I want to make a great map rather than a mediocre one. However, I do not want to burden you or other people too much. Maybe there is an example of something similar somewhere? These are my first spells, so I just started learning.
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
For some reason, the dummy is created with a different distance from the unit, depending on the direction of the cast. I want it to look like the hook comes out of his mouth. However, this dislocation of the dummy ruins it.
For this, better change the angle instead of getting the argument of the line of the caster and the target better use
vJASS:
GetUnitFacing(<Your caster>) //Is in degrees
If you cast a spell and instantly stop it, it bugs sometimes. If you do it multiple times, you get a fatal error.
I'm not sure why is this.

And thinking well, this things are not too dificult to achieve.
1. If the target unit is moved during the grapple, I need to create more effects for the "hook" to follow and change the direction of the previous effects accordingly
2. If the caster is moved, similar to the above
3. I want the hook to be connected to the caster. If the caster moves in the opposite direction from the hook, the hook should be connected accordingly ( I sent a picture before)
To do this you have to save the position of the caster when you cast the spell and with the timer check if the caster changed position (use the coords x and y and compare them every instance) like this:
If the chain doesn't reach a unit yet:
vJASS:
// When you cast
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)

// In every instance
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set TG_Angle = GetUnitFacing(TG_Caster)
    set Int = 1
    loop
        exitwhen Int > TG_Counter
        call RemoveUnit(LoadUnitHandle(Hash, TG_Loop_Int, Int))
        call SaveUnitHandle(Hash, TG_Loop_Int, Int, CreateUnit(GetOwningPlayer(TG_Caster), 'h003', First_X + 180 * Cos(TG_Angle * bj_DEGTORAD), yCaster + 180 * Sin(TG_Angle * bj_DEGTORAD), TG_Angle))
        set Int = Int + 1
    endloop
endif
If the chain reaches a unit:
vJASS:
// When you cast
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)

// When you catch it
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>)

// In every instance
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    loop
        exitwhen Int > TG_Counter
        call RemoveUnit(LoadUnitHandle(Hash, TG_Loop_Int, Int))
        set Int = Int + 1
    endloop
    // As I see every shackle is 180 units from one to another
    set TG_Angle = AngleBetweenPoints(<Caster loc>,<Target loc>) // Or you can do this with the x and y
    call SetUnitFacing(TG_Caster,TG_Angle)
    set TG_Counter = R2I(DistanceBetweenPoints(<Caster loc>,<Target loc>) / 180) // Or you can do this with the x and y
    loop
        exitwhen Int > TG_Counter
        call SaveUnitHandle(Hash, TG_Loop_Int, Int, CreateUnit(GetOwningPlayer(TG_Caster), 'h003', First_X + 180 * Cos(TG_Angle * bj_DEGTORAD), yCaster + 180 * Sin(TG_Angle * bj_DEGTORAD), TG_Angle))
        set Int = Int + 1
    endloop
endif
Obviously you have to adapt this to your code.
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
For this, better change the angle instead of getting the argument of the line of the caster and the target better use
vJASS:
GetUnitFacing(<Your caster>) //Is in degrees

I'm not sure why is this.

And thinking well, this things are not too dificult to achieve.

To do this you have to save the position of the caster when you cast the spell and with the timer check if the caster changed position (use the coords x and y and compare them every instance) like this:
If the chain doesn't reach a unit yet:
vJASS:
// When you cast
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)

// In every instance
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set TG_Angle = GetUnitFacing(TG_Caster)
    set Int = 1
    loop
        exitwhen Int > TG_Counter
        call RemoveUnit(LoadUnitHandle(Hash, TG_Loop_Int, Int))
        call SaveUnitHandle(Hash, TG_Loop_Int, Int, CreateUnit(GetOwningPlayer(TG_Caster), 'h003', First_X + 180 * Cos(TG_Angle * bj_DEGTORAD), yCaster + 180 * Sin(TG_Angle * bj_DEGTORAD), TG_Angle))
        set Int = Int + 1
    endloop
endif
If the chain reaches a unit:
vJASS:
// When you cast
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)

// When you catch it
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>)

// In every instance
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    loop
        exitwhen Int > TG_Counter
        call RemoveUnit(LoadUnitHandle(Hash, TG_Loop_Int, Int))
        set Int = Int + 1
    endloop
    // As I see every shackle is 180 units from one to another
    set TG_Angle = AngleBetweenPoints(<Caster loc>,<Target loc>) // Or you can do this with the x and y
    call SetUnitFacing(TG_Caster,TG_Angle)
    set TG_Counter = R2I(DistanceBetweenPoints(<Caster loc>,<Target loc>) / 180) // Or you can do this with the x and y
    loop
        exitwhen Int > TG_Counter
        call SaveUnitHandle(Hash, TG_Loop_Int, Int, CreateUnit(GetOwningPlayer(TG_Caster), 'h003', First_X + 180 * Cos(TG_Angle * bj_DEGTORAD), yCaster + 180 * Sin(TG_Angle * bj_DEGTORAD), TG_Angle))
        set Int = Int + 1
    endloop
endif
Obviously you have to adapt this to your code.
I changed the angel, but the problem remained (attached a screenshot):

JASS:
set TG_Angel[TG_Index] = GetUnitFacing(TG_Caster[TG_Index])
   
    set TG_Dummy[TG_Index] = CreateUnit(GetOwningPlayer(TG_Caster[TG_Index]), 'h003', xCaster + 10 * Cos(TG_Angel[TG_Index] * bj_DEGTORAD), yCaster + 10 * Sin(TG_Angel[TG_Index] * bj_DEGTORAD), TG_Angel[TG_Index])


Let me get back to you on other things after I try it
1615974299073.png
 
Level 7
Joined
Feb 9, 2021
Messages
301
For this, better change the angle instead of getting the argument of the line of the caster and the target better use
vJASS:
GetUnitFacing(<Your caster>) //Is in degrees

I'm not sure why is this.

And thinking well, this things are not too dificult to achieve.

To do this you have to save the position of the caster when you cast the spell and with the timer check if the caster changed position (use the coords x and y and compare them every instance) like this:
If the chain doesn't reach a unit yet:
vJASS:
// When you cast
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)

// In every instance
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set TG_Angle = GetUnitFacing(TG_Caster)
    set Int = 1
    loop
        exitwhen Int > TG_Counter
        call RemoveUnit(LoadUnitHandle(Hash, TG_Loop_Int, Int))
        call SaveUnitHandle(Hash, TG_Loop_Int, Int, CreateUnit(GetOwningPlayer(TG_Caster), 'h003', First_X + 180 * Cos(TG_Angle * bj_DEGTORAD), yCaster + 180 * Sin(TG_Angle * bj_DEGTORAD), TG_Angle))
        set Int = Int + 1
    endloop
endif
If the chain reaches a unit:
vJASS:
// When you cast
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)

// When you catch it
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>)

// In every instance
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    loop
        exitwhen Int > TG_Counter
        call RemoveUnit(LoadUnitHandle(Hash, TG_Loop_Int, Int))
        set Int = Int + 1
    endloop
    // As I see every shackle is 180 units from one to another
    set TG_Angle = AngleBetweenPoints(<Caster loc>,<Target loc>) // Or you can do this with the x and y
    call SetUnitFacing(TG_Caster,TG_Angle)
    set TG_Counter = R2I(DistanceBetweenPoints(<Caster loc>,<Target loc>) / 180) // Or you can do this with the x and y
    loop
        exitwhen Int > TG_Counter
        call SaveUnitHandle(Hash, TG_Loop_Int, Int, CreateUnit(GetOwningPlayer(TG_Caster), 'h003', First_X + 180 * Cos(TG_Angle * bj_DEGTORAD), yCaster + 180 * Sin(TG_Angle * bj_DEGTORAD), TG_Angle))
        set Int = Int + 1
    endloop
endif
Obviously you have to adapt this to your code.
I fixed the fatal error. The problem was that when the spell got cancelled while no dummies were created, it was deleting dummies that did not exist. Therefore, fatal. So, now I just first check if TG_Counter[TG_Loop_Int] == 0 then clear everything else remove effects.
 
Level 7
Joined
Feb 9, 2021
Messages
301
@maxodors nice, what about the rest?
Still working on additional links. However, I fixed the problem of dummies going out of the map and stopping on trees by the TerrainPathability library.

I found an interesting method of how to add new links to the hook: Meat Hook v4.0 . However, I do not understand some libraries that he is using, such as List, Dummy UntiIndexter, but it looks that they can be useful in this case. It seems like the Dummy library can solve the problem with the location of the dummy units (on creation).

Meanwhile, there are some extra features that I did not think about before:

1. Check this moment in 0.5 speed:
. After the hook connects, it relocates to the centre of the unit. Is it possible to do this?
2. If the target is moved to the side, the dummy effects (links) and the hook have to change their location to form a straight line relative to the target.
3. If the target's height change (knock-up), the hook and dummy effects should change the angle on the Z axis and increase in height accordingly (I am not sure if that is even possible).
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
Still working on additional links. However, I fixed the problem of dummies going out of the map and stopping on trees by the TerrainPathability library.

I found an interesting method of how to add new links to the hook: Meat Hook v4.0 . However, I do not understand some libraries that he is using, such as List, Dummy UntiIndexter, but it looks that they can be useful in this case. It seems like the Dummy library can solve the problem with the location of the dummy units (on creation).

Meanwhile, there are some extra features that I did not think about before:

1. Check this moment in 0.5 speed:
. After the hook connects, it relocates to the centre of the unit. Is it possible to do this?
2. If the target is moved to the side, the dummy effects (links) and the hook have to change their location to form a straight line relative to the target.
3. If the target's height change (knock-up), the hook and dummy effects should change the angle on the Z axis and increase in height accordingly (I am not sure if that is even possible).
To achieve this you have to constantly move the chain and every link and change its angle and its flying height (Just using the functions "SetUnitFlyHeight") because your links are units, but this will be easier if instead of units use a lightning and just constantly change the position of every side, and with the functions "AddLightningEx" and "MoveLightningEx" you can also change the height.
But move the chain at the end is not too difficult just follow this steps:
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Angle != GetUnitFacing(<Caster>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_Angle = GetUnitFacing(<Caster>)
    
    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        set Int = Int + 1
    endloop
endif
This is before the chain doesn't grab the target, but when it does you have to add steps:
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//When you grab the target
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>) //The facing is unnecesary

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Angle != GetUnitFacing(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    
    set dx = First_X_t - First_X
    set dy = First_Y_t - First_Y
    
    set First_Angle = Atan2(dy, dx)
    call SetUnitFacing(<Caster>, First_Angle)
    
    set Temp_Int = R2I(SquareRoot(dx * dx + dy * dy) / 180)
    
    if Temp_Int < Counter then
        Int = Temp_Int
        loop
            exitwhen Int > Counter
            call RemoveUnit(Chain[Int])
            Int = Int + 1
        endloop
    elseif Temp_Int > Counter
        Int = Counter
        loop
            exitwhen Int > Temp_Int
            set Chain[Int] = CreateUnit(player, id, First_X, First_Y, First_Angle) //Really, no matter the initial position because later you will move them
            Int = Int + 1
        endloop
    endif
    set Counter = Temp_Int
    
    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        call SetUnitFacing(Chain[Int], First_Angle)
        set Int = Int + 1
    endloop
endif

Now if you wanna also change the height of every link, this won't be to clear because I think you can't change the vertical orientation of a object in W3 but we just will change the Z position with the function "SetUnitFlyHeight" (this only works with flying units so you have to set this to your dummies):
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Z = GetUnitFlyHeight(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//When you grab the target
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>)
set First_Z_t = GetUnitFlyHeight(<Target>)

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Z != GetUnitFlyHeight(<Caster>) or First_Angle != GetUnitFacing(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) or First_Z_t != GetUnitFlyHeight(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_Z = GetUnitFlyHeight(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    set First_Z_t = GetUnitFlyHeight(<Target>)
    
    set dx = First_X_t - First_X
    set dy = First_Y_t - First_Y
    set dz = First_Z_t - First_Z
    
    set First_Angle = Atan2(dy, dx)
    call SetUnitFacing(<Caster>, First_Angle)
    
    set Temp_Int = R2I(SquareRoot(dx * dx + dy * dy) / 180)
    
    if Temp_Int < Counter then
        //If the chain needs to be smaller
        Int = Temp_Int
        loop
            exitwhen Int > Counter
            call RemoveUnit(Chain[Int])
            Int = Int + 1
        endloop
    elseif Temp_Int > Counter
        //If the chain needs to be bigger
        Int = Counter
        loop
            exitwhen Int > Temp_Int
            set Chain[Int] = CreateUnit(player, id, First_X, First_Y, First_Angle)
            Int = Int + 1
        endloop
    endif
    set Counter = Temp_Int
    set h = SquareRoot(dx * dx + dy * dy) / Counter //This is to set the Z of every link
    
    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        call SetUnitFacing(Chain[Int], First_Angle)
        call SetUnitFlyHeight(Chain[Int], First_Z + Int * h, 99999)
        set Int = Int + 1
    endloop
endif
Obviously you have to adapt this to your code.
 
Level 7
Joined
Feb 9, 2021
Messages
301
To achieve this you have to constantly move the chain and every link and change its angle and its flying height (Just using the functions "SetUnitFlyHeight") because your links are units, but this will be easier if instead of units use a lightning and just constantly change the position of every side, and with the functions "AddLightningEx" and "MoveLightningEx" you can also change the height.
But move the chain at the end is not too difficult just follow this steps:
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Angle != GetUnitFacing(<Caster>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_Angle = GetUnitFacing(<Caster>)

    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        set Int = Int + 1
    endloop
endif
This is before the chain doesn't grab the target, but when it does you have to add steps:
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//When you grab the target
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>) //The facing is unnecesary

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Angle != GetUnitFacing(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)

    set dx = First_X_t - First_X
    set dy = First_Y_t - First_Y

    set First_Angle = Atan2(dy, dx)
    call SetUnitFacing(<Caster>, First_Angle)

    set Temp_Int = R2I(SquareRoot(dx * dx + dy * dy) / 180)

    if Temp_Int < Counter then
        Int = Temp_Int
        loop
            exitwhen Int > Counter
            call RemoveUnit(Chain[Int])
            Int = Int + 1
        endloop
    elseif Temp_Int > Counter
        Int = Counter
        loop
            exitwhen Int > Temp_Int
            set Chain[Int] = CreateUnit(player, id, First_X, First_Y, First_Angle) //Really, no matter the initial position because later you will move them
            Int = Int + 1
        endloop
    endif
    set Counter = Temp_Int

    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        call SetUnitFacing(Chain[Int], First_Angle)
        set Int = Int + 1
    endloop
endif

Now if you wanna also change the height of every link, this won't be to clear because I think you can't change the vertical orientation of a object in W3 but we just will change the Z position with the function "SetUnitFlyHeight" (this only works with flying units so you have to set this to your dummies):
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Z = GetUnitFlyHeight(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//When you grab the target
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>)
set First_Z_t = GetUnitFlyHeight(<Target>)

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Z != GetUnitFlyHeight(<Caster>) or First_Angle != GetUnitFacing(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) or First_Z_t != GetUnitFlyHeight(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_Z = GetUnitFlyHeight(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    set First_Z_t = GetUnitFlyHeight(<Target>)

    set dx = First_X_t - First_X
    set dy = First_Y_t - First_Y
    set dz = First_Z_t - First_Z

    set First_Angle = Atan2(dy, dx)
    call SetUnitFacing(<Caster>, First_Angle)

    set Temp_Int = R2I(SquareRoot(dx * dx + dy * dy) / 180)

    if Temp_Int < Counter then
        //If the chain needs to be smaller
        Int = Temp_Int
        loop
            exitwhen Int > Counter
            call RemoveUnit(Chain[Int])
            Int = Int + 1
        endloop
    elseif Temp_Int > Counter
        //If the chain needs to be bigger
        Int = Counter
        loop
            exitwhen Int > Temp_Int
            set Chain[Int] = CreateUnit(player, id, First_X, First_Y, First_Angle)
            Int = Int + 1
        endloop
    endif
    set Counter = Temp_Int
    set h = SquareRoot(dx * dx + dy * dy) / Counter //This is to set the Z of every link

    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        call SetUnitFacing(Chain[Int], First_Angle)
        call SetUnitFlyHeight(Chain[Int], First_Z + Int * h, 99999)
        set Int = Int + 1
    endloop
endif
Obviously you have to adapt this to your code.
I am still implementing the changes. I think there are some problems with your code: it should save the spell target location a find an angle relative to the spell target location rather than the unit's facing. This is because the spell target location should remain the same at all times before the target is reached, while the angle should change only relative to the caster. (I fixed it already)

Menawihle, I found this problem during testing while trying to implement the system, and there is still a problem with the location of the first dummy.
1616166821637.png

1616166721539.png

1616166965844.png
1616168229761.png

Edit [19/03/2021] (19:06 GMT) I uploaded the latest version of the map for now. I will update you after I finish trying implementing your features.
 

Attachments

  • Mammon.w3x
    144.6 KB · Views: 11
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
To achieve this you have to constantly move the chain and every link and change its angle and its flying height (Just using the functions "SetUnitFlyHeight") because your links are units, but this will be easier if instead of units use a lightning and just constantly change the position of every side, and with the functions "AddLightningEx" and "MoveLightningEx" you can also change the height.
But move the chain at the end is not too difficult just follow this steps:
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Angle != GetUnitFacing(<Caster>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_Angle = GetUnitFacing(<Caster>)
 
    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        set Int = Int + 1
    endloop
endif
This is before the chain doesn't grab the target, but when it does you have to add steps:
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//When you grab the target
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>) //The facing is unnecesary

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Angle != GetUnitFacing(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
 
    set dx = First_X_t - First_X
    set dy = First_Y_t - First_Y
 
    set First_Angle = Atan2(dy, dx)
    call SetUnitFacing(<Caster>, First_Angle)
 
    set Temp_Int = R2I(SquareRoot(dx * dx + dy * dy) / 180)
 
    if Temp_Int < Counter then
        Int = Temp_Int
        loop
            exitwhen Int > Counter
            call RemoveUnit(Chain[Int])
            Int = Int + 1
        endloop
    elseif Temp_Int > Counter
        Int = Counter
        loop
            exitwhen Int > Temp_Int
            set Chain[Int] = CreateUnit(player, id, First_X, First_Y, First_Angle) //Really, no matter the initial position because later you will move them
            Int = Int + 1
        endloop
    endif
    set Counter = Temp_Int
 
    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        call SetUnitFacing(Chain[Int], First_Angle)
        set Int = Int + 1
    endloop
endif

Now if you wanna also change the height of every link, this won't be to clear because I think you can't change the vertical orientation of a object in W3 but we just will change the Z position with the function "SetUnitFlyHeight" (this only works with flying units so you have to set this to your dummies):
vJASS:
//When you cast the spell you must save the coords and the facing angle of the caster
set First_X = GetUnitX(<Caster>)
set First_Y = GetUnitY(<Caster>)
set First_Z = GetUnitFlyHeight(<Caster>)
set First_Angle = GetUnitFacing(<Caster>)

//When you grab the target
set First_X_t = GetUnitX(<Target>)
set First_Y_t = GetUnitY(<Target>)
set First_Z_t = GetUnitFlyHeight(<Target>)

//In the timer
if First_X != GetUnitX(<Caster>) or First_Y != GetUnitY(<Caster>) or First_Z != GetUnitFlyHeight(<Caster>) or First_Angle != GetUnitFacing(<Caster>) or First_X_t != GetUnitX(<Target>) or First_Y_t != GetUnitY(<Target>) or First_Z_t != GetUnitFlyHeight(<Target>) then
    set First_X = GetUnitX(<Caster>)
    set First_Y = GetUnitY(<Caster>)
    set First_Z = GetUnitFlyHeight(<Caster>)
    set First_X_t = GetUnitX(<Target>)
    set First_Y_t = GetUnitY(<Target>)
    set First_Z_t = GetUnitFlyHeight(<Target>)
 
    set dx = First_X_t - First_X
    set dy = First_Y_t - First_Y
    set dz = First_Z_t - First_Z
 
    set First_Angle = Atan2(dy, dx)
    call SetUnitFacing(<Caster>, First_Angle)
 
    set Temp_Int = R2I(SquareRoot(dx * dx + dy * dy) / 180)
 
    if Temp_Int < Counter then
        //If the chain needs to be smaller
        Int = Temp_Int
        loop
            exitwhen Int > Counter
            call RemoveUnit(Chain[Int])
            Int = Int + 1
        endloop
    elseif Temp_Int > Counter
        //If the chain needs to be bigger
        Int = Counter
        loop
            exitwhen Int > Temp_Int
            set Chain[Int] = CreateUnit(player, id, First_X, First_Y, First_Angle)
            Int = Int + 1
        endloop
    endif
    set Counter = Temp_Int
    set h = SquareRoot(dx * dx + dy * dy) / Counter //This is to set the Z of every link
 
    set Int = 1
    loop
        exitwhen Int > Counter
        call SetUnitPosition(Chain[Int], First_X + 180 * Int * Cos(First_Angle), First_Y + 180 * Int * Sin(First_Angle))
        call SetUnitFacing(Chain[Int], First_Angle)
        call SetUnitFlyHeight(Chain[Int], First_Z + Int * h, 99999)
        set Int = Int + 1
    endloop
endif
Obviously you have to adapt this to your code.
Also, as far as I can understand the logic, you remove and create units when Int more or less than the counter. The problem is that later it will make the chain look bad when the spell is finished and links starting to get deleted.

I already came up with a way possible way to fix it and will try it tomorrow: I will compare the distance between the first dummy effect and the caster and compare the distance between the last dummy effect and the target. If the distance is more than TG_Speed, I will create an extra dummy unit and reindex hashtable to make the new dummy the first in the counter or the last in the counter (depending on where it was added). After this, my function will clear it all up in the right order. My concern is that it might be not the most efficient way to do it. This guy's code looks much simpler: Meat Hook v4.0
 
Last edited:
Level 7
Joined
Feb 9, 2021
Messages
301
Well I don't have to try to make a chain, so obviously my code will have errors, but I think you can handle that.
I finally realised the idea of your code, and I think it is brilliant. One questions, how can I add/remove links efficiently before the chain grabs the target?

I came up with this idea, but this doesn't seem to be very efficient. (DistanceBetweenPoints command just calculates distance between points in a separate function)
JASS:
if TG_C_Ini_X[TG_Loop_Int] != GetUnitX(TG_Caster[TG_Loop_Int]) or TG_C_Ini_Y[TG_Loop_Int] != GetUnitY(TG_Caster[TG_Loop_Int]) then
            set TG_C_Ini_X[TG_Loop_Int] = GetUnitX(TG_Caster[TG_Loop_Int])
            set TG_C_Ini_Y[TG_Loop_Int] = GetUnitY(TG_Caster[TG_Loop_Int])
            set TG_Cur_Angle[TG_Loop_Int] = bj_RADTODEG * Atan2(GetUnitY(TG_Dummy[TG_Loop_Int]) - GetUnitY(TG_Caster[TG_Loop_Int]), GetUnitX(TG_Dummy[TG_Loop_Int]) - GetUnitX(TG_Caster[TG_Loop_Int]))
        
                    set TG_Temp_Int[TG_Loop_Int] = R2I(GetDistanceBetweenCoordinates(TG_C_Ini_X[TG_Loop_Int] + 10 * Cos(TG_Cur_Angle[TG_Loop_Int]), GetUnitX(LoadUnitHandle(Hash, TG_Loop_Int, 1)), TG_C_Ini_Y[TG_Loop_Int] + 10 * Sin(TG_Cur_Angle[TG_Loop_Int] * bj_DEGTORAD), GetUnitY(LoadUnitHandle(Hash, TG_Loop_Int, 1))))
                    if R2I(GetDistanceBetweenCoordinates(TG_C_Ini_X[TG_Loop_Int] + 10 * Cos(TG_Cur_Angle[TG_Loop_Int]), GetUnitX(LoadUnitHandle(Hash, TG_Loop_Int, 1)), TG_C_Ini_Y[TG_Loop_Int] + 10 * Sin(TG_Cur_Angle[TG_Loop_Int] * bj_DEGTORAD), GetUnitY(LoadUnitHandle(Hash, TG_Loop_Int, 1)))) > TG_Speed  then
                        loop
                        exitwhen R2I(GetDistanceBetweenCoordinates(TG_C_Ini_X[TG_Loop_Int] + 10 * Cos(TG_Cur_Angle[TG_Loop_Int]), GetUnitX(LoadUnitHandle(Hash, TG_Loop_Int, 1)), TG_C_Ini_Y[TG_Loop_Int] + 10 * Sin(TG_Cur_Angle[TG_Loop_Int] * bj_DEGTORAD), GetUnitY(LoadUnitHandle(Hash, TG_Loop_Int, 1)))) < TG_Speed 
                            TG_Counter[TG_Loop_Int] = TG_Counter[TG_Loop_Int] + 1
                            TG_Temp_Counter[TG_Loop_Int] = TG_Counter[TG_Loop_Int]
                            TG_Temp_Counter2[TG_Loop_Int] = 1
                                loop
                                exitwhen TG_Temp_Counter2[TG_Loop_Int] == TG_Counter[TG_Loop_Int]
                                    TG_Temp_Counter2[TG_Loop_Int] = TG_Temp_Counter2[TG_Loop_Int] + 1
                                    SaveUnitHandle(Hash, TG_Loop_Int, TG_Temp_Counter[TG_Loop_Int], LoadUnitHandle(Hash, TG_Loop_Int, TG_Temp_Counter[TG_Loop_Int] - 1))
                                    TG_Temp_Counter[TG_Loop_Int] = TG_Temp_Counter[TG_Loop_Int] - 1
                                endloop
                            bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(TG_Caster[TG_Loop_Int]), 'h003', TG_C_Ini_X[TG_Loop_Int], TG_C_Ini_Y[TG_Loop_Int], TG_Cur_Angle[TG_Loop_Int]) 
                            SaveUnitHandle(Hash, TG_Loop_Int, 1, bj_lastCreatedUnit)
                        endloop
                    endif
                    
                    set TG_Int[TG_Loop_Int] = 1
                    loop
                        exitwhen TG_Int[TG_Loop_Int] > TG_Counter[TG_Loop_Int]
                        call SetUnitX(LoadUnitHandle(Hash, TG_Loop_Int, TG_Int[TG_Loop_Int]), GetUnitX(TG_Dummy[TG_Loop_Int]) + (TG_Counter[TG_Loop_Int] + 1 - TG_Int[TG_Loop_Int]) * TG_Speed * Cos((TG_Cur_Angle[TG_Loop_Int] - 180) * bj_DEGTORAD))
                        call SetUnitY(LoadUnitHandle(Hash, TG_Loop_Int, TG_Int[TG_Loop_Int]), GetUnitY(TG_Dummy[TG_Loop_Int]) + (TG_Counter[TG_Loop_Int] + 1 - TG_Int[TG_Loop_Int]) * TG_Speed * Sin((TG_Cur_Angle[TG_Loop_Int] - 180) * bj_DEGTORAD))
                        call SetUnitFacing(LoadUnitHandle(Hash, TG_Loop_Int, TG_Int[TG_Loop_Int]), TG_Cur_Angle[TG_Loop_Int])
   
                        set TG_Int[TG_Loop_Int] = TG_Int[TG_Loop_Int] + 1
                    
                    endloop
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
I finally realised the idea of your code, and I think it is brilliant.
Je.
how can I add/remove links efficiently before the chain grabs the target?
I think you don't need do that, because if there was not target so if the caster do a movement we can asume he only push or grab the chain but not stretch or shrink it.
I came up with this idea, but this doesn't seem to be very efficient. (DistanceBetweenPoints command just calculates distance between points in a separate function)
I don't get why did you a more complicated version of my example, better do it how I did it, and you have to know difference what variables you need save or need a different value for every index, because you add a index to my example of "Temp_Int" when the prefix "Temp" comes from "Temporal" that means "That lasts for a relatively short time" it could just be a local variable of that function.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Je.

I think you don't need do that, because if there was not target so if the caster do a movement we can asume he only push or grab the chain but not stretch or shrink it.

I don't get why did you a more complicated version of my example, better do it how I did it, and you have to know difference what variables you need save or need a different value for every index, because you add a index to my example of "Temp_Int" when the prefix "Temp" comes from "Temporal" that means "That lasts for a relatively short time" it could just be a local variable of that function.
I finally finished it. Let me know what do you think.
 

Attachments

  • Mammon.w3x
    335.7 KB · Views: 8
Level 24
Joined
Jun 26, 2020
Messages
1,850
I finally finished it. Let me know what do you think.
From what I see, everything is fine, in fact I congratulate you, my only complaints would be having this in the part when the chain doesn't reach the target when is not neccesary
vJASS:
set Temp_Int = R2I(GetDistanceBetweenCoordinates(xDum_Default, GetUnitX(TG_Dummy[TG_Loop_Int]), yDum_Default, GetUnitY(TG_Dummy[TG_Loop_Int]))) / R2I(TG_Speed)
and there is a problem using the function R2I if you did it dividend and divisor separately you can get a different value than if you did it all together like this
vJASS:
set Temp_Int = R2I(GetDistanceBetweenCoordinates(xDum_Default, GetUnitX(TG_Dummy[TG_Loop_Int]), yDum_Default, GetUnitY(TG_Dummy[TG_Loop_Int])) / TG_Speed)
Because the result can be more inaccurate in the way you did, for example, I think the way W3 convert real to integers is by truncation so if you have 14.123213 ÷ 3.74198 = 3.774262 so apply the function: R2I(14.123213 ÷ 3.74198) = R2I(3.774262) = 3 and in the way you did it it be like this R2I(14.123213) ÷ R2I(3.74198) = 14 ÷ 3 = 4.66667, but as the variable is an integer I think it will truncated to 4, so the thing will depend of what do you want, I think there is no much problem as you use this once and not a lot of times in an operation, but anyway, you will decide it.
 
Level 7
Joined
Feb 9, 2021
Messages
301
From what I see, everything is fine, in fact I congratulate you, my only complaints would be having this in the part when the chain doesn't reach the target when is not neccesary
vJASS:
set Temp_Int = R2I(GetDistanceBetweenCoordinates(xDum_Default, GetUnitX(TG_Dummy[TG_Loop_Int]), yDum_Default, GetUnitY(TG_Dummy[TG_Loop_Int]))) / R2I(TG_Speed)
and there is a problem using the function R2I if you did it dividend and divisor separately you can get a different value than if you did it all together like this
vJASS:
set Temp_Int = R2I(GetDistanceBetweenCoordinates(xDum_Default, GetUnitX(TG_Dummy[TG_Loop_Int]), yDum_Default, GetUnitY(TG_Dummy[TG_Loop_Int])) / TG_Speed)
Because the result can be more inaccurate in the way you did, for example, I think the way W3 convert real to integers is by truncation so if you have 14.123213 ÷ 3.74198 = 3.774262 so apply the function: R2I(14.123213 ÷ 3.74198) = R2I(3.774262) = 3 and in the way you did it it be like this R2I(14.123213) ÷ R2I(3.74198) = 14 ÷ 3 = 4.66667, but as the variable is an integer I think it will truncated to 4, so the thing will depend of what do you want, I think there is no much problem as you use this once and not a lot of times in an operation, but anyway, you will decide it.
Yeah, you are right, thanks.

I will rebuild this spell with vJass and systems now because it is such a mess. I think I can use a unit indexer for saving effect instead of a hashtable.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Good for you, but didn't need the hashtable because you need 2 indexes? if yes, you can try learn about structs, but I think you know something.
Am I crazy? I tried to make the same spell and for some reason, it is not as smooth as on Jass. In particular, after the effects changed their angle once and height, they are constantly created at a wrong angle and they show the wrong height on the comparison. If you can, can you take a look, please? I have been sitting for multiple hours trying to find what's wrong.

To be precise, in c_or_t_XYZchange condition, it shows "c > t", and afterwards, in XYZdZchange, it shows "c < d", while they should be equal.

Buff removal is not working, but this is another topic.

Edit: If you have 1.26, I can upload the map for you.

JASS:
library TentacleGrapple /*


     */ uses /*

     */ SpellFramework /*
     */ UnitDex /*
     */ DummyRecycler /*
     */ BaseFunctions

     /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * SPELL CONFIGURATION *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID = 'A00J'
        static constant integer GRAPPLED_BUFF_ID = 'B00B'
        static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_CHANNEL + EVENT_SPELL_ENDCAST
        static constant real SPELL_PERIOD = FPS
        static constant integer STOP_ORDER_ID = 851972
        static constant real SFX_DEATH_TIME = 1.50
        static constant real HOOK_SCALE = 1.5
        static constant real LINK_SCALE = 1.5
        static constant real HOOK_Z_DEFAULT = 100.0
        static constant real HOOK_START_DIST = 10.0
        static constant real MAX_DIST = 1000
        static constant real SPEED = 30
        static constant real AOE = 70
        static constant string HOOK_SFX = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl"
        static constant string LINK_SFX = "Abilities\\Weapons\\WardenMissile\\WardenMissile.mdl"
        //                                      The attack type of inflicting damage
        static constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
        //                                      The damage type of inflicting damage
        static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        //                                      The weapon type of inflicting damage
        static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
       
        static constant method MaxStunDur takes integer level returns real
            return 2.0 + 1.0 * level
        endmethod
       
        static method Dmg takes integer level, unit caster returns real
            return 100. * level + 0.1 * GetHeroStr(caster, true)
        endmethod

        static method Targets takes unit target, unit caster returns boolean
            return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
        endmethod
    endmodule

     /*
     * Note: For seamless manipulation of channeling ability, I suggest using
     * an ability based on CHANNEL then be sure to set its 'Follow through
     * time' field into a really high value such as < 99999 > and set the
     * 'Disable other abilities' to false.
     * This allows you to configure the channeling (follow through time) duration
     * in the code instead of using the Object Editor.
     */
   
     /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * END OF SPELL CONFIGURATION *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

     /* == == == == == == == == == == == == = SPELL CODE == == == == == == == == == == == == = */
    native UnitAlive takes unit u returns boolean

    private struct TentacleGrapple extends array
       
        static method cXYZdZchange takes unit caster, unit dummy, real cX, real cY, real cZ returns boolean
            return cX != GetUnitX(caster) or cY != GetUnitY(caster) or cZ != GetUnitFlyHeight(caster) or GetUnitFlyHeight(dummy) != HOOK_Z_DEFAULT
        endmethod
       
        static method c_or_t_XYZchange takes unit caster, unit target, unit dummy, real cX, real cY, real cZ, real tX, real tY, real tZ returns boolean
            return cX != GetUnitX(caster) or cY != GetUnitY(caster) or cZ != GetUnitFlyHeight(caster) or tX != GetUnitX(target) or tY != GetUnitY(target) or tZ != GetUnitFlyHeight(target) or GetUnitX(target) != GetUnitX(dummy) or GetUnitY(target) != GetUnitY(dummy)
        endmethod

        implement SpellConfiguration
        Table dummyLink
        Table effectLink
       
        unit caster
        unit dummy
        unit dummyHook
        unit target
        effect spellSfx
        effect hookSfx
        real cX
        real cY
        real cZ
        real dX
        real dY
        real dZ
        real tX
        real tY
        real tZ
        real curDur
        real curDist
        real maxStunDur
        real dmg
        real speed
        real linkControl
        boolean channelingFinished
        boolean IsUnitHit
        integer unitId
        integer counter
        integer linkCounter

        static constant boolean SPELL_IS_CHANNELING = SPELL_EVENT_TYPE == EVENT_SPELL_CHANNEL + EVENT_SPELL_ENDCAST

        private static thistype array channelingInstance

        private method onSpellStart takes nothing returns thistype
            local real xDbase
            local real yDbase
            local real angle = GetAngleBetweenPoints(GetUnitX(Spell.triggerUnit), Spell.targetX, GetUnitY(Spell.triggerUnit), Spell.targetY)
           
           
           
            if SPELL_IS_CHANNELING then
                set this = GetUnitId(Spell.triggerUnit)

                if Spell.eventType == EVENT_SPELL_ENDCAST then
                     /*
                     * This block executes when a channeling spell ends, whether because
                     * it was finished or was interrupted
                     *
                     * Returning a negative value will remove the positive equivalent node
                     * and call onSpellEnd() for that node if it exists in the list
                     */
                    set this.channelingFinished = true
                    return 0
                endif
            endif
            set this.dummyLink = Table.create()
            set this.effectLink = Table.create()
           
            set this.caster = Spell.triggerUnit
            set this.cX = GetUnitX(this.caster)
            set this.cY = GetUnitY(this.caster)
            set this.cZ = GetUnitFlyHeight(this.caster)
            set this.maxStunDur = MaxStunDur(Spell.level)
            set this.dmg = Dmg(Spell.level, this.caster)
            set this.curDist = 0
            set this.linkCounter = 0
            set this.channelingFinished = false
            set this.target = null
            set this.speed = SPEED
            set this.linkControl = 0
            set this.IsUnitHit = false
            set xDbase = GetXWithOffset(this.cX, HOOK_START_DIST, angle)
            set yDbase = GetYWithOffset(this.cY, HOOK_START_DIST, angle)
            set this.dummyHook = GetRecycledDummy(xDbase, yDbase, HOOK_Z_DEFAULT, angle)
            set this.hookSfx = AddSpecialEffectTarget(HOOK_SFX, this.dummyHook, "origin")
            set this.dX = GetUnitX(this.dummyHook)
            set this.dY = GetUnitY(this.dummyHook)
            set this.dZ = GetUnitFlyHeight(this.dummyHook)
           
            call SetUnitScale(this.dummyHook, HOOK_SCALE, HOOK_SCALE, HOOK_SCALE)
            call SetUnitAnimationByIndex(this.caster, 5)

            return this
        endmethod

         /*
         * Notice that onSpellPeriodic() stops executing when the caster stops channeling,
         * either when the channeling is finished or is interrupted
         */
        private method onSpellPeriodic takes nothing returns boolean
            local unit target
            local real NewcX = GetUnitX(this.caster)
            local real NewcY = GetUnitY(this.caster)
            local real xDbase
            local real yDbase
            local integer dZAngle
            local real hDiff
            local real cZplusBaseZ
            local real tZplusBaseZ
            local real NewdX
            local real NewdY
            local real zLastCounter
            local real dX = GetUnitX(this.dummyHook)
            local real dY = GetUnitY(this.dummyHook)
            local real angle = GetAngleBetweenPoints(NewcX, dX, NewcY, dY)
            local real moveDx = GetXWithOffset(dX, this.speed, angle)
            local real moveDy = GetYWithOffset(dY, this.speed, angle)
            local real returnDx = GetXWithOffset(dX, this.speed, angle - 180)
            local real returnDy = GetYWithOffset(dY, this.speed, angle - 180)
            local integer TempInt
            local integer TempCounter
            local integer tCounter = 0
            local GrappledBuff gb

            if not channelingFinished then
                 /*
                 * Spell is channeling
                 */
                if not this.IsUnitHit then
       
                    set this.curDist = this.curDist + this.speed
                   
                    call GroupEnumUnitsInRange(g, dX , dY, AOE, null)
                    loop
                        set target = FirstOfGroup(g)
                        exitwhen tCounter == 1 or target == null
                        if Targets(target, this.caster) then
                            set this.target = target
                            set this.tX = GetUnitX(target)
                            set this.tY = GetUnitY(target)
                            set this.tZ = GetUnitFlyHeight(target)
                           
                            set gb = GrappledBuff.add(this.caster, this.target)
                            set gb.duration = this.maxStunDur
                            set this.IsUnitHit = true

                            set tCounter = tCounter + 1
                        endif
                        call GroupRemoveUnit(g, target)
                        call GroupClear(g)
                    endloop
                   
                    call UnitDamageTarget(this.caster, this.target, this.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                   
                    set this.linkControl = this.linkControl + this.speed
                    if this.linkControl >= SPEED then
                        set this.linkCounter = this.linkCounter + 1
                        set this.dummyLink.unit[this.linkCounter] = GetRecycledDummy(dX, dY, HOOK_Z_DEFAULT, angle)
                        set this.effectLink.effect[this.linkCounter] = AddSpecialEffectTarget(LINK_SFX, this.dummyLink.unit[this.linkCounter], "origin")
                        call SetUnitScale(this.dummyLink.unit[this.linkCounter], LINK_SCALE, LINK_SCALE, LINK_SCALE)
                        set this.linkControl = 0
                    endif
                    call SetUnitX(this.dummyHook, moveDx)
                    call SetUnitY(this.dummyHook, moveDy)
                endif
               
                if c_or_t_XYZchange(this.caster, this.target, this.dummyHook, this.cX, this.cY, this.cZ, this.tX, this.tY, this.tZ) and this.IsUnitHit and gb.has(this.caster, this.target, gb.typeid) then
                    //call BJDebugMsg ("Target is Captured: C or T XYZ Change")
                    set this.cX = GetUnitX(this.caster)
                    set this.cY = GetUnitY(this.caster)
                    set this.cZ = GetUnitFlyHeight(this.caster)
                    set this.tX = GetUnitX(this.target)
                    set this.tY = GetUnitY(this.target)
                    set this.tZ = GetUnitFlyHeight(this.target)
                    set angle = GetAngleBetweenPoints(NewcX, tX, NewcY, tY)
                    set xDbase = GetXWithOffset(this.cX, HOOK_START_DIST, angle)
                    set yDbase = GetYWithOffset(this.cY, HOOK_START_DIST, angle)
                    set cZplusBaseZ = HOOK_Z_DEFAULT + this.cZ
                    set tZplusBaseZ = HOOK_Z_DEFAULT + this.tZ
                    set hDiff = RMaxBJ(cZplusBaseZ, tZplusBaseZ) - RMinBJ(cZplusBaseZ, tZplusBaseZ)
                       
                    set dZAngle = R2I(bj_RADTODEG * (Atan(cZplusBaseZ / GetDistanceBetweenCoordinates(xDbase, this.tX, yDbase, this.tY))))
               
                    call SetUnitFacing(this.caster, angle)
                       
                //Move Hook to the location of the target
                    call SetUnitX(this.dummyHook, this.tX)
                    call SetUnitY(this.dummyHook, this.tY)
                    call SetUnitFlyHeight(this.dummyHook, tZplusBaseZ, 99999)
                    call SetUnitFacing(this.dummyHook, angle)
                       
                    set TempInt = R2I(GetDistanceBetweenCoordinates(xDbase, this.tX, yDbase, this.tY) / SPEED)
                    //call BJDebugMsg (R2S(TempInt))
                    if TempInt < this.linkCounter then
                        set TempCounter = this.linkCounter
                        loop
                            exitwhen TempCounter == TempInt
                            call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90)
                            call SetUnitScale(this.dummyLink.unit[TempCounter], 1., 1., 1.)
                            call DestroyEffect(this.effectLink.effect[TempCounter])
                            call RecycleDummy(this.dummyLink.unit[TempCounter])
                            set this.effectLink.effect[TempCounter] = null
                            set this.dummyLink.unit[TempCounter] = null
                            set this.linkCounter = this.linkCounter - 1
                       
                            set TempCounter = TempCounter - 1
                        endloop
                    
                    elseif TempInt > this.linkCounter then
                        set TempCounter = this.linkCounter
                        loop
                            exitwhen TempCounter == TempInt
                       
                            set TempCounter = TempCounter + 1
                            set this.dummyLink.unit[TempCounter] = GetRecycledDummy(dX, dY, HOOK_Z_DEFAULT, angle)
                            set this.effectLink.effect[TempCounter] = AddSpecialEffectTarget(LINK_SFX, this.dummyLink.unit[TempCounter], "origin")
                            call SetUnitScale(this.dummyLink.unit[TempCounter], LINK_SCALE, LINK_SCALE, LINK_SCALE)
                       
                        endloop
                    endif
                    set this.linkCounter = TempInt
               
                    set TempCounter = 1
                    loop
                        exitwhen TempCounter > this.linkCounter
                       
                        call SetUnitX(this.dummyLink.unit[TempCounter], this.cX + (HOOK_START_DIST + SPEED * (TempCounter - 1)) * Cos(angle * bj_DEGTORAD))
                        call SetUnitY(this.dummyLink.unit[TempCounter], this.cY + (HOOK_START_DIST + SPEED * (TempCounter - 1)) * Sin(angle * bj_DEGTORAD))
 
                        call SetUnitFacing(this.dummyLink.unit[TempCounter], angle)

                        if cZplusBaseZ > tZplusBaseZ then
                            call SetUnitFlyHeight(this.dummyLink.unit[TempCounter], tZplusBaseZ + (this.linkCounter + 2 - TempCounter) * (cZplusBaseZ - tZplusBaseZ) / (this.linkCounter + 1), 99999)
                            call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90 - dZAngle)
                            debug call BJDebugMsg (R2S(dZAngle))
                            debug call BJDebugMsg("t > c")
                        elseif cZplusBaseZ < tZplusBaseZ then
                            call SetUnitFlyHeight(this.dummyLink.unit[TempCounter], tZplusBaseZ + (this.linkCounter + 2 - TempCounter) * (cZplusBaseZ - tZplusBaseZ) / (this.linkCounter + 1), 99999)
                            call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90 + dZAngle)
                            debug call BJDebugMsg (R2S(dZAngle))
                            debug call BJDebugMsg("c > t")
                        elseif cZplusBaseZ == tZplusBaseZ then
                            call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90)
                            debug call BJDebugMsg("90")
                        endif
  
                        set TempCounter = TempCounter + 1
                    endloop
                endif
               
                if (this.curDist > MAX_DIST and not this.IsUnitHit) or /*
                     */ (this.IsUnitHit and GetWidgetLife(this.target) < 0.405) or /*
                     */ (this.IsUnitHit and not gb.has(this.caster, this.target, gb.typeid)) then
                    call IssueImmediateOrderById(this.caster, STOP_ORDER_ID)
                    set channelingFinished = true
                endif  
               
            else //Spell is non - channeling
           
                call SetUnitX(this.dummyHook, returnDx)
                call SetUnitY(this.dummyHook, returnDy)
               
                debug call BJDebugMsg ("Number of Links Before Removal: " + I2S(this.linkCounter))
               
                if this.linkCounter > 0 then
                    call SetUnitAnimationByIndex(this.dummyLink.unit[this.linkCounter], 90)
                    call SetUnitScale(this.dummyLink.unit[this.linkCounter], 1., 1., 1.)
                    call DestroyEffect(this.effectLink.effect[this.linkCounter])
                    call RecycleDummy(this.dummyLink.unit[this.linkCounter])
                    set this.effectLink.effect[this.linkCounter] = null
                    set this.dummyLink.unit[this.linkCounter] = null
                    set this.linkCounter = this.linkCounter - 1
                    debug call BJDebugMsg ("Link Counter - 1")
                endif
           
                debug call BJDebugMsg ("Number of Links After Removal: " + I2S(this.linkCounter))

                if this.IsUnitHit and gb.has(this.caster, this.target, gb.typeid) then
                    set gb = GrappledBuff.add(this.caster, this.target)
                    call gb.remove()
                endif
               
                set this.IsUnitHit = false
               
            endif
           
            //Adjust Links and the Hook
            if cXYZdZchange(this.caster, this.dummy, this.cX, this.cY, this.cZ) and not this.IsUnitHit and this.linkCounter > 0 then
                //debug call BJDebugMsg ("Target is Not Captured: C XYZ Change")
                set this.cX = GetUnitX(this.caster)
                set this.cY = GetUnitY(this.caster)
                set this.cZ = GetUnitFlyHeight(this.caster)
                set this.dZ = GetUnitFlyHeight(this.dummyHook)
                set dX = GetUnitX(this.dummyHook)
                set dY = GetUnitY(this.dummyHook)
                set angle = GetAngleBetweenPoints(NewcX, dX, NewcY, dY)
                set xDbase = GetXWithOffset(this.cX, HOOK_START_DIST, angle)
                set yDbase = GetYWithOffset(this.cY, HOOK_START_DIST, angle)
                set cZplusBaseZ = HOOK_Z_DEFAULT + this.cZ
                set hDiff = RMaxBJ(cZplusBaseZ, dZ) - RMinBJ(cZplusBaseZ, dZ)
                set dZAngle = R2I(bj_RADTODEG * (Atan(cZplusBaseZ / GetDistanceBetweenCoordinates(xDbase, dX, yDbase, dY))))
                set NewdX = this.cX + (HOOK_START_DIST + this.speed * this.linkCounter) * Cos(angle * bj_DEGTORAD)
                set NewdY = this.cY + (HOOK_START_DIST + this.speed * this.linkCounter) * Sin(angle * bj_DEGTORAD)
              
                set zLastCounter = GetUnitFlyHeight(this.dummyLink.unit[this.linkCounter])
               
                call SetUnitX(this.dummyHook, NewdX)
                call SetUnitY(this.dummyHook, NewdY)
                   
                if GetDistanceBetweenCoordinates(xDbase, dX, yDbase, dY) < cZplusBaseZ - this.dZ and cZplusBaseZ > this.dZ then
                    call SetUnitFlyHeight(this.dummyHook, cZplusBaseZ + this.speed - this.speed * (this.linkCounter + 1), 99999)
                    call SetUnitAnimationByIndex(this.dummyHook, 90 - dZAngle)
                elseif cZplusBaseZ < this.dZ and this.dZ > HOOK_Z_DEFAULT then
                    call SetUnitFlyHeight(this.dummyHook, zLastCounter + (this.dZ - cZplusBaseZ) / this.linkCounter, 99999)
                    call SetUnitAnimationByIndex(this.dummyHook, 90 + dZAngle)
                elseif this.dZ < HOOK_Z_DEFAULT then
                    call SetUnitFlyHeight(this.dummyHook, HOOK_Z_DEFAULT, 99999)
                    call SetUnitAnimationByIndex(this.dummyHook, 90)
                endif
                   
                set TempCounter = 1
                loop
                    exitwhen TempCounter > this.linkCounter
                           
                    call SetUnitX(this.dummyLink.unit[TempCounter], this.cX + (HOOK_START_DIST + this.speed * (TempCounter - 1)) * Cos(angle * bj_DEGTORAD))
                    call SetUnitY(this.dummyLink.unit[TempCounter], this.cY + (HOOK_START_DIST + this.speed * (TempCounter - 1)) * Sin(angle * bj_DEGTORAD))
                    call SetUnitFacing(this.dummyLink.unit[TempCounter], angle)
                       
                    if cZplusBaseZ > this.dZ then
                        call SetUnitFlyHeight(this.dummyLink.unit[TempCounter], this.dZ + (this.linkCounter + 2 - TempCounter) * (cZplusBaseZ - this.dZ) / (this.linkCounter + 1), 99999)
                        call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90 - dZAngle)
                        debug call BJDebugMsg("c > d")
                    elseif cZplusBaseZ < this.dZ and this.dZ > HOOK_Z_DEFAULT then
                        call SetUnitFlyHeight(this.dummyLink.unit[TempCounter], this.dZ + (this.linkCounter + 2 - TempCounter) * (cZplusBaseZ - this.dZ) / (this.linkCounter + 1), 99999)
                        call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90 + dZAngle)
                        debug call BJDebugMsg("c < d")
                    elseif cZplusBaseZ == HOOK_Z_DEFAULT then
                        call SetUnitFlyHeight(this.dummyLink.unit[TempCounter], HOOK_Z_DEFAULT , 99999)
                        call SetUnitAnimationByIndex(this.dummyLink.unit[TempCounter], 90)
                        debug call BJDebugMsg("c = d")
                    endif
                       
                    set TempCounter = TempCounter + 1
                endloop  
            endif
           
            debug call BJDebugMsg ("Number of Links Before Spell End: " + I2S(this.linkCounter))
           
            return this.linkCounter == 0

        endmethod
       
        private method onSpellEnd takes nothing returns nothing
       
            debug call BJDebugMsg ("TG Finished")
            call SetUnitAnimationByIndex(this.dummyHook, 90)
            call SetUnitScale(this.dummyHook, 1., 1., 1.)
            call DestroyEffect(this.hookSfx)
            call RecycleDummy(this.dummyHook)

            //leaks
            set this.caster = null
            set this.hookSfx = null
            set this.dummyHook = null
            set this.target = null

        endmethod
       
        implement SpellEvent
       
    endstruct
endlibrary
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
@maxodors Well, the thing is you climbed too fast in this of programming in vJass that you used things that I don't even know, but well, I will try my best to understant it, but I need that you pass me the map, and don't worry about the version because I must run it in Reforged because your map is in Russian (I think) and I'm from Perú and the reason is W3 don't have all the languages characters until last versions, and please add coments in your code to help me to understand better. But for now I just can say you can replace:
vJASS:
set hDiff = RMaxBJ(cZplusBaseZ, dZ) - RMinBJ(cZplusBaseZ, dZ)
for
vJASS:
set hDiff = RAbsBJ(cZplusBaseZ - dZ)
If you know what means "absolute value" and better put this:
vJASS:
call UnitDamageTarget(this.caster, this.target, this.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
In the else part of that block because it will damage a none unit until this.IsUnitHit becomes true, and I don't know how valid is that.
 
Level 7
Joined
Feb 9, 2021
Messages
301
@maxodors Well, the thing is you climbed too fast in this of programming in vJass that you used things that I don't even know, but well, I will try my best to understant it, but I need that you pass me the map, and don't worry about the version because I must run it in Reforged because your map is in Russian (I think) and I'm from Perú and the reason is W3 don't have all the languages characters until last versions, and please add coments in your code to help me to understand better. But for now I just can say you can replace:
vJASS:
set hDiff = RMaxBJ(cZplusBaseZ, dZ) - RMinBJ(cZplusBaseZ, dZ)
for
vJASS:
set hDiff = RAbsBJ(cZplusBaseZ - dZ)
If you know what means "absolute value" and better put this:
vJASS:
call UnitDamageTarget(this.caster, this.target, this.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
In the else part of that block because it will damage a none unit until this.IsUnitHit becomes true, and I don't know how valid is that.
Forgot about absolute. I fixed both of the things you pointed out. There is an older version of TG for your reference. (it does not work anymore, because one of the systems added Int global...)

Here is my map. You should delete the folder memory hack.

I added some comments for you. Let me know if you have any questions.

The Stun will not work because I had to disable the stun system, since it works on the Memory Hack.

Edit: It is weird because it works well the first time.

I added on spell end:

call this.dummyLink.flush()
call this.effectLink.flush()

call this.dummyLink.destroy()
call this.effectLink.destroy()
 

Attachments

  • Mammon Herly.w3x
    760.3 KB · Views: 21
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
Forgot about absolute. I fixed both of the things you pointed out. There is an older version of TG for your reference. (it does not work anymore, because one of the systems added Int global...)

Here is my map. You should delete the folder memory hack.

I added some comments for you. Let me know if you have any questions.

The Stun will not work because I had to disable the stun system, since it works on the Memory Hack.

Edit: It is weird because it works well the first time.

I added on spell end:

call this.dummyLink.flush()
call this.effectLink.flush()

call this.dummyLink.destroy()
call this.effectLink.destroy()
Well I tested (I didn't have time before sorry), and I don't see nothing wrong, what was exactly the problem?, but for some reason if I cast the spell again before the previous ends the game stop working for some reason (I think is because the SpellEvent and I'm using reforged).

3.) As mentioned in no. 1, within the generic event handlers, you can override the event parameters. The change will only affect the ability-specific handlers. You can also stop the ability-specific handlers from running (known as event cancelling). Note: Event cancelling is currently incompatible with Reforged (Crashes the game).
 
Level 7
Joined
Feb 9, 2021
Messages
301
Well I tested (I didn't have time before sorry), and I don't see nothing wrong, what was exactly the problem?, but for some reason if I cast the spell again before the previous ends the game stop working for some reason (I think is because the SpellEvent and I'm using reforged).
As far as I understand, you can't use the spell twice because his system uses UnitId for storing information. Therefore, if the spell is still in use, you can't use the same id.

On the problem, if use the spell the first time on the unit, it is okay. If you use it a second time on the same unit, the pitch is wrong. To make this bug, you should lift the target with test unit abilities while the unit is stunned by the chain.
 

Attachments

  • Use 1.jpg
    Use 1.jpg
    896 KB · Views: 15
  • Use 2.jpg
    Use 2.jpg
    962.8 KB · Views: 15
Level 24
Joined
Jun 26, 2020
Messages
1,850
As far as I understand, you can't use the spell twice because his system uses UnitId for storing information. Therefore, if the spell is still in use, you can't use the same id.

On the problem, if use the spell the first time on the unit, it is okay. If you use it a second time on the same unit, the pitch is wrong. To make this bug, you should lift the target with test unit abilities while the unit is stunned by the chain.
If thats the problem, you must pass me a version of your map that the stun is on.
 
Level 7
Joined
Feb 9, 2021
Messages
301
It is not about the stun. Replace my previous explanation with 'when the unit is hit by the chain' .

Edit: also, my stun works only on 1.26
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
It is not about the stun. Replace my previous explanation with 'when the unit is hit by the chain' .

Edit: also, my stun works only on 1.26
Problem, I can't run it in 1.26, for I don't know problems, the map just can be saved or if I can it doesn't look equal it is in the editor if I edited, sorry, but I thing this problem is now out of my reach, better someone else help you.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Problem, I can't run it in 1.26, for I don't know problems, the map just can be saved or if I can it doesn't look equal it is in the editor if I edited, sorry, but I thing this problem is now out of my reach, better someone else help you.
I thought it worked on reforged for you? Why did you try to install it on 1.26?
 
Status
Not open for further replies.
Top