Peuplement des zones et charge memoire

Répondre
Partager Rechercher
Dans le module sur lequel je travail, nous avons mis en place un système de peuplement par le biais de drapeaux qui permet de créer les créatures, les items et les placeables en fonction de l'heure d'entrée du premier PJ dans la zone. Bien entendu les PNJs sont détruits quand il n'y a plus de joueur dans la zone. Ce système fonctionne bien mais je me demande si il n'est pas trop gourmand compare a un peuplement fixe.

Avez vous une idée de la charge de gestion des PNJs quand ils se trouvent dans des zones ou il n'y a aucun joueur ?

Quels systèmes de peuplement utilisez vous ?

Ou plus simplement existe t il des limites quand aux nombres de PNJs a placer sur un serveur sachant que la machine est assez puissante.
Bien a mon humble avis, il est plus interessant de créer dynamiquement les pnj a l'arrivée des personnages joueurs, encore que cela dépende également de ton module.

Par exemple, les zones avec beaucoup de passage peuvent être fait en pnj statiques, et les zones peu visitées peuvent avoir des pnj qui disparraisse en même temps que les joueurs.

Pour l'instant mon module fonctionne comme ca, mais il est actuellement d'une taille trop peu élevé pour en constater les résultats....
Personnellement, je n'aime pas les systèmes de vidage de zone parce qu'ils provoquent un sentiment de frustration ou même, des fois, des aberrations. L'enchaînement logique est rompu dans les actions:
"on se fait déchirer, on quitte une zone, on revient, chouette, y a plus personne !"
J'aime pas !
Du coup, j'ai essayé un système de vidange reporté, mais je me suis dit, avec ça, je vais surcharger à mort les calculs puisque je vais délayer le traitement à chaque fois qu'un PJ sort d'une zone.

Finalement, je me suis dit un truc basique: tous les scripts OHB des créatures ont, depuis la 1.30, je crois, une toute petite ligne à l'entrée:

Code PHP:

if (GetAILevel() == AI_LEVEL_VERY_LOW) return; 

Et bien voilà le truc ! Je positionne un flag en entrée de zone, et le vire en sortie. Si le flag est nul, il n'y a pas de PJ, et je bascule toutes les créatures présentes en AI_LEVEL_VERY_LOW. Et inversément, je bascule l'AI en entrée. Du coup, j'économise tous les OHB non actifs, les CreateObject() et les DestroyObject().
D'ailleurs, j'en suis à me demander (parce que je n'ai pas vérifié) si par défaut, les AI ne sont pas en VERY_LOW s'il n'y a personne dans leur champ de vision. Du coup, j'économiserais même les scripts d'entrée et sortie de zone.

CreateObject() est l'une des fonctions les plus gourmandes de NWN surtout pour les créatures. (hormis les accès aux tables, bien sûr et pire, aux 2dA).

On gratte où on peut
Le module approche actuellement des trois cents zones. Donc ça commence a être raisonnable. C'est sur que la fréquentation des zones est très variée aussi une approche lie a la fréquentation peu être une bonne idée, je vais me plonger dessus.

Je pensais aussi a deux politiques de peuplement selon que le PNJ a un rôle dans le module ou que c'est juste pour rendre la zone vivante.

Je suis intéresse par le travail sur l'IA des PNJs du projet Nemetic Toolkit. Helas je ne sais pas trop le coût de ce système. Le problème des systèmes de peuplement dynamique est de donner l'impression que le monde évolue d'une fois pour l'autre alors qu'il est uniquement dépendant des entrées et sorties des joueurs dans les zones.
très intéressant le coup du AI_LEVEL_VERY_LOW, je connaissais po

seulement, je doute que ca réduise a néant les trucs élémentaires hardcodés, tels que la perception (scan de toutes les objets de la zone pour savoir si y'en a pas un qui est arrivée dans le champ de perception).
Enfin c'est vrai a coté de ca que le CreateObject...... AIE ! >_<

il faudrait bencher pour pouvoir affirmer, mais.... je déteste bencher :P

encore une fois je pense que la question du "où" peut se poser, la cité cachée des elfes que les joueurs ont des chances proches de zero de découvrir..... c'est p'tête pas la peine de laisser les elfes vivre leur vie dans leur coin :P
J'ai testé involontairement l'AI_LEVEL_VERY_LOW parce qu'à l'origine, j'ai mis ça dans les OnSpawn de toutes mes créatures. Je peux te dire que toutes les activités du modules passent avant.
Je ne m'étais pas rendu compte du truc, et je me suis demandé si le PC avait planté... J'avais un PNJ avec un dialogue que je testais, et il mettait 3 plombes à m'afficher les réponses. Avant de réaliser que c'était dû à mes tests d'AI, j'ai mis un moment
Les monstres ne bougent même plus. Ils ne ripostent pas quand tu leur tapes dessus, etc...
La seule chose qu'ils font, c'est se tourner vers toi.
Citation :
La seule chose qu'ils font, c'est se tourner vers toi.
voui donc ils perçoivent quand même les objets, mais peut être est-ce désactivé lorsque les joueurs quittent la zone.

cela dit, plus tu m'en parle plus je trouve ca intéressant
Je sens que je vais virer le système de drapeau pour tous les PNJs important tout en se limitant au zone les plus fréquentées comme par exemple la ville principale. Pour les zones de campagne le peuplement par drapeau permet d'avoir des zones toujours dangereuse
Franchement, à moins de demander à Dieu le Père lui-même (GZ pour ne pas le nommer ), je ne sais pas comment tester ce système. Si ça se trouve, l'AI est positionnée à VERY_LOW quand la zone est vide, parce que je SUPPOSE qu'ils n'ont pas mis cette ligne que pour faire joli, et cela était dans la droite ligne de la recherche d'économie faite à l'époque avant la 1.30.
Mais reste le doute: si c'est le cas, on ne peut pas vérifier car même en MD elle peut peut-être se réactiver. Aucun moyen de savoir...

Hmmm, tout en écrivant ça, j'échafaude déjà un plan de test
La suite dans notre prochain épisode
bah.... il suffirait de faire rentrer un pnj dans le champ de perception d'un autre pnj et vérifier si celui si réagit a une fonction nwscript qui teste la perception

euh.... a toi l'honneur
On peut toujours coller 200 PNJs dans une zone et faire des actions dans une autre pour voir si ça charge le système pour rien... En fait un module avec deux zones dont une avec plein de PNJ suffit pour avoir tout du moins une idée non ? Le cpu et la mémoire parleront d'eux mêmes

top sous linux ctrl+alt+suppr pour les autres.
Bon, ben le test est fait et concluant !

Deux tests en fait: un en mode autonome sans passer par le serveur et un avec le mod de test lancé par NWSERVER.

Ils beuglent comme des veaux dans la zone où je suis, et pas ceux dans les autres zones. Donc, ils sont "décérébrés" quand je quitte la zone. Par conséquent, Dalfy, merci d'avoir posé la question, je vais encore faire des économies

PS: je n'ai pas poussé pour voir s'il y a un temps de réponse avant qu'ils ne se mettent à crier ailleurs, mais déjà ça, ça me va bien. Oui oui, ça veut bien dire que de despawner ne sert plus à grand chose...
voui, le test est concluant

je risque également de changer ma philosophie de gestion des pnj (dommage tout était déjà >_< )

par contre, il faudra se méfier si jamais il était necessaire de prévoir des evenements dans lequels les joueurs ne sont pas directement impliqués. On sait jamais, ca peut arriver ^^
De mémoire, il me semble que GetAiLevel renvoie automatiquement AI_LEVEL_VERY_LOW lorsqu'aucun PC n'est dans la zone du NPC appelant cette fonction.

Ensuite, il y a juste à regarder le "nw_c2_default1" (OHB par défaut) pour voir que dès la première ligne si GetAiLevel == AI_LEVEL_VERY_LOW alors la créature ne fait rien.

Concernant la question initiale, j'en suis à mon 3ème système de spawn qui ne me convient pas totalement non plus. J'ai oscillé entre le tout-statique et le tout-dynamique, et je pense que le prochain que je développerai sera vraiment entre les deux :
- Quelques PC visités souvent statiques, les marchands par exemple.
- La plupart des autres dynamiques. Ils se détruisent après un certain temps où la zone n'est plus visitée.
Gc
Citation :
Publié par Azmathiel
Personnellement, je n'aime pas les systèmes de vidage de zone parce qu'ils provoquent un sentiment de frustration ou même, des fois, des aberrations. L'enchaînement logique est rompu dans les actions:
"on se fait déchirer, on quitte une zone, on revient, chouette, y a plus personne !"
J'aime pas !
Du coup, j'ai essayé un système de vidange reporté, mais je me suis dit, avec ça, je vais surcharger à mort les calculs puisque je vais délayer le traitement à chaque fois qu'un PJ sort d'une zone.
Bonjour à tous, je viens soumettre à votre sagacité un système de nettoyage ou "éboueur" basé sur le concept de Garbage Collector, il est assez consistant à mettre en place, mais me donne satisfaction depuis +9 mois sur le module sur lequel notre équipe travaille.
Je tiens à remercier quelques autres concepteurs desquels je me suis inspiré pour les fonctions : en particulier, je suis parti du module du bouchon lyonnais. Egalement, je tiens à citer mon collègue de bureau, expert JVM, absolument pas joueur, pour l'excellente explication du concept.
Tout d'abord, une bibliothèque de fonctions :

Code:
//:: Lylver 2005-01-12
//:: Include File for Area Cleaning
//:: inspire du bouchon lyonnais
//:: ly_lib_area.nss
int CheckPJInArea(object oArea) {
object oObject = GetFirstObjectInArea(oArea) ;
int nCount = 0 ;
while (GetIsObjectValid(oObject)) {
if (GetIsPC(oObject)) {
nCount++ ;
break ;
}
oObject = GetNextObjectInArea(oArea) ;
}
return nCount ;
}
// * returns true if there is no player in the area
// * has to be ran from an object
// source xp2 module modifie
int NoPlayerInArea(object oArea) {
object oFirst = GetFirstObjectInArea(oArea) ;
if( GetIsPC(oFirst) || GetIsDM(oFirst) || GetIsDMPossessed(oFirst) ) return FALSE ;
// pour le cas ou le seul objet est un PC
if( !GetIsObjectValid(oFirst) ){
SendMessageToAllDMs("Erreur : no valid object for GetFirstObjectInArea") ;
return TRUE;
}
object oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oFirst);
return !GetIsObjectValid(oPC); // * no player in area
}
int CleanBodyBag(object oArea, int nMax = 50) {
int nCost = 0 ;
object oItem ;
object oBag = GetFirstObjectInArea(oArea) ;
while (GetIsObjectValid(oBag)) {
if( (GetObjectType(oBag) == OBJECT_TYPE_PLACEABLE) && (GetTag(oBag) == "BodyBag") ){
nCost++ ; // incremente le nombre des objets effectivement traites
oItem = GetFirstItemInInventory(oBag) ;
while (oItem != OBJECT_INVALID) {
nCost++ ;
DestroyObject(oItem) ;
oItem = GetNextItemInInventory(oBag) ;
} 
// DestroyObject(oBag,0.1) ; pas besoin : le moteur le fait, mais ou passent les BodyBags ?
} 
oBag = GetNextObjectInArea(oArea) ;
if (nCost > nMax) { nCost = -1 * nCost ; break ;}
} 
return nCost ;
}
int CleanPNJ(object oArea, int nMax = 50) {
object oPnj = GetFirstObjectInArea(oArea) ;
int nCost = 0 ;
while (GetIsObjectValid(oPnj)) {
if( GetIsEncounterCreature(oPnj) ){
nCost++ ;
DestroyObject(oPnj) ;
} 
oPnj = GetNextObjectInArea(oArea) ;
if (nCost > nMax) { nCost = -1 * nCost ; break ;}
} 
return nCost ;
}
int CleanItem(object oArea, int nMax = 50) {
object oItem = GetFirstObjectInArea(oArea) ;
object oLostItem = GetObjectByTag("CHEST_LOST_ITEM") ;
int nCost = 0, bChest = GetIsObjectValid(oLostItem) ;
while (GetIsObjectValid(oItem)) {
if( GetObjectType(oItem) == OBJECT_TYPE_ITEM ){
nCost++ ;
if( bChest ) CopyItem(oItem,oLostItem) ;
DestroyObject(oItem) ;
} 
oItem = GetNextObjectInArea(oArea) ;
if (nCost > nMax) { nCost = -1 * nCost ; break ;}
} 
return nCost ;
}
// void main() {}
Le script suivant est à adapter/inclure sur le OnModuleLoad
Code:
void main()
{
// begin add-on Lylver 2005-05-21
SignalEvent(OBJECT_SELF, EventUserDefined(300));
// evenement n°300 choisi pour le GC
// end add-Lylver
}
Le script suivant est à adapter/inclure sur le OnUserDefined du module
Code:
/*
Incorporate this code into Module::OnUserDefined
*/
// Update : lylver 07/04/2004
// Nom : ly_mod_userdef
//:: Update Lylver 2005-01-12
//:: Garbage Collector
//:: + Fix du switch incorrect
void main() {
switch (GetUserDefinedEventNumber()) {
case 300:{
ExecuteScript("ly_mod_area_gc",OBJECT_SELF) ;
break ;
}
} /* switch */
}
Maintenant le script principal
Code:
//:: Lylver 2005-04-28
//:: ly_mod_area_gc.nss
//:: nouvelle fonction de test PJ dans zone
//:: GC main non affecte
//::////////////////////////////////////
//:: Lylver 2005-03-08
//:: Copie des items perdus (abandonne)
//:: dans le coffre des objets trouves
//::////////////////////////////////////
//:: Lylver 2005-01-12
//:: Module Garbage Collector
#include "ly_lib_area"
void main() {
object oMod = GetModule() ;
int nBodyBag=0,nPNJ=0,nItemLost=0,bEmptyArea ;
int CleanP = GetLocalInt(oMod,"CLEANP") ;
string sArea = "" ;
object oArea = GetLocalObject(oMod,"CLEANQ") ;
// SendMessageToAllDMs("GC : Hello ! I'm Here !") ;
if (oArea != OBJECT_INVALID) { // j'ai une zone a traiter
sArea = GetTag(oArea) ; // un run de nettoyage courant
/* code */
SendMessageToAllDMs("GC : operating on Area : " + GetName(oArea)) ;
WriteTimestampedLogEntry("GC : operating on Area : " + GetName(oArea)) ;
bEmptyArea = NoPlayerInArea(oArea) ;
if( GetLocalInt(OBJECT_SELF,"PC_HERE") == 1 ){
SendMessageToAllDMs("Warning GC : PC_HERE est a etat 1");
WriteTimestampedLogEntry("Warning GC : PC_HERE est a etat 1");
}
// default cleaning : 50 items in one pass, return negative for 2nd check later
// CleanBodyBag(object oArea, int nMax = 50)
if( bEmptyArea ){ // si la zone est effectivement vide on nettoie
nBodyBag = CleanBodyBag(oArea) ;
nPNJ = CleanPNJ(oArea) ;
nItemLost = CleanItem(oArea) ;
} else { // mais si quelqu'un est la, on abandonne et on purge la queue
DeleteLocalInt(oMod,"CLEANQ" + sArea) ;
DeleteLocalObject(oMod,"CLEANQ") ;
DelayCommand(6.0,SignalEvent(OBJECT_SELF, EventUserDefined(300))) ; // pas trop vite
return ; // exit et recommence
}
// test si c'est fini (pas de code retour negatif
if( (nBodyBag >= 0) && (nPNJ >= 0) && (nItemLost >=0) ){ // oui on prend la requete suivante
DeleteLocalInt(oMod,"CLEANQ" + sArea) ;
DeleteLocalObject(oMod,"CLEANQ") ;
DelayCommand(6.0,SignalEvent(OBJECT_SELF, EventUserDefined(300))) ; // qu'on examine
return ; // exit et recommence
} else { // on termine rapidement
SetLocalInt(oMod,"CLEANQ" + sArea, 29 + GetLocalInt(oMod,"CLEANQ" + sArea)) ; // passage + rapide
}
} else { // pas de zone en cours, alors on examine les requetes
oArea = GetLocalObject(oMod,"CLEANR") ;
if (oArea != OBJECT_INVALID) { // trouve qq chose a faire
// SendMessageToAllDMs("GC : switching to Area : " + GetName(oArea)) ;
sArea = GetTag(oArea) ; // gere la file d'attente
SetLocalObject(oMod,"CLEANQ",oArea) ; // passe en clean courant
SetLocalInt(oMod,"CLEANQ" + sArea, CleanP) ; // delai suivant
// dependant de la plus longue des autres attentes en cours
DeleteLocalInt(oMod,"CLEANR" + sArea) ;// libere le semaphore
DeleteLocalObject(oMod,"CLEANR") ;
} else { // meme si pas prise, il peut y avoir une demande de plus faible priorite
// en boucle, on en tient compte en retestant plus vite apres reset priorite
sArea = "" ;
if( CleanP > 0 ){
SetLocalInt(oMod,"CLEANQ", CleanP) ; // delai suivant
DeleteLocalInt(oMod,"CLEANP") ; // reset priorite globale
}
}
// else SendMessageToAllDMs("GC : Nothing to Do") ;
}
// on ne nettoie pas immediatement mais en fonction de la priorite de la requete
// pour conserver une latence avant destruction des monstres 174s mini + 6s du a la purge
float fDelay = 180.0 - 6.0 * IntToFloat((1+GetLocalInt(oMod,"CLEANQ"+sArea))) ; // 180 secondes maxi
if (fDelay < 6.1) fDelay = 6.1 ; // pas trop vite pour la sortie de boucle des OnExit
DelayCommand(fDelay, SignalEvent(OBJECT_SELF, EventUserDefined(300)));
}
L'activation du nettoyage par l'évênement OnExit d'une zone
Code:
//:: Lylver 2005-04-28
//:: ly_onexit_area.nss
//:: nouvelle fonction de test PJ dans zone
//:: Lylver 2005-01-15
//:: OnExit Area
//:: GC 
//:: Lylver 2005-02-07
//:: update 
void main() {
object oPC = GetExitingObject() ;
string sArea = GetTag(OBJECT_SELF) ;
int bInteractive = GetIsPC(oPC) || GetIsDM(oPC) || GetIsDMPossessed(oPC) ;
if( bInteractive ){ ExecuteScript("ly_onexit_loop",OBJECT_SELF); } // sous-fonction pour le GC
else { // traitement des PNJs qui sortent de leur zone a la poursuite des PJs ou
// par ceux qui patrouillent hors de vue des joueurs : duree de vie 180 secondes
if( GetIsEncounterCreature(oPC) && !GetIsObjectValid(GetMaster(oPC)) ){
/* if( NoPlayerInArea(OBJECT_SELF) ){
SetLocalInt(oPC,"DESTROY",180) ; // au cas ou la difference serait necessaire
} else {
SetLocalInt(oPC,"DESTROY",180) ;
} */
SetLocalInt(oPC,"DESTROY",180) ;
WriteTimestampedLogEntry("Zone "+sArea+" OnExit : "+GetName(oPC)+" marked for destroy") ;
}
return ;
}
}
Un script spécifique que j'ai détaché (j'ai parfois d'autres fonctions sur le OnExit)
Code:
//:: Lylver 2005-04-28
//:: ly_onexit_loop.nss
//:: nouvelle fonction de test PJ dans zone
//:: Lylver 2005-01-15
//:: OnExit Area
//:: Activation du GC : boucle d'attachement
#include "ly_lib_area"
void main()
{
object oMod = GetModule() ;
string sArea = GetTag(OBJECT_SELF) ;
int nPJ;
int CleanQ = GetLocalInt(oMod,"CLEANQ" + sArea) ;
int CleanR = GetLocalInt(oMod,"CLEANR" + sArea) ;
int CleanP = GetLocalInt(oMod,"CLEANP") ;
if( CleanQ > 0 ){ // GC should be here
if( GetLocalObject(oMod,"CLEANQ") == OBJECT_SELF ){ // GC ici : suppression de demande surnumeraire
DeleteLocalInt(oMod,"CLEANR" + sArea) ; // reset a neutre
if( GetLocalObject(oMod,"CLEANR") == OBJECT_SELF ){
DeleteLocalObject(oMod,"CLEANR") ; // free GC [espere operation atomique]
}
WriteTimestampedLogEntry("Warning : GC OnExitLoop : "+sArea+" GC here : cancelling outnumbered request") ;
} else { // cas d'erreur
DeleteLocalInt(oMod,"CLEANQ" + sArea) ;
WriteTimestampedLogEntry("Warning : GC OnExitLoop : "+sArea+" found area counter without object, resetting.") ;
}
return ; // GC is busy on our zone or repaired mismatch
}
if( CleanR < 0 ){ // traite l'annulation demandee
DeleteLocalInt(oMod,"CLEANR" + sArea) ; // reset a neutre
if( GetLocalObject(oMod,"CLEANR") == OBJECT_SELF ){
DeleteLocalObject(oMod,"CLEANR") ; // free GC [espere operation atomique]
}
// SendMessageToPC(GetExitingObject(), "OnExit : GC called but ordered stop : " + sArea) ;
CleanR = 0 ; // poursuit, test PJ plus loin
}
if( CleanR == 0 ){ // etat standard
// nPJ = CheckPJInArea(OBJECT_SELF) ; fonction NoPlayerInArea 5* plus rapide
if( NoPlayerInArea(OBJECT_SELF) ){ // controle
// raz variable de statut
if( GetLocalInt(OBJECT_SELF,"PC_HERE") == 1) DeleteLocalInt(OBJECT_SELF,"PC_HERE") ;
// if( nPJ > 0 ) WriteTimestampedLogEntry("Erreur : GC OnExitLoop Resultat CheckPJ et NoPlayer different");
} else {
WriteTimestampedLogEntry("Warning : GC OnExitLoop : PJ here, no hooking") ;
return ; // pas de hooking : il y a du monde
}
}
if( CleanR > 0 ){ // je suis deja passe ou alors un deuxieme PJ vient de sortir
if( NoPlayerInArea(OBJECT_SELF) ){ // controle
// raz variable de statut
if( GetLocalInt(OBJECT_SELF,"PC_HERE") == 1) DeleteLocalInt(OBJECT_SELF,"PC_HERE") ;
} else { // stop ! : quelqu'un est la, pas besoin de nettoyer
DeleteLocalInt(oMod,"CLEANR" + sArea) ; // reset a neutre
if( GetLocalObject(oMod,"CLEANR") == OBJECT_SELF ){
DeleteLocalObject(oMod,"CLEANR") ; // free GC [espere operation atomique]
}
WriteTimestampedLogEntry("Warning : GC OnExitLoop Stop Other Hooking") ;
return ; // stop other running hooking : il y a du monde
}
}
// Warning : overload
if( CleanR > 300 ) WriteTimestampedLogEntry("Warning : GC OnExitLoop Slow Hooking") ;
// hooking, ajout de prioritisation
if( (GetLocalObject(oMod,"CLEANR") == OBJECT_INVALID) && (CleanR >= CleanP) ){ // semaphore vide
SetLocalObject(oMod,"CLEANR",OBJECT_SELF) ; // signal GC [espere operation atomique]
WriteTimestampedLogEntry("Warning : GC OnExitLoop "+sArea+" Hooking") ;
return ; // yes I'm hooked !
}
if( GetLocalObject(oMod,"CLEANR") == OBJECT_SELF ){ // semaphore pris par moi-meme
WriteTimestampedLogEntry("Warning : GC OnExitLoop "+sArea+" already hooked") ;
return ; // yes I've been hooked !
}
// semaphore pas libre, increment compteur : accelere GC
SetLocalInt(oMod,"CLEANR" + sArea,++CleanR) ;
if( CleanR > CleanP ) SetLocalInt(oMod,"CLEANP",CleanR) ; // monte la priorite a soi-meme
// pour prendre une option
WriteTimestampedLogEntry("Warning : GC OnExitLoop "+sArea+" nb "+IntToString(CleanR)+" Hooking failed : recheck in 6 secondes") ;
// SendMessageToAllDMs("OnExit : GC called : " + sArea + IntToString(CleanR)) ;
DelayCommand(6.0,ExecuteScript("ly_onexit_loop",OBJECT_SELF)) ; // recheck in 6 seconds
}
Et enfin un deuxième script pour la zone à mettre sur le OnEnter
Code:
//:: Lylver 2005-04-28
//:: ly_onenter_area.nss
//:: nouvelle fonction de test PJ dans zone
//:: Lylver 2005-01-12
//:: Signal to Garbage Collector that PC are here
//:: Put OnEnter of Area
#include "ly_lib_area"
void main() {
object oPC = GetEnteringObject() ;
string sArea = GetTag(OBJECT_SELF) ;
int bInteractive = GetIsPC(oPC) || GetIsDM(oPC) || GetIsDMPossessed(oPC) ;
if( bInteractive ){
WriteTimestampedLogEntry("Zone "+sArea+" OnEnter : "+GetName(oPC)) ;
} else { // traitement des PNJs qui sortent de leur zone a la poursuite des PJs ou
// par ceux qui patrouillent hors de vue des joueurs : duree de vie 180 secondes
int nDestroy = GetLocalInt(oPC,"DESTROY") ;
if( nDestroy != 0 ){
if( NoPlayerInArea(OBJECT_SELF) ){
DestroyObject(oPC,IntToFloat(nDestroy)) ;
WriteTimestampedLogEntry("Zone "+sArea+" OnEnter, no PJs : "+GetName(oPC)+" will be destroyed") ;
}
}
return ;
}
object oMod = GetModule() ;
if( bInteractive ){
if( GetLocalObject(oMod,"CLEANQ") == OBJECT_SELF ){ // check GC is in Area
// SendMessageToAllDMs("OnEnter : GC operating : Ordering Stop : " + sArea) ;
// SendMessageToPC(GetEnteringObject(), "OnEnter : GC operating : Ordering Stop : " + sArea) ;
DeleteLocalObject(oMod,"CLEANQ") ; // stop GC on Area
DeleteLocalInt(oMod,"CLEANQ" + sArea) ; // counter also
}
if( GetLocalObject(oMod,"CLEANR") == OBJECT_SELF ){ // check GC called for Area
// SendMessageToAllDMs("OnEnter : GC called : cancelling " + sArea) ;
// SendMessageToPC(GetEnteringObject(), "OnEnter : GC called : cancelling " + sArea) ;
DeleteLocalObject(oMod,"CLEANR") ; // cancel GC call for Area
SetLocalInt(oMod,"CLEANR" + sArea,-1) ; // demande d'arret
// Note : -1 to reset zone counter done by ly_onexit_area, stoping hook-loop also
}
// variable de statut pour usage futur
if( GetLocalInt(OBJECT_SELF,"PC_HERE") == 0) SetLocalInt(OBJECT_SELF,"PC_HERE",1) ;
}
}
Si vous avez eu le courage de lire jusqu'ici voici le mode d'emploi
- créer ces scripts dans votre module : ly_lib_area, ly_mod_area_gc, ly_onenter_area, ly_onexit_area et ly_onexit_loop
- adapter/éventuellement créer des scripts pour les évênements OnModuleLoad/OnUserDefined du Module
- ajouter les scripts ly_onenter_area et ly_onexit_area sur les évênements OnEnter/Onexit des zones dont vous voulez gérer le nettoyage
Note : j'autorise les monstres à se déplacer entre plusieurs zones, GC gère ceux-ci.
Pour ceux qui veulent tester : j'ai repris ces scripts et éludé quelques lignes qui ajoute des fonctions autres, pour rester dans le cadre du nettoyeur. Ce système a été longuement vérifié (logs et nwnx2 pour les performances) mais il peut subsister quelques bugs/morceaux de code inutile, je suis preneur des remarques
Tu devrais pouvoir gagner un temps considérable en regroupant ces fonctions :

nBodyBag = CleanBodyBag(oArea) ;
nPNJ = CleanPNJ(oArea) ;
nItemLost = CleanItem(oArea) ;


Chacune des de ces 3 fonctions à une boucle while. La première explore les inventaires les deux suivantes explorent des types d'objets dans une zone. Tu les appelles les une à la suite des autres dans le script ly_mod_area_gc.nss.

Voila.
Répondre

Connectés sur ce fil

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