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

[Solved] Damage Loop in vJass

Status
Not open for further replies.
Level 6
Joined
Dec 6, 2009
Messages
168
Hello. I remade a gui trigger in vjass to get back into vjass again after some time away. The spell is a straight lined beam which is supposed to deal damage to everyone for 100 damage withing 500 range of the beam.

But my problem is that right now the damage event is running 25 times because the LOOP_NUMBER is 25 times. And it mesures a 500 radius from each dummy unit I spawn. So if a unit is withing range of 2 or more it takes damage for each dummy unit it is withing range of. So the spell deals more like 500-1000 damage. So my question is how do you guys fix it easily so that the unit only takes damage one time? Also I wonder why my damage function returns 1.00 when it's supposed to return 100? It doesn't matter if I write something like "return 200" or "return BASE_DAMAGE + 500" it still says 1.00.
JASS:
private function damage takes nothing returns real
      return BASE_DAMAGE
   endfunction


Anyway here is the code for the spell: :)
JASS:
scope WaterBeam initializer init
   
   native UnitAlive takes unit id returns boolean
   
   
   globals
      private constant integer    AID          = 'A001'  
     
      private constant attacktype ATTACK_TYPE  = ATTACK_TYPE_NORMAL      
      private constant damagetype DAMAGE_TYPE  = DAMAGE_TYPE_MAGIC
     
      private constant real       DISTANCE     = 50.00
      private constant integer    LOOP_NUMBER  = 25
      private constant real       BASE_DAMAGE  = 100
      private constant real       AOE          = 500.00
     
      private constant integer    DUMMY_UNIT   = 'h000'
     
      private group g                          = CreateGroup()
     
   endglobals
   
   
   private function TargetFilter takes unit target, player owner returns boolean
      return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
   endfunction
   
   
   private function damage takes nothing returns real
      return BASE_DAMAGE
   endfunction
   
   private function applyDamage takes nothing returns nothing
      local unit target  = GetEnumUnit()
      local player owner = GetTriggerPlayer()


      if (TargetFilter(target, owner)) then
         call UnitDamageTarget(GetTriggerUnit(), target, BASE_DAMAGE, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
         call DisplayTextToForce( GetPlayersAll(), R2S(BASE_DAMAGE) + "BaseDamage" )
         call DisplayTextToForce( GetPlayersAll(), R2S(damage) + "damage" )
         call GroupRemoveUnitSimple( target, g) 
      endif
         
      set target = null
      set owner  = null
       
    endfunction
   

      private function run takes nothing returns nothing
         local unit dummy
         local unit array dummy2
         local unit caster = GetTriggerUnit()
         local real x = GetSpellTargetX()
         local real y = GetSpellTargetY()
         local real facing = GetUnitFacing(caster)
         local player owner = GetOwningPlayer(caster)
         local integer i = 1
         
         local location loc = PolarProjectionBJ(GetUnitLoc(caster), 100.00, facing)
         local location loc_loop
         
         
         set dummy  = CreateUnitAtLoc(owner, DUMMY_UNIT, loc, facing)
         call UnitApplyTimedLifeBJ( 1.00, 'BTLF', dummy )
         
         loop
         exitwhen i > LOOP_NUMBER
           
            set loc_loop = PolarProjectionBJ( loc, DISTANCE * I2R(i), facing)
            set dummy2[i] = CreateUnitAtLoc(owner, DUMMY_UNIT, loc_loop, facing)
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy2[i] )
           
            set g = GetUnitsInRangeOfLocAll(AOE, loc_loop)

            call ForGroup( g, function applyDamage)
            call RemoveLocation(loc_loop)
            call DestroyGroup(g)
            set i = i + 1
            set dummy2[i] = null
         endloop
         
         set dummy = null
         set g = null
         set owner = null
         call RemoveLocation(loc)
      endfunction
   
   
      private function init takes nothing returns nothing
         call RegisterSpellEffectEvent(AID, function run)
      endfunction
   
endscope
 
Level 13
Joined
Mar 24, 2013
Messages
1,105
Try putting a period after BASE_DAMAGE so its "100." I think in the past there was an issue with typecasting integers to reals in returns.

The way for it to be only damaged "once" is you do something to the unit so you know its already been processed once. Often that is adding it to a unit group specific for the instance, but it could be other things, i.e custom value, add an ability, check for buff. Unit Group is probably the best though.
 
Level 7
Joined
Oct 19, 2015
Messages
286
You could also use a line segment instead of multiple points to get all units in your beam in a single enumeration, then you wouldn't have any problems with units getting hit twice.

Also, you have some leaks in your code, but that should fix itself once you move from locations to coordinates and from BJ functions to natives.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
Iterate through each selection and add the units to a group. Then iterate through that group and deal the damage. Assuming units cannot be added to groups more than once, the result will automatically limit damage to once per unit. If groups do allow multiple units, then only add units to the group if the group does not have that unit already (IsUnitInGroup is false).

As other people have suggested, perhaps there are more efficient approaches to finding the units you want affected.
 
Level 7
Joined
Oct 19, 2015
Messages
286
Can you find the code in outputWar3Map.j in the logs subfolder of your NewGen pack and see into what it compiles?
 
Level 6
Joined
Dec 6, 2009
Messages
168
I read Flame_Phoenix spell tutorial and LineSegment and came up with this. It works like I want it to but is there any leaks or anything I could do different? :)
JASS:
scope WaterBeamNew initializer init 
   
   
    native UnitAlive takes unit id returns boolean
   
   
    globals
        private constant integer    AID            = 'A002'  
     
        private constant attacktype ATTACK_TYPE    = ATTACK_TYPE_NORMAL      
        private constant damagetype DAMAGE_TYPE    = DAMAGE_TYPE_MAGIC
     
        private constant real       DISTANCE       = 1000.
        private constant real       DAMAGE         = 100.
        private constant real       AOE            = 75.
        private constant real       MISSILE_OFFSET = 50.
     
        private constant real       BEAM_OFFSET    = 100.
     
        private constant integer    DUMMY_UNIT     = 'h001'
     
        //Don't edit
        private group g                            
        private boolexpr b
    endglobals

   
    private function Targets takes unit target returns boolean
    //the units the spell will affect
        return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
   
   
    private function Pick takes nothing returns boolean 
        return Targets(GetFilterUnit())
    endfunction
   
   
    private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()
        local unit u
        local unit dummy
        local player owner      = GetOwningPlayer(caster)
     
        local location spellLoc = GetSpellTargetLoc()
        local location loc      = GetUnitLoc(caster)
        local real angel        = AngleBetweenPoints(loc, spellLoc)
        local location offset   = PolarProjectionBJ(loc, DISTANCE, angel)
        local real casterX      = GetUnitX(caster)
        local real casterY      = GetUnitY(caster)
        local real rangeX       = GetLocationX(offset)
        local real rangeY       = GetLocationY(offset) 
     
        local integer  i        = 1
        local integer  i2       = 1
        local location loopLoc
        local location loopOffset = PolarProjectionBJ(loc, BEAM_OFFSET, angel)
     
        loop 
            exitwhen I2R(i2) > DISTANCE
       
            set loopLoc = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i), angel)
       
            set dummy = CreateUnitAtLoc(owner, DUMMY_UNIT, loopLoc, angel)
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )
       
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
            call RemoveLocation(loopLoc)
        endloop      
     
     
        call GroupEnumUnitsInRangeOfSegment(g, casterX, casterY, rangeX, rangeY, AOE, b) //DISTANCE
        loop
            set u = FirstOfGroup(g)
            exitwhen(u == null)
            call GroupRemoveUnit(g, u)
            //damage
            if IsUnitEnemy(u, owner) then
                call UnitDamageTarget(caster, u, DAMAGE, true, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            endif
        endloop
        call RemoveLocation(spellLoc)
        call RemoveLocation(loc)
        call RemoveLocation(offset)
        call RemoveLocation(loopOffset)
     
        set spellLoc   = null
        set loc        = null
        set offset     = null
        set loopOffset = null
        set caster     = null
        set owner      = null
    endfunction
   
   
   
    private function init takes nothing returns nothing
        call RegisterSpellEffectEvent(AID, function run)
       
        //Setting globals
        set b = Condition(function Pick)
        set g = CreateGroup()
       
        //Preloading the ability 
        set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_UNIT, 0, 0, 0)
        call UnitAddAbility(bj_lastCreatedUnit, AID)
        call KillUnit(bj_lastCreatedUnit)
    endfunction 


endscope


I also made one that spins.
JASS:
scope WaterBeamNew initializer init 
   
    native UnitAlive takes unit id returns boolean
   
   //Settings
    globals
        private constant integer    AID            = 'A002'  
     
        private constant attacktype ATTACK_TYPE    = ATTACK_TYPE_NORMAL      
        private constant damagetype DAMAGE_TYPE    = DAMAGE_TYPE_MAGIC
     
        private constant real       DISTANCE       = 2000.
        private constant real       DAMAGE         = 100.
        private constant real       AOE            = 75.
        private constant real       MISSILE_OFFSET = 50.
     
        private constant real       SPIN           = 3.
        private constant real       BEAM_OFFSET    = 100.
     
        private constant integer    DUMMY_UNIT     = 'h001'
     
        private group g                  
        private group copy                  
        private boolexpr b
    endglobals
   
   
    private function Targets takes unit target returns boolean
    //the units the spell will affect
        return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
   
   
    private function Pick takes nothing returns boolean 
        return Targets(GetFilterUnit())
    endfunction
   
    private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()      //Casting unit
        local unit u                                      
        local unit dummy          
        local player owner      = GetOwningPlayer(caster)
                                                         
        local location spellLoc = GetSpellTargetLoc()   //Location of the spell being casted so we can get the angle 
        local location loc      = GetUnitLoc(caster)    //Location of the caster (Also for the angle)
        local real angel        = AngleBetweenPoints(loc, spellLoc) //Getting the angle
        local location offset   = PolarProjectionBJ(loc, DISTANCE, angel)
        local real rangeX       = GetLocationX(offset)
        local real rangeY       = GetLocationY(offset) 
     
        local real array damageX
        local real array damageY
     
        local integer  i       = 1 //Loop 1 integer
        local integer  i2      = 1 //Checking so the range of the beam is not longer than our DISTANCE variable.
        local integer  i3      = 1 //Loop 2 integer 
        local integer  i4      = 1 //Same as i2 but for damage instead
     
        local location dummyLoc
     
        local location array damageLoc
        local location loopOffset = PolarProjectionBJ(loc, BEAM_OFFSET, angel)
     
        loop 
            exitwhen I2R(i2) > DISTANCE
       
            set dummyLoc = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i), angel - (i*SPIN) ) //The location of the dummy unit.
         
            set dummy = CreateUnitAtLoc(owner, DUMMY_UNIT, dummyLoc, angel - (i*SPIN) ) //The creation of the dummy unit with spin.
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )                     //Timed life of the dummy unit.
         
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
            call RemoveLocation(dummyLoc)
            set dummyLoc = null
        endloop      
     
        loop
            exitwhen I2R(i4) > DISTANCE
         
            set damageLoc[i3]   = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i3), angel - (i3*SPIN) )
            set damageLoc[i3+1] = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i3-1), angel - (i3*SPIN) )
         
            set damageX[i3]    = GetLocationX(damageLoc[i3])
            set damageY[i3]    = GetLocationY(damageLoc[i3])
         
            set damageX[i3+1]  = GetLocationX(damageLoc[i3+1])
            set damageY[i3+1]  = GetLocationY(damageLoc[i3+1])
            call GroupEnumUnitsInRangeOfSegment(copy, damageX[i3+1], damageY[i3+1], damageX[i3], damageY[i3], AOE, b )
         
         
            call DisplayTextToForce( GetPlayersAll(), ( I2S(CountUnitsInGroup(copy)) + "copy" ) )
            call DisplayTextToForce( GetPlayersAll(), ( I2S(CountUnitsInGroup(g)) + "g" ) )
            loop
                set u = FirstOfGroup(copy)
                exitwhen( u == null)
                call GroupRemoveUnit(copy, u)
                if IsUnitEnemy(u, owner) then
                    call GroupAddUnit(g, u)
                endif  
            endloop
         
            call RemoveLocation(damageLoc[i3])
            set damageLoc[i3] = null
         
            set i3 = i3 + 1
            set i4 = i4 + R2I(MISSILE_OFFSET)
        endloop
     
        loop
            set u = FirstOfGroup(g)
            exitwhen(u == null)
            call GroupRemoveUnit(g, u)
            call UnitDamageTarget(caster, u, DAMAGE, true, false, ATTACK_TYPE, DAMAGE_TYPE, null)
        endloop
       
        call RemoveLocation(spellLoc)
        call RemoveLocation(loc)
        call RemoveLocation(offset)
        call RemoveLocation(loopOffset)
     
        set spellLoc   = null
        set loc        = null
        set offset     = null
        set caster     = null
        set loopOffset = null
        set owner      = null
    endfunction
   
   
   
    private function init takes nothing returns nothing
        call RegisterSpellEffectEvent(AID, function run) 
       
        //Setting globals
        set b    = Condition(function Pick)
        set g    = CreateGroup()
        set copy = CreateGroup()
       
        //Preloading the ability 
        set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_UNIT, 0, 0, 0)
        call UnitAddAbility(bj_lastCreatedUnit, AID)
        call KillUnit(bj_lastCreatedUnit)
    endfunction 


endscope
 
Last edited:
Level 7
Joined
Oct 19, 2015
Messages
286
You forgot to set loopLoc and dummy to null. You could do the whole spell without using locations at all. Also, you should use an ability preloading library instead of creating a dummy unit for each spell.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
"angel" lol

Next to which, you cant.
A location exists of 2 reals, so it cannot be converted to a real.
It can be converted to a double (which doesnt exist in JASS), but that is a different story.

If you want to get rid of the location thing, you can just look at the code of that function and implement the calculation in the script itself.

This is all you need:
JASS:
constant native GetUnitX takes unit whichUnit returns real
constant native GetUnitY takes unit whichUnit returns real
constant native GetSpellTargetX takes unit whichUnit returns real
constant native GetSpellTargetY takes unit whichUnit returns real

function PolarProjectionBJ takes location source, real dist, real angle returns location
    local real x = GetLocationX(source) + dist * Cos(angle * bj_DEGTORAD)
    local real y = GetLocationY(source) + dist * Sin(angle * bj_DEGTORAD)
    return Location(x, y)
endfunction

function AngleBetweenPoints takes location locA, location locB returns real
    return bj_RADTODEG * Atan2(GetLocationY(locB) - GetLocationY(locA), GetLocationX(locB) - GetLocationX(locA))
endfunction

EDIT:
On a side note, I would make the dummy units slightly different.
I would take the total length, divide it by the optimal dummy distance (so you have a number that would be the number of dummies), then round it to the nearest integer (because you cant have a half dummy), then divide the total distance by that number (then you have the distance between each dummy), then make the dummies with the dummyDistance*i + dummyDistance/2 (if you start with i = 0, if not, you substract the half of the dummy distance instead of adding it).

That way, the actual beam (as I think it is a beam that you are representing with the dummies) is then based around a slightly more accurate distance.
But that is high level eye candy :D (I think even Tank-Commander doesnt go that far.)
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
A location exists of 2 reals, so it cannot be converted to a real.
Hence why he asked...
How do I convert this into reals?

It can be converted to a double (which doesnt exist in JASS), but that is a different story.
No it cannot be converted to a double precision float as that still represents a single number like a single precision float except with higher accuracy.

It could be converted to a complex number though.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
The double I know is a 64-bit floating point.
Yes a single 64bit floating point number.

With 64 bits, you have 2 reals (32-bit floats)
No you have just a single double precision floating point number. You might be confusing it with an array of 2 single floating points which is also 64bits if byte or 4 byte alignment is used.

you can put a location with its effective precision inside a double (or a long).
No you cannot as the way floating point works does not allow for it to be used as a general data structure. If it did, the algorithm needed would be very slow.

Sure one could reinterpret cast, but that is not recommended outside of interpreting raw data as it removes almost all type safety features. One might as well do it to any 64bit type though including uint64_t.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
The casting of the data types of primitive variables (of the same size) should actually (logically) be at a 100% efficiency.
However, people arent that smart.

If I have an integer and I want to have a float of exactly the same bytes (instead of the same value), I for some reason cannot do that in any of the languages I tried. Instead, I have to cast it to a byte array and then to the float.

It literally is the most easiest typecast, but it just isnt there... or I didnt search well enough.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
The casting of the data types of primitive variables (of the same size) should actually (logically) be at a 100% efficiency.
Reinterpret casts yes. But changing a single to a double is not a reinterpret cast.

If I have an integer and I want to have a float of exactly the same bytes (instead of the same value), I for some reason cannot do that in any of the languages I tried. Instead, I have to cast it to a byte array and then to the float.
C++ should allow that via reinterpret cast.

Most languages do not support it because it is a stupid thing to do in the first place. The very opposite of type safety.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Not if you want to cast a 4 char sequence to an int... or the other way around.
Same for floats, or from ints to floats so you can do it from chars to ints to floats.
Recently been thinking about data storage in WC3, and the primitive types come first ofcourse. (As they are the only actual data types.)
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
Not if you want to cast a 4 char sequence to an int... or the other way around.
Except 4 chars can be bigger than a int32_t... Hence the only logical way would be via pointer and that is only legal if the memory alignment model allows it.

Same for floats, or from ints to floats so you can do it from chars to ints to floats.
Generally one should not be doing such things... It violates type safety.

Recently been thinking about data storage in WC3, and the primitive types come first ofcourse. (As they are the only actual data types.)
WC3 is not really a programming language. The fact one can reinterpret cast integer to real and real to integer is due to oversights.
 
Level 6
Joined
Dec 6, 2009
Messages
168
Lol I didn't even see that I misspelled that one :D
"angel" lol

I don't see what I gain from using this. I still need to use locations and I feel like this just gives me more code that doesn't do anything?
This is all you need:
JASS:
constant native GetUnitX takes unit whichUnit returns real
constant native GetUnitY takes unit whichUnit returns real
constant native GetSpellTargetX takes unit whichUnit returns real
constant native GetSpellTargetY takes unit whichUnit returns real

function PolarProjectionBJ takes location source, real dist, real angle returns location
    local real x = GetLocationX(source) + dist * Cos(angle * bj_DEGTORAD)
    local real y = GetLocationY(source) + dist * Sin(angle * bj_DEGTORAD)
    return Location(x, y)
endfunction

function AngleBetweenPoints takes location locA, location locB returns real
    return bj_RADTODEG * Atan2(GetLocationY(locB) - GetLocationY(locA), GetLocationX(locB) - GetLocationX(locA))
endfunction

Exactly how am I supposed to get the dummys lenght? It feels like it would be really cool to do it, but just stupid since I don't think it would look that much different haha. But if you show me an example of such a system I would tre to use it for the sake of learning and because it sounds cool :D
EDIT:
On a side note, I would make the dummy units slightly different.
I would take the total length, divide it by the optimal dummy distance (so you have a number that would be the number of dummies), then round it to the nearest integer (because you cant have a half dummy), then divide the total distance by that number (then you have the distance between each dummy), then make the dummies with the dummyDistance*i + dummyDistance/2 (if you start with i = 0, if not, you substract the half of the dummy distance instead of adding it).

That way, the actual beam (as I think it is a beam that you are representing with the dummies) is then based around a slightly more accurate distance.
But that is high level eye candy :D (I think even Tank-Commander doesnt go that far.)
 
Level 7
Joined
Oct 19, 2015
Messages
286
No, if you substitute your code correctly, you can avoid locations altogether. Instead of getUnitLoc, use GetUnitX and GetUnitY. You need two local variables instead of one to store the data, but this is far less costly than creating a location with GetUnitLoc. Then, use the internal code of PolarProjectionBJ and AngleBetweenPoints to work with coordinates directly, without having to create new locations along the way. Whenever your original code would create a location, you would instead calculate its coordinates and store those as your result in two real variables.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Dont use functions that require a location.
The only function that cannot be avoided/replaced is GetLocationZ() (which returns the terrain height).

JASS:
    private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()
        local unit u
        local unit dummy
        local player owner      = GetOwningPlayer(caster)
    
        local real spellX       = GetSpellTargetX()
        local real spellY       = GetSpellTargetY()
        //local location spellLoc = GetSpellTargetLoc()
        local real casterX      = GetUnitX(caster)
        local real casterY      = GetUnitY(caster)
        //local location loc      = GetUnitLoc(caster)
        local real angle        = Atan2(spellY - casterY, spellX - casterX)
        local real offsetX      = casterX + DISTANCE*Cos(angle)
        local real offsetY      = casterY + DISTANCE*Sin(angle)
        //local location offset   = PolarProjectionBJ(loc, DISTANCE, angel)
      
        //local real casterX      = GetUnitX(caster)
        //local real casterY      = GetUnitY(caster)
        //local real rangeX       = GetLocationX(offset) //offsetX
        //local real rangeY       = GetLocationY(offset) //offsetY
    
        local integer  i        = 1
        local integer  i2       = 1
        local real loopX
        local real loopY
        //local location loopLoc
        local real loopOffsetX = casterX + BEAM_OFFSET*Cos(angle)
        local real loopOffsetY = casterY + BEAM_OFFSET*Sin(angle)
        //local location loopOffset = PolarProjectionBJ(loc, BEAM_OFFSET, angel)
    
        loop
            exitwhen I2R(i2) > DISTANCE
          
            set loopX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle)
            set loopY = loopOffsetY + MISSILE_OFFSET*I2R(i)*Sin(angle)
            //set loopLoc = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i), angle)
          
            set dummy = CreateUnit(owner, DUMMY_UNIT, loopX, loopY, angle)
            //set dummy = CreateUnitAtLoc(owner, DUMMY_UNIT, loopLoc, angle)
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )
          
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
            //call RemoveLocation(loopLoc)
        endloop
You can finish it off.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
this is completly and utterly offtopic, but I will be the dick here

Except 4 chars can be bigger than a int32_t...

This is false.

Besides the minimal bit counts, the C++ Standard guarantees that 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long).

from Fundamental types - cppreference.com

Also int32_t will always be defined as 32 bits of storage, and will only be defined for your target platform, if that platform can represent the data in such way.

Last time my math still said that 8 * 4 == 32, so unless you are using architecture that uses non 8bit bytes, 4*char == int32_t
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
This is false.
Only in C/C++ and assuming an array of char type 4 long rather than 4 individual characters, as characters are mostly encoded in some kind of Unicode now. In Java char type is usually 2 bytes so only 2 fit in a Java int (equivelant to C/C++ int32_t) and it cannot represent all legal characters. Technically an int32_t is only guaranteed to store 1 encoded character, and even that is open to debate by some as future revisions to Unicode could add character encodings which could exceed 4 bytes in length.

One generally avoids using the C/C++ char type outside of string purposes. Except if you are Microsoft of course, who uses char* instead of void*...

This was a general argument about reinterpret casting and why one should generally avoid it outside of a very specific purposes.
 
Level 6
Joined
Dec 6, 2009
Messages
168
Amazing, works perfectly and it fells alot smoother ingame actually!
Dont use functions that require a location.
The only function that cannot be avoided/replaced is GetLocationZ() (which returns the terrain height).

JASS:
    private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()
        local unit u
        local unit dummy
        local player owner      = GetOwningPlayer(caster)
  
        local real spellX       = GetSpellTargetX()
        local real spellY       = GetSpellTargetY()
        //local location spellLoc = GetSpellTargetLoc()
        local real casterX      = GetUnitX(caster)
        local real casterY      = GetUnitY(caster)
        //local location loc      = GetUnitLoc(caster)
        local real angle        = Atan2(spellY - casterY, spellX - casterX)
        local real offsetX      = casterX + DISTANCE*Cos(angle)
        local real offsetY      = casterY + DISTANCE*Sin(angle)
        //local location offset   = PolarProjectionBJ(loc, DISTANCE, angel)
    
        //local real casterX      = GetUnitX(caster)
        //local real casterY      = GetUnitY(caster)
        //local real rangeX       = GetLocationX(offset) //offsetX
        //local real rangeY       = GetLocationY(offset) //offsetY
  
        local integer  i        = 1
        local integer  i2       = 1
        local real loopX
        local real loopY
        //local location loopLoc
        local real loopOffsetX = casterX + BEAM_OFFSET*Cos(angle)
        local real loopOffsetY = casterY + BEAM_OFFSET*Sin(angle)
        //local location loopOffset = PolarProjectionBJ(loc, BEAM_OFFSET, angel)
  
        loop
            exitwhen I2R(i2) > DISTANCE
        
            set loopX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle)
            set loopY = loopOffsetY + MISSILE_OFFSET*I2R(i)*Sin(angle)
            //set loopLoc = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i), angle)
        
            set dummy = CreateUnit(owner, DUMMY_UNIT, loopX, loopY, angle)
            //set dummy = CreateUnitAtLoc(owner, DUMMY_UNIT, loopLoc, angle)
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )
        
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
            //call RemoveLocation(loopLoc)
        endloop
You can finish it off.

But can someone please explain to me why the first one works but the second doesn't? When I made it in reals the straight beam worked perfectly but I don't understand why this one doesn't do the same? :O

EDIT: private constant real SPIN = 3.

Image of how it should look like:
XnwggVs.png

The code for the picute above:
JASS:
private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()      //Casting unit
        local unit u                                     
        local unit dummy         
        local player owner      = GetOwningPlayer(caster)
                                                        
        local location spellLoc = GetSpellTargetLoc()   
        local location loc      = GetUnitLoc(caster) 
        local real angle        = AngleBetweenPoints(loc, spellLoc)
        local location offset   = PolarProjectionBJ(loc, DISTANCE, angle)
        local real rangeX       = GetLocationX(offset)
        local real rangeY       = GetLocationY(offset)
    
        local real array damageX
        local real array damageY
    
        local integer  i       = 1 //Loop 1 integer
        local integer  i2      = 1 //Checking so the range of the beam is not longer than our DISTANCE variable.
        local integer  i3      = 1 //Loop 2 integer
        local integer  i4      = 1 //Same as i2 but for damage instead
    
        local location dummyLoc
    
        local location array damageLoc
        local location loopOffset = PolarProjectionBJ(loc, BEAM_OFFSET, angle)
    
        loop
            exitwhen I2R(i2) >= DISTANCE
      
            set dummyLoc = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i), angle - (i*SPIN) ) //The location of the dummy unit.
        
            set dummy = CreateUnitAtLoc(owner, DUMMY_UNIT, dummyLoc, angle - (i*SPIN) ) //The creation of the dummy unit with spin.
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )                     //Timed life of the dummy unit.
        
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
            call RemoveLocation(dummyLoc)
            set dummyLoc = null
        endloop
Image of how it looks like when I use reals instead:
SeTW0ox.png

And the code for it:
JASS:
private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()      //Casting unit
        local unit u                                     
        local unit dummy         
        local player owner      = GetOwningPlayer(caster)
                                                        
        
        local real spellX       = GetSpellTargetX()
        local real spellY       = GetSpellTargetY()
      
      
        local real casterX      = GetUnitX(caster)
        local real casterY      = GetUnitY(caster)
      
        local real angle        = Atan2(spellY - casterY, spellX - casterX) //Getting the angle

        local real offsetX      = casterX + DISTANCE*Cos(angle)
        local real offsetY      = casterY + DISTANCE*Sin(angle)
    
    
        local integer  i       = 1 //Loop 1 integer
        local integer  i2      = 0 //Checking so the range of the beam is not longer than our DISTANCE variable.
        local integer  i3      = 1 //Loop 2 integer
        local integer  i4      = 0 //Same as i2 but for damage instead
      
        local real dummyLoopX
        local real dummyLoopY
    
        local real array damageLoopX
        local real array damageLoopY
      
        local real loopOffsetX = casterX + BEAM_OFFSET*Cos(angle)
        local real loopOffsetY = casterY + BEAM_OFFSET*Sin(angle)
    
        loop
            exitwhen I2R(i2) >= DISTANCE
          

            set dummyLoopX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle - (i*SPIN))
            set dummyLoopY = loopOffsetY + MISSILE_OFFSET*I2R(i)*Sin(angle - (i*SPIN))
        
            set dummy = CreateUnit(owner, DUMMY_UNIT, dummyLoopX, dummyLoopY, angle*bj_RADTODEG - (i*SPIN))
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )                     //Timed life of the dummy unit.
        
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
        endloop
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
It is because you are misusing the angle.
(Now I do remember that unit creation is done by degrees, where I made a mistake in what I posted.)
You are substracting i*SPIN from an angle in radians. However, SPIN is in degrees.

It is better to stick to one kind of notation (at which point, radians ussually come out on top) but WC3's API isnt completely like that either.

I also sure hope that you do not use those damageX and damageY.
 
Level 6
Joined
Dec 6, 2009
Messages
168
why not? I use it for this in this one where it bends, I should probably call it bend instead of spin now when I think about it.
JASS:
loop
            exitwhen I2R(i4) >= DISTANCE
       
            set damageLoc[i3]   = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i3), angle - (i3*SPIN) )
            set damageLoc[i3+1] = PolarProjectionBJ( loopOffset, MISSILE_OFFSET * I2R(i3-1), angle - (i3*SPIN) )
       
            set damageX[i3]    = GetLocationX(damageLoc[i3])
            set damageY[i3]    = GetLocationY(damageLoc[i3])
       
            set damageX[i3+1]  = GetLocationX(damageLoc[i3+1])
            set damageY[i3+1]  = GetLocationY(damageLoc[i3+1])
            call GroupEnumUnitsInRangeOfSegment(copy, damageX[i3+1], damageY[i3+1], damageX[i3], damageY[i3], AOE, b )
       
            loop
                set u = FirstOfGroup(copy)
                exitwhen( u == null)
                call GroupRemoveUnit(copy, u)
                if IsUnitEnemy(u, owner) then
                    call GroupAddUnit(g, u)
                endif
            endloop
       
            call RemoveLocation(damageLoc[i3])
            call RemoveLocation(damageLoc[i3+1])
            set damageLoc[i3+1] = null
            set damageLoc[i3]   = null
       
            set i3 = i3 + 1
            set i4 = i4 + R2I(MISSILE_OFFSET)
        endloop

You are substracting i*SPIN from an angle in radians. However, SPIN is in degrees.

It is better to stick to one kind of notation (at which point, radians ussually come out on top) but WC3's API isnt completely like that either.
Oh okay I get why it doesn't work, but I don't understand what I should do to make it work. I guess I need a real that is the radians aswell? I really appreciate you helping me, I have already learned alot from just this one thread, really! :D
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Just multiply the SPIN by bj_DEGTORAD, then you can add it to the angle.

The damage variable arrays are completely not needed as you dont have to store the data, you only use one instance.
For the segment, you can do like:
set newX = loopOffsetX + MISSILE_OFFSET * Cos(angle) //stuff
set newY = loopOffsetY + MISSILE_OFFSET * Sin(angle) //stuff
do stuff with segment from prevXY to newXY
set prevX = newX
set prevY = newY

As long as you set the prevX and prevY before you start the loop, it will all work perfectly.
 
Level 6
Joined
Dec 6, 2009
Messages
168
I'm sorry but I really don't get it. I added the *bj_RADTODEG to the SPIN. And I guess I set
JASS:
local real prevX = loopOffsetX
and do the same for Y before the loop. And then I don't understand exactly where or what to do with the prevX and prevY. Sure I get that I need to save the prevXY so that I can take the old position and move it from there. But am I supposed to have it like this and add some sort of prevY - newY or I really can't figure it out :(
JASS:
set newX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle - (i*SPIN*bj_RADTODEG))
Just multiply the SPIN by bj_DEGTORAD, then you can add it to the angle.

The damage variable arrays are completely not needed as you dont have to store the data, you only use one instance.
For the segment, you can do like:
set newX = loopOffsetX + MISSILE_OFFSET * Cos(angle) //stuff
set newY = loopOffsetY + MISSILE_OFFSET * Sin(angle) //stuff
do stuff with segment from prevXY to newXY
set prevX = newX
set prevY = newY

As long as you set the prevX and prevY before you start the loop, it will all work perfectly.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
I'm sorry but I really don't get it. I added the *bj_RADTODEG to the SPIN. And I guess I set
Please post the full code, including what "SPIN" is set to...

In JASS the Cos and Sin functions use radians. Unit facing is in degrees. Multiplying an angle in radians by bj_RADTODEG will give degrees. Multiplying an angle in degrees by bj_DEGTORAD will give radians.

0 degrees = 0 radians
180 degrees = pi radians
360 degrees = 2pi radians

They teach this stuff in highschool mathematics luckily.
 
Level 6
Joined
Dec 6, 2009
Messages
168
JASS:
scope WaterBeamSpin initializer init 
   
    native UnitAlive takes unit id returns boolean
   
   //Settings
    globals
        private constant integer    AID            = 'A002'  
     
        private constant attacktype ATTACK_TYPE    = ATTACK_TYPE_NORMAL      
        private constant damagetype DAMAGE_TYPE    = DAMAGE_TYPE_MAGIC
     
        private constant real       DISTANCE       = 1000.
        private constant real       DAMAGE         = 100.
        private constant real       AOE            = 75.
        private constant real       MISSILE_OFFSET = 50.
     
        private constant real       SPIN           = 3.
        private constant real       BEAM_OFFSET    = 100.
     
        private constant integer    DUMMY_UNIT     = 'h001'
     
        private group g                  
        private group copy                  
        private boolexpr b
    endglobals
   
   
    private function Targets takes unit target returns boolean
    //the units the spell will affect
        return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
   
   
    private function Pick takes nothing returns boolean 
        return Targets(GetFilterUnit())
    endfunction
   
    private function run takes nothing returns nothing
        local unit caster       = GetTriggerUnit()      //Casting unit
        local unit u                                      
        local unit dummy          
        local player owner      = GetOwningPlayer(caster)
                                                         
         
        local real spellX       = GetSpellTargetX() 
        local real spellY       = GetSpellTargetY() 
       
       
        local real casterX      = GetUnitX(caster)
        local real casterY      = GetUnitY(caster)
       
        local real angle        = Atan2(spellY - casterY, spellX - casterX) //Getting the angle
       

        local real offsetX      = casterX + DISTANCE*Cos(angle)
        local real offsetY      = casterY + DISTANCE*Sin(angle) 
     
     
        local integer  i       = 1 //Loop 1 integer
        local integer  i2      = 0 //Checking so the range of the beam is not longer than our DISTANCE variable.
        local integer  i3      = 1 //Loop 2 integer 
        local integer  i4      = 0 //Same as i2 but for damage instead
       
        local real newX
        local real newY
        local real newXY
       
        local real dummyLoopX
        local real dummyLoopY
     
        local real array damageLoopX
        local real array damageLoopY
       
        local real loopOffsetX = casterX + BEAM_OFFSET*Cos(angle)
        local real loopOffsetY = casterY + BEAM_OFFSET*Sin(angle)
       
        local real prevX = loopOffsetX
        local real prevY = loopOffsetY
     
        loop 
            exitwhen I2R(i2) >= DISTANCE
           

            set newX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle - (i*SPIN*bj_RADTODEG))
            set newY = loopOffsetY + MISSILE_OFFSET*I2R(i)*Sin(angle - (i*SPIN*bj_RADTODEG))
           
           
           
            set dummy = CreateUnit(owner, DUMMY_UNIT, newX, newY, angle*bj_RADTODEG - (i*SPIN*bj_RADTODEG))
            call UnitApplyTimedLifeBJ( ( 0.85 - ( 0.01 * I2R(i) ) ), 'BTLF', dummy )                     //Timed life of the dummy unit.
         
            set i  = i + 1
            set i2 = i2 + R2I(MISSILE_OFFSET)
        endloop      
     
        //Will change this when the loop above works
        loop
            exitwhen I2R(i4) >= DISTANCE
         
            set damageLoopX[i3] = loopOffsetX + MISSILE_OFFSET*I2R(i3)*Cos(angle - (i3*SPIN))
            set damageLoopY[i3] = loopOffsetY + MISSILE_OFFSET*I2R(i3)*Sin(angle - (i3*SPIN))

           
            set damageLoopX[i3+1] = loopOffsetX + MISSILE_OFFSET*I2R(i3 - 1)*Cos(angle - (i3*SPIN))
            set damageLoopY[i3+1] = loopOffsetY + MISSILE_OFFSET*I2R(i3 - 1)*Sin(angle - (i3*SPIN))
         
            call GroupEnumUnitsInRangeOfSegment(copy, damageLoopX[i3+1], damageLoopY[i3+1], damageLoopX[i3], damageLoopY[i3], AOE, b )
         
            loop
                set u = FirstOfGroup(copy)
                exitwhen( u == null)
                call GroupRemoveUnit(copy, u)
                if IsUnitEnemy(u, owner) then
                    call GroupAddUnit(g, u)
                endif  
            endloop
           
            set i3 = i3 + 1
            set i4 = i4 + R2I(MISSILE_OFFSET)
        endloop
     
        loop
            set u = FirstOfGroup(g)
            exitwhen(u == null)
            call GroupRemoveUnit(g, u)
            call UnitDamageTarget(caster, u, DAMAGE, true, false, ATTACK_TYPE, DAMAGE_TYPE, null)
        endloop

        set caster     = null
        set owner      = null
        set dummy      = null
    endfunction
   
   
   
    private function init takes nothing returns nothing
        call RegisterSpellEffectEvent(AID, function run) 
       
        //Setting globals
        set b    = Condition(function Pick)
        set g    = CreateGroup()
        set copy = CreateGroup()
       
        //Preloading the ability 
        set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_UNIT, 0, 0, 0)
        call UnitAddAbility(bj_lastCreatedUnit, AID)
        call KillUnit(bj_lastCreatedUnit)
    endfunction 


endscope
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
set newX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle - (i*SPIN*bj_RADTODEG))
set newY = loopOffsetY + MISSILE_OFFSET*I2R(i)*Sin(angle - (i*SPIN*bj_RADTODEG))
Try...
JASS:
            set newX = loopOffsetX + MISSILE_OFFSET*I2R(i)*Cos(angle - (i*SPIN*bj_DEGTORAD))
            set newY = loopOffsetY + MISSILE_OFFSET*I2R(i)*Sin(angle - (i*SPIN*bj_DEGTORAD))

            set damageLoopX[i3] = loopOffsetX + MISSILE_OFFSET*I2R(i3)*Cos(angle - (i3*SPIN*bj_DEGTORAD))
            set damageLoopY[i3] = loopOffsetY + MISSILE_OFFSET*I2R(i3)*Sin(angle - (i3*SPIN*bj_DEGTORAD))

         
            set damageLoopX[i3+1] = loopOffsetX + MISSILE_OFFSET*I2R(i3 - 1)*Cos(angle - (i3*SPIN*bj_DEGTORAD))
            set damageLoopY[i3+1] = loopOffsetY + MISSILE_OFFSET*I2R(i3 - 1)*Sin(angle - (i3*SPIN*bj_DEGTORAD))
This assumes SPIN is 3 degrees and not 3 radians (171.89... degrees).

Redefining SPIN as being in radians will likely be an optimization due to poor JASS compilers not optimizing well.
 
Last edited:
Level 6
Joined
Dec 6, 2009
Messages
168
This assumes SPIN in 3 degrees and not 3 radians (171.89... degrees).

Redefining SPIN as being in radians will likely be an optimization due to poor JASS compilers not optimizing well.
Oh that makes sense. I tried changing the SPIN value, and if I put it at 1,1 then it looks almost the same as the original one. But if I change it to 1,2 it changes the whole shape of the beam.

So if I want to do this should I just use locations since it seems really hard getting the SPIN to work with an easy way of changing it?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,196
So if I want to do this should I just use locations since it seems really hard getting the SPIN to work with an easy way of changing it?
If spin is in radians then you can compute the constant like...

JASS:
private constant real SPIN = 3.0 * bj_DEGTORAD // 3 degrees in radians
private constant real SPIN = 1.0 * bj_DEGTORAD // 1 degrees in radians
private constant real SPIN = 180 * bj_DEGTORAD // 180 degrees in radians
 
Status
Not open for further replies.
Top