Saturday, 10 October 2015

RELEASE: PMAC 1.x Cleaning Utility

I've been doing some work on a very large PMAC item (18+ menus and close to 500 animations) and lost track of which of the huge number of animations in the object's inventory were actually being used so I wrote a little utility to help me and thought it might be useful to others who are working with the system.

It's a relatively simple script that analyzes all menu notecards in inventory, checks to make sure the animations they use are actually in PMAC's inventory, and then checks to see if there are animations in inventory that aren't used by any of your menus.

You can then ask it to supply a list of missing animations (it will tell you wish menu calls for it) or a list of the excess ones, or you can have it go ahead and delete all the excess ones for you.

Caution: if you use a custom add-on that calls for animations that aren't used in the menus, or there's any other scripts in the object that use animations, this utility won't catch that and would consider those as excess animations. You'd need to modify the script to handle those cases.

Just copy-paste the script from below and follow the instructions.

************** COPY EVERYTHING BELOW HERE *****************

// PMAC 1.x Cleaning Utility
// by Aine Caoimhe Oct 2015
// Release CC0 - full permission to do anything you like with it
//
// INSTRUCTIONS:
// 1. Create a new script in your PMAC object's inventory and paste this script into it.
// 2. Change the name of the base animation in the next line if you aren't using PMAC's default one
string baseAnimation="~~~~~base_DO_NOT_DELETE_ME!!!!!";
// 3. Decide whether to run in debug mode by setting the following line to either TRUE or FALSE
integer debug=FALSE; // TRUE will prevent auto-deleting of this script when you finish, FALSE will not
// 4. Save.
// 5. Normally this will auto-compile and run the script which triggers a dialog menu for you
// 6. Click the "ANALYSE" button to analyze the object
// 7. You'll see the progress of analysis in chat (which can take a little while if you have a system with tons of menus and animations)
// 8. You'll then be given a report of the analysis results.
// 9. If the utility found any animations that are called for by a menu but aren't in inventory, you can click the "MISSING" button to be given a list of them (in chat)
// 10. If the utility found any animations in inventory that aren't used by any of your menus you will have two additional buttons: "EXCESS" and "CLEAN"
//      If you click "EXCESS" the utility will give you a list of them (in chat) but not do anything about them
//      If you click "CLEAN" the utility will remove them for you (if you want a list of them, click "EXCESS" first, before you click this one)
// 11. You can re-analyze any time if you like.
// 12. Clicking "DONE" will exit the utility with no further changes and the utility script will delete itself from inventory (unless debug is enabled, in which case it will still be there and will initialize itself any time you reset the script)
// 13. If you fail to respond to a dialog within 5 minutes the script will auto-delete itself (unless in debug mode).
//
// This script doesn't check for any animation (gesture?) that might be called by an add-on or other script in the object so don't use the auto-clean unless you're sure
// nothing else in the object uses an animation.
//
// This script doesn't check for objects required by add-ons, nor does it do any other error-checking (it doesn't look for duplicate menu names, duplicate animation
// names, or anything else. This just strictly a "clean things up" utility. Of course you could modify the script to do any additional checking you want.
//
//
// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING!
integer myChannel;  // listens to a channel based on Owner UUID
integer handle;
list invAnims;
list invMenus;
list missing;
list found;
list excess;

showMain()
{
    string txtDia="Please click \"ANALYZE\" to have the PMAC Cleaner Utility examine your menus and check them against the animations in inventory.\n\nClicking \"DONE\" will abort the operation and remove the cleaner script";
    list butDia=["ANALYZE","DONE"];
    llDialog(llGetOwner(),txtDia,butDia,myChannel);
    llSetTimerEvent(300.0);
}
showResults()
{
    string txtDia="ANALYSIS RESULTS";
    txtDia+="\n* * * * * * * * * * * * * * * * * * * * * *";
    txtDia+="\nMenu notecards found: "+(string)llGetListLength(invMenus);
    txtDia+="\nAnimations needed and found: "+(string)llGetListLength(found);
    txtDia+="\nAnimations needed but missing: "+(string)(llGetListLength(missing)/2);
    txtDia+="\nAnimations found in inventory: "+(string)llGetListLength(invAnims);
    txtDia+="\nExcess (unused) animations: "+(string)llGetListLength(excess);
    txtDia+="\n* * * * * * * * * * * * * * * * * * * * * *";
    list butDia;
    if (llGetListLength(missing)>0)
    {
        txtDia+="\nPress \"MISSING\" to be given a list of missing animations";
        butDia=[]+butDia+["MISSING"];
    }
    if (llGetListLength(excess)>0)
    {
        txtDia+="\nPress \"EXCESS\" to be given a list of excess animations that can be safely deleted";
        txtDia+="\nPress \"CLEAN\" to have excess animations automatically deleted for you";
        butDia=[]+butDia+["EXCESS","CLEAN"];
    }
    txtDia+="\nPress \"ANALYZE\" to re-analyze this object";
    txtDia+="\nPress \"DONE\" to exit utility and delete the script";
    butDia=[]+butDia+["ANALYZE","DONE"];
    llDialog(llGetOwner(),txtDia,butDia,myChannel);
    llSetTimerEvent(300.0);
}
doAnalyze()
{
    llSetTimerEvent(0.0);
    buildInventoryLists();
    if (llGetListLength(invAnims)<1)
    {
        llOwnerSay("ERROR! Unable to find any animations in inventory!");
        killMe();
    }
    if (llGetListLength(invMenus)<1)
    {
        llOwnerSay("ERROR! Unable to find any menu notecards!");
        killMe();
    }
    found=[];
    missing=[];
    if (llListFindList(invAnims,[baseAnimation])==-1) missing=[]+missing+["BASE ANIMATION",baseAnimation];
    else found=[]+found+[baseAnimation];
    integer menu;
    integer stop=llGetListLength(invMenus);
    while (menu<stop)
    {
        string thisMenu=llList2String(invMenus,menu);
        llOwnerSay("Analyzing...checking "+thisMenu+"........");
        integer sitters=(integer)llGetSubString(thisMenu,7,7);
        list data=llParseString2List(osGetNotecard(thisMenu),["|","\n"],[]);
        integer line;
        integer end=llGetListLength(data);
        while (line<end)
        {
            integer thisSitter;
            while (thisSitter<sitters)
            {
                string animNeeded=llList2String(data,line+2+thisSitter*3);
                if (llListFindList(invAnims,[animNeeded])==-1) missing=[]+missing+[thisMenu,animNeeded];
                else if (llListFindList(found,[animNeeded])==-1) found=[]+found+[animNeeded];
                // else it's already in our list of found ones that we need
                thisSitter++;
            }
            line+=2+sitters*3;
        }
        menu++;
    }
    // found is now a complete list of required animations so compare actual inventory to it
    excess=[];
    integer a=llGetListLength(invAnims);
    while (--a>=0)
    {
        if (llListFindList(found,llList2List(invAnims,a,a))==-1) excess=[]+excess+[llList2String(invAnims,a)];
    }
    excess=[]+llListSort(excess,1,TRUE);
    showResults();
}
buildInventoryLists()
{
    invAnims=[];
    invMenus=[];
    llOwnerSay("Analyzing.....building animation list.....");
    integer i=llGetInventoryNumber(INVENTORY_ANIMATION);
    while (--i>=0) { invAnims=[]+[llGetInventoryName(INVENTORY_ANIMATION,i)]+invAnims;}
    llOwnerSay("Analyzing.....building menus list.....");
    i=llGetInventoryNumber(INVENTORY_NOTECARD);
    while (--i>=0)
    {
        string name=llGetInventoryName(INVENTORY_NOTECARD,i);
        if (llSubStringIndex(name,".menu")==0) invMenus=[]+[name]+invMenus;
    }
}
killMe()
{
    llSetTimerEvent(0.0);
    llListenRemove(handle);
    if (!debug)
    {
        llRemoveInventory(llGetScriptName());
        llOwnerSay("PMAC Cleaner Utility script deleted from inventory");
    }
    else llOwnerSay("PMAC Cleaner Utility script is currently in debug mode...timer and listener have been removed but the utility hasn't.");
}
default
{
    state_entry()
    {
        myChannel=0x80000000|(integer)("0x"+(string)llGetOwner());
        handle=llListen(myChannel,"",llGetOwner(),"");
        // should be impossible for the owner not to be present but just in case...
        if (llGetAgentSize(llGetOwner())!=ZERO_VECTOR) showMain();
        else if (!debug) killMe();
    }
    timer()
    {
        llOwnerSay("Dialog timed out.");
        killMe();
    }
    listen(integer channel, string name, key who, string message)
    {
        if (message=="DONE") killMe();
        else if (message=="ANALYZE") doAnalyze();
        else if (message=="MISSING")
        {
            llSetTimerEvent(0.0);
            string txtToSay="List of missing animations:";
            integer a;
            integer stop=llGetListLength(missing);
            while (a<stop)
            {
                if (llStringLength(txtToSay)>950) // avoid exceeding max chat string length
                {
                    llOwnerSay(txtToSay);
                    txtToSay="List of missing animations (continued)";
                    llSleep(0.1);
                }
                txtToSay+="\n"+llList2String(missing,a+1)+" is needed by menu "+llList2String(missing,a);
                a+=2;
            }
            llOwnerSay(txtToSay);
            showResults();
        }
        else if (message=="EXCESS")
        {
            llSetTimerEvent(0.0);
            string txtToSay="List of excess animations:";
            integer a;
            integer stop=llGetListLength(excess);
            while (a<stop)
            {
                if (llStringLength(txtToSay)>950) // avoid exceeding max chat string length
                {
                    llOwnerSay(txtToSay);
                    txtToSay="List of excess animations (continued)";
                    llSleep(0.1);
                }
                txtToSay+="\n"+llList2String(excess,a);
                a++;
            }
            llOwnerSay(txtToSay);
            showResults();
        }
        else if (message=="CLEAN")
        {
            llSetTimerEvent(0.0);
            llOwnerSay("Removing excess animations...please wait");
            integer a=llGetListLength(excess);
            while (--a>=0)
            {
                llRemoveInventory(llList2String(excess,a));
            }
            excess=[];
            llOwnerSay("Cleaning complete");
            showResults();
        }
    }