Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/io/github/pylonmc/pylon/base/BaseItems.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/io/github/pylonmc/pylon/base/util/BaseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,6 +17,8 @@
import org.joml.Matrix4f;
import org.joml.Vector3d;

import javax.swing.text.StyleContext;


@UtilityClass
public class BaseUtils {
Expand Down Expand Up @@ -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)),
Expand Down
165 changes: 103 additions & 62 deletions src/main/java/io/github/pylonmc/pylon/base/util/DisplayProjectile.java
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with all the changes in this class? It seems to be a lot more complex before but I cannot tell why it was needed. Is it not as simple as just checking the block and despawning the projectile if said block is not solid, every tick - or previous block as well or something if necessary?

Copy link
Member Author

@Intybyte Intybyte Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why have a task running uselessly if it can be stopped before
  • Why currying up something that doesn't need currying and just makes a massive parameter functions when you can just have one constructor that handles it
  • There are a lot of cases where the entity must be killed and the thing stopped, but intead are handled by a return, waiting on the removal task and kill task

So I rewrote the entire thing to match a similar logic to the confetti popper where we have a master task and everything dies when we are done, it is more like an optimization

Copy link
Contributor

@LordIdra LordIdra Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why currying up something that doesn't need currying and just makes a massive parameter functions when you can just have one constructor that handles it

Um... ????? The constructor has the same number of parameters as the 'massive parameter function' so this changes nothing except adding more code. There is no currying going on here either? Very confused here lol.

I see what you mean with the task not being cancelled, but as there are so few of these tasks running at once, and by far the most performance-intensive part is finding nearby entities/blocks and teleporting, it's a bit of a pointless optimisation.

If you do really want it as a class that is instantiated though, may I suggest you make it a PylonEntity instead? This is IMO a perfect use case for PylonEntity as we're wrapping an entity and adding our own logic. You could use the entity ticker stuff too.

Original file line number Diff line number Diff line change
@@ -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<Entity> 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<Damageable> 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<Entity> nearbyEntities = projectile.getNearbyEntities(thickness * 1.5, thickness * 1.5, thickness * 1.5);
if (nearbyEntities.isEmpty()) {
return;
}

Optional<Damageable> 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);
}
}
}