Donnerstag, 8. April 2010

Simple MACS preprocessor script

I stated in the last post, the processor script of the SDK is able to replace the commonly used resizer scripts and even allow much more usefull stuff with attachments for instance. Now its time to proove it was a true statement and no hot air. This mission will go in three steps:

First i provide the code for a generic preprocessor script. Using it will work for every object but just do not really more than the resizer scripts we want replace. In second step i will link two attachments to a single attached object and show how to change the preprocessor script, so the prims that belong to different objects prevously, are still handled individually. This step starts to use the special abilities of the processor script. In the last step the attachment is extended by further elements and other letters of TASC are used, not only the 'S' (scale). The preprocessor script becomes than also special.

The code will be provided peacevise with explanation what hapens here, so alxo if you are no scripter you have a chance to understand it.

1. Script preamble

integer MACS_CHANNEL   = -1301031903; // MACSC

list    PRIM_GROUPS    = [];

key     kOwner;

integer gMenuChannel;
integer gMenuListen;
integer gMenuPage;
integer gMenuMode;

integer gGroupsNum;

integer gTouchLink     = LINK_SET;
integer gTargetLink    = LINK_SET;
string  sTargetGroup   = "";

integer isClean        = FALSE;

What hapens here. We introduce two constants. MACS_CHANEL gives the number required for communication, so we do not touch it at all.

PRIM_GROUPS gives the names of prim groups. The preprocessor is made ready to distinct the prims but since this list is stil empty it stays generics. Later we have just to put the group names here to make the script special.

Further introduced variables you will understand by looking at further code.

2. Main menu

// Up to 6 target buttons
// [(this)] [(all)] [(clean)]
// [  <<  ] [     ] [   >>  ]
openMainMenu(integer page, integer refresh) {
    if (page > gGroupsNum-6) page = gGroupsNum-6;
    if (page < 0)            page = 0;
    gMenuPage = page;
    gMenuMode = 0;

    list    btns = llList2List(PRIM_GROUPS, page, page+5);
    integer len  = llGetListLength(btns) % 3;
    if (len > 0) {
        btns = btns + llList2List([" ", " "], 0, 3-len-1);

    list   btna = ["(this)", "(all)", "(clean)"];
    list   btnb = [];
    string p    = " ";
    string n    = " ";

    if (page > 0)            p = "<<";
    if (page < gGroupsNum-6) n = ">>";

    if (p != " " || n != " ") btnb = [p, " ", n];

    if (refresh) {
        gMenuListen = llListen(gMenuChannel, "", kOwner, "");

        "Please select target prims first:\n"+
        "(this)    - resize touched prim only\n"+
        "(all)       - resize all prims\n"+
        "(clean) - remove resizer scripts",
        btnb + btna + btns,


What hapens here. First the preconditions are resolved. The menu page is an index of shown slice in the list of target groups. It is checked first to be inside displayable bounds.The menu mode is set to 0, stating we show the main menu.

Next, the requested piece of group names is taken and extended by empty names to acheive 3 or 6 button names, making the shown menu more readable. Than the additional management and scrolling buttons are prepared.

Next the menu listener is reopened if allowed by refresh parameter. And finally the menu is shown and expiration timer is started with 30 seconds.

3. Resize menu

// [ + 1% ] [+ 5%] [ + 10% ]
// [ - 1% ] [- 5%] [ - 10% ]
// [(main)] [    ] [(reset)]
openResizeMenu() {
    gMenuMode = 1;
    string trg;
    if (sTargetGroup != "*")          trg = sTargetGroup;
    else if (gTargetLink == LINK_SET) trg = "(all prims)";
    else                              trg = "(this prim)";
        "Resize "+trg+" or abort:\n"+
        "(main)  - open main menu\n"+
        "(reset) - reset prim size to rezzing state",
        ["(main)", " ", "(reset)",
        "- 1%", "- 5%", "- 10%",
        "+ 1%", "+ 5%", "+ 10%"],


What hapens here.This is a submenu (we do not open listener here) with hardcoded menu buttons. The menu schows also the selected target group or what prims will be changed. Also the button to return to main menu is shown here.

The function resets further the expiration timer to 30 sek again. Also the menu mode is set to 1 saying we open the resize menu.

4. Closing menu handles

closeMenu() {

What hapens here. This operation is used multiple times, thus it came into a separate function. Here the timer is stoped and the menu listener is closed.

5. Handling of menu command

handleMenu(string cmd) {
    if (gMenuMode == 0) {
        if      (cmd == "<<") openMainMenu(gMenuPage-6, FALSE);
        else if (cmd == ">>") openMainMenu(gMenuPage+6, FALSE);
        else if (cmd == "(this)") {
            gTargetLink  = gTouchLink;
            sTargetGroup = "*";
        else if (cmd == "(all)") {
            gTargetLink  = LINK_SET;
            sTargetGroup = "*";
        else if (cmd == "(clean)") {
            isClean = TRUE;
                LINK_THIS, MACS_CHANNEL, "fix", NULL_KEY);
        else if (llListFindList(PRIM_GROUPS, [cmd]) > -1) {
            gTargetLink  = LINK_SET;
            sTargetGroup = cmd;
        else {
    else if (gMenuMode == 1) {  
        if (cmd == "(main)") {
            openMainMenu(gMenuPage, FALSE);
        else {
            if      (cmd == "(reset)") cmd = "";
            else if (cmd == "+ 1%")    cmd = ":1.01";
            else if (cmd == "+ 5%")    cmd = ":1.05";
            else if (cmd == "+ 10%")   cmd = ":1.1";

            else if (cmd == "- 1%")    cmd = ":0.9901";
            else if (cmd == "- 5%")    cmd = ":0.95238";
            else if (cmd == "- 10%")   cmd = ":0.90909";

            else {
            cmd = "size:"+sTargetGroup+cmd;
                gTargetLink, MACS_CHANNEL, cmd, NULL_KEY);

What hapens here. This is only function where the menu buttons are handled. First we check what menu was open, just to not to test for unused button names.

If in main menu (gMenuMode is 0)

The scrolling buttons reopen the main menu with another page number and disallowed refreshing of the listener.

The common prim selector "(this)" and "(all)" notice the target group as "*", that target matches every prim, and set the link number of the prim to receive the transformation command. If we select "(all)" the entire linkset will receive the command, if we select "(this)" only the touched prim. The resize menu is than opened.

The cleaning command "(clean)" sends out the "fix" procesor command. It says the processor to remove processor scripts from other prims but stay alive itself. Just because the processor will perform the reinstallation later if we need it. The menu is closed, that means the listen is removed and the timer stoped.

If no generic command was hit, we check if that was any target group selected. If so, the group name is noticed as target name and the resize menu is opened.

If an uncnown command was hit. the menu is just closed. That hapens if an empty button is hit in menu.

If in resize menu (gMenuMode is 1)

Here we check first if the "(main)" button was hit. If so, the main menu is reopened.

If an invalide (i.e. also empty) menu button was hit, the menu is simply closed. If one of valide menu buttons was hit, a transformation command is prepared. As we know from prevous tutorials the resize command is one of four:
  • "size:*:factor" - rescalling all prims by the given factor
  • "size:*" - resetting all prims to the size they had if rezzed
  • "size:name:factor" - rescalling only prims grouped by 'name'
  • "size:name" - resetting only prims grouped by 'name'
While factor is a float number that is multiplied to every size of the changed prims. The vactor 1.1 enlarges the prims by 10%, the factor 1/1,1 = 0.90909 reduces the prim size by 10%. This command has to be send via the command, e.g.

llMessageLinked(linknum, -1301031903, "size:*:1.5", NULL_KEY);

If we selected "(all)" before, or any special target group, entire Linkset receives this command. The processors have to decide than, if to execute or ignore the command, depends on if they are targetted or not. If we selected "(this)", this command is sent only to the prim we touched before.

6. Reinstalling scripts

restoreScripts() {
    isClean = FALSE;
        LINK_THIS, MACS_CHANNEL, "unfix", NULL_KEY);

What hapens here. Well, the menu handler has sent the "fix" command to initiate the scripts removal. After it is completed only a single processor remains, that one in the root prim. This functions initiates the opposite operation, it sends the "unfix" command which tells the remaining processor script to install itself in any other prim. How to do, it is its business.

What we just do is to notice if the scripts are actually cleaned or not, by actualizing the isClean variable.

7. Default state

default {
    state_entry() {
        gMenuChannel = (integer)llFrand(100000.0) - 2100000000;
        gGroupsNum   = llGetListLength(PRIM_GROUPS);
        kOwner       = llGetOwner();

    on_rez(integer start_param) {
        gMenuChannel = (integer)llFrand(100000.0) - 2100000000;
        gTargetLink  = LINK_SET;
        sTargetGroup = "";
        kOwner       = llGetOwner();

    touch_start(integer total_number) {
        integer i;
        for (i = 0 ; i < total_number ; ++i) {
            if (llDetectedKey(0) == kOwner) {
                if (isClean) {
                else {
                    gTouchLink = llDetectedLinkNumber(i);
                    openMainMenu(0, TRUE);

    listen(integer chan, string name, key agent, string msg) {

    timer() {

What hapens here, Here is the place to use the prevously defined functions.

state_entry event is called if the script initializes, here we select a menu channel, determine the owner key and number of prim groups. As long we have no group in the list, this number is 0.

on_rez event is called if the object is rezzed or attached. The menu channel is changed and the menu handles are closed. Also the owner key is determined again.

touch_start event: We check if the object was touched by the owner (only the owner has access) and if so, we check if the porcessor scripts are cleaned out. If yes, we initiate the script reinstalling. If not, we notice what prim was touched and open the main menu.

In the listen event we just let the function we defined above handle the menu buttons, since the listen is only open for the owner and only for the opened menu we do not have to check anything.

Finally the timer event is triggered if the menu was not responded in time or closed by ignoring it. Here we simply close the meny, that means close the listen handle and stop the timer.

That's all with the code. Now we put that into a script and test it briefly.

Preprocessor test

To do so we rez the UUC Piece three times from the SDK package. We arrange them somehow and link together, than put the preprocessor script script into the root prim.

Then we close the edit window, take the modell and rezz it again. This makes the processor scripts to notice the size and position of the prims.

Now we touch the left child prim. The main menu opens.
There we hit the "(all)" button and get the resize menu for all prims.

Here we hit the "+ 10%" button five times, than the "(main)" button and get the main menu again.

Here we hit the "(this)" button and get the resize menu for the left child prim (the script stil knows it). Now we hit "(reset)" button and revert changes on the left child prim.

This is the result we get. If we touch any prim again, hit the "(all)" button and than "(reset)", we revert changes to every prim and get the model in the state we started with.

Whats next

For now we are ready with the preprocessor script. It runs fine but does not use the special abilities of the processor script. In the next step we start to change that: We link two attachments together, equip the result with this script system and let it handle each prevous attachment individually.

Keine Kommentare:

Kommentar veröffentlichen