Initial commit

This commit is contained in:
2023-04-05 22:02:29 +02:00
commit ea3f25e278
13 changed files with 809 additions and 0 deletions

View File

@ -0,0 +1,181 @@
package net.knarcraft.placeholdersigns;
import me.clip.placeholderapi.PlaceholderAPI;
import net.knarcraft.placeholdersigns.command.EditSignCommand;
import net.knarcraft.placeholdersigns.container.LineChangeRequest;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.listener.SignBreakListener;
import net.knarcraft.placeholdersigns.listener.SignClickListener;
import net.knarcraft.placeholdersigns.listener.SignTextListener;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Sign;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This plugin's main class
*/
public final class PlaceholderSigns extends JavaPlugin {
private static PlaceholderSigns instance;
private PlaceholderSignHandler signHandler;
private Map<Player, LineChangeRequest> changeRequests;
/**
* Gets an instance of this plugin
*
* @return <p>A plugin instance</p>
*/
public static @NotNull PlaceholderSigns getInstance() {
return instance;
}
/**
* Gets this instance's placeholder sign handler
*
* @return <p>The sign handler</p>
*/
public @NotNull PlaceholderSignHandler getSignHandler() {
return this.signHandler;
}
/**
* Registers a sign change request
*
* <p>A sign change request is basically the result of running the editSign command, which must be stored until the
* player clicks a sign.</p>
*
* @param request <p>The sign change request to register</p>
*/
public void addChangeRequest(@NotNull LineChangeRequest request) {
changeRequests.put(request.player(), request);
}
/**
* Gets a sign change request
*
* @param player <p>The player to get the request for</p>
* @return <p>The sign change request, or null if not found</p>
*/
public @Nullable LineChangeRequest getChangeRequest(@NotNull Player player) {
return changeRequests.remove(player);
}
@Override
public void onLoad() {
super.onLoad();
// Register serialization classes
ConfigurationSerialization.registerClass(PlaceholderSign.class);
ConfigurationSerialization.registerClass(PlaceholderSignHandler.class);
}
@Override
public void onEnable() {
instance = this;
signHandler = new PlaceholderSignHandler();
changeRequests = new HashMap<>();
signHandler.load();
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) {
getLogger().log(Level.WARNING, "Could not find PlaceholderAPI! This plugin is required.");
Bukkit.getPluginManager().disablePlugin(this);
return;
}
// Update signs' placeholders every second
Bukkit.getScheduler().runTaskTimer(this, this::updateSigns, 20 * 10, 20 * 5);
Bukkit.getPluginManager().registerEvents(new SignBreakListener(), this);
Bukkit.getPluginManager().registerEvents(new SignTextListener(), this);
Bukkit.getPluginManager().registerEvents(new SignClickListener(), this);
PluginCommand editCommand = Bukkit.getPluginCommand("editSign");
if (editCommand != null) {
editCommand.setExecutor(new EditSignCommand());
}
}
@Override
public void onDisable() {
// Plugin shutdown logic
}
/**
* Updates all loaded and registered placeholder signs
*/
private void updateSigns() {
for (PlaceholderSign placeholderSign : signHandler.getSigns()) {
// Ignore signs away from players
Location location = placeholderSign.location();
if (!location.getChunk().isLoaded()) {
continue;
}
// If no longer a sign, remove
if (!(location.getBlock().getState() instanceof Sign sign)) {
signHandler.unregisterSign(placeholderSign);
continue;
}
// Update placeholders
Map<Integer, String> placeholders = placeholderSign.placeholders();
String[] lines = sign.getLines();
boolean updateRequired = false;
for (int i = 0; i < lines.length; i++) {
String oldText = sign.getLine(i);
// The new text of the sign is either the same, or the original placeholder
String newText;
if (!placeholders.containsKey(i) || placeholders.get(i) == null) {
newText = oldText;
} else {
newText = PlaceholderAPI.setPlaceholders(null, placeholders.get(i));
}
// Convert color codes
newText = translateAllColorCodes(newText);
// Only change the line if the text has changed
if (!newText.equals(oldText)) {
sign.setLine(i, newText);
updateRequired = true;
}
}
// Only update the sign if the text has changed
if (updateRequired) {
sign.update();
}
}
}
/**
* Translates all found color codes to formatting in a string
*
* @param message <p>The string to search for color codes</p>
* @return <p>The message with color codes translated</p>
*/
private static String translateAllColorCodes(String message) {
message = ChatColor.translateAlternateColorCodes('&', message);
Pattern pattern = Pattern.compile("&?(#[a-fA-F0-9]{6})");
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group(1)));
}
return message;
}
}

View File

@ -0,0 +1,72 @@
package net.knarcraft.placeholdersigns.command;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.LineChangeRequest;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* A command for setting a sign's text to anything longer than normally possible
*/
public class EditSignCommand implements TabExecutor {
private static final List<String> lineNumbers;
static {
lineNumbers = new ArrayList<>();
for (int i = 1; i < 5; i++) {
lineNumbers.add(String.valueOf(i));
}
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (args.length < 2 || !(commandSender instanceof Player player)) {
return false;
}
// Parse the specified line number
int lineNumber;
try {
lineNumber = Integer.parseInt(args[0]);
if (lineNumber < 0 || lineNumber > 4) {
return false;
}
} catch (NumberFormatException exception) {
return false;
}
// Get all arguments as a space-separated string
StringBuilder builder = new StringBuilder(args[1]);
for (int i = 2; i < args.length; i++) {
builder.append(" ").append(args[i]);
}
// Register the line change request
LineChangeRequest request = new LineChangeRequest(player, lineNumber - 1, builder.toString());
PlaceholderSigns.getInstance().addChangeRequest(request);
commandSender.sendMessage("Please click the sign you want to change.");
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (args.length == 1) {
return lineNumbers;
} else {
return new ArrayList<>();
}
}
}

View File

@ -0,0 +1,13 @@
package net.knarcraft.placeholdersigns.container;
import org.bukkit.entity.Player;
/**
* A record of a player's request to change a sign
*
* @param player <p>The player requesting the sign change</p>
* @param line <p>The line the player wants to change</p>
* @param text <p>The new text the player provided for the line</p>
*/
public record LineChangeRequest(Player player, int line, String text) {
}

View File

@ -0,0 +1,39 @@
package net.knarcraft.placeholdersigns.container;
import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* A sign containing one or more placeholders
*
* @param location <p>The location of the sign</p>
* @param placeholders <p>The original placeholders typed on the sign</p>
*/
public record PlaceholderSign(Location location,
Map<Integer, String> placeholders) implements ConfigurationSerializable {
@Override
@NotNull
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("location", location);
data.put("placeholders", placeholders);
return data;
}
/**
* Deserializes the placeholder-sign specified in the given data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized placeholder sign</p>
*/
@SuppressWarnings({"unchecked", "unused"})
public static PlaceholderSign deserialize(Map<String, Object> data) {
return new PlaceholderSign((Location) data.get("location"), (Map<Integer, String>) data.get("placeholders"));
}
}

View File

@ -0,0 +1,118 @@
package net.knarcraft.placeholdersigns.handler;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import org.bukkit.Location;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
/**
* A handler for keeping track of placeholder signs
*/
public class PlaceholderSignHandler implements ConfigurationSerializable {
private static final File signsFile = new File(PlaceholderSigns.getInstance().getDataFolder(), "signs.yml");
private Set<PlaceholderSign> placeholderSigns;
private Map<Location, PlaceholderSign> locationLookup;
/**
* Gets all registered signs
*
* @return <p>All registered signs</p>
*/
public @NotNull Set<PlaceholderSign> getSigns() {
return new HashSet<>(placeholderSigns);
}
/**
* Gets a placeholder sign from the given location
*
* @param location <p>The location of the sign</p>
* @return <p>The sign at the location, or null if no such sign exists</p>
*/
public PlaceholderSign getFromLocation(@NotNull Location location) {
return locationLookup.get(location);
}
/**
* Registers a new placeholder sign
*
* @param sign <p>The sign to register</p>
*/
public void registerSign(@NotNull PlaceholderSign sign) {
this.placeholderSigns.add(sign);
locationLookup.put(sign.location(), sign);
save();
}
/**
* Un-registers a placeholder sign
*
* @param sign <p>The sign to un-register</p>
*/
public void unregisterSign(@NotNull PlaceholderSign sign) {
locationLookup.remove(sign.location());
this.placeholderSigns.remove(sign);
save();
}
/**
* Loads all placeholder signs from disk
*/
public void load() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
PlaceholderSignHandler loadedHandler = (PlaceholderSignHandler) configuration.get("signHandler");
this.placeholderSigns = loadedHandler != null ? loadedHandler.placeholderSigns : new HashSet<>();
this.locationLookup = loadedHandler != null ? loadedHandler.locationLookup : new HashMap<>();
}
/**
* Saves all current placeholder signs
*/
public void save() {
try {
YamlConfiguration configuration = new YamlConfiguration();
configuration.set("signHandler", this);
configuration.save(signsFile);
} catch (IOException exception) {
PlaceholderSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to save placeholder signs!");
}
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("signs", this.placeholderSigns);
return data;
}
/**
* Deserializes the given placeholder sign handler data
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized sign handler</p>
*/
@SuppressWarnings({"unchecked", "unused"})
public static PlaceholderSignHandler deserialize(@NotNull Map<String, Object> data) {
PlaceholderSignHandler placeholderSignHandler = new PlaceholderSignHandler();
placeholderSignHandler.placeholderSigns = (Set<PlaceholderSign>) data.get("signs");
Map<Location, PlaceholderSign> lookup = new HashMap<>();
for (PlaceholderSign sign : placeholderSignHandler.placeholderSigns) {
lookup.put(sign.location(), sign);
}
placeholderSignHandler.locationLookup = lookup;
return placeholderSignHandler;
}
}

View File

@ -0,0 +1,32 @@
package net.knarcraft.placeholdersigns.listener;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
/**
* A listener for placeholder signs being broken
*/
public class SignBreakListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onSignBreak(BlockBreakEvent event) {
Block block = event.getBlock();
if (!(block.getState() instanceof Sign)) {
return;
}
PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler();
PlaceholderSign sign = signHandler.getFromLocation(block.getLocation());
if (sign != null) {
signHandler.unregisterSign(sign);
}
}
}

View File

@ -0,0 +1,53 @@
package net.knarcraft.placeholdersigns.listener;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.LineChangeRequest;
import org.bukkit.Bukkit;
import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
/**
* A listener for placeholder signs being clicked
*/
public class SignClickListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onSignClick(PlayerInteractEvent event) {
// Ignore if not a clicked sign
if (!event.hasBlock() || event.getClickedBlock() == null ||
!(event.getClickedBlock().getState() instanceof Sign sign)) {
return;
}
// Check if the player has run the /editSign command
LineChangeRequest request = PlaceholderSigns.getInstance().getChangeRequest(event.getPlayer());
if (request == null) {
return;
}
String[] lines = sign.getLines();
lines[request.line()] = request.text();
// Run the sign change event to allow protection plugins to cancel, and allow the sign text listener to trigger
SignChangeEvent changeEvent = new SignChangeEvent(event.getClickedBlock(), event.getPlayer(), lines);
Bukkit.getPluginManager().callEvent(changeEvent);
if (changeEvent.isCancelled()) {
return;
}
// Update the sign with the new text
String[] finalLines = changeEvent.getLines();
for (int i = 0; i < finalLines.length; i++) {
sign.setLine(i, finalLines[i]);
}
sign.update();
event.getPlayer().sendMessage("The sign line was successfully changed.");
}
}

View File

@ -0,0 +1,53 @@
package net.knarcraft.placeholdersigns.listener;
import me.clip.placeholderapi.PlaceholderAPI;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import java.util.HashMap;
import java.util.Map;
/**
* A listener for signs being changed to contain on or more placeholders
*/
public class SignTextListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSignCreate(SignChangeEvent event) {
String[] lines = event.getLines();
Map<Integer, String> placeholders = new HashMap<>();
// Register any lines PlaceholderAPI wants to change
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
String replaced = PlaceholderAPI.setPlaceholders(null, line);
if (line.equalsIgnoreCase(replaced)) {
continue;
}
placeholders.put(i, line);
}
Location location = event.getBlock().getLocation();
PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler();
PlaceholderSign existingSign = signHandler.getFromLocation(location);
// Register the placeholder sign
if (!placeholders.isEmpty() && existingSign == null) {
PlaceholderSign placeholderSign = new PlaceholderSign(event.getBlock().getLocation(), placeholders);
signHandler.registerSign(placeholderSign);
} else if (!placeholders.isEmpty()) {
// Overwrite the placeholders of the existing placeholder sign
for (Map.Entry<Integer, String> entry : placeholders.entrySet()) {
existingSign.placeholders().put(entry.getKey(), entry.getValue());
}
signHandler.save();
}
}
}