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() {