diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1af7e87
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,127 @@
+
+
+ 4.0.0
+
+ space.devport.wertik.firstdeathrewards
+ FirstDeathRewards
+ 1.0.0
+
+ FirstDeathRewards
+
+
+ clean install
+ ${project.name}-${project.version}
+
+
+ true
+ src/main/resources/
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+
+ 8
+ 8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.1
+
+ true
+
+
+ space.devport.utils
+ ${project.groupId}.utils
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+ devport
+ http://play.pvpcraft.cz:8081/repository/devport/
+
+
+ placeholderapi
+ http://repo.extendedclip.com/content/repositories/placeholderapi/
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.16.1-R0.1-SNAPSHOT
+ provided
+
+
+ org.jetbrains
+ annotations
+ 15.0
+ compile
+
+
+ org.projectlombok
+ lombok
+ 1.18.10
+ provided
+
+
+ space.devport.utils
+ DevportUtils
+ LATEST
+ compile
+
+
+ com.github.MilkBowl
+ VaultAPI
+ 1.7
+ provided
+
+
+ me.realized.tokenmanager
+ TokenManager
+ 3.2.5
+ provided
+
+
+ me.clip
+ placeholderapi
+ 2.10.6
+ provided
+
+
+ com.google.code.gson
+ gson
+ 2.8.5
+
+
+
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/FirstDeathLanguage.java b/src/main/java/space/devport/wertik/firstdeathrewards/FirstDeathLanguage.java
new file mode 100644
index 0000000..af0502f
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/FirstDeathLanguage.java
@@ -0,0 +1,19 @@
+package space.devport.wertik.firstdeathrewards;
+
+import space.devport.utils.text.language.LanguageDefaults;
+
+public class FirstDeathLanguage extends LanguageDefaults {
+
+ @Override
+ public void setDefaults() {
+ addDefault("Commands.Invalid-Player", "&cPlayer &f%param% &cdoes not exist.");
+
+ addDefault("Commands.Reset.Done", "&7Reset for &f%player% &7successful.");
+ addDefault("Commands.Reset.Done-All", "&7Reset for all players successful.");
+
+ addDefault("Commands.Info.Message", "&8&m &7 First Death of &f%player%", "&7Available: %available%");
+ addDefault("Commands.Info.You", "&6You");
+ addDefault("Commands.Info.Available", "&ayes &7(means he has not died yet)");
+ addDefault("Commands.Info.Not-Available", "&cno &7(means he's lame and died already)");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/FirstDeathPlugin.java b/src/main/java/space/devport/wertik/firstdeathrewards/FirstDeathPlugin.java
new file mode 100644
index 0000000..6cb09ae
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/FirstDeathPlugin.java
@@ -0,0 +1,70 @@
+package space.devport.wertik.firstdeathrewards;
+
+import lombok.Getter;
+import space.devport.utils.DevportPlugin;
+import space.devport.wertik.firstdeathrewards.commands.FirstDeathCommand;
+import space.devport.wertik.firstdeathrewards.commands.subcommands.InfoSubCommand;
+import space.devport.wertik.firstdeathrewards.commands.subcommands.ReloadSubCommand;
+import space.devport.wertik.firstdeathrewards.commands.subcommands.ResetSubCommand;
+import space.devport.wertik.firstdeathrewards.listeners.PlayerListener;
+import space.devport.wertik.firstdeathrewards.system.DataManager;
+
+public class FirstDeathPlugin extends DevportPlugin {
+
+ @Getter
+ private static FirstDeathPlugin instance;
+
+ @Getter
+ private DataManager dataManager;
+
+ @Override
+ public void onPluginEnable() {
+ instance = this;
+
+ consoleOutput.setColors(true);
+
+ new FirstDeathLanguage();
+
+ dataManager = new DataManager();
+ dataManager.loadData();
+
+ dataManager.load();
+
+ dataManager.createSaveTask();
+ if (getConfig().getBoolean("auto-save.enabled", false))
+ dataManager.getAutoSave().start();
+
+ getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
+
+ addMainCommand(new FirstDeathCommand()
+ .addSubCommand(new InfoSubCommand())
+ .addSubCommand(new ResetSubCommand())
+ .addSubCommand(new ReloadSubCommand()));
+ }
+
+ @Override
+ public void onPluginDisable() {
+ dataManager.save();
+ }
+
+ @Override
+ public void onReload() {
+ dataManager.loadData();
+ dataManager.reloadAutoSave();
+ }
+
+ @Override
+ public boolean useLanguage() {
+ return true;
+ }
+
+ @Override
+ public boolean useHolograms() {
+ return false;
+ }
+
+ @Override
+ public boolean useMenus() {
+ return false;
+ }
+}
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/commands/CommandUtils.java b/src/main/java/space/devport/wertik/firstdeathrewards/commands/CommandUtils.java
new file mode 100644
index 0000000..2ecfd02
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/commands/CommandUtils.java
@@ -0,0 +1,23 @@
+package space.devport.wertik.firstdeathrewards.commands;
+
+import lombok.experimental.UtilityClass;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+
+@UtilityClass
+public class CommandUtils {
+
+ public OfflinePlayer getOfflineTarget(CommandSender sender, String name) {
+ OfflinePlayer offlinePlayer = Bukkit.getPlayer(name);
+
+ if (offlinePlayer == null) {
+ FirstDeathPlugin.getInstance().getLanguageManager().getPrefixed("Commands.Invalid-Player")
+ .replace("%param%", name)
+ .send(sender);
+ return null;
+ }
+ return offlinePlayer;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/commands/FirstDeathCommand.java b/src/main/java/space/devport/wertik/firstdeathrewards/commands/FirstDeathCommand.java
new file mode 100644
index 0000000..aeed6bd
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/commands/FirstDeathCommand.java
@@ -0,0 +1,27 @@
+package space.devport.wertik.firstdeathrewards.commands;
+
+import org.bukkit.command.CommandSender;
+import space.devport.utils.commands.MainCommand;
+import space.devport.utils.commands.struct.CommandResult;
+
+public class FirstDeathCommand extends MainCommand {
+
+ public FirstDeathCommand() {
+ super("firstdeathrewards");
+ }
+
+ @Override
+ protected CommandResult perform(CommandSender sender, String label, String[] args) {
+ return CommandResult.SUCCESS;
+ }
+
+ @Override
+ public String getDefaultUsage() {
+ return "/%label%";
+ }
+
+ @Override
+ public String getDefaultDescription() {
+ return "Displays this.";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/InfoSubCommand.java b/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/InfoSubCommand.java
new file mode 100644
index 0000000..16b1e2e
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/InfoSubCommand.java
@@ -0,0 +1,61 @@
+package space.devport.wertik.firstdeathrewards.commands.subcommands;
+
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import space.devport.utils.commands.SubCommand;
+import space.devport.utils.commands.struct.ArgumentRange;
+import space.devport.utils.commands.struct.CommandResult;
+import space.devport.utils.commands.struct.Preconditions;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+import space.devport.wertik.firstdeathrewards.commands.CommandUtils;
+
+public class InfoSubCommand extends SubCommand {
+
+ public InfoSubCommand() {
+ super("info");
+ this.preconditions = new Preconditions()
+ .permissions("firstdeathrewards.info");
+ }
+
+ @Override
+ protected CommandResult perform(CommandSender sender, String label, String[] args) {
+
+ boolean me = false;
+
+ OfflinePlayer target;
+ if (args.length > 0) {
+ target = CommandUtils.getOfflineTarget(sender, args[0]);
+
+ if (target == null) return CommandResult.FAILURE;
+ } else {
+ if (!(sender instanceof Player)) return CommandResult.NO_CONSOLE;
+
+ target = (Player) sender;
+ me = true;
+ }
+
+ boolean dead = FirstDeathPlugin.getInstance().getDataManager().hasFirstDeath(target);
+ language.get("Commands.Info.Message")
+ .replace("%player%", me ? language.get("Commands.Info.You").color().toString() : target.getName())
+ .replace("%available%", dead ? language.get("Commands.Info.Not-Available").color().toString() : language.get("Commands.Info.Available").color().toString())
+ .send(sender);
+ return CommandResult.SUCCESS;
+ }
+
+ @Override
+ public @NotNull String getDefaultUsage() {
+ return "/%label% info (player)";
+ }
+
+ @Override
+ public @NotNull String getDefaultDescription() {
+ return "Get info about you or target.";
+ }
+
+ @Override
+ public @NotNull ArgumentRange getRange() {
+ return new ArgumentRange(0, 1);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/ReloadSubCommand.java b/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/ReloadSubCommand.java
new file mode 100644
index 0000000..6e5a71f
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/ReloadSubCommand.java
@@ -0,0 +1,39 @@
+package space.devport.wertik.firstdeathrewards.commands.subcommands;
+
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+import space.devport.utils.commands.SubCommand;
+import space.devport.utils.commands.struct.ArgumentRange;
+import space.devport.utils.commands.struct.CommandResult;
+import space.devport.utils.commands.struct.Preconditions;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+
+public class ReloadSubCommand extends SubCommand {
+
+ public ReloadSubCommand() {
+ super("reload");
+ this.preconditions = new Preconditions()
+ .permissions("firstdeathrewards.reload");
+ }
+
+ @Override
+ protected CommandResult perform(CommandSender sender, String label, String[] args) {
+ FirstDeathPlugin.getInstance().reload(sender);
+ return CommandResult.SUCCESS;
+ }
+
+ @Override
+ public @NotNull String getDefaultUsage() {
+ return "/%label% reload";
+ }
+
+ @Override
+ public @NotNull String getDefaultDescription() {
+ return "Reloads the configuration.";
+ }
+
+ @Override
+ public @NotNull ArgumentRange getRange() {
+ return new ArgumentRange(0);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/ResetSubCommand.java b/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/ResetSubCommand.java
new file mode 100644
index 0000000..d96eff3
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/commands/subcommands/ResetSubCommand.java
@@ -0,0 +1,59 @@
+package space.devport.wertik.firstdeathrewards.commands.subcommands;
+
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import space.devport.utils.commands.SubCommand;
+import space.devport.utils.commands.struct.ArgumentRange;
+import space.devport.utils.commands.struct.CommandResult;
+import space.devport.utils.commands.struct.Preconditions;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+import space.devport.wertik.firstdeathrewards.commands.CommandUtils;
+
+public class ResetSubCommand extends SubCommand {
+
+ public ResetSubCommand() {
+ super("reset");
+ this.preconditions = new Preconditions()
+ .permissions("firstdeathrewards.reset");
+ }
+
+ @Override
+ protected CommandResult perform(CommandSender sender, String label, String[] args) {
+
+ OfflinePlayer target = null;
+ if (args.length > 0) {
+ if (!args[0].equalsIgnoreCase("all")) {
+ target = CommandUtils.getOfflineTarget(sender, args[0]);
+
+ if (target == null) return CommandResult.FAILURE;
+ }
+ } else {
+ if (!(sender instanceof Player)) return CommandResult.NO_CONSOLE;
+
+ target = (Player) sender;
+ }
+
+ FirstDeathPlugin.getInstance().getDataManager().reset(target);
+ language.getPrefixed(target == null ? "Commands.Reset.Done-All" : "Commands.Reset.Done")
+ .replace("%player%", target == null ? "all" : target.getName())
+ .send(sender);
+ return CommandResult.SUCCESS;
+ }
+
+ @Override
+ public @NotNull String getDefaultUsage() {
+ return "/%label% reset (player)";
+ }
+
+ @Override
+ public @NotNull String getDefaultDescription() {
+ return "Reset all or players first death.";
+ }
+
+ @Override
+ public @NotNull ArgumentRange getRange() {
+ return new ArgumentRange(0, 1);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/listeners/PlayerListener.java b/src/main/java/space/devport/wertik/firstdeathrewards/listeners/PlayerListener.java
new file mode 100644
index 0000000..1f97d61
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/listeners/PlayerListener.java
@@ -0,0 +1,30 @@
+package space.devport.wertik.firstdeathrewards.listeners;
+
+import lombok.RequiredArgsConstructor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+
+@RequiredArgsConstructor
+public class PlayerListener implements Listener {
+
+ private final FirstDeathPlugin plugin;
+
+ @EventHandler
+ public void onDeath(PlayerDeathEvent event) {
+ Player player = event.getEntity();
+
+ if (plugin.getConfig().getStringList("disabled-worlds").contains(player.getWorld().getName()) ||
+ plugin.getDataManager().hasFirstDeath(player))
+ return;
+
+ plugin.getDataManager().setFirstDeath(player);
+
+ event.setKeepInventory(plugin.getConfig().getBoolean("keep-inventory", false));
+ event.setKeepLevel(plugin.getConfig().getBoolean("keep-exp", false));
+
+ plugin.getDataManager().getReward().give(player);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/system/AutoSaveTask.java b/src/main/java/space/devport/wertik/firstdeathrewards/system/AutoSaveTask.java
new file mode 100644
index 0000000..fcfc4fc
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/system/AutoSaveTask.java
@@ -0,0 +1,43 @@
+package space.devport.wertik.firstdeathrewards.system;
+
+import lombok.RequiredArgsConstructor;
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+
+@RequiredArgsConstructor
+public class AutoSaveTask implements Runnable {
+
+ private final FirstDeathPlugin plugin;
+
+ // in ticks
+ private int interval;
+
+ private boolean running = false;
+
+ private BukkitTask task;
+
+ public void load() {
+ this.interval = plugin.getConfig().getInt("auto-save.interval", 300) * 20;
+ }
+
+ public void start() {
+ if (running) stop();
+
+ running = true;
+ task = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this, interval, interval);
+ }
+
+ public void stop() {
+ if (!running) return;
+
+ running = false;
+ task.cancel();
+ task = null;
+ }
+
+ @Override
+ public void run() {
+ plugin.getDataManager().save();
+ }
+}
diff --git a/src/main/java/space/devport/wertik/firstdeathrewards/system/DataManager.java b/src/main/java/space/devport/wertik/firstdeathrewards/system/DataManager.java
new file mode 100644
index 0000000..f0cd60c
--- /dev/null
+++ b/src/main/java/space/devport/wertik/firstdeathrewards/system/DataManager.java
@@ -0,0 +1,119 @@
+package space.devport.wertik.firstdeathrewards.system;
+
+import com.google.common.base.Strings;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import lombok.Getter;
+import org.bukkit.OfflinePlayer;
+import space.devport.utils.struct.Rewards;
+import space.devport.wertik.firstdeathrewards.FirstDeathPlugin;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+public class DataManager {
+
+ private final FirstDeathPlugin plugin;
+
+ private final Set cache = new HashSet<>();
+
+ private final Gson gson = new GsonBuilder()
+ // .setPrettyPrinting()
+ .create();
+
+ @Getter
+ private Rewards reward;
+
+ @Getter
+ private AutoSaveTask autoSave;
+
+ public DataManager() {
+ this.plugin = FirstDeathPlugin.getInstance();
+ }
+
+ public void setFirstDeath(OfflinePlayer player) {
+ cache.add(player.getUniqueId());
+ }
+
+ public void reset(OfflinePlayer... player) {
+ if (player.length > 0)
+ cache.remove(player[0].getUniqueId());
+ else cache.clear();
+ }
+
+ public boolean hasFirstDeath(OfflinePlayer player) {
+ return cache.contains(player.getUniqueId());
+ }
+
+ public Set getCache() {
+ return Collections.unmodifiableSet(cache);
+ }
+
+ public void loadData() {
+ reward = plugin.getConfiguration().getRewards("rewards");
+ }
+
+ public void createSaveTask() {
+ autoSave = new AutoSaveTask(plugin);
+ autoSave.load();
+ }
+
+ public void reloadAutoSave() {
+ autoSave.stop();
+
+ if (plugin.getConfig().getBoolean("auto-save.enabled", false)) {
+ autoSave.load();
+ autoSave.start();
+ }
+ }
+
+ public void save() {
+
+ final Set finalCache = new HashSet<>(cache);
+
+ plugin.getConsoleOutput().debug("Saving " + cache.size() + " first death records..");
+
+ String output = gson.toJson(finalCache, new TypeToken>() {
+ }.getType());
+
+ plugin.getConsoleOutput().debug("JSON: " + output);
+
+ Path path = Paths.get(plugin.getDataFolder().getPath() + "/Data.json");
+
+ try {
+ Files.write(path, output.getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void load() {
+ cache.clear();
+
+ Path path = Paths.get(plugin.getDataFolder().getPath() + "/Data.json");
+
+ if (!Files.exists(path)) return;
+
+ String input;
+ try {
+ input = String.join("", Files.readAllLines(path));
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ if (Strings.isNullOrEmpty(input)) return;
+
+ cache.addAll(gson.fromJson(input, new TypeToken>() {
+ }.getType()));
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..99ce42f
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,28 @@
+# Configuration for ${project.name}
+#
+# Version: $[project.version}
+
+plugin-prefix: '&cFirst Death &8>> '
+debug-enabled: true
+
+# Automatic saving
+auto-save:
+ enabled: true
+ # in seconds
+ interval: 300
+
+# Where the plugin won't work.
+disabled-worlds:
+ - event_shit_world
+
+# Keep inventory and exp on fist death?
+keep-inventory: true
+keep-exp: true
+
+# Rewards given to player upon death
+rewards:
+ inform:
+ - "&7You died. You're incompetent. Basically useless when the only purpose was to &2&osurvive&7... "
+ - "&7But we'll give you one more chance... just so you know we're the &e&onice guys &7here."
+ - "&7Now live and &c&odon't die &7again you &e&odipshit."
+ # commands, inform, broadcast, items, tokens, money
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..8d2341b
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+name: ${project.name}
+version: ${project.version}
+main: ${project.groupId}.FirstDeathPlugin
+softdepend: [Vault, TokenManager, PlaceholderAPI]
+authors: [Wertik1206]
+commands:
+ firstdeathrewards:
+ description: Main plugin command
+ aliases: [fd, fdr, firstdeath]
\ No newline at end of file