Stage 12: Data storage system

This commit is contained in:
loki5512344 2026-05-31 20:47:02 +02:00
parent 82ed455cd2
commit 4fd7955d56
62 changed files with 5613 additions and 0 deletions

View file

@ -0,0 +1,99 @@
package dev.loki.killer;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
public class HitValidator {
private static final double ATTACK_ANGLE = 60.0;
private static final double MAX_VERTICAL_ANGLE = 45.0;
public static boolean isValidHit(Player killer, Player target, double maxRange) {
if (!target.isOnline() || target.isDead()) {
return false;
}
Location killerLoc = killer.getEyeLocation();
Location targetLoc = target.getEyeLocation();
double distance = killerLoc.distance(targetLoc);
if (distance > maxRange) {
return false;
}
if (!hasLineOfSight(killer, target)) {
return false;
}
return isWithinAttackAngle(killer, targetLoc);
}
private static boolean hasLineOfSight(Player from, Player to) {
Location eyeLoc = from.getEyeLocation();
Vector direction = to.getEyeLocation().toVector().subtract(eyeLoc.toVector());
double distance = direction.length();
direction.normalize();
RayTraceResult result = from.getWorld().rayTraceBlocks(
eyeLoc,
direction,
distance,
org.bukkit.FluidCollisionMode.NEVER,
true
);
return result == null || result.getHitBlock() == null;
}
private static boolean isWithinAttackAngle(Player killer, Location target) {
Vector killerDir = killer.getEyeLocation().getDirection();
Vector toTarget = target.toVector().subtract(killer.getEyeLocation().toVector());
double horizontalAngle = Math.toDegrees(killerDir.clone().setY(0).angle(toTarget.clone().setY(0)));
if (horizontalAngle > ATTACK_ANGLE) {
return false;
}
double verticalDiff = toTarget.getY();
double horizontalDist = Math.sqrt(toTarget.getX() * toTarget.getX() + toTarget.getZ() * toTarget.getZ());
double verticalAngle = Math.toDegrees(Math.atan2(verticalDiff, horizontalDist));
return Math.abs(verticalAngle) <= MAX_VERTICAL_ANGLE;
}
public static boolean isValidCarryPosition(Player killer, Player carried) {
Location kLoc = killer.getLocation();
Location cLoc = carried.getLocation();
double horizontalDist = Math.sqrt(
Math.pow(kLoc.getX() - cLoc.getX(), 2) +
Math.pow(kLoc.getZ() - cLoc.getZ(), 2)
);
double yDiff = Math.abs(kLoc.getY() - cLoc.getY());
return horizontalDist <= 2.0 && yDiff <= 1.5;
}
public static boolean hasClearPath(Player from, Location to) {
Location eyeLoc = from.getEyeLocation();
Vector direction = to.toVector().subtract(eyeLoc.toVector());
double distance = direction.length();
direction.normalize();
RayTraceResult result = from.getWorld().rayTraceBlocks(
eyeLoc,
direction,
distance,
org.bukkit.FluidCollisionMode.NEVER,
true
);
return result == null || result.getHitBlock() == null;
}
}

View file

@ -0,0 +1,126 @@
package dev.loki.killer;
import dev.loki.config.GameConfig;
import dev.loki.damage.DamageHandler;
import dev.loki.player.Killer;
import dev.loki.player.PlayerManager;
import dev.loki.player.Survivor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class KillerAttack {
private final Plugin plugin;
private final PlayerManager playerManager;
private final DamageHandler damageHandler;
private final GameConfig config;
private final Map<UUID, Long> cooldowns;
private BukkitTask cleanupTask;
public KillerAttack(Plugin plugin, PlayerManager playerManager,
DamageHandler damageHandler, GameConfig config) {
this.plugin = plugin;
this.playerManager = playerManager;
this.damageHandler = damageHandler;
this.config = config;
this.cooldowns = new HashMap<>();
startCleanupTask();
}
public boolean tryAttack(Killer killer, Survivor target) {
if (!canAttack(killer)) {
return false;
}
Player killerPlayer = killer.getPlayer();
Player targetPlayer = target.getPlayer();
if (killerPlayer == null || targetPlayer == null) return false;
if (!targetPlayer.isOnline()) return false;
if (!HitValidator.isValidHit(killerPlayer, targetPlayer, config.getAttackRange())) {
return false;
}
if (!damageHandler.applyDamage(target)) {
return false;
}
applyCooldown(killer);
playAttackEffects(killerPlayer, targetPlayer);
return true;
}
public boolean canAttack(Killer killer) {
Long lastAttack = cooldowns.get(killer.getUuid());
if (lastAttack == null) return true;
int cooldownTicks = config.getAttackCooldown();
long currentTick = Bukkit.getCurrentTick();
return currentTick - lastAttack >= cooldownTicks;
}
private void applyCooldown(Killer killer) {
cooldowns.put(killer.getUuid(), Bukkit.getCurrentTick());
}
public double getCooldownProgress(Killer killer) {
Long lastAttack = cooldowns.get(killer.getUuid());
if (lastAttack == null) return 1.0;
int cooldownTicks = config.getAttackCooldown();
long currentTick = Bukkit.getCurrentTick();
long elapsed = currentTick - lastAttack;
return Math.min(1.0, (double) elapsed / cooldownTicks);
}
public boolean isOnCooldown(Killer killer) {
return !canAttack(killer);
}
private void playAttackEffects(Player killer, Player target) {
killer.getWorld().playSound(killer.getLocation(),
org.bukkit.Sound.ENTITY_PLAYER_ATTACK_STRONG, 1.0f, 0.8f);
target.getWorld().playSound(target.getLocation(),
org.bukkit.Sound.ENTITY_PLAYER_HURT, 1.0f, 1.0f);
org.bukkit.Particle particle = target.getHealth() <= 4 ?
org.bukkit.Particle.DAMAGE_INDICATOR : org.bukkit.Particle.CRIT;
target.getWorld().spawnParticle(particle, target.getLocation().add(0, 1, 0),
10, 0.3, 0.5, 0.3, 0.1);
}
public void attackNearest(Killer killer) {
Player killerPlayer = killer.getPlayer();
if (killerPlayer == null) return;
Survivor nearest = playerManager.getNearestSurvivor(killerPlayer, config.getAttackRange());
if (nearest != null) {
tryAttack(killer, nearest);
}
}
private void startCleanupTask() {
cleanupTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
long currentTick = Bukkit.getCurrentTick();
int cooldownTicks = config.getAttackCooldown();
cooldowns.entrySet().removeIf(e -> currentTick - e.getValue() > cooldownTicks * 2);
}, 1200L, 1200L);
}
public void shutdown() {
if (cleanupTask != null) {
cleanupTask.cancel();
}
cooldowns.clear();
}
}

View file

@ -0,0 +1,189 @@
package dev.loki.killer;
import dev.loki.config.GameConfig;
import dev.loki.player.Killer;
import dev.loki.player.Survivor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class KillerCarry {
private final Plugin plugin;
private final GameConfig config;
private final Map<UUID, CarryData> activeCarries;
private BukkitTask carryTask;
private static class CarryData {
final Killer killer;
final Survivor survivor;
double wiggleProgress;
CarryData(Killer killer, Survivor survivor) {
this.killer = killer;
this.survivor = survivor;
this.wiggleProgress = 0;
}
}
public KillerCarry(Plugin plugin, GameConfig config) {
this.plugin = plugin;
this.config = config;
this.activeCarries = new HashMap<>();
}
public boolean tryPickup(Killer killer, Survivor survivor) {
if (!canPickup(killer, survivor)) {
return false;
}
Player killerPlayer = killer.getPlayer();
Player survivorPlayer = survivor.getPlayer();
if (killerPlayer == null || survivorPlayer == null) return false;
if (!HitValidator.isValidCarryPosition(killerPlayer, survivorPlayer)) {
return false;
}
startCarry(killer, survivor);
return true;
}
public boolean canPickup(Killer killer, Survivor survivor) {
if (killer.isCarrying()) return false;
if (!survivor.getState().name().equals("DOWNED")) return false;
Player killerPlayer = killer.getPlayer();
Player survivorPlayer = survivor.getPlayer();
return killerPlayer != null && survivorPlayer != null && survivorPlayer.isOnline();
}
private void startCarry(Killer killer, Survivor survivor) {
killer.setCarrying(true);
Player survivorPlayer = survivor.getPlayer();
if (survivorPlayer != null) {
survivorPlayer.setInvisible(true);
}
CarryData data = new CarryData(killer, survivor);
activeCarries.put(killer.getUuid(), data);
if (carryTask == null) {
startCarryTask();
}
}
public boolean drop(Killer killer) {
CarryData data = activeCarries.remove(killer.getUuid());
if (data == null) return false;
stopCarry(data);
return true;
}
private void stopCarry(CarryData data) {
data.killer.setCarrying(false);
Player survivorPlayer = data.survivor.getPlayer();
if (survivorPlayer != null) {
survivorPlayer.setInvisible(false);
survivorPlayer.teleport(data.killer.getPlayer().getLocation());
}
if (activeCarries.isEmpty() && carryTask != null) {
carryTask.cancel();
carryTask = null;
}
}
private void startCarryTask() {
carryTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
activeCarries.values().removeIf(data -> {
if (!isValidCarry(data)) {
stopCarry(data);
return true;
}
updateCarryPosition(data);
updateWiggle(data);
return false;
});
}, 1L, 1L);
}
private boolean isValidCarry(CarryData data) {
Player killerPlayer = data.killer.getPlayer();
Player survivorPlayer = data.survivor.getPlayer();
if (killerPlayer == null || survivorPlayer == null) return false;
if (!killerPlayer.isOnline() || !survivorPlayer.isOnline()) return false;
return true;
}
private void updateCarryPosition(CarryData data) {
Player killerPlayer = data.killer.getPlayer();
Player survivorPlayer = data.survivor.getPlayer();
Location carryLoc = killerPlayer.getLocation().clone().add(0, -0.5, 0);
survivorPlayer.teleport(carryLoc);
}
private void updateWiggle(CarryData data) {
Player survivorPlayer = data.survivor.getPlayer();
if (survivorPlayer == null) return;
if (survivorPlayer.isSneaking()) {
data.wiggleProgress += 1.5;
}
if (data.wiggleProgress >= 100) {
forceDrop(data.killer);
}
}
public void forceDrop(Killer killer) {
CarryData data = activeCarries.remove(killer.getUuid());
if (data == null) return;
Player survivorPlayer = data.survivor.getPlayer();
if (survivorPlayer != null) {
Location dropLoc = survivorPlayer.getLocation();
dropLoc.add(killer.getPlayer().getLocation().getDirection().multiply(1.5));
survivorPlayer.teleport(dropLoc);
}
stopCarry(data);
}
public boolean isCarrying(Killer killer) {
return activeCarries.containsKey(killer.getUuid());
}
public Survivor getCarried(Killer killer) {
CarryData data = activeCarries.get(killer.getUuid());
return data != null ? data.survivor : null;
}
public double getWiggleProgress(Killer killer) {
CarryData data = activeCarries.get(killer.getUuid());
return data != null ? data.wiggleProgress : 0;
}
public void shutdown() {
if (carryTask != null) {
carryTask.cancel();
}
activeCarries.values().forEach(this::stopCarry);
activeCarries.clear();
}
}

View file

@ -0,0 +1,126 @@
package dev.loki.killer;
import dev.loki.config.GameConfig;
import dev.loki.damage.AttackListener;
import dev.loki.damage.DamageHandler;
import dev.loki.player.Killer;
import dev.loki.player.PlayerManager;
import dev.loki.player.Survivor;
import org.bukkit.plugin.Plugin;
public class KillerController {
private final Plugin plugin;
private final PlayerManager playerManager;
private final DamageHandler damageHandler;
private final GameConfig config;
private final KillerAttack attack;
private final KillerCarry carry;
private final KillerMovement movement;
private AttackListener attackListener;
public KillerController(Plugin plugin, PlayerManager playerManager,
DamageHandler damageHandler, GameConfig config) {
this.plugin = plugin;
this.playerManager = playerManager;
this.damageHandler = damageHandler;
this.config = config;
this.attack = new KillerAttack(plugin, playerManager, damageHandler, config);
this.carry = new KillerCarry(plugin, config);
this.movement = new KillerMovement(plugin, config);
}
public void initialize() {
attackListener = new AttackListener(playerManager, damageHandler,
(killer, survivor) -> tryAttack(killer, survivor));
attackListener.register(plugin);
for (Killer killer : playerManager.getKillers()) {
movement.startTracking(killer);
}
}
public void shutdown() {
if (attackListener != null) {
attackListener.unregister();
}
attack.shutdown();
carry.shutdown();
movement.shutdown();
}
public boolean tryAttack(Killer killer, Survivor target) {
boolean success = attack.tryAttack(killer, target);
if (success) {
movement.applyAttackSlowdown(killer);
}
return success;
}
public boolean tryPickup(Killer killer, Survivor target) {
boolean success = carry.tryPickup(killer, target);
if (success) {
movement.updateSpeed(killer);
}
return success;
}
public boolean tryDrop(Killer killer) {
boolean success = carry.drop(killer);
if (success) {
movement.updateSpeed(killer);
}
return success;
}
public void onKillerJoin(Killer killer) {
movement.startTracking(killer);
}
public void onKillerLeave(Killer killer) {
movement.stopTracking(killer);
carry.drop(killer);
}
public void registerKiller(Killer killer) {
movement.startTracking(killer);
}
public void unregisterKiller(Killer killer) {
carry.drop(killer);
movement.stopTracking(killer);
}
public boolean isOnCooldown(Killer killer) {
return attack.isOnCooldown(killer);
}
public double getCooldownProgress(Killer killer) {
return attack.getCooldownProgress(killer);
}
public boolean isCarrying(Killer killer) {
return carry.isCarrying(killer);
}
public Survivor getCarried(Killer killer) {
return carry.getCarried(killer);
}
public double getWiggleProgress(Killer killer) {
return carry.getWiggleProgress(killer);
}
public KillerAttack getAttack() {
return attack;
}
public KillerCarry getCarry() {
return carry;
}
public KillerMovement getMovement() {
return movement;
}
}

View file

@ -0,0 +1,119 @@
package dev.loki.killer;
import dev.loki.config.GameConfig;
import dev.loki.player.Killer;
import org.bukkit.Bukkit;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class KillerMovement {
private final Plugin plugin;
private final GameConfig config;
private final Map<UUID, BukkitTask> slowdownTasks;
private BukkitTask updateTask;
private static final double BASE_SPEED = 0.1;
public KillerMovement(Plugin plugin, GameConfig config) {
this.plugin = plugin;
this.config = config;
this.slowdownTasks = new HashMap<>();
}
public void startTracking(Killer killer) {
if (updateTask == null) {
startUpdateTask();
}
updateSpeed(killer);
}
public void stopTracking(Killer killer) {
BukkitTask task = slowdownTasks.remove(killer.getUuid());
if (task != null) {
task.cancel();
}
Player player = killer.getPlayer();
if (player != null) {
resetSpeed(player);
}
}
private void startUpdateTask() {
updateTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
}, 20L, 20L);
}
public void updateSpeed(Killer killer) {
Player player = killer.getPlayer();
if (player == null || !player.isOnline()) return;
double multiplier = killer.isCarrying() ?
config.getKillerCarrySpeed() : config.getKillerSpeed();
applySpeed(player, multiplier);
}
private void applySpeed(Player player, double multiplier) {
AttributeInstance speedAttr = player.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
if (speedAttr == null) return;
double newSpeed = BASE_SPEED * multiplier;
speedAttr.setBaseValue(newSpeed);
}
private void resetSpeed(Player player) {
AttributeInstance speedAttr = player.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
if (speedAttr == null) return;
speedAttr.setBaseValue(BASE_SPEED);
}
public void applyTemporarySlowdown(Killer killer, double multiplier, int ticks) {
Player player = killer.getPlayer();
if (player == null) return;
cancelExistingSlowdown(killer);
BukkitTask task = Bukkit.getScheduler().runTaskLater(plugin, () -> {
slowdownTasks.remove(killer.getUuid());
updateSpeed(killer);
}, ticks);
slowdownTasks.put(killer.getUuid(), task);
applySpeed(player, multiplier);
}
private void cancelExistingSlowdown(Killer killer) {
BukkitTask task = slowdownTasks.remove(killer.getUuid());
if (task != null) {
task.cancel();
}
}
public void applyAttackSlowdown(Killer killer) {
applyTemporarySlowdown(killer, 0.85, 30);
}
public double getSpeedMultiplier(Killer killer) {
if (slowdownTasks.containsKey(killer.getUuid())) {
return 0.85;
}
return killer.isCarrying() ? config.getKillerCarrySpeed() : config.getKillerSpeed();
}
public void shutdown() {
if (updateTask != null) {
updateTask.cancel();
}
slowdownTasks.values().forEach(BukkitTask::cancel);
slowdownTasks.clear();
}
}