Minecarts.java

AKL City ยท akl-city.git

Branches > main > 09ec71f > 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);
      }
    }
  }
}