diff --git a/src/main/java/net/supremesurvival/supremecore/SupremeCore.java b/src/main/java/net/supremesurvival/supremecore/SupremeCore.java index 0c496f8..eef4372 100644 --- a/src/main/java/net/supremesurvival/supremecore/SupremeCore.java +++ b/src/main/java/net/supremesurvival/supremecore/SupremeCore.java @@ -18,6 +18,8 @@ import net.supremesurvival.supremecore.sanguine.Vampire; import net.supremesurvival.supremecore.realestate.RealEstateCommand; import net.supremesurvival.supremecore.loot.LootManager; +import net.supremesurvival.supremecore.minigames.MinigameCommand; +import net.supremesurvival.supremecore.minigames.MinigameManager; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.event.Listener; @@ -33,6 +35,7 @@ public final class SupremeCore extends JavaPlugin implements Listener { FileHandler fileHandler = new FileHandler(this); Vampire vampire = new Vampire(this); LootManager lootManager = new LootManager(this); + MinigameManager minigameManager = new MinigameManager(this); @Override public void onEnable() { // Plugin startup logic @@ -50,12 +53,15 @@ public void onEnable() { Logger.sendMessage((ChatColor.YELLOW + "[SupremeCore] [+] " + ChatColor.GRAY + "SupremeNerf Gold Filter Loaded."), Logger.LogType.INFO, "SupremeCore"); lootManager.enable(); this.getServer().getPluginManager().registerEvents(lootManager, this); + minigameManager.enable(); this.getCommand("HorseInfo").setExecutor(new HorseInfo()); this.getCommand("Landmarks").setExecutor(new LandmarkCommand()); this.getCommand("Vampire").setExecutor(vampire); this.getCommand("RealEstate").setExecutor(new RealEstateCommand()); this.getCommand("Morality").setExecutor(new MoralityCommand()); this.getCommand("Morality").setTabCompleter(new MoralityCommand()); + this.getCommand("Minigame").setExecutor(new MinigameCommand(minigameManager)); + this.getCommand("Minigame").setTabCompleter(new MinigameCommand(minigameManager)); Morality.enable(); this.getServer().getPluginManager().registerEvents(new Morality(), this); this.getServer().getPluginManager().registerEvents(vampire, this); @@ -68,6 +74,7 @@ public void onDisable() { // Plugin shutdown logic Morality.disable(); vampire.disable(); + minigameManager.disable(); LandmarkManager.disable(); } diff --git a/src/main/java/net/supremesurvival/supremecore/minigames/MinigameCommand.java b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameCommand.java new file mode 100644 index 0000000..6cce8f8 --- /dev/null +++ b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameCommand.java @@ -0,0 +1,247 @@ +package net.supremesurvival.supremecore.minigames; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class MinigameCommand implements CommandExecutor, TabCompleter { + private final MinigameManager manager; + + public MinigameCommand(MinigameManager manager) { + this.manager = manager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length == 0) { + sendUsage(sender); + return true; + } + + String sub = args[0].toLowerCase(Locale.ROOT); + + switch (sub) { + case "join" -> { + if (!(sender instanceof Player player)) { + sender.sendMessage("Only players can join the queue."); + return true; + } + boolean joined = manager.joinQueue(player); + if (joined) { + sender.sendMessage(ChatColor.GREEN + "Joined the minigame queue. Queue size: " + manager.getQueueSize()); + } else { + sender.sendMessage(ChatColor.YELLOW + "You are already queued."); + } + return true; + } + case "leave" -> { + if (!(sender instanceof Player player)) { + sender.sendMessage("Only players can leave the queue."); + return true; + } + boolean left = manager.leaveQueue(player); + sender.sendMessage(left + ? ChatColor.GREEN + "You left the minigame queue." + : ChatColor.YELLOW + "You are not in queue."); + return true; + } + case "status" -> { + MinigameSession session = manager.getActiveSession(); + if (session == null) { + sender.sendMessage(ChatColor.GRAY + "No active minigame. Queue size: " + manager.getQueueSize()); + } else { + sender.sendMessage(ChatColor.GOLD + "Active: " + session.getType() + ChatColor.GRAY + " Players: " + session.getParticipants().size()); + } + return true; + } + case "forcestart" -> { + if (!sender.hasPermission("minigame.admin")) { + sender.sendMessage(ChatColor.RED + "No permission."); + return true; + } + MinigameType type = resolveType(args, 1); + if (type == null) { + sender.sendMessage(ChatColor.RED + "Usage: /minigame forcestart "); + return true; + } + boolean started = manager.forceStart(type); + sender.sendMessage(started ? ChatColor.GREEN + "Forced start attempted for " + type : ChatColor.RED + "Could not start minigame."); + return true; + } + case "cancel" -> { + if (!sender.hasPermission("minigame.admin")) { + sender.sendMessage(ChatColor.RED + "No permission."); + return true; + } + boolean cancelled = manager.cancelActive("Cancelled by admin"); + sender.sendMessage(cancelled ? ChatColor.GREEN + "Minigame cancelled." : ChatColor.YELLOW + "No active minigame."); + return true; + } + case "config" -> { + if (!sender.hasPermission("minigame.admin")) { + sender.sendMessage(ChatColor.RED + "No permission."); + return true; + } + return handleConfig(sender, args); + } + default -> { + sendUsage(sender); + return true; + } + } + } + + private boolean handleConfig(CommandSender sender, String[] args) { + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "Usage: /minigame config "); + return true; + } + + switch (args[1].toLowerCase(Locale.ROOT)) { + case "reload" -> { + manager.reload(); + sender.sendMessage(ChatColor.GREEN + "Minigame config reloaded."); + return true; + } + case "interval" -> { + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "Usage: /minigame config interval "); + return true; + } + Integer minutes = parseInt(args[2]); + if (minutes == null) { + sender.sendMessage(ChatColor.RED + "Minutes must be a number."); + return true; + } + if (!manager.setIntervalMinutes(minutes)) { + sender.sendMessage(ChatColor.RED + "Failed to save interval."); + return true; + } + manager.reload(); + sender.sendMessage(ChatColor.GREEN + "Minigame interval updated to " + minutes + "m."); + return true; + } + case "threshold" -> { + if (args.length < 4) { + sender.sendMessage(ChatColor.RED + "Usage: /minigame config threshold "); + return true; + } + MinigameType type = MinigameType.fromString(args[2]); + Integer players = parseInt(args[3]); + if (type == null || players == null) { + sender.sendMessage(ChatColor.RED + "Invalid type or player count."); + return true; + } + if (!manager.setThreshold(type, players)) { + sender.sendMessage(ChatColor.RED + "Failed to save threshold."); + return true; + } + manager.reload(); + sender.sendMessage(ChatColor.GREEN + "Threshold updated for " + type + "."); + return true; + } + case "duration" -> { + if (args.length < 4) { + sender.sendMessage(ChatColor.RED + "Usage: /minigame config duration "); + return true; + } + MinigameType type = MinigameType.fromString(args[2]); + Integer seconds = parseInt(args[3]); + if (type == null || seconds == null) { + sender.sendMessage(ChatColor.RED + "Invalid type or duration."); + return true; + } + if (!manager.setDuration(type, seconds)) { + sender.sendMessage(ChatColor.RED + "Failed to save duration."); + return true; + } + manager.reload(); + sender.sendMessage(ChatColor.GREEN + "Duration updated for " + type + "."); + return true; + } + default -> { + sender.sendMessage(ChatColor.RED + "Usage: /minigame config "); + return true; + } + } + } + + private MinigameType resolveType(String[] args, int index) { + if (args.length <= index) return null; + return MinigameType.fromString(args[index]); + } + + private Integer parseInt(String raw) { + try { + return Integer.parseInt(raw); + } catch (NumberFormatException ex) { + return null; + } + } + + private void sendUsage(CommandSender sender) { + sender.sendMessage(ChatColor.YELLOW + "/minigame join"); + sender.sendMessage(ChatColor.YELLOW + "/minigame leave"); + sender.sendMessage(ChatColor.YELLOW + "/minigame status"); + if (sender.hasPermission("minigame.admin")) { + sender.sendMessage(ChatColor.YELLOW + "/minigame forcestart "); + sender.sendMessage(ChatColor.YELLOW + "/minigame cancel"); + sender.sendMessage(ChatColor.YELLOW + "/minigame config "); + } + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + List out = new ArrayList<>(); + + if (args.length == 1) { + out.add("join"); + out.add("leave"); + out.add("status"); + if (sender.hasPermission("minigame.admin")) { + out.add("forcestart"); + out.add("cancel"); + out.add("config"); + } + return filter(out, args[0]); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("config") && sender.hasPermission("minigame.admin")) { + out.add("reload"); + out.add("interval"); + out.add("threshold"); + out.add("duration"); + return filter(out, args[1]); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("forcestart") && sender.hasPermission("minigame.admin")) { + for (MinigameType type : MinigameType.values()) { + out.add(type.name().toLowerCase(Locale.ROOT)); + } + return filter(out, args[1]); + } + + if (args.length == 3 && args[0].equalsIgnoreCase("config") + && (args[1].equalsIgnoreCase("threshold") || args[1].equalsIgnoreCase("duration"))) { + for (MinigameType type : MinigameType.values()) { + out.add(type.name().toLowerCase(Locale.ROOT)); + } + return filter(out, args[2]); + } + + return out; + } + + private List filter(List options, String query) { + String q = query.toLowerCase(Locale.ROOT); + return options.stream().filter(s -> s.toLowerCase(Locale.ROOT).startsWith(q)).toList(); + } +} diff --git a/src/main/java/net/supremesurvival/supremecore/minigames/MinigameDefinition.java b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameDefinition.java new file mode 100644 index 0000000..6268d9d --- /dev/null +++ b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameDefinition.java @@ -0,0 +1,11 @@ +package net.supremesurvival.supremecore.minigames; + +import org.bukkit.Location; + +public record MinigameDefinition( + MinigameType type, + int minPlayers, + int durationSeconds, + Location arenaSpawn +) { +} diff --git a/src/main/java/net/supremesurvival/supremecore/minigames/MinigameManager.java b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameManager.java new file mode 100644 index 0000000..633dd7e --- /dev/null +++ b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameManager.java @@ -0,0 +1,304 @@ +package net.supremesurvival.supremecore.minigames; + +import net.supremesurvival.supremecore.SupremeCore; +import net.supremesurvival.supremecore.commonUtils.Logger; +import net.supremesurvival.supremecore.commonUtils.fileHandler.ConfigUtility; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class MinigameManager { + private static final String HANDLE = "Minigames"; + + private final SupremeCore plugin; + private final Set queue = new HashSet<>(); + private final Map definitions = new EnumMap<>(MinigameType.class); + private final List rotation = new ArrayList<>(); + + private MinigameSession activeSession; + private BukkitTask intervalTask; + + private int intervalMinutes; + private int rotationIndex; + private Location lobbyReturn; + + public MinigameManager(SupremeCore plugin) { + this.plugin = plugin; + } + + public void enable() { + reload(); + } + + public void disable() { + if (intervalTask != null) { + intervalTask.cancel(); + intervalTask = null; + } + if (activeSession != null) { + endActiveSession("Server shutting down"); + } + } + + public void reload() { + FileConfiguration config = ConfigUtility.getModuleConfig("Minigames"); + + this.intervalMinutes = Math.max(1, config.getInt("interval-minutes", 15)); + this.rotationIndex = 0; + + loadLobby(config); + loadDefinitions(config); + + if (intervalTask != null) { + intervalTask.cancel(); + } + + long ticks = intervalMinutes * 60L * 20L; + intervalTask = Bukkit.getScheduler().runTaskTimer(plugin, this::runScheduledTrigger, ticks, ticks); + + Logger.sendMessage("Minigames enabled with interval=" + intervalMinutes + "m and " + definitions.size() + " type(s)", + Logger.LogType.INFO, HANDLE); + } + + private void loadLobby(FileConfiguration config) { + String worldName = config.getString("lobby.world", "world"); + World world = Bukkit.getWorld(worldName); + if (world == null) { + world = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0); + } + + if (world == null) { + lobbyReturn = null; + return; + } + + double x = config.getDouble("lobby.x", world.getSpawnLocation().getX()); + double y = config.getDouble("lobby.y", world.getSpawnLocation().getY()); + double z = config.getDouble("lobby.z", world.getSpawnLocation().getZ()); + float yaw = (float) config.getDouble("lobby.yaw", 0.0); + float pitch = (float) config.getDouble("lobby.pitch", 0.0); + lobbyReturn = new Location(world, x, y, z, yaw, pitch); + } + + private void loadDefinitions(FileConfiguration config) { + definitions.clear(); + rotation.clear(); + + List configuredOrder = config.getStringList("rotation-order"); + for (String raw : configuredOrder) { + MinigameType type = MinigameType.fromString(raw); + if (type != null && !rotation.contains(type)) { + rotation.add(type); + } + } + + ConfigurationSection section = config.getConfigurationSection("types"); + if (section == null) return; + + for (String key : section.getKeys(false)) { + MinigameType type = MinigameType.fromString(key); + if (type == null) continue; + + ConfigurationSection t = section.getConfigurationSection(key); + if (t == null) continue; + + int minPlayers = Math.max(1, t.getInt("min-players", 2)); + int durationSeconds = Math.max(30, t.getInt("duration-seconds", 600)); + Location spawn = parseLocation(t.getConfigurationSection("arena")); + if (spawn == null) continue; + + definitions.put(type, new MinigameDefinition(type, minPlayers, durationSeconds, spawn)); + if (!rotation.contains(type)) { + rotation.add(type); + } + } + } + + private Location parseLocation(ConfigurationSection section) { + if (section == null) return null; + String worldName = section.getString("world", "world"); + World world = Bukkit.getWorld(worldName); + if (world == null) return null; + + double x = section.getDouble("x", world.getSpawnLocation().getX()); + double y = section.getDouble("y", world.getSpawnLocation().getY()); + double z = section.getDouble("z", world.getSpawnLocation().getZ()); + float yaw = (float) section.getDouble("yaw", 0.0); + float pitch = (float) section.getDouble("pitch", 0.0); + + return new Location(world, x, y, z, yaw, pitch); + } + + private void runScheduledTrigger() { + if (activeSession != null || rotation.isEmpty()) { + return; + } + + MinigameType type = rotation.get(rotationIndex % rotation.size()); + rotationIndex++; + tryStart(type, false); + } + + public boolean joinQueue(Player player) { + if (activeSession != null && activeSession.getParticipants().contains(player.getUniqueId())) { + return false; + } + return queue.add(player.getUniqueId()); + } + + public boolean leaveQueue(Player player) { + return queue.remove(player.getUniqueId()); + } + + public int getQueueSize() { + return queue.size(); + } + + public MinigameSession getActiveSession() { + return activeSession; + } + + public Collection getDefinitions() { + return definitions.values(); + } + + public boolean forceStart(MinigameType type) { + return tryStart(type, true); + } + + public boolean cancelActive(String reason) { + if (activeSession == null) { + return false; + } + endActiveSession(reason); + return true; + } + + private boolean tryStart(MinigameType type, boolean forced) { + if (activeSession != null) return false; + + MinigameDefinition definition = definitions.get(type); + if (definition == null) { + Logger.sendMessage("No definition for minigame type " + type, Logger.LogType.WARN, HANDLE); + return false; + } + + if (!forced && queue.size() < definition.minPlayers()) { + broadcastToQueue(ChatColor.GRAY + "Not enough players for " + type + " (" + queue.size() + "/" + definition.minPlayers() + ")"); + return false; + } + + Set participants = new HashSet<>(queue); + queue.clear(); + + long now = System.currentTimeMillis(); + long endAt = now + (definition.durationSeconds() * 1000L); + activeSession = new MinigameSession(type, participants, now, endAt); + + for (UUID id : participants) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) continue; + p.teleport(definition.arenaSpawn()); + p.sendMessage(ChatColor.GOLD + "Minigame started: " + type + ChatColor.GRAY + " (" + definition.durationSeconds() + "s)"); + } + + BukkitTask ticker = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + if (activeSession == null) return; + long remaining = Math.max(0L, (activeSession.getEndAtMillis() - System.currentTimeMillis()) / 1000L); + + for (UUID id : activeSession.getParticipants()) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) continue; + p.sendActionBar(ChatColor.YELLOW + "Minigame: " + activeSession.getType() + ChatColor.DARK_GRAY + " | " + ChatColor.WHITE + remaining + "s"); + } + + if (remaining <= 0) { + endActiveSession("Time expired"); + } + }, 20L, 20L); + + activeSession.setTickerTask(ticker); + return true; + } + + private void endActiveSession(String reason) { + MinigameSession session = activeSession; + if (session == null) return; + + if (session.getTickerTask() != null) { + session.getTickerTask().cancel(); + } + + for (UUID id : session.getParticipants()) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) continue; + if (lobbyReturn != null) { + p.teleport(lobbyReturn); + } + p.sendMessage(ChatColor.GREEN + "Minigame ended: " + session.getType() + ChatColor.GRAY + " (" + reason + ")"); + } + + activeSession = null; + } + + private void broadcastToQueue(String message) { + for (UUID id : queue) { + Player p = Bukkit.getPlayer(id); + if (p != null && p.isOnline()) { + p.sendMessage(message); + } + } + } + + public boolean setIntervalMinutes(int minutes) { + minutes = Math.max(1, minutes); + File file = new File("plugins/SupremeCore/Minigames/config.yml"); + FileConfiguration cfg = YamlConfiguration.loadConfiguration(file); + cfg.set("interval-minutes", minutes); + try { + cfg.save(file); + return true; + } catch (IOException ex) { + Logger.sendMessage("Failed to save minigame interval: " + ex.getMessage(), Logger.LogType.ERR, HANDLE); + return false; + } + } + + public boolean setThreshold(MinigameType type, int value) { + value = Math.max(1, value); + File file = new File("plugins/SupremeCore/Minigames/config.yml"); + FileConfiguration cfg = YamlConfiguration.loadConfiguration(file); + cfg.set("types." + type.name() + ".min-players", value); + try { + cfg.save(file); + return true; + } catch (IOException ex) { + Logger.sendMessage("Failed to save minigame threshold: " + ex.getMessage(), Logger.LogType.ERR, HANDLE); + return false; + } + } + + public boolean setDuration(MinigameType type, int seconds) { + seconds = Math.max(30, seconds); + File file = new File("plugins/SupremeCore/Minigames/config.yml"); + FileConfiguration cfg = YamlConfiguration.loadConfiguration(file); + cfg.set("types." + type.name() + ".duration-seconds", seconds); + try { + cfg.save(file); + return true; + } catch (IOException ex) { + Logger.sendMessage("Failed to save minigame duration: " + ex.getMessage(), Logger.LogType.ERR, HANDLE); + return false; + } + } +} diff --git a/src/main/java/net/supremesurvival/supremecore/minigames/MinigameSession.java b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameSession.java new file mode 100644 index 0000000..b32b717 --- /dev/null +++ b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameSession.java @@ -0,0 +1,45 @@ +package net.supremesurvival.supremecore.minigames; + +import org.bukkit.scheduler.BukkitTask; + +import java.util.Set; +import java.util.UUID; + +public class MinigameSession { + private final MinigameType type; + private final Set participants; + private final long startedAtMillis; + private final long endAtMillis; + private BukkitTask tickerTask; + + public MinigameSession(MinigameType type, Set participants, long startedAtMillis, long endAtMillis) { + this.type = type; + this.participants = participants; + this.startedAtMillis = startedAtMillis; + this.endAtMillis = endAtMillis; + } + + public MinigameType getType() { + return type; + } + + public Set getParticipants() { + return participants; + } + + public long getStartedAtMillis() { + return startedAtMillis; + } + + public long getEndAtMillis() { + return endAtMillis; + } + + public BukkitTask getTickerTask() { + return tickerTask; + } + + public void setTickerTask(BukkitTask tickerTask) { + this.tickerTask = tickerTask; + } +} diff --git a/src/main/java/net/supremesurvival/supremecore/minigames/MinigameType.java b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameType.java new file mode 100644 index 0000000..8d0042b --- /dev/null +++ b/src/main/java/net/supremesurvival/supremecore/minigames/MinigameType.java @@ -0,0 +1,18 @@ +package net.supremesurvival.supremecore.minigames; + +import java.util.Locale; + +public enum MinigameType { + PVP, + PARKOUR, + SPLEEF; + + public static MinigameType fromString(String raw) { + if (raw == null) return null; + try { + return MinigameType.valueOf(raw.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ex) { + return null; + } + } +} diff --git a/src/main/resources/Minigames/config.yml b/src/main/resources/Minigames/config.yml new file mode 100644 index 0000000..d937206 --- /dev/null +++ b/src/main/resources/Minigames/config.yml @@ -0,0 +1,51 @@ +# Interval in minutes between automatic trigger attempts. +interval-minutes: 15 + +# Rotation order for scheduled trigger attempts. +rotation-order: + - PVP + - PARKOUR + - SPLEEF + +# Where players are sent after a game ends. +lobby: + world: world + x: 0.5 + y: 80.0 + z: 0.5 + yaw: 0.0 + pitch: 0.0 + +types: + PVP: + min-players: 4 + duration-seconds: 600 + arena: + world: world + x: 100.5 + y: 70.0 + z: 100.5 + yaw: 0.0 + pitch: 0.0 + + PARKOUR: + min-players: 2 + duration-seconds: 420 + arena: + world: world + x: 200.5 + y: 80.0 + z: 200.5 + yaw: 0.0 + pitch: 0.0 + + SPLEEF: + min-players: 2 + duration-seconds: 480 + arena: + world: world + x: 300.5 + y: 70.0 + z: 300.5 + yaw: 0.0 + pitch: 0.0 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3f7f338..5ace79e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -46,6 +46,9 @@ permissions: realestate.bypasscooldown: description: bypass /realestate view cooldown checks default: op + minigame.admin: + description: admin controls for minigame system + default: op morality.admin: description: administer morality values and reload settings default: op @@ -76,3 +79,8 @@ commands: description: view and manage morality alignment usage: /morality aliases: [moral] + + Minigame: + description: queue and manage modular minigames + usage: /minigame + aliases: [mg]