|
|
@@ -1,192 +1,210 @@
|
|
|
package me.lethunderhawk.custom.item.concrete.ability;
|
|
|
|
|
|
-import me.lethunderhawk.bazaarflux.service.Services;
|
|
|
import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
|
|
|
import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
|
|
|
import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
|
|
|
-import me.lethunderhawk.main.Main;
|
|
|
-import org.bukkit.Location;
|
|
|
-import org.bukkit.Particle;
|
|
|
-import org.bukkit.Sound;
|
|
|
-import org.bukkit.World;
|
|
|
-import org.bukkit.entity.LivingEntity;
|
|
|
+import org.bukkit.*;
|
|
|
+import org.bukkit.block.Block;
|
|
|
+import org.bukkit.block.BlockFace;
|
|
|
import org.bukkit.entity.Player;
|
|
|
-import org.bukkit.scheduler.BukkitRunnable;
|
|
|
+import org.bukkit.util.RayTraceResult;
|
|
|
import org.bukkit.util.Vector;
|
|
|
|
|
|
public class HyperionAbility implements AbilityHandler {
|
|
|
-
|
|
|
- // Configuration constants
|
|
|
- private static final double DEFAULT_DAMAGE = 5.0;
|
|
|
private static final double DEFAULT_RANGE = 8.0;
|
|
|
- private static final double DEFAULT_DAMAGE_RADIUS = 5.0;
|
|
|
- private static final int DASH_STEPS = 4; // More steps for smoother animation
|
|
|
- private static final double STEP_INTERVAL = 1.0; // Ticks between steps (20ms)
|
|
|
|
|
|
@Override
|
|
|
public void execute(AbilityContext context, ResolvedParams params) {
|
|
|
- double damage = params.getDoubleOrDefault("damage", DEFAULT_DAMAGE);
|
|
|
- double range = params.getDoubleOrDefault("range", DEFAULT_RANGE);
|
|
|
- double damageRadius = params.getDoubleOrDefault("damageRadius", DEFAULT_DAMAGE_RADIUS);
|
|
|
-
|
|
|
Player player = context.player();
|
|
|
+ if (player == null || !player.isOnline()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- Location start = player.getLocation().clone().add(0, player.getEyeHeight(true),0);
|
|
|
- Vector direction = start.getDirection().normalize();
|
|
|
- World world = player.getWorld();
|
|
|
+ double range = params.getDoubleOrDefault("range", DEFAULT_RANGE);
|
|
|
+ Location currentLoc = player.getEyeLocation();
|
|
|
+
|
|
|
+ // Get the direction the player is looking
|
|
|
+ Vector direction = currentLoc.getDirection().normalize();
|
|
|
+
|
|
|
+ // Perform ray trace to find obstructions
|
|
|
+ RayTraceResult rayTraceResult = player.rayTraceBlocks(range, FluidCollisionMode.NEVER);
|
|
|
+
|
|
|
+ Location targetLocation;
|
|
|
+
|
|
|
+ if (rayTraceResult != null && rayTraceResult.getHitBlock() != null) {
|
|
|
+ Block hitBlock = rayTraceResult.getHitBlock();
|
|
|
+ BlockFace hitFace = rayTraceResult.getHitBlockFace();
|
|
|
+ Vector hitPosition = rayTraceResult.getHitPosition();
|
|
|
+
|
|
|
+ // Handle each face differently
|
|
|
+ if (hitFace == BlockFace.UP) {
|
|
|
+ // Top face - stand on top of the block
|
|
|
+ targetLocation = new Location(
|
|
|
+ player.getWorld(),
|
|
|
+ hitBlock.getX() + 0.5, // Center of block in X
|
|
|
+ hitBlock.getY() + 1.0, // Top of block + player height
|
|
|
+ hitBlock.getZ() + 0.5, // Center of block in Z
|
|
|
+ currentLoc.getYaw(),
|
|
|
+ currentLoc.getPitch()
|
|
|
+ );
|
|
|
|
|
|
- // Find the teleport destination - ALWAYS teleport forward
|
|
|
- Location target = findTeleportDestination(start, direction, range);
|
|
|
+ } else if (hitFace == BlockFace.DOWN) {
|
|
|
+ // Bottom face - be below the block
|
|
|
+ targetLocation = new Location(
|
|
|
+ player.getWorld(),
|
|
|
+ hitBlock.getX() + 0.5, // Center of block in X
|
|
|
+ hitBlock.getY() - 1.8, // Head just below block, feet at blockY - 1.8
|
|
|
+ hitBlock.getZ() + 0.5, // Center of block in Z
|
|
|
+ currentLoc.getYaw(),
|
|
|
+ currentLoc.getPitch()
|
|
|
+ );
|
|
|
|
|
|
- // Preserve player's original orientation
|
|
|
- target.setYaw(start.getYaw());
|
|
|
- target.setPitch(start.getPitch());
|
|
|
+ } else {
|
|
|
+ // Side faces - use the exact hit position and adjust away from face
|
|
|
+ targetLocation = new Location(
|
|
|
+ player.getWorld(),
|
|
|
+ hitPosition.getX(),
|
|
|
+ hitPosition.getY(),
|
|
|
+ hitPosition.getZ(),
|
|
|
+ currentLoc.getYaw(),
|
|
|
+ currentLoc.getPitch()
|
|
|
+ );
|
|
|
|
|
|
- // Perform smooth dash animation
|
|
|
- smoothDashTeleport(player, start, target, () -> {
|
|
|
- playExplosionEffect(world, target);
|
|
|
- damageNearbyEntities(player, target, damageRadius, damage);
|
|
|
- });
|
|
|
- }
|
|
|
+ // Move away from the face
|
|
|
+ Vector faceDirection = hitFace.getDirection().normalize();
|
|
|
+ targetLocation.add(faceDirection.multiply(0.3));
|
|
|
|
|
|
- private Location findTeleportDestination(Location start, Vector direction, double maxRange) {
|
|
|
- // First, try to find a solid block in front of the player
|
|
|
- Location hitLocation = findSolidBlockHit(start, direction, maxRange);
|
|
|
+ // Find ground level for side teleports
|
|
|
+ int groundY = player.getWorld().getHighestBlockYAt(
|
|
|
+ targetLocation.getBlockX(),
|
|
|
+ targetLocation.getBlockZ()
|
|
|
+ );
|
|
|
+ if (groundY > player.getWorld().getMinHeight()) {
|
|
|
+ targetLocation.setY(groundY + 1.0);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (hitLocation != null) {
|
|
|
- // We hit a block, teleport to just in front of it
|
|
|
- return getPositionInFrontOfBlock(hitLocation, direction);
|
|
|
+ // DEBUG
|
|
|
+ player.sendMessage("§eFace: " + hitFace +
|
|
|
+ " | Block: " + hitBlock.getX() + "," + hitBlock.getY() + "," + hitBlock.getZ() +
|
|
|
+ " | Teleport: " + String.format("%.2f,%.2f,%.2f",
|
|
|
+ targetLocation.getX(), targetLocation.getY(), targetLocation.getZ()));
|
|
|
} else {
|
|
|
- // No block hit, teleport to max range
|
|
|
- return start.clone().add(direction.clone().multiply(maxRange));
|
|
|
+ // No obstruction - teleport exactly in the direction the player is looking
|
|
|
+ // Calculate the exact target position
|
|
|
+ targetLocation = calculateExactTarget(currentLoc, direction, range);
|
|
|
+ targetLocation = currentLoc;
|
|
|
+ // DEBUG: Remove this after testing
|
|
|
+ player.sendMessage("§eTeleporting into air/direction, no block hit");
|
|
|
}
|
|
|
- }
|
|
|
- private Location findSolidBlockHit(Location start, Vector direction, double maxRange) {
|
|
|
- final double STEP = 0.2;
|
|
|
|
|
|
- for (double distance = STEP; distance <= maxRange; distance += STEP) {
|
|
|
- Location check = start.clone().add(direction.clone().multiply(distance));
|
|
|
+ // Perform the teleportation
|
|
|
+ boolean teleported = player.teleport(targetLocation);
|
|
|
|
|
|
- // Check if this location contains a solid block
|
|
|
- if (check.getBlock().getType().isSolid()) {
|
|
|
- return check;
|
|
|
- }
|
|
|
- }
|
|
|
+ if (teleported) {
|
|
|
+ // Optional effects
|
|
|
+ player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1.0f, 1.0f);
|
|
|
|
|
|
- return null; // No solid block found
|
|
|
+ // Particle effects
|
|
|
+ spawnTeleportParticles(currentLoc, player.getWorld());
|
|
|
+ spawnTeleportParticles(targetLocation, player.getWorld());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private Location getPositionInFrontOfBlock(Location blockHit, Vector direction) {
|
|
|
- // Move back slightly from the hit block to be in front of it
|
|
|
- // We use a small offset to ensure we're not inside the block
|
|
|
- double offset = 0.3;
|
|
|
-
|
|
|
- // Reverse the direction slightly
|
|
|
- Vector reverseDir = direction.clone().multiply(-offset);
|
|
|
-
|
|
|
- // Return position just in front of the block
|
|
|
- return blockHit.clone().add(reverseDir);
|
|
|
+ // Calculate exact target position when no block is hit
|
|
|
+ private Location calculateExactTarget(Location startLoc, Vector direction, double range) {
|
|
|
+ // Calculate the target position
|
|
|
+ double targetX = startLoc.getX() + (direction.getX() * range);
|
|
|
+ double targetY = startLoc.getY() + (direction.getY() * range);
|
|
|
+ double targetZ = startLoc.getZ() + (direction.getZ() * range);
|
|
|
+
|
|
|
+ return new Location(
|
|
|
+ startLoc.getWorld(),
|
|
|
+ targetX,
|
|
|
+ targetY,
|
|
|
+ targetZ,
|
|
|
+ startLoc.getYaw(),
|
|
|
+ startLoc.getPitch()
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- private void smoothDashTeleport(Player player, Location start, Location target, Runnable onComplete) {
|
|
|
- Vector startVec = start.toVector();
|
|
|
- Vector targetVec = target.toVector();
|
|
|
- Vector totalMovement = targetVec.clone().subtract(startVec);
|
|
|
-
|
|
|
- // Calculate movement per step
|
|
|
- Vector stepMovement = totalMovement.clone().multiply(1.0 / DASH_STEPS);
|
|
|
-
|
|
|
- // Store player's original look direction
|
|
|
- float originalYaw = start.getYaw();
|
|
|
- float originalPitch = start.getPitch();
|
|
|
-
|
|
|
- new BukkitRunnable() {
|
|
|
- int currentStep = 0;
|
|
|
- Location currentLocation = start.clone();
|
|
|
-
|
|
|
- @Override
|
|
|
- public void run() {
|
|
|
- if (currentStep > DASH_STEPS) {
|
|
|
- // Final teleport to exact target
|
|
|
- Location finalLocation = target.clone();
|
|
|
- finalLocation.setYaw(originalYaw);
|
|
|
- finalLocation.setPitch(originalPitch);
|
|
|
-
|
|
|
- player.teleport(finalLocation);
|
|
|
- onComplete.run();
|
|
|
- cancel();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Calculate next position
|
|
|
- Vector nextVec = startVec.clone().add(stepMovement.clone().multiply(currentStep));
|
|
|
- Location nextLocation = new Location(
|
|
|
- start.getWorld(),
|
|
|
- nextVec.getX(),
|
|
|
- nextVec.getY(),
|
|
|
- nextVec.getZ(),
|
|
|
- originalYaw,
|
|
|
- originalPitch
|
|
|
- );
|
|
|
+ // Helper method to check if a location is safe
|
|
|
+ private boolean isLocationSafe(Location location) {
|
|
|
+ World world = location.getWorld();
|
|
|
+ if (world == null) return false;
|
|
|
|
|
|
- // Teleport to next position
|
|
|
- player.teleport(nextLocation);
|
|
|
- currentLocation = nextLocation.clone();
|
|
|
-
|
|
|
- // Visual and sound effects during dash
|
|
|
- player.getWorld().spawnParticle(
|
|
|
- Particle.ELECTRIC_SPARK,
|
|
|
- nextLocation,
|
|
|
- 3, 0.1, 0.1, 0.1, 0
|
|
|
- );
|
|
|
+ int x = location.getBlockX();
|
|
|
+ int y = (int) Math.floor(location.getY());
|
|
|
+ int z = location.getBlockZ();
|
|
|
|
|
|
- if (currentStep % 2 == 0) {
|
|
|
- player.getWorld().playSound(
|
|
|
- nextLocation,
|
|
|
- Sound.BLOCK_BEACON_AMBIENT,
|
|
|
- 0.3f, 2.0f
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- currentStep++;
|
|
|
+ // Check the two blocks where player would be (feet and head)
|
|
|
+ for (int i = 0; i <= 1; i++) {
|
|
|
+ Material block = world.getBlockAt(x, y + i, z).getType();
|
|
|
+ if (block.isSolid() && !isPassable(block)) {
|
|
|
+ return false;
|
|
|
}
|
|
|
- }.runTaskTimer(Services.get(Main.class), 0L, (long) STEP_INTERVAL);
|
|
|
- }
|
|
|
-
|
|
|
- private void playExplosionEffect(World world, Location loc) {
|
|
|
- world.spawnParticle(Particle.EXPLOSION_EMITTER, loc, 1);
|
|
|
- world.spawnParticle(Particle.EXPLOSION, loc, 30, 0.5, 0.5, 0.5, 0.05);
|
|
|
- world.playSound(loc, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 1.0f);
|
|
|
- //world.spawnParticle(Particle.FLASH, loc, 1);
|
|
|
-
|
|
|
- // Create a ring of smoke particles
|
|
|
- for (int i = 0; i < 12; i++) {
|
|
|
- double angle = 2 * Math.PI * i / 12;
|
|
|
- double x = Math.cos(angle) * 0.5;
|
|
|
- double z = Math.sin(angle) * 0.5;
|
|
|
- world.spawnParticle(Particle.SMOKE,
|
|
|
- loc.getX(), loc.getY(), loc.getZ(),
|
|
|
- 0, x, 0.2, z, 0.1);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- private void damageNearbyEntities(Player source, Location center, double radius, double damage) {
|
|
|
- double radiusSquared = radius * radius;
|
|
|
|
|
|
- for (LivingEntity entity : center.getWorld().getNearbyLivingEntities(center, radius, radius, radius)) {
|
|
|
- if (entity.equals(source)) continue;
|
|
|
- if (entity.isDead()) continue;
|
|
|
+ // Check if there's something to stand on (not required for air teleportation)
|
|
|
+ // But good to have for when we're near ground
|
|
|
+ Material groundBlock = world.getBlockAt(x, y - 1, z).getType();
|
|
|
+ return !groundBlock.isSolid() || isPassable(groundBlock);
|
|
|
+ }
|
|
|
|
|
|
- if (entity.getLocation().distanceSquared(center) <= radiusSquared) {
|
|
|
- entity.damage(damage, source);
|
|
|
+ // Check if a material is passable (like water, lava, air)
|
|
|
+ private boolean isPassable(Material material) {
|
|
|
+ return material == Material.AIR ||
|
|
|
+ material == Material.WATER ||
|
|
|
+ material == Material.LAVA ||
|
|
|
+ material == Material.CAVE_AIR ||
|
|
|
+ material == Material.VOID_AIR ||
|
|
|
+ material.name().endsWith("_PLANT") ||
|
|
|
+ material.name().contains("VINE");
|
|
|
+ }
|
|
|
|
|
|
- // Add a small knockback effect
|
|
|
- Vector knockback = entity.getLocation().toVector()
|
|
|
- .subtract(center.toVector())
|
|
|
- .normalize()
|
|
|
- .multiply(0.5);
|
|
|
- entity.setVelocity(knockback);
|
|
|
+ // Find safe location near target
|
|
|
+ private Location findSafeLocation(Location target) {
|
|
|
+ World world = target.getWorld();
|
|
|
+ if (world == null) return null;
|
|
|
+
|
|
|
+ int x = target.getBlockX();
|
|
|
+ int y = target.getBlockY();
|
|
|
+ int z = target.getBlockZ();
|
|
|
+
|
|
|
+ // Search in expanding circles around the target
|
|
|
+ for (int radius = 0; radius <= 3; radius++) {
|
|
|
+ for (int dx = -radius; dx <= radius; dx++) {
|
|
|
+ for (int dz = -radius; dz <= radius; dz++) {
|
|
|
+ // Skip positions outside the current radius
|
|
|
+ if (Math.abs(dx) < radius && Math.abs(dz) < radius) continue;
|
|
|
+
|
|
|
+ // Try different Y levels
|
|
|
+ for (int dy = -2; dy <= 2; dy++) {
|
|
|
+ Location testLoc = new Location(world, x + dx, y + dy, z + dz);
|
|
|
+ testLoc.setPitch(target.getPitch());
|
|
|
+ testLoc.setYaw(target.getYaw());
|
|
|
+
|
|
|
+ if (isLocationSafe(testLoc)) {
|
|
|
+ return testLoc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Spawn teleport particles
|
|
|
+ private void spawnTeleportParticles(Location location, World world) {
|
|
|
+ if (location == null || world == null) return;
|
|
|
+
|
|
|
+ world.spawnParticle(
|
|
|
+ Particle.PORTAL,
|
|
|
+ location.clone().add(0, 1, 0),
|
|
|
+ 30,
|
|
|
+ 0.5, 1, 0.5,
|
|
|
+ 0.5
|
|
|
+ );
|
|
|
}
|
|
|
-}
|
|
|
+}
|