9.6 KiB
Scripts
The Piklib/BlooMoo engine drives game logic by interpreting text-based scripts. This chapter describes the syntax of those scripts, how the engine loads them, and the order in which objects are initialised.
File formats
Scripts are stored in files with the .CNV, .DEF, .CLASS, and .SEQ extensions. They all share the same basic textual structure and differ only in their intended use.
The engine treats all script content as uppercase and is case-insensitive. By convention, scripts are written in uppercase.
Encryption
Files shipped with the game are encrypted by default using a transposition cipher with a variable offset. An encrypted file begins with a header of the form:
{<X:N>}
where X is a letter indicating the direction of the offset (D means a negative offset) and N is the offset value. The engine detects this header automatically and decrypts the rest of the file before parsing. Files without this header are read as plain text.
Object declaration
An object begins with a line containing the OBJECT keyword:
OBJECT=OBJECT_NAME
Lines between consecutive OBJECT= lines define properties of the current object. The definition lasts until the end of the file or the next OBJECT= line.
If the same object is declared more than once in a single file, its properties are merged — later entries override earlier ones.
Object properties
Properties are written as objectName:property=value:
OBJECT_NAME:PROPERTY=VALUE
Signals can take an additional parameter after a caret (^):
OBJECT_NAME:ONBRUTALCHANGED^3=PROCEDURE_NAME
In both cases, the engine accepts both KEY=VALUE and KEY = VALUE (with spaces around the equals sign).
Variable type
The type is essential — without it, the engine does not know how to handle the object, and the result is usually a hard crash. The type is declared with the TYPE property:
OBJECT_NAME:TYPE=STRING
The full list of available types is in the Type reference.
Literals and strings
How a literal is interpreted depends on its context:
- Text in double quotes (
"...") is always treated as aSTRING. - Unquoted text is first checked against the names of existing variables — if a variable with that name exists, its value is used. Otherwise the text is taken literally.
Floating-point numbers accept both standard notation (1.234) and scientific notation with the letter e or d (1.23e4, 1.23d4).
Code blocks
Code blocks — used as the value of a signal or as the body of a procedure — are written inside curly braces. Statements are separated by semicolons; the final statement must also end with a semicolon, otherwise it may not be executed.
OBJECT_NAME:ONCHANGED={VARIABLE2^PLAY("TADA");}
The entire code block must fit on a single line — the engine does not support multi-line blocks directly inside a script file.
Comments
The engine recognises two forms of comments:
- Line comment — a line starting with
#is skipped entirely. - Statement comment — a single statement preceded by
!is treated as commented out, up to the next semicolon.
Method calls
Methods are called using the caret (^):
OBJECT_NAME^METHOD(arg1, arg2);
Arithmetic expressions
Computational expressions are written inside square brackets:
VARIABLE_NAME^SET([VARIABLE_NAME^GET()+"2"]);
Operators and typing rules are described in the Arithmetic chapter.
String pointers
A * before a variable name or an expression means that the value is to be used as the name of another variable. This allows dynamic references built from text:
*VARIABLE_NAME^PLAY();
*["ANIMO_"+_I_]^PLAY();
In the first form, VARIABLE_NAME should be a STRING containing the actual object's name. In the second, the object name is constructed from an arithmetic expression.
Procedure arguments
Inside the body of a procedure, arguments are accessed with a dollar sign followed by a number (numbered from 1):
PROCEDURE:CODE={VARIABLE_NAME^SET($1);}
The THIS variable
Inside a block that handles a signal, an implicit THIS variable is available, set to a reference to the object that fired the signal. THIS is also accessible from procedures called from within such a block.
THIS behaves unusually: calling GETNAME on it returns the string "temp", suggesting that under the hood it is a temporary wrapper. The following work reliably on THIS:
GETandSETfor primitive types,SHOW,HIDE,PLAY,PAUSE,STOP, andRESUMEfor graphical objects (ANIMO).
Calling another type-specific method (e.g. GETCFRAMEINEVENT on ANIMO) usually crashes the engine. To work around this, AidemMedia scripts use the following pattern: store the object's name in a STRING variable first, then call ^RUN(string_variable, method_name), which internally resolves the string pointer to the actual object.
Loops
@LOOP
@LOOP(BEHAVIOUR code, INTEGER start, INTEGER delta, INTEGER increment)
Executes code for counter values _I_ in the range [start, start + delta) with step increment. In pseudocode:
for (int _I_ = start; _I_ < start + delta; _I_ += increment) {
code;
}
@FOR (BlooMoo)
@FOR(INTEGER counter, BEHAVIOUR code, INTEGER start, INTEGER delta, INTEGER increment)
Identical to @LOOP, except that the first argument selects a custom variable to act as the counter instead of the default _I_.
@WHILE
@WHILE(mixed value1, STRING comparator, mixed value2, BEHAVIOUR code)
Executes code as long as the condition value1 comparator value2 holds. The list of comparators is described below in Conditional.
Conditional
The engine provides two forms of @IF.
Simple condition
@IF(mixed value1, STRING comparator, mixed value2, BEHAVIOUR codeTrue, BEHAVIOUR codeFalse)
Available comparators:
| Comparator | Meaning |
|---|---|
_ |
equal to |
!_ |
not equal to |
< |
less than |
<_ |
less than or equal |
> |
greater than |
>_ |
greater than or equal |
Compound condition
@IF(STRING condition, BEHAVIOUR codeTrue, BEHAVIOUR codeFalse)
Compound conditions add logical operators:
&&— conjunction (and)||— disjunction (or)
In a compound condition, the equals sign is written as an apostrophe (') rather than an underscore (_):
| Comparator | Meaning |
|---|---|
' |
equal to |
!' |
not equal to |
< |
less than |
<' |
less than or equal |
> |
greater than |
>' |
greater than or equal |
Dynamic variable creation
Variables can be created on the fly inside a code block:
@INT(STRING name, INTEGER value)
@DOUBLE(STRING name, DOUBLE value)
@STRING(STRING name, STRING value)
@BOOL(STRING name, BOOL value)
Each statement creates a variable of the matching type with the given name and initial value.
Jump operators
Inside loops and procedures, control flow can be redirected with:
@CONTINUE()— skips the remaining statements in the current loop iteration and moves to the next one.@BREAK()— aborts the entire call tree started by the current signal or invocation.@ONEBREAK()— aborts the current procedure only.@RETURN(mixed value)— sets the value returned by the procedure but does not stop it from executing.
Script loading order
Scripts in the engine are organised hierarchically: scripts at lower levels can see variables from their own scope and from all ancestors, but not the other way around.
Entry point
The engine starts from Application.def in the dane subdirectory. This file defines objects of types APPLICATION, EPISODE, and SCENE — other types in this file are ignored.
Example contents:
OBJECT=GAME
GAME:TYPE=APPLICATION
GAME:PATH=GAME
GAME:EPISODES=PRZYGODA
GAME:STARTWITH=PRZYGODA
OBJECT=PRZYGODA
PRZYGODA:TYPE=EPISODE
PRZYGODA:PATH=GAME\PRZYGODA
PRZYGODA:SCENES=START,CREDITS,LEBIODKA
PRZYGODA:STARTWITH=START
OBJECT=START
START:TYPE=SCENE
START:PATH=GAME\PRZYGODA\START
Loading subsequent files
After Application.def is read, the engine loads a .CNV file for each defined object. The file path is built from the object's PATH attribute (relative to the dane directory), the object's name, and the .CNV extension. If the file does not exist, loading is silently skipped.
The loading order is:
- The file bound to the
APPLICATIONobject. - The file of the first episode (
STARTWITHattribute ofAPPLICATION). - The file of the first scene of that episode (
STARTWITHattribute ofEPISODE).
When locating files, the engine also takes the currently selected language into account (see APPLICATION.SETLANGUAGE) — the chosen language identifier points to a subfolder of localised assets that is consulted while loading game files.
Variable initialisation
Within each file, variables are created and initialised in a fixed order by type:
- Procedures.
- Primitive types (
STRING,DOUBLE,INTEGER,BOOL). - Arrays and conditions.
- Animations, images, sounds, and fonts.
- Buttons, text fields, sequences, mouse, keyboard, canvas observer.
For each variable in this phase the ONINIT signal is fired. Once all variables are initialised, the engine calls the __ONINIT__ procedure if one is defined.