I've just spent some extended time exploring the scripting that may be used with the Bat client.

The scripting offers some very interesting possibilities. Although I haven't yet tried anything other than very (and I mean very) simple stuff, it would appear that Java Swing classes can be instantiated and used from the Beanshell scripts. If this really is the case, and there are no hidden problems, it would be possible to add various little graphical interface components.

For example, how about a graphical display of party status. Perhaps a drag and dropping could be used to re-arrange party places. Or, how about a graphical display of your inventory and/or eq. Nice little icons could be used etc.

If the Beanshell interpretor is being used in the expected way, efficiency should not be too bad. Apparently, all methods (functions) are parsed once into something called an Abstract Syntax Tree, this is the slow bit of parsing scripts. After this, the interpretation of the Abstract Syntax tree should be quite fast. Therefore, it seems quite likely that a large set of complex Beanshell scripts could still run reasonable quickly.

I have also found out about a few of Beanshell's shortcomings (at least for scripting of this nature). These are more a case of limitations rather than actual problems.

More annoyingly, I have found a few limitations with the actual client and scripting interfaces.

For further information see the help available within the Bat client, including the example scripts provided and also

http://www.bat.org/batclient/javadoc/ (or the bat_interfaces.jar file supplied with the client).
http://www.beanshell.org/


What follows is an explanation of a few things I have found out about the scripting in the client and some requests. Please excuse the description of functionality that will be well known to Beanshell users, these descriptions are intended as useful information for would be Bat client scripters and as part of an explanation for my requests.


The Bat Client, Trigger manager, and calling Beanshell functions

The Bat Client is equipped with some functionality to manage triggers. From the script environment it is possible to interact with this "trigger manager" functionality.

I'm not going to discuss it here, but there is a TriggerManager interface through which triggers can be added and removed.

A trigger consists of a regular expression, some flags, and a client command to be executed when the regular expression matches incoming text.

Client commands, via triggers, macros, or the actual command line may call functions of Beanshell scripts. The syntax is as follows:

$&lt;script_name>.<function_name> argument

e.g. $my_script.say_hello tim

Will call the "say_hello" function found in the my_script.bcs Beanshell script (assuming that SCRIPT_NAME="my_script" in my_script.bcs) with the argument "tim"

Note that, when no function name is specified, the default "run" function is called. I.e.

$my_script say hello

will call the "run" function within my_script.bcs with argument "say hello"


The normal procedure for creating custom triggers that require some kind of code, rather than just highlighting or gagging text, is to add a new trigger (to the trigger manager) that calls a Beanshell function.

I.e. to call the "respond" function defined in the my_script Beanshell script, the command of the defined trigger will be something like:

$my_script.respond

Note: sub patterns matched by the regular expression of the trigger are also made available to the command via a supplied interface.


Beanshell scripts and scope

This is one thing worth mentioning, although it may not effect the triggers that most people will want to create.

Each Beanshell script seems to have its own context.

When a Beanshell script is first loaded, all the variables and methods of the script are parsed and exist in the "context" of this script. When functions from this script are executed, they are executed in this "context". There is no global Beanshell context that is shared between scripts, each script is like a little separate program. The scripts are, though, tied together by the client.

There is no direct communication between scripts. Scripts can call functions in other scripts (and, I think, access certain stored variables) by using sending commands to the client (there is an interface to allow this) and using a command that calls a script (e.g. $script.function).

If you want to write a complex set of interacting functions and break them up into separate Beanshell script files. They will need to be "sourced" (or similar) by a single client script.

The Beanshell "source" command is one method (there are others) to read Beanshell code from another file.

Therefore, it is possible to create a master.bcs script which sources several other files. These files will all be read in and parsed (evaluated) as if the script of the files were added exactly where the source command is used.

Unfortunately, I haven't found any neat way of including files as required in a unique fashion. The "source" function just evaluates the specified external script wherever it is used. I'm not sure if the importCommands command does any evaluation (so variable instantiation may not work, directly, via this method).


Note: When the scripts are first loaded, they will be evaluated. However, the various client interfaces that may be available when script functions are called from the client, will not be available at this stage.

Therefore, it is not possible to instantiate variables with references to the client interfaces at this stage.

Actually, it is not clear from the documentation of the client whether this would be a good idea or not. I have no idea about
whether the references to interfaces of the client remain valid from function call to function call (and are guaranteed to do
this). There are several areas where some more information would be useful, this is one of them.


Functions called (invoked) from the client

There are various standard functions of scripts that are called at particular times. These functions are detailed in the
"introduction to scripting" available within the client.

void run()

This function has already been mentioned. It's just the default function that is called when no specific function is
included in a client command.

void bootup()

This function is called when the client first starts (I think the /scriptbooup command also invokes this). This in which basic
initialization is expected to be placed.

void connect()

This function is called when the client connects to the mud.

void disconnect()

This function is called when the client disconnects from the mud.

String trigger()

This is the function that is called when output from the mud is processed by the client. Actually, this is one function that I
would like to see altered. A lot.


One thing that is missing from the documentation is an explanation of exactly when these functions are called. The assumption is that, when the appropriate time comes to invoke one of these functions, the client invokes, in no particular order, the function in each script which contains it.


The trigger() function

There are two issues I have with this function. Both seem to be related to when the function is invoked in the chain of processing incoming text.

1) The argument used for this function does not appear to contain the raw text/output from the mud. It is stripped of any codes that would be used to achieve text colours, bold or other text attributes.

The function must return a string for further processing and output.

The upshot is that, when using _ANY_ trigger() function in a script, all text attributes (colour etc) are lost for all text.


2) It appears that the trigger() function is called for any output to any "channel". Or, rather, the trigger() function is not just
called for output from the mud, but any output generated by any other trigger.

This is both good and bad.

One upshot of this is that, in the only place where one can intercept text directly using Beanshell code (i.e. by not using the triggers managed by the trigger manager) it is necessary to be extremely careful about what output is generated within the code.

Any output (not the return value) generated by this function may cause this function to be invoked on itself. Loops must be avoided.

More about this later.


Writing customized triggers in Beanshell

Well, perhaps this is what my requests are all about. However, I think that the functionality that I am trying to achieve could
required for other forms of customization.

I'll approach this as a kind of question and answer text.


What am I actually talking about?

What I would like to do is write some triggers directly in Beanshell code. I am trying to write customizations that are not simple tied up with the trigger manager.

I would like to add some functionality not currently supplied by the native trigger functionality of the client and I also have a
preference for how the code is written.


What extra functionality am I talking about?

There are a few things. These could be achieved in various ways and I expect some of this functionality will be added to the native triggers.

1) Trigger priority.

Currently there is none. I would like to have triggers that operate in a fairly well defined order.

2) Fall through.

Currently there is no easy way to prevent text from triggering other triggers. If triggers are ordered, it would be handy to be able to stop further triggers from taking place.

3) Management of exactly what text gets send to what channel.

Currently the only way to achieve this is to set up triggers that gag text and also print the text directly to a particular channel. Unfortunately, this doesn't work very well alongside the current processing of text, you end up having either your own choice of channels or the clients choice and not both.

I'm just trying to set up a bit of a basic framework in which to achieve this, just a bit more than a function to send text to
multiple channels that can be called from trigger functions.


The priority and fall through control functionality will probably be added to the client. Can't I just wait for that?

Actually, I could, if that was all I am interested in. It's not a case of impatience. There are two reasons.

1) There is a bit more that I want. I will explain about the channel stuff later. The other thing is to do with preferences
regarding the actual writing of triggers. I'll also explain this later.

2) As far as I can tell, the changes I would like to see that might (I'm not sure) enable the customizations I want are probably
actually less complex than the addition of the functionality that I would like to see.

In fact, I am of the opinion that, it is far better to enable such functionality via user customizations that adding it directly
into the code. For a start, user customizations can be richer and are optional in nature. If additional customization features are enabled or added, then there is no reason that the same framework for customization cannot be used by the developers of the client itself.


Ok, so what about this channel nonsense?

This is my plan:

At the moment it is limited to lines of text. I suppose one could break up lines, that would be handy, but it is a complication.

I would like to take the lines of text and process them through triggers and various other processes. During this time, certain
changes could be made to text and other side effects could be invoked (typical trigger stuff). However, in addition, attributes could be attached to the line.

What do I mean by attribute? Simple, any kind of flag that indicates something about the line.

"battle", "tell", "spell", "prot", "tick", "round", "party_status" "sales", "hit", "areaspell", "map", "inventory" etc.

Any attribute that categorizes a line. Multiple attributes would be allowed (of course).

Also, "bold", "underline", "red", "green", "blue" etc formatting attributes could be added.

Such attributes could then be used later when deciding what to do with the line. What channels it should be sent to and how it should be presented.


Why do this? Couldn't I just create a set of triggers to do what I want with the line instead of using a bunch of attributes and then one or more functions to process the line and it's attributes?

Of course.

However, I am proposing breaking this kind of processing up into stages.

* A stage where triggers operate and attributes are assigned (as well as the normal side effects).

* A stage where the output is prepared and dispatched (or not).

This just allows customization of the separate stages more easily. I can add more triggers and pattern matching to mark different kind of text without having to worry about what is then going to happen and vice versa, I can change the destination and presentation of text without having to modify triggers.

The point is, the attributes extend the concept of the current channels, which are closely bound to tabs and windows. I could like to be able to manage the sending of text to different channels without necessarily having to write and modify lots of triggers. I would like to break up triggers into some different stages.

I would also like to be able to manage "channels" and window visibility at some stage, separate from triggers.


So, what about these coding preferences?

I'll illustrate my preference by considering the options.

There are definitely some big advantages to using the current trigger manager. There are possibly some advantages to using
another system.

This is just a preference, I'm whether either choice is better than the other (the regexp matching has not been tested).

A trigger to match a tell.

The current system works like this:

[codebox]
//
// Declarations of state variables
//

void tell () {

// The body of the tell trigger.

}

void bootup() {
triggerManager.newTrigger("Trigger",
"^[A-Z][a-z]* tells you '",
"$utility.tell",
false,
true,
false,
null,
Font.PLAIN);
}
[/codebox]

My alternative is very similar, just slightly different in style:

[codebox]
class Tell_Trig implements iTrigger {

//
// Declarations of state variables
//

public final Pattern p = Pattern.compile("^[A-Z][a-z]* tells you '");

public boolean process(String line) {

if (p.matcher(line).matches()) {

// Body of tell trigger.

}
}
}


Tell_Trig t = new Tell_Trig();
trigger_handler.addTrigger("tell_trigger", 100, t);
[/codebox]

There's not a lot of difference for such a simple case. Basically, some of the parameters have moved from the call that adds the triggerto the trigger manager to the code of the actual trigger.

For more complex situations where the regexp exp is dependent upon some kind of state (this isn't required often, but it can be required), there are various alternatives that use the current system.

One could use a serious of triggers that each check the state and act appropriately. Or one could use a single trigger that matches all possible regexps and then embed state dependent matching within the function (as would be done when using my alternative), e.g.

[codebox]
int state = STATE_1;

void tell () {

if ( state == STATE_1 && vars.get(1).matches((regexp1)) ) {

// Body for line 1

} else if ( state == STATE_2 && vars.get(2).matches((regexp2)) ) {

// Body for line 2

} // etc.

}

void bootup() {
triggerManager.newTrigger("Trigger",
"(regexp1)|(regexp2)",
"$utility.complex",
false,
true,
false,
null,
Font.PLAIN);
}
[/codebox]

Notice that my examples are concerned with a trigger in which the body performs some quite specific processing. What about when there is more general, get still custom processing?

Basically, I'd guess that a more general function would be used to perform the actual operation of the triggers.

In the current system one would do something like:

[codebox]
//
// Declarations of state variables (arrays?)
//

void do_operation (parameters) {

// The body of the trigger operation.

}


void trigger() {

// Process argument passed in by bat client.
// Create the parameters.

do_operation(parameters);
}


void define_trigger (triggerManager t, String name,
String regexp, String params) {
t.newTrigger(name, regexp, "$script.trigger " + params,
false, false, false, null);
}


void bootup() {
define_trigger(triggerManager, "trigger1", "regexp1", "params1 string");
define_trigger(triggerManager, "trigger2", "regexp2", "params2 string");
...
}
[/codebox]

Which is perfectly reasonable.

My alternative is:

[codebox]
class Gen_Trig implements iTrigger {

//
// Declarations of state variables
//

Pattern p;
Parameter1 param1;
Parameter2 param2;
...

Gen_Trig(String regexp, one, two, ...)
{
p = Pattern.compile(regexp);
param1 = one;
param2 = two;
...
}

public boolean process(String line) {

if (p.matcher(line).matches()) {

// Body of trigger.

}
}
}


Gen_Trig t1 = new Gen_Trig(regexp1, one, two, ...);
Gen_Trig t2 = new Gen_Trig(regexp2, some, more, ...);
Gen_Trig t3 = new Gen_Trig(regexp3, parameters, here, ...);

trigger_handler.addTrigger("trigger1", 100, t1);
trigger_handler.addTrigger("trigger1", 100, t2);
trigger_handler.addTrigger("trigger1", 100, t3);
[/codebox]

Both alternatives pretty much do the same basic stuff in both cases. In the second case, my alternative avoids some of the parsing of strings via the client commands, but there's no real advantage.

I am certainly not advocating that my alternative would replace the current system. I would use such an alternative in addition to the current system. It's just a matter of preference in which I prefer to avoid the passing of processing back up through the client and down to the Beanshell scripts again.

It also sits slightly more closely to the kind of programming (script language or otherwise) that I perform at the moment.


Requests for some extra or modified functionality


1. The hooks, Beanshell functions called, trigger()

Currently the trigger() method seems to sit before the trigger manager triggers, but after the point at which script generated output is inserted into the processing pipeline.

Could we please please have a trigger() method that hooks in at an earlier stage.

Before output from Beanshell etc (any triggers) enters the stream, before text is assigned to channels and, importantly, before any formatting characters (for bold, colours etc) are stripped away.

When writing custom code and handling of text in the current trigger() function, it is awkward to avoid loops. Yes, the useTriggers flag could be used when printing text to channels. However, the trouble is, all code that may be called directly or indirectly from trigger() would have to be made free of any text printing that would be picked up by the same code again.

Where this is most trouble is when some text is going to be duplicated to a particular channel. If a trigger should send some text, unaltered, to a user specified channel of some sort (e.g. "alert" channel), then the same text will flow back into the trigger again. The text would have to be gagged from further processing, which means it would not be picked up by any other standard channel or triggers.

Please can the text in a more raw format be retained somehow. As mentioned above, the text past to the trigger() function does not contain any kind of bold or colour attributes. Yet these do appear in general output, I am assuming that these attributes come directly from the mud (ansi codes etc).

If a trigger() function is defined in any Beanshell script, all these codes are lost because absolutely all input will go through this function and be stripped of its formatting characters.

Perhaps there can be several hooks for different stages in the processing of incoming text?

Perhaps we could also have interface functionality to insert text at different points in the process?

Please could you add a trigger() style hook to process command line input after it has gone through the normal parsing but before it is actually sent to the mud. This would be extremely useful for all sorts of purposes. Custom logging in one good example.

Obviously this would give rise to possible looping issues if such triggers generate further command line input. I guess the only way to avoid this is to devise some kind of loop detection.


2. Channels

The "channels" that the client currently uses are great.

However, I would like to see some kind of hook/interface into the assignment of text to channels.

It would be nice to be able to customize the assignment of text to channels. I don't see why the current implementation couldn't be modified or replaced so that the entire channel assignment is performed by scripts.

I imagine a framework in which a set of default scripts are provided that could be modified by users as well as there being script interface to various aspects of assigning text to channels and sending text to windows and tabs.

Currently, I can find no way to enumerate the channels that currently exist. Nor can I find out exactly what is assigned to which channel.


3. An improved GUI/script interface.

The interface at the moment does all the basic stuff. I doubt that there is a great deal more required than the various printText or similar functions.

However, when it does come to playing with the actual windows and tabs of the interface, the functionality is a bit limited.

I noted above that I suspect that Java swing components can probably be used from Beanshell scripts. So, it may be possible to create custom gui elements/components. However, it is not possible to do as much with the currently supplied set of gui windows etc.

It's not even possible to (easily) enumerate windows and their tabs and alter basic attributes (like depth, which is active etc).
Perhaps this can be done via swing itself (I'm not really familiar with swing).



4. Documentation.

Some of the documentation could be improved a bit with some detail added.