[PHP] Gestion des fichiers 2DA

Répondre
Partager Rechercher
Code PHP:

//:://///////////////////////////////////////
//:: Classe : C2DA
//:://///////////////////////////////////////
//:: Par    : CdN
//:: Le     : 15 juin 2003
//:://///////////////////////////////////////
/*
    Classe de lecture et travail sur les
    fichiers 2DA de Neverwinter Nights.
*/
//:://///////////////////////////////////////
class C2DA {
    
//:: Variables
    
var $LineLen,            // Longueur des lignes
        
$HeadLine,            // Ligne des entêtes
        
$nNumCols,            // Nombre de colones
        
$arColsLen,            // Longueur des colones
        
$arColsHead,        // Entête des colones
        
$ar2DA,                // Tableau du contenu du 2DA
        
$nNumItems,            // Nombre d'entrées dans le 2DA
        
$sHeaders;            // Entêtes du fichier
    //:: Constructeur
    // $s2DAFile    => Fichier 2DA à travailler
    
function C2DA($s2DAFile) {
        
// Ouverture du 2DA
        
$fp fopen($s2DAFile"r");
        
// Lecture des entêtes
        
$this->sHeaders trim(fgets($fp8192));
        
// Lecture du vide
        
$maxlen strlen(fgets($fp8192)) + 1;
        
$this->LineLen $maxlen;
        
// Lecture des entêtes de colones
        
$cols fgets($fp,$maxlen);
        
$this->HeadLine $cols;
        
$tbl $this->spacexplode(ltrim($cols));
        
// Enregistrement du nombre de colones
        
$this->nNumCols count($tbl);
        
// Enregistrement des entêtes et des longueurs
        
for($i=0$i<count($tbl); $i++) {
            
$this->arColsHead[$i+1] = trim($tbl[$i]);
            
$this->arColsLen[$i+1] = strlen($tbl[$i]);
        }
        
// Calcul de la longueur de la colonne de numerotation
        
$numlen=0; while($cols[$numlen] == " "$numlen++;
        
$this->arColsHead[0] = "";
        
$this->arColsLen[0] = $numlen;
        
// Scan du tableau du 2DA
        
$i 0;
        while(!
feof($fp)) {
            
$line trim(fgets($fp,$maxlen));
            if(
strlen($line)>0$this->ar2DA[$i] = preg_split("/\s+/"$line);
            
$i++;
        }
        
//Fermeture du 2DA
        
fclose($fp);
    }
    
//:: Fonction : spacexplode
    // FONCTION PRIVEE : Fait un explode special sur une chaine.
    // $texte        => Texte a exploser
    
function spacexplode($texte) {
        for (
$i=0,$mod 0;$i<strlen($texte);$i++) if (ereg("[[:space:]]",substr($texte,$i,1))) { $mod=1$act.=substr($texte,$i,1); } else { if (!$mod$act.=substr($texte,$i,1); else { $mod=0$act.="\",\"".substr($texte,$i,1);    } }
         eval(
"\$tabl = array(\"".$act."\");");
         return 
$tabl;
    }
    
//:: Fonction : Display2DA
    // Affiche le fichier 2DA dans un tableau HTML.
    // $table_class    => Classe CSS de la balise <table>
    // $tr_class    => Classe CSS de la balise <tr>
    // $td_class    => Classe CSS de la balise <td>
    
function Display2DA($table_class ""$tr_class ""$td_class "") {
        
// Définition d'un buffer de retour vide
        
$rBuffer "";
        
// Début du tableau
        
$rBuffer .= "<table";
        if(
strlen($table_class)>0$rBuffer .= " class=\"".$table_class."\">"; else $rBuffer .= ">";
        
// Ajout des entêtes
        
$rBuffer .= "<tr";
        if(
strlen($tr_class)>0$rBuffer .= " class=\"".$tr_class."\">"; else $rBuffer .= ">";
        for(
$i=0;$i $this->nNumCols;$i++) {
            
$rBuffer .= "<td";
            if(
strlen($td_class)>0$rBuffer .= " class=\"".$td_class."\">"; else $rBuffer .= ">";
            
$rBuffer .= $this->arColsHead[$i];
            
$rBuffer .= "</td>";
        }
        
$rBuffer .= "</tr>";
        
// Ajout des lignes
        
for($i=0;$i<count($this->ar2DA);$i++) {
            
$rBuffer .= "<tr";
            if(
strlen($tr_class)>0$rBuffer .= " class=\"".$tr_class."\">"; else $rBuffer .= ">";
            for(
$j=0;$j<$this->nNumCols;$j++) {
                
$rBuffer .= "<td";
                if(
strlen($td_class)>0$rBuffer .= " class=\"".$td_class."\">"; else $rBuffer .= ">";
                
$rBuffer .= $this->ar2DA[$i][$j];
                
$rBuffer .= "</td>";
            }
            
$rBuffer .= "</tr>";
        }
        
// Fin du tableau
        
$rBuffer .= "</table>";
        echo 
$rBuffer;
    }
    
//:: Fonction : Get2DAString
    // Agit comme la fonction Get2DAString de NWN, sans passage en argument du fichier 2DA (puisque chargé dans l'objet)
    // $col        => Nom de la colone (si $col est défini sous la forme Nx ou x est un nombre quelconque, $col devient le numéro de la colone et non plus sa designation. Cette particularité n'est pas présente dans NWN.)
    // $id        => ID de la ligne visée
    
function Get2DAString($col,$id) {
        
// Récupération du numéro de la colone ciblée
        
if($col[0] == "N" && is_numeric(substr($col,1))) $nCol substr($col,1);
        else 
$nCol array_search($col,$this->arColsHead);
        if(
$nCol === FALSE || $nCol == 0) return "";    // Colonne non trouvée
        // Renvoit de la valeur
        
for($i=0;$i<count($this->ar2DA);$i++) if($this->ar2DA[$i][0] == $id) return str_replace("****","",$this->ar2DA[$i][$nCol]);
        return 
"";    // Ligne non trouvée
    
}
    
//:: Fonction : Set2DAString
    // Défini la valeur dans le fichier 2DA mais SANS LA SAUVEGARDER ! Cette fonction ne change que l'objet, il faut appeller la fonction Save2DA pour affecter le fichier.
    // $col        => Nom de la colone (si $col est défini sous la forme Nx ou x est un nombre quelconque, $col devient le numéro de la colone et non plus sa designation. Cette particularité n'est pas présente dans NWN.)
    // $id        => ID de la ligne visée
    // $value    => Valeur à assigner (attention, les espaces seront supprimés, et si la longueur est excessive, elle sera coupée)
    
function Set2DAString($col,$id,$value) {
        
// Récupération du numéro de la colone ciblée
        
if($col[0] == "N" && is_numeric(substr($col,1))) $nCol substr($col,1);
        else 
$nCol array_search($col,$this->arColsHead);
        if(
$nCol === FALSE || $nCol == 0) return "";    // Colonne non trouvée
        // Test de la validité de la valeur
        
$value trim($value);
        if(
strlen($value)>$this->arColsLen[$nCol]-1$value substr($value,0,$this->arColsLen[$nCol]-1);
        
$value str_replace(" ","",$value);
        
// Ecriture à la bonne ligne
        
for($i=0;$i<count($this->ar2DA);$i++) if($this->ar2DA[$i][0] == $id) { $this->ar2DA[$i][$nCol] = $value; return; }
        
// La ligne n'existe pas encore, hum... Et bien on la crée.
        
$this->nNumItems++;
        
$this->ar2DA[][0] = $id;
        
// Et écriture !
        
for($i=0;$i<count($this->ar2DA);$i++) if($this->ar2DA[$i][0] == $id$this->ar2DA[$i][$nCol] = $value;
    }
    
//:: Fonction : Save2DA
    // Enregistre le fichier 2DA modifié
    // $file    => Chemin du fichier où sauver le 2DA (ce fichier est remplacé si déjà existant) /!\ ATTENTION : Ce fichier doit posséder les droits en écriture (CHMOD777).
    
function Save2DA($file) {
        
// Définition du caractère de fin de ligne
        
$endline "\n";
        
// Effacage si existant
        
if(file_exists($file)) unlink($file);
        
// Ouverture du fichier
        
$fp fopen($file,"a+");
        
// Ecriture des entêtes
        
fputs($fp,$this->sHeaders);
        
// Ecriture des blancs
        
for($i=0;$i<$this->LineLen-strlen($this->sHeaders)-2;$i++) $blanc .= " ";
        
fputs($fp,$blanc.$endline);
        
// Ecriture d'une ligne de blancs
        
for($i=0;$i<$this->LineLen-2;$i++) $blanc2 .= " ";
        
fputs($fp,$blanc2.$endline);
        
// Ecriture de la ligne des entêtes
        
fputs($fp,$this->HeadLine);
        
// Ecriture du tableau
        
for($i=0;$i<count($this->ar2DA);$i++) {
            
// Chaque ligne
            
$towrite "";
            for(
$j=0;$j<count($this->ar2DA[$i]);$j++) {
                
// Chaque colonne
                
$curwrite $this->ar2DA[$i][$j];
                while(
strlen($curwrite) < $this->arColsLen[$j]) $curwrite .= " ";
                
$towrite .= $curwrite;
            }
            if(
strlen(trim($towrite))>0fputs($fp,substr($towrite,0,strlen($towrite)-1).$endline);
        }
        
// Fermeture du fichier
        
fclose($fp);
    }
}
//:: MODE D'EMPLOI
/*
    Généralités sur la Programmation Orientée Objet (POO, ou OOP en anglais) en PHP :
        [url]http://ch.php.net/manual/fr/language.oop.php[/url]
        
    Cet objet charge à son initialisation le fichier en mémoire. Si celui-ci est modifié par un
    élément externe durant l'execution du script, cette modification ne sera pas prise en compte.
    
    Exemple d'initialisation : */
$MonObjet = new C2DA("baseitems.2da");    // Charge le fichier "baseitems.2da" se trouvant au même
                                        //  endroit que le script.
/*    
    Une fois l'objet initialisé, celui-ci répond à plusieures fonctions :
      - Display2DA($table_class = "", $tr_class = "", $td_class = "")
         Cette fonction affiche le contenu du 2DA dans un tableau HTML. Vous pouvez lui passer
        en argument les classes CSS que vous souhaitez utiliser pour les différents éléments
        du tableau. Par défaut, le tableau ne possède aucun élément, et est donc compatible
        XHTML.
        Exemple : */
$MonObjet->Display2DA();                // Affiche le contenu entier du 2DA et
echo "<br /><br />";                    //  revient à la ligne deux fois
/*      - Get2DAString($col,$id)
          Cette fonction travaille exactement de la même manière que le Get2DAString de Neverwinter
        Nights, exception faite qu'elle travaille sur le 2DA chargé dans l'objet, et non pas directement
        sur un fichier. Cela se ressent au niveau des temps CPU : Le Get2DAString de NWN est beaucoup
        plus lent, car le fichier est chargé à chaqune de ses executions (ce qui est soit dit en passant
        complètement abérant, passons).
        L'argument $col est un peu spécial, il peut être le nom de la colonne choisie (case sensitive,
        comme dans NWN) ou son numéro, si son 1er caractère est N. Par exemple, "ColonneDeTest" ira 
        chercher la colonne nommée ainsi, et "N9" prendra la 9ème colonne quelque soit sa désignation.
        Exemple : */
echo $MonObjet->Get2DAString("label","31")."<br />";    // Affiche la 31ème ligne de la colonne "label" de l'objet.
echo $MonObjet->Get2DAString("N2","31")."<br />";        // Affiche la même chose, car la 2ème colonne de baseitems.2da est la colonne "label"
/*      - Set2DAString($col,$id,$value)
          Cette fonction sert à modifier l'OBJET du 2DA en cours, et non pas le fichier. L'argument $col
        travaille de la même façon que dans la fonction Get2DAString.
        L'argument $value est soumis à certains critères. Sa longueur doit correspondre au maximum
        (variable) de la colonne en cours, sinon il sera coupé. Tous ses espaces seront également effacés.
        Exemple : */
$MonObjet->Set2DAString("label","31","trucmuche");        // Défini la valeur de la colonne "label" à la 31ème ligne de l'objet en cours et...
echo $MonObjet->Get2DAString("label","31")."<br />";    //  l'affiche.
/*      - Save2DA($file)
        Cette fonction enregistre l'objet en cours dans un fichier 2DA du choix de l'utilisateur. Si
        le fichier existe déjà, il sera effacé.
        Exemple : */
$MonObjet->Save2DA("newbaseitems.2da");        // Enregistre l'objet en cours dans le fichier "newbaseitems.2da", dans le même
unset($MonObjet);                            //  répertoire que le script, et détruit l'objet.
/*    J'espère que cet objet saura vous être utile, et vous souhaite une fort agréable utilisation.

    Merci de bien vouloir reporter les bugs de ce script à l'adresse suivante :
        - [email]cdn@gmx.ch[/email]
    En spécifiant dans le sujet la mension [Classe PHP - C2DA]
    
    Programmateusement,
    -- CdN
*/ 


Magnifique ! Couplé à Get2DAString, cette bibliothèque ("classe" ? ) va nous permettre de modifier toutes les options et les réglages personnalisés d'un serveur externe, grâce à une interface html

Merciiiiii
Citation :
Provient du message de Taern
cette bibliothèque ("classe" ? )
Objet
Citation :
Merciiiiii
Mais c'est un plaisir.
Citation :
Provient du message de Mastokk
heuu ça sert à quoi ?
En gros, à :
- Afficher un fichier 2DA sur une page HTML;
- Récupérer une valeur précise dans un 2DA;
- Modifier une valeur précise dans un 2DA;
- Créer un nouveau 2DA.
Désolé de mon ignorance mais...le 2DA doit se trouver où ? Il peut-être dans un hakpak ?

Enfin bravo, ça a vraiment l'air d'être du bon boulot

P.S.: Je me trompe peut-être mais j'ai l'impression que c'est la première fois que tu postes ici, alors bienvenue !
Hep hep!
Je suis nouveau aussi!

...
Ha flûte marche pô


Et bien donc, bienvenue CdN ou bon retour au cas où nous ferions un excès d'amnésie

En tout cas bon boulot, le script me semble impeccable
Mais à mon avis (qui est aussi celui d'Ormus), le 2DA ne se trouve nul part pendant que le serveur tourne, à part dans son hak-pack... à moins d'utiliser l'override
Tu dis que le script charge le fichier en mémoire, où?
Merci pour l'accueil

Le script charge le 2DA en RAM lors de son execution (sous forme de tableaux bidimensionnels, $obj->ar2DA pour le contenu, $obj->arColsHead pour les entête et $obj->arColsLen pour les longueurs).

Il ouvre un fichier 2DA qui doit se trouver dans un endroit non-compressé (je pensais à l'override en faisant ça ainsi).

On peut toutefois imaginer incorporer un autre module PHP qui sortirait les 2DA d'un HakPak pour les y remettre après modification, mais je ne sais pas trop comment réagirait NWN si le Hak était modifié en cours de route.

Y a-t-il une mise en cache du HakPak ? Et du répertoire d'override ? A tester, si au moins un des deux n'est pas préchargé, on peut imaginer modifier un 2DA en temps réel alors que le serveur tourne. C'est d'ailleurs pour ça que j'ai conçu le script à la base.

Pour ne rien vous cacher, va s'en suivre - très bientôt, j'ai quasiment terminé - une fonction Set2DAString pour le NWScript .
En effet, ça m'a l'air pô mal joué

Quand à savoir si le hakpack et l'override sont mis en cache, j'en ai aucune idée.
En fait la personne la mieux disposée à te répondre me semble bien être toi-même
Les 2da utilisés par NWN sont en cache je crois (à vrai dire ça tient de l'intime conviction ). Tu peux les modifier en cours de route ça ne changera pas le jeu (ça fait un moment que je l'ai pas fait, parce que NWN devient de plus en plus tatillon ). Quand aux autres fichiers du hak, ils sont en "mémoire" mais gérés par l'OS non je pense?
En revanche Get2daString lit directement depuis le fichier, à chaque fois, donc pour ça ton script marchera très bien je pense.
C'est en couple avec la fonction Get2DAString que je pensais utiliser la fonction. Reste à savoir quel fichier 2DA cette fonction lit. Je présume que c'est comme pour le reste, à savoir dans cet ordre :

- HakPak
- Override
- 2da.bif

Me trompe-je quelque part?
D'après ce que j'ai compris, la fameuse fonction de script va directement lire les fichiers sur le dur (d'où la parcimonie conseillée lors de son utilisation). Donc normalement y devrait pas y avoir d'histoire avec le hak/mémoire/cache/truc/machin

En fait, la vraie question c'est : où est-ce qu'il va chercher le 2da ? Les 2da du jeu sont contenus dans les .bif il me semble, donc peut-être qu'ils créeront un nouveau dossier 2da, ou alors y'en a déja un que j'ai pas vu ("2da source", je me demande à quoi il sert celui-là).

Quoiqu'il en soit, merchi bcp
Après quelques tests :

- Toutes les fonctions utilisant des données stockées dans des 2DA (j'ai testé avec GetItemACValue, aucune raison que les autres diffèrent) excepté Get2DAString ne sont pas modifiées par un changement du 2DA ni dans le HakPak, ni dans l'Override, ni dans le BIF en cours de jeu.

=> DONC : Les 2DAs sont chargés en cache au lancement du serveur.

- La fonction Get2DAString vérifie l'existence du 2DA dans le HakPak, puis dans l'Override, et si elle ne le trouve toujours pas, elle prend celui du BIF.
Cette fonction charge le 2DA à chaque exécution (et - sic - pas une seule fois par 2DA par script, malheureusement, ça aiderait 'achement).

=> DONC : On peut passer des données au jeu sans down via un 2DA personnalisé se trouvant - par exemple - dans l'override du serveur, via la commande Get2DAString.

Et c'est ça qui est super ! La palette de réglages que l'on peut choisir en cours de jeu devient infinie !
Du truc classique, genre fermer le serveur, réduire le nombre de places, réserver des places aux MJs, à des trucs plus tordus, comme par exemple restreindre des zones du module à certains moments, en ouvrir d'autres, modifier des réglages en tout genre sans avoir à recompiler ni à créer un système de paramétrage ingame bien lourd (cf. HCR) etc.

Bref, que du bonheur

Par contre, dommage pour les 2da. J'aime pas trop toucher à mon override, simple réaction instinctive
Bon, premiers résultats.

En premier, une bête petite fonction en NWScript, Set2DAString :
Code PHP:

// Ecrit dans le log la demande d'ecriture dans un fichier
//  2DA, interpretee par le 2DAWriter.
void Set2DAString(string s2DAstring sColonneint nLignestring sValeur) {
    
WriteTimestampedLogEntry("2DAWrite|"+s2DA+"|"+sColonne+"|"+IntToString(nLigne)+"|"+sValeur);

Et en second, un script PHP à mettre en shell ou a executer de temps en temps, qui parse les logs et modifie les 2DA en fonction.
Code PHP:

//:: Fonction : Parse2DAWrite
// Traite les 2DAWrite dans un fichier de log NWN et régénère un
// nouveau log sans les 2DAWrite déjà traités pour remplacer l'ancien.
// $file    => Chemin du fichier du log serveur
// $2dapath => Chemin du répertoire des 2DA (ne pas oublier le slash ou l'antislash à la fin, je ne le met pas pour la compatibilité Lin/Win)
function Parse2DAWrite($file,$2dapath) {
    
$fp fopen($file,"r");
    
$started 0;
    while(!
feof($fp)) {
        
$line fgets($fp,8192);
        while(
$line[0] == "."$line[0] = "";
        if(
$started == 1) {
            if(
strpos($line,"[") === false$started 0;
            else {
                
$line substr($line,strpos($line,"]") + 2);
                if(
substr($line,0,9) == "2DAWrite|") {
                    
$new explode("|",substr($line,9));
                    
$data[$new[0]][] = array($new[1],$new[2],$new[3]);
                }
            }
        }
        if(
strpos($line,"End Server Options") > 0$started 1;
    }
    
fclose($fp);
    foreach(
$data as $key => $value) {
        
$work = new C2DA($2dapath.$key.".2da");
        foreach(
$value as $key2 => $value2$work->Set2DAString($value2[0],$value2[1],$value2[2]);
        
$work->Save2DA($2dapath.$key.".2da");
        unset(
$work);
    }

Cette fonction nécessite au préalable l'inclusion de l'objet C2DA précédemment posté.

Encore un dernier truc : pourquoi avoir choisi PHP ? Plusieures raisons.
- C'est un langage que je maitrise pas trop mal, et ça aide à pas mettre 3 plombes à faire un script;
- C'est un langage sur, efficace, open-source;
- Il est portable sur Windows et Linux;
- On peut l'utiliser dans des pages Web.

Je demandais pas mieux, alors j'ai adopté.

J'ai testé le script, ça fonctionne. J'ai utilisé l'override pour procéder, ayant la flemme de faire (ou de chercher, je crois que ça existe déjà) un module PHP pour décompresser/recompresser les 2DA d'un HAK ou d'un BIF.

Dernière étape : Un p'tit prog (Windows, vu que sous Linux, un shell suffit) qui se lance en tray pour parser automatiquement les logs tous les X temps.
J'ai vaguement commencé le développement, mais c'est assez chiant, vu que je dois refaire toute la classe C2DA en C# (je code ça en C#) pour éviter de devoir installer à chaque fois l'interpréteur PHP.

Je sais pas si ça va vraiment servir à quelqu'un, vu que 90% des serveurs qui auraient usage du script tournent sous Linux, et vu les améliorations très très nettes de la nouvelle version du serveur Linux (encore en beta fermée, je crois).

Bref, votre avis. Ca vaut le coup que je finisse, ou je laisse dans l'état ? C'est fonctionnel ainsi, le "relanceur", c'est du gadget.
ben si tu fais ca en C je reprendrais probablement le code, mais sous une forme compilée je m'en fous complètement ^_^

voila c dit ^^

PS : tt pas sencé le sortir hier soir ca ???? tu prend tu retard dans ton boulot c pas bien
Répondre

Connectés sur ce fil

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