19,683
edits
m (wording improvements) |
(elaboration on recursion and forking; brought over warning about dprint/dmsg from BSL:Functions) |
||
| (3 intermediate revisions by the same user not shown) | |||
| Line 241: | Line 241: | ||
|} | |} | ||
Note that "=" is '''not''' for checking if one entity is equal to another (see "eq" | Note that "=" is '''not''' for checking if one entity is equal to another (see "eq" under {{SectionLink||Relational}} for that). | ||
===Arithmetical=== | ===Arithmetical=== | ||
| Line 282: | Line 282: | ||
} | } | ||
Alternatively | Alternatively you can use the sum operator, which doesn't suffer from this issue: | ||
if (5+-6 eq -1) | if (5+-6 eq -1) | ||
| Line 303: | Line 303: | ||
| eq | | eq | ||
| Equal to | | Equal to | ||
| Tests if two values are equal. | | Tests if two values are equal. | ||
|- | |- | ||
| ne | | ne | ||
| Not equal to | | Not equal to | ||
| Tests if two values are different. | | Tests if two values are different. | ||
|- | |- | ||
| > | | > | ||
| Line 336: | Line 336: | ||
| ! | | ! | ||
| Not | | Not | ||
| Logical Not. Reverses the result of a test | | Logical Not. Reverses the result of a test. | ||
|- | |- | ||
| and | | and | ||
| Line 347: | Line 347: | ||
|} | |} | ||
See {{SectionLink||if}} | To illustrate the '!' ("not") operator, the following three lines are equivalent: | ||
!(some_bool eq true) | |||
some_bool eq false | |||
some_bool ne true | |||
See {{SectionLink||if}} for detailed examples of using the logical operators. | |||
==Reserved words== | ==Reserved words== | ||
| Line 358: | Line 363: | ||
'''var''' int a = 0; | '''var''' int a = 0; | ||
After this, you | After this, you refer to the variable merely as "a". See {{SectionLink||Variables}} for details on declaring variables. | ||
====func==== | ====func==== | ||
| Line 366: | Line 371: | ||
} | } | ||
After this, you call the function merely by writing its name; "spawn_team" would work, but the proper new-style syntax is "spawn_team();". See {{SectionLink||Functions}} for details on declaring functions. | |||
===Type specification=== | ===Type specification=== | ||
| Line 487: | Line 492: | ||
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: | 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 | if (d eq 10) | ||
# special-case code for d being 10 | |||
if ((d ne 10) and (d eq 3)) | |||
# special-case code for d being 3 | |||
if (( | if ((d ne 10) and (d ne 3) and (d > 0)) | ||
# code for handling any other positive value for d | |||
if (( | |||
In checking the condition of | In checking the condition of "d" in this example, we have to keep checking after the first statement whether our previous conditions were not true; if we didn't, 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 | if (d eq 10) | ||
# special-case code for d being 10 | |||
else if (d eq | else if (d eq 3) | ||
# special-case code for d being 3 | |||
else if ( | else if (d > 0) | ||
# code for handling any other positive value for d | |||
Thanks to the "else" keyword, "d" is implicitly guaranteed to not be 10 if statement 2's code is being run, and to not be 10 or 3 if statement 3's code is being run. | |||
===Flow interrupt=== | ===Flow interrupt=== | ||
Use | Use the keywords below to interrupt the execution of BSL. Note that Bungie West also frequently used these built-in commands to halt the execution of a script until the current or previous command finished: [[chr_animate_block]], [[chr_envanim_block]], [[chr_playback_block]], [[cm_anim_block]], [[cm_interpolate_block]], [[cm_orbit_block]], [[env_anim_block]], [[env_setanim_block]], [[sound_dialog_play_block]]. | ||
====return==== | ====return==== | ||
| Line 557: | Line 558: | ||
===Defining=== | ===Defining=== | ||
When beginning to define a function, you must | When beginning to define a function, you must start the line with "func"... | ||
func int prepare_fight(int count, string enemy_name) | func int prepare_fight(int count, string enemy_name) | ||
| Line 585: | Line 586: | ||
===Calling=== | ===Calling=== | ||
When you call a function it will execute its statements from top to bottom or until it hits a "[[#Returning|return]]" statement. This means that when you call a second function from inside another function, the second function will be executed until its end and then the rest of the first function will be executed. | When you call a function it will execute its statements from top to bottom or until it hits a "[[#Returning|return]]" statement. This means that when you call a second function from inside another function, the second function will be executed until its end and then the rest of the first function will be executed. You can think of the function calls as nested parentheses, where the innermost statement must close before the next-innermost level can close, and so on. (For example, here is a parenthetical statement (and another one (and one more!) inside of it)). | ||
You do not use "func" when calling a function. You simply use its name: | You do not use "func" when calling a function. You simply use its name: | ||
| Line 604: | Line 605: | ||
===Recursive calling=== | ===Recursive calling=== | ||
A function can call itself | A function can call itself by invoking its own name: | ||
var int global_iterator = 0; | |||
func void bad_loop_idea(void) | |||
{ | |||
global_iterator = global_iterator + 1 | |||
dmsg("Calling myself..."); | |||
bad_loop_idea(); | |||
} | |||
This might seem useful for producing a loop, but a function can only call itself recursively about four times before you hit a limit in BSL and the script fails silently. Open a level with the above code placed in its level script and call bad_loop_idea() from the dev console. After you stop seeing the "Calling myself..." messages, run the command "global_iterator" to query its value and you'll probably get "5", meaning the recursion failed between the fourth and fifth levels. | |||
This limit can be bypassed if you use "[[#fork|fork]]" when calling a function from within itself, though this is not technically recursing: the logic will not complete in a predictable inside-to-outside order like our nested parentheses example in the previous section; instead it will run concurrently through all of the forked function calls. This can be very bad if you're trying to get reliable results, however there are ways to mitigate this. For a discussion of looping reliably with "fork", see {{SectionLink||Looping}}. | |||
===Returning=== | ===Returning=== | ||
As mentioned under {{SectionLink||Flow interrupt}}, "return" | As mentioned under {{SectionLink||Flow interrupt}}, "return" allows you to exit the function at that point. But because of the bug documented in that section you cannot use "return" conditionally, that is, exit early from a function depending on some logical condition ("if"). Since "return" can thus only be placed at the end of a function, there is no need to use it at all unless you are passing back a value with it like so: | ||
func int add_ten(int input) | func int add_ten(int input) | ||
| Line 615: | Line 628: | ||
} | } | ||
When a function returns a value, you can use the function to set a variable as follows: | |||
some_number = add_ten(enemy_count); | some_number = add_ten(enemy_count); | ||
Function return values can also be used in "if" statements | Function return values can also be used in "if" statements. If you have a function that returns a bool... | ||
func bool is_it_safe(void) | func bool is_it_safe(void) | ||
| Line 630: | Line 643: | ||
...then it could be called this way in some other function: | ...then it could be called this way in some other function: | ||
if (is_it_safe()) | if (is_it_safe() eq true) | ||
dprint("It is safe."); | dprint("It is safe."); | ||
else | else | ||
dprint("It is not safe."); | dprint("It is not safe."); | ||
Bizarre bug warning: Calling dprint() and dmsg() more than once within a function will produce unexpected results. One of the dprint()/dmsg() calls may become the return value for the function (even if it it's type "void"), or create a return value of 0, and more than two calls also might be ignored. | |||
===Looping=== | ===Looping=== | ||
| Line 653: | Line 668: | ||
} | } | ||
Note that calling the same function again before it finishes running the first time can have undesired effects. Using "sleep" before each call can prevent this overlapping execution. A short function like the example above should not need a "sleep" statement | Note that calling the same function again before it finishes running the first time can have undesired effects. Imagine if you have another thread in the code accessing "counter" while increment() is still running in a loop; what value will it have at that moment? Real-world scripting scenarios will be more complex and thus more problematic if multiple concurrent calls to a function are in play at the same time. Using "sleep" before each fork call can prevent this overlapping execution. A short function like the example above should not need a "sleep" statement as the remainder of the function after the "fork" call will complete in the same tick. But the more complex the function, the more ticks you will need to allow for it to complete, requiring "sleep(1);", "sleep(3);", etc. before the "fork" call. | ||
An alternate looping method: See the two variants of the "schedule" keyword under "Concurrency" below. | |||
===Concurrency=== | ===Concurrency=== | ||
| Line 680: | Line 697: | ||
} | } | ||
As you can see, | As you can see, fork-called functions can delay actions within themselves without holding up the rest of the script; this not only allows the use of "sleep" without affecting outside functions, but also any built-in functions that hold up BSL by design, like [[chr_wait_animation]]. | ||
Be aware that accidentally calling a nonexistent function using "fork" will crash Oni; when "fork" is not used, BSL will successfully recognize it as an unknown function and thus will not attempt to call it. | |||
====schedule ... at ...==== | ====schedule ... at ...==== | ||
| Line 703: | Line 718: | ||
schedule dprint("Is this annoying yet?") repeat 50 every 20; | schedule dprint("Is this annoying yet?") repeat 50 every 20; | ||
This is equivalent to the following loop in a C-style | This is equivalent to the following loop in a C-style language: | ||
int i = 0; | int i = 0; | ||
| Line 717: | Line 732: | ||
schedule dprint("Is this annoying yet?") repeat 0 every 20; # repeats forever | schedule dprint("Is this annoying yet?") repeat 0 every 20; # repeats forever | ||
"schedule-repeat-every" can be used in place of recursive functions that call themselves ''n'' times using "fork". For | "schedule-repeat-every" can be used in place of recursive functions that call themselves ''n'' times using "fork". For example… | ||
func void hey(void) | func void hey(void) | ||
| Line 731: | Line 746: | ||
} | } | ||
…can be replaced by… | |||
func void hey(void) | func void hey(void) | ||