Générateur et Nettoyeur de Monstres.

Répondre
Partager Rechercher
Générateur et Nettoyeur de Monstres.

Voici le système que j'utilise sur le module de Beckrunes Section Persistant/RP (je fais ma pub au passage ) .

Le principal problème avec les déclencheurs est qu'ils sont très gourmand en ressources et qu'ils ralentissent le chargement des maps.
Plus le nombres de rencontres augmentent et plus le problème deviendra perceptible.

La solution consiste à n'utiliser aucun déclencheur et tous contrôler grâce à des scripts.

Pour que le système soit accessible au plus grand nombre je vais tacher d'expliquer le plus clairement possible la théorie.

1) Il faut savoir si quelqu'un est dans la zone.

2) S'il y a quelqu'un alors on génère des monstres.
On génère mais on fixe un nombre maximum de monstres par zone, sinon on pourra rapidement se retrouver avec une centaine de monstres (ce qui est pas génial non plus)

3) S'il n'y a plus personne dans la zone, on réinitialise la zone.
L'initialisation consiste à :
- Fermer toutes les portes (ce qui est un gros soucis)
On déclenche une tempo pour nettoyer la zone :
- des drops
- des monstres.

Maintenant la même chose en script :

Tout d'abord on va compter le nombre de joueurs dans chaque zone pour savoir si on génère des monstres ou pas.

Ce script sera placé sur le OnEnter de la zone (de toutes les zones du module)
Il incrément une variable "nbjoueurs" qui est propre à chaque zone.
Code PHP:

///////////////////////////////////////////////
//
//    Dimencia 13 fev 2008
//
//    Incremente une variable "nbjoueurs" pour
//    Chaque zone.
//
///////////////////////////////////////////////


void main (){
    
object oPC GetEnteringObject();
    if (
GetIsPC(oPC) == TRUE){    
        
object oZone GetArea(OBJECT_SELF);
        
int iNb_joueurs GetLocalInt(oZone"nbjoueurs") + 1;    
//        SendMessageToPC(oPC, "nb joueurs ds zone:"+IntToString(iNb_joueurs));
        
SetLocalInt(oZone"nbjoueurs",iNb_joueurs);
    }        

Maintenant de la même manière on va s'occuper de la sortie des joueurs
et décrémenter la variable de zone "nbjoueurs".
Lorsqu'il ni à plus de joueur dans la zone on lancera alors un script de nettoyage.

Ce script est placé dans le OnExit de la zone (de toutes les zones du module)
Code PHP:

///////////////////////////////////////////////
//
//    Dimencia 13 fev 2008
//
//    Decremente une variable "nbjoueurs" pour
//    Chaque zone.
//
///////////////////////////////////////////////
void main (){
object oPC GetExitingObject();
if (
GetIsPC(oPC) == TRUE){
    
object oZone GetArea(OBJECT_SELF);
    
int iNb_joueurs GetLocalInt(oZone"nbjoueurs") - 1;
//    SendMessageToPC(oPC, "nb joueurs ds zone:"+IntToString(iNb_joueurs));
    
if (iNb_joueurs 0) {    
        
SetLocalInt(oZone"nbjoueurs",iNb_joueurs);
        }
    else {
        
DeleteLocalInt(oZone"nbjoueurs");    
//        SendMessageToPC(oPC,"Demarrage du Nettoyeur dans "+ GetName(oZone));    
        
ExecuteScript("a_icn_nettoyeur"OBJECT_SELF);
        }
    }        

Passons maintenant au nettoyage. Ce script est une version modifié que Icien m'avait fourni pour le premier module de Beckrunes (neverwinter1).

comment fonctionne ce script?
- Il est lancé dès qu'une zone se retrouve avec "nbjoueurs" < 1 (Voir script du OnExit de zone)
- Dès le début on va refermer toute les portes ce qui en soit sera déjà un soucis de moins!
- Ensuite on lance une tempo. Ici 70 secondes, en dur dans le code, ce qui est un temps maximum pour charger une zone et laisse la possibilité au joueur de faire des aller et retour dans la zone sans que les monstres disparaissent de suite.
- Au bout des 70 secondes, on regarde s'il n'y a toujours aucun joueur dans la zone et on nettoies les drops et les monstres sinon, on abandonne le nettoyage.
Code PHP:

// nettoyeur de zones (drop, coffre, rencontres)
// a mettre dans le OnExit de la zone
// Icien decembre04
//
//    Dimencia
//     Controle de la zone avant nettoyage apres TEMPO secondes (fevrier 08)
//    Le script de nettoyage se lance apres TEMPO secondes
//    Ajout Fermeture des portes des le depart du dernier joueur (mars 08)
//
//
void init_map(object oSourceobject oZone);

void main() {
    
float TEMPO 70.0//temps avant nettoyage en secondes    
    
object oPC GetExitingObject();
    
object oCrea GetNearestCreature(CREATURE_TYPE_PLAYER_CHARPLAYER_CHAR_IS_PCoPC1);
    
object oZone OBJECT_SELF;
    
    
int iNb_joueurs GetLocalInt(oZone"nbjoueurs");
//    SendMessageToPC(oPC, "Le script de Nettoyage voit "+IntToString(iNb_joueurs)+" joueurs ds la zone : "+GetName(oZone));        

// Je retest mais normalment c'est zero au lancement de ce script
// Le script de nettoyage à Icien sera lancer au bout de TEMPO secondes        
    
if (iNb_joueurs 1) {
        
// On ferme toutes les portes    
        
object oObj GetFirstObjectInArea(oZone);
        
int iTypeiPortes 0;
         while (
GetIsObjectValid(oObj)) {
            
iType GetObjectType(oObj);
            if (
iType == OBJECT_TYPE_DOOR) {
                
iPortes++;
                 
AssignCommand(oObjActionCloseDoor(oObj));
                }
             
oObj GetNextObjectInArea(oZone);                    
            }
//        SendMessageToPC(oPC, "fermeture de "+IntToString(iPortes)+" porte(s)");        
//        SendMessageToPC(oPC,"Le script de Nettoyage se lancera dans : "+IntToString(FloatToInt(TEMPO))+" secondes");
        
DelayCommand(TEMPO init_map(oPCOBJECT_SELF));
        }
    else{
//        SendMessageToPC(oPC,"La zone n'est plus vide.");
        
}    
    }
    


void init_map(object oPC,object oZone) {

int iPnj 0;
int iDrop 0;
int iItem 0;
//SendMessageToPC(oPC,"Le processus de nettoyage de "+ GetName(oZone) + " commence");
int iNb_joueurs GetLocalInt(oZone"nbjoueurs");    
// Le script de nettoyage à Icien est lancé, TEMPO secondes    se sont écoulées
// Je regarde à nouveau si la zone est vide sinon, je ne nettoies pas    

    
if (iNb_joueurs 1) {
         
object oSource GetFirstObjectInArea(oZone);
         
object oCrea GetNearestCreature(CREATURE_TYPE_PLAYER_CHARPLAYER_CHAR_IS_PCoSource1);
         
object oObj;
         
object oItem;
         
int nN=1;
         
int iType;
        
         if (
oCrea == OBJECT_INVALID && !(GetIsPC(oSource))) {
             
oObj oSource;
             while (
GetIsObjectValid(oObj)) {
                 
iType GetObjectType(oObj);
                 if (
GetTag(oObj) == "BodyBag")//1 Supprime les Drops
                    
{
                     
oItem=GetFirstItemInInventory(oObj);
                     while(
GetIsObjectValid(oItem))
                         {
                          
DestroyObject(oItem);
                          
oItem=GetNextItemInInventory(oObj);
                          
iDrop++;
                         }
                     
DestroyObject(oObj);
                    }
                 else
                    {
                     
//iType = GetObjectType(oObj);
                     
switch (iType)
                        {
                         case 
OBJECT_TYPE_CREATURE://2 Supprime les spawns
                                  
AssignCommand(oObjClearAllActions()); //Ajout
                                  
AssignCommand(oObjDestroyObject(oObj1.0));
                                  
iPnj++;
                             break;
                             
                         case 
OBJECT_TYPE_ITEM://3 detruit les objets par terre
                             
AssignCommand(oObjDestroyObject(oObj0.0));
                             
iItem++;
                             break;
                             
                         default:
                             break;
                             
                        }
                    }
                 
nN++;
                 
oObj GetNearestObject(OBJECT_TYPE_ALLoSourcenN);
                }
//                SendMessageToPC(oPC,IntToString(iDrop)+" Drop(s) detruit(s)");
//                SendMessageToPC(oPC,IntToString(iPnj)+" Creature(s) detruite(s)");
//                SendMessageToPC(oPC,IntToString(iItem)+" Item(s) detruit(s)");    
//                 SendMessageToPC(oPC,"nettoyage de la Zone: "+ GetName(oZone) +" terminé");
            
}
            
DeleteLocalInt(oZone"init");
            
DeleteLocalInt(oZone"pop");
            
DeleteLocalInt(oZone"valrepop");
            
DeleteLocalInt(oZone"nbjoueurs");            
//            SendMessageToPC(oPC,"Zone prête à etre initialisée");            
        
}
    else {
//        SendMessageToPC(oPC,"Nettoyage abondonné pour la zone "+ GetName(oZone));
//        SendMessageToPC(oPC,"car un joueur vient d'y entrer");
        
}
    } 
Ca, c'était pour la partie générique, on compte maintenant les joueurs dans la zone et on est capable de tout nettoyer dès qu'il n'y a plus personne.

Mais pour le moment on a généré aucun monstre.

Maintenant on va devoir créer des scripts personnalisé pour chacune des zones, car chaque zone possèdera sa propre population. (oui je sais pour les débutant c'est un peu la galère.. mais faut passer par là ! )

Le script sera placé dans le OnHeartBeat (ou script récurrent) de la zone.

Il a toujours la même structure (voir script ci dessous) :

Tout d'abord il y a une fonction de création de monstres : void createMonstre(string sTag,string sPtSpawn)
Cette fonction a besoin de 2 paramètres :
- Le tag du monstre
- Le tag du point de Spawn

Ensuite Il existe encore une fois plusieurs variables qui vont pouvoir contrôler le fonctionnement et la population de la zone.
- Init (c'est un booleen, soit il est à 0 et l'init n'est pas faites, soit il est à 1 et la zone est initialisée)
- iMaxPop = la valeur maximum de monstres dans la zone
- iRepop = la valeur à laquelle la zone régénère des monstres
Code PHP:

//////////////////////////////////////////////////////////////
//
//    10/02/08 Dimencia (modele)
//    Zone Beckrunes (Exterieur)
//
//    iMaxPop         : Population maxi de la zone
//  iRepop             : Valeur de respawn
//
//  11/03/08 
//////////////////////////////////////////////////////////////

void createMonstre(string sTag,string sPtSpawn) {
    
location lSpawn GetLocation(GetObjectByTag(sPtSpawn,0));
    
object oMonstre CreateObject(OBJECT_TYPE_CREATURE,sTag,lSpawn,FALSE,"");
    
SetLocalInt(oMonstre,"X2_L_SPAWN_USE_AMBIENT",1);    
}

void main () {


//------------ Init params ---------------
//

// Valeur de population max de la zone
int iMaxPop 30;

// temps de respawn * 6
int iRepop 2;

//----------------------------------------

object oZone GetArea(OBJECT_SELF);
int iNb_joueurs GetLocalInt(oZone"nbjoueurs");
int iInit GetLocalInt(oZone"init");

string sPtSpawn;
string sTag;

//
// Un joueur est au moins present et la zone déjà initialisée: on genere des monstres
//
if(iNb_joueurs 0  && (iInit == )) {
    
// Y a des joueurs dans la zone
    // La zone est dejà init
    
int iPop GetLocalInt(oZone"pop");
    
int iRepop_Val GetLocalInt(oZone"valrepop");
    
iRepop_Val++;

    if((
iPop iMaxPop) && (iRepop_Val >= iRepop)) {    
        
// On defini les monstres ici
        // sTag ="le tag du monstre";
        // sPtSpawn = "le tag du point de spawn";
        
createMonstre(sTag,sPtSpawn);                                                        
        
iPop iPop 1;
        
SetLocalInt(oZone"pop"iPop);
        
iRepop_Val=0;                        
        }
    else {
        if(
iRepop_Val iRepop) {
            
iRepop_Val=0;
            }             
        }
    
SetLocalInt(oZone"valrepop",iRepop_Val);         
    }
    
//
// Le joueur vient d'entrer on creer tout les monstres initiaux
// Les boss, les marchands... ce qu'on veut.
//
if(iNb_joueurs 0  && (iInit != )) {
    
// Initialisation de la zone
    
SetLocalInt(oZone"init",1);
    
// Ici Spawn PNJ à l'initialisation de la zone
    
    
SetLocalInt(oZone"pop",0);                                        
    }    

Bon d'accord, pour les novices c'est vraiment la partie la plus lourde à comprendre.

Alors voici un exemple simple. On lance l'éditeur sans grommeler

Copier/coller les 3 premiers scripts dans un module.
Ouvrez une zone exterieure par exemple et posez dessus 5 points de passage (type Waypoint)
Ouvrez à present le contenu de la zone, les proprietes.
Cliquez les points de Spawn et taggez les pt_spawn1, pt_spawn2, pt_spawn3, pt_spawn4 et pt_spawn5
Noubliez pas d'affecter les scripts du OnEnter et OnExit à la zone !

Pour le script recurent on va mettre :
int iMaxPop = 30; (30 monstres maxi)
int iRepop = 2; (12 secondes pour repeupler la zone)
sTag = "c_rat"; (le tag du monstre)
iNum = Random( 5 )+1; Le choix au hasard du point de spawn
sPtSpawn = "pt_spawn"+IntToString(iNum); l'assemblage en "string" du tag du point de spawn

Noter egalement que dans la partie init de la zone, on va generer 4 rats des l'entree du joueur dans la zone
Il ne faut pas alors oublier de mettre la variable de "pop" à jour :

SetLocalInt(oZone, "pop",4);

Ce sera la population de départ
Code PHP:

//////////////////////////////////////////////////////////////
//
//    10/02/08 Dimencia (modele)
//    Zone Exemple
//
//    iMaxPop         : Population maxi de la zone
//  iRepop             : Valeur de respawn
//
//  21/09/08 
//////////////////////////////////////////////////////////////

void createMonstre(string sTag,string sPtSpawn) {
    
location lSpawn GetLocation(GetObjectByTag(sPtSpawn,0));
    
object oMonstre CreateObject(OBJECT_TYPE_CREATURE,sTag,lSpawn,FALSE,"");
    
SetLocalInt(oMonstre,"X2_L_SPAWN_USE_AMBIENT",1);    
}

void main () {


//------------ Init params ---------------
//

// Valeur de population max de la zone
int iMaxPop 30;

// temps de respawn * 6 secondes
int iRepop 2;

//----------------------------------------

object oZone GetArea(OBJECT_SELF);
int iNb_joueurs GetLocalInt(oZone"nbjoueurs");
int iInit GetLocalInt(oZone"init");

string sPtSpawn;
string sTag;
int iType;
int iNum;

//
// Un joueur est au moins present et la zone déjà initialisée: on genere des monstres
//

if(iNb_joueurs 0  && (iInit == )) {
    
// Y a des joueurs dans la zone
    // La zone est dejà init
    
int iPop GetLocalInt(oZone"pop");
    
int iRepop_Val GetLocalInt(oZone"valrepop");
    
iRepop_Val++;

    if((
iPop iMaxPop) && (iRepop_Val >= iRepop)) {
        
iNum Random(5)+1;
        
sPtSpawn "pt_spawn"+IntToString(iNum);
        
sTag "c_rat";    
        
createMonstre(sTag,sPtSpawn);                                                        
        
iPop iPop 1;
        
SetLocalInt(oZone"pop"iPop);
        
iRepop_Val=0;                        
        }
    else {
        if(
iRepop_Val iRepop) {
            
iRepop_Val=0;
            }             
        }
    
SetLocalInt(oZone"valrepop",iRepop_Val);         
    }


//
// Le joueur vient d'entrer on creer tout les monstres initiaux
// Les boss, les marchands... ce qu'on veut.
//
    
if(iNb_joueurs 0  && (iInit != )) {
    
// Initialisation de la zone
    
SetLocalInt(oZone"init",1);
    
// Ici Spawn PNJ
    
iNum 0;
    while (
iNum 4) {
        
iNum Random(5)+1;
        
sPtSpawn "pt_spawn"+IntToString(iNum);
        
sTag "c_rat";    
        
createMonstre(sTag,sPtSpawn);    
        
iNum++;
        }
    
    
SetLocalInt(oZone"pop",4);                                        
    }    


Ah il manque un truc encore !

oui si on tue un monstre, la variable "pop" ne sera pas mis à jour et au bout d'un certain temps, plus aucun monstre ne sera generé.

C'est totalement vrai, donc sur chaque monstre qui sera generer sur votre module vous allez devoir rajouter ce script dans l'evevement OnDeath (à la mort du monstre) :
Code PHP:

void main () {
object oZone GetArea(OBJECT_SELF);
int iPop GetLocalInt(oZone"pop");
iPop iPop 1;
if (
iPop 0) {
    
iPop 0;
    }
SetLocalInt(oZone"pop"iPop);
ExecuteScript("nw_c2_default7"OBJECT_SELF);

Ce script met à jour la variable "pop" de la zone et lance toujours le script par defaut de mort : nw_c2_default7


Ensuite avec ce système en utilisant des if, vous pouvez faire comme un MD, 50% de chances que ce soit un rat et 50% une chauve souris en lançant un D100 par exemple.
Il suffit de rajouter ce bout de code au niveau de l'affectation de sTag.
Ou bien encore limité la naissance des rats au nord de la zone selon la disposition de vos points de spawn.
Sur mon module, en testant l'heure, j'ai generé des loups garous la nuit et rien le jour.
Laisser votre imagination vous guider

Bon test à tous!

Un module de test est dispo à cette adresse : http://beckrunes.info/download.php#Gen_PNJ
Je me questionnais sur plusieurs choses, à l'époque où je tentais de créer ce genre de système, à propos du cas où un joueur se déconnecte alors qu'il était dans la zone concernée.

- L'événement de la zone "OnExit" est-il déclenché par un joueur qui se déconnecte ?
- Si c'est le cas, "GetIsPC(...)" rendra-t-il la valeur TRUE s'il est utilisé sur un joueur en train de se déconnecter ?

J'avais testé quelques machins qui n'avaient rien à voir, mais il m'a semblé aussi qu'un évènement ne se déclenchait pas lorsqu'un joueur changeait de zone. Je ne saurais pas dire lequel, j'ai un peu perdu beaucoup de choses après une malencontreuse suppression, dont mon module fétiche qui réagit à tous les évènements possibles et inimaginables.
Citation :
- L'événement de la zone "OnExit" est-il déclenché par un joueur qui se déconnecte ?
C'est une bonne question... et je te remercie de me l'avoir posée

Je n'ai pas fais ce test, il faudrait que je le vérifie avec une 2eme personne, pour te donner la réponse.

Mais j'avoue que ça m'intrigue en effet
Voici le test effectué :

J'ai lancé un server en local, je me suis connecté en joueur sur une zone.
-> Les monstres ont correctement été générés.
Je me déconnecte en tant que joueur

Je me reconnecte en DM sur la même zone.
-> La zone est bien vide

J'ai du toutefois modifier les scripts qui sont placés sur le OnEnter et Le OnExit de la zone, car avec le DM était également considéré comme contrôlé par un joueur par les scripts.
Cela faisait que le DM générait également les monstres de la zone et me faisait croire au début que l'événement de déconnexion du joueur ne provoquait pas aussi l'événement OnExit de la zone.

La modification effectuée pour la vérification est la suivante

Code PHP:

if (GetIsPC(oPC) == TRUE && GetIsDM(oPC) == FALSE){
...

Les codes placés ci-dessus restent donc théoriquement valable. (Au moins pour NWN2)

Étant donné que je suis pas un expert en scripting NWN2 (Je préfère mapper les extérieurs ) , je vais tacher de faire un petit module de test à downloader pour que chacun puisse se faire facilement une idée sur le système et pourquoi pas l'améliorer.
bonjour Dimencia,
avant tout un grand merci pour avoir partagé ton script.
J'ai malgré tout un petit soucis, étant surtout mappeur, je dois dire que le script reste pour moi un univers très ...comment dire ...flou!, d'ailleurs un grand merci aussi à Dayonara qui prend si bien soin de ses petites quiches préférées.

Donc , si je comprend bien, je dois fusionner deux scripts dans le OnExit de chaque zones; celui qui va décrémenter le nombre de joueurs encore présent dans la zone, et celui qui va lancer le nettoyage. Est ce que je me trompe?
Desolé de ne répondre que maintenant (J'ai démenagé et je n'ai pas encore récupéré le net à mon domicile...)

Il n'y a pas de fusion à faire

Dans le OnEnter de la zone tu dois avoir 1 er le script du post
Code PHP:

object oPC GetEnteringObject(); 



Dans le OnExit de la zone tu dois avoir : le 2eme script du post
Code PHP:

object oPC GetExitingObject(); 



C'est à la sortie du joueur que le script Onexit évalue si oui ou non il va lancer le script de nettoyage. Ce script n'est d'ailleurs rattaché à aucune zone directement. C'est le script de sorti qui le lancera ou pas.

Le mieux c'est de downloader la petite zone de demo que j'ai fait et de regarder ou sont placés les scripts :

http://beckrunes.info/download.php
Bonjours Dimencia , y'a pas de problème !
Je te remercie pour cette réponse qui m'éclaire déjà mieux, j'avais pas capté que le script du Onexit ferait appelle au script de nettoyage sans pour autant qu'il soit placé dans le Onexit .
ça m'apprendra à essayer de mieux déchiffrer un script , "excutescript" et pourtant bien marqué et veux bien dire ce qu'il veut dire ...
c'est dure d'être une quiche et de se la prendre en pleine face

Encore merci pour tout !
Répondre

Connectés sur ce fil

 
1 connecté (0 membre et 1 invité) Afficher la liste détaillée des connectés