BSL:Introduction

From OniGalore
Revision as of 14:05, 11 August 2018 by Script 10k (talk | contribs) (bsl commands per bsl file has a limit)
Jump to navigation Jump to search

This page gives a brief look at BSL, Oni's scripting language. For those with scripting or programming experience, BSL's syntax and concepts will be very familiar, but BSL's feature set is simpler than most scripting languages. For details on any aspect of the language, as well as documentation of potentially-serious quirks in certain features of the language, read the BSL Manual.

Files

When Oni loads a level, it also loads and parses all .bsl files in the folder in IGMD which contains that level's scripts (the name of this folder being specified by the level's ONLV resource). Like C, the code automatically begins running with the function called "main" at the time of level-load, regardless of which .bsl file it is found in (the convention in Oni's scripts is to place it in a file called *_main.bsl).

Unlike C, there are other "entry points" in the code. For instance, characters can call script functions upon death and trigger volumes can call functions when they are entered or exited. See CHAR, BINA CONS, BINA DOOR, NEUT, and TRGV for all known places in the game data that can trigger BSL functions.

Note that the optional global folder is also loaded for all levels, but any function found in a .bsl file in global/ will be stranded, only accessible by developer console, unless it is called by a function in a level script or by one of the above types of game data in that level.

BSL commands per file limitation

The Oni engine has some limitation for how many bsl commands can be loaded with a single BSL file (at least on Windows).

If you input too many bsl commands on one bsl file, you may get errors on the developer mode console like this «Func "main", File "(called from engine)", Line 0: semantic error, function "main" does not exist.» (it can be other function other than main).

For instance you can check this files (extract the files and put them on IGMD\EnvWarehouse to experience the problem):

http://script10k.oni2.net/wikifiles/main_not_working_without_commenting_commands.zip

http://script10k.oni2.net/wikifiles/main_not_working_without_commenting_commands(functions_split).zip

http://script10k.oni2.net/wikifiles/main_split_in_two_files.zip

In all these bsl files (each of them is a different scenario) the message "This message should appear on the beginning" should appear when you load level 1.

However for "main_not_working_without_commenting_commands" it doesn't work while you don't comment the 5th line on the bsl file.

The scenario "main_not_working_without_commenting_commands(functions_split)" is the same, but in this one the main function is split in two functions, which is not enough for the error go away.

The last scenario "main_split_in_two_files" is the same as the previous but the 2 functions are split in two different files: "main.bsl" and "continuation.bsl". In this case the script works as expected, the message "This message should appear on the beginning" appears on level load.

So if you experience the error «Function "x" does not exist.» when in fact it does exist split your larger code for other functions on other BSL files. That should fix the problem.

Syntax

Statements

BSL supports strong and weak syntax in some ways. Here's a typical statement:

dmsg("Hello");

You can omit the semicolon, parentheses, and also the quotes if your string doesn't have any spaces in it:

dmsg Hello

Similarly, here's the formal syntax for a snippet of code:

func void some_function(void)
{
   var int a;
   a = 4;
   sleep(60);
   if (a eq 4)
   {
      dprint("Hello");
   }
}

The same code would look like this in the weak syntax:

func void some_function(void)
{
   var int a;
   a = 4
   sleep 60
   if (a eq 4)
   {
      dprint "Hello"
   }
}

Notice that parentheses are always needed with "if" statements and semicolons are always needed on variable declarations. Note that using the strong syntax avoids certain weird aspects of BSL, and that mixing aspects of strong and weak syntax can cause errors (see the Manual's Old vs. new syntax section for details).

Comments

Comments are marked with the pound sign:

var int a = 0; # this global is also modified in my_cutscenes.bsl

Reserved words

Declaration

You always need to use "var" when declaring a variable and "func" when defining a function:

var int a = 0;
func int get_enemy_count(void)

Functions do not need to be defined or declared above the place in the code where they are called, unlike in C.

Type specification

BSL does not support weak typing. You need to specify your types when making declarations, as seen in the above examples. You can choose from "bool" (buggy, so not recommended), "int", "float" (rarely useful because of BSL's limited math operations), and "string". As with C, you use "void" to mean "no type" when defining functions.

Conditional

BSL supports standard if/else statements, and you can use "and" and "or" to test compound conditions:

if ((a < b) and (c > d))
{
   ...
}
else if (a < 0)
{
   ...
}
else
{
   ...
}

Beware of a bug in BSL where variable assignments under an "if" statement will fire even when the "if" condition is false. See the Manual page's Conditional section for details.

Flow interrupt

There is no "goto" statement in BSL, nor any loop controls like "continue" or "break" (since there are no proper loops!). There's a "return" keyword in BSL, but a "return" inside of an "if" statement will fire regardless of whether the statement evaluates to true or false. So you only use "return" to return data at the end of the function, not to exit early (see the Functions section).

There's also a "sleep" command that pauses BSL execution; you pass it a number in ticks:

sleep(60); # wait for one second

Loop

There's no loop keyword like "for" or "while" in BSL, but you can kind of get a loop using one of two methods. First, you can call a function recursively, but BSL has a short stack, so don't expect to get more than four levels deep. If you just want a loop and not recursion, then you can avoid recursion and the related stack limitation by "sleep"ing for a tick or more, and then "fork"ing the call that would otherwise be recursive. See BSL:Snippets for an example that also makes up for the missing multiplication operator in BSL.

Second, you could use schedule-repeat-every:

schedule some_function() repeat 50 every 20;

...or schedule-at:

schedule some_function() at 15;
schedule some_function() at 30;
schedule some_function() at 45;
...

Just be aware that the BSL will continue executing without waiting for this loop to finish. Also, there is no way to exit the loop early, but some_function() could simply refuse to perform its task if a global variable has changed to false, simulating a loop exit.

Multi-threading

BSL doesn't have robust multi-threading, but you can sort of hack your own solution using "fork" or "schedule". Please see the Manual's Concurrency section for examples.

Operators

Like some other languages, BSL differentiates between checking for equivalency ("eq") and setting equivalency ("="):

if (counter eq 3) ...
i = 4;

BSL's operators are pretty standard stuff:

+ -
eq ne < > <= >=
and or !

But you'll note that there is no operator for multiplying or dividing. You'll need to create your own loop with addition and subtraction to perform that kind of math.

Data types

You can choose from "void", "bool", "int", "float", and "string" (with "void" only allowed when defining a function). The "bool" type is buggy in Windows Oni, so use of "int" is recommended instead.

var int a = 1;
# see the "Functions" section below on how to use a data type keyword when defining a function

The "int" type is signed 32-bit. See the Manual's Data types section to learn about various limitations on math between different data types in BSL.

Functions

As mentioned above, functions do not need to be declared or defined before they are called. Defining and calling a function looks exactly like C, except for the "func" keyword in the definition:

a = get_enemy_count(0);
func int get_enemy_count(int count_dead) { ... return(count); }

Once again, please see the Manual's Functions section to learn about concurrent and recursive calling.

Variables

As usual, variables can be explicitly initialized:

var int y = 9;

or not:

var int y;

In the second case, "y" is automatically initialized to zero.

Variables will have global scope if declared outside of a function.

Built-in functions and variables

Like any game, Oni provides access to a number of hardcoded functions and global variables that can be used to alter the game environment. They are listed on BSL:List, and grouped by common task in the Scripting tasks category.