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.
CODE
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
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
CODE
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
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.
CODE
//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
IF
OR(3)
Trigger1
Trigger2
Trigger3
THEN
RESPONSE #100
Action()
END
The block will be transformed to:
CODE
IF
!Trigger1
!Trigger2
!Trigger3
THEN
RESPONSE #100
NoAction()
END
!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:
CODE
IF
True()
THEN
RESPONSE #100
MyAction() //actions which I want to be performed in my original OR block
END
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)
CODE
// "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
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 beggining of this article. The block:
CODE
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
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:
CODE
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
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...
CODE
IF
OR(2)
!Trigger1
!Trigger2
OR(2)
!Trigger3
!Trigger4
THEN
RESPONSE #100
NoAction()
END
IF
True()
THEN
RESPONSE #100
Action()
END
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
CODE
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
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