diff --git a/src/main/java/me/trouper/alias/server/systems/AbstractWand.java b/src/main/java/me/trouper/alias/server/systems/AbstractWand.java index e0bb2f8..7dc36ac 100644 --- a/src/main/java/me/trouper/alias/server/systems/AbstractWand.java +++ b/src/main/java/me/trouper/alias/server/systems/AbstractWand.java @@ -2,17 +2,26 @@ package me.trouper.alias.server.systems; import me.trouper.alias.server.Main; import me.trouper.alias.server.events.QuickListener; +import me.trouper.alias.utils.misc.Cooldown; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; +import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.player.*; import org.bukkit.inventory.ItemStack; +import java.util.UUID; + public abstract class AbstractWand implements QuickListener, Main { private final String usePermission; private final ItemStack wandItem; + private final Cooldown debounce = new Cooldown<>(); public AbstractWand(String usePermission, ItemStack wandItem) { this.wandItem = wandItem.clone(); @@ -37,43 +46,96 @@ public abstract class AbstractWand implements QuickListener, Main { } @EventHandler - public void onSwapHands(PlayerSwapHandItemsEvent e) { + public final void onSwapHands(PlayerSwapHandItemsEvent e) { Player p = e.getPlayer(); if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; e.setCancelled(true); - if (p.isSneaking()) onSwapHandSneak(p); - else onSwapHand(p); + swapHand(p); } - @EventHandler - public void onAnimate(PlayerAnimationEvent e) { - Player p = e.getPlayer(); - if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; - if (!e.getAnimationType().equals(PlayerAnimationType.ARM_SWING)) return; - if (e.getPlayer().getTargetEntity(5) == null) return; - e.setCancelled(true); - - if (p.isSneaking()) onLeftClickSneak(p); - else onLeftClick(p); - } - - @EventHandler - public void onInteract(PlayerInteractEvent e) { + @EventHandler(priority = EventPriority.HIGHEST) + public final void onInteract(PlayerInteractEvent e) { Player p = e.getPlayer(); if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; Action action = e.getAction(); - if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) return; + switch (action) { + case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> { + e.setCancelled(true); + + if (debounce.isOnCooldown(p.getUniqueId())) return; + debounce.setCooldown(p.getUniqueId(),100); + + rightClick(p); + } + case LEFT_CLICK_AIR, LEFT_CLICK_BLOCK -> { + e.setCancelled(true); + leftClick(p); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onEntityDamage(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p)) return; + if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; + + e.setCancelled(true); + leftClick(p); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onEntityInteract(PlayerInteractEntityEvent e) { + Player p = e.getPlayer(); + if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; e.setCancelled(true); - if (p.isSneaking()) onRightClickSneak(p); - else onRightClick(p); + if (debounce.isOnCooldown(p.getUniqueId())) return; + debounce.setCooldown(p.getUniqueId(),100); + + rightClick(p); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onEntityInteractAt(PlayerInteractAtEntityEvent e) { + Player p = e.getPlayer(); + if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; + + if (debounce.isOnCooldown(p.getUniqueId())) return; + debounce.setCooldown(p.getUniqueId(),100); + + e.setCancelled(true); + rightClick(p); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onBlockBreak(BlockBreakEvent e) { + Player p = e.getPlayer(); + if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; + + e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onBlockPlace(BlockPlaceEvent e) { + Player p = e.getPlayer(); + if (!isWand(e.getItemInHand()) || !p.hasPermission(getUsePermission())) return; + + e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public final void onItemDrop(PlayerDropItemEvent e) { + Player p = e.getPlayer(); + if (!isWand(e.getItemDrop().getItemStack()) || !p.hasPermission(getUsePermission())) return; + + e.setCancelled(true); } @EventHandler - public void onScroll(PlayerItemHeldEvent e) { + public final void onScroll(PlayerItemHeldEvent e) { Player p = e.getPlayer(); if (!isHoldingWand(p) || !p.hasPermission(getUsePermission())) return; @@ -81,9 +143,27 @@ public abstract class AbstractWand implements QuickListener, Main { int curr = e.getNewSlot(); if (!p.isSneaking() || !isWand(p.getInventory().getItem(prev))) return; + e.setCancelled(true); - if (curr < prev) onScrollUp(e.getPlayer()); - else if (curr > prev) onScrollDown(e.getPlayer()); + if (curr == 8 && prev == 0) onScrollUp(p); + else if (curr == 0 && prev == 8) onScrollDown(p); + else if (curr < prev) onScrollUp(p); + else if (curr > prev) onScrollDown(p); + } + + public final void swapHand(Player p) { + if (p.isSneaking()) onSwapHandSneak(p); + else onSwapHand(p); + } + + public final void leftClick(Player p) { + if (p.isSneaking()) onLeftClickSneak(p); + else onLeftClick(p); + } + + public final void rightClick(Player p) { + if (p.isSneaking()) onRightClickSneak(p); + else onRightClick(p); } protected void onSwapHand(Player player) {} @@ -92,6 +172,14 @@ public abstract class AbstractWand implements QuickListener, Main { protected void onRightClickSneak(Player player) {} protected void onLeftClick(Player player) {} protected void onLeftClickSneak(Player player) {} + + /** + * The player must be sneaking to scroll the wand. + */ protected void onScrollUp(Player player) {} + + /** + * The player must be sneaking to scroll the wand. + */ protected void onScrollDown(Player player) {} -} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/alias/server/systems/Text.java b/src/main/java/me/trouper/alias/server/systems/Text.java index 55d9208..2d232b8 100644 --- a/src/main/java/me/trouper/alias/server/systems/Text.java +++ b/src/main/java/me/trouper/alias/server/systems/Text.java @@ -74,26 +74,6 @@ public class Text implements Main { message(pallet,true,audience,text,args); } - public static void sendWarning(Audience audience, String warning, Object... args) { - messageAny(Pallet.WARNING, audience, warning, args); - } - - public static void sendError(Audience audience, String error, Object... args) { - messageAny(Pallet.ERROR, audience, error, args); - } - - public static void sendInfo(Audience audience, String info, Object... args) { - messageAny(Pallet.INFO, audience, info, args); - } - - public static void sendSuccess(Audience audience, String success, Object... args) { - messageAny(Pallet.SUCCESS, audience, success, args); - } - - public static void sendMessage(Audience audience, String text, Object... args) { - messageAny(Pallet.NEUTRAL, audience, text, args); - } - /** * Gets the component form of a message, applying pallet formatting to the text and placeholders. Placeholders are zero-indexed and curly braced. {0}, {1}, {2}... * @param pallet The colors to use for text and arguments. diff --git a/src/main/java/me/trouper/alias/server/systems/world/ExplosionUtils.java b/src/main/java/me/trouper/alias/server/systems/world/ExplosionUtils.java index b6932ac..48da469 100644 --- a/src/main/java/me/trouper/alias/server/systems/world/ExplosionUtils.java +++ b/src/main/java/me/trouper/alias/server/systems/world/ExplosionUtils.java @@ -5,9 +5,12 @@ import me.trouper.alias.server.systems.Verbose; import me.trouper.alias.server.systems.TaskManager; import me.trouper.alias.server.systems.burning.BlockBurner; import me.trouper.alias.server.systems.burning.BurnOptions; +import me.trouper.alias.utils.TargetingUtils; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +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; @@ -23,6 +26,7 @@ public class ExplosionUtils implements Main { 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; @@ -40,6 +44,9 @@ public class ExplosionUtils implements Main { 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; } @@ -92,22 +99,22 @@ public class ExplosionUtils implements Main { } public void restore() { - // First cleanup all tasks cleanup(); - // Then restore blocks for (Map.Entry entry : originalStates.entrySet()) { Block block = entry.getKey(); BlockState snapshot = entry.getValue(); - block.setBlockData(snapshot.getBlockData(), false); - snapshot.update(true, false); + 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); - } + ItemStack[] contents = originalInventories.get(block); + if (contents != null && block.getState() instanceof InventoryHolder) { + Inventory inv = ((InventoryHolder) block.getState()).getInventory(); + inv.setContents(contents); + } + }); } } @@ -127,9 +134,11 @@ public class ExplosionUtils implements Main { World world = center.getWorld(); if (world == null) throw new IllegalArgumentException("Center location must have a valid world"); - Map affectedBlocks = getBlocksInRadius(center, options.getMaxBurnRadius()); + double maxBurnRadius = options.getMaxBurnRadius(); + + Map affectedBlocks = getBlocksInRadius(center, maxBurnRadius); + Map affectedEntities = getEntitiesInRadius(center, maxBurnRadius); - // Create shared task manager for this explosion TaskManager sharedTaskManager = new TaskManager(); BlockBurner burner = new BlockBurner(options.getBurnOptions(), sharedTaskManager); ExplosionResult result = new ExplosionResult(burner); @@ -147,6 +156,7 @@ public class ExplosionUtils implements Main { scheduleDestruction(blocksToDestroy, options, sharedTaskManager); scheduleBurning(blocksToBurn, blocksHeatMap, burner, center, options, sharedTaskManager); + scheduleDamage(affectedEntities, options, sharedTaskManager); if (options.isCreateParticles() || options.isPlaySound()) createExplosionEffects(center, options); @@ -180,6 +190,17 @@ public class ExplosionUtils implements Main { return blocks; } + public static Map getEntitiesInRadius(Location center, double radius) { + List rawList = center.getNearbyLivingEntities(radius).stream().toList(); + Map entities = new HashMap<>(); + + for (LivingEntity livingEntity : rawList) { + entities.put(livingEntity.getUniqueId(),livingEntity.getLocation().distance(center)); + } + + return entities; + } + private static void categorizeBlocks(Map affectedBlocks, ExplosionOptions options, Set blocksToDestroy, Set blocksToBurn, Map blocksHeatMap) { @@ -296,6 +317,41 @@ public class ExplosionUtils implements Main { }, burnDelayTicks); } + private static void scheduleDamage(Map affected, ExplosionOptions options, TaskManager taskManager) { + double baseDamage = options.getBaseDamage(); + double igniteDistance = options.getMaxBurnRadius(); + double halfDamageDistance = options.getFalloffRadius(); + double fullDamageDistance = options.getCoreRadius(); + + for (Map.Entry entityDistance : affected.entrySet()) { + LivingEntity liv = (LivingEntity) Bukkit.getEntity(entityDistance.getKey()); + if (liv == null) continue; + + double distance = entityDistance.getValue(); + + if (distance >= halfDamageDistance && distance <= igniteDistance) { + taskManager.scheduleTask(()->{ + liv.setFireTicks(5 * 20); + },(long) distance / 2); + return; + } + if (distance >= fullDamageDistance && distance <= halfDamageDistance) { + taskManager.scheduleTask(()->{ + liv.setFireTicks(10 * 20); + liv.damage(baseDamage / 2); + },(long) distance / 2); + return; + } + if (distance <= fullDamageDistance) { + taskManager.scheduleTask(()->{ + liv.setFireTicks(15 * 20); + liv.damage(baseDamage); + },(long) distance / 2); + return; + } + } + } + private static void createExplosionEffects(Location center, ExplosionOptions options) { World world = center.getWorld(); if (world == null) return; diff --git a/src/main/java/me/trouper/alias/server/systems/world/Snapshot.java b/src/main/java/me/trouper/alias/server/systems/world/Snapshot.java new file mode 100644 index 0000000..f97e287 --- /dev/null +++ b/src/main/java/me/trouper/alias/server/systems/world/Snapshot.java @@ -0,0 +1,5 @@ +package me.trouper.alias.server.systems.world; + +public class Snapshot { + +} diff --git a/src/main/java/me/trouper/alias/utils/VectorUtils.java b/src/main/java/me/trouper/alias/utils/VectorUtils.java new file mode 100644 index 0000000..8f06eb7 --- /dev/null +++ b/src/main/java/me/trouper/alias/utils/VectorUtils.java @@ -0,0 +1,20 @@ +package me.trouper.alias.utils; + +import org.bukkit.util.Vector; + +public class VectorUtils { + + public static float[] toAngles(Vector vector) { + double x = vector.getX(); + double y = vector.getY(); + double z = vector.getZ(); + + float yaw = (float) Math.toDegrees(Math.atan2(-x, z)); + + double xzLength = Math.sqrt(x * x + z * z); + + float pitch = (float) Math.toDegrees(Math.atan2(-y, xzLength)); + + return new float[] { yaw, pitch }; + } +} diff --git a/src/main/java/me/trouper/alias/utils/misc/TimeUtils.java b/src/main/java/me/trouper/alias/utils/misc/TimeUtils.java new file mode 100644 index 0000000..a425bd4 --- /dev/null +++ b/src/main/java/me/trouper/alias/utils/misc/TimeUtils.java @@ -0,0 +1,20 @@ +package me.trouper.alias.utils.misc; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +public class TimeUtils { + + public static String formatTime(long millis) { + Instant instant = Instant.ofEpochMilli(millis); + return DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("UTC")).format(instant); + } + + public static long deserializeTime(String rfcString) { + LocalDateTime dateTime = LocalDateTime.parse(rfcString, DateTimeFormatter.RFC_1123_DATE_TIME); + return dateTime.atZone(ZoneId.of("UTC")).toInstant().toEpochMilli(); + } +} +