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

Small Code Snippets

Level 7
Joined
Dec 3, 2006
Messages
339
Weird; I thought i tested this a long long time ago and it work'd. I'll have to do some testing and fix the function then. I might just end up using the root ability.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
@Switch33 I was testing this too. Doesn't work ;/

You might think, that little modification should work:
JASS:
function SetBuildingFacing takes unit u, real a returns nothing
    call UnitRemoveType(u, UNIT_TYPE_STRUCTURE)
    call SetUnitMoveSpeed(u, 100)
    call SetUnitTurnSpeed(u,1.)
    call SetUnitFacing(u, a)
    call SetUnitMoveSpeed(u, 0)
    call UnitAddType(u, UNIT_TYPE_STRUCTURE)
endfunction
But it doesn't. Adding 'Amov' ability won't work too, that hiden ability can't be added again as well as 'Aatk' one.

Root is the best option here, although you can always move unit to it's actual position but in differend angle ;S

About classifications.
Adding/removing those works nicely. For example removing UNIT_TYPE_SUMMONED from ladder summons will make them take no damage and be untargetable for single-target dispell spells.

I had run ton of tests about classification manipulation in case I have used idea in my entry in newest Techtree project D;


EDIT: I took main function from my GUI FaceTower script:
JASS:
function SetBuildingFacing takes unit u, real a returns nothing
    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call SetUnitFacing(u, a)
endfunction
Works perfectly as small addon function for setting building's facing angle.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Useful projection scripts

JASS:
    function GetMagnitude2D takes real x, real y returns reaal
        return SquareRoot(x*x+y*y)
    endfunction
    function GetMagnitude3D takes real x, real y, real z returns real
        return SquareRoot(x*x+y*y+z*z)
    endfunction

    function GetAngle2D takes real x, real y returns real
        return Atan2(y, x)
    endfunction
    function GetAngle3D takes real distance2D, real z returns real
        return Atan2(z, distance2D)
    endfunction

    function GetProjectedX takes real distance2D, real angle2D returns real
        return distance2D*Cos(angle2D)
    endfunction
    function GetProjectedY takes real distance2D, real angle2D returns real
        return distance2D*Sin(angle2D)
    endfunction
    function GetProjectedZ takes real distance3D, real angle3D returns real
        return distance3D*Sin(angle3D)
    endfunction
    function GetProjectedZ2 takes real distance2D, real angle3D returns real
        return Tan(angle3D)*distance2D
    endfunction

    function GetDistance2D takes real distance3D, real angle3D returns real
        return distance3D*Cos(angle3D)
    endfunction
    function GetDistance3D takes real distance2D, real angle3D returns real
        return distance2D/Cos(angle3D)
    endfunction
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
well, the GetDistance2D is pretty obvious too ; P

the 3D distance is the distance between 2 points in 3D space. So rather than 2D distance, which has x and y components, you are dealing with x, y, and z components. Both points may be anywhere on the map and at any height.

The 3D angle is the z angle between the 2 points ; ).

GetMagnitude3D is dangerous as GetLocationZ is asynchronous

Where are you passing in GetLocationZ there ; P
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I know that those are the expected values however reading from top-to-bottom it didn't make sense. If you moved GetDistance2D below the two functions then it would make sense that the expected parameters was one of the values above.

The GetAngle3D is a very important function to explain because it is extremely useful when using the xe dummy unit and good for newbies to practice coding their first projectile systems (that's many people's favorite thing to re-code).

I don't know why GetDistance2D is useful, save for it being a reverse-lookup utility. GetDistance2D makes more sense to be the GetMagnitutde2D function.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Bribe, let's say that you are given a 3D distance, a 2D angle, and a 3D angle. You need to project a point given that stuff. This means that you need to project it in the x, the y, and the z. In order to project in the x and y, you have to get the 2D distance, which is the hypotenuse of the triangle on the plane.

JASS:
        local real dist2D = dist3D*Cos(angle3D)
        set x = x0 + dist2D*Cos(angle2D)
        set y = y0 + dist2D*Sin(angle2D)
        set z = z0 + dist3D*Sin(angle3D)

When you are working in 3D, you are working with 3D distances, meaning that you have to have a way to get the 2D distance out of it : ).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
After much effort, I've come up with this for lossy compression in save/load ; ).


Lossy compression is commonly used on hero experience, hero x,y coordinates, and player gold. The most common equation for gold is R2I(gold/100), which cuts off the last 2 digits. The problem with this is that it fails with smaller values.


The equation I present will still get the max of 10,000 for gold like the above but with better results ; ).

JASS:
library CompressInt
//performs lossy compression on integers
//compressed number = number^(2/3)

//decomperssed number = roundDown(number^1.5+.5)

function CompressInt takes integer n returns integer
    return R2I(Pow(n,2/3.))
endfunction
function DecompressInt takes integer n returns integer
    return R2I(Pow(n,1.5)+.5)
endfunction

endlibrary


This does lose accuracy, but it's extremely good and works on any size numbers.


You can also compress multiple times.


The current exponents I have set up seem to be the magic numbers ; ).



Demonstration:

995995 -> 9973
9973 -> 995952


As can be seen, rather than getting 995000, 995952 was retrieved, which is quite a bit closer to the real value.


123456 -> 2479
2479 -> 123428


357832393 -> 504026
504026 -> 357832192


Side by side
357832393
357832192


Yes, the retrieved value is only 201 off from the actual number while storing a 9 digit number as a 6 digit number.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
JASS:
function Compress takes integer n returns integer
    return R2I(n,2/3.)
endfunction
function Decompress takes integer n returns integer
    return R2I(Pow(n,1.5))
endfunction

Should be:

JASS:
function CompressInt takes integer i returns integer
    return R2I(Pow(i, 2/3.) + 0.5)
endfunction
function DecompressInt takes integer i returns integer
    return R2I(Pow(i, 1.5) + 0.5)
endfunction

I like this a lot. You don't always need the exact value especially with huge numbers.
 
Try this:

JASS:
function CompressInt takes integer i returns integer
    return R2I(Pow(i, 2/5) + 0.5)
endfunction
function DecompressInt takes integer i returns integer
    return R2I(Pow(i, 5/2) + 0.5)
endfunction

It's less accurate, but compresses integers even more ;p

edit
Well, I just tested it and deduced that using 2/3 in the Pow function is much better :p
Using 2/5 compresses the number greatly, yet accuracy is lost greatly as well :eek:
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I have tested various compression using Python and I've also found any other compression pretty lame. Nestharus did find some good numbers here to work with.

Python code which you can use for really fast checking (just dl the open source GUI):

Code:
def compress(i):
    return int(i ** (2/3.)) #Python 3.X does not need the dot after the 3

def decompress(i):
    return int(i ** 1.5 + 0.5)

def test(i):
    j = compress(i)
    print j, "\n", i, "\n", decompress(j) #Python 2.X
    #print(j, "\n", i, "\n", decompress(j)) #Python 3.X
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
GroupMacro, either as a resource or just a short how-to. This eliminates needing to rely on GroupUtils to do it for you, because GroupUtils is really unsalvageable.

JASS:
library GroupMacro
/*
    Example use:
        
        local unit caster = GetTriggerUnit()
        local unit u
        local real x = GetSpellTargetX()
        local real y = GetSpellTargetY()
        local real range = 100
        //! runtextmacro GroupMacro("u", "x", "y", "range")
            call UnitDamageTarget(caster, u, false, false, null, null, null)
        //! runtextmacro GroupMacroEnd()
    
    The "172" in the below textmacro should be configured to the max collision
    size in your map. 172 is the max natural collision size which is used by
    a town-hall type building.
*/
//! textmacro GroupMacro takes UNIT_VAR, X, Y, RANGE
    call GroupEnumUnitsInRange(bj_lastCreatedGroup, $X$, $Y$, $RANGE$ + 172, null)
    loop
        set $UNIT_VAR$ = FirstOfGroup(bj_lastCreatedGroup)
        exitwhen $UNIT_VAR$ == null
        call GroupRemoveUnit(bj_lastCreatedGroup, $UNIT_VAR$)
        if IsUnitInRangeXY($UNIT_VAR$, $X$, $Y$, $RANGE$) then
//! endtextmacro
    
//! textmacro GroupMacroEnd
        endif
    endloop
//! endtextmacro
    
endlibrary
 
Level 6
Joined
Jun 20, 2011
Messages
249
Disclaimer: this ISN'T a rip off a previous version of RegisterPlayerUnitEvent
JASS:
library RectEvent uses Table

    globals
        private Table t
    endglobals
    
    function RegisterEnterRectEvent takes rect whichRect, code func returns nothing
        local integer id=GetHandleId(whichRect)
        local trigger trig=t.trigger[id]
        local region reg
        if trig==null then
            set trig=CreateTrigger()
            set reg=CreateRegion()
            call RegionAddRect(reg,whichRect)
            call TriggerRegisterEnterRegion(trig,reg,null)
            set reg=null
        endif
        call TriggerAddCondition(trig,Condition(func))
        set trig=null
    endfunction
    
    private module RectEventModule
        static method onInit takes nothing returns nothing
            set t=Table.create()
        endmethod
    endmodule
    
    private struct RectEventStruct extends array
        implement RectEventModule
    endstruct
    
endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Honestly I'd rather use this:

JASS:
    function RegisterEnterRectEvent takes rect r, code func returns nothing
        local trigger t = CreateTrigger()
        local region g = CreateRegion()
        call RegionAddRect(g, r)
        call TriggerRegisterEnterRegion(t, g, null)
        call TriggerAddCondition(t, Filter(func))
        set g = null
        set t = null
    endfunction

The chance for sharing a rect is so minimal...
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
There is TimedHandles by TriggerHappy and TimedEffects by Moyack, however I wanted a super-super light version that didn't have any more code than necessary (vJass struct safety is not required here, for example) so I made this.

JASS:
library DestroyFX requires TimerUtils
    
    globals
        private integer n = 0
        private integer array r
        private effect array x
    endglobals
    
    private function Expire takes nothing returns nothing
        local integer i = GetTimerData(GetExpiredTimer())
        call ReleaseTimer(GetExpiredTimer())
        call DestroyEffect(x[i])
        set r[i] = r[0]
        set r[0] = i
    endfunction
    
    function DestroyFX takes effect e, real duration returns nothing
        local integer i = r[0]
        if i == 0 then
            set i = n + 1
            set n = i
        else
            set r[0] = r[i]
        endif
        set x[i] = e
        call TimerStart(NewTimerEx(i), duration, false, function Expire)
    endfunction
    
endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
This takes advantage of WaterKnight's new discovery. It is a safety-queue implementation to prevent the prop window from setting to default if something else still needs it set to 0.

JASS:
library UnitPropWindow requires optional UnitIndexer, optional AIDS //Needs 1
    
    struct UnitPropWindow extends array
        
        private integer n
        
        method lock takes nothing returns nothing
            if this.n == 0 then
                static if LIBRARY_AIDS then
                    call AIDS_AddLock(this)
                    call SetUnitPropWindow(GetIndexUnit(this), 0)
                else
                    call UnitIndex(this).lock()
                    call SetUnitPropWindow(GetUnitById(this), 0)
                endif
            endif
            set this.n = this.n + 1
        endmethod
        
        method unlock takes nothing returns nothing
            set this.n = this.n - 1
            if this.n == 0 then
                static if LIBRARY_AIDS then
                    call AIDS_RemoveLock(this)
                    call SetUnitPropWindow(GetIndexUnit(this), GetUnitDefaultPropWindow(GetIndexUnit(this)))
                else
                    call UnitIndex(this).unlock()
                    call SetUnitPropWindow(GetUnitById(this), GetUnitDefaultPropWindow(GetUnitById(this)))
                endif
            endif
        endmethod
        
    endstruct
    
    function LockUnitPropWindow takes unit u returns nothing
        call UnitPropWindow(GetUnitUserData(u)).lock()
    endfunction
    
    function UnlockUnitPropWindow takes unit u returns nothing
        call UnitPropWindow(GetUnitUserData(u)).unlock()
    endfunction

endlibrary
 
Last edited:
Level 22
Joined
Nov 14, 2008
Messages
3,256
There is TimedHandles by TriggerHappy and TimedEffects by Moyack, however I wanted a super-super light version that didn't have any more code than necessary (vJass struct safety is not required here, for example) so I made this.

Fair enough, although this is just a "part" of TH's TH. TH got this great idea yet the execution can be discussed. Maybe wanna macro this to make it support more handle-types like TH?
 
Level 6
Joined
Jun 20, 2011
Messages
249
What not use UnitApplyTimedLife?

Doesn't remove the dummy from the game, and units "decaying" cause leaks and doesn't clear the index.

Also i just wrote this and i find it very useful
JASS:
library AngleTools
    
//Turns any real into a radian between 0 and bj_PI*2
    function R2A takes real a returns real
        loop
            exitwhen a>=0
            set a=a+bj_PI*2
        endloop
        loop
            exitwhen a<=bj_PI*2
            set a=a-bj_PI*2
        endloop
        return a
    endfunction

//Determines if "a" gets closer to "b" if it increases.
//Tells you which direction to turn to if you want to spin an angle
    function IsAngleGreater takes real a, real b returns boolean
        return Sin(b-a)>=0
    endfunction

//Returns if the angle "b" is inside a cone pointing towards angle "a" of overture "o"
//if Cone(GetUnitFacing(unit),Atan2(GetUnitY(target)-GetUnitY(unit),GetUnitX(target)-GetUnitX(unit)),bj_PI/12) then
    function Cone takes real a, real b, real o returns boolean
        return Cos(b-a)>=Cos(o)
    endfunction

endlibrary
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
JASS:
function IsPointOnLine takes real slope, real yIntercept, real x, real y returns boolean
    return y == slope * x + yIntercept
endfunction

This is pretty useless comparing two floating point numbers with some math between... even if the point is mathematically on a line this function may still return false.

There are constants referred to as "epsilon" which are small decimal numbers that outline possible error when dealing with certain types of floating point math, depending on the precision (single, double, etc).
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
== in jass already includes en epsilon, i don't remember its value, but i'm sure of that, ofc it should be still not enough high.
Now, >= and <= don't use an epsilon.

EDIT : Just tested, the epsilon value is 0.001

Bribe said:
What not use UnitApplyTimedLife?

Dirac said:
Doesn't remove the dummy from the game, and units "decaying" cause leaks and doesn't clear the index.

You can remove an unit using the right buffId, like the one used for reanimate dead units.

Now, i don't see how it would leak, and are you saying that indexers don't handle these kind of units ?!

EDIT : These kind of units are handled by unit indexers, now maybe it was directly related to Bribe's code.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Isn't the epsilon value 0.000000001?

No it isn't, i said i've tested it.
== has a special handling.

Now i've already found that "only" 9 numbers under the digit are taken in consideration for timer periods, and i suppose it's the same for reals in general.
So it should be the case for <= and >=, but well that's not so easy coz reals are float.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
This is a joke, really even 0.1 s is enough for the elapsed game time.
"Maybe" is not a concrete case.

And anyway this "high" epsilon is only applied when you compare 2 reals with ==, not when the real is used, or even with the other operators > , < , >= , <=.
I have not tested with != btw
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
but 0.000000001 is supposed to be used when in need of extremely accurate reals

Are you talking about Warcraft specifically? From the amount of round-off error that is generated on floating point math I would assume that Warcraft doesn't even use single precision floats, it seems like maybe they are using half-precision. Anyways, your 10^-9 is close but I think that the value of epsilon for half precision is 10^-11, roughly speaking.

Single precision is 10^-24 or so, and double precision is 10^-50 or so.

If you're counting time, you could count integer ticks instead of the time itself, allowing you to do comparisons very easily. Integers are not afraid to be compared to one another for equality.

Either way you can't really assume any amount of error is good enough. Just run this loop and see how far the values get from their actual value:
JASS:
        local real N = 5.0 // Use normal numbers
        local real x = 1.0 / N
        local integer k = 0
        call BJDebugMsg("Original Fraction: 1/"+I2S(R2I(N)))
        loop
            exitwhen (k == 10)
            set x = x * (N + 1.0) - 1.0
            set k = k + 1
        endloop
        call BJDebugMsg(R2S(x))

This basically says this:

(1 / n) = (1 / n) * (n + 1.0) - 1.0
________= (n / n) + (1 / n) - 1.0
________= 1 + (1 / n) - 1
= 1 / n

So the loop shouldn't change the value of the fraction at all. It should (in my example) still be 1/5, or 0.200 by the end of the loop.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Perhaps if you specify a certain amount of decimal places it takes it as a higher degree of floating-point.

Well that's strange. I just printed out a 9-decimal number and a 4-decimal number and the 9-decimal number (when printed) wasn't anywhere near what I had specified it as.

JASS:
        call BJDebugMsg("9-decimal number: "+R2S(0.0123000000))
        call BJDebugMsg("4-decimal number: "+R2S(0.0123))

Printed:

0.087 (way off)
0.012 (correct)

Anybody know what gives?

Well that maybe explains it. The first one was actually a 10-digit decimal. When I got rid of one of the zeroes it printed 0.012 like the other. Wow I didn't know you could overflow reals by just specifying tons of decimals... that's not very comforting. I guess its the same as filling an integer with too many digits though.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Old test :

JASS:
library TestPeriods initializer init 

globals 
    private constant real PERIOD1 = 0.000200090 
    private constant real PERIOD2 = 0.000200009 
    private constant real PERIOD3 = 0.000200000
    private constant real PERIOD4 = 0.0002
endglobals 

globals 
    private integer I1 = 0 
    private integer I2 = 0 
    private integer I3 = 0 
    private integer I4 = 0 
    private timer Tim1 = CreateTimer() 
    private timer Tim2 = CreateTimer() 
    private timer Tim3 = CreateTimer()
    private timer Tim4 = CreateTimer()
endglobals 

private function F1 takes nothing returns nothing 
    set I1 = I1+1 
endfunction 

private function F2 takes nothing returns nothing 
    set I2 = I2+1 
endfunction 

private function F3 takes nothing returns nothing 
    set I3 = I3+1 
endfunction 

private function F4 takes nothing returns nothing 
    set I4 = I4+1 
endfunction


private function init takes nothing returns nothing 
    call TimerStart(Tim1,PERIOD1,true,function F1) 
    call TimerStart(Tim2,PERIOD2,true,function F2) 
    call TimerStart(Tim3,PERIOD3,true,function F3)
    call TimerStart(Tim4,PERIOD4,true,function F4)
    
    call TriggerSleepAction(11.0) // or more time if it's needed
    call BJDebugMsg(I2S(I1)) 
    call BJDebugMsg(I2S(I2)) 
    call BJDebugMsg(I2S(I3))
    call BJDebugMsg(I2S(I4))
endfunction 

endlibrary

If i remember correctly it was the same integer displayed 3 times, meaning the timers have the same period -> "only" 8 numbers under the digit are taken in consideration for period timers ?!
Or it could be something else, i really don't own the subject.

EDIT : I've just remembered that the lowest timer period is 0.0001, so i've changed the value of constants, a new test it needed.
I've also added one more timer.
 
Last edited:
Top