cJass 1.4.0.2

Contents

1  Usage

The easiest way to start using the cJass parser is to download and install distribution. Please note, that you will need Jass New Gen Pack for correct installation of cJass. During the installation process, all required changes in NewGen files will be made automatically. All that you need to do after installation is to run World Editor and start getting benefits from using cJass syntax.

2  Syntax innovations

2.1  Clarity

As you know, JASS2 scripting language was created by Blizzard Entertainment for usage in Warcraft III. Its syntax looks alo like Turing and is neither short nor laconic. You must use call keyword for function calls, set for variable setting and local to declare local variables. You also have to isolate global variables to stand-alone block:

globals integer f = 12 endglobals function test takes nothing returns nothing local integer i call DoNothing() set i = f + 3 endfunction

While using cJass, parser automatically determines the meaning of an expression depending on the context, so we can easily omit all these keywords to reach better code readability:

integer f = 12 function test takes nothing returns nothing integer i DoNothing() i = f + 3 endfunction

2.2  Variables declaration

In JASS2 all local variables have to be declared at the beginning of the function. While using cJass, you can declare local variables anywhere inside the function.

function onKill takes nothing returns nothing unit u = GetTriggerUnit() if (GetUnitTypeId(u) == 'hfoo') then ReviveUnit(u) else effect sfx = AddSpecialEffect("deatheffect.mdx", GetUnitX(u), GetUnitY(u)) Sleep(1) DestroyEffect(sfx) endif endfunction

The parser will move all local variable declarations to the beginning of the function. As in JASS2 variables can be initialized inside the declaration, cJass will also move the initialization if the variable is initialized with exact value:

function test takes nothing returns nothing DoNothing() integer i = 0 // declaration will be moved completels, location l = Location(0,0) // this variable will be initialized here endfunction

After compilation it will become:

function test takes nothing returns nothing local integer i = 0 local location l call DoNothing() set l = Location(0,0) endfunction

cJass syntax also lets you declare variables of the same type on the same line, separating them by commas. These variables can also be initialized here:

integer i = 7, j, k

2.3  Operators

cJass syntax introduces new operators to make the life of programmers easier and to increase code readability.

2.3.1  Increment and decrement

Increment ++ (increasing by 1) and decrement -- (decreasing by 1) operators are unary (they have only one operand). They can be used alone on the line or be part of complex expression:

function test takes nothing returns nothing integer a = 7 a++ // now, a = 8 integer b; b = 3 + a-- // now, a = 7, b = 11 b-- // now, b = 10 endfunction

which will become:

function test takes nothing returns nothing local integer a = 7 local integer b set a = a + 1 set b = 3 + a set a = a - 1 set b = b - 1 endfunction

These operators can be written either in prefix (when operator is before variable) or in postfix (when operator is after variable) notation. While using them alone, it doesn’t really matter, but they will behave differently as part of complex expression. Prefix operator changes the variable and then its new value is passed to the expression, postfix operator passes the value to the expression and then changes the variable.

function test takes nothing returns nothing integer a = 7 integer b = ++a // now, a = 8, b = 8 integer c = a + b-- // now, a = 8, b = 7, c = 16 endfunction

which will be compiled as the following:

function test takes nothing returns nothing local integer a = 7 local integer b local integer c set a = a + 1 set b = a set c = a + b set b = b - 1 endfunction

2.3.2  Assignment operators

cJass syntax implements different assignment operators, which can help shorten code. For example,

a = a + 2

can be shortened using the compound summation operator +=:

a += 2

Operator += adds the expression to the right to operand on the left. The same principle is used to form other compound operators: -=, *= and /=.

function test takes nothing returns nothing integer a = 3, b = 5, c = 4, d = 6 a += 7 // as a result, a = 10 b -= 4 // as a result, b = 1 c *= 5 // as a result, c = 20 d /= 3 // as a result, d = 2 endfunction

is compiled as:

function test takes nothing returns nothing local integer a = 3 local integer b = 5 local integer c = 4 local integer d = 6 set a = a + 7 set b = b - 4 set c = c * 5 set d = d / 3 endfunction

2.3.3  Logical operations

While using cJass syntax, all logical operators can be written in simplified way. You can either use it, or use default implementation - whatever you favor.

function test takes boolean a, boolean b returns nothing boolean c if (a && b) then // a and b c = !b // not b else c = b || a // b or a endif endfunction

2.4  Blocks

JASS2 syntax is rather verbose and uses block limiting using keywords. cJass introduces ligthweight block declaraions using curly brackets: now you can simply specify block name and surround its contents with curly brackets.

function test takes nothing returns nothing integer i = 0 loop { exitwhen i > 5 i++ } endfunction

Such notation can be used with all JASS2 blocks (loop, if, else and elseif) and vJass extension blocks (library, scope, struct, interface and module). While using short notation in if and elseif blocks, the following then keyord can be omited:

function test takes integer i returns integer if (i > 3) { return --i } else { BJDebugMsg("i <= 3") } return i endfunction

which in plain jass will be:

function test takes integer i returns integer if (i > 3) then set i = i - 1 return i else call BJDebugMsg("i <= 3") endif return i endfunction

Notice: this block syntax isn’t mandatory, so you can easily use default notation.

2.5  Loops

In cJass syntax, whilenot loop is introduced as an analog of widely used loop with pre-condition. The way of using this loop is identical to the default one: after whilenot operator the loop-ending condition is specified:

function test takes integer num, integer pow returns integer integer res = 1, i = 0 whilenot (i++ >= pow) { res *= num } return res endfunction

is same as the following:

function test takes integer num, integer pow returns integer local integer res = 1 local integer i = 0 loop exitwhen (i >= pow) set i = i + 1 set res = res * num endloop return res endfunction

There also is the form of this loop, that places the loop-ending condition afther the loop’s body:

function test takes integer num, integer pow returns integer integer res = 1, i = 0 do { res *= num } whilenot (i++ >= pow) return res endfunction

will be compiled to:

function test takes integer num, integer pow returns integer local integer res = 1 local integer i = 0 loop set res = res * num exitwhen (i >= pow) set i = i + 1 endloop return res endfunction

You can also use do keyword as a synonim to loop.

2.6  Simplified function declaration

For increased usability and code clarity, cJass syntax implements simplified notation of functions and vJass methods. In general, it looks like

nothing test(integer x) { BJDebugMsg(I2S(x)) }

Now let’s see, what all this means. At first, you have to indicate the return value type (in the above example it’s nothing because the function returns nothing), which is followed by function name and arguments (please note, that the arguments should be enclosed in round braces, if a function takes nothing, these braces can be empty), and in the end goes the function body, enclosed in curly brackets. For example, here is the above code after compilation:

function test takes integer x returns nothing call BJDebugMsg(I2S(x)) endfunction

This notation also applies to vJass methods, the parser will determine if the declaration should become function or method.

2.7  Endline manipulation

cJass gives you the ability to freely manipulate line endings in your code. These symbols allow you to split one declaration into maly lines or write more than one command on a single line:

nothing RandomUnit(integer pIndex) { Sleep(1) unit u; u = CreateUnit( Player(pIndex), \ GetRandomInt('H000', 'H009'),\ GetRandomReal(-100, 100), \ GetRandomReal(-100, 100), \ GetRandomReal(0, 360)) Sleep(2) RemoveUnit(u) }

The ; symbol will be replaced to the linebreak and symbol merges the lines.

3  Macrodefinitions

Defines are one of the key cJass benefits. Macro processing is done completely on code parsing (on map save), which allows you to achieve high code readability and flexibility without its overcomplication.

3.1  General

At first look, you can imagine the processing of defines as a simple replace of macro names with their values (as if you used a Search’n’Replace in your favorite text editor).

Keyword define is used when defining macros:

#define FOOTMAN = 'hfoo'

hereby FOOTMAN is macro’s name, which you can use later in your code, and 'hfoo' is the value, to which all references will be replaced. For example,

#define FOOTMAN = 'hfoo' nothing test() { CreateUnit(GetLocalPlayer(), FOOTMAN, 0, 0, 0) }

will compile to the following:

function test takes nothing returns nothing CreateUnit(GetLocalPlayer(), 'hfoo', 0, 0, 0) endfunction

You can write multiple defines using define and enclosing the block in curly brackets:

define { FOOTMAN = 'hfoo' MAGE = 'Hblm' }

You can use either define, or #define notation - it doesn’t matter.

For those, who prefer classic JASS2 syntax, there is another macro definition style:

define FOOTMAN = 'hfoo' MAGE = 'Hblm' enddefine

Usually, macro names contain only letters, numbers and underscores. However, what should you do if you need to have some kinds of special symbols in the define’s name? In such cases you can use inequality signs:

define <GetPlayableMapRect()> = bj_mapInitialPlayableArea

If you need a multiline define, then simply enclose its body in curly brackets:

define msg = { BJDebugMsg("one!") BJDebugMsg("two!") }

3.2  Visibility scopes

Macros can be defined as private inside of scopes, libraries, structs and modules using private keyword:

scope test { #define private FOOT = 'HBlm' }

In this case private defines will not conflict with other defines with same names in global scope of visibility:

#define msg = "X" nothing test () { BJDebugMsg("Global = " + msg) } library A { #define private msg = "A" nothing testA () { BJDebugMsg("Library A = " + msg) } } scope B { #define private msg = "B" nothing testB () { BJDebugMsg("Scope B = " + msg) } }

After compilation we get:

function test takes nothing returns nothing call BJDebugMsg("Global = " + "X") endfunction library A function testA takes nothing returns nothing call BJDebugMsg("Library A = " + "A") endfunction endlibrary scope B function testB takes nothing returns nothing call BJDebugMsg("Scope B = " + "B") endfunction endscope

3.3  setdef and undef directives

If you define a macro with the name which is already used by another macro (this doesn’t apply to private defines in nested elements), cJass will show an error and stop compilation. If you want to change the value of an existing macrodefinition, you should use setdef directive. To undo macro definitions, you can use the undef directive, after which the macro will be undefined.

nothing test1 () { #define msg = "text" BJDebugMsg(msg) // will print "text" #setdef msg = "other text" BJDebugMsg(msg) // will print "other text" #undef msg BJDebugMsg(msg) // won't be replaced }

3.4  Defines with arguments

To widen the range of possible macrodefinition usages, defines, which take arguments were implemented. A macro can take arbitary number of arguments, which can be used in the macro’s body similiar to how the function’s arguments are used. The arguments can be of any type? parser does no type checks, so you should call macros with care, always checking the value types.

define msg(playerid,text) = DisplayTextToPlayer(Player(playerid), text, 0, 0)

Attention! As parser doesn’t perform typechecks, all typesafety should be controlled by the coder.

3.5  Define overloading

You can define more than one macro with same names as soon as they take different number of arguments: such defines are called overloaded. Depending on the number of arguments passed, parder decides what macro to call.

define { msg(text) = DisplayTextToPlayer(GetLocalPlayer(), text, 0, 0) msg(text,playerid) = DisplayTextToPlayer(Player(playerid), text, 0, 0) msg(text,playerid,x,y) = DisplayTextToPlayer(Player(playerid), text, x, y) } nothing test() { msg("test 1") msg("test 2", 1) msg("test 3", 2, 0.1, 0.1) }

will be compiled to the following:

function test takes nothing returns nothing call DisplayTextToPlayer(GetLocalPlayer(), "test 1", 0, 0) call DisplayTextToPlayer(Player(1), "test 2", 0, 0) call DisplayTextToPlayer(Player(2), "test 3", 0.1, 0.1) endfunction

Attention! If the group of overloaded defines has the one with no arguments, it should always be followed by empty parenthesis.

3.6  Some constructs, used in defines

Sometimes you need to output one of the macro’s arguments as text (please note: not the argument’s value - if the argument is a variable, its name will be the result). For such cases there is `` instruction, which represents its argument as string. You can also use concatenation operator ##, which merges expressions on both sides of it (unlike the previous one, this operator can be used anytime, not only in defines).

#define register_func(type) = { nothing func_##type (type t) { BJDebugMsg(`type`) } } register_func(real)

which will become:

function func_real takes real t returns nothing call BJDebugMsg("real") endfunction

3.7  Predefined macros

cJass introduces some predefined macros for usage by coders. All of them are replaced to their values at the compilation stage.

DATE — returns current date in yyyy.mm.dd format.

TIME — returns current time in hh:mm:ss format.

COUNTER — returns integer starting from 0, every use increases this number by 1. Here’s an example of usage:

define unique_name = func_##COUNTER void unique_name () {} // void func_0 () {} void unique_name () {} // void func_1 () {} void unique_name () {} // void func_2 () {}

DEBUG — returns 1 if "Debug mode" checkbox is checked, else returns 0. Is used in conditional compilation (see 4.1) to add sets of actions, which exist only in debug mode.

FUNCNAME — returns the name of the function, where it’s used.

WAR3VER — returns WAR3VER_23 or WAR3VER_24 depending on the position of the version switch in cJass menu. Can be used in conditional compilation blocks (see 4.1) to maintain two map versions: 1.23- and 1.24+ compatible. For example:

#define H2I(h) = GetHandleId(h) #if WAR3VER == WAR3VER_23 #undef H2I integer H2I (handle h) { return h; return 0 } #endif

All predefined macros return non-string value. To represent them as string, you can use either stringizing operator or formatted output (see 6.2)

3.8  Define usage examples

In this section there are some examples of macro usage for solving of non-trivial tasks.

3.8.1  Function hooks

The need to do some set of actions when calling native functions pops out sometimes. In such cases people create some wrapper functions and call them instead. Using cJass macrodefinitions, you can easily replace the calls of some function to the call to your own wrapper function, or to a set of actions.

define { SetUnitPosition = SetUnitPosition_hook RemoveUnit(u) = { BJDebugMsg("A unit is being removed!") Remove##Unit(u) } } boolean SetUnitPosition_hook (unit u, real x, real y) { BJDebugMsg("We're moving the unit to (" + R2S(x) + "," + R2S(y) + ")") SetUnit##Position(u, x, y) return (GetUnitX(u) == x) && (GetUnitY(u) == y) } nothing test() { unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0) if (SetUnitPosition(u, 100, 300)) { BJDebugMsg("Landed successfully!") } RemoveUnit(u) }

which will be compiled to the following:

function SetUnitPosition_hook takes unit u, real x, real y returns boolean call BJDebugMsg("We're moving the unit to (" + R2S(x) + "," + R2S(y) + ")") call SetUnitPosition(u, x, y) return (GetUnitX(u) == x) and (GetUnitY(u) == y) endfunction function test takes nothing returns nothing local unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0) if (SetUnitPosition_hook(u, 100, 300)) then call BJDebugMsg("Landed successfully!") endif call BJDebugMsg("A unit is being removed!") call RemoveUnit(u) endfunction

Now it’s time for some explanations. To replace the RemoveUnit function to the set of actions, we simply define a macro with the same name, so all the calls to this function will be replaced to this macro’s contents. Please note that inside of this macro the name of hooked function is written using the concatenation operator. This is done to prevent the parser from trying to replace it to this macro’s contents as it has this name too. To replace the calls to SetUnitPosition to our wrapper, we simply declare the wrapper function and replace all calls to the native with the calls to our wrapper.

3.8.2  Default arguments emulation

Many programming languages allow you to assign default values to function’s arguments. In such cases these arguments can be easily omitted if you’re ok with their default values. Such behaviour can be emulated in cJass using overloaded macrodefinitions and concatenation operator.

define { CreateUnit(p, id) = Create##Unit(p, id, 0, 0, 0) CreateUnit(p, id, x, y) = Create##Unit(p, id, x, y, 0) CreateUnit(p, id, x, y, f) = Create##Unit(p, id, x, y, f) } nothing test() { CreateUnit(Player(0), 'hfoo') CreateUnit(Player(1), 'Hblm', 100, 231) CreateUnit(Player(2), 'Ewar', 382, 16, 42) }

I think, you already know, what you’ll see now:

function test takes nothing returns nothing call CreateUnit(Player(0), 'hfoo', 0, 0, 0) call CreateUnit(Player(1), 'Hblm', 100, 231, 0) call CreateUnit(Player(2), 'Ewar', 382, 16, 42) endfunction

4  Preprocessor

Preprocessor directives are first to be parsed during map code processing. Because of this, you can use them to do some interesting things.

4.1  Including external files

Sometimes it’s comfortable to keep parts of code (e.g. libraries, systems, spells, etc. which you use often) in external files. To include code from external files there’s include directive, which should be followed by string with file name you want to include. If files are in the "..\AdicHelper\lib\" subfolder or in the map folder, you can write file name without a path to it. Also it’s possible to include files from other folders, but you should write full path to it.

#include "myClasses.j" // "(newgen path)\AdicHelper\lib\myClasses.j" or "(map path)\myClasses.j" will be included include "D:\\dev\\Warcraft 3\\my_mega_system.j"

The code within included files can be written using both cJass and vJass syntax.

Attention! Don’t forget about double backslash in file path: \\

Sometimes, things happen that one external file is included to the map several times (for example, by different libraries). In such cases inevitable errors happen because of code duplication, which we’d like to avoid. For such cases, cJass introduces the #guard ID directive, which prevents files with same IDs to be included more than once. ID can be any word, composed from letters, numbers and underscores, but it’s good to use the file name with all extra symbols replaced to underscores. For example in "my-system.j" external file the ID can be #guard my_system_j.

Attention! The #guard directive has to be the written on the first line of external file.

4.2  Conditional compilation

cJass syntax introduces useful conditional compilation commands. Using them you can include or exclude specified blocks of code during map parsing. Controlling elements of this construct can be values, defines or enums (see 5). Their syntax is the following:

#define CONTROL = 3 #if CONTROL == 3 // code #elseif CONTROL == 1 // alternative code #else // more other code #endif

Currently, only == and != comparison operations are supported.

In such conditional blocks you can write any code with one limitation: they shouldn’t contain identical define declarations:

#if 1 != 0 #define msg = "hello!" #else #define msg = "good bye!" #endif

If you try to save this code, parser will throw the define redeclaration error. If you want to use code, similar to above, you should declare the macrodefinition before conditional translation blocks and set its value in those blocks with setdef directive:

#define msg #if 1 != 0 #setdef msg = "hello!" #else #setdef msg = "good bye!" #endif

Condiitonal compilation directives can also be triggered by a state of flag - macrodefinition with a special value.

#define MY_FLAG = true #if MY_FLAG // code #else // alternative code #endif

The #if value directive will work only if the compared macrodefinition has a value of true or 1. You can use the DEBUG predefined macro to write code, which is compiled only in debug mode.

4.3  Other directives

Sometimes you need to stop compilation if some requirements are not met. In such cases the #error "MESSAGE" directive will help you. It stops the compilation, showing the defined message. Best used inside of conditional compilation blocks.

5  Other elements

This chapter describes some syntax constructions, that were not mentioned above, although not less important.

5.1  Enumerations

Enumeration is a type, that can contain values specified by coder. Integer constants can be declared as enumeration members. E.g.:

enum { RED, GREEN, BLUE }

declares three named integer constants and sets values to them. Values are assigned atomatically starting from 0, in other words, enumeration is similar to the following:

define { RED = 0 GREEN = 1 BLUE = 2 }

Enumerations can also be named:

enum (color) { RED, GREEN, BLUE }

Named enumerations have their own internal counter - value of their elements will start from zero. Using several unnamed enumerations will continue the numeration of their values:

enum { FOOTMAN, MAGE } // FOOTMAN == 0, MAGE == 1 enum (color) { RED, GREEN, BLUE } // RED == 0, GREEN == 1, BLUE == 2 enum { WARDEN, ARCHER} // WARDEN == 2, ARCHER == 3

Enumerations also support default JASS2 syntax:

enum (color) RED GREEN BLUE endenum

Enumerations can be used in conditional translation blocks:

library SomeLib { enum (LIB_SPACE) { STD_SPACE, ALT_SPACE, DEBUG_SPACE } // choose your namespace ! define private SOMELIB_SPACE = STD_SPACE // what space to use #if SOMELIB_SPACE == STD_SPACE void SomeFunc(int a) { // base } #elseif SOMELIB_SPACE == ALT_SPACE void SomeFunc(int a) { // alt } #elseif SOMELIB_SPACE == DEBUG_SPACE void SomeFunc(int a) { // debug } #endif }

Or instead of named integer constants as markers for some actions:

enum (moves) { MOVING_ANIM_STATE_WALK, MOVING_ANIM_STATE_WALKBACK, MOVING_ANIM_STATE_STOP } void MovingAnimationControl (unit u, integer state) { if (state == MOVING_ANIM_STATE_WALK) { SetUnitTimeScale(u, 1) SetUnitAnimationByIndex(u, "Walk" ) } elseif (state == MOVING_ANIM_STATE_WALKBACK) { SetUnitTimeScale(u, -1) SetUnitAnimationByIndex(u, "Walk" ) } elseif (state == MOVING_ANIM_STATE_STOP) { SetUnitAnimation(u,"Stand") SetUnitTimeScale(u, 1) } }

6  Standard libraries

The cJass default package includes standard libraries, which can be used in your maps because they will definitely be on target computer if cJass is installed there. You can find them in "lib" subfolder of your "AdicHelper" directory. These files were created specially for ease of coding and some light optimizations. You simply include the ones you need in your map. All standard included files have "cj_" prefix and ".j" extension.

6.1  cj_types and cj_typesEx

include "cj_types.j" include "cj_typesEx.j" include "cj_types_priv.j" include "cj_typesEx_priv.j"

These libraries contain macrodefinitions, aimed to make JASS2 type usage closer to C++. In the first one, aliases for main types and keywords are defined:

define { int = integer bool = boolean void = nothing float = real while = whilenot not break = exitwhen true }

In the second, constructs like new <type> are defined for main handle types:

define <new timer> = CreateTimer()

You can read the full list by opening "cj_typesEx.j" in any text editor.

There also are versions of these files for usage inside of libraries, where all defines are private so they won’t affect anything outside you library. They have "_priv" suffix in the filename.

6.2  cj_order and cj_antibj_base

include "cj_order.j" include "cj_antibj_base.j"

These libraries aim for ease usage and slight map optimization on save.

First of them replaces all calls to order conversion functions like OrderId("smart") to corresponding integer values and implements defines like order_smart for integer values of all existing orders.

The other one performs light optimization when functions and constants from "Blizzard.j" are used in the map. Constants are replaced to their values and many functions are replaced to their analogs from "common.j". Please note, that these optimizations also work for GUI triggers of your map.

6.3  cj_print

include "cj_print.j"

By including this file, you gain access to output formatting functions. The templates, used for formatting, are defined by specially formatted string (format string). Arguments, passed to formatting functions, should follow the foemat string in exact order, stated in format string.

6.3.1  Function list

In function descriptions format string is declared as string format and arguments are ... These functions can be used without arguments, in this case they will simply use given string as input.

The sprintf function formats a string and returns it as a result. It’s compiled to a simple statement, not a function call.

The printf function outputs the formatted string to local player. The output is done using DisplayTimedTextToPlayer function.

The following function do same things, as their standard analogs, but take a format string and arguments list instead of their default string argument.

void sBJDebugMsg (string format, ...) void sDisplayTextToPlayer (player p, real x, real y, string format, ...) void sDisplayTimedTextToPlayer (player p, real x, real y, real time, string format, ...) void sDisplayTimedTextFromPlayer (player p, real x, real y, real time, string format, ...) void sSetTextTagText (texttag t, string format, ..., real h) void sQuestSetTitle (quest q, string format, ...) void sQuestSetDescription (quest q, string format, ...) void sQuestItemSetDescription (questitem q, string format, ...) void sMultiboardSetTitleText (multiboard m, string format, ...) void sMultiboardSetItemsValue (multiboard m, string format, ...) void sMultiboardSetItemValue (multiboarditem m, string format, ...) void sDialogSetMessage (dialog d, string format, ...) button sDialogAddButton (dialog d, string format, ..., int hotkey) button sDialogAddQuitButton (dialog d, bool b, string format, ..., int hotkey) void sLeaderboardAddItem (leaderboard l, string format, ..., player p)

6.3.2  Format string

The formatting string is a template for arguments substitution. All the characters of this string excluding control sequences are copied to the resulting string without changes. Usually, control sequences start with % symbol (to output the percent sign, you have to escape it using backslash symbol \%). Control sequences also contain name of the type of substituted variable. By default, the following types are defined:

%p - player name, takes player variable %pc - colored player name, takes player variable %i - decimal number, takes integer %igold - decimal number, gold-colored, takes integer %ilumb - decimal number, lumber-colored, takes integer %ip - player name, takes integer (player id) %ipc - colored player name, takes integer (player id) %b - boolean, prints "true" or "false" %r - floating number without extra formatting, takes real %s - string, if a direct value is passed, it will be cleanly merged %v - variable, an argument is directly copied to output %h - decimal number, descriptor id is passed as an argument

Furthermore, the user can define his own markup for any additional argument types. To do this, you have to redefine the cj_sprintf_argTyp_User macro.

// here we define our own type handlers setdef cj_sprintf_argTyp_User = /* markup */ // here we use prints with our markup, eg. in our library // then we roll back our markup not to mess with other libraries etc. setdef cj_sprintf_argTyp_User =

Let’s look into the type definition syntax by the exaple of %i type:

Attention! The % symbol will lead the World Editor to strange behaviour if used in the Custom Script section. To use string formatting functions there, you should use the ^ symbol as a control sequence start marker.

7  Credits and thanks


This document was translated from LATEX by HEVEA.