MM Graphics Library - LeonardoTheMutant/SRB2-Murder-Mystery GitHub Wiki

Extended ASCII string renderer

Understanding the concept

This library includes custom functions for string rendering. They allow to draw strings with characters ranged from 0x80 to 0xFF (128 - 255 in decimal)(also known as Extended ASCII characters) on HUD. In vanilla SRB2 this range of characters is used by text coloring and transparency symbols.

To explain the "Extended ASCII" thing we have to get deep into the long and boring details...

Let's take this LUA-formatted string for an example:
"\xD0\x9F\xD1\x80\xD0\xB8\xD0\xB2\xD1\x96\xD1\x82 \xD1\x81\xD0\xB2\xD1\x96\xD1\x82\xD0\xB5!"
Standard HUD rendering functions in BLUA (this is what Lua 5.1 modification in SRB2 is officially called) will display this string as a mess because all of these characters marked by hexadecimal values are setting the color and the transparency of the text. If you take the Windows1251 encoding as a base and try to manually decode this string you will get something like this:
"Привіт світе!"
which means "Hello world!" in Ukrainian.

Let's take a look at this string now:
"Witaj \x9Cwiecie!"
This is supposed to be a Polish text saying "Hello world!". Windows1251 will not help here because it's designed for Cyrillic. Polish is a Latin-based language and like European languages, it has its own native symbols. Those symbols can be found in the Windows1250 code page. If you decode this string with that code page you will now get
Witaj świecie!
which looks readable now.

Now lets get into the "under the hood" details and how you can print such strings in SRB2 yourself with this library

Functions like V_DrawStrASCII(), V_DrawStrASCII_Center() and V_DrawStrASCII_Right() are designed to print strings with Extended ASCII symbols. Still, this is not enough to actually print strings like these because you're missing the font for the characters (more specifically, font graphics/patches). SRB2.pk3 contains graphics for the printable Standard ASCII characters only (0x19-0x7E range, 25-126 in decimal). You need the graphics for every single extended character you are going to use.

Let's imagine that we are making a Greek translation of Murder Mystery. We use Windows1253 as our base. We make the font graphics and name them like "1253C###" (### is a decimal number that corresponds to a character in that code page). Our prefix for Greek code page is "1253C" because we named files like that. Now we need to tell our Rendering Functions to use these files as graphics for Greek letters. The 4th argument in the V_DrawStrASCII() function specifies the character set prefix to use. In case of the Language Files for the LTM's Murder Mystery we simply need to include the "CHARSET" key to the LUA table and set its value to the Character Set prefix we got. The final result should look something like this:

    MM.AddLang("GR", {
        ["VERSION"] = "1.0", --don't forget about the compatibility!
        ["AUTHOR"] = "Sonic",
        ["NONASCII"] = true, --little outdated name but we set this to show that this language is not Latin-based
        ["CHARSET"] = "1253C", --this is the font graphic file prefix that we needed!
        ["MM"] = "Murder Mystery in Greek",
        ...
    })

And now when we kindly ask the V_DrawStrASCII() function (or the game type) to print the Delta character (Δ, 0xC4 in hex, 192 in decimal) the function will try to load the file by the name of "1253C192" and draw this graphic on the screen. Wow, it works now! Amazing isn't it?

In theory, you can create your character encoding standard that no one will understand. The possibilities are endless.

Things get a little bit complicated when it comes to the text coloring since characters like 0x80 or 0x85 are now reserved for your character encoding. V_DrawStrASCII(), like v.draw(), supports up to 16 text colors, but as color codes it uses characters ranged from 0x10 to 0x1F. Note that translucency is not supported in MM Strings (there are no free character ranges left to implement this). This is one of the cons of the MM String Renderers.

Let's say you want to use this cool string format with colors but you don't want to rewrite your 50+ Kilobytes of text to convert every color code manually. V_ConvertStringColor() exists just for this purpose - it converts SRB2 color codes in your string to MM format and as a return value it returns your modified string. You can do vise-versa with the V_ConvertStringColor2() function which converts MM color codes to vanilla SRB2 ones. Just remember to use MM strings with V_DrawStrASCII() and regular SRB2 strings with v.drawString() because they're not compatible with each other.

MM Text Color codes

List of Text Color codes for V_DrawStrASCII() function. Very similar to the vanilla SRB2 color codes

Decimal Hexadecimal Color Example
\16 \x10 White/Reset White
\17 \x11 Magenta[1] Magenta
\18 \x12 Yellow Yellow
\19 \x13 Green Green
\20 \x14 Blue Blue
\21 \x15 Red Red
\22 \x16 Gray Gray
\23 \x17 Orange Orange
\24 \x18 Cyan Cyan
\25 \x19 Purple Purple
\26 \x1A Aqua Aqua
\27 \x1B Peridot Peridot
\28 \x1C Azure Azure
\29 \x1D Brown Brown
\30 \x1E Pink Pink
\31 \x1F Black/Inverted Black

[1]: The text renderer in the MMHELP in-game command does not interpret this code correctly and does not color the text

Extended ASCII Character Sets

List of Extended ASCII tables and the available characters which are included in MM. You can also see the characters directly in the game by using the MMCHARSET [charset_prefix] console command (available only in developer builds of the add-on)

Windows1250 (1250C)

Based on Windows1250 code page. Central and Eastern European Latin-based languages such as Polish, Czech, Slovak, Hungarian, German

0 1 2 3 4 5 6 7 8 9 A B C D E F
8 Š Ś Ť Ž Ź
9 š ś ť ž ź
A Ł Ą Ş Ż
B ł ą ş Ľ ľ ż
C Ŕ Ä Ć Ç Č É Ę Ë Ě Í Î Ď
D Ń Ň Ó Ô Ő Ö Ř Ú Ű Ü Ý ß
E ŕ á â ă ä ć ç č é ę ë ě í î ď
F ń ň ó ô ő ö ř ú ű ü ý

Windows1251 (1251C)

Based on Windows1251 codepage. Cyrillic languages such as Russian, Ukrainian, Belarussian, Bulgarian, Macedonian

0 1 2 3 4 5 6 7 8 9 A B C D E F
8
9
A Ґ Ё Є Ї
B І і ґ ё є ї
C А Б В Г Д Е Ж З И Й К Л М Н О П
D Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я
E а б в г д е ж з и й к л м н о п
F р с т у ф х ц ч ш щ ъ ы ь э ю я

Windows1254 (1254C)

Based on Windows1254 codepage. Similar to Windows1252 but also supports Turkish letters

0 1 2 3 4 5 6 7 8 9 A B C D E F
8 Š
9 š Ÿ
A
B
C À Á Â Ã Ä Ç È É Ê Ë Ì Í Î Ï
D Ğ Ń Ň Ó Ô Ő Ö Ù Ú Û Ü İ Ş ß
E à á â ã ä ç è é ê ë ì í î ï
F ğ ñ ò ó ô ő ö ù ú û ü ı ş ÿ

Text Patches

As an alternative to the regular Patch files, MM HUD Library provides a way to draw bitmap graphics provided by the Text. This is developed primarily for the Language Files as these will most likely not be packed in a .pk3 archive (to additionally include all required graphics).

You must be very familliar how the TIME HUD label in vanilla SRB2 looks (if not you can open the game right now and see it)(this label can also be found in srb2.pk3 as STTTIME lump). All you need to know about the Text Patch is that it is a bitmap representation of an image where every charater is a pointer to SRB2 palette's color. This is what STTTIME patch looks like as Text Patch:

local BMP_TIME_INFO = { --offset values
	xoff = 0, --X
	yoff = 0  --Y
}
local BMP_TIME = { --actuall Patch data
	"IIIIII\x1FFFII\x1FFF\xFFII\x1FFF\xFFFF\xFFII\x1FFFIIIIII\x1F",
	"IIIIII\x1FFFII\x1FFF\xFFII\x1FFF\xFFFF\xFFII\x1FFFIIIIII\x1F",
	"\xFFFFII\x1F1F\x1FFFII\x1FFF\xFFIII\xFFFF\xFFIII\x1FFFII\x1F1F\x1F1F\x1F",
	"\xFFFFII\x1FFF\xFFFFII\x1FFF\xFFIIII\xFFIIII\x1FFFII\x1F",
	"\xFFFFII\x1FFF\xFFFFII\x1FFF\xFFIIIIIIIII\x1FFFIIIII\x1F",
	"\xFFII\x1FFF\xFFFFII\x1FFF\xFFII\x1FIII\x1FII\x1FFFIIIII\x1F",
	"\xFFII\x1FFF\xFFFFII\x1FFF\xFFII\x1F1FI\x1FFFII\x1FFFII\x1F1F\x1F1F",
	"\xFFII\x1FFF\xFFFFII\x1FFF\xFFII\x1FFF\x1FFF\xFFII\x1FFFII\x1F",
	"\xFFII\x1FFF\xFFFFII\x1FFF\xFFII\x1FFF\xFFFF\xFFII\x1FFFIIIIII\x1F",
	"\xFFII\x1FFF\xFFFFII\x1FFF\xFFII\x1FFF\xFFFF\xFFII\x1FFFIIIIII\x1F",
	"\xFF1F\x1F1F\xFFFF\xFF1F\x1F1F\xFFFF\x1F1F\x1FFF\xFFFF\xFF1F\x1F1F\xFF1F\x1F1F\x1F1F\x1F1F"
}

If you have eyes like Neo from the Matrix you can already see the image. But don't worry if you don't because this is actually a simplified/optimized version of the Text Patch where some of the Hexadecimal values are converted into ASCII Characters (the \x49 value here got converted into I) and Hexadecimal values are 16-bit (\x1F is 8-bit value, \x1F1F is 16-bit but is read as two characters). \xFF is a translucent pixel, \x49 (or I) points to color Yellow in SRB2's palette, \x1F is color black.

Getting the height of such Patch is as simple as getting the length of the table where it is stored. To get the width you can use V_TextPatchWidth(string[] textpatch) function

print(#BMP_TIME) --height, prints 11
print(V_TextPatchWidth(BMP_TIME)) --width, prints 31



To make a color swap you will need to use the V_TextPatch_SwapColor(string[] textpatch, int color1, int color2) function. Last two arguments are specifying the source and the target colors (color1 will be swapped with color2). The function returns a Text Patch with swapped color.

List of HUD Library functions

Name Return value Description
V_LoadPatch(drawer v, string patchName) nil Load the Patch into the memory. The cached patch can be accesed with MM.graphics[patchName].
V_UnloadPatch(string patchName) nil Unload the Patch from the memory. This function simpy changes the value of MM.graphics[patchName] to nil.
V_LoadCharset(drawer v, string name) nil Load the Character Set into the part of memory dedicated to storing Character Set patch graphics (MM.graphics.charset). name is the 5-sign (no more, no less) patch prefix. This function will try to load all character patches named as XXXXX### where XXXXX is name and ### is a 3-digit number from 0 to 255.
V_DrawStrASCII(drawer v, int x, int y, string charset, string str, [int videoflags, [bool small?]]) nil An alternative to v.drawString(). This function supports text rendering in different character encodings. charset specifies the character set to use (the character set has to be preloaded into the memory with V_loadCharset()). str is the string to render. If small? is set to true, it will draw the string in half of the normal size
Note: Each character from the Extended ASCII range (0x80-0xFF) should be typed as an escape code of the character (\x## format for Hexadecimal) in str string.
V_DrawStrASCII_Center(drawer v, int x, int y, string charset, string str, [int videoflags, [bool small?]]) nil Alternative to v.drawString() with the "center" alignment flag. Uses V_DrawStrASCII() to render the string relatively centered. The x coordinate is the center.
V_DrawStrASCII_Right(drawer v, int x, int y, string charset, string str, [int videoflags, [bool small?]]) nil Alternative to v.drawString() with the "right" alignment flag. Uses V_DrawStrASCII() to render the string right-aligned. The x coordinate is the right edge of the string.
V_StrWidthASCII(string str, [int videoflags, [bool small?]]) int Alternative to the v.stringWidth() but for V_DrawStrASCII() drawing function. Get the width of the string in pixels, arguments like videoflags or scale are also accounted in the final size.
V_ConvertStringColor(string str) string Converts the SRB2 text coloring symbols in str to MM format. Used for V_DrawStrASCII() function as it uses a different text coloring format.
V_ConvertStringColor2(string str) string Converts the MM text coloring symbols in str to SRB2 format. Used for v.drawString() to render the MM-formatted strings.
V_DrawTextPatch(drawer v, int x, int y, int xoff, int yoff, string[]* data, int flags) nil Draws a patch using the data in a text form at (x, y) coordinates (with the offset set by xoff and yoff). data is a table of strings, each table element represents a row and each symbol in data[row] corresponds to the color value from the SRB2's palette.
V_TextPatchWidth(string[] data) int Returns the width of the Text Patch in pixels.
V_TextPatch_SwapColor(string[] data, int sourceColor, int targetColor) string[] Swaps each sourceColor pixel to targetColor in a Patch. This function returns the Patch with the swapped colors.
V_GetTextPatchOffsetX(string language, string patchname) int Get the Patch's x offset coordinate from MM.text[language][patchname.."_INFO"] field (if exists).
V_GetTextPatchOffsetY(string language, string patchname) int Get the Patch's y offset coordinate from MM.text[language][patchname.."_INFO"] field (if exists).
V_ScrollTextPatch_Vertical(string[] patch, int offset, boolean side) string[] Scroll the Text Patch vertically by offset pixels. By default, the patch is scrolled from right to left, but if side is set to true, the patch is scrolled from left to right istead.
⚠️ **GitHub.com Fallback** ⚠️