diff --git a/src/main/java/me/sashegdev/blackknife/BlackKnifeCommand.java b/src/main/java/me/sashegdev/blackknife/BlackKnifeCommand.java index 2c6abb7..ec6ede8 100644 --- a/src/main/java/me/sashegdev/blackknife/BlackKnifeCommand.java +++ b/src/main/java/me/sashegdev/blackknife/BlackKnifeCommand.java @@ -27,7 +27,8 @@ public class BlackKnifeCommand implements CommandExecutor { } if (args.length >= 1 && args[0].equalsIgnoreCase("reload")) { if (chatConfig != null) chatConfig.load(); - sender.sendMessage(Component.text("Chat config reloaded (400 words)").color(NamedTextColor.GREEN)); + plugin.reloadConfig(); + sender.sendMessage(Component.text("Chat+config reloaded lang=" + plugin.getLang()).color(NamedTextColor.GREEN)); return true; } sender.sendMessage(Component.text("Usage: /blackknife give [player] | /blackknife reload").color(NamedTextColor.YELLOW)); diff --git a/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java b/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java index 5e52ef3..684ba90 100644 --- a/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java +++ b/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java @@ -8,9 +8,12 @@ public class BlackKnifePlugin extends JavaPlugin { private MagicListener magicListener; private ChatConfig chatConfig; private ChatHandler chatHandler; + private String lang = "ru"; @Override public void onEnable() { + saveDefaultConfig(); + lang = getConfig().getString("lang", "ru"); tension = new TensionManager(); magicManager = new MagicManager(this); magicListener = new MagicListener(this, magicManager); @@ -24,15 +27,17 @@ public class BlackKnifePlugin extends JavaPlugin { getServer().getPluginManager().registerEvents(new ParryHandler(this), this); getServer().getPluginManager().registerEvents(magicListener, this); getServer().getPluginManager().registerEvents(chatHandler, this); + getServer().getPluginManager().registerEvents(new KillMessageHandler(this), this); getCommand("blackknife").setExecutor(new BlackKnifeCommand(this, chatConfig, chatHandler)); getCommand("magic").setExecutor(new MagicCommand(magicManager, this)); getServer().getScheduler().runTaskTimer(this, () -> tension.tickShield(), 20L, 1L); - getLogger().info("BlackKnife 1.0.0 enabled - Paper 1.21.1"); + getLogger().info("BlackKnife 1.0.0 enabled - Paper 1.21.1 lang=" + lang); } public TensionManager getTension() { return tension; } public MagicManager getMagicManager() { return magicManager; } public MagicListener getMagicListener() { return magicListener; } + public String getLang() { return lang; } @Override public void onDisable() { diff --git a/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java b/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java new file mode 100644 index 0000000..eaa429e --- /dev/null +++ b/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java @@ -0,0 +1,95 @@ +package me.sashegdev.blackknife; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.persistence.PersistentDataType; +import java.util.Random; + +public class KillMessageHandler implements Listener { + private final BlackKnifePlugin plugin; + private final Random rnd = new Random(); + + public KillMessageHandler(BlackKnifePlugin plugin) { this.plugin = plugin; } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onDeath(PlayerDeathEvent e) { + Player victim = e.getEntity(); + Player killer = victim.getKiller(); + Component deathMsg = e.deathMessage(); + if (deathMsg == null) deathMsg = Component.text(victim.getName() + " died"); + boolean isRu = isRu(); + if (killer != null) { + String killerObf = obf(killer.getName()); + Component killerComp = Component.text(killerObf).decorate(TextDecoration.OBFUSCATED); + String magic = detectMagic(killer, victim); + if (magic != null) { + boolean isKnife = isKnifeMagic(magic); + Component magicComp = isKnife ? Component.text(magic).decorate(TextDecoration.OBFUSCATED) : Component.text(magic); + Component msg; + if (isRu) msg = Component.text(victim.getName() + " был убит ").append(killerComp).append(Component.text(" магией ")).append(magicComp); + else msg = Component.text(victim.getName() + " was slain by ").append(killerComp).append(Component.text("'s ")).append(magicComp); + e.deathMessage(msg); + return; + } + String plain = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(deathMsg); + if (plain.contains(killer.getName())) { + Component obf = Component.text(killerObf).decorate(TextDecoration.OBFUSCATED); + Component replaced = replaceFirst(deathMsg, killer.getName(), obf); + e.deathMessage(replaced); + } else { + Component msg = isRu ? Component.text(victim.getName() + " has swooned ").append(killerComp) : Component.text(victim.getName() + " has swooned ").append(killerComp); + e.deathMessage(msg); + } + } + } + + private String detectMagic(Player killer, Player victim) { + var lastDamage = victim.getLastDamageCause(); + if (lastDamage != null && lastDamage.getCause() != null) { + Component killerName = Component.text(killer.getName()); + } + if (killer.getPersistentDataContainer().has(SwoonHandler.SWOONED, PersistentDataType.BYTE)) return "SWOON"; + if (BlackKnifeItem.is(killer.getInventory().getItemInMainHand()) || BlackKnifeItem.is(killer.getInventory().getItemInOffHand())) { + String mode = killer.getPersistentDataContainer().has(BlackKnifeItem.MODE, PersistentDataType.STRING) ? killer.getPersistentDataContainer().get(BlackKnifeItem.MODE, PersistentDataType.STRING) : "Black Knife"; + return mode != null ? mode : "Black Knife"; + } + for (MagicType mt : MagicType.values()) { + if (killer.hasCooldown(org.bukkit.Material.AMETHYST_SHARD)) return mt.display; + } + return null; + } + + private boolean isKnifeMagic(String magic) { + if (magic == null) return false; + String m = magic.toLowerCase(); + return m.contains("swoon") || m.contains("sword") || m.contains("diamond") || m.contains("lunge") || m.contains("snipe") || m.contains("tri") || m.contains("overhead") || m.contains("stars") || m.contains("black knife") || m.contains("knife"); + } + + private String obf(String name) { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder sb = new StringBuilder(); + int len = 8 + rnd.nextInt(5); + for (int i=0;i displays = new HashMap<>(); public SwoonHandler(BlackKnifePlugin plugin) { this.plugin = plugin; } @EventHandler(priority = EventPriority.HIGHEST) - public void onHit(EntityDamageByEntityEvent e) { + public void onHit(org.bukkit.event.entity.EntityDamageByEntityEvent e) { if (!(e.getDamager() instanceof Player p)) return; if (!BlackKnifeItem.is(p.getInventory().getItemInMainHand()) && !BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return; if (!(e.getEntity() instanceof LivingEntity target)) return; @@ -35,21 +47,53 @@ public class SwoonHandler implements Listener { e.setCancelled(true); plugin.getTension().setTP(p, Math.max(0, plugin.getTension().getTP(p) - 125f)); if (target instanceof Player tp) { + double maxHp = tp.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue(); + double vhpTmp = tp.getHealth() - 999; + double vhp = Math.max(-2147483648.0, Math.min(maxHp, vhpTmp)); + final double finalVhp = vhp; tp.setHealth(1.0); + tp.setWalkSpeed(0f); tp.getPersistentDataContainer().set(SWOONED, PersistentDataType.BYTE, (byte) 1); - tp.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, 100, 2)); - tp.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, 100, 1)); - tp.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, 60, 0)); - tp.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20, 0)); + tp.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, vhp); + tp.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, 100000, 10, false, false, true)); + tp.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 100000, 128, false, false, true)); + tp.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, 100000, 10, false, false, true)); + tp.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, 100000, 0, false, false, true)); + tp.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 100, 0, false, false, true)); + TextDisplay td = tp.getWorld().spawn(tp.getLocation().add(0, 2.4, 0), TextDisplay.class, d -> { + d.text(Component.text(String.format("%.0f HP", finalVhp)).color(NamedTextColor.DARK_RED)); + d.setBillboard(Display.Billboard.CENTER); + d.setSeeThrough(true); + d.setShadowed(true); + d.setBackgroundColor(org.bukkit.Color.fromARGB(64, 40, 0, 0)); + d.setAlignment(TextDisplay.TextAlignment.CENTER); + d.getPersistentDataContainer().set(SWOONED, PersistentDataType.BYTE, (byte)1); + }); + EntityTracker.mark(td); + displays.put(tp.getUniqueId(), td); + tp.getWorld().spawnParticle(org.bukkit.Particle.SOUL, tp.getLocation().add(0,1,0), 40, 0.5,0.5,0.5,0.05); + tp.getWorld().spawnParticle(org.bukkit.Particle.SCULK_SOUL, tp.getLocation().add(0,1,0), 15, 0.5,0.5,0.5,0.02); + tp.getWorld().spawnParticle(org.bukkit.Particle.ASH, tp.getLocation().add(0,1,0), 20, 0.5,0.5,0.5,0); + for (int k=0;k<12;k++){ double a=(double)k/12*Math.PI*2; org.bukkit.Location r=tp.getLocation().add(Math.cos(a)*1.5,0.1,Math.sin(a)*1.5); r.getWorld().spawnParticle(org.bukkit.Particle.SMOKE,r,2,0.05,0.05,0.05,0); r.getWorld().spawnParticle(org.bukkit.Particle.SCULK_SOUL,r,1,0.02,0.02,0.02,0); } p.sendActionBar(Component.text("SWOON! " + tp.getName() + " -999 HP").color(NamedTextColor.DARK_RED)); - tp.sendActionBar(Component.text("SWOONED! -999 HP").color(NamedTextColor.DARK_RED)); + tp.sendActionBar(Component.text("SWOONED! -999 HP regen to revive").color(NamedTextColor.DARK_RED)); tp.getWorld().playSound(tp.getLocation(), Sound.ENTITY_WARDEN_SONIC_BOOM, 1f, 0.5f); + tp.getWorld().playSound(tp.getLocation(), Sound.ENTITY_ELDER_GUARDIAN_CURSE, 1f, 0.7f); if (Math.random() < 0.1) tp.getWorld().dropItemNaturally(tp.getLocation(), BlackKnifeItem.create()); + plugin.getServer().getScheduler().runTaskTimer(plugin, task -> { + TextDisplay d = displays.get(tp.getUniqueId()); + if (d == null || !tp.isOnline() || !tp.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) { task.cancel(); return; } + d.teleport(tp.getLocation().add(0, 2.4, 0)); + Double cur = tp.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE); + if (cur != null) d.text(Component.text(String.format("%.0f HP", cur)).color(cur < 0 ? NamedTextColor.DARK_RED : NamedTextColor.YELLOW)); + if (tp.isDead()) { d.remove(); task.cancel(); } + }, 2L, 2L); } else { target.getPersistentDataContainer().set(MAGIC_DAMAGE, PersistentDataType.BYTE, (byte) 1); target.damage(999.0, p); target.getPersistentDataContainer().remove(MAGIC_DAMAGE); target.getWorld().spawnParticle(org.bukkit.Particle.EXPLOSION, target.getLocation().add(0, 1, 0), 1); + target.getWorld().spawnParticle(org.bukkit.Particle.SOUL, target.getLocation().add(0,1,0), 30, 0.5,0.5,0.5,0.05); target.getWorld().playSound(target.getLocation(), Sound.ENTITY_WARDEN_SONIC_BOOM, 1f, 0.5f); p.sendActionBar(Component.text("SWOON! " + target.getName() + " -999").color(NamedTextColor.DARK_RED)); } @@ -59,6 +103,84 @@ public class SwoonHandler implements Listener { p.playSound(p.getLocation(), Sound.ITEM_TRIDENT_THUNDER, 1f, 0.7f); } + @EventHandler(priority = EventPriority.HIGHEST) + public void onDamage(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) return; + if (!p.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return; + e.setCancelled(true); + } + + @EventHandler + public void onMove(PlayerMoveEvent e) { + Player p = e.getPlayer(); + if (!p.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return; + if (e.getFrom().distanceSquared(e.getTo()) < 0.0001) return; + double dx = e.getTo().getX() - e.getFrom().getX(); + double dz = e.getTo().getZ() - e.getFrom().getZ(); + if (Math.abs(dx) > 0.01 || Math.abs(dz) > 0.01 || Math.abs(e.getTo().getY() - e.getFrom().getY()) > 0.01) { + e.setCancelled(true); + p.sendActionBar(Component.text("SWOONED - can't move, regen to revive").color(NamedTextColor.DARK_RED)); + } + } + + @EventHandler + public void onInteract(PlayerInteractEvent e) { + Player p = e.getPlayer(); + if (!p.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return; + e.setCancelled(true); + p.sendActionBar(Component.text("SWOONED - can't interact").color(NamedTextColor.DARK_RED)); + } + + @EventHandler + public void onBreak(BlockBreakEvent e) { + if (!e.getPlayer().getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return; + e.setCancelled(true); + } + + @EventHandler + public void onPlace(BlockPlaceEvent e) { + if (!e.getPlayer().getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return; + e.setCancelled(true); + } + + @EventHandler + public void onPotionSplash(org.bukkit.event.entity.PotionSplashEvent e) { + if (e.getPotion().getEffects().stream().noneMatch(ef -> ef.getType().equals(PotionEffectType.REGENERATION))) return; + for (LivingEntity ent : e.getAffectedEntities()) { + if (!(ent instanceof Player p)) continue; + if (!p.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) continue; + double intensity = e.getIntensity(p); + double heal = 4 * intensity; + Double vhp = p.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE); + if (vhp == null) vhp = -10.0; + double newVhp = vhp + heal; + double maxHp = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue(); + newVhp = Math.min(maxHp, newVhp); + p.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, newVhp); + TextDisplay td = displays.get(p.getUniqueId()); + if (td != null) td.text(Component.text(String.format("%.0f HP", newVhp)).color(newVhp < 0 ? NamedTextColor.DARK_RED : newVhp < 1 ? NamedTextColor.YELLOW : NamedTextColor.GREEN)); + if (newVhp >= 1) { + p.getPersistentDataContainer().remove(SWOONED); + p.getPersistentDataContainer().remove(VHP); + p.setHealth(Math.min(newVhp, maxHp)); + p.setWalkSpeed(0.2f); + p.removePotionEffect(PotionEffectType.SLOWNESS); + p.removePotionEffect(PotionEffectType.JUMP_BOOST); + p.removePotionEffect(PotionEffectType.WEAKNESS); + p.removePotionEffect(PotionEffectType.DARKNESS); + if (td != null) { td.remove(); displays.remove(p.getUniqueId()); } + p.sendActionBar(Component.text("Revived from SWOON!").color(NamedTextColor.GREEN)); + p.getWorld().playSound(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 1.2f); + } + } + } + + @EventHandler + public void onDeath(PlayerDeathEvent e) { + displays.remove(e.getEntity().getUniqueId()); + e.getEntity().getPersistentDataContainer().remove(SWOONED); + } + public static boolean isSwooned(LivingEntity e) { return e.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..17b850f --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1 @@ +lang: ru