|
package net.zyuiop.scoreboard; |
|
|
|
import net.minecraft.server.v1_8_R3.*; |
|
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; |
|
import org.bukkit.entity.Player; |
|
|
|
import java.lang.reflect.Field; |
|
import java.util.ArrayList; |
|
import java.util.List; |
|
|
|
/** |
|
* @author zyuiop |
|
*/ |
|
public class ScoreboardSign { |
|
private boolean created = false; |
|
private final VirtualTeam[] lines = new VirtualTeam[15]; |
|
private final Player player; |
|
private String objectiveName; |
|
|
|
/** |
|
* Create a scoreboard sign for a given player and using a specifig objective name |
|
* @param player the player viewing the scoreboard sign |
|
* @param objectiveName the name of the scoreboard sign (displayed at the top of the scoreboard) |
|
*/ |
|
public ScoreboardSign(Player player, String objectiveName) { |
|
this.player = player; |
|
this.objectiveName = objectiveName; |
|
} |
|
|
|
/** |
|
* Send the initial creation packets for this scoreboard sign. Must be called at least once. |
|
*/ |
|
public void create() { |
|
if (created) |
|
return; |
|
|
|
PlayerConnection player = getPlayer(); |
|
player.sendPacket(createObjectivePacket(0, objectiveName)); |
|
player.sendPacket(setObjectiveSlot()); |
|
int i = 0; |
|
while (i < lines.length) |
|
sendLine(i++); |
|
|
|
created = true; |
|
} |
|
|
|
/** |
|
* Send the packets to remove this scoreboard sign. A destroyed scoreboard sign must be recreated using {@link ScoreboardSign#create()} in order |
|
* to be used again |
|
*/ |
|
public void destroy() { |
|
if (!created) |
|
return; |
|
|
|
getPlayer().sendPacket(createObjectivePacket(1, null)); |
|
for (VirtualTeam team : lines) |
|
if (team != null) |
|
getPlayer().sendPacket(team.removeTeam()); |
|
|
|
created = false; |
|
} |
|
|
|
/** |
|
* Change the name of the objective. The name is displayed at the top of the scoreboard. |
|
* @param name the name of the objective, max 32 char |
|
*/ |
|
public void setObjectiveName(String name) { |
|
this.objectiveName = name; |
|
if (created) |
|
getPlayer().sendPacket(createObjectivePacket(2, name)); |
|
} |
|
|
|
/** |
|
* Change a scoreboard line and send the packets to the player. Can be called async. |
|
* @param line the number of the line (0 <= line < 15) |
|
* @param value the new value for the scoreboard line |
|
*/ |
|
public void setLine(int line, String value) { |
|
VirtualTeam team = getOrCreateTeam(line); |
|
String old = team.getCurrentPlayer(); |
|
|
|
if (old != null && created) |
|
getPlayer().sendPacket(removeLine(old)); |
|
|
|
team.setValue(value); |
|
sendLine(line); |
|
} |
|
|
|
/** |
|
* Remove a given scoreboard line |
|
* @param line the line to remove |
|
*/ |
|
public void removeLine(int line) { |
|
VirtualTeam team = getOrCreateTeam(line); |
|
String old = team.getCurrentPlayer(); |
|
|
|
if (old != null && created) { |
|
getPlayer().sendPacket(removeLine(old)); |
|
getPlayer().sendPacket(team.removeTeam()); |
|
} |
|
|
|
lines[line] = null; |
|
} |
|
|
|
/** |
|
* Get the current value for a line |
|
* @param line the line |
|
* @return the content of the line |
|
*/ |
|
public String getLine(int line) { |
|
if (line > 14) |
|
return null; |
|
if (line < 0) |
|
return null; |
|
return getOrCreateTeam(line).getValue(); |
|
} |
|
|
|
/** |
|
* Get the team assigned to a line |
|
* @return the {@link VirtualTeam} used to display this line |
|
*/ |
|
public VirtualTeam getTeam(int line) { |
|
if (line > 14) |
|
return null; |
|
if (line < 0) |
|
return null; |
|
return getOrCreateTeam(line); |
|
} |
|
|
|
private PlayerConnection getPlayer() { |
|
return ((CraftPlayer) player).getHandle().playerConnection; |
|
} |
|
|
|
private void sendLine(int line) { |
|
if (line > 14) |
|
return; |
|
if (line < 0) |
|
return; |
|
if (!created) |
|
return; |
|
|
|
int score = (15 - line); |
|
VirtualTeam val = getOrCreateTeam(line); |
|
for (Packet packet : val.sendLine()) |
|
getPlayer().sendPacket(packet); |
|
getPlayer().sendPacket(sendScore(val.getCurrentPlayer(), score)); |
|
val.reset(); |
|
} |
|
|
|
private VirtualTeam getOrCreateTeam(int line) { |
|
if (lines[line] == null) |
|
lines[line] = new VirtualTeam("__fakeScore" + line); |
|
|
|
return lines[line]; |
|
} |
|
|
|
/* |
|
Factories |
|
*/ |
|
private PacketPlayOutScoreboardObjective createObjectivePacket(int mode, String displayName) { |
|
PacketPlayOutScoreboardObjective packet = new PacketPlayOutScoreboardObjective(); |
|
// Nom de l'objectif |
|
setField(packet, "a", player.getName()); |
|
|
|
// Mode |
|
// 0 : créer |
|
// 1 : Supprimer |
|
// 2 : Mettre à jour |
|
setField(packet, "d", mode); |
|
|
|
if (mode == 0 || mode == 2) { |
|
setField(packet, "b", displayName); |
|
setField(packet, "c", IScoreboardCriteria.EnumScoreboardHealthDisplay.INTEGER); |
|
} |
|
|
|
return packet; |
|
} |
|
|
|
private PacketPlayOutScoreboardDisplayObjective setObjectiveSlot() { |
|
PacketPlayOutScoreboardDisplayObjective packet = new PacketPlayOutScoreboardDisplayObjective(); |
|
// Slot |
|
setField(packet, "a", 1); |
|
setField(packet, "b", player.getName()); |
|
|
|
return packet; |
|
} |
|
|
|
private PacketPlayOutScoreboardScore sendScore(String line, int score) { |
|
PacketPlayOutScoreboardScore packet = new PacketPlayOutScoreboardScore(line); |
|
setField(packet, "b", player.getName()); |
|
setField(packet, "c", score); |
|
setField(packet, "d", PacketPlayOutScoreboardScore.EnumScoreboardAction.CHANGE); |
|
|
|
return packet; |
|
} |
|
|
|
private PacketPlayOutScoreboardScore removeLine(String line) { |
|
return new PacketPlayOutScoreboardScore(line); |
|
} |
|
|
|
/** |
|
* This class is used to manage the content of a line. Advanced users can use it as they want, but they are encouraged to read and understand the |
|
* code before doing so. Use these methods at your own risk. |
|
*/ |
|
public class VirtualTeam { |
|
private final String name; |
|
private String prefix; |
|
private String suffix; |
|
private String currentPlayer; |
|
private String oldPlayer; |
|
|
|
private boolean prefixChanged, suffixChanged, playerChanged = false; |
|
private boolean first = true; |
|
|
|
private VirtualTeam(String name, String prefix, String suffix) { |
|
this.name = name; |
|
this.prefix = prefix; |
|
this.suffix = suffix; |
|
} |
|
|
|
private VirtualTeam(String name) { |
|
this(name, "", ""); |
|
} |
|
|
|
public String getName() { |
|
return name; |
|
} |
|
|
|
public String getPrefix() { |
|
return prefix; |
|
} |
|
|
|
public void setPrefix(String prefix) { |
|
if (this.prefix == null || !this.prefix.equals(prefix)) |
|
this.prefixChanged = true; |
|
this.prefix = prefix; |
|
} |
|
|
|
public String getSuffix() { |
|
return suffix; |
|
} |
|
|
|
public void setSuffix(String suffix) { |
|
if (this.suffix == null || !this.suffix.equals(prefix)) |
|
this.suffixChanged = true; |
|
this.suffix = suffix; |
|
} |
|
|
|
private PacketPlayOutScoreboardTeam createPacket(int mode) { |
|
PacketPlayOutScoreboardTeam packet = new PacketPlayOutScoreboardTeam(); |
|
setField(packet, "a", name); |
|
setField(packet, "h", mode); |
|
setField(packet, "b", ""); |
|
setField(packet, "c", prefix); |
|
setField(packet, "d", suffix); |
|
setField(packet, "i", 0); |
|
setField(packet, "e", "always"); |
|
setField(packet, "f", 0); |
|
|
|
return packet; |
|
} |
|
|
|
public PacketPlayOutScoreboardTeam createTeam() { |
|
return createPacket(0); |
|
} |
|
|
|
public PacketPlayOutScoreboardTeam updateTeam() { |
|
return createPacket(2); |
|
} |
|
|
|
public PacketPlayOutScoreboardTeam removeTeam() { |
|
PacketPlayOutScoreboardTeam packet = new PacketPlayOutScoreboardTeam(); |
|
setField(packet, "a", name); |
|
setField(packet, "h", 1); |
|
first = true; |
|
return packet; |
|
} |
|
|
|
public void setPlayer(String name) { |
|
if (this.currentPlayer == null || !this.currentPlayer.equals(name)) |
|
this.playerChanged = true; |
|
this.oldPlayer = this.currentPlayer; |
|
this.currentPlayer = name; |
|
} |
|
|
|
public Iterable<PacketPlayOutScoreboardTeam> sendLine() { |
|
List<PacketPlayOutScoreboardTeam> packets = new ArrayList<>(); |
|
|
|
if (first) { |
|
packets.add(createTeam()); |
|
} else if (prefixChanged || suffixChanged) { |
|
packets.add(updateTeam()); |
|
} |
|
|
|
if (first || playerChanged) { |
|
if (oldPlayer != null) // remove these two lines ? |
|
packets.add(addOrRemovePlayer(4, oldPlayer)); // |
|
packets.add(changePlayer()); |
|
} |
|
|
|
if (first) |
|
first = false; |
|
|
|
return packets; |
|
} |
|
|
|
public void reset() { |
|
prefixChanged = false; |
|
suffixChanged = false; |
|
playerChanged = false; |
|
oldPlayer = null; |
|
} |
|
|
|
public PacketPlayOutScoreboardTeam changePlayer() { |
|
return addOrRemovePlayer(3, currentPlayer); |
|
} |
|
|
|
public PacketPlayOutScoreboardTeam addOrRemovePlayer(int mode, String playerName) { |
|
PacketPlayOutScoreboardTeam packet = new PacketPlayOutScoreboardTeam(); |
|
setField(packet, "a", name); |
|
setField(packet, "h", mode); |
|
|
|
try { |
|
Field f = packet.getClass().getDeclaredField("g"); |
|
f.setAccessible(true); |
|
((List<String>) f.get(packet)).add(playerName); |
|
} catch (NoSuchFieldException | IllegalAccessException e) { |
|
e.printStackTrace(); |
|
} |
|
|
|
return packet; |
|
} |
|
|
|
public String getCurrentPlayer() { |
|
return currentPlayer; |
|
} |
|
|
|
public String getValue() { |
|
return getPrefix() + getCurrentPlayer() + getSuffix(); |
|
} |
|
|
|
public void setValue(String value) { |
|
if (value.length() <= 16) { |
|
setPrefix(""); |
|
setSuffix(""); |
|
setPlayer(value); |
|
} else if (value.length() <= 32) { |
|
setPrefix(value.substring(0, 16)); |
|
setPlayer(value.substring(16)); |
|
setSuffix(""); |
|
} else if (value.length() <= 48) { |
|
setPrefix(value.substring(0, 16)); |
|
setPlayer(value.substring(16, 32)); |
|
setSuffix(value.substring(32)); |
|
} else { |
|
throw new IllegalArgumentException("Too long value ! Max 48 characters, value was " + value.length() + " !"); |
|
} |
|
} |
|
} |
|
|
|
private static void setField(Object edit, String fieldName, Object value) { |
|
try { |
|
Field field = edit.getClass().getDeclaredField(fieldName); |
|
field.setAccessible(true); |
|
field.set(edit, value); |
|
} catch (NoSuchFieldException | IllegalAccessException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
} |
[12:23:36 ERROR]: Could not pass event PlayerJoinEvent to MurderMystery v1.0.0 org.bukkit.event.EventException at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:310) ~[spigot.jar:git-Spigot-4af49dc-c5e9a16] at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62) ~[spigot.jar:git-Spigot-4af49dc-c5e9a16] at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:502) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:487) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.PlayerList.onPlayerJoin(PlayerList.java:333) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.PlayerList.a(PlayerList.java:159) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.LoginListener.b(LoginListener.java:144) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.LoginListener.c(LoginListener.java:54) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.NetworkManager.a(NetworkManager.java:233) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.ServerConnection.c(ServerConnection.java:140) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.MinecraftServer.D(MinecraftServer.java:825) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.DedicatedServer.D(DedicatedServer.java:399) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.MinecraftServer.C(MinecraftServer.java:665) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at net.minecraft.server.v1_9_R2.MinecraftServer.run(MinecraftServer.java:564) [spigot.jar:git-Spigot-4af49dc-c5e9a16] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191] Caused by: java.lang.IllegalArgumentException: Can not set java.util.Collection field net.minecraft.server.v1_9_R2.PacketPlayOutScoreboardTeam.h to java.lang.Integer at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167) ~[?:1.8.0_191] at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171) ~[?:1.8.0_191] at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81) ~[?:1.8.0_191] at java.lang.reflect.Field.set(Field.java:764) ~[?:1.8.0_191] at fr.levasseur.mm.utils.ScoreboardSign.setField(ScoreboardSign.java:36) ~[?:?] at fr.levasseur.mm.utils.ScoreboardSign.access$100(ScoreboardSign.java:15) ~[?:?] at fr.levasseur.mm.utils.ScoreboardSign$VirtualTeam.createPacket(ScoreboardSign.java:269) ~[?:?] at fr.levasseur.mm.utils.ScoreboardSign$VirtualTeam.createTeam(ScoreboardSign.java:281) ~[?:?] at fr.levasseur.mm.utils.ScoreboardSign$VirtualTeam.sendLine(ScoreboardSign.java:307) ~[?:?] at fr.levasseur.mm.utils.ScoreboardSign.sendLine(ScoreboardSign.java:161) ~[?:?] at fr.levasseur.mm.utils.ScoreboardSign.setLine(ScoreboardSign.java:100) ~[?:?] at fr.levasseur.mm.listeners.OnPlayerJoinEvent.onEvent(OnPlayerJoinEvent.java:61) ~[?:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_191] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_191] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_191] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_191] at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:306) ~[spigot.jar:git-Spigot-4af49dc-c5e9a16] ... 14 more
1.9.4 field ?