Jump to content
DavidW

Coding scripts in SSL: some lessons

Recommended Posts

When I try to compile ssl scripts from a .bat file run from the main game directory I am unable to specify a directory if ssl.exe is located somewhere else relative to the .bat file in the manner of:

LCTests/SSL/ssl.exe "LCTests/SSL/testactions.ssl LCTests/SSL/testssl.ssl -l LCTests/SSL/testlib.slb IsLich=True&CreType=Undead"

I get the message "LCTests is not recognized as an internal or external command"

 

I do manage to compile them if ssl.exe is located in the same folder as the .bat file and use the following instead.

ssl.exe "LCTests/SSL/testactions.ssl LCTests/SSL/testssl.ssl -l LCTests/SSL/testlib.slb IsLich=True&CreType=Undead"

Is this intentional or is it an oversight? I guess one could either COPY ssl.exe to the main (game) directory or generate the .bat file in the folder where ssl.exe is located.

 

EDIT: Using AT_NOW in the manner below works:

AT_NOW ~LCTests/SSL/ssl.exe "LCTests/SSL/testactions.ssl LCTests/SSL/testssl.ssl -l LCTests/SSL/testlib.slb IsLich=True&CreType=Undead"~
Edited by Galactygon

Share this post


Link to post

I would be entirely unsurprised if bat files only worked with backslashes.

Share this post


Link to post

I went ahead and wrote a function that can handle .ssl => .baf => .bcs compilation in one go. The modder has to specify the following 3 things:

1. The path of SSL.exe (default is %MOD_FOLDER%/SSL/ssl.exe)

2. The name of the array with a list of .ssl libraries with their file paths specified (valid format either .slb or .ssl). Default value is "SSL_LIB".

3. The name of the associative array with of individual .ssl files compiled as .bcs files in the override folder. Default value is "SSL_LIST".

4. (optional) Any ssl variables can be defined in STR_VAR ssl_variables. Default value is "".

 

All filepaths are relative to Setup-MyMod.exe, including any INCLUDE() syntaxes within any of the .ssl files. The code generates a valid list of .ssl files and libraries, runs ssl.exe, and compiles the resulting .baf files into the override folder.

 

See the full code below with an example application:

// Compile a list of .ssl files into .baf format and then proceed to .bcs format in the override folder
 
// Example usage:
//ACTION_DEFINE_ARRAY "SSL_LIB" BEGIN
  // list of .slb files
//  "%MOD_FOLDER%/SSL/testlib"
  // list of .ssl files (i.e. to define actions)
//  "%MOD_FOLDER%/SSL/testactions"
//END
//ACTION_DEFINE_ASSOCIATIVE_ARRAY "SSL_LIST" BEGIN
  // Compiles test1.ssl into override/testssl.bcs
//  "%MOD_FOLDER%/SSL/test1.ssl" => "testssl"
  // Compiles test2.ssl into override/testtwo.bcs
//  "%MOD_FOLDER%/SSL/test2" => "testtwo.bcs"
  // Compiles test3.ssl into %MOD_FOLDER%/SSL/ssl_out/test3.baf without compiling into the override as a .bcs
  // useful for patching (i.e. EXTEND_TOP)
//  "%MOD_FOLDER%/SSL/test3" => ""
//END
//LAF "SSL"
//  STR_VAR  ssl_lib = "SSL_LIB" // List of arrays
//    ssl_files = "SSL_LIST" // List of associative arrays
//    ssl_exe = EVAL "%MOD_FOLDER%/SSL/ssl.exe" // directory to where ssl.exe is located
//END
 
DEFINE_ACTION_FUNCTION "SSL"
  STR_VAR  ssl_lib = "SSL_LIB" // name of the array array (default: "SSL_LIB")
    ssl_files = "SSL_LIST" // name of the associative array (default: "SSL_LIST")
    ssl_variables = "" // List of variables
    ssl_exe = EVAL "%MOD_FOLDER%/SSL/ssl.exe" // directory to where ssl.exe is located (default is in an ssl folder within %MOD_FOLDER%)
BEGIN
  OUTER_SPRINT ssl_execute_ssl ""
  OUTER_SPRINT ssl_execute_lib ""
  ACTION_IF "%ssl_variables%" STRING_CONTAINS_REGEXP "[0-9A-Za-z]+=[0-9A-Za-z]+" = 0 BEGIN
    OUTER_PATCH_SAVE ssl_variables "%ssl_variables%" BEGIN
      REPLACE_TEXTUALLY "[ %TAB%,;|]*=[ %TAB%,;|]*" "="
      REPLACE_TEXTUALLY "[ %TAB%,;|]*" "&"
      REPLACE_TEXTUALLY "^&" ""
      REPLACE_TEXTUALLY "&$" ""
    END
    OUTER_SPRINT ssl_variables " %ssl_variables%"
  END ELSE BEGIN
    OUTER_SPRINT ssl_variables ""
  END
 
  ACTION_IF FILE_EXISTS "%ssl_exe%" AND "%ssl_exe%" STRING_MATCHES_REGEXP "^.*ssl\.exe$" = 0 BEGIN // If ssl.exe exists
    // Record information
    ACTION_PHP_EACH "%ssl_lib%" AS ssl_lib_index => ssl_lib_src BEGIN
      // Add .slb or .ssl at the end if no file extension is given (attempt to find .slb file first then do the same for .ssl)
      ACTION_IF NOT "%ssl_lib_src%" STRING_MATCHES_REGEXP "^.+\.[0-9A-Za-z]+$" = 0 AND NOT "%ssl_lib_src%" STRING_MATCHES_REGEXP "^.+\.\(slb\|ssl\)$" = 0 BEGIN
        ACTION_IF FILE_EXISTS "%ssl_lib_src%.slb" BEGIN
          OUTER_SPRINT ssl_lib_src "%ssl_lib_src%.slb"
        END ELSE
        ACTION_IF FILE_EXISTS "%ssl_lib_src%.ssl" BEGIN
          OUTER_SPRINT ssl_lib_src "%ssl_lib_src%.ssl"
        END
      END
      ACTION_IF FILE_EXISTS "%ssl_lib_src%" AND "%ssl_lib_src%" STRING_MATCHES_REGEXP "^.+\.slb$" = 0 BEGIN // If valid .slb file
        // Append %ssl_execute_lib% with file path
        ACTION_IF "%ssl_execute_lib%" STRING_EQUAL_CASE "" BEGIN // If first item in list
          OUTER_SPRINT ssl_execute_lib "%ssl_lib_src%"
        END ELSE BEGIN // If not first item in list
          OUTER_SPRINT ssl_execute_lib "%ssl_lib_src% %ssl_execute_lib%"
        END
      END
      ACTION_IF FILE_EXISTS "%ssl_lib_src%" AND "%ssl_lib_src%" STRING_MATCHES_REGEXP "^.+\.ssl$" = 0 BEGIN // If valid .ssl file
        // Append %ssl_execute_ssl% with file path
        ACTION_IF "%ssl_execute_ssl%" STRING_EQUAL_CASE "" BEGIN // If first item in list
          OUTER_SPRINT ssl_execute_ssl "%ssl_lib_src%"
        END ELSE BEGIN // If not first item in list
          OUTER_SPRINT ssl_execute_ssl "%ssl_execute_ssl% %ssl_lib_src%"
        END
        // Make directory "ssl_out" in same folder as .ssl file & copy blank .baf file to destination
        OUTER_PATCH_SAVE ssl_out "%ssl_lib_src%" BEGIN
          REPLACE_TEXTUALLY "^\(.+\)/[^/]+\.ssl$" "\1/ssl_out"
        END
        MKDIR ~%ssl_out%/~
        OUTER_PATCH_SAVE ssl_out_res "%ssl_lib_src%" BEGIN
          REPLACE_TEXTUALLY "^\(.+\)/\([^/]+\)\.ssl$" "\2"
        END
<<<<<<<< .../%MOD_FOLDER%-Inlined/blank.txt
>>>>>>>>
        COPY ".../%MOD_FOLDER%-Inlined/blank.txt" "%ssl_out%/%ssl_out_res%.baf"
      END
    END
    ACTION_PHP_EACH "%ssl_files%" AS ssl_src => ssl_dest BEGIN
      // Add .ssl at the end if no file extension is given
      ACTION_IF NOT "%ssl_src%" STRING_MATCHES_REGEXP "^.+\.[0-9A-Za-z]+$" = 0 AND NOT "%ssl_src%" STRING_MATCHES_REGEXP "^.+\.ssl$" = 0 BEGIN
        OUTER_SPRINT ssl_src "%ssl_src%.ssl"
      END
      ACTION_IF FILE_EXISTS EVAL "%ssl_src%" AND "%ssl_src%" STRING_MATCHES_REGEXP "^.+\.ssl$" = 0 BEGIN // If valid .ssl file
        // Append %ssl_execute_ssl% with file path
        ACTION_IF "%ssl_execute_ssl%" STRING_EQUAL_CASE "" BEGIN // If first item in list
          OUTER_SPRINT ssl_execute_ssl "%ssl_src%"
        END ELSE BEGIN // If not first item in list
          OUTER_SPRINT ssl_execute_ssl "%ssl_execute_ssl% %ssl_src%"
        END
        // Make directory "ssl_out" in same folder as .ssl file & copy blank .baf file to destination
        OUTER_PATCH_SAVE ssl_out "%ssl_src%" BEGIN
          REPLACE_TEXTUALLY "^\(.+\)/[^/]+\.ssl$" "\1/ssl_out"
        END
        MKDIR ~%ssl_out%/~
        OUTER_PATCH_SAVE ssl_out_res "%ssl_src%" BEGIN
          REPLACE_TEXTUALLY "^\(.+\)/\([^/]+\)\.ssl$" "\2"
        END
<<<<<<<< .../%MOD_FOLDER%-Inlined/blank.txt
>>>>>>>>
        COPY ".../%MOD_FOLDER%-Inlined/blank.txt" "%ssl_out%/%ssl_out_res%.baf"
      END
    END
 
    // Launch ssl.exe if there are valid library and ssl files & generate .baf files
    ACTION_IF NOT "%ssl_execute_ssl%" STRING_EQUAL_CASE "" AND NOT "%ssl_execute_lib%" STRING_EQUAL_CASE "" BEGIN
      AT_NOW ~%ssl_exe% "%ssl_execute_ssl% -l %ssl_execute_lib%%ssl_variables%"~
    END
    
    // Compile generated .baf files
    ACTION_PHP_EACH "%ssl_files%" AS ssl_src => ssl_dest BEGIN
      // Get ssl_out/xxx.baf path
      // Add .ssl at the end if no file extension is given
      ACTION_IF NOT "%ssl_src%" STRING_MATCHES_REGEXP "^.+\.[0-9A-Za-z]+$" = 0 AND NOT "%ssl_src%" STRING_MATCHES_REGEXP "^.+\.ssl$" = 0 BEGIN
        OUTER_SPRINT ssl_src "%ssl_src%.ssl"
      END
      // Get location of resulting .baf file
      OUTER_PATCH_SAVE ssl_out "%ssl_src%" BEGIN
        REPLACE_TEXTUALLY "^\(.+\)/[^/]+\.ssl$" "\1/ssl_out"
      END
      OUTER_PATCH_SAVE ssl_out_res "%ssl_src%" BEGIN
        REPLACE_TEXTUALLY "^\(.+\)/\([^/]+\)\.ssl$" "\2"
      END
      // Generate new .bcs if valid input at => "%ssl_dest%"
      ACTION_IF "%ssl_dest%" STRING_MATCHES_REGEXP "^[-_!@#a-zA-Z0-9]+\(\.baf\|\.bcs\)?$" = 0 BEGIN
        ACTION_IF FILE_EXISTS "%ssl_out%/%ssl_out_res%.baf" BEGIN
          // Record contents of resulting .baf file in %ssl_out_contents%
          COPY - "%ssl_out%/%ssl_out_res%.baf" ""
            READ_ASCII 0 ssl_out_contents (BUFFER_LENGTH)
 
          // truncate file extensions
          OUTER_PATCH_SAVE ssl_dest "%ssl_dest%" BEGIN
            REPLACE_TEXTUALLY "^\(.+\)\..+$" "\1"
          END
          // Generate temporary .baf file (in case original .ssl/.baf and destination .bcs resnames do not match)
<<<<<<<< .../%MOD_FOLDER%-Inlined/SSL_DEST/%ssl_dest%.baf
%ssl_out_contents%
>>>>>>>>
          COMPILE EVAL ".../%MOD_FOLDER%-Inlined/SSL_DEST/%ssl_dest%.baf"
        END
      END
    END
  END
END

Edited by Galactygon

Share this post


Link to post

Okay, I have NO idea what I'm doing wrong, but I just can't get this to work. I got this script, for a friendly cleric who wants to help the party:

BEGIN_ACTION_DEFINITION
Name(Heal_PC)
TRIGGER
!GlobalTimerNotExpired("heal_pc", "LOCALS")
HaveSpell(scsargument1)
Allegiance(Myself, NEUTRAL)
ACTION
RESPONSE #scsprob1
SetGlobalTimer("heal_pc", "LOCALS", ONE_ROUND)
Spell(scstarget, scsargument1)
END

IF TRIGGER
TargetBlock(PCsInOrder)
StateCheck(scstarget, STATE_POISONED)
THEN DO
Action(Heal_PC, CLERIC_NEUTRALIZE_POISON)
Action(Heal_PC, CLERIC_SLOW_POISON)
END

[...]

But when I try to compile it, I get this:

angel@phoenix /virtual/warbler/baldur $ perl ssl.pl mh#cath.ssl -l library.slb
This is Stratagems Scripting Language


 Input file is mh#cath.ssl
Unrecognised TargetBlock (PCsInOrder) near line 14 at ssl.pl line 252.

The library.slb file is an unaltered copy of the one in SCS (stratagems/ssl/library.slb) and it has the TARGET=PCsInOrder right there at line 300.

 

Does anyone have any idea what I am doing wrong? (By the way, the above is done on Linux, but I get the same on Windows as well.)

 

 

Btw, it also took me way too long to figure out BEGIN_ACTION_DEFINITION is supposed to have underscores, the tutorial makes it look like they are three separate words with spaces.

Share this post


Link to post

A quick note for the 2-3 people other than me who use SSL: the version of SSL that ships with SCS v32 has three changes from the previous version. Two of them are documented above (TRIGGER_REPLACE/TARGET_REPLACE and Booleans) though that documentation refers to ‘v31’. (In fact v31 came to be the maintenance release put together by Cam and others, and the version I was working on became v32). The third is that you can now use scsargumentN in trigger blocks and it’ll be correctly evaluated (previously evaluation occurred before the trigger was subbed in).

Share this post


Link to post
Posted (edited)

@DavidW

So, I keep getting the following error:

Unrecognised TriggerBlock: Attacked

Here's my .tp2 file:

ACTION_IF	!FILE_EXISTS_IN_GAME	~GTTEST.bcs~	BEGIN	// Sanity check
	AT_NOW ~chmod 755 %MOD_FOLDER%/ssl/ssl.pl~
	AT_NOW ~/usr/bin/perl	%MOD_FOLDER%/ssl/ssl.pl	%MOD_FOLDER%/components/test/GTTEST.ssl	-l %MOD_FOLDER%/ssl/library.slb~	EXACT
	COMPILE	~%MOD_FOLDER%/components/test/ssl_out~
END

Here's what library.slb contains:

TRIGGER = Attacked
	OR(2)
		AttackedBy([GOODCUTOFF],DEFAULT)
		SpellCastOnMe([GOODCUTOFF],0)

And here's GTTEST.ssl:

BEGIN_ACTION_DEFINITION
Name(Test)
TRIGGER
    HaveSpell(scsargument1)
    Global("gttest","GLOBAL",0)
ACTION
	RESPONSE #scsprob1
    	Spell(scstarget,scsargument1)
	SetGlobal("gttest","GLOBAL",1)
END


IF TRIGGER
	TriggerBlock(Attacked)
	Target(Protagonist)
THEN DO
	Action(Test,WIZARD_CHARM_PERSON)
END

Am I missing something? Why doesn't it recognize "Attacked" defined in library.slb?

It seems @Angel bumped into this issue too...

Moreover, there's a typo in all the examples provided in the first page: "#scsprob" should be "#scsprob1"

Edited by Luke

Share this post


Link to post

@DavidW, @Angel

OK, I found the culprit:

TRIGGER = Attacked

should be

TRIGGER=Attacked

I.e.: you do not need to put spaces between the keyword TRIGGER and its name 🙂

Share this post


Link to post
Posted (edited)

I have a question about Combine():

Is it possible to combine two actions so that certain triggers only appear in one of two final blocks (i.e., the two blocks containing LastSeenBy(Myself))? Example below:

Spoiler

IF
	// Possibly some other trigger(s)...
	See(FifthNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		NoAction()

IF
	// Possibly some other trigger(s)...
	See(FourthNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		NoAction()

IF
	// Possibly some other trigger(s)...
	See(ThirdNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		NoAction()

IF
	// Possibly some other trigger(s)...
	See(SecondNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		NoAction()

IF
	// Possibly some other trigger(s)...
	See(NearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		NoAction()

IF
	Allegiance(Myself,EVILCUTOFF)	// Only in this block
	Allegiance(LastSeenBy(Myself),GOODCUTOFF)	// Only in this block
	See(LastSeenBy(Myself))
THEN
	RESPONSE #100
		Attack(LastSeenBy(Myself))
END

IF
	Allegiance(Myself,GOODCUTOFF)	// Only in this block
	ActionListEmpty()	// Only in this block
	Allegiance(LastSeenBy(Myself),EVILCUTOFF)	// Only in this block
	See(LastSeenBy(Myself))
THEN
	RESPONSE #100
		Attack(LastSeenBy(Myself))
END

 

Moreover, could you provide a couple of lessons about ConditionalTargetBlock() and LOOP? If I'm not mistaken, the first one can be used to restrict the triggers specified in the second argument to the targets specified in the first argument. For instance:

IF TRIGGER
	ConditionalTargetBlock(PCsPreferringWeak;Gender(scstarget,MALE))
	TargetBlock(EnemiesInOrderShort)
	TriggerBlock(MR100|Enemy|SIConjuration)
THEN DO
	Action(Spell,WIZARD_FLAME_ARROW,50,SPELLFAILUREMAGE|100|50)
END

Both "PCsPreferringWeak" and "EnemiesInShortOrder" are subjected to

!CheckStatGT(scstarget,99,RESISTMAGIC)
Allegiance(Myself,ENEMY)
!Allegiance(scstarget(Myself),ENEMY)
!CheckStat(scstarget,2,WIZARD_SPELL_IMMUNITY)

whereas only "PCsPreferringWeak" are subjected to

Gender(scstarget,MALE)

Am I correct?

Edited by Luke

Share this post


Link to post

Combine() - no.

ConditionalTargetBlock() - you have it right.

LOOP - basically does multiple copies of a chunk of SSL code, with a different value of a variable each time. Look at SCS, there are quite a few examples.

Share this post


Link to post
Posted (edited)

I see, thank you for clarifying.

7 hours ago, DavidW said:

Combine() - no.

This means that I need to define two separate ACTIONs, right?

Alternatively – When dealing with ACTION blocks, can "scsargument" be a script trigger? For instance, I noticed you can do something like this

Spoiler

BEGIN_ACTION_DEFINITION
Name(Test)
TRIGGER
	scsargument1
ACTION
	RESPONSE #scsprob1
		Turn()
END

IF TRIGGER
THEN DO
	Action(Test,True())
END

 

but you cannot do this

Spoiler

BEGIN_ACTION_DEFINITION
Name(Test)
TRIGGER
	scsargument1
ACTION
	RESPONSE #scsprob1
		Turn()
END

IF TRIGGER
THEN DO
	Action(Test,Allegiance(Myself,GOODCUTOFF))
END

 

because "," (commas) are used to separate arguments, so the SSL compiler cannot resolve this comma => Allegiance(Myself,GOODCUTOFF). It basically thinks there's another argument after "Allegiance(Myself". The resulting BAF file is:

Spoiler

IF
	Allegiance(Myself
THEN
	RESPONSE #100
		Turn()
END

 

 

To tell the truth, I also noticed you cannot write

Spoiler

IF TRIGGER
THEN DO
	Action(Test,~~)
END

 

i.e., feed your ACTION with the empty string in case you don't need any additional trigger, so this is probably not the best solution...

Edited by Luke

Share this post


Link to post

I'd like to ask a question about an SSL block: the following two SSL blocks generate the same BAF file:

Spoiler

IF TRIGGER
	Target(NearestEnemyOf(Myself))
	!GlobalTimerNotExpired("Dispel","LOCALS")
THEN DO
	Action(Spell,WIZARD_DISPEL_MAGIC)
	SetGlobalTimer("Dispel","LOCALS",18)
END

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

IF TRIGGER
	!GlobalTimerNotExpired("Dispel","LOCALS")
	Target(NearestEnemyOf(Myself))
THEN DO
	Action(Spell,WIZARD_DISPEL_MAGIC)
	SetGlobalTimer("Dispel","LOCALS",18)
END

 

=> the triggers present in the ACTION "Spell" are the first ones, then comes "See(NearestEnemyOf(Myself)" (i.e., Target and TargetBlock()) and finally all the other triggers (in this case "!GlobalTimerNotExpired("Dispel","LOCALS")"), regardless of what comes first in the IF TRIGGER part. Is it intended?

Share this post


Link to post
Posted (edited)
On 7/28/2019 at 4:10 PM, DavidW said:

Combine() - no.

To tell the truth, it seems to be partially possible. This SSL block

Spoiler

BEGIN_ACTION_DEFINITION
	Name(Spell1)
	TRIGGER
    	!GlobalTimerNotExpired("gt_cast","LOCALS")
        HaveSpell(scsargument1)
        Allegiance(Myself,EVILCUTOFF)
		!StateCheck(Myself,STATE_SILENCED)
		CheckStatLT(Myself,scsargument2,scsargument3)
		Allegiance(scstarget,GOODCUTOFF)
	ACTION
		RESPONSE #scsprob1
			SetGlobalTimer("gt_cast","LOCALS",ONE_ROUND)
			Spell(scstarget,scsargument1)
END

BEGIN_ACTION_DEFINITION
	Name(Spell2)
	TRIGGER
		!GlobalTimerNotExpired("gt_cast","LOCALS")
        HaveSpell(scsargument1)
        Allegiance(Myself,GOODCUTOFF)
        ActionListEmpty()
		!StateCheck(Myself,STATE_SILENCED)
		CheckStatLT(Myself,scsargument2,scsargument3)
		Allegiance(scstarget,EVILCUTOFF)
		!InParty(scstarget)
	ACTION
		RESPONSE #scsprob1
			SetGlobalTimer("gt_cast","LOCALS",ONE_ROUND)
			Spell(scstarget,scsargument1)
END

IF TRIGGER
	TargetBlock(EnemiesInReverseOrder)
	TriggerBlock(MR50|SIConjuration)
	!StateCheck(scstarget,STATE_REALLY_DEAD)
THEN DO
	Combine()
	Action(Spell1,WIZARD_SPOOK,50,SPELLFAILUREMAGE)
	Action(Spell2,WIZARD_SPOOK,50,SPELLFAILUREMAGE)
END

 

generates the following BAF code:

Spoiler

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	CheckStatLT(NearestEnemyOf(Myself),50,RESISTMAGIC)
	!CheckStat(NearestEnemyOf(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(NearestEnemyOf(Myself),STATE_REALLY_DEAD)
	See(NearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	CheckStatLT(SecondNearestEnemyOf(Myself),50,RESISTMAGIC)
	!CheckStat(SecondNearestEnemyOf(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(SecondNearestEnemyOf(Myself),STATE_REALLY_DEAD)
	See(SecondNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	CheckStatLT(ThirdNearestEnemyOf(Myself),50,RESISTMAGIC)
	!CheckStat(ThirdNearestEnemyOf(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(ThirdNearestEnemyOf(Myself),STATE_REALLY_DEAD)
	See(ThirdNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	CheckStatLT(FourthNearestEnemyOf(Myself),50,RESISTMAGIC)
	!CheckStat(FourthNearestEnemyOf(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(FourthNearestEnemyOf(Myself),STATE_REALLY_DEAD)
	See(FourthNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	CheckStatLT(FifthNearestEnemyOf(Myself),50,RESISTMAGIC)
	!CheckStat(FifthNearestEnemyOf(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(FifthNearestEnemyOf(Myself),STATE_REALLY_DEAD)
	See(FifthNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	CheckStatLT(SixthNearestEnemyOf(Myself),50,RESISTMAGIC)
	!CheckStat(SixthNearestEnemyOf(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(SixthNearestEnemyOf(Myself),STATE_REALLY_DEAD)
	See(SixthNearestEnemyOf(Myself))
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	Allegiance(Myself,EVILCUTOFF)	// Only in this script block => intended?
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	Allegiance(LastSeenBy(Myself),GOODCUTOFF)	// Only in this script block => intended?
	CheckStatLT(LastSeenBy(Myself),50,RESISTMAGIC)
	!CheckStat(LastSeenBy(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(LastSeenBy(Myself),STATE_REALLY_DEAD)
	See(LastSeenBy(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("gt_cast","LOCALS",ONE_ROUND)
		Spell(LastSeenBy(Myself),WIZARD_SPOOK)  // SPWI125.SPL (Spook)
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	HaveSpell(WIZARD_SPOOK)  // SPWI125.SPL (Spook)
	Allegiance(Myself,GOODCUTOFF)	// Only in this script block => intended?
	ActionListEmpty()	// Only in this script block => intended?
	!StateCheck(Myself,STATE_SILENCED)
	CheckStatLT(Myself,50,SPELLFAILUREMAGE)
	Allegiance(LastSeenBy(Myself),EVILCUTOFF)	// Only in this script block => intended?
	!InParty(LastSeenBy(Myself))	// Only in this script block => intended?
	CheckStatLT(LastSeenBy(Myself),50,RESISTMAGIC)
	!CheckStat(LastSeenBy(Myself),2,WIZARD_SPELL_IMMUNITY)
	!StateCheck(LastSeenBy(Myself),STATE_REALLY_DEAD)
	See(LastSeenBy(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("gt_cast","LOCALS",ONE_ROUND)
		Spell(LastSeenBy(Myself),WIZARD_SPOOK)  // SPWI125.SPL (Spook)
END

 

 

To sum up: could you explain why some triggers (e.g., 'Allegiance()' and '!InParty()') defined in the ACTION definition only appear in the 'LastSeenBy(Myself)' block instead of everywhere?

Edited by Luke

Share this post


Link to post

Combine()'s False() blocks only contain the intersection of the trigger lists for the various actions being Combine()d.

Share this post


Link to post
On 8/13/2019 at 8:40 PM, DavidW said:

Combine()'s False() blocks only contain the intersection of the trigger lists for the various actions being Combine()d.

Good to know, thanks for clarifying (the triggers I previously pointed out don't belong to the intersection, so they appear only in the final 'LastSeenBy(Myself)' blocks).

Edited by Luke

Share this post


Link to post
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...