diff --git a/pom.xml b/pom.xml index ffac8ae4..7c757e2c 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ papermc - https://papermc.io/repo/repository/maven-public/ + https://repo.papermc.io/repository/maven-public/ placeholderapi diff --git a/src/main/java/tk/shanebee/hg/game/Game.java b/src/main/java/tk/shanebee/hg/game/Game.java index da2554bc..8fbf1776 100755 --- a/src/main/java/tk/shanebee/hg/game/Game.java +++ b/src/main/java/tk/shanebee/hg/game/Game.java @@ -1,7 +1,6 @@ package tk.shanebee.hg.game; -import org.bukkit.Bukkit; -import org.bukkit.Location; +import org.bukkit.*; import org.bukkit.block.Sign; import org.bukkit.entity.Player; import tk.shanebee.hg.HG; @@ -21,6 +20,9 @@ import tk.shanebee.hg.tasks.TimerTask; import tk.shanebee.hg.util.Util; import tk.shanebee.hg.util.Vault; +import org.bukkit.Location; +import org.bukkit.entity.Firework; +import org.bukkit.inventory.meta.FireworkMeta; import java.util.*; @@ -276,12 +278,60 @@ public void stop() { stop(false); } + public void stop(Boolean death) { + // === FULL-SCREEN TITLE + 5s DELAY === + // Build winner name before we clear anything + java.util.List winPreview = new java.util.ArrayList<>(gamePlayerData.players); + String winnerName = tk.shanebee.hg.util.Util.translateStop( + tk.shanebee.hg.util.Util.convertUUIDListToStringList(winPreview) + ); + + // Send a full-screen title (not chat) to players & spectators + String title = ChatColor.GOLD + winnerName; + String subtitle = ChatColor.YELLOW + "is the Victor " + gameArenaData.getName() + "!"; + int fadeIn = 10, stay = 60, fadeOut = 10; + + java.util.List winnerPlayers = new java.util.ArrayList<>(); + for (java.util.UUID u : winPreview) { + Player wp = Bukkit.getPlayer(u); + if (wp != null) winnerPlayers.add(wp); + } + + for (java.util.UUID uuid : gamePlayerData.getPlayers()) { + Player p = Bukkit.getPlayer(uuid); + if (p != null) { + p.sendTitle(title, subtitle, fadeIn, stay, fadeOut); + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BELL, 1f, 0.8f); + p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1f, 1f); + p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f); + } + } + for (java.util.UUID uuid : gamePlayerData.getSpectators()) { + Player p = Bukkit.getPlayer(uuid); + if (p != null) { + p.sendTitle(title, subtitle, fadeIn, stay, fadeOut); + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BELL, 1f, 0.8f); + p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1f, 1f); + p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f); + } + } + + launchWinnerFireworks(winnerPlayers); + + // 5secs = 100 ticks + int POST_GAME_DELAY_TICKS = 200; + if (plugin.isEnabled()) { + Bukkit.getScheduler().runTaskLater(plugin, () -> doStop(death), POST_GAME_DELAY_TICKS); + return; // prevent running the original body now + } + doStop(death); + } /** * Stop the game * * @param death Whether the game stopped after the result of a death (false = no winnings payed out) */ - public void stop(Boolean death) { + public void doStop(Boolean death) { if (Config.borderEnabled) { gameBorderData.resetBorder(); } @@ -381,6 +431,12 @@ public void stop(Boolean death) { } void updateAfterDeath(Player player, boolean death) { + + // strike lightning effect at death location + if (player != null && player.getLocation().getWorld() != null) { + player.getWorld().strikeLightningEffect(player.getLocation()); + } + Status status = gameArenaData.status; if (status == Status.RUNNING || status == Status.BEGINNING || status == Status.COUNTDOWN) { if (isGameOver()) { @@ -431,6 +487,70 @@ boolean isGameOver() { return false; } + private void launchWinnerFireworks(java.util.Collection winners) { + if (winners == null || winners.isEmpty()) return; + + final int bursts = 6; // how many total spawns + final int intervalTicks = 10; // every 0.5s + final java.util.Random rng = new java.util.Random(); + + for (Player winner : winners) { + if (winner == null || winner.getWorld() == null) continue; + final World world = winner.getWorld(); + final Location base = winner.getLocation().clone().add(0, 1, 0); + + // schedule a short series of launches at this winner's spot + for (int i = 0; i < bursts; i++) { + final int delay = i * intervalTicks; + Bukkit.getScheduler().runTaskLater(plugin, () -> { + Firework fw = (Firework) world.spawn(base, Firework.class); + FireworkMeta meta = fw.getFireworkMeta(); + + // random style/colors each time + FireworkEffect.Type type; + switch (rng.nextInt(4)) { + case 0: + type = FireworkEffect.Type.BALL; + break; + case 1: + type = FireworkEffect.Type.BALL_LARGE; + break; + case 2: + type = FireworkEffect.Type.BURST; + break; + default: + type = FireworkEffect.Type.STAR; + break; + } + + Color c1 = randomNiceColor(rng); + Color c2 = randomNiceColor(rng); + boolean flicker = rng.nextBoolean(); + boolean trail = rng.nextBoolean(); + + meta.addEffect(FireworkEffect.builder() + .with(type) + .withColor(c1) + .withFade(c2) + .flicker(flicker) + .trail(trail) + .build()); + meta.setPower(1 + rng.nextInt(2)); // 1–2 height + fw.setFireworkMeta(meta); + }, delay); + } + } + } + + private Color randomNiceColor(java.util.Random rng) { + // a curated palette that looks good + Color[] palette = new Color[] { + Color.RED, Color.AQUA, Color.BLUE, Color.LIME, Color.FUCHSIA, + Color.ORANGE, Color.SILVER, Color.PURPLE, Color.YELLOW, Color.WHITE + }; + return palette[rng.nextInt(palette.length)]; + } + @Override public String toString() { return "Game{name='" + gameArenaData.name + '\'' + ", bound=" + gameArenaData.bound + '}'; diff --git a/src/main/java/tk/shanebee/hg/game/GamePlayerData.java b/src/main/java/tk/shanebee/hg/game/GamePlayerData.java index d33466ae..981ad3fa 100644 --- a/src/main/java/tk/shanebee/hg/game/GamePlayerData.java +++ b/src/main/java/tk/shanebee/hg/game/GamePlayerData.java @@ -132,7 +132,11 @@ void heal(Player player) { */ public void freeze(Player player) { player.setGameMode(GameMode.SURVIVAL); - player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 23423525, -10, false, false)); + PotionEffectType jumpBoost = PotionEffectType.getByName("JUMP_BOOST"); + if (jumpBoost != null) { + player.addPotionEffect(new PotionEffect(jumpBoost, 23423525, -10, false, false)); + } + player.setWalkSpeed(0.0001F); player.setFoodLevel(1); player.setAllowFlight(false); @@ -146,7 +150,10 @@ public void freeze(Player player) { * @param player Player to unfreeze */ public void unFreeze(Player player) { - player.removePotionEffect(PotionEffectType.JUMP_BOOST); + PotionEffectType jumpBoost = PotionEffectType.getByName("JUMP_BOOST"); + if (jumpBoost != null) { + player.removePotionEffect(jumpBoost); + } player.setWalkSpeed(0.2F); } @@ -353,14 +360,17 @@ public void leave(Player player, Boolean death) { if (!death) allPlayers.remove(uuid); // Only remove the player if they voluntarily left the game unFreeze(player); if (death) { - if (Config.spectateEnabled && Config.spectateOnDeath && !game.isGameOver()) { + + // NEW: always spectate on death if enabled + if (Config.spectateEnabled && Config.spectateOnDeath) { spectate(player); player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 5, 1); player.sendTitle(game.gameArenaData.getName(), Util.getColString(lang.spectator_start_title), 10, 100, 10); game.updateAfterDeath(player, true); return; - } else if (game.gameArenaData.getStatus() == Status.RUNNING) + } else if (game.gameArenaData.getStatus() == Status.RUNNING) { game.getGameBarData().removePlayer(player); + } } heal(player); PlayerData playerData = playerManager.getPlayerData(uuid); diff --git a/src/main/java/tk/shanebee/hg/listeners/GameListener.java b/src/main/java/tk/shanebee/hg/listeners/GameListener.java index 8f3d57e0..40ea463a 100755 --- a/src/main/java/tk/shanebee/hg/listeners/GameListener.java +++ b/src/main/java/tk/shanebee/hg/listeners/GameListener.java @@ -798,10 +798,24 @@ private void onTeleportIntoArena(PlayerTeleportEvent event) { @EventHandler private void onMove(PlayerMoveEvent event) { - Game game = plugin.getPlayerManager().getGame(event.getPlayer()); - if (game == null) return; - if (game.getGameArenaData().getStatus() == Status.COUNTDOWN || game.getGameArenaData().getStatus() == Status.WAITING) - event.setCancelled(true); - } + Game game = plugin.getPlayerManager().getGame(event.getPlayer()); + if (game == null) return; + + if (game.getGameArenaData().getStatus() == Status.COUNTDOWN || + game.getGameArenaData().getStatus() == Status.WAITING) { + + // Allow looking but prevent movement + Location from = event.getFrom(); + Location to = event.getTo(); + + if (from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ()) { + // Only revert movement, keep yaw and pitch + Location stayStill = from.clone(); + stayStill.setYaw(to.getYaw()); + stayStill.setPitch(to.getPitch()); + event.setTo(stayStill); + } + } +} } diff --git a/src/main/java/tk/shanebee/hg/tasks/StartingTask.java b/src/main/java/tk/shanebee/hg/tasks/StartingTask.java index b2b19ebd..72b78dd6 100755 --- a/src/main/java/tk/shanebee/hg/tasks/StartingTask.java +++ b/src/main/java/tk/shanebee/hg/tasks/StartingTask.java @@ -10,6 +10,9 @@ import tk.shanebee.hg.data.Language; import tk.shanebee.hg.game.Game; import tk.shanebee.hg.util.Util; +import org.bukkit.Sound; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import java.util.Objects; import java.util.UUID; @@ -17,7 +20,8 @@ public class StartingTask implements Runnable { private int timer; - private final int id; + private int step = 5; + private int id; private final Game game; private final Language lang; @@ -39,43 +43,82 @@ public StartingTask(Game g) { @Override public void run() { - timer-=5; + timer -= step; - if (game.getGameArenaData().getStatus() != Status.COUNTDOWN) + if (game.getGameArenaData().getStatus() != Status.COUNTDOWN) { stop(); + return; + } + // gentle pre-start heal: +3 health up to max for (UUID p : game.getGamePlayerData().getPlayers()) { Player player = Bukkit.getPlayer(p); - assert player != null; - int health = (int)player.getSaturation(); - health+= 3; - if (health < 20) - player.setHealth(health); + if (player == null) continue; + double max = Objects.requireNonNull( + player.getAttribute(Attribute.GENERIC_MAX_HEALTH) + ).getValue(); + double newHealth = Math.min(max, player.getHealth() + 3.0); + player.setHealth(newHealth); } - + // switch to 1-second ticks for 5..4..3..2..1 + if (timer == 5 && step == 5) { + Bukkit.getScheduler().cancelTask(id); + step = 1; + id = Bukkit.getScheduler().scheduleSyncRepeatingTask( + HG.getPlugin(), this, 20L, 20L + ); + } if (timer <= 0) { - //clear inventory on game start - - if ((Config.enableleaveitem) || Config.enableforcestartitem) + // clear inventory on game start if either leave/forcestart items are enabled + if (Config.enableleaveitem || Config.enableforcestartitem) { game.getGamePlayerData().getPlayers().forEach(uuid -> { Player player = Bukkit.getPlayer(uuid); - assert player != null; - if (player.getInventory().contains(Objects.requireNonNull(Material.getMaterial(Config.forcestartitem))) || player.getInventory().contains(Objects.requireNonNull(Material.getMaterial(Config.leaveitemtype)))) { + if (player == null) return; + if (player.getInventory().contains(Objects.requireNonNull(Material.getMaterial(Config.forcestartitem))) + || player.getInventory().contains(Objects.requireNonNull(Material.getMaterial(Config.leaveitemtype)))) { player.getInventory().clear(); } }); + } + + // top off players for (UUID p : game.getGamePlayerData().getPlayers()) { Player player = Bukkit.getPlayer(p); - assert player != null; - player.setHealth(Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue()); + if (player == null) continue; + double max = Objects.requireNonNull( + player.getAttribute(Attribute.GENERIC_MAX_HEALTH) + ).getValue(); + player.setHealth(max); player.setSaturation(20); + player.setFoodLevel(20); + } + + // dragon roar + speed boost at start + PotionEffect speedBoost = new PotionEffect(PotionEffectType.SPEED, 5 * 20, 1, false, true); + for (UUID p : game.getGamePlayerData().getPlayers()) { + Player player = Bukkit.getPlayer(p); + if (player == null) continue; + player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_GROWL, 1f, 1f); + player.addPotionEffect(speedBoost); } + game.startFreeRoam(); stop(); - } else game.getGamePlayerData().msgAll(lang.game_countdown.replace("", String.valueOf(timer))); - + } else { + game.getGamePlayerData().msgAll( + lang.game_countdown.replace("", String.valueOf(timer)) + ); + + if (step == 1) { // play note each last 5..4..3..2..1 + for (UUID p : game.getGamePlayerData().getPlayers()) { + Player player = Bukkit.getPlayer(p); + if (player == null) continue; + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f); + } + } + } } public void stop() {