- Kotlin 100%
|
All checks were successful
Build & Release / build (push) Successful in 30s
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> |
||
|---|---|---|
| .forgejo/workflows | ||
| gradle/wrapper | ||
| libs | ||
| src/main | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
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
- Fonctionnalités
- Installation
- Configuration
- Commandes
- Permissions
- Système économique
- Triggers
- Moteur de particules
- GUI
- Intégrations
- Architecture technique
- Build
- Déploiement
- Licence
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
- Télécharger
EkaiiCosmetics-x.x.x-ekaii26.jardepuis les releases - Placer dans
plugins/ - Redémarrer le serveur
- Les fichiers
config.ymleteffects/*.ymlsont 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 :
- Récupère la position du joueur/emplacement
- Filtre les joueurs en portée (
distanceSquared <= range²) - Pour chaque layer : génère les points de la shape, applique les modifiers
- 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é
- Créer un fichier
mon_effet.ymldansplugins/EkaiiCosmetics/effects/ - Définir les layers avec shapes et modifiers
- Ajouter l'entrée cosmétique dans
config.ymlsouscosmetics: /ecadmin reload- 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 :
- Le plugin calcule l'ordre du jour (1er, 2ème, autre)
- Applique le multiplicateur correspondant
- Dépose les ekaii sur le compte du joueur
- 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
ConcurrentHashMappour le cache joueurs et les effets actifssynchronized(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 effetsBukkit.getAsyncScheduler()pour les opérations DBplayer.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