Remembered that raytracing is in paper's api

This commit is contained in:
thetrouper
2025-05-15 07:29:43 -05:00
parent 7811ac9fd2
commit dbe0b8a4f4
11 changed files with 110 additions and 590 deletions

View File

@@ -3,6 +3,7 @@ package me.trouper.trimserver.server.commands;
import io.papermc.paper.registry.keys.TrimPatternKeys;
import me.trouper.trimserver.server.systems.AbilityBackend;
import me.trouper.trimserver.utils.SoundPlayer;
import me.trouper.trimserver.utils.Text;
import me.trouper.trimserver.utils.commands.Args;
import me.trouper.trimserver.utils.commands.CommandRegistry;
@@ -13,9 +14,7 @@ import me.trouper.trimserver.utils.misc.Randomizer;
import me.trouper.trimserver.utils.visual.CustomDisplayRaytracer;
import me.trouper.trimserver.utils.visual.DisplayUtils;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.*;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
@@ -31,6 +30,7 @@ import org.bukkit.inventory.meta.trim.TrimPattern;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
@CommandRegistry(value = "trims",permission = @Permission("trims.admin"),printStackTrace = true)
public class AdminCommand implements QuickCommand {
@@ -130,6 +130,22 @@ public class AdminCommand implements QuickCommand {
p.getEquipment().setLeggings(createTestArmor(new ItemStack(Material.NETHERITE_LEGGINGS),pattern,material));
p.getEquipment().setBoots(createTestArmor(new ItemStack(Material.NETHERITE_BOOTS),pattern,material));
for (int i = 0; i < 10; i++) {
double finalI = i;
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
DisplayUtils.ring(p.getLocation().clone().add(0, finalI / 5D,0),0.5,Color.RED,0.5F);
},i);
}
for (int i = 0; i < 10; i++) {
double finalI = i;
Bukkit.getScheduler().runTaskLater(main.getPlugin(),()->{
DisplayUtils.ring(p.getLocation().clone().add(0,2,0).subtract(0,finalI / 5D,0),0.5,Color.RED,0.5F);
},10 + i);
}
new SoundPlayer(p.getLocation(),Sound.BLOCK_BEACON_POWER_SELECT,1,2).play(p);
info(sender,"You now are wearing {0} with {1} {2} trim.", "Netherite",Text.formatEnum(validMaterial),Text.formatEnum(validPattern));
}

View File

@@ -58,7 +58,7 @@ public class EyeAbility extends AbstractAbility {
int maxTick = liv.getMaximumNoDamageTicks();
liv.setNoDamageTicks(0);
liv.setMaximumNoDamageTicks(0);
liv.damage(0.5,DamageSource.builder(DamageType.STING).withDamageLocation(owner.getLocation()).withDirectEntity(owner).build());
liv.damage(0.1,DamageSource.builder(DamageType.STING).withDamageLocation(owner.getLocation()).withDirectEntity(owner).build());
liv.setNoDamageTicks(tick);
liv.setMaximumNoDamageTicks(maxTick);

View File

@@ -72,6 +72,7 @@ public class FlowAbility extends AbstractAbility {
cancel();
return;
}
player.closeInventory();
Vector playerDirection = player.getLocation().clone().getDirection().multiply(0.5);
breeze.setVelocity(playerDirection);

View File

@@ -53,7 +53,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean amethystAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -61,7 +61,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean copperAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -69,7 +69,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean diamondAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -77,7 +77,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean emeraldAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -85,7 +85,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean goldAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -93,7 +93,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean ironAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -101,15 +101,15 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean lapisAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@MaterialInfo(name = "Netherite ", description = "Grants true invisibility for 10 seconds and makes your attacks stronger", cooldownTicks = 20 * 15)
@MaterialInfo(name = "Netherite ", description = "Grants true invisibility for 10 seconds and makes your attacks stronger", cooldownTicks = 20 * 20)
@Override
public boolean netheriteAbility(Player player) {
makeInvisible(player,10);
player.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,20*15,1,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.STRENGTH,20*6,1,true,false,true));
return true;
}
@@ -117,7 +117,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean quartzAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -125,7 +125,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean redstoneAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
@@ -133,7 +133,7 @@ public class HostAbility extends AbstractAbility {
@Override
public boolean resinAbility(Player player) {
makeInvisible(player,20);
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*15,0,true,false,true));
player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS,20*6,0,true,false,true));
return true;
}
}

View File

@@ -42,7 +42,7 @@ public class RaiserAbility extends AbstractAbility {
double healthThreshold = initialHealth / 2.0;
double targetY = initialLocation.getY() + 4.0;
int frequency = 2;
int duration = 20 * 30;
int duration = 20 * 7;
// --- Start Up Effects ---
target.getWorld().playSound(target.getLocation(), Sound.ENTITY_BAT_TAKEOFF, 0.8f, 1.0f);

View File

@@ -138,7 +138,7 @@ public class SentryAbility extends AbstractAbility {
int maxTick = liv.getMaximumNoDamageTicks();
liv.setNoDamageTicks(0);
liv.setMaximumNoDamageTicks(0);
liv.damage(1, DamageSource.builder(DamageType.ARROW).withDirectEntity(dummy).withDamageLocation(finalLoc).build());
liv.damage(8, DamageSource.builder(DamageType.ARROW).withDirectEntity(dummy).withDamageLocation(finalLoc).build());
liv.setNoDamageTicks(tick);
liv.setMaximumNoDamageTicks(maxTick);
hitSound.playWithin(20);
@@ -206,77 +206,77 @@ public class SentryAbility extends AbstractAbility {
@MaterialInfo(name = "Amethyst ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean amethystAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.AMETHYST_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.AMETHYST_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Copper ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean copperAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.COPPER_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.COPPER_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Diamond ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean diamondAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.DIAMOND_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.DIAMOND_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Emerald ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean emeraldAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.EMERALD_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.EMERALD_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Gold ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean goldAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.GOLD_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.GOLD_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Iron ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean ironAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.IRON_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.IRON_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Lapis ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean lapisAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.LAPIS_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.LAPIS_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Netherite Sentry", description = "Spawns a supercharged sentry which absolutely shreds the nearest player", cooldownTicks = 20 * 15)
@Override
public boolean netheriteAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.NETHERITE_BLOCK,Material.CRAFTER,100,60,1);
spawnSentry(player.getLocation(),player,Material.NETHERITE_BLOCK,Material.CRAFTER,50,60,2);
return true;
}
@MaterialInfo(name = "Quartz ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean quartzAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.QUARTZ_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.QUARTZ_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Redstone ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean redstoneAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.REDSTONE_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.REDSTONE_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
@MaterialInfo(name = "Resin ", description = "Spawns a sentry which shoots the nearest player", cooldownTicks = 20 * 10)
@Override
public boolean resinAbility(Player player) {
spawnSentry(player.getLocation(),player,Material.RESIN_BLOCK,Material.DISPENSER,50,60,2);
spawnSentry(player.getLocation(),player,Material.RESIN_BLOCK,Material.DISPENSER,25,60,5);
return true;
}
}

View File

@@ -54,7 +54,7 @@ public class SilenceAbility extends AbstractAbility {
Optional<Player> target = TargetingUtils.getClosestPlayer(point.getLoc(),1,entity -> !entity.equals(player) && !entity.isDead() && !main.man().trustBackend.trusts(player,entity) && !shaper.activeShellTasks.containsKey(entity.getUniqueId()));
target.ifPresent(value -> PlayerUtils.dealTrueDamage(value, DamageSource.builder(DamageType.SONIC_BOOM).withDirectEntity(player).build(), 10));
Verbose.send("Traced warden beam:");
return target.isPresent() || !point.getBlock().isPassable();
return target.isPresent();
},(point,blockHit) -> {
Verbose.send("Block was hit at %s. Return Value !Passable: %s",blockHit.getLocation(),!blockHit.isPassable());
return !blockHit.isPassable();

View File

@@ -1,6 +1,7 @@
package me.trouper.trimserver.utils.visual;
import me.trouper.trimserver.utils.Verbose;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@@ -10,9 +11,12 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
import org.bukkit.util.VoxelShape;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
@@ -160,79 +164,33 @@ public class CustomDisplayRaytracer {
return traceDelayed(plugin, start, direction, distance, 0.5, tickDelay,1, hitCondition);
}
public static Point traceWithReflection(Location start, Vector direction, double distance, double interval,
int maxReflections, Predicate<Point> hitCondition,
BiPredicate<Point,Block> blockReflectCondition,
BiPredicate<Point,Entity> entityReflectCondition) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (distance <= 0) throw new IllegalArgumentException("distance cannot be zero or negative!");
if (maxReflections < 0) throw new IllegalArgumentException("maxReflections cannot be negative!");
private static BlockFace traceBlockFace(Location startLocation, Vector direction, double maxDistance) {
Predicate<Block> blockPredicate = block -> true;
Predicate<Entity> entityPredicate = entity -> false;
Vector normalizedDir = direction.clone().normalize();
Location currentStart = start.clone();
Vector currentDir = normalizedDir.clone();
double remainingDistance = distance;
RayTraceResult result = startLocation.getWorld().rayTrace(startLocation, direction, maxDistance, FluidCollisionMode.NEVER,true,0.1,entityPredicate, blockPredicate);
for (int reflection = 0; reflection <= maxReflections; reflection++) {
Point hitPoint = trace(currentStart, currentDir, remainingDistance, interval, hitCondition);
if (hitPoint.wasMissed()) {
Verbose.send("First ray missed, returning.");
return hitPoint;
if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) {
return result.getHitBlockFace();
}
double distanceUsed = currentStart.distance(hitPoint.getLoc());
remainingDistance -= distanceUsed;
if (remainingDistance <= 0) {
Verbose.send("First ray too long, returning.");
return hitPoint;
}
Vector reflectedDirection = null;
if (HIT_BLOCK.test(hitPoint)) {
Verbose.send("hit block!");
Block hitBlock = hitPoint.getBlock();
if (blockReflectCondition.test(hitPoint,hitBlock)) {
Verbose.send("Check passed!");
BlockFace hitFace = hitPoint.getBlockFace(currentDir);
if (hitFace != null) {
Verbose.send("Face not null");
reflectedDirection = calculateBlockReflection(currentDir, hitFace);
}
}
return null;
}
else if (HIT_ENTITY.test(hitPoint)) {
Verbose.send("hit entity!");
Entity hitEntity = hitPoint.getNearbyEntities(null, 5, true, 0.1,
e -> e instanceof LivingEntity le && !le.isDead()).stream().findFirst().orElse(null);
if (hitEntity != null && entityReflectCondition != null && entityReflectCondition.test(hitPoint,hitEntity)) {
Verbose.send("Check passed!");
reflectedDirection = currentDir.clone().multiply(-1).normalize();
}
}
if (reflectedDirection == null) {
Verbose.send("Reflected direction null");
return hitPoint;
}
currentStart = hitPoint.getLoc().clone();
currentDir = reflectedDirection;
currentStart = blocksInFrontOf(currentStart,currentDir,interval*2,false).getLoc();
}
return trace(currentStart, currentDir, remainingDistance, interval, hitCondition);
}
private static Vector calculateBlockReflection(Vector incident, BlockFace face) {
private static Vector calculateReflection(Vector incident, BlockFace face) {
Verbose.send("Calculating vector reflection for face %s".formatted(face));
Vector normal = getFaceNormal(face);
return calculateReflection(incident,normal);
}
private static Vector calculateReflection(Vector incident, Vector normal) {
Verbose.send("Calculating entity reflection");
if (normal.lengthSquared() < 0.001) {
return incident.clone().multiply(-1).normalize();
}
// r = i - 2(i dot n)n
double dot = incident.dot(normal);
Vector reflection = incident.clone().subtract(normal.clone().multiply(2 * dot));

View File

@@ -1,495 +0,0 @@
package me.trouper.trimserver.utils.visual;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
public class CustomDisplayReflector {
// Commonly used reflection conditions
public static final BiPredicate<Point, BlockFace> REFLECT_ON_ANY_BLOCK = (point, face) ->
face != null && !point.getBlock().isEmpty() && point.getBlock().isCollidable();
public static final BiPredicate<Point, Entity> REFLECT_ON_ANY_ENTITY = (point, entity) ->
entity instanceof LivingEntity && !entity.isDead();
/**
* Creates a reflection condition that excludes a specific entity
* @param exclude The entity to exclude from reflection
* @return A BiPredicate that determines if reflection should occur on an entity
*/
public static BiPredicate<Point, Entity> reflectOnEntityExclude(Entity exclude) {
return (point, entity) -> entity != exclude && entity instanceof LivingEntity && !entity.isDead();
}
/**
* Traces a ray that can reflect off blocks based on provided conditions
* @param start The starting location
* @param direction The initial direction
* @param maxDistance The maximum total distance the ray can travel
* @param maxReflections The maximum number of reflections allowed
* @param interval The interval between trace points
* @param blockReflectCondition Determines if a ray should reflect off a block at a given point
* @return List of ray segments (each representing a segment between reflections)
*/
public static List<RaySegment> traceReflectingRay(
Location start,
Vector direction,
double maxDistance,
int maxReflections,
double interval,
BiPredicate<Point, BlockFace> blockReflectCondition) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
List<RaySegment> segments = new ArrayList<>();
Vector normalizedDir = direction.clone().normalize();
Location currentStart = start.clone();
double remainingDistance = maxDistance;
int reflections = 0;
while (remainingDistance > 0 && reflections <= maxReflections) {
// Trace until hit or max distance
RayResult result = traceUntilReflect(currentStart, normalizedDir, remainingDistance, interval, blockReflectCondition);
// Add segment
segments.add(new RaySegment(currentStart.clone(), result.getEndPoint().getLoc().clone(), normalizedDir.clone(), result.getDistance()));
// Check if we've hit something reflective
if (result.getReflectionType() == ReflectionType.NONE) {
break; // No reflection occurred, end of ray
}
remainingDistance -= result.getDistance();
reflections++;
// Update for next segment
currentStart = result.getEndPoint().getLoc().clone();
normalizedDir = result.getNewDirection().clone();
}
return segments;
}
/**
* Traces a ray that can reflect off both blocks and entities
* @param start The starting location
* @param direction The initial direction
* @param maxDistance The maximum total distance the ray can travel
* @param maxReflections The maximum number of reflections allowed
* @param interval The interval between trace points
* @param blockReflectCondition Determines if a ray should reflect off a block
* @param entityReflectCondition Determines if a ray should reflect off an entity
* @return List of ray segments (each representing a segment between reflections)
*/
public static List<RaySegment> traceReflectingRay(
Location start,
Vector direction,
double maxDistance,
int maxReflections,
double interval,
BiPredicate<Point, BlockFace> blockReflectCondition,
BiPredicate<Point, Entity> entityReflectCondition) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
List<RaySegment> segments = new ArrayList<>();
Vector normalizedDir = direction.clone().normalize();
Location currentStart = start.clone();
double remainingDistance = maxDistance;
int reflections = 0;
while (remainingDistance > 0 && reflections <= maxReflections) {
// Trace until hit or max distance
RayResult result = traceUntilReflect(currentStart, normalizedDir, remainingDistance, interval, blockReflectCondition, entityReflectCondition);
// Add segment
segments.add(new RaySegment(currentStart.clone(), result.getEndPoint().getLoc().clone(), normalizedDir.clone(), result.getDistance()));
// Check if we've hit something reflective
if (result.getReflectionType() == ReflectionType.NONE) {
break; // No reflection occurred, end of ray
}
remainingDistance -= result.getDistance();
reflections++;
// Update for next segment
currentStart = result.getEndPoint().getLoc().clone();
normalizedDir = result.getNewDirection().clone();
}
return segments;
}
/**
* Traces a ray until reflection or max distance
*/
private static RayResult traceUntilReflect(
Location start,
Vector direction,
double maxDistance,
double interval,
BiPredicate<Point, BlockFace> blockReflectCondition) {
Vector normalizedDir = direction.clone().normalize();
for (double i = 0.0; i < maxDistance; i += interval) {
Point point = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, i, false);
// Check for block reflection
BlockFace hitFace = getHitBlockFace(point);
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
// Calculate reflection
Vector reflectedDir = calculateBlockReflection(normalizedDir, hitFace);
return new RayResult(point, reflectedDir, i, ReflectionType.BLOCK, hitFace, null);
}
}
// No reflection found, return end point
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, maxDistance, true);
return new RayResult(endPoint, normalizedDir, maxDistance, ReflectionType.NONE, null, null);
}
/**
* Traces a ray until reflection off block or entity, or max distance
*/
private static RayResult traceUntilReflect(
Location start,
Vector direction,
double maxDistance,
double interval,
BiPredicate<Point, BlockFace> blockReflectCondition,
BiPredicate<Point, Entity> entityReflectCondition) {
Vector normalizedDir = direction.clone().normalize();
for (double i = 0.0; i < maxDistance; i += interval) {
Point point = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, i, false);
// Check for entity reflection first
List<Entity> entities = point.getNearbyEntities(null, 5, true, 0.1, e -> true);
for (Entity entity : entities) {
if (entityReflectCondition.test(point, entity)) {
// Entity reflection - return in opposite direction
Vector reflectedDir = normalizedDir.clone().multiply(-1);
return new RayResult(point, reflectedDir, i, ReflectionType.ENTITY, null, entity);
}
}
// Check for block reflection
BlockFace hitFace = getHitBlockFace(point);
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
Vector reflectedDir = calculateBlockReflection(normalizedDir, hitFace);
return new RayResult(point, reflectedDir, i, ReflectionType.BLOCK, hitFace, null);
}
}
// No reflection found, return end point
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(start, normalizedDir, maxDistance, true);
return new RayResult(endPoint, normalizedDir, maxDistance, ReflectionType.NONE, null, null);
}
/**
* Performs a delayed tracing of a reflecting ray
*/
public static BukkitTask traceReflectingRayDelayed(
Plugin plugin,
Location start,
Vector direction,
double maxDistance,
int maxReflections,
double interval,
long tickDelay,
BiPredicate<Point, BlockFace> blockReflectCondition,
Function<RaySegment, Boolean> segmentAction) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
if (tickDelay < 0) throw new IllegalArgumentException("tickDelay cannot be negative!");
Vector normalizedDir = direction.clone().normalize();
return new BukkitRunnable() {
private Location currentStart = start.clone();
private Vector currentDir = normalizedDir.clone();
private double remainingDistance = maxDistance;
private int reflections = 0;
private double currentSegmentDistance = 0;
@Override
public void run() {
if (remainingDistance <= 0 || reflections > maxReflections) {
this.cancel();
return;
}
Point point = CustomDisplayRaytracer.blocksInFrontOf(
currentStart, currentDir, currentSegmentDistance, false);
// Check for block reflection
BlockFace hitFace = getHitBlockFace(point);
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
// Create segment
RaySegment segment = new RaySegment(
currentStart.clone(),
point.getLoc().clone(),
currentDir.clone(),
currentSegmentDistance);
// Execute action (if returns false, stop the ray)
if (!segmentAction.apply(segment)) {
this.cancel();
return;
}
// Update for next segment
currentStart = point.getLoc().clone();
currentDir = calculateBlockReflection(currentDir, hitFace);
remainingDistance -= currentSegmentDistance;
reflections++;
currentSegmentDistance = 0;
return;
}
// Increment distance for this step
currentSegmentDistance += interval;
// Check if we've reached end of current segment without reflection
if (currentSegmentDistance > remainingDistance) {
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(
currentStart, currentDir, remainingDistance, true);
RaySegment segment = new RaySegment(
currentStart.clone(),
endPoint.getLoc().clone(),
currentDir.clone(),
remainingDistance);
segmentAction.apply(segment);
this.cancel();
}
}
}.runTaskTimer(plugin, 0L, tickDelay);
}
/**
* Performs a delayed tracing of a reflecting ray with both block and entity reflections
*/
public static BukkitTask traceReflectingRayDelayed(
Plugin plugin,
Location start,
Vector direction,
double maxDistance,
int maxReflections,
double interval,
long tickDelay,
BiPredicate<Point, BlockFace> blockReflectCondition,
BiPredicate<Point, Entity> entityReflectCondition,
Function<RaySegment, Boolean> segmentAction) {
if (interval <= 0) throw new IllegalArgumentException("interval cannot be zero or negative!");
if (maxDistance <= 0) throw new IllegalArgumentException("maxDistance cannot be zero or negative!");
if (tickDelay < 0) throw new IllegalArgumentException("tickDelay cannot be negative!");
Vector normalizedDir = direction.clone().normalize();
return new BukkitRunnable() {
private Location currentStart = start.clone();
private Vector currentDir = normalizedDir.clone();
private double remainingDistance = maxDistance;
private int reflections = 0;
private double currentSegmentDistance = 0;
@Override
public void run() {
if (remainingDistance <= 0 || reflections > maxReflections) {
this.cancel();
return;
}
Point point = CustomDisplayRaytracer.blocksInFrontOf(
currentStart, currentDir, currentSegmentDistance, false);
// Check for entity reflection
List<Entity> entities = point.getNearbyEntities(null, 5, true, 0.1, e -> true);
for (Entity entity : entities) {
if (entityReflectCondition.test(point, entity)) {
// Create segment
RaySegment segment = new RaySegment(
currentStart.clone(),
point.getLoc().clone(),
currentDir.clone(),
currentSegmentDistance);
// Execute action (if returns false, stop the ray)
if (!segmentAction.apply(segment)) {
this.cancel();
return;
}
// Update for next segment - reverse direction for entity reflection
currentStart = point.getLoc().clone();
currentDir = currentDir.clone().multiply(-1);
remainingDistance -= currentSegmentDistance;
reflections++;
currentSegmentDistance = 0;
return;
}
}
// Check for block reflection
BlockFace hitFace = getHitBlockFace(point);
if (hitFace != null && blockReflectCondition.test(point, hitFace)) {
// Create segment
RaySegment segment = new RaySegment(
currentStart.clone(),
point.getLoc().clone(),
currentDir.clone(),
currentSegmentDistance);
// Execute action (if returns false, stop the ray)
if (!segmentAction.apply(segment)) {
this.cancel();
return;
}
// Update for next segment
currentStart = point.getLoc().clone();
currentDir = calculateBlockReflection(currentDir, hitFace);
remainingDistance -= currentSegmentDistance;
reflections++;
currentSegmentDistance = 0;
return;
}
// Increment distance for this step
currentSegmentDistance += interval;
// Check if we've reached end of current segment without reflection
if (currentSegmentDistance > remainingDistance) {
Point endPoint = CustomDisplayRaytracer.blocksInFrontOf(
currentStart, currentDir, remainingDistance, true);
RaySegment segment = new RaySegment(
currentStart.clone(),
endPoint.getLoc().clone(),
currentDir.clone(),
remainingDistance);
segmentAction.apply(segment);
this.cancel();
}
}
}.runTaskTimer(plugin, 0L, tickDelay);
}
/**
* Represents a segment of a reflected ray
*/
public static class RaySegment {
private final Location start;
private final Location end;
private final Vector direction;
private final double distance;
public RaySegment(Location start, Location end, Vector direction, double distance) {
this.start = start;
this.end = end;
this.direction = direction;
this.distance = distance;
}
public Location getStart() {
return start;
}
public Location getEnd() {
return end;
}
public Vector getDirection() {
return direction;
}
public double getDistance() {
return distance;
}
public World getWorld() {
return start.getWorld();
}
}
/**
* Types of reflections that can occur
*/
public enum ReflectionType {
NONE,
BLOCK,
ENTITY
}
/**
* Result of a ray trace including reflection information
*/
private static class RayResult {
private final Point endPoint;
private final Vector newDirection;
private final double distance;
private final ReflectionType reflectionType;
private final BlockFace hitFace;
private final Entity hitEntity;
public RayResult(Point endPoint, Vector newDirection, double distance,
ReflectionType reflectionType, BlockFace hitFace, Entity hitEntity) {
this.endPoint = endPoint;
this.newDirection = newDirection;
this.distance = distance;
this.reflectionType = reflectionType;
this.hitFace = hitFace;
this.hitEntity = hitEntity;
}
public Point getEndPoint() {
return endPoint;
}
public Vector getNewDirection() {
return newDirection;
}
public double getDistance() {
return distance;
}
public ReflectionType getReflectionType() {
return reflectionType;
}
public BlockFace getHitFace() {
return hitFace;
}
public Entity getHitEntity() {
return hitEntity;
}
}
}

View File

@@ -19,6 +19,7 @@ public class Point {
private final Block block;
private final boolean missed;
private final double traveledDist;
private List<Point> rayPath;
public Point(Location loc, double traveledDist, boolean missed) {
this.loc = loc;
@@ -118,4 +119,16 @@ public class Point {
public double distance(Location other) {
return other.distance(loc);
}
public Point addRayPoint(Point point) {
rayPath.add(point);
return point;
}
public List<Point> setRayPath(List<Point> points) {
rayPath = points;
return rayPath;
}
public List<Point> getRayPath = new ArrayList<>();
}

View File

@@ -0,0 +1,27 @@
package me.trouper.trimserver.utils.visual;
import org.bukkit.util.Vector;
public class ReflectionResult {
private final Point hitPoint;
private final boolean shouldReflect;
private final Vector reflectedDirection;
public ReflectionResult(Point hitPoint, boolean shouldReflect, Vector reflectedDirection) {
this.hitPoint = hitPoint;
this.shouldReflect = shouldReflect;
this.reflectedDirection = reflectedDirection;
}
public Point getHitPoint() {
return hitPoint;
}
public boolean shouldReflect() {
return shouldReflect;
}
public Vector getReflectedDirection() {
return reflectedDirection;
}
}