Jump to content


Photo

Coding scripts in SSL: some lessons


18 replies to this topic

#1 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:29 AM

The BCS scripts in Sword Coast Stratagems II are largely written in an automated scripting language called "ssl" (Stratagems Scripting Language). (SSL is emphatically not a rival to WEIDU: it's a program - a Perl script, in fact - callable from inside WEIDU).

I've been promising, and putting off, releasing that language for ages - not because it's buggy (I think it's pretty stable) but because I can't really work out how to document it.

So here's a partial attempt: not a formal rigorous documentation, but an unfinished set of lessons. I've done the first six lessons so far, and they cover enough to make SSL usable, but there's quite a lot more to say and I'll add other bits if there's demand.

SSL isn't downloadable on its own at the moment: it's contained in the "ssl" subdirectory of SCSII. At some point I might make a freestanding version available; at the moment, though, I think there's something to be said for looking at how it works in the only mod that currently uses SSL.

I don't actually know if SSL will be useful to anyone or not. It's certainly not a beginner's tool: these lessons assume the reader is fluent in IE scripting. Its main use is for long complex scripts and for situations where you want to use lots of closely-related but non-identical scripts.

Comments avidly sought. (Also, someone move this if it's in the wrong place...)

Edited by DavidW, 26 March 2008 - 11:37 AM.


#2 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:30 AM

Lesson one: what is SSL?

SSL is the "Stratagems scripting language". Formally speaking, it's what you might call a "metascripting language": just as a BAF script, written in a text editor, is compiled into a BCS file, so an SSL file is compiled (by a rather messy bit of Perl scripting) into a BAF file.

In practice, though, SSL is an alternative (or perhaps a supplement) to BAF as a way of writing scripts for IE games. It can't ultimately do anything that BAF can't do (ultimately, an SSL file is just another way of producing a BCS file) but it is much more compact and easy to do powerful programming in... well, once you get the hang of it. Partly this shows itself in up-front writing costs: a 5,000 line BAF file might only be a couple of hundred lines of SSL. At least as important, though, SSL makes the logical structure of the script much clearer and easier to understand, debug, and modify. If you want to (say) make a mild change in how all wizards use their spells in a tactical mod, SSL allows you to do it by changing just one line; the BAF method might require thousands of lines to be changed.

(In a sense, SSL aims to do for scripting something like (but more modestly than) what the WEIDU .d file format did for dialog writing. Under the hood, functions like CHAIN and INTERJECT_COPY_TRANS generate hideous spaghettified code, but it doesn't matter because the user only sees the clean logical structure.)

Edited by DavidW, 07 September 2010 - 04:46 AM.


#3 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:31 AM

Lesson two: running SSL

SSL itself is a perl script (ssl.pl) which has been compiled into a Windows executable (ssl.exe). Its formal syntax is

ssl.exe "<list of ssl files> -l <list of slb files> <variable string>

The ssl files are the actual code; they conventionally have the suffix .ssl and this will be added if un-suffixed files are called. The slb files are "library files" containing common data (this will all be explained later); they conventionally have the suffix ".slb" and again this will be added to unsuffixed files.

The variable string will be explained much later; it's basically a way of modifying at the command line exactly what's done with a string.

An (imaginary) example might be
scsii/ssl/ssl.exe "scsii/mage/mage_definitions scsii/mage/dw#mage -l scsii/ssl/library scsii/ssl/autolib IsLich=True&CreType=Undead"

Here the ssl program itself, and the library files library.slb and autolib.slb are located in the directory scsii/ssl, and the ssl files mage_definitions.ssl and dw#mage are located in scsii/mage. There are two variables: IsLich is set to True and CreType is set to Undead.

The result of this process is to generate a number of .baf files: usually one per ssl file called, though some ssl files are just used to define things and don't generate output. Any file that does generate output is dumped in the directory ssl_out relative to its current location - in this case, for instance, dw#mage.ssl would generate the output file scsii/mage/ssl_out/dw#mage.baf. This file would then have to be compiled normally from WEIDU. If the directory ssl_out doesn't exist, SSL will complain: you need to create it in advance.

You can use SSL in your mods in two ways. The simplest is to write your scripts in SSL, compile them locally, and distribute the resultant BAF. For more complex mods you probably want to do the compilation inside the TP2 file, using AT_NOW. This is easiest if you just define a macro to do it for you: there's an example in SCSII.

Edited by DavidW, 26 March 2008 - 11:31 AM.


#4 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:32 AM

Lesson three: Defining ACTIONs

Now we start getting into how SSL scripts actually work.

A lot of BG2 scripting consists of repeating very similar blocks again and again. Spellcasting, for instance, requires you to check for whether the caster actually has the spell; in tactical scripting it usually also requires you to check whether a timer has expired (so that you don't try to cast until you're able to) and then to reset that timer again.

So even a rather simple-minded mage script might look like

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_FLAME_ARROW)
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_FLAME_ARROW)
END

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_MELF_ACID_ARROW)
	See([PC.0.0.MAGE])
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell([PC.0.0.MAGE],WIZARD_MELF_ACID_ARROW)
END

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_MAGIC_MISSILE)
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_MAGIC_MISSILE)
END

The core of SSL is the ability to automate production of repetitive blocks like this. This action, which we might call Spell, would be coded in SSL as follows:

BEGIN ACTION DEFINITION
	Name(Spell)
	TRIGGER
		!GlobalTimerNotExpired("cast","LOCALS")
		HaveSpell(scsargument1)
	ACTION	
		RESPONSE #scsprob
			SetGlobalTimer("cast","LOCALS",6)
			Spell(scstarget,WIZARD_MAGIC_MISSILE)
END

This looks a bit like a standard BCS block, but it has some things missing and some odd features - notably the expressions "scstarget","scsargument1" and "scsprob1". These are all choosable when the block is called. "scstarget" and "scsprob1" are common to all ACTION blocks: the first is the actual target of the action (such as NearestEnemyOf(Myself)) and the second is the probability of the action being carried out (which by default is set to 100). "scsargment1" is specific to a particular action: different actions require different arguments. The "Spell" action, for instance, requires only one argument (other than a target): the name of the spell to be cast. More complex actions require more arguments. For instance, illithids in SCS2 use their powers on a timer, so that they can case (e.g.) Maze every three rounds. A typical BCS block for an illithid would be

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	!GlobalTimerNotExpired("maze","LOCALS")
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		SetGlobalTimer("maze","LOCALS",18)
		ForceSpell(NearestEnemyOf(Myself),PSIONIC_MAZE)
END

and the corresponding ACTION would look like

BEGIN ACTION DEFINITION
Name(Psionic)
	TRIGGER
		!GlobalTimerNotExpired("cast","LOCALS")
		!GlobalTimerNotExpired(scsargument2,"LOCALS")
	ACTION
		RESPONSE #scsprob
			SetGlobalTimer("cast","LOCALS",6)
			SetGlobalTimer(scsargument2,"LOCALS",scsargument3)
			ForceSpell(scstarget,scsargument1)
END

The "Psionic" action has three arguments: scsargument1 is the name of the power to use, scsargument2 is the name of the timer to be set, and scsargument3 is the length of time the timer is to be set for.

#5 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:33 AM

Lesson Four: IF and IF TRIGGER

SSL files consist (to first approximation) of three sorts of blocks. The first are ACTION DEFINITIONS of the sort I talked about above. These are processed when they occur in the file and are kept in memory for the rest of the run (so if you call two SSL files in one compilation run, any ACTION DEFINITIONs in the first are available in the second).

The second are plain BCS blocks, of the sort you'd find in any BAF file. It's convenient to call them IF blocks, since they always start with an IF. Any BCS block is valid in SSL (though SSL sometimes requires entries to be on separate lines, so it's best to avoid multiline scripting on principle); as we'll see later, SSL also admits a few commands for IF blocks that BAF doesn't.

The most important blocks are SSL blocks, also called IF TRIGGER blocks. A simple example would be

IF TRIGGER
	Target(NearestEnemyOf(Myself))
THEN DO
	Action(Spell,WIZARD_FLAME_ARROW)
END

If the Spell action hasn't been defined already, SSL will fail on this block. But if Spell is defined as above, the block will be compiled as

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_FLAME_ARROW)
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_FLAME_ARROW)
END

So far, not much of a saving. But multiple targets can be defined.

IF TRIGGER
	Target([PC.0.0.MAGE])
	Target(NearestEnemyOf(Myself))
THEN DO
	Action(Spell,WIZARD_FLAME_ARROW)
END

compiles as

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_FLAME_ARROW)
	See([PC.0.0.MAGE])
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell([PC.0.0.MAGE],WIZARD_FLAME_ARROW)
END

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_FLAME_ARROW)
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_FLAME_ARROW)
END


And multiple actions can be applied.

IF TRIGGER
	Target(NearestEnemyOf(Myself))
THEN DO
	Action(Spell,WIZARD_FLAME_ARROW)
	Action(Spell,WIZARD_MAGIC_MISSILE)
END

compiles to

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_FLAME_ARROW)
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_FLAME_ARROW)
END

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_MAGIC_MISSILE)
	See(NearestEnemyOf(Myself))
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_MAGIC_MISSILE)
END

And this can be combined: hopefully it's obvious what
IF TRIGGER
	Target([PC.0.0.MAGE])
	Target(NearestEnemyOf(Myself))
THEN DO
	Action(Spell,WIZARD_FLAME_ARROW)
	Action(Spell,WIZARD_MAGIC_MISSILE)
END
does, though it's perhaps worth noting that each Target has the first Action applied to it before moving on to the next Action. So this block first tries to cast Flame Arrow at a mage, then at anyone; then it tries to cast Magic Missile at a mage, then at anyone.

#6 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:34 AM

Lesson Five: Customising IF TRIGGER blocks

In some ways, IF TRIGGER blocks behave very much like plain IF blocks. In particular, ordinary BCS code can be added to the trigger and action blocks. For instance,

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

generates

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_DISPEL_MAGIC)
	See(NearestEnemyOf(Myself))
	!GlobalTimerNotExpired("Dispel","LOCALS")
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_DISPEL_MAGIC)
		SetGlobalTimer("Dispel","LOCALS",18)
END

You can also use the same "scstarget" locution as in block definitions, as in

IF TRIGGER
	Target(NearestEnemyOf(Myself))
	!CheckStatGT(scstarget,50,RESISTFIRE)
THEN DO
	Action(Spell,WIZARD_FLAME_ARROW)
END

which generates

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_FLAME_ARROW)
	See(NearestEnemyOf(Myself))
	!CheckStatGT(NearestEnemyOf(Myself),50,WIZARD_FLAME_ARROW)
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(NearestEnemyOf(Myself),WIZARD_FLAME_ARROW)
END


#7 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:35 AM

Lesson Six: Target blocks and Trigger blocks

This is where savings really start to come in. Frequently you want to use the same list of Target()s for multiple attacks; frequently you want to use the same checks on a creature's immunities for multiple attacks. These can all be defined in a separate library file: here's a simple example.

TRIGGER=Paralyse
	!Kit(scstarget,UNDEADHUNTER)
	!Kit(scstarget,INQUISITOR)
	!CheckStatGT(scstarget,0,CLERIC_FREE_ACTION)
TRIGGER=MR
	!CheckStatGT(scstarget,50,RESISTMAGIC)
TARGET=PCsInOrderShort
	[PC]
	SecondNearest([PC])
	ThirdNearest([PC])
TARGET=PCSpellcasters
	[PC.0.0.MAGE_ALL]
	[PC.0.0.CLERIC_ALL]
	[PC.0.0.DRUID_ALL]
	[PC.0.0.BARD]
TARGET=PlayersInRandomOrder
	Player4
	Player5
	Player6
	Player3
	Player2
	Player1

This file needs a name like library.slb, and needs to be called when you call SSL (see lesson two for the syntax).

Once you've got a library file included, you can use two new commands: TargetBlock and TriggerBlock. For instance, this SSL code

IF TRIGGER
	TargetBlock(PCsInOrderShort)
	TriggerBlock(Paralyse)
THEN DO
	Action(Spell,WIZARD_HOLD_PERSON)
END

generates this BAF code:

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_HOLD_PERSON)
	See([PC])
	!Kit([PC],UNDEADHUNTER)
	!Kit([PC],INQUISITOR)
	!CheckStatGT([PC],0,CLERIC_FREE_ACTION)
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell([PC],WIZARD_HOLD_PERSON)
END

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_HOLD_PERSON)
	See(SecondNearest([PC]))
	!Kit(SecondNearest([PC]),UNDEADHUNTER)
	!Kit([SecondNearest([PC]),INQUISITOR)
	!CheckStatGT(SecondNearest([PC]),0,CLERIC_FREE_ACTION)
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(SecondNearest([PC]),WIZARD_HOLD_PERSON)
END

IF
	!GlobalTimerNotExpired("cast","LOCALS")
	HaveSpell(WIZARD_HOLD_PERSON)
	See(ThirdNearest([PC]))
	!Kit(ThirdNearest([PC]),UNDEADHUNTER)
	!Kit([ThirdNearest([PC]),INQUISITOR)
	!CheckStatGT(ThirdNearest([PC]),0,CLERIC_FREE_ACTION)
THEN
	RESPONSE #100
		SetGlobalTimer("cast","LOCALS",6)
		Spell(ThirdNearest([PC]),WIZARD_HOLD_PERSON)
END

Multiple trigger and target blocks can be combined inside a single TargetBlock, like this:

IF TRIGGER
	TargetBlock(PCMages|PCsInOrderShort)
	TriggerBlock(Paralyse|MR)
THEN DO
	Action(Spell,WIZARD_HOLD_PERSON)
END

Since this generates 91 lines of BAF code, I shan't write it out - hopefully the idea's clear, though.

This is perhaps the right time to mention SSL's only built-in action: Literal. If defined normally (which you don't need to do), Literal would be defined as

BEGIN ACTION DEFINITION
	Name(Literal)
	TRIGGER
	ACTION	
		RESPONSE #scsprob
END

... which looks pointless, but does allow things like this, which might be useful for catching up with off-screen enemies:

IF TRIGGER
	!See(NearestEnemyOf(Myself))
	TargetBlock(PlayersInRandomOrder)
	!StateCheck(scstarget,STATE_INVISIBLE)
THEN DO
	Action(Literal)
	MoveToObject(scstarget)
END


#8 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 26 March 2008 - 11:36 AM

Bonus Lesson Automated trigger blocks

This isn't really part of SSL proper, more an illustration of what can be done with it. The game is full of magic items which grant immunity to certain effects (Helms of Charm Protection, for instance). There usually isn't any systematic way of detecting these items, so if you want to avoid targetting protected enemies you need to add a line like "!HasItem("helm06",[target])" to the code for every such item. In the vanilla game, for instance, if you want to cast Charm Person on the nearest PC to you who's not protected, you have to do

IF TRIGGER
	TargetBlock(PCsInOrderShort)
	!HasItemEquiped("CHALCY3",scstarget)
	!HasItemEquiped("HELM06",scstarget)	
	!HasItemEquiped("MISCBC",scstarget)
	!HasItemEquiped("NPCHAN",scstarget)
	!HasItemEquiped("SHLD25",scstarget)
	!HasItemEquiped("SPER07",scstarget)
	!HasItemEquiped("STAF11",scstarget)
	!HasItemEquiped("SW1H35",scstarget)
	!HasItemEquiped("SW1H54",scstarget)
	!HasItemEquiped("SW2H14",scstarget)
THEN DO
	Action(Spell,WIZARD_CHARM_PERSON)
END

This is cumbersome; but that problem can be solved by TriggerBlocks: you could define, for instance,

TRIGGER=Charm
!HasItemEquiped("CHALCY3",scstarget)
!HasItemEquiped("HELM06",scstarget)
!HasItemEquiped("MISCBC",scstarget)
!HasItemEquiped("NPCHAN",scstarget)
!HasItemEquiped("SHLD25",scstarget)
!HasItemEquiped("SPER07",scstarget)
!HasItemEquiped("STAF11",scstarget)
!HasItemEquiped("SW1H35",scstarget)
!HasItemEquiped("SW1H54",scstarget)
!HasItemEquiped("SW2H14",scstarget)
and then just do
IF TRIGGER
	TargetBlock(PCsInOrderShort)
	TriggerBlock(Charm)
THEN DO
	Action(Spell,WIZARD_CHARM_PERSON)
END

More importantly, it will be caught out if the player installs any mod that adds new items (of which there are more than a few).

However, nothing stops you generating your library files dynamically. In SCSII, a WEIDU macro goes through every item in the game and (for about 20 different immunities) automatically generates the appropriate library file. Provided SCSII is installed after any item-introducing mod, that mod's items will be included.

(In fact, I use two library files: a "manual" one, library.slb, and an automatically generated one. Library files are cumulative: you can put two Charm entries into two different library files (or indeed, into the same library file) and they'll be merged.)

#9 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 18 August 2008 - 02:45 AM

Just to say: As far as I know no-one else is considering writing in SSL, so I've not continued this, but there is a lot more to say if anyone does get interested at a future point. Let me know.

#10 aVENGER_(RR)

aVENGER_(RR)

    Sneaksie!

  • Fixpackers
  • 1008 posts
  • Gender:Male

Posted 18 August 2008 - 03:01 AM

I am moderately interested. :)
Retired modder

Rogue Rebalancing - Author
aTweaks - Author
Wizard Slayer Rebalancing - Co-Author
BG2 Fixpack - Contributor

#11 Galactygon

Galactygon

    Nostradoctopus

  • Members
  • 646 posts
  • Location:Hungary

Posted 18 August 2008 - 09:02 AM

I am very interested, since later on I will have to deal with scripting the AI to account for all the rules and spells I have added/changed.

Looking at SCS and figuring out what everything does would be difficult without a tutorial.

-Galactygon

Edited by Galactygon, 18 August 2008 - 09:03 AM.




#12 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 19 August 2008 - 01:21 AM

Lesson Seven: INCLUDE FILE

This one is pretty simple. You might want to break your code up into blocks (either for ease of reading, or because you want to use the same subset of code in multiple places). So there needs to be some way to include one SSL file in another one.

There is: just put

INCLUDE FILE([full path])

into an SSL file, and the included file will be slotted into the first file at that point.

#13 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 19 August 2008 - 01:39 AM

Lesson Eight: Variables

If you include the line

VARIABLE(cat=dog)
in your SSL file, the string "cat" will be replaced everywhere (in that SSL file and in any SSL file it includes) by the string "dog". If, for instance, some later block included the line
SetGlobal("cat","LOCALS",1)
it would be replaced by
SetGlobal("dog","LOCALS",1)

(You need to be careful with this: if your code included "cataclysmic_attack" somewhere, it would become "dogaclysmic_attack"! So use variable names unlikely to occur as substrings. (Variable substitution is case-sensitive).

You can also set variables from outside the SSL script itself, in the command line; see above for the syntax.

You can use variables for lots of things, but the most useful (probably) is in conjunction with the IgnoreBlock and RequireBlock commands. These have syntax
RequireBlock(sometext)
and
IgnoreBlock(sometext)
and can be placed in the trigger part of an IF TRIGGER block or a plain IF block. (This is one of the exceptions to the rule that IF blocks function just like ordinary BCS blocks.)

The functionality of RequireBlock and IgnoreBlock is pretty simple. Any block (IF or IF TRIGGER) containing RequireBlock(True) is compiled normally; any block containing RequireBlock([anything except True]) is ignored by the SSL compiler. And conversely, any block containing IgnoreBlock([anything except True]) is compiled; any block containing IgnoreBlock(True) is ignored.

What's the point of this? The answer, of course, is that a variable can be placed into the RequireBlock or IgnoreBlock command. If that variable is set to True, the block will be compiled/ignored; otherwise, it will be ignored/compiled.

To give an example: SCS 1 has only one priest SSL script for all the various different types of cleric and druid. A recent block in the latest (unreleased) version of this script reads
IF TRIGGER
	IgnoreBlock(IsCleric)
	RequireBlock(HasL3)
	TargetBlock(PCsPreferringWeak)
	TriggerBlock(Disabled|MR|ResistElectric|MinorGlobe|Enemy|SIAlteration)
	AreaType(OUTDOOR)
	IgnoreBlock(Demivrgvs)
THEN DO
	Action(Spell,CLERIC_CALL_LIGHTNING|70|30)
END
This block should only be included in scripts for casters who can cast third-level spells; it should not be included in scripts for clerics (who don't get Call Lightning); it should not be included if Demivrgvs's Spell Revisions mod is installed. All of this can be achieved just by setting the variables IsCleric and Demivrgvs to True, and the variable HasL3 to False (or anything else other than True, actually).

This saves a lot of time; perhaps more importantly, it allows scripts to be changed and customised very easily. If you change your mind on how the targetting of Call Lightning should work, you need to modify only one block, instead of lots.

#14 Icendoan

Icendoan

    King of Parsing Errors

  • Modders
  • 1704 posts
  • Gender:Male
  • Location:The hall of 1000 posts!

Posted 19 August 2008 - 01:50 PM

I am very interested by this. :laugh:

Will come back and have a better look later, and perhaps on my own PC, but I think it will be invaluable.

One thing throws me, and I don't think it is crucial for me to know, save curiosity, but in the WeiDU syntax for this, what is the -1 for?

And exhaustive documentation would be awesome. :)

Icen
Mods in development: Keeping Yoshimo
Posted Image

#15 DavidW

DavidW
  • Gibberlings
  • 4117 posts
  • Gender:Male

Posted 19 August 2008 - 02:06 PM

I am very interested by this. :laugh:

Will come back and have a better look later, and perhaps on my own PC, but I think it will be invaluable.

One thing throws me, and I don't think it is crucial for me to know, save curiosity, but in the WeiDU syntax for this, what is the -1 for?

It's -l, not -1. And it separates the script files from the library files (l is for library).

And exhaustive documentation would be awesome. :)


The prospects are very remote...



Reply to this topic



  


0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users