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 :
//:: 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
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
/*
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
//:: 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
//:: 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)
//:: 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
//:: 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