From f72d5c0948534286707b390684b64bd3b49fe838 Mon Sep 17 00:00:00 2001 From: ineanto Date: Thu, 31 Oct 2024 20:05:22 +0100 Subject: [PATCH] feat: implemented NMS packet sender --- build.gradle.kts | 4 +- src/main/java/xyz/ineanto/nicko/Nicko.java | 4 - .../xyz/ineanto/nicko/anvil/AnvilManager.java | 2 +- .../nicko/appearance/AppearanceManager.java | 151 +++--------------- .../nicko/event/PlayerJoinListener.java | 4 +- .../nicko/gui/items/home/RandomSkinItem.java | 2 +- .../nicko/packet/InternalPacketSender.java | 125 +++++++++++++++ .../ineanto/nicko/packet/PacketSender.java | 11 ++ .../nicko/packet/WrapperPacketSender.java | 139 ++++++++++++++++ .../debug/RespawnPacketListener.java | 2 +- .../{ => packet}/wrapper/AbstractPacket.java | 2 +- .../WrapperPlayServerEntityDestroy.java | 2 +- .../wrapper/WrapperPlayServerRespawn.java | 13 +- .../wrapper/WrapperPlayServerSpawnEntity.java | 2 +- .../WrapperPlayerServerPlayerInfo.java | 2 +- .../WrapperPlayerServerPlayerInfoRemove.java | 2 +- .../ineanto/nicko/profile/NickoProfile.java | 7 +- 17 files changed, 325 insertions(+), 149 deletions(-) create mode 100644 src/main/java/xyz/ineanto/nicko/packet/InternalPacketSender.java create mode 100644 src/main/java/xyz/ineanto/nicko/packet/WrapperPacketSender.java rename src/main/java/xyz/ineanto/nicko/{ => packet}/debug/RespawnPacketListener.java (98%) rename src/main/java/xyz/ineanto/nicko/{ => packet}/wrapper/AbstractPacket.java (98%) rename src/main/java/xyz/ineanto/nicko/{ => packet}/wrapper/WrapperPlayServerEntityDestroy.java (95%) rename src/main/java/xyz/ineanto/nicko/{ => packet}/wrapper/WrapperPlayServerRespawn.java (89%) rename src/main/java/xyz/ineanto/nicko/{ => packet}/wrapper/WrapperPlayServerSpawnEntity.java (98%) rename src/main/java/xyz/ineanto/nicko/{ => packet}/wrapper/WrapperPlayerServerPlayerInfo.java (96%) rename src/main/java/xyz/ineanto/nicko/{ => packet}/wrapper/WrapperPlayerServerPlayerInfoRemove.java (94%) diff --git a/build.gradle.kts b/build.gradle.kts index 5a2401e..8a6d329 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("java") id("com.gradleup.shadow") version "8.3.2" id("xyz.jpenilla.run-paper") version "2.3.0" + id("io.papermc.paperweight.userdev") version "1.7.4" } group = "xyz.ineanto" @@ -38,7 +39,8 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.3-R0.1-SNAPSHOT") + paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT") + compileOnly("com.github.dmulloy2:ProtocolLib:5.3.0") compileOnly("me.clip:placeholderapi:2.11.5") compileOnly("net.kyori:adventure-api:4.17.0") diff --git a/src/main/java/xyz/ineanto/nicko/Nicko.java b/src/main/java/xyz/ineanto/nicko/Nicko.java index 40c6fb5..a0621bb 100644 --- a/src/main/java/xyz/ineanto/nicko/Nicko.java +++ b/src/main/java/xyz/ineanto/nicko/Nicko.java @@ -1,6 +1,5 @@ package xyz.ineanto.nicko; -import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.utility.MinecraftVersion; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; @@ -11,7 +10,6 @@ import xyz.ineanto.nicko.appearance.random.RandomNameFetcher; import xyz.ineanto.nicko.command.NickoCommand; import xyz.ineanto.nicko.config.Configuration; import xyz.ineanto.nicko.config.ConfigurationManager; -import xyz.ineanto.nicko.debug.RespawnPacketListener; import xyz.ineanto.nicko.event.PlayerJoinListener; import xyz.ineanto.nicko.event.PlayerQuitListener; import xyz.ineanto.nicko.language.CustomLanguage; @@ -134,8 +132,6 @@ public class Nicko extends JavaPlugin { getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this); getServer().getPluginManager().registerEvents(new PlayerQuitListener(), this); metrics = new Metrics(this, 20483); - - ProtocolLibrary.getProtocolManager().addPacketListener(new RespawnPacketListener()); } getLogger().info("Nicko has been enabled."); diff --git a/src/main/java/xyz/ineanto/nicko/anvil/AnvilManager.java b/src/main/java/xyz/ineanto/nicko/anvil/AnvilManager.java index 38c7f73..db7a7cd 100644 --- a/src/main/java/xyz/ineanto/nicko/anvil/AnvilManager.java +++ b/src/main/java/xyz/ineanto/nicko/anvil/AnvilManager.java @@ -114,7 +114,7 @@ public class AnvilManager { Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { return Collections.singletonList(AnvilGUI.ResponseAction.close()); } - final ActionResult actionResult = appearanceManager.updatePlayer(skinChange, false); + final ActionResult actionResult = appearanceManager.update(skinChange, false); if (!actionResult.isError()) { player.sendMessage(playerLanguage.translate(LanguageKey.Event.Appearance.Set.OK, true)); } else { diff --git a/src/main/java/xyz/ineanto/nicko/appearance/AppearanceManager.java b/src/main/java/xyz/ineanto/nicko/appearance/AppearanceManager.java index 7e705f6..fba9286 100644 --- a/src/main/java/xyz/ineanto/nicko/appearance/AppearanceManager.java +++ b/src/main/java/xyz/ineanto/nicko/appearance/AppearanceManager.java @@ -1,36 +1,27 @@ package xyz.ineanto.nicko.appearance; -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 org.bukkit.event.player.PlayerTeleportEvent; import xyz.ineanto.nicko.Nicko; -import xyz.ineanto.nicko.language.LanguageKey; -import xyz.ineanto.nicko.mojang.MojangAPI; -import xyz.ineanto.nicko.mojang.MojangSkin; +import xyz.ineanto.nicko.packet.InternalPacketSender; +import xyz.ineanto.nicko.packet.PacketSender; import xyz.ineanto.nicko.profile.NickoProfile; import xyz.ineanto.nicko.storage.PlayerDataStore; import xyz.ineanto.nicko.storage.name.PlayerNameStore; -import xyz.ineanto.nicko.wrapper.*; -import java.io.IOException; -import java.util.EnumSet; -import java.util.List; import java.util.Optional; -import java.util.concurrent.ExecutionException; public class AppearanceManager { private final Nicko instance = Nicko.getInstance(); private final PlayerDataStore dataStore = instance.getDataStore(); private final PlayerNameStore nameStore = instance.getNameStore(); + private final PacketSender packetSender; private final Player player; public AppearanceManager(Player player) { this.player = player; + this.packetSender = new InternalPacketSender(player, getNickoProfile()); } public ActionResult reset() { @@ -40,7 +31,7 @@ public class AppearanceManager { profile.setSkin(defaultName); dataStore.getCache().cache(player.getUniqueId(), profile); - final ActionResult result = updatePlayer(true, true); + final ActionResult result = update(true, true); if (!result.isError()) { profile.setName(null); profile.setSkin(null); @@ -49,109 +40,28 @@ public class AppearanceManager { return result; } - public ActionResult updatePlayer(boolean skinChange, boolean reset) { + public ActionResult update(boolean skinChange, boolean reset) { final NickoProfile profile = getNickoProfile(); final String displayName = profile.getName() == null ? player.getName() : profile.getName(); - final WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player).withName(displayName); - final ActionResult result = updateGameProfileSkin(gameProfile, skinChange, reset); - if (!result.isError()) { - updateMetadata(); - updateTabList(gameProfile, displayName); - respawnPlayer(); - respawnEntityForOthers(); - } + + final ActionResult result = packetSender.sendGameProfileUpdate(displayName, skinChange, reset); + + if (result.isError()) { reset(); } + + packetSender.sendEntityMetadataUpdate(); + packetSender.sendTabListUpdate(displayName); + respawnPlayer(); + packetSender.sendEntityRespawn(); + return result; } - public ActionResult updateForOthers(boolean skinChange, boolean reset) { - final NickoProfile profile = getNickoProfile(); - final String displayName = profile.getName() == null ? player.getName() : profile.getName(); - final WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player).withName(displayName); - final ActionResult result = updateGameProfileSkin(gameProfile, skinChange, reset); - if (!result.isError()) { - updateMetadata(); - updateTabList(gameProfile, displayName); - respawnEntityForOthers(); - } - return result; - } - - private NickoProfile getNickoProfile() { - final Optional optionalProfile = dataStore.getData(player.getUniqueId()); - return optionalProfile.orElse(NickoProfile.EMPTY_PROFILE.clone()); - } - - public void respawnEntityForOthers() { - final NickoProfile nickoProfile = getNickoProfile(); - if (!nickoProfile.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); - }); - } - - - private ActionResult updateGameProfileSkin(WrappedGameProfile gameProfile, boolean skinChange, boolean reset) { - final NickoProfile profile = getNickoProfile(); - - if (skinChange) { - Optional skin; - try { - final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI(); - final Optional 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 properties = gameProfile.getProperties(); - properties.get("textures").clear(); - properties.put("textures", new WrappedSignedProperty("textures", skinResult.value(), skinResult.signature())); - } else { - reset(); - return ActionResult.error(LanguageKey.Error.MOJANG_SKIN); - } - } else { - reset(); - return ActionResult.error(LanguageKey.Error.MOJANG_NAME); - } - return ActionResult.ok(); - } catch (ExecutionException e) { - return ActionResult.error(LanguageKey.Error.CACHE); - } catch (IOException e) { - reset(); - return ActionResult.error(LanguageKey.Error.MOJANG_NAME); - } catch (InterruptedException e) { - return ActionResult.error("Unknown error"); - } - } - return ActionResult.ok(); - } - - private void updateMetadata() { - final WrappedDataWatcher entityWatcher = WrappedDataWatcher.getEntityWatcher(player); - entityWatcher.setObject(17, (byte) 0x7f, true); - } - private void respawnPlayer() { - final World world = player.getWorld(); final boolean wasFlying = player.isFlying(); final boolean wasAllowedToFly = player.getAllowFlight(); final int foodLevel = player.getFoodLevel(); - 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); + packetSender.sendPlayerRespawn(); player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); player.setAllowFlight(wasAllowedToFly); @@ -161,29 +71,8 @@ public class AppearanceManager { player.setFoodLevel(foodLevel); } - private void updateTabList(WrappedGameProfile gameProfile, String displayName) { - final WrapperPlayerServerPlayerInfo add = new WrapperPlayerServerPlayerInfo(); - final WrapperPlayerServerPlayerInfoRemove remove = new WrapperPlayerServerPlayerInfoRemove(); - final EnumSet 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(); + private NickoProfile getNickoProfile() { + final Optional optionalProfile = dataStore.getData(player.getUniqueId()); + return optionalProfile.orElse(NickoProfile.EMPTY_PROFILE.clone()); } } \ No newline at end of file diff --git a/src/main/java/xyz/ineanto/nicko/event/PlayerJoinListener.java b/src/main/java/xyz/ineanto/nicko/event/PlayerJoinListener.java index b7e4408..28171f5 100644 --- a/src/main/java/xyz/ineanto/nicko/event/PlayerJoinListener.java +++ b/src/main/java/xyz/ineanto/nicko/event/PlayerJoinListener.java @@ -50,7 +50,7 @@ public class PlayerJoinListener implements Listener { if (profile.hasData()) { final AppearanceManager appearanceManager = new AppearanceManager(player); final boolean needsASkinChange = profile.getSkin() != null && !profile.getSkin().equals(player.getName()); - final ActionResult actionResult = appearanceManager.updatePlayer(needsASkinChange, false); + final ActionResult actionResult = appearanceManager.update(needsASkinChange, false); if (!actionResult.isError()) { player.sendMessage(playerLanguage.translateWithWhoosh(LanguageKey.Event.Appearance.Restore.OK)); } else { @@ -68,7 +68,7 @@ public class PlayerJoinListener implements Listener { optionalOnlinePlayerProfile.ifPresent(profile -> { final AppearanceManager appearanceManager = new AppearanceManager(online); final boolean needsASkinChange = profile.getSkin() != null && !profile.getSkin().equals(online.getName()); - final ActionResult actionResult = appearanceManager.updateForOthers(needsASkinChange, false); + final ActionResult actionResult = appearanceManager.update(needsASkinChange, false); if (actionResult.isError()) { logger.warning("Something wrong happened while updating players to joining player (" + actionResult.getErrorKey() + ")"); } diff --git a/src/main/java/xyz/ineanto/nicko/gui/items/home/RandomSkinItem.java b/src/main/java/xyz/ineanto/nicko/gui/items/home/RandomSkinItem.java index 0306017..9753c3c 100644 --- a/src/main/java/xyz/ineanto/nicko/gui/items/home/RandomSkinItem.java +++ b/src/main/java/xyz/ineanto/nicko/gui/items/home/RandomSkinItem.java @@ -40,7 +40,7 @@ public class RandomSkinItem { instance.getDataStore().updateCache(player.getUniqueId(), profile); final AppearanceManager appearanceManager = new AppearanceManager(player); - final ActionResult result = appearanceManager.updatePlayer(true, false); + final ActionResult result = appearanceManager.update(true, false); if (!result.isError()) { player.sendMessage(playerLanguage.translate(LanguageKey.Event.Appearance.Set.OK, true)); } else { diff --git a/src/main/java/xyz/ineanto/nicko/packet/InternalPacketSender.java b/src/main/java/xyz/ineanto/nicko/packet/InternalPacketSender.java new file mode 100644 index 0000000..3fb5bd3 --- /dev/null +++ b/src/main/java/xyz/ineanto/nicko/packet/InternalPacketSender.java @@ -0,0 +1,125 @@ +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.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundRespawnPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +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.Entity; +import org.bukkit.Bukkit; +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.List; +import java.util.Optional; +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 Entity entityPlayer = (Entity) player; + + final ClientboundRemoveEntitiesPacket destroy = new ClientboundRemoveEntitiesPacket(IntList.of(player.getEntityId())); + final ClientboundAddEntityPacket add = new ClientboundAddEntityPacket(entityPlayer, 0, entityPlayer.getOnPos()); + + 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 = ((ServerPlayer) player).gameProfile; + + // TODO (Ineanto, 31/10/2024): Could this be refactored to get rid of the boolean? + if (skinChange) { + Optional skin; + try { + final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI(); + final Optional 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())); + } 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 = (ServerPlayer) player; + final ServerLevel world = (ServerLevel) player.getWorld(); + + final ClientboundRespawnPacket respawn = new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(world), (byte) 0x03); + sendPacket(respawn, player); + } + + @Override + public void sendTabListUpdate(String displayName) { + + } + + private void sendPacket(Packet packet, Player player) { + ((ServerPlayer) player).connection.send(packet); + } +} diff --git a/src/main/java/xyz/ineanto/nicko/packet/PacketSender.java b/src/main/java/xyz/ineanto/nicko/packet/PacketSender.java index f801398..ef93153 100644 --- a/src/main/java/xyz/ineanto/nicko/packet/PacketSender.java +++ b/src/main/java/xyz/ineanto/nicko/packet/PacketSender.java @@ -1,4 +1,15 @@ 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); } diff --git a/src/main/java/xyz/ineanto/nicko/packet/WrapperPacketSender.java b/src/main/java/xyz/ineanto/nicko/packet/WrapperPacketSender.java new file mode 100644 index 0000000..fc2b140 --- /dev/null +++ b/src/main/java/xyz/ineanto/nicko/packet/WrapperPacketSender.java @@ -0,0 +1,139 @@ +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 skin; + try { + final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI(); + final Optional 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 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 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(); + } +} diff --git a/src/main/java/xyz/ineanto/nicko/debug/RespawnPacketListener.java b/src/main/java/xyz/ineanto/nicko/packet/debug/RespawnPacketListener.java similarity index 98% rename from src/main/java/xyz/ineanto/nicko/debug/RespawnPacketListener.java rename to src/main/java/xyz/ineanto/nicko/packet/debug/RespawnPacketListener.java index 653c178..5222dd0 100644 --- a/src/main/java/xyz/ineanto/nicko/debug/RespawnPacketListener.java +++ b/src/main/java/xyz/ineanto/nicko/packet/debug/RespawnPacketListener.java @@ -1,4 +1,4 @@ -package xyz.ineanto.nicko.debug; +package xyz.ineanto.nicko.packet.debug; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.ListeningWhitelist; diff --git a/src/main/java/xyz/ineanto/nicko/wrapper/AbstractPacket.java b/src/main/java/xyz/ineanto/nicko/packet/wrapper/AbstractPacket.java similarity index 98% rename from src/main/java/xyz/ineanto/nicko/wrapper/AbstractPacket.java rename to src/main/java/xyz/ineanto/nicko/packet/wrapper/AbstractPacket.java index 1d90240..42e138b 100644 --- a/src/main/java/xyz/ineanto/nicko/wrapper/AbstractPacket.java +++ b/src/main/java/xyz/ineanto/nicko/packet/wrapper/AbstractPacket.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package xyz.ineanto.nicko.wrapper; +package xyz.ineanto.nicko.packet.wrapper; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; diff --git a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerEntityDestroy.java b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerEntityDestroy.java similarity index 95% rename from src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerEntityDestroy.java rename to src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerEntityDestroy.java index bc94876..a23f1a6 100644 --- a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerEntityDestroy.java +++ b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerEntityDestroy.java @@ -1,4 +1,4 @@ -package xyz.ineanto.nicko.wrapper; +package xyz.ineanto.nicko.packet.wrapper; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; diff --git a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerRespawn.java b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerRespawn.java similarity index 89% rename from src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerRespawn.java rename to src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerRespawn.java index 1ed2ec8..dc71b1f 100644 --- a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerRespawn.java +++ b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerRespawn.java @@ -1,4 +1,4 @@ -package xyz.ineanto.nicko.wrapper; +package xyz.ineanto.nicko.packet.wrapper; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.InternalStructure; @@ -56,11 +56,20 @@ public class WrapperPlayServerRespawn extends AbstractPacket { } 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(); - // Doesn't work! final Field levelKeyField = spawnInfoStructureHandle.getClass().getDeclaredField(components[1].getAccessor().getName()); levelKeyField.setAccessible(true); levelKeyField.set(spawnInfoStructureHandle, BukkitConverters.getWorldKeyConverter().getGeneric(Bukkit.getWorld("world"))); diff --git a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerSpawnEntity.java b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerSpawnEntity.java similarity index 98% rename from src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerSpawnEntity.java rename to src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerSpawnEntity.java index 7f09a16..0512717 100644 --- a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayServerSpawnEntity.java +++ b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayServerSpawnEntity.java @@ -1,4 +1,4 @@ -package xyz.ineanto.nicko.wrapper; +package xyz.ineanto.nicko.packet.wrapper; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; diff --git a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayerServerPlayerInfo.java b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayerServerPlayerInfo.java similarity index 96% rename from src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayerServerPlayerInfo.java rename to src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayerServerPlayerInfo.java index 3347f98..ca37dc0 100644 --- a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayerServerPlayerInfo.java +++ b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayerServerPlayerInfo.java @@ -1,4 +1,4 @@ -package xyz.ineanto.nicko.wrapper; +package xyz.ineanto.nicko.packet.wrapper; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; diff --git a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayerServerPlayerInfoRemove.java b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayerServerPlayerInfoRemove.java similarity index 94% rename from src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayerServerPlayerInfoRemove.java rename to src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayerServerPlayerInfoRemove.java index a619249..2273c48 100644 --- a/src/main/java/xyz/ineanto/nicko/wrapper/WrapperPlayerServerPlayerInfoRemove.java +++ b/src/main/java/xyz/ineanto/nicko/packet/wrapper/WrapperPlayerServerPlayerInfoRemove.java @@ -1,4 +1,4 @@ -package xyz.ineanto.nicko.wrapper; +package xyz.ineanto.nicko.packet.wrapper; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; diff --git a/src/main/java/xyz/ineanto/nicko/profile/NickoProfile.java b/src/main/java/xyz/ineanto/nicko/profile/NickoProfile.java index 27c5c3c..9f1460f 100644 --- a/src/main/java/xyz/ineanto/nicko/profile/NickoProfile.java +++ b/src/main/java/xyz/ineanto/nicko/profile/NickoProfile.java @@ -4,14 +4,19 @@ import org.bukkit.entity.Player; import xyz.ineanto.nicko.Nicko; import xyz.ineanto.nicko.language.Language; import xyz.ineanto.nicko.storage.PlayerDataStore; +import xyz.ineanto.nicko.storage.name.PlayerNameStore; import java.util.Optional; import java.util.UUID; public class NickoProfile implements Cloneable { - public static final PlayerDataStore dataStore = Nicko.getInstance().getDataStore(); public static final NickoProfile EMPTY_PROFILE = new NickoProfile(null, null, Language.ENGLISH, true); + private static final Nicko instance = Nicko.getInstance(); + private static final PlayerDataStore dataStore = instance.getDataStore(); + + private final PlayerNameStore nameStore = instance.getNameStore(); + private String name; private String skin; private Language language;