// This script handles main behaviour for i-room helper robot // This includes: sensing avatars and issuing greetings, and // handling simple activities. // // SP Pizzicato, Jussi Aya and Ai Austin 21-Jul-2008 // Based on script by AITommy Jannings 29-May-2007 // If XML-RPC Channel Id is needed to be used in this script then // this script must be started before GreetingComms script is started. //--- Global variables ---------- // Labels for on and off state string OFF_LABEL = "I-Room Helper (off)"; string ON_LABEL = "I-Room Helper"; string ME = "I-Room Helper"; // I-X activities string START_ACTIVITY = "start"; string STOP_ACTIVITY = "stop"; string PROGRESS_REPORT = "activity progressing"; string COMPLETION_REPORT = "activity completed"; string INFORMATION_REPORT = "activity information"; string STARTED_ACTIVITY = "activity started"; string STOPPED_ACTIVITY = "activity stopped"; string STATE_CONSTRAINT = "state-constraint"; string DISPLAY = "display"; string STOP = "stop"; string SAY = "say"; string READ = "read"; string DESCRIBE = "describe"; string INFORM = "inform"; string MINUTE = "minute"; string ACTION = "action"; string DECISION = "decision"; string AGENDA = "agenda"; string TO = "to"; string OBJECTS = "objects"; string AVATARS = "avatars"; string NOTECARD = "notecard"; string CAPABILITY = "capability"; string CHANNEL = "channel"; // Demonstration using an object listening on a given channel for commands //integer SCREEN_CHANNEL = 10; string NOTECARD_NAME = "I-Room Setup"; string DISPLAY_NOTECARD_NAME = "I-Room Display"; integer num_notecard_lines = 0; key notecard_request = NULL_KEY; key display_request = NULL_KEY; key http_request_id = NULL_KEY; integer notecard_line = 0; list card_data = []; list capability_list = []; list channel_list = []; list display_data = []; list display_list = []; // Currently detected avatar string avatarname; // List of all visitors greeted list visitors; // Scan frequency (seconds) float SCAN_FREQ = 5.0; integer RANGE = 15; // Communication channel ID key RPCChannelID; string CHANNEL_ID_MESSAGE = "cid"; key XML_channel; integer channel = 9; //---------- Functions --------------------- // Turn on behaviour - reply to activity? turn_on(integer reply) { // Start spinning llTargetOmega(<0.0,0.0,1.0>, -1.5, 1.0); // Set on label llSetText(ON_LABEL, <1.0,1.0,1.0>, 1); if ( reply == TRUE ) { // Reply with STARTED_ACTIVITY message. llMessageLinked(LINK_SET, 1, STARTED_ACTIVITY, NULL_KEY); } llOpenRemoteDataChannel(); } // Turn off behaviour turn_off() { // Rotation and off text are actually set in state entry handler for default (off) state anyway // No rotation // llTargetOmega(<0.0,0.0,0.0>, 0, 0.0); // Set off label // llSetText(OFF_LABEL, <1.0,1.0,1.0>, 1); // Report back that activity is stopped llMessageLinked(LINK_THIS, 1, STOPPED_ACTIVITY, NULL_KEY); } integer is_notecard(string name) // check that that the named inventory item is a notecard { integer i = llGetInventoryType(name); return i == INVENTORY_NOTECARD; } integer get_default_display_channel(){ //return (integer)llList2String(card_data,1); // find something with the capability DISPLAY: integer occ = llListFindList(capability_list,(list)DISPLAY); if(occ==-1) // no displayers: return 0; else{ string displayer = llList2String(capability_list,occ-1); //llWhisper(0, "using "+displayer+" as default displayer"); return get_object_channel(displayer); } } integer lookup_capability_channel(string capa){ //llSay(0,(string)capability_list); integer occ = llListFindList(capability_list,(list)capa); if(occ==-1) // none capable - write to channel 0?: return 0; else{ string capable = llList2String(capability_list,occ-1); //llWhisper(0, capable +" can perform " +capa); return get_object_channel(capable); } } integer old_get_screen_channel(string name){ //llWhisper(0, "Request for channel of "+name); integer occ = llListFindList(card_data,(list)name); if(occ==-1) return 0; else return (integer)llList2String(card_data,occ+1); } integer get_object_channel(string name){ //llWhisper(0, "Request for channel of "+name+" "+(string)channel_list+" "+(string)capability_list); integer occ = llListFindList(channel_list,(list)name); if(occ==-1) return 0; else return (integer)llList2String(channel_list,occ+1); } process_card_data(string cardline){ // cardline is a string integer caps = llSubStringIndex(cardline, CAPABILITY); if(caps!=-1){ // ie this is a capability description! list templ = llParseString2List(cardline, [" "],[]); capability_list = llListInsertList(capability_list, [llList2String(templ,1),llList2String(templ,2)], llGetListLength(capability_list)); } caps = llSubStringIndex(cardline, CHANNEL); if(caps!=-1){ list templ = llParseString2List(cardline, [" "],[]); channel_list = llListInsertList(channel_list, [llList2String(templ,1),llList2String(templ,2)], llGetListLength(channel_list)); } } process_display_data(string cardline){ //llSay(0,cardline); list temp = llParseString2List(cardline, [" "],[]); display_list = llListInsertList(display_list, [llList2String(temp,0),llList2String(temp,1),llList2String(temp,2), llList2String(temp,3),llList2String(temp,4)], llGetListLength(display_list)); } string lookup_display_info(string name){ //llWhisper(0, "Request for info of "+name+" "+(string)display_list); integer occ = llListFindList(display_list,(list)name); if(occ==-1) return ""; else return llDumpList2String(llList2List(display_list,occ+1,occ+4)," "); } send_constraint(string attribute, string object, string value) { llMessageLinked(LINK_THIS, 1, STATE_CONSTRAINT+" "+attribute+" "+object+" = "+value, NULL_KEY); } send_completion_report() { llMessageLinked(LINK_THIS, 1, COMPLETION_REPORT, NULL_KEY); } send_progress_report() { llMessageLinked(LINK_THIS, 1, PROGRESS_REPORT, NULL_KEY); } send_information_report(string contents) { llMessageLinked(LINK_THIS, 1, INFORMATION_REPORT+" "+contents, NULL_KEY); } //handle_message(string strValue){ //} //-------------- States ----------------- // Entry state for script. Wait to be switched on. Acts also as off state default { state_entry() { // Set off label llSetText(OFF_LABEL, <1.0,1.0,1.0>, 1); // No rotation llTargetOmega(<0.0,0.0,0.0>, 0, 0.0); // Always start with empty list of visitors. visitors = []; } on_rez(integer start_param) { // every time we're rezzed, reset the script // this ensures that all local variables are set afresh llResetScript(); } link_message(integer sender_num, integer num, string stringValue, key id) { if ( stringValue == START_ACTIVITY ) { turn_on(TRUE); // Transition to sensing state state sensing; } else if ( llSubStringIndex( stringValue, CHANNEL_ID_MESSAGE ) == 0 ) { integer cid_len = llStringLength(CHANNEL_ID_MESSAGE); integer str_len = llStringLength(stringValue); RPCChannelID = llGetSubString(stringValue, cid_len, str_len - 1); } } // Start robot behaviour by touching it, // if one of the touches is in same group as object. touch_start(integer num) { integer x = 0; for(; x < num; x++) { if ( llSameGroup(llDetectedKey(x)) ) { turn_on(FALSE); visitors = []; if (RPCChannelID) { llInstantMessage(llDetectedKey(x), "Communication channel key is: " + (string) RPCChannelID); } else { //no valid channel ID, get comms to say it! llMessageLinked(LINK_THIS, 2, CHANNEL_ID_MESSAGE, NULL_KEY); } // check notecard exists: if(!is_notecard(NOTECARD_NAME)) { state error; } else{ //llWhisper(0,"Parsing notecard: "+NOTECARD_NAME); capability_list = []; channel_list = []; notecard_request = NULL_KEY; notecard_line = 0; num_notecard_lines = 0; notecard_request = llGetNumberOfNotecardLines(NOTECARD_NAME); // ask for the number of lines in the card llSetTimerEvent(5.0); // if we don't hear back in 5 secs, then the card might have been empty } //state sensing; } } } timer() { llSetTimerEvent(0.0); state error; } dataserver(key query_id, string data){ if(query_id == notecard_request) { llSetTimerEvent(0.0); if(data == EOF) { state sensing; } else if (num_notecard_lines==0) { num_notecard_lines = (integer) data; notecard_request = llGetNotecardLine(NOTECARD_NAME, notecard_line); } else { if(data != "" && llGetSubString(data, 0, 0) != "#") { card_data = (card_data = []) + card_data + data; process_card_data(data); } ++notecard_line; notecard_request = llGetNotecardLine(NOTECARD_NAME, notecard_line); } } //llWhisper(0,"read "+ (string)(notecard_line) + " of " + (string)num_notecard_lines + " lines"); } } state error { state_entry() { llOwnerSay("something went wrong; try checking that the notecard [ " + NOTECARD_NAME + " ] exists and contains data"); llResetScript(); } changed(integer change) { if (change & CHANGED_INVENTORY) { llResetScript(); } } } // Sense surrounding area and greet new visitors. state sensing { state_entry() { // Repeatedly sense every 20secs for 10mts for avatars. llSensorRepeat( "", "", AGENT, RANGE, PI, SCAN_FREQ ); llListen(channel,"","",""); } sensor(integer total_number) { //llShout(0,"Visitors = "+(string)visitors); integer i = 0; // store old list of visitors and reset list: list old_visitors = visitors; visitors = []; for(i=0; i < total_number; i++) { avatarname = llDetectedName(i); // add to list of current visitors: visitors += [avatarname]; integer index = llListFindList( old_visitors, [avatarname] ); // Only greet new visitors. if ( index == -1 ) { // Stop sensing for avatars //llSensorRemove(); // Greet visitor! llSay(0, "Hello, " + avatarname + ", welcome!"); // Send activity progress report //llMessageLinked(LINK_THIS, 0, PROGRESS_REPORT+avatarname, NULL_KEY); send_constraint("present", "\""+avatarname+"\"", "true"); } } for(i=0; i < llGetListLength(old_visitors); i++){ string oldv = llList2String(old_visitors,i); if(llListFindList(visitors,[oldv])==-1){ // ie if an avatar in old_visitors is not in (current) visitors: llShout(0,"Goodbye "+oldv+"!"); send_constraint("present", "\""+oldv+"\"", "false"); } } } no_sensor(){ // nothing close: integer i; integer vn = llGetListLength(visitors); for(i=0;i0) process_display_data(llList2String(inmessage,i)); else{ string temp = llList2String(inmessage,i); temp = llGetSubString(temp,llStringLength(INFORM),llStringLength(temp)); process_display_data(temp); } } if(num==-1) // ie send completion_report iff this is a remote call... send_completion_report(); } else if(verb == SAY){ // remove verb: message = llDeleteSubList(message,0,0); // is the next item an escaped forward slash (indicating channel)? string next = llList2String(message,0); //if(next == "\\\\"){ if(llSubStringIndex(next,"/")==0){ //message = llDeleteSubList(message,0,0); //integer chan = llList2Integer(message,1); integer chan = (integer) llGetSubString(next,1,llStringLength(next)); message = llDeleteSubList(message,0,0); //llSay(0, "Saying "+ llDumpList2String(message," ") +" on channel "+ (string)chan); llSay(chan,llDumpList2String(message," ")); } else // simply echo the message: llSay(0,llDumpList2String(message," ")); if(num==-1) // ie send completion_report iff this is a remote call... send_completion_report(); } else if(verb == READ){ string what = llList2String(message,1); if(what == NOTECARD){ // want to reset capability lists and re-read the notecard... card_data = []; capability_list = []; channel_list = []; notecard_request = NULL_KEY; notecard_line = 0; num_notecard_lines = 0; notecard_request = llGetNumberOfNotecardLines(NOTECARD_NAME); // ask for the number of lines in the card llSetTimerEvent(5.0); // if we don't hear back in 5 secs, then the card might have been empty if(num==-1) // ie send completion_report iff this is a remote call... send_completion_report(); } } else if(verb == DESCRIBE){ string what = llList2String(message,1); if(what == OBJECTS){ llSay(0,"knows about the following objects: " + llDumpList2String(capability_list, " ")); if(num==-1) // ie send completion_report iff this is a remote call... send_completion_report(); } if(what == AVATARS){ llSay(0,"knows the following avatars are nearby: " + llDumpList2String(visitors, " ")); integer i; for(i=0; i < llGetListLength(visitors); i++){ string visitor = llList2String(visitors,i); send_constraint("present", "\""+visitor+"\"", "true"); } if(num==-1) // ie send completion_report iff this is a remote call... send_completion_report(); } if(what == DISPLAY){ llSay(0,"knows about the following display elements: "+llDumpList2String(display_list," ")); if(num==-1) // ie send completion_report iff this is a remote call... send_completion_report(); } } else if(verb==MINUTE){ if(llGetListLength(message)>1){ // minute string what = llDumpList2String(llDeleteSubList(message,0,0)," "); llSay(0,ME+ " minuting \""+what+"\""); send_information_report(MINUTE+" "+what); } else llSay(0,"Ill-formed minute - should be \""+MINUTE+" \""); } else if(verb==DECISION){ if(llGetListLength(message)>1){ // minute string what = llDumpList2String(llDeleteSubList(message,0,0)," "); llSay(0,ME+ " recording decision: \""+what+"\""); send_information_report(DECISION+" "+what); } else llSay(0,"Ill-formed decision - should be \""+DECISION+" \""); } else if(verb==AGENDA){ if(llGetListLength(message)>1){ // minute string what = llDumpList2String(llDeleteSubList(message,0,0)," "); llSay(0,ME+ " recording agenda item for next meeting: \""+what+"\""); send_information_report(AGENDA+" "+what); } else llSay(0,"Ill-formed agenda info - should be \""+AGENDA+" \""); } else if(verb==ACTION){ if(llGetListLength(message)>3){ // action string av = llDumpList2String(llList2List(message,1,2)," "); integer ai = llListFindList(visitors,[av]); if(ai==-1) llSay(0, "Note: "+av+" is not present - recording action anyway!"); //else{ string what = llDumpList2String(llDeleteSubList(message,0,2)," "); llSay(0, ME+" noting action on "+av+": \""+what+"\""); send_information_report(ACTION + " "+llDumpList2String(llDeleteSubList(message,0,0)," ")); //} } else llSay(0,"Ill-formed action - should be \""+ACTION+" \""); } else { // unrecognised verb - does anything have this capability? integer chan = lookup_capability_channel(verb); llSay(chan, strValue); //send_progress_report(); } } } dataserver(key query_id, string data){ if(query_id == notecard_request) { llSetTimerEvent(0.0); if(data == EOF) { state sensing; } else if (num_notecard_lines==0) { num_notecard_lines = (integer) data; notecard_request = llGetNotecardLine(NOTECARD_NAME, notecard_line); } else { if(data != "" && llGetSubString(data, 0, 0) != "#") { card_data = (card_data = []) + card_data + data; process_card_data(data); } ++notecard_line; notecard_request = llGetNotecardLine(NOTECARD_NAME, notecard_line); } //llWhisper(0,"re-read "+ (string)(notecard_line) + " of " + (string)num_notecard_lines + " lines"); } else if(query_id == display_request) { // this works but is not currently used... llSetTimerEvent(0.0); if(data == EOF) { state sensing; } else if (num_notecard_lines==0) { num_notecard_lines = (integer) data; display_request = llGetNotecardLine(DISPLAY_NOTECARD_NAME, notecard_line); } else { if(data != "" && llGetSubString(data, 0, 0) != "#") { display_data = (display_data = []) + display_data + data; process_display_data(data); } ++notecard_line; display_request = llGetNotecardLine(DISPLAY_NOTECARD_NAME, notecard_line); } //llWhisper(0,"re-read "+ (string)(notecard_line) + " of " + (string)num_notecard_lines + " lines"); } } http_response(key request_id, integer status, list metadata, string body) { if (request_id == http_request_id) { llWhisper(0, body); } } // Stop robot behaviour by touching it, // if one of the touches is in same group as object. touch_start(integer num) { integer x = 0; for(; x < num; x++) { if ( llSameGroup(llDetectedKey(x)) ) { //turn_off(); // not necessary, not replying to activity. state default; } } } }