Remembered that raytracing is in paper's api
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) {
|
||||
return result.getHitBlockFace();
|
||||
}
|
||||
|
||||
return trace(currentStart, currentDir, remainingDistance, interval, hitCondition);
|
||||
return null;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user