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

[vJASS] chrord

Level 13
Joined
Nov 7, 2014
Messages
571
chrord (to be confused with chord) is just that, the two functions chr and ord.

If you're not familiar then:

chr(<character-ordinal>) returns the character with that ordinal (we are talking ASCII here folks, forget about unicode)

JASS:
    chr(65) => "A"
    chr(97) => "a"

and ord is the opposite (and kind of hard to implement in jass because nothing seems to be case sensitive, except 'Hpal', which doesn't help)
and of course there's no such native (or is it?)

JASS:
    ord("A") => 65
    ord("a") => 97

So the most intresting thing about chrord is the way the ord function is implemented:

JASS:
    function ord takes string s returns integer
        local integer o = GetLocalizedHotkey(s)

        // We care about lowercase letters
        if c[o] != s then
            return o + 32
        endif

        return o
    endfunction

Wtf is GetLocalizedHotkey you might ask? Good question:

native GetLocalizedHotkey takes string source returns integer

Normaliy it only works for strings like:
JASS:
"GAMEOVER_QUIT_GAME"
"GAMEOVER_OK"
"GAMEOVER_CONTINUE_OBSERVING"
etc.

so how did we "teach" it about "A" .. "Z", "a" .. "z", "0" .. "9", "!@#$%^&*()", etc?

We imported a file of course!

It's called war3mapMisc.txt and it's full path MUST be "war3mapMisc.txt" (without the quotes of course).
Normally when you import a file from the Import Manager it's full path will be set to "war3mapImported\<name-of-file>".

So in order to correctly import the "special" war3mapMisc.txt (see attachments) you have to delete the "war3mapImported\"
part from the full path, otherwise it won't be picked up by war3 and will be ignored.

If your map happens to have a custom war3mapMisc.txt already then simply copy the content from this war3mapMisc.txt to yours.

That's it! Well... almost

Because war3mapMisc.txt is parsed by some parser Blizzard wrote (some kind of an ini file parser)
there are some characters ord fails with, and they are:

JASS:
ord("\r") => returns 32 instead of 13
ord("\n") => returns 32 instead of 10
ord("[") => returns 32 instead of 91
ord(" ") =>returns 32 (which is correct) but by "mistake"

If you can't do without those then there's another module called Ascii that uses a different approach for ord
and of course you can do a linear search with this string (but DON'T!, it's going to be pretty slow):
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

So what good is ord anyway?

Well it's needed for base conversions think "hex2int", "Rawcode2Integer", base64 encoding/decoding, etc.

example:
JASS:
library Rawcode uses chrord

    function Rawcode2Integer takes string rawcode returns integer
        local integer result = 0

        set result = result + ord(SubString(rawcode, 0, 1)) * 0x01000000
        set result = result + ord(SubString(rawcode, 1, 2)) * 0x010000
        set result = result + ord(SubString(rawcode, 2, 3)) * 0x0100
        set result = result + ord(SubString(rawcode, 3, 4)) * 0x01

        return result
    endfunction

    function Integer2Rawcode takes integer rawcode returns string
        local string result = ""

        set result = chr(rawcode - rawcode / 0x100 * 0x100) + result
        set rawcode = rawcode / 0x100

        set result = chr(rawcode - rawcode / 0x100 * 0x100) + result
        set rawcode = rawcode / 0x100

        set result = chr(rawcode - rawcode / 0x100 * 0x100) + result
        set rawcode = rawcode / 0x100

        set result = chr(rawcode - rawcode / 0x100 * 0x100) + result
        set rawcode = rawcode / 0x100

        return result
    endfunction

endlibrary

So you might be thinking is chrord.ord faster than Ascii.Char2Ascii (what a silly name btw...)
and the answer is: they are "pretty much" the same speed =)

chrord
JASS:
library chrord // to be confused with chord

    globals
        private constant boolean SANITY_CHECK = true

        private string array c
    endglobals

    function chr takes integer i returns string
        return c[i]
    endfunction

    function ord takes string s returns integer
        local integer o = GetLocalizedHotkey(s)

        // We care about lowercase letters
        if c[o] != s then
            return o + 32
        endif

        return o
    endfunction

    private module asap
        private static method onInit takes nothing returns nothing
            set c[8]="\b"
            set c[9]="\t"
            set c[10]="\n"
            set c[12]="\f"
            set c[13]="\r"
            set c[32]=" "
            set c[33]="!"
            set c[34]="\""
            set c[35]="#"
            set c[36]="$"
            set c[37]="%"
            set c[38]="&"
            set c[39]="'"
            set c[40]="("
            set c[41]=")"
            set c[42]="*"
            set c[43]="+"
            set c[44]=","
            set c[45]="-"
            set c[46]="."
            set c[47]="/"
            set c[48]="0"
            set c[49]="1"
            set c[50]="2"
            set c[51]="3"
            set c[52]="4"
            set c[53]="5"
            set c[54]="6"
            set c[55]="7"
            set c[56]="8"
            set c[57]="9"
            set c[58]=":"
            set c[59]=";"
            set c[60]="<"
            set c[61]="="
            set c[62]=">"
            set c[63]="?"
            set c[64]="@"
            set c[65]="A"
            set c[66]="B"
            set c[67]="C"
            set c[68]="D"
            set c[69]="E"
            set c[70]="F"
            set c[71]="G"
            set c[72]="H"
            set c[73]="I"
            set c[74]="J"
            set c[75]="K"
            set c[76]="L"
            set c[77]="M"
            set c[78]="N"
            set c[79]="O"
            set c[80]="P"
            set c[81]="Q"
            set c[82]="R"
            set c[83]="S"
            set c[84]="T"
            set c[85]="U"
            set c[86]="V"
            set c[87]="W"
            set c[88]="X"
            set c[89]="Y"
            set c[90]="Z"
            set c[91]="["
            set c[92]="\\"
            set c[93]="]"
            set c[94]="^"
            set c[95]="_"
            set c[96]="`"
            set c[97]="a"
            set c[98]="b"
            set c[99]="c"
            set c[100]="d"
            set c[101]="e"
            set c[102]="f"
            set c[103]="g"
            set c[104]="h"
            set c[105]="i"
            set c[106]="j"
            set c[107]="k"
            set c[108]="l"
            set c[109]="m"
            set c[110]="n"
            set c[111]="o"
            set c[112]="p"
            set c[113]="q"
            set c[114]="r"
            set c[115]="s"
            set c[116]="t"
            set c[117]="u"
            set c[118]="v"
            set c[119]="w"
            set c[120]="x"
            set c[121]="y"
            set c[122]="z"
            set c[123]="{"
            set c[124]="|"
            set c[125]="}"
            set c[126]="~"
        endmethod
    endmodule

    private struct init_asap extends array
        implement asap

        private static method sanity_check takes nothing returns nothing
            local integer o
            local boolean has_bug = false

            set o = 32
            loop
                exitwhen o > 126

                // "[" is special for the parser, it starts a new "section", bummer...
                if c[o] != "[" then

                    if ord(c[o]) != o then
                        call BJDebugMsg("ord(\"" + c[o] + "\") !=" + I2S(o))
                        set has_bug = true
                        exitwhen true
                    endif

                endif

                set o = o + 1
            endloop

            if has_bug then
                call BJDebugMsg("|cffFF0000" + "ERROR [library chrord]: you've either forgot to import war3mapMisc.txt, specified a wrong path for it or chrord is just buggy")
            endif
        endmethod

static if DEBUG_MODE then
        private static method onInit takes nothing returns nothing
            call sanity_check()
        endmethod
endif

static if not DEBUG_MODE and SANITY_CHECK then
        private static method onInit takes nothing returns nothing
            call sanity_check()
        endmethod
endif
    endstruct

endlibrary


Edit: added chrord2 library

chrord2 uses a different method for implementing the ord function which is much faster (from my fps benchmarks =)) than Ascii.Char2Ascii! it uses the string table to store the values of the "ord table" and the GetStringId() to map the single character strings to integers.

chrord2 requires the StringTable library

JASS:
library chrord2 uses StringTable

static if DEBUG_MODE then
globals
    private constant boolean CHECK_ORD_TABLE_FOR_PROPER_INITIALIZATION = true
endglobals
endif

globals
    private string array c // chr table
endglobals
function chr takes integer i returns string
    return c[i]
endfunction

globals
    private integer p // offset in the string table for the first ascii character ("\0")
endglobals
function ord takes string s returns integer
    return GetStringId(s) - p
endfunction

struct chrord2 extends array
    static method init_tables takes nothing returns nothing
        local string s // = ""

        // find the end of the StringTable
        //
        // StringTable[0] == null
        set p = 1
        loop
            set s = StringTable
            // exitwhen s == null // crashes =)
            // exitwhen s == "" // doesn't work
            exitwhen not(s != null) // doesn't crash, works!
            set p = p + 1
        endloop

        // initialize the chr table and also implicitly initilize the ord table, i.e
        // the lines:
        //
        // set c[0] = "\x00"
        // set c[1] = "\x01"
        // ...
        //
        // are "equivalent" to:
        //
        // local integer q = p
        // set c[0] = "\x00"
        // set StringTable[q] = "\x00"
        // set q = q + 1
        // set c[1] = "\x01"
        // set StringTable[q] = "\x01"
        // set q = q + 1
        // ...
        //
        set c[0] = "\\x00"
        set c[1] = "\\x01"
        set c[2] = "\\x02"
        set c[3] = "\\x03"
        set c[4] = "\\x04"
        set c[5] = "\\x05"
        set c[6] = "\\x06"
        set c[7] = "\\x07"
        set c[8] = "\b"
        set c[9] = "\t"
        set c[10] = "\n"
        set c[11] = "\\x0B"
        set c[12] = "\f"
        set c[13] = "\r"
        set c[14] = "\\x0E"
        set c[15] = "\\x0F"
        set c[16] = "\\x10"
        set c[17] = "\\x11"
        set c[18] = "\\x12"
        set c[19] = "\\x13"
        set c[20] = "\\x14"
        set c[21] = "\\x15"
        set c[22] = "\\x16"
        set c[23] = "\\x17"
        set c[24] = "\\x18"
        set c[25] = "\\x19"
        set c[26] = "\\x1A"
        set c[27] = "\\x1B"
        set c[28] = "\\x1C"
        set c[29] = "\\x1D"
        set c[30] = "\\x1E"
        set c[31] = "\\x1F"
        set c[32] = " "
        set c[33] = "!"
        set c[34] = "\""
        set c[35] = "#"
        set c[36] = "$"
        set c[37] = "%"
        set c[38] = "&"
        set c[39] = "'"
        set c[40] = "("
        set c[41] = ")"
        set c[42] = "*"
        set c[43] = "+"
        set c[44] = ","
        set c[45] = "-"
        set c[46] = "."
        set c[47] = "/"
        set c[48] = "0"
        set c[49] = "1"
        set c[50] = "2"
        set c[51] = "3"
        set c[52] = "4"
        set c[53] = "5"
        set c[54] = "6"
        set c[55] = "7"
        set c[56] = "8"
        set c[57] = "9"
        set c[58] = ":"
        set c[59] = ";"
        set c[60] = "<"
        set c[61] = "="
        set c[62] = ">"
        set c[63] = "?"
        set c[64] = "@"
        set c[65] = "A"
        set c[66] = "B"
        set c[67] = "C"
        set c[68] = "D"
        set c[69] = "E"
        set c[70] = "F"
        set c[71] = "G"
        set c[72] = "H"
        set c[73] = "I"
        set c[74] = "J"
        set c[75] = "K"
        set c[76] = "L"
        set c[77] = "M"
        set c[78] = "N"
        set c[79] = "O"
        set c[80] = "P"
        set c[81] = "Q"
        set c[82] = "R"
        set c[83] = "S"
        set c[84] = "T"
        set c[85] = "U"
        set c[86] = "V"
        set c[87] = "W"
        set c[88] = "X"
        set c[89] = "Y"
        set c[90] = "Z"
        set c[91] = "["
        set c[92] = "\\"
        set c[93] = "]"
        set c[94] = "^"
        set c[95] = "_"
        set c[96] = "`"
        set c[97] = "a"
        set c[98] = "b"
        set c[99] = "c"
        set c[100] = "d"
        set c[101] = "e"
        set c[102] = "f"
        set c[103] = "g"
        set c[104] = "h"
        set c[105] = "i"
        set c[106] = "j"
        set c[107] = "k"
        set c[108] = "l"
        set c[109] = "m"
        set c[110] = "n"
        set c[111] = "o"
        set c[112] = "p"
        set c[113] = "q"
        set c[114] = "r"
        set c[115] = "s"
        set c[116] = "t"
        set c[117] = "u"
        set c[118] = "v"
        set c[119] = "w"
        set c[120] = "x"
        set c[121] = "y"
        set c[122] = "z"
        set c[123] = "{"
        set c[124] = "|"
        set c[125] = "}"
        set c[126] = "~"
        set c[127] = "\\x7F"
        set c[128] = "\\x80"
        set c[129] = "\\x81"
        set c[130] = "\\x82"
        set c[131] = "\\x83"
        set c[132] = "\\x84"
        set c[133] = "\\x85"
        set c[134] = "\\x86"
        set c[135] = "\\x87"
        set c[136] = "\\x88"
        set c[137] = "\\x89"
        set c[138] = "\\x8A"
        set c[139] = "\\x8B"
        set c[140] = "\\x8C"
        set c[141] = "\\x8D"
        set c[142] = "\\x8E"
        set c[143] = "\\x8F"
        set c[144] = "\\x90"
        set c[145] = "\\x91"
        set c[146] = "\\x92"
        set c[147] = "\\x93"
        set c[148] = "\\x94"
        set c[149] = "\\x95"
        set c[150] = "\\x96"
        set c[151] = "\\x97"
        set c[152] = "\\x98"
        set c[153] = "\\x99"
        set c[154] = "\\x9A"
        set c[155] = "\\x9B"
        set c[156] = "\\x9C"
        set c[157] = "\\x9D"
        set c[158] = "\\x9E"
        set c[159] = "\\x9F"
        set c[160] = "\\xA0"
        set c[161] = "\\xA1"
        set c[162] = "\\xA2"
        set c[163] = "\\xA3"
        set c[164] = "\\xA4"
        set c[165] = "\\xA5"
        set c[166] = "\\xA6"
        set c[167] = "\\xA7"
        set c[168] = "\\xA8"
        set c[169] = "\\xA9"
        set c[170] = "\\xAA"
        set c[171] = "\\xAB"
        set c[172] = "\\xAC"
        set c[173] = "\\xAD"
        set c[174] = "\\xAE"
        set c[175] = "\\xAF"
        set c[176] = "\\xB0"
        set c[177] = "\\xB1"
        set c[178] = "\\xB2"
        set c[179] = "\\xB3"
        set c[180] = "\\xB4"
        set c[181] = "\\xB5"
        set c[182] = "\\xB6"
        set c[183] = "\\xB7"
        set c[184] = "\\xB8"
        set c[185] = "\\xB9"
        set c[186] = "\\xBA"
        set c[187] = "\\xBB"
        set c[188] = "\\xBC"
        set c[189] = "\\xBD"
        set c[190] = "\\xBE"
        set c[191] = "\\xBF"
        set c[192] = "\\xC0"
        set c[193] = "\\xC1"
        set c[194] = "\\xC2"
        set c[195] = "\\xC3"
        set c[196] = "\\xC4"
        set c[197] = "\\xC5"
        set c[198] = "\\xC6"
        set c[199] = "\\xC7"
        set c[200] = "\\xC8"
        set c[201] = "\\xC9"
        set c[202] = "\\xCA"
        set c[203] = "\\xCB"
        set c[204] = "\\xCC"
        set c[205] = "\\xCD"
        set c[206] = "\\xCE"
        set c[207] = "\\xCF"
        set c[208] = "\\xD0"
        set c[209] = "\\xD1"
        set c[210] = "\\xD2"
        set c[211] = "\\xD3"
        set c[212] = "\\xD4"
        set c[213] = "\\xD5"
        set c[214] = "\\xD6"
        set c[215] = "\\xD7"
        set c[216] = "\\xD8"
        set c[217] = "\\xD9"
        set c[218] = "\\xDA"
        set c[219] = "\\xDB"
        set c[220] = "\\xDC"
        set c[221] = "\\xDD"
        set c[222] = "\\xDE"
        set c[223] = "\\xDF"
        set c[224] = "\\xE0"
        set c[225] = "\\xE1"
        set c[226] = "\\xE2"
        set c[227] = "\\xE3"
        set c[228] = "\\xE4"
        set c[229] = "\\xE5"
        set c[230] = "\\xE6"
        set c[231] = "\\xE7"
        set c[232] = "\\xE8"
        set c[233] = "\\xE9"
        set c[234] = "\\xEA"
        set c[235] = "\\xEB"
        set c[236] = "\\xEC"
        set c[237] = "\\xED"
        set c[238] = "\\xEE"
        set c[239] = "\\xEF"
        set c[240] = "\\xF0"
        set c[241] = "\\xF1"
        set c[242] = "\\xF2"
        set c[243] = "\\xF3"
        set c[244] = "\\xF4"
        set c[245] = "\\xF5"
        set c[246] = "\\xF6"
        set c[247] = "\\xF7"
        set c[248] = "\\xF8"
        set c[249] = "\\xF9"
        set c[250] = "\\xFA"
        set c[251] = "\\xFB"
        set c[252] = "\\xFC"
        set c[253] = "\\xFD"
        set c[254] = "\\xFE"
        set c[255] = "\\xFF"
    endmethod
endstruct

private module eager_init
    private static method onInit takes nothing returns nothing
static if DEBUG_MODE and CHECK_ORD_TABLE_FOR_PROPER_INITIALIZATION then
        local integer i
        local string s
        local integer o
        local boolean found_error = false
endif

        call chrord2.init_tables()

static if DEBUG_MODE and CHECK_ORD_TABLE_FOR_PROPER_INITIALIZATION then
        set i = 0
        loop
            exitwhen i >= 256

            set s = chr(i)
            set o = ord(s)
            if i != o then
                set found_error = true
                call BJDebugMsg("|cffFF0000[chrord2] error: "+ "the character '" + s + "' ord table entry has been set to " + I2S(o) + " expected " + I2S(i) + "|r")
                exitwhen true
            endif

            set i = i + 1
        endloop

        if found_error then
            call BJDebugMsg("|cffFF0000[chrord2] error: ord table was not initialized properly, an entry or entries of the ord table has been added to the string table before chrord2 was initialized; for example: check to see if you have global variables initilized with the entries of the ord table and initialize them after chrord2|r")
        endif
endif
    endmethod
endmodule

private struct dummy extends array
    implement eager_init
endstruct

endlibrary
 

Attachments

  • war3mapMisc.txt
    1.2 KB · Views: 132
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
It's more LOC (the more LOC the more time it takes jasshelper to parse..., has all those unneeded latin1 [ISO-8859-1] characters and ordinals) it uses StringHash which is only present in 1.24+, it exports silly function names but all in all I just don't like to use stuff not written by me if I can avoid it and if the export manager is something that someone wants to avoid then I guess that's fine too.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
This has extensive documentation, but sorry, this does not achieve anything above what is achieved by ASCII.

Yes 1.24+ may be valid concern, but it isnt really, we should only support the newest version of the game since it is the most stable, otherwise we would literally have to go and accept codes using the pre 1.24 return bugs.

I dont see anything bad on supporting Latin1 as well, with almost 0 cost.

The time difference to compile ASCII compared to this is probably unnoticable. Its not like ASCII is 2000 lines and this is 50.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I dont see anything bad on supporting Latin1 as well, with almost 0 cost.
Sure.

But the cost comes from the fact that a mapper might not want to use Latin-1,
he might want to use Latin-2, 3, ..., 16, or really any other characters (pile of poo, emoji, etc).

Now if he wants to use Ascii he would have to recompute the i[StringHash(p)/0x1F0748+0x40D]
table, which is not hard (thanks to LeP) but it's still quite a bit harder than opening a text file and writing: <character>=<code-point>
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I will go ahead and graveyard this since you did not reply in 4 months. If you feel I hurt you by graveyarding this, leave me a vm and I will resurrect it.
This is more of a tutorial than anything in my eyes anyways, and reply to argument "but it's still quite a bit harder than opening a text file and writing: <character>=<code-point> " is its easier, but first you have to find out how to enter that character in, and then you have to enter it, and then you have to reupload the file to your map. And the file will in the end probably eat about as much space as the ASCII itself.
 
Top