YSI:Text
From SA-MP Wiki
Introduction
The text and language system is spread over 3 files - YSI_text.own handles displaying and saving of text, YSI_languages.own handles loading of text and YSI_format.own provides new implementations of printf and format, as well as formatted display functions for the new text style. The basic idea is that instead of doing:
SendClientMessage(playerid, 0xFF0000AA, "Welcome to the server");
You do:
Text_Send(playerid, "WELCOME_MESSAGE");
You can then define "WELCOME_MESSAGE" as "Welcome to the server" in an English file, and "Welkom in de server" in a Dutch file for example, the system will then display the message corresponding to the player who you send the message to's language, rather than just having the server limited to a single language.
It also has in-line format functions so you can do:
Text_SendFormat(playerid, "WELCOME_MESSAGE", ReturnPlayerName(playerid), playerid);
WELCOME_MESSAGE = Welcome to the server %s (ID: %d)
And the player (assuming they were English) would see:
Welcome to the server Y_Less (ID: 0)
(or something similar).
The formatting is optimized for the multi-language system when sending a message to multiple people (e.g. using Text_SendToAllFormat) by saving strings collected for various languages and got from variable parameters.
It also has it's own formatting system where the style of text is also defined in a file and loaded, meaning you can change the appearance without touching any code at all.
If a text entry is not found for a users selected language the system will attempt to display the message to the player in the default language, if this is also not found the system will send a TEXT_NOT_FOUND error. To disable this error simply change YSI_TEXT_NOT_FOUND in the language files to have no value, DO NOT remove the entry as this will cause another error to be shown, one which can't easily be removed and one which there's no point removing as the first is easy to remove.
Features
- Have multiple languages on a server at once.
- Fast text lookup using Binary trees.
- Flexible system allowing simple addition of text.
- Expanded format system supporting more format type identifiers.
- /language command for dynamic language selection.
- External format system so appearance can be changed for all text without sifting through masses of code.
- Simple text hide system - simply blank the file entry to not make anything show.
Use
The use requires editing in a few places but makes things much better in the end.
Entries
Firstly you need to create the files which will store your text, these need the same filename and extensions related to the language contained within them. For the example we'll make example.EN for the English text and example.NL for the Dutch text. The identifiers MUST NOT change between files, they can be anything you like but if you translate them the system won't work properly. Both these files should be placed in the scriptfiles folder for now:
example.EN:
[join_messages] WELCOME_MESSAGE = Welcome to the server %s (ID: %d) EXIT_MESSAGE = Hope to see you again soon
example.NL:
[join_messages] WELCOME_MESSAGE = Welkom in de server %s (ID: %d) EXIT_MESSAGE = Wij hopen je hier snel weer te zien
Loading
Now we've done that we need to signal the system to load these files, in the default YSI new file in the init functions there are lines like:
Langs_AddLanguage("EN", "English"); Langs_AddFile("core", "YSI");
That tells the system to load the language files called core.XX (where XX is a language extension) in the subdirectory of scriptfiles called /YSI , this file contains all the text used by YSI itself, e.g. the property messages, login text etc. It also tells the system to load English messages only currently, informing it that English message files end in .EN and that the name of this language in it's own language is "English" (for the /languages command). We need to add the file with our text to the system and tell it to load Dutch text too (this will also load core.NL, but this is good). The example.XX files are in the scriptfiles folder, not a subdirectory of it so there's no need for the directory parameter:
Langs_AddLanguage("EN", "English"); Langs_AddLanguage("NL", "Nederlands"); Langs_AddFile("core", "YSI"); Langs_AddFile("example");
As explained in the INI documentation all data in an ini file requires an associated tag, in the examples above this is [join_messages] so we need to tell the system to load text under these tags, this is so text not used at all (e.g. in YSI libraries optionally excluded) isn't loaded to waste memory. OUTSIDE a function do:
Text_RegisterTag(join_messages);
This is just a macro which creates a public function with the name "Text_Tag_join_messages", forwards it and calls the real text save function so all required text is saved and text not required is discarded.
Displaying
You now have all the data loaded into the system in two languages (by default you can load up to 4 at once) so you can start using it. This is very simple and mostly already explained. To display a message to a player in their selected language do:
Text_Send(playerid, "TEXT_IDENTIFIER");
To send a message to all players, each one receiving it in their selected language do:
Text_SendToAll("TEXT_IDENTIFIER");
This is not the same as calling Text_Send repeatedly, there are optimizations in the code so that certain lookups and language text retrievals are only done once, a loop with Text_Send in would do these things repeatedly.
To send a message to a group of players (groups are defined by the YSI group system) do:
Text_SendToGroup(group, "TEXT_IDENTIFIER");
To send a message to a selected set of players not in a specific group use:
new Bit:players[PLAYER_BIT_ARRAY]; Bit_Set(players, 1, 1, PLAYER_BIT_ARRAY); Bit_Set(players, 5, 1, PLAYER_BIT_ARRAY); Bit_Set(players, 94, 1, PLAYER_BIT_ARRAY); Text_SendToPlayers(players, "TEXT_IDENTIFIER");
That will send the text to players 1, 5 and 94 with the optimizations mentioned above, however for a small number of players like 3 the difference in overhead of setting a load of bits and the text lookups are minimal, this is only useful for a larger number of players.
Formats
As mentioned you can change any of the functions mentioned above into format versions simply be appending "Format" to the end of the function. This turns the string in the file to a format string, as with WELCOME_MESSAGE in the example above:
Text_SendFormat(playerid, "WELCOME_MESSAGE", ReturnPlayerName(playerid), playerid);
(ReturnPlayerName is a generic function implemented in YSI).
YSI also adds a number of extra format parameters:
Format parameter | Type | Example | Explanation |
---|---|---|---|
%d, %i | Integer | 30 | Normal numbers. |
%f | Float | 30.0 | Floating point numbers. |
%h, %x | Hex | 1E | Normal number in hexadecimal radix (base 16). |
%b | Bool | 11110 | Normal number in binary radix (base 2). |
%o | Octal | 36 | Normal number in octal radix (base 8). |
%s | String | "30" | A string. |
%c | Character | 'a' | A character. |
%n | Command | "commands" | The real name of a command including renames |
%p | Prefix | th | "st", "nd", "rd" or "th" depending on the number (e.g. 1st) |
%u | Unsigned variable base | 1010 | An unsigned number to any specified radix (1010 is 30 in base 3) |
%t | Signed variable base | 1010 | A signed number to any specified radix (1010 is 30 in base 3) |
These format options and more are explained in greater detail here.
Exclusion
If you have text in a file and DON'T want it to show, simply remove the value - removing the entire line will give a "Text not found" error. The system is written so blank values will not be shown at all, it won't try show a blank line.
Appearance
As mentioned above the appearance of text is defined in a file, the name of the file is "<name>_format.YSI", e.g. the name of the format data for the YSI core data is core_format.YSI, for the example file it would be example_format.YSI. The style files uses the INI load system but is not a valid INI file so attempting to write to it from the script will destroy it, for this reason a new style system is being written using the XML system but it's not finished yet.
Sections
There are two sections to the file, the first is color section (or colour), this has a list of named colors and their hex values. The second is the data section and contains style data for the text entries.
color
[colors] ; Colour section format is: ; ; IDENTIFIER = HEX_COLOUR ; ; You can define any colour you like here for use below RED = 0xFF0000AA GREEN = 0x00FF00AA BLUE = 0x0000FFAA
data
[data] ; Data section format: ; ; name = TEXT_IDENTIFIER_TO_SET_DATA_FOR ; style = DISPLAY_STYLE ; colour = SEND_CLIENT_MESSAGE_COLOUR ; time = GAME_TEXT_TIME ; ; style (or type) is 0 for client message or game text type ; colour (or color) and time are optional depending on style ; (tehnically the whole lot are optional - all default to 0 ; style 0 and invisible black - always set color at least) ; name can also be excluded but the identifer must stay ; colour can be a hex number or an identifier from above ; ; SOME_TEXT_ENTRY ; type = 0 ; color = BLUE ; color = 0x0000FFAA ; ; Note: this is NOT grouped like in the language files ; but only the data for required text will be loaded as they ; will be the only ones which exist to have data attached ; ; You also don't need to space things out, I just like to WELCOME_MESSAGE color = GREEN ; color = 0x00FF00AA EXIT_MESSAGE time = 5000 style = 3
This will make the welcome message a green SendClientMessage style message and and exit message a GameText style 3 message appearing for 5 seconds.
API Functions
These functions can be called by the scripter to invoke actions of the text system.
YSI_text
Text_Text()
Sets up data in the script. Should be called from a script init function or is called automatically by the Script_ system.
Text_RegisterTag(tag)
Not strictly a function, it's a macro to tell the script to load a certain tag from a language file. Should be placed outside a function and tag should not have "" around it.
Text_Send(playerid, identifier[])
Sends the text defined by identifier[] to playerid in their selected language.
Text_SendToAll(identifier[])
Sends the text defined by identifier[] to all players, each in their own language.
Text_SendToGroup(group, identifier[])
Sends the text defined by identifier[] to all the player in a group (defined as a YSI group).
Text_SendToPlayers(Bit:players[], identifier[])
Sends the text defined by identifier[] to all the players specified by the bit array Bit:players[]. An example use (as above):
new Bit:players[PLAYER_BIT_ARRAY]; Bit_Set(players, 1, 1, PLAYER_BIT_ARRAY); Bit_Set(players, 5, 1, PLAYER_BIT_ARRAY); Bit_Set(players, 94, 1, PLAYER_BIT_ARRAY); Text_SendToPlayers(players, "TEXT_IDENTIFIER");
Text_SendFormat(playerid, identifier[], {Float,_}:...)
Sends the text defined by identifier[] to playerid in their selected language with identifier acting as a format specifier for the extra data.
Text_SendFormat(playerid, "WELCOME_MESSAGE", ReturnPlayerName(playerid), playerid);
Text_SendToAllFormat(identifier[], {Float,_}:...)
Sends the text defined by identifier[] to all players, each in their own language with identifier acting as a format specifier for the extra data.
Text_SendToGroupFormat(group, identifier[], {Float,_}:...)
Sends the text defined by identifier[] to all the player in a group (defined as a YSI group) with identifier acting as a format specifier for the extra data.
Text_SendToPlayersFormat(Bit:players[], identifier[], {Float,_}:...)
Sends the text defined by identifier[] to all the players specified by the bit array Bit:players[] with identifier acting as a format specifier for the extra data.
YSI_format
Format_SendFormattedText(Bit:players[], identifier[], {Float,_}:...)
This is the function which handles all the Text_SendFormat functions, the parameters are exactly the same as those for Text_SendToPlayersFormat, the other send functions are just wrappers which automatically generate the bit list of required players for this then call it, Text_SendToPlayersFormat in nothing more than a straight text replace with this function.
formatex(output[], len, const format[], {Float,_}:...)
This is a replacement for format which adds the extra YSI format identifiers to the format string. format[] is just a regular string, not an identifier as with the Text_Send functions and Format_SendFormattedText. The parameters are exactly the same as in format for easy conversion. An explanation of the format options can be found here.
printfex(const format[], {Float,_}:...)
This simply is to printf what formatex is to format. It formats a string with the additional YSI format identifiers and prints it to the server console. An explanation of the format options can be found here.
YSI_languages
Langs_Langs()
Sets up the language system. Called by the Script_ system of from the script init function if not used.
Language:Langs_AddLanguage(identifier[], name[])
Adds a language to the system, identifier[] is the two or three letter unique identifier for that language, used to identify the file with text in by extension. name[] is the name of the language in that language. It returns the id of the language. The language with ID 0 (i.e. the first one added to the system) is the server default language.
Langs_AddFile(filename[], path[] = "")
Adds a file name to the system, the text in the file will be loaded from as many specified language files as possible. The path[] variable is the subdirectory of scriptfiles the file is in however this can also be specified in the filename.
Langs_GetLanguageIdentifier(Language:languageID)
Gets the filename identifier of a specified language.
Langs_GetLanguageName(Language:languageID)
Gets the real name of a specified language.
Language:Langs_GetLanguageID(identifier[])
Gets the unique id of a language specified by filename identifier.
bool:Langs_IsValid(Language:languageID)
Checks if a specified language is valid.
Commands
YSI_text
-none-
YSI_format
-none-
YSI_languages
/language
This command, with no parameters, displays a list of all languages, along with their name in that language, filename extension (aka identifier) and their ID. With a parameter the language with the specified numerical identifier is applied as the player's default language.
If there are more than 8 languages to choose from typing the command multiple times will result in different pages of languages been shown, unlike the /commands command which displays pages based on a specified ID, as the parameter for this is used to select a language.
Compile Options
Compile options are defines which can be placed ABOVE the:
#include <YSI>
line to alter how the code is compiled, the compile options specific to the text system are:
YSI_text
MAX_TEXT
#define MAX_TEXT <num>
Defines the number of text strings loadable for each language. The default is 256.
YSI_format
-none-
YSI_languages
MAX_LANGUAGES
#define MAX_LANGUAGES <num>
Maximum number of languages loadable by the script at once. Default is 4 and due to technical restrictions the max is 32. By default YSI comes with core text for 8 languages.
MAX_LANGUAGE_FILES
This is the number of different files which can be loaded for each language. The default is four (two are all that's really needed - core and your own text (e.g. example as above)) but to change this value you need to edit the top of YSI_languages.own, there is currently no easy modification option.
Other Functions
These are functions internal in the user system and should not (and most can not) be called by the scripter.
YSI_text
Text_SetLangPointer(index, Language:languageID, pointer)
Sets the pointer to the text for a specific entry in a specific language.
Text_FindTextPointers(data[])
Finds the language pointers for a specific text entry.
Text_ResetLangPointers(Language:languageID)
Resets all the pointers for a language ready for reload.
Text_AddToBuffer(identifier[], text[])
This is the function called by all the register tag macros. If finds an empty slot in the array for the language currently being loaded, stores the text there and adds the language specific pointer to the array of pointers for that identifier.
Text_DataSave_colors(identifier[], text[])
Tag callback wrapper for people who can't spell.
Text_DataSave_colours(identifier[], text[])
Tag callback for loading color data from the styles ini file.
Text_SetColour(hash, value)
Checks to see if this color is already loaded and if not adds it to the array of colors.
Text_DataSave_data(identifier[], text[])
Tag callback for saving style data from the styles ini file.
Text_GetColour(hash)
Gets a color from the hash of it's name - color names are stored hashed to reduce space.
Text_SetStyle(identifier[], type, info)
Saves the style associated with a identifier to the identifier's array.
Text_AddText(identifier[], index)
Adds a previously unloaded identifier to the identifier array.
Text_ResetAll()
Called from the constructor to reset all arrays.
Text_NewLanguage(Language:languageID)
Resets input pointers to accept text from a new language.
Text_Parse()
Builds the binary tree of values from newly entered data. If the tree is already built from a previously loaded language new entries only existing in the new language are simply appended to the tree and this function does nothing.
Text_GetPlayerLanguage(playerid)
Gets the language of a player.
Text_GetText(identifier[], Language:languageID)
Finds the identifier from the binary tree then returns the string for the selected language.
Text_GetPlayerText(identifier[], playerid)
Gets the player's language and returns the text for that language.
Text_GetTextFromIndex(index, Language:languageID, identifier[])
Gets the actual text to do displayed given an index and language. If the index is invalid it displays an error, if the specified text doesn't exist for the given language it will try display the message in the default language, if it can't do that either it will display an error.
Text_GetErrorMessage(Language:languageID)
Gets the error message to display, first option is the file loaded error message in their language, then in the default language, then a string hard coded into the system which can't be changed.
Text_GetTextStyle(index)
Gets the display style for a given identifier index.
Text_GetTextColour(index)
Gets the SendClientMessage color for a given identifier index.
Text_GetTextTime(index)
Gets the GameText display time for a given identifier index. This is stored in the same array slot as the color but has a different default value.
YSI_format
Format_AddBin(output[], &maxlen, val, width, flags)
Wrapper for Format_AddNum to add a number as base 2.
Format_AddInt(output[], &maxlen, val, width, flags)
Wrapper for Format_AddNum to add a number as base 10.
Format_AddHex(output[], &maxlen, val, width, flags)
Wrapper for Format_AddNum to add a number as base 16.
Format_AddOct(output[], &maxlen, val, width, flags)
Wrapper for Format_AddNum to add a number as base 8.
Format_AddNum(output[], &maxlen, val, width, flags, base, sign)
Adds a number to the string being created. output[] is the string (passed by reference as default with arrays) on to which the data is appended. &maxlen is the size of output[] remaining to add data to. val is the number to add to the string. width is explained here. flags are what sort of padding is being used and it's location (again, as described in the Format Options page). base is the base to display the number to (2, 8, 10, 16 or custom). sign is whether or not to display the number as a signed number.
Format_AddFloat(output[], &maxlen, Float:fval, width, prec)
Adds a float to the output string. fval is obviously the value to add to the string. width and precision are explained in the Format Options page.
Format_AddString(output[], &maxlen, string[], width, prec)
Adds a string to the output.
Format_AddCommand(output[], &maxlen, str[], width, prec)
Adds a command to the output. YSI commands can be dynamically renamed so you cannot know what the command is called for putting in instructions to players, so this adds the currently correct name of a command to an output string by getting it from the command system.
Format_AddSuffix(output[], &maxlen, number)
Adds the correct suffix (st, nd, rd, th) for a given number, currently only supports English suffixes.
bool:Format_IsDigit(n)
Checks if n is a number character.
YSI_languages
Langs_IsActive(Language:languageID)
Checks the state of a language slot.
Langs_LanguageIdentifier(Language:languageID)
Gets the identifier of a given language internally.
Langs_NewInput(Language:languageID)
Tells the text system a new language is being loaded.
Langs_ResetAll()
Resets the language system and the text system.
Langs_SaveEnteredData()
Called after all the data for a language is loaded to tell the text system to sort it.
bool:Langs_LoadLanguage(Language:languageID)
Loads all the files for the given language slot.
bool:Langs_LoadFile(file)
Loads the specified file slot for all languages.
Langs_LoadAll()
Called from the constructor to signal the system to load all requested languages and files.
ycmd_language(playerid, params[], help)
Processes the /language command.