Last active
August 29, 2015 14:00
-
-
Save DarkSeraphim/cbe05db7479c3eed5ebb to your computer and use it in GitHub Desktop.
A tool to allow developers to use one generic scoreboard, with the freedom to assign personal objectives or teams to users.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.darkseraphim.sbt; | |
import org.bukkit.Bukkit; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.player.PlayerJoinEvent; | |
import org.bukkit.event.player.PlayerQuitEvent; | |
import org.bukkit.entity.Player; | |
import org.bukkit.scoreboard.Objective; | |
import org.bukkit.scoreboard.DisplaySlot; | |
import org.bukkit.scoreboard.Scoreboard; | |
import org.bukkit.scoreboard.Score; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import org.bukkit.scheduler.BukkitRunnable; | |
import com.comphenix.protocol.PacketType; | |
import com.comphenix.protocol.ProtocolManager; | |
import com.comphenix.protocol.ProtocolLibrary; | |
import com.comphenix.protocol.events.PacketContainer; | |
import com.comphenix.protocol.events.PacketEvent; | |
import com.comphenix.protocol.events.PacketAdapter; | |
/** | |
* Example implemention | |
* @author DarkSeraphim | |
*/ | |
public class Main extends JavaPlugin implements Listener | |
{ | |
Scoreboard sb; | |
ScoreboardOptions opts; | |
@Override | |
public void onEnable() | |
{ | |
Bukkit.getPluginManager().registerEvents(this, this); | |
this.sb = Bukkit.getScoreboardManager().getNewScoreboard(); | |
this.opts = ScoreboardUtil.getInstance().trackScoreboard(this.sb); | |
} | |
@EventHandler | |
public void onJoin(PlayerJoinEvent event) | |
{ | |
final Player player = event.getPlayer(); | |
player.setScoreboard(this.sb); | |
Objective o = this.opts.getPlayerObjective(player, DisplaySlot.SIDEBAR); | |
// As an example, we generate a player-based objective | |
o.setDisplayName("STATS: "+player.getName()); | |
// Just a mock score to get some different results ;3 | |
o.getScore("Balance:").setScore(player.getName().length()); | |
// A downside is that we don't have Scoreboard#getObjective(DisplaySlot) | |
// (or whatever the method was called). But what good is that | |
// anyway, when we have a personal objective/scoreboard. | |
o.setDisplaySlot(DisplaySlot.SIDEBAR); | |
} | |
@EventHandler | |
public void onQuit(PlayerQuitEvent event) | |
{ | |
ScoreboardUtil.getInstance().cleanup(event.getPlayer()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.darkseraphim.sbt; | |
import com.comphenix.protocol.events.PacketContainer; | |
import com.comphenix.protocol.reflect.StructureModifier; | |
import java.util.HashSet; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Set; | |
import org.bukkit.entity.Player; | |
import org.bukkit.scoreboard.DisplaySlot; | |
import org.bukkit.scoreboard.DisplaySlot.*; | |
import org.bukkit.scoreboard.Objective; | |
import org.bukkit.scoreboard.Scoreboard; | |
import org.bukkit.scoreboard.Team; | |
/** | |
* | |
* @author DarkSeraphim | |
*/ | |
public class ScoreboardOptions | |
{ | |
private final Scoreboard sb; | |
private final Map<String, Integer> personal = new HashMap<String, Integer>(); | |
private final Map<String, Map<String, TeamOptions>> teamopts = new HashMap<String, Map<String, TeamOptions>>(); | |
public ScoreboardOptions(Scoreboard sb) | |
{ | |
this.sb = sb; | |
} | |
private TeamOptions getTeamOptions(Player player, Team team) | |
{ | |
Map<String, TeamOptions> gopts = this.teamopts.get(player.getName()); | |
TeamOptions opts = null; | |
if(gopts == null) | |
{ | |
gopts = new HashMap<String, TeamOptions>(); | |
this.teamopts.put(player.getName(), gopts); | |
} | |
else | |
opts = gopts.get(team.getName()); | |
if(opts == null) | |
{ | |
opts = new TeamOptions(team); | |
gopts.put(team.getName(), opts); | |
} | |
return opts; | |
} | |
/** | |
* Sets the prefix for the team of a specific scoreboard, but only | |
* for the specified player | |
*/ | |
public void setPrefix(Player player, Team team, String prefix) | |
{ | |
if(!isTracked(player)) | |
return; | |
if(!hasTeam(player, team)) | |
return; | |
getTeamOptions(player, team).setPrefix(prefix); | |
} | |
/** | |
* Sets the suffix for the team of a specific scoreboard, but only | |
* for the specified player | |
*/ | |
public void setSuffix(Player player, Team team, String suffix) | |
{ | |
if(!isTracked(player)) | |
return; | |
if(!hasTeam(player, team)) | |
return; | |
getTeamOptions(player, team).setSuffix(suffix); | |
} | |
protected void overridePacket(PacketContainer teamPacket, Player player, Team team) | |
{ | |
StructureModifier<Integer> ints = teamPacket.getIntegers(); | |
if(ints.read(0) != 0 && ints.read(0) != 2) | |
return; | |
TeamOptions opts = getTeamOptions(player, team); | |
if(opts == null) | |
return; | |
StructureModifier<String> strings = teamPacket.getStrings(); | |
strings.write(2, opts.getPrefix()); | |
strings.write(3, opts.getSuffix()); | |
int i = ints.read(1); | |
/** | |
* Do note that the following most likely has no effect | |
* whatsoever on the teams | |
*/ | |
if(opts.canSeeInvisibles()) | |
i |= 0x2; | |
else | |
i &= ~0x2; | |
ints.write(1, i); | |
} | |
/** | |
* Cleans up any references to the player from the options | |
*/ | |
public void cleanup(Player player) | |
{ | |
this.teamopts.remove(player.getName()); | |
this.personal.remove(player.getName()); | |
} | |
public void cleanup() | |
{ | |
this.teamopts.clear(); | |
this.personal.clear(); | |
} | |
private boolean isTracked(Player player) | |
{ | |
return ScoreboardUtil.getInstance().isTracked(player.getScoreboard()); | |
} | |
private boolean hasTeam(Player player, Team team) | |
{ | |
return team != null && player.getScoreboard().getTeam(team.getName()) == team; | |
} | |
/** | |
* Generates a player specific Objective and shows it to the player. | |
* If you need this later on, get the scoreboard and call | |
* Scoreboard#getObjective(player.getName()); | |
* | |
* Note: this will remove any objective that is using the player's name, | |
* I use this as an 'unique' id | |
*/ | |
public Objective getPlayerObjective(Player player, DisplaySlot slot) | |
{ | |
if(!isTracked(player)) | |
return null; | |
String name = player.getName(); | |
this.personal.put(name, displaySlotToInt(slot)); | |
Scoreboard sb = player.getScoreboard(); | |
Objective o = sb.getObjective(name); | |
if(o == null) | |
o = sb.registerNewObjective(name, "dummy"); | |
o.setDisplaySlot(slot); | |
return o; | |
} | |
private int displaySlotToInt(DisplaySlot slot) | |
{ | |
switch(slot) | |
{ | |
case BELOW_NAME: | |
return 2; | |
case SIDEBAR: | |
return 1; | |
case PLAYER_LIST: | |
return 0; | |
} | |
return -1; | |
} | |
/** | |
* Checks whether an Objective is a personal Objective | |
*/ | |
public boolean isPlayerObjective(Objective o) | |
{ | |
if(o == null) | |
return false; | |
return o.getScoreboard() == this.sb && this.personal.containsKey(o.getName()); | |
} | |
public int getDisplaySlot(Objective o) | |
{ | |
return this.personal.get(o.getName()); | |
} | |
private class TeamOptions | |
{ | |
String prefix; | |
String suffix; | |
boolean canSeeInvis; | |
private TeamOptions(Team team) | |
{ | |
this.prefix = team.getPrefix(); | |
this.suffix = team.getSuffix(); | |
this.canSeeInvis = team.canSeeFriendlyInvisibles(); | |
} | |
public void setPrefix(String prefix) | |
{ | |
this.prefix = prefix; | |
} | |
public String getPrefix() | |
{ | |
return this.prefix; | |
} | |
public void setSuffix(String suffix) | |
{ | |
this.suffix = suffix; | |
} | |
public String getSuffix() | |
{ | |
return this.suffix; | |
} | |
/** | |
* Do note that the following most likely has no effect | |
* whatsoever on the teams | |
*/ | |
public boolean canSeeInvisibles() | |
{ | |
return this.canSeeInvis; | |
} | |
public void setCanSeeInvisibles(boolean canSeeInvis) | |
{ | |
this.canSeeInvis = canSeeInvis; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.darkseraphim.sbt; | |
import com.comphenix.protocol.PacketType; | |
import com.comphenix.protocol.ProtocolLibrary; | |
import com.comphenix.protocol.ProtocolManager; | |
import com.comphenix.protocol.events.PacketAdapter; | |
import com.comphenix.protocol.events.PacketContainer; | |
import com.comphenix.protocol.events.PacketEvent; | |
import java.util.Collections; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.WeakHashMap; | |
import org.bukkit.Bukkit; | |
import org.bukkit.entity.Player; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import org.bukkit.scheduler.BukkitRunnable; | |
import org.bukkit.scoreboard.DisplaySlot.*; | |
import org.bukkit.scoreboard.Objective; | |
import org.bukkit.scoreboard.Scoreboard; | |
import org.bukkit.scoreboard.Team; | |
/** | |
* | |
* @author DarkSeraphim | |
*/ | |
public class ScoreboardUtil | |
{ | |
private final Set<String> nope = new HashSet<String>(); | |
private final Map<Scoreboard, ScoreboardOptions> tracked = new WeakHashMap<Scoreboard, ScoreboardOptions>(); | |
private final PacketAdapter listener; | |
private static ScoreboardUtil instance; | |
// PUT YOUR OWN MAIN CLASS HERE | |
// I need to borrow it for the packet listener :D | |
private final Class<? extends JavaPlugin> mainClass = Main.class; | |
private ScoreboardUtil() | |
{ | |
ProtocolManager pm = ProtocolLibrary.getProtocolManager(); | |
PacketType[] scoreboardPackets = new PacketType[] | |
{ | |
PacketType.Play.Server.SCOREBOARD_OBJECTIVE, | |
PacketType.Play.Server.SCOREBOARD_SCORE, | |
PacketType.Play.Server.SCOREBOARD_DISPLAY_OBJECTIVE, | |
PacketType.Play.Server.SCOREBOARD_TEAM | |
}; | |
this.listener = new PacketAdapter(JavaPlugin.getPlugin(this.mainClass), scoreboardPackets) | |
{ | |
@Override | |
public void onPacketSending(PacketEvent event) | |
{ | |
PacketType pt = event.getPacketType(); | |
PacketContainer packet = event.getPacket(); | |
final Player player = event.getPlayer(); | |
if(!ScoreboardUtil.this.isTracked(player.getScoreboard())) | |
{ | |
return; | |
} | |
if(pt == PacketType.Play.Server.SCOREBOARD_OBJECTIVE) | |
{ | |
Scoreboard sb = player.getScoreboard(); | |
ScoreboardOptions opts = ScoreboardUtil.this.getOptions(sb); | |
String oname = packet.getStrings().read(0); | |
if((!oname.equals(player.getName())) && opts.isPlayerObjective(sb.getObjective(oname))) | |
event.setCancelled(true); | |
else if(!ScoreboardUtil.this.nope.contains(player.getName()) && packet.getIntegers().read(0) == 0) | |
{ | |
new BukkitRunnable() | |
{ | |
@Override | |
public void run() | |
{ | |
ScoreboardUtil.this.nope.add(player.getName()); | |
} | |
}.runTaskLater(JavaPlugin.getPlugin(ScoreboardUtil.this.mainClass), 1L); | |
} | |
else if(ScoreboardUtil.this.nope.contains(player.getName()) && packet.getIntegers().read(0) == 1) | |
event.setCancelled(true); | |
} | |
else if(pt == PacketType.Play.Server.SCOREBOARD_TEAM) | |
{ | |
Scoreboard sb = player.getScoreboard(); | |
ScoreboardOptions opts = ScoreboardUtil.this.getOptions(sb); | |
String tname = packet.getStrings().read(0); | |
Team t = sb.getTeam(tname); | |
if(t == null) | |
return; // Weird :o | |
opts.overridePacket(packet, player, t); | |
} | |
else if(pt == PacketType.Play.Server.SCOREBOARD_DISPLAY_OBJECTIVE) | |
{ | |
Scoreboard sb = player.getScoreboard(); | |
ScoreboardOptions opts = ScoreboardUtil.this.getOptions(sb); | |
String oname = packet.getStrings().read(0); | |
Objective o = sb.getObjective(player.getName()); | |
if(!oname.equals(player.getName()) && opts.isPlayerObjective(o) | |
&& (opts.getDisplaySlot(o) == packet.getIntegers().read(0))) | |
event.setCancelled(true); | |
} | |
else if(pt == PacketType.Play.Server.SCOREBOARD_SCORE) | |
{ | |
Scoreboard sb = player.getScoreboard(); | |
ScoreboardOptions opts = ScoreboardUtil.this.getOptions(sb); | |
String oname = packet.getStrings().read(1); | |
Objective o = sb.getObjective(player.getName()); | |
Objective o2 = sb.getObjective(oname); | |
if(!oname.equals(player.getName()) && opts.isPlayerObjective(o) | |
&& opts.isPlayerObjective(o2) && opts.getDisplaySlot(o) == opts.getDisplaySlot(o2)) | |
event.setCancelled(true); | |
} | |
} | |
}; | |
pm.addPacketListener(this.listener); | |
} | |
/** | |
* Returns the singleton instance of the API | |
*/ | |
public static ScoreboardUtil getInstance() | |
{ | |
if(instance == null) | |
instance = new ScoreboardUtil(); | |
return instance; | |
} | |
/** | |
* Call this in onDisable, does the cleanup | |
*/ | |
public static void destroy() | |
{ | |
getInstance()._destroy(); | |
instance = null; | |
} | |
/** | |
* Inner method for cleaning up instance stuff | |
*/ | |
private void _destroy() | |
{ | |
ProtocolLibrary.getProtocolManager().removePacketListener(this.listener); | |
for(Player player : Bukkit.getOnlinePlayers()) | |
cleanup(player); | |
this.tracked.clear(); | |
this.nope.clear(); | |
} | |
/** | |
* Add a scoreboard to be tracked. For instance, the main scoreboard | |
* This is a mandarory step to start using the API. The ScoreboardOptions | |
* instance that it returns can be used to give players their unique | |
* Objective (and allows you to revoke it) WITHOUT creating a new scoreboard, | |
* and preserving any Teams. | |
* | |
* Great, isn't it? | |
*/ | |
public ScoreboardOptions trackScoreboard(Scoreboard sb) | |
{ | |
ScoreboardOptions options = new ScoreboardOptions(sb); | |
this.tracked.put(sb, options); | |
return options; | |
} | |
/** | |
* Checks if the Scoreboard is currently tracked | |
*/ | |
public boolean isTracked(Scoreboard sb) | |
{ | |
return this.tracked.containsKey(sb); | |
} | |
/** | |
* Retrieves the ScoreboardOptions of a specific Scoreboard. | |
* | |
* Returns null if it isn't being tracked by the API | |
*/ | |
public ScoreboardOptions getOptions(Scoreboard sb) | |
{ | |
return this.tracked.get(sb); | |
} | |
/** | |
* Stops manipulating the Scoreboard. Java GC will do the cleanup here | |
*/ | |
public void untrackScoreboard(Scoreboard sb) | |
{ | |
ScoreboardOptions options = this.tracked.remove(sb); | |
if(options != null) | |
{ | |
options.cleanup(); | |
} | |
} | |
/** | |
* Removes any references regarding the player. | |
*/ | |
public void cleanup(Player player) | |
{ | |
ScoreboardOptions opts = this.tracked.get(player.getScoreboard()); | |
if(opts != null) | |
opts.cleanup(player); | |
this.nope.remove(player.getName()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Is there a way with this that every single player has a diffrent belowname slot? I'm making a roleplay plugin in which I want the belowname slot to be used for a players character name... right now everyone has my charactername...