Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</repository>
<repository>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>placeholderapi</id>
Expand Down
126 changes: 123 additions & 3 deletions src/main/java/tk/shanebee/hg/game/Game.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.*;

Expand Down Expand Up @@ -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<java.util.UUID> 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<Player> 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();
}
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -431,6 +487,70 @@ boolean isGameOver() {
return false;
}

private void launchWinnerFireworks(java.util.Collection<Player> 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 + '}';
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/tk/shanebee/hg/game/GamePlayerData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down
24 changes: 19 additions & 5 deletions src/main/java/tk/shanebee/hg/listeners/GameListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

}
79 changes: 61 additions & 18 deletions src/main/java/tk/shanebee/hg/tasks/StartingTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
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;

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;

Expand All @@ -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("<timer>", String.valueOf(timer)));

} else {
game.getGamePlayerData().msgAll(
lang.game_countdown.replace("<timer>", 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() {
Expand Down