├── .gitignore ├── .github ├── FUNDING.yml ├── issue_template.md └── workflows │ └── depoy_release.yml ├── src └── main │ ├── java │ └── com │ │ └── gmail │ │ └── llmdlio │ │ └── townyflight │ │ ├── util │ │ ├── Permission.java │ │ ├── MessageBuilder.java │ │ ├── Message.java │ │ └── MetaData.java │ │ ├── listeners │ │ ├── PlayerLogOutListener.java │ │ ├── PlayerFallListener.java │ │ ├── TownStatusScreenListener.java │ │ ├── PlayerPVPListener.java │ │ ├── TownRemoveResidentListener.java │ │ ├── PlayerTeleportListener.java │ │ ├── PlayerJoinListener.java │ │ ├── PlayerLeaveTownListener.java │ │ ├── TownUnclaimListener.java │ │ └── PlayerEnterTownListener.java │ │ ├── tasks │ │ ├── TaskHandler.java │ │ └── TempFlightTask.java │ │ ├── config │ │ ├── TownyFlightConfig.java │ │ ├── Settings.java │ │ └── ConfigNodes.java │ │ ├── command │ │ ├── TownToggleFlightCommandAddon.java │ │ └── TownyFlightCommand.java │ │ ├── integrations │ │ └── TownyFlightPlaceholderExpansion.java │ │ ├── TownyFlight.java │ │ └── TownyFlightAPI.java │ └── resources │ └── plugin.yml ├── pom.xml ├── README.md └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | /.idea 4 | /.classpath 5 | /.project 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: LlmDl 4 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/util/Permission.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.util; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | public class Permission { 6 | 7 | public static boolean has(CommandSender sender, String node, boolean silent) { 8 | if (sender.hasPermission(node)) 9 | return true; 10 | if (!silent) Message.noPerms(node).to(sender); 11 | return false; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### What is the issue that you see? 2 | 3 | ### What steps can be done to repeat the issue on a test server? 4 | 1. 5 | 2. 6 | 3. 7 | 8 | ### What is supposed to happen instead? 9 | 1. 10 | 2. 11 | 3. 12 | 13 | ### Versions/Files 14 | 15 | Towny Version (use '/towny v' in game) : 16 | TownyFlight Version : 17 | Link to pastebin.com with full server startup from the latest.log : 18 | Link to pastebin.com with TownyFlight config.yml : 19 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/util/MessageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.util; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import com.palmergames.bukkit.towny.TownyMessaging; 6 | 7 | public class MessageBuilder { 8 | String message; 9 | boolean serious; 10 | 11 | public Message build() { 12 | return new Message(this); 13 | } 14 | 15 | public MessageBuilder serious() { 16 | this.serious = true; 17 | return this; 18 | } 19 | 20 | public void to(CommandSender sender) { 21 | Message message = build(); 22 | TownyMessaging.sendMessage(sender, message.getMessage()); 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerLogOutListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.bukkit.event.Listener; 5 | import org.bukkit.event.player.PlayerQuitEvent; 6 | 7 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 8 | import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; 9 | 10 | public class PlayerLogOutListener implements Listener { 11 | 12 | @EventHandler 13 | public void onPlayerQuit(PlayerQuitEvent event) { 14 | TownyFlightAPI.getInstance().testForFlight(event.getPlayer(), true); 15 | TownyFlightAPI.removeCachedPlayer(event.getPlayer()); 16 | TempFlightTask.logOutPlayerWithRemainingTempFlight(event.getPlayer()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerFallListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import org.bukkit.entity.EntityType; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.entity.EntityDamageEvent; 9 | import org.bukkit.event.entity.EntityDamageEvent.DamageCause; 10 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 11 | 12 | public class PlayerFallListener implements Listener { 13 | 14 | @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true) 15 | private void playerFallEvent(EntityDamageEvent event) { 16 | 17 | if (event.getCause().equals(DamageCause.FALL) && event.getEntityType().equals(EntityType.PLAYER)) 18 | event.setCancelled(TownyFlightAPI.getInstance().removeFallProtection((Player) event.getEntity())); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/tasks/TaskHandler.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.tasks; 2 | 3 | import com.gmail.llmdlio.townyflight.TownyFlight; 4 | import com.palmergames.bukkit.towny.scheduling.ScheduledTask; 5 | 6 | public class TaskHandler { 7 | 8 | private static ScheduledTask tempFlightTask = null; 9 | private static Runnable tempFlightRunnable = null; 10 | 11 | public static void toggleTempFlightTask(boolean on) { 12 | if (on && !isTempFlightTaskRunning()) { 13 | if (tempFlightRunnable == null) 14 | tempFlightRunnable = new TempFlightTask(); 15 | tempFlightTask = TownyFlight.getPlugin().getScheduler().runRepeating(tempFlightRunnable, 60L, 20L); 16 | } else if (!on && isTempFlightTaskRunning()) { 17 | tempFlightTask.cancel(); 18 | tempFlightTask = null; 19 | tempFlightRunnable = null; 20 | } 21 | } 22 | 23 | public static boolean isTempFlightTaskRunning() { 24 | return tempFlightTask != null && !tempFlightTask.isCancelled(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/TownStatusScreenListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.bukkit.event.Listener; 5 | 6 | import com.gmail.llmdlio.townyflight.util.Message; 7 | import com.gmail.llmdlio.townyflight.util.MetaData; 8 | import com.palmergames.bukkit.towny.event.statusscreen.TownStatusScreenEvent; 9 | import com.palmergames.bukkit.towny.object.Translation; 10 | 11 | import net.kyori.adventure.text.Component; 12 | import net.kyori.adventure.text.event.HoverEvent; 13 | 14 | public class TownStatusScreenListener implements Listener { 15 | private final Component comp = Component 16 | .text(Translation.of("status_format_key_value_key") + Message.getLangString("statusScreenComponent")) 17 | .hoverEvent(HoverEvent.showText(Component.text(Message.getLangString("statusScreenComponentHover")))); 18 | 19 | @EventHandler 20 | public void on(TownStatusScreenEvent event) { 21 | if (MetaData.getFreeFlightMeta(event.getTown())) 22 | event.getStatusScreen().addComponentOf("freeflight", comp); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerPVPListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import org.bukkit.GameMode; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 9 | import com.palmergames.bukkit.towny.event.damage.TownyPlayerDamagePlayerEvent; 10 | 11 | 12 | public class PlayerPVPListener implements Listener { 13 | 14 | /* 15 | * Listener to turn off flight if flying player enters PVP combat. Runs only if 16 | * the config.yml's disable_Combat_Prevention is set to true. 17 | */ 18 | @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) 19 | private void playerPVPEvent(TownyPlayerDamagePlayerEvent event) { 20 | Player attackingPlayer = event.getAttackingPlayer(); 21 | Player defendingPlayer = event.getVictimPlayer(); 22 | 23 | if (!attackingPlayer.getAllowFlight()) 24 | return; 25 | 26 | if (attackingPlayer.getGameMode().equals(GameMode.CREATIVE) || !defendingPlayer.canSee(attackingPlayer)) { 27 | event.setCancelled(true); 28 | return; 29 | } 30 | 31 | TownyFlightAPI.getInstance().removeFlight(attackingPlayer, false, true, "pvp"); 32 | event.setCancelled(true); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/TownRemoveResidentListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import com.gmail.llmdlio.townyflight.TownyFlight; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | 9 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 10 | import com.palmergames.bukkit.towny.event.TownRemoveResidentEvent; 11 | import com.palmergames.bukkit.towny.object.Resident; 12 | 13 | public class TownRemoveResidentListener implements Listener { 14 | private final TownyFlight plugin; 15 | 16 | public TownRemoveResidentListener(TownyFlight plugin) { 17 | this.plugin = plugin; 18 | } 19 | 20 | /* 21 | * Listener for a player who stops being a resident of a town. 22 | */ 23 | @EventHandler(priority = EventPriority.LOWEST) 24 | private void playerLeftTownEvent(TownRemoveResidentEvent event) { 25 | Resident resident = event.getResident(); 26 | if (!resident.isOnline()) 27 | return; 28 | Player player = resident.getPlayer(); 29 | if (!player.getAllowFlight() || player.hasPermission("townyflight.bypass")) 30 | return; 31 | 32 | plugin.getScheduler().runLater(player, () -> testPlayer(player), 1); 33 | } 34 | 35 | /* 36 | * Check if the player is allowed to fly at their location. 37 | */ 38 | private void testPlayer(Player player) { 39 | if (!TownyFlightAPI.getInstance().canFly(player, true)) { 40 | TownyFlightAPI.getInstance().removeFlight(player, false, true, ""); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/util/Message.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.util; 2 | 3 | import com.gmail.llmdlio.townyflight.config.Settings; 4 | 5 | import net.md_5.bungee.api.ChatColor; 6 | 7 | public class Message { 8 | 9 | private String message; 10 | private boolean serious; 11 | 12 | public Message(MessageBuilder builder) { 13 | this.message = builder.message; 14 | this.serious = builder.serious; 15 | } 16 | 17 | public String getMessage() { 18 | return getLangString("pluginPrefix") + (serious ? ChatColor.RED : "") + message; 19 | } 20 | 21 | public static MessageBuilder of(String message) { 22 | MessageBuilder builder = new MessageBuilder(); 23 | if (hasLangString(message)) 24 | message = getLangString(message); 25 | builder.message = message; 26 | return builder; 27 | } 28 | 29 | public static MessageBuilder noPerms(String node) { 30 | MessageBuilder builder = new MessageBuilder(); 31 | builder.message = String.format(getLangString("noPermission"), (Settings.showPermissionInMessage ? String.format(getLangString("missingNode"), node) : "")); 32 | builder.serious(); 33 | return builder; 34 | } 35 | 36 | public static String getLangString(String message) { 37 | String langString = Settings.getLangString(message); 38 | if (langString == null) langString = "TownyFlight asked for a missing language string (" + message + ") please report this."; 39 | return langString; 40 | } 41 | 42 | private static boolean hasLangString(String message) { 43 | return Settings.hasLangString(message); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerTeleportListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.player.PlayerTeleportEvent; 9 | import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; 10 | 11 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 12 | import com.palmergames.bukkit.towny.TownyUniverse; 13 | import com.palmergames.bukkit.towny.object.Resident; 14 | 15 | public class PlayerTeleportListener implements Listener { 16 | 17 | @EventHandler(priority = EventPriority.MONITOR) 18 | private void playerTeleports(PlayerTeleportEvent event) { 19 | if (!aTeleportCauseThatMatters(event.getCause())) 20 | return; 21 | 22 | Player player = event.getPlayer(); 23 | if (player.hasPermission("townyflight.bypass") 24 | || !player.getAllowFlight() 25 | || flightAllowedDestination(player, event.getTo())) { 26 | return; 27 | } 28 | 29 | TownyFlightAPI.getInstance().removeFlight(player, false, true, ""); 30 | } 31 | 32 | private boolean aTeleportCauseThatMatters(TeleportCause teleportCause) { 33 | return teleportCause == TeleportCause.PLUGIN || teleportCause == TeleportCause.COMMAND || 34 | teleportCause == TeleportCause.ENDER_PEARL || teleportCause == TeleportCause.CHORUS_FRUIT; // TODO: change over when 1.21.4 and older support is dropped. 35 | } 36 | 37 | private boolean flightAllowedDestination(Player player, Location loc) { 38 | Resident resident = TownyUniverse.getInstance().getResident(player.getUniqueId()); 39 | return resident != null && TownyFlightAPI.allowedLocation(player, loc, resident); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/depoy_release.yml: -------------------------------------------------------------------------------- 1 | # This file will result in a new townyflight release version being deployed. 2 | name: Deploy Release 3 | on: 4 | workflow_dispatch: 5 | env: 6 | CURR_VER: 0.0.0 7 | SUPPORTED: " for Towny " 8 | TOWNY_VER: 0.0.0.0 9 | jobs: 10 | deploy_release_as_draft: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | java: ['21'] 15 | name: Creating TownyFlight Release 16 | steps: 17 | - name: checkout repo content 18 | uses: actions/checkout@v3 # checkout the repository content to github runner. 19 | - name: set up eclipse temurin 20 | uses: actions/setup-java@v3 21 | with: 22 | distribution: 'temurin' 23 | java-version: ${{ matrix.java }} 24 | java-package: jdk 25 | cache: 'maven' 26 | - name: compile townyflight with maven 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | run: mvn -B clean package 30 | - name: find current version 31 | run: | 32 | export POM_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) 33 | echo "CURR_VER=$POM_VERSION" >> $GITHUB_ENV 34 | - name: find towny dependency version 35 | run: | 36 | export DEP_VERSION=$(mvn help:evaluate -Dexpression=towny.version -q -DforceStdout) 37 | echo "TOWNY_VER=$DEP_VERSION" >> $GITHUB_ENV 38 | 39 | - name: create release 40 | uses: softprops/action-gh-release@v1 41 | with: 42 | generate_release_notes: true 43 | draft: false 44 | prerelease: false 45 | token: ${{ github.token }} 46 | tag_name: ${{ env.CURR_VER }} 47 | name: ${{ env.CURR_VER }}${{ env.SUPPORTED }}${{ env.TOWNY_VER }}+ 48 | files: | 49 | ./target/TownyFlight-${{ env.CURR_VER }}.jar 50 | env: 51 | GITHUB_REPOSITORY: my_gh_org/my_gh_repo 52 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerJoinListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import com.gmail.llmdlio.townyflight.TownyFlight; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.player.PlayerJoinEvent; 9 | 10 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 11 | import com.gmail.llmdlio.townyflight.config.Settings; 12 | import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; 13 | import com.gmail.llmdlio.townyflight.util.MetaData; 14 | 15 | public class PlayerJoinListener implements Listener { 16 | private final TownyFlight plugin; 17 | 18 | public PlayerJoinListener(TownyFlight plugin) { 19 | this.plugin = plugin; 20 | } 21 | 22 | /* 23 | * Listener for a player who joins the server successfully. Check if flight is 24 | * allowed where they are currently and if not, remove it. 25 | */ 26 | @EventHandler(priority = EventPriority.MONITOR) 27 | private void playerJoinEvent(PlayerJoinEvent event) { 28 | final Player player = event.getPlayer(); 29 | 30 | plugin.getScheduler().runLater(player, () -> { 31 | long seconds = MetaData.getSeconds(player.getUniqueId()); 32 | if (seconds > 0L) 33 | TempFlightTask.addPlayerTempFlightSeconds(player.getUniqueId(), seconds); 34 | 35 | boolean canFly = TownyFlightAPI.getInstance().canFly(player, true); 36 | boolean isFlying = player.isFlying(); 37 | if (isFlying && canFly) 38 | return; 39 | 40 | if (isFlying && !canFly) { 41 | TownyFlightAPI.getInstance().removeFlight(player, false, true, ""); 42 | return; 43 | } 44 | 45 | if (!isFlying && canFly && Settings.autoEnableFlight) 46 | TownyFlightAPI.getInstance().addFlight(player, false); 47 | 48 | TownyFlightAPI.cachePlayerFlight(player, canFly); 49 | }, 1); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerLeaveTownListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import com.gmail.llmdlio.townyflight.TownyFlight; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | 9 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 10 | import com.gmail.llmdlio.townyflight.config.Settings; 11 | import com.gmail.llmdlio.townyflight.util.Message; 12 | import com.palmergames.bukkit.towny.event.player.PlayerExitsFromTownBorderEvent; 13 | 14 | public class PlayerLeaveTownListener implements Listener { 15 | private final TownyFlight plugin; 16 | 17 | public PlayerLeaveTownListener(TownyFlight plugin) { 18 | this.plugin = plugin; 19 | } 20 | 21 | /* 22 | * Listener for a player who leaves town. Runs one tick after the 23 | * PlayerLeaveTownEvent in order to get the proper location. 24 | */ 25 | @EventHandler(priority = EventPriority.LOWEST) 26 | private void playerLeftTownEvent(PlayerExitsFromTownBorderEvent event) { 27 | Player player = event.getPlayer(); 28 | if (!player.getAllowFlight() || player.hasPermission("townyflight.bypass")) 29 | return; 30 | 31 | plugin.getScheduler().runLater(player, () -> executeLeaveTown(player), 1); 32 | } 33 | 34 | /* 35 | * If player has left the town borders into an area they cannot fly in, remove 36 | * their flight. Handles the flightDisableTimer if in use. 37 | */ 38 | private void executeLeaveTown(Player player) { 39 | if (!TownyFlightAPI.getInstance().canFly(player, true)) { 40 | if (Settings.flightDisableTimer < 1) { 41 | TownyFlightAPI.getInstance().removeFlight(player, false, true, ""); 42 | } else { 43 | Message.of(String.format(Message.getLangString("returnToAllowedArea"), Settings.flightDisableTimer)).serious().to(player); 44 | plugin.getScheduler().runLater(player, () -> TownyFlightAPI.getInstance().testForFlight(player, true), Settings.flightDisableTimer * 20); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: TownyFlight 2 | author: LlmDl 3 | main: com.gmail.llmdlio.townyflight.TownyFlight 4 | version: ${project.version} 5 | api-version: ${project.bukkitAPIVersion} 6 | depend: [Towny] 7 | softdepend: [SiegeWar] 8 | prefix: TownyFlight 9 | 10 | commands: 11 | tfly: 12 | description: Turns flight on and off. 13 | aliases: [townyfly, townyflight] 14 | usage: "/" 15 | 16 | permissions: 17 | townyflight.command.tfly: 18 | description: User is able to toggle flight on while in their town. 19 | default: op 20 | townyflight.command.tfly.reload: 21 | description: User is able to reload the config. 22 | default: op 23 | townyflight.command.tfly.tempflight: 24 | description: User is able to grant tempflight to players. 25 | default: op 26 | townyflight.command.tfly.other: 27 | description: User is able to remove flight from other players. 28 | default: op 29 | townyflight.command.tfly.town: 30 | description: User is able to use /tfly town command. 31 | default: op 32 | townyflight.command.town.toggle.flight: 33 | description: Mayors can toggle flight on and off for their town. 34 | default: op 35 | townyflight.bypass: 36 | description: Bypass permission node for admins to not have their flight disabled while they travel the server. 37 | default: op 38 | townyflight.alliedtowns: 39 | description: Allows players to use /townyflight in towns which are allied to their own. 40 | default: op 41 | townyflight.alltowns: 42 | description: Allows players to use /townyflight in any town. Wilderness is still disallowed. 43 | default: op 44 | townyflight.nationtowns: 45 | description: Allows players to use /townyflight in towns which are in their nation. 46 | default: op 47 | townyflight.trustedtowns: 48 | description: Allows players to use /townyflight in towns in which they are Trusted. 49 | default: op 50 | townyflight.wilderness: 51 | description: Allows players to use /townyflight in the wilderness. 52 | 53 | default: op 54 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/TownUnclaimListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.gmail.llmdlio.townyflight.TownyFlight; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.EventPriority; 11 | import org.bukkit.event.Listener; 12 | 13 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 14 | import com.palmergames.bukkit.towny.event.town.TownUnclaimEvent; 15 | import com.palmergames.bukkit.towny.object.WorldCoord; 16 | 17 | 18 | public class TownUnclaimListener implements Listener { 19 | private final TownyFlight plugin; 20 | 21 | public TownUnclaimListener(TownyFlight plugin) { 22 | this.plugin = plugin; 23 | } 24 | 25 | /* 26 | * Listener for when players unclaim territory. Will cause any player in that 27 | * area to lose flight. 28 | */ 29 | @EventHandler(priority = EventPriority.MONITOR) 30 | private void TownUnclaimEvent(TownUnclaimEvent event) { 31 | plugin.getScheduler().runLater(() -> scanForFlightAbilities(selectArea(event.getWorldCoord())), 2); 32 | } 33 | 34 | private void scanForFlightAbilities(List plots) { 35 | 36 | // Cycle through all the online players, because multiple players could be in a plot that is unclaimed. 37 | for (final Player player : new ArrayList<>(Bukkit.getOnlinePlayers())) { 38 | plugin.getScheduler().run(player, () -> { 39 | if (player.hasPermission("townyflight.bypass") 40 | || !player.getAllowFlight() 41 | || !plots.contains(WorldCoord.parseWorldCoord(player)) 42 | || TownyFlightAPI.getInstance().canFly(player, true)) 43 | return; 44 | 45 | TownyFlightAPI.getInstance().removeFlight(player, false, true, ""); 46 | }); 47 | } 48 | } 49 | 50 | private List selectArea(WorldCoord centre) { 51 | List plots = new ArrayList<>(9); 52 | plots.add(centre.add(-1, -1)); 53 | plots.add(centre.add(-1, 0)); 54 | plots.add(centre.add(-1, 1)); 55 | plots.add(centre.add(0, -1)); 56 | plots.add(centre); 57 | plots.add(centre.add(0, 1)); 58 | plots.add(centre.add(1, -1)); 59 | plots.add(centre.add(1, 0)); 60 | plots.add(centre.add(1, 1)); 61 | 62 | return plots; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/util/MetaData.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.util; 2 | 3 | import java.util.UUID; 4 | 5 | import com.palmergames.bukkit.towny.TownyAPI; 6 | import com.palmergames.bukkit.towny.object.Resident; 7 | import com.palmergames.bukkit.towny.object.Town; 8 | import com.palmergames.bukkit.towny.object.metadata.BooleanDataField; 9 | import com.palmergames.bukkit.towny.object.metadata.LongDataField; 10 | import com.palmergames.bukkit.towny.utils.MetaDataUtil; 11 | 12 | public class MetaData { 13 | 14 | private static BooleanDataField freeFlight = new BooleanDataField("townyflight_freeflight"); 15 | 16 | public static boolean getFreeFlightMeta(Town town) { 17 | return com.palmergames.bukkit.towny.utils.MetaDataUtil.getBoolean(town, freeFlight); 18 | } 19 | 20 | public static void setFreeFlightMeta(Town town, boolean active) { 21 | if (!town.hasMeta("townyflight_freeflight")) { 22 | MetaDataUtil.addNewBooleanMeta(town, "townyflight_freeflight", active, true); 23 | return; 24 | } 25 | MetaDataUtil.setBoolean(town, freeFlight, active, true); 26 | } 27 | 28 | private static LongDataField tempFlightSeconds = new LongDataField("townyflight_tempflightseconds"); 29 | 30 | 31 | public static void addTempFlight(UUID uuid, long seconds) { 32 | Resident resident = TownyAPI.getInstance().getResident(uuid); 33 | if (resident == null) 34 | return; 35 | addTempFlight(resident, seconds); 36 | } 37 | 38 | public static void addTempFlight(Resident resident, long seconds) { 39 | long existingSeconds = MetaDataUtil.getLong(resident, tempFlightSeconds); 40 | MetaDataUtil.setLong(resident, tempFlightSeconds, existingSeconds + seconds, true); 41 | } 42 | 43 | public static long getSeconds(UUID uuid) { 44 | Resident resident = TownyAPI.getInstance().getResident(uuid); 45 | if (resident == null) 46 | return 0L; 47 | return getSeconds(resident); 48 | } 49 | 50 | private static long getSeconds(Resident resident) { 51 | return MetaDataUtil.getLong(resident, tempFlightSeconds); 52 | } 53 | 54 | public static void setSeconds(Resident resident, long seconds, boolean save) { 55 | MetaDataUtil.setLong(resident, tempFlightSeconds, seconds, save); 56 | } 57 | 58 | public static void removeFlightMeta(UUID uuid) { 59 | Resident resident = TownyAPI.getInstance().getResident(uuid); 60 | if (resident == null) 61 | return; 62 | removeFlightMeta(resident); 63 | } 64 | 65 | public static void removeFlightMeta(Resident resident) { 66 | resident.removeMetaData(tempFlightSeconds); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/listeners/PlayerEnterTownListener.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.listeners; 2 | 3 | import com.gmail.llmdlio.townyflight.TownyFlight; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | 9 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 10 | import com.gmail.llmdlio.townyflight.config.Settings; 11 | import com.palmergames.bukkit.towny.TownyAPI; 12 | import com.palmergames.bukkit.towny.event.player.PlayerEntersIntoTownBorderEvent; 13 | import com.palmergames.bukkit.towny.object.Resident; 14 | import com.palmergames.bukkit.towny.object.Town; 15 | import com.palmergames.bukkit.towny.utils.CombatUtil; 16 | 17 | public class PlayerEnterTownListener implements Listener { 18 | private final TownyFlight plugin; 19 | 20 | public PlayerEnterTownListener(TownyFlight plugin) { 21 | this.plugin = plugin; 22 | } 23 | 24 | /* 25 | * Listener for a player who enters town. Used only if the config has 26 | * auto-flight enabled. 27 | */ 28 | @EventHandler(priority = EventPriority.LOWEST) 29 | private void playerEnterTownEvent(PlayerEntersIntoTownBorderEvent event) { 30 | final Player player = event.getPlayer(); 31 | // Do nothing to players who are already flying. 32 | if (player.getAllowFlight()) return; 33 | 34 | plugin.getScheduler().runLater(player, () -> { 35 | if (!TownyFlightAPI.getInstance().canFly(player, true)) 36 | return; 37 | if (Settings.autoEnableFlight) 38 | TownyFlightAPI.getInstance().addFlight(player, Settings.autoEnableSilent); 39 | 40 | TownyFlightAPI.cachePlayerFlight(player, true); 41 | }, 1); 42 | } 43 | 44 | /* 45 | * Listener which takes flight from a town's online players if an enemy enters 46 | * into the town. 47 | * 48 | * TODO: Set this up in the config as an option. 49 | */ 50 | // @EventHandler 51 | @SuppressWarnings("unused") 52 | private void enemyEnterTownEvent(PlayerEntersIntoTownBorderEvent event) { 53 | final Resident resident = TownyAPI.getInstance().getResident(event.getPlayer().getUniqueId()); 54 | if (resident == null || !resident.hasTown()) 55 | return; 56 | final Town town = event.getEnteredTown(); 57 | 58 | if (CombatUtil.isEnemy(town, TownyAPI.getInstance().getResidentTownOrNull(resident))) { 59 | TownyAPI.getInstance().getOnlinePlayersInTown(town).stream().filter(player -> player.getAllowFlight()) 60 | .filter(player -> TownyAPI.getInstance().getTown(player.getLocation()).equals(town)) 61 | .forEach(player -> TownyFlightAPI.getInstance().removeFlight(player, false, true, "")); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.gmail.llmdlio 6 | TownyFlight 7 | 1.14.1 8 | TownyFlight 9 | A flight plugin for Towny servers. 10 | 11 | 21 12 | 1.19 13 | UTF-8 14 | 0.101.2.5 15 | 16 | 17 | clean package 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-compiler-plugin 22 | 3.11.0 23 | 24 | ${java.version} 25 | ${java.version} 26 | ${java.version} 27 | 28 | 29 | 30 | 31 | 32 | src/main/resources 33 | true 34 | 35 | 36 | 37 | 38 | 39 | jitpack.io 40 | https://jitpack.io 41 | 42 | 43 | papermc 44 | https://repo.papermc.io/repository/maven-public/ 45 | 46 | 47 | placeholderapi 48 | https://repo.extendedclip.com/content/repositories/placeholderapi/ 49 | 50 | 51 | glaremasters repo 52 | https://repo.glaremasters.me/repository/towny/ 53 | 54 | 55 | 56 | 57 | io.papermc.paper 58 | paper-api 59 | 1.21.8-R0.1-SNAPSHOT 60 | provided 61 | 62 | 63 | com.palmergames.bukkit.towny 64 | towny 65 | ${towny.version} 66 | provided 67 | 68 | 69 | com.github.TownyAdvanced 70 | SiegeWar 71 | 2.0.0 72 | provided 73 | 74 | 75 | org.jetbrains 76 | annotations 77 | 24.0.1 78 | provided 79 | 80 | 81 | me.clip 82 | placeholderapi 83 | 2.11.6 84 | provided 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/config/TownyFlightConfig.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.config; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Path; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | import com.gmail.llmdlio.townyflight.TownyFlight; 11 | import com.palmergames.bukkit.config.CommentedConfiguration; 12 | 13 | public class TownyFlightConfig { 14 | private TownyFlight plugin; 15 | private static CommentedConfiguration config; 16 | private static CommentedConfiguration newConfig; 17 | 18 | public TownyFlightConfig(TownyFlight plugin) { 19 | this.plugin = plugin; 20 | } 21 | 22 | public boolean reload() { 23 | return loadConfig(); 24 | } 25 | 26 | // Method to load TownyFlight\config.yml 27 | private boolean loadConfig() { 28 | File f = new File(plugin.getDataFolder(), "config.yml"); 29 | 30 | if (!f.exists()) { 31 | try { 32 | f.createNewFile(); 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | config = new CommentedConfiguration(f.toPath()); 39 | 40 | if (!config.load()) 41 | return false; 42 | 43 | setDefaults(plugin.getPluginMeta().getVersion(), f.toPath()); 44 | 45 | config.save(); 46 | 47 | return true; 48 | } 49 | 50 | /** 51 | * Builds a new config reading old config data. 52 | */ 53 | private static void setDefaults(String version, Path configPath) { 54 | newConfig = new CommentedConfiguration(configPath); 55 | newConfig.load(); 56 | 57 | for (ConfigNodes node : ConfigNodes.values()) { 58 | String root = node.getRoot(); 59 | if (node.getComments().length > 0) 60 | addComment(root, node.getComments()); 61 | 62 | if (root.equals(ConfigNodes.VERSION.getRoot())) 63 | setNewProperty(root, version); 64 | else 65 | setNewProperty(root, (config.get(root) != null) ? config.get(root) : node.getDefault()); 66 | } 67 | 68 | config = newConfig; 69 | newConfig = null; 70 | } 71 | 72 | public CommentedConfiguration getConfig() { 73 | return config; 74 | } 75 | 76 | private static void addComment(String root, String... comments) { 77 | newConfig.addComment(root, comments); 78 | } 79 | 80 | private static void setNewProperty(String root, Object value) { 81 | if (value == null) 82 | value = ""; 83 | newConfig.set(root, value.toString()); 84 | } 85 | 86 | public static String getString(String root, String def) { 87 | 88 | String data = config.getString(root.toLowerCase(), def); 89 | if (data == null) { 90 | TownyFlight.getPlugin().getLogger().warning(root.toLowerCase() + " from config.yml"); 91 | return ""; 92 | } 93 | return data; 94 | } 95 | 96 | public static String getString(ConfigNodes node) { 97 | return config.getString(node.getRoot().toLowerCase(), node.getDefault()); 98 | } 99 | 100 | public List getStrArr(ConfigNodes node) { 101 | return Arrays.stream(getString(node).split(",")).collect(Collectors.toList()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/command/TownToggleFlightCommandAddon.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.command; 2 | 3 | import com.gmail.llmdlio.townyflight.TownyFlight; 4 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 5 | import com.gmail.llmdlio.townyflight.util.Message; 6 | import com.gmail.llmdlio.townyflight.util.MetaData; 7 | import com.gmail.llmdlio.townyflight.util.Permission; 8 | 9 | import com.palmergames.bukkit.towny.TownyCommandAddonAPI; 10 | import com.palmergames.bukkit.towny.TownyMessaging; 11 | import com.palmergames.bukkit.towny.TownyCommandAddonAPI.CommandType; 12 | import com.palmergames.bukkit.towny.command.BaseCommand; 13 | import com.palmergames.bukkit.towny.exceptions.TownyException; 14 | import com.palmergames.bukkit.towny.object.AddonCommand; 15 | import com.palmergames.bukkit.towny.object.Resident; 16 | import com.palmergames.bukkit.towny.object.Town; 17 | import com.palmergames.bukkit.towny.utils.NameUtil; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | import org.bukkit.command.Command; 23 | import org.bukkit.command.CommandSender; 24 | import org.bukkit.command.TabExecutor; 25 | import org.bukkit.entity.Player; 26 | 27 | import org.jetbrains.annotations.NotNull; 28 | 29 | public class TownToggleFlightCommandAddon extends BaseCommand implements TabExecutor { 30 | 31 | @Override 32 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 33 | if (!(sender instanceof Player)) 34 | return Collections.emptyList(); 35 | 36 | switch (args[0].toLowerCase()) { 37 | case "flight": 38 | if (args.length == 2) 39 | return NameUtil.filterByStart(BaseCommand.setOnOffCompletes, args[1]); 40 | else 41 | return Collections.emptyList(); 42 | } 43 | return Collections.emptyList(); 44 | } 45 | 46 | public TownToggleFlightCommandAddon() { 47 | TownyCommandAddonAPI.addSubCommand(new AddonCommand(CommandType.TOWN_TOGGLE, "flight", this)); 48 | } 49 | 50 | public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { 51 | if (!(sender instanceof Player)) { 52 | TownyFlight.getPlugin().getLogger().warning("This command is not for Console use."); 53 | return true; 54 | } 55 | try { 56 | parseTownToggleFlightCommand(sender, args); 57 | } catch (TownyException e) { 58 | TownyMessaging.sendErrorMsg(sender, e.getMessage()); 59 | } 60 | return true; 61 | } 62 | 63 | private void parseTownToggleFlightCommand(CommandSender sender, String[] args) throws TownyException { 64 | Resident resident = getResidentOrThrow((Player) sender); 65 | if (!resident.isMayor()) { 66 | Message.of("notMayorMsg").serious().to(sender); 67 | return; 68 | } 69 | 70 | if (!Permission.has(sender, "townyflight.command.town.toggle.flight", false)) 71 | return; 72 | 73 | Town town = getTownFromResidentOrThrow(resident); 74 | boolean futurestate = !MetaData.getFreeFlightMeta(town); 75 | 76 | if (args.length > 0) { 77 | if (args[0].equalsIgnoreCase("on")) { 78 | futurestate = true; 79 | } else if (args[0].equalsIgnoreCase("off")) { 80 | futurestate = false; 81 | } 82 | } 83 | 84 | MetaData.setFreeFlightMeta(town, futurestate); 85 | Message.of(String.format(Message.getLangString("townWideFlight"), 86 | Message.getLangString(futurestate ? "enabled" : "disabled"), town)).to(sender); 87 | if (!futurestate) 88 | TownyFlightAPI.getInstance().takeFlightFromPlayersInTown(town); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/tasks/TempFlightTask.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.tasks; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.UUID; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.stream.Collectors; 10 | 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.entity.Player; 13 | 14 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 15 | import com.gmail.llmdlio.townyflight.util.Message; 16 | import com.gmail.llmdlio.townyflight.util.MetaData; 17 | import com.gmail.llmdlio.townyflight.util.Permission; 18 | import com.palmergames.bukkit.towny.TownyAPI; 19 | import com.palmergames.bukkit.towny.object.Resident; 20 | 21 | public class TempFlightTask implements Runnable { 22 | 23 | private static Map playerUUIDSecondsMap = new ConcurrentHashMap<>(); 24 | private int cycles = 0; 25 | 26 | @Override 27 | public void run() { 28 | cycles++; 29 | 30 | removeFlightFromPlayersWithNoTimeLeft(); 31 | 32 | Set uuidsToDecrement = new HashSet<>(); 33 | for (Player player : new ArrayList<>(Bukkit.getOnlinePlayers())) { 34 | if (!TownyAPI.getInstance().isTownyWorld(player.getWorld())) 35 | continue; 36 | if (Permission.has(player, "townyflight.bypass", true)) 37 | continue; 38 | if (!player.getAllowFlight() || !player.isFlying()) 39 | continue; 40 | UUID uuid = player.getUniqueId(); 41 | if (!playerUUIDSecondsMap.containsKey(uuid)) 42 | continue; 43 | uuidsToDecrement.add(uuid); 44 | } 45 | 46 | uuidsToDecrement.forEach(uuid -> decrementSeconds(uuid)); 47 | if (cycles % 10 == 0) 48 | cycles = 0; 49 | } 50 | 51 | private void decrementSeconds(UUID uuid) { 52 | long seconds = playerUUIDSecondsMap.get(uuid); 53 | playerUUIDSecondsMap.put(uuid, --seconds); 54 | // Save players every 10 seconds; 55 | if (cycles % 10 == 0) { 56 | Resident resident = TownyAPI.getInstance().getResident(uuid); 57 | if (resident == null) 58 | return; 59 | MetaData.setSeconds(resident, seconds, true); 60 | } 61 | } 62 | 63 | private void removeFlightFromPlayersWithNoTimeLeft() { 64 | Set uuidsToRemove = playerUUIDSecondsMap.entrySet().stream() 65 | .filter(e -> e.getValue() <= 0) 66 | .map(e -> e.getKey()) 67 | .collect(Collectors.toSet()); 68 | uuidsToRemove.forEach(uuid -> { 69 | removeFlight(uuid); 70 | Player player = Bukkit.getPlayer(uuid); 71 | if (player != null && player.isOnline()) 72 | Message.of(String.format(Message.getLangString("yourTempFlightHasExpired"))).to(player); 73 | }); 74 | } 75 | 76 | private void removeFlight(UUID uuid) { 77 | playerUUIDSecondsMap.remove(uuid); 78 | Player player = Bukkit.getPlayer(uuid); 79 | if (player != null && player.isOnline()) 80 | TownyFlightAPI.getInstance().removeFlight(player, false, true, "time"); 81 | MetaData.removeFlightMeta(uuid); 82 | } 83 | 84 | public static long getSeconds(UUID uuid) { 85 | return playerUUIDSecondsMap.containsKey(uuid) ? playerUUIDSecondsMap.get(uuid) : 0L; 86 | } 87 | 88 | public static void addPlayerTempFlightSeconds(UUID uuid, long seconds) { 89 | long existingSeconds = playerUUIDSecondsMap.containsKey(uuid) ? playerUUIDSecondsMap.get(uuid) : 0L; 90 | playerUUIDSecondsMap.put(uuid, existingSeconds + seconds); 91 | } 92 | 93 | public static void removeAllPlayerTempFlightSeconds(UUID uuid) { 94 | playerUUIDSecondsMap.put(uuid, 0L); 95 | } 96 | 97 | public static void logOutPlayerWithRemainingTempFlight(Player player) { 98 | if (!playerUUIDSecondsMap.containsKey(player.getUniqueId())) 99 | return; 100 | long seconds = playerUUIDSecondsMap.get(player.getUniqueId()); 101 | if (seconds <= 0L) 102 | return; 103 | Resident resident = TownyAPI.getInstance().getResident(player); 104 | if (resident == null) 105 | return; 106 | MetaData.setSeconds(resident, seconds, true); 107 | playerUUIDSecondsMap.remove(player.getUniqueId()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TownyFlight 2 | 3 | Finally, you can allow towny residents to fly while in their own towns, for free, from the lead developer of Towny. 4 | 5 | __As of version 1.5.0 This plugin supports Towny 0.94.0.2+ and MC 1.14.*.__ 6 | __Pre-1.5 versions requires the use of Towny 0.92.0.0 or older.__ 7 | 8 | Players that are flying and make a PVP combat attack will have their attack cancelled and their flight taken away. Players that are in creative and make attacks will be allowed to continue flying, but will have their pvp attack canceled. 9 | 10 | Players that have their flight taken away will fall harmlessly to the ground. 11 | 12 | If you want to support the developer consider [becoming a Sponsor](https://github.com/sponsors/LlmDl). 13 | ___ 14 | 15 | ## Commands: 16 | 17 | - /tfly - Enable/disable flight. 18 | - /tfly reload - Reload the config. 19 | - /tfly {name} - Removes flight from someone. 20 | - /tfly town {townname} toggleflight - Enables free flight in a town (use this on your Spawn city if you want everyone to be able to fly there.) 21 | - /tfly tempflight {playername} 1000 - Gives 1000 seconds of tempflight. 22 | - /tfly tempflight {playername} 10s - Gives 10 seconds of tempflight. 23 | - /tfly tempflight {playername} 10m - Gives 10 minutes of tempflight. 24 | - /tfly tempflight {playername} 1h - Gives 1 hour of tempflight. 25 | - /tfly tempflight {playername} 1d - Gives 1 day of tempflight. 26 | 27 | --- 28 | 29 | ## Permission nodes: 30 | 31 | - townyflight.command.tfly - required to use `/tfly`. 32 | - townyflight.command.tfly.reload - required to use `/tfly reload`. 33 | - townyflight.command.tfly.town - required to use `/tfly town {townname} toggleflight`. 34 | - townyflight.command.tfly.tempflight - required to use `/tfly tempflight`. 35 | - townyflight.command.tfly.other - required to use `/tfly {name}`. 36 | - townyflight.alliedtowns - allows players to use `/tfly` in towns which consider that player an ally. 37 | - townyflight.nationtowns - allows players to use `/tfly` in towns which are part of the player's nation. 38 | - townyflight.trustedtowns - allows players to use `/tfly` in towns which they are trusted. 39 | - townyflight.alltowns - allows players to use `/tfly` in any town, but not the wilderness. 40 | - townyflight.wilderness - allows players to use `/tfly` in the wilderness. 41 | - townyflight.bypass - default to Ops, bypasses removal of flight, use `/tfly` anywhere. 42 | 43 | --- 44 | 45 | ## Config: 46 | - Config auto-updates with new features while keeping your old settings intact (like Towny.) 47 | - Includes all language strings in case translation is required. 48 | 49 | - options.auto_Enable_Flight 50 | - default: false 51 | - If set to true, players entering their town will have flight auto-enabled. 52 | - When set to true, the plugin will use slightly more resources due to the EnterTown listener. 53 | - options.auto_Enable_Silent 54 | - default: false 55 | - If set to true, players entering their town will have flight auto-enabled without being notified in chat 56 | - options.disable_During_Wartime 57 | - default: true 58 | - If set to false, players can still fly in their town while war is active. 59 | - options.disable_Combat_Prevention 60 | - default: false 61 | - If set to false, TownyFlight will not prevent combat from player who fly or take their flight away. 62 | - options.show_Permission_After_No_Permission_Message 63 | - default: true 64 | - If set to false, the language.noPermission message will not display the permission node. 65 | 66 | --- 67 | 68 | ## PAPI Placeholders: 69 | - `%townyflight_can_player_fly%` - Displays true or false, based on whether the player could fly at their current location. 70 | - `%townyflight_temp_flight_seconds_remaining%` - Shows a formatted flight time remaining, ie: 30 seconds, or 5 minutes 2 seconds. 71 | - `%townyflight_temp_flight_seconds_remaining_raw%` - Shows the number of seconds of flight time remaining with no formatting, ie: 30, or 302. 72 | --- 73 | 74 | I'm open to new features and willing to fix any bugs found. 75 | 76 | 77 | License: http://creativecommons.org/licenses/by-nc-nd/3.0/ 78 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/config/Settings.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import org.bukkit.configuration.ConfigurationSection; 7 | 8 | import com.palmergames.bukkit.util.Colors; 9 | 10 | public class Settings { 11 | 12 | private static TownyFlightConfig config; 13 | public static Boolean autoEnableFlight; 14 | public static Boolean autoEnableSilent; 15 | public static Boolean disableCombatPrevention; 16 | public static Boolean disableDuringWar; 17 | public static Boolean showPermissionInMessage; 18 | public static Boolean siegeWarFound; 19 | public static int flightDisableTimer; 20 | public static List allowedTempFlightAreas; 21 | private static Map lang = new HashMap(); 22 | 23 | public static boolean loadSettings(TownyFlightConfig _config) { 24 | config = _config; 25 | loadOptions(); 26 | loadStrings(); 27 | return true; 28 | } 29 | 30 | private static void loadOptions() { 31 | autoEnableFlight = Boolean.valueOf(getOption("auto_Enable_Flight")); 32 | autoEnableSilent = Boolean.valueOf(getOption("auto_Enable_Silent")); 33 | disableDuringWar = Boolean.valueOf(getOption("disable_During_Wartime")); 34 | disableCombatPrevention = Boolean.valueOf(getOption("disable_Combat_Prevention")); 35 | showPermissionInMessage = Boolean.valueOf(getOption("show_Permission_After_No_Permission_Message")); 36 | flightDisableTimer = Integer.valueOf(getOption("flight_Disable_Timer")); 37 | allowedTempFlightAreas = allowedTempFlightAreas(); 38 | } 39 | 40 | public static void loadStrings() { 41 | lang.clear(); 42 | lang.put("pluginPrefix", colour(config.getConfig().getString("pluginPrefix"))); 43 | lang.put("flightOnMsg", getString("flightOnMsg")); 44 | lang.put("flightOffMsg", getString("flightOffMsg")); 45 | lang.put("noTownMsg", getString("noTownMsg")); 46 | lang.put("notInTownMsg", getString("notInTownMsg")); 47 | lang.put("flightDeactivatedMsg", getString("flightDeactivatedMsg")); 48 | lang.put("flightDeactivatedPVPMsg", getString("flightDeactivatedPVPMsg")); 49 | lang.put("flightDeactivatedConsoleMsg", getString("flightDeactivatedConsoleMsg")); 50 | lang.put("flightDeactivatedTimeMsg", getString("flightDeactivatedTimeMsg")); 51 | lang.put("noPermission", getString("noPermission")); 52 | lang.put("missingNode", getString("missingNode")); 53 | lang.put("notMayorMsg", getString("notMayorMsg")); 54 | lang.put("notDuringWar", getString("notDuringWar")); 55 | lang.put("returnToAllowedArea", getString("returnToAllowedArea")); 56 | lang.put("noTownFound", getString("noTownFound")); 57 | lang.put("townWideFlight", getString("townWideFlight")); 58 | lang.put("disabled", getString("disabled")); 59 | lang.put("enabled", getString("enabled")); 60 | lang.put("statusScreenComponent", getString("statusScreenComponent")); 61 | lang.put("statusScreenComponentHover", getString("statusScreenComponentHover")); 62 | lang.put("tempFlightGrantedToPlayer", getString("tempFlightGrantedToPlayer")); 63 | lang.put("youHaveReceivedTempFlight", getString("youHaveReceivedTempFlight")); 64 | lang.put("yourTempFlightHasExpired", getString("yourTempFlightHasExpired")); 65 | } 66 | 67 | public static String getLangString(String languageString) { 68 | return lang.get(languageString); 69 | } 70 | 71 | public static boolean hasLangString(String languageString) { 72 | return lang.containsKey(languageString); 73 | } 74 | 75 | private static String getOption(String string) { 76 | return getConfig("options").getString(string); 77 | } 78 | 79 | private static String getString(String string) { 80 | return colour(getConfig("language").getString(string)); 81 | } 82 | 83 | private static ConfigurationSection getConfig(String path) { 84 | return config.getConfig().getConfigurationSection(path); 85 | } 86 | 87 | private static String colour(String string) { 88 | return Colors.translateColorCodes(string); 89 | } 90 | 91 | public static List allowedTempFlightAreas() { 92 | return config.getStrArr(ConfigNodes.OPTIONS_TEMPFLIGHT_ALLOWED_AREAS); 93 | } 94 | 95 | public static boolean isAllowedTempFlightArea(String area) { 96 | return allowedTempFlightAreas.contains(area); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/integrations/TownyFlightPlaceholderExpansion.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.integrations; 2 | 3 | import org.bukkit.OfflinePlayer; 4 | import org.bukkit.entity.Player; 5 | 6 | import com.gmail.llmdlio.townyflight.TownyFlight; 7 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 8 | import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; 9 | import com.palmergames.util.TimeMgmt; 10 | 11 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 12 | import net.md_5.bungee.api.ChatColor; 13 | 14 | public class TownyFlightPlaceholderExpansion extends PlaceholderExpansion { 15 | 16 | private final TownyFlight plugin; 17 | 18 | /** 19 | * Since we register the expansion inside our own plugin, we can simply use this 20 | * method here to get an instance of our plugin. 21 | * 22 | * @param plugin The instance of our plugin. 23 | */ 24 | public TownyFlightPlaceholderExpansion(TownyFlight plugin) { 25 | this.plugin = plugin; 26 | } 27 | 28 | /** 29 | * Because this is an internal class, you must override this method to let 30 | * PlaceholderAPI know to not unregister your expansion class when 31 | * PlaceholderAPI is reloaded 32 | * 33 | * @return true to persist through reloads 34 | */ 35 | @Override 36 | public boolean persist() { 37 | return true; 38 | } 39 | 40 | /** 41 | * Because this is a internal class, this check is not needed and we can simply 42 | * return {@code true} 43 | * 44 | * @return Always true since it's an internal class. 45 | */ 46 | @Override 47 | public boolean canRegister() { 48 | return true; 49 | } 50 | 51 | /** 52 | * The name of the person who created this expansion should go here.
53 | * For convienience do we return the author from the plugin.yml 54 | * 55 | * @return The name of the author as a String. 56 | */ 57 | @Override 58 | public String getAuthor() { 59 | return plugin.getPluginMeta().getAuthors().toString(); 60 | } 61 | 62 | /** 63 | * The placeholder identifier should go here.
64 | * This is what tells PlaceholderAPI to call our onRequest method to obtain a 65 | * value if a placeholder starts with our identifier.
66 | * This must be unique and can not contain % or _ 67 | * 68 | * @return The identifier in {@code %_%} as String. 69 | */ 70 | @Override 71 | public String getIdentifier() { 72 | return "townyflight"; 73 | } 74 | 75 | /** 76 | * This is the version of the expansion.
77 | * You don't have to use numbers, since it is set as a String. 78 | * 79 | * For convienience do we return the version from the plugin.yml 80 | * 81 | * @return The version as a String. 82 | */ 83 | @Override 84 | public String getVersion() { 85 | return plugin.getPluginMeta().getVersion(); 86 | } 87 | 88 | /** 89 | * This is the method called when a placeholder with our identifier is found and 90 | * needs a value.
91 | * We specify the value identifier in this method.
92 | * Since version 2.9.1 can you use OfflinePlayers in your requests. 93 | * 94 | * @param player A OfflinePlayer. 95 | * @param identifier A String containing the identifier/value. 96 | * 97 | * @return possibly-null String of the requested identifier. 98 | */ 99 | @Override 100 | public String onRequest(OfflinePlayer player, String identifier) { 101 | return ChatColor.translateAlternateColorCodes('&', getOfflinePlayerPlaceholder(player, identifier)); 102 | } 103 | 104 | private String getOfflinePlayerPlaceholder(OfflinePlayer offlineplayer, String identifier) { 105 | if (offlineplayer == null || !offlineplayer.isOnline()) 106 | return ""; 107 | 108 | Player player = offlineplayer.getPlayer(); 109 | 110 | switch (identifier) { 111 | case "can_player_fly": // %townyflight_can_player_fly% 112 | return String.valueOf(TownyFlightAPI.canFlyAccordingToCache(player)); 113 | case "temp_flight_seconds_remaining": // %townyflight_temp_flight_seconds_remaining% 114 | return TimeMgmt.getFormattedTimeValue(TempFlightTask.getSeconds(offlineplayer.getUniqueId()) * 1000L); 115 | case "temp_flight_seconds_remaining_raw": // %townyflight_temp_flight_seconds_remaining_raw% 116 | return String.valueOf(TempFlightTask.getSeconds(offlineplayer.getUniqueId())); 117 | 118 | default: 119 | return ""; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/TownyFlight.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight; 2 | 3 | import com.palmergames.bukkit.towny.scheduling.TaskScheduler; 4 | import com.palmergames.bukkit.towny.scheduling.impl.BukkitTaskScheduler; 5 | import com.palmergames.bukkit.towny.scheduling.impl.FoliaTaskScheduler; 6 | 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.HandlerList; 10 | import org.bukkit.plugin.Plugin; 11 | import org.bukkit.plugin.PluginManager; 12 | import org.bukkit.plugin.java.JavaPlugin; 13 | 14 | import com.gmail.llmdlio.townyflight.command.TownToggleFlightCommandAddon; 15 | import com.gmail.llmdlio.townyflight.command.TownyFlightCommand; 16 | import com.gmail.llmdlio.townyflight.config.Settings; 17 | import com.gmail.llmdlio.townyflight.config.TownyFlightConfig; 18 | import com.gmail.llmdlio.townyflight.integrations.TownyFlightPlaceholderExpansion; 19 | import com.gmail.llmdlio.townyflight.listeners.PlayerEnterTownListener; 20 | import com.gmail.llmdlio.townyflight.listeners.PlayerFallListener; 21 | import com.gmail.llmdlio.townyflight.listeners.PlayerJoinListener; 22 | import com.gmail.llmdlio.townyflight.listeners.PlayerLeaveTownListener; 23 | import com.gmail.llmdlio.townyflight.listeners.PlayerLogOutListener; 24 | import com.gmail.llmdlio.townyflight.listeners.PlayerPVPListener; 25 | import com.gmail.llmdlio.townyflight.listeners.PlayerTeleportListener; 26 | import com.gmail.llmdlio.townyflight.listeners.TownRemoveResidentListener; 27 | import com.gmail.llmdlio.townyflight.listeners.TownStatusScreenListener; 28 | import com.gmail.llmdlio.townyflight.listeners.TownUnclaimListener; 29 | import com.gmail.llmdlio.townyflight.tasks.TaskHandler; 30 | import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; 31 | import com.gmail.llmdlio.townyflight.util.MetaData; 32 | import com.palmergames.bukkit.util.Version; 33 | 34 | public class TownyFlight extends JavaPlugin { 35 | private static final Version requiredTownyVersion = Version.fromString("0.101.2.5"); 36 | private TownyFlightConfig config = new TownyFlightConfig(this); 37 | private static TownyFlight plugin; 38 | private static TownyFlightAPI api; 39 | private TownyFlightPlaceholderExpansion papiExpansion = null; 40 | private final TaskScheduler scheduler; 41 | 42 | public TownyFlight() { 43 | plugin = this; 44 | this.scheduler = isFoliaClassPresent() ? new FoliaTaskScheduler(this) : new BukkitTaskScheduler(this); 45 | } 46 | 47 | public void onEnable() { 48 | api = new TownyFlightAPI(this); 49 | String townyVersion = getServer().getPluginManager().getPlugin("Towny").getPluginMeta().getVersion(); 50 | 51 | if (!loadSettings()) { 52 | getLogger().severe("Config failed to load!"); 53 | disable(); 54 | return; 55 | } 56 | 57 | if (!townyVersionCheck(townyVersion)) { 58 | getLogger().severe("Towny version does not meet required version: " + requiredTownyVersion.toString()); 59 | disable(); 60 | return; 61 | } 62 | 63 | checkWarPlugins(); 64 | checkIntegrations(); 65 | registerEvents(); 66 | registerCommands(); 67 | getLogger().info("Towny version " + townyVersion + " found."); 68 | getLogger().info(this.getPluginMeta().getDisplayName() + " by LlmDl Enabled."); 69 | 70 | cycleTimerTasksOn(); 71 | reGrantTempFlightToOnlinePlayer(); 72 | } 73 | 74 | public static TownyFlight getPlugin() { 75 | return plugin; 76 | } 77 | 78 | /** 79 | * @return the API. 80 | */ 81 | public static TownyFlightAPI getAPI() { 82 | return api; 83 | } 84 | 85 | private void disable() { 86 | unregisterEvents(); 87 | getLogger().severe("TownyFlight Disabled."); 88 | } 89 | 90 | public boolean loadSettings() { 91 | return loadConfig() && Settings.loadSettings(config); 92 | } 93 | 94 | private boolean loadConfig() { 95 | if (!getDataFolder().exists()) 96 | getDataFolder().mkdirs(); 97 | return config.reload(); 98 | } 99 | 100 | private boolean townyVersionCheck(String version) { 101 | return Version.fromString(version).compareTo(requiredTownyVersion) >= 0; 102 | } 103 | 104 | private void checkWarPlugins() { 105 | Settings.siegeWarFound = getServer().getPluginManager().getPlugin("SiegeWar") != null; 106 | } 107 | 108 | 109 | private void checkIntegrations() { 110 | Plugin test; 111 | test = getServer().getPluginManager().getPlugin("PlaceholderAPI"); 112 | if (test != null) { 113 | papiExpansion = new TownyFlightPlaceholderExpansion(this); 114 | papiExpansion.register(); 115 | } 116 | } 117 | 118 | public void registerEvents() { 119 | final PluginManager pm = getServer().getPluginManager(); 120 | 121 | pm.registerEvents(new PlayerJoinListener(this), this); 122 | pm.registerEvents(new PlayerLogOutListener(), this); 123 | pm.registerEvents(new PlayerLeaveTownListener(this), this); 124 | pm.registerEvents(new TownRemoveResidentListener(this), this); 125 | pm.registerEvents(new TownUnclaimListener(this), this); 126 | pm.registerEvents(new PlayerFallListener(), this); 127 | pm.registerEvents(new PlayerTeleportListener(), this); 128 | pm.registerEvents(new TownStatusScreenListener(), this); 129 | pm.registerEvents(new PlayerEnterTownListener(this), this); 130 | 131 | if (Settings.disableCombatPrevention) 132 | pm.registerEvents(new PlayerPVPListener(), this); 133 | } 134 | 135 | public void unregisterEvents() { 136 | HandlerList.unregisterAll(this); 137 | } 138 | 139 | private void registerCommands() { 140 | getCommand("tfly").setExecutor(new TownyFlightCommand(this)); 141 | new TownToggleFlightCommandAddon(); 142 | } 143 | 144 | private void cycleTimerTasksOn() { 145 | cycleTimerTasksOff(); 146 | TaskHandler.toggleTempFlightTask(true); 147 | } 148 | 149 | private void cycleTimerTasksOff() { 150 | TaskHandler.toggleTempFlightTask(false); 151 | } 152 | 153 | private void reGrantTempFlightToOnlinePlayer() { 154 | for (Player player : Bukkit.getOnlinePlayers()) { 155 | long seconds = MetaData.getSeconds(player.getUniqueId()); 156 | if (seconds > 0L) 157 | TempFlightTask.addPlayerTempFlightSeconds(player.getUniqueId(), seconds); 158 | } 159 | } 160 | 161 | public TaskScheduler getScheduler() { 162 | return this.scheduler; 163 | } 164 | 165 | private static boolean isFoliaClassPresent() { 166 | try { 167 | Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); 168 | return true; 169 | } catch (ClassNotFoundException e) { 170 | return false; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/config/ConfigNodes.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.config; 2 | 3 | public enum ConfigNodes { 4 | VERSION("Version", ""), 5 | PLUGIN_PREFIX("pluginPrefix", 6 | "&8[&3TownyFlight&8] ", 7 | ""), 8 | LANG("language", "", "", 9 | "####################", 10 | "# Language Strings #", 11 | "####################"), 12 | LANG_FLIGHTON( 13 | "language.flightOnMsg", 14 | "Flight Activated. ", 15 | "", 16 | "# Message shown when flight activated."), 17 | LANG_FLIGHTOFF( 18 | "language.flightOffMsg", 19 | "Flight De-Activated. ", 20 | "", 21 | "# Message shown when flight de-activated."), 22 | LANG_NOTMAYOR( 23 | "language.notMayorMsg", 24 | "You are not the mayor of a town. ", 25 | "", 26 | "# Message shown when player lacks mayor status somewhere."), 27 | LANG_NOTOWN( 28 | "language.noTownMsg", 29 | "Flight cannot be activated, you don't belong to a town. ", 30 | "", 31 | "# Message shown when player lacks a town. "), 32 | LANG_NOTINTOWN( 33 | "language.notInTownMsg", 34 | "Flight cannot be activated, return to your town and try again. ", 35 | "", 36 | "# Message shown when flight cannot be turned on."), 37 | LANG_FLIGHTDEACTIVATEDMSG( 38 | "language.flightDeactivatedMsg", 39 | "Left town boundaries. ", 40 | "", 41 | "# Message shown when a player has flight taken away."), 42 | LANG_FLIGHTDEACTIVATEDPVPMSG( 43 | "language.flightDeactivatedPVPMsg", 44 | "Entering PVP combat. ", 45 | "", 46 | "# Message shown when a player has flight taken away because of PVP."), 47 | LANG_FLIGHTDEACTIVATEDCONSOLEMSG( 48 | "language.flightDeactivatedConsoleMsg", 49 | "Flight priviledges removed. ", 50 | "", 51 | "# Message shown when a player has flight taken away by console."), 52 | LANG_FLIGHTDEACTIVATEDTIMEMSG( 53 | "language.flightDeactivatedTimeMsg", 54 | "You ran out of flight time. ", 55 | "", 56 | "# Message shown when a player has flight taken away because their tempflight ran out."), 57 | LANG_NOPERMISSION( 58 | "language.noPermission", 59 | "You do not have permission for this command%s. ", 60 | "", 61 | "# Message shown when a player lacks a permission node."), 62 | LANG_MISSINGNODE( 63 | "language.missingNode", 64 | ", missing %s", 65 | "", 66 | "# Message attached to noPermission when options.show_Permission_After_No_Permission_Message is true"), 67 | LANG_NOTDURINGWAR( 68 | "language.notDuringWar", 69 | "You cannot use flight while Towny war is active. ", 70 | "", 71 | "# Message shown when war is active and flight is disallowed."), 72 | LANG_RETURNTOALLOWEDAREA( 73 | "language.returnToAllowedArea", 74 | "You have %s seconds to return to an allowed flight area. ", 75 | "", 76 | "# Message telling a player to return to an allowed flight area."), 77 | LANG_NOTOWNFOUND( 78 | "language.noTownFound", 79 | "TownyFlight cannot find a town by the name %s. ", 80 | "", 81 | "# Message when a town cannot be found by the name."), 82 | LANG_TOWNWIDEFLIGHT( 83 | "language.townWideFlight", 84 | "Free flight has been %s in %s. ", 85 | "", 86 | "# Message when a town has free flight enabled or disabled."), 87 | LANG_DISABLED( 88 | "language.disabled", 89 | "disabled", 90 | "", 91 | "# The word disabled."), 92 | LANG_ENABLED( 93 | "language.enabled", 94 | "enabled", 95 | "", 96 | "# The world enabled."), 97 | LANG_STATUSSCREENCOMP( 98 | "language.statusScreenComponent", 99 | "Free Flight", 100 | "", 101 | "# The component shown on towns' status screens when they have free flight enabled."), 102 | LANG_STATUSCOMPHOVER( 103 | "language.statusScreenComponentHover", 104 | "Flight enabled for everyone within this town's borders.", 105 | "", 106 | "# The hover text shown on the free flight status screen component."), 107 | LANG_TEMPFLIGHTGRANTEDTOPLAYER( 108 | "language.tempFlightGrantedToPlayer", 109 | "%s has had tempflight granted for %s.", 110 | "", 111 | "# The message shown to the admin/console when temp flight is given to a player."), 112 | LANG_YOUHAVEBEENGIVENTEMPFLIGHT( 113 | "language.youHaveReceivedTempFlight", 114 | "You have been given %s of tempflight priviledges.", 115 | "", 116 | "# The message shown to the player when they receive temp flight."), 117 | LANG_TEMPFLIGHTEXPIRED( 118 | "language.yourTempFlightHasExpired", 119 | "Your tempflight priviledges have expired.", 120 | "", 121 | "# The message shown to the player when they run out of temp flight."), 122 | 123 | 124 | OPTIONS("options", "", "", 125 | "#################", 126 | "# Options #", 127 | "#################"), 128 | OPTIONS_AUTO_ENABLE_FLIGHT( 129 | "options.auto_Enable_Flight", 130 | "false", 131 | "", 132 | "# If set to true, players entering their town will have flight auto-enabled.", 133 | "# When set to true, the plugin will use slightly more resources due to the EnterTown listener."), 134 | OPTIONS_AUTO_ENABLE_SILENT( 135 | "options.auto_Enable_Silent", 136 | "false", 137 | "", 138 | "# If set to true, players entering their town will have flight auto-enabled without being notified in chat."), 139 | OPTIONS_DISABLE_DURING_WARTIME( 140 | "options.disable_During_Wartime", 141 | "true", 142 | "", 143 | "# If set to false, players can still fly in their town while war is active."), 144 | OPTIONS_DISABLE_COMBAT_PREVENT( 145 | "options.disable_Combat_Prevention", 146 | "false", 147 | "", 148 | "# If set to false, TownyFlight will not prevent combat of flying people."), 149 | OPTIONS_TEMPFLIGHT_ALLOWED_AREAS( 150 | "options.tempflight_allowed_areas", 151 | "owntown,nationtowns", 152 | "", 153 | "# The list of areas which allow tempflight, allowed words: owntown, nationtowns, alliedtowns, alltowns, trustedtowns, wilderness"), 154 | OPTIONS_SHOW_PERMISSION( 155 | "options.show_Permission_After_No_Permission_Message", 156 | "true", 157 | "", 158 | "# If set to false, the language.noPermission message will not display the permission node."), 159 | OPTIONS_DISABLE_TIME( 160 | "options.flight_Disable_Timer", 161 | "3", 162 | "", 163 | "# Number of seconds after leaving an allowed flight area before flight is taken away.", "# Set to 0 to take flight away immediately."); 164 | 165 | private final String Root; 166 | private final String Default; 167 | private String[] comments; 168 | 169 | ConfigNodes(String root, String def, String... comments) { 170 | 171 | this.Root = root; 172 | this.Default = def; 173 | this.comments = comments; 174 | } 175 | 176 | /** 177 | * Retrieves the root for a config option 178 | * 179 | * @return The root for a config option 180 | */ 181 | public String getRoot() { 182 | 183 | return Root; 184 | } 185 | 186 | /** 187 | * Retrieves the default value for a config path 188 | * 189 | * @return The default value for a config path 190 | */ 191 | public String getDefault() { 192 | 193 | return Default; 194 | } 195 | 196 | /** 197 | * Retrieves the comment for a config path 198 | * 199 | * @return The comments for a config path 200 | */ 201 | public String[] getComments() { 202 | 203 | if (comments != null) { 204 | return comments; 205 | } 206 | 207 | String[] comments = new String[1]; 208 | comments[0] = ""; 209 | return comments; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/TownyFlightAPI.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.UUID; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.GameMode; 11 | import org.bukkit.Location; 12 | import org.bukkit.NamespacedKey; 13 | import org.bukkit.entity.Player; 14 | 15 | import com.gmail.goosius.siegewar.SiegeController; 16 | import com.gmail.goosius.siegewar.enums.SiegeStatus; 17 | import com.gmail.llmdlio.townyflight.config.Settings; 18 | import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; 19 | import com.gmail.llmdlio.townyflight.util.Message; 20 | import com.gmail.llmdlio.townyflight.util.MetaData; 21 | import com.gmail.llmdlio.townyflight.util.Permission; 22 | import com.palmergames.bukkit.towny.TownyAPI; 23 | import com.palmergames.bukkit.towny.TownyUniverse; 24 | import com.palmergames.bukkit.towny.object.Resident; 25 | import com.palmergames.bukkit.towny.object.Town; 26 | import com.palmergames.bukkit.towny.utils.CombatUtil; 27 | import org.bukkit.persistence.PersistentDataType; 28 | 29 | public class TownyFlightAPI { 30 | 31 | private static TownyFlight plugin; 32 | private static TownyFlightAPI instance; 33 | public Set fallProtectedPlayers = ConcurrentHashMap.newKeySet(); 34 | private static Map canFlyCache = new ConcurrentHashMap<>(); 35 | private static NamespacedKey forceAllowFlight; 36 | 37 | public TownyFlightAPI(TownyFlight plugin) { 38 | TownyFlightAPI.plugin = plugin; 39 | forceAllowFlight = new NamespacedKey(plugin, "force_allow_flight"); 40 | } 41 | 42 | public static TownyFlightAPI getInstance() { 43 | if (instance == null) 44 | instance = new TownyFlightAPI(plugin); 45 | return instance; 46 | } 47 | 48 | /** 49 | * Returns true if a player can fly according to TownyFlight's rules. 50 | * 51 | * @param player {@link Player} to test for flight allowance. 52 | * @param silent true will show messages to player. 53 | * @return true if the {@link Player} is allowed to fly. 54 | **/ 55 | public boolean canFly(Player player, boolean silent) { 56 | if (player.hasPermission("townyflight.bypass") 57 | || player.getGameMode().equals(GameMode.SPECTATOR) 58 | || player.getGameMode().equals(GameMode.CREATIVE) 59 | || getForceAllowFlight(player)) 60 | return true; 61 | 62 | if (hasTempFlight(player) && tempFlightAllowsLocation(player)) 63 | return true; 64 | 65 | if (!hasTempFlight(player) && !Permission.has(player, "townyflight.command.tfly", silent)) return false; 66 | 67 | Resident resident = TownyUniverse.getInstance().getResident(player.getUniqueId()); 68 | if (resident == null) return false; 69 | 70 | if (warPrevents(player.getLocation(), resident)) { 71 | if (!silent) Message.of("notDuringWar").to(player); 72 | return false; 73 | } 74 | 75 | if (!allowedLocation(player, player.getLocation(), resident)) { 76 | if (!silent) Message.of("notInTownMsg").to(player); 77 | return false; 78 | } 79 | return true; 80 | } 81 | 82 | /** 83 | * Returns true when a player is at a suitable location, matching the allowed 84 | * areas in config.yml. 85 | * 86 | * @param player Player to test. 87 | * @return true when tempflight is allowed here. 88 | */ 89 | private boolean tempFlightAllowsLocation(Player player) { 90 | Location location = player.getLocation(); 91 | Resident resident = TownyAPI.getInstance().getResident(player); 92 | if (resident == null) 93 | return false; 94 | 95 | if (TownyAPI.getInstance().isWilderness(location)) 96 | return Settings.isAllowedTempFlightArea("wilderness"); 97 | 98 | if (Settings.isAllowedTempFlightArea("alltowns")) 99 | return true; 100 | 101 | Town town = TownyAPI.getInstance().getTown(location); 102 | if (Settings.isAllowedTempFlightArea("owntown") && town.hasResident(resident)) 103 | return true; 104 | 105 | if (Settings.isAllowedTempFlightArea("trustedtowns") && town.getTrustedResidents().contains(resident)) 106 | return true; 107 | 108 | if (!town.hasNation() || !resident.hasTown()) 109 | return false; 110 | 111 | Town residentTown = resident.getTownOrNull(); 112 | if (Settings.isAllowedTempFlightArea("nationtowns") && CombatUtil.isSameNation(town, residentTown)) 113 | return true; 114 | 115 | if (Settings.isAllowedTempFlightArea("alliedtowns") && CombatUtil.isAlly(town, residentTown)) 116 | return true; 117 | 118 | return false; 119 | } 120 | 121 | 122 | /** 123 | * Returns true if a player is allowed to fly at their current location. Checks 124 | * if they are in the wilderness, in their own town and if not, whether they 125 | * have the alliedtowns permission and if they are in an allied area. 126 | * 127 | * @param player The {@link Player}. 128 | * @param location The {@link Location} to test for the player. 129 | * @param resident The {@link Resident} of the {@link Player}. 130 | * @return true if player is allowed to be flying at their present location. 131 | */ 132 | public static boolean allowedLocation(Player player, Location location, Resident resident) { 133 | if (instance.getForceAllowFlight(player)) 134 | return true; 135 | 136 | if (TownyAPI.getInstance().isWilderness(location)) 137 | return player.hasPermission("townyflight.wilderness"); 138 | 139 | Town town = TownyAPI.getInstance().getTown(location); 140 | 141 | if (player.hasPermission("townyflight.alltowns")) 142 | return true; 143 | 144 | if (player.hasPermission("townyflight.trustedtowns") && town.hasTrustedResident(resident)) 145 | return true; 146 | 147 | if (MetaData.getFreeFlightMeta(town)) 148 | return true; 149 | 150 | if (!resident.hasTown()) 151 | return false; 152 | 153 | Town residentTown = resident.getTownOrNull(); 154 | if (residentTown == null) 155 | return false; 156 | 157 | if (residentTown.getUUID() == town.getUUID()) 158 | return true; 159 | 160 | if (player.hasPermission("townyflight.nationtowns") && CombatUtil.isSameNation(residentTown, town)) 161 | return true; 162 | 163 | if (player.hasPermission("townyflight.alliedtowns") && CombatUtil.isAlly(town, residentTown)) 164 | return true; 165 | 166 | return false; 167 | } 168 | 169 | /** 170 | * Turn off flight from a {@link Player}. 171 | * 172 | * @param player the {@link Player} to take flight from. 173 | * @param silent true will mean no message is shown to the {@link Player}. 174 | * @param forced true if this is a forced deactivation or not. 175 | * @param cause String cause of disabling flight ("", "pvp", "console"). 176 | */ 177 | @SuppressWarnings("deprecation") 178 | public void removeFlight(Player player, boolean silent, boolean forced, String cause) { 179 | if (!silent) { 180 | if (forced) { 181 | String reason = Message.getLangString("flightDeactivatedMsg"); 182 | if (cause == "pvp") reason = Message.getLangString("flightDeactivatedPVPMsg"); 183 | if (cause == "console") reason = Message.getLangString("flightDeactivatedConsoleMsg"); 184 | if (cause == "time") reason = Message.getLangString("flightDeactivatedTimeMsg"); 185 | Message.of(reason + Message.getLangString("flightOffMsg")).to(player); 186 | } else { 187 | Message.of("flightOffMsg").to(player); 188 | } 189 | } 190 | if (player.isFlying()) { 191 | // As of 1.15 the below line does not seem to be reliable. 192 | player.setFallDistance(-100000); 193 | // As of 1.15 the below is required. 194 | if (!player.isOnGround()) 195 | protectFromFall(player); 196 | } 197 | player.setAllowFlight(false); 198 | cachePlayerFlight(player, false); 199 | } 200 | 201 | /** 202 | * Turn flight on for a {@link Player}. 203 | * 204 | * @param player {@link Player} who receives flight. 205 | * @param silent true will mean no message is shown to the {@link Player}. 206 | */ 207 | public void addFlight(Player player, boolean silent) { 208 | if (!silent) Message.of("flightOnMsg").to(player); 209 | player.setAllowFlight(true); 210 | cachePlayerFlight(player, true); 211 | } 212 | 213 | /** 214 | * Protects a player from receiving fall damage when their flight is revoked. 215 | * 216 | * @param player {@link Player} who is losing their flight. 217 | */ 218 | public void protectFromFall(Player player) { 219 | fallProtectedPlayers.add(player); 220 | plugin.getScheduler().runAsyncLater(() -> removeFallProtection(player), 100); 221 | } 222 | 223 | public boolean removeFallProtection(Player player) { 224 | return fallProtectedPlayers.remove(player); 225 | } 226 | 227 | /** 228 | * Check if the {@link Player} is able to fly, and remove it if unabled. 229 | * 230 | * @param player {@link Player} who is being tested. 231 | * @param silent true will mean no message is shown to the {@link Player}. 232 | */ 233 | public void testForFlight(Player player, boolean silent) { 234 | if (!canFly(player, silent)) removeFlight(player, false, true, ""); 235 | } 236 | 237 | /** 238 | * Parse over the players online in the server and if they're in the given {@link Town}, 239 | * and are not given a flight bypass of some kind, remove their flight. Called when 240 | * a town has their free flight disabled. 241 | */ 242 | public void takeFlightFromPlayersInTown(Town town) { 243 | for (final Player player : new ArrayList<>(Bukkit.getOnlinePlayers())) { 244 | if (player.hasPermission("townyflight.bypass") 245 | || !player.getAllowFlight() 246 | || TownyAPI.getInstance().isWilderness(player.getLocation()) 247 | || !TownyAPI.getInstance().getTown(player.getLocation()).equals(town) 248 | || TownyFlightAPI.getInstance().canFly(player, true)) 249 | continue; 250 | 251 | TownyFlightAPI.getInstance().removeFlight(player, false, true, ""); 252 | } 253 | } 254 | 255 | /** 256 | * This method allows the player to fly everywhere, even if they are not in a town. Useful for 257 | * plugins that have items that can make you fly. 258 | * 259 | * @param player {@link Player} who is flying. 260 | * @param force true will allow the player to fly anywhere. 261 | */ 262 | public void setForceAllowFlight(Player player, boolean force) { 263 | player.getPersistentDataContainer().set(forceAllowFlight, PersistentDataType.BYTE, (byte) (force ? 1 : 0)); 264 | } 265 | 266 | /** 267 | * This method checks if the player is allowed to fly anywhere 268 | * 269 | * @param player {@link Player} who is being checked. 270 | * @return true if the player is allowed to fly anywhere. 271 | */ 272 | public boolean getForceAllowFlight(Player player) { 273 | return player.getPersistentDataContainer().getOrDefault(forceAllowFlight, PersistentDataType.BYTE, (byte) 0) == 1; 274 | } 275 | 276 | private boolean hasTempFlight(Player player) { 277 | return TempFlightTask.getSeconds(player.getUniqueId()) > 0L; 278 | } 279 | 280 | private boolean warPrevents(Location location, Resident resident) { 281 | return Settings.disableDuringWar && (townHasActiveWar(location, resident) || residentIsSieged(location)); 282 | } 283 | 284 | private static boolean townHasActiveWar(Location loc, Resident resident) { 285 | return (resident.hasTown() && resident.getTownOrNull().hasActiveWar()) || (!TownyAPI.getInstance().isWilderness(loc) && TownyAPI.getInstance().getTown(loc).hasActiveWar()); 286 | } 287 | 288 | private static boolean residentIsSieged(Location location) { 289 | Town town = TownyAPI.getInstance().getTown(location); 290 | return Settings.siegeWarFound && town != null && SiegeController.hasSiege(town) && SiegeController.getSiege(town).getStatus().equals(SiegeStatus.IN_PROGRESS); 291 | } 292 | 293 | public static boolean canFlyAccordingToCache(Player player) { 294 | if (!canFlyCache.containsKey(player.getUniqueId())) 295 | cachePlayerFlight(player, TownyFlightAPI.getInstance().canFly(player, true)); 296 | 297 | return canFlyCache.get(player.getUniqueId()); 298 | } 299 | 300 | public static void cachePlayerFlight(Player player, boolean canFly) { 301 | if (canFlyCache.containsKey(player.getUniqueId()) && canFlyCache.get(player.getUniqueId()) == canFly) 302 | return; 303 | canFlyCache.put(player.getUniqueId(), canFly); 304 | } 305 | 306 | public static void removeCachedPlayer(Player player) { 307 | canFlyCache.remove(player.getUniqueId()); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/main/java/com/gmail/llmdlio/townyflight/command/TownyFlightCommand.java: -------------------------------------------------------------------------------- 1 | package com.gmail.llmdlio.townyflight.command; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.command.Command; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.command.ConsoleCommandSender; 13 | import org.bukkit.command.TabExecutor; 14 | import org.bukkit.entity.Player; 15 | 16 | import com.gmail.llmdlio.townyflight.TownyFlight; 17 | import com.gmail.llmdlio.townyflight.TownyFlightAPI; 18 | import com.gmail.llmdlio.townyflight.config.Settings; 19 | import com.gmail.llmdlio.townyflight.tasks.TempFlightTask; 20 | import com.gmail.llmdlio.townyflight.util.Message; 21 | import com.gmail.llmdlio.townyflight.util.MetaData; 22 | import com.gmail.llmdlio.townyflight.util.Permission; 23 | import com.palmergames.bukkit.towny.TownyAPI; 24 | import com.palmergames.bukkit.towny.TownyUniverse; 25 | import com.palmergames.bukkit.towny.object.Resident; 26 | import com.palmergames.bukkit.towny.object.Town; 27 | import com.palmergames.bukkit.towny.utils.NameUtil; 28 | import com.palmergames.bukkit.util.Colors; 29 | import com.palmergames.util.StringMgmt; 30 | import com.palmergames.util.TimeMgmt; 31 | import com.palmergames.util.TimeTools; 32 | 33 | public class TownyFlightCommand implements TabExecutor { 34 | 35 | private TownyFlight plugin; 36 | private CommandSender sender; 37 | private static final List tflyTabCompletes = Arrays.asList( 38 | "reload","tempflight","town","help","?" 39 | ); 40 | 41 | public TownyFlightCommand(TownyFlight plugin) { 42 | this.plugin = plugin; 43 | } 44 | 45 | @Override 46 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 47 | if (args.length > 0) { 48 | switch (args[0].toLowerCase()) { 49 | case "tempflight": 50 | if (args.length == 2) 51 | return getTownyStartingWith(args[1], "r"); 52 | if (args.length == 3) 53 | return NameUtil.filterByStart(Arrays.asList("remove", "10", "1000s", "60m", "1h", "1d"), args[2]); 54 | case "town": 55 | if (args.length == 2) 56 | return getTownyStartingWith(args[1], "t"); 57 | if (args.length == 3) 58 | return Collections.singletonList("toggleflight"); 59 | break; 60 | default: 61 | if (args.length == 1) 62 | return filterByStartOrGetTownyStartingWith(tflyTabCompletes, args[0], "r"); 63 | else 64 | Collections.emptyList(); 65 | } 66 | } 67 | return Collections.emptyList(); 68 | } 69 | 70 | @Override 71 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 72 | this.sender = sender; 73 | parseCommand(args); 74 | return true; 75 | } 76 | 77 | private void parseCommand(String[] args) { 78 | if (sender instanceof ConsoleCommandSender) { 79 | if (args.length > 0) { 80 | if (args[0].equalsIgnoreCase("?") || args[0].equalsIgnoreCase("help")) { 81 | showTflyHelp(); 82 | } else if (args[0].equalsIgnoreCase("reload")) { 83 | // Reload the plugin. 84 | reloadPlugin(); 85 | } else if (args[0].equalsIgnoreCase("tempflight")) { 86 | // We have /tfly tempflight and tested for permission node. 87 | parseTempFlightCommand(StringMgmt.remFirstArg(args)); 88 | } else if (args[0].equalsIgnoreCase("town")) { 89 | // parse /tfly town NAME OPTION command. 90 | parseTownCommand(StringMgmt.remFirstArg(args)); 91 | } else { 92 | // It's not any other subcommand of /tfly so handle removing flight via /tfly {name} 93 | toggleFlightOnOther(args[0]); 94 | } 95 | } 96 | return; 97 | } 98 | 99 | if (sender instanceof Player) { 100 | // We have more than just /tfly 101 | if (args.length > 0) { 102 | if (args[0].equalsIgnoreCase("?") || args[0].equalsIgnoreCase("help")) { 103 | showTflyHelp(); 104 | } else if (args[0].equalsIgnoreCase("reload") && Permission.has(sender,"townyflight.command.tfly.reload", false)) { 105 | // We have /tfly reload and tested for permission node. 106 | reloadPlugin(); 107 | } else if (args[0].equalsIgnoreCase("tempflight") && Permission.has(sender,"townyflight.command.tfly.tempflight", false)) { 108 | // We have /tfly tempflight and tested for permission node. 109 | parseTempFlightCommand(StringMgmt.remFirstArg(args)); 110 | } else if (args[0].equalsIgnoreCase("town") && Permission.has(sender,"townyflight.command.tfly.town", false)) { 111 | // parse /tfly town NAME OPTION command. 112 | parseTownCommand(StringMgmt.remFirstArg(args)); 113 | } else { 114 | // It's not any other subcommand of /tfly so handle removing flight via /tfly {name} 115 | if (Permission.has(sender, "townyflight.command.tfly.other", false)) 116 | toggleFlightOnOther(args[0]); 117 | } 118 | return; 119 | } 120 | 121 | // We have only /tfly 122 | if (!TownyFlightAPI.getInstance().canFly((Player) sender, false)) 123 | return; 124 | 125 | toggleFlight((Player) sender, false, false, ""); 126 | } 127 | } 128 | 129 | private void parseTempFlightCommand(String[] args) { 130 | if (args.length < 2) { 131 | showTflyHelp(); 132 | return; 133 | } 134 | 135 | String name = args[0]; 136 | Player player = Bukkit.getPlayerExact(name); 137 | UUID uuid = player != null ? player.getUniqueId() : null; 138 | if (uuid == null && TownyUniverse.getInstance().hasResident(name)) { 139 | Resident resident = TownyAPI.getInstance().getResident(name); 140 | if (resident != null && resident.hasUUID()) 141 | uuid = resident.getUUID(); 142 | } 143 | 144 | if (uuid == null) { 145 | Message.of("Player " + name + " not found. Could not grant temp flight.").to(sender); 146 | return; 147 | } 148 | 149 | if (args[1].equalsIgnoreCase("remove")) { 150 | TempFlightTask.removeAllPlayerTempFlightSeconds(uuid); 151 | Message.of(name + " has had their flight time set to 0.").to(sender); 152 | return; 153 | } 154 | 155 | long seconds = parseSeconds(args[1]); 156 | if (seconds == 0L) { 157 | Message.of("Could not grant 0 seconds of temp flight.").to(sender); 158 | return; 159 | } 160 | 161 | String formattedTimeValue = TimeMgmt.getFormattedTimeValue(seconds * 1000L); 162 | Message.of(String.format(Message.getLangString("tempFlightGrantedToPlayer"), name, formattedTimeValue)).to(sender); 163 | MetaData.addTempFlight(uuid, seconds); 164 | 165 | if (player != null && player.isOnline()) { 166 | TempFlightTask.addPlayerTempFlightSeconds(uuid, seconds); 167 | Message.of(String.format(Message.getLangString("youHaveReceivedTempFlight"), formattedTimeValue)).to(player); 168 | 169 | if (Settings.autoEnableFlight && TownyFlightAPI.getInstance().canFly(player, true)) 170 | TownyFlightAPI.getInstance().addFlight(player, Settings.autoEnableSilent); 171 | } 172 | 173 | } 174 | 175 | private long parseSeconds(String string) { 176 | if (string.endsWith("s") || string.endsWith("m") || string.endsWith("h") || string.endsWith("d")) 177 | return TimeTools.getSeconds(string); 178 | 179 | long seconds; 180 | try { 181 | seconds = Long.valueOf(string); 182 | } catch (NumberFormatException e) { 183 | Message.of("The number " + string + " cannot be parsed into a number of seconds.").to(sender); 184 | return 0L; 185 | } 186 | return seconds; 187 | } 188 | 189 | private void parseTownCommand(String[] args) { 190 | if (args.length < 2) { 191 | showTflyHelp(); 192 | return; 193 | } 194 | 195 | Town town = TownyAPI.getInstance().getTown(args[0]); 196 | if (town == null) { 197 | Message.of(String.format(Message.getLangString("noTownFound"), args[0])).serious().to(sender); 198 | return; 199 | } 200 | 201 | if (args[1].equalsIgnoreCase("toggleflight")) { 202 | boolean futurestate = !MetaData.getFreeFlightMeta(town); 203 | MetaData.setFreeFlightMeta(town, futurestate); 204 | Message.of(String.format(Message.getLangString("townWideFlight"), Message.getLangString(futurestate? "enabled" : "disabled"), town)).to(sender); 205 | if (!futurestate) 206 | TownyFlightAPI.getInstance().takeFlightFromPlayersInTown(town); 207 | return; 208 | } 209 | 210 | showTflyHelp(); 211 | } 212 | 213 | 214 | private void showTflyHelp() { 215 | if (Permission.has(sender, "townyflight.command.tfly", true)) 216 | Message.of(Colors.White + "/tfly - Toggle flight.").to(sender); 217 | if (Permission.has(sender, "townyflight.command.tfly.reload", true)) 218 | Message.of(Colors.White + "/tfly reload - Reload the TownyFlight config.").to(sender); 219 | if (Permission.has(sender, "townyflight.command.tfly.tempflight", true)) { 220 | Message.of(Colors.White + "/tfly tempflight [playername] [seconds] - Grant a player temp flight in seconds.").to(sender); 221 | Message.of(Colors.White + "/tfly tempflight [playername] [time]m - Grant a player temp flight in minutes.").to(sender); 222 | Message.of(Colors.White + "/tfly tempflight [playername] [time]h - Grant a player temp flight in hours.").to(sender); 223 | Message.of(Colors.White + "/tfly tempflight [playername] [time]d - Grant a player temp flight in days.").to(sender); 224 | Message.of(Colors.White + "/tfly tempflight [playername] remove - Remove a player's temp flight.").to(sender); 225 | } 226 | if (Permission.has(sender, "townyflight.command.tfly.other", true)) 227 | Message.of(Colors.White + "/tfly [playername] - Toggle flight for a player.").to(sender); 228 | if (Permission.has(sender, "townyflight.command.tfly.town", true)) 229 | Message.of(Colors.White + "/tfly town [townname] toggleflight - Toggle free flight in the given town.").to(sender); 230 | } 231 | 232 | /** 233 | * If flight is on, turn it off and vice versa 234 | * 235 | * @param player {@link Player} toggling flight. 236 | * @param silent true will mean no message is shown to the {@link Player}. 237 | * @param forced true if this is a forced deactivation or not. 238 | * @param cause String cause of disabling flight ("", "pvp", "console"). 239 | */ 240 | public void toggleFlight(Player player, boolean silent, boolean forced, String cause) { 241 | if (player.getAllowFlight()) 242 | TownyFlightAPI.getInstance().removeFlight(player, silent, forced, cause); 243 | else 244 | TownyFlightAPI.getInstance().addFlight(player, silent); 245 | } 246 | 247 | public void toggleFlightOnOther(String name) { 248 | Player player = Bukkit.getPlayerExact(name); 249 | if (player != null && player.isOnline()) { 250 | if (!player.getAllowFlight()) { 251 | Message.of("Player " + name + " is already unable to fly. Could not remove flight.").to(sender); 252 | } else { 253 | toggleFlight(player.getPlayer(), false, true, "console"); 254 | Message.of("Flight removed from " + name + ".").to(sender); 255 | } 256 | } else { 257 | Message.of("Player " + name + " not found, or is offline. Could not remove flight.").to(sender); 258 | } 259 | } 260 | 261 | public void reloadPlugin() { 262 | plugin.loadSettings(); 263 | plugin.unregisterEvents(); 264 | plugin.registerEvents(); 265 | Message.of("TownyFlight Config & Listeners reloaded.").to(sender); 266 | } 267 | 268 | 269 | /** 270 | * Returns a List containing strings of resident, town, and/or nation names that match with arg. 271 | * Can check for multiple types, for example "rt" would check for residents and towns but not nations or worlds. 272 | * 273 | * @param arg the string to match with the chosen type 274 | * @param type the type of Towny object to check for, can be r(esident), t(own), n(ation), w(orld), or any combination of those to check 275 | * @return Matches for the arg with the chosen type 276 | */ 277 | static List getTownyStartingWith(String arg, String type) { 278 | 279 | List matches = new ArrayList<>(); 280 | TownyUniverse townyUniverse = TownyUniverse.getInstance(); 281 | 282 | if (type.contains("r")) { 283 | matches.addAll(townyUniverse.getResidentsTrie().getStringsFromKey(arg)); 284 | } 285 | 286 | if (type.contains("t")) { 287 | matches.addAll(townyUniverse.getTownsTrie().getStringsFromKey(arg)); 288 | } 289 | 290 | if (type.contains("n")) { 291 | matches.addAll(townyUniverse.getNationsTrie().getStringsFromKey(arg)); 292 | } 293 | 294 | if (type.contains("w")) { // There aren't many worlds so check even if arg is empty 295 | matches.addAll(NameUtil.filterByStart(NameUtil.getNames(townyUniverse.getWorldMap().values()), arg)); 296 | } 297 | 298 | return matches; 299 | } 300 | 301 | /** 302 | * Checks if arg starts with filters, if not returns matches from {@link #getTownyStartingWith(String, String)}. 303 | * Add a "+" to the type to return both cases 304 | * 305 | * @param filters the strings to filter arg with 306 | * @param arg the string to check with filters and possibly match with Towny objects if no filters are found 307 | * @param type the type of check to use, see {@link #getTownyStartingWith(String, String)} for possible types. Add "+" to check for both filters and {@link #getTownyStartingWith(String, String)} 308 | * @return Matches for the arg filtered by filters or checked with type 309 | */ 310 | static List filterByStartOrGetTownyStartingWith(List filters, String arg, String type) { 311 | List filtered = NameUtil.filterByStart(filters, arg); 312 | if (type.isEmpty()) 313 | return filtered; 314 | else if (type.contains("+")) { 315 | filtered.addAll(getTownyStartingWith(arg, type)); 316 | return filtered; 317 | } else { 318 | if (filtered.size() > 0) { 319 | return filtered; 320 | } else { 321 | return getTownyStartingWith(arg, type); 322 | } 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## [Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0)](https://creativecommons.org/licenses/by-nc-nd/3.0/) 2 | 3 | ### License 4 | 5 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 6 | 7 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 8 | 9 | **1. Definitions** 10 | 11 |
    12 |
  1. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 13 | 14 |
  2. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. 15 | 16 |
  3. "Distribute" means to make available to the public the original and copies of the Work through sale or other transfer of ownership. 17 | 18 |
  4. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 19 | 20 |
  5. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 21 | 22 |
  6. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 23 | 24 |
  7. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 25 | 26 |
  8. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 27 | 28 |
  9. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 29 |
30 | 31 | **2. Fair Dealing Rights.** Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 32 | 33 | **3. License Grant.** Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 34 | 35 |
    36 |
  1. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; and, 37 |
  2. to Distribute and Publicly Perform the Work including as incorporated in Collections. 38 |
39 | 40 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats, but otherwise you have no rights to make Adaptations. Subject to 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Section 4(d). 41 | 42 | **4. Restrictions.** The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 43 | 44 |
    45 | 46 |
  1. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. 47 | 48 |
  2. You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works. 49 | 50 |
  3. If You Distribute, or Publicly Perform the Work or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work. The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Collection, at a minimum such credit will appear, if a credit for all contributing authors of Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 51 | 52 |
  4. For the avoidance of doubt: 53 |
      54 |
    1. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 55 | 56 |
    2. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and, 57 | 58 |
    3. Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b). 59 |
    60 | 61 |
  5. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. 62 |
63 | 64 | **5. Representations, Warranties and Disclaimer** 65 | 66 | UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 67 | 68 | **6. Limitation on Liability.** EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 69 | 70 | **7. Termination** 71 | 72 |
    73 |
  1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 74 | 75 |
  2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 76 |
77 | 78 | **8. Miscellaneous** 79 | 80 |
    81 |
  1. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 82 | 83 |
  2. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 84 | 85 |
  3. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 86 | 87 |
  4. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 88 | 89 |
  5. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 90 |
91 | 92 | > #### Creative Commons Notice 93 | >Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 94 | > 95 | >Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. 96 | > 97 | > Creative Commons may be contacted at [https://creativecommons.org/](https://creativecommons.org/). 98 | --------------------------------------------------------------------------------