|
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
|