From c9697b3588dfbbb9328344c6bee6fd703ddaf766 Mon Sep 17 00:00:00 2001 From: LlmDl Date: Wed, 14 Feb 2024 12:22:44 -0600 Subject: [PATCH] Adds tempflight command. (#75) * Adds tempflight command. Closes https://github.com/TownyAdvanced/Towny/issues/7252 TODO: add feedback messages, test Placeholder, test saving every 10 cycles, test logging out storing remaining flight, logging in restoring it. * Clean up todo requirements and fix bugs found along the way. --- pom.xml | 2 +- .../llmdlio/townyflight/TownyFlight.java | 26 +++++ .../llmdlio/townyflight/TownyFlightAPI.java | 29 ++--- .../townyflight/TownyFlightCommand.java | 86 ++++++++++++++- .../townyflight/config/ConfigNodes.java | 21 ++++ .../llmdlio/townyflight/config/Settings.java | 4 + .../TownyFlightPlaceholderExpansion.java | 7 ++ .../listeners/PlayerJoinListener.java | 6 + .../listeners/PlayerLogOutListener.java | 2 + .../townyflight/tasks/TaskHandler.java | 26 +++++ .../townyflight/tasks/TempFlightTask.java | 104 ++++++++++++++++++ .../llmdlio/townyflight/util/MetaData.java | 46 ++++++++ src/main/resources/plugin.yml | 3 + 13 files changed, 346 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/gmail/llmdlio/townyflight/tasks/TaskHandler.java create mode 100644 src/main/java/com/gmail/llmdlio/townyflight/tasks/TempFlightTask.java diff --git a/pom.xml b/pom.xml index 33ad431..6760898 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.gmail.llmdlio TownyFlight - 1.9.0 + 1.10.0 TownyFlight A flight plugin for Towny servers. diff --git a/src/main/java/com/gmail/llmdlio/townyflight/TownyFlight.java b/src/main/java/com/gmail/llmdlio/townyflight/TownyFlight.java index e2919c6..71de20d 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/TownyFlight.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/TownyFlight.java @@ -3,6 +3,9 @@ import com.palmergames.bukkit.towny.scheduling.TaskScheduler; import com.palmergames.bukkit.towny.scheduling.impl.BukkitTaskScheduler; import com.palmergames.bukkit.towny.scheduling.impl.FoliaTaskScheduler; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; @@ -20,6 +23,9 @@ import com.gmail.llmdlio.townyflight.listeners.PlayerTeleportListener; import com.gmail.llmdlio.townyflight.listeners.TownStatusScreenListener; import com.gmail.llmdlio.townyflight.listeners.TownUnclaimListener; +import com.gmail.llmdlio.townyflight.tasks.TaskHandler; +import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; +import com.gmail.llmdlio.townyflight.util.MetaData; import com.palmergames.bukkit.util.Version; public class TownyFlight extends JavaPlugin { @@ -57,6 +63,9 @@ public void onEnable() { registerCommands(); getLogger().info("Towny version " + townyVersion + " found."); getLogger().info(this.getDescription().getFullName() + " by LlmDl Enabled."); + + cycleTimerTasksOn(); + reGrantTempFlightToOnlinePlayer(); } public static TownyFlight getPlugin() { @@ -127,6 +136,23 @@ private void registerCommands() { getCommand("tfly").setExecutor(new TownyFlightCommand(this)); } + private void cycleTimerTasksOn() { + cycleTimerTasksOff(); + TaskHandler.toggleTempFlightTask(true); + } + + private void cycleTimerTasksOff() { + TaskHandler.toggleTempFlightTask(false); + } + + private void reGrantTempFlightToOnlinePlayer() { + for (Player player : Bukkit.getOnlinePlayers()) { + long seconds = MetaData.getSeconds(player.getUniqueId()); + if (seconds > 0L) + TempFlightTask.addPlayerTempFlightSeconds(player.getUniqueId(), seconds); + } + } + public TaskScheduler getScheduler() { return this.scheduler; } diff --git a/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightAPI.java b/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightAPI.java index 4340f67..32c153e 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightAPI.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightAPI.java @@ -15,6 +15,7 @@ import com.gmail.goosius.siegewar.SiegeController; import com.gmail.goosius.siegewar.enums.SiegeStatus; import com.gmail.llmdlio.townyflight.config.Settings; +import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; import com.gmail.llmdlio.townyflight.util.Message; import com.gmail.llmdlio.townyflight.util.MetaData; import com.gmail.llmdlio.townyflight.util.Permission; @@ -52,24 +53,17 @@ public static TownyFlightAPI getInstance() { * @return true if the {@link Player} is allowed to fly. **/ public boolean canFly(Player player, boolean silent) { - Town town = TownyAPI.getInstance().getTown(player.getLocation()); if (player.hasPermission("townyflight.bypass") || player.getGameMode().equals(GameMode.SPECTATOR) || player.getGameMode().equals(GameMode.CREATIVE) - || town != null && MetaData.getFreeFlightMeta(town) || getForceAllowFlight(player)) return true; - if (!Permission.has(player, "townyflight.command.tfly", silent)) return false; + if (!hasTempFlight(player) && !Permission.has(player, "townyflight.command.tfly", silent)) return false; Resident resident = TownyUniverse.getInstance().getResident(player.getUniqueId()); if (resident == null) return false; - if (!resident.hasTown() && !locationTrustsResidents(town, resident)) { - if (!silent) Message.of("noTownMsg").to(player); - return false; - } - if (warPrevents(player.getLocation(), resident)) { if (!silent) Message.of("notDuringWar").to(player); return false; @@ -82,11 +76,6 @@ public boolean canFly(Player player, boolean silent) { return true; } - private boolean locationTrustsResidents(Town town, Resident resident) { - return town != null && town.hasTrustedResident(resident); - } - - /** * Returns true if a player is allowed to fly at their current location. Checks * if they are in the wilderness, in their own town and if not, whether they @@ -104,13 +93,20 @@ public static boolean allowedLocation(Player player, Location location, Resident if (TownyAPI.getInstance().isWilderness(location)) return false; + Town town = TownyAPI.getInstance().getTown(location); + if (player.hasPermission("townyflight.alltowns")) return true; - Town town = TownyAPI.getInstance().getTown(location); if (player.hasPermission("townyflight.trustedtowns") && town.hasTrustedResident(resident)) return true; + if (MetaData.getFreeFlightMeta(town)) + return true; + + if (!resident.hasTown()) + return false; + Town residentTown = resident.getTownOrNull(); if (residentTown == null) return false; @@ -142,6 +138,7 @@ public void removeFlight(Player player, boolean silent, boolean forced, String c String reason = Message.getLangString("flightDeactivatedMsg"); if (cause == "pvp") reason = Message.getLangString("flightDeactivatedPVPMsg"); if (cause == "console") reason = Message.getLangString("flightDeactivatedConsoleMsg"); + if (cause == "time") reason = Message.getLangString("flightDeactivatedTimeMsg"); Message.of(reason + Message.getLangString("flightOffMsg")).to(player); } else { Message.of("flightOffMsg").to(player); @@ -233,6 +230,10 @@ public boolean getForceAllowFlight(Player player) { return player.getPersistentDataContainer().getOrDefault(forceAllowFlight, PersistentDataType.BYTE, (byte) 0) == 1; } + private boolean hasTempFlight(Player player) { + return TempFlightTask.getSeconds(player.getUniqueId()) > 0L; + } + private boolean warPrevents(Location location, Resident resident) { return Settings.disableDuringWar && (townHasActiveWar(location, resident) || residentIsSieged(location)); } diff --git a/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightCommand.java b/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightCommand.java index 3ff3579..e1295d6 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightCommand.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/TownyFlightCommand.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -12,22 +13,27 @@ import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; +import com.gmail.llmdlio.townyflight.config.Settings; +import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; import com.gmail.llmdlio.townyflight.util.Message; import com.gmail.llmdlio.townyflight.util.MetaData; import com.gmail.llmdlio.townyflight.util.Permission; import com.palmergames.bukkit.towny.TownyAPI; import com.palmergames.bukkit.towny.TownyUniverse; +import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.Town; import com.palmergames.bukkit.towny.utils.NameUtil; import com.palmergames.bukkit.util.Colors; import com.palmergames.util.StringMgmt; +import com.palmergames.util.TimeMgmt; +import com.palmergames.util.TimeTools; public class TownyFlightCommand implements TabExecutor { private TownyFlight plugin; private CommandSender sender; private static final List tflyTabCompletes = Arrays.asList( - "reload","town","help","?" + "reload","tempflight","town","help","?" ); public TownyFlightCommand(TownyFlight plugin) { @@ -38,6 +44,11 @@ public TownyFlightCommand(TownyFlight plugin) { public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { if (args.length > 0) { switch (args[0].toLowerCase()) { + case "tempflight": + if (args.length == 2) + return getTownyStartingWith(args[1], "r"); + if (args.length == 3) + return NameUtil.filterByStart(Arrays.asList("remove", "10", "1000s", "60m", "1h", "1d"), args[2]); case "town": if (args.length == 2) return getTownyStartingWith(args[1], "t"); @@ -69,6 +80,9 @@ private void parseCommand(String[] args) { } else if (args[0].equalsIgnoreCase("reload")) { // Reload the plugin. reloadPlugin(); + } else if (args[0].equalsIgnoreCase("tempflight")) { + // We have /tfly tempflight and tested for permission node. + parseTempFlightCommand(StringMgmt.remFirstArg(args)); } else if (args[0].equalsIgnoreCase("town")) { // parse /tfly town NAME OPTION command. parseTownCommand(StringMgmt.remFirstArg(args)); @@ -88,6 +102,9 @@ private void parseCommand(String[] args) { } else if (args[0].equalsIgnoreCase("reload") && Permission.has(sender,"townyflight.command.tfly.reload", false)) { // We have /tfly reload and tested for permission node. reloadPlugin(); + } else if (args[0].equalsIgnoreCase("tempflight") && Permission.has(sender,"townyflight.command.tfly.tempflight", false)) { + // We have /tfly tempflight and tested for permission node. + parseTempFlightCommand(StringMgmt.remFirstArg(args)); } else if (args[0].equalsIgnoreCase("town") && Permission.has(sender,"townyflight.command.tfly.town", false)) { // parse /tfly town NAME OPTION command. parseTownCommand(StringMgmt.remFirstArg(args)); @@ -107,6 +124,66 @@ private void parseCommand(String[] args) { } } + private void parseTempFlightCommand(String[] args) { + if (args.length < 2) { + showTflyHelp(); + return; + } + + String name = args[0]; + Player player = Bukkit.getPlayerExact(name); + UUID uuid = player != null ? player.getUniqueId() : null; + if (uuid == null && TownyUniverse.getInstance().hasResident(name)) { + Resident resident = TownyAPI.getInstance().getResident(name); + if (resident != null && resident.hasUUID()) + uuid = resident.getUUID(); + } + + if (uuid == null) { + Message.of("Player " + name + " not found. Could not grant temp flight.").to(sender); + return; + } + + if (args[1].equalsIgnoreCase("remove")) { + TempFlightTask.removeAllPlayerTempFlightSeconds(uuid); + Message.of(name + " has had their flight time set to 0.").to(sender); + return; + } + + long seconds = parseSeconds(args[1]); + if (seconds == 0L) { + Message.of("Could not grant 0 seconds of temp flight.").to(sender); + return; + } + + String formattedTimeValue = TimeMgmt.getFormattedTimeValue(seconds * 1000L); + Message.of(String.format(Message.getLangString("tempFlightGrantedToPlayer"), name, formattedTimeValue)).to(sender); + MetaData.addTempFlight(uuid, seconds); + + if (player != null && player.isOnline()) { + TempFlightTask.addPlayerTempFlightSeconds(uuid, seconds); + Message.of(String.format(Message.getLangString("youHaveReceivedTempFlight"), formattedTimeValue)).to(player); + + if (Settings.autoEnableFlight && TownyFlightAPI.getInstance().canFly(player, true)) + TownyFlightAPI.getInstance().addFlight(player, Settings.autoEnableSilent); + } + + } + + private long parseSeconds(String string) { + if (string.endsWith("s") || string.endsWith("m") || string.endsWith("h") || string.endsWith("d")) + return TimeTools.getSeconds(string); + + long seconds; + try { + seconds = Long.valueOf(string); + } catch (NumberFormatException e) { + Message.of("The number " + string + " cannot be parsed into a number of seconds.").to(sender); + return 0L; + } + return seconds; + } + private void parseTownCommand(String[] args) { if (args.length < 2) { showTflyHelp(); @@ -137,6 +214,13 @@ private void showTflyHelp() { Message.of(Colors.White + "/tfly - Toggle flight.").to(sender); if (Permission.has(sender, "townyflight.command.tfly.reload", true)) Message.of(Colors.White + "/tfly reload - Reload the TownyFlight config.").to(sender); + if (Permission.has(sender, "townyflight.command.tfly.tempflight", true)) { + Message.of(Colors.White + "/tfly tempflight [playername] [seconds] - Grant a player temp flight in seconds.").to(sender); + Message.of(Colors.White + "/tfly tempflight [playername] [time]m - Grant a player temp flight in minutes.").to(sender); + Message.of(Colors.White + "/tfly tempflight [playername] [time]h - Grant a player temp flight in hours.").to(sender); + Message.of(Colors.White + "/tfly tempflight [playername] [time]d - Grant a player temp flight in days.").to(sender); + Message.of(Colors.White + "/tfly tempflight [playername] remove - Remove a player's temp flight.").to(sender); + } if (Permission.has(sender, "townyflight.command.tfly.other", true)) Message.of(Colors.White + "/tfly [playername] - Toggle flight for a player.").to(sender); if (Permission.has(sender, "townyflight.command.tfly.town", true)) diff --git a/src/main/java/com/gmail/llmdlio/townyflight/config/ConfigNodes.java b/src/main/java/com/gmail/llmdlio/townyflight/config/ConfigNodes.java index 35eef4b..f5ca32b 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/config/ConfigNodes.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/config/ConfigNodes.java @@ -44,6 +44,11 @@ public enum ConfigNodes { "Flight priviledges removed. ", "", "# Message shown when a player has flight taken away by console."), + LANG_FLIGHTDEACTIVATEDTIMEMSG( + "language.flightDeactivatedTimeMsg", + "You ran out of flight time. ", + "", + "# Message shown when a player has flight taken away because their tempflight ran out."), LANG_NOPERMISSION( "language.noPermission", "You do not have permission for this command%s. ", @@ -94,6 +99,22 @@ public enum ConfigNodes { "Flight enabled for everyone within this town's borders.", "", "# The hover text shown on the free flight status screen component."), + LANG_TEMPFLIGHTGRANTEDTOPLAYER( + "language.tempFlightGrantedToPlayer", + "%s has had tempflight granted for %s.", + "", + "# The message shown to the admin/console when temp flight is given to a player."), + LANG_YOUHAVEBEENGIVENTEMPFLIGHT( + "language.youHaveReceivedTempFlight", + "You have been given %s of tempflight priviledges.", + "", + "# The message shown to the player when they receive temp flight."), + LANG_TEMPFLIGHTEXPIRED( + "language.yourTempFlightHasExpired", + "Your tempflight priviledges have expired.", + "", + "# The message shown to the player when they run out of temp flight."), + OPTIONS("options", "", "", "#################", diff --git a/src/main/java/com/gmail/llmdlio/townyflight/config/Settings.java b/src/main/java/com/gmail/llmdlio/townyflight/config/Settings.java index 49f265f..d50ee6c 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/config/Settings.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/config/Settings.java @@ -44,6 +44,7 @@ public static void loadStrings() { lang.put("flightDeactivatedMsg", getString("flightDeactivatedMsg")); lang.put("flightDeactivatedPVPMsg", getString("flightDeactivatedPVPMsg")); lang.put("flightDeactivatedConsoleMsg", getString("flightDeactivatedConsoleMsg")); + lang.put("flightDeactivatedTimeMsg", getString("flightDeactivatedTimeMsg")); lang.put("noPermission", getString("noPermission")); lang.put("missingNode", getString("missingNode")); lang.put("notDuringWar", getString("notDuringWar")); @@ -54,6 +55,9 @@ public static void loadStrings() { lang.put("enabled", getString("enabled")); lang.put("statusScreenComponent", getString("statusScreenComponent")); lang.put("statusScreenComponentHover", getString("statusScreenComponentHover")); + lang.put("tempFlightGrantedToPlayer", getString("tempFlightGrantedToPlayer")); + lang.put("youHaveReceivedTempFlight", getString("youHaveReceivedTempFlight")); + lang.put("yourTempFlightHasExpired", getString("yourTempFlightHasExpired")); } public static String getLangString(String languageString) { diff --git a/src/main/java/com/gmail/llmdlio/townyflight/integrations/TownyFlightPlaceholderExpansion.java b/src/main/java/com/gmail/llmdlio/townyflight/integrations/TownyFlightPlaceholderExpansion.java index 808b0ce..25c0551 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/integrations/TownyFlightPlaceholderExpansion.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/integrations/TownyFlightPlaceholderExpansion.java @@ -5,6 +5,8 @@ import com.gmail.llmdlio.townyflight.TownyFlight; import com.gmail.llmdlio.townyflight.TownyFlightAPI; +import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; +import com.palmergames.util.TimeMgmt; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import net.md_5.bungee.api.ChatColor; @@ -108,6 +110,11 @@ private String getOfflinePlayerPlaceholder(OfflinePlayer offlineplayer, String i switch (identifier) { case "can_player_fly": // %townyflight_can_player_fly% return String.valueOf(TownyFlightAPI.canFlyAccordingToCache(player)); + case "temp_flight_seconds_remaining": // %townyflight_temp_flight_seconds_remaining% + return TimeMgmt.getFormattedTimeValue(TempFlightTask.getSeconds(offlineplayer.getUniqueId()) * 1000L); + case "temp_flight_seconds_remaining_raw": // %townyflight_temp_flight_seconds_remaining_raw% + return String.valueOf(TempFlightTask.getSeconds(offlineplayer.getUniqueId())); + default: return ""; } diff --git a/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerJoinListener.java b/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerJoinListener.java index bb976ed..003b294 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerJoinListener.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerJoinListener.java @@ -9,6 +9,8 @@ import com.gmail.llmdlio.townyflight.TownyFlightAPI; import com.gmail.llmdlio.townyflight.config.Settings; +import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; +import com.gmail.llmdlio.townyflight.util.MetaData; public class PlayerJoinListener implements Listener { private final TownyFlight plugin; @@ -26,6 +28,10 @@ private void playerJoinEvent(PlayerJoinEvent event) { final Player player = event.getPlayer(); plugin.getScheduler().runLater(player, () -> { + long seconds = MetaData.getSeconds(player.getUniqueId()); + if (seconds > 0L) + TempFlightTask.addPlayerTempFlightSeconds(player.getUniqueId(), seconds); + boolean canFly = TownyFlightAPI.getInstance().canFly(player, true); boolean isFlying = player.isFlying(); if (isFlying && canFly) diff --git a/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerLogOutListener.java b/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerLogOutListener.java index 998f571..466bb6e 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerLogOutListener.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerLogOutListener.java @@ -5,6 +5,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import com.gmail.llmdlio.townyflight.TownyFlightAPI; +import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; public class PlayerLogOutListener implements Listener { @@ -12,5 +13,6 @@ public class PlayerLogOutListener implements Listener { public void onPlayerQuit(PlayerQuitEvent event) { TownyFlightAPI.getInstance().testForFlight(event.getPlayer(), true); TownyFlightAPI.removeCachedPlayer(event.getPlayer()); + TempFlightTask.logOutPlayerWithRemainingTempFlight(event.getPlayer()); } } diff --git a/src/main/java/com/gmail/llmdlio/townyflight/tasks/TaskHandler.java b/src/main/java/com/gmail/llmdlio/townyflight/tasks/TaskHandler.java new file mode 100644 index 0000000..b4ef36e --- /dev/null +++ b/src/main/java/com/gmail/llmdlio/townyflight/tasks/TaskHandler.java @@ -0,0 +1,26 @@ +package com.gmail.llmdlio.townyflight.tasks; + +import com.gmail.llmdlio.townyflight.TownyFlight; +import com.palmergames.bukkit.towny.scheduling.ScheduledTask; + +public class TaskHandler { + + private static ScheduledTask tempFlightTask = null; + private static Runnable tempFlightRunnable = null; + + public static void toggleTempFlightTask(boolean on) { + if (on && !isTempFlightTaskRunning()) { + if (tempFlightRunnable == null) + tempFlightRunnable = new TempFlightTask(); + tempFlightTask = TownyFlight.getPlugin().getScheduler().runRepeating(tempFlightRunnable, 60L, 20L); + } else if (!on && isTempFlightTaskRunning()) { + tempFlightTask.cancel(); + tempFlightTask = null; + tempFlightRunnable = null; + } + } + + public static boolean isTempFlightTaskRunning() { + return tempFlightTask != null && !tempFlightTask.isCancelled(); + } +} diff --git a/src/main/java/com/gmail/llmdlio/townyflight/tasks/TempFlightTask.java b/src/main/java/com/gmail/llmdlio/townyflight/tasks/TempFlightTask.java new file mode 100644 index 0000000..f36e939 --- /dev/null +++ b/src/main/java/com/gmail/llmdlio/townyflight/tasks/TempFlightTask.java @@ -0,0 +1,104 @@ +package com.gmail.llmdlio.townyflight.tasks; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.gmail.llmdlio.townyflight.TownyFlightAPI; +import com.gmail.llmdlio.townyflight.util.Message; +import com.gmail.llmdlio.townyflight.util.MetaData; +import com.palmergames.bukkit.towny.TownyAPI; +import com.palmergames.bukkit.towny.object.Resident; + +public class TempFlightTask implements Runnable { + + private static Map playerUUIDSecondsMap = new ConcurrentHashMap<>(); + private int cycles = 0; + + @Override + public void run() { + cycles++; + + removeFlightFromPlayersWithNoTimeLeft(); + + Set uuidsToDecrement = new HashSet<>(); + for (Player player : new ArrayList<>(Bukkit.getOnlinePlayers())) { + if (!player.getAllowFlight() || !player.isFlying()) + continue; + UUID uuid = player.getUniqueId(); + if (!playerUUIDSecondsMap.containsKey(uuid)) + continue; + uuidsToDecrement.add(uuid); + } + + uuidsToDecrement.forEach(uuid -> decrementSeconds(uuid)); + if (cycles % 10 == 0) + cycles = 0; + } + + private void decrementSeconds(UUID uuid) { + long seconds = playerUUIDSecondsMap.get(uuid); + playerUUIDSecondsMap.put(uuid, --seconds); + // Save players every 10 seconds; + if (cycles % 10 == 0) { + Resident resident = TownyAPI.getInstance().getResident(uuid); + if (resident == null) + return; + MetaData.setSeconds(resident, seconds, true); + } + } + + private void removeFlightFromPlayersWithNoTimeLeft() { + Set uuidsToRemove = playerUUIDSecondsMap.entrySet().stream() + .filter(e -> e.getValue() <= 0) + .map(e -> e.getKey()) + .collect(Collectors.toSet()); + uuidsToRemove.forEach(uuid -> { + removeFlight(uuid); + Player player = Bukkit.getPlayer(uuid); + if (player != null && player.isOnline()) + Message.of(String.format(Message.getLangString("yourTempFlightHasExpired"))).to(player); + }); + } + + private void removeFlight(UUID uuid) { + playerUUIDSecondsMap.remove(uuid); + Player player = Bukkit.getPlayer(uuid); + if (player != null && player.isOnline()) + TownyFlightAPI.getInstance().removeFlight(player, false, true, "time"); + MetaData.removeFlightMeta(uuid); + } + + public static long getSeconds(UUID uuid) { + return playerUUIDSecondsMap.containsKey(uuid) ? playerUUIDSecondsMap.get(uuid) : 0L; + } + + public static void addPlayerTempFlightSeconds(UUID uuid, long seconds) { + long existingSeconds = playerUUIDSecondsMap.containsKey(uuid) ? playerUUIDSecondsMap.get(uuid) : 0L; + playerUUIDSecondsMap.put(uuid, existingSeconds + seconds); + } + + public static void removeAllPlayerTempFlightSeconds(UUID uuid) { + playerUUIDSecondsMap.put(uuid, 0L); + } + + public static void logOutPlayerWithRemainingTempFlight(Player player) { + if (!playerUUIDSecondsMap.containsKey(player.getUniqueId())) + return; + long seconds = playerUUIDSecondsMap.get(player.getUniqueId()); + if (seconds <= 0L) + return; + Resident resident = TownyAPI.getInstance().getResident(player); + if (resident == null) + return; + MetaData.setSeconds(resident, seconds, true); + playerUUIDSecondsMap.remove(player.getUniqueId()); + } +} diff --git a/src/main/java/com/gmail/llmdlio/townyflight/util/MetaData.java b/src/main/java/com/gmail/llmdlio/townyflight/util/MetaData.java index c0ad48b..de74020 100644 --- a/src/main/java/com/gmail/llmdlio/townyflight/util/MetaData.java +++ b/src/main/java/com/gmail/llmdlio/townyflight/util/MetaData.java @@ -1,7 +1,12 @@ package com.gmail.llmdlio.townyflight.util; +import java.util.UUID; + +import com.palmergames.bukkit.towny.TownyAPI; +import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.Town; import com.palmergames.bukkit.towny.object.metadata.BooleanDataField; +import com.palmergames.bukkit.towny.object.metadata.LongDataField; import com.palmergames.bukkit.towny.utils.MetaDataUtil; public class MetaData { @@ -19,4 +24,45 @@ public static void setFreeFlightMeta(Town town, boolean active) { } MetaDataUtil.setBoolean(town, freeFlight, active, true); } + + private static LongDataField tempFlightSeconds = new LongDataField("townyflight_tempflightseconds"); + + + public static void addTempFlight(UUID uuid, long seconds) { + Resident resident = TownyAPI.getInstance().getResident(uuid); + if (resident == null) + return; + addTempFlight(resident, seconds); + } + + public static void addTempFlight(Resident resident, long seconds) { + long existingSeconds = MetaDataUtil.getLong(resident, tempFlightSeconds); + MetaDataUtil.setLong(resident, tempFlightSeconds, existingSeconds + seconds, true); + } + + public static long getSeconds(UUID uuid) { + Resident resident = TownyAPI.getInstance().getResident(uuid); + if (resident == null) + return 0L; + return getSeconds(resident); + } + + private static long getSeconds(Resident resident) { + return MetaDataUtil.getLong(resident, tempFlightSeconds); + } + + public static void setSeconds(Resident resident, long seconds, boolean save) { + MetaDataUtil.setLong(resident, tempFlightSeconds, seconds, save); + } + + public static void removeFlightMeta(UUID uuid) { + Resident resident = TownyAPI.getInstance().getResident(uuid); + if (resident == null) + return; + removeFlightMeta(resident); + } + + public static void removeFlightMeta(Resident resident) { + resident.removeMetaData(tempFlightSeconds); + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 455234a..dc00cb5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -21,6 +21,9 @@ permissions: townyflight.command.tfly.reload: description: User is able to reload the config. default: op + townyflight.command.tfly.tempflight: + description: User is able to grant tempflight to players. + default: op townyflight.command.tfly.other: description: User is able to remove flight from other players. default: op