[Spell] Point attachment to unit's body part\current height?

Level 5
Joined
Feb 22, 2025
Messages
78
:")
Long story short there are two spells I'm trying to merge - one is chain hook and the other is backwards salto ( both spells from HIVE authors Kazeon and B-UP:ogre_datass:).
Chains use dummies attached to points if I'm not mistaken. So when my caster jumps backwards the chains follow but it doesn't rise to the caster's current height.
Is there a way to attach the starting point of chain spawn to the current height of caster\ body part?
 

Attachments

  • IMG_7434.mp4
    10.8 MB
Level 45
Joined
Feb 27, 2007
Messages
5,578
and presumably this, as I couldn't find anything by that user with the name salto (a type of reverse flip on a balance beam):

Note that as it stands this spell also does not respect the fly height of the targeted unit. The chain will affix to the ground beneath a flying unit.

No dummies are attached to any points. That's not how it works and it's not a thing you can do with units. Each timer interval the dummies are manually placed in a line starting from the current XY of the caster in the direction of the angle the spell was originally cast. You will need to read the current fly height of the caster and then move each link the chain proportionally higher up the closer to the caster the link is. While this is not a particularly difficult computation, the way this trigger is written has a massive amount of redundant code because the guy was like trying to reinvent structs in GUI and failed at it; this means it will be necessary to make a change in all four places the chain links (not the chain head or the target) are moved with SetUnitX/Y:
  • -------- these are the two lines you are looking for in 4 different places in the cast trigger --------
    • Custom script: call SetUnitX(udg_CH_Chains[udg_CH_Loop[3]], GetUnitX(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Cos(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Custom script: call SetUnitY(udg_CH_Chains[udg_CH_Loop[3]], GetUnitY(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Sin(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
  • -------- these variables can be set right at the top of the first loop like this, since they only need to be set once per instance rather than recomputed 4+ times per timer tick per instance: --------
  • For each (Integer CH_Loop[2] ) from 1 to CH_IndexMax , do (Actions)
    • Loop - Actions
      • Set HeightMax = (CH_Caster[CH_Loop[2]] flying height)
      • Set HeightMin = CF_ConfChainHeight //this could conceivably take target fly height into account so I wrote it this way instead of using the variable directly
      • Set HeightDelta = ((HeightMax - HeightMin)/Real(CH_DummyCount[CH_Loop[2]] - 1)) //yes the -1 is important
  • -------- when properly changed, each of the 4 XY-setting sections should look like this: --------
    • Custom script: call SetUnitX(udg_CH_Chains[udg_CH_Loop[3]], GetUnitX(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Cos(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Custom script: call SetUnitY(udg_CH_Chains[udg_CH_Loop[3]], GetUnitY(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Sin(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Set Height = (HeightMax - (HeightDelta x (CH_ChainsIndex[udg_CH_Loop[3]] - 1)))
    • Set Height = Max(Height, CF_ConfChainHeight) //if you want to be safe, this line is optional but mostly won't ever do anything
    • Unit - Set CH_Chains[udg_CH_Loop[3]] flying height to Height
This should make the chain linear like this: \ but you could use any sort of function to control the height dropoff to achieve other shapes like curves: U that might look better.
 
Last edited:
Level 5
Joined
Feb 22, 2025
Messages
78
and presumably this, as I couldn't find anything by that user with the name salto (a type of reverse flip on a balance beam):

Note that as it stands this spell also does not respect the fly height of the targeted unit. The chain will affix to the ground beneath a flying unit.

No dummies are attached to any points. That's not how it works and it's not a thing you can do with units. Each timer interval the dummies are manually placed in a line starting from the current XY of the caster in the direction of the angle the spell was originally cast. You will need to read the current fly height of the caster and then move each link the chain proportionally higher up the closer to the caster the link is. While this is not a particularly difficult computation, the way this trigger is written has a massive amount of redundant code because the guy was like trying to reinvent structs in GUI and failed at it; this means it will be necessary to make a change in all four places the chain links (not the chain head or the target) are moved with SetUnitX/Y:
  • -------- these are the two lines you are looking for in 4 different places in the cast trigger --------
    • Custom script: call SetUnitX(udg_CH_Chains[udg_CH_Loop[3]], GetUnitX(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Cos(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Custom script: call SetUnitY(udg_CH_Chains[udg_CH_Loop[3]], GetUnitY(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Sin(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
  • -------- these variables can be set right at the top of the first loop like this, since they only need to be set once per instance rather than recomputed 4+ times per timer tick per instance: --------
  • For each (Integer CH_Loop[2] ) from 1 to CH_IndexMax , do (Actions)
    • Loop - Actions
      • Set HeightMax = (CH_Caster[CH_Loop[2]] flying height)
      • Set HeightMin = CF_ConfChainHeight //this could conceivably take target fly height into account so I wrote it this way instead of using the variable directly
      • Set HeightDelta = ((HeightMax - HeightMin)/Real(CH_DummyCount[CH_Loop[2]] - 1)) //yes the -1 is important
  • -------- when properly changed, each of the 4 XY-setting sections should look like this: --------
    • Custom script: call SetUnitX(udg_CH_Chains[udg_CH_Loop[3]], GetUnitX(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Cos(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Custom script: call SetUnitY(udg_CH_Chains[udg_CH_Loop[3]], GetUnitY(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Sin(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Set Height = (HeightMax - (HeightDelta x (CH_ChainsIndex[udg_CH_Loop[3]] - 1)))
    • Set Height = Max(Height, CF_ConfChainHeight) //if you want to be safe, this line is optional but mostly won't ever do anything
    • Unit - Set CH_Chains[udg_CH_Loop[3]] flying height to Height
I’ll implement it later today after work and post results, tnx for explaining 😇
 
Level 5
Joined
Feb 22, 2025
Messages
78
set.gif
Set HeightMax = (CH_Caster[CH_Loop[2]] flying height)
Is this supposed to determine the unit's real field flying height?
Also can you define are new variables integer or real, cuz I'm getting lost with the oroginal variables being integer or real :")
 
Last edited:
Level 5
Joined
Feb 22, 2025
Messages
78
-------- these are the two lines you are looking for in 4 different places in the cast trigger --------
Did u mean in the loop trigger, cuz there are no such scripts in the cast trigger.

  • Loop - Actions
    • Set VariableSet CH_Heigh_Max = (Unit: CH_Caster[CH_Loop[2]]'s Real Field: Fly Height ('ufyh'))
    • Set VariableSet CH_Height_MIN = CH_ConfChainHeight
    • Set VariableSet CH_Height_Delta = ((CH_Heigh_Max - CH_Height_MIN) / ((Real(CH_DummyCount[CH_Loop[2]])) - 1.00))
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)]
  • Then - Actions
    • Animation - Change CH_Chains[CH_Loop[3]]'s vertex coloring to (100.00%, 100.00%, 100.00%) with 100.00% transparency
    • Custom script: call SetUnitX(udg_CH_Chains[udg_CH_Loop[3]], GetUnitX(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Cos(udg_CH_Angle[udg_CH_Loop[2]] * bj_DEGTORAD))
    • Custom script: call SetUnitY(udg_CH_Chains[udg_CH_Loop[3]], GetUnitY(udg_CH_Caster[udg_CH_Loop[2]]) + ( I2R (udg_CH_ChainsIndex[udg_CH_Loop[3]]) * udg_CH_ConfDistancePerChain) * Sin(udg_CH_Angle[udg_CH_Loop[2]] *bj_DEGTORAD))
    • Set VariableSet CH_Set_Height = (CH_Heigh_Max - (CH_Height_Delta x ((Real(udg_CH_Chains[udg_CH_Loop[3])) - 1.00)))
    • Unit - Set Unit: CH_Chains[CH_Loop[(Length of udg_CH_Loop[3])]]'s Real Field: Fly Height ('ufyh') to Value: CH_Set_Height]
* Changed all 4 instances still nothing, I guess I messed up Integer\Real Variables?
 
Last edited by a moderator:
Level 45
Joined
Feb 27, 2007
Messages
5,578
To be clear the reason my syntax looks a little different from the trigger editor (or I use the wrong word) is because I'm typing from memory and not because I'm opening the trigger editor and copying some other function you don't see. There are discrepancies when I don't remember the correct wording (or forget that flying height change requires a rate argument).
Did u mean in the loop trigger, cuz there are no such scripts in the cast trigger.
Yes.
Is this supposed to determine the unit's real field flying height?
Yes. Do not use the field reader native for this, that's not what I wrote and it probably either doesn't work or doesn't update. There is an actual function that returns a real called "Unit - Flying Height (Current)" that you can find in the trigger editor. It returns a real value because heights are real values... not integers. You will not find it when looking at other things that return integers because it isn't an integer.

Since you used the wrong function, that probably reads 0 from that field so Height_Max (btw you spelled this one wrong and named your variable Heigh_Max) Height_Delta, and CH_Set_Height will all also be 0. Do not use the field native to try to set fly height, there's an actual function for setting fly height you should be using. That function expects a 'rate of change' argument, and you can just put 0 there to make it instantaneous.
Also can you define are new variables integer or real, cuz I'm getting lost with the oroginal variables being integer or real :")
You should be able to look at what value the variable is being set to and determine the type of variable from context alone. Height_Max reads the unit's flying height which is a real so it must also be a real. CH_ConfChainHeight, the value Min is assigned to, is also a real so Min must be a real. (Another way: if Max is a real, why wouldn't Min also be a real?) Height_Delta is just a proportional part of the difference between Max and Min, so it must also be a real. Again, the flying height we want things to have is a real value (not an integer) so the variable that holds the final computed CH_Set_Height is also still a real.

I guess you became confused when you saw CH_Loop[3] being used in the Set Height computation because I didn't type Real(CH_Loop[3]). You really should not be confused by that; integers and reals are often used in many computations together so you will need to be familiar with 'converting' between them. The vast majority of the time you will always want to work with real numbers, because operations with integers truncate and never round:
  • 5.0/4.0 = 1.25
  • 5/4 = 1 in integer-land because it truncated
  • 99/100 = 0 in integer-land

  • 5.0/4 = 1.25 even though the 4 was an integer; since it was directly involved in a computation with a non-integer variable, the result is a real'
  • 3.5 x (7/10) = 0.0 again (but a real this time not an integer). Because the 7/10 is an integer operation it will reduce to 0 first and then multiply the 3.5 to give a total of 0.0
I hate writing Real() in a bunch of places just because GUI is so on-rails it won't let you substitute an integer for a real even though it would work just fine in JASS and Lua directly.
 
Last edited:
Level 5
Joined
Feb 22, 2025
Messages
78
To be clear the reason my syntax looks a little different from the trigger editor (or I use the wrong word) is because I'm typing from memory and not because I'm opening the trigger editor and copying some other function you don't see. There are discrepancies when I don't remember the correct wording (or forget that flying height change requires a rate argument).

Yes.

Yes. Do not use the field reader native for this, that's not what I wrote and it probably either doesn't work or doesn't update. There is an actual function that returns a real called "Unit - Flying Height (Current)" that you can find in the trigger editor. It returns a real value because heights are real values... not integers. You will not find it when looking at other things that return integers because it isn't an integer.

Since you used the wrong function, that probably reads 0 from that field so Height_Max (btw you spelled this one wrong and named your variable Heigh_Max) Height_Delta, and CH_Set_Height will all also be 0. Do not use the field native to try to set fly height, there's an actual function for setting fly height you should be using. That function expects a 'rate of change' argument, and you can just put 0 there to make it instantaneous.

You should be able to look at what value the variable is being set to and determine the type of variable from context alone. Height_Max reads the unit's flying height which is a real so it must also be a real. CH_ConfChainHeight, the value Min is assigned to, is also a real so Min must be a real. (Another way: if Max is a real, why wouldn't Min also be a real?) Height_Delta is just a proportional part of the difference between Max and Min, so it must also be a real. Again, the flying height we want things to have is a real value (not an integer) so the variable that holds the final computed CH_Set_Height is also still a real.

I guess you became confused when you saw CH_Loop[3] being used in the Set Height computation because I didn't type Real(CH_Loop[3]). You really should not be confused by that; integers and reals are often used in many computations together so you will need to be familiar with 'converting' between them. The vast majority of the time you will always want to work with real numbers, because operations with integers truncate and never round:
  • 5.0/4.0 = 1.25
  • 5/4 = 1 in integer-land because it truncated
  • 99/100 = 0 in integer-land

  • 5.0/4 = 1.25 even though the 4 was an integer; since it was directly involved in a computation with a non-integer variable, the result is a real'
  • 3.5 \* (7/10) = 0.0 again (but a real this time not an integer). Because the 7/10 is an integer operation it will reduce to 0 first and then multiply the 3.5 to give a total of 0.0
I hate writing Real() in a bunch of places just because GUI is so on-rails it won't let you substitute an integer for a real even though it would work just fine in JASS and Lua directly.
Thanks for the detailed explenation and patience.
Fixed everything ngl looks goddamn good! :ogre_datass:

  • Animation - Change CH_Chains[CH_Loop[3]] flying height to CH_Set_Height at 0.00
It was actually in the animation section that's why I mistakenly disregarded it xx
 

Attachments

  • IMG_7461.mp4
    12.4 MB
Last edited by a moderator:
Level 45
Joined
Feb 27, 2007
Messages
5,578
It was actually in the animation section that's why I mistakenly disregarded it xx
Hah, yep that's from me guessing the category and not knowing I was wrong.

It does look good, I'm happy you're happy with that emote lmao! There's one more thing you could optimize if you want to: the chain currently connects to the origin (feet) of both the casting unit and the target. On the target there's not much you can do that will be consistent across a variety of models (and recall it will definitely look weird if used on a flying unit), but you could move the 'attachment' height at the caster end higher, if you want to. The fix is relatively easy, since we're already manipulating the chain height. All you need to do is alter variable CH_Height_Max to be a little higher:
  • Set CH_Height_Max = ((CH_Caster[CH_Loop[2]] current flying height) + BONUS_OFFSET)
I would guess a bonus offset of like 30-80 might look good for that caster, though in principle the bonus offset could be different depending which unit casts the spell (if they have different models). It could be worth looking at the unit model's projectile launch or projectile impact heights. Accounting for a flying target isn't particularly difficult, either, but it might look weird if terrain isn't flat between caster and target.
 
Last edited:
Level 5
Joined
Feb 22, 2025
Messages
78
Hah, yep that's from me guessing the category and not knowing I was wrong.

It does look good, I'm happy you're happy with that emote lmao! There's one more thing you could optimize if you want to: the chain currently connects to the origin (feet) of both the casting unit and the target. On the target there's not much you can do that will be consistent across a variety of models (and recall it will definitely look weird if used on a flying unit), but you could move the 'attachment' height at the caster end higher, if you want to. The fix is relatively easy, since we're already manipulating the chain height. All you need to do is alter variable CH_Height_Max to be a little higher:
  • Set CH_Height_Max = ((CH_Caster[CH_Loop[2]] current flying height) + BONUS_OFFSET)
I would guess a bonus offset of like 30-80 might look good for that caster, though in principle the bonus offset could be different depending which unit casts the spell (if they have different models). It could be worth looking at the unit model's projectile launch or projectile impact heights. Accounting for a flying target isn't particularly difficult, either, but it might look weird if terrain isn't flat between caster and target.
Yup looks better with setting the offset to around 80 as u mentioned... I also added a cooldown trigger that is not initially on(starts with the cast trigger-
CH_Loop[1] Equal to 1 - Turn ChOd Cooldown) which sets the ability on cooldown if the caster chained 3 targets in a row, since the loops are limited to 3 chained targets.
  • CHoD Cooldown
    • Events
      • Time - Every 0.30 seconds of game time
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • CH_CastIndex[CH_IndexMax] Equal to 3
        • Then - Actions
          • Unit - For Unit CH_Caster[CH_IndexMax], start cooldown of ability |cffff0064Chains of Domination|r " over "10.00 seconds.
          • Trigger - Turn off (This trigger)
        • Else - Actions
 
Level 5
Joined
Feb 22, 2025
Messages
78
Hah, yep that's from me guessing the category and not knowing I was wrong.

It does look good, I'm happy you're happy with that emote lmao! There's one more thing you could optimize if you want to: the chain currently connects to the origin (feet) of both the casting unit and the target. On the target there's not much you can do that will be consistent across a variety of models (and recall it will definitely look weird if used on a flying unit), but you could move the 'attachment' height at the caster end higher, if you want to. The fix is relatively easy, since we're already manipulating the chain height. All you need to do is alter variable CH_Height_Max to be a little higher:
  • Set CH_Height_Max = ((CH_Caster[CH_Loop[2]] current flying height) + BONUS_OFFSET)
I would guess a bonus offset of like 30-80 might look good for that caster, though in principle the bonus offset could be different depending which unit casts the spell (if they have different models). It could be worth looking at the unit model's projectile launch or projectile impact heights. Accounting for a flying target isn't particularly difficult, either, but it might look weird if terrain isn't flat between caster and target.
Quick Question
I've set the pathing system for the backward leap using the item:
  • EslPathing
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • Set VariableSet EsL_TempPoint = (Position of EsL_Caster[EsL_Current_Index])
      • Custom script: set udg_Pathing_ESL = IsTerrainWalkableLoc(udg_EsL_TempPoint)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Pathing_ESL Equal to True
        • Then - Actions
        • Else - Actions
          • Custom script: call RemoveLocation(udg_EsL_Start_Point[udg_EsL_Current_Index])
          • Custom script: call PauseUnit(udg_EsL_Caster[udg_EsL_Current_Index], false)
          • Unit - Order EsL_Caster[EsL_Current_Index] to Stop.
          • Unit - Turn collision for EsL_Caster[EsL_Current_Index] On.
          • Animation - Change EsL_Caster[EsL_Current_Index] flying height to 0.00 at 0.00
          • Special Effect - Destroy EsL_SE_1[EsL_Current_Index]
          • Special Effect - Destroy EsL_SE_2[EsL_Current_Index]
          • Animation - Reset EsL_Caster[EsL_Current_Index]'s animation
          • Set VariableSet EsL_Index_Listener = (EsL_Index_Listener - 1)
          • Set VariableSet EsL_Index_Container[EsL_Loop] = EsL_Index_Container[EsL_Index_Listener]
          • Set VariableSet EsL_Recycle_Container[EsL_Current_Index] = EsL_Current_Index
          • Set VariableSet EsL_Recycle_Size = (EsL_Recycle_Size + 1)
          • Set VariableSet EsL_Loop = (EsL_Loop - 1)
          • Custom script: call RemoveLocation(udg_EsL_Caster_Point)
          • Custom script: call RemoveLocation(udg_EsL_TempPoint)
          • Trigger - Turn off EsL Loop <gen>
          • Trigger - Turn off (This trigger)
function IsTerrainWalkable takes real x, real y returns boolean
local real dX
local real dY
call SetItemVisible(udg_PathingChecker, true)
call SetItemPosition(udg_PathingChecker, x, y)
set dX = GetItemX(udg_PathingChecker)
set dY = GetItemY(udg_PathingChecker)
call SetItemVisible(udg_PathingChecker, false)
// adjust 10 to modify the distance that's considered pathable
return (x - dX) * (x - dX) + (y - dY) * (y - dY) < 10 * 10
endfunction

function IsTerrainWalkableLoc takes location l returns boolean
local real x = GetLocationX(l)
local real y = GetLocationY(l)
local real dX
local real dY
call SetItemVisible(udg_PathingChecker, true)
call SetItemPosition(udg_PathingChecker, x, y)
set dX = GetItemX(udg_PathingChecker)
set dY = GetItemY(udg_PathingChecker)
call SetItemVisible(udg_PathingChecker, false)
// adjust 10 to modify the distance that's considered pathable
return (x - dX) * (x - dX) + (y - dY) * (y - dY) < 10 * 10
endfunction
-------------------------------------------------------------------------------------------------------------------------------
My question would be: Is there a way for pathing system to ignore for example rocks and minor doodas/destructable cuz Caster gets stopped leaping if there is a rock on the ground :")
 
Level 45
Joined
Feb 27, 2007
Messages
5,578
No, not really. Such functions don't actually look at what caused the item to move, just if it has moved at all from the location the system thought it was putting the item (the location where pathability is being checked). Determining what caused the move is much more resource-intensive process that probably isn't worth it.

The hybrid solution is to edit the pathing map for such rocks so that they either don't have one (and maybe you place a pathing blocker manually) or it's minimal. You could also make another version of the function which takes the "10" as an argument instead of hardcoding it; when you call the function with higher values for that number the pathing check is more lenient about how far the item can move before it's consider unpathable. You could do something like:
JASS:
function IsTerrainWalkable takes real x, real y, real RANGE_OF_MOTION returns boolean
  local real dX
  local real dY
  call SetItemVisible(udg_PathingChecker, true)
  call SetItemPosition(udg_PathingChecker, x, y)
  set dX = GetItemX(udg_PathingChecker)
  set dY = GetItemY(udg_PathingChecker)
  call SetItemVisible(udg_PathingChecker, false)
  // adjust RANGE_OF_MOTION to modify the distance that's considered pathable
  return (x - dX) * (x - dX) + (y - dY) * (y - dY) < RANGE_OF_MOTION * RANGE_OF_MOTION
endfunction

function IsTerrainWalkableLoc takes location l, real RANGE_OF_MOTION returns boolean
  return IsTerrainWalkable(GetLocationX(l), GetLocationY(l), RANGE_OF_MOTION)
endfunction

//call it like this:
IsTerrainWalkable(someX, someY, 10.) //default tolerence for range of motion of 10
IsTerrainWalkableLoc(someLoc, 100.) //10x bigger allowed tolerance for motion
IsTerrainWalkable(otherX, otherY, 30.) //a middle ground tolerance?
 
Top