feat(disguise): update others players + fixes/tweaks

This commit is contained in:
ineanto 2023-06-28 23:22:50 +02:00
parent f3ebbd6a8d
commit 2fbf3318f9
17 changed files with 334 additions and 106 deletions

BIN
logs/2023-06-28-1.log.gz Normal file

Binary file not shown.

BIN
logs/2023-06-28-2.log.gz Normal file

Binary file not shown.

View file

@ -44,8 +44,8 @@
-->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19.4-R0.1-SNAPSHOT</version>
<artifactId>spigot</artifactId>
<version>1.20.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -98,8 +98,10 @@ public class NickoBukkit extends JavaPlugin {
command.setExecutor(new NickoCommand());
}
Structure.addGlobalIngredient('#', new SimpleItem(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setDisplayName(" ")));
Structure.addGlobalIngredient('%', new SimpleItem(new ItemBuilder(Material.ORANGE_STAINED_GLASS_PANE).setDisplayName(" ")));
//new SimpleItem(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setDisplayName(" "))
//new SimpleItem(new ItemBuilder(Material.ORANGE_STAINED_GLASS_PANE).setDisplayName(" "))
Structure.addGlobalIngredient('#', new SimpleItem(new ItemBuilder(Material.AIR)));
Structure.addGlobalIngredient('%', new SimpleItem(new ItemBuilder(Material.AIR)));
Structure.addGlobalIngredient('U', new OptionUnavailable());
Structure.addGlobalIngredient('E', new ExitGUI());

View file

@ -39,7 +39,6 @@ public class AnvilManager {
public AnvilGUI.Builder getNameThenSkinAnvil() {
return new AnvilGUI.Builder()
.plugin(NickoBukkit.getInstance())
.preventClose()
.itemLeft(getLeftItem(false))
.interactableSlots(AnvilGUI.Slot.OUTPUT)
.onClick((slot, snapshot) -> {
@ -60,7 +59,6 @@ public class AnvilManager {
public AnvilGUI.Builder getNameAnvil() {
return new AnvilGUI.Builder()
.plugin(NickoBukkit.getInstance())
.preventClose()
.itemLeft(getLeftItem(false))
.interactableSlots(AnvilGUI.Slot.OUTPUT)
.onClick((slot, snapshot) -> {
@ -99,10 +97,12 @@ public class AnvilManager {
}
private List<AnvilGUI.ResponseAction> sendResultAndClose(ActionResult<Void> actionResult) {
final I18N i18n = new I18N(player);
if (!actionResult.isError()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.Disguise.SUCCESS));
player.sendMessage(i18n.translate(I18NDict.Event.Disguise.SUCCESS));
} else {
player.sendMessage(I18N.translate(player, I18NDict.Event.Disguise.FAIL, I18N.translateWithoutPrefix(player, actionResult.getErrorMessage())));
// TODO (Ineanto, 6/28/23): Check weirdness with error message not being translated sometimes
player.sendMessage(i18n.translate(I18NDict.Event.Disguise.FAIL, i18n.translateWithoutPrefix(actionResult.getErrorKey())));
}
return Collections.singletonList(AnvilGUI.ResponseAction.close());
}

View file

@ -14,10 +14,11 @@ public class NickoCheckSubCmd {
public void execute(Player player, String[] args) {
final String targetName = args[1];
final Player target = Bukkit.getPlayerExact(targetName);
final I18N i18n = new I18N(player);
AppearanceManager appearanceManager;
if (MojangUtils.isUsernameInvalid(targetName)) {
player.sendMessage(I18N.translate(player, I18NDict.Error.INVALID_USERNAME));
player.sendMessage(i18n.translate(I18NDict.Error.INVALID_USERNAME));
return;
}
@ -28,15 +29,16 @@ public class NickoCheckSubCmd {
}
final StringJoiner builder = new StringJoiner("\n");
builder.add("§c" + NickoBukkit.getInstance().getNickoConfig().getPrefix() + "§6Check for: §f§o" + targetName);
if (!appearanceManager.hasData()) {
builder.add("§cThis player has not data.");
builder.add("§c" + NickoBukkit.getInstance().getNickoConfig().getPrefix() + "§cCheck for: §f§o" + targetName);
if (appearanceManager.hasData()) {
builder.add("§cNicked: §a✔");
builder.add("§cName: §6" + appearanceManager.getName());
builder.add("§cSkin: §6" + appearanceManager.getSkin());
} else {
builder.add("§7- §fNicked: §a✔");
builder.add("§7- §fName: §6" + appearanceManager.getName());
builder.add("§7- §fSkin: §6" + appearanceManager.getSkin());
builder.add("§cNicked: §c❌");
builder.add("§cName: §7N/A");
builder.add("§cSkin: §7N/A");
}
player.sendMessage(builder.toString());
}
}

View file

@ -1,24 +1,22 @@
package xyz.atnrch.nicko.disguise;
import xyz.atnrch.nicko.i18n.I18NDict;
public class ActionResult<R> {
private final I18NDict errorMessage;
private final String errorKey;
private boolean error = false;
private R result;
public ActionResult() {
this.errorMessage = null;
this.errorKey = null;
}
public ActionResult(I18NDict errorMessage) {
this.errorMessage = errorMessage;
public ActionResult(String errorMessage) {
this.errorKey = errorMessage;
this.error = true;
this.result = null;
}
public ActionResult(R result) {
this.errorMessage = null;
this.errorKey = null;
this.result = result;
}
@ -30,7 +28,7 @@ public class ActionResult<R> {
return error;
}
public I18NDict getErrorMessage() {
return errorMessage;
public String getErrorKey() {
return errorKey;
}
}

View file

@ -4,6 +4,7 @@ import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.*;
import com.google.common.collect.ImmutableList;
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;
@ -14,9 +15,7 @@ import xyz.atnrch.nicko.mojang.MojangAPI;
import xyz.atnrch.nicko.mojang.MojangSkin;
import xyz.atnrch.nicko.storage.PlayerDataStore;
import xyz.atnrch.nicko.storage.name.PlayerNameStore;
import xyz.atnrch.nicko.wrapper.WrapperPlayServerRespawn;
import xyz.atnrch.nicko.wrapper.WrapperPlayerServerPlayerInfo;
import xyz.atnrch.nicko.wrapper.WrapperPlayerServerPlayerInfoRemove;
import xyz.atnrch.nicko.wrapper.*;
import java.io.IOException;
import java.util.EnumSet;
@ -104,11 +103,25 @@ public class AppearanceManager {
updateMetadata();
updateTabList(gameProfile, displayName);
respawnPlayer();
updateOthers();
}
player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
return new ActionResult<>();
}
public void updateOthers() {
WrapperPlayServerEntityDestroy destroy = new WrapperPlayServerEntityDestroy();
WrapperPlayServerNamedEntitySpawn spawn = new WrapperPlayServerNamedEntitySpawn();
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<Void> updateGameProfileSkin(WrappedGameProfile gameProfile, boolean skinChange) {
final boolean changeOnlyName = profile.getSkin() != null && !profile.getSkin().equalsIgnoreCase(player.getName());
@ -154,7 +167,6 @@ public class AppearanceManager {
respawn.setCopyMetadata(true);
respawn.sendPacket(player);
player.setFlying(wasFlying);
player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
player.updateInventory();
}
@ -171,13 +183,13 @@ public class AppearanceManager {
EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE,
EnumWrappers.PlayerInfoAction.UPDATE_LATENCY);
remove.setUUIDs(ImmutableList.of(player.getUniqueId()));
remove.sendPacket(player);
remove.broadcastPacket();
add.setActions(actions);
} else {
final WrapperPlayerServerPlayerInfo remove = new WrapperPlayerServerPlayerInfo();
remove.setActions(EnumSet.of(EnumWrappers.PlayerInfoAction.REMOVE_PLAYER));
add.setActions(EnumSet.of(EnumWrappers.PlayerInfoAction.ADD_PLAYER));
remove.sendPacket(player);
remove.broadcastPacket();
}
// Yes, I skip providing chat session data.
@ -194,6 +206,6 @@ public class AppearanceManager {
gameProfile,
WrappedChatComponent.fromText(displayName)
)));
add.sendPacket(player);
add.broadcastPacket();
}
}

View file

@ -10,7 +10,6 @@ import xyz.atnrch.nicko.disguise.ActionResult;
import xyz.atnrch.nicko.disguise.AppearanceManager;
import xyz.atnrch.nicko.i18n.I18N;
import xyz.atnrch.nicko.i18n.I18NDict;
import xyz.atnrch.nicko.storage.PlayerDataStore;
import xyz.atnrch.nicko.storage.name.PlayerNameStore;
public class PlayerJoinListener implements Listener {
@ -18,20 +17,19 @@ public class PlayerJoinListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
final Player player = event.getPlayer();
final NickoBukkit instance = NickoBukkit.getInstance();
final PlayerDataStore dataStore = instance.getDataStore();
final I18N i18n = new I18N(player);
final PlayerNameStore nameStore = instance.getNameStore();
nameStore.storeName(player);
// TODO: 2/20/23 BungeeCord transfer
// TODO: 2/20/23 Fetch data from BungeeCord
nameStore.storeName(player);
Bukkit.getScheduler().runTaskLater(instance, () -> {
final AppearanceManager appearanceManager = AppearanceManager.get(player);
if (appearanceManager.hasData()) {
final ActionResult<Void> actionResult = appearanceManager.updatePlayer(appearanceManager.needsASkinChange());
if (!actionResult.isError()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.PreviousSkin.SUCCESS));
player.sendMessage(i18n.translate(I18NDict.Event.PreviousSkin.SUCCESS));
} else {
player.sendMessage(I18N.translate(player, I18NDict.Event.PreviousSkin.FAIL, I18N.translateWithoutPrefix(player, actionResult.getErrorMessage())));
player.sendMessage(i18n.translate(I18NDict.Event.PreviousSkin.FAIL, i18n.translateWithoutPrefix(actionResult.getErrorKey())));
}
}
}, 20L);

View file

@ -25,8 +25,9 @@ public class InvalidateCompleteCache extends SuppliedItem {
final ClickType clickType = click.getClickType();
if (clickType.isLeftClick() || clickType.isRightClick()) {
final Player player = click.getPlayer();
final I18N i18n = new I18N(player);
click.getEvent().getView().close();
player.sendMessage(I18N.translate(player, I18NDict.Event.Admin.CACHE_CLEAN));
player.sendMessage(i18n.translate(I18NDict.Event.Admin.CACHE_CLEAN));
NickoBukkit.getInstance().getMojangAPI().getCache().invalidateAll();
return true;
}

View file

@ -18,21 +18,23 @@ public class ResetAppearance extends SuppliedItem {
return builder;
}, (event) -> {
final Player player = event.getPlayer();
final I18N i18n = new I18N(player);
final ClickType clickType = event.getClickType();
if (clickType.isLeftClick() || clickType.isRightClick()) {
final AppearanceManager appearanceManager = AppearanceManager.get(player);
if (!appearanceManager.hasData()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.Undisguise.NONE));
player.sendMessage(i18n.translate(I18NDict.Event.Undisguise.NONE));
event.getEvent().getView().close();
return true;
}
if (!appearanceManager.reset().isError()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.Undisguise.SUCCESS));
player.sendMessage(i18n.translate(I18NDict.Event.Undisguise.SUCCESS));
return true;
} else {
player.sendMessage(I18N.translate(player, I18NDict.Event.Undisguise.FAIL));
player.sendMessage(i18n.translate(I18NDict.Event.Undisguise.FAIL));
return false;
}
}

View file

@ -11,8 +11,51 @@ import java.util.Optional;
public class I18N {
private final static MessageFormat formatter = new MessageFormat("");
private final Player player;
private final Locale playerLocale;
private static Locale getLocale(Player player) {
public I18N(Player player) {
this.player = player;
this.playerLocale = getPlayerLocale();
}
public String translate(String key, Object... arguments) {
final NickoBukkit instance = NickoBukkit.getInstance();
final String string = readString(key);
try {
formatter.applyPattern(string);
return instance.getNickoConfig().getPrefix() + formatter.format(arguments);
} catch (Exception e) {
return instance.getNickoConfig().getPrefix() + key;
}
}
public String translateWithoutPrefix(String key, Object... arguments) {
final String translation = readString(key);
try {
formatter.applyPattern(translation);
return formatter.format(arguments);
} catch (Exception e) {
return key;
}
}
private String readString(String key) {
final NickoBukkit instance = NickoBukkit.getInstance();
String string;
if (playerLocale == Locale.CUSTOM) {
string = instance.getLocaleFileManager().get(key);
} else {
final InputStream resource = instance.getResource(playerLocale.getCode() + ".yml");
final YamlConfig yamlConfig = YamlConfig.load(resource);
string = yamlConfig.getString(key);
}
return string;
}
private Locale getPlayerLocale() {
final NickoBukkit instance = NickoBukkit.getInstance();
try {
final Optional<NickoProfile> profile = instance.getDataStore().getData(player.getUniqueId());
@ -22,41 +65,4 @@ public class I18N {
return Locale.FALLBACK_LOCALE;
}
}
public static String translate(Player player, I18NDict key, Object... arguments) {
final NickoBukkit instance = NickoBukkit.getInstance();
final String translation = findTranslation(player, key);
try {
formatter.applyPattern(translation);
return instance.getNickoConfig().getPrefix() + formatter.format(arguments);
} catch (Exception e) {
return instance.getNickoConfig().getPrefix() + key.key();
}
}
public static String translateWithoutPrefix(Player player, I18NDict key, Object... arguments) {
final String translation = findTranslation(player, key);
try {
formatter.applyPattern(translation);
return formatter.format(arguments);
} catch (Exception e) {
return key.key();
}
}
private static String findTranslation(Player player, I18NDict key) {
final NickoBukkit instance = NickoBukkit.getInstance();
final Locale locale = getLocale(player);
String translation;
if (locale == Locale.CUSTOM) {
translation = instance.getLocaleFileManager().get(key.key());
} else {
final InputStream resource = instance.getResource(locale.getCode() + ".yml");
final YamlConfig yamlConfig = YamlConfig.load(resource);
translation = yamlConfig.getString(key.key());
}
return translation;
}
}

View file

@ -1,44 +1,36 @@
package xyz.atnrch.nicko.i18n;
public class I18NDict {
private final String key;
public I18NDict(String key) { this.key = key; }
public static class Event {
public static class Admin {
public static final I18NDict CACHE_CLEAN = new I18NDict("event.admin.cache_clear");
public static final String CACHE_CLEAN = "event.admin.cache_clear";
}
public static class Disguise {
public static final I18NDict SUCCESS = new I18NDict("event.disguise.success");
public static final I18NDict FAIL = new I18NDict("event.disguise.fail");
public static final String SUCCESS = "event.disguise.success";
public static final String FAIL = "event.disguise.fail";
}
public static class Undisguise {
public static final I18NDict SUCCESS = new I18NDict("event.undisguise.success");
public static final I18NDict FAIL = new I18NDict("event.undisguise.fail");
public static final I18NDict NONE = new I18NDict("event.undisguise.none");
public static final String SUCCESS = "event.undisguise.success";
public static final String FAIL = "event.undisguise.fail";
public static final String NONE = "event.undisguise.none";
}
public static class PreviousSkin {
public static final I18NDict SUCCESS = new I18NDict("event.previous_skin_applied.success");
public static final I18NDict FAIL = new I18NDict("event.previous_skin_applied.fail");
public static final String SUCCESS = "event.previous_skin_applied.success";
public static final String FAIL = "event.previous_skin_applied.fail";
}
}
public static class Error {
public static final I18NDict PLAYER_OFFLINE = new I18NDict("error.player_offline");
public static final I18NDict SKIN_FAIL_MOJANG = new I18NDict("error.couldnt_get_skin_from_mojang");
public static final I18NDict SKIN_FAIL_CACHE = new I18NDict("error.couldnt_get_skin_from_cache");
public static final I18NDict NAME_FAIL_MOJANG = new I18NDict("error.couldnt_get_name_from_mojang");
public static final I18NDict INVALID_USERNAME = new I18NDict("error.invalid_username");
public static final I18NDict UNEXPECTED_ERROR = new I18NDict("error.generic");
public static final I18NDict SQL_ERROR = new I18NDict("error.sql");
public static final I18NDict JSON_ERROR = new I18NDict("error.json");
}
public String key() {
return key;
public static final String UNEXPECTED_ERROR = "error.invalid_name";
public static final String PLAYER_OFFLINE = "error.player_offline";
public static final String SKIN_FAIL_MOJANG = "error.couldnt_get_skin_from_mojang";
public static final String SKIN_FAIL_CACHE = "error.couldnt_get_skin_from_cache";
public static final String NAME_FAIL_MOJANG = "error.couldnt_get_name_from_mojang";
public static final String INVALID_USERNAME = "error.invalid_username";
public static final String SQL_ERROR = "error.sql";
public static final String JSON_ERROR = "error.json";
}
}

View file

@ -73,6 +73,7 @@ public class MojangAPI {
con.setRequestMethod("GET");
switch (con.getResponseCode()) {
case 404:
case 400:
logger.warning("Failed to parse request: Invalid Name");
return getErrorObject();

View file

@ -0,0 +1,41 @@
package xyz.atnrch.nicko.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();
}
/**
* Gets a list of entity ids to remove
*
* @return 'entityIds' to remove
*/
public IntList getEntityIds() {
return this.handle.getModifier().withType(IntList.class, Converters.passthrough(IntList.class)).read(0);
}
/**
* 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)).write(0, value);
}
}

View file

@ -0,0 +1,173 @@
package xyz.atnrch.nicko.wrapper;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import org.bukkit.Location;
import org.bukkit.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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 WrapperPlayServerNamedEntitySpawn extends AbstractPacket {
/**
* The packet type that is wrapped by this wrapper.
*/
public static final PacketType TYPE = PacketType.Play.Server.NAMED_ENTITY_SPAWN;
/**
* Constructors a new wrapper for the specified packet
*/
public WrapperPlayServerNamedEntitySpawn() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
}
/**
* Retrieves entity id of the player
*
* @return 'entityId'
*/
public int getEntityId() {
return this.handle.getIntegers().read(0);
}
/**
* Sets the entity id of the player
*
* @param value New value for field 'entityId'
*/
public void setEntityId(int value) {
this.handle.getIntegers().write(0, value);
}
/**
* Retrieves the unique id of the player
*
* @return 'playerId'
*/
public UUID getPlayerId() {
return this.handle.getUUIDs().read(0);
}
/**
* Sets the unique id of the player
*
* @param value New value for field 'playerId'
*/
public void setPlayerId(UUID value) {
this.handle.getUUIDs().write(0, value);
}
/**
* Retrieves the value of field 'x'
*
* @return 'x'
*/
public double getX() {
return this.handle.getDoubles().read(0);
}
/**
* Sets the value of field 'x'
*
* @param value New value for field 'x'
*/
public void setX(double value) {
this.handle.getDoubles().write(0, value);
}
/**
* Retrieves the value of field 'y'
*
* @return 'y'
*/
public double getY() {
return this.handle.getDoubles().read(1);
}
/**
* Sets the value of field 'y'
*
* @param value New value for field 'y'
*/
public void setY(double value) {
this.handle.getDoubles().write(1, value);
}
/**
* Retrieves the value of field 'z'
*
* @return 'z'
*/
public double getZ() {
return this.handle.getDoubles().read(2);
}
/**
* Sets the value of field 'z'
*
* @param value New value for field 'z'
*/
public void setZ(double value) {
this.handle.getDoubles().write(2, value);
}
/**
* Retrieves the discrete rotation around the y-axis (yaw)
*
* @return 'yRot'
*/
public byte getYRotRaw() {
return this.handle.getBytes().read(0);
}
/**
* Sets the discrete rotation around the y-axis (yaw)
*
* @param value New value for field 'yRot'
*/
public void setYRotRaw(byte value) {
this.handle.getBytes().write(0, value);
}
/**
* Retrieves the value of field 'xRot'
*
* @return 'xRot'
*/
public byte getXRotRaw() {
return this.handle.getBytes().read(1);
}
/**
* Sets the discrete rotation around the x-axis (pitch)
*
* @param value New value for field 'xRot'
*/
public void setXRotRaw(byte value) {
this.handle.getBytes().write(1, value);
}
public Location getLocation(@Nullable World world) {
return new Location(world, getX(), getY(), getZ(), angleToDegrees(getYRotRaw()), angleToDegrees(getXRotRaw()));
}
public void setLocation(@Nonnull Location location) {
setX(location.getX());
setY(location.getY());
setZ(location.getZ());
setYRotRaw(degreesToAngle(location.getYaw()));
setXRotRaw(degreesToAngle(location.getPitch()));
}
private float angleToDegrees(byte rawAngle) {
return rawAngle / 256.0F * 360.0F;
}
private byte degreesToAngle(float degree) {
return (byte)((int)(degree * 256.0F / 360.0F));
}
}

View file

@ -1,5 +1,5 @@
error:
couldnt_get_name_from_mojang: "Unable to get username from Mojang."
invalid_name: "Unable to get username from Mojang."
couldnt_get_skin_from_cache: "Unable to get skin from the cache."
couldnt_get_skin_from_mojang: "Unable to get skin from Mojang."
generic: "Unknown error"