Skip to content

Instantly share code, notes, and snippets.

@DarkSeraphim
Last active August 29, 2015 14:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DarkSeraphim/cbe05db7479c3eed5ebb to your computer and use it in GitHub Desktop.
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.
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());
}
}
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;
}
}
}
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());
}
}
@LeahAuroraV
Copy link

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...

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