• 🏆 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 17
Joined
Apr 27, 2008
Messages
2,455
Here is one for the lulz.

Again it's for pointing a bug, even if "bug" is probably not the right word, the function IsUnitHeroId still doesn't work as it should :

JASS:
function IsFirstLetterOfRawcodeAnUpperCase takes integer rawcode returns boolean
   return IsHeroUnitId(rawcode)
endfunction
It will return true for all integers which begins with an upper-case letter (A to Z), no matter if the rawcode exists or not, or even if it's not an unit id but something else like an ability.
Such as :

'Hpal' // human paladin
'Fake' // doesn't exist but still will return true

Ofc it will act the same with integers in decimal, hexadecimal or every other form supported in jass (i don't think we have more, have we ?).
And so yes the IsHeroUnitId function is just probably an if which compares the integer with some range of integer values.

A link which explains this 'XXXX' integer format :
http://www.wc3c.net/showthread.php?t=107321&highlight=rawcode

Oh and also for those who don't know :
Making the first letter an upper-case for an unit rawcode will make it as an hero, and an unit for all other accepted ascii character.
It's handy sometimes with the newgen worleditor, you still must likely have to edit more fields but it speeds the transformation process unit <-> hero in the object editor.

But it's safe to use if you're sure that the rawcode is an unit one.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
PurgeAndFire's cinematic scripts have been linked in the first thread. Still pondering Troll-Brain's script as I like the find UnitId2String(unitid) != null but the library itself is only useful if the game was loaded.

I'm agree with you i did the library for completeness reason (the same library for all cases) but if you don't care about loaded game then you just should inline UnitId2String(unitid) != null.
A library requirement for a such easy inline seems over killed and lame.

So let's me remake it :

JASS:
library IsUnitTypeId requires Table

/* The point of this library is to bypass a nasty bug, but it's only present
with loaded game, if you don't care about them then you would just inline
UnitId2String(yourUnitTypeId) != null instead of using this library */
 
    private keyword s_dummy 
     
    private function GameLoaded takes nothing returns boolean  
        set s_dummy.is_game_loaded = true  
        call DestroyBoolExpr(Condition(function GameLoaded))  
        call DestroyTrigger(GetTriggeringTrigger())  
        return false  
    endfunction  
     
    // coz of vJass madness initializer module priority, even if i  hardly see the usefulness of registring a such event on a module  initializer ... 
    // but in some case this kind of initializer can be "handy", here we avoid an unecessary init 
  
    private module onInit  
  
        static method onInit takes nothing returns nothing  
            local trigger trig = CreateTrigger()  
            call TriggerRegisterGameEvent(trig,EVENT_GAME_LOADED)  
            call TriggerAddCondition(trig,Condition(function GameLoaded))  
            set trig = null  
            set s_dummy.tab = Table.create() 
        endmethod  
  
    endmodule  
  
    private struct s_dummy extends array // the extends array avoid some unneeded stuff 
        static Table tab 
        static boolean is_game_loaded = false 
        implement onInit  
    endstruct  
     
    function IsGameLoaded takes nothing returns boolean 
        return s_dummy.is_game_loaded 
    endfunction 
 
function IsUnitTypeId takes integer unitid returns boolean 
 
        local unit u 
        if IsGameLoaded() then 
 
            if s_dummy.tab.exists(unitid) then 
                return s_dummy.tab[unitid] != 0 
            endif 
 
            // IsUnitIdType is not enough , IsUnitHeroId only check if  the first letter of the id is an uppercase and not if the id is a valid  hero id 
            // so i've figured only this way, or you can use a third  party tool instead, like GMSI, but imho it's overskilled here 
            set u = CreateUnit(Player(13),unitid,0.,0.,0.) 
 
            if u != null then 
                call RemoveUnit(u) 
                set u = null 
                set s_dummy.tab[unitid] = 1 
                return true 
            else 
                set s_dummy.tab[unitid] = 0 
                return false 
            endif 
        endif
    return UnitId2String(unitid) != null // always returns null when the game was saved and loaded, and yes it's a bug 
      
endfunction 
 
endlibrary

Plus i've just realized (lol) that anyway the user still can use this library as it, even if he doesn't care about loaded games, just instead of an inlined function it will be a bit slower, but pretty negligible in 99,99 % of cases.
The only real drawback is the library Table requirement and this library by itself, but for Table it's pretty a standard, especially with the 256 hashtables limit.
And for this one you still could use a scope if you mind it :p
 
Last edited by a moderator:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Have you ever tried to give a no target order when an order event occurs ?
Or have you ever tried to forbid a no target order ?
Here is the solution :

JASS:
library StopOrder

globals 
    constant integer ORDER_STUN = 851973 
    /*  
    Special "stun" order, in case the unit had a previous no target order, like holdposition, it will keep it. 
    Plus you can filter it easily, since this order can be given only with a trigger action and have no string equivalent. 
    But be aware that this order also occurs when an unit is being paused or stunned. 
    In case of pause it will be a (not null) point order event, and for stun it will be a target event, where the target is the widget which has stunned the unit. 
    */
endglobals

private function interface func takes unit u returns nothing

globals
    private unit U = null
    private func array F
endglobals
 
function StopOrder takes unit u, func f returns nothing 
 
    call DisableTrigger(GetTriggeringTrigger()) // just in case you have a point order event registered
    call PauseUnit(u,true) // it will generate a point order event if the unit is not already stunned or paused (851973, no string equivalent)
    call IssueImmediateOrderById(u,ORDER_STUN)
    call PauseUnit(u,false)
    call EnableTrigger(GetTriggeringTrigger())
    set F[GetUnitId(u)] = f
    set u = null
 
endfunction

private function Act takes nothing returns boolean
    local unit u
    local integer i
    
    if GetIssuedOrderId() == ORDER_STUN then
        set u = GetOrderedUnit()
        set i = GetUnitId(u)
        call F[i].evaluate(u)
        set F[i] = 0
        set u = null
    endif
    return false
endfunction

private function init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(trig,function Act)
endfunction 
 
endlibrary
And an example of use :

JASS:
library Example initializer init requires StopOrder 
 
private function DisplayUnitName takes unit u returns nothing 
    call BJDebugMsg(GetUnitName(u) +" has been stopped") 
endfunction 
 
private function Act takes nothing returns boolean 
    // All no target orders will be stopped. 
    if GetIssuedOrderId() != ORDER_STUN then 
        call StopOrder(GetTriggerUnit(),DisplayUnitName) 
    endif 
    return false 
endfunction 
 
private function init takes nothing returns nothing 
    local trigger trig = CreateTrigger() 
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_ISSUED_ORDER) 
    call TriggerAddCondition(trig,function Act) 
     
endfunction 
 
endlibrary
Notes :

1) You can inline the method instead of using the library (no credits required)

3) You can use "0" for the field "func f" if you don't need it.
 
Last edited:
Not sure why I didn't post this yet. Ever tired of manually assigning all your rects to an array? Well, thanks to MasterofSickness, there is an easier way to do that.

I basically made a version of his script, but modified it so it would do it all automatically:
JASS:
library RectData requires optional Typecasting
    // Credits to MasterofSickness for this technique. 
    // This assigns all the rects to a unique index of a specified array. 
    // Just use it upon initialization. Example: 
/*    //! runtextmacro SetRectData( gg_rct_Region_000 , udg_RectArray )
*/  // "FIRST_RECT" is the first rect listed in the "Region Palette". The sorting doesn't matter. 
    // "ARRAY" is the array you want to assign data to. It doesn't have to start with udg_ or anything. 
    
    globals
        private hashtable hash  = InitHashtable()
        public integer COUNT    = 0
    endglobals
    
    static if (not LIBRARY_Typecasting) then
        function Int2Rect takes integer id returns rect
            call SaveFogStateHandle(hash,0,0,ConvertFogState(id))
            return LoadRectHandle(hash,0,0)
        endfunction
    endif
    
    //! textmacro SetRectData takes FIRST_RECT, ARRAY
        local integer i = GetHandleId($FIRST_RECT$)
        loop
            set $ARRAY$[RectData_COUNT] = Int2Rect(i)
            exitwhen $ARRAY$[RectData_COUNT]==null
            set RectData_COUNT = RectData_COUNT + 1
            set i = i + 1
        endloop
    //! endtextmacro
endlibrary

It optionally requires the typecasting library. It will work fine without it, though.

The first rect is considered the first rect listed in the region palette, whether or not you sorted by name or anything like that. It will do the rest by itself. You can retrieve the rect count via RectData_COUNT. Here is a sample of running the textmacro:

JASS:
scope Example initializer Init
    globals
        rect array RECTS
    endglobals
    private function Init takes nothing returns nothing
        //! runtextmacro SetRectData( "gg_rct_Region_000" , "RECTS" )
    endfunction
endscope
  • ExampleGUI
    • Events
      • Map Initialization
    • Conditions
    • Actions
      • Custom script: //! runtextmacro SetRectData("gg_rct_FunFun", "udg_MyRectArray")


If you still don't understand what I mean when I say "the first rect", then there is also this script. It has the same syntax, but you can input any rect, and it will assign the rest for you. Just note that it will assign them in a really weird order, but that shouldn't matter in most cases.
JASS:
library RectData requires optional Typecasting
    // Credits to MasterofSickness for this technique. 
    // This assigns all the rects in the "Region Palette" to a unique index of a specified array. 
    // Just use it upon initialization. Example: 
/*    //! runtextmacro SetRectData( gg_rct_Lulu_lala , udg_RectArray )
*/  // "ANY_RECT" is any rect defined in the "Region Palette". 
    // "ARRAY" is the array you want to assign data to. It doesn't have to start with udg_ or anything. 
    
    globals
        private hashtable hash  = InitHashtable()
        public integer COUNT    = 0
    endglobals
    
    static if (not LIBRARY_Typecasting) then
        function Int2Rect takes integer id returns rect
            call SaveFogStateHandle(hash,0,0,ConvertFogState(id))
            return LoadRectHandle(hash,0,0)
        endfunction
    endif
    
    //! textmacro SetRectData takes ANY_RECT, ARRAY
        local integer i = GetHandleId($ANY_RECT$)
        loop
            set $ARRAY$[RectData_COUNT] = Int2Rect(i)
            exitwhen $ARRAY$[RectData_COUNT]==null
            set RectData_COUNT = RectData_COUNT + 1
            set i = i - 1
        endloop
        set i = GetHandleId($ANY_RECT$)+1
        loop
            set $ARRAY$[RectData_COUNT] = Int2Rect(i)
            exitwhen $ARRAY$[RectData_COUNT]==null
            set RectData_COUNT = RectData_COUNT + 1
            set i = i + 1
        endloop
    //! endtextmacro
endlibrary


The indexes start from 0. If you need to know what index a certain rect is, then you can just look at the region palette and count.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Here is a way to catch ghost handles (destroyed but still have a reference at least)

JASS:
library CheckHandles

   globals
      private hashtable HashT = InitHashtable()
   endglobals

   function IsAgentValid takes agent whichAgent returns boolean
      return SaveAgentHandle(HashT,0,0,whichAgent)
   endfunction

   //! textmacro t_CheckHandles takes TYPE_NAME , TYPE

      function Is$TYPE_NAME$Valid takes $TYPE$ which$TYPE_NAME$ returns boolean
         return Save$TYPE_NAME$Handle(HashT,0,0,which$TYPE_NAME$)
      endfunction

    //! endtextmacro

   // example of textmacro usage
  //! runtextmacro t_CheckHandles("Group","group")

endlibrary

What's the point for that : i only see for "advanced" debugging.
It should be use only for a personal resource, not required for a public one, that's why i don't care about all the agent types, if you have the need do it yourself.

And i post it because it's not obvious for all.

A discussion about it :

http://www.thehelper.net/forums/showthread.php/146662-A-way-to-detect-if-an-handle-is-invalid
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Because sometimes you needs to keep an unit group during time, and because i hate splitting the code just for a ForForce call i made this.
There is no way than an UnitList contains a ghost unit (removed from the game)
I also really think that the natives Group/Add/Remove/Unit should returns a boolean, i've made such booleans for my methods.

I find it quite cool, but i'm too lazy for making a decent documentation and argue about the usefulness of it.
Plus i hate publics unit indexer, yes all of them, each one for different reasons, simply because if you control the whole process of unit creation/destruction by trigger, which is not so hard, at least in a custom map.
Then in this case it's pretty easy to make a very light one.

But i'm agree it's way too specific and can't be a public resource.

So if you like it, feel free to edit the code the way you want or even publish it with your modifications if you feel it (no credits required)

JASS:
library UnitList requires AutoIndex

globals
    group GROUP = CreateGroup()
endglobals

struct UnitList

    readonly integer Count
    readonly thistype First
    readonly thistype Last
    
    readonly thistype Prev
    readonly thistype Next
    readonly unit me
    
    method clear takes nothing returns nothing
        local thistype s = this.First
        local integer i
                
        loop
        exitwhen s == 0
            // evil trick in order to avoid a loop, see comments at the end of the struct if you want understand (or at least try xD, i'm quite confusing)
            set i = GetUnitId(s.me)
            set s.Next.Prev = s.Prev
            set s.Prev.Next = s.Next
            set thistype(i).instancesNumber = thistype(i).instancesNumber-1
            call SaveInteger(thistype.hashT,-i, LoadInteger(thistype.hashT,-i,-s) , LoadInteger(thistype.hashT,-i,thistype(i).instancesNumber) )
            call RemoveSavedInteger(thistype.hashT,-i,thistype(i).instancesNumber)
            call RemoveSavedInteger(thistype.hashT,-i,-s)
            call RemoveSavedInteger(thistype.hashT,this,s)
            set s.me = null
            call s.deallocate()          
        set s = s.Next
        endloop
        set this.Count = 0
        set this.First = 0
        set this.Last = 0
        call FlushChildHashtable(thistype.hashT,this)

    endmethod
    
    method destroy takes nothing returns nothing
        call this.clear()
        call this.deallocate()
    endmethod
    
    method addUnit takes unit u returns boolean
        local integer index = GetUnitId(u)
    
        if index == 0 or LoadInteger(thistype.hashT,this,index) != 0 then // the unit is not valid or already inside the UnitList
            return false
        endif
        
        if this.Count == 0 then // the UnitList was empty
            set this.First = thistype.allocate()
            set this.Last = this.First
            set this.First.Prev = 0
        else // the UnitList contains one unit or more
            set this.Last.Next = thistype.allocate()
            set this.Last.Next.Prev = this.Last
            set this.Last = this.Last.Next
        endif
        set this.Last.Next = 0 // for recycled instances
        set this.Last.me = u // link the unit to the new member
        set this.Last.whichList = this // used internally
        call SaveInteger(thistype.hashT,this,index,integer(this.Last)) // link the UnitList and the unit
        set this.Count = this.Count+1
        
        // For each unit we need to store all their instances, in order to remove them when the unit is removed
        // I use "-" in order to use only one hashtable without conflicts and still keep it efficient
        call SaveInteger(thistype.hashT,-index,thistype(index).instancesNumber,this.Last)
        call SaveInteger(thistype.hashT,-index,-this.Last,thistype(index).instancesNumber) // avoid a loop when the instance is destroyed
        set thistype(index).instancesNumber = thistype(index).instancesNumber +1
        
        return true
        
    endmethod
    
    method removeUnit takes unit u returns boolean
        local integer i = GetUnitId(u)
        local thistype x = thistype(LoadInteger(thistype.hashT,this,i))
    
        if x == 0 then // the unit wasn't inside the group
            return false
        endif
        set this.Count = this.Count-1
        if x == this.First then
            set this.First = this.First.Next
            
        endif
        if x == this.Last then
            set this.Last = this.Last.Prev
        endif
        set x.Next.Prev = x.Prev
        set x.Prev.Next = x.Next
        set thistype(i).instancesNumber = thistype(i).instancesNumber-1
        call RemoveSavedInteger(thistype.hashT,this,i)
        call SaveInteger(thistype.hashT,-i, LoadInteger(thistype.hashT,-i,-x) , LoadInteger(thistype.hashT,-i,thistype(i).instancesNumber) )
        call RemoveSavedInteger(thistype.hashT,-i,thistype(i).instancesNumber)
        call RemoveSavedInteger(thistype.hashT,-i,-x)
        set x.me = null
        call x.deallocate()
        
        return true
    endmethod
    
    method isUnitInList takes unit u returns boolean
        return LoadInteger(thistype.hashT,this,GetUnitId(u)) != 0
    endmethod
    
    method addUnitList takes UnitList whichUnitList returns nothing
        set whichUnitList = whichUnitList.First
        loop
        exitwhen whichUnitList == 0
            call .addUnit(whichUnitList.me)
            set whichUnitList = whichUnitList.Next
        endloop
    endmethod
    
    static method wrap takes group g returns UnitList
        if not SaveGroupHandle(thistype.hashT,0,0,g) then // group invalid
            return 0
        endif
        set thistype.wrapList = thistype.allocate()
        call ForGroup(g,function thistype.WrapList)

        return thistype.wrapList
    endmethod
    
    
    private static method WrapList takes nothing returns nothing
        call thistype.wrapList.addUnit(GetEnumUnit())
    endmethod
    
    private static method OnRemove takes unit u returns nothing
        local integer index = GetUnitId(u)
        local integer i = thistype(index).instancesNumber
        local thistype x
        
        loop
        exitwhen i == 0
        set i = i-1

            set x = thistype(LoadInteger(thistype.hashT,-index,i))
            set x.me = null
            if x == x.whichList.First then
                set x.whichList.First = x.whichList.First.Next
            endif
            if x == x.whichList.Last then
                set x.whichList.Last = x.whichList.Last.Prev
            endif
            set x.Next.Prev = x.Prev
            set x.Prev.Next = x.Next
            set x.whichList.Count = x.whichList.Count-1
            call RemoveSavedInteger(thistype.hashT,x.whichList,index)
            call x.deallocate()
        
        endloop
        
        call FlushChildHashtable(thistype.hashT,index)
        set thistype(-index).instancesNumber = 0   
        
    endmethod
    
    private thistype whichList = 0
    private static hashtable hashT
    private static thistype wrapList = 0
    private integer instancesNumber
    
    private static method onInit takes nothing returns nothing
        set thistype.hashT = InitHashtable()
        call OnUnitDeindexed(OnRemove)
    endmethod
    
endstruct
// for hashtable (used internally)
// UnitList,GetUnitId(<unit>) -> which instance of UnitList
// -GetUnitId(<unit>),GetUnitId(<unit>).instancesNumber -> which instance of UnitList
// -GetUnitId(<unit>),-which instance of UnitList -> GetUnitId(<unit>).instancesNumber

endlibrary

If i remember correctly it was slower than a native group with less than 100 units, but becomes faster and faster with more units (don't remember with how many units it's starting to be faster) inside an UnitList (was less than 2 times slower in the worst case if i remember correctly), thought the point was not the speed but the neat use of it, and anyway in any real scenario it would be slower than a native group.

A discussion about it here :

http://www.thehelper.net/forums/showthread.php/146492-UnitList

(the code and benchmarks results are outdated, mostly coz i've got rid of this huge lame autodestroy thing)
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Colorize a string depending the characters inside it, useful for displaying save and load codes :

JASS:
library ColorString 

    globals 
        // define the hexadecimal colors you want
        constant string LOWER_CASE = "FF0000FF" 
        constant string CAPITAL_LETTER = "FF00FF00" 
        constant string NUMBER = "FFFF0000" 
        constant string OTHER = "FFFFFF00" 
    endglobals 
    
  
    function ColorString takes string source returns string 
    
     local integer i= StringLength(source) 
     local string dest= null 
     local string min 
     local string maj 
     local string s 

        loop 
    
        exitwhen i== 0 
    
        set i=i-1 
        
            set s= SubString(source,i,i+1) 
            set min= StringCase(s,false) 
            set maj= StringCase(s,true) 
        
            if s== min and s!= maj then 
                set dest = "|c"+LOWER_CASE+s+"|r" + dest 
            elseif s== maj and s!= min then 
                set dest = "|c"+CAPITAL_LETTER+s+"|r" + dest 
            elseif s== "0" or S2I(s) != 0 then 
                set dest = "|c"+NUMBER+s+"|r" + dest 
            else 
                set dest = "|c"+OTHER+s+"|r" + dest 
            endif 
            
        endloop 
    
    return dest 
    
    endfunction 

endlibrary
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
You should be passing the strings into the function rather than having them as globals.

The only useful case i have in mind if for displaying save and load code, so the user will easily make the difference between "I" "1" ,"l" and so one, so there is no need of new arguments, constant colors are enough.
Hmm or maybe if you care about Color blindness :p

Have you a concrete case where you would use different colors for the same game ?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
There is also another problem. Your function should take a BigInt, not a string ;p. Any save/load system not running on BigInt isn't worth supporting, lol.

Here is my colorizing stuff with spacing

b is a Base
i is a BigInt
h is just an integer
s is a string
c is character
JASS:
                set b = eb[de[this]]
                set i.base = b
                set h = SPACE_COUNT
                loop
                    set i = i.previous
                    exitwhen i.end
                    set c = b.char(i.digit)
                    if (h == 0) then
                        set h = SPACE_COUNT
                        set s = s + SPACE + ct[Char2Ascii(c)] + c
                    else
                        set s = s + ct[Char2Ascii(c)] + c
                    endif
                    set h = h - 1
                endloop

Also, I think your method of colorizing with the upper/lower/etc is better than mine ;D.


Also, your thing should be
if s == min and s == max then //special or number
if (s == "0" or S2I(s) != 0) then //number
else //special
endif
elseif s == min then //lower
else //upper
endif


And settings for a save/load system should be inside of the save/load system, not the coloring ;P. It just makes everything easier to work with. This is why you should take the stuff as parameters and not have globals =D.

edit
Here's my updated colorizer, lol
JASS:
//colorize
set b = eb[de[this]]
set i.base = b
set h = SPACE_COUNT
loop
	set i = i.previous
	exitwhen i.end
	set c = b.char(i.digit)
	if (h == 0) then
		set h = SPACE_COUNT
		set s = s + SPACE
	endif
	set l = StringCase(c, false) == c
	if (c == StringCase(c, true) and l) then
		if (c == "0" or S2I(c) != 0) then
			//number
			set s = s + NUM_COLOR + c
		else
			//special
			set s = s + SPEC_COLOR + c
		endif
	elseif (l) then
		//lower
		set s = s + LOWER_COLOR + c
	else
		//upper
		set s = s + UPPER_COLOR + c
	endif
	set h = h - 1
endloop
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Code optimization is good but here you have to admit it truly doesn't matter, my ifs makes more sense like that.
When it doesn't matter i prefer coding like this way (makes easier debugging, future edits, ...)

I still think the best option is global, not new arguments, coz i don't expect to be required, you should just inline it in the needed library, just like you did, with your optimizations.
I mean i don't to see where you would need different colors for the same game.
Again i don't care about credits for a such simple thing.
 
I used to use this script by Blade.dk a lot. Sadly, it runs on handle vars:
http://wc3jass.com/viewtopic.php?t=2010

It creates an effect on a widget every X seconds for Y duration. So I remade it to use TimerUtils. Feel free to edit it, because I kind of made it to suit my purposes. The difference between this and the other script is that mine will create the effect, and then start the timer. (whereas the other script will start the timer, and then creates the effect) The other script also has an option to destroy the effect, which I threw out since it isn't really that necessary to have. This library also allows you to destroy the effect loop early by calling instance.destroy().

Usage is simple and shown in the library.

JASS:
library EffectLoop requires TimerUtils

/*  call EffectLoop.create( string modelName, widget target, string attachmentPoint, real interval, real duration ) */
    // modelName        = The model of the effect to create.
    // target           = The widget to attach the effect to.
    // attachmentPoint  = The attachment point of the widget to attach the effect to.
    // interval         = How long it will take before the next effect is created for the loop.
    // duration         = The total time length that the effect looping will last.

    struct EffectLoop extends array
        private static integer iC = 0
        private static thistype recycle = 0
        private thistype recycleNext
        private string mn
        private widget tw
        private string apn
        private real dur
        private real int
        private timer tt
        private effect fx
        
        method destroy takes nothing returns nothing //incase the user wants to destroy it early
            call DestroyEffect(this.fx)
            call ReleaseTimer(this.tt)
            set this.recycleNext = thistype.recycle
            set thistype.recycle = this
        endmethod
        
        private static method effectLoop takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            set this.dur = this.dur - this.int
            if this.dur < 0 then
                call this.destroy()
            else
                call DestroyEffect(this.fx)
                set this.fx = AddSpecialEffectTarget(this.mn,this.tw,this.apn)
            endif
        endmethod
        
        static method create takes string modelName, widget targetWidget, string attachPointName, real interval, real duration returns thistype
            local thistype this
            if thistype.recycle==0 then
                set thistype.iC = thistype.iC + 1
                set this = thistype.iC
            else
                set this = thistype.recycle
                set thistype.recycle = thistype.recycle.recycleNext
            endif
            set this.mn   = modelName
            set this.tw   = targetWidget
            set this.apn  = attachPointName
            set this.dur  = duration
            set this.int  = interval
            set this.fx   = AddSpecialEffectTarget(modelName,targetWidget,attachPointName)
            set this.tt   = NewTimer()
            call SetTimerData(this.tt,this)
            call TimerStart(this.tt,interval,true,function thistype.effectLoop)
            return this
        endmethod
    endstruct
endlibrary
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
A simple function that adds commas to a number. Was asked to do this by someone and well, seemed useful when displaying numbers to players =).

JASS:
library AddCommas
function AddCommas takes integer n returns string
    local integer m = 0
    local string s = ""
    local boolean b = false
    if (n == 0) then
        return "0"
    elseif (n < 0) then
        set n = -n
        set b = true
    endif
    loop
        set m = n
        set n = n/1000
        set m = m-n*1000
        if (n > 0) then
            if (m < 10) then
                set s = ",00" + I2S(m) + s
            elseif (m < 100) then
                set s = ",0" + I2S(m) + s
            else
                set s = "," + I2S(m) + s
            endif
        elseif (b) then
            return "-" + I2S(m) + s
        else
            return I2S(m) + s
        endif
    endloop
    return ""
endfunction
endlibrary

Demo
JASS:
struct tester extends array
    private static method onInit takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-30000000))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-3000000))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-300000))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-30000))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-3000))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-300))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-30))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(-3))
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, AddCommas(0))
    endmethod
endstruct
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
I had the need to know if an issued order could interrupt the current order of an unit or not (like for example "defend" order doesn't interrupt current orders), i've found this solution.
Maybe it's not obvious for all, so i post this snippet.

To clarify it a bit :

When an unit receive a not interrupting order, the previous order is not stopped, the unit keeps it, but the previous order doesn't fire order events one more time.

Here is a concrete case where you would need to make a such difference between issued orders:

To keep the track of possible workers (which peons have currently a build order).
Indeed, when an unit receive a build order, it could take sometimes between the instant the order fire and the build is started (need to reach the building point, ...).
In a first approach i thought that i could retire of the possible builders each one received an other order, but there are orders which don't interrupt current ones.

I suppose it has other uses but to be honest that was the first time i needed it.

But alternatively you could simply inline GetIssuedOrderId() == GetUnitCurrentOrder(GetOrderedUnit()), when an order event fire, and then instead of a library requirement you have to learn the method which is not so hard anyway ^^

JASS:
library IsIssuedOrderInterrupting

globals
    private constant boolean SAFE_CHECK = true
endglobals

function IsIssuedOrderInterrupting takes nothing returns boolean
    static if SAFE_CHECK then
    
        local eventid evd = GetTriggerEventId()
        
        if evd==EVENT_UNIT_ISSUED_ORDER or evd==EVENT_UNIT_ISSUED_POINT_ORDER /*
     */ or evd==EVENT_UNIT_ISSUED_TARGET_ORDER or evd==EVENT_PLAYER_UNIT_ISSUED_ORDER /*
     */ or evd==EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER or evd==EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
            return GetIssuedOrderId() == GetUnitCurrentOrder(GetOrderedUnit())
        else
            debug call BJDebugMsg("IsIssuedOrderInterrupting : use this function with an order event only")
            return false
        endif
        
    else
    
        return GetIssuedOrderId() == GetUnitCurrentOrder(GetOrderedUnit())
    endif

    
endfunction

Note :

Obviously it won't work on a boolexpr register event, since you would have to use GetFilterUnit() instead of GetOrderedUnit().
Though, you could inline the method and use GetFilterUnit() instead.

But seriously just don't use these boolexpr, it's obvious that Blizzard didn't finished them, only few responses events work in them, also be warned that they act differently that in trigger conditions.

Last time i tested them they worked like a stack, where trigger conditions worked like a queue (order of registration -> order of code fired)
If you don't understand what i mean with that comment i could make some code sample (i'm just lazy to do it now, since i have to reboot on windows)

EDIT :

It just doesn't work, i was probably out of my mind when i did it, sorry for that ...
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
RectData approved to the first post.

Troll-Brain, I am unsure of the usage of StopOrder. Is it similar to AbortOrder from Rising_Dusks LastOrder? http://www.wc3c.net/showthread.php?t=104175

I like that method better as it's a lot more efficient... does your method do something that his doesn't?

AddCommas approved to the first post.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
It does less, but what it does is better made.
It's only useful if you want to stop a no-target order. Because it uses the special "stun" order (851973), it will keep the previous no target order the unit had.
Let's imagine the unit has a "holdposition" order, and you want to force the unit to keep it.
If you use the StopOrder function it will keep it without the need to re-order the unit to use "holdposition".

Also as far i know the LastOrder library isn't able to forbid some no target orders like learning an ability.
I mean i've never used this library but the function AbortOrder will fail with this kind of order for sure (maybe there is an other way though).

Technically the function StopOrder can forbid any order, but it should be used only on a no target order event fire.
With other events you don't need the timer and also maybe not the pausing/unpausing (i don't remember this last one).

I could detect which order event fire and use the most appropriate method to stop the order but if it's a no-target order which fires it's pretty easy to inline the method, so i don't know if it's worth this overhead.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
What does the 0-second timer actually do?

Coz of a blizzard joke, the only orders which are not truly instant are the immediate orders (no target), funny isn't it ?

A 0-second timer is the lowest period possible which is equal to 0.0001 s

That's why pausing/order the unit to stop/unpausing can work with some orders but not all, indeed in this case the precedent order will still be proceed, but the "stop" order will be given before 0.0001 s but still after the precedent order.
Obviously it can't work with an order like learning an hero ability in the skill menu, since the ability will be already learned when the stop order will be given.

Or something like that, i'm just sure that you can't order a no target order on a no target order event fire.
I don't remember exactly if it works on other order events.
Let's me reboot on windows and i will edit my message in few minutes.

EDIT :

Wtf apparently the timer is totally useless, i have to test it with a newer patch though.
The only weird thing about no target order that i have noticed now is that on order event, some orders are not proceed (GetUnitCurrentOrder == 0), which is the case for learning hero ability, training unit, an probably more.

I will update this evening (less than 8 hours for now)
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Hmm, after many tests here are my statements :

- The Timer is not needed, it's strange i was pretty sure it was needed, i'm usually not a dude which is saying wrong statements, so i assume i did something wrong on my preview tests, i apologize that, i myself really hate wrong statements or even not enough accurate ones.

- no-target orders are not immediate, it takes about 0.01 s (little more) to be given, at least when the unit is paused.

- if you don't pause the unit, you won't be able to give a no-target order when any order event fire, using a timer(0) won't help you for orders like learning an hero ability, training an unit, ...

- pausing an unit and giving an order which have a point or target order are immediate things.
But maybe unpausing an unit is not immediate (can't really test it)

- there is two kind of no-target order, ones which are given at the same time when the order event fire (GetUnitCurrentOrder == GetIssuedOrderId) : "holdposition" , "stop", ...
And the others , the order is not effective when the order event fire (GetUnitCurrentOrder == 0) : training an unit, learning an hero ability in the skill menu, ...
For the firsts there is nothing special, but for the others, the given order will fire a second time when the unit is being unpaused (and this second time GetUnitCurrentOrder will be equal to GetIssuedOrderId).
There is nothing about it that can be done, but it's not really important anyway, just to be clear it's exactly the same if i used the "stop" order instead.
And the "stun" order will fire only one time (well, technically it will fire a point order event when the unit is paused, but that what happens by itself when you pause a no-paused/no-stunned unit.)

So, my other script IsIssuedOrderInterrupting is just a big shit which won't work properly.
It seems i was out of my mind when i've made it ...

Now that i've updated the script it requires an unit indexer but contrarily to LastOrder there is a simple way to forbid an order, keep the older no-target order if there was one, and can catch when the abort is done.
LastOrder is still valuable if you need more control, like re-ordering the previous no-target order.
In fact Rising_Dusk should edit his library and use the "stun" order instead of "stop".
Because "stun" order is easily filterable and keeping the older no-target order is always cool.

In fact i don't care if you inline the method instead of using the library, it's pretty easy especially if you don't have to do something just after the abort order.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Super simple library for testing stuff... was tired of writing it... I've written like this exact same thing hundreds of times ;D.

JASS:
library Tester
    function Print takes string msg returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 240, msg)
    endfunction
    module Test
        private static method runs takes nothing returns nothing
            call DestroyTimer(GetExpiredTimer())
            call thistype.run()
        endmethod
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), 0, false, function thistype.runs)
        endmethod
    endmodule
endlibrary

demo
JASS:
struct tester extends array
    private static method run takes nothing returns nothing
        call Print("Hello World")
    endmethod
    implement Test
endstruct
 
This isn't a snippet, but it has limited uses, so I decided I'd just post it here.

This is a string size library, which measures the width of a string. The only problems is that this is not accurate for all resolutions, so its use is a bit limited. However, it is still useful if used properly.

The string widths are taken from Bob666's library [link]. However, his uses several if/then/else comparisons, which can actually run into the op limit very fast. Ammorth's TextParse system is currently dead at the moment, and ToukoAozaki's one uses a hashtable instead of this one which uses ASCII for arrays, and it also does not support color codes for lengths.

It basically converts the text to plain text (removing the color codes and all that, essentially having it as pure text that would be displayed in-game), and then it measures each character size and returns the sum. The hex check only checks for color codes formed as AARRGGBB, and differs from vjeux's because it utilizes ASCII as well.

This one also is a bit less prone to hit the op limit, which is common when handling with strings. Anyway, here it is:
JASS:
library StringSize /* v2.0.0.0 
********************************************************************
*
*  This library can calculate the width of a string in pixels.
*  Useful for word wrapping in multiboards and texttags. 
*
*  The sizes might not be 100% accurate but are far more reliable
*  than using just StringLength().
*
*  Note that actual sizes may very depending on resolution.
*
*********************************************************************
*
*   */uses/*
*   
*       */ Ascii /*       [url]http://www.hiveworkshop.com/forums/jass-functions-413/snippet-ascii-190746/[/url]
*
*********************************************************************
*
*  function MeasureString takes string source returns real
*
*      - Measures the string and returns the calculated width.
*
*  function MeasureCharacter takes string char returns real
*
*      - Returns the width of an individual character.
*
*********************************************************************
*
*  struct StringSize
*
*      static method measure takes string source returns real
*
*      static method measureChar takes string char returns real
*
*********************************************************************
*
*  Credits
*
*    - Bob666 aka N-a-z-g-u-l for the character widths.
*    - Tukki for pointing out an error in the system.	
*
*********************************************************************/

    globals
        private real array size
        private string src
    endglobals
    
    private module StringSizeModule
        static method onInit takes nothing returns nothing
            set size[124]  =  3
            set size[39]   =  4
            set size[58]   =  4
            set size[59]   =  4
            set size[46]   =  4
            set size[44]   =  4
            set size[49]   =  5
            set size[105]  =  5
            set size[33]   =  5
            set size[108]  =  6
            set size[73]   =  6
            set size[106]  =  6
            set size[40]   =  6
            set size[91]   =  6
            set size[93]   =  6
            set size[123]  =  6
            set size[125]  =  6
            set size[32]   =  7
            set size[34]   =  7
            set size[41]   =  7
            set size[74]   =  7
            set size[114]  =  8
            set size[102]  =  8
            set size[96]   =  8
            set size[116]  =  9
            set size[45]   =  9
            set size[92]   =  9
            set size[42]   =  9
            set size[70]   = 10
            set size[115]  = 11
            set size[47]   = 11
            set size[63]   = 11
            set size[69]   = 12
            set size[76]   = 12
            set size[55]   = 12
            set size[43]   = 12
            set size[61]   = 12
            set size[60]   = 12
            set size[62]   = 12
            set size[36]   = 12
            set size[97]   = 12
            set size[107]  = 13
            set size[84]   = 13
            set size[99]   = 13
            set size[83]   = 13
            set size[110]  = 13
            set size[122]  = 13
            set size[80]   = 13
            set size[51]   = 13
            set size[53]   = 13
            set size[95]   = 13
            set size[126]  = 13
            set size[94]   = 13
            set size[98]   = 14
            set size[66]   = 14
            set size[54]   = 14
            set size[118]  = 14
            set size[101]  = 14
            set size[120]  = 14
            set size[121]  = 14
            set size[50]   = 14
            set size[57]   = 14
            set size[104]  = 14
            set size[117]  = 14
            set size[111]  = 15
            set size[100]  = 15
            set size[48]   = 15
            set size[103]  = 15
            set size[56]   = 15
            set size[52]   = 15
            set size[113]  = 15
            set size[112]  = 15
            set size[115]  = 15
            set size[67]   = 16
            set size[82]   = 16
            set size[90]   = 16
            set size[86]   = 16
            set size[89]   = 16
            set size[68]   = 16
            set size[75]   = 16
            set size[85]   = 16
            set size[35]   = 16
            set size[78]   = 17
            set size[72]   = 17
            set size[37]   = 17
            set size[71]   = 18
            set size[88]   = 18
            set size[64]   = 18
            set size[65]   = 19
            set size[119]  = 20
            set size[79]   = 20
            set size[109]  = 21
            set size[81]   = 21
            set size[38]   = 21
            set size[77]   = 25
            set size[87]   = 26
        endmethod
    endmodule
    
    private function IsColorCode takes string s returns boolean 
    /* checks if the string has hexadecimal color coding */
        local integer l = StringLength(s)
        local integer a
        if l == 8 then
            loop
                exitwhen l == 0
                set a  = Char2Ascii(SubString(s, l-1, l))
                if (a > 64 and a < 71) or (a > 96 and a < 103) or (a > 47 and a < 58) then
                    set l = l - 1
                else
                    return false
                endif
            endloop
            return true
        endif
        return false
    endfunction
    
    private function RemoveTextFormatting takes nothing returns nothing
    /* removes the formatting on a text */
        local integer l = StringLength(src)
        local integer i = 0
        local string sub
        loop
            exitwhen i >= l
            set sub = SubString(src, i, i+1)
            if sub == "|" then
                set sub = SubString(src, i+1, i+2)
                if sub == "c" then
                    if IsColorCode(SubString(src, i+2, i+10)) then
                        set src = SubString(src, 0, i) + SubString(src, i+10, l)
                        set l = l - 10
                        set i = i - 1
                    endif
                elseif sub == "r" then
                    set src = SubString(src, 0, i) + SubString(src, i+2, l)
                    set l = l - 2
                    set i = i - 1
                endif
            endif
            set i = i + 1
        endloop
    endfunction
    
    private function ConvertToPlainText takes string source returns string
        set src = source
        call ForForce(bj_FORCE_PLAYER[0], function RemoveTextFormatting) 
        return src
    endfunction

    struct StringSize extends array
        static method measureChar takes string char returns real
            return size[Char2Ascii(char)]
        endmethod
        
        static method measure takes string s returns real
            local integer i = StringLength(s)
            local real result = 0
            if i == 0 then
                return 0
            elseif i == 1 then
                return size[Char2Ascii(s)]
            endif
            set s = ConvertToPlainText(s)
            set i = StringLength(s)
            loop
                exitwhen i == 0
                set result = result + size[Char2Ascii(SubString(s, i-1, i))]
                set i = i - 1
            endloop
            return result
        endmethod
        
        implement StringSizeModule
    endstruct

    function MeasureString takes string s returns real
        return StringSize.measure(s)
    endfunction

    function MeasureCharacter takes string s returns real
        return StringSize.measureChar(s)
    endfunction

endlibrary

EDIT: Whoops, I left a debug msg in there.
EDIT2: Switched to a manual trigger evaluation because jasshelper was duplicating the code.
EDIT3: 8/16/12 updated.
 
Last edited:
EDIT: Please go to this link instead for an updated version: http://wc3jass.com/5016/system-wordwrap/new/#new

Another cool string snippet, but sadly limited to the same purposes. This will simulate word wrapping for strings. Line wrapping will split words, while this one will keep words in tact when line breaking.

This is, once again, used for annoying multiboards and centering strings and texttags and all that mumbo jumbo. It will take a string and width (the width is how long the string can be before it breaks to the next line) and it will split it into multiple lines.

Here is the code, it requires Ascii and StringSize:
JASS:
library LineBreak requires StringSize
//~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
//
//      Line Break               v. 1 . 0 . 1 . 0
//      ����������               ����������������
//
//          What is it?
//          �����������
//                -  This library can separate strings into several lines, based on each line's width.
//                -  It is not line wrapping, but rather word wrapping.    [url]http://en.wikipedia.org/wiki/Word_wrapping[/url]
//                -  Supports color codes.
//                -  Useful for multiboards, centering strings, and anything alike.
//
//          Requirements:
//          �������������
//                - Ascii       [url]http://www.hiveworkshop.com/forums/jass-functions-413/snippet-ascii-190746/[/url]
//                - StringSize  [url]http://www.hiveworkshop.com/forums/1886277-post379.html[/url]
//
//          Usage:
//          ������
/*              LineBreak.create( string source , real size, boolean preserveColors)                                        */
//
//                  Divides the "source" string into several lines. Each line has a maximum width of the "size" provided.
//                  "Preserve Colors" will activate a feature to make sure that colors transfer to the next line as well
//                  if they are supposed to.
//
/*              string array LineBreak.Line[]                                                                               */
/*              integer      LineBreak.Count                                                                                */
//
//                  LineBreak.Count returns how many lines were created. 
//                  LineBreak.Line[n] returns the nth line of the line breaks.
//
//          Credits:
//          ��������
//              - cleeezzz & tooltiperror for the idea.
//
//          Bugs:
//          �����
//              - If there is a single word that is longer than the "size" input, this system will fail. I might add support
//              for that later, but it may add a few more function calls and checks to the code.
//
//~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

    private module LBInit 
        static method onInit takes nothing returns nothing
            call TriggerAddAction(preserveExec,function thistype.preserveColor)
        endmethod
    endmodule

    struct LineBreak extends array 
        static trigger preserveExec = CreateTrigger()
        static string array Line
        static integer Count = 0
        
        private static method refresh takes nothing returns nothing
            loop
                exitwhen thistype.Count == 0
                set thistype.Count = thistype.Count - 1
                set thistype.Line[thistype.Count] = ""
            endloop
        endmethod  
        
        private static method preserveColor takes nothing returns nothing
            local integer i       = 0
            local integer i2      = 0
            local integer l       = 0
            local string temp     = ""
            local string hex      = ""
            local boolean carryOn = false
            loop
                exitwhen i == thistype.Count
                set carryOn = false
                set hex = ""
                set i2  = 0
                set l   = StringLength(thistype.Line[i])
                loop
                    exitwhen i2 == l 
                    set temp = SubString(thistype.Line[i],i2,i2+1)
                    if temp == "|" then
                        set temp = SubString(thistype.Line[i],i2+1,i2+2)
                        if temp == "c" then
                            if IsStringHexCC(SubString(thistype.Line[i],i2+2,i2+10)) then
                                set hex = SubString(thistype.Line[i],i2,i2+10)
                                set carryOn = true
                            endif
                        elseif temp == "r" then
                            set carryOn = false
                        endif
                    endif
                    set i2 = i2 + 1
                endloop
                set i = i + 1
                if carryOn then
                    set thistype.Line[i] = hex+thistype.Line[i] 
                endif
            endloop
        endmethod
        
        static method create takes string source, real size, boolean preserveColors returns thistype
            local integer l  = StringLength(source)
            local integer i  = 0
            local integer i2 = 0 
            local string s   = ""
            
            local string word
            local real result   = 0
            local real ssize    = 0
            
            call thistype.refresh()
            loop
                exitwhen i == l
                set s = SubString(source,i,i+1)
                if s == " " or i == (l-1) then
                    set word = SubString(source,i2,i)+s
                    set ssize = StringSize.measure(word)
                    set result = result + ssize
                    if result <= size then
                        set thistype.Line[thistype.Count] = thistype.Line[thistype.Count]+word
                    else
                        set thistype.Count = thistype.Count + 1
                        set thistype.Line[thistype.Count] = word
                        set result = ssize
                    endif
                    set i2 = i + 1
                endif
                set i = i + 1
            endloop
            set thistype.Count = thistype.Count + 1
            if preserveColors then
                call TriggerExecute(preserveExec)
            endif
            return 0
        endmethod
        
        implement LBInit
    endstruct
endlibrary

Here is a sample of the usage:
JASS:
scope Testing initializer Init
    
    private function esc takes nothing returns nothing
        local string text = "Hello, my name is Joe. I work in a button factory. One day, my boss said to me, \"Hiya Joe! Are you busy?\" "+/*
        */" I said, \"No\"."    //our original text
        local integer i = 0
        call LineBreak.create(text,450,true) //We will break it into lines, 450 maximum pixels per line
        loop
            exitwhen i == LineBreak.Count    //This is how many lines there are
            call BJDebugMsg(LineBreak.Line[i])  //This is how you read a line
            set i = i + 1
        endloop
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t,function esc)
    endfunction
endscope

It is a bit hard to explain, so take a look at the screenshots.

This is that sample above, with a width of 150:
attachment.php

This is that sample above, but with a width of 450:
attachment.php


For color preservation, it is basically an option to preserve color codes when skipping to the next line. If it is set to "false", then it will not preserve the colors, boosting performance, but ruining how it looks. If you have something like this:
|cff00FF00Hello World! It is a
beautiful day!|r

It should color the entire text. However, without color preservation, it will only color the top line. It should be fine to disable this if you are only color coding individual words, or if you aren't using color codes, but otherwise it should be enabled.

Here is an example with color preservation:
attachment.php


Without it, it would look like this:
attachment.php


EDIT: JassHelper created a prototype func for no reason. I fixed that by just manually creating the .execute().
 

Attachments

  • lineBreak00.png
    lineBreak00.png
    56.8 KB · Views: 1,408
  • lineBreak01.png
    lineBreak01.png
    38.8 KB · Views: 1,411
  • LineBreak02.png
    LineBreak02.png
    36.8 KB · Views: 1,470
  • lineBreak03.png
    lineBreak03.png
    62.4 KB · Views: 1,385
Last edited:
I'm not sure what you mean. Maybe I should rename the library to "Word Wrapping", because I was trying to cut out a crap load of annoying work for the users. I'm afraid that if I simplify it further, then it may end up being a lot more work for the users. :(

Now that I think about it, LineBreak doesn't make as much sense as the name, because that is just something it is used for. Whatever, I'll change it later.
 
Level 6
Joined
Feb 10, 2008
Messages
300
PurgeandFire111, try using this in your StringSize library: "|r|cff00aabbsomeshit|r"

It should result in an incorrect measuring, since you're skipping the next char when you detect those |'s.

It's easy to fix, just add set i = i - 1
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
To be honest i'm not sure it works with all patchs of wc3.
Because i remember that i said it didn't worked anymore, but i've tested it with the new patch and it works as intented.

You can find alternative ways here :
http://www.wc3c.net/showthread.php?t=104921
Read the whole thread, each solutions have their cons and pros.

JASS:
library IsGameOnline initializer init

// IT SEEMS TO WORK, AT LEAST FOR THE PATCH 1.26
// lan games are considered to be "on line"

// ofc you can't use the function IsGameOnline on map initialisation, at least after a time (0.), (0.01) would be better

globals

    private trigger Trig = CreateTrigger()
    private boolean IS_GAME_ONLINE = false
    //TRUE  = Game is online, no cheats possible.
    //FALSE = Game is offline, cheats possible.
    //If your map have some sort of secrets, you can cover them in offline games
endglobals

function IsGameOnline takes nothing returns boolean
    return IS_GAME_ONLINE
endfunction

private function Clean takes nothing returns nothing
    set IS_GAME_ONLINE = GetTriggerEvalCount(Trig) == 0
    call DestroyTrigger(Trig)
    set Trig = null
    call DestroyTimer(GetExpiredTimer())
endfunction

private function init takes nothing returns nothing
    local unit u = CreateUnit(Player(13),'hpea',0.,0.,0.)

    call TriggerRegisterUnitEvent(Trig,u,EVENT_UNIT_SELECTED)
    call SelectUnit(u,true)
    call RemoveUnit(u) // if the game is online (network local or battle.net) the unit won't have the time to be selected, it takes about 0.1 s
    call TimerStart(CreateTimer(),0.,false,function Clean)
    set u = null
endfunction

endlibrary
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Replays are only useful for watching pro games from Moon or Axslav. Sometimes Grubby.

Wrong answer, you have forgotten "for me".
Really, the real con of my script is that you can't use the function IsGameOnLine() on map initialisation but at least after a Time(0), not so much a burden.
Anyway i say there are other alternatives and give the link, i don't care that much if you don't use mine, but personnaly when i write something i want to it working in all situations.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I should run some tests, but something so substantial as a replay has to be in some way detectable.

Wouldn't GetPlayerName(GetLocalPlayer()) return the replay-viewer's name and not the name of the actual players? If so, you could just make a unique name that you ONLY use for viewing replays and, if GetPlayerName(GetLocalPlayer()) returns that string, you know you're watching a replay.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
I should run some tests, but something so substantial as a replay has to be in some way detectable.

Wouldn't GetPlayerName(GetLocalPlayer()) return the replay-viewer's name and not the name of the actual players? If so, you could just make a unique name that you ONLY use for viewing replays and, if GetPlayerName(GetLocalPlayer()) returns that string, you know you're watching a replay.

Hf, i already did some without any sucess.
But maybe you will have a better idea than me, who knows.

In a replay GetLocalPlayer() returns the first player slot which was used during the game (computers are no considered), change the view does not change the player returned by GetLocalPlayer().
For some reason it seems to affect only "visual" things, i means things which don't have an influence on the game.
For example if you use DisplayText with GetLocalPlayer, in a replay you will see the text message only if you select the first player which was used by a player.
But if you try to select an unit with GetLocalPlayer() == Player(1), the select event will fire in replay, even if the first player slot was Player(0).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
But if you try to select an unit with GetLocalPlayer() == Player(1), the select event will fire in replay, even if the first player slot was Player(0).

That's under the condition that Player(0) was not a human player?

Then for replays, here you go:

Nes:

I think ReloadGamecache or w/e is the best method hands down, and it doesn't need any timer or anything.

Troll:

Only if you don't care about replays (always returns "on line").
Else i'm totally agree that is the most elegant solution.

So by using your "IsGameOnline" script, you check if the game is on battle.net. If false, then if ReloadGameCache also returns false, you're watching a replay.

Found the solution?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I think you missed what I said.

Check the ReloadGameCachesFromDisk() behavior, if it is false (returns false for online and replay), then check your unit-selection detector. Let me just JASS it out:

JASS:
library IsGameOnline initializer init
 
globals
    private trigger t
    private boolean replay = not ReloadGameCachesFromDisk()
    private boolean online = false
    
    //If your map have some sort of secrets, you can cover them in offline games
endglobals
 
function IsGameOnline takes nothing returns boolean
    return online
endfunction
 
function IsGameReplay takes nothing returns boolean
    return replay
endfunction
 
private function Clean takes nothing returns nothing
    if GetTriggerEvalCount(t) == 0 then
        set online = true
        set replay = false
    endif
    
    call DestroyTimer(GetExpiredTimer())
    call DestroyTrigger(t)
    set t = null
endfunction
 
private function Init takes nothing returns nothing
    local unit u
    
    if replay then
        set u = CreateUnit(Player(13),'hpea',0.,0.,0.)
        
        set t = CreateTrigger()
        call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_SELECTED)
        call SelectUnit(u, true)
        call RemoveUnit(u) // if the game is online (network local or battle.net) the unit won't have the time to be selected, it takes about 0.1 s
        call TimerStart(CreateTimer(), 0, false, function Clean)
        set u = null
    endif
endfunction

endlibrary
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Sure, but that is silly, because depends the case you could use the function IsGameOnline at any time or after a time(0).
Just pick one or the other solution but not both.

Also i dunno if it could mess with gamecaches.
I would prefer this way :

http://www.wc3c.net/showpost.php?p=1111669&postcount=40

EDIT :

Didn't saw tre replay function.

Nah, that was i said ReloadGameCachesFromDisk() will return false with a real-not-replay multiplayer game also, so it's a no-no.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
ReloadGameCachesFromDisk() in the globals block shouldn't cause any harm to caches according to the comments.

But the SaveGame way by nature seems more safe.

Nah, that was i said ReloadGameCachesFromDisk() will return false with a real-not-replay multiplayer game also, so it's a no-no.

I still think you're misunderstanding my work, or maybe it's because I was editing it still. The way I have it set up is this:

1. if reload is false, replay is true,
2. if replay is true, it still might not be replay, so,
3. check if the unit-selection-event fires,
4. if the trigger eval count is 0, the game is online and not a replay.

See, so it detects both replay and battle.net.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
No, it will fail for a solo replay, it will be considered on-line.

I've tried to make tests because i know i've written that it returned "on-line", but Reload... or Save... would return true for a replay, as we are on local.
But no matter i did, with or without a new map, with or without the official editor, i always get a replay with is compatible for 1.24, not for 1.26 (i recently updated to 1.26)

So is it me or replays are no longer available for 1.26 ?!
It must be something wrong with me ...
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
You're saying the selection-event won't work for a replay? It will still have the 0.1 second delay?

I thought you said you're using it because it DOESN'T fail with replays. Look again with what I made:

It first checks if you can use gamecache. If it doesn't, then it's either a replay OR a saved game.

Then it checks if the selection-event has the 0.1 second delay. If it delays, then it's not a replay but an online game (that's why I set replay to false and online to true) but if it doesn't delay than it's a replay.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
You're saying the selection-event won't work for a replay? It will still have the 0.1 second delay?

No.

I'm positive it works as intended in a replay (my library and Diod's ones).

I'm also sure in a replay Save... and Reload... returns always the same boolean (solo or multi), but now it sounds weirds that it returns false, i would say "true" instead, but i can't test it ><

But let's imagine it's "false" like i said two years before.

Then in a replay-solo game :

ReloadGameCachesFromDisk() -> false
replay -> true
// do the sync test and the select event fire
online -> true // fail ...
replay -> false // fail ...
 
Top