Compare commits

...

10 Commits

11 changed files with 161 additions and 49 deletions

View File

@ -5,6 +5,7 @@ import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.LootItems;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.event_listeners.ChestListener;
import com.alttd.hunger_games.event_listeners.ItemSpawnListener;
import com.alttd.hunger_games.event_listeners.PlayerDamageListener;
import com.alttd.hunger_games.event_listeners.PlayerDisconnectListener;
import com.alttd.hunger_games.event_listeners.PlayerJoinListener;
@ -38,8 +39,8 @@ public final class Main extends JavaPlugin {
round = Round.createSingletonInstance();
lootService = new LootService();
statService = new StatService(this, round);
roundService = RoundService.createSingletonInstance(round, this, lootService, statService);
playerTeleporterService = PlayerTeleporterService.createSingletonInstance();
roundService = RoundService.createSingletonInstance(round, this, lootService, statService, playerTeleporterService);
playerService = PlayerService.createSingletonInstance(round, roundService, playerTeleporterService);
bossBarService = BossBarService.createSingletonInstance(round, roundService);
@ -62,6 +63,7 @@ public final class Main extends JavaPlugin {
pluginManager.registerEvents(new PlayerJoinListener(playerService), this);
pluginManager.registerEvents(new PlayerDamageListener(roundService, playerService, statService), this);
pluginManager.registerEvents(new ChestListener(roundService, lootService), this);
pluginManager.registerEvents(new ItemSpawnListener(roundService), this);
}
public void reloadConfigs() {

View File

@ -155,18 +155,28 @@ public class Stuck extends SubCommand implements Listener {
private Optional<Location> findSafeLocation(Location start) {
int radius = 10;
Location finaleCenter = Config.DESTINATION.FINALE_CENTER;
boolean prioritizeFinale = finaleCenter != null && finaleCenter.getWorld().equals(start.getWorld());
for (int r = 0; r <= radius; r++) {
List<Location> candidates = new ArrayList<>();
for (int x = -r; x <= r; x++) {
for (int z = -r; z <= r; z++) {
if (Math.abs(x) != r && Math.abs(z) != r) {
continue;
}
candidates.add(start.clone().add(x, 0, z));
}
}
Location baseLocation = start.clone().add(x, 0, z);
Optional<Location> safeY = findSafeY(baseLocation);
if (safeY.isPresent()) {
return safeY;
}
if (prioritizeFinale) {
candidates.sort(Comparator.comparingDouble(loc -> loc.distanceSquared(finaleCenter)));
}
for (Location baseLocation : candidates) {
Optional<Location> safeY = findSafeY(baseLocation);
if (safeY.isPresent()) {
return safeY;
}
}
}

View File

@ -89,8 +89,8 @@ public class Messages extends AbstractConfig {
public static String PLAYER_DEATH = "<red><player> has died! <gold><remaining></gold> players remaining.</red>";
public static String BOSSBAR_COUNTDOWN = "<gold>Loot phase in <time></gold>";
public static String BOSSBAR_SAFE_PHASE = "<green>Kill phase in <time></green>";
public static String BOSSBAR_KILL_PHASE = "<red>Border shrink in <time></red>";
public static String BOSSBAR_SAFE_PHASE = "<green>Border shrinking in <time></green>";
public static String BOSSBAR_KILL_PHASE = "<red>Border shrinking... <time></red>";
public static String BOSSBAR_FINALE = "<gold>Remaining Players: <remaining></gold>";
public static String NEXT_STATE = "<gold>Starting <round>!</gold>";
@ -105,8 +105,8 @@ public class Messages extends AbstractConfig {
PLAYER_DEATH = config.getString(prefix, "player-death", PLAYER_DEATH);
BOSSBAR_COUNTDOWN = config.getString(prefix, "bossbar-countdown", BOSSBAR_COUNTDOWN);
BOSSBAR_SAFE_PHASE = config.getString(prefix, "bossbar-safe-phase", BOSSBAR_SAFE_PHASE);
BOSSBAR_KILL_PHASE = config.getString(prefix, "bossbar-kill-phase", BOSSBAR_KILL_PHASE);
BOSSBAR_SAFE_PHASE = config.getString(prefix, "bossbar-border-shrinking-in", BOSSBAR_SAFE_PHASE);
BOSSBAR_KILL_PHASE = config.getString(prefix, "bossbar-border-shrinking", BOSSBAR_KILL_PHASE);
BOSSBAR_FINALE = config.getString(prefix, "bossbar-finale", BOSSBAR_FINALE);
NEXT_STATE = config.getString(prefix, "next-state", NEXT_STATE);

View File

@ -0,0 +1,34 @@
package com.alttd.hunger_games.event_listeners;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.services.RoundService;
import lombok.RequiredArgsConstructor;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
@RequiredArgsConstructor
public class ItemSpawnListener implements Listener {
private final RoundService roundService;
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onItemSpawn(ItemSpawnEvent event) {
setDespawnDelay(event.getEntity());
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerDropItem(PlayerDropItemEvent event) {
setDespawnDelay(event.getItemDrop());
}
private void setDespawnDelay(Item item) {
ROUND_STATE roundState = roundService.getRoundState();
if (roundState == ROUND_STATE.SAFE_PHASE || roundState == ROUND_STATE.KILL_PHASE || roundState == ROUND_STATE.FINALE) {
item.setTicksLived(5800);
}
}
}

View File

@ -57,6 +57,6 @@ public class PlayerDamageListener implements Listener {
@EventHandler
public void onPlayerRespawn(PlayerRespawnEvent event) {
playerService.handlePlayerRespawn(event.getPlayer());
playerService.handlePlayerRespawn(event);
}
}

View File

@ -1,27 +1,17 @@
package com.alttd.hunger_games.game;
import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.utils.WorldBorderUtil;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldBorder;
import java.util.Optional;
@UtilityClass
public class GameStageHandler {
private static final int BORDER_TRANSITION_SECONDS = 10;
public static void handleStageChange(int worldBorderSize) {
World world = getWorld().orElseThrow(() -> new IllegalStateException("No worlds available during state change,"));
WorldBorder border = world.getWorldBorder();
border.setSize(worldBorderSize, BORDER_TRANSITION_SECONDS);
WorldBorderUtil.setSize(worldBorderSize);
String message = Messages.GAME.BORDER_SHRINK
.replace("<size>", String.valueOf(worldBorderSize));
@ -33,26 +23,9 @@ public class GameStageHandler {
}
public static void handleWarmup() {
Location center = Config.DESTINATION.START_CENTER;
if (center == null || center.getWorld() == null) {
throw new IllegalStateException("Start center is not set during warmup.");
}
WorldBorder border = center.getWorld().getWorldBorder();
border.setCenter(center.getX(), center.getZ());
border.setSize(Config.ROUND.INITIAL_BORDER_SIZE);
broadcast(Messages.GAME.WARMUP);
}
private static Optional<World> getWorld() {
Location center = Config.DESTINATION.START_CENTER;
if (center != null && center.getWorld() != null) {
return Optional.ofNullable(center.getWorld());
}
return Bukkit.getWorlds().isEmpty() ? Optional.empty() : Optional.of(Bukkit.getWorlds().getFirst());
}
private static void broadcast(String message) {
Component component = MiniMessage.miniMessage().deserialize(message);
Bukkit.broadcast(component);

View File

@ -7,7 +7,9 @@ import com.alttd.hunger_games.data_objects.ROUND_STATE;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerRespawnEvent;
import java.util.*;
@ -36,6 +38,7 @@ public class PlayerService implements RoundListener {
@Override
public void stateChange(ROUND_STATE roundState) {
this.roundState = roundState;
playerTeleporterService.reset();
switch (roundState) {
case PLAYER_REGISTRATION, KILL_PHASE, SAFE_PHASE -> {
//Nothing
@ -113,10 +116,9 @@ public class PlayerService implements RoundListener {
Placeholder.unparsed("remaining", String.valueOf(remaining))));
}
public void handlePlayerRespawn(Player player) {
roundService.getPlayerState(player.getUniqueId())
.filter(state -> state == PLAYER_STATE.SPECTATING)
.ifPresent(state -> playerTeleporterService.teleportPlayer(player, DESTINATION.SPECTATOR_AREA));
public void handlePlayerRespawn(PlayerRespawnEvent event) {
Location spectatorArea = playerTeleporterService.getLocationFromDestination(DESTINATION.SPECTATOR_AREA);
event.setRespawnLocation(spectatorArea);
}
private Optional<PLAYER_STATE> determinePlayerState(UUID playerUuid) {

View File

@ -4,6 +4,7 @@ import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.data_objects.DESTINATION;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@ -12,6 +13,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Slf4j
@NoArgsConstructor
public class PlayerTeleporterService implements RoundListener {
@ -29,10 +31,16 @@ public class PlayerTeleporterService implements RoundListener {
}
public void teleportPlayer(Player player, DESTINATION destination) {
log.info("Teleporting {} to {}}", player.getName(), destination.name());
player.teleport(getLocationFromDestination(destination));
}
private Location getLocationFromDestination(DESTINATION destination) {
public void reset() {
placementCount.clear();
usedLocations.clear();
}
public Location getLocationFromDestination(DESTINATION destination) {
return switch (destination) {
case START_AREA -> {
Location startCenter = Config.DESTINATION.START_CENTER;

View File

@ -2,6 +2,7 @@ package com.alttd.hunger_games.services;
import com.alttd.hunger_games.config.Config;
import com.alttd.hunger_games.data_objects.GameStage;
import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.game.GameStageHandler;
import lombok.Setter;
@ -99,6 +100,13 @@ public class Round {
final Instant endTime = Instant.now().plus(duration);
countdownFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
if (roundState.equals(ROUND_STATE.FINALE)) {
if (countdownFuture != null) {
countdownFuture.cancel(false);
countdownFuture = null;
}
return;
}
Duration timeLeft = Duration.between(Instant.now(), endTime);
if (bossBarService != null) {
bossBarService.updateCountdown(timeLeft, duration, roundState);
@ -111,6 +119,12 @@ public class Round {
private void scheduleNextStage(int index) {
List<GameStage> gameStageList = Config.ROUND.STAGES;
if (gameStageList.isEmpty()) {
nextStage(); // Transition to SAFE_PHASE
nextStage(); // Transition to KILL_PHASE
return;
}
if (gameStageList.size() <= index) {
nextStage();
return;
@ -120,8 +134,17 @@ public class Round {
Duration duration = gameStage.getDuration();
startCountdown(duration, () -> {
GameStageHandler.handleStageChange(gameStage.getWorldBorderSize());
scheduleNextStage(index + 1);
if (gameStageList.size() <= index + 1) {
nextStage();
GameStageHandler.handleStageChange(gameStage.getWorldBorderSize());
startCountdown(Duration.ofMinutes(5), this::nextStage);
} else {
if (index == 0 && roundState.equals(ROUND_STATE.SAFE_PHASE)) {
nextStage();
}
GameStageHandler.handleStageChange(gameStage.getWorldBorderSize());
scheduleNextStage(index + 1);
}
});
}

View File

@ -3,9 +3,11 @@ package com.alttd.hunger_games.services;
import com.alttd.hunger_games.Main;
import com.alttd.hunger_games.config.Messages;
import com.alttd.hunger_games.data_objects.DESTINATION;
import com.alttd.hunger_games.data_objects.GameStage;
import com.alttd.hunger_games.data_objects.PLAYER_STATE;
import com.alttd.hunger_games.data_objects.ROUND_STATE;
import com.alttd.hunger_games.event_listeners.PlayerMovementListener;
import com.alttd.hunger_games.utils.WorldBorderUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@ -23,11 +25,11 @@ import java.util.stream.Collectors;
public class RoundService implements RoundListener {
private static RoundService instance = null;
private PlayerMovementListener playerMovementListener;
private final PlayerTeleporterService playerTeleporterService;
private final Main main;
private final LootService lootService;
private final StatService statService;
private PlayerMovementListener playerMovementListener;
@Getter
private ROUND_STATE roundState;
@ -97,6 +99,9 @@ public class RoundService implements RoundListener {
player.setHealth(20);
player.setFoodLevel(20);
player.getInventory().clear();
player.setTotalExperience(0);
player.setExp(0.0f);
player.setLevel(0);
round.endRound();
}
@ -124,10 +129,19 @@ public class RoundService implements RoundListener {
player.setFoodLevel(20);
player.setGameMode(GameMode.SURVIVAL);
player.getInventory().clear();
player.setTotalExperience(0);
player.setExp(0.0f);
player.setLevel(0);
});
startFreeze();
WorldBorderUtil.resetBorder();
}
if (roundState.equals(ROUND_STATE.FINALE)) {
WorldBorderUtil.setSize(60);
}
if (!roundState.equals(ROUND_STATE.ENDED) && !roundState.equals(ROUND_STATE.PLAYER_REGISTRATION)) {
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.NEXT_STATE, Placeholder.parsed("round", roundState.getHumanReadableName())));
}
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(Messages.GAME.NEXT_STATE, Placeholder.parsed("round", roundState.getHumanReadableName())));
}
private void stopFreeze() {

View File

@ -0,0 +1,46 @@
package com.alttd.hunger_games.utils;
import com.alttd.hunger_games.config.Config;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldBorder;
import java.util.Optional;
@UtilityClass
public class WorldBorderUtil {
private static final int BORDER_TRANSITION_SECONDS = 45;
public static void setSize(int size) {
setSize(size, BORDER_TRANSITION_SECONDS);
}
public static void setSize(int size, int seconds) {
getWorld().ifPresent(world -> {
WorldBorder border = world.getWorldBorder();
border.setSize(size, seconds);
});
}
public static void resetBorder() {
Location center = Config.DESTINATION.START_CENTER;
if (center == null || center.getWorld() == null) {
return;
}
WorldBorder border = center.getWorld().getWorldBorder();
border.setCenter(center.getX(), center.getZ());
border.setSize(Config.ROUND.INITIAL_BORDER_SIZE);
}
private static Optional<World> getWorld() {
Location center = Config.DESTINATION.START_CENTER;
if (center != null && center.getWorld() != null) {
return Optional.ofNullable(center.getWorld());
}
return Bukkit.getWorlds().isEmpty() ? Optional.empty() : Optional.of(Bukkit.getWorlds().getFirst());
}
}