Skip to content

Instantly share code, notes, and snippets.

@zyuiop
Last active April 2, 2023 11:40
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save zyuiop/8fcf2ca47794b92d7caa to your computer and use it in GitHub Desktop.
Save zyuiop/8fcf2ca47794b92d7caa to your computer and use it in GitHub Desktop.
A simple tool to manage scoreboards in minecraft (lines up to 48 characters !). /

About

This class allow you to use a scoreboard as a sign in which you can write any text. It is designed to be used with a CraftBukkit server (or any similar variant). It uses fake teams to allow lines that are longer than usernames.

Other versions

Licence

Copyright © 2018, Louis Vialar (zyuiop) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.

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();
}
}
}
@git-init-wesley
Copy link

Working for 1.13.2 ?

@git-init-wesley
Copy link

[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 ?

@Aurelien30000
Copy link

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;

import net.minecraft.server.v1_13_R2.EnumChatFormat;
import net.minecraft.server.v1_13_R2.IChatBaseComponent;
import net.minecraft.server.v1_13_R2.IScoreboardCriteria;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketPlayOutScoreboardDisplayObjective;
import net.minecraft.server.v1_13_R2.PacketPlayOutScoreboardObjective;
import net.minecraft.server.v1_13_R2.PacketPlayOutScoreboardScore;
import net.minecraft.server.v1_13_R2.PacketPlayOutScoreboardTeam;
import net.minecraft.server.v1_13_R2.PlayerConnection;
import net.minecraft.server.v1_13_R2.ScoreboardServer.Action;

/**
 * @author zyuiop
 * Updated by MrZalTy
 */
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;
	}

	@SuppressWarnings("rawtypes")
	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", IChatBaseComponent.ChatSerializer.a("{\"text\":\"" + 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) {

		return new PacketPlayOutScoreboardScore(Action.CHANGE, player.getName(), line, score);
	}

	private PacketPlayOutScoreboardScore removeLine(String line) {
		return new PacketPlayOutScoreboardScore(Action.REMOVE, player.getName(), line, 0);
	}

	/**
	 * 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, "b", IChatBaseComponent.ChatSerializer.a("{\"text\":\"" + "" + "\"}"));
			setField(packet, "c", IChatBaseComponent.ChatSerializer.a("{\"text\":\"" + prefix + "\"}"));
			setField(packet, "d", IChatBaseComponent.ChatSerializer.a("{\"text\":\"" + suffix + "\"}"));
			setField(packet, "i", 0);
			setField(packet, "e", "always");
			setField(packet, "g", EnumChatFormat.RESET);
			setField(packet, "i", mode);

			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, "i", 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);
		}

		@SuppressWarnings("unchecked")
		public PacketPlayOutScoreboardTeam addOrRemovePlayer(int mode, String playerName) {
			PacketPlayOutScoreboardTeam packet = new PacketPlayOutScoreboardTeam();
			setField(packet, "a", name);
			setField(packet, "i", mode);

			try {
				Field f = packet.getClass().getDeclaredField("h");
				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();
		}
	}
}

Update for 1.13.2, just a few modifications !

Enjoy it :)

@ZEERDFORME
Copy link

The link for the 1.7.10 please !!!

@PluginExcecption
Copy link

Does someone know how to update the Scoreboard then because setLine doesnt work in other methods, also i can't destroy it and create a new one because i have to user another method

@zyuiop
Copy link
Author

zyuiop commented Jun 16, 2019

Okay, that's kind of fun to see that this class is still used... Sadly I don't do Minecraft anymore, so I won't update it. But have a look at the comments and forks for different versions of this :)

Also, it's just the names of the fields you have to change, usually. Go to wiki.vz for more info! And thanks for using this code, it's really great to see that something I wrote 4 years ago can still be useful

@tompointexe
Copy link

Do it work with spigot ??

@WeiiswurstDev
Copy link

I rewrote it to use the ProtocolLibrary plugin.
This means:

  • No Java Reflect
  • No direct Minecraft implementation
  • Crossversion-Compatibility (as long as the protocol stays the same)
  • You need to use ProtocolLib as a library

@SonaahJava
Copy link

For the 1.9.4? please :)

@KalvanMC
Copy link

KalvanMC commented Dec 25, 2020

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.bukkit.craftbukkit.v1_9_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;

import net.minecraft.server.v1_9_R2.IScoreboardCriteria;
import net.minecraft.server.v1_9_R2.Packet;
import net.minecraft.server.v1_9_R2.PacketPlayOutScoreboardDisplayObjective;
import net.minecraft.server.v1_9_R2.PacketPlayOutScoreboardObjective;
import net.minecraft.server.v1_9_R2.PacketPlayOutScoreboardScore;
import net.minecraft.server.v1_9_R2.PacketPlayOutScoreboardTeam;
import net.minecraft.server.v1_9_R2.PlayerConnection;




/**
 * @author zyuiop
 * Updated by Kalvan
 */
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;
	}

	@SuppressWarnings("rawtypes")
	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, "b", "");
			setField(packet, "c", prefix);
			setField(packet, "d", suffix);
			setField(packet, "i", 0);
			setField(packet, "e", "always");
			setField(packet, "g", 0);
			setField(packet, "i", mode);

			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, "i", 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);
		}

		@SuppressWarnings("unchecked")
		public PacketPlayOutScoreboardTeam addOrRemovePlayer(int mode, String playerName) {
			PacketPlayOutScoreboardTeam packet = new PacketPlayOutScoreboardTeam();
			setField(packet, "a", name);
			setField(packet, "i", mode);

			try {
				Field f = packet.getClass().getDeclaredField("h");
				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();
		}
	}
}

Update 1.9.4, personally it works :)

@D151l
Copy link

D151l commented Oct 3, 2021

Is there a version for the 1.17.1?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment