feat: init pe

This commit is contained in:
ineanto 2025-06-27 14:54:35 +02:00
parent 073d96bf99
commit ab2ffcbd37
Signed by: ineanto
GPG key ID: E511F9CAA2F9CE84
5 changed files with 234 additions and 189 deletions

View file

@ -2,7 +2,6 @@ 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.0"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
} }
group = "xyz.ineanto" group = "xyz.ineanto"
@ -31,14 +30,14 @@ repositories {
} }
dependencies { dependencies {
paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.21.6-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.5") compileOnly("me.clip:placeholderapi:2.11.5")
compileOnly("net.kyori:adventure-api:4.21.0") compileOnly("net.kyori:adventure-api:4.21.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.github.retrooper:packetevents-spigot:2.8.0")
implementation("com.github.retrooper:packetevents-spigot:2.8.0")
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")

View file

@ -2,6 +2,7 @@ package xyz.ineanto.nicko;
import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.manager.server.ServerVersion;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -39,20 +40,29 @@ public class Nicko extends JavaPlugin {
private PlayerNameStore nameStore; private PlayerNameStore nameStore;
private RandomNameFetcher nameFetcher; private RandomNameFetcher nameFetcher;
@Override
public void onLoad() {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().load();
}
@Override @Override
public void onEnable() { public void onEnable() {
plugin = this; plugin = this;
PacketEvents.getAPI().init();
PacketEvents.getAPI().getSettings().checkForUpdates(false).kickOnPacketException(true);
configurationManager = new ConfigurationManager(getDataFolder()); configurationManager = new ConfigurationManager(getDataFolder());
configurationManager.saveDefaultConfig(); configurationManager.saveDefaultConfig();
dataStore = new PlayerDataStore(mojangAPI, getNickoConfig()); dataStore = new PlayerDataStore(mojangAPI, getNickoConfig());
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_20)) { if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_20)) {
getLogger().severe("This version (" + PacketEvents.getAPI().getServerManager().getVersion() + ") is not supported by Nicko!"); getLogger().severe("This version (" + PacketEvents.getAPI().getServerManager().getVersion() + ") is not officially supported by Nicko!");
getLogger().severe("As of version 1.2.0, Nicko only supports the two latest major Minecraft versions. (Currently 1.20 to 1.21.5)"); getLogger().severe("As of version 1.3.0, Nicko only supports the two latest major Minecraft versions. (Currently 1.20 to 1.21.5)");
dataStore.getStorage().setError(true); getLogger().severe("Do NOT complain about it not working, you've been warned!");
Bukkit.getPluginManager().disablePlugin(this);
} }
if (!Bukkit.getOnlineMode()) { if (!Bukkit.getOnlineMode()) {

View file

@ -5,8 +5,8 @@ import org.bukkit.entity.Player;
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.PacketEventsPacketSender;
import xyz.ineanto.nicko.packet.PacketSender; import xyz.ineanto.nicko.packet.PacketSender;
import xyz.ineanto.nicko.packet.PaperPacketSender;
import xyz.ineanto.nicko.profile.NickoProfile; import xyz.ineanto.nicko.profile.NickoProfile;
import xyz.ineanto.nicko.storage.PlayerDataStore; import xyz.ineanto.nicko.storage.PlayerDataStore;
import xyz.ineanto.nicko.storage.name.PlayerNameStore; import xyz.ineanto.nicko.storage.name.PlayerNameStore;
@ -23,7 +23,7 @@ public class AppearanceManager {
public AppearanceManager(Player player) { public AppearanceManager(Player player) {
this.player = player; this.player = player;
this.packetSender = new PaperPacketSender(player, getNickoProfile()); this.packetSender = new PacketEventsPacketSender(player, getNickoProfile());
} }
public ActionResult reset() { public ActionResult reset() {

View file

@ -1,10 +1,17 @@
package xyz.ineanto.nicko.packet; package xyz.ineanto.nicko.packet;
import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.destroystokyo.paper.profile.PlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile;
import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.world.Difficulty;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerDestroyEntities;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRespawn;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerSpawnEntity;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import xyz.ineanto.nicko.Nicko; import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.appearance.ActionResult; import xyz.ineanto.nicko.appearance.ActionResult;
@ -15,6 +22,7 @@ import xyz.ineanto.nicko.profile.NickoProfile;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public class PacketEventsPacketSender implements PacketSender { public class PacketEventsPacketSender implements PacketSender {
@ -30,27 +38,39 @@ public class PacketEventsPacketSender implements PacketSender {
public void sendEntityRespawn() { public void sendEntityRespawn() {
if (!profile.hasData()) return; if (!profile.hasData()) return;
// TODO (Ineanto, 27/06/2025): Create/Delete packets here final WrapperPlayServerDestroyEntities destroy = new WrapperPlayServerDestroyEntities(player.getEntityId());
final WrapperPlayServerSpawnEntity spawn = new WrapperPlayServerSpawnEntity(
new Random().nextInt(9999),
Optional.of(player.getUniqueId()),
EntityTypes.PLAYER,
new Vector3d(player.getX(), player.getY(), player.getZ()),
player.getPitch(),
player.getYaw(),
player.getBodyYaw(),
0,
Optional.empty()
);
Bukkit.getOnlinePlayers().stream().filter(receiver -> receiver.getUniqueId() != player.getUniqueId()).forEach(receiver -> { Bukkit.getOnlinePlayers().stream().filter(receiver -> receiver.getUniqueId() != player.getUniqueId()).forEach(receiver -> {
// TODO (Ineanto, 27/06/2025): Send packets sendPacket(destroy, player);
//sendPacket(destroy, player); sendPacket(spawn, player);
//sendPacket(create, player);
}); });
} }
@Override @Override
public ActionResult updatePlayerProfile(String name) { public ActionResult updatePlayerProfile(String name) {
final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), name); final PlayerProfile previousProfile = player.getPlayerProfile();
final PlayerProfile newProfile = Bukkit.getServer().createProfile(player.getUniqueId(), name);
// Copy previous properties to preserve skin // Copy previous properties to preserve skin
playerProfile.setProperties(playerProfile.getProperties()); newProfile.setProperties(previousProfile.getProperties());
player.setPlayerProfile(playerProfile); player.setPlayerProfile(newProfile);
return ActionResult.ok(); return ActionResult.ok();
} }
@Override @Override
public ActionResult updatePlayerProfileProperties() { public ActionResult updatePlayerProfileProperties() {
// TODO (Ineanto, 27/06/2025): Player profile final PlayerProfile playerProfile = player.getPlayerProfile();
try { try {
final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI(); final MojangAPI mojangAPI = Nicko.getInstance().getMojangAPI();
@ -66,8 +86,8 @@ public class PacketEventsPacketSender implements PacketSender {
} }
final MojangSkin skinResult = skin.get(); final MojangSkin skinResult = skin.get();
//playerProfile.setProperties(skinResult.asProfileProperties()); playerProfile.setProperties(skinResult.asProfileProperties());
//player.setPlayerProfile(playerProfile); player.setPlayerProfile(playerProfile);
return ActionResult.ok(); return ActionResult.ok();
} catch (ExecutionException | IOException e) { } catch (ExecutionException | IOException e) {
return ActionResult.error(LanguageKey.Error.CACHE); return ActionResult.error(LanguageKey.Error.CACHE);
@ -82,8 +102,24 @@ public class PacketEventsPacketSender implements PacketSender {
@Override @Override
public void sendPlayerRespawn() { public void sendPlayerRespawn() {
// TODO (Ineanto, 27/06/2025): Respawn packet final World world = player.getWorld();
//sendPacket(respawn, player);
final WrapperPlayServerRespawn respawn = new WrapperPlayServerRespawn(
SpigotConversionUtil.typeFromBukkitWorld(world),
world.getName(),
Difficulty.getById(world.getDifficulty().ordinal()),
world.getSeed(),
SpigotConversionUtil.fromBukkitGameMode(player.getGameMode()),
SpigotConversionUtil.fromBukkitGameMode(player.getPreviousGameMode()),
false,
false,
true,
null,
null,
null
);
sendPacket(respawn, player);
} }
@Override @Override

View file

@ -1,167 +1,167 @@
package xyz.ineanto.nicko.packet; //package xyz.ineanto.nicko.packet;
//
import com.destroystokyo.paper.profile.CraftPlayerProfile; //import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.destroystokyo.paper.profile.PlayerProfile; //import com.destroystokyo.paper.profile.PlayerProfile;
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;
import net.minecraft.network.chat.RemoteChatSession; //import net.minecraft.network.chat.RemoteChatSession;
import net.minecraft.network.chat.contents.PlainTextContents; //import net.minecraft.network.chat.contents.PlainTextContents;
import net.minecraft.network.protocol.Packet; //import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*; //import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor; //import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; //import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData; //import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel; //import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; //import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType; //import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3; //import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit; //import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.entity.CraftPlayer; //import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player; //import org.bukkit.entity.Player;
import xyz.ineanto.nicko.Nicko; //import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.appearance.ActionResult; //import xyz.ineanto.nicko.appearance.ActionResult;
import xyz.ineanto.nicko.language.LanguageKey; //import xyz.ineanto.nicko.language.LanguageKey;
import xyz.ineanto.nicko.mojang.MojangAPI; //import xyz.ineanto.nicko.mojang.MojangAPI;
import xyz.ineanto.nicko.mojang.MojangSkin; //import xyz.ineanto.nicko.mojang.MojangSkin;
import xyz.ineanto.nicko.profile.NickoProfile; //import xyz.ineanto.nicko.profile.NickoProfile;
//
import java.io.IOException; //import java.io.IOException;
import java.util.EnumSet; //import java.util.EnumSet;
import java.util.List; //import java.util.List;
import java.util.Optional; //import java.util.Optional;
import java.util.Random; //import java.util.Random;
import java.util.concurrent.ExecutionException; //import java.util.concurrent.ExecutionException;
//
/** ///**
* Look at this Mojang. // * Look at this Mojang.
* 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 PaperPacketSender implements PacketSender { //public class PaperPacketSender implements PacketSender {
private final Player player; // private final Player player;
private final NickoProfile profile; // private final NickoProfile profile;
//
public PaperPacketSender(Player player, NickoProfile profile) { // public PaperPacketSender(Player player, NickoProfile profile) {
this.player = player; // this.player = player;
this.profile = profile; // this.profile = profile;
} // }
//
@Override // @Override
public void sendEntityRespawn() { // public void sendEntityRespawn() {
if (!profile.hasData()) return; // if (!profile.hasData()) return;
//
final ClientboundRemoveEntitiesPacket destroy = new ClientboundRemoveEntitiesPacket(IntList.of(player.getEntityId())); // final ClientboundRemoveEntitiesPacket destroy = new ClientboundRemoveEntitiesPacket(IntList.of(player.getEntityId()));
final ClientboundAddEntityPacket add = new ClientboundAddEntityPacket( // final ClientboundAddEntityPacket add = new ClientboundAddEntityPacket(
new Random().nextInt(9999), // new Random().nextInt(9999),
player.getUniqueId(), // player.getUniqueId(),
player.getX(), // player.getX(),
player.getY(), // player.getY(),
player.getZ(), // player.getZ(),
player.getPitch(), // player.getPitch(),
player.getYaw(), // player.getYaw(),
EntityType.PLAYER, // EntityType.PLAYER,
0, // 0,
Vec3.ZERO, // Vec3.ZERO,
player.getBodyYaw() // player.getBodyYaw()
); // );
//
Bukkit.getOnlinePlayers().stream().filter(receiver -> receiver.getUniqueId() != player.getUniqueId()).forEach(receiver -> { // Bukkit.getOnlinePlayers().stream().filter(receiver -> receiver.getUniqueId() != player.getUniqueId()).forEach(receiver -> {
sendPacket(destroy, player); // sendPacket(destroy, player);
sendPacket(add, player); // sendPacket(add, player);
}); // });
} // }
//
@Override // @Override
public ActionResult updatePlayerProfile(String name) { // public ActionResult updatePlayerProfile(String name) {
final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), name); // final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), name);
// Copy previous properties to preserve skin // // Copy previous properties to preserve skin
playerProfile.setProperties(playerProfile.getProperties()); // playerProfile.setProperties(playerProfile.getProperties());
player.setPlayerProfile(playerProfile); // player.setPlayerProfile(playerProfile);
return ActionResult.ok(); // return ActionResult.ok();
} // }
//
@Override // @Override
public ActionResult updatePlayerProfileProperties() { // public ActionResult updatePlayerProfileProperties() {
final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), profile.getName() == null ? player.getName() : profile.getName()); // final PlayerProfile playerProfile = new CraftPlayerProfile(player.getUniqueId(), profile.getName() == null ? player.getName() : profile.getName());
//
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.isEmpty()) { // if (uuid.isEmpty()) {
return ActionResult.error(LanguageKey.Error.MOJANG); // return ActionResult.error(LanguageKey.Error.MOJANG);
} // }
//
final Optional<MojangSkin> skin = mojangAPI.getSkin(uuid.get()); // final Optional<MojangSkin> skin = mojangAPI.getSkin(uuid.get());
if (skin.isEmpty()) { // if (skin.isEmpty()) {
return ActionResult.error(LanguageKey.Error.MOJANG); // return ActionResult.error(LanguageKey.Error.MOJANG);
} // }
//
final MojangSkin skinResult = skin.get(); // final MojangSkin skinResult = skin.get();
playerProfile.setProperties(skinResult.asProfileProperties()); // playerProfile.setProperties(skinResult.asProfileProperties());
player.setPlayerProfile(playerProfile); // player.setPlayerProfile(playerProfile);
return ActionResult.ok(); // return ActionResult.ok();
} catch (ExecutionException | IOException e) { // } catch (ExecutionException | IOException e) {
return ActionResult.error(LanguageKey.Error.CACHE); // return ActionResult.error(LanguageKey.Error.CACHE);
} // }
} // }
//
@Override // @Override
public void sendEntityMetadataUpdate() { // public void sendEntityMetadataUpdate() {
final SynchedEntityData.DataValue<?> dataValueComponent = // final SynchedEntityData.DataValue<?> dataValueComponent =
new SynchedEntityData.DataItem<>( // new SynchedEntityData.DataItem<>(
new EntityDataAccessor<>(17, EntityDataSerializers.BYTE), // new EntityDataAccessor<>(17, EntityDataSerializers.BYTE),
(byte) 0x7f // (byte) 0x7f
).value(); // ).value();
//
final ClientboundSetEntityDataPacket data = new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(dataValueComponent)); // final ClientboundSetEntityDataPacket data = new ClientboundSetEntityDataPacket(player.getEntityId(), List.of(dataValueComponent));
sendPacket(data, player); // sendPacket(data, player);
} // }
//
@Override // @Override
public void sendPlayerRespawn() { // public void sendPlayerRespawn() {
final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); // final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle();
final ServerLevel level = serverPlayer.serverLevel(); // final ServerLevel level = serverPlayer.serverLevel();
//
final ClientboundRespawnPacket respawn = new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(level), (byte) 0x03); // final ClientboundRespawnPacket respawn = new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(level), (byte) 0x03);
sendPacket(respawn, player); // sendPacket(respawn, player);
} // }
//
@Override // @Override
public void sendTabListUpdate(String displayName) { // public void sendTabListUpdate(String displayName) {
final ServerPlayer serverPlayer = (((CraftPlayer) player)).getHandle(); // final ServerPlayer serverPlayer = (((CraftPlayer) player)).getHandle();
//
final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.of( // final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = EnumSet.of(
ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, // ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER,
ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, // ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, // ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, // ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, // ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE,
ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY); // ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
//
final List<ClientboundPlayerInfoUpdatePacket.Entry> entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry( // final List<ClientboundPlayerInfoUpdatePacket.Entry> entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry(
serverPlayer.getUUID(), // serverPlayer.getUUID(),
serverPlayer.gameProfile, // serverPlayer.gameProfile,
true, // true,
serverPlayer.connection.latency(), // serverPlayer.connection.latency(),
serverPlayer.gameMode.getGameModeForPlayer(), // serverPlayer.gameMode.getGameModeForPlayer(),
MutableComponent.create(new PlainTextContents.LiteralContents(displayName)), // MutableComponent.create(new PlainTextContents.LiteralContents(displayName)),
true, // true,
serverPlayer.getTabListOrder(), // serverPlayer.getTabListOrder(),
Optionull.map(serverPlayer.getChatSession(), RemoteChatSession::asData) // Optionull.map(serverPlayer.getChatSession(), RemoteChatSession::asData)
)); // ));
//
final ClientboundPlayerInfoUpdatePacket update = new ClientboundPlayerInfoUpdatePacket(actions, entries); // final ClientboundPlayerInfoUpdatePacket update = new ClientboundPlayerInfoUpdatePacket(actions, entries);
final ClientboundPlayerInfoRemovePacket remove = new ClientboundPlayerInfoRemovePacket(List.of(player.getUniqueId())); // final ClientboundPlayerInfoRemovePacket remove = new ClientboundPlayerInfoRemovePacket(List.of(player.getUniqueId()));
//
Bukkit.getOnlinePlayers().forEach(onlinePlayer -> { // Bukkit.getOnlinePlayers().forEach(onlinePlayer -> {
sendPacket(remove, onlinePlayer); // sendPacket(remove, onlinePlayer);
sendPacket(update, onlinePlayer); // sendPacket(update, onlinePlayer);
}); // });
} // }
//
private void sendPacket(Packet<?> packet, Player player) { // private void sendPacket(Packet<?> packet, Player player) {
(((CraftPlayer) player).getHandle()).connection.send(packet); // (((CraftPlayer) player).getHandle()).connection.send(packet);
} // }
} //}