Suite au
tuto sur la gestion du rez je post maintenant un script d'exemple d'utilisation.
Il s'agit donc d'un système de modules avec un follower comme exemple. Un follower est un objet qui suit un avatar où qu'il soit dans la sim (d'où le nom follower/suiveur
).
Le système permet de lancer des followers sur plusieurs personnes à la fois. Il permet également de créer ses propres followers facilement puisqu'il est modulaire.
Note : C'est un système très basique à titre d'exemple, je précise également que lancer ce genre d'objet sur un avatar sans son consentement est contraire au TOS de Second Life (sauf dans le cas de sims de combat prévues pour ).
Installation
Commencez par créer un objet (peut importe) et attachez en HUD sur l'attachement qui vous plaira.
Dans votre HUD, créez un premier script, nommez-le par exemple
Rez Handler et placez-y ce code :
// !! NOTE !! Le script a été mis à jour le 19/12/09 et fonctionne un peu différemment de l'ancienne version.
//----------------------------------------------------------------------------------
// LIBRARY FUNCTIONS
//----------------------------------------------------------------------------------
// -- Fonction de "correction" de vecteurs de position pour ne pas aller offworld ou au dessus de 4096m --
// @ param [vector] position à corriger
// @ return [vector] retourne le vecteur corrigé
vector CorrectVectorPos( vector p ) {
if( p.x > 256.0 ) p.x = 256.0;
if( p.x < 0.0 ) p.x = 0.0;
if( p.y > 256.0 ) p.y = 256.0;
if( p.y < 0.0 ) p.y = 0.0;
if( p.z > 4096.0 ) p.z = 4096.0;
return p;
}
// -- Fonction permettant de vérifier si l'objet est autorisé à rez à cette position --
// @ param [vector] position à vérifier
// @ return [bool] retourne true si il est autorisé, false dans le cas contraire
integer ParcelCheck( vector p ) {
integer flags = llGetParcelFlags( p );
if( flags & PARCEL_FLAG_ALLOW_CREATE_OBJECTS ||
(flags & PARCEL_FLAG_ALLOW_CREATE_GROUP_OBJECTS &&
llList2Key(llGetObjectDetails(llGetKey(), [OBJECT_GROUP]), 0) == llList2Key(llGetParcelDetails(p, [PARCEL_DETAILS_GROUP]), 0)) )
return TRUE;
return FALSE;
}
// -- Fonction qui génere un canal au hasard (négatif) --
// @ return [integer] retourne le canal
integer GetRandomChannel() {
return ~(integer)llFrand( (float)DEBUG_CHANNEL );
}
//----------------------------------------------------------------------------------
// SCRIPT FUNCTIONS
//----------------------------------------------------------------------------------
// -- Fonction llRezObject protégée qui vérifie si le rez est possible et envoie des données à l'objet
// de façon automatique --
// @ param [string] nom de l'objet dans l'inventaire
// @ param [vector] position du rez
// @ param [vector] vélocité de l'objet rezzé
// @ param [rotation] rotation de l'objet
// @ param [integer] canal de l'objet pour transmettre les données
// @ param [integer] type de rez, position locale/rotation locale ou non
// @ param [string] les données à envoyer
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
// Est-ce que l'objet est bien un objet et présent dans l'inventaire ? si non, on envoie une erreur au owner.
if( llGetInventoryType(name) != INVENTORY_OBJECT ) {
llOwnerSay( "Erreur :: " + name + " n'est pas un objet ou n'est pas présent dans l'inventaire." );
return;
}
// on stock temporairement la pos/rot de l'objet/avatar
vector myPos = llGetPos();
rotation myRot = llGetRot();
vector rezPos = myPos + pos;
// on calcule le type de rez demandé
if( rezType & POS_TYPE_REGION )
rezPos = pos;
else if( rezType & POS_TYPE_LOCAL_ROTATED )
rezPos = myPos + pos * myRot;
if( rezType & ROT_TYPE_LOCAL )
rot *= myRot;
// si la distance de rez est plus de 10 mètres, on la réduit au max possible
// pour éviter une erreur de rez silencieuse
if( llVecDist(myPos, rezPos) > 10.0 )
rezPos = myPos + llVecNorm(rezPos-myPos) * 10.0;
// Si il y a possibilité de rez à la position
if( CorrectVectorPos(rezPos) == rezPos && ParcelCheck(rezPos) ) {
// Si le canal n'est pas 0 et que les données à envoyer ne sont pas vides, on crée une connexion
if( chan != 0 && data != "" ) {
if( !CreateConnection(chan, data) )
return;
}
llRezObject( name, rezPos, vel, rot, chan );
// Sinon on vérifie la présence d'un proxy dans la région
} else if( IsProxyAvailable() ) {
// Si le canal n'est pas 0 et que les données à envoyer ne sont pas vides, on crée une connexion
if( chan != 0 && data != "" ) {
if( !CreateConnection(chan, data) )
return;
}
llGiveInventory( gProxyKey, name );
llSleep( 0.2 );
llRegionSay( gProxyChan, name + PRIM_SEP + (string)vel + PRIM_SEP + (string)rot + PRIM_SEP + (string)chan );
}
}
// -- Vérifie si un proxy est disponible --
// @ return [bool] true : disponible, false : non disponible
integer IsProxyAvailable() {
return ( PROXY_ACTIVE && llKey2Name(gProxyKey) != "" );
}
// -- Crée une connexion et stock les données dans une mémoire tampon en attendant le rez --
// @ param [integer] canal du transfert
// @ param [string] les données à envoyer
// @ return [bool] true : la connexion est établie, false : echec de la création d'une connexion
integer CreateConnection( integer chan, string data ) {
if( (gListenChan != []) > 64 )
return FALSE;
gListenData += data;
gListenChan += chan;
gListenID += llListen( chan, "", NULL_KEY, "get" );
return TRUE;
}
// -- Envoie les données mises en mémoire sur le canal concerné --
// @ param [integer] le canal du transfert
FlushData( integer chan ) {
integer fd = llListFindList( gListenChan, [chan] );
// si le canal possède bien des données à envoyer
if( ~fd ) {
llListenRemove( llList2Integer(gListenID, fd) );
llRegionSay( chan, llList2String(gListenData, fd) );
gListenID = llDeleteSubList( gListenID, fd, fd );
gListenChan = llDeleteSubList( gListenChan, fd, fd );
gListenData = llDeleteSubList( gListenData, fd, fd );
}
}
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
// CONSTANTS
//--------------------------------------------------------------------------------------
//----------------------------------------------
// INTERNAL COMM
//----------------------------------------------
//----------------
// ME
//----------------
integer IC_REZ_REQUEST = -1621184001;
integer IC_REZ_CONNECTION = -250865153;
integer IC_REZ_FLUSH = -605179777;
integer IC_REZ_CLEAR = -2105587073;
//----------------
//SCRIPT ADDRESSES
//----------------
//----------------
// GLOBAL
//----------------
integer IC_GLOBAL_REZ_RETCONNECTION = -1204281601;
//----------------------------------------------
// PARAMETERS
//----------------------------------------------
// activer ou désactiver la fonction du proxy.
integer PROXY_ACTIVE = FALSE;
// le délais de rafraichissement de timer de vérification
float PROXY_REFRESH = 5.0;
// le nom du proxy dans l'inventaire
string PROXY_NAME = "MyProxy";
// le temps de vie du proxy
float PROXY_LIFE = 600.0; // 10 minutes
// rez d'un autre proxy X secondes avant que l'ancien se supprime
float PROXY_TIME_REZ = 20.0;
// offset de rez du proxy
vector PROXY_REZ_OFFSET = <0.0, 0.0, 2.0>;
//----------------------------------------------
// REZ TYPE
//----------------------------------------------
// rez par position relative à la région
integer POS_TYPE_REGION = 0x01;
// rez par position locale selon la rotation de l'objet/l'avatar
integer POS_TYPE_LOCAL_ROTATED = 0x02;
// rez par rotation locale
integer ROT_TYPE_LOCAL = 0x04;
//----------------------------------------------
// UTIL
//----------------------------------------------
string PRIM_SEP = "|";
string SUB_SEP = ":";
//--------------------------------------------------------------------------------------
// GLOBAL VARIABLES | State : Default
//--------------------------------------------------------------------------------------
//----------------------------------------------
// MEMORY
//----------------------------------------------
list gListenData;
list gListenChan;
list gListenID;
//----------------------------------------------
// PROXY
//----------------------------------------------
key gProxyKey;
integer gProxyChan;
//----------------------------------------------
// OTHERS
//----------------------------------------------
key gOwnerKey;
default
{
on_rez( integer r ) {
state clearconnections;
}
state_entry() {
gOwnerKey = llGetOwner();
if( PROXY_ACTIVE )
llSetTimerEvent( 0.1 );
}
changed( integer c ) {
if( c & CHANGED_REGION )
state clearconnections;
}
link_message( integer link, integer address, string data, key data2 ) {
// Si l'adresse correspond à une requête de rez, on reforme le packet pour l'envoyer à la fonction
if( address == IC_REZ_REQUEST ) {
list packet = llParseString2List( data, [PRIM_SEP], [] );
SafeRezObject(
llList2String(packet, 0), // name
(vector)llList2String(packet, 1), // pos
(vector)llList2String(packet, 2), // vel
(rotation)llList2String(packet, 3), // rot
(integer)llList2String(packet, 4), // chan
(integer)llList2String(packet, 5), // rezType
(string)data2 // data
);
}
// Si l'adresse est une demande de création de connexion
else if( address == IC_REZ_CONNECTION )
llMessageLinked( LINK_SET, IC_GLOBAL_REZ_RETCONNECTION, (string)data2, (string)CreateConnection((integer)data, (string)data2) );
// Si l'adresse correspond à une requête d'envoi de données
else if( address == IC_REZ_FLUSH )
FlushData( (integer)data );
// Demande de clearconnections
else if( address == IC_REZ_CLEAR )
state clearconnections;
}
listen( integer chan, string name, key id, string msg ) {
if( llGetOwnerKey(id) == gOwnerKey ) {
FlushData( chan );
if( name == PROXY_NAME )
gProxyKey = id;
}
}
timer() {
// si aucun proxy détecté ou en fin de vie, on en rez un autre
if( llKey2Name(gProxyKey) == "" || (llGetTime() + PROXY_TIME_REZ) >= PROXY_LIFE ) {
integer rand = GetRandomChannel();
SafeRezObject( PROXY_NAME, PROXY_REZ_OFFSET, ZERO_VECTOR, ZERO_ROTATION, rand, 0, (string)PROXY_LIFE );
gProxyChan = rand;
llResetTime();
}
llSetTimerEvent( PROXY_REFRESH );
}
}
// State permettant de désactiver tous les listens du script rapidement
// ainsi qu'éffacer la mémoire.
state clearconnections
{
state_entry() {
gListenData = [];
gListenChan = [];
gListenID = [];
state default;
}
}
Créez un second script
Module Storage et mettez ce code :
//----------------------------------------------------------------------------------
// EXTERNAL FUNCTIONS
//----------------------------------------------------------------------------------
//----------------------------------
// OWN EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------
// SCRIPTS EXTERNAL FUNCTIONS
//----------------------------------
SafeRezObject( string name, vector pos, vector vel, rotation rot, integer chan, integer rezType, string data ) {
llMessageLinked( LINK_SET, -1621184001,
name + "|" +
(string)pos + "|" +
(string)vel + "|" +
(string)rot + "|" +
(string)chan + "|" +
(string)rezType, data
);
}
//----------------------------------
// GLOBAL EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------------------------------------------------------
// LIBRARY FUNCTIONS
//----------------------------------------------------------------------------------
// -- Fonction qui génere un canal au hasard (négatif) --
// @ return [integer] retourne le canal
integer GetRandomChannel() {
return ~(integer)llFrand( (float)DEBUG_CHANNEL );
}
//----------------------------------------------------------------------------------
// SCRIPT FUNCTIONS
//----------------------------------------------------------------------------------
// -- Supprime tous les modules de la mémoire --
ClearMemory() {
gModuleListCmd = [];
gModuleListInv = [];
gModuleListOff = [];
gModuleListRot = [];
}
// -- Charge l'inventaire pour les modules --
ReloadInventory() {
// on compte le nombre d'items dans l'inventaire (tous les types)
integer invCount = llGetInventoryNumber( INVENTORY_ALL );
string invName;
string cmdName;
list invParsed;
integer i;
// on compte le nombre de modules avant le reload
integer modCountB = ( gModuleListCmd != [] );
while( i < invCount ) {
// on prend le nom correspondant au numéro i dans la liste
invName = llGetInventoryName( INVENTORY_ALL, i++ );
// les modules commencent tous par M\
if( llGetSubString(invName, 0, 1) == "M\\" ) {
// on parse le nom en une list, les séparateurs sont \
// exemple : M\commande_chat\<0,0,9>\<0,0,0,1>
// 0 1 2 3
invParsed = llParseString2List( invName, ["\\"], [] );
// on prend la commande de chat dans la list
cmdName = llList2String( invParsed, 1 );
// on regarde si la commande de chat n'existe pas déjà
if( !~ llListFindList(gModuleListCmd, [cmdName]) ) {
gModuleListCmd += cmdName; // commande chat
gModuleListInv += invName; // nom entier dans l'inventaire
gModuleListOff += (vector)llList2String( invParsed, 2 ); // offset de rez
gModuleListRot += (rotation)llList2String( invParsed, 3 ); // rotation de rez
}
}
}
// on compte le nombre de modules après le reload
integer modCountA = ( gModuleListCmd != [] );
// on compte le nombre de nouveaux modules installés
integer modAdded = modCountA - modCountB;
// si il y a des nouveaux modules
if( modAdded > 0 )
llOwnerSay( "Il y a " + (string)modAdded + " nouveau(x) module(s) installé(s). Total = " + (string)modCountA );
}
// -- initialise le listen --
Init() {
// on sauvegarde la key du owner
gOwnerKey = llGetOwner();
// on désactive la dernière écoute si il y en a
llListenRemove( gListenID );
// et on la réactive
gListenID = llListen( CHANNEL_COMMAND, "", gOwnerKey, "" );
}
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
// CONSTANTS
//--------------------------------------------------------------------------------------
//----------------------------------------------
// INTERNAL COMM
//----------------------------------------------
//----------------
// ME
//----------------
//----------------
//SCRIPT ADRESSES
//----------------
//----------------
// GLOBAL
//----------------
// adresse pour lancer un module interne (script)
integer IC_GLOBAL_LAUNCH_MODULE = -1779072513;
// adresse pour stopper les modules internes
integer IC_GLOBAL_END_MODULE = -39668585;
//----------------------------------------------
// UTIL
//----------------------------------------------
string PRIM_SEP = "|";
string SUB_SEP = ":";
//----------------------------------------------
// REZ TYPE
//----------------------------------------------
// rez par position relative à la région
integer POS_TYPE_REGION = 0x01;
// rez par position locale selon la rotation de l'objet/l'avatar
integer POS_TYPE_LOCAL_ROTATED = 0x02;
// rez par rotation locale
integer ROT_TYPE_LOCAL = 0x04;
//----------------------------------------------
// VOICE PARAMETERS
//----------------------------------------------
// le canal pour les commandes ( 0 par défaut )
integer CHANNEL_COMMAND = 0;
// on active ou non le trigger char ( qui permet de taper des commandes comme ça : !kill blackshade )
integer TRIGCHAR_ENABLED = FALSE;
// quel caractère utiliser pour le trigchar ?
string TRIGCHAR = "!";
//----------------------------------------------
// CHANNEL END
//----------------------------------------------
// channel pour stopper les modules (objets)
integer CHANNEL_END = -1584311041;
//--------------------------------------------------------------------------------------
// GLOBAL VARIABLES
//--------------------------------------------------------------------------------------
//----------------------------------------------
// OWNER INFOS
//----------------------------------------------
key gOwnerKey;
//----------------------------------------------
// LISTEN
//----------------------------------------------
integer gListenID;
//----------------------------------------------
// MODULE STORAGE
//----------------------------------------------
list gModuleListCmd;
list gModuleListInv;
list gModuleListOff;
list gModuleListRot;
//----------------------------------------------
// VOICE MEMORY
//----------------------------------------------
list gTargets;
// module selectionné
integer gCurModType;
string gCurModInv;
vector gCurModOff;
rotation gCurModRot;
default
{
on_rez( integer p ) {
// on initialise au rez
Init();
}
state_entry() {
// on initialise après le reset du script ou de sa création
Init();
}
touch_start( integer num ) {
// si le owner touche le prim, on recharge entièrement les modules
if( llDetectedKey(0) == gOwnerKey ) {
ClearMemory();
ReloadInventory();
}
}
changed( integer c ) {
// on reload les modules dès que l'inventaire change
if( c & CHANGED_INVENTORY )
ReloadInventory();
}
listen( integer chan, string name, key id, string data ) {
// si il y a un caractère obligatoire à mettre en premier pour déclencher une commande
if( TRIGCHAR_ENABLED ) {
// si le premier caractère est bien celui du script
if( llGetSubString(data, 0, 0) == TRIGCHAR )
data = llDeleteSubString( data, 0, 0 );
// sinon on stoppe le processus d'analyse de la commande
else
return;
}
// commande spéciale pour stopper tous les modules
if( data == "end" ) {
llMessageLinked( LINK_SET, IC_GLOBAL_END_MODULE, "", NULL_KEY );
llRegionSay( CHANNEL_END, "die" );
return;
}
// on cherche la position du premier espace dans la commande
integer fiSpos = llSubStringIndex( data, " " );
// si il y a bien au moins un espace dans la commande
if( ~ fiSpos ) {
integer fdModule = llListFindList( gModuleListCmd, [llGetSubString(data, 0, fiSpos - 1)] );
// si il y a bien un module avec cette commande
if( ~ fdModule ) {
// on sauvegarde le/les target(s)
gTargets = llParseString2List( llToLower(llGetSubString(data, fiSpos + 1, -1)), [" "], [] );
// on sauvegarde les paramètres du module
gCurModInv = llList2String( gModuleListInv, fdModule );
gCurModType = ( llGetInventoryType(gCurModInv) == INVENTORY_OBJECT );
// on sauvegarde l'offset de rez et la rotation seulement si il s'agit d'un objet
if( gCurModType ) {
gCurModOff = llList2Vector( gModuleListOff, fdModule );
gCurModRot = llList2Rot( gModuleListRot, fdModule );
}
// on lance un sensor
llSensor( "", NULL_KEY, AGENT, 96.0, PI );
}
}
}
sensor( integer num ) {
integer i;
integer ii;
string curTrgt;
// on compte le nombre de targets dans la commande
integer targetCount = ( gTargets != [] );
while( i < targetCount ) {
// on prend la target numéro i dans la list
curTrgt = llList2String( gTargets, i++ );
ii = 0;
while( ii < num ) {
// si le nom correspond
if( ~ llSubStringIndex(llToLower(llDetectedName(ii)), curTrgt) ) {
// si c'est un module objet
if( gCurModType )
SafeRezObject( gCurModInv, gCurModOff, ZERO_VECTOR, gCurModRot, GetRandomChannel(), 0, (string)llDetectedKey(ii) ); // sinon c'est un module interne (script)
else
llMessageLinked( LINK_SET, IC_GLOBAL_LAUNCH_MODULE, gCurModInv, llDetectedKey(ii) );
jump outwtrgt;
}
++ii;
}
@outwtrgt;
}
}
no_sensor() {
// il y a personne dans les 96 mètres
llOwnerSay( "Il n'y a personne dans les 96 mètres" );
}
}
Votre système est désormais prêt à recevoir des modules ! Des modules sont des objets ou des scripts qui possèdent une commande (en chat) pour les lancer sur un ou plusieurs avatars.
Nous allons créer notre follower avec comme commande
follow
Création du follower
Commencez par créer un cylindre avec comme taille
1.3 en
X,
1.3 en
Y et
4.0 en
Z.
Mettez-lui une couleur avec un peu de transparence et surtout, mettez-le
Phantom .
Créez un script dans votre cylindre que vous nommerez :
Boot et placez ce code :
//----------------------------------------------------------------------------------
// EXTERNAL FUNCTIONS
//----------------------------------------------------------------------------------
//----------------------------------
// OWN EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------
// SCRIPTS EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------
// GLOBAL EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------------------------------------------------------
// LIBRARY FUNCTIONS
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// SCRIPT FUNCTIONS
//----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
// CONSTANTS
//--------------------------------------------------------------------------------------
//----------------------------------------------
// INTERNAL COMM
//----------------------------------------------
//----------------
// ME
//----------------
//----------------
//SCRIPT ADRESSES
//----------------
//----------------
// GLOBAL
//----------------
integer IC_GLOBAL_TARGET_KEY = -1877491457;
//----------------------------------------------
// UTIL
//----------------------------------------------
string PRIM_SEP = "|";
string SUB_SEP = ":";
//----------------------------------------------
// CHANNEL END
//----------------------------------------------
// channel pour stopper les modules (objets)
integer CHANNEL_END = -1584311041;
//--------------------------------------------------------------------------------------
// GLOBAL VARIABLES
//--------------------------------------------------------------------------------------
//----------------------------------------------
// OWNER INFOS
//----------------------------------------------
key gOwnerKey;
//----------------------------------------------
// LISTEN
//----------------------------------------------
integer gListenTargetID;
integer gChannelTarget;
default
{
on_rez( integer channel ) {
// on active le module seulement si il a pas un channel 0
if( channel != 0 ) {
// on sauvegarde la key du owner
gOwnerKey = llGetOwner();
// on sauvegarde le canal donné
gChannelTarget = channel;
// on écoute sur le canal donné (pour reçevoir la key)
gListenTargetID = llListen( channel, "", NULL_KEY, "" );
// on écoute sur le canal "end"
llListen( CHANNEL_END, "", NULL_KEY, "die" );
// on demande la key de l'avatar ciblé
llRegionSay( channel, "get" );
}
}
listen( integer chan, string name, key id, string msg ) {
// si le owner du message n'est pas owner, on refuse d'analyser son message
if( llGetOwnerKey(id) != gOwnerKey )
return;
// on reçoit la key de l'avatar
if( chan == gChannelTarget ) {
// on envoit la key aux autres scripts
llMessageLinked( LINK_SET, IC_GLOBAL_TARGET_KEY, "", (key)msg );
// on désactive l'écoute sur ce canal car il devient inutile
llListenRemove( gListenTargetID );
}
// on reçoit sur le canal "end", on supprime le module
else if( chan == CHANNEL_END )
llDie();
}
}
Il permet le transfert de données entre votre HUD et le follower (pour obtenir la key de la cible par exemple)
Créez un second script nommé
Follower avec comme code :
//----------------------------------------------------------------------------------
// EXTERNAL FUNCTIONS
//----------------------------------------------------------------------------------
//----------------------------------
// OWN EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------
// SCRIPTS EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------
// GLOBAL EXTERNAL FUNCTIONS
//----------------------------------
//----------------------------------------------------------------------------------
// LIBRARY FUNCTIONS
//----------------------------------------------------------------------------------
safe_posJump(vector target_pos)
{
// An alternative to the warpPos trick without all the overhead.
// Trickery discovered by Uchi Desmoulins and Gonta Maltz. More exact value provided by Fake Fitzgerald. Safe movement modification provided by Alias Turbo.
vector start_pos = llGetPos();
llSetPrimitiveParams([PRIM_POSITION, <1.304382E+19, 1.304382E+19, 0.0>, PRIM_POSITION, target_pos, PRIM_POSITION, start_pos, PRIM_POSITION, target_pos]);
}
//----------------------------------------------------------------------------------
// SCRIPT FUNCTIONS
//----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
// CONSTANTS
//--------------------------------------------------------------------------------------
//----------------------------------------------
// INTERNAL COMM
//----------------------------------------------
//----------------
// ME
//----------------
//----------------
//SCRIPT ADRESSES
//----------------
//----------------
// GLOBAL
//----------------
integer IC_GLOBAL_TARGET_KEY = -1877491457;
//----------------------------------------------
// UTIL
//----------------------------------------------
string PRIM_SEP = "|";
string SUB_SEP = ":";
//--------------------------------------------------------------------------------------
// GLOBAL VARIABLES
//--------------------------------------------------------------------------------------
//----------------------------------------------
// TARGET INFOS
//----------------------------------------------
key gTargetKey;
default
{
link_message( integer link, integer address, string data, key data2 ) {
// on reçoit la key de l'avatar
if( address == IC_GLOBAL_TARGET_KEY ) {
// on l'enregistre
gTargetKey = data2;
// on active le timer
llSetTimerEvent( 1.0 );
}
}
timer() {
// si l'avatar n'est plus dans la sim, on supprime le follower
if( llGetAgentSize(gTargetKey) == ZERO_VECTOR )
llDie();
// on sauvegarde les infos de l'avatar dans une list (ça peut etre la position, la rotation etc)
// dans notre cas on a seulement besoin de sa position
list details = llGetObjectDetails( gTargetKey, [OBJECT_POS] );
// on utilise PosJump pour bouger le follower
// qui permet d'aller à des distances illimitées
// et est beaucoup plus rapide que warpPos
safe_posJump( llList2Vector(details, 0) );
}
}
Maintenant que notre follower possède ses scripts propres à son fonctionnement, il nous faut l'installer dans notre HUD.
Les paramètres d'un module se placent dans
le nom de l'objet . Voici la convention à respecter :
M\nom_commande_de_chat\<0.0, 0.0, 9.0>\<0.0, 0.0, 0.0, 1.0>
Les
\ sont des séparateurs entre chaque paramètres.
Les modules commencent tous avec un
M\
nom_commande_de_chat : ici on place le nom de la commande pour lancer notre module
<0.0, 0.0, 9.0> : ce paramètre est un vecteur pour le rez (
position locale ). Pour ce vecteur il s'agit d'une position à 9 mètres au dessus de vous.
<0.0, 0.0, 0.0, 1.0> : la rotation qu'aura notre module au rez. Pour une rotation nulle on met : <0.0, 0.0, 0.0, 1.0> ou encore : <0, 0, 0, 1>.
Nous allons donc nommer notre cylindre avec comme nom :
M\follow\<0,0,9>\<0,0,0,1>
Prenez une copie et placez l'objet dans votre HUD. Vous devriez avoir une confirmation qu'il s'est bien installé :
module inst basic: Il y a 1 nouveau(x) module(s) installé(s). Total = 1
Pour lancer le follower il faut utiliser sa commande en chat (
follow dans notre cas ) avec un ou plusieurs noms partiels d'avatars. Exemple si c'était pour le lancer sur Philip Linden (je ne vous le conseil pas
) il faut taper en chat :
ou encore simplement :
Un autre exemple pour le lancer sur moi et 2 fois sur philip linden :
follow philip philip blackshade
ou encore
Pour l'arrêter il faut utiliser la commande end en chat.
Améliorations
Le système est très basique mais vous pouvez l'améliorer comme vous le souhaitez, quelques exemples :
- Utiliser un Simscaner à la place d'un sensor de 96 mètres grâce notamment au viewer Emerald qui envoie les keys des avatars présents sur la sim à l'aide de votre avatar sur le canal
-777777777.
- Faire un système de dialog pour choisir sa cible, un très bon exemple le script de bestmomo ici :
https://forums.jeuxonline.info/showthread.php?t=1051053
- Faire un système de dialog pour choisir son module à lancer (pareil que l'exemple précèdent ).
Si vous avez des suggestions ou vous ne comprenez pas quelque chose, n'hésitez pas à demander.