From d1f854fc7957731f843eeef4897addd2db336e55 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sun, 31 May 2026 20:49:28 +0200 Subject: [PATCH] Stage 2-3: Player states and Killer system --- src/main/java/dev/loki/perk/BasePerk.java | 79 +++++++++++ src/main/java/dev/loki/perk/PerkRegistry.java | 60 +++++++++ .../dev/loki/perk/killer/BBQChiliPerk.java | 105 +++++++++++++++ .../perk/killer/CorruptInterventionPerk.java | 97 +++++++++++++ .../dev/loki/perk/killer/HexRuinPerk.java | 94 +++++++++++++ .../loki/perk/survivor/BorrowedTimePerk.java | 76 +++++++++++ .../dev/loki/perk/survivor/DeadHardPerk.java | 76 +++++++++++ .../dev/loki/perk/survivor/SelfCarePerk.java | 53 ++++++++ .../loki/perk/survivor/SprintBurstPerk.java | 80 +++++++++++ src/main/java/dev/loki/totem/DullTotem.java | 52 +++++++ src/main/java/dev/loki/totem/HexTotem.java | 86 ++++++++++++ src/main/java/dev/loki/totem/Totem.java | 86 ++++++++++++ .../java/dev/loki/totem/TotemManager.java | 127 ++++++++++++++++++ src/main/java/dev/loki/totem/TotemState.java | 24 ++++ 14 files changed, 1095 insertions(+) create mode 100644 src/main/java/dev/loki/perk/BasePerk.java create mode 100644 src/main/java/dev/loki/perk/PerkRegistry.java create mode 100644 src/main/java/dev/loki/perk/killer/BBQChiliPerk.java create mode 100644 src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java create mode 100644 src/main/java/dev/loki/perk/killer/HexRuinPerk.java create mode 100644 src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java create mode 100644 src/main/java/dev/loki/perk/survivor/DeadHardPerk.java create mode 100644 src/main/java/dev/loki/perk/survivor/SelfCarePerk.java create mode 100644 src/main/java/dev/loki/perk/survivor/SprintBurstPerk.java create mode 100644 src/main/java/dev/loki/totem/DullTotem.java create mode 100644 src/main/java/dev/loki/totem/HexTotem.java create mode 100644 src/main/java/dev/loki/totem/Totem.java create mode 100644 src/main/java/dev/loki/totem/TotemManager.java create mode 100644 src/main/java/dev/loki/totem/TotemState.java diff --git a/src/main/java/dev/loki/perk/BasePerk.java b/src/main/java/dev/loki/perk/BasePerk.java new file mode 100644 index 0000000..2dc56f7 --- /dev/null +++ b/src/main/java/dev/loki/perk/BasePerk.java @@ -0,0 +1,79 @@ +package dev.loki.perk; + +import dev.loki.player.BasePlayer; +import dev.loki.mdbd; + +public abstract class BasePerk implements IPerk { + protected final mdbd plugin; + protected final String id; + protected final String name; + protected final String description; + protected final PerkType type; + protected final boolean passive; + protected final int cooldownSeconds; + protected long cooldownEndTime = 0; + + protected BasePerk(mdbd plugin, String id, String name, String description, + PerkType type, boolean passive, int cooldownSeconds) { + this.plugin = plugin; + this.id = id; + this.name = name; + this.description = description; + this.type = type; + this.passive = passive; + this.cooldownSeconds = cooldownSeconds; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public PerkType getType() { + return type; + } + + @Override + public boolean isPassive() { + return passive; + } + + @Override + public int getCooldownSeconds() { + return cooldownSeconds; + } + + @Override + public boolean isOnCooldown() { + return System.currentTimeMillis() < cooldownEndTime; + } + + @Override + public void startCooldown() { + if (cooldownSeconds > 0) { + cooldownEndTime = System.currentTimeMillis() + (cooldownSeconds * 1000L); + } + } + + @Override + public void resetCooldown() { + cooldownEndTime = 0; + } + + protected void sendPerkMessage(BasePlayer player, String message) { + if (player.getPlayer() != null) { + player.getPlayer().sendMessage("§6[" + name + "] §7" + message); + } + } +} diff --git a/src/main/java/dev/loki/perk/PerkRegistry.java b/src/main/java/dev/loki/perk/PerkRegistry.java new file mode 100644 index 0000000..18aebf6 --- /dev/null +++ b/src/main/java/dev/loki/perk/PerkRegistry.java @@ -0,0 +1,60 @@ +package dev.loki.perk; + +import dev.loki.mdbd; +import dev.loki.perk.killer.BBQChiliPerk; +import dev.loki.perk.killer.CorruptInterventionPerk; +import dev.loki.perk.killer.HexRuinPerk; +import dev.loki.perk.survivor.BorrowedTimePerk; +import dev.loki.perk.survivor.DeadHardPerk; +import dev.loki.perk.survivor.SelfCarePerk; +import dev.loki.perk.survivor.SprintBurstPerk; + +import java.util.HashMap; +import java.util.Map; + +public class PerkRegistry { + private final Map perks = new HashMap<>(); + private final mdbd plugin; + + public PerkRegistry(mdbd plugin) { + this.plugin = plugin; + registerDefaultPerks(); + } + + private void registerDefaultPerks() { + register(new SelfCarePerk(plugin)); + register(new DeadHardPerk(plugin)); + register(new SprintBurstPerk(plugin)); + register(new BorrowedTimePerk(plugin)); + register(new HexRuinPerk(plugin)); + register(new CorruptInterventionPerk(plugin)); + register(new BBQChiliPerk(plugin)); + } + + public void register(IPerk perk) { + perks.put(perk.getId().toLowerCase(), perk); + plugin.getLogger().info("Registered perk: " + perk.getName()); + } + + public IPerk getPerk(String id) { + return perks.get(id.toLowerCase()); + } + + public Map getAllPerks() { + return new HashMap<>(perks); + } + + public Map getPerksByType(PerkType type) { + Map result = new HashMap<>(); + for (Map.Entry entry : perks.entrySet()) { + if (entry.getValue().getType() == type) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + public boolean hasPerk(String id) { + return perks.containsKey(id.toLowerCase()); + } +} diff --git a/src/main/java/dev/loki/perk/killer/BBQChiliPerk.java b/src/main/java/dev/loki/perk/killer/BBQChiliPerk.java new file mode 100644 index 0000000..f258132 --- /dev/null +++ b/src/main/java/dev/loki/perk/killer/BBQChiliPerk.java @@ -0,0 +1,105 @@ +package dev.loki.perk.killer; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import dev.loki.player.Survivor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class BBQChiliPerk extends BasePerk { + private static final int REVEAL_DURATION_SECONDS = 4; + private static final int AURA_RANGE = 100; + private static final int BONUS_STACKS = 4; + private final Map hookedPlayers = new HashMap<>(); + private int currentStacks = 0; + + public BBQChiliPerk(mdbd plugin) { + super(plugin, "bbq_chili", "Барбекю и Чили", + "После подвешивания видите ауру всех выживших на 4 секунды", + PerkType.KILLER, true, 0); + } + + @Override + public void onEquip(BasePlayer player) { + currentStacks = 0; + sendPerkMessage(player, "Активировано: ауры видны после подвешивания"); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + if (trigger == PerkTrigger.ON_KILLER_HOOK) { + if (context instanceof Survivor survivor) { + revealSurvivorAuras(player, survivor); + } + } + } + + private void revealSurvivorAuras(BasePlayer killer, Survivor hookedSurvivor) { + Player killerPlayer = killer.getPlayer(); + if (killerPlayer == null) return; + + currentStacks = Math.min(currentStacks + 1, BONUS_STACKS); + hookedPlayers.put(hookedSurvivor.getUuid(), currentStacks); + + Collection allPlayers = Bukkit.getOnlinePlayers(); + for (Player player : allPlayers) { + if (player.equals(killerPlayer)) continue; + if (player.getLocation().distance(killerPlayer.getLocation()) > AURA_RANGE) { + continue; + } + + revealAura(killerPlayer, player); + } + + sendPerkMessage(killer, "§cАуры выживших видны! (x" + currentStacks + " стаков)"); + + new BukkitRunnable() { + @Override + public void run() { + sendPerkMessage(killer, "§7Эффект ауры закончился"); + } + }.runTaskLater(plugin, REVEAL_DURATION_SECONDS * 20L); + } + + private void revealAura(Player killer, Player survivor) { + Location survivorLoc = survivor.getLocation(); + for (int i = 0; i < 3; i++) { + Location particleLoc = survivorLoc.clone().add(0, i * 0.5, 0); + killer.spawnParticle(Particle.GLOW, particleLoc, 5, 0.3, 0.5, 0.3, 0.01); + } + } + + @Override + public void onRemove(BasePlayer player) { + currentStacks = 0; + hookedPlayers.clear(); + sendPerkMessage(player, "Перк деактивирован"); + } + + public int getStacks() { + return currentStacks; + } + + public static int getRevealDuration() { + return REVEAL_DURATION_SECONDS; + } + + public static int getAuraRange() { + return AURA_RANGE; + } + + public static int getMaxStacks() { + return BONUS_STACKS; + } +} diff --git a/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java b/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java new file mode 100644 index 0000000..1988ec8 --- /dev/null +++ b/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java @@ -0,0 +1,97 @@ +package dev.loki.perk.killer; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class CorruptInterventionPerk extends BasePerk { + private static final int BLOCK_DURATION_SECONDS = 120; + private static final int FARTHEST_GENERATORS = 3; + private final Set blockedGenerators = new HashSet<>(); + private boolean effectActive = false; + + public CorruptInterventionPerk(mdbd plugin) { + super(plugin, "corrupt_intervention", "Вмешательство Порчи", + "3 самых дальних генератора от киллера заблокированы на 120 секунд", + PerkType.KILLER, true, 0); + } + + @Override + public void onEquip(BasePlayer player) { + effectActive = true; + sendPerkMessage(player, "§cВмешательство активировано! Дальние генераторы заблокированы"); + startEffectTimer(); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + if (trigger == PerkTrigger.ON_GENERATOR_START && effectActive) { + if (context instanceof UUID genId) { + if (blockedGenerators.contains(genId)) { + preventRepair(player, genId); + } + } + } + } + + private void preventRepair(BasePlayer player, UUID genId) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return; + bukkitPlayer.sendMessage("§cЭтот генератор заблокирован Порчей!"); + spawnBlockEffect(bukkitPlayer); + } + + private void spawnBlockEffect(Player player) { + Location loc = player.getLocation(); + player.getWorld().spawnParticle(Particle.BARRIER, loc.add(0, 1, 0), 5, 0.5, 0.5, 0.5, 0); + } + + private void startEffectTimer() { + new BukkitRunnable() { + @Override + public void run() { + effectActive = false; + blockedGenerators.clear(); + } + }.runTaskLater(plugin, BLOCK_DURATION_SECONDS * 20L); + } + + @Override + public void onRemove(BasePlayer player) { + effectActive = false; + blockedGenerators.clear(); + sendPerkMessage(player, "Перк деактивирован"); + } + + public void blockGenerator(UUID generatorId) { + if (blockedGenerators.size() < FARTHEST_GENERATORS) { + blockedGenerators.add(generatorId); + } + } + + public boolean isGeneratorBlocked(UUID generatorId) { + return effectActive && blockedGenerators.contains(generatorId); + } + + public boolean isEffectActive() { + return effectActive; + } + + public static int getBlockDuration() { + return BLOCK_DURATION_SECONDS; + } + + public static int getFarthestGeneratorCount() { + return FARTHEST_GENERATORS; + } +} diff --git a/src/main/java/dev/loki/perk/killer/HexRuinPerk.java b/src/main/java/dev/loki/perk/killer/HexRuinPerk.java new file mode 100644 index 0000000..e021904 --- /dev/null +++ b/src/main/java/dev/loki/perk/killer/HexRuinPerk.java @@ -0,0 +1,94 @@ +package dev.loki.perk.killer; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class HexRuinPerk extends BasePerk { + private static final double REGRESSION_RATE = 0.25; + private static final double KICK_REGRESSION = 2.5; + private final Map affectedGenerators = new HashMap<>(); + private boolean hexActive = true; + + public HexRuinPerk(mdbd plugin) { + super(plugin, "hex_ruin", "Порча: Губительность", + "Генераторы автоматически теряют прогресс когда не чинятся", + PerkType.KILLER, true, 0); + } + + @Override + public void onEquip(BasePlayer player) { + hexActive = true; + sendPerkMessage(player, "§cПорча активирована! Генераторы регрессируют"); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + switch (trigger) { + case ON_GENERATOR_START -> handleGeneratorStart(player, context); + case ON_GENERATOR_COMPLETE -> handleGeneratorComplete(player, context); + case ON_TOTEM_CLEANSED -> handleTotemCleansed(player); + } + } + + private void handleGeneratorStart(BasePlayer player, Object context) { + if (!hexActive) return; + if (context instanceof UUID genId) { + affectedGenerators.put(genId, 0.0); + spawnHexParticles(player); + } + } + + private void handleGeneratorComplete(BasePlayer player, Object context) { + if (context instanceof UUID genId) { + affectedGenerators.remove(genId); + } + } + + private void handleTotemCleansed(BasePlayer player) { + hexActive = false; + affectedGenerators.clear(); + sendPerkMessage(player, "§cПорча очищена! Губительность деактивирована"); + } + + private void spawnHexParticles(BasePlayer player) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return; + for (int i = 0; i < 5; i++) { + Location loc = bukkitPlayer.getLocation().add( + Math.random() * 4 - 2, + Math.random() * 2, + Math.random() * 4 - 2 + ); + bukkitPlayer.getWorld().spawnParticle(Particle.SOUL_FIRE_FLAME, loc, 3, 0.2, 0.2, 0.2, 0.01); + } + } + + @Override + public void onRemove(BasePlayer player) { + hexActive = false; + affectedGenerators.clear(); + sendPerkMessage(player, "Перк деактивирован"); + } + + public static double getRegressionRate() { + return REGRESSION_RATE; + } + + public static double getKickRegression() { + return KICK_REGRESSION; + } + + public boolean isHexActive() { + return hexActive; + } +} diff --git a/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java b/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java new file mode 100644 index 0000000..a464ed3 --- /dev/null +++ b/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java @@ -0,0 +1,76 @@ +package dev.loki.perk.survivor; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +public class BorrowedTimePerk extends BasePerk { + private static final int ENDURANCE_DURATION_SECONDS = 15; + private static final int SPEED_BOOST_SECONDS = 2; + private static final int SPEED_AMPLIFIER = 1; + + public BorrowedTimePerk(mdbd plugin) { + super(plugin, "borrowed_time", "Заемное Время", + "После снятия с крюка получите 15 сек неуязвимости и рывок скорости", + PerkType.SURVIVOR, true, 0); + } + + @Override + public void onEquip(BasePlayer player) { + sendPerkMessage(player, "Активировано: защита после спасения"); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + if (trigger == PerkTrigger.ON_UNHOOKED) { + applyEndurance(player); + } + } + + private void applyEndurance(BasePlayer player) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return; + + bukkitPlayer.addPotionEffect(new PotionEffect( + PotionEffectType.DAMAGE_RESISTANCE, + ENDURANCE_DURATION_SECONDS * 20, + 4, + false, false, true)); + + bukkitPlayer.addPotionEffect(new PotionEffect( + PotionEffectType.SPEED, + SPEED_BOOST_SECONDS * 20, + SPEED_AMPLIFIER, + false, false, true)); + + sendPerkMessage(player, "§aЗаемное Время активировано! 15 сек защиты"); + + new BukkitRunnable() { + @Override + public void run() { + if (player.getPlayer() != null) { + player.getPlayer().sendMessage("§cЗаемное Время закончилось"); + } + } + }.runTaskLater(plugin, ENDURANCE_DURATION_SECONDS * 20L); + } + + @Override + public void onRemove(BasePlayer player) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer != null) { + bukkitPlayer.removePotionEffect(PotionEffectType.DAMAGE_RESISTANCE); + } + sendPerkMessage(player, "Перк деактивирован"); + } + + public static int getEnduranceDuration() { + return ENDURANCE_DURATION_SECONDS; + } +} diff --git a/src/main/java/dev/loki/perk/survivor/DeadHardPerk.java b/src/main/java/dev/loki/perk/survivor/DeadHardPerk.java new file mode 100644 index 0000000..9095bfa --- /dev/null +++ b/src/main/java/dev/loki/perk/survivor/DeadHardPerk.java @@ -0,0 +1,76 @@ +package dev.loki.perk.survivor; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import dev.loki.player.PlayerState; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +public class DeadHardPerk extends BasePerk { + private static final int DASH_DISTANCE = 4; + private static final int INVULNERABILITY_TICKS = 20; + private static final int EXHAUSTION_SECONDS = 60; + + public DeadHardPerk(mdbd plugin) { + super(plugin, "dead_hard", "Тяжелый Рывок", + "При ранении активируйте для рывка и неуязвимости", + PerkType.SURVIVOR, false, EXHAUSTION_SECONDS); + } + + @Override + public void onEquip(BasePlayer player) { + sendPerkMessage(player, "Готов к использованию"); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + if (trigger == PerkTrigger.ON_KILLER_HIT && !isOnCooldown()) { + if (shouldActivate(player)) { + performDash(player); + } + } + } + + private boolean shouldActivate(BasePlayer player) { + return player.getState() == PlayerState.INJURED; + } + + private void performDash(BasePlayer player) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return; + + Vector direction = bukkitPlayer.getLocation().getDirection(); + direction.setY(0).normalize().multiply(DASH_DISTANCE); + + Location target = bukkitPlayer.getLocation().add(direction); + bukkitPlayer.teleport(target); + bukkitPlayer.getWorld().spawnParticle(Particle.CLOUD, bukkitPlayer.getLocation(), 10); + + startCooldown(); + sendPerkMessage(player, "Рывок выполнен!"); + + new BukkitRunnable() { + @Override + public void run() { + if (player.getPlayer() != null) { + player.getPlayer().sendMessage("§aТяжелый Рывок готов к использованию!"); + } + } + }.runTaskLater(plugin, cooldownSeconds * 20L); + } + + @Override + public void onRemove(BasePlayer player) { + sendPerkMessage(player, "Перк деактивирован"); + } + + public static int getInvulnerabilityTicks() { + return INVULNERABILITY_TICKS; + } +} diff --git a/src/main/java/dev/loki/perk/survivor/SelfCarePerk.java b/src/main/java/dev/loki/perk/survivor/SelfCarePerk.java new file mode 100644 index 0000000..7780cd7 --- /dev/null +++ b/src/main/java/dev/loki/perk/survivor/SelfCarePerk.java @@ -0,0 +1,53 @@ +package dev.loki.perk.survivor; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import dev.loki.player.PlayerState; + +public class SelfCarePerk extends BasePerk { + private static final double HEAL_RATE = 0.5; + private static final int SELF_HEAL_TIME = 24; + + public SelfCarePerk(mdbd plugin) { + super(plugin, "self_care", "Самолечение", + "Позволяет лечить себя без аптечки на 50% скорости", + PerkType.SURVIVOR, true, 0); + } + + @Override + public void onEquip(BasePlayer player) { + sendPerkMessage(player, "Активировано: вы можете лечить себя без аптечки"); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + if (trigger == PerkTrigger.ON_HEAL_START) { + handleSelfHeal(player, context); + } + } + + private void handleSelfHeal(BasePlayer player, Object context) { + if (player.getState() != PlayerState.INJURED) { + return; + } + if (context instanceof Boolean && (Boolean) context) { + sendPerkMessage(player, "Вы начали лечить себя..."); + } + } + + @Override + public void onRemove(BasePlayer player) { + sendPerkMessage(player, "Перк деактивирован"); + } + + public static double getHealRate() { + return HEAL_RATE; + } + + public static int getSelfHealTime() { + return SELF_HEAL_TIME; + } +} diff --git a/src/main/java/dev/loki/perk/survivor/SprintBurstPerk.java b/src/main/java/dev/loki/perk/survivor/SprintBurstPerk.java new file mode 100644 index 0000000..cc26397 --- /dev/null +++ b/src/main/java/dev/loki/perk/survivor/SprintBurstPerk.java @@ -0,0 +1,80 @@ +package dev.loki.perk.survivor; + +import dev.loki.mdbd; +import dev.loki.perk.BasePerk; +import dev.loki.perk.PerkTrigger; +import dev.loki.perk.PerkType; +import dev.loki.player.BasePlayer; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +public class SprintBurstPerk extends BasePerk { + private static final int SPRINT_DURATION_SECONDS = 3; + private static final int EXHAUSTION_SECONDS = 40; + private static final int SPEED_AMPLIFIER = 2; + + public SprintBurstPerk(mdbd plugin) { + super(plugin, "sprint_burst", "Рывок Скорости", + "При старте бега получите 150% скорости на 3 секунды", + PerkType.SURVIVOR, false, EXHAUSTION_SECONDS); + } + + @Override + public void onEquip(BasePlayer player) { + sendPerkMessage(player, "Готов к использованию"); + } + + @Override + public void onTrigger(BasePlayer player, PerkTrigger trigger, Object context) { + if (trigger == PerkTrigger.ON_SPRINT_START && !isOnCooldown()) { + activateSprint(player); + } + } + + private void activateSprint(BasePlayer player) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return; + + bukkitPlayer.addPotionEffect(new PotionEffect( + PotionEffectType.SPEED, + SPRINT_DURATION_SECONDS * 20, + SPEED_AMPLIFIER, + false, false, true)); + + startCooldown(); + sendPerkMessage(player, "§aРывок активирован!"); + + new BukkitRunnable() { + @Override + public void run() { + if (player.getPlayer() != null) { + player.getPlayer().sendMessage("§cИзнурение... Рывок на перезарядке"); + } + } + }.runTaskLater(plugin, SPRINT_DURATION_SECONDS * 20L); + + new BukkitRunnable() { + @Override + public void run() { + if (player.getPlayer() != null) { + player.getPlayer().sendMessage("§aРывок Скорости снова готов!"); + } + } + }.runTaskLater(plugin, cooldownSeconds * 20L); + } + + @Override + public void onRemove(BasePlayer player) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer != null) { + bukkitPlayer.removePotionEffect(PotionEffectType.SPEED); + } + sendPerkMessage(player, "Перк деактивирован"); + } + + public static int getSprintDuration() { + return SPRINT_DURATION_SECONDS; + } +} diff --git a/src/main/java/dev/loki/totem/DullTotem.java b/src/main/java/dev/loki/totem/DullTotem.java new file mode 100644 index 0000000..dbd4004 --- /dev/null +++ b/src/main/java/dev/loki/totem/DullTotem.java @@ -0,0 +1,52 @@ +package dev.loki.totem; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import java.util.UUID; + +public class DullTotem extends Totem { + private static final String TOTEM_SKULL_TEXTURE = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTBiNzE5ZTNlODUwMTRlMDEyZGM1ZTMxNTU2OGIwZjI0N2Y1ZDhkZjE5YjcyOWEyNTEwYmNjY2ViYzdhNmIifX19"; + + public DullTotem(Location location) { + super(location); + this.state = TotemState.DULL; + } + + @Override + public void updateVisuals() { + if (display == null || display.isDead()) return; + display.setCustomName(state.getColorCode() + state.getDisplayName()); + display.getEquipment().setHelmet(createTotemItem()); + } + + private ItemStack createTotemItem() { + ItemStack skull = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) skull.getItemMeta(); + if (meta != null) { + meta.setDisplayName(state.getDisplayName()); + skull.setItemMeta(meta); + } + return skull; + } + + @Override + public void onInteract(UUID playerId) { + if (state == TotemState.DULL) { + setState(TotemState.CLEANSED); + } + } + + public boolean canBecomeHex() { + return state == TotemState.DULL; + } + + public void convertToHex(UUID hexPerkId) { + if (canBecomeHex()) { + setState(TotemState.HEX); + setLinkedPerkId(hexPerkId); + } + } +} diff --git a/src/main/java/dev/loki/totem/HexTotem.java b/src/main/java/dev/loki/totem/HexTotem.java new file mode 100644 index 0000000..f0699d7 --- /dev/null +++ b/src/main/java/dev/loki/totem/HexTotem.java @@ -0,0 +1,86 @@ +package dev.loki.totem; + +import dev.loki.perk.killer.HexRuinPerk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import java.util.UUID; + +public class HexTotem extends Totem { + private final String hexName; + private boolean active; + + public HexTotem(Location location, String hexName) { + super(location); + this.state = TotemState.HEX; + this.hexName = hexName; + this.active = true; + } + + @Override + public void spawn() { + super.spawn(); + startParticleEffect(); + } + + @Override + public void updateVisuals() { + if (display == null || display.isDead()) return; + String name = state.getColorCode() + "Порча: " + hexName; + display.setCustomName(name); + display.getEquipment().setHelmet(createHexItem()); + } + + private ItemStack createHexItem() { + ItemStack skull = new ItemStack(Material.WITHER_SKELETON_SKULL); + SkullMeta meta = (SkullMeta) skull.getItemMeta(); + if (meta != null) { + meta.setDisplayName("§cHex Totem"); + skull.setItemMeta(meta); + } + return skull; + } + + private void startParticleEffect() { + if (location.getWorld() == null) return; + location.getWorld().spawnParticle(Particle.SOUL_FIRE_FLAME, + location.clone().add(0, 1.5, 0), + 2, 0.3, 0.5, 0.3, 0.01); + } + + @Override + public void onInteract(UUID playerId) { + if (state == TotemState.HEX && active) { + cleanse(playerId); + } + } + + public void cleanse(UUID playerId) { + active = false; + setState(TotemState.CLEANSED); + spawnCleanseEffect(); + } + + private void spawnCleanseEffect() { + if (location.getWorld() == null) return; + location.getWorld().spawnParticle(Particle.END_ROD, + location.clone().add(0, 1, 0), + 20, 0.5, 0.5, 0.5, 0.1); + location.getWorld().playSound(location, org.bukkit.Sound.BLOCK_BEACON_DEACTIVATE, 1.0f, 0.5f); + } + + public boolean isActive() { + return active; + } + + public String getHexName() { + return hexName; + } + + public Class getHexType() { + return HexRuinPerk.class; + } +} diff --git a/src/main/java/dev/loki/totem/Totem.java b/src/main/java/dev/loki/totem/Totem.java new file mode 100644 index 0000000..9e3509d --- /dev/null +++ b/src/main/java/dev/loki/totem/Totem.java @@ -0,0 +1,86 @@ +package dev.loki.totem; + +import org.bukkit.Location; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.EntityType; + +import java.util.UUID; + +public abstract class Totem { + protected final UUID id; + protected final Location location; + protected ArmorStand display; + protected TotemState state; + protected UUID linkedPerkId; + + public Totem(Location location) { + this.id = UUID.randomUUID(); + this.location = location; + this.state = TotemState.DULL; + this.linkedPerkId = null; + } + + public void spawn() { + if (display != null && !display.isDead()) { + return; + } + display = (ArmorStand) location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND); + display.setVisible(false); + display.setGravity(false); + display.setSmall(true); + display.setMarker(true); + display.setCustomNameVisible(true); + updateVisuals(); + } + + public void remove() { + if (display != null && !display.isDead()) { + display.remove(); + } + } + + public abstract void updateVisuals(); + + public abstract void onInteract(UUID playerId); + + public UUID getId() { + return id; + } + + public Location getLocation() { + return location; + } + + public TotemState getState() { + return state; + } + + public void setState(TotemState state) { + this.state = state; + updateVisuals(); + } + + public UUID getLinkedPerkId() { + return linkedPerkId; + } + + public void setLinkedPerkId(UUID linkedPerkId) { + this.linkedPerkId = linkedPerkId; + } + + public boolean isHexTotem() { + return state == TotemState.HEX; + } + + public boolean isBlessedTotem() { + return state == TotemState.BLESSED; + } + + public boolean isCleansed() { + return state == TotemState.CLEANSED; + } + + public double distanceTo(Location other) { + return location.distance(other); + } +} diff --git a/src/main/java/dev/loki/totem/TotemManager.java b/src/main/java/dev/loki/totem/TotemManager.java new file mode 100644 index 0000000..39aba04 --- /dev/null +++ b/src/main/java/dev/loki/totem/TotemManager.java @@ -0,0 +1,127 @@ +package dev.loki.totem; + +import org.bukkit.Location; + +import java.util.*; + +public class TotemManager { + private static final int DEFAULT_TOTEM_COUNT = 5; + private final List totems = new ArrayList<>(); + private final Map totemById = new HashMap<>(); + + public void initializeTotems(List locations) { + clearTotems(); + int count = Math.min(locations.size(), DEFAULT_TOTEM_COUNT); + for (int i = 0; i < count; i++) { + DullTotem totem = new DullTotem(locations.get(i)); + addTotem(totem); + totem.spawn(); + } + } + + public void addTotem(Totem totem) { + totems.add(totem); + totemById.put(totem.getId(), totem); + } + + public void removeTotem(UUID id) { + Totem totem = totemById.remove(id); + if (totem != null) { + totem.remove(); + totems.remove(totem); + } + } + + public void clearTotems() { + for (Totem totem : totems) { + totem.remove(); + } + totems.clear(); + totemById.clear(); + } + + public Totem getTotem(UUID id) { + return totemById.get(id); + } + + public Totem getNearestTotem(Location location) { + Totem nearest = null; + double minDist = Double.MAX_VALUE; + for (Totem totem : totems) { + double dist = totem.distanceTo(location); + if (dist < minDist) { + minDist = dist; + nearest = totem; + } + } + return nearest; + } + + public Totem getNearestDullTotem(Location location) { + Totem nearest = null; + double minDist = Double.MAX_VALUE; + for (Totem totem : totems) { + if (totem.getState() != TotemState.DULL) continue; + double dist = totem.distanceTo(location); + if (dist < minDist) { + minDist = dist; + nearest = totem; + } + } + return nearest; + } + + public List getAllTotems() { + return new ArrayList<>(totems); + } + + public List getHexTotems() { + List hexTotems = new ArrayList<>(); + for (Totem totem : totems) { + if (totem.isHexTotem()) { + hexTotems.add(totem); + } + } + return hexTotems; + } + + public List getCleansedTotems() { + List cleansed = new ArrayList<>(); + for (Totem totem : totems) { + if (totem.isCleansed()) { + cleansed.add(totem); + } + } + return cleansed; + } + + public int getActiveHexCount() { + int count = 0; + for (Totem totem : totems) { + if (totem.isHexTotem() && totem instanceof HexTotem hex && hex.isActive()) { + count++; + } + } + return count; + } + + public boolean hasActiveHexes() { + return getActiveHexCount() > 0; + } + + public void convertToHex(UUID totemId, String hexName) { + Totem totem = totemById.get(totemId); + if (totem instanceof DullTotem dull && dull.canBecomeHex()) { + Location loc = totem.getLocation(); + removeTotem(totemId); + HexTotem hexTotem = new HexTotem(loc, hexName); + hexTotem.setLinkedPerkId(totemId); + addTotem(hexTotem); + hexTotem.spawn(); + } + } + + public static int getDefaultTotemCount() { + return DEFAULT_TOTEM_COUNT; + } +} diff --git a/src/main/java/dev/loki/totem/TotemState.java b/src/main/java/dev/loki/totem/TotemState.java new file mode 100644 index 0000000..926580e --- /dev/null +++ b/src/main/java/dev/loki/totem/TotemState.java @@ -0,0 +1,24 @@ +package dev.loki.totem; + +public enum TotemState { + DULL("Тусклый тотем", "§7"), + HEX("§cПроклятый тотем", "§c"), + BLESSED("§dБлагословенный тотем", "§d"), + CLEANSED("Очищенный тотем", "§a"); + + private final String displayName; + private final String colorCode; + + TotemState(String displayName, String colorCode) { + this.displayName = displayName; + this.colorCode = colorCode; + } + + public String getDisplayName() { + return displayName; + } + + public String getColorCode() { + return colorCode; + } +}