diff --git a/src/main/java/com/alttd/hunger_games/Main.java b/src/main/java/com/alttd/hunger_games/Main.java index e7373cd..d616bc3 100644 --- a/src/main/java/com/alttd/hunger_games/Main.java +++ b/src/main/java/com/alttd/hunger_games/Main.java @@ -30,7 +30,7 @@ public final class Main extends JavaPlugin { private void registerServices() { Round round = Round.createSingletonInstance(); - roundService = new RoundService(round); + roundService = RoundService.createSingletonInstance(round); playerService = new PlayerService(roundService); } diff --git a/src/main/java/com/alttd/hunger_games/config/AbstractConfig.java b/src/main/java/com/alttd/hunger_games/config/AbstractConfig.java index 1f08cc3..8bc3598 100644 --- a/src/main/java/com/alttd/hunger_games/config/AbstractConfig.java +++ b/src/main/java/com/alttd/hunger_games/config/AbstractConfig.java @@ -5,6 +5,9 @@ import com.alttd.hunger_games.Main; import com.google.common.collect.ImmutableMap; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; @@ -18,6 +21,7 @@ import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; @Slf4j @@ -107,6 +111,38 @@ abstract class AbstractConfig { return yaml.getDouble(path, yaml.getDouble(path)); } + boolean contains(String prefix, String path) { + path = prefix + path; + return !yaml.contains(path); + } + + Optional getLocation(String prefix, String path) { + if (contains(prefix, path)) { + return Optional.empty(); + } + + String rootPath = prefix + path + "."; + if (contains(rootPath, "world") || contains(rootPath, path + "x") || contains(rootPath, path + "y") || contains(rootPath, path + "z")) { + log.error("Invalid location configuration for {}", path); + return Optional.empty(); + } + + String worldString = getString(rootPath, "world", null); + + double x = getDouble(rootPath, "x", 0); + double y = getDouble(rootPath, "y", 0); + double z = getDouble(rootPath, "z", 0); + + World world = Bukkit.getWorld(worldString); + + if (world == null) { + log.error("Invalid world for location {}", path); + return Optional.empty(); + } + + return Optional.of(new Location(world, x, y, z)); + } + List getList(String prefix, String path, T def) { path = prefix + path; yaml.addDefault(path, def); diff --git a/src/main/java/com/alttd/hunger_games/config/Config.java b/src/main/java/com/alttd/hunger_games/config/Config.java index 83638fe..55f2aa2 100644 --- a/src/main/java/com/alttd/hunger_games/config/Config.java +++ b/src/main/java/com/alttd/hunger_games/config/Config.java @@ -2,12 +2,15 @@ package com.alttd.hunger_games.config; import com.alttd.hunger_games.data_objects.GameStage; import lombok.extern.slf4j.Slf4j; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.configuration.ConfigurationSection; import java.io.File; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; @Slf4j @@ -93,4 +96,27 @@ public class Config extends AbstractConfig { return gameStageList; } } + + public static class DESTINATION { + private static final String prefix = "destination."; + public static Location START_CENTER = null; + public static int START_RADIUS = 15; + public static Location FINALE_CENTER = null; + public static int FINALE_RADIUS = 10; + public static Location SPECTATOR_AREA = null; + + @SuppressWarnings("unused") + private static void load() { + Config.DESTINATION.START_RADIUS = config.getInt(prefix, "start-radius", START_RADIUS); + config.getLocation(prefix, "start-center") + .ifPresent(location -> DESTINATION.START_CENTER = location); + + config.getLocation(prefix, "spectator-area") + .ifPresent(location -> DESTINATION.SPECTATOR_AREA = location); + + DESTINATION.FINALE_RADIUS = config.getInt(prefix, "finale-radius", FINALE_RADIUS); + config.getLocation(prefix, "finale-center") + .ifPresent(location -> DESTINATION.FINALE_CENTER = location); + } + } } diff --git a/src/main/java/com/alttd/hunger_games/data_objects/DESTINATION.java b/src/main/java/com/alttd/hunger_games/data_objects/DESTINATION.java new file mode 100644 index 0000000..ba2d0f8 --- /dev/null +++ b/src/main/java/com/alttd/hunger_games/data_objects/DESTINATION.java @@ -0,0 +1,7 @@ +package com.alttd.hunger_games.data_objects; + +public enum DESTINATION { + START_AREA, + FINALE_AREA, + SPECTATOR_AREA; +} diff --git a/src/main/java/com/alttd/hunger_games/data_objects/ROUND_STATE.java b/src/main/java/com/alttd/hunger_games/data_objects/ROUND_STATE.java index 7e77fe7..a6c12e3 100644 --- a/src/main/java/com/alttd/hunger_games/data_objects/ROUND_STATE.java +++ b/src/main/java/com/alttd/hunger_games/data_objects/ROUND_STATE.java @@ -5,6 +5,7 @@ import java.util.Optional; public enum ROUND_STATE { PLAYER_REGISTRATION, COUNTDOWN, + SAFE_PHASE, KILL_PHASE, FINALE, ENDED; @@ -20,6 +21,7 @@ public enum ROUND_STATE { return switch (this) { case PLAYER_REGISTRATION -> "Player Registration"; case COUNTDOWN -> "Countdown"; + case SAFE_PHASE -> "Safe Phase"; case KILL_PHASE -> "Kill Phase"; case FINALE -> "Finale"; case ENDED -> "Ended"; diff --git a/src/main/java/com/alttd/hunger_games/services/PlayerService.java b/src/main/java/com/alttd/hunger_games/services/PlayerService.java index cb4a637..6bac650 100644 --- a/src/main/java/com/alttd/hunger_games/services/PlayerService.java +++ b/src/main/java/com/alttd/hunger_games/services/PlayerService.java @@ -19,7 +19,7 @@ public class PlayerService implements RoundListener { public void stateChange(ROUND_STATE roundState) { this.roundState = roundState; switch (roundState) { - case PLAYER_REGISTRATION, KILL_PHASE -> { + case PLAYER_REGISTRATION, KILL_PHASE, SAFE_PHASE -> { //Nothing } case COUNTDOWN -> { @@ -94,7 +94,7 @@ public class PlayerService implements RoundListener { } return switch (roundState) { case PLAYER_REGISTRATION, COUNTDOWN -> Optional.of(PLAYER_STATE.REGISTERED); - case KILL_PHASE, FINALE, ENDED -> Optional.of(PLAYER_STATE.SPECTATING); + case KILL_PHASE, FINALE, ENDED, SAFE_PHASE -> Optional.of(PLAYER_STATE.SPECTATING); }; } } diff --git a/src/main/java/com/alttd/hunger_games/services/PlayerTeleporterService.java b/src/main/java/com/alttd/hunger_games/services/PlayerTeleporterService.java new file mode 100644 index 0000000..ddb5d3d --- /dev/null +++ b/src/main/java/com/alttd/hunger_games/services/PlayerTeleporterService.java @@ -0,0 +1,90 @@ +package com.alttd.hunger_games.services; + +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 org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@NoArgsConstructor +public class PlayerTeleporterService implements RoundListener { + + private static PlayerTeleporterService instance = null; + + private final Map placementCount = new HashMap<>(); + private final Map> usedLocations = new HashMap<>(); + + public static PlayerTeleporterService createSingletonInstance() { + if (instance != null) { + throw new IllegalStateException("PlayerTeleporterService is already initialized."); + } + instance = new PlayerTeleporterService(); + return instance; + } + + public void teleportPlayer(Player player, DESTINATION destination) { + player.teleport(getLocationFromDestination(destination)); + } + + private Location getLocationFromDestination(DESTINATION destination) { + return switch (destination) { + case START_AREA -> { + Location startCenter = Config.DESTINATION.START_CENTER; + if (startCenter == null) { + throw new IllegalStateException("Start center is not set."); + } + int startRadius = Config.DESTINATION.START_RADIUS; + yield calculateLocation(destination, startCenter, startRadius); + } + case FINALE_AREA -> { + Location finaleCenter = Config.DESTINATION.FINALE_CENTER; + if (finaleCenter == null) { + throw new IllegalStateException("Finale center is not set."); + } + int finaleRadius = Config.DESTINATION.FINALE_RADIUS; + yield calculateLocation(destination, finaleCenter, finaleRadius); + } + case SPECTATOR_AREA -> { + Location spectatorArea = Config.DESTINATION.SPECTATOR_AREA; + if (spectatorArea == null) { + throw new IllegalStateException("Spectator area is not set."); + } + yield spectatorArea; + } + }; + } + + private Location calculateLocation(DESTINATION destination, Location center, int radius) { + int count = placementCount.merge(destination, 1, Integer::sum); + double angleDegrees = vanDerCorput(count - 1) * 360.0; + double angleRadians = Math.toRadians(angleDegrees); + double x = center.getX() + radius * Math.cos(angleRadians); + double z = center.getZ() + radius * Math.sin(angleRadians); + Location location = new Location(center.getWorld(), x, center.getY(), z); + usedLocations.computeIfAbsent(destination, k -> new HashSet<>()).add(location); + return location; + } + + private static double vanDerCorput(int n) { + double result = 0.0; + double denominator = 1.0; + while (n > 0) { + denominator *= 2.0; + result += (n % 2) / denominator; + n /= 2; + } + return result; + } + + @Override + public void stateChange(ROUND_STATE roundState) { + placementCount.clear(); + usedLocations.clear(); + } +} diff --git a/src/main/java/com/alttd/hunger_games/services/RoundService.java b/src/main/java/com/alttd/hunger_games/services/RoundService.java index 48d277e..d7225fb 100644 --- a/src/main/java/com/alttd/hunger_games/services/RoundService.java +++ b/src/main/java/com/alttd/hunger_games/services/RoundService.java @@ -9,11 +9,21 @@ import java.util.stream.Collectors; public class RoundService implements RoundListener { + private static RoundService instance = null; + @Getter private ROUND_STATE roundState; private final HashMap players = new HashMap<>(); - public RoundService(Round round) { + public static RoundService createSingletonInstance(Round round) { + if (instance != null) { + throw new IllegalStateException("RoundService is already initialized."); + } + instance = new RoundService(round); + return instance; + } + + private RoundService(Round round) { this.roundState = round.register(this); }