| 1 | import org.bukkit.Location;
|
| 2 | import org.bukkit.Material;
|
| 3 | import org.bukkit.entity.ArmorStand;
|
| 4 | import org.bukkit.entity.Player;
|
| 5 | import org.bukkit.inventory.ItemStack;
|
| 6 | import org.bukkit.util.Vector;
|
| 7 | import org.bukkit.block.Block;
|
| 8 | import org.bukkit.scheduler.BukkitRunnable;
|
| 9 | import org.bukkit.util.EulerAngle;
|
| 10 | import org.bukkit.configuration.file.FileConfiguration;
|
| 11 | import org.bukkit.inventory.PlayerInventory;
|
| 12 | import java.util.Arrays;
|
| 13 | import java.util.HashSet;
|
| 14 | import java.util.Set;
|
| 15 | import org.bukkit.entity.Entity;
|
| 16 | import org.bukkit.event.entity.EntityDamageEvent;
|
| 17 |
|
| 18 |
|
| 19 | public class DrillVehicle {
|
| 20 | private final Player owner;
|
| 21 | private final ArmorStand mainStand;
|
| 22 | private final Set<ArmorStand> modelParts = new HashSet<>();
|
| 23 | private double fuel = 100.0;
|
| 24 | private boolean isDrilling = false;
|
| 25 | private boolean isMoving = false;
|
| 26 | private ArmorStand drillHead;
|
| 27 | private boolean fuelMessageSent = false;
|
| 28 | private float currentYaw = 0;
|
| 29 | private static final double MOUNT_HEIGHT_OFFSET = -0.2;
|
| 30 |
|
| 31 |
|
| 32 | public boolean isPartOfDrill(ArmorStand stand) {
|
| 33 | return modelParts.contains(stand);
|
| 34 | }
|
| 35 |
|
| 36 |
|
| 37 | private static final Set<Material> UNBREAKABLE_BLOCKS = new HashSet<>(Arrays.asList(
|
| 38 | Material.BEDROCK,
|
| 39 | Material.OBSIDIAN,
|
| 40 | Material.BARRIER,
|
| 41 | Material.END_PORTAL_FRAME,
|
| 42 | Material.END_PORTAL,
|
| 43 | Material.NETHER_PORTAL
|
| 44 | ));
|
| 45 |
|
| 46 |
|
| 47 | public DrillVehicle(Player owner) {
|
| 48 | this.owner = owner;
|
| 49 | Location spawnLoc = owner.getLocation();
|
| 50 | spawnLoc.setY(spawnLoc.getY() + 0.1);
|
| 51 | spawnLoc.setYaw(0);
|
| 52 | currentYaw = 0;
|
| 53 |
|
| 54 | mainStand = spawnLoc.getWorld().spawn(spawnLoc, ArmorStand.class);
|
| 55 | mainStand.setVisible(false);
|
| 56 | mainStand.setGravity(false);
|
| 57 | mainStand.setInvulnerable(true);
|
| 58 | mainStand.setBasePlate(false);
|
| 59 | mainStand.setArms(false);
|
| 60 | mainStand.setSmall(false);
|
| 61 | mainStand.setMarker(true);
|
| 62 | mainStand.setCanPickupItems(false);
|
| 63 | mainStand.setRemoveWhenFarAway(false);
|
| 64 |
|
| 65 | createDrillModel();
|
| 66 | startHandlers();
|
| 67 | }
|
| 68 |
|
| 69 |
|
| 70 | private void createDrillModel() {
|
| 71 | Location loc = mainStand.getLocation();
|
| 72 |
|
| 73 | // Base platform (2x3 flat platform)
|
| 74 | double spacing = 0.4;
|
| 75 | for (int x = -1; x <= 1; x++) {
|
| 76 | for (int z = -1; z <= 1; z++) {
|
| 77 | createBodyPart(loc, x * spacing, 0, z * spacing, Material.IRON_BLOCK);
|
| 78 | }
|
| 79 | }
|
| 80 |
|
| 81 |
|
| 82 | // Walls (1 block high, on the edges) - now 0.75x height
|
| 83 | double wallHeight = 1.2;
|
| 84 |
|
| 85 | // Left and right walls with glass
|
| 86 | for (double y = 0.4; y <= wallHeight; y += 0.4) {
|
| 87 | // Left wall
|
| 88 | createBodyPart(loc, -spacing, y, -spacing, Material.BLACK_CONCRETE);
|
| 89 | createBodyPart(loc, -spacing, y, 0, Material.GLASS);
|
| 90 | createBodyPart(loc, -spacing, y, spacing, Material.BLACK_CONCRETE);
|
| 91 |
|
| 92 | // Right wall
|
| 93 | createBodyPart(loc, spacing, y, -spacing, Material.BLACK_CONCRETE);
|
| 94 | createBodyPart(loc, spacing, y, 0, Material.GLASS);
|
| 95 | createBodyPart(loc, spacing, y, spacing, Material.BLACK_CONCRETE);
|
| 96 |
|
| 97 | // Back wall (between concrete)
|
| 98 | createBodyPart(loc, 0, y, -spacing, Material.GLASS);
|
| 99 | }
|
| 100 |
|
| 101 |
|
| 102 | // 3x3 Gold connection platform (without glass on top)
|
| 103 | for (double y = 0.4; y <= 0.8; y += 0.4) {
|
| 104 | for (int x = -1; x <= 1; x++) {
|
| 105 | createBodyPart(loc, x * 0.4, y, 0.8, Material.GOLD_BLOCK);
|
| 106 | }
|
| 107 | }
|
| 108 |
|
| 109 |
|
| 110 | // Front drill assembly
|
| 111 | createBodyPart(loc, -0.2, 0.5, 1.2, Material.DIAMOND_BLOCK);
|
| 112 | createBodyPart(loc, 0.2, 0.5, 1.2, Material.DIAMOND_BLOCK);
|
| 113 | createBodyPart(loc, 0, 0.9, 1.2, Material.DIAMOND_BLOCK);
|
| 114 |
|
| 115 | drillHead = createModelPart(loc.clone().add(0, 0.7, 1.6));
|
| 116 | drillHead.getEquipment().setHelmet(new ItemStack(Material.DIAMOND_BLOCK));
|
| 117 | drillHead.setSmall(true);
|
| 118 | modelParts.add(drillHead);
|
| 119 |
|
| 120 | // Make all parts non-pickupable
|
| 121 | for (ArmorStand part : modelParts) {
|
| 122 | part.setCanPickupItems(false);
|
| 123 | }
|
| 124 | }
|
| 125 | // This method handles the vehicle controls for WASD movement
|
| 126 | public void handleVehicleControls(float forward, float sideways, boolean jump) {
|
| 127 | if (owner.getVehicle() != mainStand || fuel <= 0) {
|
| 128 | if (!fuelMessageSent && fuel <= 0) {
|
| 129 | owner.sendMessage("§cOUT OF FUEL");
|
| 130 | fuelMessageSent = true;
|
| 131 | }
|
| 132 | return;
|
| 133 | }
|
| 134 |
|
| 135 |
|
| 136 | // Make sure player is still mounted
|
| 137 | if (owner.getVehicle() != mainStand) {
|
| 138 | mount(); // Try to remount if somehow dismounted
|
| 139 | return;
|
| 140 | }
|
| 141 |
|
| 142 |
|
| 143 | double baseSpeed = DrillPlugin.getInstance().getConfig().getDouble("movement-speed", 0.3);
|
| 144 |
|
| 145 | if (forward != 0 || sideways != 0) {
|
| 146 | // Calculate movement vectors
|
| 147 | double yaw = Math.toRadians(currentYaw);
|
| 148 | Vector direction = new Vector(-Math.sin(yaw), 0, Math.cos(yaw));
|
| 149 | Vector right = new Vector(-direction.getZ(), 0, direction.getX());
|
| 150 |
|
| 151 | // Combine movements
|
| 152 | Vector movement = direction.multiply(forward).add(right.multiply(-sideways));
|
| 153 | movement.normalize().multiply(baseSpeed);
|
| 154 |
|
| 155 | // Apply movement
|
| 156 | Location newLoc = mainStand.getLocation().add(movement);
|
| 157 | if (newLoc.getBlock().getType() == Material.AIR) {
|
| 158 | newLoc.setYaw(currentYaw);
|
| 159 | mainStand.teleport(newLoc);
|
| 160 |
|
| 161 | // Keep player in sync
|
| 162 | Location playerLoc = newLoc.clone();
|
| 163 | playerLoc.setY(newLoc.getY() + MOUNT_HEIGHT_OFFSET);
|
| 164 | playerLoc.setYaw(currentYaw);
|
| 165 | owner.teleport(playerLoc);
|
| 166 |
|
| 167 | updateModelParts();
|
| 168 | consumeFuel(0.1);
|
| 169 | }
|
| 170 | }
|
| 171 |
|
| 172 | if (jump) {
|
| 173 | currentYaw = (currentYaw + 180) % 360;
|
| 174 | Location loc = mainStand.getLocation();
|
| 175 | loc.setYaw(currentYaw);
|
| 176 | mainStand.teleport(loc);
|
| 177 |
|
| 178 | Location playerLoc = owner.getLocation();
|
| 179 | playerLoc.setYaw(currentYaw);
|
| 180 | owner.teleport(playerLoc);
|
| 181 |
|
| 182 | updateModelParts();
|
| 183 | }
|
| 184 | }
|
| 185 | private void startHandlers() {
|
| 186 | new BukkitRunnable() {
|
| 187 | u/Override
|
| 188 | public void run() {
|
| 189 | if (!mainStand.isValid() || !owner.isOnline()) {
|
| 190 | remove();
|
| 191 | cancel();
|
| 192 | return;
|
| 193 | }
|
| 194 |
|
| 195 | if (isDrilling) {
|
| 196 | handleDrilling();
|
| 197 | }
|
| 198 | }
|
| 199 | }.runTaskTimer(DrillPlugin.getInstance(), 1L, 1L);
|
| 200 | }
|
| 201 | public void toggleMovement() {
|
| 202 | isMoving = !isMoving;
|
| 203 | String message = isMoving ?
|
| 204 | DrillPlugin.getInstance().getConfig().getString("messages.movement-activated", "§aDrill movement activated!") :
|
| 205 | DrillPlugin.getInstance().getConfig().getString("messages.movement-deactivated", "§cDrill movement deactivated!");
|
| 206 | owner.sendMessage(message);
|
| 207 | }
|
| 208 |
|
| 209 |
|
| 210 | private void handleMovement() {
|
| 211 | if (fuel <= 0) {
|
| 212 | if (!fuelMessageSent) {
|
| 213 | owner.sendMessage("§cOUT OF FUEL");
|
| 214 | fuelMessageSent = true;
|
| 215 | }
|
| 216 | isMoving = false;
|
| 217 | return;
|
| 218 | }
|
| 219 |
|
| 220 |
|
| 221 | Vector direction = owner.getLocation().getDirection().setY(0).normalize();
|
| 222 | double moveSpeed = DrillPlugin.getInstance().getConfig().getDouble("movement-speed", 0.3);
|
| 223 | Location currentLoc = mainStand.getLocation();
|
| 224 | Location newLoc = currentLoc.clone().add(direction.multiply(moveSpeed));
|
| 225 |
|
| 226 | if (newLoc.getBlock().getType().equals(Material.AIR)) {
|
| 227 | // Keep current yaw when moving
|
| 228 | newLoc.setYaw(currentYaw);
|
| 229 | mainStand.teleport(newLoc);
|
| 230 | updateModelParts();
|
| 231 | consumeFuel(0.1);
|
| 232 | }
|
| 233 | }
|
| 234 |
|
| 235 |
|
| 236 |
|
| 237 | private void handleDrilling() {
|
| 238 | if (fuel <= 0) {
|
| 239 | isDrilling = false;
|
| 240 | return;
|
| 241 | }
|
| 242 |
|
| 243 | if (drillHead != null) {
|
| 244 | EulerAngle rotation = drillHead.getHeadPose();
|
| 245 | drillHead.setHeadPose(rotation.add(0, 0.5, 0));
|
| 246 | }
|
| 247 |
|
| 248 | Location loc = mainStand.getLocation();
|
| 249 | Vector direction = getForwardVector();
|
| 250 |
|
| 251 | for (int i = 1; i <= 3; i++) {
|
| 252 | Location blockLoc = loc.clone().add(direction.clone().multiply(i + 1));
|
| 253 | for (int y = -1; y <= 1; y++) {
|
| 254 | for (int x = -1; x <= 1; x++) {
|
| 255 | Vector right = new Vector(direction.getZ(), 0, -direction.getX());
|
| 256 | Location miningLoc = blockLoc.clone().add(right.multiply(x)).add(0, y, 0);
|
| 257 | Block block = miningLoc.getBlock();
|
| 258 | if (block.getType() != Material.AIR && !UNBREAKABLE_BLOCKS.contains(block.getType())) {
|
| 259 | block.breakNaturally();
|
| 260 | }
|
| 261 | }
|
| 262 | }
|
| 263 | }
|
| 264 |
|
| 265 | consumeFuel(0.5);
|
| 266 | }
|
| 267 |
|
| 268 |
|
| 269 | public void mount() {
|
| 270 | if (owner != null && mainStand != null) {
|
| 271 | mainStand.addPassenger(owner);
|
| 272 |
|
| 273 | new BukkitRunnable() {
|
| 274 | u/Override
|
| 275 | public void run() {
|
| 276 | Location mountLoc = mainStand.getLocation().clone();
|
| 277 | mountLoc.setY(mainStand.getLocation().getY() + MOUNT_HEIGHT_OFFSET);
|
| 278 | mountLoc.setYaw(currentYaw);
|
| 279 | mountLoc.setPitch(0);
|
| 280 | owner.teleport(mountLoc);
|
| 281 |
|
| 282 | new BukkitRunnable() {
|
| 283 | u/Override
|
| 284 | public void run() {
|
| 285 | mainStand.addPassenger(owner);
|
| 286 | Location playerLoc = owner.getLocation();
|
| 287 | playerLoc.setYaw(currentYaw);
|
| 288 | owner.teleport(playerLoc);
|
| 289 | }
|
| 290 | }.runTaskLater(DrillPlugin.getInstance(), 2L);
|
| 291 | }
|
| 292 | }.runTaskLater(DrillPlugin.getInstance(), 1L);
|
| 293 | } else {
|
| 294 | if (owner != null) {
|
| 295 | owner.sendMessage("Failed to mount: mainStand is null");
|
| 296 | }
|
| 297 | }
|
| 298 | }
|
| 299 |
|
| 300 |
|
| 301 |
|
| 302 | private Vector getForwardVector() {
|
| 303 | double rad = Math.toRadians(currentYaw);
|
| 304 | return new Vector(-Math.sin(rad), 0, Math.cos(rad)).normalize();
|
| 305 | }
|
| 306 |
|
| 307 |
|
| 308 | private void consumeFuel(double amount) {
|
| 309 | FileConfiguration config = DrillPlugin.getInstance().getConfig();
|
| 310 | double maxFuelCapacity = config.getDouble("fuel.max-fuel-capacity", 500);
|
| 311 |
|
| 312 |
|
| 313 | if (fuel > 0) {
|
| 314 | fuel -= amount;
|
| 315 | if (fuel < 0) fuel = 0;
|
| 316 | fuelMessageSent = false;
|
| 317 | return;
|
| 318 | }
|
| 319 |
|
| 320 |
|
| 321 | PlayerInventory inventory = owner.getInventory();
|
| 322 | double fuelPerCoal = config.getDouble("fuel.coal-fuel-amount", 50);
|
| 323 |
|
| 324 |
|
| 325 | ItemStack coal = null;
|
| 326 | for (ItemStack item : inventory.getContents()) {
|
| 327 | if (item != null && item.getType() == Material.COAL) {
|
| 328 | coal = item;
|
| 329 | break;
|
| 330 | }
|
| 331 | }
|
| 332 |
|
| 333 |
|
| 334 | if (coal != null) {
|
| 335 | double addedFuel = Math.min(fuelPerCoal, maxFuelCapacity);
|
| 336 | fuel += addedFuel;
|
| 337 | coal.setAmount(coal.getAmount() - 1);
|
| 338 |
|
| 339 | String message = config.getString("messages.fuel-added", "§aAdded fuel! Current fuel level: §6%fuel%")
|
| 340 | .replace("%fuel%", String.format("%.1f", fuel));
|
| 341 | owner.sendMessage(message);
|
| 342 | fuelMessageSent = false;
|
| 343 | }
|
| 344 | }
|
| 345 |
|
| 346 |
|
| 347 | public void addFuel(ItemStack item) {
|
| 348 | if (item == null || item.getType() != Material.COAL) {
|
| 349 | return;
|
| 350 | }
|
| 351 |
|
| 352 |
|
| 353 | FileConfiguration config = DrillPlugin.getInstance().getConfig();
|
| 354 | double maxFuelCapacity = config.getDouble("fuel.max-fuel-capacity", 500);
|
| 355 | double fuelPerCoal = config.getDouble("fuel.coal-fuel-amount", 50);
|
| 356 |
|
| 357 |
|
| 358 | double addedFuel = Math.min(fuelPerCoal, maxFuelCapacity - fuel);
|
| 359 |
|
| 360 | if (addedFuel > 0) {
|
| 361 | fuel += addedFuel;
|
| 362 | item.setAmount(item.getAmount() - 1);
|
| 363 |
|
| 364 | String message = config.getString("messages.fuel-added", "§aAdded fuel! Current fuel level: §6%fuel%")
|
| 365 | .replace("%fuel%", String.format("%.1f", fuel));
|
| 366 | owner.sendMessage(message);
|
| 367 | fuelMessageSent = false;
|
| 368 | }
|
| 369 | }
|
| 370 |
|
| 371 |
|
| 372 | public void toggleDrilling() {
|
| 373 | isDrilling = !isDrilling;
|
| 374 | String message = isDrilling ?
|
| 375 | "§aDrill activated!" :
|
| 376 | "§cDrill deactivated!";
|
| 377 | owner.sendMessage(message);
|
| 378 | }
|
| 379 |
|
| 380 |
|
| 381 | public void dismount() {
|
| 382 | if (owner.getVehicle() == mainStand) {
|
| 383 | Location dismountLoc = mainStand.getLocation().clone().add(0, 1.5, 0);
|
| 384 | owner.leaveVehicle();
|
| 385 | owner.teleport(dismountLoc);
|
| 386 | }
|
| 387 | }
|
| 388 |
|
| 389 |
|
| 390 | public void rotate() {
|
| 391 | if (owner.getVehicle() == mainStand) {
|
| 392 | currentYaw = (currentYaw + 90) % 360;
|
| 393 | Location loc = mainStand.getLocation();
|
| 394 | loc.setYaw(currentYaw);
|
| 395 | mainStand.teleport(loc);
|
| 396 |
|
| 397 | Location playerLoc = owner.getLocation();
|
| 398 | playerLoc.setYaw(currentYaw);
|
| 399 | owner.teleport(playerLoc);
|
| 400 |
|
| 401 | updateModelParts();
|
| 402 | }
|
| 403 | }
|
| 404 |
|
| 405 |
|
| 406 | private void updateModelParts() {
|
| 407 | Location baseLoc = mainStand.getLocation();
|
| 408 | for (ArmorStand part : modelParts) {
|
| 409 | Location partLoc = baseLoc.clone();
|
| 410 | Vector offset = part.getLocation().subtract(baseLoc).toVector();
|
| 411 | offset = rotateVector(offset, currentYaw);
|
| 412 | partLoc.add(offset);
|
| 413 | partLoc.setYaw(currentYaw);
|
| 414 | part.teleport(partLoc);
|
| 415 | }
|
| 416 | }
|
| 417 |
|
| 418 |
|
| 419 | private Vector rotateVector(Vector vector, float yaw) {
|
| 420 | double rad = Math.toRadians(yaw);
|
| 421 | double x = vector.getX() * Math.cos(rad) - vector.getZ() * Math.sin(rad);
|
| 422 | double z = vector.getX() * Math.sin(rad) + vector.getZ() * Math.cos(rad);
|
| 423 | return new Vector(x, vector.getY(), z);
|
| 424 | }
|
| 425 |
|
| 426 |
|
| 427 | public void remove() {
|
| 428 | mainStand.remove();
|
| 429 | for (ArmorStand part : modelParts) {
|
| 430 | part.remove();
|
| 431 | }
|
| 432 | modelParts.clear();
|
| 433 | }
|
| 434 |
|
| 435 |
|
| 436 | public ArmorStand getMainStand() {
|
| 437 | return mainStand;
|
| 438 | }
|
| 439 |
|
| 440 |
|
| 441 | public Player getOwner() {
|
| 442 | return owner;
|
| 443 | }
|
| 444 |
|
| 445 |
|
| 446 | private ArmorStand createModelPart(Location loc) {
|
| 447 | ArmorStand stand = loc.getWorld().spawn(loc, ArmorStand.class);
|
| 448 | stand.setVisible(false);
|
| 449 | stand.setGravity(false);
|
| 450 | stand.setInvulnerable(true);
|
| 451 | stand.setSmall(true);
|
| 452 | modelParts.add(stand);
|
| 453 | return stand;
|
| 454 | }
|
| 455 |
|
| 456 |
|
| 457 | private void createBodyPart(Location loc, double x, double y, double z, Material material) {
|
| 458 | ArmorStand part = createModelPart(loc.clone().add(x, y, z));
|
| 459 | part.getEquipment().setHelmet(new ItemStack(material));
|
| 460 | }
|
| 461 | }
|