Je propose de mettre en commun tous les trucs et astuces connus et moins connus qui remplissent vos scripts, qu'ils soient issus de vos lectures pertinentes ou de votre propre imagination et expérience qu'importe! Partagez les ...
- Ce wiki ne doit pas être un lieu de messages, afin de qu'il garde une structure lisible et bien organisée.
- Chaque entrée devra commencer par petite explication.
- Un exemple de code possible, clair et commenté facilitant la lecture et la compréhension est de rigueur.
- Les ligne de codes LSL présentées doivent utiliser les balises PHP pour garantir leur structure de code LSL.
- Un petit commentaire final sur l'utilisation ou les limites de l'astuce sera le bien venu.
Merci de respecter ces petites clauses pour le bien de tous.
Seb,
PS:
Si un astuce / truc ne fonctionne pas ou plus, présente des limites non énoncées etc... déposez une alerte chez le dépositaire si pas de réaction intervenez ici.
-Pour insérer un article-
1. Sélectionner modifier le wiki.
2. Entrer vote titre entre les balises [titre]Votre titre[/titre].
3. Laisser 1 ligne blanche.
4. Entrer votre nom en gras entre les balises [B]Votre Nom[/B].
5. Laisser 3 lignes blanches.
6. Insérer votre article.
7. Entrer le motif de modification.
8. Enregistrer.
Générer un numéro de canal
Par Seb_01
Il est souvent assez pénible d'affecter un numéro de channel dans les coms.
Cette petite routine devrait vous aidez quelques fois...
integer channel(key uuid) // Retourne un channel (integer)
{
return (integer)("0x"+llGetSubString((string)uuid,-8,-1)); // utilisation du codage hexa ("0x")
}
Génère un nombre entier issu des 8 derniers caractères codés hexa d'un UUID passé en paramètre de fonction.
Simple exemple:
integer Channel1;
integer Channel2;
integer Channel3;
integer channel(key uuid) // Retourne un channel (integer)
{
return (integer)("0x"+llGetSubString((string)uuid,-8,-1)); // utilisation du codage hexa ("0x")
}
default
{
on_rez (integer param)
{
Channel1 = channel ( llGetKey() ); // peu confidentiel mais le - peut devenir un +++
Channel3 = channel ( llGetOwner()); // si on veut 1 comm bilatérale
Channel2 = channel ( llGetInventoryKey( llGetScriptName())); // plus confidentiel
// etc ...
llOwnerSay ((string) Channel1 + " " + (string) Channel2 + " " + (string) Channel3 );
}
}
// Peut afficher: 563253999 -1886078597 1456048149
Remarque:
* Suivant besoin de confidentialité on peut renforcer la routine en ajoutant des caractères par exemple...
* On peut forcé un integer négatif ...
* Un UUID peut être connu et...partageable (pas le FRAND)
* etc...
Cleanup sélectif et rapide.
Par Seb_01
Basé sur les avertissements de l'instruction "state"
On state change:
* All listens are released.
* The event queue is cleared.
* Repeating sensors are released.
* The timer event clock is not cleared.
default
{
...
{
...
// n'importe où dans le code //
state Cleanup;
...
}
...
}
state Cleanup
{
state_entry() // fermera tous les listens / les sensors / events en cours sauf le timer
{
state defaut; // ou autre state
}
}
Remarques:
Fermerture ciblée ultra rapide ...
Passer des paramètres à un script
Par Seb_01
Tous les programmes que l'on lance en informatique offre la possibilité de "passer" des paramètres à leur lancement...
exemple au hasard :
"C:\Program Files\GreenLife Emerald Viewer\GreenLife.exe" --channel "GreenLife Emerald Viewer" --settings settings_emerald.xml -set SystemLanguage en-us -multiple
Quand j'ai commencé à scripter j'ai été très vite désorienté par cette lacune LSL et fait sans.
Puis un jour cette option est devenu incontournable pour mes projets alors j'ai cogité une paire d'heure...
Je vous livre une de mes actuces figurant au TOP 5, je suis sûr qu'elle tiendra toutes ses promesses.
Mode 1: Setup par le script lui même
string Save_Desc;
string Message = " Merci d'avoir acheter ce produit etc...";
Verify_Option_Rez ()
{ // on teste si une option a été programmée
string obj_desc = llGetObjectDesc();
if ( obj_desc == Save_Desc)
return;
else if ( obj_desc == Save_Desc + "-reset" )
llResetScript ();
else if ( obj_desc == Save_Desc + "-vente" )
llOwnerSay ( Message );
llSetObjectDesc( Save_Desc ); // on remet la valeur originale
}
default
{
on_rez( integer start_params )
{
Verify_Option_Rez(); // on verifie les options de redémarrage
}
...
Save_Desc = llGetObjectDesc(); // uniquement si la description peut être modifiée par le owner
llSetObjectDesc( Save_Desc + "-vente" ); // L'objet est prêt pour la vente ;)
//llSetObjectDesc( Save_Desc + "-reset" ); // Le script doit être reseter!
...
}
}
Dans le script lui même on définit si besoin l'option du prochain rez par l'utilisation du champs Description de l'objet.
Mode 2: on passe le paramètre (ou plusieurs) par l'éditeur de l'objet dans le champs Description.
integer Debug_Mode;
Verify_Option_Rez ()
{ // on teste si une option a été programmée
string obj_desc = llGetObjectDesc();
if ( obj_desc == save_Desc)
return;
else if ( obj_desc == save_Desc + "-reset" )
llResetScript ();
else if ( obj_desc == save_Desc + "-debug" )
Debug_Mode = TRUE;
llSetObjectDesc( save_Desc ); // on remet la valeur originale
}
default
{
on_rez( integer start_params )
{
Verify_Option_Rez(); // on verifie les options de redémarrage
}
....
if (Debug_Mode)
llOwnerSay ( )
....
if (Debug_Mode)
llOwnerSay ( )
....
if (Debug_Mode)
llOwnerSay ( )
....
}
On peut activer le mode debug sans modifier le script..
Dernier exemple:
Vous avez vendu un objet mais malheureusement il présente un bug et vous passez votre temps à chercher à comprendre etc..
Mais comme vous ne faites PLUS de reset intempestifs suite à la lecture de:
https://forums.jeuxonline.info/showthread.php?t=1023670
vous demandez à votre gentil client le retour de cet objet et vous ajoutez -debug dans la description de l'objet ( Dans VOTRE inventaire option propriétés) et votre écran affiche par magie le contexte du plantage ( si vous avez pris le temps de prévoir l'utilisation de cette option)
integer Debug_Mode;
string Var1 = "valeur1";
string Var2= " valeur2";
string Save_Desc = "V1.0";
Verify_Option_Rez ()
{ // on teste si une option a été programmée
string obj_desc = llGetObjectDesc();
if ( obj_desc == Save_Desc)
return;
else if ( obj_desc == Save_Desc + "-reset" )
llResetScript ();
else if ( obj_desc == Save_Desc + "-debug" )
Dump_Mode ();
llSetObjectDesc( Save_Desc ); // on remet la valeur originale
}
Dump_Mode ()
{
Debug_Mode = TRUE;
llOwnerSay ( "Var1: " + Var1 );
llOwnerSay ( "Var2: " + Var2 );
// etc..
// etc..
// etc..
}
default
{
on_rez( integer start_params )
{
Verify_Option_Rez(); // on verifie les options de redémarrage
}
state_entry()
{
llSetObjectDesc( Save_Desc );
}
//....
if (Debug_Mode)
llOwnerSay ( "");
//....
if (Debug_Mode)
llOwnerSay ("" );
//....
if (Debug_Mode)
llOwnerSay ("" );
//....
}
Remarques:
Attention il y a 1 bug concernant le llSetObjectDesc() concernant les attachments
La description est modifiable par script mais ... pas pris en compte de retour dans l'invent!
http://jira.secondlife.com/browse/SVC-3429
Optimiser vos requêtes
Par Seb_01
Tous les accès au serveurs Dataserver (llGetNotecardLine llGetNumberOfNotecardLines llRequestAgentData llRequestInventoryData llRequestSimulatorData), HTTPserver etc... se font traditionnellement à 99,99% de la façon suivante:
J'envoie une requète , j'attends (sagement) la réponse du serveur je teste si la id réponse = id requête et je renvois une nouvelle requêtes etc...
ce qui se code ainsi
exemple 1
key http_request_id;
default
{
....
http_request_id = ( requète 1)
....
http_response(key request_id, integer status, list metadata, string body)
{
if (request_id == http_request_id)
{
// sauvegarde des datas / test/ décision etc...
http_request_id = (requête 2)
....
}
}
exemple 2
key IdQuery1;
key IdQuery2;
default
{
touch_start(integer total_number)
{
IdQuery1= l (requête 1) ;
}
dataserver(key queryId, string data)
{
if (queryId == IdQuery1)
{
// sauvegarde des datas / test/ décision etc...
IdQuery2= (nouvelle requête) ;
}
else if (queryId == IdQuery2)
{
// sauvegarde des datas / test/ décision etc...
}
}
}
Certainement dù aux avertissements du wiki:
* Dataserver answers do not necessarily come in the order they were requested.
o If there are multiple pending requests, always use the queryid key to determine which answer is being received.
* Dataserver requests will trigger dataserver events in all scripts within the same prim where the request was made.
o If there are multiple scripts with dataserver events in the same prim, always use the queryid key to determine which answer is being received.
o dataserver events will not be triggered in scripts contained in other prims in the same linked object.
* If more data is requested using the same query_id before the dataserver event dealing with that query_id has resolved the data will be trashed. To avoid the trashing do not ask if(query_key == query_id). Though (see above caveats) this has its own drawbacks.
* Requesting data from within the dataserver event is quite valid. However, be aware not to make your own loop inside the event. It will lock the event open until the loop is finished then send all the requests made from within, with the data from the first retrieval.
Toujours est il que les serveurs fonctionnent toujours de la même façon sensors + messages linked + listens et de façon plus général tous les events, ils utilisent
une file d'attente d'une profondeur de 64 sauf avis contraire ou restriction clairement établis ( HTTPrequest par exemple).
Les dataservers et Httpservers n'échappent pas à cette règle, et la petite omission de Linden est un peu compréhensive ceci dit...
Partant de ce principe les handles de ces serveurs peuvent alors être gérés complètement différemment.
1 . On empile la file d'attente (dans les limites indiquées) et on continue son code..
2. On recoit un évènement du serveur on dépile la file d'attente.
Style de code utilisé:
on empile...
for (i = 0; i < nrequetes; i = i + 1)
List_Handles += (list)(requete);
on dépile...
dataserver(key query_id, string data)
{
integer index = llListFindList(List_Ids, (list)query_id);
if ( index != -1 )
... save/test/decision...
}
J'utilise cela depuis des mois et vous aussi à travers certains de mes produits ...
Je vous donne un petit code à titre d'exemple qui permet de lire une notecard en deux coups de cuillère à pot.
string NoteCard = "note_test";
key Qline;
list List_Ids;
read_data (integer nlignes)
{// MAXIMUM 63 lignes blanches comprises (limite queue dataserver) //
integer i;
key id;
List_Ids = [];
for (i = 0; i < nlignes; i = i + 1)
List_Ids += (list)llGetNotecardLine(NoteCard, i);
}
default {
touch_start ( integer number)
{
Qline = llGetNumberOfNotecardLines(NoteCard);
}
dataserver(key query_id, string data)
{
integer index = llListFindList(List_Ids, (list)query_id);
if (query_id == Qline)
read_data ( (integer) data);
else if ( index != -1 )
llOwnerSay (data);
}
}
Copier ce script dans un script et
aussi dans une notecard appelée NoteCard
vous obtiendrez cela
[3:46] Object: string NoteCard = "note_test";
[3:46] Object: key Qline;
[3:46] Object: list List_Ids;
[3:46] Object:
[3:46] Object: read_data (integer nlignes)
[3:46] Object: {// MAXIMUM 63 lignes blanches comprises (limite queue dataserver) //
[3:46] Object: integer i;
[3:46] Object: key id;
[3:46] Object: List_Ids = [];
[3:46] Object: for (i = 0; i < nlignes; i = i + 1)
[3:46] Object: List_Ids += (list)llGetNotecardLine(NoteCard, i);
[3:46] Object: }
[3:46] Object:
[3:46] Object:
[3:46] Object: default {
[3:46] Object:
[3:46] Object: touch_start ( integer number)
[3:46] Object: {
[3:46] Object: Qline = llGetNumberOfNotecardLines(NoteCard);
[3:46] Object: }
[3:46] Object: dataserver(key query_id, string data)
[3:46] Object: {
[3:46] Object: integer index = llListFindList(List_Ids, (list)query_id);
[3:46] Object:
[3:46] Object: if (query_id == Qline)
[3:46] Object: read_data ( (integer) data);
[3:46] Object: else if ( index != -1 )
[3:46] Object: llOwnerSay (data);
[3:46] Object: }
[3:46] Object: }
Je posté
ICI une version plus aboutie basée exactement sur principe.
remarques
Opérateurs de bits
Par BlackShade Nightfire
Il arrive souvent de vouloir envoyer plusieurs paramètres que ce soit à un objet ou un autre script. Il est possible d'échanger ces "paramètres" grâce aux opérateurs de bits que nous stockons dans un seul integer. Nous allons utiliser ceux-là :
& Opérateur ET
| Opérateur OU
~ Opérateur NON
^ Opérateur OU Exclusif
Un bon exemple pour vous montrer la simplicité de ces opérateurs :
Test( integer p ) {
// test des valeurs paramètres avec l'opérateur ET &
list pl;
if( p & PARAM_01 ) pl += ["PARAM_01"];
if( p & PARAM_02 ) pl += ["PARAM_02"];
if( p & PARAM_03 ) pl += ["PARAM_03"];
if( p & PARAM_04 ) pl += ["PARAM_04"];
if( p & PARAM_05 ) pl += ["PARAM_05"];
if( p & PARAM_06 ) pl += ["PARAM_06"];
if( pl != [] )
llSay( 0, "Parametres : " + llList2CSV(pl) );
else
llSay( 0, "aucun parametre !" );
}
integer PARAM_01 = 0x01;
integer PARAM_02 = 0x02;
integer PARAM_03 = 0x04;
integer PARAM_04 = 0x08;
integer PARAM_05 = 0x10;
integer PARAM_06 = 0x20;
// etc...
integer params;
default
{
state_entry() {
// Assignation des paramètres avec l'opérateur OU |
params = PARAM_01 | PARAM_02 | PARAM_06 | PARAM_03;
Test( params );
// Parametres : PARAM_01, PARAM_02, PARAM_03, PARAM_06
//-------------------------------------------------------------------------------
// On ajoute un autre paramètre encore avec l'opérateur OU |
params = params |PARAM_04;
Test( params );
// Parametres : PARAM_01, PARAM_02, PARAM_03, PARAM_04, PARAM_06
//-------------------------------------------------------------------------------
// On enlève un paramètre avec l'opérateur NON ~ et ET &
params = params & ~PARAM_03;
Test( params );
// Parametres : PARAM_01, PARAM_02, PARAM_04, PARAM_06
//-------------------------------------------------------------------------------
// On enlève 2 paramètres avec l'opérateur NON ~ , ET & et OU |
params = params & ~( PARAM_01 | PARAM_02 );
Test( params );
// Parametres : PARAM_04, PARAM_06
//-------------------------------------------------------------------------------
// On utilise le XOR (OU exclusif) ^ pour inverser les paramètres (si le paramètre est présent il l'enleve, sinon il l'ajoute)
params = params^PARAM_04;
Test( params );
// Parametres : PARAM_06
params = params^PARAM_04;
Test( params );
// Parametres : PARAM_04, PARAM_06
//-------------------------------------------------------------------------------
// Ajout de plusieurs paramètres avec l'opérateur OU |
params = params | PARAM_01 | PARAM_02 | PARAM_05;
Test( params );
// Parametres : PARAM_01, PARAM_02, PARAM_04, PARAM_05, PARAM_06
//-------------------------------------------------------------------------------
// On test plusieurs valeurs avec l'opérateur OU | et ET &
if( params & (PARAM_02 | PARAM_05) )
llSay( 0, "PARAM_02 et PARAM_05 sont bien presents !" );
// PARAM_02 et PARAM_05 sont bien presents !
}
}
Edition des liens dans un linkset
Par Ahuri Serenity
Un linkset est un ensemble de prims liées. Pour faire communiquer des scripts au sein d'un linkset, le mieu est d'utiliser les
messages liés à savoir le couple llMessageLinked / link_message :
llMessageLinked( integer linknum, integer num, string str, key id );
Déclenche un évènement link_message avec les paramètres num, str et id dans le groupe ou le lien linknum.
link_message( integer envoyeur_num, integer num, string car, key id ){ ; }
Déclenché lorsqu'un script reçoit un message lié par un appel à la fonction llMessageLinked. llMessageLinked est utilisé pour envoyer des informations entre scripts
Le souci majeur lorsqu'on utilise la fonction llMessageLinked est le suivant :
Il faut que le(s) destinataire(s) du message se reconnaissent. Pour cela, il existe différentes méthodes plus ou moins bonnes. La majorité des gens ne veulent pas se prendre la tête et envoi leur message sur LINK_ALL_OTHERS, LINK_SET, ... quand ca n'a pas lieu d'etre. Autant dire que si vous avez pleins de scripts qui communiquent dans tous les sens c'est le chaos, et niveau lag on trouvera mieux
Alors, j'entend déjà certains me dire "Oui t'es bien gentil, mais je connais pas les linknumber de mes destinataires je veux pas m'embeter avec ca !", et bien ils ont raisons de rouspeter car cela nous amène à la suite : l'édition des liens.
Voici donc une solution super simple pour alleger vos communications et rendre votre système beaucoup plus efficace !
Principe :
Il s'agit de donner a chaque module les linknumbers des modules avec lesquels il communique. Cela se fait de manière automatique et autonome. On fait donc ce que j'appel une édition des liens. Cela évite d'aller déranger les modules voisins pour rien par contre si vous devez envoyer un message groupé au linkset utilisez directement LINK_*.
Regles :
Répartir votre linkset en modules. Chaque module possède un nom clé (unique dans le linkset) sauf pour les modules dans une même prim qui doivent avoir le même nom clé.
Ensuite dans chaque module :
Ajouter la variable globale NOM_OBJET qui contient le nom du module :
string NOM_OBJET = "Gamer";
Toujours en global, définir les couples de variables de liaison :
integer iLINK_TARGET_SAVE; // link number du module
string sLINK_TARGET_SAVE = "Loader"; // nom module
integer iLINK_TARGET_PVRAFF; // link number du module
string sLINK_TARGET_PVRAFF = "PvrAff"; // nom module
integer iLINK_TARGET_HPAFF; // link number du module
string sLINK_TARGET_HPAFF = "HpAff"; // nom module
// etc ...
A chaque couple de variables correspond un module destinataire.
Il faut maintenant définir nos fonctions d'édition de liens :
// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //
// '' '' //
// '' getLinkNumber : Obtenir le link number d'un module ayant un '' //
// '' nom donné. '' //
// '' Envoi LINK_SET si rien n'est trouvé pour '' //
// '' envoyer le message a toutes les prims. '' //
// '' '' //
// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //
integer getLinkNumber(string name)
{
integer i;
integer max = llGetNumberOfPrims();
for(i=2; i < max + 2; ++i)
if (llGetLinkName(i) == name)
return i;
return LINK_SET;
}
// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //
// '' '' //
// '' setLinks : Faire l'edition des liens entre les différents '' //
// '' modules pour les communication en linkmessage. '' //
// '' '' //
// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //
setLinks()
{
llSetObjectName( NOM_OBJET);
iLINK_TARGET_SAVE = getLinkNumber(sLINK_TARGET_SAVE);
iLINK_TARGET_PVRAFF = getLinkNumber(sLINK_TARGET_PVRAFF);
iLINK_TARGET_HPAFF = getLinkNumber(sLINK_TARGET_HPAFF);
}
La première fonction retourne le linknumber du module dont le nom est passé en parametre. La seconde fonction permet de faire l'édition des liens entre le nom du module et son linknumber grâce aux couples de variables. Cette dernière fonction est à appeller au tout début du fonctionnement de votre script (ex : state_entry de l'etat default, on_rez ... ) et a chaque fois que le link est amené a changer.
Voila pour la démarche de construction. Maintenant vous pouvez utiliser vos linknumbers comme bon vous semble, exemple de focntion l'utilisant :
// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //
// '' '' //
// '' updateJaugeHp : Mettre a jour l'affichage de la jauge de vie '' //
// '' '' //
// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' //
updateJaugeHp()
{
llSetLinkPrimitiveParams( iLINK_TARGET_HPAFF, [PRIM_SIZE, < 0.02, (fHp * fDimJaugePleine) / fHp_max, 0.02> ]);
}
llMessageLinked( iLINK_TARGET_SAVE, -5, "exemple", NULL_KEY);
Le message sera envoyé directement au module concerné sans gener les autres.
Intérets :
- communication allegée
- clarification
- pas besoin de se soucier de la valeur des linknumbers, ca se fait tout seul, pratique

defauts : Implémentation statique et nécéssité d'appeler setLinks a chaque changement de linkset.
Edition des liens dans un linkset (Alternative)
Par Seb
L'idée d'Ahuri sur l'adressage des llMessageLinked directement aux prims concernées n'est pas discutable si nous avons un nombre conséquent de prims.
Le code si dessous présente une alternative qui peut être intéressante dans certains cas :
Principe:
Utilisation d'une
liste de paires ( nom de prim, numéro de link)
Build_Index_Prims()
{
integer i;
Index_Prims = [];
integer max = llGetNumberOfPrims()+1;
for(i=1; i < max; ++i)
{
Index_Prims += [llGetLinkName(i),i]; // chargement du linkset dans l'index
}
}
Surveillance du linkset
changed(integer change)
{
if (change & CHANGED_LINK) // si modification du linkset on reconstruit le tableau
Build_Index_Prims();
}
Obtenir le numéro de link à partir d'un nom
integer Name2Link ( string name_link)
{// on recherche dans la liste index du nom et on prend la valeur à l'index + 1
return llList2Integer (Index_Prims, llListFindList( Index_Prims, [name_link]) +1);
}
Pour récupérer un numéro
Name2Link ("nom de la prim destinataire");
Voilà c'est tout.
Un exemple complet avec 7 prims
Si l'objet avait
2 ou 40 prims le code du script ne changerait pas puise que le script se
charge de construire l'index du link_set.
Et que le nom des modules
n'ont pas à être déclarés
Changement de couleur de 3 prims
string RC = "\n";
list Index_Prims;
Build_Index_Prims()
{
integer i;
Index_Prims = [];
integer max = llGetNumberOfPrims()+1;
for(i=1; i < max; ++i)
{
Index_Prims += [llGetLinkName(i),i];
}
llSay(0, RC + llDumpList2String(Index_Prims,RC));
// has returned Root,1,Child funny,2,Child shy,3,Child angry,4,Child sad,5,Child happy,6,Child nice,7
}
integer Name2Link ( string name_link)
{
return llList2Integer (Index_Prims, llListFindList( Index_Prims, [name_link]) +1);
}
default
{
state_entry()
{
Build_Index_Prims();
llSetLinkColor ( LINK_SET, <1.0,1.0,1.0>, ALL_SIDES );
}
touch_start(integer total_number)
{
llSetLinkColor ( Name2Link ("Child sad"), <1.0,0.0,0.0>, ALL_SIDES );
llSetLinkColor ( Name2Link ("Child funny"), <0.0,1.0,0.0>, ALL_SIDES );
llSetLinkColor ( Name2Link ("Child angry"), <0.0,0.0,1.0>, ALL_SIDES );
}
changed(integer change)
{
if (change & CHANGED_LINK)
Build_Index_Prims();
}
}
Remarques:
* Code fixe quelque soit le nombre de prims,
* Plus de paramétrage de noms de module à déclarer.
* Utilisation simple (fonctions).
Créer des menus dynamiques
Par Bestmomo
Je vois presque systématiquement des menus (boites bleus) qui proposent des options qui comprennent le choix déjà en cours, ce qui est vraiment illogique. Voici un exemple standard de menu avec 3 options :
list MENU = ["un","deux","trois"];
integer CANAL = -65214;
integer listen_hd;
default
{
touch_start(integer total_number)
{
listen_hd = llListen(CANAL, "", NULL_KEY, "");
llDialog(llDetectedKey(0), "Faites votre choix", MENU, CANAL);
llSetTimerEvent(30.0);
}
listen(integer channel, string name, key id, string message)
{
llSetTimerEvent(.0);
llListenRemove(listen_hd);
if(message == "un")
llOwnerSay("un");
if(message == "deux")
llOwnerSay("deux");
if(message == "trois")
llOwnerSay("trois");
}
timer()
{
llSetTimerEvent(.0);
llListenRemove(listen_hd);
}
}
Vous remarquez que chaque fois qu'on clique on obtient les 3 options dans le menu. Voici une façon de gérer cela plus efficacement :
list MENU = ["un","deux","trois"];
integer CANAL = -65214;
integer listen_hd;
string memo = "un";
list dynamic_menu()
{
integer i = llListFindList(MENU, [memo]);
return llDeleteSubList(MENU, i, i);
}
default
{
touch_start(integer total_number)
{
listen_hd = llListen(CANAL, "", NULL_KEY, "");
llDialog(llDetectedKey(0), "Faites votre choix", dynamic_menu(), CANAL);
llSetTimerEvent(30.0);
}
listen(integer channel, string name, key id, string message)
{
llSetTimerEvent(.0);
llListenRemove(listen_hd);
memo = message;
if(message == "un")
llOwnerSay("un");
if(message == "deux")
llOwnerSay("deux");
if(message == "trois")
llOwnerSay("trois");
}
timer()
{
llSetTimerEvent(.0);
llListenRemove(listen_hd);
}
}
C'est pratiquement le même code mais j'ai ajouté une fonction qui crée la list en fonction du choix mémorisé. Maintenant le menu n'affiche plus que les 2 options possibles. J'ai simplifié le code au maximum pour mettre l'accent uniquement sur ce point là.
Formatage de texte dans le Chat
Par Bestmomo
Il arrive souvent d’afficher dans le Chat du texte destiné à figurer ensuite dans une notecard, se pose alors la question du formatage de ces données avec comme problèmes : apparition du nom de l’objet, limitation à 1023 caractères, délimitation cohérente des données… Voici une façon de faire :
// -----------------------------------------------
// Variables globales
// -----------------------------------------------
string total;
string nom;
// -----------------------------------------------
// Constantes
// -----------------------------------------------
string LIGNE1 = "::::::::::::::: Debut de la note ::::::::::::::::::::";
string LIGNE2 = "::::::::::::::: Suite de la note ::::::::::::::::";
string LIGNE3 = "::::::::::::::: Fin de la note ::::::::::::::::::::::";
// -----------------------------------------------
// Affichage dans le Chat pour sauvegarde
// -----------------------------------------------
affiche(string s) {
if(s == "start") {
total = "";
nom = llGetObjectName();
llSetObjectName(LIGNE1);}
else if(s == "end") {
if(llStringLength(total) > 0) {
llOwnerSay(total);
llSetObjectName(LIGNE3);
llOwnerSay(" ");}
llSetObjectName(nom);}
else {
if((llStringLength(total) + llStringLength(s)) < 1024)
total += "\n" + s;
else {
llOwnerSay(total);
total = "\n" + s;
llSetObjectName(LIGNE2);}
}
}
// -----------------------------------------------
// Etat par défaut
// -----------------------------------------------
default
{
touch_start(integer total_number)
{
// Création d'une valeur de test
string s = "01234567890123456789012345678901234567890123456789";
// Envoi dans le Chat
affiche("start");
integer c;
for(c = 0; c < 40; ++c)
affiche(s);
affiche("end");
}
}
Le seul élément qui subsiste sans qu’on puisse agir dessus par code est l’affichage de l’heure.
Astuce d'adressage et de communication entre scripts
Par BlackShade Nightfire
Dans des projets qui deviennent complexes et/ou complets, nous sommes amenés à utiliser une solution de partage des ressources et des fonctions dans plusieurs scripts. C'est tout à fait normal à cause de la limitation extrême du LSL. Nous en arrivons donc à utiliser toute sorte de techniques pour "détourner" les limites de ce langage.
Le thème abordé ici est la communication interne entre des scripts qui sont placés dans le
même objet (
Petite précision : J'appelle un objet un ensemble de prims. Si un prim est seul, ce n'est donc pas un prim mais un objet). Pour arriver à nos fins, nous utilisions une fonction prévue pour ça ainsi qu'un event associé à cette fonction.
La fonction (le script envoie) :
llMessageLinked( integer numero_link, integer nombre, string data, key uuid );
L'event (l'autre script reçoit) :
link_message( integer numero_link_envoyeur, integer nombre, string data, key uuid );
Pour mettre en ordre nos communications et leur permettre d'avoir une logique simple et facile à maintenir, nous allons utiliser un système d'adressage entre scripts. Un script peut posséder
une ou plusieurs adresses. C'est grâce à ce système que nous pourrons demander une fonction spécifique à un autre script ainsi que recevoir une réponse.
Il y a donc :
- Un script qui envoit un message avec une adresse par llMessageLinked
- Un autre script qui reçoit ce message (ce script est celui ciblé par l'envoyeur avec son adresse).
Il se pose directement un autre problème : Comment le
script receveur va pouvoir envoyer une réponse au
script envoyeur qui ne serait seulement destinée qu'à lui seul ?
La solution : Le
script envoyeur va devoir indiquer non seulement l'adresse de destination de l'autre script mais aussi une adresse à lui pour recevoir une possible réponse.
Nous avons vu précédemment que la fonction
llMessageLinked se constituait de 4 paramètres :
- [1] integer numero_link : Qui permet de définir le numéro du link d'un objet ou une famille de link(s) [LINK_THIS, LINK_SET, LINK_ALL_OTHERS etc]
- [2] integer nombre : Ici nous pouvons indiquer un nombre que nous souhaitons communiquer
- [3] string data : Ici nous pouvons indiquer une chaine de caractères que nous souhaitons communiquer
- [4] key uuid : Ici nous pouvons indiquer une key ou une autre chaine de caractères que nous souhaitons également communiquer
Nous avons également vu que l'event
link_message se constituait de 4 paramètres :
- [1] integer numero_link_envoyeur : Le numéro du link de l'envoyeur
- [2] integer nombre : Le nombre envoyé dans le paramètre [2] de llMessageLinked
- [3] string data : La chaine de caractères envoyée dans le paramètre [3] de llMessageLinked
- [4] key uuid : La key ou la chaine de caractère envoyée dans le paramètre [4] de llMessageLinked
Comment procéder ? Nous allons utiliser 3 paramètres pour une architecture de communication avec système d'adressage :
Pour llMessageLinked (script envoyeur) :
- Le paramètre [2] nous servira à définir l'adresse de destination
- Le paramètre [3] nous servira à envoyer une donnée quelconque
- Le paramètre [4] nous servira à envoyer l'adresse de retour (celle pour la réponse destinée au script envoyeur)
Pour link_message (script receveur) :
- Le paramètre [2] va nous permettre de vérifier si l'adresse indiquée par le script envoyeur sera bien destinée à ce script
- Le paramètre [3] sera la donnée envoyée que nous pourrons traiter par la suite
- Le paramètre [4] sera l'adresse de retour pour que le script receveur envoie une réponse au script envoyeur
Constitution d'une adresse ?
Quand je parle d'une adresse d'un script, il s'agit un nombre unique destiné à un script ou à une fonction d'un script (comme je l'ai précisé plus haut, un script peut avoir plusieurs adresses).
Exemple détaillé
Nous allons mettre en place une communication très simple. Nous avons 2 scripts (nommés respectivement A et B). Le script A voudrait utiliser une fonction qui retourne une valeur du script B. Pour cela, nous avons besoin de 2 adresses :
- L'adresse de destination du script B pour atteindre sa fonction
- L'adresse de réception de la valeur retournée par la fonction du script B (qui sera destinée au script A)
- Pour l'adresse de destination nous allons prendre :
45000
- Pour l'adresse de réception nous allons prendre :
120
[Note : ce sont des valeurs que j'ai pris au hasard, vous pouvez mettre l'adresse que vous voulez]
Script A :
//...
touch_start( integer n ) {
llMessageLinked( LINK_THIS, 45000, "test", "120" );
}
link_message( integer link, integer adress, string data, key data2 ) {
if( adress == 120 ) {
// ici nous reçevons la valeur de retour
llSay( 0, "valeur fonction script B retournee : " + data );
}
}
// ...
Script B :
//...
link_message( integer link, integer adress, string data, key data2 ) {
if( adress == 45000 ) {
llMessageLinked( link, (integer)((string)data2), data + "-ok!", NULL_KEY );
}
}
// ...
Pour le script A, dans l'event touch_start nous envoyons un message linked avec la fonction llMessageLinked en spécifiant :
- 45000 : Qui est l'adresse du script B pour la fonction
- "test" : Qui est une donnée que nous faisons parvenir à la fonction du script B
- "120" : Qui est l'adresse de réception du script A de la valeur retournée de la fonction du script B
Toujours dans le script A, nous voyons l'event link_message. Pourquoi ? Parce dans cet exemple, l'event link_message du script A va nous permettre de recevoir la réponse du script B qui sera envoyée à l'adresse 120 (spécifiée dans la fonction llMessageLinked).
Nous utilisons donc une condition sur le paramètre [2] pour savoir si l'adresse envoyée est bien 120.
Pour le script B, nous avons seulement besoin de l'event link_message qui va vérifier si le paramètre [2] est bien l'adresse 45000 qui correspond à une de ses fonctions. Il va ensuite envoyer lui-même un llMessageLinked à l'adresse de retour (soit 120 dans cet exemple) pour faire parvenir la donnée.
Les points positifs de cette architecture : :
- Très facile à faire et à maintenir
- Un script peut recevoir plusieurs demandes à la fois sans risque de "collisions"
- Ce système n'est pas une usine à gaz =)
Les points négatifs de cette architecture : :
J'en vois pas o.o
Pensez à céer vos propres fonctions et constantes.
Par Seb_01
Cet exemple de code n'est pas une astuce dans le sens usuel, simplement un exemple quand définissant VOS constantes et VOS fonctions vous gagnerez en temps et dans cette exemple en présentation plus agréable pour le lecteur.
Le code dans le
bloc defaut n'est pas à prendre en considération juste
le style de fonctions et les constantes
Je donnerai un deuxième exemple basé sur le premier pour rendre les données plus "maintenables".
// Globales Constantes //
string NL = "\n"; // Nouvelle ligne
string NLT = "\n\t"; // Nouvelle ligne + Tabulation
string NL2T = "\n\n\t"; // 2 Nouvelles lignes + Tabulation
string NL2 = "\n\n"; // 2 Nouvelles lignes + Tabulation
string TAB = "\t"; // Tabulation
string TAB2 = "\t\t"; // Double tabulation
string ARR = " => "; // Explicite
string SUP = ">";
string INF = "<";
// Globales Fonctions //
Screen (string text)
{
llOwnerSay ( NL + text + NL);
}
string Si ( integer i){
return " " + (string) i + " ";}
string Ss ( string s){
return " " + s + " ";}
string Sf ( float f){
return " " + (string) f + " ";}
string Sl ( list f){
return " " + llDumpList2String(f," - ");}
string Smc ( integer nombre, string char)
{
integer i;
string final_char;
for (i=0; i <nombre; ++i)
{
final_char += char;
}
return final_char;
}
default
{
touch_start(integer total_number)
{
integer i = 32562362;
float f = PI / 0.1254;
list l = ["Integer",i,"Float",f];
string string1 = "Entrer votre nom,";
string string2 = "votre prénom,";
string string3 = "et votre age";
string output = Smc(35,"_") + NL2T + "Ceci est une texte formaté qui présente" + NL + "la particularité d'offrir une vision plus propre" + NL +
"voire plus profesionnelle." + NL + Smc(35,"_") + NL2 + "Prenons un exemple:" + NL2 + "Affichons la valeur PI:" + NL2T + TAB2 +
"PI" + ARR + Sf(PI) + NL2;
output += "Les integers sont faciles à présentés si on utilise " + NL + "la fonction Si(integer)" + NL2T + "Valeur de l'intéger" + ARR + Si(i) + NL2 +
"Que dire des décimaux si on utilise " + NL + "la fonction Sf(float)" + NL2T + "Valeur de du décimal" + ARR + Sf(f) + NL2 +
"Que dire des listes si on utilise " + NL + "la fonction Sl(list)" + NL2T + "Liste" + ARR + Sl(l) + NL2 ;
output += "La fonction Ss(string) est utilisée pour formater les variables 'string' " + NL2T + SUP +string1 + string2 + string3 + "< est peu lisible. " + NLT +
" avec Ss(string)" + NLT + SUP + Ss(string1) + Ss(string2) + Ss(string3)+ "< cela le devient."+ NL2 + TAB2 + Smc(15,"=+");
Screen (output);
}
}
Résultat sur l'écran.
Propre non?
[8:16] Object:
___________________________________
Ceci est une texte formaté qui présente
la particularité d'offrir une vision plus propre
voire plus profesionnelle.
___________________________________
Prenons un exemple:
Affichons la valeur PI:
PI => 3.141593
Les integers sont faciles à présentés si on utilise
la fonction Si(integer)
Valeur de l'intéger => 32562362
Que dire des décimaux si on utilise
la fonction Sf(float)
Valeur de du décimal => 25.052570
Que dire des listes si on utilise
la fonction Sl(list)
Liste => Integer - 32562362 - Float - 25.052572
La fonction Ss(string) est utilisée pour formater les variables 'string'
>Entrer votre nom,votre prénom,et votre age< est peu lisible.
avec Ss(string)
> Entrer votre nom, votre prénom, et votre age < cela le devient.
=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
Le code mieux présenté avec des routines de mise en forme.
// Globales Constantes //
string NL = "\n";
string NLT = "\n\t";
string NL2T = "\n\n\t";
string NL2T2 = "\n\n\t\t";
string NL2 = "\n\n";
string TAB = "\t";
string TAB2 = "\t\t";
string TAB4 = "\t\t\t\t";
string ARR = " => ";
string SUP = ">";
string INF = "<";
// Globale Variables //
integer i = 312457;
// Globales Fonctions //
Screen (string text)
{
llOwnerSay ( NL + text + NL);
}
string header (string line1, string line2, string line3 ){
return Smc(35,"_") + NL2T + line1 + NL + line2 + NL + line3 + NL + Smc(35,"_") + NL2 ;}
string footer (string line1){
return Smc(35,"_") + NL2T2 + TAB4 + line1 + NL + Smc(35,"_") + NL2 ;}
string demo1 (string line1, string line2, string line3 ){
return line1 + NL + line2 + NL2T + line3 + ARR + Si(i) + NL2; }
string demo2 (string line1, string line2, string line3 ){
return line1+ NL + line2 + NL2T + line3 + ARR ;}
string Si ( integer i){
return " " + (string) i + " ";}
string Ss ( string s){
return " " + s + " ";}
string Sf ( float f){
return " " + (string) f + " ";}
string Sl ( list f){
return " " + llDumpList2String(f," - ");}
string Smc ( integer nombre, string char)
{
integer i;
string final_char;
for (i=0; i <nombre; ++i)
{
final_char += char;
}
return final_char;
}
default
{
touch_start(integer total_number)
{
float f = PI / 0.1254;
list l = ["Integer",i,"Float",f];
string string1 = "Entrer votre nom,";
string string2 = "votre prénom,";
string string3 = "et votre age";
string line1 = "Ceci est une texte formaté qui présente";
string line2 = "la particularité d'offrir une vision plus propre";
string line3 = "voire plus profesionnelle.";
string line4 = "Les integers sont faciles à présentés si on utilise ";
string line5 = "la fonction Si(integer)";
string line6 = "Valeur de l'intéger" ;
string line7 = "Que dire des décimaux si on utilise ";
string line8 = "la fonction Sf(float)";
string line9 = "Valeur de du décimal";
string line10 = "Que dire des listes si on utilise ";
string line11 = "la fonction Sl(list)";
string line12 = "Liste" ;
string output = header ( line1,line2, line3);
output += "Prenons un exemple:" + NL2 + "Affichons la valeur PI:" + NL2T2 + "PI" + ARR + Sf(PI) + NL2;
output += demo1(line4,line5,line6);
output += demo2 (line7,line8,line9) + Sf(f) + NL2;
output += demo2 (line10,line11,line12) + Sl(l) + NL2;
output += footer( "Fin de demo" );
Screen (output);
}
}
On aurait pu mettre les lignes de texte dans une liste pour économiser les variables.
style:
list texte = [ "Ceci est une texte formaté qui présente",
"la particularité d'offrir une vision plus propre",
....
];
string output = header ( llList2String(texte,0), llList2String(texte,1), llList2String(texte,2) );
et le résultat:
[12:05] Object:
___________________________________
Ceci est une texte formaté qui présente
la particularité d'offrir une vision plus propre
voire plus profesionnelle.
___________________________________
Prenons un exemple:
Affichons la valeur PI:
PI => 3.141593
Les integers sont faciles à présentés si on utilise
la fonction Si(integer)
Valeur de l'intéger => 312457
Que dire des décimaux si on utilise
la fonction Sf(float)
Valeur de du décimal => 25.052570
Que dire des listes si on utilise
la fonction Sl(list)
Liste => Integer - 312457 - Float - 25.052572
___________________________________
Fin de demo
___________________________________
Optimiser son code
Par Bestmomo
Ce n’est pas une astuce mais plutôt un exemple d’optimisation de code.
Voici un code qui m’a été soumis par une amie pour avoir mon avis. Il s’agit de la commande d’une baie qui doit passer en fantôme pour être franchie en utilisant un clic de la souris :
default
{
state_entry()
{
llSay(0, "Baie en action"); // information
state ferme; // passe à l'état initial fermé
}
}
state ouvert // état porte ouverte
{
state_entry()
{
llSetTimerEvent(10.0);
llSay(PUBLIC_CHANNEL, "Vous avez 10 secondes pour traverser la baie");
}
timer()
{
llSetTimerEvent(0.0);
integer c;
for (c = 1; c <= llGetNumberOfPrims(); c++)
llSetLinkPrimitiveParams(c, [PRIM_PHANTOM, FALSE]);
llSay(PUBLIC_CHANNEL, "Baie fermee");
state ferme;
}
}
state ferme // état porte fermée
{
touch_start(integer total_number)
{
if (!llSameGroup(llDetectedKey(0))){
llSay(PUBLIC_CHANNEL, "Vous n'etes pas autorise a agir");
}
else{
integer c;
for (c = 1; c <= llGetNumberOfPrims(); c++)
llSetLinkPrimitiveParams(c, [PRIM_PHANTOM, TRUE]);
state ouvert; // passage à l'état ouvert
}
}
}
Je pense qu’il s’agit d’un bon exemple d’accumulation d’erreurs et maladresses :
Mauvaise utilisation d’un paramètre d’une fonction LSL :
La fonction llSetLinkPrimitiveParams réclame comme premier paramètre le numéro de la prim sur laquelle doit s’effectuer l’action. Mais il est possible d’utiliser la constante LINK_SET pour agir sur toutes les prims liées ce qui évite l’utilisation d’une boucle.
Mauvaise connaissance d’une propriété des objets :
La propriété phantom concerne un objet complet et l’action sur une seule prim suffit à modifier cet objet. D’autre part il suffit d’utiliser la fonction llSetStatus qui est plus simple et n’entraine pas le délai de 0,2 secondes imposé par llSetLinkPrimitiveParams.
Répétition de code :
Il faut éviter de répéter du code au sein d’un script parce que ça l’alourdit et ça rend difficile sa maintenance. Il est aussi toujours judicieux de factoriser son code au sein de fonctions que l’on peut généraliser, de cette façon les évolutions ultérieures du code sont favorisées.
Utilisation inadaptée d’un timer :
Il est souvent fait usage d’un timer alors qu’un simple llSleep serait suffisant.
Utilisation inadaptée d’un état :
L’utilisation d’états dans les scripts LSL me paraît logique et correspondre à ce langage mais cette utilisation doit être justifiée. Ici on n’a pas vraiment deux états puisque l’un d’entre eux (baie ouverte) est instable. L’utilisation d’un seul état dans ce cas simplifie le code.
Utilisation de valeurs numériques littérales dans le code :
La valeur de la temporisation est insérée dans ce code à l’emplacement où on en a besoin. Si on désire changer cette valeur il faut retrouver cet emplacement, il est plus judicieux de prévoir une constante en tête du script. Même si pour un script aussi simple ce n’est pas vraiment évident ça le devient vite lorsque le code s’allonge.
Mauvaise initialisation :
Au démarrage le script va dans l’état de fermeture sans prendre la précaution de rendre la baie non fantôme.
Voici une version corrigée qui tient compte de toutes ces réflexions et qui rejoint un peu l’exemple précédent de Seb sur les fonctions et constantes :
// Constantes
integer GROUPE = 0;
integer OWNER = 1;
integer ALL = 2;
integer OUVERTURE = 1;
integer FERMETURE = 0;
float DELAI = 10.0;
// Affichage info dans le Chat
info(string s) {llSay(0, s);}
// Commande de la baie
commande(integer etat, string s) {
llSetStatus(STATUS_PHANTOM, etat);
info(s);}
// Test d'utilisateur
integer test(key id, integer mode) {
if(mode == GROUPE)
return llSameGroup(id);
else if(mode == OWNER)
return llGetOwner() == id;
else
return TRUE;}
// Etat par défaut
default
{
state_entry()
{
commande(FERMETURE, "Baie en action.\n Baie fermée.");
}
touch_start(integer total_number)
{
if (test(llDetectedKey(0), GROUPE))
{
commande(OUVERTURE, "Vous avez " + (string)DELAI + " secondes pour traverser la baie");
llSleep(DELAI);
commande(FERMETURE, "Baie fermée");
}
else
info("Vous n'êtes pas autorisé à ouvrir la baie, désolé.");
}
}
Le code est devenu très lisible.
Sécuriser des données
Par Bestmomo
Quoi qu’on fasse il peut toujours arriver un crash de serveur avec perte des valeurs de variables dans les scripts. Les éléments persistants d’un prim ne sont pas nombreux. Parmi eux il y a les champs « nom » et « description » de dimensions relatives 63 et 127 caractères. Un objet est en général constitué de plusieurs prims et, si les deux champs en questions sont utilisés au niveau de la prim racine il n’en est pas de même au niveau des enfants. Du coup ils peuvent servir de dépôt de valeurs. Les problèmes :
• ils sont disséminés et ne permettent pas une sauvegarde de grandes valeurs
• ils ne sont accessibles en écriture qu’à partir d’un script situé dans la prim concernée.
A bien y regarder il est quand même intéressant de s’engager sur cette piste même si je sais qu’elle sera source de polémique à cause de la (relative) multiplication des scripts et du lag généré. Mais c’est une astuce alors je la livre
Pour tester ça constituez un objet de 4 prims et mettez ce script dans les 3 prims enfants :
// Satellite
// Constantes
integer SAVE_NAME = 1111;
integer SAVE_DESC = 1113;
// Etat par défaut
default
{
link_message(integer sender_number, integer number, string message, key id)
{
if(number == SAVE_NAME)
llSetObjectName(message);
else if(number == SAVE_DESC)
llSetObjectDesc(message);
}
}
Ce script est destiné à l’écriture des valeurs dans les champs « nom » et « description ».
Mettez ce script dans la prim racine :
// Stockage
// Chaque prim enfant peut conserver 190 caractères (63 + 127)
// Présence d'au moins 2 prims enfants
// Valeur sauvée limitée à 9999 caractères
// Constantes
integer SAVE_NAME = 1111;
integer SAVE_DESC = 1113;
// Variables
integer nom;
integer desc;
// Détermination longueur string à traiter
integer length(string s, integer mod)
{
integer l = llStringLength(s);
if(l > mod)
return mod;
else
return l;
}
// Formatage nombre en string
string formatNumber(integer n, integer l)
{
string s = (string)n;
while(llStringLength(s) < l)
s = "0" + s;
return s;
}
// Enregistrement de la valeur
integer save(string s)
{
integer links = llGetNumberOfPrims() - 1;
integer n = llStringLength(s) + 4;
if(n > (127 + 255) * links)
return FALSE;
s = formatNumber(n, 4) + s;
nom = 2;
desc = 2;
integer lg;
links += 2;
while(llStringLength(s))
{
if(nom < links)
{
lg = length(s, 63) - 1;
llMessageLinked(nom, SAVE_NAME, llGetSubString(s, 0, lg), NULL_KEY);
nom++;
}
else
{
lg = length(s, 127) - 1;
llMessageLinked(desc, SAVE_DESC, llGetSubString(s, 0, lg), NULL_KEY);
desc++;
}
if(lg + 1 < llStringLength(s))
s = llGetSubString(s, lg + 1, -1);
else
s = "";
}
return TRUE;
}
// Récupération de la valeur
string restore()
{
list l = llGetObjectDetails(llGetLinkKey(2), [OBJECT_NAME]);
integer n = (integer)llGetSubString(llList2String(l, 0), 0, 3);
if(n < 64)
return llGetSubString(llList2String(l, 0), 4, n + 3);
string st = llGetSubString(llList2String(l, 0), 4, -1);
integer links = llGetNumberOfPrims() + 1;
nom = 3;
desc = 2;
while(llStringLength(st) < n - 4)
{
if(nom < links)
{
l = llGetObjectDetails(llGetLinkKey(nom), [OBJECT_NAME]);
nom++;
}
else
{
l = llGetObjectDetails(llGetLinkKey(desc), [OBJECT_DESC]);
desc++;
}
st += llList2String(l, 0);
}
return st;
}
// Etat par défaut
default
{
touch_start(integer total_number)
{
string s = "0123454678901234546789012345467890123454678901234546789";
string t;
integer c;
for(c = 0; c < 10; c++)
t += s;
if(!save(t)) // Enregistrement de la valeur
llOwnerSay("Valeur trop grande !");
llOwnerSay(restore()); // Restauration de la valeur
}
}
C’est le script principal. Il montre comment stocker une valeur de type « string » dans les champs « nom » et « description » des prims enfants comme s’il s’agissait d’un seul champ global. Rien n’empêche de placer des signes séparateurs au niveau de la valeur pour la découper en plusieurs zones de stockage faciles à identifier.
Menu à suivre : BACK ... NEXT
Elenia Boucher
C'est la 1ére fonction que j'ai mise au point quand je me suis mise à scripter : présenter une liste longue par block de choix de 9 avec les touches NEXT et BACK pour naviguer dedans; cette version ne fonctionne qu'avec le owner : elle est faite pour les HUD. Modification facile pour utilisation tout le monde ou groupe. Je vous laisse un peu de travail quand même.
list glChoix = ["un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix","onze","douze"];
//attention à la longueur de chaque choix : ne pas dépasser 24 caractéres dont seulement environ la moitié paraitra dans le bouton
integer giNb ;
integer giIndexMenu;
integer giChannel;
integer giShiftChan = -786492;
integer giHandle;
key gkOwner;
Init(){
giNb = llGetListLength(glChoix);
gkOwner = llGetOwner();
giChannel = -(integer)("0x"+llGetSubString(gkOwner,-7,-1)) + giShiftChan;
//notez le calcul du canal sur la clé owner modifiée par un nombre arbitraire; on peut aussi utiliser la clé objet.
}
BuildMenu() {
list menu;
giHandle = llListen( giChannel, "", gkOwner, "" );
string affich = "Faites votre choix\ntime-out 30 secondes :";
llSetTimerEvent(30.0);
if(giIndexMenu > 0)
menu += ["BACK"];
else
menu += [" "];
menu += [" "];//touche centrale : vous pouvez y mettre MAIN si cela n'est pas le menu principal
if(giIndexMenu*8+8 < giNb - 1)
menu += ["NEXT"];
else
menu += [" "];
menu += llList2List(glChoix, giIndexMenu*9, giIndexMenu*9+8);
llDialog(gkOwner, affich, menu, giChannel);
}
default{
state_entry(){
Init();
}
changed(integer chg){ // si on change d'owner il faut recalculer le channel
if (chg & CHANGED_OWNER) llResetScript();
}
touch_start(integer total_number){
giIndexMenu=0;BuildMenu();
}
timer(){
llSetTimerEvent(0);
//llOwnerSay("time out");
llListenRemove(giHandle);
}
listen(integer chan, string name, key id, string mess){
llSetTimerEvent(0);
llListenRemove(giHandle);
if (mess == "NEXT"){giIndexMenu++; BuildMenu();}
else if (mess == "BACK"){giIndexMenu--; BuildMenu();}
//else if (mess == "MAIN"){} //retour éventuel au menu principal
else { // ici le traitement du choix
llOwnerSay("Vous avez choisi : " + mess);
//éventuellement je traite comme ça
integer s = llListFindList(glChoix,[mess]);
//en fonction de la variable s extraction d'une valeur dans une liste associée
// action = llList2String(glActions, s); // ou key ou vector ou rotation ou integer ou float
}
}
}
Une application : le testeur et giver d'animation : vous mettez ce script avec des animation dans un cube, n'importe qui clicquant dessus aura un menu de choix lui permettant de choisir et jouer une animation et de l'obtenir.
J'ai utilisé ici une variante : les choix sont proposé avec des numéros et une liste en regard : pratique si vos choix ne tiennent pas en 24 caractéres, et indispensable si vous ne maitrisez pas la longueur et qu'elle est donc succptible de dépasser les fatidiques 24 caractéres qui autrement provoqueraient une erreur de script.
list glChoix;
integer giNb ;
string gsAnimCur;
integer giIndexMenu;
integer giChannel = 786492;
integer giHandle;
key gkAvi;
string gsAvi;
integer gbPermAck;
ChargeAnim(){
integer i;
glChoix=[];
giNb = llGetInventoryNumber(INVENTORY_ANIMATION);
for ( i = 0; i < giNb; i++) {
string item = llGetInventoryName(INVENTORY_ANIMATION, i);
glChoix += [item];
}
}
BuildMenu() {
list menu;
integer i;
menu = [];
string affich = "Choose a animation or GIVE/STOP :\nCurrent animation : " + gsAnimCur;
string num;
llSetTimerEvent(60.0);
giHandle = llListen( giChannel, "", gkAvi, "" );
if(giIndexMenu > 0)
menu += ["BACK"];
else
menu += [" "];
menu += ["GIVE/STOP"];
if(giIndexMenu*8+8 < giNb - 1)
menu += ["NEXT"];
else
menu += [" "];
for(i=giIndexMenu*9; (i <= giIndexMenu*9+8) && (i <= giNb - 1);i++) {
num = (string)(i+1);
affich += "\n"+num+":"+llList2String(glChoix,i);
menu += [num];
}
llDialog(gkAvi, affich, menu, giChannel);
}
Popup(key id, string message){ //petite fonction très pratique; ne pas utiliser le canal pour autre chose
llDialog(id, message, [], 298479);
}
default{
state_entry(){
ChargeAnim();
}
changed(integer change){
if (change & CHANGED_INVENTORY) ChargeAnim();
}
touch_start(integer tot){//vous remarquerez le traitement de plusieurs avatars qui clicque dessus en même temps
integer i;
key kavi;
string savi;
for (i=0;i<tot;i++){
kavi = llDetectedKey(i);
savi = llDetectedName(i);
if (!gbPermAck && !giHandle && i == 0){
gkAvi = kavi;
gsAvi = savi;
gbPermAck = TRUE;
llRequestPermissions(gkAvi, PERMISSION_TRIGGER_ANIMATION);
}else
Popup(kavi,"Sorry " + savi + " but " + gsAvi + " uses the menu");
}
}
run_time_permissions(integer perm) {
gbPermAck = FALSE;
if(perm & PERMISSION_TRIGGER_ANIMATION){
gsAnimCur = ""; giIndexMenu=0; BuildMenu();
}
}
timer(){
if (gsAnimCur !="") llStopAnimation(gsAnimCur);
llRequestPermissions(gkAvi, FALSE);
llSetTimerEvent(0);
llListenRemove(giHandle);
giHandle = FALSE;
}
listen(integer chan, string name, key id, string mess){
llSetTimerEvent(0);
llListenRemove(giHandle);
integer numanim = (integer)mess - 1;
if (numanim >= 0 ){
if (gsAnimCur !="") llStopAnimation(gsAnimCur);
gsAnimCur = llList2String(glChoix, numanim);
llStartAnimation(gsAnimCur);
BuildMenu();
}
else if (mess == "NEXT"){giIndexMenu++; BuildMenu();}
else if (mess == "BACK"){giIndexMenu--; BuildMenu();}
else if (mess == "GIVE/STOP"){
giHandle = FALSE;
if (gsAnimCur !="") {
llStopAnimation(gsAnimCur);
llGiveInventory(gkAvi, gsAnimCur);
}
llRequestPermissions(gkAvi, FALSE);
}
}
}
Exploitez les clics!!
By Seb
99,99999 % des scripts n'utilise que touch_start comme event user ( action utilisateur) et pourtant...
Vous avez
beaucoup plus d'events à votre dispo!!
Exemple d'actions et de reset (différé ou immédiat...)
integer Clics;
default
{
state_entry()
{
llOwnerSay( "Bonjour click man!");
}
touch_start(integer total_number)
{
llOwnerSay ( "Action comme dab ICI ..." );
llResetTime();
}
touch_end(integer total_number)
{
Clics = 0;
float stop_clic = llGetTime();
if (stop_clic > 1.0)
{
llOwnerSay( "Reset si le temp > 1s >>apres<< le relachement. ==>" + (string) stop_clic);
llResetScript();
}
llOwnerSay ( "OU Action possible ICI ..." );
}
touch(integer total_number)
{
++Clics;
if ( Clics > 50 )
{
llOwnerSay( "Reset IMMEDIAT si clics > 50 ==>" + (string) Clics);
llResetScript();
}
llOwnerSay ( "OU encore ICI ...mais prudence .. prudence..." );
}
}