Minecarts.java
AKL City ยท akl-city.git
Branches > main > 02babc9 > files / src / main / java / dev / grahp / aklcity / Minecarts.java
package dev.grahp.aklcity;
import static dev.grahp.aklcity.PlayerUtils.isHoldingFist;
import org.bukkit.event.Listener;
import org.bukkit.event.EventHandler;
import org.bukkit.Material;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Vehicle;
import org.bukkit.entity.Player;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.util.Vector;
import org.bukkit.Bukkit;
import org.bukkit.block.data.Powerable;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.Map;
import java.util.HashMap;
import org.bukkit.scheduler.BukkitTask;
import net.kyori.adventure.text.Component;
import org.bukkit.World;
import org.bukkit.event.vehicle.VehicleExitEvent;
import org.bukkit.event.vehicle.VehicleEnterEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.block.Action;
// TODO:
// - Stops mobs from pathfinding onto rails? Or... Just kill them when they do smiemsie, keep momentum
public class Minecarts implements Listener {
// Returns a safe place to dismount the player if there is one, or just the normal position a player would dismount a minecart from.
private Location findSafeDismount(Minecart cart) {
Location base = cart.getLocation();
World world = base.getWorld();
// for (int dx = -2; dx <= 2; dx++) {
// for (int dz = -2; dz <= 2; dz++) {
//
// Location feet = base.clone().add(dx, 0, dz);
//
// Block ground = feet.clone().subtract(0, 1, 0).getBlock();
// Block feetBlock = feet.getBlock();
// Block headBlock = feet.clone().add(0, 1, 0).getBlock();
//
// boolean solidGround = ground.getType().isSolid();
// boolean clear = feetBlock.isPassable() && headBlock.isPassable();
// boolean notRail =
// !feetBlock.getType().toString().endsWith("RAIL") &&
// !ground.getType().toString().endsWith("RAIL");
//
// if (solidGround && clear && notRail) {
// return feet.add(0.5, 0, 0.5);
// }
// }
// }
//
// Bukkit.broadcastMessage("choosing default");
return base;
}
private Player playerInMinecart(Minecart cart) {
if (!cart.getPassengers().isEmpty() && cart.getPassengers().get(0) instanceof Player p) {
return p;
}
return null;
}
private void dismount(Minecart cart) {
cart.eject();
// var p = playerInMinecart(cart);
// if (p != null) {
// p.teleport(findSafeDismount(cart));
// }
}
private void killMinecart(Minecart cart) {
dismount(cart);
Timers.schedule(5L, () -> {
if (!cart.isDead()) {
cart.remove();
}
});
}
private static final Material stopperRailMaterial = Material.DETECTOR_RAIL;
/// Returns whether a Minecart is on top of a rail that it should slow down and allow the player to exit for
private boolean isOnStopperRail(Minecart cart) {
return cart.getLocation().getBlock().getType() == stopperRailMaterial;
}
/// Returns whether a Minecart is stopped, or moving so slow that it's basically not moving at all
private boolean isCartStopped(Minecart cart) {
return cart.getVelocity().lengthSquared() < 0.001;
}
@EventHandler
public void onEnterMinecart(VehicleEnterEvent e) {
if (e.getVehicle() instanceof Minecart && e.getEntered() instanceof Player p) {
Timers.runNextTick(() -> {
p.sendActionBar(
Component.text("Press ")
.append(Component.keybind("key.sneak"))
.append(Component.text(" to exit at the next stop"))
);
});
}
}
private static Map<UUID, Location> stopLocations = new HashMap();
@EventHandler
public void onExitMinecart(VehicleExitEvent e) {
if (e.getVehicle() instanceof Minecart cart && cart.getPassengers().get(0) instanceof Player p) {
if (isOnStopperRail(cart) || isCartStopped(cart)) {
killMinecart(cart);
} else if (stopLocations.containsKey(p.getUniqueId())) {
// TODO looks similar to killMinecart...
cart.eject();
cart.remove();
p.teleport(stopLocations.get(p.getUniqueId()));
} else {
e.setCancelled(true);
p.sendActionBar(Component.text("You may only exit at a stop!"));
}
stopLocations.remove(p.getUniqueId());
}
}
// Called once when a Minecart enters a stop
public BukkitTask onEnterStop(Minecart cart) {
var p = playerInMinecart(cart);
if (p == null) {
killMinecart(cart);
return null;
} else {
var velocity = cart.getVelocity().clone();
cart.setVelocity(new Vector(0, 0, 0));
var puuid = p.getUniqueId();
stopLocations.put(puuid, cart.getLocation());
var task = Timers.schedule(20L, () -> {
if (!cart.isDead() && cart.isValid()) {
cart.setVelocity(velocity.normalize().multiply(0.4));
}
});
// TODO also need to clear the stop when the player chooses to go forwards on their own
Timers.schedule(30L, () -> {
stopLocations.remove(puuid); // Could clear the wrong thing?
});
return task;
}
}
private static Set<UUID> cartsAtStops = new HashSet();
private static Map<UUID, BukkitTask> tasks = new HashMap();
// TODO Being block-based has the fundamental problem that travelling too fast can cause you to skip over a block entirely
@EventHandler
public void onMove(VehicleMoveEvent e) {
if (e.getVehicle() instanceof Minecart cart && cart.getPassengers().get(0) instanceof Player p) {
var uuid = cart.getUniqueId();
if (isOnStopperRail(cart)) {
if (cartsAtStops.contains(uuid)) {
// If you're moving forward, resume velocity.
// TODO Extract this and the task above yeag
if (cart.getVelocity().lengthSquared() > 0) {
tasks.get(uuid).cancel();
// Only runninng after the timer has been set...
if (!cart.isDead() && cart.isValid()) {
cart.setVelocity(cart.getVelocity().clone().normalize().multiply(0.4));
stopLocations.remove(p.getUniqueId());
}
}
} else {
var task = onEnterStop(cart);
if (task != null) {
tasks.put(uuid, task);
}
cartsAtStops.add(uuid);
}
} else {
cartsAtStops.remove(uuid);
tasks.remove(uuid);
}
}
}
/// Spawns a Minecart properly on a block and returns the new Minecart entity
public Minecart spawnMinecartOnBlock(Block b) {
var spawnLocation = b.getLocation().add(0.5, 0.1, 0.5);
var cart = (Minecart) b.getWorld().spawn(spawnLocation, Minecart.class);
return cart;
}
public boolean isRightClickingOnPoweredRail(PlayerInteractEvent e) {
Block b = e.getClickedBlock();
return b != null && e.getAction().isRightClick() && b.getType() == Material.POWERED_RAIL;
}
@EventHandler
public void onRailClick(PlayerInteractEvent e) {
if (isRightClickingOnPoweredRail(e)) {
var p = e.getPlayer();
if (isHoldingFist(p)) {
e.setCancelled(true);
var b = e.getClickedBlock();
spawnMinecartOnBlock(b).addPassenger(p);
}
}
}
}