| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- //
- // The line above should be left blank to avoid script errors in OpenSim.
- // Sloodle quiz chair
- // Allows SL users to take Moodle quizzes in-world
- // Part of the Sloodle project (www.sloodle.org)
- //
- // Copyright (c) 2006-9 Sloodle (various contributors)
- // Released under the GNU GPL
- //
- // Contributors:
- // Edmund Edgar
- // Paul Preibisch
- // Peter R. Bloomfield
- // Once configured in the usual way, this script waits for a request to ask a question in the form of a linked message with num SLOODLE_CHANNEL_QUIZ_ASK_QUESTION.
- // When the student answers the question, it sends out a linked message with num SLOODLE_CHANNEL_QUESTION_ANSWERED_AVATAR.
- // Note that it doesn't handle timeouts in case the user doesn't respond - all that should be done by the calling script.
-
- integer SLOODLE_CHANNEL_QUIZ_STOP_FOR_AVATAR = -1639271119; //Tells us to STOP a quiz for the avatar
- integer SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST=-1828374651;
- integer doRepeat = 0; // whether we should run through the questions again when we're done
- integer doDialog = 1; // whether we should ask the questions using dialog rather than chat
- integer menu_channel;
- string sloodlehttpvars;
- integer answerDialogListenHandler;
- integer answerChatListenHandler;
- integer answerChatListenHandlerNonPublic;
-
- integer SLOODLE_CHANNEL_QUESTION_ASKED_AVATAR = -1639271105; //Sent by main quiz script to tell UI scripts that question has been asked to avatar with key. String contains question ID + "|" + question text
- integer SLOODLE_CHANNEL_QUESTION_ANSWERED_AVATAR = -1639271106; //Sent by main quiz script to tell UI scripts that question has been answered by avatar with key. String contains selected option ID + "|" + option text + "|"
- integer SLOODLE_CHANNEL_QUIZ_LOADING_QUESTION = -1639271107;
- integer SLOODLE_CHANNEL_ANSWER_SCORE_FOR_AVATAR = -1639271113; // Tells anyone who might be interested that we scored the answer. Score in string, avatar in key.
- integer SLOODLE_CHANNEL_QUIZ_STATE_ENTRY_QUIZZING = -1639271117; //mod quiz script is in state quizzing
- integer SLOODLE_CHANNEL_QUIZ_ERROR_INVALID_QUESION = -1639271121; //
- integer SLOODLE_CHANNEL_QUIZ_ERROR_NO_ATTEMPTS_LEFT= -1639271123; //
- integer SLOODLE_CHANNEL_QUIZ_ERROR_NO_QUESTIONS= -1639271124; //
- integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_CHAT = -1639271125; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA CHAT.
- integer SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG = -1639271126; // Tells the question handler scripts to ask the question with the ID in str to the avatar with key VIA DIALOG.
- ///// TRANSLATION /////
- // Link message channels
- integer SLOODLE_CHANNEL_TRANSLATION_REQUEST = -1928374651;
- // Translation output methods
- string SLOODLE_TRANSLATE_WHISPER = "whisper"; // 1 output parameter: chat channel number
- string SLOODLE_TRANSLATE_SAY = "say"; // 1 output parameter: chat channel number
- string SLOODLE_TRANSLATE_OWNER_SAY = "ownersay"; // No output parameters
- string SLOODLE_TRANSLATE_DIALOG = "dialog"; // Recipient avatar should be identified in link message keyval. At least 2 output parameters: first the channel number for the dialog, and then 1 to 12 button label strings.
- string SLOODLE_TRANSLATE_LOAD_URL = "loadurl"; // Recipient avatar should be identified in link message keyval. 1 output parameter giving URL to load.
- string SLOODLE_TRANSLATE_IM = "instantmessage"; // Recipient avatar should be identified in link message keyval. No output parameters.
- string sloodle_full_quiz_url;
- key httpfetchquestionquery = NULL_KEY;
- key feedbackreq = NULL_KEY;
- float request_timeout = 20.0;
- integer question_id = -1;
- // Text and type of the current and next question
- string qtext = "";
- string qtype = "";
- // Lists of option information for the current question
- list opids = []; // IDs
- list optext = []; // Texts
- list opgrade = []; // Grades
- list opfeedback = []; // Feedback if this option is selected
- // Avatar currently using this cahir
- key sitter = NULL_KEY;
-
- ///// FUNCTIONS /////
- /******************************************************************************************************************************
- * sloodle_error_code -
- * Author: Paul Preibisch
- * Description - This function sends a linked message on the SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST channel
- * The error_messages script hears this, translates the status code and sends an instant message to the avuuid
- * Params: method - SLOODLE_TRANSLATE_SAY, SLOODLE_TRANSLATE_IM etc
- * Params: avuuid - this is the avatar UUID to that an instant message with the translated error code will be sent to
- * Params: status code - the status code of the error as on our wiki: http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes
- *******************************************************************************************************************************/
- sloodle_error_code(string method, key avuuid,integer statuscode){
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ERROR_TRANSLATION_REQUEST, method+"|"+(string)avuuid+"|"+(string)statuscode, NULL_KEY);
- } sloodle_debug(string msg)
- {
- llMessageLinked(LINK_THIS, DEBUG_CHANNEL, msg, NULL_KEY);
- }
- integer random_integer( integer min, integer max )
- {
- return min + (integer)( llFrand( max - min + 1 ) );
- }
- // Query the server for the identified question (request by global question ID)
- key request_question( integer qid )
- {
-
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_QUIZ_LOADING_QUESTION, (string)qid, sitter);
-
- // Request the identified question from Moodle
-
- string body = sloodlehttpvars+"<q=" + (string)qid;
- key newhttp = llHTTPRequest(sloodle_full_quiz_url, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
-
- llSetTimerEvent(0.0);
- llSetTimerEvent(request_timeout);
-
- return newhttp;
- }
-
- // Query the server for the feedback for a particular choice.
- // This is only called if the server has told us that the feedback is too long to go in the regular request
- // It does this by substituting the feedback [[[LONG]]]
- key request_feedback( integer qid, string fid ) {
- // Request the identified question from Moodle
- string body = sloodlehttpvars;
- body += "<q=" + (string)qid;
- body += "&fid=" + (string)fid;
-
- key reqid = llHTTPRequest(sloodle_full_quiz_url, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
- llSleep(3.0); // Hopefully the message will come back before the next question is asked. But if it comes back out of order, we won't insist.
-
- return reqid;
-
- }
-
- // Notify the server of a response
- notify_server(string qtype, integer questioncode, string responsecode, float scorechange)
- {
- string body =sloodlehttpvars;
- body += "&resp" + (string)questioncode + "_=" + responsecode;
- body += "&resp" + (string)questioncode + "_submit=1";
- body += "&questionids=" + (string)questioncode;
- body += "&action=notify";
- body += "&scorechange="+(string)scorechange;
-
- llHTTPRequest(sloodle_full_quiz_url, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], body);
- }
-
-
- // Ask the current question
- ask_question()
- {
-
- // Are we using dialogs?
- if (doDialog == 1) {
-
- // We want to create a dialog with the option texts embedded into the main text,
- // and numbers on the buttons
- integer qi;
- list qdialogoptions = [];
-
- string qdialogtext = qtext + "\n";
- // Go through each option
- integer num_options = llGetListLength(optext);
-
- if ((qtype == "numerical")|| (qtype == "shortanswer")) {
- // Ask the question via IM
- llInstantMessage(sitter, qtext);
- } else {
- for (qi = 1; qi <= num_options; qi++) {
- // Append this option to the main dialog (remebering buttons are 1-based, but lists 0-based)
- qdialogtext += (string)qi + ": " + llList2String(optext,qi-1) + "\n";
- // Add a button for this option
- qdialogoptions = qdialogoptions + [(string)qi];
- }
- // Present the dialog to the user
- answerDialogListenHandler = llListen(menu_channel, "", sitter, "");
- llDialog(sitter, qdialogtext, qdialogoptions, menu_channel);
- }
- } else {
-
- // Ask the question via IM
- llListenRemove(answerChatListenHandler); // cancel any existing listens before creating a new one
- answerChatListenHandler = llListen(0, "", sitter, "");
-
- // Listen on channel 111 to allow people to give answers without everyone else hearing them.
- llListenRemove(answerChatListenHandlerNonPublic); // cancel any existing listens before creating a new one
- answerChatListenHandlerNonPublic = llListen(111, "", sitter, "");
-
- llInstantMessage(sitter, qtext);
- // Offer the options via IM
- integer x = 0;
- integer num_options = llGetListLength(optext);
- for (x = 0; x < num_options; x++) {
- llInstantMessage(sitter, (string)(x + 1) + ". " + llList2String(optext, x));
- }
- }
-
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_QUESTION_ASKED_AVATAR, (string)question_id+"|"+qtext, sitter);
-
- }
- // Send a translation request link message
- sloodle_translation_request(string output_method, list output_params, string string_name, list string_params, key keyval, string batch){
- llMessageLinked(LINK_THIS, SLOODLE_CHANNEL_TRANSLATION_REQUEST, output_method + "|" + llList2CSV(output_params) + "|" + string_name + "|" + llList2CSV(string_params) + "|" + batch, keyval);
- }
- ///// ----------- /////
- ///// STATES /////
- default{
- on_rez(integer param)
- {
- llResetScript();
- }
-
- link_message(integer sender_num, integer num, string str, key id){
- if (num ==SLOODLE_CHANNEL_QUIZ_STOP_FOR_AVATAR){
- llListenRemove(answerDialogListenHandler);
- llListenRemove(answerChatListenHandler);
- llListenRemove(answerChatListenHandlerNonPublic);
- }
- else
- if (num == SLOODLE_CHANNEL_QUIZ_STATE_ENTRY_QUIZZING){
- sitter=id;
-
- llListenRemove(answerDialogListenHandler); // Cancel any existing listens
- menu_channel = random_integer(-90000,-900000);
- }
- else
- if (num == SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG||num == SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_CHAT) {//todo add to dia
- list data = llParseString2List(str, ["|"], []);
- question_id = llList2Integer(data, 0);
- sloodle_full_quiz_url = llList2String(data, 1);//todo add to dia
- integer offset = llStringLength(llList2String(data,0)+"|"+sloodle_full_quiz_url+"|");
- sloodlehttpvars = llGetSubString(str, offset, -1);//-1 is the end of the list
- sitter = id;
- if (num == SLOODLE_CHANNEL_QUIZ_ASK_QUESTION_DIALOG) {//todo add to dia
- doDialog=TRUE; //todo add to dia
- }else{
- doDialog=FALSE;//todo add to dia
- }
- httpfetchquestionquery = request_question((integer)str);
- }
- }
- state_exit()
- {
- llSetTimerEvent(0.0);
- }
-
- listen(integer channel, string name, key id, string message)
- {
- // If using dialogs, then only listen to the dialog channel
-
- if (doDialog && ((qtype == "multichoice") || (qtype == "truefalse"))) {
- if (channel != menu_channel) {
- sloodle_translation_request(SLOODLE_TRANSLATE_IM, [0], "usedialogs", [llKey2Name(sitter)], sitter, "quiz");
- return;
- }
- } else {
- if (channel != 0) return;
- }
-
- string opid; // used when the feedback is too long, and we have to fetch it off the server
- // Only listen to the sitter
- if (id == sitter) {
- // Handle the answer...
- float scorechange = 0;
- string feedback = "";
- string answeroptext = "";
-
- // Check the type of question this was
- if ((qtype == "multichoice") || (qtype == "truefalse")) {
- // Multiple choice - the response should be a number from the dialog box (1-based)
- integer answer_num = (integer)message;
- // Make sure it's valid
- if ((answer_num > 0) && (answer_num <= llGetListLength(opids))) {
- // Correct to 0-based
- answer_num -= 1;
-
- feedback = llList2String(opfeedback, answer_num);
- scorechange = llList2Float(opgrade, answer_num);
- opid = llList2String(opids, answer_num);
- answeroptext = llList2String(optext, answer_num);
- // Notify the server of the response
- notify_server(qtype, question_id, llList2String(opids, answer_num),scorechange);
- } else {
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "invalidchoice", [llKey2Name(sitter)], NULL_KEY, "quiz");
- ask_question();
- }
- } else if (qtype == "shortanswer") {
- // Notify the server of the response
- integer x = 0;
- integer num_options = llGetListLength(optext);
- for (x = 0; x < num_options; x++) {
- if (llToLower(message) == llToLower(llList2String(optext, x))) {
- feedback = llList2String(opfeedback, x);
- scorechange = llList2Float(opgrade, x);
- opid = llList2String(opids, x);
- answeroptext = llList2String(optext, x);
- }
- notify_server(qtype, question_id, message, scorechange);
- }
- } else if (qtype == "numerical") {
- // Notify the server of the response
- float number = (float)message;
- integer x = 0;
- integer num_options = llGetListLength(optext);
- for (x = 0; x < num_options; x++) {
- if (number == (float)llList2String(optext, x)) {
- feedback = llList2String(opfeedback, x);
- scorechange = llList2Float(opgrade, x);
- opid = llList2String(opids, x);
- answeroptext = llList2String(optext, x);
- }
- notify_server(qtype, question_id, message, scorechange);
- }
- }
-
-
- else {
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "invalidtype", [qtype], NULL_KEY, "quiz");
- }
-
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_QUESTION_ANSWERED_AVATAR, opid+"|"+answeroptext, sitter);
-
- if (feedback == "[[LONG]]") // special long feedback placeholder for when there is too much feedback to give to the script
- feedbackreq = request_feedback( question_id, opid );
- else if (feedback != "") llInstantMessage(sitter, feedback); // Text feedback
- else if (scorechange > 0.0) {
- sloodle_translation_request(SLOODLE_TRANSLATE_IM, [0], "correct", [llKey2Name(sitter)], sitter, "quiz");
- //num_correct += 1; SAL commented out this
- } else {
- sloodle_translation_request(SLOODLE_TRANSLATE_IM, [0], "incorrect", [llKey2Name(sitter)], sitter, "quiz");
- }
- llSleep(1.); //wait to finish the sloodle_translation_request before next question.
-
- // Clear out our current data (a feeble attempt to save memory!)
- qtext = "";
- qtype = "";
- opids = [];
- optext = [];
- opgrade = [];
- opfeedback = [];
-
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_ANSWER_SCORE_FOR_AVATAR, (string)scorechange, sitter);
-
- }
- }
-
- timer()
- {
- // There has been a timeout of the HTTP request
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "httptimeout", [], NULL_KEY, "");
- llSetTimerEvent(0.0);
-
- if (question_id > -1) {
- httpfetchquestionquery = request_question(question_id);
- }
- }
-
- http_response(key request_id, integer status, list metadata, string body)
- {
-
- // This response will always contain question data.
- // If the current question is being loaded, then ask it right away, and load the next.
- // If the next question is being loaded, then just store it.
- // It will be made current and asked whenever the current one gets answered.
- // If the user ever gets ahead of our loading, then they will be waiting on the 'current' question.
- // As soon as that is loaded, it will get asked.
-
- // Is this the response we are expecting?
- if (request_id == httpfetchquestionquery) {
- httpfetchquestionquery = NULL_KEY;
- llSetTimerEvent(0.0);
- } else if (request_id != feedbackreq) {
- return;
- }
-
- // Make sure the response was OK
- if (status != 200) {
- sloodle_error_code(SLOODLE_TRANSLATE_SAY, NULL_KEY,status); //send message to error_message.lsl
- if (request_id!=feedbackreq){
- request_question( question_id );//TODO update DIA
- }
-
- }
-
- // Split the response into several lines
- list lines = llParseStringKeepNulls(body, ["\n"], []);
- integer numlines = llGetListLength(lines);
- body = "";
- list statusfields = llParseStringKeepNulls(llList2String(lines,0), ["|"], []);
- integer statuscode = llList2Integer(statusfields, 0);
-
- // Was it an error code?
- if (statuscode == -10301) {
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "noattemptsleft", [llKey2Name(sitter)], NULL_KEY, "");
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_QUIZ_ERROR_NO_ATTEMPTS_LEFT, (string)question_id, sitter);//todo add to dia
- return;
-
- } else if (statuscode == -10302) {
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "noquestions", [], NULL_KEY, "");
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_QUIZ_ERROR_NO_QUESTIONS, (string)question_id, sitter);//todo add to dia
-
- return;
-
- } else if (statuscode <= 0) {
- //sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "servererror", [statuscode], NULL_KEY, "");
- sloodle_error_code(SLOODLE_TRANSLATE_IM, sitter,statuscode); //send message to error_message.lsl
- // Check if an error message was reported
- if (numlines > 1) sloodle_debug(llList2String(lines, 1));
- return;
- }
-
- if (request_id == feedbackreq) {
- llInstantMessage( sitter, llList2String(lines, 1) );
- return;
- }
-
- // Save a tiny bit of memory!
- statusfields = [];
-
- // Go through each line of the response
-
- integer i = 0;
- for (i = 1; i < numlines; i++) {
-
- // Extract and parse the current line
- list thisline = llParseStringKeepNulls(llList2String(lines, i),["|"],[]);
- string rowtype = llList2String( thisline, 0 );
-
- // Check what type of line this is
- if ( rowtype == "question" ) {
-
- // Grab the question information and reset the options
- qtext = llList2String(thisline, 4);
- qtype = llList2String(thisline, 7);
-
- opids = [];
- optext = [];
- opgrade = [];
- opfeedback = [];
-
- // Make sure it's a valid question type
- if ((qtype != "multichoice") && (qtype != "truefalse") && (qtype != "numerical") && (qtype != "shortanswer")) {
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "invalidtype", [qtype], NULL_KEY, "quiz");
- sloodle_translation_request(SLOODLE_TRANSLATE_SAY, [0], "resetting", [], NULL_KEY, "");
- llMessageLinked(LINK_SET, SLOODLE_CHANNEL_QUIZ_ERROR_INVALID_QUESION, (string)question_id, sitter);//todo add to dia
-
- return;
- }
-
- } else if ( rowtype == "questionoption" ) {
- // Add this option to the appropriate place
- opids += [(integer)llList2String(thisline, 2)];
- optext += [llList2String(thisline, 4)];
- opgrade += [(float)llList2String(thisline, 5)];
- opfeedback += [llList2String(thisline, 6)];
- }
- }
-
- // Automatically ask this question
- ask_question();
- }
-
- }
- // Please leave the following line intact to show where the script lives in Git:
- // SLOODLE LSL Script Git Location: mod/quiz-1.0/objects/default/assets/sloodle_quiz_question_handler.lslp
|