feat: spigot branch

This commit is contained in:
ineanto 2024-12-12 19:22:11 +01:00
parent ff892a7451
commit 4948610860
Signed by: ineanto
GPG key ID: E511F9CAA2F9CE84
35 changed files with 144 additions and 1291 deletions

View file

@ -64,7 +64,7 @@ public class Nicko extends JavaPlugin {
if (!MinecraftVersion.TRAILS_AND_TAILS.atOrAbove()) {
getLogger().severe("This version (" + MinecraftVersion.getCurrentVersion().getVersion() + ") is not supported by Nicko!");
getLogger().severe("As of version 1.2.0, Nicko only supports the latest Minecraft version. (Currently 1.21.3)");
getLogger().severe("As of version 1.2.0, Nicko only supports the latest two majors Minecraft versions. (Currently 1.20.6-1.21.X)");
dataStore.getStorage().setError(true);
Bukkit.getPluginManager().disablePlugin(this);
}

View file

@ -1,35 +0,0 @@
package xyz.ineanto.nicko.appearance;
public class ActionResult {
private final String errorKey;
private boolean error = false;
public static ActionResult ok() {
return new ActionResult();
}
public static ActionResult error() {
return new ActionResult(null);
}
public static ActionResult error(String errorMessage) {
return new ActionResult(errorMessage);
}
private ActionResult() {
this.errorKey = null;
}
private ActionResult(String errorMessage) {
this.errorKey = errorMessage;
this.error = true;
}
public boolean isError() {
return error;
}
public String getErrorKey() {
return errorKey;
}
}

View file

@ -3,6 +3,7 @@ package xyz.ineanto.nicko.appearance;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.action.ActionResult;
import xyz.ineanto.nicko.packet.InternalPacketSender;
import xyz.ineanto.nicko.packet.PacketSender;
import xyz.ineanto.nicko.profile.NickoProfile;

View file

@ -0,0 +1,19 @@
package xyz.ineanto.nicko.loader;
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
import io.papermc.paper.plugin.loader.PluginLoader;
import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.repository.RemoteRepository;
import org.jetbrains.annotations.NotNull;
public class NickoLibraryLoader implements PluginLoader {
@Override
public void classloader(@NotNull PluginClasspathBuilder pluginClasspathBuilder) {
MavenLibraryResolver resolver = new MavenLibraryResolver();
resolver.addRepository(new RemoteRepository.Builder("xenondevs", "default", "https://repo.xenondevs.xyz/releases/").build());
resolver.addDependency(new Dependency(new DefaultArtifact("xyz.xenondevs.invui", "invui", "pom", "1.41"), null));
pluginClasspathBuilder.addLibrary(resolver);
}
}

View file

@ -1,155 +0,0 @@
package xyz.ineanto.nicko.mojang;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import javax.annotation.Nonnull;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class MojangAPI {
public static final String URL_NAME = "https://api.mojang.com/users/profiles/minecraft/{name}";
public static final String URL_SKIN = "https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false";
private final Logger logger = Logger.getLogger("MojangAPI");
private final HashMap<String, String> uuidToName = new HashMap<>();
private final ExecutorService worker = Executors.newFixedThreadPool(6);
private final CacheLoader<String, Optional<MojangSkin>> skinLoader = new CacheLoader<>() {
@Nonnull
public Optional<MojangSkin> load(@Nonnull String uuid) throws Exception {
return getSkinFromMojang(uuid);
}
};
private final LoadingCache<String, Optional<MojangSkin>> skinCache = CacheBuilder
.newBuilder()
.recordStats()
.expireAfterWrite(24, TimeUnit.HOURS)
.build(skinLoader);
private final CacheLoader<String, Optional<String>> uuidLoader = new CacheLoader<>() {
@Nonnull
public Optional<String> load(@Nonnull String name) throws Exception {
return getUUIDFromMojang(name);
}
};
private final LoadingCache<String, Optional<String>> uuidCache = CacheBuilder
.newBuilder()
.expireAfterWrite(2, TimeUnit.DAYS)
.build(uuidLoader);
public Optional<MojangSkin> getSkin(String uuid) throws IOException, ExecutionException {
return skinCache.get(uuid);
}
public Optional<MojangSkin> getSkinWithoutCaching(String uuid) throws IOException, ExecutionException, InterruptedException {
return getSkinFromMojang(uuid);
}
public Optional<String> getUUID(String name) throws IOException, ExecutionException {
return uuidCache.get(name);
}
private Optional<String> getUUIDFromMojang(String name) throws ExecutionException, InterruptedException {
final String parametrizedUrl = URL_NAME.replace("{name}", name);
final JsonObject object = getRequestToUrl(parametrizedUrl);
if (hasNoError(object)) {
final JsonElement idObject = object.get("id");
final String uuid = idObject.getAsString();
final Optional<String> uuidOptional = Optional.of(uuid);
uuidCache.put(name, uuidOptional);
uuidToName.put(uuid, name);
return uuidOptional;
}
return Optional.empty();
}
public void eraseFromCache(String uuid) {
skinCache.invalidate(uuid);
uuidToName.remove(uuid);
uuidCache.invalidate(uuid);
}
private Optional<MojangSkin> getSkinFromMojang(String uuid) throws ExecutionException, InterruptedException {
final String parametrizedUrl = URL_SKIN.replace("{uuid}", uuid);
final JsonObject object = getRequestToUrl(parametrizedUrl);
if (hasNoError(object)) {
final MojangSkin skin = MojangSkin.buildFromJson(object);
return Optional.of(skin);
}
return Optional.empty();
}
public String getUUIDName(String uuid) {
return uuidToName.get(uuid);
}
private JsonObject getRequestToUrl(String parametrizedUrl) throws ExecutionException, InterruptedException {
return worker.submit(() -> {
final URL url = URI.create(parametrizedUrl).toURL();
final HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setDoInput(true);
con.setRequestMethod("GET");
switch (con.getResponseCode()) {
case 404:
case 400:
logger.warning("Failed to parse request: Invalid Name");
return getErrorObject();
case 429:
logger.warning("Failed to parse request: The connection is throttled.");
return getErrorObject();
case 200:
final BufferedReader input = new BufferedReader(new InputStreamReader(con.getInputStream()));
final StringBuilder builder = new StringBuilder();
String line;
while ((line = input.readLine()) != null) {
builder.append(line);
}
try {
final JsonElement jsonElt = JsonParser.parseString(builder.toString());
return jsonElt.getAsJsonObject();
} catch (JsonParseException | IllegalStateException exception) {
logger.warning("Failed to parse request (" + parametrizedUrl + ")!");
return getErrorObject();
}
default:
logger.warning("Unhandled response code from Mojang: " + con.getResponseCode());
return getErrorObject();
}
}).get();
}
private JsonObject getErrorObject() {
final JsonObject errorObject = new JsonObject();
errorObject.addProperty("error", "An error occurred.");
return errorObject;
}
private boolean hasNoError(JsonObject object) {
return object.get("error") == null;
}
public LoadingCache<String, Optional<MojangSkin>> getSkinCache() {
return skinCache;
}
}

View file

@ -1,12 +0,0 @@
package xyz.ineanto.nicko.mojang;
import com.google.gson.JsonObject;
public record MojangSkin(String value, String signature) {
public static MojangSkin buildFromJson(JsonObject object) {
final JsonObject properties = object.get("properties").getAsJsonArray().get(0).getAsJsonObject();
final String value = properties.get("value").getAsString();
final String signature = properties.get("signature").getAsString();
return new MojangSkin(value, signature);
}
}

View file

@ -1,26 +0,0 @@
package xyz.ineanto.nicko.mojang;
import java.util.UUID;
import java.util.regex.Pattern;
public class MojangUtils {
public static boolean isUsernameInvalid(String username) {
return !Pattern.matches("^\\w{3,16}$", username);
}
public static UUID fromTrimmed(String trimmedUUID) throws IllegalArgumentException {
if (trimmedUUID == null) throw new IllegalArgumentException();
StringBuilder builder = new StringBuilder(trimmedUUID.trim());
/* Backwards adding to avoid index adjustments */
try {
builder.insert(20, "-");
builder.insert(16, "-");
builder.insert(12, "-");
builder.insert(8, "-");
} catch (StringIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
return UUID.fromString(builder.toString());
}
}

View file

@ -1,168 +0,0 @@
package xyz.ineanto.nicko.packet;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.Optionull;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.RemoteChatSession;
import net.minecraft.network.chat.contents.PlainTextContents;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.entity.CraftPlayer;
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.profile.NickoProfile;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ExecutionException;
/**
* Look at this Mojang.
* I want you to really stare at this code.
* You made me do this.
*/
public class InternalPacketSender implements PacketSender {
private final Player player;
private final NickoProfile profile;
public InternalPacketSender(Player player, NickoProfile profile) {
this.player = player;
this.profile = profile;
}
@Override
public void sendEntityRespawn() {
if (!profile.hasData()) return;
final ClientboundRemoveEntitiesPacket destroy = new ClientboundRemoveEntitiesPacket(IntList.of(player.getEntityId()));
final ClientboundAddEntityPacket add = new ClientboundAddEntityPacket(
new Random().nextInt(9999),
player.getUniqueId(),
player.getX(),
player.getY(),
player.getZ(),
player.getPitch(),
player.getYaw(),
EntityType.PLAYER,
0,
Vec3.ZERO,
player.getBodyYaw()
);
Bukkit.getOnlinePlayers().stream().filter(receiver -> receiver.getUniqueId() != player.getUniqueId()).forEach(receiver -> {
sendPacket(destroy, player);
sendPacket(add, player);
});
}
@Override
public ActionResult sendGameProfileUpdate(String name, boolean skinChange, boolean reset) {
final GameProfile gameProfile = new GameProfile(player.getUniqueId(), name);
// TODO (Ineanto, 31/10/2024): Could this be refactored to get rid of the 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 PropertyMap properties = gameProfile.getProperties();
properties.get("textures").clear();
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();
} 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 SynchedEntityData.DataValue<?> dataValueComponent =
new SynchedEntityData.DataItem<>(
new EntityDataAccessor<>(17, EntityDataSerializers.BYTE),
(byte) 0x7f
).value();
final ClientboundSetEntityDataPacket data = new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(dataValueComponent));
sendPacket(data, player);
}
@Override
public void sendPlayerRespawn() {
final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle();
final ServerLevel level = serverPlayer.serverLevel();
final ClientboundRespawnPacket respawn = new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(level), (byte) 0x03);
sendPacket(respawn, player);
}
@Override
public void sendTabListUpdate(String displayName) {
final ServerPlayer serverPlayer = (((CraftPlayer) player)).getHandle();
final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.of(
ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER,
ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
final List<ClientboundPlayerInfoUpdatePacket.Entry> entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry(
serverPlayer.getUUID(),
serverPlayer.gameProfile,
true,
serverPlayer.connection.latency(),
serverPlayer.gameMode.getGameModeForPlayer(),
MutableComponent.create(new PlainTextContents.LiteralContents(displayName)),
serverPlayer.getTabListOrder(),
Optionull.map(serverPlayer.getChatSession(), RemoteChatSession::asData)
));
final ClientboundPlayerInfoUpdatePacket update = new ClientboundPlayerInfoUpdatePacket(actions, entries);
final ClientboundPlayerInfoRemovePacket remove = new ClientboundPlayerInfoRemovePacket(List.of(player.getUniqueId()));
Bukkit.getOnlinePlayers().forEach(onlinePlayer -> {
sendPacket(remove, onlinePlayer);
sendPacket(update, onlinePlayer);
});
}
private void sendPacket(Packet<?> packet, Player player) {
(((CraftPlayer) player).getHandle()).connection.send(packet);
}
}

View file

@ -1,15 +0,0 @@
package xyz.ineanto.nicko.packet;
import xyz.ineanto.nicko.appearance.ActionResult;
public interface PacketSender {
void sendEntityRespawn();
ActionResult sendGameProfileUpdate(String name, boolean skinChange, boolean reset);
void sendEntityMetadataUpdate();
void sendPlayerRespawn();
void sendTabListUpdate(String displayName);
}

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,82 +0,0 @@
package xyz.ineanto.nicko.packet.debug;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.wrappers.MinecraftKey;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import xyz.ineanto.nicko.Nicko;
import java.util.Arrays;
import java.util.Optional;
public class RespawnPacketListener implements PacketListener {
@Override
public void onPacketSending(PacketEvent event) {
try {
final Optional<World> world = getWorld(event);
if (world.isEmpty()) {
Bukkit.broadcast(Component.text("did not find the world the player was in"));
return;
}
Bukkit.broadcast(Component.text("found " + world.get().getName() + "!"));
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void onPacketReceiving(PacketEvent event) {
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return ListeningWhitelist.newBuilder().types(PacketType.Play.Server.RESPAWN).build();
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return ListeningWhitelist.EMPTY_WHITELIST;
}
@Override
public Plugin getPlugin() {
return Nicko.getInstance();
}
private boolean keysEquals(MinecraftKey wrappedKey, NamespacedKey bukkitKey) {
// compare bukkit minecraft key and NMS wrapped minecraft key
return wrappedKey.getPrefix().equals(bukkitKey.getNamespace()) && wrappedKey.getKey().equals(bukkitKey.getKey());
}
public Optional<World> getWorld(PacketEvent event) throws Throwable {
// access CommonPlayerSpawnInfo, first field of that type in the Respawn / Login packets
final Object packetHandle = event.getPacket().getHandle();
final Object commonSpawnData = packetHandle.getClass().getRecordComponents()[0].getAccessor().invoke(packetHandle);
Arrays.stream(commonSpawnData.getClass().getRecordComponents()).forEach(component -> {
component.getAccessor().setAccessible(true);
System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-");
System.out.println(component.getName());
System.out.println(component.getType().getSimpleName());
component.getAccessor().setAccessible(false);
}
);
// get the key of the level the player is joining. Second field in the object. First of type ResourceKey
final MinecraftKey key = MinecraftKey.fromHandle(commonSpawnData.getClass().getRecordComponents()[1]);
for (World world : Bukkit.getWorlds()) {
if (keysEquals(key, world.getKey())) {
return Optional.of(world);
}
}
return Optional.empty();
}
}

View file

@ -1,73 +0,0 @@
/**
* PacketWrapper - ProtocolLib wrappers for Minecraft packets
* Copyright (C) dmulloy2 <http://dmulloy2.net>
* Copyright (C) Kristian S. Strangeland
* <p>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.ineanto.nicko.packet.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketContainer;
import com.google.common.base.Objects;
import org.bukkit.entity.Player;
public abstract class AbstractPacket {
protected PacketContainer handle;
/**
* Constructs a new strongly typed wrapper for the given packet.
*
* @param handle - handle to the raw packet data.
* @param type - the packet type.
*/
protected AbstractPacket(PacketContainer handle, PacketType type) {
// Make sure we're given a valid packet
if (handle == null)
throw new IllegalArgumentException("Packet handle cannot be NULL.");
if (!Objects.equal(handle.getType(), type))
throw new IllegalArgumentException(handle.getHandle()
+ " is not a packet of type " + type);
this.handle = handle;
}
/**
* Retrieve a handle to the raw packet data.
*
* @return Raw packet data.
*/
public PacketContainer getHandle() {
return handle;
}
/**
* Send the current packet to the given receiver.
*
* @param receiver - the receiver.
* @throws RuntimeException If the packet cannot be sent.
*/
public void sendPacket(Player receiver) {
ProtocolLibrary.getProtocolManager().sendServerPacket(receiver,
getHandle());
}
/**
* Send the current packet to all online players.
*/
public void broadcastPacket() {
ProtocolLibrary.getProtocolManager().broadcastServerPacket(getHandle());
}
}

View file

@ -1,32 +0,0 @@
package xyz.ineanto.nicko.packet.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.Converters;
import it.unimi.dsi.fastutil.ints.IntList;
/**
* Sent by the server to the client to remove one or more entities.
*/
public class WrapperPlayServerEntityDestroy extends AbstractPacket {
/**
* The packet type that is wrapped by this wrapper.
*/
public static final PacketType TYPE = PacketType.Play.Server.ENTITY_DESTROY;
public WrapperPlayServerEntityDestroy() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
}
/**
* Sets the list of entity ids to remove
*
* @param value New value for field 'entityIds'
*/
public void setEntityIds(IntList value) {
this.handle.getModifier().withType(IntList.class, Converters.passthrough(IntList.class)).writeSafely(0, value);
}
}

View file

@ -1,115 +0,0 @@
package xyz.ineanto.nicko.packet.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.InternalStructure;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.google.common.hash.Hashing;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.World;
import java.lang.reflect.Field;
import java.lang.reflect.RecordComponent;
/**
* PacketPlayServerRespawn Wrapper class (1.20.X to 1.21.X)
* <p>
* In 1.20.2, all the fields were merged inside a
* single "CommonPlayerSpawnInfo" record.
*
* @author inenato (w/ additional help from lukalt), based on work from dmulloy2 and Kristian S. Strangeland
*/
public class WrapperPlayServerRespawn extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.RESPAWN;
private InternalStructure spawnInfoStructure = null;
public WrapperPlayServerRespawn() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
spawnInfoStructure = handle.getStructures().read(0);
}
}
public void setDimension(World value) {
final MinecraftVersion v1_20_5 = new MinecraftVersion(1, 20, 5);
if (!MinecraftVersion.getCurrentVersion().isAtLeast(v1_20_5)) {
// 1.20 - 1.20.4
final StructureModifier<InternalStructure> structureModifier = spawnInfoStructure == null ?
handle.getStructures() : spawnInfoStructure.getStructures();
final StructureModifier<World> worldStructureModifier = spawnInfoStructure == null ?
handle.getWorldKeys() : spawnInfoStructure.getWorldKeys();
final InternalStructure dimensionType = structureModifier.read(0);
dimensionType.getMinecraftKeys().writeSafely(0, new MinecraftKey("minecraft", "dimension_type"));
dimensionType.getMinecraftKeys().writeSafely(1, new MinecraftKey("minecraft", "overworld"));
structureModifier.writeSafely(0, dimensionType);
worldStructureModifier.writeSafely(0, value);
} else {
// 1.20.5 to 1.21.1
/*
Honestly, I've tried everything to make this work.
Fields inside the CommonPlayerSpawnInfo are Record Components and are
marked final.
This would work with some trickery involved, but here's the
caveat: Record Components/Fields and are immutable by DESIGN.
So... here we are now, stopped right in my track by Java's language design and Mojang themselves.
*/
try {
final Object spawnInfoStructureHandle = spawnInfoStructure.getHandle();
final RecordComponent[] components = spawnInfoStructureHandle.getClass().getRecordComponents();
final Field levelKeyField = spawnInfoStructureHandle.getClass().getDeclaredField(components[1].getAccessor().getName());
levelKeyField.setAccessible(true);
levelKeyField.set(spawnInfoStructureHandle, BukkitConverters.getWorldKeyConverter().getGeneric(Bukkit.getWorld("world")));
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException();
}
}
}
public void setGameMode(GameMode value) {
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
// 1.20 to 1.20.1
handle.getGameModes().writeSafely(0, EnumWrappers.NativeGameMode.fromBukkit(value));
return;
}
spawnInfoStructure.getGameModes().writeSafely(0, EnumWrappers.NativeGameMode.fromBukkit(value));
}
public void setPreviousGameMode(GameMode value) {
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
// 1.20 to 1.20.1
handle.getGameModes().writeSafely(1, EnumWrappers.NativeGameMode.fromBukkit(value));
return;
}
spawnInfoStructure.getGameModes().writeSafely(1, EnumWrappers.NativeGameMode.fromBukkit(value));
}
public void setCopyMetadata(boolean value) {
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) return;
// 1.20 to 1.20.1
handle.getBooleans().writeSafely(0, value);
}
public void setSeed(long value) {
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
// 1.20 to 1.20.1
handle.getLongs().writeSafely(0, Hashing.sha256().hashLong(value).asLong());
}
}
}

View file

@ -1,103 +0,0 @@
package xyz.ineanto.nicko.packet.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import javax.annotation.Nonnull;
import java.util.UUID;
/**
* This packet is sent by the server when a player comes into visible range, not when a player joins.
*/
public class WrapperPlayServerSpawnEntity extends AbstractPacket {
/**
* The packet type that is wrapped by this wrapper.
*/
public static final PacketType TYPE = PacketType.Play.Server.SPAWN_ENTITY;
/**
* Constructors a new wrapper for the specified packet
*/
public WrapperPlayServerSpawnEntity() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
}
/**
* Sets the entity id of the player
*
* @param value New value for field 'entityId'
*/
public void setEntityId(int value) {
this.handle.getIntegers().writeSafely(0, value);
this.handle.getEntityTypeModifier().writeSafely(0, EntityType.PLAYER);
}
/**
* Sets the unique id of the player
*
* @param value New value for field 'playerId'
*/
public void setPlayerId(UUID value) {
this.handle.getUUIDs().writeSafely(0, value);
}
/**
* Sets the value of field 'x'
*
* @param value New value for field 'x'
*/
public void setX(double value) {
this.handle.getDoubles().writeSafely(0, value);
}
/**
* Sets the value of field 'y'
*
* @param value New value for field 'y'
*/
public void setY(double value) {
this.handle.getDoubles().writeSafely(1, value);
}
/**
* Sets the value of field 'z'
*
* @param value New value for field 'z'
*/
public void setZ(double value) {
this.handle.getDoubles().write(2, value);
}
/**
* Sets the discrete rotation around the y-axis (yaw)
*
* @param value New value for field 'yRot'
*/
public void setYRotRaw(byte value) {
this.handle.getBytes().writeSafely(0, value);
}
/**
* Sets the discrete rotation around the x-axis (pitch)
*
* @param value New value for field 'xRot'
*/
public void setXRotRaw(byte value) {
this.handle.getBytes().writeSafely(1, value);
}
public void setLocation(@Nonnull Location location) {
setX(location.getX());
setY(location.getY());
setZ(location.getZ());
setYRotRaw(degreesToAngle(location.getYaw()));
setXRotRaw(degreesToAngle(location.getPitch()));
}
private byte degreesToAngle(float degree) {
return (byte)((int)(degree * 256.0F / 360.0F));
}
}

View file

@ -1,37 +0,0 @@
package xyz.ineanto.nicko.packet.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import java.util.List;
import java.util.Set;
/**
* Up-to-date version of the Wrapper class
* for the PlayerServerPlayerInfo.
*
* @author ineanto, based on work from dmulloy2 and Kristian S. Strangeland
*/
public class WrapperPlayerServerPlayerInfo extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.PLAYER_INFO;
public WrapperPlayerServerPlayerInfo() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
}
public void setActions(Set<EnumWrappers.PlayerInfoAction> value) {
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
handle.getPlayerInfoActions().writeSafely(0, value);
} else {
handle.getPlayerInfoAction().writeSafely(0, value.stream().iterator().next()); // Get the first Value.
}
}
public void setData(List<PlayerInfoData> value) {
handle.getPlayerInfoDataLists().writeSafely(1, value);
}
}

View file

@ -1,27 +0,0 @@
package xyz.ineanto.nicko.packet.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import java.util.List;
import java.util.UUID;
/**
* Up-to-date version of the Wrapper class
* for the PlayerServerPlayerInfoRemove.
*
* @author ineanto, based on work from dmulloy2 and Kristian S. Strangeland
*/
public class WrapperPlayerServerPlayerInfoRemove extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.PLAYER_INFO_REMOVE;
public WrapperPlayerServerPlayerInfoRemove() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
}
public void setUUIDs(List<UUID> value) {
handle.getUUIDLists().writeSafely(0, value);
}
}

View file

@ -1,17 +1,13 @@
name: Nicko
main: xyz.ineanto.nicko.Nicko
loader: xyz.ineanto.nicko.loader.NickoLibraryLoader
version: ${version}
author: Ineanto
authors: [ Ineanto ]
description: "The feature packed, next generation disguise plugin for Minecraft."
api-version: 1.21
softdepend: [ PlaceholderAPI ]
depend:
- ProtocolLib
load: POSTWORLD
commands:
nicko:
description: "Opens Nicko's GUI."
permission: nicko.use
api-version: "1.20"
dependencies:
server:
{ "ProtocolLib" }
permissions:
nicko.*:
default: op

View file

@ -1,33 +0,0 @@
package xyz.ineanto.nicko.test;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class NickoPluginTest {
private static Nicko plugin;
@BeforeAll
public static void setup() {
final Configuration config = Configuration.DEFAULT;
MockBukkit.mock();
plugin = MockBukkit.load(Nicko.class, config);
}
@Test
@DisplayName("Plugin Initialization")
public void initializePlugin() {
assertNotNull(plugin);
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,38 +0,0 @@
package xyz.ineanto.nicko.test.appearance;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.appearance.random.RandomNameFetcher;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.mojang.MojangUtils;
import static org.junit.jupiter.api.Assertions.*;
public class RandomNameTest {
private static Nicko plugin;
@BeforeAll
public static void setup() {
final Configuration config = Configuration.DEFAULT;
MockBukkit.mock();
plugin = MockBukkit.load(Nicko.class, config);
}
@Test
@DisplayName("Get random name")
public void getRandomName() {
final RandomNameFetcher randomNameFetcher = new RandomNameFetcher(plugin);
final String username = randomNameFetcher.getRandomUsername();
assertNotNull(username);
assertFalse(MojangUtils.isUsernameInvalid(username));
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,35 +0,0 @@
package xyz.ineanto.nicko.test.config;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class ConfigurationTest {
private static Nicko plugin;
@BeforeAll
public static void setup() {
MockBukkit.mock();
final Configuration config = Configuration.DEFAULT;
plugin = MockBukkit.load(Nicko.class, config);
}
@Test
@DisplayName("Read configuration")
public void readConfiguration() {
final Configuration configuration = plugin.getNickoConfig();
assertFalse(configuration.getSqlConfiguration().isEnabled());
assertFalse(configuration.getRedisConfiguration().isEnabled());
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,63 +0,0 @@
package xyz.ineanto.nicko.test.config;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.DefaultDataSources;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ConfigurationVersionTest {
@BeforeAll
public static void setup() {
MockBukkit.mock();
final Configuration configuration = Configuration.DEFAULT;
MockBukkit.load(Nicko.class, configuration);
}
@Test
@DisplayName("Compare configuration version")
public void compareConfigurationVersion() {
final Configuration configuration = Configuration.DEFAULT;
assertEquals(configuration.getVersionObject().compareTo(Configuration.VERSION), 0);
}
@Test
@DisplayName("Compare newer configuration version")
public void compareNewerConfigurationVersion() {
final Configuration configuration = new Configuration("24.1.0",
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
false);
assertEquals(configuration.getVersionObject().compareTo(Configuration.VERSION), 1);
}
@Test
@DisplayName("Compare older configuration version")
public void compareOlderConfigurationVersion() {
final Configuration configuration = new Configuration("0.23.3",
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
false);
assertEquals(configuration.getVersionObject().compareTo(Configuration.VERSION), -1);
}
@Test
@DisplayName("Compare unknown configuration version")
public void compareUnknownConfigurationVersion() {
final Configuration configuration = new Configuration(null,
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
false);
assertEquals(configuration.getVersionObject().compareTo(Configuration.VERSION), -1);
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,56 +0,0 @@
package xyz.ineanto.nicko.test.i18n;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.language.Language;
import xyz.ineanto.nicko.language.PlayerLanguage;
import xyz.ineanto.nicko.language.LanguageKey;
import xyz.ineanto.nicko.language.Translation;
import static org.junit.jupiter.api.Assertions.*;
public class ItemTranslationTest {
private static PlayerMock player;
@BeforeAll
public static void setup() {
final Configuration config = Configuration.DEFAULT;
MockBukkit.mock();
MockBukkit.load(Nicko.class, config);
}
@Test
@DisplayName("Translate Item Without Lore")
public void translateItemTranslationWithoutLore() {
final PlayerLanguage playerLanguage = new PlayerLanguage(Language.FRENCH);
final Translation translation = playerLanguage.translateAndReplace(LanguageKey.GUI.GO_BACK);
assertTrue(translation.lore().isEmpty());
assertEquals(translation.name(), "Retour");
}
@Test
@DisplayName("Translate Item")
public void translateItemLore() {
final PlayerLanguage playerLanguage = new PlayerLanguage(Language.FRENCH);
final Translation test = playerLanguage.translateAndReplace(LanguageKey.GUI.Settings.TOGGLEABLE_BUTTON, "EST", "EST");
test.lore().forEach(System.out::println);
final Translation translation = playerLanguage.translateAndReplace(LanguageKey.GUI.Admin.Cache.STATISTICS, "1", "1");
assertFalse(translation.lore().isEmpty());
assertEquals("Nombre de requêtes: <aqua>1</aqua>", translation.lore().get(0));
assertEquals("Nb. de skin dans le cache: <aqua>1</aqua>", translation.lore().get(1));
assertEquals("<dark_gray><i>Le cache est vidé toutes les 24 heures.</i></dark_gray>", translation.lore().get(2));
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,36 +0,0 @@
package xyz.ineanto.nicko.test.i18n;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.language.Language;
import xyz.ineanto.nicko.language.PlayerLanguage;
import xyz.ineanto.nicko.language.LanguageKey;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TranslationTest {
@BeforeAll
public static void setup() {
final Configuration config = Configuration.DEFAULT;
MockBukkit.mock();
MockBukkit.load(Nicko.class, config);
}
@Test
@DisplayName("Translate Line With Replacement")
public void translateItemTranslationWithoutLore() {
final PlayerLanguage playerLanguage = new PlayerLanguage(Language.FRENCH);
final String translation = playerLanguage.translate(LanguageKey.Event.Settings.ERROR, false, "Test");
assertEquals("§cImpossible de mettre à jour vos paramètres. §7§o(Test)", translation);
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,73 +0,0 @@
package xyz.ineanto.nicko.test.migration;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.DefaultDataSources;
import xyz.ineanto.nicko.language.CustomLanguage;
import xyz.ineanto.nicko.migration.CustomLocaleMigrator;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MigrationTest {
private static Nicko plugin;
private static File folder;
private static File localeFile;
@BeforeAll
public static void setup() throws IOException {
MockBukkit.mock();
final Configuration configuration = new Configuration(Configuration.VERSION.toString(),
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
true);
plugin = MockBukkit.load(Nicko.class, configuration);
folder = new File(plugin.getDataFolder(), "/locale/");
localeFile = new File(folder, "locale.yml");
folder.mkdirs();
localeFile.createNewFile();
}
@Test
public void testLanguageFileMigration() throws IOException {
final String content = """
# Nicko - Language File:
# hello I'm the invalid version
version: "1.0.0"
""";
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(localeFile));
outputStream.write(content.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
// Get wrong locale
final CustomLanguage customLanguageBeforeMigration = new CustomLanguage();
assertEquals(customLanguageBeforeMigration.getVersion(), "1.0.0");
// Migrate the wrong locale to the correct one
final CustomLocaleMigrator localeMigrator = new CustomLocaleMigrator(plugin, customLanguageBeforeMigration);
localeMigrator.migrate();
// Get the migrated locale
final CustomLanguage customLanguageMigrated = new CustomLanguage();
assertEquals(customLanguageMigrated.getVersion(), "1.1.0");
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
folder.delete();
localeFile.delete();
}
}

View file

@ -1,42 +0,0 @@
package xyz.ineanto.nicko.test.storage;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.profile.NickoProfile;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MapCacheTest {
private static Nicko plugin;
private static PlayerMock player;
@BeforeAll
public static void setup() {
final Configuration config = Configuration.DEFAULT;
final ServerMock server = MockBukkit.mock();
plugin = MockBukkit.load(Nicko.class, config);
player = server.addPlayer();
}
@Test
@DisplayName("Cache Player Data")
public void cachePlayerData() {
final Optional<NickoProfile> optionalProfile = plugin.getDataStore().getData(player.getUniqueId());
assertTrue(optionalProfile.isPresent());
assertTrue(plugin.getDataStore().getCache().isCached(player.getUniqueId()));
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,78 +0,0 @@
package xyz.ineanto.nicko.test.storage;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.junit.jupiter.api.*;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.appearance.ActionResult;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.DataSourceConfiguration;
import xyz.ineanto.nicko.config.DefaultDataSources;
import xyz.ineanto.nicko.profile.NickoProfile;
import xyz.ineanto.nicko.storage.PlayerDataStore;
import xyz.ineanto.nicko.storage.redis.RedisCacheProvider;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RedisCacheTest {
private static Nicko plugin;
private static PlayerMock player;
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
"",
DefaultDataSources.SQL_EMPTY,
new DataSourceConfiguration(true, "127.0.0.1", 6379, "", ""),
false);
final ServerMock server = MockBukkit.mock();
plugin = MockBukkit.load(Nicko.class, config);
player = server.addPlayer();
assertInstanceOf(RedisCacheProvider.class, plugin.getDataStore().getCache().getProvider());
}
@Test
@DisplayName("Cache Profile")
@Order(1)
public void cacheProfile() {
final Optional<NickoProfile> optionalProfile = plugin.getDataStore().getData(player.getUniqueId());
assertTrue(optionalProfile.isPresent());
assertTrue(plugin.getDataStore().getCache().isCached(player.getUniqueId()));
}
@Test
@DisplayName("Update Cache Profile")
@Order(2)
public void updateCache() {
final Optional<NickoProfile> optionalProfile = NickoProfile.get(player);
assertTrue(optionalProfile.isPresent());
final NickoProfile profile = optionalProfile.get();
final PlayerDataStore dataStore = plugin.getDataStore();
profile.setName("Notch");
dataStore.updateCache(player.getUniqueId(), profile);
final Optional<NickoProfile> retrieve = dataStore.getCache().retrieve(player.getUniqueId());
assertTrue(retrieve.isPresent());
final NickoProfile retrieved = retrieve.get();
assertEquals(retrieved.getName(), "Notch");
}
@Test
@DisplayName("Delete Cache Profile")
@Order(3)
public void deleteCache() {
final PlayerDataStore dataStore = plugin.getDataStore();
final ActionResult cacheDelete = dataStore.getCache().delete(player.getUniqueId());
assertFalse(cacheDelete.isError());
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -1,104 +0,0 @@
package xyz.ineanto.nicko.test.storage;
import be.seeseemelk.mockbukkit.MockBukkit;
import org.junit.jupiter.api.*;
import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.appearance.ActionResult;
import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.DefaultDataSources;
import xyz.ineanto.nicko.config.SQLDataSourceConfiguration;
import xyz.ineanto.nicko.language.Language;
import xyz.ineanto.nicko.profile.NickoProfile;
import xyz.ineanto.nicko.storage.PlayerDataStore;
import xyz.ineanto.nicko.storage.mariadb.MariaDBStorageProvider;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SQLStorageTest {
private static PlayerDataStore dataStore;
private static UUID uuid;
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
"",
new SQLDataSourceConfiguration(true, "127.0.0.1", 3306, "root", "12345", true),
DefaultDataSources.REDIS_EMPTY,
false);
MockBukkit.mock();
final Nicko plugin = MockBukkit.load(Nicko.class, config);
dataStore = plugin.getDataStore();
uuid = UUID.randomUUID();
assertInstanceOf(MariaDBStorageProvider.class, dataStore.getStorage().getProvider());
}
@Test
@DisplayName("Create tables")
@Order(1)
public void createTables() {
assertFalse(dataStore.getStorage().isError());
}
@Test
@DisplayName("Store empty profile")
@Order(2)
public void storeEmptyProfile() {
final Optional<NickoProfile> optionalProfile = NickoProfile.get(uuid);
assertTrue(optionalProfile.isPresent());
}
@Test
@DisplayName("Update profile")
@Order(3)
public void updateProfile() {
final Optional<NickoProfile> optionalProfile = NickoProfile.get(uuid);
assertTrue(optionalProfile.isPresent());
final NickoProfile profile = optionalProfile.get();
assertNull(profile.getName());
assertNull(profile.getSkin());
assertEquals(profile.getLocale(), Language.ENGLISH);
assertTrue(profile.isRandomSkin());
profile.setName("Notch");
profile.setSkin("Notch");
profile.setLocale(Language.FRENCH);
profile.setRandomSkin(false);
final ActionResult result = dataStore.getStorage().store(uuid, profile);
assertFalse(result.isError());
}
@Test
@DisplayName("Get updated profile")
@Order(4)
public void hasProfileBeenUpdated() {
final Optional<NickoProfile> optionalProfile = NickoProfile.get(uuid);
assertTrue(optionalProfile.isPresent());
final NickoProfile updatedProfile = optionalProfile.get();
assertEquals(updatedProfile.getName(), "Notch");
assertEquals(updatedProfile.getSkin(), "Notch");
assertEquals(updatedProfile.getLocale(), Language.FRENCH);
assertFalse(updatedProfile.isRandomSkin());
}
@Test
@DisplayName("Delete profile")
@Order(5)
public void deleteProfile() {
final ActionResult sqlDelete = dataStore.getStorage().delete(uuid);
assertFalse(sqlDelete.isError());
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}