GroupeZ Docs
AddonszAuctionHouseStats

API Développeur

Lire les classements, les statistiques des joueurs et les prix des objets de zAuctionHouseStats depuis votre propre plugin

zAuctionHouseStats expose une petite API en lecture seule via le ServicesManager de Bukkit. Elle permet à votre plugin de lire les mêmes classements, statistiques de joueurs et prix d'objets que ceux utilisés par les commandes, les placeholders et les inventaires de l'addon.

Tous les types publics se trouvent dans le package fr.maxlego08.stats.api :

TypeNatureDescription
StatsManagerInterfacePoint d'entrée : classements, statistiques des joueurs, prix des objets
LeaderboardMetricEnumLes 6 classements disponibles
StatsPeriodEnumFenêtres calendaires : DAY, WEEK, MONTH, ALL
LeaderboardEntryRecordUne ligne d'un classement
PlayerStatsSnapshotRecordFiche de statistiques complète d'un joueur
ItemPriceStatsRecordNombre de ventes, prix moyen et prix médian d'un objet

L'API est stable sur toutes les versions 2.x : aucune constante de LeaderboardMetric ou de StatsPeriod n'est supprimée en 2.x.

Dépendance

zAuctionHouseStats est un addon commercial : son build est publié sur le repository GroupeZ privé (https://repo.groupez.dev/private), qui nécessite des identifiants. Si vous n'y avez pas accès, compilez directement contre le jar du plugin.

Compiler contre le jar

Placez zAuctionHouseStats.jar dans un dossier libs/ de votre projet.

Gradle (Kotlin DSL)

dependencies {
    compileOnly(files("libs/zAuctionHouseStats.jar"))
}

Maven

<dependency>
    <groupId>fr.maxlego08.stats</groupId>
    <artifactId>zauctionhousestats</artifactId>
    <version>2.0.0</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/libs/zAuctionHouseStats.jar</systemPath>
</dependency>

Depuis le repository privé

Si votre compte a accès au repository privé :

repositories {
    maven {
        name = "groupezPrivate"
        url = uri("https://repo.groupez.dev/private")
        credentials(PasswordCredentials::class)
    }
}

dependencies {
    compileOnly("fr.maxlego08.stats:zauctionhousestats:2.0.0")
}

Utilisez toujours compileOnly (Gradle) ou provided / system (Maven) : n'intégrez (shade) jamais zAuctionHouseStats dans votre plugin. Les classes sont fournies à l'exécution par l'addon installé.

plugin.yml

Déclarez l'addon comme dépendance afin qu'il soit activé avant votre plugin :

depend:
  - zAuctionHouseStats
# ou, si votre plugin peut fonctionner sans :
softdepend:
  - zAuctionHouseStats

Obtenir le StatsManager

Le StatsManager est enregistré dans le ServicesManager tout à la fin du démarrage de l'addon (une fois la base de données, les caches, les menus, les placeholders et les commandes prêts), de sorte que l'API n'est jamais exposée dans un état à moitié initialisé. Il est désenregistré lorsque l'addon est désactivé.

import fr.maxlego08.stats.api.StatsManager;
import org.bukkit.Bukkit;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;

public class MyPlugin extends JavaPlugin {

    private StatsManager statsManager;

    @Override
    public void onEnable() {
        RegisteredServiceProvider<StatsManager> registration =
            Bukkit.getServicesManager().getRegistration(StatsManager.class);

        if (registration == null) {
            getLogger().warning("zAuctionHouseStats introuvable, fonctionnalités de statistiques désactivées.");
            return;
        }

        this.statsManager = registration.getProvider();
        getLogger().info("Connecté à zAuctionHouseStats !");
    }
}

Si zAuctionHouseStats ne parvient pas à démarrer (par exemple lorsque sa configuration ne peut pas être satisfaite), il se désactive et n'enregistre jamais le service : getRegistration renvoie alors null.

Garanties de threading

Chaque méthode de StatsManager renvoie un CompletableFuture et est servie depuis les caches de l'addon. Le contrat est le suivant :

GarantieDétails
Jamais bloquantAucun appel n'exécute de SQL synchrone ni ne bloque le thread appelant. Vous pouvez appeler l'API depuis le thread principal, un thread de région ou un thread asynchrone.
Cache chaudLe future renvoyé est déjà complété.
Valeur périméeLorsque la valeur en cache est plus ancienne que son TTL, l'ancienne valeur est renvoyée immédiatement (future déjà complété) et un rafraîchissement en arrière-plan est déclenché.
Cache froidLe future se complète lorsque le premier chargement asynchrone se termine.
Chargements regroupésLes requêtes simultanées pour une même clé partagent une seule requête en base de données (50 appels à froid = 1 requête).
Possiblement périméLes valeurs peuvent avoir jusqu'à un TTL de cache d'ancienneté (voir la section caches de la configuration et la Source des données ci-dessous).
Copies défensivesChaque appelant reçoit sa propre copie du future : appeler cancel, complete ou orTimeout dessus n'affecte jamais les autres appelants.
Thread de complétionLes futures se complètent sur le thread de stockage de l'addon (zAHStats-Storage), pas sur le thread principal.

Revenez sur le bon thread

Comme les futures se complètent sur le thread zAHStats-Storage, vous devez revenir sur le thread principal / de région / de l'entité avant de toucher à l'état du jeu (inventaires, entités, mondes). Sur Folia, utilisez le scheduler d'entité du joueur, par exemple via le scheduler de zAuctionHouse : auctionPlugin.getScheduler().runAtEntity(player, task -> ...). N'appelez jamais join() ou get() sur ces futures depuis le thread principal.

Durées de vie des caches utilisés par l'API :

MéthodeClé du TTL de cacheDéfaut
getLeaderboardcaches.leaderboard-ttl-seconds60
getPlayerStatscaches.player-stats-ttl-seconds60
getItemPriceStatisticscaches.material-price-ttl-seconds3600

Échecs et arrêt

SituationRésultat
Un rafraîchissement échoue alors qu'une valeur est déjà en cacheLa valeur précédente est conservée et renvoyée ; un avertissement est loggé et la tentative suivante a lieu après un TTL complet.
Le tout premier chargement d'une clé échoueLe future se complète exceptionnellement. Tant qu'un TTL complet ne s'est pas écoulé, les appels suivants pour cette clé renvoient un future en échec (la base de données n'est pas surchargée).
L'addon est en cours d'arrêtLes futures en cours se complètent immédiatement avec la dernière valeur en cache, ou exceptionnellement avec une CancellationException si aucune valeur n'a jamais été chargée.

Gérez toujours le cas exceptionnel, par exemple avec exceptionally(...) ou whenComplete(...).

StatsManager

public interface StatsManager {

    CompletableFuture<List<LeaderboardEntry>> getLeaderboard(LeaderboardMetric metric, StatsPeriod period,
                                                             String economyName);

    CompletableFuture<PlayerStatsSnapshot> getPlayerStats(UUID uniqueId);

    CompletableFuture<ItemPriceStats> getItemPriceStatistics(String economyName, Material material);

    CompletableFuture<ItemPriceStats> getItemPriceStatistics(String economyName, String material,
                                                             Integer customModelData, StatsPeriod period);
}

getLeaderboard

CompletableFuture<List<LeaderboardEntry>> getLeaderboard(LeaderboardMetric metric, StatsPeriod period, String economyName)

Renvoie le top N d'un classement, où N vaut leaderboards.size (défaut 10, borné à [1, 100]). La liste renvoyée est immuable et triée par rang.

ParamètreDescription
metricLe classement, voir LeaderboardMetric. Obligatoire (NullPointerException si null).
periodLa fenêtre calendaire, voir StatsPeriod. null = ALL.
economyNameLe nom de l'économie zAuctionHouse (les espaces autour sont supprimés). null ou vide = l'économie par défaut pour les métriques monétaires, toutes les économies cumulées pour les métriques de comptage. Un nom non vide restreint toujours le classement à cette économie.

Cas particuliers :

SituationRésultat
La métrique est listée dans leaderboards.disabledFuture complété avec une liste vide, sans requête
Une métrique monétaire est demandée sans économie et aucune économie par défaut ne peut être résolueFuture complété avec une liste vide
Le classement compte moins de N joueursUne liste plus courte

L'économie par défaut est leaderboards.default-economy, ou l'économie par défaut de zAuctionHouse lorsque cette clé est vide. Les métriques monétaires sont toujours calculées pour une seule économie : les montants de différentes économies ne sont jamais additionnés.

getPlayerStats

CompletableFuture<PlayerStatsSnapshot> getPlayerStats(UUID uniqueId)

Renvoie la fiche de statistiques d'un joueur, voir PlayerStatsSnapshot. uniqueId est obligatoire (NullPointerException si null). Un joueur sans aucune statistique enregistrée renvoie un snapshot vide (isEmpty() vaut true), jamais null.

Les joueurs en ligne sont préchargés à leur connexion : leur snapshot est donc généralement déjà en cache.

getItemPriceStatistics (Material)

CompletableFuture<ItemPriceStats> getItemPriceStatistics(String economyName, Material material)

Statistiques de prix d'un matériau depuis toujours, sans filtre de custom model data. Équivaut à getItemPriceStatistics(economyName, material.name(), null, StatsPeriod.ALL).

ParamètreDescription
economyNameNom de l'économie. null ou vide = économie par défaut.
materialLe matériau Bukkit. Obligatoire (NullPointerException si null).

getItemPriceStatistics (complète)

CompletableFuture<ItemPriceStats> getItemPriceStatistics(String economyName, String material,
                                                         Integer customModelData, StatsPeriod period)
ParamètreDescription
economyNameNom de l'économie. null ou vide = économie par défaut.
materialNom du matériau Bukkit, insensible à la casse ("diamond" = "DIAMOND"). null ou vide = filtre uniquement sur customModelData, tous matériaux confondus.
customModelDataFiltre sur le custom model data de l'objet vendu. null = aucun filtre.
periodFenêtre calendaire. null = ALL.
SituationRésultat
material est null / vide et customModelData est nullIllegalArgumentException levée immédiatement (pas via le future)
Aucune économie ne peut être résolueFuture complété avec un ItemPriceStats vide (nombre 0, moyenne et médiane 0.00)
Aucune vente correspondanteFuture complété avec un ItemPriceStats vide

Les prix sont calculés sur le prix de mise en vente des ventes conclues. Dans les fenêtres DAY / WEEK / MONTH, seules les ventes réalisées depuis minuit du premier jour de la fenêtre (dans statistics.timezone) sont comptées.

Types

LeaderboardMetric

ConstanteValeur classéeMétrique monétaireDescription
TOP_SELLERS_MONEYSomme de l'argent reçu par le vendeurOuiArgent réellement gagné par les vendeurs, net de taxes
TOP_BUYERS_MONEYSomme de l'argent payé par l'acheteurOuiArgent réellement dépensé par les acheteurs, taxes incluses
TOP_SELLERS_COUNTNombre de ventesNonVentes conclues en tant que vendeur
TOP_BUYERS_COUNTNombre d'achatsNonAchats conclus en tant qu'acheteur
TOP_TAX_PAIDSomme des taxes payéesOuiTaxes supportées par le joueur (part de la taxe d'achat + taxe de mise en vente)
BIGGEST_SALEVente unique maximaleOuiPlus grosse vente unique reçue en tant que vendeur

Méthodes :

MéthodeType de retourDescription
isMoney()booleantrue pour les métriques monétaires : une économie est toujours requise (l'économie par défaut si aucune n'est fournie), et la valeur peut être formatée avec le format d'économie de zAuctionHouse
getAggregation()LeaderboardMetric.AggregationSUM ou MAX (MAX uniquement pour BIGGEST_SALE)
getAllTimeColumn()StringColonne interne de la base de données utilisée pour la période ALL
getDailyColumn()StringColonne interne de la base de données utilisée pour DAY / WEEK / MONTH, null pour BIGGEST_SALE

StatsPeriod

Les périodes sont des fenêtres calendaires, calculées dans le fuseau horaire défini par statistics.timezone (vide = fuseau horaire du serveur) :

ConstanteFenêtre
DAYAujourd'hui, depuis minuit
WEEKDepuis lundi (semaine ISO)
MONTHDepuis le 1er du mois en cours
ALLDepuis toujours

Méthodes :

MéthodeType de retourDescription
isWindowed()booleantrue pour toutes les constantes sauf ALL
minEpochDay(ZoneId zone)longPremier epoch-day inclus dans la fenêtre. Lève IllegalStateException pour ALL
minEpochDay(Clock clock, ZoneId zone)longIdem, avec une horloge injectée
minEpochDay(LocalDate today)longPremier epoch-day de la fenêtre contenant today. Lève IllegalStateException pour ALL
windowStart(Clock clock, ZoneId zone)InstantMinuit du premier jour de la fenêtre. Lève IllegalStateException pour ALL

LeaderboardEntry

public record LeaderboardEntry(int rank, UUID uuid, String name, BigDecimal value) {}
ChampTypeDescription
rankintPosition, de 1 à N. Les rangs sont positionnels (1, 2, 3…) : les ex æquo ne partagent jamais un rang et sont départagés par UUID
uuidUUIDLe joueur
nameStringDernier nom connu du joueur, ou leaderboards.unknown-name (défaut ?) s'il est inconnu
valueBigDecimalLa valeur classée. Échelle 2 pour les métriques monétaires, échelle 0 pour les comptages

PlayerStatsSnapshot

public record PlayerStatsSnapshot(UUID uniqueId, String name, Map<String, PlayerEconomyStats> byEconomy,
                                  Map<PeriodKey, PlayerPeriodStats> periods, long loadedAt) {}
ChampTypeDescription
uniqueIdUUIDLe joueur
nameStringDernier nom connu, null s'il est inconnu
byEconomyMap<String, PlayerEconomyStats>Totaux depuis toujours, indexés par nom d'économie (immuable)
periodsMap<PeriodKey, PlayerPeriodStats>Totaux des fenêtres DAY, WEEK et MONTH, indexés par (période, économie) (immuable)
loadedAtlongDate du chargement, en millisecondes epoch
MéthodeType de retourDescription
economy(String economyName)Optional<PlayerEconomyStats>Totaux depuis toujours d'une économie, vide si le joueur ne l'a jamais utilisée
period(StatsPeriod period, String economyName)Optional<PlayerPeriodStats>Totaux d'une fenêtre pour une économie, vide s'il n'y a eu aucune activité dans cette fenêtre
isEmpty()booleantrue si le joueur n'a aucune statistique enregistrée

PlayerStatsSnapshot.PeriodKey

public record PeriodKey(StatsPeriod period, String economyName) {}

period vaut DAY, WEEK ou MONTH (jamais ALL, qui est servi par byEconomy).

PlayerStatsSnapshot.PlayerEconomyStats

Totaux depuis toujours d'une économie.

ChampTypeDescription
itemsListedlongObjets mis en vente
itemsSoldlongObjets vendus
itemsBoughtlongObjets achetés
itemsExpiredlongMises en vente expirées sans avoir été vendues
itemsRemovedlongMises en vente retirées de la vente
moneyEarnedBigDecimalArgent gagné en tant que vendeur, net de taxes
moneySpentBigDecimalArgent dépensé en tant qu'acheteur, taxes incluses
taxPaidBigDecimalTaxes payées
biggestSaleBigDecimalPlus grosse vente unique reçue
firstSaleAtInstantPremière vente, null si aucune
lastSaleAtInstantDernière vente, null si aucune
firstPurchaseAtInstantPremier achat, null si aucun
lastPurchaseAtInstantDernier achat, null si aucun

PlayerStatsSnapshot.PlayerPeriodStats

Totaux d'une fenêtre (DAY / WEEK / MONTH) pour une économie.

ChampTypeDescription
itemsSoldlongObjets vendus
itemsBoughtlongObjets achetés
itemsListedlongObjets mis en vente
moneyEarnedBigDecimalArgent gagné, net de taxes
moneySpentBigDecimalArgent dépensé, taxes incluses
taxPaidBigDecimalTaxes payées

ItemPriceStats

public record ItemPriceStats(long count, BigDecimal average, BigDecimal median) {}
ChampTypeDescription
countlongNombre exact de ventes correspondantes
averageBigDecimalPrix moyen (échelle 2), 0 si aucune vente
medianBigDecimalPrix médian (échelle 2), calculé sur les ventes les plus récentes, dans la limite de statistics.material-prices.median-sample-limit (défaut 50000) ; 0 si aucune vente
MéthodeType de retourDescription
isEmpty()booleantrue si aucune vente ne correspond (count == 0)

Exemples

Afficher les meilleurs vendeurs du jour

import fr.maxlego08.stats.api.LeaderboardEntry;
import fr.maxlego08.stats.api.LeaderboardMetric;
import fr.maxlego08.stats.api.StatsManager;
import fr.maxlego08.stats.api.StatsPeriod;
import fr.maxlego08.zauctionhouse.api.AuctionPlugin;
import org.bukkit.entity.Player;

public void sendDailyTop(AuctionPlugin auctionPlugin, StatsManager stats, Player player) {
    stats.getLeaderboard(LeaderboardMetric.TOP_SELLERS_MONEY, StatsPeriod.DAY, null) // économie par défaut
        .whenComplete((entries, throwable) -> {
            // Retour sur le thread du joueur avant d'envoyer des messages / de toucher à l'état du jeu
            auctionPlugin.getScheduler().runAtEntity(player, task -> {
                if (throwable != null) {
                    player.sendMessage("Les statistiques sont indisponibles pour le moment.");
                    return;
                }
                if (entries.isEmpty()) {
                    player.sendMessage("Aucune vente aujourd'hui.");
                    return;
                }
                for (LeaderboardEntry entry : entries) {
                    player.sendMessage("#" + entry.rank() + " " + entry.name() + " - " + entry.value());
                }
            });
        });
}

Lire les statistiques d'un joueur

import fr.maxlego08.stats.api.PlayerStatsSnapshot;
import fr.maxlego08.stats.api.StatsPeriod;

stats.getPlayerStats(player.getUniqueId()).thenAccept(snapshot -> {
    if (snapshot.isEmpty()) return;

    // Totaux depuis toujours pour l'économie "vault"
    snapshot.economy("vault").ifPresent(total -> {
        long sold = total.itemsSold();
        java.math.BigDecimal earned = total.moneyEarned();
        // ...
    });

    // Totaux de la semaine pour la même économie
    snapshot.period(StatsPeriod.WEEK, "vault").ifPresent(week -> {
        long boughtThisWeek = week.itemsBought();
        // ...
    });
});

Obtenir le prix moyen d'un objet

import fr.maxlego08.stats.api.ItemPriceStats;
import fr.maxlego08.stats.api.StatsPeriod;
import org.bukkit.Material;

// Depuis toujours, économie par défaut
stats.getItemPriceStatistics(null, Material.DIAMOND).thenAccept(prices -> {
    if (prices.isEmpty()) return; // jamais vendu
    java.math.BigDecimal average = prices.average();
    java.math.BigDecimal median = prices.median();
    long sales = prices.count();
});

// Ce mois-ci, économie "vault", uniquement les objets avec le custom model data 1001
stats.getItemPriceStatistics("vault", "PAPER", 1001, StatsPeriod.MONTH)
    .thenAccept(prices -> { /* ... */ });

N'appelez jamais join() ou get() sur ces futures depuis le thread principal : avec un cache froid, cela attendrait une requête en base de données. Enchaînez toujours avec thenAccept, whenComplete, etc., puis revenez sur le bon thread.

Source des données : événements zAuctionHouse

zAuctionHouseStats n'expose aucun événement propre. Il collecte ses données en écoutant (en priorité MONITOR, sans jamais les modifier) les événements de zAuctionHouse V4. Les événements dédiés aux statistiques se trouvent dans le package fr.maxlego08.zauctionhouse.api.event.events.stats :

ÉvénementDéclenché quandUtilisé pour
AuctionItemListedEventUn objet a été mis en vente avec succèsMises en vente, taxe de mise en vente
AuctionItemPurchasedEventUn achat a été entièrement validé (une fois par achat, sur le serveur de l'acheteur)Ventes, argent gagné/dépensé, taxes, prix des objets, classements
AuctionMoneyClaimEventUn joueur a récupéré de l'argent en attenteRécupérations d'argent par économie
AuctionOpenEventUn joueur ouvre (ou revient sur) l'inventaire principal de l'hôtel des ventesCompteur d'activité

Ces quatre événements sont purement informatifs (non annulables) et sont déclenchés de manière synchrone sur le thread principal (le thread de la région globale sur Folia).

L'addon écoute également des événements existants de zAuctionHouse :

ÉvénementPackageUtilisé pour
AuctionExpireEventfr.maxlego08.zauctionhouse.api.event.eventsMises en vente expirées sans avoir été vendues (uniquement les objets du stockage LISTED)
AuctionRemoveListedItemEventfr.maxlego08.zauctionhouse.api.event.events.removeMises en vente retirées de la vente (attribuées au vendeur, même lorsqu'un administrateur retire l'objet)
AuctionRemoveExpiredItemEventfr.maxlego08.zauctionhouse.api.event.events.removeMise à jour des noms des joueurs
AuctionRemovePurchasedItemEventfr.maxlego08.zauctionhouse.api.event.events.removeMise à jour des noms des joueurs

Les gestionnaires d'événements ne touchent jamais à la base de données : ils se contentent de mettre les données en file d'attente, qui sont écrites de manière asynchrone par lots (voir flush.interval-seconds dans la configuration). Conséquences pour l'API :

  • Un PlayerStatsSnapshot déjà en cache sur le serveur où l'activité a eu lieu est mis à jour immédiatement en mémoire, sans aucune requête.
  • Après chaque flush, les caches des joueurs concernés sont rafraîchis depuis la base de données (ce qui récupère aussi l'activité enregistrée par les autres serveurs d'un réseau), et les prix d'objets en cache qui n'avaient encore aucune vente sont rafraîchis lorsque ce matériau est vendu.
  • Les classements et les autres prix d'objets en cache prennent en compte la nouvelle activité à l'expiration de leur TTL de cache.

Les événements de statistiques nécessitent un build récent de zAuctionHouse V4. Avec collection.mode: AUTO, si le zAuctionHouse installé ne les fournit pas, l'addon peut se rabattre sur la lecture de la table des logs de zAuctionHouse (uniquement lorsque collection.polling.enabled vaut true). Il s'agit d'un mode dégradé : les montants sont approximatifs (prix de mise en vente, taxes inconnues) et les expirations ainsi que les ouvertures de l'hôtel des ventes ne sont pas enregistrées. L'API est identique dans les deux modes. Voir Installation et Configuration.

Pour écouter vous-même ces événements, consultez la page Événements de zAuctionHouse.

Sur cette page

GroupeZ MCP

La documentation dans ton IA

Le MCP permet à ton IA de chercher dans la documentation GroupeZ et de lire les exemples à jour pour t’aider à configurer tes plugins.

Lecture seule · Sans clé API côté documentation

1. Copie l’adresse du serveur

Transport : HTTP (Streamable HTTP). Authentification : aucune. Ton outil utilise son propre modèle et ses propres limites d’abonnement.

  1. Ouvre Plugins dans ChatGPT sur le web, puis + → Ajouter un serveur MCP personnalisé.
  2. Nomme-le « GroupeZ Documentation », colle l’URL ci-dessus et choisis « Aucune authentification ». Crée puis installe le plugin.
  3. Dans une conversation, tape @ et sélectionne ce plugin avant d’envoyer ta question. Si nécessaire, active search, get_page et list_pages dans ses paramètres.
Guide officiel de cet outil

3. Pose une question

Utilise le MCP GroupeZ pour m’aider à créer un inventaire zMenu. Recherche les pages pertinentes, lis les exemples et cite tes sources.
Autres clients et dépannage

Pour les clients utilisant mcpServers, adapte cet exemple à leur format :

{
  "mcpServers": {
    "groupez-docs": {
      "url": ""
    }
  }
}

Les outils disponibles sont search, get_page et list_pages. Après une mise à jour, actualise les outils du connecteur. Si ton IA n’y accède pas, vérifie que le connecteur est activé dans la conversation et que ton compte autorise les MCP personnalisés. Ouvrir l’URL dans un navigateur n’est pas un test MCP.