Pour faire un bon parseur (et plus généralement un bon logiciel) il faut bien découpler toutes les parties logicielles en composants simples et réutilisables.
L'approche orientée objet est actuellement la meilleure que je connaisse (oubliez AutoIt) !
Pour faire quelque chose de propre :
- on créé une interface définissant le contrat à remplir,
- on implémente cette interface avec une (ou plusieurs classes) abstraites pour définir le code commun à toutes les implémentations,
- on hérite de la classe abstraite avec une classe concrète pour implémenter le spécifique (ce que l'on veut faire)
Donc, pour ce tuto ce sera Java, vous pourrez porter ça sous C# ou encore plus facilement sous J#, les amateurs de C++ peuvent aussi profiter du voyage.
Je pars du principe que vous :
- connaissez un peu le jargon technique,
- savez vous connecter, lire et écrire le flux réseau.
De toutes façon, vous poserez des questions et la connexion ça ressemble à ce pseudo code:
Socket socket = new Socket("xxx.xxx.xxx.xxx", 443);
while(isRunning) {
String line = socket.readLineTerminéeParNull();
faireQuelqueChoseAvec(line);
}
A plusieurs reprises, je parle de ligne : cette ligne est la chaine de caractères reçue du serveur et qui se termine par un caractère NULL ('\0' ou chr$(0) ou toute autre notation spécifique à votre language).
Le traitement se fait toujours ligne par ligne.
Nous allons donc commencer par créer une classe ProtocolProcessor contenant une methode process() qui va s'occuper de l'aiguillage.
Par exemple :
public class ProtocolProcessor {
/**
* Pré-traitement de la ligne reçue du serveur
* @param sData : la ligne reçue
* @throws IOException si une exception d'entrée/sortie à lieu (coupure réseau par exemple)
*/
public void process(String sData) throws IOException {
// Premier caractère de la ligne
char type = sData.charAt(0);
// Deuxième caractère de la ligne
char action = sData.charAt(1);
boolean hasError = false;
try {
// Troisième caractère de la ligne
hasError = sData.charAt(2) == 'E';
} catch (StringIndexOutOfBoundsException e) {
}
// Traitement par lui même
this.postProcess(type, action, hasError, sData);
}
}
Comme on vient de le voir la méthode process() est appelée pour chaque ligne et elle sous-traite le fonctionnement réel à postProcess() dont voici le début.
/**
* Traitement de la ligne reçue du serveur
* @param sType : Type de message
* @param sAction : Sous type ou action
* @param bError : si vrai ; le message contient une erreur.
* @param sData ligne reçue du serveur dans son intégralité (sauf le NULL terminateur)
* @throws IOException si une exception d'entrée/sortie à lieu (coupure réseau par exemple)
*/
private void postProcess(char sType, char sAction, boolean bError, String sData) throws IOException {
switch (sType) {
case 'H': {
switch (sAction) {
case 'C': {
this.aks.onHelloConnectionServer(sData.substring(2));
break;
}
case 'G': {
this.aks.onHelloGameServer(sData.substring(2));
break;
}
default: {
this.aks.disconnect(false, true);
}
} // End of switch
break;
}
//Les autres cas sont traités ci-après, pour la lisibilité, je les ai supprimés
}
}
Comme on peut le voir, les messages sont constitués de 2 ou 3 lettres au minimum et on aiguille une première fois sur la première lettre, puis un deuxième fois sur
la 2è lettre.
Les messages sont regroupés de manière logique par la première lettre.
Dans ce bout de code je viens d'intégrer un nouvel objet que j'ai nommé aks (this.aks est en Java une notation qui permet de signifier que aks est une instance
de notre classe ProtocolProcessor). Cet objet est passé au constructeur de la classe. J'ai nommé la classe de cet objet ProcessorListener car elle va "écouter"
le parseur et effectuer les actions correspondantes à chaque cas (ceux qui connaissent les parseurs XML de type SAX ne seront pas dépaysés).
Ci-dessous la déclaration de la classe ProtocolProcessor avec le constructeur prenant en argument le ProcessorListener.
public class ProtocolProcessor {
private ProcessorListener aks;
private Logger logger = Logger.getLogger(ProtocolProcessor.class);
public ProtocolProcessor(ProcessorListener oAKS) {
aks = oAKS;
}
La classe complete
Cliquez pour révéler
Cliquez pour masquer
package dofus.aks;
import java.io.IOException;
import org.apache.log4j.Logger;
import dofus.aks.processor.listeners.ProcessorListener;
public class ProtocolProcessor {
private ProcessorListener aks;
private Logger logger = Logger.getLogger(ProtocolProcessor.class);
public ProtocolProcessor(ProcessorListener oAKS/* , int oAPI */) {
aks = oAKS;
}
/**
* Pré-traitement de la ligne reçue du serveur
* @param sData : la ligne reçue
* @throws IOException si une exception d'entrée/sortie à lieu (coupure réseau par exemple)
*/
public void process(String sData) throws IOException {
char type = sData.charAt(0);
char action = sData.charAt(1);
boolean hasError = false;
try {
hasError = sData.charAt(2) == 'E';
} catch (StringIndexOutOfBoundsException e) {
}
this.postProcess(type, action, hasError, sData);
}
/**
* Traitement de la ligne reçue du serveur
* @param sType : Type de message
* @param sAction : Sous type ou action
* @param bError : si vrai ; le message contient une erreur.
* @param sData ligne reçue du serveur dans son intégralité (sauf le NULL terminateur)
* @throws IOException si une exception d'entrée/sortie à lieu (coupure réseau par exemple)
*/
private void postProcess(char sType, char sAction, boolean bError, String sData) throws IOException {
switch (sType) {
case 'H': {
switch (sAction) {
case 'C': {
this.aks.onHelloConnectionServer(sData.substring(2));
break;
}
case 'G': {
this.aks.onHelloGameServer(sData.substring(2));
break;
}
default: {
this.aks.disconnect(false, true);
}
} // End of switch
break;
}
case 'p': {
this.aks.onPong();
break;
}
case 'q': {
this.aks.onQuickPong();
break;
}
case 'r': {
this.aks.onResponsePong(sData.substring(5));
break;
}
case 'M': {
this.aks.onServerMessage(sData.substring(1));
break;
}
case 'k': {
this.aks.onServerWillDisconnect();
break;
}
case 'B': {
switch (sAction) {
case 'N': {
return;
}
case 'A': {
switch (sData.charAt(2)) {
case 'T': {
this.aks.getBasics().onAuthorizedCommand(true, sData.substring(3));
break;
}
case 'L': {
this.aks.getBasics().onAuthorizedLine(sData.substring(3));
break;
}
case 'P': {
this.aks.getBasics().onAuthorizedCommandPrompt(sData.substring(3));
break;
}
case 'C': {
this.aks.getBasics().onAuthorizedCommandClear();
break;
}
case 'E': {
this.aks.getBasics().onAuthorizedCommand(false);
break;
}
case 'I': {
if (sData.charAt(3) != 'O') {
break;
} // end if
this.aks.getBasics().onAuthorizedInterfaceOpen(sData.substring(4));
// TODO que veut dire ce double break en AS ?
// break;
this.aks.getBasics().onAuthorizedInterfaceClose(sData.substring(4));
break;
}
} // End of switch
break;
}
case 'T': {
this.aks.getBasics().onReferenceTime(sData.substring(2));
break;
}
case 'D': {
this.aks.getBasics().onDate(sData.substring(2));
break;
}
case 'W': {
this.aks.getBasics().onWhoIs(!bError, sData.substring(3));
break;
}
case 'P': {
this.aks.getBasics().onSubscriberRestriction(sData.substring(2));
break;
}
case 'C': {
this.aks.getBasics().onFileCheck(sData.substring(2));
break;
}
case 'p': {
this.aks.getBasics().onAveragePing(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'A': {
switch (sAction) {
case 'c': {
this.aks.getAccount().onCommunity(sData.substring(2));
break;
}
case 'd': {
this.aks.getAccount().onDofusPseudo(sData.substring(2));
break;
}
case 'l': {
this.aks.getAccount().onLogin(!bError, sData.substring(3));
break;
}
case 'L': {
this.aks.getAccount().onCharactersList(!bError, sData.substring(3));
break;
}
case 'x': {
this.aks.getAccount().onServersList(!bError, sData.substring(3));
break;
}
case 'A': {
this.aks.getAccount().onCharacterAdd(!bError, sData.substring(3));
break;
}
case 'T': {
this.aks.getAccount().onTicketResponse(!bError, sData.substring(3));
break;
}
case 'X': {
this.aks.getAccount().onSelectServer(!bError, true, sData.substring(3));
break;
}
case 'Y': {
this.aks.getAccount().onSelectServer(!bError, false, sData.substring(3));
break;
}
case 'S': {
this.aks.getAccount().onCharacterSelected(!bError, sData.substring(4));
break;
}
case 's': {
this.aks.getAccount().onStats(sData.substring(2));
break;
}
case 'N': {
this.aks.getAccount().onNewLevel(sData.substring(2));
break;
}
case 'R': {
this.aks.getAccount().onRestrictions(sData.substring(2));
break;
}
case 'H': {
this.aks.getAccount().onHosts(sData.substring(2));
break;
}
case 'r': {
this.aks.getAccount().onRescue(!bError);
break;
}
case 'g': {
this.aks.getAccount().onGiftsList(sData.substring(2));
break;
}
case 'G': {
this.aks.getAccount().onGiftStored(!bError);
break;
}
case 'q': {
this.aks.getAccount().onQueue(sData.substring(2));
break;
}
case 'f': {
this.aks.getAccount().onNewQueue(sData.substring(2));
break;
}
case 'V': {
this.aks.getAccount().onRegionalVersion(sData.substring(2));
break;
}
case 'P': {
this.aks.getAccount().onCharacterNameGenerated(!bError, sData.substring(3));
break;
}
case 'K': {
this.aks.getAccount().onKey(sData.substring(2));
break;
}
case 'Q': {
this.aks.getAccount().onSecretQuestion(sData.substring(2));
break;
}
case 'D': {
this.aks.getAccount().onCharacterDelete(!bError, sData.substring(3));
break;
}
case 'M': {
switch (sData.charAt(2)) {
case '?': {
this.aks.getAccount().onCharactersMigrationAskConfirm(sData.substring(3));
break;
}
default: {
this.aks.getAccount().onCharactersList(!bError, sData.substring(3), true);
break;
}
} // End of switch
break;
}
case 'F': {
this.aks.getAccount().onFriendServerList(sData.substring(2));
break;
}
case 'm': {
// if (!CONFIG.isStreaming) {
// this.aks.getAccount().onMiniClipInfo();
// } else {
int value;
try {
value = Integer.parseInt("" + sData.charAt(2), 10);
} catch (Exception e) {
value = 3;
}
logger.warn("GoToCongratulation : " + value + " Not implemented");
// }
break;
}
} // End of switch
break;
}
case 'G': {
switch (sAction) {
case 'C': {
this.aks.getGame().onCreate(!bError, sData.substring(4));
break;
}
case 'J': {
this.aks.getGame().onJoin(sData.substring(3));
break;
}
case 'P': {
this.aks.getGame().onPositionStart(sData.substring(2));
break;
}
case 'R': {
this.aks.getGame().onReady(sData.substring(2));
break;
}
case 'S': {
this.aks.getGame().onStartToPlay();
break;
}
case 'E': {
this.aks.getGame().onEnd(sData.substring(2));
break;
}
case 'M': {
this.aks.getGame().onMovement(sData.substring(3));
break;
}
case 'c': {
this.aks.getGame().onChallenge(sData.substring(2));
break;
}
case 't': {
this.aks.getGame().onTeam(sData.substring(2));
break;
}
case 'V': {
this.aks.getGame().onLeave(true, sData.substring(2));
break;
}
case 'f': {
this.aks.getGame().onFlag(sData.substring(2));
break;
}
case 'I': {
switch (sData.charAt(2)) {
case 'C': {
this.aks.getGame().onPlayersCoordinates(sData.substring(4));
break;
}
case 'E': {
this.aks.getGame().onEffect(sData.substring(3));
break;
}
case 'e': {
this.aks.getGame().onClearAllEffect(sData.substring(3));
break;
}
case 'P': {
this.aks.getGame().onPVP(sData.substring(3), false);
break;
}
} // End of switch
break;
}
case 'D': {
switch (sData.charAt(2)) {
case 'M': {
this.aks.getGame().onMapData(sData.substring(4));
break;
}
case 'K': {
this.aks.getGame().onMapLoaded();
break;
}
case 'C': {
this.aks.getGame().onCellData(sData.substring(3));
break;
}
case 'Z': {
this.aks.getGame().onZoneData(sData.substring(3));
break;
}
case 'O': {
this.aks.getGame().onCellObject(sData.substring(3));
break;
}
case 'F': {
this.aks.getGame().onFrameObject2(sData.substring(4));
break;
}
case 'E': {
this.aks.getGame().onFrameObjectExternal(sData.substring(4));
break;
}
} // End of switch
break;
}
case 'd': {
switch (sData.charAt(3)) {
case 'K': {
this.aks.getGame().onFightChallengeUpdate(sData.substring(4), true);
break;
}
case 'O': {
this.aks.getGame().onFightChallengeUpdate(sData.substring(4), false);
break;
}
default: {
this.aks.getGame().onFightChallenge(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'A': {
switch (sData.charAt(2)) {
case 'S': {
this.aks.getGameActions().onActionsStart(sData.substring(3));
break;
}
case 'F': {
this.aks.getGameActions().onActionsFinish(sData.substring(3));
break;
}
default: {
this.aks.getGameActions().onActions(sData.substring(2));
}
} // End of switch
break;
}
case 'T': {
switch (sData.charAt(2)) {
case 'S': {
this.aks.getGame().onTurnStart(sData.substring(3));
break;
}
case 'F': {
this.aks.getGame().onTurnFinish(sData.substring(3));
break;
}
case 'L': {
this.aks.getGame().onTurnlist(sData.substring(4));
break;
}
case 'M': {
this.aks.getGame().onTurnMiddle(sData.substring(4));
break;
}
case 'R': {
this.aks.getGame().onTurnReady(sData.substring(3));
break;
}
} // End of switch
break;
}
case 'X': {
this.aks.getGame().onExtraClip(sData.substring(2));
break;
}
case 'o': {
this.aks.getGame().onFightOption(sData.substring(2));
break;
}
case 'O': {
this.aks.getGame().onGameOver();
break;
}
} // End of switch
break;
}
case 'c': {
switch (sAction) {
case 'M': {
this.aks.getChat().onMessage(!bError, sData.substring(3));
break;
}
case 's': {
this.aks.getChat().onServerMessage(sData.substring(2));
break;
}
case 'S': {
this.aks.getChat().onSmiley(sData.substring(2));
break;
}
case 'C': {
this.aks.getChat().onSubscribeChannel(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'D': {
switch (sAction) {
case 'A': {
this.aks.getDialog().onCustomAction(sData.substring(2));
break;
}
case 'C': {
this.aks.getDialog().onCreate(!bError, sData.substring(3));
break;
}
case 'Q': {
this.aks.getDialog().onQuestion(sData.substring(2));
break;
}
case 'V': {
this.aks.getDialog().onLeave();
break;
}
case 'P': {
this.aks.getDialog().onPause();
break;
}
} // End of switch
break;
}
case 'I': {
switch (sAction) {
case 'M': {
this.aks.getInfos().onInfoMaps(sData.substring(2));
break;
}
case 'C': {
this.aks.getInfos().onInfoCompass(sData.substring(2));
break;
}
case 'H': {
this.aks.getInfos().onInfoCoordinatespHighlight(sData.substring(2));
break;
}
case 'm': {
this.aks.getInfos().onMessage(sData.substring(2));
break;
}
case 'Q': {
this.aks.getInfos().onQuantity(sData.substring(2));
break;
}
case 'O': {
this.aks.getInfos().onObject(sData.substring(2));
break;
}
case 'L': {
switch (sData.charAt(2)) {
case 'S': {
this.aks.getInfos().onLifeRestoreTimerStart(sData.substring(3));
break;
}
case 'F': {
this.aks.getInfos().onLifeRestoreTimerFinish(sData.substring(3));
break;
}
} // End of switch
break;
}
} // End of switch
break;
}
case 'S': {
switch (sAction) {
case 'L': {
switch (sData.charAt(2)) {
case 'o': {
this.aks.getSpells().onChangeOption(sData.substring(3));
break;
}
default: {
this.aks.getSpells().onList(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'U': {
this.aks.getSpells().onUpgradeSpell(!bError, sData.substring(3));
break;
}
case 'B': {
this.aks.getSpells().onSpellBoost(sData.substring(2));
break;
}
case 'F': {
this.aks.getSpells().onSpellForget(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'O': {
switch (sAction) {
case 'a': {
this.aks.getItems().onAccessories(sData.substring(2));
break;
}
case 'D': {
this.aks.getItems().onDrop(!bError, sData.substring(3));
break;
}
case 'A': {
this.aks.getItems().onAdd(!bError, sData.substring(3));
break;
}
case 'C': {
this.aks.getItems().onChange(sData.substring(3));
break;
}
case 'R': {
this.aks.getItems().onRemove(sData.substring(2));
break;
}
case 'Q': {
this.aks.getItems().onQuantity(sData.substring(2));
break;
}
case 'M': {
this.aks.getItems().onMovement(sData.substring(2));
break;
}
case 'T': {
this.aks.getItems().onTool(sData.substring(2));
break;
}
case 'w': {
this.aks.getItems().onWeight(sData.substring(2));
break;
}
case 'S': {
this.aks.getItems().onItemSet(sData.substring(2));
break;
}
case 'K': {
this.aks.getItems().onItemUseCondition(sData.substring(2));
break;
}
case 'F': {
this.aks.getItems().onItemFound(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'F': {
switch (sAction) {
case 'A': {
this.aks.getFriends().onAddFriend(!bError, sData.substring(3));
break;
}
case 'D': {
this.aks.getFriends().onRemoveFriend(!bError, sData.substring(3));
break;
}
case 'L': {
this.aks.getFriends().onFriendsList(sData.substring(3));
break;
}
case 'S': {
this.aks.getFriends().onSpouse(sData.substring(2));
break;
}
case 'O': {
this.aks.getFriends().onNotifyChange(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'i': {
switch (sAction) {
case 'A': {
this.aks.getEnemies().onAddEnemy(!bError, sData.substring(3));
break;
}
case 'D': {
this.aks.getEnemies().onRemoveEnemy(!bError, sData.substring(3));
break;
}
case 'L': {
this.aks.getEnemies().onEnemiesList(sData.substring(3));
break;
}
} // End of switch
break;
}
case 'K': {
switch (sAction) {
case 'C': {
this.aks.getKey().onCreate(sData.substring(3));
break;
}
case 'K': {
this.aks.getKey().onKey(!bError);
break;
}
case 'V': {
this.aks.getKey().onLeave();
break;
}
} // End of switch
break;
}
case 'J': {
switch (sAction) {
case 'S': {
this.aks.getJob().onSkills(sData.substring(3));
break;
}
case 'X': {
this.aks.getJob().onXP(sData.substring(3));
break;
}
case 'N': {
this.aks.getJob().onLevel(sData.substring(2));
break;
}
case 'R': {
this.aks.getJob().onRemove(sData.substring(2));
break;
}
case 'O': {
this.aks.getJob().onOptions(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'E': {
switch (sAction) {
case 'R': {
this.aks.getExchange().onRequest(!bError, sData.substring(3));
break;
}
case 'K': {
this.aks.getExchange().onReady(sData.substring(2));
break;
}
case 'V': {
this.aks.getExchange().onLeave(!bError, sData.substring(2));
break;
}
case 'C': {
this.aks.getExchange().onCreate(!bError, sData.substring(3));
break;
}
case 'c': {
this.aks.getExchange().onCraft(!bError, sData.substring(3));
break;
}
case 'M': {
this.aks.getExchange().onLocalMovement(!bError, sData.substring(3));
break;
}
case 'm': {
this.aks.getExchange().onDistantMovement(!bError, sData.substring(3));
break;
}
case 'r': {
this.aks.getExchange().onCoopMovement(!bError, sData.substring(3));
break;
}
case 'p': {
this.aks.getExchange().onPayMovement(!bError, sData.substring(2));
break;
}
case 's': {
this.aks.getExchange().onStorageMovement(!bError, sData.substring(3));
break;
}
case 'i': {
this.aks.getExchange().onPlayerShopMovement(!bError, sData.substring(3));
break;
}
case 'W': {
this.aks.getExchange().onCraftPublicMode(sData.substring(2));
break;
}
case 'e': {
this.aks.getExchange().onMountStorage(sData.substring(2));
break;
}
case 'f': {
this.aks.getExchange().onMountPark(sData.substring(2));
break;
}
case 'w': {
this.aks.getExchange().onMountPods(sData.substring(2));
break;
}
case 'L': {
this.aks.getExchange().onList(sData.substring(2));
break;
}
case 'S': {
this.aks.getExchange().onSell(!bError);
break;
}
case 'B': {
this.aks.getExchange().onBuy(!bError);
break;
}
case 'q': {
this.aks.getExchange().onAskOfflineExchange(sData.substring(2));
break;
}
case 'H': {
switch (sData.charAt(2)) {
case 'S': {
this.aks.getExchange().onSearch(sData.substring(3));
break;
}
case 'L': {
this.aks.getExchange().onBigStoreTypeItemsList(sData.substring(3));
break;
}
case 'M': {
this.aks.getExchange().onBigStoreTypeItemsMovement(sData.substring(3));
break;
}
case 'l': {
this.aks.getExchange().onBigStoreItemsList(sData.substring(3));
break;
}
case 'm': {
this.aks.getExchange().onBigStoreItemsMovement(sData.substring(3));
break;
}
case 'P': {
this.aks.getExchange().onItemMiddlePriceInBigStore(sData.substring(3));
break;
}
} // End of switch
break;
}
case 'J': {
this.aks.getExchange().onCrafterListChanged(sData.substring(2));
break;
}
case 'j': {
this.aks.getExchange().onCrafterReference(sData.substring(2));
break;
}
case 'A': {
this.aks.getExchange().onCraftLoop(sData.substring(2));
break;
}
case 'a': {
this.aks.getExchange().onCraftLoopEnd(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'h': {
switch (sAction) {
case 'L': {
this.aks.getHouses().onList(sData.substring(2));
break;
}
case 'P': {
this.aks.getHouses().onProperties(sData.substring(2));
break;
}
case 'X': {
this.aks.getHouses().onLockedProperty(sData.substring(2));
break;
}
case 'C': {
this.aks.getHouses().onCreate(sData.substring(3));
break;
}
case 'S': {
this.aks.getHouses().onSell(!bError, sData.substring(3));
break;
}
case 'B': {
this.aks.getHouses().onBuy(!bError, sData.substring(3));
break;
}
case 'V': {
this.aks.getHouses().onLeave();
break;
}
case 'G': {
this.aks.getHouses().onGuildInfos(sData.substring(2));
break;
}
} // End of switch
break;
}
case 's': {
switch (sAction) {
case 'L': {
this.aks.getStorages().onList(sData.substring(2));
break;
}
case 'X': {
this.aks.getStorages().onLockedProperty(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'e': {
switch (sAction) {
case 'U': {
this.aks.getEmotes().onUse(!bError, sData.substring(3));
break;
}
case 'L': {
this.aks.getEmotes().onList(sData.substring(2));
break;
}
case 'A': {
this.aks.getEmotes().onAdd(sData.substring(2));
break;
}
case 'R': {
this.aks.getEmotes().onRemove(sData.substring(2));
break;
}
case 'D': {
this.aks.getEmotes().onDirection(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'd': {
switch (sAction) {
case 'C': {
this.aks.getDocuments().onCreate(!bError, sData.substring(3));
break;
}
case 'V': {
this.aks.getDocuments().onLeave();
break;
}
} // End of switch
break;
}
case 'g': {
switch (sAction) {
case 'n': {
this.aks.getGuild().onNew();
break;
}
case 'C': {
this.aks.getGuild().onCreate(!bError, sData.substring(3));
break;
}
case 'S': {
this.aks.getGuild().onStats(sData.substring(2));
break;
}
case 'I': {
switch (sData.charAt(2)) {
case 'G': {
this.aks.getGuild().onInfosGeneral(sData.substring(3));
break;
}
case 'M': {
this.aks.getGuild().onInfosMembers(sData.substring(3));
break;
}
case 'B': {
this.aks.getGuild().onInfosBoosts(sData.substring(3));
break;
}
case 'F': {
this.aks.getGuild().onInfosMountPark(sData.substring(3));
break;
}
case 'T': {
switch (sData.charAt(3)) {
case 'M': {
this.aks.getGuild().onInfosTaxCollectorsMovement(sData.substring(4));
break;
}
case 'P': {
this.aks.getGuild().onInfosTaxCollectorsPlayers(sData.substring(4));
break;
}
case 'p': {
this.aks.getGuild().onInfosTaxCollectorsAttackers(sData.substring(4));
break;
}
} // End of switch
break;
}
case 'H': {
this.aks.getGuild().onInfosHouses(sData.substring(3));
break;
}
} // End of switch
break;
}
case 'J': {
switch (sData.charAt(2)) {
case 'E': {
this.aks.getGuild().onJoinError(sData.substring(3));
break;
}
case 'R': {
this.aks.getGuild().onRequestLocal(sData.substring(3));
break;
}
case 'r': {
this.aks.getGuild().onRequestDistant(sData.substring(3));
break;
}
case 'K': {
this.aks.getGuild().onJoinOk(sData.substring(3));
break;
}
case 'C': {
this.aks.getGuild().onJoinDistantOk();
break;
}
} // End of switch
break;
}
case 'V': {
this.aks.getGuild().onLeave();
break;
}
case 'K': {
this.aks.getGuild().onBann(!bError, sData.substring(3));
break;
}
case 'H': {
this.aks.getGuild().onHireTaxCollector(!bError, sData.substring(3));
break;
}
case 'A': {
this.aks.getGuild().onTaxCollectorAttacked(sData.substring(2));
break;
}
case 'T': {
this.aks.getGuild().onTaxCollectorInfo(sData.substring(2));
break;
}
case 'U': {
this.aks.getGuild().onUserInterfaceOpen(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'W': {
switch (sAction) {
case 'C': {
this.aks.getWaypoints().onCreate(sData.substring(2));
break;
}
case 'V': {
this.aks.getWaypoints().onLeave();
break;
}
case 'U': {
this.aks.getWaypoints().onUseError();
break;
}
case 'c': {
this.aks.getSubway().onCreate(sData.substring(2));
break;
}
case 'v': {
this.aks.getSubway().onLeave();
break;
}
case 'u': {
this.aks.getSubway().onUseError();
break;
}
case 'p': {
this.aks.getSubway().onPrismCreate(sData.substring(2));
break;
}
case 'w': {
this.aks.getSubway().onPrismLeave();
break;
}
} // End of switch
break;
}
case 'a': {
switch (sAction) {
case 'l': {
this.aks.getSubareas().onList(sData.substring(3));
break;
}
case 'm': {
this.aks.getSubareas().onAlignmentModification(sData.substring(2));
break;
}
case 'M': {
this.aks.getConquest().onAreaAlignmentChanged(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'C': {
switch (sAction) {
case 'I': {
switch (sData.charAt(2)) {
case 'J': {
this.aks.getConquest().onPrismInfosJoined(sData.substring(3));
break;
}
case 'V': {
this.aks.getConquest().onPrismInfosClosing(sData.substring(3));
}
} // End of switch
break;
}
case 'B': {
this.aks.getConquest().onConquestBonus(sData.substring(2));
break;
}
case 'A': {
this.aks.getConquest().onPrismAttacked(sData.substring(2));
break;
}
case 'S': {
this.aks.getConquest().onPrismSurvived(sData.substring(2));
break;
}
case 'D': {
this.aks.getConquest().onPrismDead(sData.substring(2));
break;
}
case 'P': {
this.aks.getConquest().onPrismFightAddPlayer(sData.substring(2));
break;
}
case 'p': {
this.aks.getConquest().onPrismFightAddEnemy(sData.substring(2));
break;
}
case 'W': {
this.aks.getConquest().onWorldData(sData.substring(2));
break;
}
case 'b': {
this.aks.getConquest().onConquestBalance(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'Z': {
switch (sAction) {
case 'S': {
this.aks.getSpecialization().onSet(sData.substring(2));
break;
}
case 'C': {
this.aks.getSpecialization().onChange(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'f': {
switch (sAction) {
case 'C': {
this.aks.getFights().onCount(sData.substring(2));
break;
}
case 'L': {
this.aks.getFights().onList(sData.substring(2));
break;
}
case 'D': {
this.aks.getFights().onDetails(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'T': {
switch (sAction) {
case 'C': {
this.aks.getTutorial().onCreate(sData.substring(2));
break;
}
case 'T': {
this.aks.getTutorial().onShowTip(sData.substring(2));
break;
}
case 'B': {
this.aks.getTutorial().onGameBegin();
break;
}
} // End of switch
break;
}
case 'Q': {
switch (sAction) {
case 'L': {
this.aks.getQuests().onList(sData.substring(3));
break;
}
case 'S': {
this.aks.getQuests().onStep(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'P': {
switch (sAction) {
case 'I': {
this.aks.getParty().onInvite(!bError, sData.substring(3));
break;
}
case 'L': {
this.aks.getParty().onLeader(sData.substring(2));
break;
}
case 'R': {
this.aks.getParty().onRefuse(sData.substring(2));
break;
}
case 'A': {
this.aks.getParty().onAccept(sData.substring(2));
break;
}
case 'C': {
this.aks.getParty().onCreate(!bError, sData.substring(3));
break;
}
case 'V': {
this.aks.getParty().onLeave(sData.substring(2));
break;
}
case 'F': {
this.aks.getParty().onFollow(!bError, sData.substring(3));
break;
}
case 'M': {
this.aks.getParty().onMovement(sData.substring(2));
break;
}
} // End of switch
break;
}
case 'R': {
switch (sAction) {
case 'e': {
this.aks.getMount().onEquip(sData.substring(2));
break;
}
case 'x': {
this.aks.getMount().onXP(sData.substring(2));
break;
}
case 'n': {
this.aks.getMount().onName(sData.substring(2));
break;
}
case 'd': {
this.aks.getMount().onData(sData.substring(2));
break;
}
case 'p': {
this.aks.getMount().onMountPark(sData.substring(2));
break;
}
case 'D': {
this.aks.getMount().onMountParkBuy(sData.substring(2));
break;
}
case 'v': {
this.aks.getMount().onLeave(sData.substring(2));
break;
}
case 'r': {
this.aks.getMount().onRidingState(sData.substring(2));
break;
}
} // End of switch
break;
}
} // End of switch
};
}
Contrairement à ce que j'ai écrit un peu plus haut, le ProcessorListener ne va rien faire : c'est une interface qui va définir le contrat que devra respecter
toute implémentation.
Voici l'interface ProcessorListener:
Cliquez pour révéler
Cliquez pour masquer
package dofus.aks.processor.listeners;
import java.io.IOException;
import dofus.aks.network.IO;
public interface ProcessorListener {
BasicsProcessorListener getBasics();
AccountProcessorListener getAccount();
GameProcessorListener getGame();
GameActionsProcessorListener getGameActions();
ChatProcessorListener getChat();
DialogProcessorListener getDialog();
InfosProcessorListener getInfos();
ItemsProcessorListener getItems();
SpellsProcessorListener getSpells();
FriendsProcessorListener getFriends();
EnemiesProcessorListener getEnemies();
KeysProcessorListener getKey();
JobProcessorListener getJob();
ExchangeProcessorListener getExchange();
HousesProcessorListener getHouses();
StoragesProcessorListener getStorages();
EmotesProcessorListener getEmotes();
DocumentsProcessorListener getDocuments();
GuildProcessorListener getGuild();
WaypointsProcessorListener getWaypoints();
SubwayProcessorListener getSubway();
SubareasProcessorListener getSubareas();
ConquestProcessorListener getConquest();
SpecializationProcessorListener getSpecialization();
FightsProcessorListener getFights();
TutorialProcessorListener getTutorial();
QuestsProcessorListener getQuests();
PartyProcessorListener getParty();
MountProcessorListener getMount();
void onHelloConnectionServer(String substring) throws IOException;
void onHelloGameServer(String substring) throws IOException;
void disconnect(boolean b, boolean c) throws IOException;
void onPong() throws IOException;
/**
* This method should send <b>"rpong" + string</b>
* <br>The default implementation does nothing since Dofus will answer to the rpong request.
* <br>If a pluging eats the rpong request it is its responsibility to answer the rpong request
* @param string
*/
void onResponsePong(String string);
void onQuickPong() throws IOException;
void onServerMessage(String substring) throws IOException;
void onServerWillDisconnect() throws IOException;
IO getIO();
}
Les plus attentifs auront remarqué que l'interface ProcessorListener contient beaucoup de méthodes du type getQuelqueChose() et que ces méthodes renvoient un
objet QuelqueChoseListener. En effet, la gestion des messages va être sous traitée à des objets spécialisés en fonction de leur type (généralement la 1re
lettre de la ligne).
Penons l'exemple de toute ligne commençant par c, dans le source de ProcessorListener on a :
case 'c': {
switch (sAction) {
case 'M': {
this.aks.getChat().onMessage(!bError, sData.substring(3));
break;
}
case 's': {
this.aks.getChat().onServerMessage(sData.substring(2));
break;
}
case 'S': {
this.aks.getChat().onSmiley(sData.substring(2));
break;
}
case 'C': {
this.aks.getChat().onSubscribeChannel(sData.substring(2));
break;
}
} // End of switch
break;
}
aks.getChat() renvoit une instance de l'interface ChatProcessorListener définie comme suit :
package dofus.aks.processor.listeners;
public interface ChatProcessorListener {
void onMessage(boolean b, String substring);
void onServerMessage(String substring);
void onSmiley(String substring);
void onSubscribeChannel(String substring);
}
Encore une fois, nous avons une interface qui définit le contrat à respecter pour gérer (dans ce cas) les messages de type Chat
Eclipse a créé pour moi toutes ces interfaces automatiquement à partir de classe ProtocolProcessor.
Une fois toutes les interfaces définies, il est souvent bon de créer une classe abstraite par interface pour y loger tout le code générique qui sera réutilisé par
toutes les classes concrètes qui vont implémenter ces interfaces.
J'ai choisi d'implémenter quasiment toute les méthodes dans chaque classe abstraite pour que ça ne fasse rien : POURQUOI ?
Oui, pourquoi écrire du code qui ne fait rien ?
- ça va très bien avec mon bot de type Man In the Middle
- ça permet de ne définir que ce qui nous intéresse
- c'est très efficace avec l'architecture à base de plugin que j'ai retenu pour mon bot.
- une fois cette partie rébarbative écrite on gagne beaucoup de temps.
Exemple de classe abstraite pour le chat.
Cliquez pour révéler
Cliquez pour masquer
package dofus.aks.processor.nullImpl;
import dofus.aks.processor.listeners.ChatProcessorListener;
public class NullChatProcessor implements ChatProcessorListener {
@Override
public void onMessage(boolean b, String substring) {
// TODO Auto-generated method stub
}
@Override
public void onServerMessage(String substring) {
// TODO Auto-generated method stub
}
@Override
public void onSmiley(String substring) {
// TODO Auto-generated method stub
}
@Override
public void onSubscribeChannel(String substring) {
// TODO Auto-generated method stub
}
}
J'ai décidé de faire précéder le nom de mes classe par Null pour mettre en évidence que cette implementation ne fait rien.
Encore une fois, Eclipse a fait 90% pour moi.
La partie pénible à écrire c'est l'implémentation de ProcessorListener car j'ai décidé de créer les objets en mode paresseux (lazy)
L'intéret de cette pratique est que l'objet est créé à la demande et tant qu'il n'est pas nécessaire il ne prends pas de place en mémoire.
Cliquez pour révéler
Cliquez pour masquer
package dofus.aks.processor.nullImpl;
import java.io.IOException;
import dofus.aks.processor.listeners.AccountProcessorListener;
import dofus.aks.processor.listeners.BasicsProcessorListener;
import dofus.aks.processor.listeners.ChatProcessorListener;
import dofus.aks.processor.listeners.ConquestProcessorListener;
import dofus.aks.processor.listeners.DialogProcessorListener;
import dofus.aks.processor.listeners.DocumentsProcessorListener;
import dofus.aks.processor.listeners.EmotesProcessorListener;
import dofus.aks.processor.listeners.EnemiesProcessorListener;
import dofus.aks.processor.listeners.ExchangeProcessorListener;
import dofus.aks.processor.listeners.FightsProcessorListener;
import dofus.aks.processor.listeners.FriendsProcessorListener;
import dofus.aks.processor.listeners.GameActionsProcessorListener;
import dofus.aks.processor.listeners.GameProcessorListener;
import dofus.aks.processor.listeners.GuildProcessorListener;
import dofus.aks.processor.listeners.HousesProcessorListener;
import dofus.aks.processor.listeners.InfosProcessorListener;
import dofus.aks.processor.listeners.ItemsProcessorListener;
import dofus.aks.processor.listeners.JobProcessorListener;
import dofus.aks.processor.listeners.KeysProcessorListener;
import dofus.aks.processor.listeners.MountProcessorListener;
import dofus.aks.processor.listeners.PartyProcessorListener;
import dofus.aks.processor.listeners.ProcessorListener;
import dofus.aks.processor.listeners.QuestsProcessorListener;
import dofus.aks.processor.listeners.SpecializationProcessorListener;
import dofus.aks.processor.listeners.SpellsProcessorListener;
import dofus.aks.processor.listeners.StoragesProcessorListener;
import dofus.aks.processor.listeners.SubareasProcessorListener;
import dofus.aks.processor.listeners.SubwayProcessorListener;
import dofus.aks.processor.listeners.TutorialProcessorListener;
import dofus.aks.processor.listeners.WaypointsProcessorListener;
public abstract class NullProtocolProcessor implements ProcessorListener {
protected AccountProcessorListener account;
protected BasicsProcessorListener basics;
protected ChatProcessorListener chat;
protected ConquestProcessorListener conquest;
protected DialogProcessorListener dialog;
protected DocumentsProcessorListener documents;
protected EmotesProcessorListener emotes;
protected EnemiesProcessorListener enemies;
protected ExchangeProcessorListener exchange;
protected FightsProcessorListener fight;
protected FriendsProcessorListener friends;
protected GameProcessorListener game;
protected GameActionsProcessorListener gameActions;
protected GuildProcessorListener guild;
protected HousesProcessorListener houses;
protected InfosProcessorListener infos;
protected ItemsProcessorListener items;
protected JobProcessorListener job;
protected KeysProcessorListener keys;
protected MountProcessorListener mount;
protected PartyProcessorListener party;
protected QuestsProcessorListener quest;
protected SpecializationProcessorListener specialisation;
protected SpellsProcessorListener spells;
protected StoragesProcessorListener storage;
protected SubareasProcessorListener subareas;
protected SubwayProcessorListener subway;
protected TutorialProcessorListener tutorial;
protected WaypointsProcessorListener waypoints;
@Override
public AccountProcessorListener getAccount() {
if(account == null) {
account = new NullAccountProcessor();
}
return account;
}
@Override
public BasicsProcessorListener getBasics() {
if(basics == null) {
basics = new NullBasicsProcessor();
}
return basics;
}
@Override
public ChatProcessorListener getChat() {
if(chat == null) {
chat = new NullChatProcessor();
}
return chat;
}
@Override
public ConquestProcessorListener getConquest() {
if(conquest == null) {
conquest = new NullConquestProcessor();
}
return conquest;
}
@Override
public DialogProcessorListener getDialog() {
if(dialog == null) {
dialog = new NullDialogProcessor();
}
return dialog;
}
@Override
public DocumentsProcessorListener getDocuments() {
if(documents == null) {
documents = new NullDocumentsProcessor();
}
return documents;
}
@Override
public EmotesProcessorListener getEmotes() {
if(emotes == null) {
emotes = new NullEmotesProcessor();
}
return emotes;
}
@Override
public EnemiesProcessorListener getEnemies() {
if(enemies == null) {
enemies = new NullEnemiesProcessor();
}
return enemies;
}
@Override
public ExchangeProcessorListener getExchange() {
if(exchange == null) {
exchange = new NullExchangeProcessor();
}
return exchange;
}
@Override
public FightsProcessorListener getFights() {
if(fight == null) {
fight = new NullFightsProcessor();
}
return fight;
}
@Override
public FriendsProcessorListener getFriends() {
if(friends == null) {
friends = new NullFriendsProcessor();
}
return friends;
}
@Override
public GameProcessorListener getGame() {
if(game == null) {
game = new NullGameProcessor();
}
return game;
}
@Override
public GameActionsProcessorListener getGameActions() {
if(gameActions == null) {
gameActions = new NullGameActionsProcessor();
}
return gameActions;
}
@Override
public GuildProcessorListener getGuild() {
if(guild == null) {
guild = new NullGuildProcessor();
}
return guild;
}
@Override
public HousesProcessorListener getHouses() {
if(houses == null) {
houses = new NullHousesProcessor();
}
return houses;
}
@Override
public InfosProcessorListener getInfos() {
if(infos == null) {
infos = new NullInfosProcessor();
}
return infos;
}
@Override
public ItemsProcessorListener getItems() {
if(items == null) {
items = new NullItemsProcessor();
}
return items;
}
@Override
public JobProcessorListener getJob() {
if(job == null) {
job = new NullJobProcessor();
}
return job;
}
@Override
public KeysProcessorListener getKey() {
if(keys == null) {
keys = new NullKeysProcessor();
}
return keys;
}
@Override
public MountProcessorListener getMount() {
if(mount == null) {
mount = new NullMountProcessor();
}
return mount;
}
@Override
public PartyProcessorListener getParty() {
if(party == null) {
party = new NullPartyProcessor();
}
return party;
}
@Override
public QuestsProcessorListener getQuests() {
if(quest == null) {
quest = new NullQuestProcessor();
}
return quest;
}
@Override
public SpecializationProcessorListener getSpecialization() {
if(specialisation == null) {
specialisation = new NullSpecializationProcessor();
}
return specialisation;
}
@Override
public SpellsProcessorListener getSpells() {
if(spells == null){
spells = new NullSpellsProcessor();
}
return spells;
}
@Override
public StoragesProcessorListener getStorages() {
if(storage == null) {
storage = new NullStoragesProcessor();
}
return storage;
}
@Override
public SubareasProcessorListener getSubareas() {
if(subareas == null) {
subareas = new NullSubareasProcessor();
}
return subareas;
}
@Override
public SubwayProcessorListener getSubway() {
if(subway == null) {
subway = new NullSubwayProcessor();
}
return subway;
}
@Override
public TutorialProcessorListener getTutorial() {
if(tutorial == null) {
tutorial = new NullTutorialProcessor();
}
return tutorial;
}
@Override
public WaypointsProcessorListener getWaypoints() {
if(waypoints == null) {
waypoints = new NullWaypointsProcessor();
}
return waypoints;
}
@Override
public void onPong() throws IOException {
}
@Override
public void onQuickPong() throws IOException {
}
@Override
public void onResponsePong(String string) {
}
}
Une fois tout ça écrit on a un superbe parseur qui ne fait rien. Dans le prochain tuto, j'utiliserai ce parseur pour décrypter les maps.
J'ai écrit tout ça d'un trait et il est possible que je sois allé un peu vite sur certaines parties.
Pour approfondir la programmation objet je vous recommande de lire http://abrillant.developpez.com/tutorie ... roduction/
car même si c'est le Java qui est pris en exemple, la grande majorité des infos restent valables dans n'importe quel langage objet.
J'ai ajouté les sources de mon parseur vide pour exemple :
--Xvolks