Improved memory optimization of the explosion utils
This commit is contained in:
@@ -3,13 +3,12 @@ package me.trouper.alias.server.systems;
|
|||||||
import me.trouper.alias.server.Main;
|
import me.trouper.alias.server.Main;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.io.Closeable;
|
||||||
import java.util.HashSet;
|
import java.util.*;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class TaskManager implements Main {
|
public class TaskManager implements Closeable, Main {
|
||||||
private final ConcurrentHashMap<Integer, Boolean> tasks = new ConcurrentHashMap<>();
|
private final Map<Integer, Boolean> tasks = new HashMap<>();
|
||||||
private volatile boolean closed = false;
|
private volatile boolean closed = false;
|
||||||
|
|
||||||
public int scheduleTask(Runnable task, long delay) {
|
public int scheduleTask(Runnable task, long delay) {
|
||||||
@@ -22,7 +21,7 @@ public class TaskManager implements Main {
|
|||||||
}, delay).getTaskId();
|
}, delay).getTaskId();
|
||||||
|
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
tasks.put(taskId, Boolean.TRUE);
|
tasks.put(taskId,Boolean.TRUE);
|
||||||
return taskId;
|
return taskId;
|
||||||
} else {
|
} else {
|
||||||
Bukkit.getScheduler().cancelTask(taskId);
|
Bukkit.getScheduler().cancelTask(taskId);
|
||||||
@@ -30,6 +29,7 @@ public class TaskManager implements Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
closed = true;
|
closed = true;
|
||||||
tasks.keySet().forEach(Bukkit.getScheduler()::cancelTask);
|
tasks.keySet().forEach(Bukkit.getScheduler()::cancelTask);
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package me.trouper.alias.server.systems;
|
|||||||
import me.trouper.alias.server.Main;
|
import me.trouper.alias.server.Main;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
public class Verbose implements Main {
|
public class Verbose implements Main {
|
||||||
|
|
||||||
@@ -26,12 +28,18 @@ public class Verbose implements Main {
|
|||||||
if (className.contains("-")) callerInfo = "Protected";
|
if (className.contains("-")) callerInfo = "Protected";
|
||||||
else callerInfo = className + "." + caller.getMethodName();
|
else callerInfo = className + "." + caller.getMethodName();
|
||||||
|
|
||||||
|
|
||||||
if (main.getCommon().getDebuggerExclusions().contains(callerInfo)) return;
|
if (main.getCommon().getDebuggerExclusions().contains(callerInfo)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component message = Text.format(Text.Pallet.INFO,verbose,args);
|
Object[] processedArgs = processArgs(args);
|
||||||
message = Text.format(Text.Pallet.INFO,Component.text("{0} [DEBUG ^ {1}] [{2}] » {3}"),Component.text(main.getCommon().getPluginName()), Component.text(backtrace), Component.text(callerInfo), message);
|
Component message = Text.format(Text.Pallet.INFO, verbose, processedArgs);
|
||||||
|
message = Text.format(Text.Pallet.INFO,
|
||||||
|
Component.text("{0} [DEBUG ^ {1}] [{2}] » {3}"),
|
||||||
|
Component.text(main.getCommon().getPluginName()),
|
||||||
|
Component.text(backtrace),
|
||||||
|
Component.text(callerInfo),
|
||||||
|
message
|
||||||
|
);
|
||||||
|
|
||||||
main.getPlugin().getComponentLogger().info(message);
|
main.getPlugin().getComponentLogger().info(message);
|
||||||
|
|
||||||
@@ -49,4 +57,24 @@ public class Verbose implements Main {
|
|||||||
public static void send(String verbose, Object... args) {
|
public static void send(String verbose, Object... args) {
|
||||||
send(1,verbose,args);
|
send(1,verbose,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Object[] processArgs(Object... args) {
|
||||||
|
Object[] processed = new Object[args.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
Object arg = args[i];
|
||||||
|
if (arg instanceof Location loc) {
|
||||||
|
processed[i] = String.format("(%s, %.2f, %.2f, %.2f)",
|
||||||
|
loc.getWorld() != null ? loc.getWorld().getName() : "null",
|
||||||
|
loc.getX(), loc.getY(), loc.getZ());
|
||||||
|
} else if (arg instanceof Vector vec) {
|
||||||
|
processed[i] = String.format("(%.2f, %.2f, %.2f)",
|
||||||
|
vec.getX(), vec.getY(), vec.getZ());
|
||||||
|
} else {
|
||||||
|
processed[i] = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package me.trouper.alias.server.systems.burning;
|
|||||||
|
|
||||||
import me.trouper.alias.server.Main;
|
import me.trouper.alias.server.Main;
|
||||||
import me.trouper.alias.server.systems.TaskManager;
|
import me.trouper.alias.server.systems.TaskManager;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockFace;
|
import org.bukkit.block.BlockFace;
|
||||||
@@ -27,12 +26,6 @@ public class BlockBurner implements Closeable, Main {
|
|||||||
this.taskManager = new TaskManager();
|
this.taskManager = new TaskManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockBurner(BurnOptions options, TaskManager sharedTaskManager) {
|
|
||||||
this.options = options;
|
|
||||||
this.palette = new BurnPalette();
|
|
||||||
this.taskManager = sharedTaskManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
taskManager.close();
|
taskManager.close();
|
||||||
@@ -40,8 +33,8 @@ public class BlockBurner implements Closeable, Main {
|
|||||||
burning.clear();
|
burning.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void burn(Block block, float heat) {
|
public void burn(Block block, float heat, boolean checkOcclusion) {
|
||||||
if (isOccluded(block)) return;
|
if (checkOcclusion && isOccluded(block)) return;
|
||||||
if (visited.contains(block)) return;
|
if (visited.contains(block)) return;
|
||||||
|
|
||||||
visited.add(block);
|
visited.add(block);
|
||||||
@@ -77,7 +70,7 @@ public class BlockBurner implements Closeable, Main {
|
|||||||
long totalDelay = 0;
|
long totalDelay = 0;
|
||||||
|
|
||||||
for (BurnStage stage : stages) {
|
for (BurnStage stage : stages) {
|
||||||
totalDelay += stage.getDelay();
|
totalDelay += stage.getDelayTicks();
|
||||||
|
|
||||||
taskManager.scheduleTask(() -> {
|
taskManager.scheduleTask(() -> {
|
||||||
if (block.getType().isAir()) return;
|
if (block.getType().isAir()) return;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package me.trouper.alias.server.systems.burning;
|
|||||||
|
|
||||||
public class BurnOptions {
|
public class BurnOptions {
|
||||||
private boolean disabled = false;
|
private boolean disabled = false;
|
||||||
private double setFireChance = 1.0 / 80;
|
private double setFireChance = 0.2;
|
||||||
|
|
||||||
public boolean isDisabled() { return disabled; }
|
public boolean isDisabled() { return disabled; }
|
||||||
public void setDisabled(boolean disabled) { this.disabled = disabled; }
|
public void setDisabled(boolean disabled) { this.disabled = disabled; }
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package me.trouper.alias.server.systems.burning;
|
|||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
|
|
||||||
public class BurnStage {
|
public class BurnStage {
|
||||||
private final long delay;
|
private final long delayTicks;
|
||||||
private final BlockData blockData;
|
private final BlockData blockData;
|
||||||
|
|
||||||
public BurnStage(long delay, BlockData blockData) {
|
public BurnStage(long delayTicks, BlockData blockData) {
|
||||||
this.delay = delay;
|
this.delayTicks = delayTicks;
|
||||||
this.blockData = blockData;
|
this.blockData = blockData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDelay() { return delay; }
|
public long getDelayTicks() { return delayTicks; }
|
||||||
public BlockData getBlockData() { return blockData; }
|
public BlockData getBlockData() { return blockData; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,17 +77,19 @@ public class CustomDisplayRaytracer {
|
|||||||
return point -> HIT_BLOCK.test(point) && !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
|
return point -> HIT_BLOCK.test(point) && !point.getNearbyEntities(null, 5, true, 0.1, e -> e instanceof LivingEntity le && !le.isDead() && condition.test(e)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Point trace(Location start, Location end, Predicate<Point> hitCondition) {
|
public static Point trace(Location start, Location end, Predicate<Point> hitCondition) {
|
||||||
return trace(start, end, 0.5, hitCondition);
|
return trace(start, end, 0.5, hitCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Point trace(Location start, Location end, double interval, Predicate<Point> hitCondition) {
|
public static Point trace(Location start, Location end, double interval, Predicate<Point> hitCondition) {
|
||||||
return trace(start, end.toVector().subtract(start.toVector()), end.distance(start), interval, hitCondition);
|
Vector direction = end.toVector().subtract(start.toVector()).normalize();
|
||||||
|
double distance = end.distance(start);
|
||||||
|
return trace(start, direction, distance, interval, hitCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Point trace(Location start, Vector direction, double distance, Predicate<Point> hitCondition) {
|
public static Point trace(Location start, Vector direction, double distance, Predicate<Point> hitCondition) {
|
||||||
return trace(start, direction, distance, 0.5, hitCondition);
|
Vector normal = direction.clone().normalize();
|
||||||
|
return trace(start, normal, distance, 0.5, hitCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Point trace(Location start, Vector direction, double distance, double interval, Predicate<Point> hitCondition) {
|
public static Point trace(Location start, Vector direction, double distance, double interval, Predicate<Point> hitCondition) {
|
||||||
@@ -285,7 +287,8 @@ public class CustomDisplayRaytracer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Point blocksInFrontOf(Location loc, Vector dir, double blocks, boolean missed) {
|
public static Point blocksInFrontOf(Location loc, Vector dir, double blocks, boolean missed) {
|
||||||
return new Point(loc.clone().add(dir.getX() * blocks, dir.getY() * blocks, dir.getZ() * blocks), blocks, missed);
|
Vector normal = dir.clone().normalize();
|
||||||
|
return new Point(loc.clone().add(normal.getX() * blocks, normal.getY() * blocks, normal.getZ() * blocks), blocks, missed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector offsetVector(Vector original, double angleDegrees) {
|
public static Vector offsetVector(Vector original, double angleDegrees) {
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ public class DisplayUtils implements Main {
|
|||||||
return l -> l.getWorld().spawnParticle(Particle.DUST, l, 1, 0, 0, 0, 0, dust);
|
return l -> l.getWorld().spawnParticle(Particle.DUST, l, 1, 0, 0, 0, 0, dust);
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final Function<Boolean, Consumer<Location>> FLAME_PARTICLE_FACTORY = soul -> {
|
|
||||||
Particle flame = soul ? Particle.SOUL_FIRE_FLAME : Particle.FLAME;
|
|
||||||
return l -> l.getWorld().spawnParticle(flame, l, 1, 0, 0, 0, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void ring(Location loc, double radius, Color color, float thickness) {
|
public static void ring(Location loc, double radius, Color color, float thickness) {
|
||||||
ring(loc, radius, DUST_PARTICLE_FACTORY.apply(color, thickness));
|
ring(loc, radius, DUST_PARTICLE_FACTORY.apply(color, thickness));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package me.trouper.alias.server.systems.visual;
|
||||||
|
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ParticleUtils {
|
||||||
|
|
||||||
|
public static ParticleBuilder builder() {
|
||||||
|
return new ParticleBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ParticleBuilder {
|
||||||
|
private Particle type = Particle.FLAME;
|
||||||
|
private int count = 1;
|
||||||
|
private double offsetX = 0;
|
||||||
|
private double offsetY = 0;
|
||||||
|
private double offsetZ = 0;
|
||||||
|
private float speed = 0.0f;
|
||||||
|
private Object data = null;
|
||||||
|
private Set<Player> viewers = new HashSet<>();
|
||||||
|
|
||||||
|
public ParticleBuilder type(Particle type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder count(int count) {
|
||||||
|
this.count = count;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder offset(double x, double y, double z) {
|
||||||
|
this.offsetX = x;
|
||||||
|
this.offsetY = y;
|
||||||
|
this.offsetZ = z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder speed(float speed) {
|
||||||
|
this.speed = speed;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder data(Particle.DustOptions dust) {
|
||||||
|
this.data = dust;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder data(Material block) {
|
||||||
|
this.data = block.createBlockData();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder data(BlockData blockData) {
|
||||||
|
this.data = blockData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder data(Object customData) {
|
||||||
|
this.data = customData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleBuilder viewers(Set<Player> viewers) {
|
||||||
|
if (viewers != null) {
|
||||||
|
this.viewers = viewers;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawn(Location location) {
|
||||||
|
if (location == null || location.getWorld() == null) return;
|
||||||
|
|
||||||
|
if (viewers == null || viewers.isEmpty()) {
|
||||||
|
location.getWorld().spawnParticle(type, location, count, offsetX, offsetY, offsetZ, speed, data);
|
||||||
|
} else {
|
||||||
|
for (Player player : viewers) {
|
||||||
|
if (player != null && player.isOnline()) {
|
||||||
|
player.spawnParticle(type, location, count, offsetX, offsetY, offsetZ, speed, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package me.trouper.alias.server.systems.world;
|
||||||
|
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
public class BlockHistory {
|
||||||
|
|
||||||
|
private final List<State> history = new ArrayList<>();
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private int currentIndex = -1;
|
||||||
|
|
||||||
|
public void addChange(State state) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (currentIndex < history.size() - 1) {
|
||||||
|
history.subList(currentIndex + 1, history.size()).clear();
|
||||||
|
}
|
||||||
|
history.add(state);
|
||||||
|
currentIndex = history.size() - 1;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChange(Collection<Block> blocks) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (currentIndex < history.size() - 1) {
|
||||||
|
history.subList(currentIndex + 1, history.size()).clear();
|
||||||
|
}
|
||||||
|
State newState = new State(blocks);
|
||||||
|
history.add(newState);
|
||||||
|
currentIndex = history.size() - 1;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canUndo() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return currentIndex > 0;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canRedo() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return currentIndex < history.size() - 1;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean undo() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (!canUndo()) return false;
|
||||||
|
currentIndex--;
|
||||||
|
history.get(currentIndex).restore();
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean redo() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (!canRedo()) return false;
|
||||||
|
currentIndex++;
|
||||||
|
history.get(currentIndex).restore();
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentIndex() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return currentIndex;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHistorySize() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return history.size();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Block> getCurrentBlocks() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (currentIndex >= 0 && currentIndex < history.size()) {
|
||||||
|
return history.get(currentIndex).getBlocks();
|
||||||
|
}
|
||||||
|
return Collections.emptySet();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package me.trouper.alias.server.systems.world;
|
||||||
|
|
||||||
|
import me.trouper.alias.server.systems.burning.BurnOptions;
|
||||||
|
|
||||||
|
public class ExplosionOptions {
|
||||||
|
private double coreRadius = 3.0;
|
||||||
|
private double falloffRadius = 8.0;
|
||||||
|
private double maxBurnRadius = 15.0;
|
||||||
|
private double baseDamage = 20;
|
||||||
|
private double destructionDelay = 0.0; // SECONDS
|
||||||
|
private double burnDelay = 0.5; // SECONDS
|
||||||
|
private double maxHeat = 1.0;
|
||||||
|
private double minHeat = 0.1;
|
||||||
|
private boolean createParticles = true;
|
||||||
|
private boolean playSound = true;
|
||||||
|
private BurnOptions burnOptions = new BurnOptions();
|
||||||
|
|
||||||
|
public double getCoreRadius() { return coreRadius; }
|
||||||
|
public ExplosionOptions setCoreRadius(double coreRadius) { this.coreRadius = coreRadius; return this; }
|
||||||
|
|
||||||
|
public double getFalloffRadius() { return falloffRadius; }
|
||||||
|
public ExplosionOptions setFalloffRadius(double falloffRadius) { this.falloffRadius = falloffRadius; return this; }
|
||||||
|
|
||||||
|
public double getMaxBurnRadius() { return maxBurnRadius; }
|
||||||
|
public ExplosionOptions setMaxBurnRadius(double maxBurnRadius) { this.maxBurnRadius = maxBurnRadius; return this; }
|
||||||
|
|
||||||
|
public double getBaseDamage() { return baseDamage; }
|
||||||
|
public ExplosionOptions setBaseDamage(double baseDamage) { this.baseDamage = baseDamage; return this; }
|
||||||
|
|
||||||
|
public double getDestructionDelay() { return destructionDelay; }
|
||||||
|
public ExplosionOptions setDestructionDelay(double destructionDelay) { this.destructionDelay = destructionDelay; return this; }
|
||||||
|
|
||||||
|
public double getBurnDelay() { return burnDelay; }
|
||||||
|
public ExplosionOptions setBurnDelay(double burnDelay) { this.burnDelay = burnDelay; return this; }
|
||||||
|
|
||||||
|
public double getMaxHeat() { return maxHeat; }
|
||||||
|
public ExplosionOptions setMaxHeat(double maxHeat) { this.maxHeat = maxHeat; return this; }
|
||||||
|
|
||||||
|
public double getMinHeat() { return minHeat; }
|
||||||
|
public ExplosionOptions setMinHeat(double minHeat) { this.minHeat = minHeat; return this; }
|
||||||
|
|
||||||
|
public boolean isCreateParticles() { return createParticles; }
|
||||||
|
public ExplosionOptions setCreateParticles(boolean createParticles) { this.createParticles = createParticles; return this; }
|
||||||
|
|
||||||
|
public boolean isPlaySound() { return playSound; }
|
||||||
|
public ExplosionOptions setPlaySound(boolean playSound) { this.playSound = playSound; return this; }
|
||||||
|
|
||||||
|
public BurnOptions getBurnOptions() { return burnOptions; }
|
||||||
|
public ExplosionOptions setBurnOptions(BurnOptions burnOptions) { this.burnOptions = burnOptions; return this; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package me.trouper.alias.server.systems.world;
|
||||||
|
|
||||||
|
import me.trouper.alias.server.Main;
|
||||||
|
import me.trouper.alias.server.systems.TaskManager;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ExplosionResult implements Closeable, Main {
|
||||||
|
private State previousState;
|
||||||
|
private TaskManager taskManager;
|
||||||
|
private volatile boolean closed = false;
|
||||||
|
|
||||||
|
public ExplosionResult(Set<Block> affectedBlocks, TaskManager manager) {
|
||||||
|
this.taskManager = manager;
|
||||||
|
this.previousState = new State(affectedBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closed) return;
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
if (taskManager != null) {
|
||||||
|
taskManager.close();
|
||||||
|
taskManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousState != null) {
|
||||||
|
previousState.close();
|
||||||
|
previousState = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restore() {
|
||||||
|
if (closed) {
|
||||||
|
throw new IllegalStateException("ExplosionResult has been closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (previousState != null) {
|
||||||
|
previousState.restore();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getPreviousState() {
|
||||||
|
if (closed) return null;
|
||||||
|
return previousState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskManager getTaskManager() {
|
||||||
|
if (closed) return null;
|
||||||
|
return taskManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,156 +1,36 @@
|
|||||||
package me.trouper.alias.server.systems.world;
|
package me.trouper.alias.server.systems.world;
|
||||||
|
|
||||||
import me.trouper.alias.server.Main;
|
import me.trouper.alias.server.Main;
|
||||||
import me.trouper.alias.server.systems.Verbose;
|
|
||||||
import me.trouper.alias.server.systems.TaskManager;
|
import me.trouper.alias.server.systems.TaskManager;
|
||||||
import me.trouper.alias.server.systems.burning.BlockBurner;
|
import me.trouper.alias.server.systems.burning.BlockBurner;
|
||||||
import me.trouper.alias.server.systems.burning.BurnOptions;
|
import me.trouper.alias.utils.SoundPlayer;
|
||||||
import me.trouper.alias.utils.TargetingUtils;
|
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockState;
|
|
||||||
import org.bukkit.entity.LivingEntity;
|
import org.bukkit.entity.LivingEntity;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.inventory.Inventory;
|
|
||||||
import org.bukkit.inventory.InventoryHolder;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.bukkit.util.Vector;
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
public class ExplosionUtils implements Main {
|
public class ExplosionUtils implements Main {
|
||||||
|
|
||||||
public static class ExplosionOptions {
|
|
||||||
private double coreRadius = 3.0;
|
|
||||||
private double falloffRadius = 8.0;
|
|
||||||
private double maxBurnRadius = 15.0;
|
|
||||||
private double baseDamage = 20;
|
|
||||||
private double destructionDelay = 0.0; // SECONDS
|
|
||||||
private double burnDelay = 0.5; // SECONDS
|
|
||||||
private double maxHeat = 1.0;
|
|
||||||
private double minHeat = 0.1;
|
|
||||||
private boolean createParticles = true;
|
|
||||||
private boolean playSound = true;
|
|
||||||
private BurnOptions burnOptions = new BurnOptions();
|
|
||||||
|
|
||||||
public double getCoreRadius() { return coreRadius; }
|
|
||||||
public void setCoreRadius(double coreRadius) { this.coreRadius = coreRadius; }
|
|
||||||
|
|
||||||
public double getFalloffRadius() { return falloffRadius; }
|
|
||||||
public void setFalloffRadius(double falloffRadius) { this.falloffRadius = falloffRadius; }
|
|
||||||
|
|
||||||
public double getMaxBurnRadius() { return maxBurnRadius; }
|
|
||||||
public void setMaxBurnRadius(double maxBurnRadius) { this.maxBurnRadius = maxBurnRadius; }
|
|
||||||
|
|
||||||
public double getBaseDamage() { return baseDamage; }
|
|
||||||
public void setBaseDamage(double baseDamage) { this.baseDamage = baseDamage; }
|
|
||||||
|
|
||||||
public double getDestructionDelay() { return destructionDelay; }
|
|
||||||
public void setDestructionDelay(double destructionDelay) { this.destructionDelay = destructionDelay; }
|
|
||||||
|
|
||||||
public double getBurnDelay() { return burnDelay; }
|
|
||||||
public void setBurnDelay(double burnDelay) { this.burnDelay = burnDelay; }
|
|
||||||
|
|
||||||
public double getMaxHeat() { return maxHeat; }
|
|
||||||
public void setMaxHeat(double maxHeat) { this.maxHeat = maxHeat; }
|
|
||||||
|
|
||||||
public double getMinHeat() { return minHeat; }
|
|
||||||
public void setMinHeat(double minHeat) { this.minHeat = minHeat; }
|
|
||||||
|
|
||||||
public boolean isCreateParticles() { return createParticles; }
|
|
||||||
public void setCreateParticles(boolean createParticles) { this.createParticles = createParticles; }
|
|
||||||
|
|
||||||
public boolean isPlaySound() { return playSound; }
|
|
||||||
public void setPlaySound(boolean playSound) { this.playSound = playSound; }
|
|
||||||
|
|
||||||
public BurnOptions getBurnOptions() { return burnOptions; }
|
|
||||||
public void setBurnOptions(BurnOptions burnOptions) { this.burnOptions = burnOptions; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ExplosionResult {
|
|
||||||
private final Map<Block, BlockState> originalStates = new HashMap<>();
|
|
||||||
private final Map<Block, ItemStack[]> originalInventories = new HashMap<>();
|
|
||||||
private final TaskManager taskManager;
|
|
||||||
private final BlockBurner burner;
|
|
||||||
|
|
||||||
public ExplosionResult(TaskManager taskManager) {
|
|
||||||
this.taskManager = taskManager;
|
|
||||||
this.burner = new BlockBurner(new BurnOptions(), taskManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExplosionResult(BlockBurner burner) {
|
|
||||||
this.burner = burner;
|
|
||||||
this.taskManager = burner.getTaskManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanup() {
|
|
||||||
taskManager.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void recordSnapshot(Block block) {
|
|
||||||
BlockState state = block.getState();
|
|
||||||
originalStates.put(block, state);
|
|
||||||
if (state instanceof InventoryHolder) {
|
|
||||||
Inventory inv = ((InventoryHolder) state).getInventory();
|
|
||||||
originalInventories.put(block, inv.getContents());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restore() {
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
for (Map.Entry<Block, BlockState> entry : originalStates.entrySet()) {
|
|
||||||
Block block = entry.getKey();
|
|
||||||
BlockState snapshot = entry.getValue();
|
|
||||||
|
|
||||||
Bukkit.getScheduler().runTask(main.getPlugin(),()->{
|
|
||||||
block.setBlockData(snapshot.getBlockData(), false);
|
|
||||||
snapshot.update(true, false);
|
|
||||||
|
|
||||||
ItemStack[] contents = originalInventories.get(block);
|
|
||||||
if (contents != null && block.getState() instanceof InventoryHolder) {
|
|
||||||
Inventory inv = ((InventoryHolder) block.getState()).getInventory();
|
|
||||||
inv.setContents(contents);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockBurner getBurner() { return burner; }
|
|
||||||
public TaskManager getTaskManager() { return taskManager; }
|
|
||||||
|
|
||||||
public Map<Block, BlockState> getOriginalStates() {
|
|
||||||
return originalStates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Block, ItemStack[]> getOriginalInventories() {
|
|
||||||
return originalInventories;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExplosionResult createExplosion(Location center, ExplosionOptions options) {
|
public static ExplosionResult createExplosion(Location center, ExplosionOptions options) {
|
||||||
World world = center.getWorld();
|
World world = center.getWorld();
|
||||||
if (world == null) throw new IllegalArgumentException("Center location must have a valid world");
|
if (world == null) throw new IllegalArgumentException("Center location must have a valid world");
|
||||||
|
|
||||||
double maxBurnRadius = options.getMaxBurnRadius();
|
double maxBurnRadius = options.getMaxBurnRadius();
|
||||||
|
|
||||||
|
try {
|
||||||
Map<Block, Double> affectedBlocks = getBlocksInRadius(center, maxBurnRadius);
|
Map<Block, Double> affectedBlocks = getBlocksInRadius(center, maxBurnRadius);
|
||||||
Map<UUID, Double> affectedEntities = getEntitiesInRadius(center, maxBurnRadius);
|
Map<UUID, Double> affectedEntities = getEntitiesInRadius(center, maxBurnRadius);
|
||||||
|
|
||||||
TaskManager sharedTaskManager = new TaskManager();
|
BlockBurner burner = new BlockBurner(options.getBurnOptions());
|
||||||
BlockBurner burner = new BlockBurner(options.getBurnOptions(), sharedTaskManager);
|
TaskManager sharedTaskManager = burner.getTaskManager();
|
||||||
ExplosionResult result = new ExplosionResult(burner);
|
ExplosionResult result = new ExplosionResult(affectedBlocks.keySet(), sharedTaskManager);
|
||||||
|
|
||||||
for (Block block : affectedBlocks.keySet()) {
|
List<Block> blocksToDestroy = new ArrayList<>();
|
||||||
if (block.getType().isAir()) continue;
|
List<Block> blocksToBurn = new ArrayList<>();
|
||||||
result.recordSnapshot(block);
|
Map<Block, Float> blocksHeatMap = new HashMap<>(affectedBlocks.size());
|
||||||
}
|
|
||||||
|
|
||||||
Set<Block> blocksToDestroy = new HashSet<>();
|
|
||||||
Set<Block> blocksToBurn = new HashSet<>();
|
|
||||||
Map<Block, Float> blocksHeatMap = new HashMap<>();
|
|
||||||
|
|
||||||
categorizeBlocks(affectedBlocks, options, blocksToDestroy, blocksToBurn, blocksHeatMap);
|
categorizeBlocks(affectedBlocks, options, blocksToDestroy, blocksToBurn, blocksHeatMap);
|
||||||
|
|
||||||
@@ -158,29 +38,41 @@ public class ExplosionUtils implements Main {
|
|||||||
scheduleBurning(blocksToBurn, blocksHeatMap, burner, center, options, sharedTaskManager);
|
scheduleBurning(blocksToBurn, blocksHeatMap, burner, center, options, sharedTaskManager);
|
||||||
scheduleDamage(affectedEntities, options, sharedTaskManager);
|
scheduleDamage(affectedEntities, options, sharedTaskManager);
|
||||||
|
|
||||||
if (options.isCreateParticles() || options.isPlaySound()) createExplosionEffects(center, options);
|
if (options.isCreateParticles() || options.isPlaySound()) {
|
||||||
|
createExplosionEffects(center, options);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to create explosion: " + e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<Block, Double> getBlocksInRadius(Location center, double radius) {
|
private static Map<Block, Double> getBlocksInRadius(Location center, double radius) {
|
||||||
Map<Block, Double> blocks = new HashMap<>();
|
|
||||||
World world = center.getWorld();
|
World world = center.getWorld();
|
||||||
|
if (world == null) return Collections.emptyMap();
|
||||||
|
|
||||||
int radiusInt = (int) Math.ceil(radius);
|
int radiusInt = (int) Math.ceil(radius);
|
||||||
Vector centerVec = center.toVector();
|
|
||||||
|
int estimatedBlocks = (int) ((4.0 / 3.0) * Math.PI * Math.pow(radius, 3));
|
||||||
|
Map<Block, Double> blocks = new HashMap<>(estimatedBlocks * 4 / 3 + 1);
|
||||||
|
|
||||||
|
int centerX = center.getBlockX();
|
||||||
|
int centerY = center.getBlockY();
|
||||||
|
int centerZ = center.getBlockZ();
|
||||||
|
double radiusSquared = radius * radius;
|
||||||
|
|
||||||
for (int x = -radiusInt; x <= radiusInt; x++) {
|
for (int x = -radiusInt; x <= radiusInt; x++) {
|
||||||
for (int y = -radiusInt; y <= radiusInt; y++) {
|
for (int y = -radiusInt; y <= radiusInt; y++) {
|
||||||
for (int z = -radiusInt; z <= radiusInt; z++) {
|
for (int z = -radiusInt; z <= radiusInt; z++) {
|
||||||
Block block = world.getBlockAt(
|
double distanceSquared = x*x + y*y + z*z;
|
||||||
center.getBlockX() + x,
|
if (distanceSquared > radiusSquared) continue;
|
||||||
center.getBlockY() + y,
|
|
||||||
center.getBlockZ() + z
|
|
||||||
);
|
|
||||||
|
|
||||||
double distance = block.getLocation().toVector().distance(centerVec);
|
Block block = world.getBlockAt(centerX + x, centerY + y, centerZ + z);
|
||||||
if (distance <= radius) {
|
if (block != null && !block.getType().isAir()) {
|
||||||
|
double distance = Math.sqrt(distanceSquared);
|
||||||
blocks.put(block, distance);
|
blocks.put(block, distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,22 +82,33 @@ public class ExplosionUtils implements Main {
|
|||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<UUID, Double> getEntitiesInRadius(Location center, double radius) {
|
private static Map<UUID, Double> getEntitiesInRadius(Location center, double radius) {
|
||||||
List<LivingEntity> rawList = center.getNearbyLivingEntities(radius).stream().toList();
|
List<LivingEntity> rawList = center.getNearbyEntities(radius, radius, radius).stream()
|
||||||
Map<UUID, Double> entities = new HashMap<>();
|
.filter(entity -> entity instanceof LivingEntity)
|
||||||
|
.map(entity -> (LivingEntity) entity)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (rawList.isEmpty()) return Collections.emptyMap();
|
||||||
|
|
||||||
|
Map<UUID, Double> entities = new HashMap<>(rawList.size() * 4 / 3 + 1);
|
||||||
|
|
||||||
for (LivingEntity livingEntity : rawList) {
|
for (LivingEntity livingEntity : rawList) {
|
||||||
entities.put(livingEntity.getUniqueId(),livingEntity.getLocation().distance(center));
|
if (livingEntity != null && livingEntity.isValid()) {
|
||||||
|
entities.put(livingEntity.getUniqueId(), livingEntity.getLocation().distance(center));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void categorizeBlocks(Map<Block, Double> affectedBlocks, ExplosionOptions options,
|
private static void categorizeBlocks(Map<Block, Double> affectedBlocks, ExplosionOptions options,
|
||||||
Set<Block> blocksToDestroy, Set<Block> blocksToBurn,
|
List<Block> blocksToDestroy, List<Block> blocksToBurn,
|
||||||
Map<Block, Float> blocksHeatMap) {
|
Map<Block, Float> blocksHeatMap) {
|
||||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
|
|
||||||
|
int totalBlocks = affectedBlocks.size();
|
||||||
|
List<Block> tempBlocksToBurn = new ArrayList<>(totalBlocks / 2);
|
||||||
|
|
||||||
for (Map.Entry<Block, Double> entry : affectedBlocks.entrySet()) {
|
for (Map.Entry<Block, Double> entry : affectedBlocks.entrySet()) {
|
||||||
Block block = entry.getKey();
|
Block block = entry.getKey();
|
||||||
double distance = entry.getValue();
|
double distance = entry.getValue();
|
||||||
@@ -215,7 +118,11 @@ public class ExplosionUtils implements Main {
|
|||||||
float heat = calculateHeat(distance, options);
|
float heat = calculateHeat(distance, options);
|
||||||
blocksHeatMap.put(block, heat);
|
blocksHeatMap.put(block, heat);
|
||||||
|
|
||||||
if (distance <= options.getCoreRadius()) blocksToDestroy.add(block);
|
if (distance <= options.getCoreRadius()) {
|
||||||
|
blocksToDestroy.add(block);
|
||||||
|
tempBlocksToBurn.add(block);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (distance <= options.getFalloffRadius()) {
|
if (distance <= options.getFalloffRadius()) {
|
||||||
double destructionChance = 1.0 - ((distance - options.getCoreRadius()) /
|
double destructionChance = 1.0 - ((distance - options.getCoreRadius()) /
|
||||||
@@ -223,14 +130,26 @@ public class ExplosionUtils implements Main {
|
|||||||
|
|
||||||
destructionChance *= (0.7 + random.nextDouble() * 0.6);
|
destructionChance *= (0.7 + random.nextDouble() * 0.6);
|
||||||
|
|
||||||
if (random.nextDouble() < destructionChance) blocksToDestroy.add(block);
|
if (random.nextDouble() < destructionChance) {
|
||||||
else blocksToBurn.add(block);
|
blocksToDestroy.add(block);
|
||||||
|
} else {
|
||||||
|
tempBlocksToBurn.add(block);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
double burnChance = 1.0 - ((distance - options.getFalloffRadius()) /
|
double burnChance = 1.0 - ((distance - options.getFalloffRadius()) /
|
||||||
(options.getMaxBurnRadius() - options.getFalloffRadius()));
|
(options.getMaxBurnRadius() - options.getFalloffRadius()));
|
||||||
burnChance *= (0.8 + random.nextDouble() * 0.6);
|
burnChance *= (0.8 + random.nextDouble() * 0.6);
|
||||||
|
|
||||||
if (random.nextDouble() < burnChance) blocksToBurn.add(block);
|
if (random.nextDouble() < burnChance) {
|
||||||
|
tempBlocksToBurn.add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Block> toDestroySet = new HashSet<>(blocksToDestroy);
|
||||||
|
for (Block block : tempBlocksToBurn) {
|
||||||
|
if (!isOccluded(block, toDestroySet)) {
|
||||||
|
blocksToBurn.add(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,42 +157,43 @@ public class ExplosionUtils implements Main {
|
|||||||
private static float calculateHeat(double distance, ExplosionOptions options) {
|
private static float calculateHeat(double distance, ExplosionOptions options) {
|
||||||
double normalizedDistance = distance / options.getMaxBurnRadius();
|
double normalizedDistance = distance / options.getMaxBurnRadius();
|
||||||
double heatRange = options.getMaxHeat() - options.getMinHeat();
|
double heatRange = options.getMaxHeat() - options.getMinHeat();
|
||||||
|
|
||||||
double heatFactor = Math.pow(1.0 - normalizedDistance, 2.0);
|
double heatFactor = Math.pow(1.0 - normalizedDistance, 2.0);
|
||||||
|
|
||||||
return (float) (options.getMinHeat() + heatRange * heatFactor);
|
return (float) (options.getMinHeat() + heatRange * heatFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scheduleDestruction(Set<Block> blocksToDestroy, ExplosionOptions options, TaskManager taskManager) {
|
private static void scheduleDestruction(List<Block> blocksToDestroy, ExplosionOptions options, TaskManager taskManager) {
|
||||||
if (blocksToDestroy.isEmpty()) return;
|
if (blocksToDestroy.isEmpty()) return;
|
||||||
|
|
||||||
long destructionDelayTicks = (long) (options.getDestructionDelay() * 20);
|
long destructionDelayTicks = (long) (options.getDestructionDelay() * 20);
|
||||||
|
|
||||||
taskManager.scheduleTask(() -> {
|
taskManager.scheduleTask(() -> {
|
||||||
List<Block> blockList = new ArrayList<>(blocksToDestroy);
|
Collections.shuffle(blocksToDestroy);
|
||||||
Collections.shuffle(blockList);
|
int blocksPerWave = Math.max(1, blocksToDestroy.size() / 5);
|
||||||
|
|
||||||
int blocksPerWave = Math.max(1, blockList.size() / 5);
|
|
||||||
|
|
||||||
for (int wave = 0; wave < 5; wave++) {
|
for (int wave = 0; wave < 5; wave++) {
|
||||||
int startIndex = wave * blocksPerWave;
|
int startIndex = wave * blocksPerWave;
|
||||||
int endIndex = Math.min(startIndex + blocksPerWave, blockList.size());
|
int endIndex = Math.min(startIndex + blocksPerWave, blocksToDestroy.size());
|
||||||
|
|
||||||
if (startIndex >= blockList.size()) break;
|
if (startIndex >= blocksToDestroy.size()) break;
|
||||||
|
|
||||||
|
final int finalStartIndex = startIndex;
|
||||||
|
final int finalEndIndex = endIndex;
|
||||||
|
|
||||||
taskManager.scheduleTask(() -> {
|
taskManager.scheduleTask(() -> {
|
||||||
for (int i = startIndex; i < endIndex; i++) {
|
for (int i = finalStartIndex; i < finalEndIndex; i++) {
|
||||||
Block block = blockList.get(i);
|
if (i < blocksToDestroy.size()) {
|
||||||
if (!block.getType().isAir()) {
|
Block block = blocksToDestroy.get(i);
|
||||||
|
if (block != null && !block.getType().isAir()) {
|
||||||
block.setType(Material.AIR);
|
block.setType(Material.AIR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, wave * 2);
|
}
|
||||||
|
}, wave * 2L);
|
||||||
}
|
}
|
||||||
}, destructionDelayTicks);
|
}, destructionDelayTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scheduleBurning(Set<Block> blocksToMaybeBurn, Map<Block, Float> blocksHeatMap,
|
private static void scheduleBurning(List<Block> blocksToMaybeBurn, Map<Block, Float> blocksHeatMap,
|
||||||
BlockBurner burner, Location center, ExplosionOptions options,
|
BlockBurner burner, Location center, ExplosionOptions options,
|
||||||
TaskManager taskManager) {
|
TaskManager taskManager) {
|
||||||
if (blocksToMaybeBurn.isEmpty()) return;
|
if (blocksToMaybeBurn.isEmpty()) return;
|
||||||
@@ -281,34 +201,34 @@ public class ExplosionUtils implements Main {
|
|||||||
long burnDelayTicks = (long) (options.getBurnDelay() * 20);
|
long burnDelayTicks = (long) (options.getBurnDelay() * 20);
|
||||||
|
|
||||||
taskManager.scheduleTask(() -> {
|
taskManager.scheduleTask(() -> {
|
||||||
List<Block> blockList = new ArrayList<>(blocksToMaybeBurn);
|
Collections.shuffle(blocksToMaybeBurn);
|
||||||
Collections.shuffle(blockList);
|
|
||||||
|
|
||||||
Map<Integer, List<Block>> burnWaves = new HashMap<>();
|
Map<Integer, List<Block>> burnWaves = new HashMap<>();
|
||||||
|
|
||||||
for (Block block : blockList) {
|
for (Block block : blocksToMaybeBurn) {
|
||||||
|
if (block != null) {
|
||||||
double distance = block.getLocation().distance(center);
|
double distance = block.getLocation().distance(center);
|
||||||
int waveIndex = (int) (distance / 2.0);
|
int waveIndex = (int) (distance / 2.0);
|
||||||
|
|
||||||
burnWaves.computeIfAbsent(waveIndex, k -> new ArrayList<>()).add(block);
|
burnWaves.computeIfAbsent(waveIndex, k -> new ArrayList<>()).add(block);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (Map.Entry<Integer, List<Block>> waveEntry : burnWaves.entrySet()) {
|
for (Map.Entry<Integer, List<Block>> waveEntry : burnWaves.entrySet()) {
|
||||||
int waveDelay = waveEntry.getKey() * 3;
|
int waveDelay = waveEntry.getKey() * 3;
|
||||||
List<Block> waveBlocks = waveEntry.getValue();
|
List<Block> waveBlocks = waveEntry.getValue();
|
||||||
|
|
||||||
taskManager.scheduleTask(() -> {
|
taskManager.scheduleTask(() -> {
|
||||||
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||||
|
|
||||||
for (Block block : waveBlocks) {
|
for (Block block : waveBlocks) {
|
||||||
if (burner.isClosed()) continue;
|
if (burner.isClosed() || block == null) continue;
|
||||||
|
|
||||||
float heat = blocksHeatMap.getOrDefault(block, 0.1f);
|
float heat = blocksHeatMap.getOrDefault(block, 0.1f);
|
||||||
|
|
||||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
||||||
int randomDelay = random.nextInt(0, 10);
|
int randomDelay = random.nextInt(0, 10);
|
||||||
|
|
||||||
taskManager.scheduleTask(() -> {
|
taskManager.scheduleTask(() -> {
|
||||||
if (!burner.isClosed() && !block.getType().isAir()) {
|
if (!burner.isClosed() && block != null && !block.getType().isAir()) {
|
||||||
burner.burn(block, heat);
|
burner.burn(block, heat, false);
|
||||||
}
|
}
|
||||||
}, randomDelay);
|
}, randomDelay);
|
||||||
}
|
}
|
||||||
@@ -318,36 +238,43 @@ public class ExplosionUtils implements Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void scheduleDamage(Map<UUID, Double> affected, ExplosionOptions options, TaskManager taskManager) {
|
private static void scheduleDamage(Map<UUID, Double> affected, ExplosionOptions options, TaskManager taskManager) {
|
||||||
|
if (affected.isEmpty()) return;
|
||||||
|
|
||||||
double baseDamage = options.getBaseDamage();
|
double baseDamage = options.getBaseDamage();
|
||||||
double igniteDistance = options.getMaxBurnRadius();
|
double igniteDistance = options.getMaxBurnRadius();
|
||||||
double halfDamageDistance = options.getFalloffRadius();
|
double halfDamageDistance = options.getFalloffRadius();
|
||||||
double fullDamageDistance = options.getCoreRadius();
|
double fullDamageDistance = options.getCoreRadius();
|
||||||
|
|
||||||
for (Map.Entry<UUID, Double> entityDistance : affected.entrySet()) {
|
for (Map.Entry<UUID, Double> entityDistance : affected.entrySet()) {
|
||||||
LivingEntity liv = (LivingEntity) Bukkit.getEntity(entityDistance.getKey());
|
UUID entityId = entityDistance.getKey();
|
||||||
if (liv == null) continue;
|
if (entityId == null) continue;
|
||||||
|
|
||||||
double distance = entityDistance.getValue();
|
double distance = entityDistance.getValue();
|
||||||
|
long delay = Math.max(1, (long) distance / 2);
|
||||||
|
|
||||||
if (distance >= halfDamageDistance && distance <= igniteDistance) {
|
if (distance >= halfDamageDistance && distance <= igniteDistance) {
|
||||||
taskManager.scheduleTask(()->{
|
taskManager.scheduleTask(() -> {
|
||||||
liv.setFireTicks(5 * 20);
|
LivingEntity entity = (LivingEntity) Bukkit.getEntity(entityId);
|
||||||
},(long) distance / 2);
|
if (entity != null && entity.isValid()) {
|
||||||
return;
|
entity.setFireTicks(5 * 20);
|
||||||
}
|
}
|
||||||
if (distance >= fullDamageDistance && distance <= halfDamageDistance) {
|
}, delay);
|
||||||
taskManager.scheduleTask(()->{
|
} else if (distance >= fullDamageDistance && distance <= halfDamageDistance) {
|
||||||
liv.setFireTicks(10 * 20);
|
taskManager.scheduleTask(() -> {
|
||||||
liv.damage(baseDamage / 2);
|
LivingEntity entity = (LivingEntity) Bukkit.getEntity(entityId);
|
||||||
},(long) distance / 2);
|
if (entity != null && entity.isValid()) {
|
||||||
return;
|
entity.setFireTicks(10 * 20);
|
||||||
|
entity.damage(baseDamage / 2);
|
||||||
}
|
}
|
||||||
if (distance <= fullDamageDistance) {
|
}, delay);
|
||||||
taskManager.scheduleTask(()->{
|
} else if (distance <= fullDamageDistance) {
|
||||||
liv.setFireTicks(15 * 20);
|
taskManager.scheduleTask(() -> {
|
||||||
liv.damage(baseDamage);
|
LivingEntity entity = (LivingEntity) Bukkit.getEntity(entityId);
|
||||||
},(long) distance / 2);
|
if (entity != null && entity.isValid()) {
|
||||||
return;
|
entity.setFireTicks(15 * 20);
|
||||||
|
entity.damage(baseDamage);
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,9 +283,12 @@ public class ExplosionUtils implements Main {
|
|||||||
World world = center.getWorld();
|
World world = center.getWorld();
|
||||||
if (world == null) return;
|
if (world == null) return;
|
||||||
|
|
||||||
|
double soundRange = options.getMaxBurnRadius() * 20;
|
||||||
|
|
||||||
if (options.isPlaySound()) {
|
if (options.isPlaySound()) {
|
||||||
world.playSound(center, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.8f);
|
new SoundPlayer(Sound.ENTITY_WARDEN_SONIC_BOOM, 40, 0.5F).playAt(center, soundRange);
|
||||||
world.playSound(center, Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 0.5f, 1.2f);
|
new SoundPlayer(Sound.ENTITY_WARDEN_SONIC_BOOM, 40, 1.0F).playAt(center, soundRange);
|
||||||
|
new SoundPlayer(Sound.ITEM_TOTEM_USE, 40, 0.5F).playAt(center, soundRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.isCreateParticles()) {
|
if (options.isCreateParticles()) {
|
||||||
@@ -368,22 +298,25 @@ public class ExplosionUtils implements Main {
|
|||||||
world.spawnParticle(Particle.FLAME, center, 30, 3, 3, 3, 0.1);
|
world.spawnParticle(Particle.FLAME, center, 30, 3, 3, 3, 0.1);
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
|
Bukkit.getScheduler().runTaskLater(main.getPlugin(), () -> {
|
||||||
world.spawnParticle(Particle.SMOKE, center, 50, 4, 4, 4, 0.02);
|
if (center.getWorld() != null) {
|
||||||
}, 20);
|
center.getWorld().spawnParticle(Particle.SMOKE, center, 50, 4, 4, 4, 0.02);
|
||||||
|
}
|
||||||
|
}, 20L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExplosionResult createExplosion(Location center) {
|
private static boolean isOccluded(Block block, Set<Block> doesNotOcclude) {
|
||||||
return createExplosion(center, new ExplosionOptions());
|
if (block == null) return true;
|
||||||
|
|
||||||
|
return isOccluding(block.getRelative(0, 1, 0), doesNotOcclude) &&
|
||||||
|
isOccluding(block.getRelative(0, -1, 0), doesNotOcclude) &&
|
||||||
|
isOccluding(block.getRelative(1, 0, 0), doesNotOcclude) &&
|
||||||
|
isOccluding(block.getRelative(-1, 0, 0), doesNotOcclude) &&
|
||||||
|
isOccluding(block.getRelative(0, 0, 1), doesNotOcclude) &&
|
||||||
|
isOccluding(block.getRelative(0, 0, -1), doesNotOcclude);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExplosionResult createExplosion(Location center, double coreRadius, double falloffRadius, double maxBurnRadius) {
|
private static boolean isOccluding(Block block, Set<Block> doesNotOcclude) {
|
||||||
ExplosionOptions options = new ExplosionOptions();
|
return block != null && block.getType().isOccluding() && !doesNotOcclude.contains(block);
|
||||||
options.setCoreRadius(coreRadius);
|
|
||||||
options.setFalloffRadius(falloffRadius);
|
|
||||||
options.setMaxBurnRadius(maxBurnRadius);
|
|
||||||
options.setBurnDelay(0);
|
|
||||||
options.setDestructionDelay(0);
|
|
||||||
return createExplosion(center, options);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,85 @@
|
|||||||
package me.trouper.alias.server.systems.world;
|
package me.trouper.alias.server.systems.world;
|
||||||
|
|
||||||
public class Snapshot {
|
import me.trouper.alias.server.Main;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Snapshot {
|
||||||
|
private final BlockState state;
|
||||||
|
private final Map<Integer, ItemStack> inventory;
|
||||||
|
|
||||||
|
public Snapshot(Block block) {
|
||||||
|
this.state = block.getState();
|
||||||
|
|
||||||
|
if (state instanceof InventoryHolder) {
|
||||||
|
Inventory inv = ((InventoryHolder) state).getInventory();
|
||||||
|
int size = inv.getSize();
|
||||||
|
|
||||||
|
int nonEmptySlots = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ItemStack item = inv.getItem(i);
|
||||||
|
if (item != null && !item.isEmpty()) {
|
||||||
|
nonEmptySlots++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonEmptySlots > 0) {
|
||||||
|
this.inventory = new HashMap<>(nonEmptySlots * 4 / 3 + 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ItemStack item = inv.getItem(i);
|
||||||
|
if (item != null && !item.isEmpty()) {
|
||||||
|
inventory.put(i, item.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.inventory = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.inventory = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restore(Block block) {
|
||||||
|
if (block == null || state == null) return;
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(Main.main.getPlugin(), () -> {
|
||||||
|
try {
|
||||||
|
block.setBlockData(state.getBlockData());
|
||||||
|
|
||||||
|
if (inventory != null && block.getState() instanceof InventoryHolder) {
|
||||||
|
InventoryHolder holder = (InventoryHolder) block.getState();
|
||||||
|
Inventory inv = holder.getInventory();
|
||||||
|
|
||||||
|
inv.clear();
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, ItemStack> entry : inventory.entrySet()) {
|
||||||
|
int slot = entry.getKey();
|
||||||
|
ItemStack item = entry.getValue();
|
||||||
|
|
||||||
|
if (slot >= 0 && slot < inv.getSize() && item != null) {
|
||||||
|
inv.setItem(slot, item.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to restore block at " + block.getLocation() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockState getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, ItemStack> getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package me.trouper.alias.server.systems.world;
|
||||||
|
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class State implements Closeable {
|
||||||
|
private Map<Location, Snapshot> snapshots;
|
||||||
|
private volatile boolean closed = false;
|
||||||
|
|
||||||
|
public State(Collection<Block> blocks) {
|
||||||
|
this.snapshots = new HashMap<>(blocks.size() * 4 / 3 + 1);
|
||||||
|
|
||||||
|
for (Block block : blocks) {
|
||||||
|
if (block != null && block.getWorld() != null) {
|
||||||
|
snapshots.put(block.getLocation(), new Snapshot(block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restore() {
|
||||||
|
if (closed || snapshots == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Map.Entry<Location, Snapshot> entry : snapshots.entrySet()) {
|
||||||
|
Location loc = entry.getKey();
|
||||||
|
Snapshot snapshot = entry.getValue();
|
||||||
|
|
||||||
|
if (loc != null && snapshot != null && loc.getWorld() != null) {
|
||||||
|
Block block = loc.getBlock();
|
||||||
|
if (block != null) {
|
||||||
|
snapshot.restore(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Block> getBlocks() {
|
||||||
|
if (closed || snapshots == null) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Block> blocks = new HashSet<>(snapshots.size());
|
||||||
|
for (Location loc : snapshots.keySet()) {
|
||||||
|
if (loc != null && loc.getWorld() != null) {
|
||||||
|
Block block = loc.getBlock();
|
||||||
|
if (block != null) {
|
||||||
|
blocks.add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closed) return;
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
if (snapshots != null) {
|
||||||
|
snapshots.clear();
|
||||||
|
snapshots = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/java/me/trouper/alias/utils/FormatUtils.java
Normal file
21
src/main/java/me/trouper/alias/utils/FormatUtils.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package me.trouper.alias.utils;
|
||||||
|
|
||||||
|
public class FormatUtils {
|
||||||
|
public static String formatEnum(Enum<?> obj) {
|
||||||
|
if (obj == null) return "Null";
|
||||||
|
String name = obj.name();
|
||||||
|
String[] words = name.toLowerCase().split("_");
|
||||||
|
|
||||||
|
StringBuilder formatted = new StringBuilder();
|
||||||
|
|
||||||
|
for (String word : words) {
|
||||||
|
if (!word.isEmpty()) {
|
||||||
|
formatted.append(Character.toUpperCase(word.charAt(0)))
|
||||||
|
.append(word.substring(1))
|
||||||
|
.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted.toString().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/java/me/trouper/alias/utils/InventoryUtils.java
Normal file
29
src/main/java/me/trouper/alias/utils/InventoryUtils.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package me.trouper.alias.utils;
|
||||||
|
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.block.Container;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||||
|
|
||||||
|
public final class InventoryUtils {
|
||||||
|
|
||||||
|
public static Inventory getInventory(Entity entity) {
|
||||||
|
if (entity instanceof org.bukkit.inventory.InventoryHolder inventoryHolder) {
|
||||||
|
return inventoryHolder.getInventory();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Inventory getInventory(ItemStack containerItem) {
|
||||||
|
if (containerItem.getItemMeta() instanceof BlockStateMeta blockStateMeta) {
|
||||||
|
BlockState blockState = blockStateMeta.getBlockState();
|
||||||
|
if (blockState instanceof Container container) {
|
||||||
|
return container.getInventory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user