diff --git a/src/main/aether/Aether.java b/src/main/aether/Aether.java new file mode 100644 index 0000000..c432e94 --- /dev/null +++ b/src/main/aether/Aether.java @@ -0,0 +1,201 @@ +package aether; + +import aether.event.BoardCreateEvent; +import aether.scoreboard.Board; +import aether.scoreboard.BoardAdapter; +import aether.scoreboard.BoardEntry; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +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.Score; +import org.bukkit.scoreboard.Scoreboard; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static aether.AetherOptions.defaultOptions; + +public class Aether implements Listener { + + @Getter BoardAdapter adapter; + @Getter private JavaPlugin plugin; + @Getter private AetherOptions options; + + private Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})"); + + public Aether(JavaPlugin plugin, BoardAdapter adapter, AetherOptions options) { + this.options = options; + this.plugin = plugin; + + Bukkit.getPluginManager().registerEvents(this, plugin); + + setAdapter(adapter); + run(); + } + + public Aether(JavaPlugin plugin, BoardAdapter adapter) { + this(plugin, adapter, defaultOptions()); + } + + public Aether(JavaPlugin plugin) { + this(plugin, null, defaultOptions()); + } + + private void run() { + new BukkitRunnable() { + @Override + public void run() { + if (adapter == null) return; + for (Player player : Bukkit.getOnlinePlayers()) { + Board board = Board.getByPlayer(player); + if (board != null) { + List scores = adapter.getScoreboard(player, board, board.getCooldowns()); + List translatedScores = new ArrayList<>(); + if (scores == null) { + if (!board.getEntries().isEmpty()) { + for (BoardEntry boardEntry : board.getEntries()) { + boardEntry.remove(); + } + board.getEntries().clear(); + } + continue; + } + + for (String line : scores) { + translatedScores.add(ChatColor.translateAlternateColorCodes('&', line)); + } + + if (!options.scoreDirectionDown()) { + Collections.reverse(scores); + } + + Scoreboard scoreboard = board.getScoreboard(); + Objective objective = board.getObjective(); + + if (!(objective.getDisplayName().equals(adapter.getTitle(player)))) { + objective.setDisplayName(ChatColor.translateAlternateColorCodes('&', adapter.getTitle(player))); + } + + outer: + for (int i = 0; i < scores.size(); i++) { + String text = scores.get(i); + int position; + if (options.scoreDirectionDown()) { + position = 15 - i; + } else { + position = i + 1; + } + + Iterator iterator = new ArrayList<>(board.getEntries()).iterator(); + while (iterator.hasNext()) { + BoardEntry boardEntry = iterator.next(); + Score score = objective.getScore(boardEntry.getKey()); + + if (score != null && boardEntry.getText().equals(ChatColor.translateAlternateColorCodes('&', text))) { + if (score.getScore() == position) { + continue outer; + } + } + } + + int positionToSearch = options.scoreDirectionDown() ? 15 - position : position - 1; + + iterator = board.getEntries().iterator(); + while (iterator.hasNext()) { + BoardEntry boardEntry = iterator.next(); + int entryPosition = scoreboard.getObjective(DisplaySlot.SIDEBAR).getScore(boardEntry.getKey()).getScore(); + + if (!options.scoreDirectionDown()) { + if (entryPosition > scores.size()) { + iterator.remove(); + boardEntry.remove(); + } + } + + } + + BoardEntry entry = board.getByPosition(positionToSearch); + + if (entry == null) { + new BoardEntry(board, text).send(position); + } else { + entry.setText(text).setup().send(position); + } + + if (board.getEntries().size() > scores.size()) { + iterator = board.getEntries().iterator(); + while (iterator.hasNext()) { + BoardEntry boardEntry = iterator.next(); + if ((!translatedScores.contains(boardEntry.getText())) || Collections.frequency(board.getBoardEntriesFormatted(), boardEntry.getText()) > 1) { + iterator.remove(); + boardEntry.remove(); + } + } + } + } + adapter.onScoreboardCreate(player, scoreboard); + player.setScoreboard(scoreboard); + } + } + } + }.runTaskTimerAsynchronously(plugin, 20L, 2L); + } + + public void setAdapter(BoardAdapter adapter) { + this.adapter = adapter; + for (Player player : Bukkit.getOnlinePlayers()) { + Board board = Board.getByPlayer(player); + if (board != null) { + Board.getBoards().remove(board); + } + Bukkit.getPluginManager().callEvent(new BoardCreateEvent(new Board(player, this, options), player)); + } + } + + @EventHandler + public void onPlayerJoinEvent(PlayerJoinEvent event) { + if (Board.getByPlayer(event.getPlayer()) == null) { + Bukkit.getPluginManager().callEvent(new BoardCreateEvent(new Board(event.getPlayer(), this, options), event.getPlayer())); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerQuitEvent(PlayerQuitEvent event) { + Board board = Board.getByPlayer(event.getPlayer()); + if (board != null) { + Board.getBoards().remove(board); + } + } + + public String colorize(String message) { + return ChatColor.translateAlternateColorCodes('&', message); + } + + public String translateHexColorCodes(String message) { + char colorChar = '§'; + Matcher matcher = HEX_PATTERN.matcher(message); + StringBuffer buffer = new StringBuffer(message.length() + 32); + while (matcher.find()) { + String group = matcher.group(1); + matcher.appendReplacement(buffer, "§x§" + group + .charAt(0) + '§' + group.charAt(1) + '§' + group + .charAt(2) + '§' + group.charAt(3) + '§' + group + .charAt(4) + '§' + group.charAt(5)); + } + return matcher.appendTail(buffer).toString(); + } +} diff --git a/src/main/aether/AetherOptions.java b/src/main/aether/AetherOptions.java new file mode 100644 index 0000000..526c513 --- /dev/null +++ b/src/main/aether/AetherOptions.java @@ -0,0 +1,18 @@ +package aether; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(chain = true, fluent = true) +public class AetherOptions { + + private boolean hook; + private boolean scoreDirectionDown; + + static AetherOptions defaultOptions() { + return new AetherOptions().hook(false).scoreDirectionDown(false); + } +} diff --git a/src/main/aether/event/BoardCreateEvent.java b/src/main/aether/event/BoardCreateEvent.java new file mode 100644 index 0000000..0cc018e --- /dev/null +++ b/src/main/aether/event/BoardCreateEvent.java @@ -0,0 +1,27 @@ +package aether.event; + +import aether.scoreboard.Board; +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class BoardCreateEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + @Getter private final Board board; + @Getter private final Player player; + + public BoardCreateEvent(Board board, Player player) { + this.board = board; + this.player = player; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public HandlerList getHandlers() { + return HANDLERS; + } +} diff --git a/src/main/aether/scoreboard/Board.java b/src/main/aether/scoreboard/Board.java new file mode 100644 index 0000000..2bdc888 --- /dev/null +++ b/src/main/aether/scoreboard/Board.java @@ -0,0 +1,129 @@ +package aether.scoreboard; + +import aether.Aether; +import aether.AetherOptions; +import aether.scoreboard.cooldown.BoardCooldown; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Scoreboard; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Board { + + @Getter private static Set boards = new HashSet<>(); + + private final Aether aether; + private final AetherOptions options; + + @Getter private Scoreboard scoreboard; + @Getter private Player player; + @Getter private Objective objective; + @Getter private Set keys; + @Getter private List entries; + + private Set cooldowns; + + public Board(Player player, Aether aether, AetherOptions options) { + this.player = player; + this.aether = aether; + this.options = options; + + this.keys = new HashSet<>(); + this.entries = new ArrayList<>(); + + this.cooldowns = new HashSet<>(); + + setup(); + } + + public static Board getByPlayer(Player player) { + for (Board board : boards) { + if (board.getPlayer().getName().equals(player.getName())) { + return board; + } + } + + return null; + } + + private void setup() { + if (options.hook() && !player.getScoreboard().equals(Bukkit.getScoreboardManager().getMainScoreboard())) { + scoreboard = player.getScoreboard(); + } else { + scoreboard = Bukkit.getScoreboardManager().getNewScoreboard(); + } + + objective = scoreboard.registerNewObjective("glaedr_is_shit", "dummy"); + objective.setDisplaySlot(DisplaySlot.SIDEBAR); + + if (aether.getAdapter() != null) { + objective.setDisplayName(ChatColor.translateAlternateColorCodes('&', aether.getAdapter().getTitle(player))); + } else { + objective.setDisplayName("Default Title"); + } + + boards.add(this); + } + + public String getNewKey(BoardEntry entry) { + for (ChatColor color : ChatColor.values()) { + String colorText = color + "" + ChatColor.WHITE; + if (entry.getText().length() > 16) { + String sub = entry.getText().substring(0, 16); + colorText = colorText + ChatColor.getLastColors(sub); + } + + if (!keys.contains(colorText)) { + keys.add(colorText); + return colorText; + } + } + + throw new IndexOutOfBoundsException("No more keys available!"); + } + + public List getBoardEntriesFormatted() { + List toReturn = new ArrayList<>(); + for (BoardEntry entry : new ArrayList<>(entries)) { + toReturn.add(entry.getText()); + } + + return toReturn; + } + + public BoardEntry getByPosition(int position) { + int i = 0; + + for (BoardEntry board : entries) { + if (i == position) { + return board; + } + i++; + } + + return null; + } + + public BoardCooldown getCooldown(String id) { + for (BoardCooldown cooldown : getCooldowns()) { + if (cooldown.getId().equals(id)) { + return cooldown; + } + } + + return null; + } + + public Set getCooldowns() { + cooldowns.removeIf(cooldown -> System.currentTimeMillis() >= cooldown.getEnd()); + return cooldowns; + } +} diff --git a/src/main/aether/scoreboard/BoardAdapter.java b/src/main/aether/scoreboard/BoardAdapter.java new file mode 100644 index 0000000..2b1b1b8 --- /dev/null +++ b/src/main/aether/scoreboard/BoardAdapter.java @@ -0,0 +1,17 @@ +package aether.scoreboard; + +import aether.scoreboard.cooldown.BoardCooldown; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.Scoreboard; + +import java.util.List; +import java.util.Set; + +public interface BoardAdapter { + + String getTitle(Player player); + + List getScoreboard(Player player, Board board, Set cooldowns); + + void onScoreboardCreate(Player player, Scoreboard board); +} diff --git a/src/main/aether/scoreboard/BoardEntry.java b/src/main/aether/scoreboard/BoardEntry.java new file mode 100644 index 0000000..903832e --- /dev/null +++ b/src/main/aether/scoreboard/BoardEntry.java @@ -0,0 +1,90 @@ +package aether.scoreboard; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.bukkit.ChatColor; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.Team; + +@Accessors(chain = true) +public class BoardEntry { + + @Getter private Board board; + @Getter @Setter private String text; + @Getter private String originalText; + @Getter private String key; + @Getter private Team team; + + public BoardEntry(Board board, String text) { + this.board = board; + this.text = text; + this.originalText = text; + this.key = board.getNewKey(this); + + setup(); + } + + public BoardEntry setup() { + Scoreboard scoreboard = board.getScoreboard(); + + text = ChatColor.translateAlternateColorCodes('&', text); + + String teamName = key; + + if (teamName.length() > 16) { + teamName = teamName.substring(0, 16); + } + + if (scoreboard.getTeam(teamName) != null) { + team = scoreboard.getTeam(teamName); + } else { + team = scoreboard.registerNewTeam(teamName); + } + + if (!(team.getEntries().contains(key))) { + team.addEntry(key); + } + + if (!(board.getEntries().contains(this))) { + board.getEntries().add(this); + } + + return this; + } + + public BoardEntry send(int position) { + Objective objective = board.getObjective(); + + if (text.length() > 16) { + boolean fix = text.toCharArray()[15] == ChatColor.COLOR_CHAR; + + String prefix = fix ? text.substring(0, 15) : text.substring(0, 16); + String suffix = fix ? text.substring(15) : ChatColor.getLastColors(prefix) + text.substring(16); + + team.setPrefix(prefix); + + if (suffix.length() > 16) { + team.setSuffix(suffix.substring(0, 16)); + } else { + team.setSuffix(suffix); + } + } else { + team.setPrefix(text); + team.setSuffix(""); + } + + Score score = objective.getScore(key); + score.setScore(position); + + return this; + } + + public void remove() { + board.getKeys().remove(key); + board.getScoreboard().resetScores(key); + } + +} diff --git a/src/main/aether/scoreboard/cooldown/BoardCooldown.java b/src/main/aether/scoreboard/cooldown/BoardCooldown.java new file mode 100644 index 0000000..cfb9482 --- /dev/null +++ b/src/main/aether/scoreboard/cooldown/BoardCooldown.java @@ -0,0 +1,42 @@ +package aether.scoreboard.cooldown; + +import aether.scoreboard.Board; +import lombok.Getter; +import org.apache.commons.lang3.time.DurationFormatUtils; + +import java.text.DecimalFormat; + +public class BoardCooldown { + + private static final DecimalFormat SECONDS_FORMATTER = new DecimalFormat("#0.0"); + + @Getter private final Board board; + @Getter private final String id; + @Getter private final double duration; + @Getter private final long end; + + public BoardCooldown(Board board, String id, double duration) { + this.board = board; + this.id = id; + this.duration = duration; + this.end = (long) (System.currentTimeMillis() + (duration * 1000)); + + board.getCooldowns().add(this); + } + + public String getFormattedString(BoardFormat format) { + if (format == null) { + throw new NullPointerException(); + } + if (format == BoardFormat.SECONDS) { + return SECONDS_FORMATTER.format(((end - System.currentTimeMillis()) / 1000.0f)); + } else { + return DurationFormatUtils.formatDuration(end - System.currentTimeMillis(), "mm:ss"); + } + } + + public void cancel() { + board.getCooldowns().remove(this); + } + +} \ No newline at end of file diff --git a/src/main/aether/scoreboard/cooldown/BoardFormat.java b/src/main/aether/scoreboard/cooldown/BoardFormat.java new file mode 100644 index 0000000..24f34cf --- /dev/null +++ b/src/main/aether/scoreboard/cooldown/BoardFormat.java @@ -0,0 +1,5 @@ +package aether.scoreboard.cooldown; + +public enum BoardFormat { + SECONDS, MINUTES, HOURS +}