Jump to content

Short questions about scripting for efficiency and reliability


Recommended Posts

Posted

I have a few questions that are pretty much explained by the title of the thread.

It's difficult to learn what is best practice when I can see, for example, that the makers of the game have written the same checks in different ways on different occasions.

It's getting to the stage that re-writing my scripts to improve reliability and efficiency will be a massive pain in the arse, so I'd like to make sure what I write in future is up to scratch first time around.

So I would be very grateful if anyone can give me a few pointers.

(1) What is good coding practice for BALDUR.BCS and scripts such as area scripts? Are the below assumptions correct?

(a) Adding large numbers of IF checks is the worst thing one can do;
(b) Single IF checks with multiple conditions are generally better than multiple IF checks;
(c) Long response blocks are better than multiple IF blocks;
(d) Response blocks that are not executed in a script cycle don't affect the processing of other blocks in the script cycle.

(2) At what stage is better to set a variable in a block, and check for that variable in a subsequent block, instead of adding more AND or OR conditions to that first block?

(3) The two 3-line snippets below do the same thing. Which is better, and why?

Spoiler

Option A:
OR(2)
    Global("BK_Dream","GLOBAL",2)
    Global("BK_Dream","GLOBAL",3)

Option B:
GlobalGT("BK_Dream","GLOBAL",1)
GlobalLT("BK_Dream","GLOBAL",4)

(4) The below variable is only meant to have values of "0" or "1". Which of the three 1-line code snippets below is better, and why?

Spoiler

Option A:
Global("MetVai","GLOBAL",1)

Option B:
!Global("MetVai","GLOBAL",0)

Option 😄
GlobalGT("MetVai","GLOBAL",0)

(5) The below variable can have a number of different values above "1". Which of the two 1-line code snippets below is better, and why?

Spoiler

Option A:
GlobalGT("JoinedBandits","GLOBAL",0)

Option B:
!Global("JoinedBandits","GLOBAL",0)

(6) At what point does adding redundant checks become unhelpful? The spoilered code is a snippet from AR3300.BCS:

Spoiler

IF
    Global("Chapter","GLOBAL",3)
    Global("ChloeSpawn","GLOBAL",0)
    !Exists("Chloe")  // Chloe
    !Dead("Chloe")  // Chloe
THEN
    RESPONSE #100
        SetGlobal("ChloeSpawn","GLOBAL",1)
        CreateCreature("CHLOE",[4127.2881],S)  // Chloe
        Continue()
END

Aren't the lines !Exists("Chloe") and !Dead("Chloe") redundant because SetGlobal("ChloeSpawn","GLOBAL",1) makes sure Chloe is created only once? Some scripts in the vanilla game include these redundant checks. But other scripts check only the specific variable relating to the spawing of that creature (in the same way this block has the "ChloeSpawn" GLOBAL at 0 or 1). So I get that !Exists("Chloe") and !Dead("Chloe") help maybe if the above response block skips over the line SetGlobal("ChloeSpawn","GLOBAL",1). But doesn't the addition of those redundant checks just make it more likely that other code blocks might skip?

Thanks in advance for any answers.

Posted (edited)

About your performance questions: I've heard on pretty good authority that you shouldn't worry about putting load on the script compiler. For most things you could care to try, OR(2) vs. GT-LT doesn't make a difference. Just write what you need. But I prefer to run my script actions from scripts of invisible minions that I summon for the occasion. This doesn't clutter BALDUR.BCS, and for performance, I can say that it takes a minion, say, one fifth of a second to process 30 trigger lines. So there is a small delay, in addition to the delay before the minion appears and starts processing the script. Sometimes even in the same script it makes sense to separate code into blocks to help the engine out. I've noticed that setting verbal constant lines for creatures (if you want to make an NPC mute and erase the string references for his right-click responses, yeses, insults/compliments etc., or give him a different voice) cause the engine to halt unless there is a break after the first 20 or so of these commands. The block needs to be ended with Continue(), and the rest of these actions put in the block below. But this is the only case I know when the engine really can't handle too much code... although I have never piled hundreds and thousands of lines on it.

There are a few ways to speed scripts up a little. One possibility is to put the least likely conditions on top of the block. If, say, I'm checking for the death of Xzar and then somebody's gender, a variable and so on, I put Dead("xzar") on top. If that one doesn't return true, the compiler moves along. Another way is to prepare a replacement script for when the original is going to run only once. When you have selected some mode of action for this minion after a long list of triggers, switch it to a short script with ChangeAIScript("SCRIPT",OVERRIDE), where there will be only the one or two blocks that you need. 

As for BALDUR, I only put blocks there that must be triggerable everywhere or that kick off my mods, i.e. I check a global, apply a custom spell to Player1 that gives the current party new spells and abilities, then set the global. This lets me avoid requiring that players start a new game for their characters to experience those changes. Every block added to BALDUR must end with Continue(), of course. And about that question in the end: !Exists and !Dead are both unnecessary, you are right, unless this Chloe has appeared somewhere before and has been moved to this area with one of the area-traveling actions without setting a global or has been killed elsewhere.

Edited by temnix
Posted (edited)

Cheers for your answer; much appreciated. Everything noted, even if not commented on below (some stuff I've seen before).

On 6/1/2021 at 3:15 PM, temnix said:

About your performance questions: I've heard on pretty good authority that you shouldn't worry about putting load on the script compiler. For most things you could care to try, OR(2) vs. GT-LT doesn't make a difference. Just write what you need.

Awesome. So the minor points about differing language for the same checks are effectively irrelevant as far as performance issues are concerned.

On 6/1/2021 at 3:15 PM, temnix said:

One possibility is to put the least likely conditions on top of the block. If, say, I'm checking for the death of Xzar and then somebody's gender, a variable and so on, I put Dead("xzar") on top. If that one doesn't return true, the compiler moves along.

This is EXACTLY the kind of thing I was looking for. Thank you. I've read that scripts are processed top-down, but I've not seen descriptions of what happens within a block after the IF is detected. After all, in dialog, state triggers are checked top-down, but transitions are evaluated bottom-up, so I figured who knows what happens after the IF?!

To extrapolate from your answer, it is correct, then, that long response blocks that are not activated don't register with the compiler? I found the Switch trigger exceptionally useful for managing chapter change with my flexible plot (where what happens depend on the value of the current chapter), but it does lead to relatively long response blocks.

On 6/1/2021 at 3:15 PM, temnix said:

But I prefer to run my script actions from scripts of invisible minions that I summon for the occasion. This doesn't clutter BALDUR.BCS, and for performance, I can say that it takes a minion, say, one fifth of a second to process 30 trigger lines. So there is a small delay, in addition to the delay before the minion appears and starts processing the script. Sometimes even in the same script it makes sense to separate code into blocks to help the engine out.

I understand the necessity of putting some blocks in BALDUR.BCS, and the reasons to avoid it if possible. Equally, the utility of invisi-creatures with scripts ending in DestroySelf() or a change to a low-maintenance script is fairly obvious. What I'm less clear on is when one decides that an area script is the best home for a particular block.

I have the suspicion that OnCreation() in an area script would serve to process script quickest, but I feel that I shouldn't put my trust in that because (if I understand correctly) I'm depending on everyone who uses EXTEND_TOP after me to end their blocks with Continue().

On 6/1/2021 at 3:15 PM, temnix said:

And about that question in the end: !Exists and !Dead are both unnecessary, you are right, unless this Chloe has appeared somewhere before and has been moved to this area with one of the area-traveling actions without setting a global or has been killed elsewhere.

Won't happen in the vanilla game. She's the little girl who directs GW to Officer Vai in the Jovial Juggler for the Vai's Bounty Upon Bandits quest, and she's only ever in Beregost. I guess it's worth bearing in mind for mod-conflicts though. It's just weird to see these checks applied unevenly to vanilla content that is only ever spawned in the same place. Officer Vai doesn't even have her own "VaiSpawn" variable; the block that creates her checks only for !Exists and !Dead (well, and Chapter 3).

Edited by The_Baffled_King

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...