import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.bukkit.block.Block;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.EulerAngle;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.inventory.PlayerInventory;
import java.util.HashSet;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.EntityDamageEvent;
public class DrillVehicle {
private final Player owner;
private final ArmorStand mainStand;
private final Set<ArmorStand> modelParts = new HashSet<>();
private double fuel = 100.0;
private boolean isDrilling = false;
private boolean isMoving = false;
private ArmorStand drillHead;
private boolean fuelMessageSent = false;
private float currentYaw = 0;
private static final double MOUNT_HEIGHT_OFFSET = -0.2;
public boolean isPartOfDrill(ArmorStand stand) {
return modelParts.contains(stand);
private static final Set<Material> UNBREAKABLE_BLOCKS = new HashSet<>(Arrays.asList(
Material.END_PORTAL_FRAME,
public DrillVehicle(Player owner) {
Location spawnLoc = owner.getLocation();
spawnLoc.setY(spawnLoc.getY() + 0.1);
mainStand = spawnLoc.getWorld().spawn(spawnLoc, ArmorStand.class);
mainStand.setVisible(false);
mainStand.setGravity(false);
mainStand.setInvulnerable(true);
mainStand.setBasePlate(false);
mainStand.setArms(false);
mainStand.setSmall(false);
mainStand.setMarker(true);
mainStand.setCanPickupItems(false);
mainStand.setRemoveWhenFarAway(false);
private void createDrillModel() {
Location loc = mainStand.getLocation();
// Base platform (2x3 flat platform)
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
createBodyPart(loc, x * spacing, 0, z * spacing, Material.IRON_BLOCK);
// Walls (1 block high, on the edges) - now 0.75x height
// Left and right walls with glass
for (double y = 0.4; y <= wallHeight; y += 0.4) {
createBodyPart(loc, -spacing, y, -spacing, Material.BLACK_CONCRETE);
createBodyPart(loc, -spacing, y, 0, Material.GLASS);
createBodyPart(loc, -spacing, y, spacing, Material.BLACK_CONCRETE);
createBodyPart(loc, spacing, y, -spacing, Material.BLACK_CONCRETE);
createBodyPart(loc, spacing, y, 0, Material.GLASS);
createBodyPart(loc, spacing, y, spacing, Material.BLACK_CONCRETE);
// Back wall (between concrete)
createBodyPart(loc, 0, y, -spacing, Material.GLASS);
// 3x3 Gold connection platform (without glass on top)
for (double y = 0.4; y <= 0.8; y += 0.4) {
for (int x = -1; x <= 1; x++) {
createBodyPart(loc, x * 0.4, y, 0.8, Material.GOLD_BLOCK);
createBodyPart(loc, -0.2, 0.5, 1.2, Material.DIAMOND_BLOCK);
createBodyPart(loc, 0.2, 0.5, 1.2, Material.DIAMOND_BLOCK);
createBodyPart(loc, 0, 0.9, 1.2, Material.DIAMOND_BLOCK);
drillHead = createModelPart(loc.clone().add(0, 0.7, 1.6));
drillHead.getEquipment().setHelmet(new ItemStack(Material.DIAMOND_BLOCK));
drillHead.setSmall(true);
modelParts.add(drillHead);
// Make all parts non-pickupable
for (ArmorStand part : modelParts) {
part.setCanPickupItems(false);
// This method handles the vehicle controls for WASD movement
public void handleVehicleControls(float forward, float sideways, boolean jump) {
if (owner.getVehicle() != mainStand || fuel <= 0) {
if (!fuelMessageSent && fuel <= 0) {
owner.sendMessage("§cOUT OF FUEL");
// Make sure player is still mounted
if (owner.getVehicle() != mainStand) {
mount(); // Try to remount if somehow dismounted
double baseSpeed = DrillPlugin.getInstance().getConfig().getDouble("movement-speed", 0.3);
if (forward != 0 || sideways != 0) {
// Calculate movement vectors
double yaw = Math.toRadians(currentYaw);
Vector direction = new Vector(-Math.sin(yaw), 0, Math.cos(yaw));
Vector right = new Vector(-direction.getZ(), 0, direction.getX());
Vector movement = direction.multiply(forward).add(right.multiply(-sideways));
movement.normalize().multiply(baseSpeed);
Location newLoc = mainStand.getLocation().add(movement);
if (newLoc.getBlock().getType() == Material.AIR) {
newLoc.setYaw(currentYaw);
mainStand.teleport(newLoc);
Location playerLoc = newLoc.clone();
playerLoc.setY(newLoc.getY() + MOUNT_HEIGHT_OFFSET);
playerLoc.setYaw(currentYaw);
owner.teleport(playerLoc);
currentYaw = (currentYaw + 180) % 360;
Location loc = mainStand.getLocation();
Location playerLoc = owner.getLocation();
playerLoc.setYaw(currentYaw);
owner.teleport(playerLoc);
private void startHandlers() {
if (!mainStand.isValid() || !owner.isOnline()) {
}.runTaskTimer(DrillPlugin.getInstance(), 1L, 1L);
public void toggleMovement() {
String message = isMoving ?
DrillPlugin.getInstance().getConfig().getString("messages.movement-activated", "§aDrill movement activated!") :
DrillPlugin.getInstance().getConfig().getString("messages.movement-deactivated", "§cDrill movement deactivated!");
owner.sendMessage(message);
private void handleMovement() {
owner.sendMessage("§cOUT OF FUEL");
Vector direction = owner.getLocation().getDirection().setY(0).normalize();
double moveSpeed = DrillPlugin.getInstance().getConfig().getDouble("movement-speed", 0.3);
Location currentLoc = mainStand.getLocation();
Location newLoc = currentLoc.clone().add(direction.multiply(moveSpeed));
if (newLoc.getBlock().getType().equals(Material.AIR)) {
// Keep current yaw when moving
newLoc.setYaw(currentYaw);
mainStand.teleport(newLoc);
private void handleDrilling() {
EulerAngle rotation = drillHead.getHeadPose();
drillHead.setHeadPose(rotation.add(0, 0.5, 0));
Location loc = mainStand.getLocation();
Vector direction = getForwardVector();
for (int i = 1; i <= 3; i++) {
Location blockLoc = loc.clone().add(direction.clone().multiply(i + 1));
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
Vector right = new Vector(direction.getZ(), 0, -direction.getX());
Location miningLoc = blockLoc.clone().add(right.multiply(x)).add(0, y, 0);
Block block = miningLoc.getBlock();
if (block.getType() != Material.AIR && !UNBREAKABLE_BLOCKS.contains(block.getType())) {
if (owner != null && mainStand != null) {
mainStand.addPassenger(owner);
Location mountLoc = mainStand.getLocation().clone();
mountLoc.setY(mainStand.getLocation().getY() + MOUNT_HEIGHT_OFFSET);
mountLoc.setYaw(currentYaw);
owner.teleport(mountLoc);
mainStand.addPassenger(owner);
Location playerLoc = owner.getLocation();
playerLoc.setYaw(currentYaw);
owner.teleport(playerLoc);
}.runTaskLater(DrillPlugin.getInstance(), 2L);
}.runTaskLater(DrillPlugin.getInstance(), 1L);
owner.sendMessage("Failed to mount: mainStand is null");
private Vector getForwardVector() {
double rad = Math.toRadians(currentYaw);
return new Vector(-Math.sin(rad), 0, Math.cos(rad)).normalize();
private void consumeFuel(double amount) {
FileConfiguration config = DrillPlugin.getInstance().getConfig();
double maxFuelCapacity = config.getDouble("fuel.max-fuel-capacity", 500);
PlayerInventory inventory = owner.getInventory();
double fuelPerCoal = config.getDouble("fuel.coal-fuel-amount", 50);
for (ItemStack item : inventory.getContents()) {
if (item != null && item.getType() == Material.COAL) {
double addedFuel = Math.min(fuelPerCoal, maxFuelCapacity);
coal.setAmount(coal.getAmount() - 1);
String message = config.getString("messages.fuel-added", "§aAdded fuel! Current fuel level: §6%fuel%")
.replace("%fuel%", String.format("%.1f", fuel));
owner.sendMessage(message);
public void addFuel(ItemStack item) {
if (item == null || item.getType() != Material.COAL) {
FileConfiguration config = DrillPlugin.getInstance().getConfig();
double maxFuelCapacity = config.getDouble("fuel.max-fuel-capacity", 500);
double fuelPerCoal = config.getDouble("fuel.coal-fuel-amount", 50);
double addedFuel = Math.min(fuelPerCoal, maxFuelCapacity - fuel);
item.setAmount(item.getAmount() - 1);
String message = config.getString("messages.fuel-added", "§aAdded fuel! Current fuel level: §6%fuel%")
.replace("%fuel%", String.format("%.1f", fuel));
owner.sendMessage(message);
public void toggleDrilling() {
isDrilling = !isDrilling;
String message = isDrilling ?
owner.sendMessage(message);
if (owner.getVehicle() == mainStand) {
Location dismountLoc = mainStand.getLocation().clone().add(0, 1.5, 0);
owner.teleport(dismountLoc);
if (owner.getVehicle() == mainStand) {
currentYaw = (currentYaw + 90) % 360;
Location loc = mainStand.getLocation();
Location playerLoc = owner.getLocation();
playerLoc.setYaw(currentYaw);
owner.teleport(playerLoc);
private void updateModelParts() {
Location baseLoc = mainStand.getLocation();
for (ArmorStand part : modelParts) {
Location partLoc = baseLoc.clone();
Vector offset = part.getLocation().subtract(baseLoc).toVector();
offset = rotateVector(offset, currentYaw);
partLoc.setYaw(currentYaw);
private Vector rotateVector(Vector vector, float yaw) {
double rad = Math.toRadians(yaw);
double x = vector.getX() * Math.cos(rad) - vector.getZ() * Math.sin(rad);
double z = vector.getX() * Math.sin(rad) + vector.getZ() * Math.cos(rad);
return new Vector(x, vector.getY(), z);
for (ArmorStand part : modelParts) {
public ArmorStand getMainStand() {
public Player getOwner() {
private ArmorStand createModelPart(Location loc) {
ArmorStand stand = loc.getWorld().spawn(loc, ArmorStand.class);
stand.setInvulnerable(true);
private void createBodyPart(Location loc, double x, double y, double z, Material material) {
ArmorStand part = createModelPart(loc.clone().add(x, y, z));
part.getEquipment().setHelmet(new ItemStack(material));