Le LSL de Second Life

Ce site est consacré principalement au langage LSL de Second Life...

« Retour aux projets


Visualiser les avatars sur une sim


Dans SL on dispose d'une map qui nous permet de voir les constructions d'une sim ainsi que la localisation des avatars présents et leur nombre. Il est parfois intéressant de savoir nommément qui se trouve à tel emplacement. Je vous propose de réaliser un HUD...

Pour visualiser la carte de la sim avec les avatars présents mettez ce script dans une simple primitive :


//----------------------------------------------------------------------------------
//                                 Avatars Show V 2.02
//----------------------------------------------------------------------------------
//                          Copyright (c) 2013 by Bestmomo Lagan
//----------------------------------------------------------------------------------

//----------------------------------------------
//                  PARAMETRES
//----------------------------------------------
integer FACE = 4;
string VERSION = "2.02";
list LANG_SHORT = ["fr","es","ja","de","en","pt","ko","zh","it","da","hu","nl","pl","ru","tr","uk","el","fi"];
list LANG = ["french","spanish","japanese","german","english","portuguese","korean","chinese","italiano",
    "danish","hungarian","dutch","polish","russian","turkish","ukrainian","greek","finnish"];

//----------------------------------------------
//                  VARIABLES
//----------------------------------------------
string myURL;
list l_keys;
key k_script;
key k_ava_select;
string s_ava_select;
integer i_listen;

//----------------------------------------------
//          PARAMETRES MODULE MENU
//----------------------------------------------
key         KEY_MENU                = "168d4f3c-e095-a9c8-058d-d6075e6e51b8";
integer        MENU_INIT                = 10;     // Initialisation d'un menu
integer        MENU_ANSWER                = 11;     // Réponse d'un menu
integer        MENU_BUSY                = 12;     // Réponse busy
//----------------------------------------------
//          FONCTIONS MODULE MENU
//----------------------------------------------

send_menu (key id_script, key id, string texte, integer type_menu, list boutons) {
    llMessageLinked(LINK_THIS, MENU_INIT, (string)id_script + "@"
        + (string)id + "@"
        + texte + "@"
        + (string)type_menu + "@"
        + llDumpList2String(boutons, "|"), KEY_MENU);
}
//----------------------------------------------
//                  FIN
//----------------------------------------------

browse(integer face, string url) {
    llClearLinkMedia(LINK_THIS, face);
    llSetLinkMedia(LINK_THIS, face, [
        PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI,
        PRIM_MEDIA_AUTO_PLAY, TRUE,
        PRIM_MEDIA_AUTO_LOOP, TRUE,
        PRIM_MEDIA_AUTO_SCALE, TRUE,
        PRIM_MEDIA_CURRENT_URL, url,
        PRIM_MEDIA_HOME_URL, url,
        PRIM_MEDIA_HEIGHT_PIXELS, 512,
        PRIM_MEDIA_WIDTH_PIXELS, 512]);
}

string getPage(string noms, string pos, string url_map) {
    return "
        <!DOCTYPE HTML>
        <html>
  <head>
    <style>
    body {
        margin: 0px;
        padding: 0px;
        background: url(" + url_map + ") no-repeat;
        background-size: 512px;
        height: 512px;
        width: 512px;
      }
      #container {
        height: 502px;
        width: 502px;    
        border: 5px ridge orange;
    }
    </style>
  </head>
<body>
<div id='container'></div>
<script src='http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.4.3.min.js'></script>
<script>
    
    var noms = [" + noms + "];
    var pos = [" + pos + "];

    var stage = new Kinetic.Stage({
      container: 'container',
      width: 502,
      height: 502
    });
    
    var mapLayer = new Kinetic.Layer();
    var topLayer = new Kinetic.Layer();
    var tooltipLayer = new Kinetic.Layer();

    function ajustePos(mp, height) {
        if(mp > stage.getWidth() / 2) {
            return mp - height - 10;
        } else {
            return mp + 10;
        }
    }

    for(var i = 0; i < noms.length; ++i) {

        var circle = new Kinetic.Circle({
            name: noms[i],
            id: i,
            x: pos[i].x,
            y: stage.getHeight() - pos[i].y,
            radius: 5,
            fill: 'blue'
        });

        circle.on('mouseover', function() {
            document.body.style.cursor = 'pointer';
            this.setFill('red');
            var mousePos = stage.getMousePosition();
            var x = ajustePos(mousePos.x, tooltip.getWidth());
            var y = ajustePos(mousePos.y, tooltip.getHeight());
            tooltip.setPosition(x , y);
            rect.setPosition(x, y);
            tooltip.setText(this.getName());
            rect.setWidth(tooltip.getWidth());
            tooltip.show();
            rect.show();
            mapLayer.draw();
            tooltipLayer.draw(); 
        });

        circle.on('mouseout', function() {
            document.body.style.cursor = 'default';
            this.setFill('blue');
            mapLayer.draw();
            tooltip.hide();
            rect.hide();
            tooltipLayer.draw();
        });

        circle.on('dblclick', function() {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.open('GET', '" + myURL + "/' + " + "this.getId(), true);
            xmlhttp.send();
        });
            
        mapLayer.add(circle);
    }

    var tooltip = new Kinetic.Text({
        text: 'Test',
        fontFamily: 'Calibri',
        fontSize: 18,
        padding: 5,
        textFill: 'white',
        fill: 'black',
        visible: false,
        padding: 4,
        align: 'center'
    });

    var rect = new Kinetic.Rect({
        stroke: '#555',
        strokeWidth: 1,
        fill: '#ddd',
        shadowColor: 'black',
        shadowBlur: 5,
        shadowOffset: [5, 5],
        shadowOpacity: 0.3,
        cornerRadius: 6,
        visible: false,
        height: tooltip.getHeight()
    });

    tooltipLayer.add(rect);
    tooltipLayer.add(tooltip);

    stage.add(mapLayer);
    stage.add(topLayer);
    stage.add(tooltipLayer);

</script>

</body>
</html>";
}
 
default
{
    changed(integer change) {
        if (change & (CHANGED_REGION_START | CHANGED_REGION | CHANGED_TELEPORT))
            llResetScript();
    }

    state_entry() {
        llOwnerSay("Avatars Show V " + VERSION);
        k_script = llGetInventoryKey(llGetScriptName());
        llRequestURL();
    }

    on_rez(integer start_param) {
        llResetScript();
    }

    http_request(key id, string method, string body) {
        if (method == URL_REQUEST_GRANTED) {
            myURL = body;
            browse(FACE, body);
        }
        else if (method == "GET") {
            string path_info = llGetHTTPHeader(id, "x-path-info");
            // Appel url de base
            if (path_info == "") {
                // Liste des clés des avatars dans la région
                l_keys = llGetAgentList(AGENT_LIST_REGION, []);
                // Noms
                integer i;
                integer n = llGetListLength(l_keys);
                string s_noms;
                for(; i < n; ++i) s_noms += "'" + llKey2Name(llList2Key(l_keys, i)) + "',";
                s_noms = llGetSubString(s_noms, 0, -2);
                // Adresse de la map
                vector sim_coord = llGetRegionCorner();
                string x = (string)((integer)(sim_coord.x / 256.0));
                string y = (string)((integer)(sim_coord.y / 256.0));
                string url_map = "http://map.secondlife.com/map-1-" + x + "-" + y + "-objects.jpg";
                // Positions des avatars
                string s_pos;
                for(i = 0; i < n; ++i) {
                    vector v = llList2Vector(llGetObjectDetails(llList2Key(l_keys, i), [OBJECT_POS]), 0);
                    s_pos += "{x : " + (string)(v.x * 2) + ",y : " + (string)(v.y * 2) + "},";
                }
                // Réponse
                llSetContentType(id, CONTENT_TYPE_HTML);
                llHTTPResponse(id, 200, getPage(s_noms, s_pos, url_map));
            }
            // Appel avec index avatar
            else {
                // Index
                integer i = (integer)llGetSubString(path_info, 1, -1);
                // Clé de l'avatar
                k_ava_select = llList2Key(l_keys, i);
                // Avatar toujours là
                if(llGetAgentSize(k_ava_select) != ZERO_VECTOR) {
                    s_ava_select = llKey2Name(k_ava_select);
                    // Menu d'options
                    send_menu (k_script, llGetOwner(), "Select an option for avatar " + s_ava_select + " :", 0, ["Exit","Send IM","Get Infos","TP to"]);
                }
                // Avatar parti
                else llOwnerSay("Sorry but this avatar is not in this region now, you should refresh the map");
            }
        }
    }

    //----------------------------------------------
    //          RETOUR DU MODULE MENU
    //----------------------------------------------
    link_message(integer sender_number, integer number, string message, key id) {
        if(id == KEY_MENU) {
            if(number == MENU_ANSWER) {
                list l_sections = llParseString2List(message, ["@"], []);
                // Clé du script
                key id_script = (key)llList2String(l_sections, 0);
                // Test bon script
                if(id_script == k_script) {
                    if(llGetAgentSize(k_ava_select) != ZERO_VECTOR) {
                        // Nom du bouton cliqué
                        string s_message = llList2String(l_sections, 3);
                        if(s_message == "Send IM") {
                            llSetTimerEvent(60.0);
                            i_listen = llListen(11, "", NULL_KEY, "");
                            string s = "You have 60 seconds to type your message for " + s_ava_select;
                            llTextBox(llGetOwner(), s, 11);
                        }
                        else if(s_message == "Get Infos") {
                            integer i_infos = llGetAgentInfo(k_ava_select);
                            string s_infos = "Infos for " + s_ava_select + " :\n";
                            if(i_infos & AGENT_FLYING) s_infos += "is flying.\n";
                            if(i_infos & AGENT_IN_AIR) s_infos += "is in the air.\n";
                            if(i_infos & AGENT_AWAY) s_infos += "is away.\n";
                            if(i_infos & AGENT_BUSY) s_infos += "is busy.\n";
                            if(i_infos & AGENT_SITTING) s_infos += "is sitting.\n";
                            if(i_infos & AGENT_TYPING) s_infos += "is typing.\n";
                            if(i_infos & AGENT_WALKING) s_infos += "is walking.\n";
                            if(i_infos & AGENT_BUSY) s_infos += "is busy.\n";
                            if(i_infos & AGENT_MOUSELOOK) s_infos += "is in mouselook.\n";
                            string s = llGetAgentLanguage(k_ava_select);
                            integer i = llListFindList(LANG_SHORT, [s]);
                            if(~i) s_infos += "has language " + llList2String(LANG, i) + "\n";
                            list l = llGetObjectDetails(k_ava_select, [OBJECT_POS, OBJECT_RUNNING_SCRIPT_COUNT]);
                            s_infos += "has position " + llList2String(l, 0) + "\n";
                            s_infos += "has " + llList2String(l, 1) + " running scripts\n";
                            s_infos += "has displayname " + llGetDisplayName(k_ava_select) + "\n";
                            llOwnerSay(s_infos);
                        }
                        else if(s_message == "TP to") {
                            list l = llGetObjectDetails(k_ava_select, [OBJECT_POS]);
                            llMapDestination(llGetRegionName(), llList2Vector(l, 0), ZERO_VECTOR);
                        }
                    }
                    // Avatar parti
                    else llOwnerSay("Sorry but this avatar is not in this region now, you should refresh the map");
                }
            }
            else if(number == MENU_BUSY) {
                if(message == (string)k_script)
                    // Réponse en cas de mémoire limite atteinte
                    llOwnerSay("Sorry but module menu is busy now, please try later.");
            }
        }
    }

    listen(integer channel, string name, key id, string message) {
        llListenRemove(i_listen);
        llSetTimerEvent(.0);
        if(message != "") {
            llInstantMessage(k_ava_select, message);
            llOwnerSay("Message sent to " + llKey2Name(k_ava_select));
        }            
    }
        
    timer() {
        llSetTimerEvent(.0);
        llOwnerSay("Time out !");
    }
}  

Mettez aussi ce script pour la gestion des menus :


//----------------------------------------------------------------------------------
//                              Module Menu V2.05
//----------------------------------------------------------------------------------
//                          Copyright (c) 2013 by Bestmomo Lagan
//----------------------------------------------------------------------------------
//
//    Format du message reçu : id script @ id avatar @ texte @ type de menu @ Bouton1 | bouton2 | ...
//  Format du message retourné : id script @ id avatar @ type de menu @ bouton cliqué
//
//
//----------------------------------------------------------------------------------
//                              LIBRARY FUNCTIONS
//----------------------------------------------------------------------------------


//---------------------------------------------------------------
//                        MENU FUNCTIONS
//---------------------------------------------------------------

// -- Initialisation menu --
// @ param [key]         clé de l'avatar
// @ param [string]     texte pour le menu
// @ param [list]         noms des boutons
InitMenu(key id_script, key id, string texte, integer menu_type, list boutons) {
    // Test avatar déjà présent
    if(~llListFindList(lAvaMenu, [id])) CancelMenu(id);
    // Détermination de l'index du menu
    integer i = llGetListLength(lAvaMenu);
    // Choix d'un canal
    integer c = GetRandomChannel();
    // Enregistrement des paramètres
    lIndexScript += id_script;
    lAvaMenu += id;
    lBoutonsMenu += llDumpList2String(GetNomsCourts(boutons), "|");
    lBoutonsLongs += llDumpList2String(GetNomsLongs(boutons), "|");
    lCanalMenu += c;
    lTexteMenu += texte;
    lIndexMenu += 0;
    lTypeMenu += menu_type;
    // Mise en place et enregistrement de l'écoute
    lEcouteMenu += llListen(llList2Integer(lCanalMenu, i), "", id, "");
    // Envoi du menu
    GestMenu(id, i);
}

// -- Gestion de l'index des boutons du menu --
// @ param [key]         clé de l'avatar
// @ param [string]     bouton de déplacement
GestIndexBoutons(key id, string browse) {
    // Index du menu
    integer i = llListFindList(lAvaMenu, [id]);
    // Index dans les boutons du menu
    integer IndexBoutons = llList2Integer(lIndexMenu, i);
    // Nombre de boutons
    integer n = llGetListLength(llParseString2List(llList2String(lBoutonsMenu, i), ["|"], []));
    // Navigation simple pour deux pages
    if(n < 23) {
        if(IndexBoutons) IndexBoutons = 0;
        else IndexBoutons = 11;}
    // Navigation riche pour plus de deux pages
    else {
        if(browse == NEXT) {
            IndexBoutons += 10;
            if(IndexBoutons >= n) IndexBoutons = 0;}
        else {
            IndexBoutons -= 10;
            if(IndexBoutons < 0) {
                integer x = n % 10;
                if(!x) x = 10;
                IndexBoutons = n - x;}
        }
    }
    // Mise à jour de l'index des boutons du menu
    lIndexMenu = llListReplaceList(lIndexMenu, [IndexBoutons], i, i);
    // Envoi du menu
    GestMenu(id, i);
}

//    -- Gestion menu --
// @ param [key]      clé de l'avatar
// @ param [index]    index du menu
GestMenu(key id, integer i) {
    // Index pour les boutons
    integer IndexBoutons = llList2Integer(lIndexMenu, i);
    // Liste globale des boutons
    list lBoutons = llParseString2List(llList2String(lBoutonsMenu, i), ["|"], []);
    // Nombre total de boutons
    integer n = llGetListLength(lBoutons);
    // Si plusieurs pages
    if(n > 12) {
        // Que 2 pages -> navigation simple
        if(n < 23) {
            // Deuxième page
            if(IndexBoutons)
                lBoutons = [PREVIOUS] + llList2List(lBoutons, IndexBoutons, -1);
            // Première page
            else
                lBoutons = llList2List(lBoutons, 0, 1) + [NEXT] + llList2List(lBoutons, 2, 10);}
        // Plus de 2 pages -> navigation riche
        else {
            list l = [PREVIOUS, llList2String(lBoutons, IndexBoutons), NEXT];
            // Première page ou page intermédiaire
            if(n - IndexBoutons > 10)
                lBoutons = l + llList2List(lBoutons, IndexBoutons + 1, IndexBoutons + 9);
            // Dernière page
            else {
                if(IndexBoutons + 1 < n)
                    lBoutons = l + llList2List(lBoutons, IndexBoutons + 1, -1);
                else lBoutons = l;}
        }
    }
    // Mise en route ou réinitialisation du timer
    llSetTimerEvent(TIMEOUT);
    // Envoi du menu
    llDialog(id, llList2String(lTexteMenu, i), lBoutons, llList2Integer(lCanalMenu, i));
}

// -- Suppression d'un menu --
// @ param [key] clé de l'avatar
CancelMenu(key id) {
    // Index du menu
    integer i = llListFindList(lAvaMenu, [id]);
    // On teste s'il y a quelque chose à arrêter
    if(~i) {
        // Arrêt de l'écoute du canal
        llListenRemove(llList2Integer(lEcouteMenu, i));
        // Suppression des paramètres pour ce menu
        lIndexScript        = llDeleteSubList(lIndexScript, i, i);
        lAvaMenu             = llDeleteSubList(lAvaMenu, i, i);
        lBoutonsMenu        = llDeleteSubList(lBoutonsMenu, i, i);
        lBoutonsLongs        = llDeleteSubList(lBoutonsLongs, i, i);
        lCanalMenu            = llDeleteSubList(lCanalMenu, i, i);
        lEcouteMenu         = llDeleteSubList(lEcouteMenu, i, i);
        lIndexMenu            = llDeleteSubList(lIndexMenu, i, i);
        lTexteMenu            = llDeleteSubList(lTexteMenu, i, i);
        lTypeMenu            = llDeleteSubList(lTypeMenu, i, i);
        // Si c'est le dernier on arrête le timer
        if(!llGetListLength(lAvaMenu)) llSetTimerEvent(.0);}
}

// -- Fin du délai d'écoute des menu --
FinMenu() {
    // Balayage de tous les menus
    while(llGetListLength(lAvaMenu)) {
        key k = llList2Key(lAvaMenu, 0);
        // On prévient l'avatar
        llInstantMessage(llList2Key(lAvaMenu, 0), "Time out for dialog.");
        // On supprime le menu
        CancelMenu(k);}
}

// -- Récupération du type de menu --
// @ param [key] clé de l'avatar
// @ return [string] retourne le type du menu
string get_type_menu(key id) {
    // Index du menu
    integer i = llListFindList(lAvaMenu, [id]);
    // Type du menu
    return llList2String(lTypeMenu, i);
}

// -- Génération d'un canal négatif --
// @ return [integer] retourne le canal
integer GetRandomChannel() {
    return ~(integer)llFrand((float)DEBUG_CHANNEL);
}

// -- Génération d'une liste avec noms courts --
// @ param [list] liste de noms longs
// @ return [list] retourne la liste de noms courts
list GetNomsCourts(list l_noms) {
    integer c;
    string s;
    integer n = llGetListLength(l_noms);
    list l;
    for(;c < n; c++) {
        s = llList2String(l_noms, c);
        if(llStringLength(s) > 24) l += llGetSubString(s, 0, 23);
        else l += s;
    }
    return l;
}

// -- Génération d'une liste avec noms longs --
// @ param [list] liste de noms
// @ return [list] retourne la liste de noms longs
list GetNomsLongs(list l_noms) {
    integer c;
    integer n = llGetListLength(l_noms);
    list l;
    for(;c < n; c++) l += llList2String(l_noms, c);
    return l;
}

// -- Récupération index du script appelant --
// @ param [key] clé de l'avatar
// @ return [key] clé du script
string GetIndexScript(key id) {
    // Index du menu
    integer i = llListFindList(lAvaMenu, [id]);
    // Réponse
    return llList2String(lIndexScript, i);
}

// -- Récupération nom long --
// @ param [key] clé de l'avatar
// @ param [string] nom court
// @ return [string] nom long
string GetNomLong(key id, string message) {
    // Index du menu
    integer i = llListFindList(lAvaMenu, [id]);
    // Boutons de ce menu
    list l_boutons = llParseString2List(llList2String(lBoutonsMenu, i), ["|"], []);
    // Noms longs
    list l_boutons_longs = llParseString2List(llList2String(lBoutonsLongs, i), ["|"], []);
    // Index du nom
    i = llListFindList(l_boutons, [message]);
    // Nom long
    return llList2String(l_boutons_longs, i);
}

//----------------------------------------------------------------------------------
//                        LIBRARY VARIABLES
//----------------------------------------------------------------------------------

//----------------------------------------------
//                  MENUS
//----------------------------------------------
string      PREVIOUS        = "<<<";        // Bouton pour page arrière
string      NEXT            = ">>>";        // Bouton pour page avant
float       TIMEOUT         = 60.0;         // Délai pour réponse au menu
list        lCanalMenu;                     // Canaux du Chat
list        lEcouteMenu;                    // Handle de l'écoute
list        lIndexMenu;                     // Index de position dans les boutons
list        lTexteMenu;                     // Texte pour le menu
list        lAvaMenu;                       // Clé de l'avatar
list        lBoutonsMenu;                   // Boutons des menus
list        lBoutonsLongs;                  // Noms longs
list        lTypeMenu;                        // Type du menu
list        lIndexScript;                   // Index script appelant

//----------------------------------------------
//                  PARAMETRES
//----------------------------------------------
key         KEY_MENU                = "168d4f3c-e095-a9c8-058d-d6075e6e51b8";
string      VERSION                 = "2.05";
integer        MENU_INIT                = 10;     // Initialisation d'un menu
integer        MENU_ANSWER                = 11;     // Réponse d'un menu
integer        MENU_BUSY                = 12;     // Réponse busy

//--------------------------------------------------------------------------------------
//        STATE par defaut
//--------------------------------------------------------------------------------------
default
{
    state_entry() {
        // Accueil
        llWhisper(0, "Module Menu V " + VERSION);}

    link_message(integer sender_number, integer number, string message, key id) {
        if(id == KEY_MENU) {
            // Initialisation d'un menu
            if(number == MENU_INIT) {
                list l_sections = llParseString2List(message, ["@"], []);
                // Test mémoire libre et traitement
                if(llGetUsedMemory() < 56000) {
                    InitMenu(
                        (key)llList2String(l_sections, 0),
                        (key)llList2String(l_sections, 1),
                        llList2String(l_sections, 2),
                        (integer)llList2String(l_sections, 3),
                        llParseString2List(llList2String(l_sections, 4), ["|"], [])
                            );
                }
                else llMessageLinked(LINK_THIS, MENU_BUSY, llList2String(l_sections, 0), KEY_MENU);
            }
        }
    }

    listen(integer channel, string name, key id, string message) {
        // Test de changement de page
        if(~llListFindList([PREVIOUS,NEXT], [message]))
            GestIndexBoutons(id, message);
        else {
            // Envoi de la réponse
            llMessageLinked(LINK_THIS, MENU_ANSWER, GetIndexScript(id) + "@"
                + (string)id + "@"
                + get_type_menu(id) + "@"
                + GetNomLong(id, message), KEY_MENU);
            CancelMenu(id);
        }
    }

    timer() {
        FinMenu();
    }
}  

La carte apparaît, tous les avatars dans la région sont sous la forme de points bleus. Pour éviter de saturer l'affichage le nom d'un avatar apparaît lorsqu'on dispose le curseur de la souris sur le point bleu qui le représente. Pour actualiser il suffit de rafraîchir la page. Je n'ai pas prévu d'actualisation automatique.

Il doit s'utiliser obligatoirement en HUD, l'affichage se rafraîchira automatiquement à chaque changement de région. Pour obtenir des informations sur un avatar particulier il suffit d'un double-clic sur le petit cercle bleu qui le représente, un menu classique de SL apparaît, il suffit alors de cliquer sur le bouton qui correspond à ce que l'on désire connaître : position, displayname, envoi d'IM...



« Retour aux projets