Difference between revisions of "Talk:Describe Chatter"

From Second Life Wiki
Jump to navigation Jump to search
m (→‎Date Of Birth: explain rollback to "date-of-birth" with more detail than fit in the article's history)
(→‎Describe More Than One Chatter: Maybe this script should remember every passing avatar, not just the last)
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== Change History ==
Wow.
I mis/remember the first 29 October 2007 version did compile. If I remember correctly, then the [[User:Winter_Ventura|Winter Ventura]] observation that the 1 February 2008 change makes the code compile again means someone broke the code by 3 November 2007 and then in all this time between no one managed to notice and complain or help.
I think I remain persuaded that the final consensus over correct textual representation of the numbers will be to match Profile > 2nd Life precedent exactly. We might add commentary in the source code clarifying what that somewhat cryptic English really means.
I know I myself at a glance see no clear sense presented in any version of the code past the original: that's why I haven't yet found any enthusiasm for spending any time compiling the later versions myself.
One of my projects conceived but not carried thru was to compile each version and simply experiment to see what it does, so I could come back here and write down those results to make them clear at a glance, like the newer code isn't.
Would be a community service for anyone to do that, I think.
-- [[User:Ppaatt Lynagh|Ppaatt Lynagh]] 17:01, 1 February 2008 (PST)
== Profile > 2nd Life Precedent ==
Ah. News to newbie me. The LL client already has established precedent here. LL has already provided a poor translation into English at Profile > 2nd Life as a precedent defining what conventional is here. I think we could/ should tweak the script to match that precedent. I'll try that next. -- [[User:Ppaatt Lynagh|Ppaatt Lynagh]] 08:31, 3 November 2007 (PDT)
== Describe More Than One Chatter ==
I've begun tweaking this script to meet and remember more than one of the chatters, also touchers, etc.
I'm not sure if such a minor variation should be its own article, or added as an alternative, or just discussed here, or what.
I've not yet really finished this variation -- I've not yet worked thru what it means to receive more chat while still processing chat received earlier. I saw one sim crash while wearing this gadget with a dozen people chatting, I don't know if the script helped or not. Certainly it would be more polite to code the script to remember more than one avatar but still only as many as fit, not indefinitely many.
Like so:
<lsl>
// Remember who chatted
// A la http://wiki.secondlife.com/wiki/Describe_Chatter
// FIXME: Cope reasonably well even if people chat more while this script is chatting
// FIXME: cope when one listen interrupts requestAgentData etc.
// Receive chat from one semi-private channel, not just chat from the public /0 channel
integer theSemiPrivateChannel = 1;
// Notice when this object is duplicated
key theGottenKey;
// Remember the avatars who chatted
list theAvatarKeys;
// Remember the names of the avatars who chatted
list theAvatarNames;
// Chat of all the avatars in response to chat of the full name of this object
// Chat of only one avatar in response to chat of the full name of that avatar
integer theVerbosely;
// Often work with one element of a list at a time
list thePoppableList;
// Reply by private instant message else by /0 public chat
key theReplyKey;
// Choose which avatar to ask the servers to describe
key theSentKey;
// Count news articles received since sending out the avatar's key
integer theReceivedServings;
// Count how many articles of news should be received per avatar
integer theReceiveableServings = 4;
// Assign a key to distinguish each article of news of an avatar
key theDataNameKey;
key theDataBornKey;
key theDataPayInfoKey;
key theDataOnlineKey;
// Remember each article of news of an avatar
string theDataNameString;
string theDataBornString;
string theDataPayInfoString;
string theDataOnlineString;
// Say how to convert to a list of words from a string of chars
list theSeparators = [" "]; // at most eight elements
list theSpacers = ["!", "(", ")", ",", ".", ":", ";", "?"]; // at most eight elements
// Start up
startup()
{
    scrubKeys();   
    llOwnerSay("Chat of the " + llGetObjectName() + " to hear news of every avatar who has chatted.");
    llOwnerSay("Chat the full name of any one avatar to hear only news of that avatar.");
}
// Forget old dataserver data
scrubKeys()
{   
    theDataNameKey = NULL_KEY;
    theDataBornKey = NULL_KEY;
    theDataPayInfoKey = NULL_KEY;
    theDataOnlineKey = NULL_KEY;
    theReceivedServings = 0;
}
// Consume one avatar key
string popString()
{
    string resultString = "";
   
    integer remaining = llGetListLength(thePoppableList);
    if (0 < remaining)
    {
        resultString = llList2String(thePoppableList, 0);
    }
   
    if (remaining == 1)
    {
        thePoppableList = [];
    }
    else
    {
        thePoppableList = llList2List(thePoppableList, 1, remaining);
    }
   
    return resultString;
}
// Ask to receive news of the next avatar
lookAtNextAvatar()
{
    scrubKeys();   
    string popped = popString();
    if (popped == "")
    {
        if (theVerbosely)
        {
            reply(countOfAvatars());
        }
    }
    else
    {
        theSentKey = (key) popped;
        sendKeys();
    }
}
// Chat news of the avatar
receiveKeys()
{
    list chatStrings = [
        theDataNameString,
        "Born " + theDataBornString,
        toPayInfoEcho(theDataPayInfoString),
        toOnlineEcho(theDataOnlineString)];
    if (llGetAgentSize(theSentKey) != ZERO_VECTOR)
    {
        chatStrings += "In the same region";
    }
    integer bits = llGetAgentInfo(theSentKey);
    chatStrings += toAgentInfoEchoes(bits);
   
    if (theVerbosely)
    {
        chatStrings += "Always found at " + (string) theSentKey;
    }
   
    string chatLine = llDumpList2String(
        chatStrings, " -- ");
    reply(chatLine);
   
    lookAtNextAvatar();
}
// Reply
reply(string chatLine)
{
    if (theReplyKey == NULL_KEY)
    {
        llSay(0, chatLine);
    }
    else
    {
        llInstantMessage(theReplyKey, chatLine);
    }
}
// Convert to string from llGetAgentInfo
list toAgentInfoEchoes(integer bits)
{
    list echoes = [];
    if (bits & AGENT_ALWAYS_RUN) echoes += "Always running"; // ??
    if (bits & AGENT_ATTACHMENTS) echoes += "Has attachments"; // ??
    if (bits & AGENT_AWAY) echoes += "Away";
    if (bits & AGENT_BUSY) echoes += "Busy";
    if (bits & AGENT_CROUCHING) echoes += "Crouching"; // ??
    if (bits & AGENT_FLYING) echoes += "Flying";
    if (bits & AGENT_IN_AIR) echoes += "In air";
    if (bits & AGENT_MOUSELOOK) echoes += "In mouselook";
    if (bits & AGENT_ON_OBJECT) echoes += "On object";
    if (bits & AGENT_SCRIPTED) echoes += "Has scripted attachments"; // ??
    if (bits & AGENT_SITTING) echoes += "Sitting";
    if (bits & AGENT_TYPING) echoes += "Typing";
    if (bits & AGENT_WALKING) echoes += "Walking";
    return echoes;
}
// Convert to string from PAYMENT_INFO
string toPayInfoEcho(string data)
{
    integer payInfo = (integer) data;
    if (payInfo == 0)
    {
        return "No payment info on file";
    }
    else if (payInfo & PAYMENT_INFO_USED)
    {
        return "Payment info used";
    }
    else if (payInfo & PAYMENT_INFO_ON_FILE)
    {
        return "Payment info on file";
    }
    else
    {
        return data;
    }
}
// Convert to string from DATA_ONLINE
string toOnlineEcho(string data)
{
    integer online = (integer) data;
    if (!online)
    {
        return "Not online now";
    }
    return "Online now";
}
// Receive an image of the chatter
catchKey(key queryid, string data)
{
        if (queryid == theDataNameKey)
        {
            theDataNameString = data;
        }
        else if (queryid == theDataBornKey)
        {
            theDataBornString = data;
        }       
        else if (queryid == theDataPayInfoKey)
        {
            theDataPayInfoString = data;
        }
        else if (queryid == theDataOnlineKey)
        {
            theDataOnlineString = data;
        }
}
// Ask to receive an image of the chatter in pieces
sendKeys()
{
    theDataNameKey = llRequestAgentData(theSentKey, DATA_NAME);
    theDataBornKey = llRequestAgentData(theSentKey, DATA_BORN);
    theDataPayInfoKey = llRequestAgentData(theSentKey, DATA_PAYINFO);
    theDataOnlineKey = llRequestAgentData(theSentKey, DATA_ONLINE);
}
// Count avatars who have chatted
string countOfAvatars()
{
    return "This " + llGetObjectName() +
        " has heard chat from " +
        (string) llGetListLength(theAvatarKeys)
        + " avatars.";
}
// Remember each avatar who chatted (until reset)
meetAvatar(string name, key id)
{
    if (llGetAgentSize(id) != ZERO_VECTOR)
    {
        if (llListFindList(theAvatarKeys, [id]) < 0)
        {
            theAvatarKeys += id;
            theAvatarNames += name;
            llOwnerSay(countOfAvatars());
        }
    }
}
// Reply to /0 chat with /0 chat, reply to /1 chat with instant messages.
directReply(integer channel, key id)
{
        theReplyKey = NULL_KEY;
        if (channel != 0)
        {
            theReplyKey = id;
        }
        else
        {
            llInstantMessage(id, // sometimes id is llGetOwner(), sometimes not
                "Chat at /" + ((string) theSemiPrivateChannel) +
                " to hear the news by private instant message like this.");
        }
}
// For each reset ...
default
{
    // Start up and then listen for chat
   
    state_entry()
    {
        theGottenKey = llGetKey();
        startup();
        llListen(PUBLIC_CHANNEL, "", NULL_KEY, ""); // ordinary chat at /0
        llListen(theSemiPrivateChannel, "", NULL_KEY, ""); // semi-private chat at /1
    }
   
    // React to a chatline
   
    listen(integer channel, string name, key id, string message)
    {
       
        // Meet the avatar who chatted, for the first time or again later
        meetAvatar(name, id);
        // Convert to words from string.
        list chattedWords = llParseString2List(message, theSeparators, theSpacers);
        // Respond to mention of this object.
       
        list objectNameParts = llParseString2List(llGetObjectName(), theSeparators, theSpacers);
        if (0 <= llListFindList(chattedWords, objectNameParts))
        {
            theVerbosely = 1;
            thePoppableList = theAvatarKeys;
            directReply(channel, id);
            lookAtNextAvatar();
            return;
        }
        // Respond to mention of any one avatar's name.
       
        thePoppableList = theAvatarNames;
        integer missed = 0;
        string avatarName = popString();
        while (avatarName != "")
        {
            list avatarNameParts = llParseString2List(avatarName, theSeparators, theSpacers);
            if (0 <= llListFindList(chattedWords, avatarNameParts))
            {
                theVerbosely = 0;
                thePoppableList = [llList2Key(theAvatarKeys, missed)];
                directReply(channel, id);
                lookAtNextAvatar();
                return;
            }
            avatarName = popString();
            missed += 1;
        }
    }
   
    // Receive every piece of an image of the chatter
   
    dataserver(key queryid, string data)
    {       
        catchKey(queryid, data); 
        theReceivedServings += 1;
        if (theReceivedServings == theReceiveableServings)
        {
            receiveKeys();
        }       
    }
}
</lsl>
== Date Of Birth ==
== Date Of Birth ==


Line 46: Line 458:


For me, the new code isn't as easy to read at a glance as the old code. I see the sample results have not changed. I suspect that me rerunning the code might produce different sample results.
For me, the new code isn't as easy to read at a glance as the old code. I see the sample results have not changed. I suspect that me rerunning the code might produce different sample results.
--[[User:Ppaatt Lynagh|Ppaatt Lynagh]]
:Payment info indicates the status of your account with regards to you being able to pay tier or buy $Lindens. If you have payment info on file then they have a CC number or Paypal. If they have made a charge to it then they also set the USED flag. Your account can have a positive US$ balance without having payment info on file or used, so saying "Has (no) money" is not a fact, the person may also be broke in real life but have payment info on file. -- [[User:Strife Onizuka|Strife Onizuka]] 13:11, 3 November 2007 (PDT)

Latest revision as of 17:15, 1 February 2008

Change History

Wow.

I mis/remember the first 29 October 2007 version did compile. If I remember correctly, then the Winter Ventura observation that the 1 February 2008 change makes the code compile again means someone broke the code by 3 November 2007 and then in all this time between no one managed to notice and complain or help.

I think I remain persuaded that the final consensus over correct textual representation of the numbers will be to match Profile > 2nd Life precedent exactly. We might add commentary in the source code clarifying what that somewhat cryptic English really means.

I know I myself at a glance see no clear sense presented in any version of the code past the original: that's why I haven't yet found any enthusiasm for spending any time compiling the later versions myself.

One of my projects conceived but not carried thru was to compile each version and simply experiment to see what it does, so I could come back here and write down those results to make them clear at a glance, like the newer code isn't.

Would be a community service for anyone to do that, I think.

-- Ppaatt Lynagh 17:01, 1 February 2008 (PST)

Profile > 2nd Life Precedent

Ah. News to newbie me. The LL client already has established precedent here. LL has already provided a poor translation into English at Profile > 2nd Life as a precedent defining what conventional is here. I think we could/ should tweak the script to match that precedent. I'll try that next. -- Ppaatt Lynagh 08:31, 3 November 2007 (PDT)

Describe More Than One Chatter

I've begun tweaking this script to meet and remember more than one of the chatters, also touchers, etc.

I'm not sure if such a minor variation should be its own article, or added as an alternative, or just discussed here, or what.

I've not yet really finished this variation -- I've not yet worked thru what it means to receive more chat while still processing chat received earlier. I saw one sim crash while wearing this gadget with a dozen people chatting, I don't know if the script helped or not. Certainly it would be more polite to code the script to remember more than one avatar but still only as many as fit, not indefinitely many.

Like so:

<lsl> // Remember who chatted // A la http://wiki.secondlife.com/wiki/Describe_Chatter

// FIXME: Cope reasonably well even if people chat more while this script is chatting // FIXME: cope when one listen interrupts requestAgentData etc.

// Receive chat from one semi-private channel, not just chat from the public /0 channel

integer theSemiPrivateChannel = 1;

// Notice when this object is duplicated

key theGottenKey;

// Remember the avatars who chatted

list theAvatarKeys;

// Remember the names of the avatars who chatted

list theAvatarNames;

// Chat of all the avatars in response to chat of the full name of this object // Chat of only one avatar in response to chat of the full name of that avatar

integer theVerbosely;

// Often work with one element of a list at a time

list thePoppableList;

// Reply by private instant message else by /0 public chat

key theReplyKey;

// Choose which avatar to ask the servers to describe

key theSentKey;

// Count news articles received since sending out the avatar's key

integer theReceivedServings;

// Count how many articles of news should be received per avatar

integer theReceiveableServings = 4;

// Assign a key to distinguish each article of news of an avatar

key theDataNameKey; key theDataBornKey; key theDataPayInfoKey; key theDataOnlineKey;

// Remember each article of news of an avatar

string theDataNameString; string theDataBornString; string theDataPayInfoString; string theDataOnlineString;

// Say how to convert to a list of words from a string of chars

list theSeparators = [" "]; // at most eight elements list theSpacers = ["!", "(", ")", ",", ".", ":", ";", "?"]; // at most eight elements

// Start up

startup() {

   scrubKeys();    
   llOwnerSay("Chat of the " + llGetObjectName() + " to hear news of every avatar who has chatted.");
   llOwnerSay("Chat the full name of any one avatar to hear only news of that avatar.");

}

// Forget old dataserver data

scrubKeys() {

   theDataNameKey = NULL_KEY;
   theDataBornKey = NULL_KEY;
   theDataPayInfoKey = NULL_KEY;
   theDataOnlineKey = NULL_KEY;
   theReceivedServings = 0;

}

// Consume one avatar key

string popString() {

   string resultString = "";
   
   integer remaining = llGetListLength(thePoppableList);
   if (0 < remaining)
   {
       resultString = llList2String(thePoppableList, 0);
   }
   
   if (remaining == 1)
   {
       thePoppableList = [];
   }
   else
   {
       thePoppableList = llList2List(thePoppableList, 1, remaining);
   }
   
   return resultString;

}

// Ask to receive news of the next avatar

lookAtNextAvatar() {

   scrubKeys();    
   string popped = popString();
   if (popped == "")
   {
       if (theVerbosely)
       {
           reply(countOfAvatars());
       }
   }
   else
   {
       theSentKey = (key) popped;
       sendKeys();
   }

}

// Chat news of the avatar

receiveKeys() {

   list chatStrings = [
       theDataNameString,
       "Born " + theDataBornString,
       toPayInfoEcho(theDataPayInfoString),
       toOnlineEcho(theDataOnlineString)];
   if (llGetAgentSize(theSentKey) != ZERO_VECTOR)
   {
       chatStrings += "In the same region";
   }
   integer bits = llGetAgentInfo(theSentKey);
   chatStrings += toAgentInfoEchoes(bits);
   
   if (theVerbosely)
   {
       chatStrings += "Always found at " + (string) theSentKey;
   }
   
   string chatLine = llDumpList2String(
       chatStrings, " -- ");
   reply(chatLine);
   
   lookAtNextAvatar();

}

// Reply

reply(string chatLine) {

   if (theReplyKey == NULL_KEY)
   {
       llSay(0, chatLine);
   }
   else
   {
       llInstantMessage(theReplyKey, chatLine);
   }

}

// Convert to string from llGetAgentInfo

list toAgentInfoEchoes(integer bits) {

   list echoes = [];
   if (bits & AGENT_ALWAYS_RUN) echoes += "Always running"; // ??
   if (bits & AGENT_ATTACHMENTS) echoes += "Has attachments"; // ??
   if (bits & AGENT_AWAY) echoes += "Away";
   if (bits & AGENT_BUSY) echoes += "Busy";
   if (bits & AGENT_CROUCHING) echoes += "Crouching"; // ??
   if (bits & AGENT_FLYING) echoes += "Flying";
   if (bits & AGENT_IN_AIR) echoes += "In air";
   if (bits & AGENT_MOUSELOOK) echoes += "In mouselook";
   if (bits & AGENT_ON_OBJECT) echoes += "On object";
   if (bits & AGENT_SCRIPTED) echoes += "Has scripted attachments"; // ??
   if (bits & AGENT_SITTING) echoes += "Sitting";
   if (bits & AGENT_TYPING) echoes += "Typing";
   if (bits & AGENT_WALKING) echoes += "Walking";
   return echoes;

}

// Convert to string from PAYMENT_INFO

string toPayInfoEcho(string data) {

   integer payInfo = (integer) data;
   if (payInfo == 0)
   {
       return "No payment info on file";
   }
   else if (payInfo & PAYMENT_INFO_USED)
   {
       return "Payment info used";
   }
   else if (payInfo & PAYMENT_INFO_ON_FILE)
   {
       return "Payment info on file";
   }
   else
   {
       return data;
   }

}

// Convert to string from DATA_ONLINE

string toOnlineEcho(string data) {

   integer online = (integer) data;
   if (!online)
   {
       return "Not online now";
   }
    return "Online now";

}

// Receive an image of the chatter

catchKey(key queryid, string data) {

       if (queryid == theDataNameKey)
       {
           theDataNameString = data;
       }
       else if (queryid == theDataBornKey)
       {
           theDataBornString = data;
       }        
       else if (queryid == theDataPayInfoKey)
       {
           theDataPayInfoString = data;
       }
       else if (queryid == theDataOnlineKey)
       {
           theDataOnlineString = data;
       }

}

// Ask to receive an image of the chatter in pieces

sendKeys() {

   theDataNameKey = llRequestAgentData(theSentKey, DATA_NAME);
   theDataBornKey = llRequestAgentData(theSentKey, DATA_BORN);
   theDataPayInfoKey = llRequestAgentData(theSentKey, DATA_PAYINFO);
   theDataOnlineKey = llRequestAgentData(theSentKey, DATA_ONLINE);

}

// Count avatars who have chatted

string countOfAvatars() {

   return "This " + llGetObjectName() +
       " has heard chat from " +
       (string) llGetListLength(theAvatarKeys)
       + " avatars.";

}

// Remember each avatar who chatted (until reset)

meetAvatar(string name, key id) {

   if (llGetAgentSize(id) != ZERO_VECTOR)
   {
       if (llListFindList(theAvatarKeys, [id]) < 0)
       {
           theAvatarKeys += id;
           theAvatarNames += name;
           llOwnerSay(countOfAvatars());
       }
   }

}

// Reply to /0 chat with /0 chat, reply to /1 chat with instant messages.

directReply(integer channel, key id) {

       theReplyKey = NULL_KEY;
       if (channel != 0)
       {
           theReplyKey = id;
       }
       else
       {
           llInstantMessage(id, // sometimes id is llGetOwner(), sometimes not
               "Chat at /" + ((string) theSemiPrivateChannel) +
               " to hear the news by private instant message like this.");
       }

}

// For each reset ...

default {

   // Start up and then listen for chat
    
   state_entry()
   {
       theGottenKey = llGetKey();
       startup();
       llListen(PUBLIC_CHANNEL, "", NULL_KEY, ""); // ordinary chat at /0
       llListen(theSemiPrivateChannel, "", NULL_KEY, ""); // semi-private chat at /1
   }
   
   // React to a chatline
    
   listen(integer channel, string name, key id, string message)
   {
       
       // Meet the avatar who chatted, for the first time or again later
       meetAvatar(name, id);
       // Convert to words from string.
       list chattedWords = llParseString2List(message, theSeparators, theSpacers);
       // Respond to mention of this object.
       
       list objectNameParts = llParseString2List(llGetObjectName(), theSeparators, theSpacers);
       if (0 <= llListFindList(chattedWords, objectNameParts))
       {
           theVerbosely = 1;
           thePoppableList = theAvatarKeys;
           directReply(channel, id);
           lookAtNextAvatar();
           return;
       }
       // Respond to mention of any one avatar's name.
       
       thePoppableList = theAvatarNames;
       integer missed = 0;
       string avatarName = popString();
       while (avatarName != "")
       {
           list avatarNameParts = llParseString2List(avatarName, theSeparators, theSpacers);
           if (0 <= llListFindList(chattedWords, avatarNameParts))
           {
               theVerbosely = 0;
               thePoppableList = [llList2Key(theAvatarKeys, missed)];
               directReply(channel, id);
               lookAtNextAvatar();
               return;
           }
           avatarName = popString();
           missed += 1;
       }
   }
   
   // Receive every piece of an image of the chatter
   
   dataserver(key queryid, string data)
   {        
       catchKey(queryid, data);   
       theReceivedServings += 1;
       if (theReceivedServings == theReceiveableServings)
       {
           receiveKeys();
       }        
   }

} </lsl>

Date Of Birth

DATA_BORN is the Second Life identifier for this.

I think our old phrase "date-of-birth" is the correct English to represent that source code, not our new coinage "date-of-creation". It is the second birth that we are discussing, I think. -- Ppaatt Lynagh 29 October 2007

I notice alongside the drearily conventional phrase "date-of-birth" we also had the drearily conventional phrase "legal name" representing DATA_NAME, together with "online" and "not online" representing DATA_ONLINE. The history of English phrases we use to represent DATA_PAYINFO I do not yet understand. -- Ppaatt Lynagh 06:09, 2 November 2007 (PDT)
By the discussion above, I have now rolled the article back to the drearily conventional phrase "date-of-birth", as in http://en.wikipedia.org/wiki/DOB, away from the new coinage "date-of-creation". Maybe DOB is by now our consensus least-awful design choice here. -- Ppaatt Lynagh 06:09, 2 November 2007 (PDT)

Has No Money

Sounds like we're confused over whether we talk of second money denoted in L$, which any avatar may have, and the real money that comes from a registered credit card, denoted in Euros or US$ or whatever.

I see the history says:

Your account can be credited money without having payment info; payment info can be removed from the account after using it.

This comment explains the new code vs. the old code that was:

// Convert to string from PAYMENT_INFO

string toPayInfoEcho(string data)
{
    integer payInfo = (integer) data;
    if (payInfo == 0)
    {
        return "Has no money";
    }
    else if (payInfo & PAYMENT_INFO_USED)
    {
        return "Has spent money";
    }
    else if (payInfo & PAYMENT_INFO_ON_FILE)
    {
        return "Has money";
    }
    else
    {
        return data;
    }

I don't yet understand this change.

For me, the new code isn't as easy to read at a glance as the old code. I see the sample results have not changed. I suspect that me rerunning the code might produce different sample results. --Ppaatt Lynagh

Payment info indicates the status of your account with regards to you being able to pay tier or buy $Lindens. If you have payment info on file then they have a CC number or Paypal. If they have made a charge to it then they also set the USED flag. Your account can have a positive US$ balance without having payment info on file or used, so saying "Has (no) money" is not a fact, the person may also be broke in real life but have payment info on file. -- Strife Onizuka 13:11, 3 November 2007 (PDT)