/* * This file is a part of the SteamWar software. * * Copyright (C) 2022 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package de.steamwar.bausystem.utils; import de.steamwar.entity.REntity; import de.steamwar.entity.RFallingBlockEntity; import lombok.Data; import lombok.experimental.UtilityClass; import org.bukkit.FluidCollisionMode; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; import java.util.List; @UtilityClass public class RayTraceUtils { public static RayTraceResult trace(Player player, Location to, List entityList) { if (player.getGameMode() == GameMode.SPECTATOR) { return null; } Location startPos = to.clone().add(0.0, player.getEyeHeight(), 0.0); Vector direction = to.getDirection(); RayTraceResult blocks = player.getWorld().rayTraceBlocks(startPos, direction, 10.0, FluidCollisionMode.NEVER, true); Entity nearestHitEntity = null; RayTraceResult nearestHitResult = null; double nearestDistanceSq = Double.MAX_VALUE; for (Entity entity : entityList) { BoundingBox boundingBox = entity.getBoundingBox(); RayTraceResult hitResult = boundingBox.rayTrace(startPos.toVector(), direction, 10.0); if (hitResult != null) { double distanceSq = startPos.toVector().distanceSquared(hitResult.getHitPosition()); if (distanceSq < nearestDistanceSq) { nearestHitEntity = entity; nearestHitResult = hitResult; nearestDistanceSq = distanceSq; } } } RayTraceResult entities = nearestHitEntity == null ? null : new RayTraceResult(nearestHitResult.getHitPosition(), nearestHitEntity, nearestHitResult.getHitBlockFace()); if (blocks == null) { return entities; } else if (entities == null) { return blocks; } else { Vector startVec = startPos.toVector(); double blockHitDistance = startVec.distance(blocks.getHitPosition()); double entityHitDistanceSquared = startVec.distanceSquared(entities.getHitPosition()); return entityHitDistanceSquared < blockHitDistance * blockHitDistance ? entities : blocks; } } public static RRayTraceResult traceREntity(Player player, Location to, List entityList) { if (player.getGameMode() == GameMode.SPECTATOR) { return null; } Location startPos = to.clone().add(0.0, player.getEyeHeight(), 0.0); Vector direction = to.getDirection(); RayTraceResult blocks = player.getWorld().rayTraceBlocks(startPos, direction, 10.0, FluidCollisionMode.NEVER, true); REntity nearestHitEntity = null; RRayTraceResult nearestHitResult = null; double nearestDistanceSq = Double.MAX_VALUE; for (REntity entity: entityList) { if (!isOccluded(startPos.toVector(), direction, new Vector(entity.getX(), entity.getY() + 0.5, entity.getZ()))) continue; double distanceSq = new Vector(entity.getX(), entity.getY() + 0.5, entity.getZ()).distanceSquared(startPos.toVector()); if (distanceSq < nearestDistanceSq) { nearestHitEntity = entity; nearestHitResult = new RRayTraceResult(new Vector(entity.getX(), entity.getY() + 0.5, entity.getZ()), null, null, entity); nearestDistanceSq = distanceSq; } } RRayTraceResult entities = nearestHitEntity == null ? null : new RRayTraceResult(nearestHitResult.getHitPosition(), nearestHitResult.getHitBlock(), nearestHitResult.getHitBlockFace(), nearestHitEntity); if (blocks == null) { return entities; } else if (entities == null) { return RRayTraceResult.fromRayTraceResult(blocks); } else { Vector startVec = startPos.toVector(); double blockHitDistance = startVec.distance(blocks.getHitPosition()); double entityHitDistanceSquared = startVec.distanceSquared(entities.getHitPosition()); return entityHitDistanceSquared < blockHitDistance * blockHitDistance ? entities : RRayTraceResult.fromRayTraceResult(blocks); } } @Data public static class RRayTraceResult { private final Vector hitPosition; private final Block hitBlock; private final BlockFace hitBlockFace; private final REntity hitEntity; public static RRayTraceResult fromRayTraceResult(RayTraceResult rayTraceResult) { return new RRayTraceResult(rayTraceResult.getHitPosition(), rayTraceResult.getHitBlock(), rayTraceResult.getHitBlockFace(), null); } } public static boolean isOccluded(Vector a, Vector n, Vector b) { // a = Head pos, n = View direction (normalized), b = entity center // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation double abX = b.getX() - a.getX(); double abY = b.getY() - a.getY(); double abZ = b.getZ() - a.getZ(); double lambda = abX * n.getX() + abY * n.getY() + abZ * n.getZ(); double distX = abX - n.getX() * lambda; double distY = abY - n.getY() * lambda; double distZ = abZ - n.getZ() * lambda; return Math.abs(distX) < 0.5 && Math.abs(distY) < 0.5 && Math.abs(distZ) < 0.5; } }