feat: update paper plugin sender, fix protocollib classpath access

This commit is contained in:
ineanto 2025-04-02 18:23:19 +02:00
parent 9adf5f34f5
commit 875a04a850
Signed by: ineanto
GPG key ID: E511F9CAA2F9CE84
14 changed files with 84 additions and 210 deletions

View file

@ -1,7 +1,7 @@
plugins { plugins {
id("java") id("java")
id("com.gradleup.shadow") version "8.3.2" id("com.gradleup.shadow") version "8.3.2"
id("xyz.jpenilla.run-paper") version "2.3.0" id("xyz.jpenilla.run-paper") version "2.3.1"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.10" id("io.papermc.paperweight.userdev") version "2.0.0-beta.10"
} }
@ -31,11 +31,11 @@ repositories {
dependencies { dependencies {
paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT") paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT")
compileOnly("com.comphenix.protocol:ProtocolLib:5.4.0-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.5") compileOnly("me.clip:placeholderapi:2.11.5")
compileOnly("net.kyori:adventure-api:4.17.0") compileOnly("net.kyori:adventure-api:4.17.0")
compileOnly("xyz.xenondevs.invui:invui-core:$invuiVersion") compileOnly("xyz.xenondevs.invui:invui-core:$invuiVersion")
compileOnly("net.wesjd:anvilgui:1.10.4-SNAPSHOT") compileOnly("net.wesjd:anvilgui:1.10.4-SNAPSHOT")
compileOnly("com.comphenix.protocol:ProtocolLib:5.4.0-SNAPSHOT")
implementation("de.rapha149.signgui:signgui:2.5.0") implementation("de.rapha149.signgui:signgui:2.5.0")
implementation("com.github.jsixface:yamlconfig:1.2") implementation("com.github.jsixface:yamlconfig:1.2")
@ -62,7 +62,7 @@ tasks {
relocate("net.wesjd", "xyz.ineanto.nicko.libs.anvilgui") relocate("net.wesjd", "xyz.ineanto.nicko.libs.anvilgui")
relocate("com.github.jsixface", "xyz.ineanto.nicko.libs.yaml") relocate("com.github.jsixface", "xyz.ineanto.nicko.libs.yaml")
relocate("me.clip", "xyz.ineanto.nicko.libs.placeholderapi") relocate("me.clip", "xyz.ineanto.nicko.libs.placeholderapi")
relocate("com.fasterxml.jackson", "xyz.ineanto.nicko.libs.jacksonpr") relocate("com.fasterxml.jackson", "xyz.ineanto.nicko.libs.jackson")
relocate("com.mysql", "xyz.ineanto.nicko.libs.mysql") relocate("com.mysql", "xyz.ineanto.nicko.libs.mysql")
relocate("org.mariadb.jdbc", "xyz.ineanto.nicko.libs.mariadb") relocate("org.mariadb.jdbc", "xyz.ineanto.nicko.libs.mariadb")
relocate("redis.clients", "xyz.ineanto.nicko.libs.redis") relocate("redis.clients", "xyz.ineanto.nicko.libs.redis")
@ -95,10 +95,10 @@ tasks {
runServer { runServer {
downloadPlugins { downloadPlugins {
url("https://download.luckperms.net/1568/bukkit/loader/LuckPerms-Bukkit-5.4.151.jar") url("https://download.luckperms.net/1575/bukkit/loader/LuckPerms-Bukkit-5.4.158.jar")
// 1.20.5 - latest testing // 1.20.5 - latest testing
url("https://ci.dmulloy2.net/job/ProtocolLib/lastSuccessfulBuild/artifact/build/libs/ProtocolLib.jar") //url("https://ci.dmulloy2.net/job/ProtocolLib/lastSuccessfulBuild/artifact/build/libs/ProtocolLib.jar")
} }
minecraftVersion("1.21.4") minecraftVersion("1.21.4")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -6,7 +6,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
import xyz.ineanto.nicko.Nicko; import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.event.custom.PlayerDisguiseEvent; import xyz.ineanto.nicko.event.custom.PlayerDisguiseEvent;
import xyz.ineanto.nicko.event.custom.PlayerResetDisguiseEvent; import xyz.ineanto.nicko.event.custom.PlayerResetDisguiseEvent;
import xyz.ineanto.nicko.packet.InternalPacketSender; import xyz.ineanto.nicko.packet.PaperPacketSender;
import xyz.ineanto.nicko.packet.PacketSender; import xyz.ineanto.nicko.packet.PacketSender;
import xyz.ineanto.nicko.profile.NickoProfile; import xyz.ineanto.nicko.profile.NickoProfile;
import xyz.ineanto.nicko.storage.PlayerDataStore; import xyz.ineanto.nicko.storage.PlayerDataStore;
@ -24,7 +24,7 @@ public class AppearanceManager {
public AppearanceManager(Player player) { public AppearanceManager(Player player) {
this.player = player; this.player = player;
this.packetSender = new InternalPacketSender(player, getNickoProfile()); this.packetSender = new PaperPacketSender(player, getNickoProfile());
} }
public ActionResult reset() { public ActionResult reset() {
@ -51,11 +51,15 @@ public class AppearanceManager {
final NickoProfile profile = getNickoProfile(); final NickoProfile profile = getNickoProfile();
final String displayName = profile.getName() == null ? player.getName() : profile.getName(); final String displayName = profile.getName() == null ? player.getName() : profile.getName();
final ActionResult result = packetSender.sendGameProfileUpdate(displayName, skinChange, reset); final ActionResult result = packetSender.updatePlayerProfile(displayName);
if (result.isError()) { if (skinChange) {
final ActionResult propertiesUpdateResult = packetSender.updatePlayerProfileProperties();
if (propertiesUpdateResult.isError()) {
return reset(); return reset();
} }
}
// Call the event. // Call the event.
final PlayerDisguiseEvent event = new PlayerDisguiseEvent(player, profile.getSkin(), profile.getName()); final PlayerDisguiseEvent event = new PlayerDisguiseEvent(player, profile.getSkin(), profile.getName());
@ -63,8 +67,8 @@ public class AppearanceManager {
packetSender.sendEntityMetadataUpdate(); packetSender.sendEntityMetadataUpdate();
packetSender.sendTabListUpdate(displayName); packetSender.sendTabListUpdate(displayName);
respawnPlayer(); //respawnPlayer();
packetSender.sendEntityRespawn(); //packetSender.sendEntityRespawn();
return result; return result;
} }

View file

@ -8,6 +8,7 @@ import org.bukkit.entity.Player;
import xyz.ineanto.nicko.language.LanguageKey; import xyz.ineanto.nicko.language.LanguageKey;
import xyz.ineanto.nicko.language.PlayerLanguage; import xyz.ineanto.nicko.language.PlayerLanguage;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -16,12 +17,13 @@ import java.util.concurrent.atomic.AtomicReference;
public class SignPrompt implements Prompt { public class SignPrompt implements Prompt {
private final Player player; private final Player player;
private final PlayerLanguage playerLanguage; private final PlayerLanguage playerLanguage;
private final ArrayList<String> lines = new ArrayList<>(
private List<String> lines = List.of( List.of(
"VVVVVVVVVVVVVVV", "VVVVVVVVVVVVVVV",
null, "",
null, "",
"ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ" "ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ"
)
); );
private final AtomicReference<Optional<String>> name = new AtomicReference<>(); private final AtomicReference<Optional<String>> name = new AtomicReference<>();
@ -73,6 +75,7 @@ public class SignPrompt implements Prompt {
try { try {
final SignGUI gui = SignGUI.builder() final SignGUI gui = SignGUI.builder()
.setLines(lines.toArray(new String[0])) .setLines(lines.toArray(new String[0]))
.setLine(2, null)
.setType(Material.OAK_SIGN) .setType(Material.OAK_SIGN)
.setHandler((_, result) -> { .setHandler((_, result) -> {
final String internalLine2 = result.getLineWithoutColor(2); final String internalLine2 = result.getLineWithoutColor(2);
@ -89,6 +92,7 @@ public class SignPrompt implements Prompt {
gui.open(player); gui.open(player);
return reference.get(); return reference.get();
} catch (SignGUIVersionException exception) { } catch (SignGUIVersionException exception) {
exception.printStackTrace();
return Optional.empty(); return Optional.empty();
} }
} }

View file

@ -9,7 +9,7 @@ public enum Language implements Serializable {
FRENCH("fr", "Français"), FRENCH("fr", "Français"),
CUSTOM("cm", "Server Custom"); CUSTOM("cm", "Server Custom");
public static final Version VERSION = new Version(1, 2, 0); public static final Version VERSION = new Version(1, 3, 0);
private final String code; private final String code;
private transient final String name; private transient final String name;

View file

@ -10,8 +10,7 @@ public class LanguageKey {
public static final String PERMISSION = ERROR_KEY + "permission"; public static final String PERMISSION = ERROR_KEY + "permission";
public static final String CACHE = ERROR_KEY + "cache"; public static final String CACHE = ERROR_KEY + "cache";
public static final String MOJANG_NAME = ERROR_KEY + "mojang_name"; public static final String MOJANG = ERROR_KEY + "mojang";
public static final String MOJANG_SKIN = ERROR_KEY + "mojang_skin";
} }
public static class Event { public static class Event {

View file

@ -3,8 +3,8 @@ package xyz.ineanto.nicko.loader;
import io.papermc.paper.plugin.loader.PluginClasspathBuilder; import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
import io.papermc.paper.plugin.loader.PluginLoader; import io.papermc.paper.plugin.loader.PluginLoader;
import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver; import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RemoteRepository;
public class NickoPluginLoader implements PluginLoader { public class NickoPluginLoader implements PluginLoader {
@ -15,7 +15,6 @@ public class NickoPluginLoader implements PluginLoader {
resolver.addRepository(new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases/").build()); resolver.addRepository(new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases/").build());
resolver.addRepository(new RemoteRepository.Builder("codemc", "default", "https://repo.codemc.io/repository/maven-snapshots/").build()); resolver.addRepository(new RemoteRepository.Builder("codemc", "default", "https://repo.codemc.io/repository/maven-snapshots/").build());
resolver.addDependency(new Dependency(new DefaultArtifact("xyz.xenondevs.invui:invui:pom:1.44"), null)); resolver.addDependency(new Dependency(new DefaultArtifact("xyz.xenondevs.invui:invui:pom:1.44"), null));
//resolver.addDependency(new Dependency(new DefaultArtifact("net.wesjd:anvilgui:1.10.4-SNAPSHOT"), null));
pluginClasspathBuilder.addLibrary(resolver); pluginClasspathBuilder.addLibrary(resolver);
} }

View file

@ -1,7 +1,11 @@
package xyz.ineanto.nicko.mojang; package xyz.ineanto.nicko.mojang;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import java.util.Collection;
import java.util.Collections;
public record MojangSkin(String value, String signature) { public record MojangSkin(String value, String signature) {
public static MojangSkin buildFromJson(JsonObject object) { public static MojangSkin buildFromJson(JsonObject object) {
final JsonObject properties = object.get("properties").getAsJsonArray().get(0).getAsJsonObject(); final JsonObject properties = object.get("properties").getAsJsonArray().get(0).getAsJsonObject();
@ -9,4 +13,8 @@ public record MojangSkin(String value, String signature) {
final String signature = properties.get("signature").getAsString(); final String signature = properties.get("signature").getAsString();
return new MojangSkin(value, signature); return new MojangSkin(value, signature);
} }
public Collection<ProfileProperty> asProfileProperties() {
return Collections.singleton(new ProfileProperty("textures", value, signature));
}
} }

View file

@ -5,7 +5,9 @@ import xyz.ineanto.nicko.appearance.ActionResult;
public interface PacketSender { public interface PacketSender {
void sendEntityRespawn(); void sendEntityRespawn();
ActionResult sendGameProfileUpdate(String name, boolean skinChange, boolean reset); ActionResult updatePlayerProfile(String name);
ActionResult updatePlayerProfileProperties();
void sendEntityMetadataUpdate(); void sendEntityMetadataUpdate();

View file

@ -1,8 +1,7 @@
package xyz.ineanto.nicko.packet; package xyz.ineanto.nicko.packet;
import com.mojang.authlib.GameProfile; import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.mojang.authlib.properties.Property; import com.destroystokyo.paper.profile.PlayerProfile;
import com.mojang.authlib.properties.PropertyMap;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.Optionull; import net.minecraft.Optionull;
import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.MutableComponent;
@ -39,11 +38,11 @@ import java.util.concurrent.ExecutionException;
* I want you to really stare at this code. * I want you to really stare at this code.
* You made me do this. * You made me do this.
*/ */
public class InternalPacketSender implements PacketSender { public class PaperPacketSender implements PacketSender {
private final Player player; private final Player player;
private final NickoProfile profile; private final NickoProfile profile;
public InternalPacketSender(Player player, NickoProfile profile) { public PaperPacketSender(Player player, NickoProfile profile) {
this.player = player; this.player = player;
this.profile = profile; this.profile = profile;
} }
@ -74,40 +73,39 @@ public class InternalPacketSender implements PacketSender {
} }
@Override @Override
public ActionResult sendGameProfileUpdate(String name, boolean skinChange, boolean reset) { public ActionResult updatePlayerProfile(String name) {
final GameProfile gameProfile = new GameProfile(player.getUniqueId(), name); final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), name);
// Copy previous properties to preserve skin
playerProfile.setProperties(playerProfile.getProperties());
player.setPlayerProfile(playerProfile);
return ActionResult.ok();
}
@Override
public ActionResult updatePlayerProfileProperties() {
final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), profile.getName() == null ? player.getName() : profile.getName());
// TODO (Ineanto, 31/10/2024): Could this be refactored to get rid of the boolean?
if (skinChange) {
Optional<MojangSkin> skin;
try { try {
final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI(); final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI();
final Optional<String> uuid = mojangAPI.getUUID(profile.getSkin()); final Optional<String> uuid = mojangAPI.getUUID(profile.getSkin());
if (uuid.isPresent()) { if (uuid.isEmpty()) {
skin = reset ? mojangAPI.getSkinWithoutCaching(uuid.get()) : mojangAPI.getSkin(uuid.get()); return ActionResult.error(LanguageKey.Error.MOJANG);
if (skin.isPresent()) { }
final Optional<MojangSkin> skin = mojangAPI.getSkin(uuid.get());
if (skin.isEmpty()) {
return ActionResult.error(LanguageKey.Error.MOJANG);
}
final MojangSkin skinResult = skin.get(); final MojangSkin skinResult = skin.get();
final PropertyMap properties = gameProfile.getProperties(); playerProfile.setProperties(skinResult.asProfileProperties());
properties.get("textures").clear(); player.setPlayerProfile(playerProfile);
properties.put("textures", new Property("textures", skinResult.value(), skinResult.signature()));
((CraftPlayer) player).getHandle().gameProfile = gameProfile;
} else {
return ActionResult.error(LanguageKey.Error.MOJANG_SKIN);
}
} else {
return ActionResult.error(LanguageKey.Error.MOJANG_NAME);
}
return ActionResult.ok(); return ActionResult.ok();
} catch (ExecutionException e) { } catch (ExecutionException | IOException e) {
return ActionResult.error(LanguageKey.Error.CACHE); return ActionResult.error(LanguageKey.Error.CACHE);
} catch (IOException e) {
return ActionResult.error(LanguageKey.Error.MOJANG_NAME);
} catch (InterruptedException e) {
return ActionResult.error("Unknown error");
} }
} }
return ActionResult.ok();
}
@Override @Override
public void sendEntityMetadataUpdate() { public void sendEntityMetadataUpdate() {

View file

@ -1,139 +0,0 @@
package xyz.ineanto.nicko.packet;
import com.comphenix.protocol.wrappers.*;
import com.google.common.collect.Multimap;
import it.unimi.dsi.fastutil.ints.IntList;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.appearance.ActionResult;
import xyz.ineanto.nicko.language.LanguageKey;
import xyz.ineanto.nicko.mojang.MojangAPI;
import xyz.ineanto.nicko.mojang.MojangSkin;
import xyz.ineanto.nicko.packet.wrapper.*;
import xyz.ineanto.nicko.profile.NickoProfile;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
public class WrapperPacketSender implements PacketSender {
private final Player player;
private final NickoProfile profile;
private WrappedGameProfile gameProfile;
public WrapperPacketSender(Player player, NickoProfile profile) {
this.player = player;
this.profile = profile;
}
@Override
public void sendEntityRespawn() {
if (!profile.hasData()) return;
final WrapperPlayServerEntityDestroy destroy = new WrapperPlayServerEntityDestroy();
final WrapperPlayServerSpawnEntity spawn = new WrapperPlayServerSpawnEntity();
destroy.setEntityIds(IntList.of(player.getEntityId()));
spawn.setEntityId(player.getEntityId());
spawn.setLocation(player.getLocation());
spawn.setPlayerId(player.getUniqueId());
Bukkit.getOnlinePlayers().stream().filter(receiver -> receiver.getUniqueId() != player.getUniqueId()).forEach(receiver -> {
destroy.sendPacket(receiver);
spawn.sendPacket(receiver);
});
}
@Override
public ActionResult sendGameProfileUpdate(String name, boolean skinChange, boolean reset) {
this.gameProfile = WrappedGameProfile.fromPlayer(player).withName(name);
// TODO (Ineanto, 31/10/2024): Could get refactored to omit this boolean?
if (skinChange) {
Optional<MojangSkin> skin;
try {
final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI();
final Optional<String> uuid = mojangAPI.getUUID(profile.getSkin());
if (uuid.isPresent()) {
skin = reset ? mojangAPI.getSkinWithoutCaching(uuid.get()) : mojangAPI.getSkin(uuid.get());
if (skin.isPresent()) {
final MojangSkin skinResult = skin.get();
final Multimap<String, WrappedSignedProperty> properties = gameProfile.getProperties();
properties.get("textures").clear();
properties.put("textures", new WrappedSignedProperty("textures", skinResult.value(), skinResult.signature()));
} else {
return ActionResult.error(LanguageKey.Error.MOJANG_SKIN);
}
} else {
return ActionResult.error(LanguageKey.Error.MOJANG_NAME);
}
return ActionResult.ok();
} catch (ExecutionException e) {
return ActionResult.error(LanguageKey.Error.CACHE);
} catch (IOException e) {
return ActionResult.error(LanguageKey.Error.MOJANG_NAME);
} catch (InterruptedException e) {
return ActionResult.error("Unknown error");
}
}
return ActionResult.ok();
}
@Override
public void sendEntityMetadataUpdate() {
final WrappedDataWatcher entityWatcher = WrappedDataWatcher.getEntityWatcher(player);
entityWatcher.setObject(17, (byte) 0x7f, true);
}
@Override
public void sendPlayerRespawn() {
final World world = player.getWorld();
final WrapperPlayServerRespawn respawn = new WrapperPlayServerRespawn();
respawn.setDimension(world);
respawn.setSeed(world.getSeed());
respawn.setGameMode(player.getGameMode());
respawn.setPreviousGameMode(player.getGameMode());
respawn.setCopyMetadata(true);
respawn.sendPacket(player);
}
@Override
public void sendTabListUpdate(String displayName) {
if (gameProfile == null) {
Nicko.getInstance().getLogger().warning("Hello. I sincerely hope you're doing great out there.");
Nicko.getInstance().getLogger().warning("If you see this message, I've failed at my task and I'm a terrible programmer.");
Nicko.getInstance().getLogger().warning("Report this issue on https://git.ineanto.xyz/ineanto/nicko, thank you!");
return;
}
final WrapperPlayerServerPlayerInfo add = new WrapperPlayerServerPlayerInfo();
final WrapperPlayerServerPlayerInfoRemove remove = new WrapperPlayerServerPlayerInfoRemove();
final EnumSet<EnumWrappers.PlayerInfoAction> actions = EnumSet.of(
EnumWrappers.PlayerInfoAction.ADD_PLAYER,
EnumWrappers.PlayerInfoAction.INITIALIZE_CHAT,
EnumWrappers.PlayerInfoAction.UPDATE_LISTED,
EnumWrappers.PlayerInfoAction.UPDATE_DISPLAY_NAME,
EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE,
EnumWrappers.PlayerInfoAction.UPDATE_LATENCY);
remove.setUUIDs(List.of(player.getUniqueId()));
remove.broadcastPacket();
add.setActions(actions);
add.setData(List.of(new PlayerInfoData(
player.getUniqueId(),
player.getPing(),
true,
EnumWrappers.NativeGameMode.fromBukkit(player.getGameMode()),
gameProfile,
WrappedChatComponent.fromText(displayName),
WrappedRemoteChatSessionData.fromPlayer(player)
)));
add.broadcastPacket();
}
}

View file

@ -1,7 +1,7 @@
# Nicko ${version} - Language File: # Nicko ${version} - Language File:
# Specifies the configuration version, don't change. # Specifies the configuration version, don't change.
version: "1.2.0" version: "1.3.0"
prefix: "<b><gradient:#01a97c:#8ffd54>NICKO</gradient></b>" prefix: "<b><gradient:#01a97c:#8ffd54>NICKO</gradient></b>"
whoosh: "<b><gradient:#01a97c:#8ffd54>WHOOSH!</gradient></b>" whoosh: "<b><gradient:#01a97c:#8ffd54>WHOOSH!</gradient></b>"
@ -10,8 +10,7 @@ oops: "<b><color:#ff4640>OOPS!</color></b>"
error: error:
permission: "<gray>You're missing the permission to do that.</gray>" permission: "<gray>You're missing the permission to do that.</gray>"
invalid_username: "<gray>This is an invalid Minecraft username.</gray>" invalid_username: "<gray>This is an invalid Minecraft username.</gray>"
mojang_name: "<gray>There's is not Minecraft account with this username.</gray>" mojang: "<gray>Something went wrong while fetching data from Mojang.</gray>"
mojang_skin: "<gray>This Minecraft account has no skin.</gray>"
cache: "<gray>Unable to get data from the cache.</gray>" cache: "<gray>Unable to get data from the cache.</gray>"
event: event:

View file

@ -1,7 +1,7 @@
# Nicko ${version} - Fichier de langue: # Nicko ${version} - Fichier de langue:
# Précise la version de la configuration, ne pas changer. # Précise la version de la configuration, ne pas changer.
version: "1.2.0" version: "1.3.0"
prefix: "<b><gradient:#01a97c:#8ffd54>NICKO</gradient></b>" prefix: "<b><gradient:#01a97c:#8ffd54>NICKO</gradient></b>"
whoosh: "<b><gradient:#01a97c:#8ffd54>WHOOSH!</gradient></b>" whoosh: "<b><gradient:#01a97c:#8ffd54>WHOOSH!</gradient></b>"
@ -10,8 +10,7 @@ oops: "<b><color:#ff4640>OOPS!</color></b>"
error: error:
permission: "<gray>Vous n'avez pas la permission de faire cela.<gray>" permission: "<gray>Vous n'avez pas la permission de faire cela.<gray>"
invalid_username: "<gray>Nom d'utilisateur Minecraft invalide.<gray>" invalid_username: "<gray>Nom d'utilisateur Minecraft invalide.<gray>"
mojang_name: "<gray>Aucun compte Minecraft associé à ce nom d'utilisateur.<gray>" mojang: "<gray>Une erreur est surevenue en récupérant les information depuis Mojang.<gray>"
mojang_skin: "<gray>Ce compte Minecraft n'a pas de skin.<gray>"
cache: "<gray>Impossible de récupérer les données depuis le cache.<gray>" cache: "<gray>Impossible de récupérer les données depuis le cache.<gray>"
event: event:

View file

@ -9,10 +9,11 @@ softdepend: [ PlaceholderAPI ]
depend: depend:
- ProtocolLib - ProtocolLib
# Suppose we require ProtocolLib to be loaded for our plugin dependencies:
AnvilGUI: server:
ProtocolLib:
load: BEFORE
join-classpath: true join-classpath: true
required: true
permissions: permissions:
nicko.*: nicko.*: