Le but de cet article est d’expliquer les APIs disponibles pour les retroachievements qui s’affiche dans un thème comme le thème de Pixl (basé sur le thème GameOS).
En fait, cela utilise une api existant: « Game » avec des nouvelles fonctions, setter, getter et autres properties
Le principe est donc de pouvoir savoir si un jeu possède des retroachievements ou pas, en calculant le hash de la rom (hash spécifique pour cela), pour trouver son GameID puis récupérer la liste des achievements pour un jeu. Ensuite, on veut pouvoir savoir si c’est un ‘achievement’ déjà acquis par l’utilisateur donc un compte valide est nécessaire (utilisant le login et password configurable dans le menu de pegasus)
Pour l’instant, les systèmes possédant déjà des jeux sont les suivants (mais plus de systèmes sont potentiellement déjà supportés mais pas encore de retroachievements ou d’émulateur le supportant) :
- Nintendo
- Atari
- NEC
- Sega
- Sony
- Other
Pour information, le hash est calculé de différentes manières en fonction du système:
- Pour les consoles (hors format cd), le hash est calculé sur la rom (avec ou sans header) et dois être dézippé avant dans /tmp si besoin. La rom sera supprimé automatiquement après le calcul.
- Pour les formats CD, le hash est calculé sur les secteurs directement et pas sur la totalité du CD bien sur mais cela demande plus de temps de calcul.
- Et pour finir, pour l’arcade, le hash est calculé sur le nom de la rom au fomat xxxxxxxx.zip
Les nouvelles DATA disponibles
Une nouvelle structure de donnée est disponible dans l’objet « Game » via une QList de « Retroachievement » :
struct RetroAchievement {
int ID;
QString Title;
QString Description;
int Points;
QString Author;
QString BadgeName;
int Flags;
bool Unlocked;
bool HardcoreMode;
};
Pour le QML, on ne peut pas accéder à ce type de ‘Qlist’ directement. il faut pouvoir « invoquer » des fonctions pour récupérer ces données.
Mais il a aussi 3 autres « getter » utilisables et qui sont stockés dans « Game » aussi, le premier pour savoir si le jeu a un GameID de la base de retroachievement, le second pour savoir si un Hash a été calculé précédemment pour ce jeu, et pour finir le dernier est pour savoir combien de retroachievement sont disponibles pour ce jeu :
Q_PROPERTY(int RaGameID READ RaGameID CONSTANT)
Q_PROPERTY(QString RaHash READ RaHash CONSTANT)
Q_PROPERTY(int retroAchievementsCount READ getRetroAchievementsCount CONSTANT)
Les fonctions disponibles
La liste des fonctions sont les suivantes et c’est celles qui seront appelées par le QML :
//Functions to init/update retroachievements
Q_INVOKABLE void updateRetroAchievements();
Q_INVOKABLE void initRetroAchievements();
//Functions to get information of any retroachievements using its index in QList
Q_INVOKABLE QString GetRaTitleAt (const int index);
Q_INVOKABLE QString GetRaDescriptionAt (const int index);
Q_INVOKABLE QString GetRaPointsAt (const int index);
Q_INVOKABLE QString GetRaAuthorAt (const int index);
Q_INVOKABLE QString GetRaBadgeAt (const int index);
Q_INVOKABLE bool isRaUnlockedAt (const int index);
Q_INVOKABLE bool isRaHardcoreAt (const int index);
Utilisations de l’API
1) Exemple d’appel pour lancer la recherche « initial » de retroachievements pour un jeu:
onGameChanged: { console.log("GameView - onGameChanged"); //reset default value for a new game loading reset(); //launch initialization of retroachievements //the initialization is done in a separate thread to avoid conflicts and blocking in user interface) game.initRetroAchievements(); }
En fait, « initRetroAchievements » va essayer de récupérer et calculer une seul fois le hash par jeu. L’utilisation d’un cache est là pour aider à garder le hash, le gameid et les jsons retrouvé à partir du site de retroachievements.
Attention: du fait que cela peut prendre plusieurs secondes, la fonction doit pouvoir être appelé par le QML et dans une vue détaillé du jeu. Il est déconseillé de vouloir le faire sur une liste système avec plusieurs lancement de cette fonction en parrallèle même si cela peut s’exécuter en tache de fond.
2) Retour de l’API pour dire que Pegasus a fini d’interroger le service de retroachievements et que l’on peut donc afficher le bouton/icone correspondant dans cet exemple:
Connections {
target: game
function onRetroAchievementsInitialized() {
//console.log("GameView - retroAchievements is now initialized !");
setRetroAchievements();
}
}
function setRetroAchievements(){
if(game.retroAchievementsCount !== 0){
button5.visible = true;
}
}
Donc on peut gérer avec un event/signal « onRetroAchievementsInitialized() » venant en retour de Pegasus qui va dire quand les infos de retroachivements sont prêtes finalement. Et dans le cas de gameOS, on joue sur la visibilité ou pas des boutons dans la fonction appelée ensuite
3) Exemple d’appel pour mettre à jour les retroachievements pour un jeu (suite à une session de jeu et on veut pouvoir voir ceux que l’on a acquis):
Component.onCompleted: { //to update retroarchievements after a game session //the update is done in a separate thread to avoid conflicts and blocking in user interface if (game.retroAchievementsCount !== 0) game.updateRetroAchievements(); }
4) Exemple pour afficher la liste des retroachievements pour un jeu donné et dans le bon statut :
model: gameData.retroAchievementsCount delegate: Component { Item{ Column { Image { Layout.fillWidth: true Layout.fillHeight: true fillMode: Image.PreserveAspectFit source: { if(game.retroAchievementsCount !== 0) { console.log("GameAchievements - game.isRaUnlockedAt(index) : ",game.GetRaBadgeAt(index), game.isRaUnlockedAt(index)); if(game.isRaUnlockedAt(index)) return "https://s3-eu-west-1.amazonaws.com/i.retroachievements.org/Badge/" + game.GetRaBadgeAt(index) + ".png"; else return "https://s3-eu-west-1.amazonaws.com/i.retroachievements.org/Badge/" + game.GetRaBadgeAt(index) + "_lock.png"; } else ""; } width:icon_size; height:icon_size smooth: true visible: true asynchronous: true } } } }
5) Exemple pour afficher les détails d’un retroachievement pour un jeu donné et dans le bon statut :
function updateDetails(index) { console.log("GameAchievements - updateDetails(index)"); if(game.retroAchievementsCount !== 0) { retroachievementstitle.text = gameData.GetRaTitleAt(index); pointstext.text = gameData.GetRaPointsAt(index); authortext.text = gameData.GetRaAuthorAt(index); descriptiontext.text = gameData.GetRaDescriptionAt(index); showDirection(); } else { retroachievementstitle.text = ""; pointstext.text = ""; authortext.text = ""; descriptiontext.text = ""; } }
Enjoy !!! 😉
P.S: C’est pas simple, si vous voulez rajouter cela dans votre thème, je vous conseille vivement de regarder l’implementation déjà faite dans le thème de Pixl basé sur le thème GameOS. En fait, il faut regarder le fichier « GameAchievements.qml » qui est dans le répertoire « Global » du thème. Le fichier « GameView.qml » et « LaunchGame.qml » ont été modifié aussi pour cet usage.





