Jump to content

BSL:Manual: Difference between revisions

781 bytes added ,  30 December 2016
copy-edited through end of Conditional section
(→‎Files: as in BSL:Introduction, adding note that some game data types can trigger BSL too; please add any other types that we know of)
(copy-edited through end of Conditional section)
Line 1: Line 1:
Oni's scripting language is called [[BSL]]. Like any typical scripting or programming language, BSL scripts consist of plain-text files which employ branching logic and various operators according to certain rules of syntax in order to process data using functions that act upon variables. If any of those terms are not familiar to you now, keep reading. If you found that sentence boringly obvious, then you should be experienced enough to get by with [[BSL:Introduction]].
Oni's level scripts are written in BFW Scripting Language, or [[BSL]]. Like any typical scripting or programming language, BSL scripts consist of plain-text files which employ branching logic and various operators according to certain rules of syntax in order to process data using functions that act upon variables. If any of those terms are not familiar to you now, keep reading. If you found that sentence boringly obvious, then you should be experienced enough to get by with [[BSL:Introduction]].
{{TOClimit|3}}
{{TOClimit|3}}
==Files==
==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 the folder being specified by the level's [[ONLV]] resource). The code automatically begins running with the function called "main", regardless of which .bsl file it is found in (Oni's scripts always place it in a file ending in "_main.bsl"). The code then flows through whichever functions are called by "main". You can also manually call any of those functions from the [[Developer Mode|developer console]] -- however, you cannot actually define variables or functions through the console.
When Oni loads a level's resources, it also loads and parses all .bsl files in the level's corresponding folder in [[IGMD]] (the name of the folder being specified by the level's [[ONLV]] resource). The code automatically begins running with the function called "main", regardless of which .bsl file it is found in (Oni's scripts always place it in a file ending in "_main.bsl"). The code then flows through whichever functions are called by "main". You can also manually call any of those functions from the [[Developer Mode|developer console]] -- however, you cannot actually define variables or functions through the console, so scripts cannot be written this way.


However, besides the "main" entry point in a level's BSL scripting, there are types of game data which store function names that are to be called upon certain events. For instance, characters can call script functions upon death, and trigger volumes can call functions when they are entered or exited. See [[CHAR]], [[OBD:BINA/OBJC/CONS|BINA CONS]], [[OBD:BINA/OBJC/DOOR|BINA DOOR]], [[NEUT]], and [[TRGV]] for all known places in the game data that can trigger BSL functions.
Besides the automatic starting point represented by the "main" function, there are types of game data which specify function names that are to be called upon certain events. For instance, characters can call script functions upon death, and trigger volumes can call functions when they are entered or exited. See [[CHAR]], [[OBD:BINA/OBJC/CONS|CONS (BINA)]], [[OBD:BINA/OBJC/DOOR|DOOR (BINA)]], [[NEUT]], and [[TRGV]] for all known places in the game data that can trigger BSL functions.


Note that the optional [[IGMD/global|global]] folder is also loaded for all levels, but any function found in a .bsl file there will be stranded and only accessible by console unless you edit a function in some level's existing BSL file so that it calls your global function.
Note that the optional [[IGMD/global|global]] folder is also loaded for all levels, but any function found in a global .bsl file will be stranded, only accessible by console, unless you edit a function in some level's existing BSL file so that it calls your global function.


==Syntax==
==Syntax==
As with natural languages such as English, there are rules about punctuation, spacing, and word order in BSL.
As with natural languages like English, there are rules about punctuation, spacing, and word order in BSL.


===Statements===
===Statements===
All code consists of discrete statements. These statements can be gathered into larger structures to create more efficient programs.
All code consists of discrete statements. These statements can be gathered into larger structures like "blocks" and "functions" to create more efficient programs.


====Statement separators====
====Statement separators====
BSL accepts 2 statement separators: the semicolon (;) and the linebreak. The following pieces of code are equivalent:
BSL accepts two statement separators: the semicolon (;) and the line-break. The following three pieces of code are equivalent:


  [[dmsg]]("Statement 1"); dmsg("Statement 2");
  [[dmsg]]("Statement 1"); dmsg("Statement 2");
Line 29: Line 29:
  dmsg("Statement 2")
  dmsg("Statement 2")


The third example is in old-style syntax, which is discussed under [[#Old vs. new syntax|Old vs. new syntax]].
The third example is partly in old-style syntax, which is discussed under [[#Old vs. new syntax|Old vs. new syntax]].


===Compound statements===
===Compound statements===
Compound statements are groups of statements held together by a pair of curly braces:
Compound statements are groups of statements grouped together by a pair of curly braces:


  {
  <b>{</b>
     dmsg("Statement 1");
     dmsg("Statement 1");
     dmsg("Statement 2");
     dmsg("Statement 2");
     dmsg("Statement 3");
     dmsg("Statement 3");
  }
  <b>}</b>


The purpose of doing this is to group some statements under either a function declaration (see [[#Declaration|Declaration]]) or an "if" statement (see [[#Conditional|Conditional]]). Such a group of statements is referred to as a block, and is also said to be in a scope. Traditionally in programming, anything inside a certain level of shared braces can be seen from elsewhere within those braces, or from within a scope inside that scope, but not from a scope outside that scope. This allows careful management of which code can alter which data. In BSL's case, there are certain issues with scope not being respected (see the [[#if|if statement]] section). Variables outside of all scopes (such as functions) are referred to as "globals".
The purpose of doing this is to place statements under either a function declaration (see [[#Declaration|Declaration]]) or an "if" statement (see [[#Conditional|Conditional]]). Such a group of statements is referred to as a "block", and sometimes also defines a "scope". Traditionally in programming, anything inside a certain scope can be seen from elsewhere within that scope, or from within a scope inside that scope, but not from a scope outside that scope. This allows careful management of which code can alter which data. In BSL's case, however, there are certain issues with scope not being respected (see the [[#if|if statement]] section). Variables outside of all scopes (including functions) are referred to as "globals".


===Comments===
===Comments===
Comments are notes from the programmer to explain some code. In BSL, the comment marker is the "#". Any text after that character is not interpreted as BSL. Comments are supposed to be placed after a line of code...
Comments are notes from the programmer to explain some code. In BSL, the comment marker is the "#". Any text after that character is not interpreted as BSL. Comments are supposed to be placed after a line of code...


  var int a = 0; '''# this global is also modified in my_cutscenes.bsl'''
  var int a = 4; '''# here is a trailing comment explaining why 'a' is 4'''


...or above a line or block of code:
...or above a line or block of code:
Line 55: Line 55:
  }
  }


Do not using trailing comments when not ending statements with semicolons (see [[#Old vs. new syntax|Old vs. new syntax]] for explanation).
Do not use a trailing comment unless you end the statement with a semicolon (see [[#Old vs. new syntax|Old vs. new syntax]] for explanation).


In documentation outside of source code or script files, such as this page, comments are sometimes used to tell the reader something in a way that doesn't break the actual code, if the user should type it in exactly as it appears, comments and all.
In documentation outside of source code or script files, such as this page's BSL samples, comments are sometimes used to tell the reader something in a way that won't break the actual code if the user copies the whole line into a script file, comments and all.


===Old vs. new syntax===
===Old vs. new syntax===
Line 68: Line 68:
  dprint("Hello");
  dprint("Hello");


This dual syntax can make it more complicated to talk about the language, and you can also generate errors if you accidentally mix syntax. For instance, if you try to end a function call with a semicolon, but you don't use a C-style function call...
This dual syntax can make it more complicated to talk about the language, and you can also generate errors if you accidentally mix syntaxes. For instance, if you try to end a function call with a semicolon, but you don't use a C-style function call...


  dprint "Hello";
  dprint "Hello";
Line 85: Line 85:


===Coding style===
===Coding style===
There are other aspects of the language which are flexible, and yet not connected to the old-style/new-style division. For instance, the quotes around "Hello" can be left out in both syntax versions of the above calls to dprint() and dmsg(). You can also omit stating the type and parameters of a function (with "void" being assumed in their absence). You also do not need to enclose code after an if/else statement in curly braces if there is only a single line. None of these syntax choices are in conflict with the "new style" syntax.
There are other aspects of the language which are flexible, and yet not connected to the old-style/new-style division. For instance, the quotes around "Hello" can be left out in both syntax versions of the above calls to dprint() and dmsg(). You can also omit stating the type and parameters of a function (with "void" being assumed in their absence). You also do not need to enclose code after an if/else statement in curly braces if there is only a single line of code intended to be in that scope. None of these syntax choices are in conflict with the "new style" syntax.


Additionally, you can use whitespace and newlines in different ways:
Additionally, you can use whitespace and newlines in different ways:
Line 110: Line 110:


===Declaration===
===Declaration===
Before using a function or a variable for the first time, you need to declare its name and data type.
Before using a variable for the first time, you need to declare it with the "var" keyword. Function definitions must start with the "func" keyword.


====var====
====var====
  '''var''' int a = 0;
  '''var''' int a = 0;


See [[#Variables|Variables]] for details on declaring variables.
After this, you can refer to the variable merely as "a". See [[#Variables|Variables]] for details on declaring variables.


====func====
====func====
Line 123: Line 123:
  }
  }


See [[#Functions|Functions]] for details on declaring functions.
Besides this, you can call the function merely by writing "spawn_team". See [[#Functions|Functions]] for details on declaring functions.


===Type specification===
===Type specification===
When declaring a variable or defining a function, you need to state what type of data is contained in that variable or what kind the function works with.
When declaring a variable or defining a function, you need to state the type of data that is contained in that variable or that is returned by the function.


====void, bool, int, float, string====
====void, bool, int, float, string====
Line 135: Line 135:
  }
  }


Besides "void" (used in function definitions to indicate "no type"), these are the types of data that can be assigned to variables, passed to functions, and returned by functions. See [[#Data types|Data types]] for details on the types.
Besides "void", which is only used in function definitions (indicating that no data is returned), these are the types of data that can be assigned to variables, passed into functions, and returned by functions. See [[#Data types|Data types]] for details.


===Conditional===
===Conditional===
Line 146: Line 146:
  }
  }


The condition in the parentheses needs to evaluate to "true" or "false". For examples of operators, see [[#Operators|Operators]].
For examples of operators, see [[#Operators|Operators]]. A typical example would be:
 
if (kills < 10)


The condition can technically also evaluate to an int value, which then is converted to true/false:
The result of the operation within the parentheses will always boil down to either "true" or "false" (a "yes" or a "no"), even if you would think that the result would be a number. For instance:


  if (8 - 8) # evaluates to false because it is zero
  if (8 - 8) # evaluates to false because 8 minus 8 is zero
  if (8 - 7) # evaluates to true because it is non-zero
  if (8 - 7) # evaluates to true because 8 minus 7 is non-zero


Braces are optional when you only have one line of code under the "if" (this is also true for "else" and "else if", discussed below):
Braces are optional when you only have one line of code under the "if" (this is also true for "else" and "else if", discussed below):
Line 159: Line 161:
  spawn_considered = true;
  spawn_considered = true;


Here, "spawn_considered" will be set to true regardless of whether the "if" condition was true and spawn_enemy_at_flag() was run. Though it's nice to save the vertical space, the danger in omitting braces is writing something like this:
Here, "spawn_considered" will be set to true regardless of whether the "if" condition was true and spawn_enemy_at_flag() was run. (The indentation has no effect on how the code runs; it's simply to guide the reader.) Though it's nice to save the vertical space by omitting the braces, the danger is in writing something like this:


  if (a > 0)
  if (a > 0)
Line 165: Line 167:
     spawn_considered = true;
     spawn_considered = true;


That indentation on "spawn_considered" is misleading; when glancing at this code, you might expect that "spawn_considered" only is changed if "a" is greater than zero. It could be that you even intended to add that line to the "if" statement but forgot to add braces. Always using braces around an "if" statement's body will prevent that mistake.
That indentation on "spawn_considered" is misleading; when glancing at this code, you might expect that "spawn_considered" only is changed if "a" is greater than zero. It could be the script's writer really did want that line to be subject to the "if" statement, but he forgot to add braces. Always using braces around an "if" statement's body, even when it's one line long, will prevent that mistake.


Be aware that, even ''with'' braces, BSL does not always respect scope for blocks of code under "if" statements; for instance, a "return" statement will fire even if the surrounding "if" condition is false (see [[#Flow interrupt|Flow interrupt]] for an example). An even more alarming example of bad scope is this:
Unfortunately, even if the programmer added braces around the two lines above, they would not run as intended. That's because, even ''with'' braces, BSL does not always respect scope for blocks of code under "if" statements; for instance, a "return" statement will fire even when the surrounding "if" condition is false (see [[#Flow interrupt|Flow interrupt]] for an example). An even more alarming example of bad scoping is this:


  func void broken_if(void)
  func void broken_if(void)
Line 208: Line 210:
  }
  }


One very handy feature that you also won't see in Oni's game scripts is that you can use the logical operators "and" and "or" to string together multiple conditions:
One very handy feature that you won't see used in Oni's game scripts is that the logical operators "and" and "or" can string together multiple conditions:


  if ((a < b) and (c > d))
  if ((a < b) and (c > d))
Line 248: Line 250:
     dprint("Unknown error code!");
     dprint("Unknown error code!");


Often, programmers only use "else if" statements to save some CPU cycles. For instance, if you had a long string of "if" statements to handle different possible values for a variable, why should they all be evaluated once you have already found the one that is true? Imagine the above example, but with 30 possible values for "error". In reality, you won't find much benefit in trying to save cycles with BSL. However, "else if" can also simplify logic:
Programmers commonly use "else if" statements to save some CPU cycles. For instance, if you had a long string of "if" statements to handle different possible values for a variable, why should they all be evaluated once you have already found the one that is true? Imagine the above example, but with 30 possible values for "error". In reality, you won't find much benefit in trying to save cycles with BSL since typical game scripts are not computationally expensive. However, "else if" can also simplify the flow of logic:


  if (d eq 0)
  if (d eq 0)
Line 259: Line 261:
     ...
     ...


In checking the condition of "n" and "d" in this example, we have to keep checking after the second statement whether "d" is greater than 1; otherwise, multiple "if" statements could evaluate to true and execute their code, but we only want to act on one of these possible situations. Using "else if" allows us to be confident that only one statement can run:
In checking the condition of "n" and "d" in this example, we have to keep checking after the second statement whether "d" is greater than 1; otherwise, multiple "if" statements could evaluate to true and execute their code, and we only want to act on one of these possible situations. Using "else if" allows us to be confident that only one statement can run:


  if (d eq 0)
  if (d eq 0)
Line 270: Line 272:
     ...
     ...


Notice how we can shorten the logical tests without losing anything. "d" is still implicitly required to be above zero in statements 2-4, or else they will not even be evaluated.
Notice how we can shorten the logical tests without losing anything. "d" is still implicitly required to be non-zero in statements 2-4, or else they will not even be evaluated.


===Flow interrupt===
===Flow interrupt===