Jump to content

Coding scripts in SSL: some lessons


Recommended Posts

On 5/15/2020 at 2:11 PM, DavidW said:

It attaches a condition to the particular action used (so that different Action()s in the same block can have different conditions). 

It seems it doesn't always work...?

The following SSL block

Spoiler

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

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

IF TRIGGER
	TargetBlock(EnemiesInOrder)
	!StateCheck(scstarget,STATE_REALLY_DEAD)
THEN DO
	Combine()
	ActionCondition(Spell_test,WIZARD_HORROR,55,SPELLFAILUREMAGE;Allegiance(Myself,GOODCUTOFF)&ActionListEmpty()&!Allegiance(scstarget,GOODCUTOFF)&!InParty(scstarget))
	ActionCondition(Spell_test,WIZARD_HORROR,55,SPELLFAILUREMAGE;Allegiance(Myself,EVILCUTOFF)&!Allegiance(scstarget,EVILCUTOFF))
END

 

generates the following BAF line (which of course is not valid syntax):

Spoiler

CheckStatLT(Myself,55,SPELLFAILUREMAGE;Allegiance(Myself)

 

Is it because you can't combine 'ActionCondition()' with 'Combine()'...?

Edited by Luke
Link to post

Another feature: DEFAULT TRIGGER(Infinity Engine trigger list)

The triggers defined in this way will be inserted as the first triggers in every subsequent IF TRIGGER ... THEN DO ... END block.

Example:

Spoiler

DEFAULT TRIGGER(AmIInWatchersKeepPleaseIgnoreTheLackOfApostophe()ActuallyInCombat())

IF TRIGGER
	Target(NearestEnemyOf(Myself))
	Range(scstarget,4)
THEN DO
	AttackOneRound(scstarget)
END

IF TRIGGER
	Target([PC])
	True()
THEN DO
	RandomTurn()
END

 

Will compile into:

Spoiler

IF
	InWatchersKeep()
	ActuallyInCombat()
	See(NearestEnemyOf(Myself))
	Range(NearestEnemyOf(Myself),4)
THEN
	RESPONSE #100
		AttackOneRound(NearestEnemyOf(Myself))
END

IF
	InWatchersKeep()
	ActuallyInCombat()
	See([PC])
	True()
THEN
	RESPONSE #100
		RandomTurn()
END

 

 

Edited by Luke
Link to post

@DavidW

What's the correct way to redefine variables defined via VARIABLE ?

For instance, the following file

Spoiler

VARIABLE(x=65)
IF
	Race(LastSeenBy(Myself),x)
THEN
	RESPONSE #100
		RandomTurn()
END

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

VARIABLE(x=85)
IF
	Race(LastSeenBy(Myself),x)
THEN
	RESPONSE #100
		RandomTurn()
END

 

will compile into

Spoiler

IF
	Race(LastSeenBy(Myself),85)
THEN
	RESPONSE #100
		RandomTurn()
END

IF
	Race(LastSeenBy(Myself),85)
THEN
	RESPONSE #100
		RandomTurn()
END

 

You see? 85 is used twice (65 is somehow ignored...)

What am I missing? I'm asking because I'm pretty sure it's doable, I saw this in SCS

Spoiler

// ...
VARIABLE(scscloudmelee=!See([PC]))
INCLUDE FILE(%scsroot%/genai/ssl/initial.ssl)

INCLUDE FILE(%scsroot%/genai/ssl/hla.ssl)
INCLUDE FILE(%scsroot%/genai/ssl/backstab.ssl)
INCLUDE FILE(%scsroot%/genai/ssl/potionuse.ssl)

VARIABLE(scscloudmelee=See([PC])!Range([PC],8))
// ...

 

where you're redefining variable "scscloudmelee"...

Edited by Luke
Link to post

I don't 100% recall, but I think the answer is that variables are read from an SSL file before substitutions are made to that file, but INCLUDEs and VARIABLEs are processed in parallel. So if you want to redefine a variable as you go along, make sure references to the variable live in a sub-script rather than in the top-level script.

Link to post
1 hour ago, DavidW said:

I don't 100% recall, but I think the answer is that variables are read from an SSL file before substitutions are made to that file, but INCLUDEs and VARIABLEs are processed in parallel. So if you want to redefine a variable as you go along, make sure references to the variable live in a sub-script rather than in the top-level script.

Do you mean something like this?

VARIABLE(x=65)
INCLUDE FILE(mod_folder/test.ssl)


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

VARIABLE(x=85)
INCLUDE FILE(mod_folder/test.ssl)

where "test.ssl" is

IF
	Race(LastSeenBy(Myself),x)
THEN
	RESPONSE #100
		RandomTurn()
END

Unfortunately, it's not working... Now it compiles to

IF
	Race(LastSeenBy(Myself),65)
THEN
	RESPONSE #100
		RandomTurn()
END

IF
	Race(LastSeenBy(Myself),65)
THEN
	RESPONSE #100
		RandomTurn()
END

So basically "x=85" is ignored...

Link to post

No idea, then. It may be that this bit of SCS isn't working properly either.

Ultimately this is the price paid for SSL being a messy bit of PERL I wrote 13 years ago when I knew a lot less about program structure, but also so embedded into SCS that I don't want to risk breaking anything by rewriting it. 

Link to post

@Luke you can do it with LOOPs though.

BEGIN LOOP(x||65)
INCLUDE FILE(test.ssl)
END LOOP

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

BEGIN LOOP(x||85)
INCLUDE FILE(test.ssl)
END LOOP

I actually think I might have done it that way before and changed it on an erroneous assumption about how variable-substitution works in SSL.

(Obviously, in this particular case it would be better to do BEGIN LOOP(x||65;85)

Link to post
On 5/19/2020 at 3:26 PM, DavidW said:

Probably.

For posterity, I was wrong somehow... "ActionCondition()" should work fine when used with "Combine()"...

Just keep in mind that only the "LastSeenBy(Myself)" BCS block will contain the condition(s) specified by "ActionCondition()".

For instance, the following SSL script

Spoiler



BEGIN_ACTION_DEFINITION
	Name(ForceSpellRES)
	TRIGGER
		!GlobalTimerNotExpired("gt_cast","LOCALS")
	ACTION
		RESPONSE #scsprob1
			SetGlobalTimer("gt_cast","LOCALS",ONE_ROUND)
			ForceSpellRES(scsargument1,scstarget)
END

IF TRIGGER
	Target(Protagonist)
	!ImmuneToSpellLevel(scstarget,1)
	!StateCheck(scstarget,STATE_REALLY_DEAD)
THEN DO
	Combine()
	ActionCondition(ForceSpellRES,"SPWI112";OR(2)StoryModeOn()&!PartyHasItem("DAGG01"))
END

 

compiles as

Spoiler



IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	!ImmuneToSpellLevel(Protagonist,1)
	!StateCheck(Protagonist,STATE_REALLY_DEAD)
	See(Protagonist)
	False()
THEN
	RESPONSE #100
		Continue()
END

IF
	!GlobalTimerNotExpired("gt_cast","LOCALS")
	OR(2)
		StoryModeOn()
		!PartyHasItem("DAGG01")  // Dagger
	!ImmuneToSpellLevel(LastSeenBy(Myself),1)
	!StateCheck(LastSeenBy(Myself),STATE_REALLY_DEAD)
	See(LastSeenBy(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("gt_cast","LOCALS",ONE_ROUND)
		ForceSpellRES("SPWI112",LastSeenBy(Myself))  // Magic Missile
END

 

 

Edited by Luke
Link to post
On 4/14/2021 at 12:57 PM, Luke said:

For posterity, I was wrong somehow... "ActionCondition()" should work fine when used with "Combine()"...

I apologize for the mess, but I was actually right that day.

"ActionCondition()" does not (always) work with "Combine()" (see here).

That's because the wrong array is processed in get_combine_top(). In case you are interested in a fix, here it is:

# ...
@temp=split('\|',$forcombine);
@temp=split(';',$temp[0]);	# NEW: ActionCondition() check!!!
@actionargs=split(',',$temp[0]);
# ...

 

Edited by Luke
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...