Compare commits

..

No commits in common. "f72d5c0948534286707b390684b64bd3b49fe838" and "d804fb1207658ead1801ee39a06a102dedab2ba0" have entirely different histories.

17 changed files with 152 additions and 334 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 "1.7.4"
} }
group = "xyz.ineanto" group = "xyz.ineanto"
@ -39,13 +38,12 @@ repositories {
} }
dependencies { dependencies {
paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
compileOnly("com.github.dmulloy2:ProtocolLib:-SNAPSHOT")
compileOnly("com.github.dmulloy2:ProtocolLib:5.3.0")
compileOnly("me.clip:placeholderapi:2.11.5") compileOnly("me.clip:placeholderapi:2.11.5")
compileOnly("net.kyori:adventure-api:4.17.0") compileOnly("net.kyori:adventure-api:4.17.0")
implementation("xyz.xenondevs.invui:invui:1.39") implementation("xyz.xenondevs.invui:invui:1.36")
implementation("net.wesjd:anvilgui:1.10.2-SNAPSHOT") implementation("net.wesjd:anvilgui:1.10.2-SNAPSHOT")
implementation("com.github.jsixface:yamlconfig:1.2") implementation("com.github.jsixface:yamlconfig:1.2")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2")
@ -56,7 +54,7 @@ dependencies {
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation("org.bstats:bstats-bukkit:3.0.2") implementation("org.bstats:bstats-bukkit:3.0.2")
testImplementation("com.github.MockBukkit:MockBukkit:v3.133.2") testImplementation("com.github.MockBukkit:MockBukkit:v3.99.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")

View file

@ -1,5 +1,6 @@
package xyz.ineanto.nicko; package xyz.ineanto.nicko;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftVersion;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -10,6 +11,7 @@ import xyz.ineanto.nicko.appearance.random.RandomNameFetcher;
import xyz.ineanto.nicko.command.NickoCommand; import xyz.ineanto.nicko.command.NickoCommand;
import xyz.ineanto.nicko.config.Configuration; import xyz.ineanto.nicko.config.Configuration;
import xyz.ineanto.nicko.config.ConfigurationManager; import xyz.ineanto.nicko.config.ConfigurationManager;
import xyz.ineanto.nicko.debug.RespawnPacketListener;
import xyz.ineanto.nicko.event.PlayerJoinListener; import xyz.ineanto.nicko.event.PlayerJoinListener;
import xyz.ineanto.nicko.event.PlayerQuitListener; import xyz.ineanto.nicko.event.PlayerQuitListener;
import xyz.ineanto.nicko.language.CustomLanguage; import xyz.ineanto.nicko.language.CustomLanguage;
@ -132,6 +134,8 @@ public class Nicko extends JavaPlugin {
getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this); getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this);
getServer().getPluginManager().registerEvents(new PlayerQuitListener(), this); getServer().getPluginManager().registerEvents(new PlayerQuitListener(), this);
metrics = new Metrics(this, 20483); metrics = new Metrics(this, 20483);
ProtocolLibrary.getProtocolManager().addPacketListener(new RespawnPacketListener());
} }
getLogger().info("Nicko has been enabled."); getLogger().info("Nicko has been enabled.");

View file

@ -114,7 +114,7 @@ public class AnvilManager {
Bukkit.getPluginManager().callEvent(event); Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) { return Collections.singletonList(AnvilGUI.ResponseAction.close()); } if (event.isCancelled()) { return Collections.singletonList(AnvilGUI.ResponseAction.close()); }
final ActionResult actionResult = appearanceManager.update(skinChange, false); final ActionResult actionResult = appearanceManager.updatePlayer(skinChange, false);
if (!actionResult.isError()) { if (!actionResult.isError()) {
player.sendMessage(playerLanguage.translate(LanguageKey.Event.Appearance.Set.OK, true)); player.sendMessage(playerLanguage.translate(LanguageKey.Event.Appearance.Set.OK, true));
} else { } else {

View file

@ -1,27 +1,36 @@
package xyz.ineanto.nicko.appearance; 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.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
import xyz.ineanto.nicko.Nicko; import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.packet.InternalPacketSender; import xyz.ineanto.nicko.language.LanguageKey;
import xyz.ineanto.nicko.packet.PacketSender; import xyz.ineanto.nicko.mojang.MojangAPI;
import xyz.ineanto.nicko.mojang.MojangSkin;
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;
import xyz.ineanto.nicko.wrapper.*;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
public class AppearanceManager { public class AppearanceManager {
private final Nicko instance = Nicko.getInstance(); private final Nicko instance = Nicko.getInstance();
private final PlayerDataStore dataStore = instance.getDataStore(); private final PlayerDataStore dataStore = instance.getDataStore();
private final PlayerNameStore nameStore = instance.getNameStore(); private final PlayerNameStore nameStore = instance.getNameStore();
private final PacketSender packetSender;
private final Player player; private final Player player;
public AppearanceManager(Player player) { public AppearanceManager(Player player) {
this.player = player; this.player = player;
this.packetSender = new InternalPacketSender(player, getNickoProfile());
} }
public ActionResult reset() { public ActionResult reset() {
@ -31,7 +40,7 @@ public class AppearanceManager {
profile.setSkin(defaultName); profile.setSkin(defaultName);
dataStore.getCache().cache(player.getUniqueId(), profile); dataStore.getCache().cache(player.getUniqueId(), profile);
final ActionResult result = update(true, true); final ActionResult result = updatePlayer(true, true);
if (!result.isError()) { if (!result.isError()) {
profile.setName(null); profile.setName(null);
profile.setSkin(null); profile.setSkin(null);
@ -40,29 +49,108 @@ public class AppearanceManager {
return result; return result;
} }
public ActionResult update(boolean skinChange, boolean reset) { public ActionResult updatePlayer(boolean skinChange, boolean reset) {
final NickoProfile profile = getNickoProfile(); final NickoProfile profile = getNickoProfile();
final String displayName = profile.getName() == null ? player.getName() : profile.getName(); final String displayName = profile.getName() == null ? player.getName() : profile.getName();
final WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player).withName(displayName);
final ActionResult result = packetSender.sendGameProfileUpdate(displayName, skinChange, reset); final ActionResult result = updateGameProfileSkin(gameProfile, skinChange, reset);
if (!result.isError()) {
if (result.isError()) { reset(); } updateMetadata();
updateTabList(gameProfile, displayName);
packetSender.sendEntityMetadataUpdate(); respawnPlayer();
packetSender.sendTabListUpdate(displayName); respawnEntityForOthers();
respawnPlayer(); }
packetSender.sendEntityRespawn();
return result; 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<NickoProfile> 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<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 {
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() { private void respawnPlayer() {
final World world = player.getWorld();
final boolean wasFlying = player.isFlying(); final boolean wasFlying = player.isFlying();
final boolean wasAllowedToFly = player.getAllowFlight(); final boolean wasAllowedToFly = player.getAllowFlight();
final int foodLevel = player.getFoodLevel(); final int foodLevel = player.getFoodLevel();
final WrapperPlayServerRespawn respawn = new WrapperPlayServerRespawn();
packetSender.sendPlayerRespawn(); respawn.setDimension(world);
respawn.setSeed(world.getSeed());
respawn.setGameMode(player.getGameMode());
respawn.setPreviousGameMode(player.getGameMode());
respawn.setCopyMetadata(true);
respawn.sendPacket(player);
player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
player.setAllowFlight(wasAllowedToFly); player.setAllowFlight(wasAllowedToFly);
player.setFlying(wasFlying); player.setFlying(wasFlying);
@ -71,8 +159,29 @@ public class AppearanceManager {
player.setFoodLevel(foodLevel); player.setFoodLevel(foodLevel);
} }
private NickoProfile getNickoProfile() { private void updateTabList(WrappedGameProfile gameProfile, String displayName) {
final Optional<NickoProfile> optionalProfile = dataStore.getData(player.getUniqueId()); final WrapperPlayerServerPlayerInfo add = new WrapperPlayerServerPlayerInfo();
return optionalProfile.orElse(NickoProfile.EMPTY_PROFILE.clone()); 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,4 +1,4 @@
package xyz.ineanto.nicko.packet.debug; package xyz.ineanto.nicko.debug;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;

View file

@ -50,7 +50,7 @@ public class PlayerJoinListener implements Listener {
if (profile.hasData()) { if (profile.hasData()) {
final AppearanceManager appearanceManager = new AppearanceManager(player); final AppearanceManager appearanceManager = new AppearanceManager(player);
final boolean needsASkinChange = profile.getSkin() != null && !profile.getSkin().equals(player.getName()); final boolean needsASkinChange = profile.getSkin() != null && !profile.getSkin().equals(player.getName());
final ActionResult actionResult = appearanceManager.update(needsASkinChange, false); final ActionResult actionResult = appearanceManager.updatePlayer(needsASkinChange, false);
if (!actionResult.isError()) { if (!actionResult.isError()) {
player.sendMessage(playerLanguage.translateWithWhoosh(LanguageKey.Event.Appearance.Restore.OK)); player.sendMessage(playerLanguage.translateWithWhoosh(LanguageKey.Event.Appearance.Restore.OK));
} else { } else {
@ -68,7 +68,7 @@ public class PlayerJoinListener implements Listener {
optionalOnlinePlayerProfile.ifPresent(profile -> { optionalOnlinePlayerProfile.ifPresent(profile -> {
final AppearanceManager appearanceManager = new AppearanceManager(online); final AppearanceManager appearanceManager = new AppearanceManager(online);
final boolean needsASkinChange = profile.getSkin() != null && !profile.getSkin().equals(online.getName()); final boolean needsASkinChange = profile.getSkin() != null && !profile.getSkin().equals(online.getName());
final ActionResult actionResult = appearanceManager.update(needsASkinChange, false); final ActionResult actionResult = appearanceManager.updateForOthers(needsASkinChange, false);
if (actionResult.isError()) { if (actionResult.isError()) {
logger.warning("Something wrong happened while updating players to joining player (" + actionResult.getErrorKey() + ")"); logger.warning("Something wrong happened while updating players to joining player (" + actionResult.getErrorKey() + ")");
} }

View file

@ -40,7 +40,7 @@ public class RandomSkinItem {
instance.getDataStore().updateCache(player.getUniqueId(), profile); instance.getDataStore().updateCache(player.getUniqueId(), profile);
final AppearanceManager appearanceManager = new AppearanceManager(player); final AppearanceManager appearanceManager = new AppearanceManager(player);
final ActionResult result = appearanceManager.update(true, false); final ActionResult result = appearanceManager.updatePlayer(true, false);
if (!result.isError()) { if (!result.isError()) {
player.sendMessage(playerLanguage.translate(LanguageKey.Event.Appearance.Set.OK, true)); player.sendMessage(playerLanguage.translate(LanguageKey.Event.Appearance.Set.OK, true));
} else { } else {

View file

@ -1,125 +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.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<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()));
} 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);
}
}

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

@ -4,19 +4,14 @@ import org.bukkit.entity.Player;
import xyz.ineanto.nicko.Nicko; import xyz.ineanto.nicko.Nicko;
import xyz.ineanto.nicko.language.Language; import xyz.ineanto.nicko.language.Language;
import xyz.ineanto.nicko.storage.PlayerDataStore; import xyz.ineanto.nicko.storage.PlayerDataStore;
import xyz.ineanto.nicko.storage.name.PlayerNameStore;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class NickoProfile implements Cloneable { 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); 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 name;
private String skin; private String skin;
private Language language; private Language language;

View file

@ -16,7 +16,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package xyz.ineanto.nicko.packet.wrapper; package xyz.ineanto.nicko.wrapper;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;

View file

@ -1,4 +1,4 @@
package xyz.ineanto.nicko.packet.wrapper; package xyz.ineanto.nicko.wrapper;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;

View file

@ -1,4 +1,4 @@
package xyz.ineanto.nicko.packet.wrapper; package xyz.ineanto.nicko.wrapper;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.events.InternalStructure;
@ -56,20 +56,11 @@ public class WrapperPlayServerRespawn extends AbstractPacket {
} else { } else {
// 1.20.5 to 1.21.1 // 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 { try {
final Object spawnInfoStructureHandle = spawnInfoStructure.getHandle(); final Object spawnInfoStructureHandle = spawnInfoStructure.getHandle();
final RecordComponent[] components = spawnInfoStructureHandle.getClass().getRecordComponents(); final RecordComponent[] components = spawnInfoStructureHandle.getClass().getRecordComponents();
// Doesn't work!
final Field levelKeyField = spawnInfoStructureHandle.getClass().getDeclaredField(components[1].getAccessor().getName()); final Field levelKeyField = spawnInfoStructureHandle.getClass().getDeclaredField(components[1].getAccessor().getName());
levelKeyField.setAccessible(true); levelKeyField.setAccessible(true);
levelKeyField.set(spawnInfoStructureHandle, BukkitConverters.getWorldKeyConverter().getGeneric(Bukkit.getWorld("world"))); levelKeyField.set(spawnInfoStructureHandle, BukkitConverters.getWorldKeyConverter().getGeneric(Bukkit.getWorld("world")));

View file

@ -1,4 +1,4 @@
package xyz.ineanto.nicko.packet.wrapper; package xyz.ineanto.nicko.wrapper;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;

View file

@ -1,4 +1,4 @@
package xyz.ineanto.nicko.packet.wrapper; package xyz.ineanto.nicko.wrapper;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;

View file

@ -1,4 +1,4 @@
package xyz.ineanto.nicko.packet.wrapper; package xyz.ineanto.nicko.wrapper;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;