Visit the
Tutorials forum


 
 
Warrior KitPack
 
 
 
 

Modding Section / Tutorials and Articles / AND & OR in scripting

AND & OR IN SCRIPTING

by Vlasak

 

This is rather the compilation of my posts from Black Wyrm Lair, Pocket Plane and Czech modding forums than tutorial. It provides the method how to simulate OR operators and nested ANDs in various Infinity games.

First of all, a small recapitulation of how the script proceeds (for the detailed explaining, see SimDing0's Complete Scripting Guide).
The script is parsed from the top. When the triggers of one block are evaluated as true, the actions of the block are performed and the parsing restarts from the top (unless there is the Continue() action in actions of the block - in this case, the parsing continues to the next block).
When the trigger of one block is evaluated as false, the parsing skips to the next block.
When it reaches the bottom of script (last block), it starts from the top again.

 

Simulating of OR in Baldur's Gate I

It is known that there is no OR in BG1. It is also well-known how solve this situation - divide the blocks with n triggers in OR relation into n separate blocks - each with one trigger which would be in OR.

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
OR(4)
Class(Player1,FIGHTER_ALL)
Class(Player1,PALADIN_ALL)
Class(Player1,RANGER_ALL)
Class(Player1,CLERIC_ALL)
THEN
RESPONSE #100
Action()
END

Previous block have to be divided into four blocks in BG1

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
Class(Player1,FIGHTER_ALL)
THEN
RESPONSE #100
Action()
END

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
Class(Player1,PALADIN_ALL)
THEN
RESPONSE #100
Action()
END

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
Class(Player1,RANGER_ALL)
THEN
RESPONSE #100
Action()
END

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
Class(Player1,CLERIC_ALL)
THEN
RESPONSE #100
Action()
END

However, there is another way how to accomplish this. The way is based on deMorgan's theorems from Boolean algebra. This way it is possible to simulate OR operator by fixed amount of block - this amount is three blocks regardless of the number of triggers in OR relation.

According deMorgan's theorem we can negate OR(n) formula.

With Trigger1 OR Trigger2 OR ... OR TriggerN we have:

!Trigger1 AND !Trigger2 AND ... AND !TriggerN

Ok, but what advantage is hidden in it?

We can catch by this negated OR formula the cases when our original OR formula is not evaluated as true. This trick is the core of this method, so some examples how to imagine its logic:

Let's compare:

"If the enemy is a dwarf or an elf, I'll do something."
-
"If the enemy is not a dwarf as well as an elf (neither a dwarf, nor an elf), I'll do something else (or maybe nothing)".

First block's triggers is true if the enemy is a dwarf or an elf. Second block's triggers is true if the enemy is neither dwarf nor elf - half-elf for example.

"If the variable has the value 2 or 4, I'll do something."
-
"If the variable has not the value 2 and 4, I'll do something else (or maybe nothing)." - the variable could have the value 0, 3, 1000,... - this condition is true in these cases (because of !(var=2 or var=4) ); in case when the variable has the value 2 (or 4), this condition is false.

With this logic we can transform following block with OR to the blocks with the same result but without OR.

//this is the original OR block (can be coded in BG2, cannot be coded in BG1)
IF
OR(3)
Trigger1
Trigger2
Trigger3
THEN
RESPONSE #100
Action()
END

The block will be transformed to:

IF
!Trigger1
!Trigger2
!Trigger3
THEN
RESPONSE #100
NoAction()
END

If all triggers are evaluated as true it is the case when my original OR would be evaluated as false, so this block catches this case - the case what is a negation of what we want to do. From the parsing logic of the script we know that when the triggers are evaluated as true, the script restarts from the top. Here, when my original OR would be false, this block is true so the cycling of the script occurs. But when my original OR can be evaluated as true (at least one trigger is true in it), this block would be false (one or more trigger in this AND is not true -> whole AND is not true) and the script continues to the next block which looks like this:

IF
True()
THEN
RESPONSE #100
MyAction() //actions which I want to be performed in my original OR block
END

So it is clear that these two blocks simulate the OR operator.

However, it is just a rough form. It can be used just in the case when the script would be made by these two blocks only. Because of one of them is always true, they make the cycle and other block after them would be cut off from the proceeding of the script.
For the complete simulation of OR usable anywhere we'll need the third block and one local variable.

Following three blocks represent the correct behavior of my original OR block - if the OR block would be performed, the script will restart; if the OR block would not be performed, the script continues to next block...

Local variable "OR" -
1: my original OR block would not be performed (it would be false)
0: my original OR block would be performed (it would be true)

// "OR" variable must be set to 0 - we don't know whether original OR block would be performed and initially we must expect both cases

IF
GlobalGT("OR","LOCALS",0) // "OR" is greater than 0
THEN
RESPONSE #100
SetGlobal("OR","LOCALS",0) // "OR" is set to 0
Continue() // script continues to the next block !!!
END

IF
!Trigger1
!Trigger2
!Trigger3
THEN
RESPONSE #100
SetGlobal("OR","LOCALS",1) // original OR would be false
Continue() // script continues to the next block according to the script parsing rules (original OR would not be performed, the script continues to the next block)
END

IF
!GlobalGT("OR","LOCALS",0) // "OR" is not greater than 0, this block is performed. If "OR" is greater than 0, this block is not performed and the script continues to the next block
THEN
RESPONSE #100
MyAction() // the actions that we want to be performed
//no Continue() unless we explicitly want to. The original OR block would be performed, so the script should restart
END

 

We can return to the beginning of this article. The block:

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
OR(4)
Class(Player1,FIGHTER_ALL)
Class(Player1,PALADIN_ALL)
Class(Player1,RANGER_ALL)
Class(Player1,CLERIC_ALL)
THEN
RESPONSE #100
Action()
END


will look like this:

IF
GlobalGT("OR","LOCALS",0)
THEN
RESPONSE #100
SetGlobal("OR","LOCALS",0)
Continue()
END

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
!Class(Player1,FIGHTER_ALL)
!Class(Player1,PALADIN_ALL)
!Class(Player1,RANGER_ALL)
!Class(Player1,CLERIC_ALL)
THEN
RESPONSE #100
SetGlobal("OR","LOCALS",1)
Continue()
END

IF
Race(Player1,ELF)
Global("Variable","GLOBAL",1)
!GlobalGT("OR","LOCALS",0)
THEN
RESPONSE #100
Action()
END

With possible more triggers in OR, this construction will have always three blocks regardless of the number of triggers in OR.

 

Simulating of AND in IE scripts.

(Trigger1 AND Trigger2) OR (Trigger3 AND Trigger4)

AND is implicit in triggers and no single AND(n) as OR(n) doesn't exist. But there is a three block solution that can simulate various Sum of Products boolean formulas ( (...AND...AND...AND...) OR (...AND...AND...AND...) OR (...AND...AND...AND...) OR...)

I wanted do this two years ago - I tried to simulate AND by !OR() and it didn't work.But it can be accomplished in the following way...

The principle - make the negation of: (Trigger1 AND Trigger2) OR (Trigger3 AND Trigger4) - according deMorgan we have:
(!Trigger1 OR !Trigger2) AND (!Trigger3 Or !Trigger4). This negation can be coded in IE script block.

The action of that block must be the reverse of the original action - or just NoAction() - "I want to do something if condition (Trigger1 AND Trigger2) OR (Trigger3 AND Trigger4) is true and do nothing when it is not true".
There is the second block which follow after this. This block has its condition always true and its purpose is to perform the action that we want to perform.

Because of the proccesing of scripts the following two possibilites occurs:
- the first block condition is true (="Oposition of my original requirements are fulfiled"="My original requirements are not fulfiled") - NoAction (or reverse action) is performed and the script restarts from its beginning. So the second "always true" block is not performed. So I "catch" the undesired cases.
- the first block's condition is false (="Oposition of my original requirements are not fulfiled"="My original requirements are fulfiled") - script skips to second block and it is performed then...

IF
OR(2)
!Trigger1
!Trigger2
OR(2)
!Trigger3
!Trigger4
THEN
RESPONSE #100
NoAction()
END

IF
True()
THEN
RESPONSE #100
Action()
END

Well, this solution is rough, however - the rest of the script after the mentioned second block is always cut off from the script procceeding due to second block's always true condition. I can add there Continue() action that ensures the continue of the script proceeding. But by this I'll break a little the standart rules of the script proceeding (when the block is performed, the script restarts).

So, I'll add "AND" local variable - its values meaning:
- 0: my desired action can be performed
- 1: my desired action cannot be performed due to the fact that the opposite conditions are true

IF
GlobalGT("AND",LOCALS",0) //if "AND" is greater than 0
THEN
RESPONSE #100
SetGlobal("AND","LOCALS",0) // reset to 0
Continue() // I want to continue in the script
END

IF
OR(2)
!Trigger1
!Trigger2
OR(2)
!Trigger3
!Trigger4
THEN
RESPONSE #100
SetGlobal("AND","LOCALS",1) //My desired action cannot be performed (opposite conditions are true), so AND=1
Continue() //!! I want to continue in script because in my imaginary "(Trigger1 AND Trigger2) OR (Trigger3 AND Trigger4)" block I have now the case when its conditions are not true and the script continues to following block in these cases
END

IF
!GlobalGT("AND","LOCALS",0) // "AND" is not greater than 0
THEN
RESPONSE #100
Action() //perform desired action
//no Continue() - if this block is performed, the script restarts as it is standart.
END

 

 
   
  Baldur's Gate, Tales of the Sword Coast, Baldur's Gate II: Shadows of Amn and Throne of Bhaal are © Bioware Corp. Icewind Dale I and II, Heart of Winter and Planescape: Torment are © Interplay Productions. Dungeons & Dragons material is © Wizards of the Coast. All original content is © Black Wyrm Group. Copying or reproducing of any part of this site without the express permission of the Black Wyrm Group is strictly prohibited.