diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..eebec9d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: "chore(deps)" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..2206d70 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,52 @@ +name: Publish Snapshot + +on: + push: + # any branch in this repo + branches: + - '**' + +jobs: + publish-snapshot: + runs-on: ubuntu-latest + environment: Gradle Deploy + permissions: + contents: write + if: "!endsWith(github.actor, '[bot]')" + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for accurate version extraction + + - name: Set up Java 8 & cache Gradle + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + cache: gradle + server-id: stagingDeploy + settings-path: ${{ github.workspace }} + + - name: Extract project version + id: get-version + run: | + echo "version=$(./gradlew -q properties \ + | grep '^version:' \ + | awk '{print $2}')" >> $GITHUB_OUTPUT + + - name: Publish snapshot to Maven + if: endsWith(steps.get-version.outputs.version, '-SNAPSHOT') + run: ./gradlew publish --no-daemon + + - name: Release with JReleaser + if: endsWith(steps.get-version.outputs.version, '-SNAPSHOT') + run: ./gradlew jreleaserRelease --no-daemon --no-configuration-cache + env: + JRELEASER_GITHUB_TOKEN: ${{ secrets.JRELEASER_GITHUB_TOKEN }} + JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} + JRELEASER_NEXUS2_SONATYPESNAPSHOTS_TOKEN: ${{ secrets.JRELEASER_NEXUS2_SONATYPESNAPSHOTS_TOKEN }} + JRELEASER_NEXUS2_SONATYPESNAPSHOTS_USERNAME: ${{ secrets.JRELEASER_NEXUS2_SONATYPESNAPSHOTS_USERNAME }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9dd7f72 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Java 8 & cache Gradle + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + cache: gradle + + - name: Build & run tests + run: ./gradlew clean build --no-daemon diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 84198b8..e2e9ba7 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ 21 ] + java: [ 17 ] fail-fast: true steps: - uses: actions/checkout@v4 diff --git a/README.MD b/README.MD index 25c5c07..ad88672 100644 --- a/README.MD +++ b/README.MD @@ -1,12 +1,14 @@ # Hyperion -[![Gradle CI](https://img.shields.io/github/actions/workflow/status/PrimordialMoros/Hyperion/gradle.yml?branch=master&style=flat-square)](https://github.com/PrimordialMoros/Hyperion/actions) -[![License](https://img.shields.io/github/license/PrimordialMoros/Hyperion?color=blue&style=flat-square)](LICENSE) -[![GitHub release](https://img.shields.io/github/v/release/PrimordialMoros/Hyperion?style=flat-square)](https://github.com/PrimordialMoros/Hyperion/releases) +[![Gradle CI](https://img.shields.io/github/actions/workflow/status/Hihelloy-main/Hyperion/gradle.yml?branch=master&style=flat-square)](https://github.com/Hihelloy-main/Hyperion/actions) +[![License](https://img.shields.io/github/license/Hihelloy-main/Hyperion?color=blue&style=flat-square)](LICENSE) +[![GitHub release](https://img.shields.io/github/v/release/Hihelloy-main/Hyperion?style=flat-square)](https://github.com/Hihelloy-main/Hyperion/releases) +[![Github Downloads](https://img.shields.io/github/downloads/Hihelloy-main/Hyperion/total.svg)](https://github.com/Hihelloy-main/Hyperion/releases) -**New**: Modifier System. Read more about it on the [wiki](https://github.com/PrimordialMoros/Hyperion/wiki/Modifiers)! +Hi, I'm Hihelloy, and this is my UNOFFICIAL fork of Moro's ProjectKorra Addon plugin, Hyperion! -Addon plugin for ProjectKorra with an emphasis on intricate mechanics +please note that this is NOT my plugin by any means, it is really Moro's plugin. But I have changed a few things (like how I added [Folia](https://github.com/PaperMC/Folia) support) +the original plugin can be found [here](https://github.com/PrimordialMoros/Hyperion), but if you want Folia support or the RainbowFire element please download the latest version of my fork! ## Abilities ### Air diff --git a/build.gradle.kts b/build.gradle.kts index 2cc0201..1640c48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,14 @@ plugins { java - id("io.github.goooler.shadow").version("8.1.8") + id("com.github.johnrengelman.shadow") version "8.1.1" } group = "me.moros" -version = "1.7.3" +version = "1.7.5" java { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(17)) } } @@ -16,33 +16,51 @@ repositories { mavenCentral() maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") maven("https://jitpack.io") + maven("https://repo.papermc.io/repository/maven-public/") + mavenLocal() } dependencies { - implementation("org.bstats", "bstats-bukkit", "3.0.2") - compileOnly("org.spigotmc:spigot-api:1.20.6-R0.1-SNAPSHOT") - compileOnly("com.github.ProjectKorra:ProjectKorra:v1.11.2") + // Provided by server + compileOnly("org.spigotmc:spigot-api:1.20.2-R0.1-SNAPSHOT") + compileOnly("com.projectkorra:projectkorra:1.12.1-PRE-RELEASE-1") + + // Included in plugin jar + implementation("org.bstats:bstats-bukkit:3.1.0") + implementation("com.cjcrafter:foliascheduler:0.7.2") + implementation("net.kyori:adventure-api:4.26.1") + implementation("net.kyori:adventure-platform-bukkit:4.4.1") + implementation("org.jetbrains:annotations:26.0.2") } tasks { shadowJar { archiveClassifier.set("") - dependencies { - relocate("org.bstats", "me.moros.hyperion.bstats") - } - minimize() + + // Relocate bStats to avoid plugin conflicts + relocate("org.bstats", "me.moros.hyperion.bstats") + + // Relocate FoliaScheduler to avoid classpath issues + relocate("com.cjcrafter.foliascheduler", "me.moros.hyperion.foliascheduler") + + //relocate("io.papermc.paperlib", "me.moros.hyperion.paperlib") + } + build { dependsOn(shadowJar) } + withType { options.compilerArgs.addAll(listOf("-Xlint:unchecked", "-Xlint:deprecation")) options.encoding = "UTF-8" } + withType { isPreserveFileTimestamps = false isReproducibleFileOrder = true } + named("processResources") { expand("pluginVersion" to project.version) } diff --git a/src/main/java/me/moros/hyperion/Elements.java b/src/main/java/me/moros/hyperion/Elements.java new file mode 100644 index 0000000..d0b274f --- /dev/null +++ b/src/main/java/me/moros/hyperion/Elements.java @@ -0,0 +1,27 @@ +package me.moros.hyperion; + + +import com.projectkorra.projectkorra.Element; +import com.projectkorra.projectkorra.Element.ElementType; +import com.projectkorra.projectkorra.Element.SubElement; +import com.projectkorra.projectkorra.ProjectKorra; +import me.moros.hyperion.util.HexColor; +import net.md_5.bungee.api.ChatColor; + + +public class Elements { + + public static final SubElement RAINBOWFIRE; + + public Elements() { + } + + static { + RAINBOWFIRE = new SubElement("RainbowFire", Element.FIRE, ElementType.BENDING, ProjectKorra.plugin) { + @Override + public ChatColor getColor() { + return ChatColor.of("#FF7F00"); + } + }; + } +} diff --git a/src/main/java/me/moros/hyperion/Hyperion.java b/src/main/java/me/moros/hyperion/Hyperion.java index 29b8f06..993405f 100755 --- a/src/main/java/me/moros/hyperion/Hyperion.java +++ b/src/main/java/me/moros/hyperion/Hyperion.java @@ -19,49 +19,153 @@ package me.moros.hyperion; + +import com.cjcrafter.foliascheduler.FoliaCompatibility; +import com.cjcrafter.foliascheduler.ServerImplementation; +import com.projectkorra.projectkorra.BendingPlayer; +import com.projectkorra.projectkorra.util.TempBlock; +import com.projectkorra.projectkorra.util.TempFallingBlock; +import me.moros.hyperion.abilities.Elements.FireAbility; import me.moros.hyperion.commands.HyperionCommand; import me.moros.hyperion.configuration.ConfigManager; import me.moros.hyperion.listeners.AbilityListener; import me.moros.hyperion.listeners.CoreListener; +import me.moros.hyperion.listeners.PlayerJoinListener; import me.moros.hyperion.methods.CoreMethods; -import me.moros.hyperion.util.BendingFallingBlock; -import me.moros.hyperion.util.TempArmorStand; -import org.bstats.bukkit.Metrics; +import me.moros.hyperion.util.*; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import org.bstats.bukkit.Metrics;; +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.entity.FallingBlock; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import java.util.function.Consumer; import java.util.logging.Logger; +import static com.projectkorra.projectkorra.util.TempFallingBlock.get; +import static com.projectkorra.projectkorra.util.TempFallingBlock.manage; + public class Hyperion extends JavaPlugin { - private static Hyperion plugin; + public static Hyperion plugin; private static String author; private static String version; - private static Logger log; + public static Logger log; private static PersistentDataLayer layer; + public static boolean isFolia; + public static boolean paper; + public static boolean luminol; + public static boolean spigot; + private PotionEffectAdapter potionEffectAdapter; + public static ServerImplementation scheduler; + private static UpdateChecker updateChecker; + private BukkitAudiences adventure; + @Override public void onEnable() { plugin = this; + scheduler = new FoliaCompatibility(plugin).getServerImplementation(); log = getLogger(); version = getDescription().getVersion(); author = getDescription().getAuthors().get(0); + this.adventure = BukkitAudiences.create(this); + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + isFolia = true; + } catch (ClassNotFoundException ignored) {} + + try { + Class.forName("com.destroystokyo.paper.PaperConfig"); + paper = true; + } catch (ClassNotFoundException ignored) {} + + try { + Class.forName("me.earthme.luminol.api.ThreadedRegion"); + luminol = true; + } catch (ClassNotFoundException ignored) {} + + if (!isLuminol() && isPaper()) { + getLogger().info("Hyperion is running on Paper/Folia"); + } + + if (isLuminol() && !spigot) { + getLogger().info("Hyperion is running on Luminol"); + } + + if (!isFolia && !paper && !luminol) { + spigot = true; + getLogger().info("Hyperion is running on Spigot"); + } new Metrics(this, 8212); new ConfigManager(); new HyperionCommand(); + new Elements(); + updateChecker = new UpdateChecker(this, "Hihelloy-main/Hyperion"); + + ThreadUtil.runAsync(() -> updateChecker.checkForUpdate()); + getLogger().info("Initialized Hyperion Elements/Configs/Commands/Metrics/UpdateChecker"); + getLogger().info("Attempting to load PaperLib"); + new PaperLib(); layer = new PersistentDataLayer(); + checkMaintainer(); CoreMethods.loadAbilities(); getServer().getPluginManager().registerEvents(new AbilityListener(), this); getServer().getPluginManager().registerEvents(new CoreListener(), this); - getServer().getScheduler().scheduleSyncRepeatingTask(this, TempArmorStand::manage, 0, 1); - getServer().getScheduler().scheduleSyncRepeatingTask(this, BendingFallingBlock::manage, 0, 5); + getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this); + + ThreadUtil.runGlobalTimer(() -> { + manage(); + }, 1L, 5L); + + ThreadUtil.runGlobalTimer(() -> { + TempArmorStand.manage(); + }, 1L, 1L); + + ThreadUtil.runGlobalTimer(() -> { + BendingFallingBlock.manage(); + }, 1L, 5L); + + ThreadUtil.runGlobalTimer(() -> { + FireAbility.getAbilities(); + }, 1L, 5L); + + + PotionEffectAdapterFactory potionEffectAdapterFactory = new PotionEffectAdapterFactory(); + potionEffectAdapter = potionEffectAdapterFactory.getAdapter(); + + if (author.contains("Hihelloy (Maintainer)")) { + getLogger().info("Hihelloy is the current maintainer of Hyperion"); + } else { + getLogger().warning("Hihelloy is the current maintainer of Hyperion, but the plugin had trouble loading that D:"); + } } @Override public void onDisable() { + ThreadUtil.shutdown(); + TempFallingBlock.removeAllFallingBlocks(); + TempBlock.removeAll(); BendingFallingBlock.removeAll(); TempArmorStand.removeAll(); - getServer().getScheduler().cancelTasks(this); + + if (!isFolia && !luminol) { + getServer().getScheduler().cancelTasks(this); + } + + if (isFolia || luminol) { + scheduler.global().cancelTasks(); + scheduler.async().cancelTasks(); + scheduler.cancelTasks(); + } + + if (this.adventure != null) { + this.adventure.close(); + this.adventure = null; + } } public static void reload() { @@ -70,9 +174,21 @@ public static void reload() { BendingFallingBlock.removeAll(); TempArmorStand.removeAll(); CoreMethods.loadAbilities(); + getLog().info("Trying to initialize commands once more"); + try { + new HyperionCommand(); + } catch (Exception e) { + e.printStackTrace(); + } getLog().info("Hyperion Reloaded."); } + public static void checkMaintainer() { + if (!author.contains("Hihelloy (Maintainer)")) { + author = author + ", Hihelloy (Maintainer)"; + } + } + public static Hyperion getPlugin() { return plugin; } @@ -92,4 +208,41 @@ public static Logger getLog() { public static PersistentDataLayer getLayer() { return layer; } + + public PotionEffectAdapter getPotionEffectAdapter() { + return this.potionEffectAdapter; + } + + public static boolean isFolia() { + return isFolia; + } + + public static boolean isPaper() { + return paper; + } + + public static boolean isLuminol() { + return luminol; + } + + public static boolean isSpigot() { + return spigot; + } + + public static ServerImplementation getScheduler() { + return scheduler; + } + + public static UpdateChecker getUpdateChecker() { + return updateChecker; + } + + @NotNull + public BukkitAudiences adventure() { + if (this.adventure == null) { + throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); + } + return this.adventure; + } + } diff --git a/src/main/java/me/moros/hyperion/abilities/Elements/FireAbility.java b/src/main/java/me/moros/hyperion/abilities/Elements/FireAbility.java new file mode 100644 index 0000000..c8cc5cc --- /dev/null +++ b/src/main/java/me/moros/hyperion/abilities/Elements/FireAbility.java @@ -0,0 +1,313 @@ +package me.moros.hyperion.abilities.Elements; + +import com.projectkorra.projectkorra.BendingPlayer; +import com.projectkorra.projectkorra.Element; +import com.projectkorra.projectkorra.GeneralMethods; +import com.projectkorra.projectkorra.ProjectKorra; +import com.projectkorra.projectkorra.Element.SubElement; +import com.projectkorra.projectkorra.ability.CoreAbility; +import com.projectkorra.projectkorra.ability.ElementalAbility; +import com.projectkorra.projectkorra.ability.util.Collision; +import com.projectkorra.projectkorra.configuration.ConfigManager; +import com.projectkorra.projectkorra.util.LightManager; +import com.projectkorra.projectkorra.util.ParticleEffect; +import com.projectkorra.projectkorra.util.TempBlock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import me.moros.hyperion.Elements; + + +import me.moros.hyperion.Hyperion; +import net.kyori.adventure.key.Key; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Fire; +import org.bukkit.entity.Player; + +import static me.moros.hyperion.Hyperion.plugin; + +public abstract class FireAbility extends ElementalAbility { + private static final Map SOURCE_PLAYERS = new ConcurrentHashMap<>(); + private static final Set IGNITE_FACES; + + public FireAbility(Player player) { + super(player); + } + + @Override + public boolean isIgniteAbility() { + return true; + } + + @Override + public boolean isExplosiveAbility() { + return false; + } + + @Override + public Element getElement() { + return Element.FIRE; + } + + @Override + public void handleCollision(Collision collision) { + super.handleCollision(collision); + if (collision.isRemovingFirst()) { + ParticleEffect.BLOCK_CRACK.display(collision.getLocationFirst(), 10, 1.0F, 1.0F, 1.0F, 0.1, this.getFireType().createBlockData()); + } + } + + public Material getFireType() { + return this.getBendingPlayer().canUseSubElement(SubElement.BLUE_FIRE) ? Material.SOUL_FIRE : Material.FIRE; + } + + public static boolean canFireGrief() { + return getConfig().getBoolean("Properties.Fire.FireGriefing"); + } + + public void createTempFire(Location loc) { + this.createTempFire(loc, getConfig().getLong("Properties.Fire.RevertTicks") + (long) ((new Random()).nextDouble() * (double) getConfig().getLong("Properties.Fire.RevertTicks"))); + } + + public void createTempFire(Location loc, long time) { + if (isIgnitable(loc.getBlock())) { + new TempBlock(loc.getBlock(), createFireState(loc.getBlock(), this.getFireType() == Material.SOUL_FIRE), time); + SOURCE_PLAYERS.put(loc.getBlock(), this.getPlayer()); + } + } + + public double getDayFactor(double value) { + return this.player != null ? value * getDayFactor(this.player.getWorld()) : value; + } + + public static double getDayFactor() { + return getConfig().getDouble("Properties.Fire.DayFactor"); + } + + public static double getDayFactor(double value, World world) { + return isDay(world) ? value * getDayFactor() : value; + } + + public static double getDayFactor(World world) { + return getDayFactor(1.0F, world); + } + + public static ChatColor getSubChatColor() { + return ChatColor.valueOf(ConfigManager.getConfig().getString("Properties.Chat.Colors.FireSub")); + } + + public static boolean isIgnitable(Block block) { + Block support = block.getRelative(BlockFace.DOWN); + Location loc = support.getLocation(); + boolean supported = support.getBoundingBox().overlaps(loc.add(0.0F, 0.8, 0.0F).toVector(), loc.add(1.0F, 1.0F, 1.0F).toVector()); + return !isWater(block) && !block.isLiquid() && GeneralMethods.isTransparent(block) && (supported && support.getType().isSolid() || IGNITE_FACES.stream().map((face) -> block.getRelative(face).getType()).anyMatch(FireAbility::isIgnitable)); + } + + public static boolean isIgnitable(Material material) { + return material.isFlammable() || material.isBurnable(); + } + + public static BlockData createFireState(Block position, boolean blue) { + Fire fire = (Fire) Material.FIRE.createBlockData(); + if (isIgnitable(position) && position.getRelative(BlockFace.DOWN).getType().isSolid()) { + return blue ? Material.SOUL_FIRE.createBlockData() : fire; + } else { + for (BlockFace face : IGNITE_FACES) { + fire.setFace(face, false); + if (isIgnitable(position.getRelative(face))) { + fire.setFace(face, true); + } + } + return fire; + } + } + + public static void dryWetBlocks(Block block, CoreAbility ability, boolean playSound) { + if (!GeneralMethods.isRegionProtectedFromBuild(ability, block.getLocation())) { + if (block.getType() == Material.WET_SPONGE) { + block.setType(Material.SPONGE); + if (playSound) { + plugin.adventure().world(Key.key(block.getWorld().getName())).playSound(net.kyori.adventure.sound.Sound.sound(Key.key("block.fire.extinguish"), net.kyori.adventure.sound.Sound.Source.PLAYER, 0.5F, 1.0F), block.getLocation().getX(), block.getLocation().getY(), block.getLocation().getZ()); + } + } else if (isSnow(block)) { + block.getWorld().spawnParticle(Particle.BLOCK_DUST, block.getLocation().add(0.5F, 0.5F, 0.5F), 2, 0.5F, 0.5F, 0.5F, 0.1, Material.SNOW_BLOCK.createBlockData()); + block.setType(Material.AIR); + if (playSound) { + block.getWorld().playSound(block.getLocation(), Sound.BLOCK_SNOW_BREAK, 1.0F, 1.0F); + } + } + } + } + + public static void dryWetBlocks(Block block, CoreAbility ability) { + dryWetBlocks(block, ability, false); + } + + @Deprecated + public static boolean isWithinFireShield(Location loc) { + List list = new ArrayList<>(); + list.add("FireShield"); + return GeneralMethods.blockAbilities(null, list, loc, 0.0F); + } + + public static void playCombustionSound(Location loc) { + if (getConfig().getBoolean("Properties.Fire.PlaySound")) { + float volume = (float) getConfig().getDouble("Properties.Fire.CombustionSound.Volume"); + float pitch = (float) getConfig().getDouble("Properties.Fire.CombustionSound.Pitch"); + Sound sound = Sound.ENTITY_FIREWORK_ROCKET_BLAST; + + try { + sound = Sound.valueOf(getConfig().getString("Properties.Fire.CombustionSound.Sound")); + } catch (IllegalArgumentException var8) { + ProjectKorra.log.warning("Your current value for 'Properties.Fire.CombustionSound.Sound' is not valid."); + } finally { + loc.getWorld().playSound(loc, sound, volume, pitch); + } + } + } + + public void emitFirebendingLight(Location location) { + if (getConfig().getBoolean("Properties.Fire.DynamicLight.Enabled")) { + int brightness = getConfig().getInt("Properties.Fire.DynamicLight.Brightness"); + long keepAlive = getConfig().getLong("Properties.Fire.DynamicLight.KeepAlive"); + if (brightness < 1 || brightness > 15) { + throw new IllegalArgumentException("Properties.Fire.DynamicLight.Brightness must be between 1 and 15."); + } + LightManager.createLight(location).brightness(brightness).timeUntilFadeout(keepAlive).emit(); + } + } + + /** + * Plays the firebending particles, prioritizing RAINBOWFIRE subelement first, + * then BLUE_FIRE, then default flame particles. + */ + public void playFirebendingParticles(Location loc, int amount, double xOffset, double yOffset, double zOffset) { + if (this.getBendingPlayer().canUseSubElement(Elements.RAINBOWFIRE)) { + // Instantiate and start the swirling rainbow fire particles task + RainbowFireAbility.playRainbowFireParticles(loc, amount, xOffset, yOffset, zOffset); + } else if (this.getBendingPlayer().canUseSubElement(SubElement.BLUE_FIRE)) { + ParticleEffect.SOUL_FIRE_FLAME.display(loc, amount, xOffset, yOffset, zOffset); + } else { + ParticleEffect.FLAME.display(loc, amount, xOffset, yOffset, zOffset); + } + } + + public static void playFirebendingSound(Location loc) { + if (getConfig().getBoolean("Properties.Fire.PlaySound")) { + float volume = (float) getConfig().getDouble("Properties.Fire.FireSound.Volume"); + float pitch = (float) getConfig().getDouble("Properties.Fire.FireSound.Pitch"); + Sound sound = Sound.BLOCK_FIRE_AMBIENT; + + try { + sound = Sound.valueOf(getConfig().getString("Properties.Fire.FireSound.Sound")); + } catch (IllegalArgumentException var8) { + ProjectKorra.log.warning("Your current value for 'Properties.Fire.FireSound.Sound' is not valid."); + } finally { + loc.getWorld().playSound(loc, sound, volume, pitch); + } + } + } + + public static void playLightningbendingParticle(Location loc) { + playLightningbendingParticle(loc, Math.random(), Math.random(), Math.random()); + } + + public static void playLightningbendingParticle(Location loc, double xOffset, double yOffset, double zOffset) { + GeneralMethods.displayColoredParticle("#01E1FF", loc, 1, xOffset, yOffset, zOffset); + } + + public static void playLightningbendingSound(Location loc) { + if (getConfig().getBoolean("Properties.Fire.PlaySound")) { + float volume = (float) getConfig().getDouble("Properties.Fire.LightningSound.Volume"); + float pitch = (float) getConfig().getDouble("Properties.Fire.LightningSound.Pitch"); + Sound sound = Sound.ENTITY_CREEPER_HURT; + + try { + sound = Sound.valueOf(getConfig().getString("Properties.Fire.LightningSound.Sound")); + } catch (IllegalArgumentException var8) { + ProjectKorra.log.warning("Your current value for 'Properties.Fire.LightningSound.Sound' is not valid."); + } finally { + loc.getWorld().playSound(loc, sound, volume, pitch); + } + } + } + + public static void playLightningbendingChargingSound(Location loc) { + if (getConfig().getBoolean("Properties.Fire.PlaySound")) { + float volume = (float) getConfig().getDouble("Properties.Fire.LightningCharge.Volume"); + float pitch = (float) getConfig().getDouble("Properties.Fire.LightningCharge.Pitch"); + Sound sound = Sound.BLOCK_BEEHIVE_WORK; + + try { + sound = Sound.valueOf(getConfig().getString("Properties.Fire.LightningCharge.Sound")); + } catch (IllegalArgumentException var8) { + ProjectKorra.log.warning("Your current value for 'Properties.Fire.LightningCharge.Sound' is not valid."); + } finally { + loc.getWorld().playSound(loc, sound, volume, pitch); + } + } + } + + public static void playLightningbendingHitSound(Location loc) { + if (getConfig().getBoolean("Properties.Fire.PlaySound")) { + float volume = (float) getConfig().getDouble("Properties.Fire.LightningHit.Volume"); + float pitch = (float) getConfig().getDouble("Properties.Fire.LightningHit.Pitch"); + Sound sound = Sound.ENTITY_LIGHTNING_BOLT_THUNDER; + + try { + sound = Sound.valueOf(getConfig().getString("Properties.Fire.LightningHit.Sound")); + } catch (IllegalArgumentException var8) { + ProjectKorra.log.warning("Your current value for 'Properties.Fire.LightningHit.Sound' is not valid."); + } finally { + loc.getWorld().playSound(loc, sound, volume, pitch); + } + } + } + + @Deprecated + public double applyModifiers(double value) { + return GeneralMethods.applyModifiers(value, this.getDayFactor(1.0F)); + } + + @Deprecated + public double applyInverseModifiers(double value) { + return GeneralMethods.applyInverseModifiers(value, this.getDayFactor(1.0F)); + } + + @Deprecated + public double applyModifiersDamage(double value) { + return GeneralMethods.applyModifiers(value, this.getDayFactor(1.0F), this.bPlayer.hasElement(Element.BLUE_FIRE) ? getConfig().getDouble("Properties.Fire.BlueFire.DamageFactor", 1.1) : 1.0); + } + + @Deprecated + public double applyModifiersRange(double value) { + return GeneralMethods.applyModifiers(value, this.getDayFactor(1.0F), this.bPlayer.hasElement(Element.BLUE_FIRE) ? getConfig().getDouble("Properties.Fire.BlueFire.RangeFactor", 1.2) : 1.0); + } + + @Deprecated + public long applyModifiersCooldown(long value) { + return GeneralMethods.applyInverseModifiers(value, this.getDayFactor(1.0F), this.bPlayer.hasElement(Element.BLUE_FIRE) ? 1.0 / getConfig().getDouble("Properties.Fire.BlueFire.CooldownFactor", 0.9) : 1.0); + } + + public static void stopBending() { + SOURCE_PLAYERS.clear(); + } + + public static Map getSourcePlayers() { + return SOURCE_PLAYERS; + } + + static { + IGNITE_FACES = new HashSet<>(Arrays.asList(BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.UP)); + } +} diff --git a/src/main/java/me/moros/hyperion/abilities/Elements/RainbowFireAbility.java b/src/main/java/me/moros/hyperion/abilities/Elements/RainbowFireAbility.java new file mode 100644 index 0000000..e5079e1 --- /dev/null +++ b/src/main/java/me/moros/hyperion/abilities/Elements/RainbowFireAbility.java @@ -0,0 +1,57 @@ +package me.moros.hyperion.abilities.Elements; + +import com.projectkorra.projectkorra.Element; +import com.projectkorra.projectkorra.ability.Ability; +import com.projectkorra.projectkorra.ability.SubAbility; +import me.moros.hyperion.Elements; +import me.moros.hyperion.Hyperion; +import me.moros.hyperion.util.RainbowParticleEffect; +import org.bukkit.Location; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +public abstract class RainbowFireAbility extends FireAbility implements SubAbility { + + public RainbowFireAbility(Player player) { + super(player); + } + + /** + * Always plays rainbow fire particles (no fallback). + * + * @param loc Location to play particles at + * @param amount Number of particles + * @param xOffset X offset for particle spread + * @param yOffset Y offset for particle spread + * @param zOffset Z offset for particle spread + */ + public static void playRainbowFireParticles(Location loc, int amount, double xOffset, double yOffset, double zOffset) { + + RainbowParticleEffect.spawnRainbowParticleEffect(loc, amount, xOffset, yOffset, zOffset); + } + + @Override + public Class getParentAbility() { + return FireAbility.class; + } + + @Override + public Element getElement() { + return Elements.RAINBOWFIRE; + } + + public static double getDamageFactor() { + FileConfiguration config = Hyperion.getPlugin().getConfig(); + return config.getDouble("Properties.Fire.RainbowFire.DamageFactor", 1.0); + } + + public static double getCooldownFactor() { + FileConfiguration config = Hyperion.getPlugin().getConfig(); + return config.getDouble("Properties.Fire.RainbowFire.CooldownFactor", 1.0); + } + + public static double getRangeFactor() { + FileConfiguration config = Hyperion.getPlugin().getConfig(); + return config.getDouble("Properties.Fire.RainbowFire.RangeFactor", 1.0); + } +} diff --git a/src/main/java/me/moros/hyperion/abilities/airbending/Evade.java b/src/main/java/me/moros/hyperion/abilities/airbending/Evade.java index 814511d..671ed67 100644 --- a/src/main/java/me/moros/hyperion/abilities/airbending/Evade.java +++ b/src/main/java/me/moros/hyperion/abilities/airbending/Evade.java @@ -21,6 +21,7 @@ import com.projectkorra.projectkorra.ability.AddonAbility; import com.projectkorra.projectkorra.ability.AirAbility; +import com.projectkorra.projectkorra.ability.CoreAbility; import me.moros.hyperion.Hyperion; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -31,6 +32,7 @@ public class Evade extends AirAbility implements AddonAbility { private long cooldown; + private double angleStep; private int ticks = 0; @@ -39,6 +41,7 @@ public Evade(Player player) { super(player); if (!player.isOnGround() || player.getEyeLocation().getBlock().isLiquid() || hasAbility(player, Evade.class) || !bPlayer.canBend(this)) { + Hyperion.plugin.getServer().getLogger().info("Player Can't bend ability Evade because they are either not on the ground or they can't bend"); return; } diff --git a/src/main/java/me/moros/hyperion/abilities/chiblocking/Smokescreen.java b/src/main/java/me/moros/hyperion/abilities/chiblocking/Smokescreen.java index 8aacfbc..cfbe928 100644 --- a/src/main/java/me/moros/hyperion/abilities/chiblocking/Smokescreen.java +++ b/src/main/java/me/moros/hyperion/abilities/chiblocking/Smokescreen.java @@ -24,6 +24,8 @@ import com.projectkorra.projectkorra.attribute.Attribute; import me.moros.hyperion.Hyperion; import me.moros.hyperion.methods.CoreMethods; +import me.moros.hyperion.util.PotionEffectAdapter; +import me.moros.hyperion.util.PotionMetaUtil; import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.entity.AreaEffectCloud; diff --git a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGlove.java b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGlove.java index c0d9421..58be6cc 100755 --- a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGlove.java +++ b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGlove.java @@ -29,6 +29,7 @@ import com.projectkorra.projectkorra.util.ParticleEffect; import me.moros.hyperion.Hyperion; import me.moros.hyperion.methods.CoreMethods; +import me.moros.hyperion.util.PaperLib; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.ArmorStand; @@ -114,7 +115,7 @@ public void progress() { return; } grabbedTarget.setVelocity(GeneralMethods.getDirection(grabbedTarget.getLocation(), returnLocation).normalize().multiply(GLOVE_GRABBED_SPEED)); - glove.teleport(grabbedTarget.getEyeLocation().subtract(0, grabbedTarget.getHeight() / 2, 0)); + PaperLib.teleportAsync(glove, grabbedTarget.getEyeLocation().subtract(0, grabbedTarget.getHeight() / 2, 0)); return; } else { setGloveVelocity(GeneralMethods.getDirection(glove.getLocation(), returnLocation).normalize().multiply(GLOVE_SPEED)); @@ -179,7 +180,7 @@ public void grabTarget(final LivingEntity entity) { returning = true; grabbed = true; grabbedTarget = entity; - glove.teleport(grabbedTarget.getEyeLocation().subtract(0, grabbedTarget.getHeight() / 2, 0)); + PaperLib.teleportAsync(glove, grabbedTarget.getEyeLocation().subtract(0, grabbedTarget.getHeight() / 2, 0)); } public void checkDamage() { diff --git a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuard.java b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuard.java index ce93e45..fe7333b 100755 --- a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuard.java +++ b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuard.java @@ -31,7 +31,10 @@ import com.projectkorra.projectkorra.util.TempPotionEffect; import me.moros.hyperion.Hyperion; import me.moros.hyperion.util.BendingFallingBlock; -import org.bukkit.ChatColor; +import me.moros.hyperion.util.PotionEffectAdapter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Color; import org.bukkit.GameMode; import org.bukkit.Location; @@ -39,6 +42,7 @@ import org.bukkit.Sound; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -59,6 +63,7 @@ public class EarthGuard extends EarthAbility implements AddonAbility { private BendingFallingBlock armorFallingBlock; private BlockData blockData; private GameMode originalMode; + private static Entity entity; @Attribute(Attribute.COOLDOWN) private long cooldown; @@ -110,14 +115,13 @@ public EarthGuard(Player player) { } } - @Override public void progress() { if (!formed) { if (!bPlayer.canBendIgnoreBindsCooldowns(this)) { remove(); return; } - moveBlock(); + moveBlock(entity); } else { if (!canRemainActive()) { remove(); @@ -153,7 +157,7 @@ private boolean canRemainActive() { return true; } - private void formArmor(Material material) { + private void formArmor(Material material, Object entity) { if (formed) return; final ItemStack head, chest, leggings, boots; @@ -188,8 +192,10 @@ private void formArmor(Material material) { if (generalMeta instanceof LeatherArmorMeta meta) { meta.setColor(color); } - generalMeta.setDisplayName(ChatColor.GREEN + "Earth Guard Armor"); - generalMeta.setLore(Collections.singletonList(ChatColor.DARK_GREEN + "Temporary")); + generalMeta.setDisplayName(toLegacy( + Component.text("Earth Guard Armor", NamedTextColor.GREEN) + )); + generalMeta.setLore(Collections.singletonList(toLegacy(Component.text("Temporary", NamedTextColor.DARK_GREEN)))); Hyperion.getLayer().addEarthGuardKey(generalMeta); item.setItemMeta(generalMeta); } @@ -207,12 +213,12 @@ private void formArmor(Material material) { originalMode = player.getGameMode(); player.getInventory().setArmorContents(newArmor.toArray(new ItemStack[4])); - new TempPotionEffect(player, new PotionEffect(PotionEffectType.RESISTANCE, NumberConversions.round(duration / 50F), resistance)); + new TempPotionEffect(player, Hyperion.plugin.getPotionEffectAdapter().getResistanceEffect(NumberConversions.round(duration), resistance)); time = System.currentTimeMillis(); formed = true; } - private void moveBlock() { + private void moveBlock(Object entity) { if (!player.getWorld().equals(armorFallingBlock.getFallingBlock().getWorld())) { remove(); return; @@ -234,7 +240,7 @@ private void moveBlock() { if (distanceSquared <= 0.5 * 0.5) { Material mat = armorFallingBlock.getFallingBlock().getBlockData().getMaterial(); armorFallingBlock.remove(); - formArmor(mat); + formArmor(mat, entity); return; } @@ -315,7 +321,7 @@ public void remove() { armorFallingBlock.remove(); } if (formed) { - player.removePotionEffect(PotionEffectType.RESISTANCE); + player.removePotionEffect(PotionEffectType.DAMAGE_RESISTANCE); if (!originalMode.equals(player.getGameMode())) { for (ItemStack armorItem : oldArmor) { if (armorItem != null && armorItem.getType() != Material.AIR) { @@ -342,4 +348,9 @@ public void remove() { @Override public void stop() { } -} + + public static String toLegacy(Component component) { + return LegacyComponentSerializer.legacySection().serialize(component); + } + +} \ No newline at end of file diff --git a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuardWall.java b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuardWall.java index 3ee903d..e0ca96f 100755 --- a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuardWall.java +++ b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthGuardWall.java @@ -28,6 +28,7 @@ import com.projectkorra.projectkorra.util.ParticleEffect; import me.moros.hyperion.Hyperion; import me.moros.hyperion.methods.CoreMethods; +import me.moros.hyperion.util.ThreadUtil; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; diff --git a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthLine.java b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthLine.java index 2c0bdbf..3c985ae 100755 --- a/src/main/java/me/moros/hyperion/abilities/earthbending/EarthLine.java +++ b/src/main/java/me/moros/hyperion/abilities/earthbending/EarthLine.java @@ -30,7 +30,6 @@ import com.projectkorra.projectkorra.earthbending.passive.DensityShift; import com.projectkorra.projectkorra.firebending.util.FireDamageTimer; import com.projectkorra.projectkorra.region.RegionProtection; -import com.projectkorra.projectkorra.util.ActionBar; import com.projectkorra.projectkorra.util.DamageHandler; import com.projectkorra.projectkorra.util.MovementHandler; import com.projectkorra.projectkorra.util.ParticleEffect; @@ -39,6 +38,8 @@ import me.moros.hyperion.methods.CoreMethods; import me.moros.hyperion.util.BendingFallingBlock; import me.moros.hyperion.util.TempArmorStand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; @@ -51,8 +52,10 @@ import org.bukkit.entity.Player; import org.bukkit.util.Vector; + import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -94,6 +97,7 @@ private enum EarthLineMode {NORMAL, PRISON, MAGMA} private boolean launched; private boolean targetLocked; private boolean collapsing; + private final List armorStands = new ArrayList<>(); private boolean makeSpikes; private double earthLineSpeed; @@ -141,8 +145,20 @@ public EarthLine(Player player) { } } + private void removeExpiredArmorStands() { + Iterator it = armorStands.iterator(); + while (it.hasNext()) { + TempArmorStand stand = it.next(); + if (stand.isExpired()) { + stand.remove(); + it.remove(); + } + } + } @Override public void progress() { + removeExpiredArmorStands(); + TempArmorStand.manage(); if (launched) { if (!bPlayer.canBendIgnoreBindsCooldowns(this)) { remove(); @@ -218,7 +234,7 @@ public void setPrisonMode() { if (mode != EarthLineMode.NORMAL) return; ticks = 0; mode = EarthLineMode.PRISON; - ActionBar.sendActionBar(getElement().getColor() + "* Prison Mode *", player); + Hyperion.plugin.adventure().player(player).sendActionBar(Component.text("* Prison Mode *", TextColor.color(getElement().getColor().getColor().getRGB()))); } private void imprisonTarget() { @@ -249,7 +265,7 @@ private void imprisonTarget() { new TempArmorStand(this, loc.add(0, -0.6, 0), material, prisonDuration, true); } final MovementHandler mh = new MovementHandler(target, CoreAbility.getAbility(EarthLine.class)); - mh.stopWithDuration(prisonDuration / 50, Element.EARTH.getColor() + "* Imprisoned *"); + mh.stopWithDuration(prisonDuration, Element.EARTH.getColor() + "* Imprisoned *"); remove(); } } @@ -264,6 +280,7 @@ private void raiseSpikes() { pillar2.setCooldown(0); pillar2.setInterval(100); remove(); + removeExpiredArmorStands(); } private void advanceLocation() { @@ -450,7 +467,9 @@ public void stop() { @Override public void remove() { + removeExpiredArmorStands(); sourceBlock.revertBlock(); + BendingFallingBlock.removeAll(); super.remove(); } diff --git a/src/main/java/me/moros/hyperion/abilities/earthbending/MetalCable.java b/src/main/java/me/moros/hyperion/abilities/earthbending/MetalCable.java index a62ec68..8cb7e0b 100755 --- a/src/main/java/me/moros/hyperion/abilities/earthbending/MetalCable.java +++ b/src/main/java/me/moros/hyperion/abilities/earthbending/MetalCable.java @@ -32,6 +32,7 @@ import me.moros.hyperion.methods.CoreMethods; import me.moros.hyperion.util.BendingFallingBlock; import me.moros.hyperion.util.MaterialCheck; +import me.moros.hyperion.util.PaperLib; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -123,7 +124,7 @@ public void progress() { Entity entityToMove = player; Location targetLocation = location; if (target.getType() == CableTarget.Type.ENTITY) { - cable.teleport(target.getEntity().getLocation()); + PaperLib.teleportAsync(cable, target.getEntity().getLocation()); if (player.isSneaking()) { entityToMove = target.getEntity(); Vector dir = player.getEyeLocation().getDirection().multiply(distance / 2); diff --git a/src/main/java/me/moros/hyperion/abilities/earthbending/passive/Locksmithing.java b/src/main/java/me/moros/hyperion/abilities/earthbending/passive/Locksmithing.java index 4866d14..3aa8b9a 100755 --- a/src/main/java/me/moros/hyperion/abilities/earthbending/passive/Locksmithing.java +++ b/src/main/java/me/moros/hyperion/abilities/earthbending/passive/Locksmithing.java @@ -1,22 +1,3 @@ -/* - * Copyright 2016-2024 Moros - * - * This file is part of Hyperion. - * - * Hyperion is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Hyperion is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Hyperion. If not, see . - */ - package me.moros.hyperion.abilities.earthbending.passive; import com.projectkorra.projectkorra.BendingPlayer; @@ -26,9 +7,12 @@ import com.projectkorra.projectkorra.ability.MetalAbility; import com.projectkorra.projectkorra.ability.PassiveAbility; import com.projectkorra.projectkorra.region.RegionProtection; -import com.projectkorra.projectkorra.util.ActionBar; import me.moros.hyperion.Hyperion; import me.moros.hyperion.util.MaterialCheck; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -98,13 +82,19 @@ public static void act(BendingPlayer bPlayer, Block block) { String keyName = getOrCreateKey(key, meta); container.setLock(keyName); state.update(); - block.getWorld().playSound(loc, Sound.BLOCK_CHEST_LOCKED, 1, 1); - ActionBar.sendActionBar(Element.METAL.getColor() + "Locked", player); + Hyperion.plugin.adventure().world(Key.key(block.getWorld().getName())).playSound(net.kyori.adventure.sound.Sound.sound(Key.key("block.chest.locked"), net.kyori.adventure.sound.Sound.Source.PLAYER, 1, 1), block.getX(), block.getY(), block.getZ()); + Hyperion.plugin.adventure().player(player).sendMessage( + Component.text("Locked", TextColor.color(Element.METAL.getColor().getColor().getRGB())) + .decorate(TextDecoration.BOLD) + ); } else if (player.isSneaking() && (player.hasPermission(OVERRIDE) || validKey(container, meta))) { container.setLock(null); state.update(); block.getWorld().playSound(loc, Sound.BLOCK_CHEST_LOCKED, 1, 2); - ActionBar.sendActionBar(Element.METAL.getColor() + "Unlocked", player); + Hyperion.plugin.adventure().player(player).sendMessage( + Component.text("Unlocked", TextColor.color(Element.METAL.getColor().getColor().getRGB())) + .decorate(TextDecoration.BOLD) + ); } } } @@ -125,7 +115,6 @@ private static boolean validKey(Lockable container, ItemMeta meta) { return container.getLock().equals(meta.getDisplayName()); } - @Override public boolean isInstantiable() { return false; diff --git a/src/main/java/me/moros/hyperion/abilities/firebending/Bolt.java b/src/main/java/me/moros/hyperion/abilities/firebending/Bolt.java index fc41387..c04819e 100755 --- a/src/main/java/me/moros/hyperion/abilities/firebending/Bolt.java +++ b/src/main/java/me/moros/hyperion/abilities/firebending/Bolt.java @@ -30,6 +30,7 @@ import me.moros.hyperion.Hyperion; import me.moros.hyperion.methods.CoreMethods; import me.moros.hyperion.util.MaterialCheck; +import net.kyori.adventure.key.Key; import org.bukkit.Location; import org.bukkit.Sound; import org.bukkit.entity.ArmorStand; @@ -169,7 +170,7 @@ private void strike() { } location = targetLocation; player.getWorld().strikeLightningEffect(location); - player.getWorld().playSound(location, Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 5, 1.2F); + Hyperion.plugin.adventure().world(Key.key(player.getWorld().getName())).playSound(net.kyori.adventure.sound.Sound.sound(Key.key("entity.lightning_bolt.thunder"), net.kyori.adventure.sound.Sound.Source.PLAYER, 5, 1.2F), location.getX(), location.getY(), location.getZ()); bPlayer.addCooldown(this); if (!Bolt.isNearbyChannel(location, player)) { dealDamage(location); diff --git a/src/main/java/me/moros/hyperion/abilities/firebending/Combustion.java b/src/main/java/me/moros/hyperion/abilities/firebending/Combustion.java index 297b5c5..9aa9c9f 100755 --- a/src/main/java/me/moros/hyperion/abilities/firebending/Combustion.java +++ b/src/main/java/me/moros/hyperion/abilities/firebending/Combustion.java @@ -29,12 +29,12 @@ import com.projectkorra.projectkorra.firebending.util.FireDamageTimer; import com.projectkorra.projectkorra.region.RegionProtection; import com.projectkorra.projectkorra.util.DamageHandler; -import com.projectkorra.projectkorra.util.ParticleEffect; import com.projectkorra.projectkorra.util.TempBlock; import me.moros.hyperion.Hyperion; import me.moros.hyperion.methods.CoreMethods; import me.moros.hyperion.util.FastMath; import me.moros.hyperion.util.MaterialCheck; +import net.kyori.adventure.key.Key; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; @@ -48,6 +48,11 @@ import java.util.concurrent.ThreadLocalRandom; + +import static com.projectkorra.projectkorra.util.ParticleEffect.*; +import static com.projectkorra.projectkorra.util.ParticleEffect.FIREWORKS_SPARK; +import static com.projectkorra.projectkorra.util.ParticleEffect.SMOKE_LARGE; + public class Combustion extends CombustionAbility implements AddonAbility { private Location location; @@ -157,13 +162,13 @@ private void advanceLocation() { final Vector direction = player.getEyeLocation().getDirection(); ThreadLocalRandom rand = ThreadLocalRandom.current(); if (distanceTravelled >= randomBeamDistance) { - player.getWorld().playSound(location, Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.5f, 0.01F); + Hyperion.plugin.adventure().player(player).playSound(net.kyori.adventure.sound.Sound.sound(Key.key("entity.firework_rocket.blast"), net.kyori.adventure.sound.Sound.Source.PLAYER, 1.5f, 0.01f), location.getX(), location.getY(), location.getZ()); randomBeamDistance = distanceTravelled + 7 + 3 * rand.nextGaussian(); double radius = rand.nextDouble(0.6, 1.6); for (int angle = 0; angle <= 360; angle += 12) { final Vector temp = GeneralMethods.getOrthogonalVector(direction, angle, 0.2); final Vector dir = GeneralMethods.getOrthogonalVector(direction, angle, radius); - ParticleEffect.FIREWORKS_SPARK.display(location.clone().add(temp), 0, dir.getX(), dir.getY(), dir.getZ(), 0.12); + FIREWORKS_SPARK.display(location.clone().add(temp), 0, dir.getX(), dir.getY(), dir.getZ(), 0.12); } } for (int i = 0; i < NumberConversions.round(speed / 0.4); i++) { @@ -173,15 +178,15 @@ private void advanceLocation() { return; } location.add(direction.clone().multiply(0.4)); - ParticleEffect.SMOKE_LARGE.display(location, 1, 0, 0, 0, 0.06); - ParticleEffect.FIREWORKS_SPARK.display(location, 1, 0, 0, 0, 0.06); + SMOKE_LARGE.display(location, 1, 0, 0, 0, 0.06); + FIREWORKS_SPARK.display(location, 1, 0, 0, 0, 0.06); if (i % 2 != 0) { if (RegionProtection.isRegionProtected(this, location)) { remove(); return; } if (rand.nextInt(3) == 0) { - location.getWorld().playSound(location, Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1, 0.01F); + Hyperion.plugin.adventure().world(Key.key(location.getWorld().getName())).playSound(net.kyori.adventure.sound.Sound.sound(Key.key("entity.firework_rocket.blast"), net.kyori.adventure.sound.Sound.Source.PLAYER, 1, 0.01F), location.getX(), location.getY(), location.getZ()); } if (location.getBlock().isLiquid() || !isTransparent(location.getBlock())) { createExplosion(location, power, damage); @@ -195,11 +200,11 @@ private void advanceLocation() { private void createExplosion(Location center, double size, double damage) { if (hasExploded) return; hasExploded = true; - ParticleEffect.FLAME.display(center, 20, 1, 1, 1, 0.5f, 20); - ParticleEffect.SMOKE_LARGE.display(center, 20, 1, 1, 1, 0.5f); - ParticleEffect.FIREWORKS_SPARK.display(center, 20, 1, 1, 1, 0.5f); - ParticleEffect.SMOKE_LARGE.display(center, 20, 1, 1, 1, 0.5f); - ParticleEffect.EXPLOSION_HUGE.display(center, 5, 1, 1, 1, 0.5f); + FLAME.display(center, 20, 1, 1, 1, 0.5f, 20); + SMOKE_LARGE.display(center, 20, 1, 1, 1, 0.5f); + FIREWORKS_SPARK.display(center, 20, 1, 1, 1, 0.5f); + SMOKE_LARGE.display(center, 20, 1, 1, 1, 0.5f); + EXPLOSION_HUGE.display(center, 5, 1, 1, 1, 0.5f); center.getWorld().playSound(center, Sound.ENTITY_GENERIC_EXPLODE, 1, 1); if (regenDelay > 0 && !center.getBlock().isLiquid()) { @@ -243,8 +248,8 @@ private void playParticleRing() { double x = 1.75 * FastMath.cos(currentRingPoint); double z = 1.75 * FastMath.sin(currentRingPoint); Location loc = player.getLocation().clone().add(x, 1, z); - ParticleEffect.FLAME.display(loc, 2, 0, 0, 0, 0.01); - ParticleEffect.SMOKE_NORMAL.display(loc, 2, 0, 0, 0, 0.01); + FLAME.display(loc, 2, 0, 0, 0, 0.01); + SMOKE_NORMAL.display(loc, 2, 0, 0, 0, 0.01); } } diff --git a/src/main/java/me/moros/hyperion/abilities/firebending/FlameRush.java b/src/main/java/me/moros/hyperion/abilities/firebending/FlameRush.java index 495fd8f..99c71a5 100644 --- a/src/main/java/me/moros/hyperion/abilities/firebending/FlameRush.java +++ b/src/main/java/me/moros/hyperion/abilities/firebending/FlameRush.java @@ -22,7 +22,6 @@ import com.projectkorra.projectkorra.GeneralMethods; import com.projectkorra.projectkorra.ProjectKorra; import com.projectkorra.projectkorra.ability.AddonAbility; -import com.projectkorra.projectkorra.ability.FireAbility; import com.projectkorra.projectkorra.ability.util.Collision; import com.projectkorra.projectkorra.command.Commands; import com.projectkorra.projectkorra.earthbending.EarthSmash; @@ -31,8 +30,10 @@ import com.projectkorra.projectkorra.util.DamageHandler; import com.projectkorra.projectkorra.util.ParticleEffect; import me.moros.hyperion.Hyperion; +import me.moros.hyperion.abilities.Elements.FireAbility; import me.moros.hyperion.methods.CoreMethods; import me.moros.hyperion.util.BendingFallingBlock; +import me.moros.hyperion.util.ThreadUtil; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; @@ -292,12 +293,12 @@ public void handleCollision(Collision collision) { .distinct().toList(); smash.remove(); if (!smashBlocks.isEmpty()) { - ProjectKorra.plugin.getServer().getScheduler().runTaskLater(ProjectKorra.plugin, () -> { - for (Location loc : smashBlocks) { + for (Location loc : smashBlocks) { + ThreadUtil.ensureLocationLater(loc, () -> { Vector vel = CoreMethods.gaussianVector(0.2, 0.1, 0.2); new BendingFallingBlock(loc, Material.MAGMA_BLOCK.createBlockData(), vel, this, true, 5000); - } - }, 1); + }, 1); + } } } else { collision.setRemovingSecond(false); diff --git a/src/main/java/me/moros/hyperion/abilities/firebending/RainbowWave.java b/src/main/java/me/moros/hyperion/abilities/firebending/RainbowWave.java new file mode 100644 index 0000000..0cd3482 --- /dev/null +++ b/src/main/java/me/moros/hyperion/abilities/firebending/RainbowWave.java @@ -0,0 +1,161 @@ +package me.moros.hyperion.abilities.firebending; + +import com.projectkorra.projectkorra.GeneralMethods; +import com.projectkorra.projectkorra.ability.AddonAbility; +import com.projectkorra.projectkorra.attribute.Attribute; +import com.projectkorra.projectkorra.util.DamageHandler; +import me.moros.hyperion.Hyperion; +import me.moros.hyperion.abilities.Elements.FireAbility; +import me.moros.hyperion.abilities.Elements.RainbowFireAbility; +import org.bukkit.Location; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static me.moros.hyperion.Elements.RAINBOWFIRE; +import static me.moros.hyperion.abilities.Elements.RainbowFireAbility.playRainbowFireParticles; + +public class RainbowWave extends FireAbility implements AddonAbility { + + private static final String path = "Abilities.Fire.RainbowWave."; + + private boolean started; + private double speed; + private final double fallSpeed = -0.05; + private final double hitRadius = 1.5; + + private double damage; + private int maxTicks; + private long cooldown; + + private int ticksLived = 0; + private Location currentLocation; + private Vector direction; + private final Set hitEntities = new HashSet<>(); + + + public RainbowWave(Player player) { + super(player); + + if (!player.isOnline()) return; + if (!bPlayer.canBend(this)) return; + if (hasAbility(player, RainbowWave.class)) return; + if (bPlayer.isOnCooldown(this)) return; + if (bPlayer.canUseSubElement(RAINBOWFIRE)) { + damage *= RainbowFireAbility.getDamageFactor(); + cooldown *= RainbowFireAbility.getCooldownFactor(); + } + + loadConfigValues(); + + this.currentLocation = player.getEyeLocation().add(0, -0.5, 0); + this.direction = currentLocation.getDirection().normalize().multiply(speed); + + start(); + } + + private void loadConfigValues() { + FileConfiguration config = Hyperion.getPlugin().getConfig(); + + this.cooldown = config.getLong(path + "Cooldown"); + this.damage = config.getDouble(path + "Damage"); + double range = config.getDouble(path + "Range"); + this.speed = config.getDouble(path + "Speed"); + + if (speed <= 0) speed = 0.1; // prevent zero or negative speed + this.maxTicks = (int) (range / speed); + } + + @Override + public void progress() { + if (player == null || !player.isOnline() || !bPlayer.canBend(this)) { + remove(); + return; + } + + if (!started) return; + + currentLocation.add(direction); + currentLocation.add(0, fallSpeed, 0); + + playFirebendingParticles(currentLocation, 10, hitRadius, hitRadius, hitRadius); + + List nearby = GeneralMethods.getEntitiesAroundPoint(currentLocation, hitRadius); + for (Entity entity : nearby) { + if (entity instanceof LivingEntity living && !entity.equals(player) && !hitEntities.contains(living)) { + DamageHandler.damageEntity(living, damage, this); + hitEntities.add(living); + Vector knockback = living.getLocation().toVector().subtract(player.getLocation().toVector()).normalize().multiply(0.5); + knockback.setY(0.2); + living.setVelocity(living.getVelocity().add(knockback)); + } + } + + ticksLived++; + if (ticksLived > maxTicks) { + bPlayer.addCooldown(this); + remove(); + } + } + + public void triggerWave() { + started = true; + } + + @Override + public boolean isSneakAbility() { + return false; + } + + @Override + public boolean isHarmlessAbility() { + return false; + } + + @Override + public long getCooldown() { + return cooldown; + } + + @Override + public String getName() { + return "RainbowWave"; + } + + @Override + public Location getLocation() { + return currentLocation; + } + + @Override + public void load() {} + + @Override + public void stop() {} + + @Override + public String getAuthor() { + return Hyperion.getAuthor(); + } + + @Override + public String getVersion() { + return Hyperion.getVersion(); + } + + @Override + public String getInstructions() { + return "Punch twice"; + } + + @Override + public String getDescription() { + return "Shoot a beam/wave of RainbowFire at your opponent"; + } +} diff --git a/src/main/java/me/moros/hyperion/abilities/firebending/combo/FireWave.java b/src/main/java/me/moros/hyperion/abilities/firebending/combo/FireWave.java index 70c1e61..7a53ef0 100755 --- a/src/main/java/me/moros/hyperion/abilities/firebending/combo/FireWave.java +++ b/src/main/java/me/moros/hyperion/abilities/firebending/combo/FireWave.java @@ -19,13 +19,12 @@ package me.moros.hyperion.abilities.firebending.combo; -import com.projectkorra.projectkorra.Element.SubElement; + import com.projectkorra.projectkorra.GeneralMethods; import com.projectkorra.projectkorra.ability.AddonAbility; import com.projectkorra.projectkorra.ability.AirAbility; import com.projectkorra.projectkorra.ability.BlueFireAbility; import com.projectkorra.projectkorra.ability.ComboAbility; -import com.projectkorra.projectkorra.ability.FireAbility; import com.projectkorra.projectkorra.ability.util.Collision; import com.projectkorra.projectkorra.ability.util.ComboManager.AbilityInformation; import com.projectkorra.projectkorra.attribute.Attribute; @@ -38,6 +37,8 @@ import com.projectkorra.projectkorra.waterbending.SurgeWall; import com.projectkorra.projectkorra.waterbending.SurgeWave; import me.moros.hyperion.Hyperion; +import me.moros.hyperion.abilities.Elements.FireAbility; +import me.moros.hyperion.abilities.Elements.RainbowFireAbility; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.ArmorStand; @@ -56,6 +57,9 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; +import static com.projectkorra.projectkorra.Element.BLUE_FIRE; +import static me.moros.hyperion.Elements.RAINBOWFIRE; + public class FireWave extends FireAbility implements AddonAbility, ComboAbility { private final Set blocks = new HashSet<>(); private ListIterator waveIterator; @@ -96,7 +100,7 @@ public FireWave(Player player) { maxHeight = Hyperion.getPlugin().getConfig().getInt("Abilities.Fire.FireCombo.FireWave.MaxHeight"); width = Hyperion.getPlugin().getConfig().getInt("Abilities.Fire.FireCombo.FireWave.Width"); - if (bPlayer.canUseSubElement(SubElement.BLUE_FIRE)) { + if (bPlayer.canUseSubElement(BLUE_FIRE)) { damage *= BlueFireAbility.getDamageFactor(); height *= BlueFireAbility.getRangeFactor(); maxHeight *= BlueFireAbility.getRangeFactor(); @@ -104,6 +108,14 @@ public FireWave(Player player) { cooldown *= BlueFireAbility.getCooldownFactor(); } + if (bPlayer.canUseSubElement(RAINBOWFIRE)) { + damage *= RainbowFireAbility.getDamageFactor(); + height *= RainbowFireAbility.getRangeFactor(); + maxHeight *= RainbowFireAbility.getRangeFactor(); + width *= RainbowFireAbility.getRangeFactor(); + cooldown *= RainbowFireAbility.getCooldownFactor(); + } + damage = getDayFactor(damage, player.getWorld()); height = (int) getDayFactor(2, player.getWorld()); maxHeight = (int) getDayFactor(maxHeight, player.getWorld()); @@ -301,7 +313,7 @@ public double getCollisionRadius() { @Override public void handleCollision(Collision collision) { if (collision.getAbilitySecond() instanceof SurgeWave || collision.getAbilitySecond() instanceof SurgeWall) { - if (!bPlayer.canUseSubElement(SubElement.BLUE_FIRE)) collision.setRemovingFirst(true); + if (!bPlayer.canUseSubElement(BLUE_FIRE)) collision.setRemovingFirst(true); if (collision.getAbilitySecond() instanceof SurgeWall && ((SurgeWall) collision.getAbilitySecond()).isFrozen()) { collision.setRemovingSecond(false); } diff --git a/src/main/java/me/moros/hyperion/abilities/waterbending/IceBreath.java b/src/main/java/me/moros/hyperion/abilities/waterbending/IceBreath.java index e89e531..81336ee 100755 --- a/src/main/java/me/moros/hyperion/abilities/waterbending/IceBreath.java +++ b/src/main/java/me/moros/hyperion/abilities/waterbending/IceBreath.java @@ -203,7 +203,7 @@ private void checkArea(double radius) { final MovementHandler mh = new MovementHandler((LivingEntity) entity, CoreAbility.getAbility(IceCrawl.class)); mh.stopWithDuration(frostDuration / 50, Element.ICE.getColor() + "* Frozen *"); new BendingFallingBlock(entity.getLocation().clone().add(0, -0.2, 0), Material.PACKED_ICE.createBlockData(), new Vector(), this, false, frostDuration); - new TempPotionEffect((LivingEntity) entity, new PotionEffect(PotionEffectType.SLOWNESS, (int) (frostDuration / 50), 3)); + new TempPotionEffect((LivingEntity) entity, Hyperion.plugin.getPotionEffectAdapter().getSlownessEffect((int) (frostDuration), 3)); } } diff --git a/src/main/java/me/moros/hyperion/abilities/waterbending/IceCrawl.java b/src/main/java/me/moros/hyperion/abilities/waterbending/IceCrawl.java index a77ec99..8bb50eb 100755 --- a/src/main/java/me/moros/hyperion/abilities/waterbending/IceCrawl.java +++ b/src/main/java/me/moros/hyperion/abilities/waterbending/IceCrawl.java @@ -1,22 +1,3 @@ -/* - * Copyright 2016-2024 Moros - * - * This file is part of Hyperion. - * - * Hyperion is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Hyperion is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Hyperion. If not, see . - */ - package me.moros.hyperion.abilities.waterbending; import com.projectkorra.projectkorra.Element; @@ -49,10 +30,14 @@ import org.bukkit.util.NumberConversions; import org.bukkit.util.Vector; +import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class IceCrawl extends IceAbility implements AddonAbility { + private Location location; private Location endLocation; private LivingEntity target; @@ -75,13 +60,13 @@ public class IceCrawl extends IceAbility implements AddonAbility { private boolean launched; private boolean locked; + private final List armorStands = new ArrayList<>(); + private final List tempBlocks = new ArrayList<>(); + public IceCrawl(Player player) { super(player); - if (!bPlayer.canBend(this)) { - return; - } - + if (!bPlayer.canBend(this)) return; if (hasAbility(player, IceCrawl.class)) { getAbility(player, IceCrawl.class).prepare(); return; @@ -113,22 +98,24 @@ public void progress() { return; } if (locked) { - if (target == null || !target.isValid() || (target instanceof Player && !((Player) target).isOnline()) || !target.getWorld().equals(location.getWorld())) { + if (target == null || !target.isValid() + || (target instanceof Player && !((Player) target).isOnline()) + || !target.getWorld().equals(location.getWorld())) { locked = false; + } else if (target.getLocation().distanceSquared(endLocation) < 25) { + endLocation = target.getLocation().clone(); + direction = CoreMethods.calculateFlatVector(sourceBlock.getLocation(), endLocation); } else { - if (target.getLocation().distanceSquared(endLocation) < 25) { - endLocation = target.getLocation().clone(); - direction = CoreMethods.calculateFlatVector(sourceBlock.getLocation(), endLocation); - } else { - locked = false; - } + locked = false; } } advanceLocation(); checkDamage(); if (ThreadLocalRandom.current().nextInt(5) == 0) playIcebendingSound(location); + cleanupArmorStands(); } else { - if (!bPlayer.canBendIgnoreCooldowns(this) || sourceBlock.getLocation().distanceSquared(player.getLocation()) > Math.pow(selectRange + 5, 2)) { + if (!bPlayer.canBendIgnoreCooldowns(this) + || sourceBlock.getLocation().distanceSquared(player.getLocation()) > Math.pow(selectRange + 5, 2)) { remove(); return; } @@ -144,62 +131,95 @@ private void advanceLocation() { if (isLava(location.getBlock())) { CoreMethods.playExtinguishEffect(location.clone().add(0, 0.2, 0), 8); remove(); + return; + } + + Block down = location.getBlock().getRelative(BlockFace.DOWN); + if (isWater(down)) { + TempBlock tb = new TempBlock(down, Material.ICE.createBlockData(), iceDuration); + PhaseChange.getFrozenBlocksMap().put(tb, player); + tempBlocks.add(tb); // Track the temporary ice block + + // Debugging info + System.out.println("TempBlock added: " + tb.getBlock().getLocation()); } - if (isWater(location.getBlock().getRelative(BlockFace.DOWN))) - PhaseChange.getFrozenBlocksMap().put(new TempBlock(location.getBlock().getRelative(BlockFace.DOWN), Material.ICE.createBlockData(), iceDuration), player); double x = ThreadLocalRandom.current().nextDouble(-0.125, 0.125); double z = ThreadLocalRandom.current().nextDouble(-0.125, 0.125); - new TempArmorStand(this, location.clone().add(x, -2, z), Material.PACKED_ICE, 1400); + TempArmorStand stand = new TempArmorStand(this, location.clone().add(x, -2, z), Material.PACKED_ICE, 1400); + armorStands.add(stand); location.add(direction.clone().multiply(0.7)); - final Block baseBlock = location.getBlock().getRelative(BlockFace.DOWN); - if (!isValidBlock(baseBlock)) { - if (isValidBlock(baseBlock.getRelative(BlockFace.UP))) { + + Block base = location.getBlock().getRelative(BlockFace.DOWN); + if (!isValidBlock(base)) { + if (isValidBlock(base.getRelative(BlockFace.UP))) { location.add(0, 1, 0); - } else if (isValidBlock(baseBlock.getRelative(BlockFace.DOWN))) { + } else if (isValidBlock(base.getRelative(BlockFace.DOWN))) { location.add(0, -1, 0); } else { remove(); return; } - } else if (endLocation.getBlockY() != location.getBlockY()) { // Advance location vertically while under water - if (endLocation.getBlockY() > location.getBlockY() && isValidBlock(baseBlock.getRelative(BlockFace.UP))) { + } else if (endLocation.getBlockY() != location.getBlockY()) { + if (endLocation.getBlockY() > location.getBlockY() + && isValidBlock(base.getRelative(BlockFace.UP))) { location.add(0, 1, 0); - } else if (endLocation.getBlockY() < location.getBlockY() && isValidBlock(baseBlock.getRelative(BlockFace.DOWN))) { + } else if (endLocation.getBlockY() < location.getBlockY() + && isValidBlock(base.getRelative(BlockFace.DOWN))) { location.add(0, -1, 0); } } - if (RegionProtection.isRegionProtected(this, location) || location.distanceSquared(sourceBlock.getLocation()) > range * range) { + + if (RegionProtection.isRegionProtected(this, location) + || location.distanceSquared(sourceBlock.getLocation()) > range * range) { remove(); } } - private boolean isValidBlock(final Block block) { + private void cleanupArmorStands() { + Iterator it = armorStands.iterator(); + while (it.hasNext()) { + TempArmorStand stand = it.next(); + if (stand.isExpired()) { + stand.remove(); + it.remove(); + } + } + } + + private boolean isValidBlock(Block block) { if (!isTransparent(block.getRelative(BlockFace.UP))) return false; return isWater(block) || isIce(block) || GeneralMethods.isSolid(block); } private void checkDamage() { - boolean hasHit = false; + boolean hit = false; for (Entity entity : GeneralMethods.getEntitiesAroundPoint(location, 0.8)) { - if (entity instanceof LivingEntity && entity.getEntityId() != player.getEntityId() && !(entity instanceof ArmorStand)) { + if (entity instanceof LivingEntity && entity.getEntityId() != player.getEntityId() + && !(entity instanceof ArmorStand)) { + if (entity instanceof Player && Commands.invincible.contains(entity.getName())) { continue; } + DamageHandler.damageEntity(entity, getNightFactor(damage, player.getWorld()), this); if (entity.isValid()) { - final MovementHandler mh = new MovementHandler((LivingEntity) entity, CoreAbility.getAbility(IceCrawl.class)); - mh.stopWithDuration(duration / 50, Element.ICE.getColor() + "* Frozen *"); - new BendingFallingBlock(entity.getLocation().clone().add(0, -0.2, 0), Material.PACKED_ICE.createBlockData(), new Vector(), this, false, duration); - new TempPotionEffect((LivingEntity) entity, new PotionEffect(PotionEffectType.SLOWNESS, NumberConversions.round(duration / 50F), 5)); + MovementHandler mh = new MovementHandler((LivingEntity) entity, + CoreAbility.getAbility(IceCrawl.class)); + mh.stopWithDuration(duration / 50, + Element.ICE.getColor() + "* Frozen *"); + new BendingFallingBlock(entity.getLocation().clone().add(0, -0.2, 0), + Material.PACKED_ICE.createBlockData(), + new Vector(), this, false, duration); + new TempPotionEffect((LivingEntity) entity, Hyperion.plugin.getPotionEffectAdapter().getSlownessEffect(NumberConversions.round(duration), 5)); } - hasHit = true; + + hit = true; } } - if (hasHit) { - remove(); - } + + if (hit) remove(); } public boolean prepare() { @@ -208,12 +228,12 @@ public boolean prepare() { if (block == null) { block = getWaterSourceBlock(player, selectRange, false); } - - if (block == null || (!isWater(block) && !isIce(block)) || !isTransparent(block.getRelative(BlockFace.UP))) { + if (block == null + || (!isWater(block) && !isIce(block)) + || !isTransparent(block.getRelative(BlockFace.UP))) { if (isStarted()) remove(); return false; } - sourceBlock = block; playFocusWaterEffect(sourceBlock); location = sourceBlock.getLocation(); @@ -221,67 +241,45 @@ public boolean prepare() { } @Override - public boolean isEnabled() { - return Hyperion.getPlugin().getConfig().getBoolean("Abilities.Water.IceCrawl.Enabled"); - } - - @Override - public String getName() { - return "IceCrawl"; - } + public void remove() { + super.remove(); - @Override - public String getDescription() { - return Hyperion.getPlugin().getConfig().getString("Abilities.Water.IceCrawl.Description"); - } - - @Override - public String getAuthor() { - return Hyperion.getAuthor(); - } - - @Override - public String getVersion() { - return Hyperion.getVersion(); - } - - @Override - public boolean isHarmlessAbility() { - return false; - } - - @Override - public boolean isSneakAbility() { - return true; - } - - @Override - public long getCooldown() { - return cooldown; - } - - @Override - public Location getLocation() { - return location; - } + // Cleanup Armor Stands + for (TempArmorStand stand : armorStands) { + stand.remove(); + } + armorStands.clear(); - @Override - public boolean isCollidable() { - return launched; - } + // Revert all temporary ice blocks + for (TempBlock tb : tempBlocks) { + tb.revertBlock(); // This should remove the ice block + } + tempBlocks.clear(); - @Override - public double getCollisionRadius() { - return 0.8; + // Additional debugging log + System.out.println("IceCrawl ability removed, ice blocks reverted."); } - @Override - public void load() { + @Override public boolean isEnabled() { + return Hyperion.getPlugin().getConfig() + .getBoolean("Abilities.Water.IceCrawl.Enabled"); } - @Override - public void stop() { + @Override public String getName() { return "IceCrawl"; } + @Override public String getDescription() { + return Hyperion.getPlugin().getConfig() + .getString("Abilities.Water.IceCrawl.Description"); } + @Override public String getAuthor() { return Hyperion.getAuthor(); } + @Override public String getVersion() { return Hyperion.getVersion(); } + @Override public boolean isHarmlessAbility() { return false; } + @Override public boolean isSneakAbility() { return true; } + @Override public long getCooldown() { return cooldown; } + @Override public Location getLocation() { return location; } + @Override public boolean isCollidable() { return launched; } + @Override public double getCollisionRadius() { return 0.8; } + @Override public void load() {} + @Override public void stop() {} public static void shootLine(Player player) { if (hasAbility(player, IceCrawl.class)) { @@ -291,10 +289,12 @@ public static void shootLine(Player player) { private void shootLine() { if (launched) return; - final Entity targetedEntity = GeneralMethods.getTargetedEntity(player, range + selectRange, Collections.singletonList(player)); - if (targetedEntity instanceof LivingEntity && targetedEntity.getLocation().distanceSquared(location) <= range * range) { + Entity ent = GeneralMethods.getTargetedEntity(player, range + selectRange, + Collections.singletonList(player)); + if (ent instanceof LivingEntity + && ent.getLocation().distanceSquared(location) <= range * range) { locked = true; - target = (LivingEntity) targetedEntity; + target = (LivingEntity) ent; endLocation = target.getLocation().clone(); } else { endLocation = GeneralMethods.getTargetedLocation(player, range, Material.WATER); diff --git a/src/main/java/me/moros/hyperion/commands/HyperionCommand.java b/src/main/java/me/moros/hyperion/commands/HyperionCommand.java index eff9929..71d53ca 100755 --- a/src/main/java/me/moros/hyperion/commands/HyperionCommand.java +++ b/src/main/java/me/moros/hyperion/commands/HyperionCommand.java @@ -22,11 +22,24 @@ import com.projectkorra.projectkorra.command.PKCommand; import me.moros.hyperion.Hyperion; import me.moros.hyperion.configuration.ConfigManager; +import me.moros.hyperion.util.HexColor; +import me.moros.hyperion.util.ThreadUtil; +import me.moros.hyperion.util.UpdateChecker; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.checkerframework.checker.units.qual.C; +import org.jetbrains.annotations.NotNull; import java.util.List; + + public class HyperionCommand extends PKCommand { public HyperionCommand() { super("hyperion", "/bending hyperion ", "Show information about Hyperion and optionally reload its config.", new String[]{"hyperion"}); @@ -35,15 +48,45 @@ public HyperionCommand() { @Override public void execute(CommandSender sender, List args) { if (!hasPermission(sender) || !correctLength(sender, args.size(), 0, 1)) return; - if (args.size() == 0) { - sender.sendMessage(ChatColor.GREEN + "Hyperion Version: " + ChatColor.RED + Hyperion.getVersion()); - sender.sendMessage(ChatColor.GREEN + "Developed by: " + ChatColor.RED + Hyperion.getAuthor()); + + if (args.isEmpty()) { + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Hyperion Version: ", HexColor.GREEN).append(Component.text(Hyperion.getVersion(), NamedTextColor.RED))); + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Developed by: ", NamedTextColor.GREEN).append(Component.text(Hyperion.getAuthor(), NamedTextColor.RED))); } else if (args.size() == 1) { - if (args.get(0).equals("reload") && hasPermission(sender, "reload")) { - Hyperion.getPlugin().reloadConfig(); - ConfigManager.modifiersConfig.reloadConfig(); - sender.sendMessage(ChatColor.GREEN + "Hyperion config has been reloaded."); + String sub = args.get(0).toLowerCase(); + + if (sub.equals("reload") && hasPermission(sender, "reload")) { + ThreadUtil.runGlobal(Hyperion::reload); + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Hyperion config has been reloaded.", NamedTextColor.GREEN)); + } + + + else if (sub.equals("checkupdate") && hasPermission(sender, "checkupdate")) { + UpdateChecker checker = Hyperion.getUpdateChecker(); + + if (checker == null) { + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Update checker not initialized.", NamedTextColor.RED)); + return; + } + + if (!checker.hasChecked()) { + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Still checking for updates, please try again shortly.", NamedTextColor.GRAY)); + return; + } + + if (checker.isUpdateAvailable()) { + String current = checker.getCurrentVersion() != null ? checker.getCurrentVersion() : "unknown"; + String latest = checker.getLatestVersion() != null ? checker.getLatestVersion() : "unknown"; + + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("[Hyperion] " + " A new version is available!", HexColor.ORANGE)); + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("You're running: " + current, HexColor.RED)); + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Latest version: ", NamedTextColor.GRAY).append(Component.text(latest, HexColor.GREEN))); + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("Download: ", NamedTextColor.GRAY).decorate(TextDecoration.UNDERLINED).append(Component.text("https://github.com/Hihelloy-main/Hyperion", NamedTextColor.BLUE).decorate(TextDecoration.UNDERLINED).clickEvent(ClickEvent.openUrl("https://github.com/Hihelloy-main/Hyperion")))); + } else { + Hyperion.plugin.adventure().sender(sender).sendMessage(Component.text("You're running the latest version of Hyperion.", HexColor.GREEN)); + } } } } + } diff --git a/src/main/java/me/moros/hyperion/configuration/ConfigManager.java b/src/main/java/me/moros/hyperion/configuration/ConfigManager.java index 424790d..630965f 100755 --- a/src/main/java/me/moros/hyperion/configuration/ConfigManager.java +++ b/src/main/java/me/moros/hyperion/configuration/ConfigManager.java @@ -19,12 +19,13 @@ package me.moros.hyperion.configuration; +import com.cjcrafter.foliascheduler.folia.FoliaTask; import me.moros.hyperion.Hyperion; import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; public class ConfigManager { - + private static final String path = "Abilities.Fire.RainbowWave."; public static Config modifiersConfig; public ConfigManager() { @@ -200,6 +201,10 @@ public void setupMainConfig() { config.addDefault("Abilities.Fire.FlameRush.CollisionRadius", 0.5); config.addDefault("Abilities.Fire.FlameRush.Knockback", 0.9); config.addDefault("Abilities.Fire.FlameRush.FireTicks", 15); + config.addDefault(path + "Cooldown", 2000); + config.addDefault(path + "Damage", 4.0); + config.addDefault(path + "Range", 16.0); + config.addDefault(path + "Speed", 0.4); config.addDefault("Abilities.Fire.FireCombo.FireWave.Enabled", true); config.addDefault("Abilities.Fire.FireCombo.FireWave.Description", "Master Jeong Jeong used this advanced technique to cast a great fire wave that grows in size while it advances forward."); @@ -243,6 +248,9 @@ public void setupMainConfig() { config.addDefault("Abilities.Chi.Smokescreen.CloudDuration", 7000); config.addDefault("Abilities.Chi.Smokescreen.BlindnessTicks", 30); config.addDefault("Abilities.Chi.Smokescreen.Radius", 5.0); + config.addDefault("Properties.Fire.RainbowFire.DamageFactor", 1.0); + config.addDefault("Properties.Fire.RainbowFire.CooldownFactor", 1.0); + config.addDefault("Properties.Fire.RainbowFire.RangeFactor", 1.0); config.options().copyDefaults(true); Hyperion.getPlugin().saveConfig(); diff --git a/src/main/java/me/moros/hyperion/listeners/AbilityListener.java b/src/main/java/me/moros/hyperion/listeners/AbilityListener.java index f4cfd51..286737e 100755 --- a/src/main/java/me/moros/hyperion/listeners/AbilityListener.java +++ b/src/main/java/me/moros/hyperion/listeners/AbilityListener.java @@ -29,6 +29,8 @@ import com.projectkorra.projectkorra.ability.EarthAbility; import com.projectkorra.projectkorra.ability.FireAbility; import com.projectkorra.projectkorra.ability.WaterAbility; +import com.projectkorra.projectkorra.event.PlayerSwingEvent; +import me.moros.hyperion.Elements; import me.moros.hyperion.abilities.airbending.Evade; import me.moros.hyperion.abilities.chiblocking.Smokescreen; import me.moros.hyperion.abilities.earthbending.EarthGlove; @@ -39,23 +41,28 @@ import me.moros.hyperion.abilities.earthbending.LavaDisk; import me.moros.hyperion.abilities.earthbending.MetalCable; import me.moros.hyperion.abilities.earthbending.passive.Locksmithing; -import me.moros.hyperion.abilities.firebending.Bolt; -import me.moros.hyperion.abilities.firebending.Combustion; -import me.moros.hyperion.abilities.firebending.FlameRush; +import me.moros.hyperion.abilities.firebending.*; import me.moros.hyperion.abilities.waterbending.IceBreath; import me.moros.hyperion.abilities.waterbending.IceCrawl; import me.moros.hyperion.abilities.waterbending.combo.IceDrill; +import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.entity.Player; 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.player.PlayerAnimationEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.inventory.EquipmentSlot; +import static com.projectkorra.projectkorra.ability.CoreAbility.getAbility; +import static com.projectkorra.projectkorra.ability.CoreAbility.hasAbility; + + public class AbilityListener implements Listener { + @EventHandler public void onPlayerSneak(final PlayerToggleSneakEvent event) { final Player player = event.getPlayer(); @@ -110,8 +117,8 @@ public void onPlayerSneak(final PlayerToggleSneakEvent event) { public void onPlayerSwing(final PlayerInteractEvent event) { final Player player = event.getPlayer(); if (event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_AIR) { - if (CoreAbility.hasAbility(player, EarthLine.class)) { - CoreAbility.getAbility(player, EarthLine.class).setPrisonMode(); + if (hasAbility(player, EarthLine.class)) { + getAbility(player, EarthLine.class).setPrisonMode(); } return; } @@ -181,4 +188,47 @@ public void onPlayerInteract(PlayerInteractEvent event) { } } } + + @EventHandler + public void onPlayerLeftClick(PlayerInteractEvent event) { + if (event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK) return; + + BendingPlayer bPlayer = BendingPlayer.getBendingPlayer(event.getPlayer()); + final String abilityName = bPlayer.getBoundAbilityName(); + if (bPlayer == null || !bPlayer.canUseSubElement(me.moros.hyperion.Elements.RAINBOWFIRE)) return; + if (abilityName.equalsIgnoreCase("rainbowtest")) { + Bukkit.getLogger().info("Rainbowtest is not currently available"); + } + } + + @EventHandler + public void onLeftClick(PlayerInteractEvent event) { + if (event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + BendingPlayer bPlayer = BendingPlayer.getBendingPlayer(player); + + if (bPlayer == null || !bPlayer.canUseSubElement(Elements.RAINBOWFIRE)) { + return; + } + + final String abilityName = bPlayer.getBoundAbilityName(); + + if ("RainbowWave".equalsIgnoreCase(abilityName)) { + // Check if player doesn't already have this ability active + if (!RainbowWave.hasAbility(player, RainbowWave.class)) { + // Instantiate the ability (this will call start() automatically) + new RainbowWave(player); + } else { + // If ability already exists, trigger the wave movement + RainbowWave ability = RainbowWave.getAbility(player, RainbowWave.class); + if (ability != null) { + ability.triggerWave(); + } + } + } + } } + diff --git a/src/main/java/me/moros/hyperion/listeners/CoreListener.java b/src/main/java/me/moros/hyperion/listeners/CoreListener.java index 313d264..5e4086a 100755 --- a/src/main/java/me/moros/hyperion/listeners/CoreListener.java +++ b/src/main/java/me/moros/hyperion/listeners/CoreListener.java @@ -19,8 +19,12 @@ package me.moros.hyperion.listeners; + import com.projectkorra.projectkorra.BendingPlayer; import com.projectkorra.projectkorra.ability.CoreAbility; +import com.projectkorra.projectkorra.attribute.AttributeModification; +import com.projectkorra.projectkorra.attribute.AttributeModifier; +import com.projectkorra.projectkorra.event.AbilityRecalculateAttributeEvent; import com.projectkorra.projectkorra.event.AbilityStartEvent; import com.projectkorra.projectkorra.event.BendingReloadEvent; import me.moros.hyperion.Hyperion; @@ -33,21 +37,21 @@ import me.moros.hyperion.configuration.ConfigManager; import me.moros.hyperion.methods.CoreMethods; import me.moros.hyperion.util.BendingFallingBlock; +import me.moros.hyperion.util.ThreadUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.title.Title; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TranslatableComponent; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Nameable; -import org.bukkit.Sound; +import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.Lockable; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.FallingBlock; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Snowball; +import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -69,7 +73,10 @@ import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; +import static me.moros.hyperion.Hyperion.*; + public class CoreListener implements Listener { + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void EntityChangeBlockEvent(final EntityChangeBlockEvent event) { if (event.getEntityType().equals(EntityType.FALLING_BLOCK)) { @@ -208,9 +215,19 @@ public void onPlayerLogout(final PlayerQuitEvent event) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPKReload(final BendingReloadEvent event) { - Bukkit.getScheduler().runTaskLater(Hyperion.getPlugin(), Hyperion::reload, 1); + final CommandSender sender = event.getSender(); + ThreadUtil.runGlobalLater(Hyperion::reload, 1); + plugin.adventure().sender(sender).sendMessage( + Component.text("[Hyperion]", NamedTextColor.GRAY) + .append(Component.space()) + .append( + Component.text("Config reloaded", NamedTextColor.RED) + .decorate(TextDecoration.ITALIC) + ) + ); } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onAbilityStart(final AbilityStartEvent event) { if (event.getAbility() instanceof CoreAbility ability) { @@ -226,6 +243,16 @@ public void onAbilityStart(final AbilityStartEvent event) { } } + @EventHandler(priority = EventPriority.LOW) + public void onAbilityRecalculateAttribute(AbilityRecalculateAttributeEvent event) { + if (CoreMethods.HassetAttributesbeencalled) { + if ((CoreMethods.attributedabil.equals(event.getAbility()))) { + NamespacedKey key = new NamespacedKey(Hyperion.getPlugin(), CoreMethods.key1); + event.addModification(AttributeModification.of(AttributeModifier.SET, CoreMethods.value1, key)); + } + } + } + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { Player player = event.getPlayer(); @@ -235,7 +262,7 @@ public void onBlockBreak(BlockBreakEvent event) { if (name == null) { name = "Container"; } - player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TranslatableComponent("container.isLocked", name)); + plugin.adventure().player(player).sendActionBar(Component.translatable("container.isLocked", Component.text(name))); Location loc = block.getLocation().add(0.5, 0.5, 0.5); block.getWorld().playSound(loc, Sound.BLOCK_CHEST_LOCKED, 1, 1); event.setCancelled(true); diff --git a/src/main/java/me/moros/hyperion/listeners/PlayerJoinListener.java b/src/main/java/me/moros/hyperion/listeners/PlayerJoinListener.java new file mode 100644 index 0000000..a63f510 --- /dev/null +++ b/src/main/java/me/moros/hyperion/listeners/PlayerJoinListener.java @@ -0,0 +1,102 @@ +package me.moros.hyperion.listeners; + +import me.moros.hyperion.Hyperion; +import me.moros.hyperion.util.ThreadUtil; +import me.moros.hyperion.util.UpdateChecker; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import static me.moros.hyperion.Hyperion.plugin; + +public class PlayerJoinListener implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + if (!player.isOp()) return; + + UpdateChecker checker = Hyperion.getUpdateChecker(); + if (checker == null) { + plugin.adventure().player(player).sendMessage( + Component.text("[Hyperion] ", TextColor.color(0xFFA500)) + .append(Component.text("Update checker not initialized.", NamedTextColor.RED)) + ); + return; + } + + if (!checker.hasChecked()) { + plugin.adventure().player(player).sendMessage( + Component.text("[Hyperion] ", TextColor.color(0xFFA500)) + .append( + Component.text("Checking for updates...", NamedTextColor.GRAY) + ) + ); + return; + } + + if (checker.isUpdateAvailable()) { + String current = checker.getCurrentVersion() != null ? checker.getCurrentVersion() : "unknown"; + String latest = checker.getLatestVersion() != null ? checker.getLatestVersion() : "unknown"; + var audience = Hyperion.plugin.adventure().player(player); + + audience.sendMessage( + Component.text("[Hyperion] ", TextColor.fromHexString("#FFA500")) + .append( + Component.text("A new version is available!", + TextColor.fromHexString("#FFFF00")) + .decorate(TextDecoration.UNDERLINED) + ) + ); + + audience.sendMessage( + Component.text("You're running: ", NamedTextColor.GRAY) + .append(Component.text(current, NamedTextColor.RED)) + ); + + audience.sendMessage( + Component.text("Latest version: ", NamedTextColor.GRAY) + .append(Component.text(latest, NamedTextColor.GREEN)) + ); + + audience.sendMessage( + Component.text("Download: ", NamedTextColor.GRAY) + .append( + Component.text("https://github.com/Hihelloy-main/Hyperion", + NamedTextColor.BLUE) + .decorate(TextDecoration.UNDERLINED) + .clickEvent(ClickEvent.openUrl("https://github.com/Hihelloy-main/Hyperion")) + ) + ); + + } else { + String latest = checker.getLatestVersion() != null ? checker.getLatestVersion() : "unknown"; + var audience = Hyperion.plugin.adventure().player(player); + + audience.sendMessage( + Component.text("[Hyperion] ", TextColor.fromHexString("#FFA500")) + .append( + Component.text( + "No updates available. You're on the latest version.", + NamedTextColor.GREEN + ) + ) + ); + + audience.sendMessage( + Component.text("Latest GitHub version: ", NamedTextColor.GRAY) + .append(Component.text(latest, NamedTextColor.GREEN)) + ); + } + + } +} diff --git a/src/main/java/me/moros/hyperion/methods/CoreMethods.java b/src/main/java/me/moros/hyperion/methods/CoreMethods.java index 4f21dbc..3ed9c1d 100755 --- a/src/main/java/me/moros/hyperion/methods/CoreMethods.java +++ b/src/main/java/me/moros/hyperion/methods/CoreMethods.java @@ -69,6 +69,10 @@ public class CoreMethods { public static final String GLOVE_KEY = "BENDING_HYPERION_EARTH_GLOVE"; public static final String CABLE_KEY = "BENDING_HYPERION_METAL_CABLE_KEY"; public static final String SMOKESCREEN_KEY = "BENDING_HYPERION_SMOKESCREEN_KEY"; + public static Number value1; + public static String key1; + public static boolean HassetAttributesbeencalled; + public static CoreAbility attributedabil; public static List getCirclePoints(Location location, int points, double size) { List locations = new ArrayList<>(); @@ -249,7 +253,13 @@ public static void setAttributes(ConfigurationSection section, CoreAbility abili } else { continue; } + attributedabil = ability; + HassetAttributesbeencalled = true; + value1 = value; + key1 = key; + // Deprecated - PK 1.12.0 ability.setAttribute(key, value); + ability.recalculateAttributes(); } } } diff --git a/src/main/java/me/moros/hyperion/util/BendingFallingBlock.java b/src/main/java/me/moros/hyperion/util/BendingFallingBlock.java index be9da99..d6a3c66 100755 --- a/src/main/java/me/moros/hyperion/util/BendingFallingBlock.java +++ b/src/main/java/me/moros/hyperion/util/BendingFallingBlock.java @@ -1,22 +1,3 @@ -/* - * Copyright 2016-2024 Moros - * - * This file is part of Hyperion. - * - * Hyperion is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Hyperion is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Hyperion. If not, see . - */ - package me.moros.hyperion.util; import com.projectkorra.projectkorra.ability.CoreAbility; @@ -24,14 +5,10 @@ import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Firework; import org.bukkit.util.Vector; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Map; -import java.util.PriorityQueue; -import java.util.Queue; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -47,7 +24,7 @@ public BendingFallingBlock(Location location, BlockData data, Vector velocity, C } public BendingFallingBlock(Location location, BlockData data, Vector velocity, CoreAbility abilityInstance, boolean gravity, long delay) { - fallingBlock = location.getWorld().spawnFallingBlock(location, data); + fallingBlock = Objects.requireNonNull(location.getWorld()).spawnFallingBlock(location, data); fallingBlock.setVelocity(velocity); fallingBlock.setGravity(gravity); fallingBlock.setDropItem(false); @@ -88,7 +65,7 @@ public static void manage() { final BendingFallingBlock bfb = bfbQueue.peek(); if (currentTime > bfb.getExpirationTime()) { bfbQueue.poll(); - bfb.remove(); + bfb.remove(); // Remove expired falling blocks } else { return; } @@ -98,20 +75,34 @@ public static void manage() { while (iterator.hasNext()) { BendingFallingBlock bfb = iterator.next(); if (currentTime > bfb.getExpirationTime()) { - bfb.getFallingBlock().remove(); - iterator.remove(); + // Safely remove the falling block if it exists + if (bfb.getFallingBlock() != null && bfb.getFallingBlock().isValid()) { + bfb.getFallingBlock().remove(); + } + iterator.remove(); // Clean up the map entry after removal } } } public void remove() { - instances.remove(fallingBlock); - fallingBlock.remove(); + // Ensure the falling block is valid before trying to remove it + if (fallingBlock != null && fallingBlock.isValid()) { + instances.remove(fallingBlock); + fallingBlock.remove(); + } else { + // Log or handle the case where the falling block is already removed or invalid + System.out.println("Falling block is null or invalid, cannot remove."); + } } public static void removeAll() { bfbQueue.clear(); - instances.keySet().forEach(Entity::remove); + // Ensure entities are valid before removal + instances.keySet().forEach(fb -> { + if (fb != null && fb.isValid()) { + fb.remove(); + } + }); instances.clear(); } } diff --git a/src/main/java/me/moros/hyperion/util/HexColor.java b/src/main/java/me/moros/hyperion/util/HexColor.java new file mode 100644 index 0000000..1bf5df5 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/HexColor.java @@ -0,0 +1,63 @@ +package me.moros.hyperion.util; + +import net.kyori.adventure.text.format.TextColor; + +public class HexColor { + + /** + * Returns a TextColor from a hex string like "#FF0000". + * + * @param hex the hex color string + * @return TextColor instance + */ + public static TextColor of(String hex) { + if (hex == null || !hex.matches("^#([A-Fa-f0-9]{6})$")) { + throw new IllegalArgumentException("Invalid hex color format: " + hex); + } + return TextColor.fromHexString(hex); + } + + // Classic rainbow color constants + public static final TextColor RED = of("#FF0000"); + public static final TextColor ORANGE = of("#FF7F00"); + public static final TextColor YELLOW = of("#FFFF00"); + public static final TextColor GREEN = of("#00FF00"); + public static final TextColor BLUE = of("#0000FF"); + public static final TextColor INDIGO = of("#4B0082"); + public static final TextColor VIOLET = of("#8F00FF"); + + /** + * Special rainbow color (can be used as a placeholder). + * Since TextColor doesn't support multi-color, this is just a bright yellow to represent rainbow. + */ + public static final TextColor RAINBOW = YELLOW; + + /** + * Builds a rainbow-colored string by coloring each character with a different rainbow hex. + * + * @param text the input text + * @return rainbow-colored string + */ + public static String rainbowify(String text) { + String[] rainbow = { + "#FF0000", // red + "#FF7F00", // orange + "#FFFF00", // yellow + "#00FF00", // green + "#0000FF", // blue + "#4B0082", // indigo + "#8F00FF" // violet + }; + + + StringBuilder result = new StringBuilder(); + int colorIndex = 0; + + for (char c : text.toCharArray()) { + result.append(TextColor.fromHexString(rainbow[colorIndex % rainbow.length])).append(c); + colorIndex++; + } + + return result.toString(); + } +} diff --git a/src/main/java/me/moros/hyperion/util/PaperLib.java b/src/main/java/me/moros/hyperion/util/PaperLib.java new file mode 100644 index 0000000..dd08236 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/PaperLib.java @@ -0,0 +1,104 @@ +package me.moros.hyperion.util; + + +import me.moros.hyperion.Hyperion; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + +import java.util.concurrent.CompletableFuture; + +public class PaperLib { + private static Environment ENVIRONMENT; + + static { + if (Hyperion.isFolia()) { + ENVIRONMENT = new Folia(); + } else if (Hyperion.isPaper()) { + ENVIRONMENT = new Paper(); + } else if (Hyperion.isSpigot()) { + ENVIRONMENT = new Spigot(); + } + + } + + public static CompletableFuture getChunkAtAsync(Location location) { + return ENVIRONMENT.getChunkAtAsync(location); + } + + public static CompletableFuture getChunkAtAsync(Block block) { + return ENVIRONMENT.getChunkAtAsync(block); + } + + public static void teleportAsync(Entity entity, Location location) { + ENVIRONMENT.teleportAsync(entity, location); + } + + public static CompletableFuture teleportAsync(Entity entity, Location location, TeleportCause cause) { + return ENVIRONMENT.teleportAsync(entity, location, cause); + } + + private interface Environment { + default CompletableFuture getChunkAtAsync(Location location) { + return this.getChunkAtAsync(location.getBlock()); + } + + CompletableFuture getChunkAtAsync(Block block); + + default CompletableFuture teleportAsync(Entity entity, Location location) { + return this.teleportAsync(entity, location, TeleportCause.PLUGIN); + } + + CompletableFuture teleportAsync(Entity entity, Location location, TeleportCause cause); + } + + private static class Spigot implements Environment { + @Override + public CompletableFuture getChunkAtAsync(Block block) { + return CompletableFuture.completedFuture(block.getChunk()); + } + + @Override + public CompletableFuture teleportAsync(Entity entity, Location location, TeleportCause cause) { + entity.teleport(location, cause); + return CompletableFuture.completedFuture(true); + } + } + + private static class Paper implements Environment { + @Override + public CompletableFuture getChunkAtAsync(Block block) { + CompletableFuture future = new CompletableFuture<>(); + Hyperion.scheduler.region(block.getLocation()).run(() -> { + future.complete(block.getChunk()); + }); + return future; + } + + @Override + public CompletableFuture teleportAsync(Entity entity, Location location, TeleportCause cause) { + return Hyperion.scheduler.teleportAsync(entity, location, cause); + } + } + + private static class Folia implements Environment { + @Override + public CompletableFuture getChunkAtAsync(Block block) { + CompletableFuture future = new CompletableFuture<>(); + // Use CJCrafter's scheduler to run synchronously in the region thread + Hyperion.scheduler.region(block.getLocation()).run(task -> { + Chunk chunk = block.getWorld().getChunkAt(block); + future.complete(chunk); + }); + return future; + } + + @Override + public CompletableFuture teleportAsync(Entity entity, Location location, TeleportCause cause) { + // Folia supports async teleport natively + return Hyperion.scheduler.teleportAsync(entity, location, cause); + } + } +} diff --git a/src/main/java/me/moros/hyperion/util/PotionEffectAdapter.java b/src/main/java/me/moros/hyperion/util/PotionEffectAdapter.java new file mode 100644 index 0000000..bbd9d13 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/PotionEffectAdapter.java @@ -0,0 +1,15 @@ +package me.moros.hyperion.util; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionType; + +public interface PotionEffectAdapter { + PotionType getHarmingPotionType(); + PotionEffect getSlownessEffect(int duration, int strength); + PotionEffect getResistanceEffect(int duration, int strength); + PotionEffect getNauseaEffect(int duration); + void applyJumpBoost(Player player, int duration, int strength); + boolean hasWaterPotion(Inventory inventory); +} diff --git a/src/main/java/me/moros/hyperion/util/PotionEffectAdapterFactory.java b/src/main/java/me/moros/hyperion/util/PotionEffectAdapterFactory.java new file mode 100644 index 0000000..515aa80 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/PotionEffectAdapterFactory.java @@ -0,0 +1,25 @@ +package me.moros.hyperion.util; + +import com.projectkorra.projectkorra.GeneralMethods; +import org.bukkit.Bukkit; + +public class PotionEffectAdapterFactory { + + private PotionEffectAdapter adapter; + + public PotionEffectAdapterFactory() { + int serverVersion = GeneralMethods.getMCVersion(); + + if (serverVersion >= 1205) { + Bukkit.getLogger().info("[Hyperion] Using 1.20.5+ PotionEffectAdapter"); + adapter = new PotionEffectAdapter_1_20_5(); + } else { + Bukkit.getLogger().info("[Hyperion] Using 1.20.4- PotionEffectAdapter"); + adapter = new PotionEffectAdapter_1_20_4(); + } + } + + public PotionEffectAdapter getAdapter() { + return adapter; + } +} diff --git a/src/main/java/me/moros/hyperion/util/PotionEffectAdapter_1_20_4.java b/src/main/java/me/moros/hyperion/util/PotionEffectAdapter_1_20_4.java new file mode 100644 index 0000000..2cd8db6 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/PotionEffectAdapter_1_20_4.java @@ -0,0 +1,55 @@ +package me.moros.hyperion.util; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +public class PotionEffectAdapter_1_20_4 implements PotionEffectAdapter { + + @Override + public PotionType getHarmingPotionType() { + return PotionType.valueOf("INSTANT_DAMAGE"); + } + + @Override + public PotionEffect getSlownessEffect(int duration, int strength) { + return new PotionEffect(PotionEffectType.getByName("SLOW"), duration / 50, strength); + } + + @Override + public PotionEffect getResistanceEffect(int duration, int strength) { + return new PotionEffect(PotionEffectType.getByName("DAMAGE_RESISTANCE"), duration / 50, strength - 1); + } + + @Override + public PotionEffect getNauseaEffect(int duration) { + return new PotionEffect(PotionEffectType.getByName("CONFUSION"), duration / 50, 1); + } + + @Override + public void applyJumpBoost(Player player, int duration, int strength) { + if (player.hasPotionEffect(PotionEffectType.getByName("JUMP"))) { + player.removePotionEffect(PotionEffectType.getByName("JUMP")); + } + player.addPotionEffect(new PotionEffect(PotionEffectType.getByName("JUMP"), duration / 50, strength - 1)); + } + + @Override + public boolean hasWaterPotion(Inventory inventory) { + if (inventory.contains(Material.POTION)) { + ItemStack item = inventory.getItem(inventory.first(Material.POTION)); + if (item == null) return false; + PotionMeta meta = (PotionMeta) item.getItemMeta(); + if (meta == null) return false; + + PotionType potionType = PotionMetaUtil.getPotionType(meta); + return potionType == PotionType.WATER; + } + return false; + } +} diff --git a/src/main/java/me/moros/hyperion/util/PotionEffectAdapter_1_20_5.java b/src/main/java/me/moros/hyperion/util/PotionEffectAdapter_1_20_5.java new file mode 100644 index 0000000..4738fb0 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/PotionEffectAdapter_1_20_5.java @@ -0,0 +1,57 @@ +package me.moros.hyperion.util; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +import java.util.Objects; + +public class PotionEffectAdapter_1_20_5 implements PotionEffectAdapter { + + @Override + public PotionType getHarmingPotionType() { + return PotionType.valueOf("HARMING"); + } + + @Override + public PotionEffect getSlownessEffect(int duration, int strength) { + return new PotionEffect(Objects.requireNonNull(PotionEffectType.getByName("SLOWNESS")), duration / 50, strength - 1); + } + + @Override + public PotionEffect getResistanceEffect(int duration, int strength) { + return new PotionEffect(Objects.requireNonNull(PotionEffectType.getByName("RESISTANCE")), duration / 50, strength - 1); + } + + @Override + public PotionEffect getNauseaEffect(int duration) { + return new PotionEffect(Objects.requireNonNull(PotionEffectType.getByName("NAUSEA")), duration / 50, 1); + } + + @Override + public void applyJumpBoost(Player player, int duration, int strength) { + if (player.hasPotionEffect(Objects.requireNonNull(PotionEffectType.getByName("JUMP_BOOST")))) { + player.removePotionEffect(Objects.requireNonNull(PotionEffectType.getByName("JUMP_BOOST"))); + } + player.addPotionEffect(new PotionEffect(Objects.requireNonNull(PotionEffectType.getByName("JUMP_BOOST")), duration / 50, strength - 1)); + } + + @Override + public boolean hasWaterPotion(Inventory inventory) { + if (inventory.contains(Material.POTION)) { + ItemStack item = inventory.getItem(inventory.first(Material.POTION)); + if (item == null) return false; + PotionMeta meta = (PotionMeta) item.getItemMeta(); + if (meta == null) return false; + + PotionType potionType = PotionMetaUtil.getPotionType(meta); + return potionType == PotionType.WATER; + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/me/moros/hyperion/util/PotionMetaUtil.java b/src/main/java/me/moros/hyperion/util/PotionMetaUtil.java new file mode 100644 index 0000000..0a3c4ae --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/PotionMetaUtil.java @@ -0,0 +1,29 @@ +package me.moros.hyperion.util; + +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionType; + +import java.lang.reflect.Method; + +public class PotionMetaUtil { + + public static PotionType getPotionType(PotionMeta meta) { + try { + Method getBasePotionType = PotionMeta.class.getMethod("getBasePotionType"); + return (PotionType) getBasePotionType.invoke(meta); + } catch (NoSuchMethodException e) { + try { + Method getBasePotionData = PotionMeta.class.getMethod("getBasePotionData"); + Object basePotionData = getBasePotionData.invoke(meta); + + Method getType = basePotionData.getClass().getMethod("getType"); + return (PotionType) getType.invoke(basePotionData); + } catch (Exception ex) { + ex.printStackTrace(); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/me/moros/hyperion/util/RainbowParticleEffect.java b/src/main/java/me/moros/hyperion/util/RainbowParticleEffect.java new file mode 100644 index 0000000..e07081e --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/RainbowParticleEffect.java @@ -0,0 +1,146 @@ +package me.moros.hyperion.util; + +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.Particle.DustOptions; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.Vector; + +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class RainbowParticleEffect { + + private static final Random random = new Random(); + + /** + * Spawns a fixed number of rainbow particles gradually over time. + * + * @param center The center location + * @param amount Total number of particles to spawn + * @param xOffset Horizontal spread + * @param yOffset Vertical spread + * @param zOffset Depth spread + */ + public static void spawnRainbowParticleEffect(Location center, int amount, double xOffset, double yOffset, double zOffset) { + World world = center.getWorld(); + if (world == null) return; + + AtomicInteger spawned = new AtomicInteger(0); + AtomicReference hue = new AtomicReference<>(0f); + + Runnable task = () -> { + if (!center.isWorldLoaded()) { + ThreadUtil.cancelTask(center); + return; + } + + int remaining = amount - spawned.get(); + if (remaining <= 0) { + ThreadUtil.cancelTask(center); + return; + } + + int particlesThisTick = Math.min(5, remaining); // spawn up to 5 per tick + for (int i = 0; i < particlesThisTick; i++) { + spawnSingleRainbowParticle(world, center, hue.get(), xOffset, yOffset, zOffset); + spawned.incrementAndGet(); + } + + float newHue = hue.get() + 3f; + if (newHue >= 360f) newHue -= 360f; + hue.set(newHue); + }; + + ThreadUtil.ensureLocationTimer(center, task, 1L, 1L); + } + + /** + * Animated rainbow effect — spawns exactly `amount` particles total over time, or until duration is reached. + * + * @param center Center location + * @param amount Total number of particles to spawn + * @param plugin Your plugin instance (for scheduler) + * @param duration Maximum duration in ticks + */ + public static void spawnAnimatedRainbow(Location center, int amount, Plugin plugin, int duration) { + World world = center.getWorld(); + if (world == null) return; + + AtomicInteger ticks = new AtomicInteger(0); + AtomicInteger spawned = new AtomicInteger(0); + AtomicReference hue = new AtomicReference<>(0f); + + Runnable task = () -> { + if (ticks.incrementAndGet() >= duration || !center.isWorldLoaded()) { + ThreadUtil.cancelTask(center); + return; + } + + int remaining = amount - spawned.get(); + if (remaining <= 0) { + ThreadUtil.cancelTask(center); + return; + } + + int particlesThisTick = Math.min(5, remaining); // spawn up to 5 per tick + for (int i = 0; i < particlesThisTick; i++) { + spawnSingleRainbowParticle(world, center, hue.get(), 0.3, 0.4, 0.3); + spawned.incrementAndGet(); + } + + float newHue = hue.get() + 3f; + if (newHue >= 360f) newHue -= 360f; + hue.set(newHue); + }; + + ThreadUtil.ensureLocationTimer(center, task, 1L, 1L); + } + + /** + * Spawns a single rainbow particle (smooth hue-based). + */ + private static void spawnSingleRainbowParticle(World world, Location center, float baseHue, + double xOffset, double yOffset, double zOffset) { + Particle particle = Particle.REDSTONE; + + float hue = (baseHue + random.nextFloat() * 20f) % 360f; + Color color = hsvToColor(hue, 1f, 1f); + + float size = 1.2f + random.nextFloat() * 0.8f; + DustOptions data = new DustOptions(color, size); + + double offsetX = (random.nextDouble() - 0.5) * 2 * xOffset; + double offsetY = random.nextDouble() * yOffset; + double offsetZ = (random.nextDouble() - 0.5) * 2 * zOffset; + Location loc = center.clone().add(offsetX, offsetY, offsetZ); + + Vector velocity = new Vector( + (random.nextDouble() - 0.5) * 0.08, + random.nextDouble() * 0.12, + (random.nextDouble() - 0.5) * 0.08 + ); + + world.spawnParticle( + particle, + loc, + 0, + velocity.getX(), + velocity.getY(), + velocity.getZ(), + 0, + data + ); + } + + /** + * Converts HSV to Bukkit Color. + */ + private static Color hsvToColor(float hue, float saturation, float value) { + int rgb = java.awt.Color.HSBtoRGB(hue / 360f, saturation, value); + return Color.fromRGB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); + } +} diff --git a/src/main/java/me/moros/hyperion/util/TempArmorStand.java b/src/main/java/me/moros/hyperion/util/TempArmorStand.java index ecd32e4..4defcc4 100755 --- a/src/main/java/me/moros/hyperion/util/TempArmorStand.java +++ b/src/main/java/me/moros/hyperion/util/TempArmorStand.java @@ -1,34 +1,21 @@ -/* - * Copyright 2016-2024 Moros - * - * This file is part of Hyperion. - * - * Hyperion is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Hyperion is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Hyperion. If not, see . - */ - package me.moros.hyperion.util; import com.projectkorra.projectkorra.ability.CoreAbility; import com.projectkorra.projectkorra.util.ParticleEffect; + +import com.projectkorra.projectkorra.util.TempBlock; import me.moros.hyperion.Hyperion; import me.moros.hyperion.methods.CoreMethods; + import org.bukkit.Location; import org.bukkit.Material; + import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Entity; + import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; + import java.util.Comparator; import java.util.Map; @@ -39,6 +26,8 @@ import java.util.stream.Collectors; public class TempArmorStand { + + private static final Plugin PLUGIN = Hyperion.getPlugin(); private static final Map instances = new ConcurrentHashMap<>(); private static final Queue tasQueue = new PriorityQueue<>(Comparator.comparingLong(TempArmorStand::getExpirationTime)); @@ -53,27 +42,30 @@ public TempArmorStand(CoreAbility abilityInstance, Location location, Material m } public TempArmorStand(CoreAbility abilityInstance, Location location, Material material, long delay, boolean showRemoveParticles) { - headMaterial = material; - armorStand = location.getWorld().spawn(location, ArmorStand.class, entity -> { + this.headMaterial = material; + this.ability = abilityInstance; + this.expirationTime = System.currentTimeMillis() + delay; + this.particles = showRemoveParticles; + + this.armorStand = location.getWorld().spawn(location, ArmorStand.class, entity -> { entity.setInvulnerable(true); entity.setVisible(false); entity.setGravity(false); entity.getEquipment().setHelmet(new ItemStack(headMaterial)); - entity.setMetadata(CoreMethods.NO_INTERACTION_KEY, new FixedMetadataValue(Hyperion.getPlugin(), "")); + entity.setMetadata(CoreMethods.NO_INTERACTION_KEY, new FixedMetadataValue(PLUGIN, "")); }); - expirationTime = System.currentTimeMillis() + delay; - ability = abilityInstance; + instances.put(armorStand, this); tasQueue.add(this); - particles = showRemoveParticles; showParticles(true); } public void showParticles(boolean show) { - if (show) { - ParticleEffect.BLOCK_CRACK.display(armorStand.getEyeLocation().add(0, 0.2, 0), 4, 0.25, 0.125, 0.25, 0, headMaterial.createBlockData()); - ParticleEffect.BLOCK_DUST.display(armorStand.getEyeLocation().add(0, 0.2, 0), 6, 0.25, 0.125, 0.25, 0, headMaterial.createBlockData()); - } + if (!show) return; + + Location loc = armorStand.getEyeLocation().add(0, 0.2, 0); + ParticleEffect.BLOCK_CRACK.display(loc, 4, 0.25, 0.125, 0.25, 0, headMaterial.createBlockData()); + ParticleEffect.BLOCK_DUST.display(loc, 6, 0.25, 0.125, 0.25, 0, headMaterial.createBlockData()); } public CoreAbility getAbility() { @@ -88,6 +80,10 @@ public long getExpirationTime() { return expirationTime; } + public boolean isExpired() { + return System.currentTimeMillis() > expirationTime; + } + public static boolean isTempArmorStand(ArmorStand as) { return instances.containsKey(as); } @@ -97,31 +93,49 @@ public static TempArmorStand get(ArmorStand as) { } public static Set getFromAbility(CoreAbility ability) { - return instances.values().stream().filter(tas -> tas.getAbility().equals(ability)).collect(Collectors.toSet()); + return instances.values().stream() + .filter(tas -> tas.getAbility().equals(ability)) + .collect(Collectors.toSet()); } public static void manage() { final long currentTime = System.currentTimeMillis(); + while (!tasQueue.isEmpty()) { final TempArmorStand tas = tasQueue.peek(); - if (currentTime > tas.getExpirationTime()) { - tasQueue.poll(); - tas.remove(); - } else { + + if (tas == null || currentTime < tas.getExpirationTime()) { return; } + + tasQueue.poll(); + + if (tas.armorStand == null || !tas.armorStand.isValid()) { + instances.remove(tas.armorStand); + continue; + } + + + Location loc = tas.armorStand.getLocation(); + ThreadUtil.ensureLocation(loc, tas::remove); } } public void remove() { + if (armorStand != null && armorStand.isValid()) { + showParticles(particles); + armorStand.remove(); + } instances.remove(armorStand); - showParticles(particles); - armorStand.remove(); } public static void removeAll() { tasQueue.clear(); - instances.keySet().forEach(Entity::remove); + instances.keySet().forEach(entity -> { + if (entity != null && entity.isValid()) { + entity.remove(); + } + }); instances.clear(); } } diff --git a/src/main/java/me/moros/hyperion/util/ThreadUtil.java b/src/main/java/me/moros/hyperion/util/ThreadUtil.java new file mode 100644 index 0000000..09eef30 --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/ThreadUtil.java @@ -0,0 +1,369 @@ +package me.moros.hyperion.util; + + + +import com.cjcrafter.foliascheduler.TaskImplementation; +import com.cjcrafter.foliascheduler.folia.FoliaTask; +import me.moros.hyperion.Hyperion; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.logging.Level; + +import static me.moros.hyperion.Hyperion.scheduler; + +/** + * Utility class for ensuring that a task is run on the correct thread. + * Ensures compatibility between Folia and non-Folia servers. */ +public class ThreadUtil { + + private static AtomicBoolean SHUTTING_DOWN = new AtomicBoolean(false); + + /** + * Runs a task on the same thread as an entity. On Spigot, this is the main + * thread. On Folia, this is the thread that the entity is on.

+ * + * @param entity The entity to run the task on. + * @param runnable The task to run. + */ + public static void ensureEntity(@NotNull Entity entity, @NotNull Runnable runnable) { + if (entity instanceof Player && !((Player)entity).isOnline()) return; + + if (Hyperion.isFolia()) { + if (scheduler.isOwnedByCurrentRegion(entity) || SHUTTING_DOWN.get()) { + runCatch(runnable, "Error in ensureEntity task on shutdown"); + return; + } + scheduler.entity(entity).execute(runnable, null, 1L); + } else { + if (Bukkit.isPrimaryThread()) { + runCatch(runnable, "Error in ensureEntity task"); + return; + } + Bukkit.getScheduler().runTask(Hyperion.plugin, runnable); + } + } + + /** + * Runs a task on the same thread as an entity after a delay. On Spigot, this is the main + * thread. On Folia, this is the thread that the entity is on. + * @param entity The entity to run the task on. + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static Object ensureEntityLater(@NotNull Entity entity, @NotNull Runnable runnable, long delay) { + if (entity instanceof Player && !((Player)entity).isOnline()) return null; + delay = Math.max(1, delay); + if (Hyperion.isFolia()) { + return scheduler.entity(entity).execute(runnable, null, delay); + } else { + return Bukkit.getScheduler().runTaskLater(Hyperion.plugin, runnable, delay); + } + } + + /** + * Runs a task on the same thread as an entity after a delay and repeats it until cancelled. + * On Spigot, this is the main thread. On Folia, this is the thread that the entity is on. + * @param entity The entity to run the task on. + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @param repeat The delay in ticks between each repeat of the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static Object ensureEntityTimer(@NotNull Entity entity, @NotNull Runnable runnable, long delay, long repeat) { + if (entity instanceof Player && !((Player)entity).isOnline()) return null; + delay = Math.max(1, delay); + repeat = Math.max(1, repeat); + if (Hyperion.isFolia()) { + return scheduler.entity(entity).runAtFixedRate((task) -> { + if (!runCatch(runnable, "Error in ensureEntityTimer task")) { + task.cancel(); + } + }, null, delay, repeat); + } else { + return Bukkit.getScheduler().runTaskTimer(Hyperion.plugin, runnable, delay, repeat); + } + } + + /** + * Runs a task on the same thread as a location. On Spigot, this is the main + * thread. On Folia, this is the thread that the location is in. + * @param location The location to run the task on. + * @param runnable The task to run. + */ + public static void ensureLocation(@NotNull Location location, @NotNull Runnable runnable) { + if (Hyperion.isFolia()) { + if (scheduler.isOwnedByCurrentRegion(location) || SHUTTING_DOWN.get()) { + runCatch(runnable, "Error in ensureLocation task on shutdown"); + return; + } + + scheduler.region(location).execute(runnable); + } else { + if (Bukkit.isPrimaryThread()) { + runCatch(runnable, "Error in ensureLocation task on shutdown"); + return; + } + Bukkit.getScheduler().runTask(Hyperion.plugin, runnable); + } + } + + /** + * Runs a task on the same thread as a location after a delay. On Spigot, this is the main + * thread. On Folia, this is the thread that the location is in. + * @param location The location to run the task on. + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static @NotNull Object ensureLocationLater(@NotNull Location location, @NotNull Runnable runnable, long delay) { + delay = Math.max(1, delay); + if (Hyperion.isFolia()) { + return scheduler.region(location).runDelayed((task) -> { + if (!runCatch(runnable, "Error in ensureLocationLater")) { + task.cancel(); + } + }, delay); + } else { + return Bukkit.getScheduler().runTaskLater(Hyperion.plugin, runnable, delay); + } + } + + /** + * Runs a task on the same thread as a location after a delay and repeats it until cancelled. + * On Spigot, this is the main thread. On Folia, this is the thread that the location is in. + * @param location The location to run the task on. + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @param repeat The delay in ticks between each repeat of the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static @NotNull Object ensureLocationTimer(@NotNull Location location, @NotNull Runnable runnable, long delay, long repeat) { + delay = Math.max(1, delay); + repeat = Math.max(1, repeat); + if (Hyperion.isFolia()) { + + return scheduler.region(location).runAtFixedRate((task) -> { + if (!runCatch(runnable, "Error in ensureLocationTimer task")) + task.cancel(); + }, delay, repeat); + } else { + return Bukkit.getScheduler().runTaskTimer(Hyperion.plugin, runnable, delay, repeat); + } + } + + /** + * Runs a task asynchronously. + * @param runnable The task to run. + */ + public static void runAsync(@NotNull Runnable runnable) { + if (Hyperion.isFolia()) { + if (SHUTTING_DOWN.get()) { + runCatch(runnable, "Error in runAsync task on shutdown"); + return; + } + + scheduler.async().runNow((task) -> { + if (!runCatch(runnable, "Error in runAsync task")) { + task.cancel(); + } + }); + } else { + Bukkit.getScheduler().runTaskAsynchronously(Hyperion.plugin, runnable); + } + } + + /** + * Runs a task asynchronously after a delay. + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static @NotNull Object runAsyncLater(@NotNull Runnable runnable, long delay) { + delay = Math.max(1, delay); + if (Hyperion.isFolia()) { + return scheduler.async().runDelayed((task) -> { + if (!runCatch(runnable, "Error in runAsyncLater task")) { + task.cancel(); + } + }, delay * 50, TimeUnit.MILLISECONDS); + } else { + return Bukkit.getScheduler().runTaskLater(Hyperion.plugin, runnable, delay); + } + } + + /** + * Runs a task asynchronously after a delay and repeats it until cancelled. + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @param repeat The delay in ticks between each repeat of the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static @NotNull Object runAsyncTimer(@NotNull Runnable runnable, long delay, long repeat) { + delay = Math.max(1, delay); + if (Hyperion.isFolia()) { + return scheduler.async().runAtFixedRate((Consumer>) (task) -> runnable.run(), delay * 50, repeat * 50, TimeUnit.MILLISECONDS); + } else { + return Bukkit.getScheduler().runTaskTimerAsynchronously(Hyperion.plugin, runnable, delay, repeat); + } + } + + /** + * Runs a task synchronously. On Spigot, this is on the main thread. On Folia, + * this is on the global region thread.

+ * + * Warning: This should only be used for tasks that affect the world itself! Such as + * modifying gamerules, the weather, world time, etc. It should not be used for tasks + * that modify entities or the world, as those should be run using {@link #ensureEntity(Entity, Runnable)} + * or {@link #ensureLocation(Location, Runnable)}. + * + * @param runnable The task to run. + */ + public static void runGlobal(@NotNull Runnable runnable) { + if (Hyperion.isFolia()) { + if (SHUTTING_DOWN.get()) { + runCatch(runnable, "Error in runGlobal task on shutdown"); + return; + } + scheduler.global().run((task) -> { + if (!runCatch(runnable, "Error in runGlobal task")) { + task.cancel(); + } + }); + } else { + Bukkit.getScheduler().runTask(Hyperion.plugin, runnable); + } + } + + /** + * Runs a task synchronously after a delay. On Spigot, this is on the main thread. + * On Folia, this is on the global region thread.

+ * + * Warning: This should only be used for tasks that affect the world itself! Such as + * modifying gamerules, the weather, world time, etc. It should not be used for tasks + * that modify entities or the world, as those should be run using {@link #ensureEntityLater(Entity, Runnable, long)} + * or {@link #ensureLocationLater(Location, Runnable, long)}. + * + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static @NotNull Object runGlobalLater(@NotNull Runnable runnable, long delay) { + delay = Math.max(1, delay); + if (Hyperion.isFolia()) { + return scheduler.global().runDelayed((task) -> { + if (!runCatch(runnable, "Error in runGlobalLater task")) { + task.cancel(); + } + }, delay); + } else { + return Bukkit.getScheduler().runTaskLater(Hyperion.plugin, runnable, delay); + } + } + + /** + * Runs a task synchronously after a delay and repeats it until cancelled. + * On Spigot, this is on the main thread. On Folia, this is on the global region thread.

+ * + * Warning: This should only be used for tasks that affect the world itself! Such as + * modifying gamerules, the weather, world time, etc. It should not be used for tasks + * that modify entities or the world, as those should be run using + * {@link #ensureEntityTimer(Entity, Runnable, long, long)} + * or {@link #ensureLocationTimer(Location, Runnable, long, long)} + * + * @param runnable The task to run. + * @param delay The delay in ticks before running the task. + * @param repeat The delay in ticks between each repeat of the task. + * @return The task object that can be used to cancel the task. Is a + * {@link com.cjcrafter.foliascheduler.folia.FoliaTask} on Folia and a + * {@link org.bukkit.scheduler.BukkitTask} on Spigot. + */ + public static @NotNull Object runGlobalTimer(@NotNull Runnable runnable, long delay, long repeat) { + delay = Math.max(1, delay); + if (Hyperion.isFolia()) { + return scheduler.global().runAtFixedRate((task) -> { + if (!runCatch(runnable, "Error in runGlobalTimer task")) { + task.cancel(); + } + }, delay, repeat); + } else { + return Bukkit.getScheduler().runTaskTimer(Hyperion.plugin, runnable, delay, repeat); + } + } + + /** + * Cancels a task that was created with either timers or delayed tasks. + * @param task The task to cancel. This is the object returned from + * {@link #ensureLocationTimer(Location, Runnable, long, long)} or + * {@link #ensureEntityTimer(Entity, Runnable, long, long)}. + * @return True if the task was cancelled successfully, false otherwise. + */ + public static boolean cancelTask(Object task) { + if (task == null) return false; + if (Hyperion.isFolia()) { + if (task instanceof FoliaTask) { + ((FoliaTask) task).cancel(); + return true; + } + } else { + if (task instanceof org.bukkit.scheduler.BukkitTask) { + ((org.bukkit.scheduler.BukkitTask) task).cancel(); + return true; + } + } + return false; + } + + /** + * Checks if a task is cancelled. + * @return True if the task is cancelled, false otherwise. + */ + public static boolean isTaskCancelled(Object task) { + if (task == null) return true; + if (Hyperion.isFolia()) { + if (task instanceof FoliaTask) { + return ((FoliaTask) task).isCancelled(); + } + } else { + if (task instanceof org.bukkit.scheduler.BukkitTask) { + return ((org.bukkit.scheduler.BukkitTask) task).isCancelled(); + } + } + return false; + } + + private static boolean runCatch(Runnable runnable, String error) { + try { + runnable.run(); + return true; + } catch (Exception e) { + Hyperion.log.log(Level.WARNING, error, e); + return false; + } + } + + public static void shutdown() { + SHUTTING_DOWN.set(true); + } +} \ No newline at end of file diff --git a/src/main/java/me/moros/hyperion/util/UpdateChecker.java b/src/main/java/me/moros/hyperion/util/UpdateChecker.java new file mode 100644 index 0000000..525dc5f --- /dev/null +++ b/src/main/java/me/moros/hyperion/util/UpdateChecker.java @@ -0,0 +1,132 @@ +package me.moros.hyperion.util; + +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitScheduler; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.stream.Collectors; + +public class UpdateChecker { + + private final JavaPlugin plugin; + private final String apiUrl; + + private boolean updateAvailable = false; + private boolean checked = false; + + private String currentVersion; + private String latestVersion; + + public UpdateChecker(JavaPlugin plugin, String repo) { + this.plugin = plugin; + this.apiUrl = "https://api.github.com/repos/" + repo + "/releases/latest"; + } + + public void checkForUpdate() { + currentVersion = plugin.getDescription().getVersion(); + + try { + HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); + conn.setRequestProperty("Accept", "application/vnd.github.v3+json"); + + String token = System.getenv("GITHUB_TOKEN"); + if (token != null && !token.isEmpty()) { + conn.setRequestProperty("Authorization", "token " + token); + } + + conn.setRequestMethod("GET"); + + String json = new BufferedReader(new InputStreamReader(conn.getInputStream())) + .lines().collect(Collectors.joining()); + + String tagName = parseJsonField(json, "tag_name"); + if (tagName == null) { + plugin.getLogger().warning("Update check failed: tag_name not found in GitHub API response"); + checked = true; + return; + } + + latestVersion = tagName.startsWith("v") ? tagName.substring(1) : tagName; + currentVersion = currentVersion.startsWith("v") ? currentVersion.substring(1) : currentVersion; + + updateAvailable = compareVersions(currentVersion, latestVersion) < 0; + checked = true; + + } catch (IOException e) { + plugin.getLogger().warning("Update check failed: " + e.getMessage()); + checked = true; + } + } + + private int compareVersions(String v1, String v2) { + String[] parts1 = v1.split("[.-]"); + String[] parts2 = v2.split("[.-]"); + + int length = Math.max(parts1.length, parts2.length); + for (int i = 0; i < length; i++) { + String p1 = i < parts1.length ? parts1[i] : "0"; + String p2 = i < parts2.length ? parts2[i] : "0"; + + int cmp; + + try { + // Compare numeric parts + int n1 = Integer.parseInt(p1); + int n2 = Integer.parseInt(p2); + cmp = Integer.compare(n1, n2); + } catch (NumberFormatException e) { + // Handle pre-release comparison + boolean isPre1 = p1.toUpperCase().startsWith("PRE"); + boolean isPre2 = p2.toUpperCase().startsWith("PRE"); + + if (isPre1 && !isPre2) { + cmp = -1; // pre-release is older than stable + } else if (!isPre1 && isPre2) { + cmp = 1; // stable is newer than pre-release + } else if (isPre1 && isPre2) { + cmp = p1.compareTo(p2); // compare pre-releases lexically + } else { + cmp = p1.compareTo(p2); // fallback string compare + } + } + + if (cmp != 0) return cmp; + } + + return 0; + } + + + private String parseJsonField(String json, String field) { + String search = "\"" + field + "\":\""; + int index = json.indexOf(search); + if (index == -1) return null; + + int start = index + search.length(); + int end = json.indexOf("\"", start); + if (end == -1) return null; + + return json.substring(start, end); + } + + // Getters + public boolean isUpdateAvailable() { + return updateAvailable; + } + + public boolean hasChecked() { + return checked; + } + + public String getCurrentVersion() { + return currentVersion; + } + + public String getLatestVersion() { + return latestVersion; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 02d5de4..efd62ae 100755 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,7 +4,10 @@ author: Moros version: ${pluginVersion} main: me.moros.hyperion.Hyperion depend: [ProjectKorra] -api-version: 1.17 +folia-supported: true +api-version: '1.20' +load: POSTWORLD + permissions: bending.admin.overridelock: default: false @@ -15,6 +18,9 @@ permissions: bending.command.hyperion.reload: default: op description: Reload the config of Hyperion. + bending.command.hyperion.checkupdate: + default: op + description: Allows the player to manually check for plugin updates. hyperion.player: default: true description: Access to hyperion abilities.