From 733cd22c294a7bf2dccdff4b6b4bad1e6abde62e Mon Sep 17 00:00:00 2001 From: Auxilor Date: Tue, 8 Dec 2020 18:55:16 +0000 Subject: [PATCH] Initial commit --- .gitignore | 23 ++ LICENSE.md | 44 +++ NMS/API/build.gradle | 9 + .../illusioner/nms/api/BlockBreakWrapper.java | 11 + .../illusioner/nms/api/CooldownWrapper.java | 10 + .../nms/api/EntityIllusionerWrapper.java | 12 + .../illusioner/nms/api/IllusionerWrapper.java | 10 + .../nms/api/OpenInventoryWrapper.java | 7 + .../nms/api/TridentStackWrapper.java | 11 + NMS/v1_15_R1/build.gradle | 10 + .../illusioner/v1_15_R1/BlockBreak.java | 14 + .../willfp/illusioner/v1_15_R1/Cooldown.java | 14 + .../illusioner/v1_15_R1/OpenInventory.java | 12 + .../illusioner/v1_15_R1/TridentStack.java | 16 ++ NMS/v1_16_R1/build.gradle | 10 + .../illusioner/v1_16_R1/BlockBreak.java | 14 + .../willfp/illusioner/v1_16_R1/Cooldown.java | 11 + .../illusioner/v1_16_R1/OpenInventory.java | 12 + .../illusioner/v1_16_R1/TridentStack.java | 16 ++ NMS/v1_16_R2/build.gradle | 10 + .../illusioner/v1_16_R2/BlockBreak.java | 14 + .../willfp/illusioner/v1_16_R2/Cooldown.java | 11 + .../illusioner/v1_16_R2/OpenInventory.java | 12 + .../illusioner/v1_16_R2/TridentStack.java | 16 ++ NMS/v1_16_R3/build.gradle | 10 + .../illusioner/v1_16_R3/BlockBreak.java | 14 + .../willfp/illusioner/v1_16_R3/Cooldown.java | 11 + .../illusioner/v1_16_R3/EntityIllusioner.java | 64 +++++ .../illusioner/v1_16_R3/Illusioner.java | 15 + .../illusioner/v1_16_R3/OpenInventory.java | 12 + .../illusioner/v1_16_R3/TridentStack.java | 16 ++ Plugin/build.gradle | 61 ++++ .../willfp/illusioner/IllusionerPlugin.java | 49 ++++ .../illusioner/command/AbstractCommand.java | 71 +++++ .../command/AbstractTabCompleter.java | 31 ++ .../command/commands/CommandIldebug.java | 41 +++ .../command/commands/CommandIlreload.java | 20 ++ .../illusioner/config/ConfigManager.java | 34 +++ .../illusioner/config/UpdatingYamlConfig.java | 77 +++++ .../illusioner/config/configs/Config.java | 55 ++++ .../illusioner/config/configs/Lang.java | 36 +++ .../events/armorequip/ArmorEquipEvent.java | 123 ++++++++ .../events/armorequip/ArmorListener.java | 204 +++++++++++++ .../events/armorequip/ArmorType.java | 38 +++ .../armorequip/DispenserArmorListener.java | 28 ++ .../EntityDeathByEntityBuilder.java | 58 ++++ .../EntityDeathByEntityEvent.java | 115 ++++++++ .../EntityDeathByEntityListeners.java | 69 +++++ .../NaturalExpGainBuilder.java | 46 +++ .../NaturalExpGainEvent.java | 46 +++ .../NaturalExpGainListeners.java | 56 ++++ .../illusioner/AttackListeners.java | 105 +++++++ .../illusioner/illusioner/DeathListeners.java | 24 ++ .../illusioner/illusioner/SpawnListeners.java | 36 +++ .../illusioner/integrations/Integration.java | 12 + .../anticheat/AnticheatManager.java | 49 ++++ .../anticheat/AnticheatWrapper.java | 23 ++ .../anticheat/plugins/AnticheatAAC.java | 40 +++ .../anticheat/plugins/AnticheatMatrix.java | 40 +++ .../anticheat/plugins/AnticheatNCP.java | 37 +++ .../anticheat/plugins/AnticheatSpartan.java | 40 +++ .../antigrief/AntigriefManager.java | 66 +++++ .../antigrief/AntigriefWrapper.java | 48 ++++ .../plugins/AntigriefFactionsUUID.java | 71 +++++ .../plugins/AntigriefGriefPrevention.java | 56 ++++ .../antigrief/plugins/AntigriefKingdoms.java | 60 ++++ .../antigrief/plugins/AntigriefLands.java | 64 +++++ .../antigrief/plugins/AntigriefTowny.java | 50 ++++ .../plugins/AntigriefWorldGuard.java | 79 +++++ .../anvilgui/AnvilGUIIntegration.java | 17 ++ .../anvilgui/AnvilGUIManager.java | 35 +++ .../anvilgui/plugins/AnvilGUIImpl.java | 21 ++ .../integrations/mcmmo/McmmoIntegration.java | 14 + .../integrations/mcmmo/McmmoManager.java | 41 +++ .../mcmmo/plugins/McmmoIntegrationImpl.java | 20 ++ .../placeholder/PlaceholderEntry.java | 65 +++++ .../placeholder/PlaceholderIntegration.java | 24 ++ .../placeholder/PlaceholderManager.java | 67 +++++ .../plugins/PlaceholderIntegrationPAPI.java | 59 ++++ .../com/willfp/illusioner/nms/BlockBreak.java | 39 +++ .../com/willfp/illusioner/nms/Cooldown.java | 38 +++ .../willfp/illusioner/nms/NMSIllusioner.java | 41 +++ .../willfp/illusioner/nms/OpenInventory.java | 38 +++ .../willfp/illusioner/nms/TridentStack.java | 40 +++ .../willfp/illusioner/util/BlockUtils.java | 37 +++ .../willfp/illusioner/util/ClassUtils.java | 19 ++ .../illusioner/util/DurabilityUtils.java | 112 ++++++++ .../illusioner/util/LightningUtils.java | 26 ++ .../willfp/illusioner/util/NumberUtils.java | 148 ++++++++++ .../willfp/illusioner/util/StringUtils.java | 94 ++++++ .../willfp/illusioner/util/VectorUtils.java | 141 +++++++++ .../illusioner/util/interfaces/Callable.java | 9 + .../util/interfaces/ObjectBiCallable.java | 12 + .../util/interfaces/ObjectCallable.java | 11 + .../util/interfaces/Registerable.java | 8 + .../illusioner/util/internal/Loader.java | 270 ++++++++++++++++++ .../illusioner/util/internal/Logger.java | 39 +++ .../internal/updater/PlayerJoinListener.java | 19 ++ .../util/internal/updater/UpdateChecker.java | 86 ++++++ .../util/optional/Prerequisite.java | 89 ++++++ .../willfp/illusioner/util/tuplets/Pair.java | 64 +++++ .../illusioner/util/tuplets/Triplet.java | 86 ++++++ Plugin/src/main/resources/config.yml | 4 + Plugin/src/main/resources/lang.yml | 5 + Plugin/src/main/resources/plugin.yml | 47 +++ README.md | 37 +++ build.gradle | 123 ++++++++ gradle.properties | 2 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++++++ gradlew.bat | 104 +++++++ lib/SpartanAPI.jar | Bin 0 -> 15405 bytes settings.gradle | 14 + 114 files changed, 4984 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 NMS/API/build.gradle create mode 100644 NMS/API/src/main/java/com/willfp/illusioner/nms/api/BlockBreakWrapper.java create mode 100644 NMS/API/src/main/java/com/willfp/illusioner/nms/api/CooldownWrapper.java create mode 100644 NMS/API/src/main/java/com/willfp/illusioner/nms/api/EntityIllusionerWrapper.java create mode 100644 NMS/API/src/main/java/com/willfp/illusioner/nms/api/IllusionerWrapper.java create mode 100644 NMS/API/src/main/java/com/willfp/illusioner/nms/api/OpenInventoryWrapper.java create mode 100644 NMS/API/src/main/java/com/willfp/illusioner/nms/api/TridentStackWrapper.java create mode 100644 NMS/v1_15_R1/build.gradle create mode 100644 NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/BlockBreak.java create mode 100644 NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/Cooldown.java create mode 100644 NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/OpenInventory.java create mode 100644 NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/TridentStack.java create mode 100644 NMS/v1_16_R1/build.gradle create mode 100644 NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/BlockBreak.java create mode 100644 NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/Cooldown.java create mode 100644 NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/OpenInventory.java create mode 100644 NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/TridentStack.java create mode 100644 NMS/v1_16_R2/build.gradle create mode 100644 NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/BlockBreak.java create mode 100644 NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/Cooldown.java create mode 100644 NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/OpenInventory.java create mode 100644 NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/TridentStack.java create mode 100644 NMS/v1_16_R3/build.gradle create mode 100644 NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/BlockBreak.java create mode 100644 NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Cooldown.java create mode 100644 NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/EntityIllusioner.java create mode 100644 NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Illusioner.java create mode 100644 NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/OpenInventory.java create mode 100644 NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/TridentStack.java create mode 100644 Plugin/build.gradle create mode 100644 Plugin/src/main/java/com/willfp/illusioner/IllusionerPlugin.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/command/AbstractCommand.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/command/AbstractTabCompleter.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIldebug.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIlreload.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/config/ConfigManager.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/config/UpdatingYamlConfig.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/config/configs/Config.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/config/configs/Lang.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorEquipEvent.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorListener.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorType.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/armorequip/DispenserArmorListener.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityBuilder.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityEvent.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityListeners.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainBuilder.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainEvent.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainListeners.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/illusioner/AttackListeners.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/illusioner/DeathListeners.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/illusioner/SpawnListeners.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/Integration.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatManager.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatWrapper.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatAAC.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatMatrix.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatNCP.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatSpartan.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefManager.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefWrapper.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefFactionsUUID.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefGriefPrevention.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefKingdoms.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefLands.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefTowny.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefWorldGuard.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIIntegration.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIManager.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/plugins/AnvilGUIImpl.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoIntegration.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoManager.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/plugins/McmmoIntegrationImpl.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderEntry.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderIntegration.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderManager.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/plugins/PlaceholderIntegrationPAPI.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/nms/BlockBreak.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/nms/Cooldown.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/nms/NMSIllusioner.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/nms/OpenInventory.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/nms/TridentStack.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/BlockUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/ClassUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/DurabilityUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/LightningUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/NumberUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/StringUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/VectorUtils.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Callable.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectBiCallable.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectCallable.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Registerable.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/internal/Loader.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/internal/Logger.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/PlayerJoinListener.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/UpdateChecker.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/optional/Prerequisite.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Pair.java create mode 100644 Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Triplet.java create mode 100644 Plugin/src/main/resources/config.yml create mode 100644 Plugin/src/main/resources/lang.yml create mode 100644 Plugin/src/main/resources/plugin.yml create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 lib/SpartanAPI.jar create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8eddf6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Java +*.class + +# Eclipse IDE +.settings/ +bin/ +.classpath +.project + +# IntelliJ IDEA +.idea/ +*.iml + +# Gradle +.gradle +**/build/ +!src/**/build/ +.gradletasknamecache +!gradle-wrapper.jar +gradle-app.setting + +# Mac OSX +.DS_Store \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bd001c6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,44 @@ +# ILLUSIONER LICENSE + + +## Version 1.0, August 2020 + + +## Definitions + +"Package" refers to the collection of files (unless something else is indicated inside the file) distributed +by the Copyright Holder, and derivatives of that collection of files created through textual modification. + +"Copyright Holder" is whoever is named in the copyright or copyrights for the package. + +"You" is you, if you're thinking about copying or distributing this Package. + + +## Copyright (C) 2020 Will Favier-Parsons + +You are not allowed to redistribute full, partial or modified versions of the package to which the license +is associated. This license does not apply to modules or extensions that use but do not contain full, +partial or modified code from this package. + +By purchasing the package from the offical distributor (spigotmc.org) you agree to the following terms: + +1. No refunds, under any circumstance, unless explicitly approved by the copyright holder. + +2. You are of legal age and/or have permission from your legal guardian. + + +## ILLUSIONER LICENSE +### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +1. Do whatever you like with the original work, as long as you keep it for yourself or have the Copyright +Holder's permission. + +2. You are not allowed to redistribute full, partial or modified versions of the package + +3. Code is provided with no warranty. You have the right (and are even encouraged) to propose improvements +to the package as long as you don't use it as a way to circumvent the restrictions of the license. + +4. By proposing a modification you agree to assign all your rights on your code to the Copyright Holder. +You must therefore own the modifications you propose or have the permission to assign their rights. + +5. All modified versions of the package must also be open-source \ No newline at end of file diff --git a/NMS/API/build.gradle b/NMS/API/build.gradle new file mode 100644 index 0000000..32b0140 --- /dev/null +++ b/NMS/API/build.gradle @@ -0,0 +1,9 @@ +dependencies { + compileOnly 'org.spigotmc:spigot-api:1.15.2-R0.1-SNAPSHOT' +} + +jar{ + archiveFileName = findProperty("Name") + " v" + findProperty("version") + ".jar" +} + +description = 'Illusioner API' diff --git a/NMS/API/src/main/java/com/willfp/illusioner/nms/api/BlockBreakWrapper.java b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/BlockBreakWrapper.java new file mode 100644 index 0000000..1ddae00 --- /dev/null +++ b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/BlockBreakWrapper.java @@ -0,0 +1,11 @@ +package com.willfp.illusioner.nms.api; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +/** + * NMS Interface for breaking blocks as player + */ +public interface BlockBreakWrapper { + void breakBlock(Player player, Block block); +} diff --git a/NMS/API/src/main/java/com/willfp/illusioner/nms/api/CooldownWrapper.java b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/CooldownWrapper.java new file mode 100644 index 0000000..df03f6d --- /dev/null +++ b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/CooldownWrapper.java @@ -0,0 +1,10 @@ +package com.willfp.illusioner.nms.api; + +import org.bukkit.entity.Player; + +/** + * NMS Interface for getting attack cooldown + */ +public interface CooldownWrapper { + double getAttackCooldown(Player player); +} diff --git a/NMS/API/src/main/java/com/willfp/illusioner/nms/api/EntityIllusionerWrapper.java b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/EntityIllusionerWrapper.java new file mode 100644 index 0000000..2bd1a7d --- /dev/null +++ b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/EntityIllusionerWrapper.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.nms.api; + +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.plugin.Plugin; + +/** + * NMS Interface for managing illusioner bosses + */ +public interface EntityIllusionerWrapper { + void createBossbar(Plugin plugin, BarColor color, BarStyle style); +} \ No newline at end of file diff --git a/NMS/API/src/main/java/com/willfp/illusioner/nms/api/IllusionerWrapper.java b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/IllusionerWrapper.java new file mode 100644 index 0000000..ab341c0 --- /dev/null +++ b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/IllusionerWrapper.java @@ -0,0 +1,10 @@ +package com.willfp.illusioner.nms.api; + +import org.bukkit.Location; + +/** + * NMS Interface for managing illusioner bosses + */ +public interface IllusionerWrapper { + EntityIllusionerWrapper spawn(Location location, double maxHealth, double attackDamage); +} \ No newline at end of file diff --git a/NMS/API/src/main/java/com/willfp/illusioner/nms/api/OpenInventoryWrapper.java b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/OpenInventoryWrapper.java new file mode 100644 index 0000000..127d2bb --- /dev/null +++ b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/OpenInventoryWrapper.java @@ -0,0 +1,7 @@ +package com.willfp.illusioner.nms.api; + +import org.bukkit.entity.Player; + +public interface OpenInventoryWrapper { + Object getOpenInventory(Player player); +} diff --git a/NMS/API/src/main/java/com/willfp/illusioner/nms/api/TridentStackWrapper.java b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/TridentStackWrapper.java new file mode 100644 index 0000000..1dc1fd2 --- /dev/null +++ b/NMS/API/src/main/java/com/willfp/illusioner/nms/api/TridentStackWrapper.java @@ -0,0 +1,11 @@ +package com.willfp.illusioner.nms.api; + +import org.bukkit.entity.Trident; +import org.bukkit.inventory.ItemStack; + +/** + * NMS Interface for getting an ItemStack from a Trident + */ +public interface TridentStackWrapper { + ItemStack getTridentStack(Trident trident); +} \ No newline at end of file diff --git a/NMS/v1_15_R1/build.gradle b/NMS/v1_15_R1/build.gradle new file mode 100644 index 0000000..6eff629 --- /dev/null +++ b/NMS/v1_15_R1/build.gradle @@ -0,0 +1,10 @@ +dependencies { + compileOnly project(':API') + compileOnly 'org.spigotmc:spigot:1.15.2-R0.1-SNAPSHOT' +} + +jar{ + archiveFileName = project.name + " v" + findProperty("version") + ".jar" +} + +description = 'v1_15_R1' diff --git a/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/BlockBreak.java b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/BlockBreak.java new file mode 100644 index 0000000..8dc0039 --- /dev/null +++ b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/BlockBreak.java @@ -0,0 +1,14 @@ +package com.willfp.illusioner.v1_15_R1; + +import com.willfp.illusioner.nms.api.BlockBreakWrapper; +import net.minecraft.server.v1_15_R1.BlockPosition; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class BlockBreak implements BlockBreakWrapper { + @Override + public void breakBlock(Player player, Block block) { + ((CraftPlayer)player).getHandle().playerInteractManager.breakBlock(new BlockPosition(block.getX(), block.getY(), block.getZ())); + } +} diff --git a/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/Cooldown.java b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/Cooldown.java new file mode 100644 index 0000000..0645b3a --- /dev/null +++ b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/Cooldown.java @@ -0,0 +1,14 @@ +package com.willfp.illusioner.v1_15_R1; + +import com.willfp.illusioner.nms.api.CooldownWrapper; +import net.minecraft.server.v1_15_R1.EntityHuman; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class Cooldown implements CooldownWrapper { + @Override + public double getAttackCooldown(Player player) { + EntityHuman entityHuman = ((CraftPlayer) player).getHandle(); + return entityHuman.s(0); + } +} diff --git a/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/OpenInventory.java b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/OpenInventory.java new file mode 100644 index 0000000..d5e3592 --- /dev/null +++ b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/OpenInventory.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.v1_15_R1; + +import com.willfp.illusioner.nms.api.OpenInventoryWrapper; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class OpenInventory implements OpenInventoryWrapper { + @Override + public Object getOpenInventory(Player player) { + return ((CraftPlayer) player).getHandle().activeContainer; + } +} diff --git a/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/TridentStack.java b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/TridentStack.java new file mode 100644 index 0000000..c91da96 --- /dev/null +++ b/NMS/v1_15_R1/src/main/java/com/willfp/illusioner/v1_15_R1/TridentStack.java @@ -0,0 +1,16 @@ +package com.willfp.illusioner.v1_15_R1; + +import com.willfp.illusioner.nms.api.TridentStackWrapper; +import net.minecraft.server.v1_15_R1.EntityThrownTrident; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftTrident; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.entity.Trident; +import org.bukkit.inventory.ItemStack; + +public class TridentStack implements TridentStackWrapper { + @Override + public ItemStack getTridentStack(Trident trident) { + EntityThrownTrident t = ((CraftTrident) trident).getHandle(); + return CraftItemStack.asBukkitCopy(t.trident); + } +} \ No newline at end of file diff --git a/NMS/v1_16_R1/build.gradle b/NMS/v1_16_R1/build.gradle new file mode 100644 index 0000000..7c2e23d --- /dev/null +++ b/NMS/v1_16_R1/build.gradle @@ -0,0 +1,10 @@ +dependencies { + compileOnly project(':API') + compileOnly 'org.spigotmc:spigot:1.16.1-R0.1-SNAPSHOT' +} + +jar{ + archiveFileName = project.name + " v" + findProperty("version") + ".jar" +} + +description = 'v1_16_R1' diff --git a/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/BlockBreak.java b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/BlockBreak.java new file mode 100644 index 0000000..c2656fe --- /dev/null +++ b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/BlockBreak.java @@ -0,0 +1,14 @@ +package com.willfp.illusioner.v1_16_R1; + +import com.willfp.illusioner.nms.api.BlockBreakWrapper; +import net.minecraft.server.v1_16_R1.BlockPosition; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class BlockBreak implements BlockBreakWrapper { + @Override + public void breakBlock(Player player, Block block) { + ((CraftPlayer)player).getHandle().playerInteractManager.breakBlock(new BlockPosition(block.getX(), block.getY(), block.getZ())); + } +} diff --git a/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/Cooldown.java b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/Cooldown.java new file mode 100644 index 0000000..d291abf --- /dev/null +++ b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/Cooldown.java @@ -0,0 +1,11 @@ +package com.willfp.illusioner.v1_16_R1; + +import com.willfp.illusioner.nms.api.CooldownWrapper; +import org.bukkit.entity.Player; + +public class Cooldown implements CooldownWrapper { + @Override + public double getAttackCooldown(Player player) { + return player.getAttackCooldown(); + } +} diff --git a/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/OpenInventory.java b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/OpenInventory.java new file mode 100644 index 0000000..6623fb7 --- /dev/null +++ b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/OpenInventory.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.v1_16_R1; + +import com.willfp.illusioner.nms.api.OpenInventoryWrapper; +import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class OpenInventory implements OpenInventoryWrapper { + @Override + public Object getOpenInventory(Player player) { + return ((CraftPlayer) player).getHandle().activeContainer; + } +} diff --git a/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/TridentStack.java b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/TridentStack.java new file mode 100644 index 0000000..9596d19 --- /dev/null +++ b/NMS/v1_16_R1/src/main/java/com/willfp/illusioner/v1_16_R1/TridentStack.java @@ -0,0 +1,16 @@ +package com.willfp.illusioner.v1_16_R1; + +import com.willfp.illusioner.nms.api.TridentStackWrapper; +import net.minecraft.server.v1_16_R1.EntityThrownTrident; +import org.bukkit.craftbukkit.v1_16_R1.entity.CraftTrident; +import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack; +import org.bukkit.entity.Trident; +import org.bukkit.inventory.ItemStack; + +public class TridentStack implements TridentStackWrapper { + @Override + public ItemStack getTridentStack(Trident trident) { + EntityThrownTrident t = ((CraftTrident) trident).getHandle(); + return CraftItemStack.asBukkitCopy(t.trident); + } +} \ No newline at end of file diff --git a/NMS/v1_16_R2/build.gradle b/NMS/v1_16_R2/build.gradle new file mode 100644 index 0000000..de4e64c --- /dev/null +++ b/NMS/v1_16_R2/build.gradle @@ -0,0 +1,10 @@ +dependencies { + compileOnly project(':API') + compileOnly 'org.spigotmc:spigot:1.16.2-R0.1-SNAPSHOT' +} + +jar{ + archiveFileName = project.name + " v" + findProperty("version") + ".jar" +} + +description = 'v1_16_R2' diff --git a/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/BlockBreak.java b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/BlockBreak.java new file mode 100644 index 0000000..0667c85 --- /dev/null +++ b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/BlockBreak.java @@ -0,0 +1,14 @@ +package com.willfp.illusioner.v1_16_R2; + +import com.willfp.illusioner.nms.api.BlockBreakWrapper; +import net.minecraft.server.v1_16_R2.BlockPosition; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class BlockBreak implements BlockBreakWrapper { + @Override + public void breakBlock(Player player, Block block) { + ((CraftPlayer)player).getHandle().playerInteractManager.breakBlock(new BlockPosition(block.getX(), block.getY(), block.getZ())); + } +} diff --git a/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/Cooldown.java b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/Cooldown.java new file mode 100644 index 0000000..da51219 --- /dev/null +++ b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/Cooldown.java @@ -0,0 +1,11 @@ +package com.willfp.illusioner.v1_16_R2; + +import com.willfp.illusioner.nms.api.CooldownWrapper; +import org.bukkit.entity.Player; + +public class Cooldown implements CooldownWrapper { + @Override + public double getAttackCooldown(Player player) { + return player.getAttackCooldown(); + } +} diff --git a/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/OpenInventory.java b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/OpenInventory.java new file mode 100644 index 0000000..fd289d1 --- /dev/null +++ b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/OpenInventory.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.v1_16_R2; + +import com.willfp.illusioner.nms.api.OpenInventoryWrapper; +import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class OpenInventory implements OpenInventoryWrapper { + @Override + public Object getOpenInventory(Player player) { + return ((CraftPlayer) player).getHandle().activeContainer; + } +} diff --git a/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/TridentStack.java b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/TridentStack.java new file mode 100644 index 0000000..ef7973c --- /dev/null +++ b/NMS/v1_16_R2/src/main/java/com/willfp/illusioner/v1_16_R2/TridentStack.java @@ -0,0 +1,16 @@ +package com.willfp.illusioner.v1_16_R2; + +import com.willfp.illusioner.nms.api.TridentStackWrapper; +import net.minecraft.server.v1_16_R2.EntityThrownTrident; +import org.bukkit.craftbukkit.v1_16_R2.entity.CraftTrident; +import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack; +import org.bukkit.entity.Trident; +import org.bukkit.inventory.ItemStack; + +public class TridentStack implements TridentStackWrapper { + @Override + public ItemStack getTridentStack(Trident trident) { + EntityThrownTrident t = ((CraftTrident) trident).getHandle(); + return CraftItemStack.asBukkitCopy(t.trident); + } +} \ No newline at end of file diff --git a/NMS/v1_16_R3/build.gradle b/NMS/v1_16_R3/build.gradle new file mode 100644 index 0000000..30bc19f --- /dev/null +++ b/NMS/v1_16_R3/build.gradle @@ -0,0 +1,10 @@ +dependencies { + compileOnly project(':API') + compileOnly 'org.spigotmc:spigot:1.16.4-R0.1-SNAPSHOT' +} + +jar{ + archiveFileName = project.name + " v" + findProperty("version") + ".jar" +} + +description = 'v1_16_R3' diff --git a/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/BlockBreak.java b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/BlockBreak.java new file mode 100644 index 0000000..11902a6 --- /dev/null +++ b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/BlockBreak.java @@ -0,0 +1,14 @@ +package com.willfp.illusioner.v1_16_R3; + +import com.willfp.illusioner.nms.api.BlockBreakWrapper; +import net.minecraft.server.v1_16_R3.BlockPosition; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class BlockBreak implements BlockBreakWrapper { + @Override + public void breakBlock(Player player, Block block) { + ((CraftPlayer)player).getHandle().playerInteractManager.breakBlock(new BlockPosition(block.getX(), block.getY(), block.getZ())); + } +} diff --git a/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Cooldown.java b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Cooldown.java new file mode 100644 index 0000000..92f2d98 --- /dev/null +++ b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Cooldown.java @@ -0,0 +1,11 @@ +package com.willfp.illusioner.v1_16_R3; + +import com.willfp.illusioner.nms.api.CooldownWrapper; +import org.bukkit.entity.Player; + +public class Cooldown implements CooldownWrapper { + @Override + public double getAttackCooldown(Player player) { + return player.getAttackCooldown(); + } +} diff --git a/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/EntityIllusioner.java b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/EntityIllusioner.java new file mode 100644 index 0000000..b20d2ea --- /dev/null +++ b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/EntityIllusioner.java @@ -0,0 +1,64 @@ +package com.willfp.illusioner.v1_16_R3; + +import com.willfp.illusioner.nms.api.EntityIllusionerWrapper; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.attribute.Attribute; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.entity.LivingEntity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +public class EntityIllusioner extends EntityIllagerIllusioner implements EntityIllusionerWrapper { + public EntityIllusioner(Location location, double maxHealth, double attackDamage) { + super(EntityTypes.ILLUSIONER, ((CraftWorld) location.getWorld()).getHandle()); + + this.setPosition(location.getX(), location.getY(), location.getZ()); + + this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(maxHealth); + this.setHealth((float) maxHealth); + + this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(attackDamage); + + this.goalSelector.a(0, new PathfinderGoalFloat(this)); + this.goalSelector.a(1, new EntityIllagerWizard.b()); + this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false)); + this.goalSelector.a(2, new PathfinderGoalBowShoot<>(this, 1.0D, 20, 15.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); + this.goalSelector.a(0, new PathfinderGoalFloat(this)); + this.goalSelector.a(6, new PathfinderGoalBowShoot(this, 0.5D, 20, 15.0F)); + this.goalSelector.a(8, new PathfinderGoalRandomStroll(this, 0.6D)); + this.goalSelector.a(9, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 3.0F, 1.0F)); + this.goalSelector.a(10, new PathfinderGoalLookAtPlayer(this, EntityInsentient.class, 8.0F)); + this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[]{EntityRaider.class})).a(new Class[0])); + this.targetSelector.a(2, (new PathfinderGoalNearestAttackableTarget(this, EntityHuman.class, true)).a(300)); + this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget(this, EntityVillagerAbstract.class, false)).a(300)); + this.targetSelector.a(3, (new PathfinderGoalNearestAttackableTarget(this, EntityIronGolem.class, false)).a(300)); + } + + @Override + public void createBossbar(Plugin plugin, BarColor color, BarStyle style) { + String name = this.getName(); + BossBar bossBar = Bukkit.getServer().createBossBar(name, color, style, (BarFlag) null); + Bukkit.getServer().getOnlinePlayers().forEach(bossBar::addPlayer); + LivingEntity entity = (LivingEntity) this.getBukkitEntity(); + + new BukkitRunnable() { + @Override + public void run() { + if(!entity.isDead()) { + bossBar.setProgress(entity.getHealth() / entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + } else { + bossBar.getPlayers().forEach(bossBar::removePlayer); + bossBar.setVisible(false); + this.cancel(); + } + } + }.runTaskTimer(plugin, 0, 1); + } +} diff --git a/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Illusioner.java b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Illusioner.java new file mode 100644 index 0000000..0d98190 --- /dev/null +++ b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/Illusioner.java @@ -0,0 +1,15 @@ +package com.willfp.illusioner.v1_16_R3; + +import com.willfp.illusioner.nms.api.EntityIllusionerWrapper; +import com.willfp.illusioner.nms.api.IllusionerWrapper; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; + +public class Illusioner implements IllusionerWrapper { + @Override + public EntityIllusionerWrapper spawn(Location location, double maxHealth, double attackDamage) { + EntityIllusioner illusioner = new EntityIllusioner(location, maxHealth, attackDamage); + ((CraftWorld) location.getWorld()).getHandle().addEntity(illusioner); + return illusioner; + } +} diff --git a/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/OpenInventory.java b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/OpenInventory.java new file mode 100644 index 0000000..970fe74 --- /dev/null +++ b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/OpenInventory.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.v1_16_R3; + +import com.willfp.illusioner.nms.api.OpenInventoryWrapper; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class OpenInventory implements OpenInventoryWrapper { + @Override + public Object getOpenInventory(Player player) { + return ((CraftPlayer) player).getHandle().activeContainer; + } +} diff --git a/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/TridentStack.java b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/TridentStack.java new file mode 100644 index 0000000..54fdfce --- /dev/null +++ b/NMS/v1_16_R3/src/main/java/com/willfp/illusioner/v1_16_R3/TridentStack.java @@ -0,0 +1,16 @@ +package com.willfp.illusioner.v1_16_R3; + +import com.willfp.illusioner.nms.api.TridentStackWrapper; +import net.minecraft.server.v1_16_R3.EntityThrownTrident; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftTrident; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.Trident; +import org.bukkit.inventory.ItemStack; + +public class TridentStack implements TridentStackWrapper { + @Override + public ItemStack getTridentStack(Trident trident) { + EntityThrownTrident t = ((CraftTrident) trident).getHandle(); + return CraftItemStack.asBukkitCopy(t.trident); + } +} \ No newline at end of file diff --git a/Plugin/build.gradle b/Plugin/build.gradle new file mode 100644 index 0000000..5fd323a --- /dev/null +++ b/Plugin/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '5.2.0' +} + +dependencies { + implementation project(':API') + implementation project(':v1_15_R1') + implementation project(':v1_16_R1') + implementation project(':v1_16_R2') + implementation project(':v1_16_R3') + implementation 'org.apache.maven:maven-artifact:3.0.3' + implementation 'org.jetbrains:annotations:19.0.0' + implementation 'org.bstats:bstats-bukkit:1.7' + compileOnly 'org.spigotmc:spigot-api:1.16.3-R0.1-SNAPSHOT' + compileOnly 'commons-io:commons-io:2.8.0' + compileOnly 'com.sk89q.worldguard:worldguard-bukkit:7.0.4-SNAPSHOT' + compileOnly 'com.github.TechFortress:GriefPrevention:16.14.0' + compileOnly 'com.massivecraft:Factions:1.6.9.5-U0.5.10' + compileOnly 'com.github.cryptomorin:kingdoms:1.10.3.1' + shadow files('../lib/SpartanAPI.jar') + compileOnly 'com.github.TownyAdvanced:Towny:0.96.2.0' + compileOnly 'com.github.angeschossen:LandsAPI:4.7.3' + compileOnly 'fr.neatmonster:nocheatplus:3.16.1-SNAPSHOT' + compileOnly 'de.janmm14:aac-api:4.2.0' + compileOnly 'com.github.jiangdashao:matrix-api-repo:317d4635fd' + compileOnly 'com.comphenix.protocol:ProtocolLib:4.6.0-SNAPSHOT' + compileOnly 'com.destroystokyo.paper:paper-api:1.16.3-R0.1-SNAPSHOT' + compileOnly 'com.gmail.nossr50.mcMMO:mcMMO:2.1.157' + compileOnly 'me.clip:placeholderapi:2.10.9' +} + +shadowJar { + relocate('org.bstats.bukkit', 'com.willfp.illusioner.shaded.bstats') + relocate('org.jetbrains', 'com.willfp.illusioner.shaded.jetbrains') + relocate('org.intellij', 'com.willfp.illusioner.shaded.intellij') + relocate('org.apache.maven', 'com.willfp.illusioner.shaded.maven') + archiveFileName = findProperty("plugin-name") + " v" + findProperty("version") + ".jar" +} + +processResources { + filesNotMatching(["**/*.png", "**/models/**", "**/textures/**"]) { + expand projectVersion: findProperty("version") + } +} + +jar { + archiveFileName = findProperty("plugin-name") + " v" + findProperty("version") + " " + "unshaded" + ".jar" +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +tasks.withType(Jar) { + destinationDirectory = file("$rootDir/bin/") +} + +build.dependsOn shadowJar + +description = 'Illusioner' +compileJava.options.encoding = 'UTF-8' diff --git a/Plugin/src/main/java/com/willfp/illusioner/IllusionerPlugin.java b/Plugin/src/main/java/com/willfp/illusioner/IllusionerPlugin.java new file mode 100644 index 0000000..12694ea --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/IllusionerPlugin.java @@ -0,0 +1,49 @@ +package com.willfp.illusioner; + +import com.willfp.illusioner.util.internal.Loader; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +/** + * The Main class for Illusioner + */ +public class IllusionerPlugin extends JavaPlugin { + /** + * Instance of Illusioner + */ + private static IllusionerPlugin instance; + + /** + * NMS version + */ + public static final String NMS_VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + + /** + * Calls {@link Loader#load()} + */ + public void onEnable() { + Loader.load(); + } + + /** + * Calls {@link Loader#unload()} + */ + public void onDisable() { + Loader.unload(); + } + + /** + * Sets instance + */ + public void onLoad() { + instance = this; + } + + /** + * Get plugin instance + * @return Plugin instance + */ + public static IllusionerPlugin getInstance() { + return instance; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/command/AbstractCommand.java b/Plugin/src/main/java/com/willfp/illusioner/command/AbstractCommand.java new file mode 100644 index 0000000..bb32e7e --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/command/AbstractCommand.java @@ -0,0 +1,71 @@ +package com.willfp.illusioner.command; + +import com.willfp.illusioner.config.ConfigManager; +import com.willfp.illusioner.util.interfaces.Registerable; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public abstract class AbstractCommand implements CommandExecutor, Registerable { + private final String name; + private final String permission; + private final boolean playersOnly; + + protected AbstractCommand(String name, String permission, boolean playersOnly) { + this.name = name; + this.permission = permission; + this.playersOnly = playersOnly; + } + + public AbstractTabCompleter getTab() { + return null; + } + + public String getPermission() { + return this.permission; + } + + public String getName() { + return this.name; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, Command command, @NotNull String label, String[] args) { + if(!command.getName().equalsIgnoreCase(name)) return false; + + if(playersOnly && !(sender instanceof Player)) { + sender.sendMessage(ConfigManager.getLang().getMessage("not-player")); + return true; + } + + if (!sender.hasPermission(permission) && sender instanceof Player) { + sender.sendMessage(ConfigManager.getLang().getNoPermission()); + return true; + } + + onExecute(sender, Arrays.asList(args)); + + return true; + } + + @Override + public final void register() { + PluginCommand command = Bukkit.getPluginCommand(name); + assert command != null; + command.setExecutor(this); + + AbstractTabCompleter tabCompleter = this.getTab(); + if(tabCompleter != null) { + command.setTabCompleter(tabCompleter); + } + } + + public abstract void onExecute(CommandSender sender, List args); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/command/AbstractTabCompleter.java b/Plugin/src/main/java/com/willfp/illusioner/command/AbstractTabCompleter.java new file mode 100644 index 0000000..51271bd --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/command/AbstractTabCompleter.java @@ -0,0 +1,31 @@ +package com.willfp.illusioner.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +public abstract class AbstractTabCompleter implements TabCompleter { + private final AbstractCommand command; + + public AbstractTabCompleter(AbstractCommand command) { + this.command = command; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if(!command.getName().equalsIgnoreCase(this.command.getName())) + return null; + + if(!sender.hasPermission(this.command.getPermission())) + return null; + + return onTab(sender, Arrays.asList(args)); + } + + public abstract List onTab(CommandSender sender, List args); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIldebug.java b/Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIldebug.java new file mode 100644 index 0000000..18512c5 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIldebug.java @@ -0,0 +1,41 @@ +package com.willfp.illusioner.command.commands; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.command.AbstractCommand; +import com.willfp.illusioner.util.internal.Logger; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +@SuppressWarnings("unchecked") +public class CommandIldebug extends AbstractCommand { + public CommandIldebug() { + super("ildebug", "illusioner.debug", false); + } + + @Override + public void onExecute(CommandSender sender, List args) { + Logger.info("--------------- BEGIN DEBUG ----------------"); + if(sender instanceof Player) { + Player player = (Player) sender; + player.sendMessage("Held Item: " + player.getInventory().getItemInMainHand().toString()); + Logger.info(""); + + Logger.info("Held Item: " + player.getInventory().getItemInMainHand().toString()); + Logger.info(""); + } + + + Logger.info("Running Version: " + IllusionerPlugin.getInstance().getDescription().getVersion()); + Logger.info(""); + + Logger.info("Server Information: "); + Logger.info("Players Online: " + Bukkit.getServer().getOnlinePlayers().size()); + Logger.info("Bukkit IP: " + Bukkit.getIp()); + Logger.info("Running Version: " + Bukkit.getVersion() + ", Bukkit Version: " + Bukkit.getBukkitVersion() + ", Alt Version: " + Bukkit.getServer().getVersion()); + Logger.info("Motd: " + Bukkit.getServer().getMotd()); + Logger.info("--------------- END DEBUG ----------------"); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIlreload.java b/Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIlreload.java new file mode 100644 index 0000000..0fd5169 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/command/commands/CommandIlreload.java @@ -0,0 +1,20 @@ +package com.willfp.illusioner.command.commands; + +import com.willfp.illusioner.command.AbstractCommand; +import com.willfp.illusioner.config.ConfigManager; +import com.willfp.illusioner.util.internal.Loader; +import org.bukkit.command.CommandSender; + +import java.util.List; + +public class CommandIlreload extends AbstractCommand { + public CommandIlreload() { + super("ilreload", "illusioner.reload", false); + } + + @Override + public void onExecute(CommandSender sender, List args) { + Loader.reload(); + sender.sendMessage(ConfigManager.getLang().getMessage("reloaded")); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/config/ConfigManager.java b/Plugin/src/main/java/com/willfp/illusioner/config/ConfigManager.java new file mode 100644 index 0000000..169d498 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/config/ConfigManager.java @@ -0,0 +1,34 @@ +package com.willfp.illusioner.config; + +import com.willfp.illusioner.config.configs.Config; +import com.willfp.illusioner.config.configs.Lang; + +public class ConfigManager { + private static final Lang LANG = new Lang(); + private static final Config CONFIG = new Config(); + + /** + * Update all configs + * Called on /ecoreload + */ + public static void updateConfigs() { + LANG.update(); + CONFIG.update(); + } + + /** + * Get lang.yml + * @return lang.yml + */ + public static Lang getLang() { + return LANG; + } + + /** + * Get config.yml + * @return config.yml + */ + public static Config getConfig() { + return CONFIG; + } +} \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/config/UpdatingYamlConfig.java b/Plugin/src/main/java/com/willfp/illusioner/config/UpdatingYamlConfig.java new file mode 100644 index 0000000..de25b33 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/config/UpdatingYamlConfig.java @@ -0,0 +1,77 @@ +package com.willfp.illusioner.config; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.util.internal.Logger; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public abstract class UpdatingYamlConfig { + public YamlConfiguration config; + private File configFile; + private final String name; + private final boolean removeUnused; + + public UpdatingYamlConfig(String name, boolean removeUnused) { + this.name = name + ".yml"; + this.removeUnused = removeUnused; + init(); + } + + private void init() { + if (!new File(IllusionerPlugin.getInstance().getDataFolder(), name).exists()) { + createFile(); + } + + this.configFile = new File(IllusionerPlugin.getInstance().getDataFolder(), name); + this.config = YamlConfiguration.loadConfiguration(configFile); + + update(); + } + + private void createFile() { + IllusionerPlugin.getInstance().saveResource(name, false); + } + + public void update() { + try { + config.load(configFile); + + InputStream newIn = IllusionerPlugin.getInstance().getResource(name); + if(newIn == null) { + Logger.error(name + " is null?"); + return; + } + BufferedReader reader = new BufferedReader(new InputStreamReader(newIn, StandardCharsets.UTF_8)); + YamlConfiguration newConfig = new YamlConfiguration(); + newConfig.load(reader); + + if(newConfig.getKeys(true).equals(config.getKeys(true))) + return; + + newConfig.getKeys(true).forEach((s -> { + if (!config.getKeys(true).contains(s)) { + config.set(s, newConfig.get(s)); + } + })); + + if(this.removeUnused) { + config.getKeys(true).forEach((s -> { + if(!newConfig.getKeys(true).contains(s)) { + config.set(s, null); + } + })); + } + + config.save(configFile); + } catch (IOException | InvalidConfigurationException e) { + e.printStackTrace(); + } + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/config/configs/Config.java b/Plugin/src/main/java/com/willfp/illusioner/config/configs/Config.java new file mode 100644 index 0000000..04f177b --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/config/configs/Config.java @@ -0,0 +1,55 @@ +package com.willfp.illusioner.config.configs; + +import com.willfp.illusioner.config.UpdatingYamlConfig; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +/** + * Wrapper for config.yml + */ +public class Config extends UpdatingYamlConfig { + public Config() { + super("config", true); + } + + public int getInt(String path) { + return config.getInt(path); + } + + public int getInt(String path, int def) { + return config.getInt(path, def); + } + + public List getInts(String path) { + return config.getIntegerList(path); + } + + public boolean getBool(String path) { + return config.getBoolean(path); + } + + public List getBools(String path) { + return config.getBooleanList(path); + } + + public String getString(String path) { + return config.getString(path); + } + + public List getStrings(String path) { + return config.getStringList(path); + } + + public double getDouble(String path) { + return config.getDouble(path); + } + + public List getDoubles(String path) { + return config.getDoubleList(path); + } + + public ItemStack getItemStack(String path) { + return config.getItemStack(path); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/config/configs/Lang.java b/Plugin/src/main/java/com/willfp/illusioner/config/configs/Lang.java new file mode 100644 index 0000000..b5b156f --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/config/configs/Lang.java @@ -0,0 +1,36 @@ +package com.willfp.illusioner.config.configs; + +import com.willfp.illusioner.config.UpdatingYamlConfig; +import com.willfp.illusioner.util.StringUtils; + +import java.util.List; + +/** + * Wrapper for lang.yml + */ +public class Lang extends UpdatingYamlConfig { + public Lang() { + super("lang", false); + } + + public String getString(String path) { + return StringUtils.translate(String.valueOf(config.getString(path))); + } + + public List getStrings(String path) { + return config.getStringList(path); + } + + + public String getPrefix() { + return StringUtils.translate(config.getString("messages.prefix")); + } + + public String getNoPermission() { + return getPrefix() + StringUtils.translate(config.getString("messages.no-permission")); + } + + public String getMessage(String message) { + return getPrefix() + StringUtils.translate(config.getString("messages." + message)); + } +} \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorEquipEvent.java b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorEquipEvent.java new file mode 100644 index 0000000..ce1ceb0 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorEquipEvent.java @@ -0,0 +1,123 @@ +package com.willfp.illusioner.events.armorequip; + + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * @author Arnah + * @since Jul 30, 2015 + */ +public class ArmorEquipEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final EquipMethod equipType; + private final ArmorType type; + private ItemStack oldArmorPiece, newArmorPiece; + + public ArmorEquipEvent(final Player player, final EquipMethod equipType, final ArmorType type, final ItemStack oldArmorPiece, final ItemStack newArmorPiece) { + super(player); + this.equipType = equipType; + this.type = type; + this.oldArmorPiece = oldArmorPiece; + this.newArmorPiece = newArmorPiece; + } + + /** + * Gets a list of handlers handling this event. + * + * @return A list of handlers handling this event. + */ + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Sets if this event should be cancelled. + * + * @param cancel If this event should be cancelled. + */ + public final void setCancelled(final boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets if this event is cancelled. + * + * @return If this event is cancelled + */ + public final boolean isCancelled() { + return cancel; + } + + public final ArmorType getType() { + return type; + } + public final ItemStack getOldArmorPiece() { + return oldArmorPiece; + } + + public final void setOldArmorPiece(final ItemStack oldArmorPiece) { + this.oldArmorPiece = oldArmorPiece; + } + + public final ItemStack getNewArmorPiece() { + return newArmorPiece; + } + + public final void setNewArmorPiece(final ItemStack newArmorPiece) { + this.newArmorPiece = newArmorPiece; + } + + public EquipMethod getMethod() { + return equipType; + } + + public enum EquipMethod {// These have got to be the worst documentations ever. + /** + * When you shift click an armor piece to equip or unequip + */ + SHIFT_CLICK, + /** + * When you drag and drop the item to equip or unequip + */ + DRAG, + /** + * When you manually equip or unequip the item. Use to be DRAG + */ + PICK_DROP, + /** + * When you right click an armor piece in the hotbar without the inventory open to equip. + */ + HOTBAR, + /** + * When you press the hotbar slot number while hovering over the armor slot to equip or unequip + */ + HOTBAR_SWAP, + /** + * When in range of a dispenser that shoots an armor piece to equip.
+ * Requires the spigot version to have {@link org.bukkit.event.block.BlockDispenseArmorEvent} implemented. + */ + DISPENSER, + /** + * When an armor piece is removed due to it losing all durability. + */ + BROKE, + /** + * When you die causing all armor to unequip + */ + DEATH, + ; + } +} \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorListener.java b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorListener.java new file mode 100644 index 0000000..09bc503 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorListener.java @@ -0,0 +1,204 @@ +package com.willfp.illusioner.events.armorequip; + +import com.willfp.illusioner.events.armorequip.ArmorEquipEvent.EquipMethod; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Result; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemBreakEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.inventory.ItemStack; + +/** + * @author Arnah + * @since Jul 30, 2015 + */ +@SuppressWarnings("deprecation") +public class ArmorListener implements Listener { + //Event Priority is highest because other plugins might cancel the events before we check. + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public final void inventoryClick(final InventoryClickEvent e) { + boolean shift = false, numberkey = false; + if (e.isCancelled()) return; + if (e.getAction() == InventoryAction.NOTHING) return;// Why does this get called if nothing happens?? + if (e.getClick().equals(ClickType.SHIFT_LEFT) || e.getClick().equals(ClickType.SHIFT_RIGHT)) { + shift = true; + } + if (e.getClick().equals(ClickType.NUMBER_KEY)) { + numberkey = true; + } + if (e.getSlotType() != SlotType.ARMOR && e.getSlotType() != SlotType.QUICKBAR && e.getSlotType() != SlotType.CONTAINER) + return; + if (e.getClickedInventory() != null && !e.getClickedInventory().getType().equals(InventoryType.PLAYER)) return; + if (!e.getInventory().getType().equals(InventoryType.CRAFTING) && !e.getInventory().getType().equals(InventoryType.PLAYER)) + return; + if (!(e.getWhoClicked() instanceof Player)) return; + ArmorType newArmorType = ArmorType.matchType(shift ? e.getCurrentItem() : e.getCursor()); + if (!shift && newArmorType != null && e.getRawSlot() != newArmorType.getSlot()) { + // Used for drag and drop checking to make sure you aren't trying to place a helmet in the boots slot. + return; + } + if (shift) { + newArmorType = ArmorType.matchType(e.getCurrentItem()); + if (newArmorType != null) { + boolean equipping = true; + if (e.getRawSlot() == newArmorType.getSlot()) { + equipping = false; + } + if (newArmorType.equals(ArmorType.HELMET) && (equipping == isAirOrNull(e.getWhoClicked().getInventory().getHelmet())) || newArmorType.equals(ArmorType.CHESTPLATE) && (equipping == isAirOrNull(e.getWhoClicked().getInventory().getChestplate())) || newArmorType.equals(ArmorType.LEGGINGS) && (equipping == isAirOrNull(e.getWhoClicked().getInventory().getLeggings())) || newArmorType.equals(ArmorType.BOOTS) && (equipping == isAirOrNull(e.getWhoClicked().getInventory().getBoots()))) { + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent((Player) e.getWhoClicked(), EquipMethod.SHIFT_CLICK, newArmorType, equipping ? null : e.getCurrentItem(), equipping ? e.getCurrentItem() : null); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + if (armorEquipEvent.isCancelled()) { + e.setCancelled(true); + } + } + } + } else { + ItemStack newArmorPiece = e.getCursor(); + ItemStack oldArmorPiece = e.getCurrentItem(); + if (numberkey) { + if (e.getClickedInventory().getType().equals(InventoryType.PLAYER)) {// Prevents shit in the 2by2 crafting + // e.getClickedInventory() == The players inventory + // e.getHotBarButton() == key people are pressing to equip or unequip the item to or from. + // e.getRawSlot() == The slot the item is going to. + // e.getSlot() == Armor slot, can't use e.getRawSlot() as that gives a hotbar slot ;-; + ItemStack hotbarItem = e.getClickedInventory().getItem(e.getHotbarButton()); + if (!isAirOrNull(hotbarItem)) {// Equipping + newArmorType = ArmorType.matchType(hotbarItem); + newArmorPiece = hotbarItem; + oldArmorPiece = e.getClickedInventory().getItem(e.getSlot()); + } else {// Unequipping + newArmorType = ArmorType.matchType(!isAirOrNull(e.getCurrentItem()) ? e.getCurrentItem() : e.getCursor()); + } + } + } else { + if (isAirOrNull(e.getCursor()) && !isAirOrNull(e.getCurrentItem())) {// unequip with no new item going into the slot. + newArmorType = ArmorType.matchType(e.getCurrentItem()); + } + // e.getCurrentItem() == Unequip + // e.getCursor() == Equip + // newArmorType = ArmorType.matchType(!isAirOrNull(e.getCurrentItem()) ? e.getCurrentItem() : e.getCursor()); + } + if (newArmorType != null && e.getRawSlot() == newArmorType.getSlot()) { + EquipMethod method = EquipMethod.PICK_DROP; + if (e.getAction().equals(InventoryAction.HOTBAR_SWAP) || numberkey) method = EquipMethod.HOTBAR_SWAP; + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent((Player) e.getWhoClicked(), method, newArmorType, oldArmorPiece, newArmorPiece); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + if (armorEquipEvent.isCancelled()) { + e.setCancelled(true); + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void playerInteractEvent(PlayerInteractEvent e) { + if (e.useItemInHand().equals(Result.DENY)) return; + // + if (e.getAction() == Action.PHYSICAL) return; + if (e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) { + Player player = e.getPlayer(); + if (!e.useInteractedBlock().equals(Result.DENY)) { + if (e.getClickedBlock() != null && e.getAction() == Action.RIGHT_CLICK_BLOCK && !player.isSneaking()) {// Having both of these checks is useless, might as well do it though. + // Some blocks have actions when you right click them which stops the client from equipping the armor in hand. + Material mat = e.getClickedBlock().getType(); + } + } + ArmorType newArmorType = ArmorType.matchType(e.getItem()); + if (newArmorType != null) { + if (newArmorType.equals(ArmorType.HELMET) && isAirOrNull(e.getPlayer().getInventory().getHelmet()) || newArmorType.equals(ArmorType.CHESTPLATE) && isAirOrNull(e.getPlayer().getInventory().getChestplate()) || newArmorType.equals(ArmorType.LEGGINGS) && isAirOrNull(e.getPlayer().getInventory().getLeggings()) || newArmorType.equals(ArmorType.BOOTS) && isAirOrNull(e.getPlayer().getInventory().getBoots())) { + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(e.getPlayer(), EquipMethod.HOTBAR, ArmorType.matchType(e.getItem()), null, e.getItem()); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + if (armorEquipEvent.isCancelled()) { + e.setCancelled(true); + player.updateInventory(); + } + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void inventoryDrag(InventoryDragEvent event) { + // getType() seems to always be even. + // Old Cursor gives the item you are equipping + // Raw slot is the ArmorType slot + // Can't replace armor using this method making getCursor() useless. + ArmorType type = ArmorType.matchType(event.getOldCursor()); + if (event.getRawSlots().isEmpty()) return;// Idk if this will ever happen + if (type != null && type.getSlot() == event.getRawSlots().stream().findFirst().orElse(0)) { + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent((Player) event.getWhoClicked(), EquipMethod.DRAG, type, null, event.getOldCursor()); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + if (armorEquipEvent.isCancelled()) { + event.setResult(Result.DENY); + event.setCancelled(true); + } + } + // Debug shit + } + + @EventHandler + public void playerJoinEvent(PlayerJoinEvent e) { + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(e.getPlayer(), null, null, null, null); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + } + + @EventHandler + public void playerRespawnEvent(PlayerRespawnEvent e) { + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(e.getPlayer(), null, null, null, null); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + } + + @EventHandler + public void itemBreakEvent(PlayerItemBreakEvent e) { + ArmorType type = ArmorType.matchType(e.getBrokenItem()); + if (type != null) { + Player p = e.getPlayer(); + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(p, EquipMethod.BROKE, type, e.getBrokenItem(), null); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + if (armorEquipEvent.isCancelled()) { + ItemStack i = e.getBrokenItem().clone(); + i.setAmount(1); + i.setDurability((short) (i.getDurability() - 1)); + if (type.equals(ArmorType.HELMET)) { + p.getInventory().setHelmet(i); + } else if (type.equals(ArmorType.CHESTPLATE)) { + p.getInventory().setChestplate(i); + } else if (type.equals(ArmorType.LEGGINGS)) { + p.getInventory().setLeggings(i); + } else if (type.equals(ArmorType.BOOTS)) { + p.getInventory().setBoots(i); + } + } + } + } + + @EventHandler + public void playerDeathEvent(PlayerDeathEvent e) { + Player p = e.getEntity(); + if (e.getKeepInventory()) return; + for (ItemStack i : p.getInventory().getArmorContents()) { + if (!isAirOrNull(i)) { + Bukkit.getPluginManager().callEvent(new ArmorEquipEvent(p, EquipMethod.DEATH, ArmorType.matchType(i), i, null)); + // No way to cancel a death event. + } + } + } + public static boolean isAirOrNull(ItemStack item) { + return item == null || item.getType().equals(Material.AIR); + } +} \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorType.java b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorType.java new file mode 100644 index 0000000..63e4689 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/ArmorType.java @@ -0,0 +1,38 @@ +package com.willfp.illusioner.events.armorequip; + +import org.bukkit.inventory.ItemStack; + +/** + * @author Arnah + * @since Jul 30, 2015 + */ +public enum ArmorType { + HELMET(5), CHESTPLATE(6), LEGGINGS(7), BOOTS(8); + + private final int slot; + + ArmorType(int slot) { + this.slot = slot; + } + + /** + * Attempts to match the ArmorType for the specified ItemStack. + * + * @param itemStack The ItemStack to parse the type of. + * + * @return The parsed ArmorType, or null if not found. + */ + public static ArmorType matchType(final ItemStack itemStack) { + if (ArmorListener.isAirOrNull(itemStack)) return null; + String type = itemStack.getType().name(); + if (type.endsWith("_HELMET") || type.endsWith("_SKULL") || type.endsWith("PLAYER_HEAD")) return HELMET; + else if (type.endsWith("_CHESTPLATE") || type.endsWith("ELYTRA")) return CHESTPLATE; + else if (type.endsWith("_LEGGINGS")) return LEGGINGS; + else if (type.endsWith("_BOOTS")) return BOOTS; + else return null; + } + + public int getSlot() { + return slot; + } +} \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/DispenserArmorListener.java b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/DispenserArmorListener.java new file mode 100644 index 0000000..32f8bf7 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/armorequip/DispenserArmorListener.java @@ -0,0 +1,28 @@ +package com.willfp.illusioner.events.armorequip; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockDispenseArmorEvent; + +/** + * @author Arnah + * @since Feb 08, 2019 + */ +public class DispenserArmorListener implements Listener { + @EventHandler + public void dispenseArmorEvent(BlockDispenseArmorEvent event) { + ArmorType type = ArmorType.matchType(event.getItem()); + if (type != null) { + if (event.getTargetEntity() instanceof Player) { + Player p = (Player) event.getTargetEntity(); + ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(p, ArmorEquipEvent.EquipMethod.DISPENSER, type, null, event.getItem()); + Bukkit.getPluginManager().callEvent(armorEquipEvent); + if (armorEquipEvent.isCancelled()) { + event.setCancelled(true); + } + } + } + } +} \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityBuilder.java b/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityBuilder.java new file mode 100644 index 0000000..29584f1 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityBuilder.java @@ -0,0 +1,58 @@ +package com.willfp.illusioner.events.entitydeathbyentity; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +class EntityDeathByEntityBuilder { + private LivingEntity victim = null; + private Entity damager; + private EntityDeathEvent deathEvent; + + private List drops; + private int xp = 0; + private boolean dropItems; + + public EntityDeathByEntityBuilder() { + + } + + public LivingEntity getVictim() { + return this.victim; + } + + public void setDeathEvent(EntityDeathEvent deathEvent) { + this.deathEvent = deathEvent; + } + + public void setVictim(LivingEntity victim) { + this.victim = victim; + } + + public void setDamager(Entity damager) { + this.damager = damager; + } + + public void setDrops(List drops) { + this.drops = drops; + } + + public void setXp(int xp) { + this.xp = xp; + } + + public void push() { + if(this.victim == null) return; + if(this.damager == null) return; + if(this.drops == null) return; + if(this.deathEvent == null) return; + + EntityDeathByEntityEvent event = new EntityDeathByEntityEvent(victim, damager, drops, xp, deathEvent); + + Bukkit.getPluginManager().callEvent(event); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityEvent.java b/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityEvent.java new file mode 100644 index 0000000..28e060d --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityEvent.java @@ -0,0 +1,115 @@ +package com.willfp.illusioner.events.entitydeathbyentity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Event triggered when entity is killed by entity. + */ +public class EntityDeathByEntityEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + + /** + * The {@link LivingEntity} killed + */ + private final LivingEntity victim; + + /** + * The {@link Entity} that killed; + */ + private final Entity damager; + + /** + * The associated {@link EntityDeathEvent} + */ + private final EntityDeathEvent deathEvent; + + /** + * The entity drops + */ + private final List drops; + + /** + * The xp to drop + */ + private final int xp; + + /** + * Create event based off parameters + * + * @param victim The killed entity + * @param damager The killer + * @param drops The item drops + * @param xp The amount of xp to drop + * @param deathEvent The associated {@link EntityDeathEvent} + */ + public EntityDeathByEntityEvent(@NotNull LivingEntity victim, @NotNull Entity damager, @NotNull List drops, int xp, @NotNull EntityDeathEvent deathEvent) { + this.victim = victim; + this.damager = damager; + this.drops = drops; + this.xp = xp; + this.deathEvent = deathEvent; + } + + /** + * Get victim + * + * @return The victim + */ + public LivingEntity getVictim() { + return this.victim; + } + + /** + * Get killer + * + * @return The killer + */ + public Entity getKiller() { + return this.damager; + } + + /** + * Get xp amount + * + * @return The xp + */ + public int getDroppedExp() { + return this.xp; + } + + /** + * Get drops + * + * @return {@link List} of drops + */ + public List getDrops() { + return this.drops; + } + + /** + * Get associated {@link EntityDeathEvent} + * Use this to modify event parameters. + * + * @return The associated {@link EntityDeathEvent} + */ + public EntityDeathEvent getDeathEvent() { + return this.deathEvent; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityListeners.java b/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityListeners.java new file mode 100644 index 0000000..8f6547f --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/entitydeathbyentity/EntityDeathByEntityListeners.java @@ -0,0 +1,69 @@ +package com.willfp.illusioner.events.entitydeathbyentity; + +import com.willfp.illusioner.IllusionerPlugin; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +public class EntityDeathByEntityListeners implements Listener { + + final Set events = new HashSet<>(); + + @EventHandler(priority = EventPriority.HIGH) + public void onEntityDamage(EntityDamageByEntityEvent event) { + if(!(event.getEntity() instanceof LivingEntity)) return; + + LivingEntity victim = (LivingEntity) event.getEntity(); + + if(victim.getHealth() > event.getFinalDamage()) return; + + EntityDeathByEntityBuilder builtEvent = new EntityDeathByEntityBuilder(); + builtEvent.setVictim(victim); + builtEvent.setDamager(event.getDamager()); + events.add(builtEvent); + + new BukkitRunnable() { + @Override + public void run() { + events.remove(builtEvent); + } + }.runTaskLater(IllusionerPlugin.getInstance(), 1); + } + + @EventHandler + public void onEntityDeath(EntityDeathEvent event) { + LivingEntity victim = event.getEntity(); + + List drops = event.getDrops(); + int xp = event.getDroppedExp(); + + AtomicReference atomicBuiltEvent = new AtomicReference<>(null); + EntityDeathByEntityBuilder builtEvent; + + events.forEach((deathByEntityEvent) -> { + if(deathByEntityEvent.getVictim().equals(victim)) { + atomicBuiltEvent.set(deathByEntityEvent); + } + }); + + if(atomicBuiltEvent.get() == null) return; + + builtEvent = atomicBuiltEvent.get(); + events.remove(builtEvent); + builtEvent.setDrops(drops); + builtEvent.setXp(xp); + builtEvent.setDeathEvent(event); + + builtEvent.push(); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainBuilder.java b/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainBuilder.java new file mode 100644 index 0000000..294a7ce --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainBuilder.java @@ -0,0 +1,46 @@ +package com.willfp.illusioner.events.naturalexpgainevent; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.player.PlayerExpChangeEvent; + +class NaturalExpGainBuilder { + private final LivingEntity victim = null; + private boolean cancelled = false; + private PlayerExpChangeEvent event; + private Location loc; + + public NaturalExpGainBuilder() { + + } + + public LivingEntity getVictim() { + return this.victim; + } + + public void setEvent(PlayerExpChangeEvent event) { + this.event = event; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public void setLoc(Location location) { + this.loc = location; + } + + public Location getLoc() { + return this.loc; + } + + public void push() { + if(this.event == null) return; + if(this.cancelled) return; + + NaturalExpGainEvent naturalExpGainEvent = new NaturalExpGainEvent(event); + + Bukkit.getPluginManager().callEvent(naturalExpGainEvent); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainEvent.java b/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainEvent.java new file mode 100644 index 0000000..7c4a83c --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainEvent.java @@ -0,0 +1,46 @@ +package com.willfp.illusioner.events.naturalexpgainevent; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerExpChangeEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Event triggered when player receives experience not from bottle + */ +public class NaturalExpGainEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + + /** + * The associated {@link PlayerExpChangeEvent} + */ + private final PlayerExpChangeEvent event; + + /** + * Create event based off parameters + * + * @param event The associate PlayerExpChangeEvent + */ + public NaturalExpGainEvent(@NotNull PlayerExpChangeEvent event) { + this.event = event; + } + + /** + * Get associated {@link PlayerExpChangeEvent} + * Use this to modify event parameters. + * + * @return The associated {@link PlayerExpChangeEvent} + */ + public PlayerExpChangeEvent getExpChangeEvent() { + return this.event; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainListeners.java b/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainListeners.java new file mode 100644 index 0000000..b25f9b8 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/events/naturalexpgainevent/NaturalExpGainListeners.java @@ -0,0 +1,56 @@ +package com.willfp.illusioner.events.naturalexpgainevent; + +import com.willfp.illusioner.IllusionerPlugin; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.ExpBottleEvent; +import org.bukkit.event.player.PlayerExpChangeEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class NaturalExpGainListeners implements Listener { + + final Set events = new HashSet<>(); + + @EventHandler + public void onExpChange(PlayerExpChangeEvent event) { + NaturalExpGainBuilder builtEvent = new NaturalExpGainBuilder(); + builtEvent.setEvent(event); + + AtomicBoolean isNatural = new AtomicBoolean(true); + AtomicReference atomicBuiltEvent = new AtomicReference<>(); + + Set eventsClone = new HashSet<>(events); + eventsClone.forEach((builder) -> { + if(builder.getLoc().getWorld().getNearbyEntities(builder.getLoc(), 7.25, 7.25, 7.25).contains(event.getPlayer())) { + events.remove(builder); + isNatural.set(false); + atomicBuiltEvent.set(builder); + } + }); + + if(isNatural.get()) { + events.remove(atomicBuiltEvent.get()); + builtEvent.push(); + } + + new BukkitRunnable() { + @Override + public void run() { + events.remove(builtEvent); + } + }.runTaskLater(IllusionerPlugin.getInstance(), 1); + } + + @EventHandler + public void onExpBottle(ExpBottleEvent event) { + NaturalExpGainBuilder builtEvent = new NaturalExpGainBuilder(); + builtEvent.setLoc(event.getEntity().getLocation()); + + events.add(builtEvent); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/illusioner/AttackListeners.java b/Plugin/src/main/java/com/willfp/illusioner/illusioner/AttackListeners.java new file mode 100644 index 0000000..b72e690 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/illusioner/AttackListeners.java @@ -0,0 +1,105 @@ +package com.willfp.illusioner.illusioner; + +import com.willfp.illusioner.util.NumberUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AttackListeners implements Listener { + @EventHandler + public void onIllusionerAttack(EntityDamageByEntityEvent event) { + if(!event.getDamager().getType().equals(EntityType.ILLUSIONER)) + return; + + if(!(event.getEntity() instanceof Player)) + return; + + Player player = (Player) event.getEntity(); + + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 40, 1)); + player.playSound(player.getLocation(), Sound.ENTITY_ILLUSIONER_CAST_SPELL, 1, 1f); + if(!player.hasPotionEffect(PotionEffectType.CONFUSION)) { + player.addPotionEffect(new PotionEffect(PotionEffectType.CONFUSION, 200, 10)); + player.playSound(player.getLocation(), Sound.ENTITY_ILLUSIONER_CAST_SPELL, 1, 2f); + } + if(NumberUtils.randInt(1, 10) <= 2) { + player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 40, 2)); + player.playSound(player.getLocation(), Sound.ENTITY_ILLUSIONER_CAST_SPELL, 1, 2f); + } + if(NumberUtils.randInt(1, 10) <= 2) { + player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_DIGGING, 40, 2)); + player.playSound(player.getLocation(), Sound.ENTITY_ILLUSIONER_CAST_SPELL, 1, 2f); + } + + if(NumberUtils.randInt(1, 20) == 1) { + List hotbar = new ArrayList<>(); + for(int i = 0; i<9; i++) { + hotbar.add(player.getInventory().getItem(i)); + } + Collections.shuffle(hotbar); + int i2 = 0; + for(ItemStack item : hotbar) { + player.getInventory().setItem(i2, item); + i2++; + } + player.playSound(player.getLocation(), Sound.ENTITY_ENDER_PEARL_THROW, 1, 0.5f); + } + } + + @EventHandler + public void onIllusionerDamageByPlayer(EntityDamageByEntityEvent event) { + if(!event.getEntity().getType().equals(EntityType.ILLUSIONER)) + return; + + if(!(event.getDamager() instanceof Player)) + return; + + Player player = (Player) event.getDamager(); + + if(NumberUtils.randInt(1, 10) == 1) { + Location loc = player.getLocation().add(NumberUtils.randInt(2,6), 0, NumberUtils.randInt(2,6)); + while(!loc.getBlock().getType().equals(Material.AIR)) { + loc.add(0, 1, 0); + } + player.getWorld().spawnEntity(loc, EntityType.EVOKER); + player.playSound(player.getLocation(), Sound.ENTITY_EVOKER_PREPARE_ATTACK, 1, 2f); + } + + if(NumberUtils.randInt(1, 10) == 1) { + Location loc = player.getLocation().add(NumberUtils.randInt(2,6), 0, NumberUtils.randInt(2,6)); + while(!loc.getBlock().getType().equals(Material.AIR)) { + loc.add(0, 1, 0); + } + player.getWorld().spawnEntity(loc, EntityType.VINDICATOR); + player.playSound(player.getLocation(), Sound.ENTITY_EVOKER_PREPARE_ATTACK, 1, 2f); + } + + ExperienceOrb experienceOrb = (ExperienceOrb) event.getEntity().getWorld().spawnEntity(event.getEntity().getLocation(), EntityType.EXPERIENCE_ORB); + experienceOrb.setExperience(NumberUtils.randInt(5,20)); + } + + @EventHandler + public void onIllusionerDamage(EntityDamageEvent event) { + if(!event.getEntity().getType().equals(EntityType.ILLUSIONER)) + return; + + if(event.getCause().equals(EntityDamageEvent.DamageCause.ENTITY_EXPLOSION) + || event.getCause().equals(EntityDamageEvent.DamageCause.BLOCK_EXPLOSION)) { + event.setCancelled(true); + } + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/illusioner/DeathListeners.java b/Plugin/src/main/java/com/willfp/illusioner/illusioner/DeathListeners.java new file mode 100644 index 0000000..8924385 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/illusioner/DeathListeners.java @@ -0,0 +1,24 @@ +package com.willfp.illusioner.illusioner; + +import com.willfp.illusioner.util.NumberUtils; +import org.bukkit.Sound; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; + +public class DeathListeners implements Listener { + @EventHandler + public void onIllusionerDeath(EntityDeathEvent event) { + if(!event.getEntityType().equals(EntityType.ILLUSIONER)) + return; + + event.getEntity().getWorld().playSound(event.getEntity().getLocation(), Sound.ENTITY_EVOKER_PREPARE_WOLOLO, 50, 0.8f); + event.getEntity().getWorld().playSound(event.getEntity().getLocation(), Sound.ENTITY_ILLUSIONER_PREPARE_BLINDNESS, 50, 1f); + event.getEntity().getWorld().playSound(event.getEntity().getLocation(), Sound.ENTITY_WITHER_DEATH, 50, 2f); + + ExperienceOrb eo1 = (ExperienceOrb) event.getEntity().getWorld().spawnEntity(event.getEntity().getLocation(), EntityType.EXPERIENCE_ORB); + eo1.setExperience(NumberUtils.randInt(20000,25000)); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/illusioner/SpawnListeners.java b/Plugin/src/main/java/com/willfp/illusioner/illusioner/SpawnListeners.java new file mode 100644 index 0000000..459e8cd --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/illusioner/SpawnListeners.java @@ -0,0 +1,36 @@ +package com.willfp.illusioner.illusioner; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.nms.NMSIllusioner; +import com.willfp.illusioner.nms.api.EntityIllusionerWrapper; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; + +public class SpawnListeners implements Listener { + @EventHandler + public void onSpawn(BlockPlaceEvent event) { + if(!event.getBlock().getType().equals(Material.DRAGON_HEAD)) + return; + if(!event.getBlock().getLocation().add(0,-1,0).getBlock().getType().equals(Material.BEACON)) + return; + if(!event.getBlock().getLocation().add(0,-2,0).getBlock().getType().equals(Material.DIAMOND_BLOCK)) + return; + + try { + event.getBlock().getLocation().getBlock().setType(Material.AIR); + event.getBlock().getLocation().add(0, -1, 0).getBlock().setType(Material.AIR); + event.getBlock().getLocation().add(0, -2, 0).getBlock().setType(Material.AIR); + + event.getBlock().getWorld().playSound(event.getBlock().getLocation(), Sound.ENTITY_ILLUSIONER_MIRROR_MOVE, 1000, 0.5f); + event.getBlock().getWorld().playSound(event.getBlock().getLocation(), Sound.ENTITY_WITHER_SPAWN, 1000, 2f); + + EntityIllusionerWrapper illusioner = NMSIllusioner.spawn(event.getBlock().getLocation(), 600, 50); + illusioner.createBossbar(IllusionerPlugin.getInstance(), BarColor.BLUE, BarStyle.SOLID); + } catch(Exception ignored) {} + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/Integration.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/Integration.java new file mode 100644 index 0000000..00ff40e --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/Integration.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.integrations; + +/** + * Interface for all integrations with optional dependencies + */ +public interface Integration { + /** + * Get the name of integration + * @return The name + */ + String getPluginName(); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatManager.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatManager.java new file mode 100644 index 0000000..92a6dbd --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatManager.java @@ -0,0 +1,49 @@ +package com.willfp.illusioner.integrations.anticheat; + +import com.willfp.illusioner.IllusionerPlugin; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; + +import java.util.HashSet; +import java.util.Set; + +/** + * Utility class for Anticheat Integrations + */ +public class AnticheatManager { + private static final Set anticheats = new HashSet<>(); + + /** + * Register a new anticheat + * + * @param anticheat The anticheat to register + */ + public static void register(AnticheatWrapper anticheat) { + if (anticheat instanceof Listener) { + Bukkit.getPluginManager().registerEvents((Listener) anticheat, IllusionerPlugin.getInstance()); + } + anticheats.add(anticheat); + } + + /** + * Exempt a player from triggering anticheats + * + * @param player The player to exempt + */ + public static void exemptPlayer(Player player) { + anticheats.forEach(anticheat -> anticheat.exempt(player)); + } + + /** + * Unexempt a player from triggering anticheats + * This is ran a tick after it is called to ensure that there are no event timing conflicts + * + * @param player The player to remove the exemption + */ + public static void unexemptPlayer(Player player) { + Bukkit.getScheduler().runTaskLater(IllusionerPlugin.getInstance(), () -> { + anticheats.forEach(anticheat -> anticheat.unexempt(player)); + }, 1); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatWrapper.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatWrapper.java new file mode 100644 index 0000000..4c4b0df --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/AnticheatWrapper.java @@ -0,0 +1,23 @@ +package com.willfp.illusioner.integrations.anticheat; + +import com.willfp.illusioner.integrations.Integration; +import org.bukkit.entity.Player; + +/** + * Interface for anticheat integrations + */ +public interface AnticheatWrapper extends Integration { + /** + * Exempt a player from checks + * + * @param player The player to exempt + */ + void exempt(Player player); + + /** + * Unexempt a player from checks + * + * @param player The player to unexempt + */ + void unexempt(Player player); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatAAC.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatAAC.java new file mode 100644 index 0000000..a78ccc3 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatAAC.java @@ -0,0 +1,40 @@ +package com.willfp.illusioner.integrations.anticheat.plugins; + +import com.willfp.illusioner.integrations.anticheat.AnticheatWrapper; +import me.konsolas.aac.api.PlayerViolationEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class AnticheatAAC implements AnticheatWrapper, Listener { + private final Set exempt = new HashSet<>(); + + @Override + public String getPluginName() { + return "AAC"; + } + + @Override + public void exempt(Player player) { + this.exempt.add(player.getUniqueId()); + } + + @Override + public void unexempt(Player player) { + this.exempt.remove(player.getUniqueId()); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onViolate(PlayerViolationEvent event) { + if (!exempt.contains(event.getPlayer().getUniqueId())) { + return; + } + + event.setCancelled(true); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatMatrix.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatMatrix.java new file mode 100644 index 0000000..48e8aa4 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatMatrix.java @@ -0,0 +1,40 @@ +package com.willfp.illusioner.integrations.anticheat.plugins; + +import com.willfp.illusioner.integrations.anticheat.AnticheatWrapper; +import me.rerere.matrix.api.events.PlayerViolationEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class AnticheatMatrix implements AnticheatWrapper, Listener { + private final Set exempt = new HashSet<>(); + + @Override + public String getPluginName() { + return "Matrix"; + } + + @Override + public void exempt(Player player) { + this.exempt.add(player.getUniqueId()); + } + + @Override + public void unexempt(Player player) { + this.exempt.remove(player.getUniqueId()); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onViolate(PlayerViolationEvent event) { + if (!exempt.contains(event.getPlayer().getUniqueId())) { + return; + } + + event.setCancelled(true); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatNCP.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatNCP.java new file mode 100644 index 0000000..e94a996 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatNCP.java @@ -0,0 +1,37 @@ +package com.willfp.illusioner.integrations.anticheat.plugins; + +import com.willfp.illusioner.integrations.anticheat.AnticheatWrapper; +import fr.neatmonster.nocheatplus.checks.CheckType; +import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class AnticheatNCP implements AnticheatWrapper { + private final Set exempt = new HashSet<>(); + + @Override + public String getPluginName() { + return "NCP"; + } + + @Override + public void exempt(Player player) { + if (!NCPExemptionManager.isExempted(player, CheckType.ALL)) { + return; + } + + if (exempt.add(player.getUniqueId())) { + NCPExemptionManager.exemptPermanently(player, CheckType.ALL); + } + } + + @Override + public void unexempt(Player player) { + if (exempt.remove(player.getUniqueId())) { + NCPExemptionManager.unexempt(player, CheckType.ALL); + } + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatSpartan.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatSpartan.java new file mode 100644 index 0000000..456eedf --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anticheat/plugins/AnticheatSpartan.java @@ -0,0 +1,40 @@ +package com.willfp.illusioner.integrations.anticheat.plugins; + +import com.willfp.illusioner.integrations.anticheat.AnticheatWrapper; +import me.vagdedes.spartan.api.PlayerViolationEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class AnticheatSpartan implements AnticheatWrapper, Listener { + private final Set exempt = new HashSet<>(); + + @Override + public String getPluginName() { + return "Spartan"; + } + + @Override + public void exempt(Player player) { + this.exempt.add(player.getUniqueId()); + } + + @Override + public void unexempt(Player player) { + this.exempt.remove(player.getUniqueId()); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onViolate(PlayerViolationEvent event) { + if (!exempt.contains(event.getPlayer().getUniqueId())) { + return; + } + + event.setCancelled(true); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefManager.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefManager.java new file mode 100644 index 0000000..a7c32f6 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefManager.java @@ -0,0 +1,66 @@ +package com.willfp.illusioner.integrations.antigrief; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; + +public class AntigriefManager { + private static final Set antigriefs = new HashSet<>(); + + /** + * Register a new AntiGrief/Land Management integration + * + * @param antigrief The integration to register + */ + public static void register(AntigriefWrapper antigrief) { + antigriefs.add(antigrief); + } + + /** + * Can player break block + * + * @param player The player + * @param block The block + * @return If player can break block + */ + public static boolean canBreakBlock(Player player, Block block) { + return antigriefs.stream().allMatch(antigriefWrapper -> antigriefWrapper.canBreakBlock(player, block)); + } + + /** + * Can player create explosion at location + * + * @param player The player + * @param location The location + * @return If player can create explosion + */ + public static boolean canCreateExplosion(Player player, Location location) { + return antigriefs.stream().allMatch(antigriefWrapper -> antigriefWrapper.canCreateExplosion(player, location)); + } + + /** + * Can player place block + * + * @param player The player + * @param block The block + * @return If player can place block + */ + public static boolean canPlaceBlock(Player player, Block block) { + return antigriefs.stream().allMatch(antigriefWrapper -> antigriefWrapper.canPlaceBlock(player, block)); + } + + /** + * Can player injure living entity + * + * @param player The player + * @param victim The victim + * @return If player can injure + */ + public static boolean canInjure(Player player, LivingEntity victim) { + return antigriefs.stream().allMatch(antigriefWrapper -> antigriefWrapper.canInjure(player, victim)); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefWrapper.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefWrapper.java new file mode 100644 index 0000000..b9f9b92 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/AntigriefWrapper.java @@ -0,0 +1,48 @@ +package com.willfp.illusioner.integrations.antigrief; + +import com.willfp.illusioner.integrations.Integration; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +/** + * Interface for Antigrief integrations + */ +public interface AntigriefWrapper extends Integration { + /** + * Can player break block + * + * @param player The player + * @param block The block + * @return If player cna break block + */ + boolean canBreakBlock(Player player, Block block); + + /** + * Can player create explosion at location + * + * @param player The player + * @param location The location + * @return If player can create explosion + */ + boolean canCreateExplosion(Player player, Location location); + + /** + * Can player place block + * + * @param player The player + * @param block The block + * @return If player can place block + */ + boolean canPlaceBlock(Player player, Block block); + + /** + * Can player injure living entity + * + * @param player The player + * @param victim The victim + * @return If player can injure + */ + boolean canInjure(Player player, LivingEntity victim); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefFactionsUUID.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefFactionsUUID.java new file mode 100644 index 0000000..8b2e1f2 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefFactionsUUID.java @@ -0,0 +1,71 @@ +package com.willfp.illusioner.integrations.antigrief.plugins; + +import com.massivecraft.factions.Board; +import com.massivecraft.factions.FLocation; +import com.massivecraft.factions.FPlayer; +import com.massivecraft.factions.FPlayers; +import com.massivecraft.factions.Faction; +import com.massivecraft.factions.perms.PermissibleAction; +import com.willfp.illusioner.integrations.antigrief.AntigriefWrapper; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +public class AntigriefFactionsUUID implements AntigriefWrapper { + @Override + public boolean canBreakBlock(Player player, Block block) { + FPlayer fplayer = FPlayers.getInstance().getByPlayer(player); + FLocation flocation = new FLocation(block.getLocation()); + Faction faction = Board.getInstance().getFactionAt(flocation); + + if (!faction.hasAccess(fplayer, PermissibleAction.DESTROY)) { + return fplayer.isAdminBypassing(); + } + return true; + } + + @Override + public boolean canCreateExplosion(Player player, Location location) { + FPlayer fplayer = FPlayers.getInstance().getByPlayer(player); + FLocation flocation = new FLocation(location); + Faction faction = Board.getInstance().getFactionAt(flocation); + + return !faction.noExplosionsInTerritory(); + } + + @Override + public boolean canPlaceBlock(Player player, Block block) { + FPlayer fplayer = FPlayers.getInstance().getByPlayer(player); + FLocation flocation = new FLocation(block.getLocation()); + Faction faction = Board.getInstance().getFactionAt(flocation); + + if (!faction.hasAccess(fplayer, PermissibleAction.BUILD)) { + return fplayer.isAdminBypassing(); + } + return true; + } + + @Override + public boolean canInjure(Player player, LivingEntity victim) { + FPlayer fplayer = FPlayers.getInstance().getByPlayer(player); + FLocation flocation = new FLocation(victim.getLocation()); + Faction faction = Board.getInstance().getFactionAt(flocation); + + if(victim instanceof Player) { + if (faction.isPeaceful()) { + return fplayer.isAdminBypassing(); + } + } else { + if (faction.hasAccess(fplayer, PermissibleAction.DESTROY)) { + return fplayer.isAdminBypassing(); + } + } + return true; + } + + @Override + public String getPluginName() { + return "FactionsUUID"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefGriefPrevention.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefGriefPrevention.java new file mode 100644 index 0000000..21d8d0a --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefGriefPrevention.java @@ -0,0 +1,56 @@ +package com.willfp.illusioner.integrations.antigrief.plugins; + +import com.willfp.illusioner.integrations.antigrief.AntigriefWrapper; +import me.ryanhamshire.GriefPrevention.Claim; +import me.ryanhamshire.GriefPrevention.GriefPrevention; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +public class AntigriefGriefPrevention implements AntigriefWrapper { + @Override + public boolean canBreakBlock(Player player, Block block) { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(block.getLocation(), false, null); + if (claim != null) { + return claim.allowBreak(player, block.getType()) == null; + } + return true; + } + + @Override + public boolean canCreateExplosion(Player player, Location location) { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, false, null); + if (claim != null) { + return claim.areExplosivesAllowed; + } + return true; + } + + @Override + public boolean canPlaceBlock(Player player, Block block) { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(block.getLocation(), false, null); + if (claim != null) { + return claim.allowBuild(player, block.getType()) == null; + } + return true; + } + + @Override + public boolean canInjure(Player player, LivingEntity victim) { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(victim.getLocation(), false, null); + if(victim instanceof Player) { + return claim == null; + } else { + if (claim != null && claim.ownerID != null) { + return claim.ownerID.equals(player.getUniqueId()); + } + return true; + } + } + + @Override + public String getPluginName() { + return "GriefPrevention"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefKingdoms.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefKingdoms.java new file mode 100644 index 0000000..e00c379 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefKingdoms.java @@ -0,0 +1,60 @@ +package com.willfp.illusioner.integrations.antigrief.plugins; + +import com.willfp.illusioner.integrations.antigrief.AntigriefWrapper; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.kingdoms.constants.kingdom.Kingdom; +import org.kingdoms.constants.land.Land; +import org.kingdoms.managers.PvPManager; +import org.kingdoms.managers.land.LandManager; + +public class AntigriefKingdoms implements AntigriefWrapper { + @Override + public boolean canBreakBlock(Player player, Block block) { + BlockBreakEvent event = new BlockBreakEvent(block, player); + LandManager.onBreak(event); + return !event.isCancelled(); + } + + @Override + public boolean canCreateExplosion(Player player, Location location) { + Land land = Land.getLand(location); + if (land == null) return true; + if(!land.isClaimed()) return true; + + Kingdom kingdom = land.getKingdom(); + return kingdom.isMember(player); + } + + @Override + public boolean canPlaceBlock(Player player, Block block) { + Block placedOn = block.getRelative(0, -1, 0); + BlockPlaceEvent event = new BlockPlaceEvent(block, block.getState(), placedOn, player.getInventory().getItemInMainHand(), player, true, EquipmentSlot.HAND); + LandManager.onPlace(event); + return !event.isCancelled(); + } + + @Override + public boolean canInjure(Player player, LivingEntity victim) { + if(victim instanceof Player) { + return PvPManager.canFight(player, (Player) victim); + } else { + Land land = Land.getLand(victim.getLocation()); + if (land == null) return true; + if(!land.isClaimed()) return true; + + Kingdom kingdom = land.getKingdom(); + return kingdom.isMember(player); + } + } + + @Override + public String getPluginName() { + return "Kingdoms"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefLands.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefLands.java new file mode 100644 index 0000000..0259e24 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefLands.java @@ -0,0 +1,64 @@ +package com.willfp.illusioner.integrations.antigrief.plugins; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.integrations.antigrief.AntigriefWrapper; +import me.angeschossen.lands.api.integration.LandsIntegration; +import me.angeschossen.lands.api.land.Area; +import me.angeschossen.lands.api.role.enums.RoleSetting; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +public class AntigriefLands implements AntigriefWrapper { + @Override + public boolean canBreakBlock(Player player, Block block) { + LandsIntegration landsIntegration = new LandsIntegration(IllusionerPlugin.getInstance()); + Area area = landsIntegration.getAreaByLoc(block.getLocation()); + if (area != null) { + return area.canSetting(player, RoleSetting.BLOCK_BREAK, false); + } + return true; + } + + @Override + public boolean canCreateExplosion(Player player, Location location) { + LandsIntegration landsIntegration = new LandsIntegration(IllusionerPlugin.getInstance()); + Area area = landsIntegration.getAreaByLoc(location); + if (area != null) { + return area.canSetting(player, RoleSetting.BLOCK_IGNITE, false); + } + return true; + } + + @Override + public boolean canPlaceBlock(Player player, Block block) { + LandsIntegration landsIntegration = new LandsIntegration(IllusionerPlugin.getInstance()); + Area area = landsIntegration.getAreaByLoc(block.getLocation()); + if (area != null) { + return area.canSetting(player, RoleSetting.BLOCK_PLACE, false); + } + return true; + } + + @Override + public boolean canInjure(Player player, LivingEntity victim) { + LandsIntegration landsIntegration = new LandsIntegration(IllusionerPlugin.getInstance()); + Area area = landsIntegration.getAreaByLoc(victim.getLocation()); + if(victim instanceof Player) { + if (area != null) { + return area.canSetting(player, RoleSetting.ATTACK_PLAYER, false); + } + } else { + if (area != null) { + return area.canSetting(player, RoleSetting.ATTACK_ANIMAL, false); + } + } + return true; + } + + @Override + public String getPluginName() { + return "Lands"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefTowny.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefTowny.java new file mode 100644 index 0000000..ce66567 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefTowny.java @@ -0,0 +1,50 @@ +package com.willfp.illusioner.integrations.antigrief.plugins; + +import com.palmergames.bukkit.towny.object.Town; +import com.palmergames.bukkit.towny.object.TownyPermission; +import com.palmergames.bukkit.towny.object.WorldCoord; +import com.palmergames.bukkit.towny.utils.PlayerCacheUtil; +import com.willfp.illusioner.integrations.antigrief.AntigriefWrapper; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +public class AntigriefTowny implements AntigriefWrapper { + @Override + public boolean canBreakBlock(Player player, Block block) { + return PlayerCacheUtil.getCachePermission(player, block.getLocation(), block.getType(), TownyPermission.ActionType.DESTROY); + } + + @Override + public boolean canCreateExplosion(Player player, Location location) { + return PlayerCacheUtil.getCachePermission(player, location, Material.TNT, TownyPermission.ActionType.ITEM_USE); + } + + @Override + public boolean canPlaceBlock(Player player, Block block) { + return PlayerCacheUtil.getCachePermission(player, block.getLocation(), block.getType(), TownyPermission.ActionType.BUILD); + } + + @Override + public boolean canInjure(Player player, LivingEntity victim) { + if(victim instanceof Player) { + try { + Town town = WorldCoord.parseWorldCoord(victim.getLocation()).getTownBlock().getTown(); + return town.isPVP(); + } catch (Exception ignored) {} + } else { + try { + Town town = WorldCoord.parseWorldCoord(victim.getLocation()).getTownBlock().getTown(); + return town.hasMobs(); + } catch (Exception ignored) {} + } + return true; + } + + @Override + public String getPluginName() { + return "Towny"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefWorldGuard.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefWorldGuard.java new file mode 100644 index 0000000..73ca351 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/antigrief/plugins/AntigriefWorldGuard.java @@ -0,0 +1,79 @@ +package com.willfp.illusioner.integrations.antigrief.plugins; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import com.willfp.illusioner.integrations.antigrief.AntigriefWrapper; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +public class AntigriefWorldGuard implements AntigriefWrapper { + @Override + public boolean canBreakBlock(Player player, Block block) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); + + if (!query.testState(BukkitAdapter.adapt(block.getLocation()), localPlayer, Flags.BUILD)) { + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, BukkitAdapter.adapt(block.getWorld())); + } + return true; + } + + @Override + public boolean canCreateExplosion(Player player, Location location) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); + World world = location.getWorld(); + Validate.notNull(world, "World cannot be null!"); + + if (!query.testState(BukkitAdapter.adapt(location), localPlayer, Flags.OTHER_EXPLOSION)) { + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, BukkitAdapter.adapt(world)); + } + return true; + } + + @Override + public boolean canPlaceBlock(Player player, Block block) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); + + if (!query.testState(BukkitAdapter.adapt(block.getLocation()), localPlayer, Flags.BLOCK_PLACE)) { + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, BukkitAdapter.adapt(block.getWorld())); + } + return true; + } + + @Override + public boolean canInjure(Player player, LivingEntity victim) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionQuery query = container.createQuery(); + + if(victim instanceof Player) { + if (!query.testState(BukkitAdapter.adapt(victim.getLocation()), localPlayer, Flags.PVP)) { + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, BukkitAdapter.adapt(player.getWorld())); + } + } else { + if (!query.testState(BukkitAdapter.adapt(victim.getLocation()), localPlayer, Flags.DAMAGE_ANIMALS)) { + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, BukkitAdapter.adapt(player.getWorld())); + } + } + return true; + } + + @Override + public String getPluginName() { + return "WorldGuard"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIIntegration.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIIntegration.java new file mode 100644 index 0000000..077c41c --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIIntegration.java @@ -0,0 +1,17 @@ +package com.willfp.illusioner.integrations.anvilgui; + +import com.willfp.illusioner.integrations.Integration; + +/** + * Interface for AnvilGUI integrations + */ +public interface AnvilGUIIntegration extends Integration { + /** + * Get if the NMS inventory is an instance of an AnvilGUI + * + * @param object The NMS inventory to check + * @return If the object is an AnvilGUI + * @see com.willfp.illusioner.nms.OpenInventory + */ + boolean isInstance(Object object); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIManager.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIManager.java new file mode 100644 index 0000000..9af7046 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/AnvilGUIManager.java @@ -0,0 +1,35 @@ +package com.willfp.illusioner.integrations.anvilgui; + +import com.willfp.illusioner.nms.OpenInventory; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; + +/** + * Utility class for interfacing with plugins that use WesJD's AnvilGUI library + */ +public class AnvilGUIManager { + private static final Set integrations = new HashSet<>(); + + /** + * Register a new AnvilGUI integration + * + * @param integration The integration to register + */ + public static void registerIntegration(AnvilGUIIntegration integration) { + integrations.add(integration); + } + + /** + * Get if a player's open inventory is an AnvilGUI + * + * @param player The player to check + * @return If the player's open inventory is an AnvilGUI + */ + public static boolean hasAnvilGUIOpen(Player player) { + if (integrations.isEmpty()) + return false; + return integrations.stream().anyMatch(integration -> integration.isInstance(OpenInventory.getOpenInventory(player))); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/plugins/AnvilGUIImpl.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/plugins/AnvilGUIImpl.java new file mode 100644 index 0000000..3922a9a --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/anvilgui/plugins/AnvilGUIImpl.java @@ -0,0 +1,21 @@ +package com.willfp.illusioner.integrations.anvilgui.plugins; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.integrations.anvilgui.AnvilGUIIntegration; + +/** + * Concrete implementation of {@link AnvilGUIIntegration} + */ +public class AnvilGUIImpl implements AnvilGUIIntegration { + private static final String ANVIL_GUI_CLASS = "net.wesjd.anvilgui.version.Wrapper" + IllusionerPlugin.NMS_VERSION.substring(1) + "$AnvilContainer"; + + @Override + public boolean isInstance(Object object) { + return object.getClass().toString().equals(ANVIL_GUI_CLASS); + } + + @Override + public String getPluginName() { + return "AnvilGUI"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoIntegration.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoIntegration.java new file mode 100644 index 0000000..3144712 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoIntegration.java @@ -0,0 +1,14 @@ +package com.willfp.illusioner.integrations.mcmmo; + +import com.willfp.illusioner.integrations.Integration; +import org.bukkit.event.Event; + +/** + * Interface for mcMMO integrations + */ +public interface McmmoIntegration extends Integration { + /** + * @see McmmoManager#isFake(Event) + */ + boolean isFake(Event event); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoManager.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoManager.java new file mode 100644 index 0000000..a4d64ab --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/McmmoManager.java @@ -0,0 +1,41 @@ +package com.willfp.illusioner.integrations.mcmmo; + +import com.willfp.illusioner.util.ClassUtils; +import org.bukkit.event.Event; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Utility class for interfacing with mcMMO + */ +public class McmmoManager { + private static final Set integrations = new HashSet<>(); + + /** + * Register a new mcMMO integration + * + * @param integration The integration to register + */ + public static void registerIntegration(McmmoIntegration integration) { + if(!ClassUtils.exists("com.gmail.nossr50.events.fake.FakeEvent")) + return; + integrations.add(integration); + } + + /** + * Get if an event is fake + * + * @param event The event to check + * @return If the event is fake + */ + public static boolean isFake(Event event) { + AtomicBoolean isFake = new AtomicBoolean(false); + integrations.forEach(integration -> { + if (integration.isFake(event)) isFake.set(true); + }); + + return isFake.get(); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/plugins/McmmoIntegrationImpl.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/plugins/McmmoIntegrationImpl.java new file mode 100644 index 0000000..c04f2db --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/mcmmo/plugins/McmmoIntegrationImpl.java @@ -0,0 +1,20 @@ +package com.willfp.illusioner.integrations.mcmmo.plugins; + +import com.gmail.nossr50.events.fake.FakeEvent; +import com.willfp.illusioner.integrations.mcmmo.McmmoIntegration; +import org.bukkit.event.Event; + +/** + * Concrete implementation of {@link McmmoIntegration} + */ +public class McmmoIntegrationImpl implements McmmoIntegration { + @Override + public boolean isFake(Event event) { + return event instanceof FakeEvent; + } + + @Override + public String getPluginName() { + return "mcMMO"; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderEntry.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderEntry.java new file mode 100644 index 0000000..8ff9b9b --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderEntry.java @@ -0,0 +1,65 @@ +package com.willfp.illusioner.integrations.placeholder; + +import com.willfp.illusioner.util.interfaces.ObjectBiCallable; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +/** + * A placeholder entry consists of an identifier and an {@link ObjectBiCallable} to fetch the result + */ +public class PlaceholderEntry { + private final String identifier; + private final ObjectBiCallable function; + private final boolean requiresPlayer; + + /** + * Create a placeholder entry that doesn't require a player + * + * @param identifier The identifier of the placeholder + * @param function A lambda to get the result of the placeholder + */ + public PlaceholderEntry(String identifier, ObjectBiCallable function) { + this(identifier, function, false); + } + + /** + * Create a placeholder entry that may require a player + * + * @param identifier The identifier of the placeholder + * @param function A lambda to get the result of the placeholder + * @param requiresPlayer If the placeholder requires a player + */ + public PlaceholderEntry(String identifier, ObjectBiCallable function, boolean requiresPlayer) { + this.identifier = identifier; + this.function = function; + this.requiresPlayer = requiresPlayer; + } + + /** + * Get the identifier of the placeholder + * + * @return The identifier + */ + public String getIdentifier() { + return this.identifier; + } + + /** + * Get the result of the placeholder with respect to a player + * + * @param player The player to translate with respect to + * @return The result of the placeholder + */ + public String getResult(@Nullable Player player) { + return this.function.call(player); + } + + /** + * Get if the placeholder requires a player to get a result + * + * @return If the placeholder requires a player + */ + public boolean requiresPlayer() { + return requiresPlayer; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderIntegration.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderIntegration.java new file mode 100644 index 0000000..6ec6fe7 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderIntegration.java @@ -0,0 +1,24 @@ +package com.willfp.illusioner.integrations.placeholder; + +import com.willfp.illusioner.integrations.Integration; +import org.bukkit.entity.Player; + +/** + * Interface for Placeholder integrations + */ +public interface PlaceholderIntegration extends Integration { + /** + * Register the integration with the specified plugin + * Not to be confused with internal registration in {@link PlaceholderManager#addIntegration(PlaceholderIntegration)} + */ + void registerIntegration(); + + /** + * Translate all the placeholders in a string with respect to a player + * + * @param text The text to translate + * @param player The player to translate with respect to + * @return The string, translated + */ + String translate(String text, Player player); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderManager.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderManager.java new file mode 100644 index 0000000..19ab829 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/PlaceholderManager.java @@ -0,0 +1,67 @@ +package com.willfp.illusioner.integrations.placeholder; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Utility class for placeholders + */ +public class PlaceholderManager { + private static final Set placeholders = new HashSet<>(); + private static final Set integrations = new HashSet<>(); + + /** + * Register a new placeholder integration + * + * @param integration The {@link PlaceholderIntegration} to register + */ + public static void addIntegration(PlaceholderIntegration integration) { + integration.registerIntegration(); + integrations.add(integration); + } + + /** + * Register a placeholder + * + * @param expansion The {@link PlaceholderEntry} to register + */ + public static void registerPlaceholder(PlaceholderEntry expansion) { + placeholders.removeIf(placeholderEntry -> placeholderEntry.getIdentifier().equalsIgnoreCase(expansion.getIdentifier())); + placeholders.add(expansion); + } + + /** + * Get the result of a placeholder with respect to a player + * + * @param player The player to get the result from + * @param identifier The placeholder identifier + * @return The value of the placeholder + */ + public static String getResult(@Nullable Player player, String identifier) { + Optional matching = placeholders.stream().filter(expansion -> expansion.getIdentifier().equalsIgnoreCase(identifier)).findFirst(); + if (!matching.isPresent()) + return null; + PlaceholderEntry entry = matching.get(); + if (player == null && entry.requiresPlayer()) + return ""; + return entry.getResult(player); + } + + /** + * Translate all placeholders with respect to a player + * + * @param text The text that may contain placeholders to translate + * @param player The player to translate the placeholders with respect to + * @return The text, translated + */ + public static String translatePlaceholders(String text, @Nullable Player player) { + AtomicReference translatedReference = new AtomicReference<>(text); + integrations.forEach(placeholderIntegration -> translatedReference.set(placeholderIntegration.translate(translatedReference.get(), player))); + return translatedReference.get(); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/plugins/PlaceholderIntegrationPAPI.java b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/plugins/PlaceholderIntegrationPAPI.java new file mode 100644 index 0000000..ad234ac --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/integrations/placeholder/plugins/PlaceholderIntegrationPAPI.java @@ -0,0 +1,59 @@ +package com.willfp.illusioner.integrations.placeholder.plugins; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.integrations.placeholder.PlaceholderIntegration; +import com.willfp.illusioner.integrations.placeholder.PlaceholderManager; +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * PlaceholderAPI integration + */ +public class PlaceholderIntegrationPAPI extends PlaceholderExpansion implements PlaceholderIntegration { + @Override + public boolean persist() { + return true; + } + + @Override + public boolean canRegister() { + return true; + } + + @Override + public @NotNull String getAuthor() { + return "Auxilor"; + } + + @Override + public @NotNull String getIdentifier() { + return "illusioner"; + } + + @Override + public @NotNull String getVersion() { + return IllusionerPlugin.getInstance().getDescription().getVersion(); + } + + @Override + public String onPlaceholderRequest(Player player, @NotNull String identifier) { + return PlaceholderManager.getResult(player, identifier); + } + + @Override + public void registerIntegration() { + this.register(); + } + + @Override + public String getPluginName() { + return "PlaceholderAPI"; + } + + @Override + public String translate(String text, Player player) { + return PlaceholderAPI.setPlaceholders(player, text); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/nms/BlockBreak.java b/Plugin/src/main/java/com/willfp/illusioner/nms/BlockBreak.java new file mode 100644 index 0000000..2eda5af --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/nms/BlockBreak.java @@ -0,0 +1,39 @@ +package com.willfp.illusioner.nms; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.nms.api.BlockBreakWrapper; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class to break a block as if the player had done it manually + */ +public class BlockBreak { + private static BlockBreakWrapper blockBreakWrapper; + + @ApiStatus.Internal + public static boolean init() { + try { + final Class class2 = Class.forName("com.willfp.illusioner." + IllusionerPlugin.NMS_VERSION + ".BlockBreak"); + if (BlockBreakWrapper.class.isAssignableFrom(class2)) { + blockBreakWrapper = (BlockBreakWrapper) class2.getConstructor().newInstance(); + } + } catch (Exception e) { + e.printStackTrace(); + blockBreakWrapper = null; + } + return blockBreakWrapper != null; + } + + /** + * Break a block as a player + * + * @param player The player to break the block as + * @param block The block to break + */ + public static void breakBlock(Player player, Block block) { + assert blockBreakWrapper != null; + blockBreakWrapper.breakBlock(player, block); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/nms/Cooldown.java b/Plugin/src/main/java/com/willfp/illusioner/nms/Cooldown.java new file mode 100644 index 0000000..d42223a --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/nms/Cooldown.java @@ -0,0 +1,38 @@ +package com.willfp.illusioner.nms; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.nms.api.CooldownWrapper; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class to get the attack cooldown of a player + */ +public class Cooldown { + private static CooldownWrapper cooldown; + + @ApiStatus.Internal + public static boolean init() { + try { + final Class class2 = Class.forName("com.willfp.illusioner." + IllusionerPlugin.NMS_VERSION + ".Cooldown"); + if (CooldownWrapper.class.isAssignableFrom(class2)) { + cooldown = (CooldownWrapper) class2.getConstructor().newInstance(); + } + } catch (Exception e) { + e.printStackTrace(); + cooldown = null; + } + return cooldown != null; + } + + /** + * Get a player's attack cooldown + * + * @param player The player to check + * @return A value between 0 and 1, with 1 representing max strength + */ + public static double getCooldown(Player player) { + assert cooldown != null; + return cooldown.getAttackCooldown(player); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/nms/NMSIllusioner.java b/Plugin/src/main/java/com/willfp/illusioner/nms/NMSIllusioner.java new file mode 100644 index 0000000..1475d7f --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/nms/NMSIllusioner.java @@ -0,0 +1,41 @@ +package com.willfp.illusioner.nms; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.nms.api.EntityIllusionerWrapper; +import com.willfp.illusioner.nms.api.IllusionerWrapper; +import org.bukkit.Location; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class to manage NMS illusioner + */ +public class NMSIllusioner { + private static IllusionerWrapper illusionerWrapper; + + @ApiStatus.Internal + public static boolean init() { + try { + final Class class2 = Class.forName("com.willfp.illusioner." + IllusionerPlugin.NMS_VERSION + ".Illusioner"); + if (IllusionerWrapper.class.isAssignableFrom(class2)) { + illusionerWrapper = (IllusionerWrapper) class2.getConstructor().newInstance(); + } + } catch (Exception e) { + e.printStackTrace(); + illusionerWrapper = null; + } + return illusionerWrapper != null; + } + + /** + * Spawn a new Illusioner Boss + * + * @param location The location to spawn it at + * @param maxHealth The max health for the illusioner to have + * @param attackDamage The attack damage for the illusioner + * @return The illusioner + */ + public static EntityIllusionerWrapper spawn(Location location, double maxHealth, double attackDamage) { + assert illusionerWrapper != null; + return illusionerWrapper.spawn(location, maxHealth, attackDamage); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/nms/OpenInventory.java b/Plugin/src/main/java/com/willfp/illusioner/nms/OpenInventory.java new file mode 100644 index 0000000..2c29a21 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/nms/OpenInventory.java @@ -0,0 +1,38 @@ +package com.willfp.illusioner.nms; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.nms.api.OpenInventoryWrapper; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class to get the NMS implementation of a players' currently open inventory + */ +public class OpenInventory { + private static OpenInventoryWrapper openInventoryWrapper; + + @ApiStatus.Internal + public static boolean init() { + try { + final Class class2 = Class.forName("com.willfp.illusioner." + IllusionerPlugin.NMS_VERSION + ".OpenInventory"); + if (OpenInventoryWrapper.class.isAssignableFrom(class2)) { + openInventoryWrapper = (OpenInventoryWrapper) class2.getConstructor().newInstance(); + } + } catch (Exception e) { + e.printStackTrace(); + openInventoryWrapper = null; + } + return openInventoryWrapper != null; + } + + /** + * Get the NMS container of the inventory + * + * @param player The player to check + * @return The NMS container + */ + public static Object getOpenInventory(Player player) { + assert openInventoryWrapper != null; + return openInventoryWrapper.getOpenInventory(player); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/nms/TridentStack.java b/Plugin/src/main/java/com/willfp/illusioner/nms/TridentStack.java new file mode 100644 index 0000000..48a4b8b --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/nms/TridentStack.java @@ -0,0 +1,40 @@ +package com.willfp.illusioner.nms; + + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.nms.api.TridentStackWrapper; +import org.bukkit.entity.Trident; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class to get the {@link ItemStack} of a given {@link Trident} + */ +public class TridentStack { + private static TridentStackWrapper tridentStackWrapper; + + @ApiStatus.Internal + public static boolean init() { + try { + final Class class2 = Class.forName("com.willfp.illusioner." + IllusionerPlugin.NMS_VERSION + ".TridentStack"); + if (TridentStackWrapper.class.isAssignableFrom(class2)) { + tridentStackWrapper = (TridentStackWrapper) class2.getConstructor().newInstance(); + } + } catch (Exception e) { + e.printStackTrace(); + tridentStackWrapper = null; + } + return tridentStackWrapper != null; + } + + /** + * Get the {@link ItemStack} of a given {@link Trident} + * + * @param trident The trident to get the ItemStack from + * @return The ItemStack associated with the trident + */ + public static ItemStack getTridentStack(Trident trident) { + assert tridentStackWrapper != null; + return tridentStackWrapper.getTridentStack(trident); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/BlockUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/BlockUtils.java new file mode 100644 index 0000000..bcc4a52 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/BlockUtils.java @@ -0,0 +1,37 @@ +package com.willfp.illusioner.util; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BlockUtils { + private static Set getNearbyBlocks(Block start, List allowedMaterials, HashSet blocks, int limit) { + for (BlockFace face : BlockFace.values()) { + Block block = start.getRelative(face); + if (!blocks.contains(block) && allowedMaterials.contains(block.getType())) { + blocks.add(block); + if (blocks.size() > limit) return blocks; + if (blocks.size() > 2500) return blocks; // anti stack overflow + blocks.addAll(getNearbyBlocks(block, allowedMaterials, blocks, limit)); + } + } + return blocks; + } + + + /** + * Get a set of all blocks in contact with each other of a specific type + * + * @param start The initial block + * @param allowedMaterials A list of all valid {@link Material}s + * @param limit The maximum size of vein to return + * @return A set of all {@link Block}s + */ + public static Set getVein(Block start, List allowedMaterials, int limit) { + return getNearbyBlocks(start, allowedMaterials, new HashSet<>(), limit); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/ClassUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/ClassUtils.java new file mode 100644 index 0000000..a0360aa --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/ClassUtils.java @@ -0,0 +1,19 @@ +package com.willfp.illusioner.util; + +public class ClassUtils { + /** + * Get if a class exists + * + * @param className The class to check + * @return If the class exists + * @see Class#forName(String) + */ + public static boolean exists(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/DurabilityUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/DurabilityUtils.java new file mode 100644 index 0000000..821ce7f --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/DurabilityUtils.java @@ -0,0 +1,112 @@ +package com.willfp.illusioner.util; + +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerItemBreakEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * Contains methods for damaging/repairing items + */ +public class DurabilityUtils { + /** + * Damage an item in a player's inventory + * The slot of a held item can be obtained with {@link PlayerInventory#getHeldItemSlot()} + * Armor slots are 39 (helmet), 38 (chestplate), 37 (leggings), 36 (boots) + * + * @param player The player + * @param item The item to damage + * @param damage The amount of damage to deal + * @param slot The slot in the inventory of the item + */ + public static void damageItem(Player player, ItemStack item, int damage, int slot) { + if (item == null) return; + if (item.getItemMeta() == null) return; + + if (item.getItemMeta().isUnbreakable()) return; + + PlayerItemDamageEvent event3 = new PlayerItemDamageEvent(player, item, damage); + Bukkit.getPluginManager().callEvent(event3); + + if (!event3.isCancelled()) { + int damage2 = event3.getDamage(); + if (item.getItemMeta() instanceof Damageable) { + Damageable meta = (Damageable) item.getItemMeta(); + meta.setDamage(meta.getDamage() + damage2); + + if (meta.getDamage() >= item.getType().getMaxDurability()) { + meta.setDamage(item.getType().getMaxDurability()); + item.setItemMeta((ItemMeta) meta); + PlayerItemBreakEvent event = new PlayerItemBreakEvent(player, item); + Bukkit.getPluginManager().callEvent(event); + player.getInventory().clear(slot); + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_BREAK, SoundCategory.BLOCKS, 1, 1); + } else { + item.setItemMeta((ItemMeta) meta); + } + } + } + } + + /** + * Damage an item in a player's inventory without breaking it + * The slot of a held item can be obtained with {@link PlayerInventory#getHeldItemSlot()} + * Armor slots are 39 (helmet), 38 (chestplate), 37 (leggings), 36 (boots) + * + * @param item The item to damage + * @param damage The amount of damage to deal + * @param player The player + */ + public static void damageItemNoBreak(ItemStack item, int damage, Player player) { + if (item == null) return; + if (item.getItemMeta() == null) return; + + if (item.getItemMeta().isUnbreakable()) return; + + PlayerItemDamageEvent event3 = new PlayerItemDamageEvent(player, item, damage); + Bukkit.getPluginManager().callEvent(event3); + + if (!event3.isCancelled()) { + int damage2 = event3.getDamage(); + if (item.getItemMeta() instanceof Damageable) { + Damageable meta = (Damageable) item.getItemMeta(); + meta.setDamage(meta.getDamage() + damage2); + + if (meta.getDamage() >= item.getType().getMaxDurability()) { + meta.setDamage(item.getType().getMaxDurability() - 1); + } + item.setItemMeta((ItemMeta) meta); + } + } + } + + /** + * Repair an item in a player's inventory + * The slot of a held item can be obtained with {@link PlayerInventory#getHeldItemSlot()} + * Armor slots are 39 (helmet), 38 (chestplate), 37 (leggings), 36 (boots) + * + * @param item The item to damage + * @param repair The amount of damage to heal + */ + public static void repairItem(ItemStack item, int repair) { + if (item == null) return; + if (item.getItemMeta() == null) return; + + if (item.getItemMeta().isUnbreakable()) return; + if (item.getItemMeta() instanceof Damageable) { + Damageable meta = (Damageable) item.getItemMeta(); + meta.setDamage(meta.getDamage() - repair); + + if (meta.getDamage() < 0) { + meta.setDamage(0); + } + item.setItemMeta((ItemMeta) meta); + } + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/LightningUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/LightningUtils.java new file mode 100644 index 0000000..6a6c209 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/LightningUtils.java @@ -0,0 +1,26 @@ +package com.willfp.illusioner.util; + +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; + +/** + * Class containing methods for striking lightning + */ +public class LightningUtils { + + /** + * Strike lightning on player without fire + * + * @param victim The entity to smite + * @param damage The damage to deal + */ + public static void strike(LivingEntity victim, double damage) { + if (victim == null) return; + + Location loc = victim.getLocation(); + + victim.getWorld().strikeLightningEffect(loc); + + victim.damage(damage); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/NumberUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/NumberUtils.java new file mode 100644 index 0000000..b447c90 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/NumberUtils.java @@ -0,0 +1,148 @@ +package com.willfp.illusioner.util; + +import java.text.DecimalFormat; +import java.util.TreeMap; + +public class NumberUtils { + + private final static TreeMap NUMERALS = new TreeMap<>(); + + static { + + NUMERALS.put(1000, "M"); + NUMERALS.put(900, "CM"); + NUMERALS.put(500, "D"); + NUMERALS.put(400, "CD"); + NUMERALS.put(100, "C"); + NUMERALS.put(90, "XC"); + NUMERALS.put(50, "L"); + NUMERALS.put(40, "XL"); + NUMERALS.put(10, "X"); + NUMERALS.put(9, "IX"); + NUMERALS.put(5, "V"); + NUMERALS.put(4, "IV"); + NUMERALS.put(1, "I"); + + } + + /** + * Bias the input value according to a curve + * + * @param input The input value + * @param bias The bias between -1 and 1, where higher values bias input values to lower output values + * @return The biased output + */ + public static double bias(double input, double bias) { + double k = Math.pow(1 - bias, 3); + + return (input * k) / (input * k - input + 1); + } + + /** + * If value is above maximum, set it to maximum + * + * @param toChange The value to test + * @param limit The maximum + * @return The new value + */ + public static int equalIfOver(int toChange, int limit) { + if (toChange > limit) { + toChange = limit; + } + return toChange; + } + + /** + * If value is above maximum, set it to maximum + * + * @param toChange The value to test + * @param limit The maximum + * @return The new value + */ + public static double equalIfOver(double toChange, double limit) { + if (toChange > limit) { + toChange = limit; + } + return toChange; + } + + /** + * Get Roman Numeral from number + * + * @param number The number to convert + * @return The number, converted to a roman numeral + */ + public static String toNumeral(int number) { + if (number >= 1 && number <= 4096) { + int l = NUMERALS.floorKey(number); + if (number == l) { + return NUMERALS.get(number); + } + return NUMERALS.get(l) + toNumeral(number - l); + } else return String.valueOf(number); + } + + /** + * Generate random integer in range + * + * @param min Minimum + * @param max Maximum + * @return Random integer + */ + public static int randInt(int min, int max) { + return (int) ((long) min + Math.random() * ((long) max - min + 1)); + } + + /** + * Generate random double in range + * + * @param min Minimum + * @param max Maximum + * @return Random double + */ + public static double randFloat(double min, double max) { + java.util.Random rand = new java.util.Random(); + return rand.nextFloat() * (max - min) + min; + } + + /** + * Generate random double with a triangular distribution + * + * @param minimum Minimum + * @param maximum Maximum + * @param peak Peak + * @return Random double + */ + public static double triangularDistribution(double minimum, double maximum, double peak) { + double F = (peak - minimum) / (maximum - minimum); + double rand = Math.random(); + if (rand < F) { + return minimum + Math.sqrt(rand * (maximum - minimum) * (peak - minimum)); + } else { + return maximum - Math.sqrt((1 - rand) * (maximum - minimum) * (maximum - peak)); + } + } + + /** + * Get Log base 2 of a number + * + * @param N The number + * @return The result + */ + public static int log2(int N) { + return (int) (Math.log(N) / Math.log(2)); + } + + /** + * Format double to string + * + * @param toFormat The number to format + * @return Formatted + */ + public static String format(double toFormat) { + DecimalFormat df = new DecimalFormat("0.00"); + String formatted = df.format(toFormat); + + return formatted.endsWith("00") ? String.valueOf((int) toFormat) : formatted; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/StringUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/StringUtils.java new file mode 100644 index 0000000..aa35e46 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/StringUtils.java @@ -0,0 +1,94 @@ +package com.willfp.illusioner.util; + +import com.willfp.illusioner.integrations.placeholder.PlaceholderManager; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static net.md_5.bungee.api.ChatColor.COLOR_CHAR; + +public class StringUtils { + /** + * Translate a string - converts Placeholders and Color codes + * + * @param message The message to translate + * @param player The player to translate placeholders with respect to + * @return The message, translated + */ + public static String translate(String message, @Nullable Player player) { + message = PlaceholderManager.translatePlaceholders(message, player); + message = translateHexColorCodes(message); + message = ChatColor.translateAlternateColorCodes('&', message); + return ChatColor.translateAlternateColorCodes('&', translateHexColorCodes(message)); + } + + /** + * Translate a string without respect to a player + * + * @param message The message to translate + * @return The message, translated + * @see StringUtils#translate(String, Player) + */ + public static String translate(String message) { + message = PlaceholderManager.translatePlaceholders(message, null); + message = translateHexColorCodes(message); + message = ChatColor.translateAlternateColorCodes('&', message); + return ChatColor.translateAlternateColorCodes('&', translateHexColorCodes(message)); + } + + private static String translateHexColorCodes(String message) { + Pattern hexPattern = Pattern.compile("&#" + "([A-Fa-f0-9]{6})" + ""); + Matcher matcher = hexPattern.matcher(message); + StringBuffer buffer = new StringBuffer(message.length() + 4 * 8); + while (matcher.find()) { + String group = matcher.group(1); + matcher.appendReplacement(buffer, COLOR_CHAR + "x" + + COLOR_CHAR + group.charAt(0) + COLOR_CHAR + group.charAt(1) + + COLOR_CHAR + group.charAt(2) + COLOR_CHAR + group.charAt(3) + + COLOR_CHAR + group.charAt(4) + COLOR_CHAR + group.charAt(5)); + } + + return matcher.appendTail(buffer).toString(); + } + + /** + * Internal implementation of {@link String#valueOf} + * Formats collections and doubles better + * + * @param object The object to convert to string + * @return The object stringified + */ + public static String internalToString(@Nullable Object object) { + if (object == null) return "null"; + + if (object instanceof Integer) { + return ((Integer) object).toString(); + } else if (object instanceof String) { + return (String) object; + } else if (object instanceof Double) { + return NumberUtils.format((Double) object); + } else if (object instanceof Collection) { + Collection c = (Collection) object; + return c.stream().map(String::valueOf).collect(Collectors.joining(", ")); + } else return String.valueOf(object); + } + + /** + * Remove a string of characters from the start of a string + * + * @param s The string to remove the prefix from + * @param prefix The substring to remove + * @return The string with the prefix removed + */ + public static String removePrefix(String s, String prefix) { + if (s != null && prefix != null && s.startsWith(prefix)) { + return s.substring(prefix.length()); + } + return s; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/VectorUtils.java b/Plugin/src/main/java/com/willfp/illusioner/util/VectorUtils.java new file mode 100644 index 0000000..7914328 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/VectorUtils.java @@ -0,0 +1,141 @@ +package com.willfp.illusioner.util; + +import org.bukkit.util.NumberConversions; +import org.bukkit.util.Vector; + +import java.util.ArrayList; + +public class VectorUtils { + /** + * If vector has all components as finite + * + * @param vector The vector to check + * @return If the vector is finite + */ + public static boolean isFinite(Vector vector) { + try { + NumberConversions.checkFinite(vector.getX(), "x not finite"); + NumberConversions.checkFinite(vector.getY(), "y not finite"); + NumberConversions.checkFinite(vector.getZ(), "z not finite"); + } catch (IllegalArgumentException e) { + return false; + } + + return true; + } + + /** + * Only keep largest part of normalised vector. + * For example: -0.8, 0.01, -0.2 would become -1, 0, 0 + * + * @param vec The vector to simplify + * @return The vector, simplified + */ + public static Vector simplifyVector(Vector vec) { + double x = Math.abs(vec.getX()); + double y = Math.abs(vec.getY()); + double z = Math.abs(vec.getZ()); + double max = Math.max(x, Math.max(y, z)); + if (x > 1 || z > 1) { + max = y; + } + if (max == x) { + if (vec.getX() < 0) { + return new Vector(-1, 0, 0); + } + return new Vector(1, 0, 0); + } else if (max == y) { + if (vec.getY() < 0) { + return new Vector(0, -1, 0); + } + return new Vector(0, 1, 0); + } else { + if (vec.getZ() < 0) { + return new Vector(0, 0, -1); + } + return new Vector(0, 0, 1); + } + } + + /** + * Get circle as relative vectors + * + * @param radius The radius + * @return An array of {@link Vector}s + */ + public static Vector[] getCircle(int radius) { + ArrayList circleVecs = new ArrayList<>(); + + int xoffset = -radius; + int zoffset = -radius; + + while (zoffset <= radius) { + while (xoffset <= radius) { + if (Math.round(Math.sqrt((xoffset * xoffset) + (zoffset * zoffset))) <= radius) { + circleVecs.add(new Vector(xoffset, 0, zoffset)); + } else { + xoffset++; + continue; + } + xoffset++; + } + xoffset = -radius; + zoffset++; + } + + return circleVecs.toArray(new Vector[0]); + } + + /** + * Get square as relative vectors + * + * @param radius The radius of the square + * @return An array of {@link Vector}s + */ + public static Vector[] getSquare(int radius) { + ArrayList circleVecs = new ArrayList<>(); + + int xoffset = -radius; + int zoffset = -radius; + + while (zoffset <= radius) { + while (xoffset <= radius) { + circleVecs.add(new Vector(xoffset, 0, zoffset)); + xoffset++; + } + xoffset = -radius; + zoffset++; + } + + return circleVecs.toArray(new Vector[0]); + } + + /** + * Get cube as relative vectors + * + * @param radius The radius of the cube + * @return An array of {@link Vector}s + */ + public static Vector[] getCube(int radius) { + ArrayList cubeVecs = new ArrayList<>(); + + int xoffset = -radius; + int zoffset = -radius; + int yoffset = -radius; + + while (yoffset <= radius) { + while (zoffset <= radius) { + while (xoffset <= radius) { + cubeVecs.add(new Vector(xoffset, yoffset, zoffset)); + xoffset++; + } + xoffset = -radius; + zoffset++; + } + zoffset = -radius; + yoffset++; + } + + return cubeVecs.toArray(new Vector[0]); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Callable.java b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Callable.java new file mode 100644 index 0000000..4d77fc7 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Callable.java @@ -0,0 +1,9 @@ +package com.willfp.illusioner.util.interfaces; + +/** + * Simple functional interface to run some code on demand + */ +@FunctionalInterface +public interface Callable { + void call(); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectBiCallable.java b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectBiCallable.java new file mode 100644 index 0000000..06404e2 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectBiCallable.java @@ -0,0 +1,12 @@ +package com.willfp.illusioner.util.interfaces; + +/** + * Functional Interface to return a value of a specified type given a certain parameter + * + * @param The type of object to return + * @param The type of object for the parameter + */ +@FunctionalInterface +public interface ObjectBiCallable { + A call(B object); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectCallable.java b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectCallable.java new file mode 100644 index 0000000..16fc3ba --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/ObjectCallable.java @@ -0,0 +1,11 @@ +package com.willfp.illusioner.util.interfaces; + +/** + * Functional Interface to return a value of a given type + * + * @param The type to return + */ +@FunctionalInterface +public interface ObjectCallable { + A call(); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Registerable.java b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Registerable.java new file mode 100644 index 0000000..39f4406 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/interfaces/Registerable.java @@ -0,0 +1,8 @@ +package com.willfp.illusioner.util.interfaces; + +/** + * Interface for objects that can be internally registered + */ +public interface Registerable { + void register(); +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/internal/Loader.java b/Plugin/src/main/java/com/willfp/illusioner/util/internal/Loader.java new file mode 100644 index 0000000..7ab1ae4 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/internal/Loader.java @@ -0,0 +1,270 @@ +package com.willfp.illusioner.util.internal; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.command.commands.CommandIldebug; +import com.willfp.illusioner.command.commands.CommandIlreload; +import com.willfp.illusioner.config.ConfigManager; +import com.willfp.illusioner.events.armorequip.ArmorListener; +import com.willfp.illusioner.events.armorequip.DispenserArmorListener; +import com.willfp.illusioner.events.entitydeathbyentity.EntityDeathByEntityListeners; +import com.willfp.illusioner.events.naturalexpgainevent.NaturalExpGainListeners; +import com.willfp.illusioner.illusioner.AttackListeners; +import com.willfp.illusioner.illusioner.DeathListeners; +import com.willfp.illusioner.illusioner.SpawnListeners; +import com.willfp.illusioner.integrations.anticheat.AnticheatManager; +import com.willfp.illusioner.integrations.anticheat.plugins.AnticheatAAC; +import com.willfp.illusioner.integrations.anticheat.plugins.AnticheatMatrix; +import com.willfp.illusioner.integrations.anticheat.plugins.AnticheatNCP; +import com.willfp.illusioner.integrations.anticheat.plugins.AnticheatSpartan; +import com.willfp.illusioner.integrations.antigrief.AntigriefManager; +import com.willfp.illusioner.integrations.antigrief.plugins.AntigriefFactionsUUID; +import com.willfp.illusioner.integrations.antigrief.plugins.AntigriefGriefPrevention; +import com.willfp.illusioner.integrations.antigrief.plugins.AntigriefKingdoms; +import com.willfp.illusioner.integrations.antigrief.plugins.AntigriefLands; +import com.willfp.illusioner.integrations.antigrief.plugins.AntigriefTowny; +import com.willfp.illusioner.integrations.antigrief.plugins.AntigriefWorldGuard; +import com.willfp.illusioner.integrations.anvilgui.AnvilGUIManager; +import com.willfp.illusioner.integrations.anvilgui.plugins.AnvilGUIImpl; +import com.willfp.illusioner.integrations.mcmmo.McmmoManager; +import com.willfp.illusioner.integrations.mcmmo.plugins.McmmoIntegrationImpl; +import com.willfp.illusioner.integrations.placeholder.PlaceholderManager; +import com.willfp.illusioner.integrations.placeholder.plugins.PlaceholderIntegrationPAPI; +import com.willfp.illusioner.nms.BlockBreak; +import com.willfp.illusioner.nms.Cooldown; +import com.willfp.illusioner.nms.NMSIllusioner; +import com.willfp.illusioner.nms.OpenInventory; +import com.willfp.illusioner.nms.TridentStack; +import com.willfp.illusioner.util.interfaces.Callable; +import com.willfp.illusioner.util.internal.updater.PlayerJoinListener; +import com.willfp.illusioner.util.internal.updater.UpdateChecker; +import com.willfp.illusioner.util.optional.Prerequisite; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Class containing methods for the loading and unloading of Illusioner + */ +public class Loader { + + /** + * Called by {@link IllusionerPlugin#onEnable()} + */ + public static void load() { + Logger.info("=========================================="); + Logger.info(""); + Logger.info("Loading &9Illusioner"); + Logger.info("Made by &9Auxilor&f - willfp.com"); + Logger.info(""); + Logger.info("=========================================="); + + /* + Load Configs + */ + + Logger.info("Loading Configs..."); + ConfigManager.updateConfigs(); + Logger.info(""); + + /* + Load NMS + */ + + Logger.info("Loading NMS APIs..."); + + if (Cooldown.init()) { + Logger.info("Cooldown: &aSUCCESS"); + } else { + Logger.info("Cooldown: &cFAILURE"); + Logger.error("&cAborting..."); + Bukkit.getPluginManager().disablePlugin(IllusionerPlugin.getInstance()); + } + + if (TridentStack.init()) { + Logger.info("Trident API: &aSUCCESS"); + } else { + Logger.info("Trident API: &cFAILURE"); + Logger.error("&cAborting..."); + Bukkit.getPluginManager().disablePlugin(IllusionerPlugin.getInstance()); + } + + if (BlockBreak.init()) { + Logger.info("Block Break: &aSUCCESS"); + } else { + Logger.info("Block Break: &cFAILURE"); + Logger.error("&cAborting..."); + Bukkit.getPluginManager().disablePlugin(IllusionerPlugin.getInstance()); + } + + if (OpenInventory.init()) { + Logger.info("Open Inventory: &aSUCCESS"); + } else { + Logger.info("Open Inventory: &cFAILURE"); + Logger.error("&cAborting..."); + Bukkit.getPluginManager().disablePlugin(IllusionerPlugin.getInstance()); + } + + if (NMSIllusioner.init()) { + Logger.info("NMS Illusioner: &aSUCCESS"); + } else { + Logger.info("NMS Illusioner: &cFAILURE"); + Logger.error("&cAborting..."); + Bukkit.getPluginManager().disablePlugin(IllusionerPlugin.getInstance()); + } + + Logger.info(""); + + /* + Register Events + */ + + Logger.info("Registering Events..."); + Bukkit.getPluginManager().registerEvents(new ArmorListener(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new DispenserArmorListener(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new PlayerJoinListener(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new EntityDeathByEntityListeners(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new NaturalExpGainListeners(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new AttackListeners(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new DeathListeners(), IllusionerPlugin.getInstance()); + Bukkit.getPluginManager().registerEvents(new SpawnListeners(), IllusionerPlugin.getInstance()); + Logger.info(""); + + /* + Load integrations + */ + + Logger.info("Loading Integrations..."); + + final HashMap integrations = new HashMap() {{ + // AntiGrief + put("WorldGuard", () -> AntigriefManager.register(new AntigriefWorldGuard())); + put("GriefPrevention", () -> AntigriefManager.register(new AntigriefGriefPrevention())); + put("FactionsUUID", () -> AntigriefManager.register(new AntigriefFactionsUUID())); + put("Towny", () -> AntigriefManager.register(new AntigriefTowny())); + put("Lands", () -> AntigriefManager.register(new AntigriefLands())); + put("Kingdoms", () -> AntigriefManager.register(new AntigriefKingdoms())); + + // AntiCheat + put("AAC", () -> AnticheatManager.register(new AnticheatAAC())); + put("Matrix", () -> AnticheatManager.register(new AnticheatMatrix())); + put("NoCheatPlus", () -> AnticheatManager.register(new AnticheatNCP())); + put("Spartan", () -> AnticheatManager.register(new AnticheatSpartan())); + + // MISC + put("PlaceholderAPI", () -> PlaceholderManager.addIntegration(new PlaceholderIntegrationPAPI())); + put("mcMMO", () -> McmmoManager.registerIntegration(new McmmoIntegrationImpl())); + }}; + + Set enabledPlugins = Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName).collect(Collectors.toSet()); + + integrations.forEach(((s, callable) -> { + StringBuilder log = new StringBuilder(); + log.append(s).append(": "); + if (enabledPlugins.contains(s)) { + callable.call(); + log.append("&aENABLED"); + } else { + log.append("&9DISABLED"); + } + Logger.info(log.toString()); + })); + + Prerequisite.update(); + AnvilGUIManager.registerIntegration(new AnvilGUIImpl()); // No direct lib, can always register + Logger.info(""); + + + /* + Load Commands + */ + + Logger.info("Loading Commands..."); + new CommandIlreload().register(); + new CommandIldebug().register(); + Logger.info(""); + + /* + Start bStats + */ + + Logger.info("Hooking into bStats..."); + new Metrics(IllusionerPlugin.getInstance(), 7666); + Logger.info(""); + + /* + Finish + */ + + Bukkit.getScheduler().runTaskLater(IllusionerPlugin.getInstance(), Loader::postLoad, 1); + + Logger.info("Loaded &9Illusioner!"); + } + + /** + * Called after server is loaded + */ + public static void postLoad() { + new UpdateChecker(IllusionerPlugin.getInstance(), 79573).getVersion((version) -> { + DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(IllusionerPlugin.getInstance().getDescription().getVersion()); + DefaultArtifactVersion mostRecentVersion = new DefaultArtifactVersion(version); + Logger.info("----------------------------"); + Logger.info(""); + Logger.info("Illusioner Updater"); + Logger.info(""); + if (currentVersion.compareTo(mostRecentVersion) > 0 || currentVersion.equals(mostRecentVersion)) { + Logger.info("&aIllusioner is up to date! (Version " + IllusionerPlugin.getInstance().getDescription().getVersion() + ")"); + } else { + UpdateChecker.setOutdated(true); + UpdateChecker.setNewVersion(version); + + Bukkit.getScheduler().runTaskTimer(IllusionerPlugin.getInstance(), () -> { + Logger.info("&6Illusioner is out of date! (Version " + IllusionerPlugin.getInstance().getDescription().getVersion() + ")"); + Logger.info("&6The newest version is &f" + version); + Logger.info("&6Download the new version here: &fhttps://www.spigotmc.org/resources/illusioner.79573/"); + }, 0, 864000); + } + Logger.info(""); + Logger.info("----------------------------"); + }); + + /* + Check for paper + */ + + if (!Prerequisite.HasPaper.isMet()) { + Logger.error(""); + Logger.error("----------------------------"); + Logger.error(""); + Logger.error("You don't seem to be running paper!"); + Logger.error("Paper is strongly recommended for all servers,"); + Logger.error("and some features may not function properly without it"); + Logger.error("Download Paper from &fhttps://papermc.io"); + Logger.error(""); + Logger.error("----------------------------"); + Logger.error(""); + } + + Logger.info(""); + } + + /** + * Called by {@link IllusionerPlugin#onDisable()} + */ + public static void unload() { + Logger.info("&cDisabling Illusioner..."); + Logger.info("&fBye! :)"); + } + + /** + * Called by /ecoreload + */ + public static void reload() { + ConfigManager.updateConfigs(); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/internal/Logger.java b/Plugin/src/main/java/com/willfp/illusioner/util/internal/Logger.java new file mode 100644 index 0000000..48f534a --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/internal/Logger.java @@ -0,0 +1,39 @@ +package com.willfp.illusioner.util.internal; + +import com.willfp.illusioner.IllusionerPlugin; +import com.willfp.illusioner.util.StringUtils; + +/** + * The internal logger for Illusioner + * Automatically formats all inputs using {@link StringUtils#translate(String)} + */ +public class Logger { + private static final IllusionerPlugin INSTANCE = IllusionerPlugin.getInstance(); + + /** + * Print an info (neutral) message to console + * + * @param message The message to send + */ + public static void info(String message) { + INSTANCE.getLogger().info(StringUtils.translate(message)); + } + + /** + * Print a warning to console + * + * @param message The warning + */ + public static void warn(String message) { + INSTANCE.getLogger().warning(StringUtils.translate(message)); + } + + /** + * Print an error to console + * + * @param message The error + */ + public static void error(String message) { + INSTANCE.getLogger().severe(StringUtils.translate(message)); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/PlayerJoinListener.java b/Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/PlayerJoinListener.java new file mode 100644 index 0000000..9b8fae2 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/PlayerJoinListener.java @@ -0,0 +1,19 @@ +package com.willfp.illusioner.util.internal.updater; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class PlayerJoinListener implements Listener { + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + /* + if (UpdateChecker.isOutdated()) { + if (event.getPlayer().hasPermission("illusioner.updateannounce")) { + event.getPlayer().sendMessage(ConfigManager.getLang().getMessage("outdated").replace("%ver%", IllusionerPlugin.getInstance().getDescription().getVersion()) + .replace("%newver%", UpdateChecker.getNewVersion())); + } + } + */ + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/UpdateChecker.java b/Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/UpdateChecker.java new file mode 100644 index 0000000..5a0f86d --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/internal/updater/UpdateChecker.java @@ -0,0 +1,86 @@ +package com.willfp.illusioner.util.internal.updater; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.Consumer; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Scanner; + +/** + * Checks spigot if the plugin is out of date + */ +public class UpdateChecker { + private static boolean outdated; + private static String newVersion; + + private final Plugin plugin; + private final int resourceId; + + /** + * Create an update checker for the specified spigot resource id + * + * @param plugin The plugin to check + * @param resourceId The resource ID of the plugin + */ + public UpdateChecker(Plugin plugin, int resourceId) { + this.plugin = plugin; + this.resourceId = resourceId; + } + + /** + * Get the latest version of the plugin + * + * @param consumer The process to run after checking + */ + public void getVersion(final Consumer consumer) { + Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { + try (InputStream inputStream = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream(); Scanner scanner = new Scanner(inputStream)) { + if (scanner.hasNext()) { + consumer.accept(scanner.next()); + } + } catch (IOException exception) { + this.plugin.getLogger().warning("Failed to check for Illusioner updates: " + exception.getMessage()); + } + }); + } + + /** + * Get if the plugin is outdated + * + * @return If the plugin is outdated + */ + public static boolean isOutdated() { + return outdated; + } + + /** + * Get the newest available version of the plugin + * + * @return The latest version + */ + public static String getNewVersion() { + return newVersion; + } + + /** + * Mark the plugin as outdated or not + * + * @param outdated Whether the plugin is outdated + */ + public static void setOutdated(boolean outdated) { + UpdateChecker.outdated = outdated; + } + + /** + * Set the newest available version of the plugin + * + * @param newVersion The newest version + */ + public static void setNewVersion(String newVersion) { + UpdateChecker.newVersion = newVersion; + } +} + \ No newline at end of file diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/optional/Prerequisite.java b/Plugin/src/main/java/com/willfp/illusioner/util/optional/Prerequisite.java new file mode 100644 index 0000000..5ca1249 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/optional/Prerequisite.java @@ -0,0 +1,89 @@ +package com.willfp.illusioner.util.optional; + +import com.willfp.illusioner.util.ClassUtils; +import com.willfp.illusioner.util.interfaces.ObjectCallable; +import org.bukkit.Bukkit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * An object representing a condition that must be met in order to perform an action + */ +public class Prerequisite { + private static final List values = new ArrayList<>(); + + /** + * Requires the server to be running minecraft version 1.16 or higher + */ + public static final Prerequisite MinVer1_16 = new Prerequisite( + () -> !Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3].contains("15"), + "Requires minimum server version of 1.16" + ); + + /** + * Requires the server to be running an implementation of paper + */ + public static final Prerequisite HasPaper = new Prerequisite( + () -> ClassUtils.exists("com.destroystokyo.paper.event.player.PlayerElytraBoostEvent"), + "Requires server to be running paper (or a fork)" + ); + + private boolean isMet; + private final ObjectCallable isMetCallable; + private final String description; + + /** + * Create a prerequisite + * + * @param isMetCallable An {@link ObjectCallable} that returns if the prerequisite is met + * @param description The description of the prerequisite, shown to the user if it isn't + */ + public Prerequisite(ObjectCallable isMetCallable, String description) { + this.isMetCallable = isMetCallable; + this.isMet = isMetCallable.call(); + this.description = description; + values.add(this); + } + + /** + * Get the description of the prerequisite + * + * @return The description + */ + public String getDescription() { + return description; + } + + /** + * Get if the prerequisite has been met + * + * @return If the prerequisite is met + */ + public boolean isMet() { + return isMet; + } + + private void refresh() { + this.isMet = this.isMetCallable.call(); + } + + /** + * Update all prerequisites' {@link Prerequisite#isMet} + */ + public static void update() { + values.forEach(Prerequisite::refresh); + } + + /** + * Check if all prerequisites in array are met + * + * @param prerequisites A primitive array of prerequisites to check + * @return If all the prerequisites are met + */ + public static boolean areMet(Prerequisite[] prerequisites) { + update(); + return Arrays.stream(prerequisites).allMatch(Prerequisite::isMet); + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Pair.java b/Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Pair.java new file mode 100644 index 0000000..97955c3 --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Pair.java @@ -0,0 +1,64 @@ +package com.willfp.illusioner.util.tuplets; + +/** + * Spigot doesn't include javafx + */ +public class Pair { + private A first; + private B second; + + /** + * Create a pair + * + * @param first The first item in the tuplet + * @param second The second item in the tuplet + */ + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + /** + * Get the first item in the tuplet + * + * @return The first item + */ + public A getFirst() { + return first; + } + + /** + * Get the second item in the tuplet + * + * @return The second item + */ + public B getSecond() { + return second; + } + + /** + * Set the first item in the tuplet + * + * @param first The value to set the first item to + */ + public void setFirst(A first) { + this.first = first; + } + + /** + * Set the second item in the tuplet + * + * @param second The value to set the second item to + */ + public void setSecond(B second) { + this.second = second; + } + + @Override + public String toString() { + return "Pair{" + + "first=" + first + + ", second=" + second + + '}'; + } +} diff --git a/Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Triplet.java b/Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Triplet.java new file mode 100644 index 0000000..9425dbd --- /dev/null +++ b/Plugin/src/main/java/com/willfp/illusioner/util/tuplets/Triplet.java @@ -0,0 +1,86 @@ +package com.willfp.illusioner.util.tuplets; + +/** + * Spigot doesn't include javafx + */ +public class Triplet { + private A first; + private B second; + private C third; + + /** + * Create a triplet + * + * @param first The first item in the tuplet + * @param second The second item in the tuplet + * @param third The third item in the tuplet + */ + public Triplet(A first, B second, C third) { + this.first = first; + this.second = second; + this.third = third; + } + + /** + * Get the first item in the tuplet + * + * @return The first item + */ + public A getFirst() { + return first; + } + + /** + * Get the second item in the tuplet + * + * @return The second item + */ + public B getSecond() { + return second; + } + + /** + * Get the third item in the tuplet + * + * @return The third item + */ + public C getThird() { + return third; + } + + /** + * Set the first item in the tuplet + * + * @param first The value to set + */ + public void setFirst(A first) { + this.first = first; + } + + /** + * Set the second item in the tuplet + * + * @param second The value to set + */ + public void setSecond(B second) { + this.second = second; + } + + /** + * Set the third item in the tuplet + * + * @param third The value to set + */ + public void setThird(C third) { + this.third = third; + } + + @Override + public String toString() { + return "Triplet{" + + "first=" + first + + ", second=" + second + + ", third=" + third + + '}'; + } +} diff --git a/Plugin/src/main/resources/config.yml b/Plugin/src/main/resources/config.yml new file mode 100644 index 0000000..a6dbb8a --- /dev/null +++ b/Plugin/src/main/resources/config.yml @@ -0,0 +1,4 @@ +# +# Illusioner +# by Auxilor +# \ No newline at end of file diff --git a/Plugin/src/main/resources/lang.yml b/Plugin/src/main/resources/lang.yml new file mode 100644 index 0000000..c144af1 --- /dev/null +++ b/Plugin/src/main/resources/lang.yml @@ -0,0 +1,5 @@ +messages: + prefix: "&9&lIllusioner &f» " + no-permission: "&cYou don't have permission to do this!" + not-player: "&cThis command must be run by a player" + reloaded: "Reloaded!" \ No newline at end of file diff --git a/Plugin/src/main/resources/plugin.yml b/Plugin/src/main/resources/plugin.yml new file mode 100644 index 0000000..949591d --- /dev/null +++ b/Plugin/src/main/resources/plugin.yml @@ -0,0 +1,47 @@ +name: Illusioner +version: ${projectVersion} +main: com.willfp.illusioner.IllusionerPlugin +api-version: 1.15 +authors: [Auxilor] +website: willfp.com +load: STARTUP +softdepend: + - WorldGuard + - GriefPrevention + - Towny + - FactionsUUID + - Lands + - Kingdoms + - NoCheatPlus + - AAC + - Matrix + - Spartan + - PlaceholderAPI + - mcMMO + +commands: + ilreload: + description: Reloads config + permission: illusioner.reload + ildebug: + description: Debug information + permission: illusioner.debug + +permissions: + illusioner.*: + description: All illusioner permissions + default: op + children: + illusioner.reload: true + illusioner.updateannounce: true + illusioner.ecodebug: true + + illusioner.updateannounce: + description: Informs admins of a new update + default: op + illusioner.reload: + description: Allows reloading the config + default: op + illusioner.ecodebug: + description: Allows the use of /ildebug to print verbose debug information to console + default: op diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f7b1fb --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +

+
+ Illusioner logo +
+

+ +

Source code for Illusioner, a premium spigot plugin.

+ +

+ + spigot + + + + + + + + + + + + + +

+ + +[![Title](https://i.imgur.com/hyPlV1m.png)]() +[![Features](https://i.imgur.com/Tqcu1o2.png)]() +[![Docs](https://i.imgur.com/TRDDt5W.png)](https://ecoenchants.willfp.com/enchantments/all-enchantments) +[![Compatibility](https://i.imgur.com/mlAGlKn.png)]() + +## License +*Click here to read [the entire license](https://github.com/Auxilor/Illusioner/blob/master/LICENSE.md).* + +Illusioner is not freeware. In order to use it you must purchase a license on [spigotmc.org](https://spigotmc.org). +Distributing compiled versions of the plugin is not permitted. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1c96d77 --- /dev/null +++ b/build.gradle @@ -0,0 +1,123 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'java' +} + +allprojects { + apply plugin: 'java' + + repositories { + mavenCentral() + jcenter() + mavenLocal() + maven { + url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' + } + + maven { + url 'https://repo.codemc.org/repository/maven-public' + } + + maven { + url 'https://oss.sonatype.org/content/groups/public/' + } + + maven { + url 'https://maven.enginehub.org/repo/' + } + + maven { + url 'https://jitpack.io' + } + + maven { + url 'https://ci.ender.zone/plugin/repository/project/' + } + + maven { + url 'https://ci.ender.zone/plugin/repository/everything/' + } + + maven { + url 'https://repo.md-5.net/content/repositories/snapshots/' + } + + maven { + url 'https://repo.janmm14.de/repository/public/' + } + + maven { + url 'https://repo.dmulloy2.net/nexus/repository/public/' + } + + maven { + url 'https://papermc.io/repo/repository/maven-public/' + } + + maven { + url 'https://repo.maven.apache.org/maven2/' + } + + maven { + url 'https://repo.dustplanet.de/artifactory/ext-release-local/' + } + + maven { + url 'https://maven.seyfahni.de/repository/snapshots/' + } + + maven { + url 'https://libraries.minecraft.net/' + } + + maven { + url 'https://repo.spongepowered.org/maven/' + } + + maven { + url 'https://org.kitteh.pastegg' + } + + maven { + url 'https://repo.mikeprimm.com/' + } + + maven { + url 'https://maven.sk89q.com/repo/' + } + + maven { + url 'https://github.com/factions-site/repo/raw/public/' + } + + maven { + url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' + } + } + + tasks.withType(JavaCompile) { + options.deprecation = true + options.encoding = 'UTF-8' + } + compileJava.options.encoding = 'UTF-8' + + compileJava.dependsOn clean +} + +clean.doLast { + file("${rootDir}/bin").deleteDir() +} + +group = 'com.willfp.illusioner' +archivesBaseName = project.name +version = project.version +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..3256d99 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +version = 1.0.0 +plugin-name = Illusioner \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..622ab64 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..fbd7c51 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/SpartanAPI.jar b/lib/SpartanAPI.jar new file mode 100644 index 0000000000000000000000000000000000000000..1f2d578a14103df362628c2d461c260fda90635f GIT binary patch literal 15405 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 literal 0 HcmV?d00001 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9592b4a --- /dev/null +++ b/settings.gradle @@ -0,0 +1,14 @@ +rootProject.name = 'parent' +include('v1_15_R1') +include('plugin') +include('API') +include('v1_16_R1') +include('v1_16_R2') +include('v1_16_R3') + +project(":v1_15_R1").projectDir = file('NMS/v1_15_R1') +project(":plugin").projectDir = file('Plugin') +project(":API").projectDir = file('NMS/API') +project(":v1_16_R1").projectDir = file('NMS/v1_16_R1') +project(":v1_16_R2").projectDir = file('NMS/v1_16_R2') +project(":v1_16_R3").projectDir = file('NMS/v1_16_R3') \ No newline at end of file