Cosmetic particle effects for Ekaii MC — votes, events, mini-games rewards
Find a file
exo 9885c4b266
All checks were successful
Build & Release / build (push) Successful in 30s
fix: single evoker fang centered on death location
Reduced from 5 overlapping fangs to exactly 1 at the player's position.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-09 19:30:09 +02:00
.forgejo/workflows feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
gradle/wrapper feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
libs feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
src/main fix: single evoker fang centered on death location 2026-05-09 19:30:09 +02:00
.gitignore feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
build.gradle.kts feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
gradlew feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
gradlew.bat feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00
README.md docs: add comprehensive README with full documentation 2026-05-09 15:16:48 +02:00
settings.gradle.kts feat: initial EkaiiCosmetics v1.0.0-ekaii26 2026-05-09 15:10:56 +02:00

EkaiiCosmetics

Plugin de cosmétiques par particules pour les serveurs Ekaii. Les joueurs débloquent et équipent des effets visuels déclenchés par leurs actions en jeu — récompenses pour les votes, événements et mini-jeux.

MC 26.1.2 | Folia-compatible | Kotlin | Paper API natif

Table des matières


Philosophie

Les cosmétiques sont des effets visuels courts et non intrusifs, créés à l'aide de particules vanilla. Ils ne doivent jamais :

  • Gêner la visibilité des joueurs
  • Créer une obstruction visuelle
  • Provoquer une distraction excessive
  • Nuire à l'immersion du gameplay vanilla

Le système récompense l'investissement des joueurs (votes, participation aux événements, mini-jeux) sans impacter l'équilibre du jeu. Aucun cosmétique ne peut être acheté avec de l'argent réel. Les cosmétiques ne sont pas échangeables entre joueurs.


Fonctionnalités

Fonctionnalité Détail
Moteur de particules 10 shapes, 6 modifiers, rendu natif Paper
Triggers 6 types : mort, connexion, enderpearl, sneak, manger, elytra
Économie Monnaie interne "ekaii" (pas Vault)
Persistance SQLite via HikariCP, cache in-memory
GUI 3 menus : principal, par trigger, boutique
Commandes /cosmetics (joueur) + /ecadmin (admin)
Folia GlobalRegionScheduler + EntityScheduler, 0 BukkitRunnable
Intégrations NuVotifier, PlaceholderAPI, Velocity (soft-dependencies)
Effets par défaut 12 effets pré-configurés (2 par trigger)

Installation

  1. Télécharger EkaiiCosmetics-x.x.x-ekaii26.jar depuis les releases
  2. Placer dans plugins/
  3. Redémarrer le serveur
  4. Les fichiers config.yml et effects/*.yml sont extraits automatiquement

Pré-requis

  • Paper/Luminol 26.1.2 (ou fork compatible)
  • Java 25+
  • Folia-compatible : déclaré folia-supported: true

Dépendances optionnelles

Plugin Usage
NuVotifier Récompenses automatiques sur vote
PlaceholderAPI Placeholders %ekaii_*%

Ces intégrations sont chargées par réflexion — le plugin fonctionne sans elles.


Configuration

config.yml

# Base de données SQLite
database:
  file: cosmetics.db       # Fichier dans plugins/EkaiiCosmetics/

# Récompenses de vote (NuVotifier requis)
vote:
  base-reward: 1            # Ekaii par vote
  first-multiplier: 3       # x3 pour le 1er votant du jour
  second-multiplier: 2      # x2 pour le 2ème votant du jour

# Paramètres des effets
effects:
  default-range: 32         # Portée de visibilité (blocs)
  max-active-per-player: 1  # Effets simultanés par joueur
  preview-duration-ticks: 60 # Durée de la prévisualisation (3 secondes)

# Cooldowns des triggers
triggers:
  sneak-cooldown-ms: 2000   # Anti-spam sneak (2 secondes)

Catalogue cosmétiques

Les cosmétiques sont définis dans config.yml sous la clé cosmetics:. Chaque entrée :

cosmetics:
  death_smoke:                    # ID unique (snake_case)
    display-name: "Fumée Funèbre" # Nom affiché dans les menus
    description:                  # Lore (lignes multiples, §7 pour gris)
      - "§7Un nuage de fumée sombre"
      - "§7s'élève à votre mort"
    trigger: DEATH                # Type de déclencheur (voir Triggers)
    effect: death_smoke           # Nom du fichier YAML dans effects/
    rarity: COMMON                # Rareté (voir ci-dessous)
    price: 5                      # Prix en ekaii (-1 = non achetable)
    icon: COAL                    # Material Bukkit pour l'icône GUI
    exclusive: false              # true = obtention spéciale uniquement

Raretés

Rareté Couleur Prix par défaut Description
COMMON Blanc 5 ekaii Effets de base
UNCOMMON Vert 15 ekaii Effets améliorés
RARE Cyan 40 ekaii Effets élaborés
EPIC Violet 100 ekaii Effets spectaculaires
LEGENDARY Or 250 ekaii Effets exceptionnels
EXCLUSIVE Rouge Non achetable Récompenses podium/événements

Effets YAML

Chaque effet est un fichier .yml dans plugins/EkaiiCosmetics/effects/. Structure complète :

name: death_smoke               # Identifiant unique
duration: 40                     # Durée en ticks (20 ticks = 1 seconde)
range: 16                        # Portée de visibilité en blocs
repeat: false                    # true = boucle infinie (elytra trails)

layers:                          # Liste de couches de particules
  - particle: CAMPFIRE_COSY_SMOKE  # Type de particule vanilla
    shape: CIRCLE                  # Forme géométrique (voir Shapes)
    options:
      radius: 0.8                # Rayon de la forme
      particleCount: 12          # Nombre de points générés
      density: 0.8               # Multiplicateur de densité (0.1-1.0)
      speed: 0.02                # Vitesse des particules
      count: 0                   # Particules par point (0 = directionnel)
      spreadX: 0.0               # Dispersion X
      spreadY: 0.1               # Dispersion Y
      spreadZ: 0.0               # Dispersion Z
      scale: 1.0                 # Échelle (particules DUST)
      skip: 0                    # Ticks à sauter entre envois
      # Couleurs (pour DUST et DUST_COLOR_TRANSITION)
      fromRed: 255
      fromGreen: 100
      fromBlue: 50
      toRed: 50                  # Couleur d'arrivée (DUST_COLOR_TRANSITION)
      toGreen: 200
      toBlue: 255
      # Dimensions des formes
      width: 1.0                 # Largeur (LINE, RECTANGLE, CUBE)
      length: 1.0                # Longueur (RECTANGLE, CUBE)
      height: 1.0                # Hauteur (SPIRAL, CUBE, RANDOM)
      turns: 3                   # Tours (SPIRAL)
    modifiers:                   # Transformations appliquées chaque tick
      - type: MOVE
        speed: 0.5
        upward: 0.05
      - type: PULSE
        minScale: 0.8
        maxScale: 1.3
        speed: 2.0

sounds:                          # Sons joués pendant l'effet
  - name: entity_wither_ambient  # Clé son Minecraft (Registry.SOUNDS)
    volume: 0.3
    pitch: 0.5
    start: 0                     # Tick de début
    interval: 10                 # Intervalle en ticks
    end: 30                      # Tick de fin

Commandes

Joueur — /cosmetics (alias : /cos, /cosmetiques)

Commande Description
/cosmetics Ouvre le menu principal
/cosmetics menu Ouvre le menu principal
/cosmetics equip <id> Équipe un cosmétique possédé
/cosmetics unequip <trigger> Déséquipe le slot d'un trigger
/cosmetics preview <id> Prévisualise un effet (3 secondes)
/cosmetics shop Ouvre la boutique
/cosmetics balance Affiche le solde en ekaii
/cosmetics help Liste des commandes

Admin — /ecadmin

Commande Description
/ecadmin reload Recharge config + effets YAML
/ecadmin give <joueur> <montant> Donne des ekaii
/ecadmin take <joueur> <montant> Retire des ekaii
/ecadmin setbalance <joueur> <montant> Définit le solde
/ecadmin givecosmetic <joueur> <id> Donne un cosmétique
/ecadmin removecosmetic <joueur> <id> Retire un cosmétique
/ecadmin play <effet> [joueur] Joue un effet (test)
/ecadmin stop [joueur] Stoppe les effets actifs
/ecadmin list Liste tous les cosmétiques
/ecadmin listeffects Liste tous les effets chargés

Permissions

Permission Description Défaut
ekaiicosmetics.use Accès aux commandes joueur et menus true
ekaiicosmetics.preview Prévisualisation des effets true
ekaiicosmetics.admin Commandes admin /ecadmin op
ekaiicosmetics.effect.visible Voir les effets des autres joueurs true

Système économique

Monnaie "ekaii"

Monnaie interne, indépendante de Vault. Pas de lien avec l'économie vanilla.

Obtention :

Source Récompense
Vote (NuVotifier) 1 ekaii/vote (3x pour le 1er du jour, 2x pour le 2ème)
Mini-jeux 1 point = 1 ekaii (via API)
Admin /ecadmin give <joueur> <montant>

Dépense :

  • Achat de cosmétiques dans la boutique GUI ou via /cosmetics shop

Restrictions :

  • Pas d'achat en argent réel
  • Pas de transfert entre joueurs
  • Transactions historisées en base (table transactions)

Podium / Exclusifs

Les cosmétiques marqués exclusive: true et rarity: EXCLUSIVE ne sont pas achetables en boutique. Ils sont attribués via :

  • /ecadmin givecosmetic <joueur> <id> pour les places de podium
  • Systèmes d'événements externes via l'API

Triggers

Chaque trigger déclenche l'effet cosmétique équipé par le joueur dans le slot correspondant.

Trigger Événement Bukkit Comportement
DEATH PlayerDeathEvent Effet joué à l'emplacement de mort
JOIN PlayerJoinEvent Effet joué à la position du joueur (via entity scheduler)
ENDERPEARL ProjectileLaunchEvent + PlayerTeleportEvent(ENDER_PEARL) Effet au lancer ET à l'arrivée
SNEAK PlayerToggleSneakEvent Effet aux pieds, cooldown 2s anti-spam
EAT PlayerItemConsumeEvent Effet à la position du joueur
ELYTRA EntityToggleGlideEvent Effet au décollage

Moteur de particules

Le moteur de rendu tourne via Bukkit.getGlobalRegionScheduler().runAtFixedRate() à 1 tick d'intervalle. Chaque tick :

  1. Récupère la position du joueur/emplacement
  2. Filtre les joueurs en portée (distanceSquared <= range²)
  3. Pour chaque layer : génère les points de la shape, applique les modifiers
  4. Envoie les particules via Player.spawnParticle() (API Paper natif, pas de NMS)

Shapes

Shape Description Paramètres utilisés
CIRCLE Cercle plat (XZ) radius, particleCount
SPHERE Sphère 3D (algorithme Fibonacci) radius, particleCount
SPIRAL Hélice radius, height, turns, particleCount
LINE Ligne droite centrée width, particleCount
RECTANGLE Périmètre d'un rectangle width, length, particleCount
CUBE Cube fil de fer (12 arêtes) width, length, height, particleCount
RANDOM Points aléatoires dans un cylindre radius, height, particleCount
POINT Point unique à l'origine
HEART Courbe cardiaque paramétrique radius, particleCount
STAR Étoile 5 branches radius, particleCount

Modifiers

Les modifiers transforment chaque point généré par la shape à chaque tick.

Modifier Type Description Paramètres clés
ROTATE Stateful Rotation autour d'un axe axis, speed, angle, yawOrigin, pitchOrigin
MOVE Stateful Translation accumulée x/y/z, forward/sideward/upward, speed, usePitch
PULSE Stateless Pulsation sinusoïdale minScale, maxScale, speed
WAVE Stateless Oscillation verticale amplitude, frequency, speed
OSCILLATE Stateless Orbite autour d'un axe axis, x/y/z, speed
RANDOM Stateless Déplacement aléatoire strength

Stateful : accumule un état entre les ticks (angle de rotation, position). Stateless : calcul pur basé sur le tick courant.

Paramètres communs des modifiers

modifiers:
  - type: ROTATE           # Type de modifier
    speed: 1.0              # Vitesse de l'effet
    axis: Y                 # Axe (X, Y, Z, ALL)
    angle: 5.0              # Angle par tick (degrés)
    yawOrigin: false         # Aligner sur le yaw du joueur
    pitchOrigin: false       # Aligner sur le pitch du joueur
    start: 0                # Tick de début
    end: 2147483647          # Tick de fin (MAX_VALUE = toujours)
    # Translation (MOVE)
    x: 0.0                  # Offset absolu X par tick
    y: 0.0                  # Offset absolu Y par tick
    z: 0.0                  # Offset absolu Z par tick
    forward: 0.0            # Offset directionnel (avant/arrière)
    sideward: 0.0           # Offset directionnel (gauche/droite)
    upward: 0.0             # Offset directionnel (haut/bas)
    usePitch: false          # Inclure le pitch dans les calculs directionnels
    # Pulse
    minScale: 0.8           # Échelle minimale
    maxScale: 1.2           # Échelle maximale
    # Wave
    amplitude: 1.0          # Amplitude de l'oscillation
    frequency: 1.0          # Fréquence de l'oscillation
    # Random
    strength: 1.0           # Force du déplacement aléatoire

Créer un effet personnalisé

  1. Créer un fichier mon_effet.yml dans plugins/EkaiiCosmetics/effects/
  2. Définir les layers avec shapes et modifiers
  3. Ajouter l'entrée cosmétique dans config.yml sous cosmetics:
  4. /ecadmin reload
  5. Tester avec /ecadmin play mon_effet

Exemple — spirale de flammes montante :

name: flame_spiral
duration: 60
range: 24
repeat: false
layers:
  - particle: FLAME
    shape: SPIRAL
    options:
      radius: 0.5
      height: 2.0
      turns: 4
      particleCount: 40
      density: 0.9
      speed: 0.01
      count: 0
    modifiers:
      - type: ROTATE
        axis: Y
        speed: 2.0
        angle: 10.0
      - type: MOVE
        upward: 0.03
        speed: 1.0
  - particle: SOUL_FIRE_FLAME
    shape: POINT
    options:
      particleCount: 1
      speed: 0.05
      spreadX: 0.2
      spreadY: 0.3
      spreadZ: 0.2
      count: 3
sounds:
  - name: block_fire_ambient
    volume: 0.4
    pitch: 1.2
    interval: 5

Types de particules courants :

Particule Rendu Notes
FLAME Flamme orange Classique feu
SOUL_FIRE_FLAME Flamme bleue Ambiance soul
CAMPFIRE_COSY_SMOKE Fumée douce Discret
SMOKE Fumée noire Sombre
CLOUD Nuage blanc Léger
HEART Cœur rouge Manger/heal
HAPPY_VILLAGER Étincelles vertes Positif
ENCHANT Glyphes Magique
END_ROD Particule blanche brillante Élégant
PORTAL Particule violette Ender
REVERSE_PORTAL Particule violette inversée Ender inversé
DUST Point coloré (RGB) Requiert fromRed/Green/Blue, scale
DUST_COLOR_TRANSITION Transition colorée Requiert fromRGB + toRGB, scale
FIREWORK Étoile colorée Festif

GUI

Menu principal

Inventaire 54 slots. Rangée du haut décorée. Les 6 triggers sont affichés avec leur icône Material aux slots configurés. Chaque icône montre le nombre de cosmétiques disponibles et le cosmétique actuellement équipé.

  • Slot 49 : Boutique (lingot d'or)
  • Slot 53 : Solde ekaii (tournesol)
  • Clic sur un trigger : ouvre le sous-menu de ce trigger

Sous-menu trigger

Grille de 28 slots (4 lignes de 7). Affiche tous les cosmétiques du trigger sélectionné.

Chaque item montre :

  • Nom coloré par rareté
  • Description
  • Statut : Équipé (vert + glint), Possédé (jaune), Non possédé (rouge + prix)
  • Clic sur possédé : équipe/déséquipe
  • Clic sur non possédé : message "non possédé"

Boutique

Même grille. Affiche les cosmétiques non possédés et non exclusifs, triés par rareté.

  • Prix en vert (suffisant) ou rouge (insuffisant)
  • Clic pour acheter (avec confirmation via message)
  • Sons : tintement pour achat, clic pour navigation

Intégrations

NuVotifier

Quand un joueur vote :

  1. Le plugin calcule l'ordre du jour (1er, 2ème, autre)
  2. Applique le multiplicateur correspondant
  3. Dépose les ekaii sur le compte du joueur
  4. Envoie un message en jeu si le joueur est connecté

L'intégration est chargée par réflexion — pas de dépendance hard. Si NuVotifier n'est pas installé, cette fonctionnalité est silencieusement désactivée.

PlaceholderAPI

Placeholder Valeur
%ekaii_balance% Solde ekaii du joueur
%ekaii_equipped_death% Nom du cosmétique équipé (ou "Aucun")
%ekaii_equipped_join% idem pour chaque trigger
%ekaii_equipped_enderpearl%
%ekaii_equipped_sneak%
%ekaii_equipped_eat%
%ekaii_equipped_elytra%
%ekaii_cosmetics_count% Nombre de cosmétiques possédés
%ekaii_cosmetics_total% Nombre total de cosmétiques disponibles

Velocity (cross-server)

Skeleton de synchronisation via plugin messaging channel ekaiicosmetics:sync. Les cosmétiques équipés sont transmis entre serveurs lors des changements de serveur.


Architecture technique

fr.ekaii.cosmetics/
├── EkaiiCosmetics.kt          # Plugin principal, wiring
├── api/                        # Enums (TriggerType, ShapeType, etc.)
├── command/
│   ├── CosmeticCommand.kt     # /cosmetics
│   └── AdminCommand.kt        # /ecadmin
├── config/
│   ├── PluginConfig.kt        # Valeurs config.yml
│   └── EffectLoader.kt        # Parser YAML des effets
├── cosmetic/
│   ├── CosmeticDefinition.kt  # Data class cosmétique
│   ├── CosmeticRegistry.kt    # Registre chargé depuis config
│   └── PlayerData.kt          # État joueur en mémoire
├── economy/
│   └── EkaiiEconomy.kt        # Service monnaie ekaii
├── effect/
│   ├── EffectDefinition.kt    # Data class effet
│   ├── EffectEngine.kt        # Gestionnaire des instances actives
│   ├── EffectInstance.kt       # Instance en cours (tick loop)
│   ├── VectorUtil.kt          # Maths directionnelles
│   ├── shape/                  # 10 implémentations Shape
│   └── modifier/               # 6 implémentations PointModifier
├── gui/
│   ├── CosmeticMenu.kt        # Builders des inventaires
│   └── MenuListener.kt        # Gestion des clics
├── integration/
│   ├── VoteListener.kt        # NuVotifier (reflection-loaded)
│   ├── PlaceholderHook.kt     # PlaceholderAPI (reflection-loaded)
│   └── CrossServerSync.kt     # Velocity plugin messaging
├── listener/
│   ├── TriggerListener.kt     # 6 événements trigger
│   └── PlayerListener.kt      # Join/Quit lifecycle
└── persistence/
    ├── Database.kt             # SQLite + HikariCP
    └── PlayerRepository.kt     # Cache in-memory + DB

Base de données

SQLite, 4 tables :

Table Rôle
players UUID + solde ekaii
player_cosmetics Cosmétiques possédés (UUID + cosmetic_id)
player_equipped Cosmétiques équipés par trigger
transactions Historique des transactions (audit)

Thread safety

  • ConcurrentHashMap pour le cache joueurs et les effets actifs
  • synchronized(playerData) pour les opérations économiques atomiques
  • Chargement DB asynchrone via Bukkit.getAsyncScheduler()
  • Ouverture d'inventaire via player.getScheduler().run() (Folia-safe)

Compatibilité Folia

Le plugin n'utilise aucun scheduler Bukkit legacy. Tout passe par :

  • Bukkit.getGlobalRegionScheduler() pour le tick loop des effets
  • Bukkit.getAsyncScheduler() pour les opérations DB
  • player.getScheduler() pour les actions spécifiques au joueur (GUI, delayed stop)

Build

Pré-requis

  • JDK 25 (Temurin recommandé)
  • Gradle 9.4.1 (wrapper inclus)

Commandes

# Compiler
export JAVA_HOME=/path/to/jdk-25
./gradlew shadowJar

# JAR de sortie
ls build/libs/EkaiiCosmetics-*.jar

Dépendances

Dépendance Type Usage
paper-api:26.1.2.build.53-stable compileOnly API Bukkit/Paper
HikariCP:6.3.0 shaded (relocated) Pool connexions SQLite
placeholderapi:2.11.7 compileOnly Placeholders
votifier-stub.jar compileOnly (local) Stub compile NuVotifier

HikariCP est relocaté sous fr.ekaii.cosmetics.lib.hikari pour éviter les conflits.


Déploiement

# Build
export JAVA_HOME=/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home
./gradlew shadowJar

# Deploy sur preprod
scp build/libs/EkaiiCosmetics-*.jar exo:/opt/mc-preprod/server/plugins/
ssh exo 'docker compose -f /opt/mc-preprod/docker-compose.yml restart'

# Vérifier les logs
ssh exo 'docker logs --tail 20 mc-preprod 2>&1' | grep EkaiiCosmetics

CI/CD

Forgejo Actions (.forgejo/workflows/build.yml) :

  • Build automatique sur push main
  • Release automatique sur push de tag v*
  • Container node:20-bookworm + Temurin JDK 25
  • Artefact JAR uploadé sur chaque build
  • Asset JAR attaché à la release Forgejo

Licence

MIT