diff --git a/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java b/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java index cf5f6cbcb..fbbfd964b 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java +++ b/src/main/java/io/github/pylonmc/pylon/base/BaseItems.java @@ -1819,7 +1819,8 @@ private BaseItems() { BasePages.SIMPLE_MACHINES.addItem(VACUUM_HOPPER_4); } - public static final ItemStack HYDRAULIC_CANNON = ItemStackBuilder.pylonItem(Material.IRON_HORSE_ARMOR, BaseKeys.HYDRAULIC_CANNON) + public static final ItemStack HYDRAULIC_CANNON = ItemStackBuilder.pylonItem(Material.CLAY_BALL, BaseKeys.HYDRAULIC_CANNON) + .set(DataComponentTypes.ITEM_MODEL, Material.IRON_HORSE_ARMOR.getKey()) .set(DataComponentTypes.USE_COOLDOWN, UseCooldown .useCooldown( Settings.get(BaseKeys.HYDRAULIC_CANNON).getOrThrow("cooldown-ticks", ConfigAdapter.INT) / 20.0F diff --git a/src/main/java/io/github/pylonmc/pylon/base/content/tools/HydraulicCannon.java b/src/main/java/io/github/pylonmc/pylon/base/content/tools/HydraulicCannon.java index f3facead3..6d16516a8 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/content/tools/HydraulicCannon.java +++ b/src/main/java/io/github/pylonmc/pylon/base/content/tools/HydraulicCannon.java @@ -99,7 +99,7 @@ public void onUsedToRightClick(@NotNull PlayerInteractEvent event) { Location source = player.getEyeLocation() .subtract(0, 0.5, 0) .add(direction.clone().multiply(1.5)); - DisplayProjectile.spawn( + new DisplayProjectile( player, PROJECTILE_MATERIAL, source, diff --git a/src/main/java/io/github/pylonmc/pylon/base/util/BaseUtils.java b/src/main/java/io/github/pylonmc/pylon/base/util/BaseUtils.java index 1574e4df8..4bad45efa 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/util/BaseUtils.java +++ b/src/main/java/io/github/pylonmc/pylon/base/util/BaseUtils.java @@ -5,7 +5,9 @@ import io.github.pylonmc.pylon.core.util.gui.unit.UnitFormat; import lombok.experimental.UtilityClass; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Location; @@ -15,6 +17,8 @@ import org.joml.Matrix4f; import org.joml.Vector3d; +import javax.swing.text.StyleContext; + @UtilityClass public class BaseUtils { @@ -89,6 +93,15 @@ private int clampAndRound(double value) { public @NotNull Component createFluidAmountBar(double amount, double capacity, int bars, TextColor fluidColor) { int filledBars = (int) Math.round(bars * amount / capacity); + if (filledBars == 0) { + return Component.translatable("pylon.pylonbase.gui.fluid_amount_bar.text").arguments( + PylonArgument.of("filled_bars", Component.empty()), + PylonArgument.of("empty_bars", "|".repeat(bars)), + PylonArgument.of("amount", Math.round(amount)), + PylonArgument.of("capacity", UnitFormat.MILLIBUCKETS.format(Math.round(capacity))) + ); + } + return Component.translatable("pylon.pylonbase.gui.fluid_amount_bar.text").arguments( PylonArgument.of("filled_bars", Component.text("|".repeat(filledBars)).color(fluidColor)), PylonArgument.of("empty_bars", "|".repeat(bars - filledBars)), diff --git a/src/main/java/io/github/pylonmc/pylon/base/util/DisplayProjectile.java b/src/main/java/io/github/pylonmc/pylon/base/util/DisplayProjectile.java index 905c1c3a4..82f97b1e0 100644 --- a/src/main/java/io/github/pylonmc/pylon/base/util/DisplayProjectile.java +++ b/src/main/java/io/github/pylonmc/pylon/base/util/DisplayProjectile.java @@ -1,101 +1,142 @@ package io.github.pylonmc.pylon.base.util; import io.github.pylonmc.pylon.base.PylonBase; +import io.github.pylonmc.pylon.core.entity.PylonEntity; import io.github.pylonmc.pylon.core.entity.display.BlockDisplayBuilder; import io.github.pylonmc.pylon.core.entity.display.transform.LineBuilder; -import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Particle; import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; import org.bukkit.entity.BlockDisplay; import org.bukkit.entity.Damageable; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; import org.joml.Vector3d; import java.util.List; import java.util.Optional; -@UtilityClass -public class DisplayProjectile { - public void spawn( - Player player, - Material material, - Location source, - Vector direction, - float thickness, - float length, - float speedBlockPerSecond, - double damage, - int tickInterval, - int lifetimeTicks +public final class DisplayProjectile { + private final Player player; + private final float thickness; + private final double damage; + private final int tickInterval; + private final Vector locationStep; + private final BlockDisplay projectile; + + + public DisplayProjectile( + Player player, + Material material, + Location source, + Vector direction, + float thickness, + float length, + float speedBlockPerSecond, + double damage, + int tickInterval, + int lifetimeTicks ) { - Vector locationStep = direction.clone().multiply(speedBlockPerSecond * tickInterval / 20.0); - BlockDisplay projectile = new BlockDisplayBuilder() - .transformation(new LineBuilder() - .from(new Vector3d(0, 0, 0)) - .to(direction.clone().multiply(length).toVector3d()) - .thickness(thickness) - .build() - ) - .material(material) - .build(source); - projectile.setPersistent(false); - - BukkitTask task = Bukkit.getScheduler().runTaskTimer(PylonBase.getInstance(), () -> { - tick(player, projectile, locationStep, tickInterval, thickness, damage); - }, tickInterval, tickInterval); - - Bukkit.getScheduler().runTaskLater(PylonBase.getInstance(), projectile::remove, lifetimeTicks); - Bukkit.getScheduler().runTaskLater(PylonBase.getInstance(), task::cancel, lifetimeTicks); + this.player = player; + this.thickness = thickness; + this.damage = damage; + this.tickInterval = tickInterval; + this.locationStep = direction.clone().multiply(speedBlockPerSecond * tickInterval / 20.0); + this.projectile = new BlockDisplayBuilder() + .transformation(new LineBuilder() + .from(new Vector3d(0, 0, 0)) + .to(direction.clone().multiply(length).toVector3d()) + .thickness(thickness) + .build() + ) + .material(material) + .build(source); + this.projectile.setPersistent(false); + + new Task(tickInterval, lifetimeTicks).start(); } - private void tick( - Player player, - @NotNull BlockDisplay projectile, - Vector locationStep, - int tickInterval, - float thickness, - double damage - ) { - if (projectile.isDead()) { - return; + private class Task extends BukkitRunnable { + private final int tickInterval; + private final int lifetimeTicks; + private int livedTicks; + + public Task(int tickInterval, int lifetimeTicks) { + this.tickInterval = tickInterval; + this.lifetimeTicks = lifetimeTicks; } - projectile.setTeleportDuration(tickInterval); - projectile.teleport(projectile.getLocation().add(locationStep)); - List nearbyEntities = projectile.getNearbyEntities(thickness * 1.5, thickness * 1.5, thickness * 1.5); - if (nearbyEntities.isEmpty()) { - return; - } + @Override + public void run() { + if (livedTicks >= lifetimeTicks) { + projectile.remove(); + cancel(); + return; + } + livedTicks += tickInterval; + + if (projectile.isDead()) { + cancel(); + return; + } + + projectile.setTeleportDuration(tickInterval); - Optional hitEntity = nearbyEntities.stream() + Location teleportTo = projectile.getLocation().add(locationStep); + if (!teleportTo.getBlock().isPassable()) { + teleportTo.getWorld().spawnParticle( + Particle.CRIT, + teleportTo, + 15 + ); + projectile.remove(); + cancel(); + return; + } + + projectile.teleport(teleportTo); + + List nearbyEntities = projectile.getNearbyEntities(thickness * 1.5, thickness * 1.5, thickness * 1.5); + if (nearbyEntities.isEmpty()) { + return; + } + + Optional hitEntity = nearbyEntities.stream() .filter(Damageable.class::isInstance) .map(Damageable.class::cast) .findFirst(); - hitEntity.ifPresent(entity -> { - EntityDamageEvent event = new EntityDamageEvent( + hitEntity.ifPresent(entity -> { + EntityDamageEvent event = new EntityDamageEvent( entity, EntityDamageEvent.DamageCause.ENTITY_ATTACK, DamageSource.builder(DamageType.ARROW) - .withCausingEntity(player) - .withDirectEntity(entity) - .build(), + .withCausingEntity(player) + .withDirectEntity(entity) + .build(), damage - ); - if (event.callEvent()) { - entity.damage(damage); - entity.setVelocity(locationStep.clone().normalize().multiply(0.2)); - } - projectile.remove(); - }); + ); + if (event.callEvent()) { + entity.damage(damage); + entity.setVelocity(locationStep.clone().normalize().multiply(0.2)); + } + projectile.remove(); + Task.this.cancel(); + }); + } + + public void start() { + this.runTaskTimer(PylonBase.getInstance(), tickInterval, tickInterval); + } } }