v1.0.0
This commit is contained in:
73
src/main/java/tk/alex3025/headstones/Headstones.java
Normal file
73
src/main/java/tk/alex3025/headstones/Headstones.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package tk.alex3025.headstones;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tk.alex3025.headstones.commands.HeadstonesCommand;
|
||||
import tk.alex3025.headstones.commands.subcommands.ClearDatabaseCommand;
|
||||
import tk.alex3025.headstones.commands.subcommands.ReloadConfigCommand;
|
||||
import tk.alex3025.headstones.listeners.BlockBreakListener;
|
||||
import tk.alex3025.headstones.listeners.PlayerDeathListener;
|
||||
import tk.alex3025.headstones.listeners.RightClickListener;
|
||||
import tk.alex3025.headstones.utils.ConfigFile;
|
||||
|
||||
public final class Headstones extends JavaPlugin {
|
||||
|
||||
private static Headstones instance;
|
||||
|
||||
private ConfigFile config;
|
||||
private ConfigFile messages;
|
||||
private ConfigFile database;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
|
||||
this.loadConfigurationFiles();
|
||||
this.registerListeners();
|
||||
this.registerCommands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Plugin shutdown logic
|
||||
}
|
||||
|
||||
private void loadConfigurationFiles() {
|
||||
this.config = new ConfigFile(this,"config.yml");
|
||||
this.messages = new ConfigFile(this,"messages.yml");
|
||||
this.database = new ConfigFile(this,"database.yml");
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
new PlayerDeathListener();
|
||||
new BlockBreakListener();
|
||||
new RightClickListener();
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
new HeadstonesCommand();
|
||||
|
||||
// Subcommands
|
||||
new ClearDatabaseCommand();
|
||||
new ReloadConfigCommand();
|
||||
}
|
||||
|
||||
public static Headstones getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Config getters
|
||||
@Override
|
||||
public @NotNull ConfigFile getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public ConfigFile getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public ConfigFile getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package tk.alex3025.headstones.commands;
|
||||
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.StringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
import tk.alex3025.headstones.commands.subcommands.SubcommandBase;
|
||||
import tk.alex3025.headstones.utils.Message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class HeadstonesCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
public HeadstonesCommand() {
|
||||
PluginCommand command = Headstones.getInstance().getCommand("headstones");
|
||||
if (command != null) {
|
||||
command.setExecutor(this);
|
||||
command.setTabCompleter(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) {
|
||||
if (args.length == 0) {
|
||||
String prefix = Message.getTranslation("prefix");
|
||||
Message.sendMessage(sender, "&8&m+----------+&r " + prefix + " &8&m+----------+");
|
||||
sender.sendMessage("");
|
||||
Message.sendMessage(sender, " &7Author: &balex3025");
|
||||
sender.sendMessage("");
|
||||
Message.sendMessage(sender, " &7Version: &b" + Headstones.getInstance().getDescription().getVersion());
|
||||
sender.sendMessage("");
|
||||
Message.sendMessage(sender, "&8&m+----------+&r " + prefix + " &8&m+----------+");
|
||||
} else {
|
||||
SubcommandBase subcommand = SubcommandBase.getSubcommand(args[0]);
|
||||
if (subcommand != null) {
|
||||
// Remove the subcommand name from the args
|
||||
String[] newArgs = new String[args.length - 1];
|
||||
System.arraycopy(args, 1, newArgs, 0, args.length - 1);
|
||||
|
||||
if (subcommand.isPlayersOnly() && !(sender instanceof Player)) {
|
||||
new Message(sender).translation("player-only").send();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!subcommand.hasPermission(sender)) {
|
||||
new Message(sender).translation("no-permissions").send();
|
||||
return true;
|
||||
}
|
||||
|
||||
return subcommand.onCommand(sender, newArgs);
|
||||
}
|
||||
new Message(sender).translation("unknown-subcommand").send();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String @NotNull [] args) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
if (args.length == 1) {
|
||||
for (SubcommandBase subcommand : SubcommandBase.getRegisteredSubcommands())
|
||||
if (subcommand.hasPermission(sender))
|
||||
matches.add(subcommand.getName());
|
||||
|
||||
return StringUtil.copyPartialMatches(args[0], matches, new ArrayList<>());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package tk.alex3025.headstones.commands.subcommands;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
import tk.alex3025.headstones.utils.ConfigFile;
|
||||
import tk.alex3025.headstones.utils.Message;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClearDatabaseCommand extends SubcommandBase {
|
||||
|
||||
private final Map<String, Long> waitingConfirmPlayers = new HashMap<>();;
|
||||
|
||||
public ClearDatabaseCommand() {
|
||||
super("cleardb", "headstones.cleardb");
|
||||
|
||||
// Clear waiting players after 10 seconds
|
||||
Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(Headstones.getInstance(), () -> {
|
||||
for (Map.Entry<String, Long> entry : this.waitingConfirmPlayers.entrySet())
|
||||
if (System.currentTimeMillis() - entry.getValue() > 10000)
|
||||
this.waitingConfirmPlayers.remove(entry.getKey());
|
||||
}, 0, 40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, String[] args) {
|
||||
Player player = (Player) sender;
|
||||
|
||||
if (this.waitingConfirmPlayers.containsKey(player.getUniqueId().toString())) {
|
||||
this.waitingConfirmPlayers.remove(player.getUniqueId().toString());
|
||||
|
||||
ConfigFile headstonesFile = Headstones.getInstance().getDatabase();
|
||||
headstonesFile.set("headstones", new HashMap<>());
|
||||
headstonesFile.save();
|
||||
|
||||
Message.sendPrefixedMessage(player, "&aDatabase cleared!");
|
||||
} else {
|
||||
this.waitingConfirmPlayers.put(player.getUniqueId().toString(), System.currentTimeMillis());
|
||||
Message.sendPrefixedMessage(player, "&eAre you sure you want to clear the database? &c&lTHIS WILL MAKE ALL EXISTING HEADSTONES USELESS. &7Type &f/headstones cleardb &7again to confirm.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package tk.alex3025.headstones.commands.subcommands;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import tk.alex3025.headstones.utils.ConfigFile;
|
||||
import tk.alex3025.headstones.utils.Message;
|
||||
|
||||
public class ReloadConfigCommand {
|
||||
|
||||
public ReloadConfigCommand() {
|
||||
new SubcommandBase("reload", "headstones.reload") {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, String[] args) {
|
||||
ConfigFile.reloadAll();
|
||||
Message.sendPrefixedMessage(sender, "&aSuccessfully reloaded all configuration files!");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package tk.alex3025.headstones.commands.subcommands;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SubcommandBase {
|
||||
|
||||
private static final List<SubcommandBase> registeredSubcommands = new ArrayList<>();
|
||||
|
||||
private final String name;
|
||||
private String permission = null;
|
||||
private boolean playersOnly = false;
|
||||
|
||||
public SubcommandBase(@NotNull String name) {
|
||||
this.name = name;
|
||||
this.registerSubcommand();
|
||||
}
|
||||
|
||||
public SubcommandBase(@NotNull String name, String permission) {
|
||||
this(name);
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
public SubcommandBase(@NotNull String name, String permission, boolean playersOnly) {
|
||||
this(name, permission);
|
||||
this.playersOnly = playersOnly;
|
||||
}
|
||||
|
||||
public abstract boolean onCommand(CommandSender sender, String[] args);
|
||||
|
||||
public boolean hasPermission(CommandSender sender) {
|
||||
return this.getPermission() == null || (this.getPermission() != null && sender.hasPermission(this.getPermission()));
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
public boolean isPlayersOnly() {
|
||||
return this.playersOnly;
|
||||
}
|
||||
|
||||
public void registerSubcommand() {
|
||||
registeredSubcommands.add(this);
|
||||
}
|
||||
|
||||
public static List<SubcommandBase> getRegisteredSubcommands() {
|
||||
return registeredSubcommands;
|
||||
}
|
||||
|
||||
public static @Nullable SubcommandBase getSubcommand(String subcommand) {
|
||||
for (SubcommandBase registered : registeredSubcommands)
|
||||
if (registered.getName().equals(subcommand))
|
||||
return registered;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package tk.alex3025.headstones.listeners;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tk.alex3025.headstones.utils.Headstone;
|
||||
import tk.alex3025.headstones.utils.Message;
|
||||
|
||||
public class BlockBreakListener extends ListenerBase {
|
||||
|
||||
@EventHandler
|
||||
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
||||
Headstone headstone = Headstone.fromBlock(event.getBlock());
|
||||
|
||||
if (headstone != null)
|
||||
if (headstone.isOwner(event.getPlayer()))
|
||||
headstone.onBreak(event);
|
||||
else {
|
||||
event.setCancelled(true);
|
||||
new Message(event.getPlayer()).translation("cannot-break-others").prefixed(false).send();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package tk.alex3025.headstones.listeners;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.Listener;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
|
||||
public abstract class ListenerBase implements Listener {
|
||||
|
||||
public ListenerBase() {
|
||||
Bukkit.getPluginManager().registerEvents(this, Headstones.getInstance());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package tk.alex3025.headstones.listeners;
|
||||
|
||||
import com.bgsoftware.wildloaders.api.npc.ChunkLoaderNPC;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tk.alex3025.headstones.utils.ExperienceManager;
|
||||
import tk.alex3025.headstones.utils.Headstone;
|
||||
|
||||
public class PlayerDeathListener extends ListenerBase {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// Check if the player is a chunk loader from the WildLoaders plugin
|
||||
if (player instanceof ChunkLoaderNPC) return;
|
||||
|
||||
boolean keepExperience = !event.getKeepLevel() && player.hasPermission("headstones.keep-experience");
|
||||
boolean keepInventory = !event.getKeepInventory() && player.hasPermission("headstones.keep-inventory");
|
||||
|
||||
if (!(keepExperience && keepInventory) || !player.getInventory().isEmpty() || ExperienceManager.getExperience(player) != 0)
|
||||
new Headstone(player).onPlayerDeath(event, keepExperience, keepInventory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package tk.alex3025.headstones.listeners;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
import tk.alex3025.headstones.utils.Headstone;
|
||||
import tk.alex3025.headstones.utils.Message;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class RightClickListener extends ListenerBase {
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
||||
if (event.getClickedBlock() != null && event.getAction().isRightClick() && event.getHand().name().equals("HAND")) {
|
||||
Headstone headstone = Headstone.fromBlock(event.getClickedBlock());
|
||||
|
||||
if (headstone != null)
|
||||
new Message(event.getPlayer(), new HashMap<>() {{
|
||||
put("username", headstone.getOwner().getName());
|
||||
put("datetime", new SimpleDateFormat(Headstones.getInstance().getConfig().getString("date-format")).format(new Date(headstone.getTimestamp())));
|
||||
}}).translation("headstone-info").send();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
70
src/main/java/tk/alex3025/headstones/utils/ConfigFile.java
Normal file
70
src/main/java/tk/alex3025/headstones/utils/ConfigFile.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package tk.alex3025.headstones.utils;
|
||||
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ConfigFile extends YamlConfiguration {
|
||||
|
||||
private final static List<ConfigFile> CONFIGS = new ArrayList<>();
|
||||
|
||||
private final Headstones instance;
|
||||
private File file;
|
||||
|
||||
public ConfigFile(Headstones instance, String filename) {
|
||||
this.instance = instance;
|
||||
|
||||
try {
|
||||
this.createOrLoadConfig(filename);
|
||||
} catch (IOException | InvalidConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
CONFIGS.add(this);
|
||||
}
|
||||
|
||||
public void createOrLoadConfig(String filename) throws IOException, InvalidConfigurationException {
|
||||
this.file = new File(this.instance.getDataFolder(), filename);
|
||||
|
||||
if (!this.file.exists()) {
|
||||
this.file.getParentFile().mkdirs();
|
||||
instance.saveResource(filename, false);
|
||||
}
|
||||
|
||||
this.load(this.file);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
this.save(this.file);
|
||||
this.reload();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
try {
|
||||
this.load(this.file);
|
||||
} catch (InvalidConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException ignored) {
|
||||
try {
|
||||
this.createOrLoadConfig(this.file.getName());
|
||||
} catch (IOException | InvalidConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void reloadAll() {
|
||||
for (ConfigFile config : CONFIGS)
|
||||
config.reload();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tk.alex3025.headstones.utils;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ExperienceManager {
|
||||
|
||||
public static int getExperience(int level) {
|
||||
int xp = 0;
|
||||
|
||||
if (level >= 0 && level <= 15)
|
||||
xp = (int) Math.round(Math.pow(level, 2) + 6 * level);
|
||||
else if (level > 15 && level <= 30)
|
||||
xp = (int) Math.round((2.5 * Math.pow(level, 2) - 40.5 * level + 360));
|
||||
else if (level > 30)
|
||||
xp = (int) Math.round(((4.5 * Math.pow(level, 2) - 162.5 * level + 2220)));
|
||||
|
||||
return xp;
|
||||
}
|
||||
|
||||
public static int getExperience(@NotNull Player player) {
|
||||
return Math.round(player.getExp() * player.getExpToLevel()) + getExperience(player.getLevel());
|
||||
}
|
||||
|
||||
public static void setExperience(Player player, int amount) {
|
||||
float a = 0, b = 0, c = -amount;
|
||||
|
||||
if (amount > getExperience(0) && amount <= getExperience(15)) {
|
||||
a = 1;
|
||||
b = 6;
|
||||
} else if (amount > getExperience(15) && amount <= getExperience(30)) {
|
||||
a = 2.5f;
|
||||
b = -40.5f;
|
||||
c += 360;
|
||||
} else if (amount > getExperience(30)) {
|
||||
a = 4.5f;
|
||||
b = -162.5f;
|
||||
c += 2220;
|
||||
}
|
||||
|
||||
int level = (int) Math.floor((-b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a));
|
||||
int xp = amount - getExperience(level);
|
||||
|
||||
player.setLevel(level);
|
||||
player.setExp(0);
|
||||
player.giveExp(xp);
|
||||
}
|
||||
}
|
||||
238
src/main/java/tk/alex3025/headstones/utils/Headstone.java
Normal file
238
src/main/java/tk/alex3025/headstones/utils/Headstone.java
Normal file
@@ -0,0 +1,238 @@
|
||||
package tk.alex3025.headstones.utils;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Rotatable;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
public class Headstone {
|
||||
|
||||
private final String uuid;
|
||||
private final OfflinePlayer owner;
|
||||
private final Location location;
|
||||
private final long timestamp;
|
||||
private final int experience;
|
||||
private final ItemStack[] inventory;
|
||||
|
||||
public Headstone(@NotNull Player player) {
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
|
||||
this.owner = player;
|
||||
this.location = player.getLocation();
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
|
||||
this.experience = ExperienceManager.getExperience(player);
|
||||
this.inventory = player.getInventory().getContents();
|
||||
}
|
||||
|
||||
private Headstone(String uuid, OfflinePlayer player, Location location, long timestamp, int experience, ItemStack[] inventory) {
|
||||
this.uuid = uuid;
|
||||
|
||||
this.owner = player;
|
||||
this.location = location;
|
||||
this.timestamp = timestamp;
|
||||
|
||||
this.experience = experience;
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
public static @Nullable Headstone fromUUID(String uuid) {
|
||||
ConfigurationSection headstones = Headstone.getHeadstonesData().getConfigurationSection("headstones");
|
||||
if (headstones != null) {
|
||||
ConfigurationSection hs = headstones.getConfigurationSection(uuid);
|
||||
if (hs != null) {
|
||||
Location location = new Location(Bukkit.getWorld(hs.getString("world")), hs.getDouble("x"), hs.getDouble("y"), hs.getDouble("z"));
|
||||
|
||||
ItemStack[] inventory = null;
|
||||
if (hs.getString("inventory") != null)
|
||||
try {
|
||||
inventory = InventorySerializer.deserialize(hs.getString("inventory"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
OfflinePlayer owner = Bukkit.getOfflinePlayer(UUID.fromString(hs.getString("owner")));
|
||||
return new Headstone(uuid, owner, location, hs.getLong("timestamp"), hs.getInt("experience", 0), inventory);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable Headstone fromLocation(Location location) {
|
||||
ConfigurationSection headstones = Headstone.getHeadstonesData().getConfigurationSection("headstones");
|
||||
|
||||
if (headstones != null)
|
||||
for (String uuid : headstones.getKeys(false)) {
|
||||
Headstone hs = Headstone.fromUUID(uuid);
|
||||
if (hs != null)
|
||||
if (location.equals(hs.getLocation()))
|
||||
return hs;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable Headstone fromBlock(@NotNull Block block) {
|
||||
return block.getType().equals(Material.PLAYER_HEAD) ? Headstone.fromLocation(block.getLocation()) : null;
|
||||
}
|
||||
|
||||
public void onPlayerDeath(PlayerDeathEvent event, boolean keepExperience, boolean keepInventory) {
|
||||
Location skullLocation = this.createPlayerSkull();
|
||||
|
||||
if (skullLocation != null) {
|
||||
// Disable drops
|
||||
event.getDrops().clear();
|
||||
event.setShouldDropExperience(false);
|
||||
|
||||
this.savePlayerData(skullLocation, keepExperience, keepInventory);
|
||||
}
|
||||
}
|
||||
|
||||
public void onBreak(@NotNull BlockBreakEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
this.restorePlayerInventory(player);
|
||||
this.deletePlayerData();
|
||||
|
||||
event.setDropItems(Headstones.getInstance().getConfig().getBoolean("drop-player-head"));
|
||||
|
||||
player.spawnParticle(Particle.REDSTONE, this.location.add(0.5, 0.2, 0.5), 10, 0.2, 0.1, 0.2, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 1.5F));
|
||||
player.playSound(this.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 2.0F, 1.0F);
|
||||
|
||||
new Message(player).translation("headstone-broken").prefixed(false).send();
|
||||
}
|
||||
|
||||
private void savePlayerData(@NotNull Location skullLocation, boolean keepExperience, boolean keepInventory) {
|
||||
ConfigFile headstonesFile = Headstone.getHeadstonesData();
|
||||
ConfigurationSection hs = headstonesFile.createSection("headstones." + this.uuid);
|
||||
|
||||
hs.set("owner", this.owner.getUniqueId().toString());
|
||||
|
||||
hs.set("x", (int) Math.floor(skullLocation.getX()));
|
||||
hs.set("y", (int) Math.floor(skullLocation.getY()));
|
||||
hs.set("z", (int) Math.floor(skullLocation.getZ()));
|
||||
hs.set("world", skullLocation.getWorld().getName());
|
||||
|
||||
hs.set("timestamp", Instant.now().toEpochMilli());
|
||||
|
||||
if (keepExperience)
|
||||
hs.set("experience", this.experience);
|
||||
|
||||
if (keepInventory)
|
||||
hs.set("inventory", InventorySerializer.serialize(this.inventory));
|
||||
|
||||
headstonesFile.save();
|
||||
}
|
||||
|
||||
private void deletePlayerData() {
|
||||
ConfigFile headstonesFile = Headstone.getHeadstonesData();
|
||||
ConfigurationSection headstones = headstonesFile.getConfigurationSection("headstones");
|
||||
|
||||
if (headstones != null)
|
||||
headstones.set(this.uuid, null);
|
||||
|
||||
headstonesFile.save();
|
||||
}
|
||||
|
||||
private void restorePlayerInventory(Player player) {
|
||||
ExperienceManager.setExperience(player, this.experience);
|
||||
|
||||
if (this.inventory != null)
|
||||
for (int i = 0, size = this.inventory.length; i < size; i++)
|
||||
if (this.inventory[i] != null) {
|
||||
PlayerInventory playerInventory = player.getInventory();
|
||||
if (playerInventory.getItem(i) == null)
|
||||
playerInventory.setItem(i, this.inventory[i]);
|
||||
else {
|
||||
HashMap<Integer, ItemStack> drops = playerInventory.addItem(this.inventory[i]);
|
||||
for (ItemStack item : drops.values())
|
||||
player.getWorld().dropItem(this.location, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Block checkForSafeBlock() {
|
||||
int playerX = this.location.getBlockX();
|
||||
int playerY = this.location.getBlockY();
|
||||
int playerZ = this.location.getBlockZ();
|
||||
|
||||
int radius = 0;
|
||||
|
||||
for (int x = playerX - radius; x <= playerX + radius; x++) {
|
||||
for (int y = playerY - radius; y <= playerY + radius; y++)
|
||||
for (int z = playerZ - radius; z <= playerZ + radius; z++) {
|
||||
Block block = this.location.getWorld().getBlockAt(x,y,z);
|
||||
if (block.getType().isEmpty())
|
||||
return block;
|
||||
}
|
||||
|
||||
if (radius <= 5)
|
||||
radius++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Location createPlayerSkull() {
|
||||
Block block = this.checkForSafeBlock();
|
||||
|
||||
if (block != null) {
|
||||
block.setType(Material.PLAYER_HEAD);
|
||||
|
||||
if (block.getState() instanceof Skull skull) {
|
||||
skull.setOwningPlayer(this.owner);
|
||||
|
||||
BlockData data = skull.getBlockData();
|
||||
|
||||
List<BlockFace> faces = new ArrayList<>(List.of(BlockFace.values()));
|
||||
// Remove invalid faces
|
||||
faces.remove(BlockFace.UP);
|
||||
faces.remove(BlockFace.DOWN);
|
||||
faces.remove(BlockFace.SELF);
|
||||
|
||||
((Rotatable) data).setRotation(faces.get(new Random().nextInt(faces.size())));
|
||||
|
||||
skull.setBlockData(data);
|
||||
skull.update();
|
||||
|
||||
return block.getLocation();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isOwner(@NotNull Player player) {
|
||||
return player.getUniqueId().equals(this.owner.getUniqueId());
|
||||
}
|
||||
|
||||
public OfflinePlayer getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
private static ConfigFile getHeadstonesData() {
|
||||
return Headstones.getInstance().getDatabase();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package tk.alex3025.headstones.utils;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class InventorySerializer {
|
||||
|
||||
public static @NotNull String serialize(ItemStack[] inventoryContents) throws IllegalStateException {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
|
||||
dataOutput.writeInt(inventoryContents.length);
|
||||
|
||||
for (ItemStack item : inventoryContents)
|
||||
if (item != null)
|
||||
dataOutput.writeObject(item.serializeAsBytes());
|
||||
else
|
||||
dataOutput.writeObject(null);
|
||||
|
||||
dataOutput.close();
|
||||
return Base64Coder.encodeLines(outputStream.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to save item stacks.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack @NotNull [] deserialize(String serializedInventory) throws IOException {
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(serializedInventory));
|
||||
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
|
||||
|
||||
ItemStack[] items = new ItemStack[dataInput.readInt()];
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
byte[] stack = (byte[]) dataInput.readObject();
|
||||
|
||||
if (stack != null)
|
||||
items[i] = ItemStack.deserializeBytes(stack);
|
||||
else
|
||||
items[i] = null;
|
||||
}
|
||||
|
||||
dataInput.close();
|
||||
return items;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IOException("Unable to decode class type.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
68
src/main/java/tk/alex3025/headstones/utils/Message.java
Normal file
68
src/main/java/tk/alex3025/headstones/utils/Message.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package tk.alex3025.headstones.utils;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tk.alex3025.headstones.Headstones;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Message {
|
||||
|
||||
private String rawMessage;
|
||||
private final CommandSender sender;
|
||||
private Map<String, String> placeholders;
|
||||
|
||||
private boolean prefixed = true;
|
||||
|
||||
public Message(CommandSender sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
public Message(CommandSender sender, Map<String, String> placeholders) {
|
||||
this.sender = sender;
|
||||
this.placeholders = placeholders;
|
||||
}
|
||||
|
||||
public Message text(String message) {
|
||||
this.rawMessage = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message translation(String key) {
|
||||
return this.text(Headstones.getInstance().getMessages().getString(key));
|
||||
}
|
||||
|
||||
public Message prefixed(boolean prefixed) {
|
||||
this.prefixed = prefixed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void send() {
|
||||
if (this.rawMessage != null && !this.rawMessage.isEmpty()) {
|
||||
// Format placeholders
|
||||
if (this.placeholders != null)
|
||||
for (Map.Entry<String, String> entry : this.placeholders.entrySet())
|
||||
this.rawMessage = this.rawMessage.replace("%" + entry.getKey() + "%", entry.getValue());
|
||||
|
||||
if (this.prefixed)
|
||||
Message.sendPrefixedMessage(this.sender, this.rawMessage);
|
||||
else
|
||||
Message.sendMessage(this.sender, this.rawMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendMessage(@NotNull CommandSender sender, String message) {
|
||||
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
|
||||
}
|
||||
|
||||
public static void sendPrefixedMessage(CommandSender sender, String message) {
|
||||
String prefix = Headstones.getInstance().getMessages().getString("prefix");
|
||||
Message.sendMessage(sender, prefix + " " + message);
|
||||
}
|
||||
|
||||
public static String getTranslation(String key) {
|
||||
return Headstones.getInstance().getMessages().getString(key);
|
||||
}
|
||||
|
||||
}
|
||||
12
src/main/resources/config.yml
Normal file
12
src/main/resources/config.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# ===================================== #
|
||||
# #
|
||||
# Headstones v${project.version} - by alex3025 #
|
||||
# PLUGIN CONFIGURATION #
|
||||
# #
|
||||
# ===================================== #
|
||||
|
||||
# The player head will be dropped on break?
|
||||
drop-player-head: false
|
||||
|
||||
# The date format to use in the headstone info message.
|
||||
date-format: 'dd/MM/yyyy HH:mm'
|
||||
6
src/main/resources/database.yml
Normal file
6
src/main/resources/database.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
# -------------------------- #
|
||||
# Headstones Database File #
|
||||
# /!\ DO NOT EDIT /!\ #
|
||||
# -------------------------- #
|
||||
|
||||
headstones: {}
|
||||
25
src/main/resources/messages.yml
Normal file
25
src/main/resources/messages.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
# ===================================== #
|
||||
# #
|
||||
# Headstones v${project.version} - by alex3025 #
|
||||
# MESSAGES CUSTOMIZATION #
|
||||
# #
|
||||
# ===================================== #
|
||||
|
||||
prefix: '&8[&bHeadstones&8]'
|
||||
|
||||
# You can set any message to an empty string to disable it.
|
||||
|
||||
# These messages won't have the prefix.
|
||||
no-permissions: '&cYou don''t have the permission to use this command!'
|
||||
cannot-break-others: '&cYou cannot break other players'' headstones!'
|
||||
|
||||
headstone-broken: '&aYou broke your headstone and got all your items back!'
|
||||
|
||||
# These messages will have the prefix.
|
||||
unknown-subcommand: '&cUnknown subcommand!'
|
||||
player-only: '&cThis command can be executed by players only!'
|
||||
headstone-info: '&f%username%&7 died here on &f%datetime%&7'
|
||||
|
||||
# These messages will appear on the action bar.
|
||||
inventory-full: '&cYour inventory is full!'
|
||||
some-items-dropped: '&cSome items were dropped on the ground!'
|
||||
13
src/main/resources/plugin.yml
Normal file
13
src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Headstones
|
||||
description: Don't lose your precious items when you die!
|
||||
authors: [ alex3025 ]
|
||||
website: https://alex3025.tk
|
||||
|
||||
version: '${project.version}'
|
||||
api-version: 1.18
|
||||
main: tk.alex3025.headstones.Headstones
|
||||
|
||||
commands:
|
||||
headstones:
|
||||
aliases: [ hs, headstone ]
|
||||
usage: /headstones
|
||||
Reference in New Issue
Block a user