v0.9.7
This commit is contained in:
1 parent
629d2f008c
commit
313be2da3c
25 files changed
+950
-330
No files matched your search
@@ -1,3 +1,25 @@
|
||||
**0.9.7:**
|
||||
|
||||
- Added an in-game scoreboard: map, players alive, own blocks broken and time left, with new `Scoreboard.Map`, `Scoreboard.Alive` and `Scoreboard.TimeLeft` labels in config.yml. With Scoreboard-OG 1.2.0 or newer installed, both the queue and match sidebars are drawn through Scoreboard-OG's sidebar API, so the network board returns by itself on leave and `/togglescoreboard` is honoured; without it a plain Bukkit sidebar is used as before.
|
||||
|
||||
- Fixed floor detection: floor cuboids and the knockout height were read from config.yml instead of the map's own file, so eggs never vaporised blocks. Present since 0.9.2.
|
||||
|
||||
- Stopped logging "ready" once per map per second; the line now prints only when a map becomes playable.
|
||||
|
||||
- Removed `vote` and `v` from plugin.yml. The preprocess listener still claims `/vote` and `/v` inside Splegg territory, and VotingPlugin keeps both labels everywhere else.
|
||||
|
||||
- Return locations are now kept on disk in `prejoin-locations.yaml` with an `Options.PreJoinLocationExpiryDays` expiry, matching TheHerobrine-OG and BuildBattle-OG. A relog or a restart still returns the player to where they came from, and any route into a Splegg world records the spot.
|
||||
|
||||
- Dropped the Essentials-OG dependency; nothing used it.
|
||||
|
||||
- The shop resolves balances off the main thread before it opens and no longer pre-checks funds on click, so DiamondBank calls stop stalling the server.
|
||||
|
||||
- The startup purge only removes per-match copies of known templates, so an `SP2-...` world that is not in config yet survives a restart.
|
||||
|
||||
- Player tracking is keyed by UUID and pruned on quit.
|
||||
|
||||
- `/splegg` subcommands no longer print a stray usage line, map names are limited to one path segment, and sign locations parse world names that contain dots.
|
||||
|
||||
**0.9.6:**
|
||||
|
||||
- Added persistent per-player points and the Hive Java Splegg rank ladder (Farmer through Oliver, with The Eggspert reserved for the top scorer once they also hold Oliver). Points are awarded per block an egg destroys and to the match winner, tunable under the new `Points` config section, and stored in `plugins/Splegg-OG/stats.yml`.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Splegg-OG 0.9.2 BETA
|
||||
# Splegg-OG 0.9.7
|
||||
|
||||

|
||||
|
||||
**Splegg-OG** is a Splegg plugin originally made by MrLuangamer, updated for Worldedit 7.2 and Spigot 1.16.4 by Hraponssi, then updated for Purpur 1.19.4 for use at [TrueOG Network](https://true-og.net/) by [NotAlexNoyle](https://github.com/NotAlexNoyle/).
|
||||
|
||||
Runtime dependencies: WorldEdit, DiamondBank-OG, Essentials-OG, and MyWorlds. Chat-OG is optional and integrated when present.
|
||||
Runtime dependencies: WorldEdit, DiamondBank-OG, Utilities-OG, and MyWorlds. Chat-OG and Scoreboard-OG are optional and integrated when present.
|
||||
|
||||
Current feature state:
|
||||
|
||||
@@ -12,7 +12,7 @@ Current feature state:
|
||||
- Splegg creates isolated MyWorlds inventory groups for configured lobby and in-game worlds. It does not manage the vanilla main-world inventory group.
|
||||
- Players can join by lobby id with `/spjoin`, by map with `/splegg join`, by random queue, or through registered lobby signs, then vote for the map while waiting in the lobby.
|
||||
- Players can leave with `/splegg leave`, `/hub`, or the lobby slimeball.
|
||||
- Players who reconnect inside a Splegg world are returned to a protected main-world spawn instead of remaining in an abandoned match world.
|
||||
- Players who leave, relog, or reconnect inside a Splegg world are returned to where they were before they entered it. Return locations are kept on disk in `plugins/Splegg-OG/prejoin-locations.yaml` and expire after `Options.PreJoinLocationExpiryDays` days; the protected main-world spawn is the fallback.
|
||||
|
||||
**Protected vanilla worlds:** Splegg will *never* read, write, or configure the vanilla overworld dimensions (`world`, `world_nether`, `world_the_end`). This guard is hard-coded -- listing those names under `Worlds.Lobby` or `Worlds.InGame` in `config.yml` is rejected with startup warnings, and `/splegg create`, `/splegg setspawn`, `/splegg setlobby`, and `/splegg addfloor` all refuse to run while you are standing in one of them. Run Splegg only inside dedicated worlds that you create yourself.
|
||||
|
||||
@@ -137,7 +137,7 @@ When a player joins a Splegg lobby, the lobby picks up to `Options.VotingMaps` p
|
||||
|
||||
When the start countdown reaches `Options.EndVotingAt`, voting ends and the highest-voted map wins. If the winning map is different from the joined map, Splegg prepares a new per-game world copy for the winner, moves lobby players to that map's queue lobby, updates signs and scoreboards, and starts the match on the winning map.
|
||||
|
||||
`/v` and `/vote` are claimed inside Splegg territory before any other plugin sees them, so VotingPlugin cannot take the `/vote` label away from map voting. The claim covers a player who is in a Splegg game, and anyone standing in a configured lobby or in-game world or a per-match copy. Everywhere else `/vote` behaves normally.
|
||||
`/v` and `/vote` are claimed inside Splegg territory before any other plugin sees them, so VotingPlugin cannot take the `/vote` label away from map voting. The claim covers a player who is in a Splegg game, and anyone standing in a configured lobby or in-game world or a per-match copy. Everywhere else `/vote` behaves normally: neither label is declared in plugin.yml, so Splegg never competes for them.
|
||||
|
||||
**Force Starting:**
|
||||
|
||||
@@ -149,6 +149,10 @@ When Chat-OG is installed, Splegg registers a chat formatter for its `Worlds.Gam
|
||||
|
||||
Chat-OG only routes worlds named `<letters><number>-<name>`, so a lobby world named anything else stays in global chat while Splegg still treats it as its own. Splegg warns at startup naming any such lobby world. `Worlds.InGame` entries are deliberately not checked: they are match templates, nobody queues or plays in one, and each per-game copy is named `<prefix><gameId>-<template>` and routes whatever the template is called.
|
||||
|
||||
**Scoreboard:**
|
||||
|
||||
Queued players see a sidebar with the map, the players waiting and the countdown; during the match it shows the map, the players still alive, the player's own blocks broken and the time left. The labels are the `Scoreboard.*` keys in `config.yml`. With Scoreboard-OG `1.2.0` or newer installed, the sidebar is drawn through Scoreboard-OG's sidebar API, so the network board returns on its own when the player leaves and `/togglescoreboard` is honoured. Without it, a plain Bukkit sidebar is used and the main scoreboard is restored on leave.
|
||||
|
||||
**Match Lobbies:**
|
||||
|
||||
A map's match lobby is stored as bare coordinates and rebased into whichever per-game world copy the match runs in, so it only means anything when it was surveyed inside the map's own world. One saved elsewhere is ignored at runtime -- the queue falls through to the global lobby, then to spawn 1 -- and `/splegg info <map>` reports it as `ignored` with the command to re-run. Splegg logs this once per map on load rather than on every sign refresh.
|
||||
@@ -204,6 +208,4 @@ The resulting .jar file will be in build/libs/
|
||||
|
||||
- PlaceholderAPI support.
|
||||
|
||||
- In-Game scoreboard.
|
||||
|
||||
**Licensed under the GPLv3.**
|
||||
+2
-3
@@ -27,7 +27,7 @@ kotlin { jvmToolchain(17) }
|
||||
/* ----------------------------- Metadata ------------------------------ */
|
||||
group = "net.trueog.splegg-og" // Declare bundle identifier.
|
||||
|
||||
version = "0.9.6" // Declare plugin version (will be in .jar).
|
||||
version = "0.9.7" // Declare plugin version (will be in .jar).
|
||||
|
||||
val apiVersion = "1.19" // Declare minecraft server target version.
|
||||
|
||||
@@ -46,7 +46,6 @@ repositories {
|
||||
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
|
||||
maven { url = uri("https://jitpack.io") } // Import the Jitpack Maven Repository.
|
||||
maven { url = uri("https://papermc.io/repo/repository/maven-public/") } // Import the Paper Maven Repository.
|
||||
maven { url = uri("https://repo.essentialsx.net/releases/") } // Import the EssentialsX Maven Repository.
|
||||
maven { url = uri("https://maven.enginehub.org/repo") } // Import the WorldEdit Maven Repository.
|
||||
}
|
||||
|
||||
@@ -57,12 +56,12 @@ dependencies {
|
||||
compileOnly(
|
||||
"com.sk89q.worldedit:worldedit-bukkit:7.2.15"
|
||||
) // Import WorldEdit API (7.2.15 matches the server's WorldEdit build).
|
||||
compileOnly("net.essentialsx:EssentialsX:2.21.0") // Import EssentialsX API.
|
||||
compileOnly(files("libs/MyWorlds/MyWorlds-1.19.4-v1.jar")) // Import MyWorlds API (local build).
|
||||
compileOnly(
|
||||
files("libs/BKCommonLib/BKCommonLib-1.19.4-v2.jar")
|
||||
) // Import BKCommonLib API (local build, required by MyWorlds at compile time).
|
||||
compileOnly(files("libs/Chat-OG/Chat-OG.jar")) // Import Chat-OG API for world chat formatting.
|
||||
compileOnly(files("libs/Scoreboard-OG/Scoreboard-OG.jar")) // Import Scoreboard-OG sidebar API.
|
||||
implementation(project(":libs:GxUI-OG")) // Import TrueOG Network GxUI-OG Java API (from source).
|
||||
compileOnlyApi(project(":libs:Utilities-OG")) // Import TrueOG Network Utilities-OG Java API (from source).
|
||||
compileOnlyApi(project(":libs:DiamondBank-OG")) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
Binary file not shown.
@@ -1,8 +1,5 @@
|
||||
package commands;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -25,14 +22,10 @@ public class HubCommand implements CommandExecutor {
|
||||
|
||||
}
|
||||
|
||||
UtilPlayer trackedPlayer = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
if (trackedPlayer == null) {
|
||||
|
||||
trackedPlayer = new UtilPlayer(player);
|
||||
SpleggOG.getPlugin().pm.PLAYERS.put(player.getName(), trackedPlayer);
|
||||
|
||||
}
|
||||
final SpleggOG plugin = SpleggOG.getPlugin();
|
||||
final UtilPlayer trackedPlayer = plugin.pm.track(player);
|
||||
|
||||
// leaveGame already returns the player to their pre-join spot.
|
||||
final Game game = trackedPlayer.getGame();
|
||||
if (game != null && trackedPlayer.isAlive()) {
|
||||
|
||||
@@ -41,16 +34,28 @@ public class HubCommand implements CommandExecutor {
|
||||
|
||||
}
|
||||
|
||||
final World mainWorld = findMainWorld();
|
||||
if (mainWorld == null) {
|
||||
if (plugin.hasPreJoinLocation(player.getUniqueId())) {
|
||||
|
||||
if (!plugin.returnPlayer(player, false)) {
|
||||
|
||||
Utils.spleggOGMessage(player, "&cUnable to return you to your previous location.");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Utils.spleggOGMessage(player, "&aReturned to your previous location.");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (plugin.findMainWorld() == null) {
|
||||
|
||||
Utils.spleggOGMessage(player, "&cNo main world is available.");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
final Location destination = mainWorld.getSpawnLocation();
|
||||
if (!player.teleport(destination)) {
|
||||
if (!plugin.returnPlayer(player, true)) {
|
||||
|
||||
Utils.spleggOGMessage(player, "&cUnable to return you to the hub.");
|
||||
return true;
|
||||
@@ -62,21 +67,4 @@ public class HubCommand implements CommandExecutor {
|
||||
|
||||
}
|
||||
|
||||
private World findMainWorld() {
|
||||
|
||||
for (String worldName : SpleggOG.getPlugin().getMainWorlds()) {
|
||||
|
||||
final World world = Bukkit.getWorld(worldName);
|
||||
if (world != null) {
|
||||
|
||||
return world;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
@@ -31,6 +32,9 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
"start", "stop", "info");
|
||||
private static final List<String> MAP_ARG_SUBS = Arrays.asList("join", "create", "delete", "setspawn", "setlobby",
|
||||
"addfloor", "start", "stop", "info");
|
||||
// Map names become file names under the data folder, so they stay on one path
|
||||
// segment.
|
||||
private static final Pattern MAP_NAME = Pattern.compile("[A-Za-z0-9_-]{1,48}");
|
||||
|
||||
private boolean ensureSpleggWorld(Player player) {
|
||||
|
||||
@@ -58,7 +62,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
if (sender instanceof Player) {
|
||||
|
||||
Player player = (Player) sender;
|
||||
UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
UtilPlayer u = SpleggOG.getPlugin().pm.track(player);
|
||||
|
||||
if (args.length == 0) {
|
||||
|
||||
@@ -215,7 +219,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
if (!ensureSpleggWorld(player)) {
|
||||
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -249,7 +253,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
if (!ensureSpleggWorld(player)) {
|
||||
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -328,12 +332,17 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
if (!ensureSpleggWorld(player)) {
|
||||
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
firstUserCommandArgument = args[1];
|
||||
if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
|
||||
if (!MAP_NAME.matcher(firstUserCommandArgument).matches()) {
|
||||
|
||||
Utils.spleggOGMessage(player,
|
||||
"&cERROR: Map names may only contain letters, digits, '-' and '_'.");
|
||||
|
||||
} else if (SpleggOG.getPlugin().maps.mapExists(firstUserCommandArgument)) {
|
||||
|
||||
Utils.spleggOGMessage(player,
|
||||
"&cERROR: The map: &e" + firstUserCommandArgument + " &calready exists.");
|
||||
@@ -475,7 +484,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
if (!ensureSpleggWorld(player)) {
|
||||
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -529,7 +538,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
if (!ensureSpleggWorld(player)) {
|
||||
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -632,7 +641,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
if (!player.hasPermission("splegg.admin")) {
|
||||
|
||||
permissionMessage(player);
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -667,7 +676,7 @@ public class SpleggCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import main.SpleggOG;
|
||||
import managers.Game;
|
||||
import utils.UtilPlayer;
|
||||
|
||||
public class VoteCompleter implements TabCompleter {
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
|
||||
if (!(sender instanceof Player player)) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final UtilPlayer trackedPlayer = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
final Game game = trackedPlayer != null ? trackedPlayer.getGame() : null;
|
||||
if (game == null) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
final List<String> completions = new ArrayList<>();
|
||||
game.getVotingMaps().keySet().forEach(id -> completions.add(id.toString()));
|
||||
return completions;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,17 +72,16 @@ public class Map {
|
||||
public void usableDecider(Map map) {
|
||||
|
||||
final boolean valid = this.spawncount >= 2 && this.floorcount > 0 && hasValidConfiguredWorlds();
|
||||
if (valid) {
|
||||
// Logged once per transition: the sign updater re-evaluates every map every
|
||||
// second.
|
||||
if (valid && !this.usable) {
|
||||
|
||||
SpleggOG.getPlugin().getLogger().info("Floor and spawn point(s) detected. The map is ready to go!");
|
||||
this.usable = true;
|
||||
|
||||
} else {
|
||||
|
||||
this.usable = false;
|
||||
SpleggOG.getPlugin().getLogger().info("Map " + this.name + " has floors and spawn points and is ready.");
|
||||
|
||||
}
|
||||
|
||||
this.usable = valid;
|
||||
|
||||
}
|
||||
|
||||
public boolean isUsable(Map map) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package events;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -67,7 +70,12 @@ public class Listeners implements Listener {
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
|
||||
final Player player = (Player) event.getWhoClicked();
|
||||
if (!(event.getWhoClicked() instanceof Player player)) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (!SpleggOG.getPlugin().isSpleggWorld(player.getWorld())) {
|
||||
|
||||
return;
|
||||
@@ -75,7 +83,7 @@ public class Listeners implements Listener {
|
||||
}
|
||||
|
||||
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
if (u.getGame() != null && u.isAlive()) {
|
||||
if (u != null && u.getGame() != null && u.isAlive()) {
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
@@ -107,14 +115,8 @@ public class Listeners implements Listener {
|
||||
|
||||
}
|
||||
|
||||
if (!isShovelAffordable(player, configPath)) {
|
||||
|
||||
Utils.spleggOGMessage(player, SpleggOG.getPlugin().getConfig().getString("Messages.NoEnoughMoney"));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// No pre-check: consumeFromPlayer refuses an unaffordable purchase itself,
|
||||
// and every DiamondBank call blocks the main thread.
|
||||
final DiamondBankAPIJava diamondBankAPI = SpleggOG.getPlugin().getDiamondBankAPI();
|
||||
if (diamondBankAPI == null) {
|
||||
|
||||
@@ -207,9 +209,33 @@ public class Listeners implements Listener {
|
||||
|
||||
}
|
||||
|
||||
// Balance lookups block, so they run off the main thread and the GUI opens once
|
||||
// they are in.
|
||||
public static void openShop(Player player) {
|
||||
|
||||
new SpleggShopGUI(player).open(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(SpleggOG.getPlugin(), () -> {
|
||||
|
||||
final Map<String, Boolean> affordable = new HashMap<>();
|
||||
for (String configPath : SpleggShopGUI.SHOVEL_CONFIG_PATHS) {
|
||||
|
||||
affordable.put(configPath, isShovelAffordable(player, configPath));
|
||||
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTask(SpleggOG.getPlugin(), () -> {
|
||||
|
||||
final UtilPlayer tracked = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
if (!player.isOnline() || tracked == null || tracked.getGame() == null) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
new SpleggShopGUI(player, affordable).open(true);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -376,11 +402,12 @@ public class Listeners implements Listener {
|
||||
if (player == null)
|
||||
return;
|
||||
final UtilPlayer u = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
if (u == null)
|
||||
return;
|
||||
final Game game = u.getGame();
|
||||
if (game != null)
|
||||
game.leaveGame(u);
|
||||
if (u != null && u.getGame() != null)
|
||||
u.getGame().leaveGame(u);
|
||||
|
||||
// The wrapper holds a dead Player reference from here on; a relog creates a
|
||||
// fresh one.
|
||||
SpleggOG.getPlugin().pm.untrack(player);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package events;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -22,15 +20,7 @@ public class PlayerListener implements Listener {
|
||||
|
||||
private UtilPlayer getTrackedPlayer(Player player) {
|
||||
|
||||
UtilPlayer trackedPlayer = SpleggOG.getPlugin().pm.getPlayer(player);
|
||||
if (trackedPlayer == null) {
|
||||
|
||||
trackedPlayer = new UtilPlayer(player);
|
||||
SpleggOG.getPlugin().pm.PLAYERS.put(player.getName(), trackedPlayer);
|
||||
|
||||
}
|
||||
|
||||
return trackedPlayer;
|
||||
return SpleggOG.getPlugin().pm.track(player);
|
||||
|
||||
}
|
||||
|
||||
@@ -87,18 +77,13 @@ public class PlayerListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
// A fresh wrapper per login; the reconnect teleport lives in
|
||||
// PreJoinLocationListener.
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
|
||||
final Player player = event.getPlayer();
|
||||
final UtilPlayer u = new UtilPlayer(player);
|
||||
|
||||
SpleggOG.getPlugin().pm.PLAYERS.put(player.getName(), u);
|
||||
if (isSpleggReconnectWorld(player.getWorld())) {
|
||||
|
||||
Bukkit.getScheduler().runTask(SpleggOG.getPlugin(), () -> returnToMainWorld(player));
|
||||
|
||||
}
|
||||
SpleggOG.getPlugin().pm.untrack(event.getPlayer());
|
||||
SpleggOG.getPlugin().pm.track(event.getPlayer());
|
||||
|
||||
}
|
||||
|
||||
@@ -161,59 +146,4 @@ public class PlayerListener implements Listener {
|
||||
|
||||
}
|
||||
|
||||
private boolean isSpleggReconnectWorld(World world) {
|
||||
|
||||
if (world == null) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
String worldName = world.getName();
|
||||
return SpleggOG.getPlugin().isSpleggWorld(worldName)
|
||||
|| SpleggOG.getPlugin().getGameWorldManager().isGameCopyName(worldName);
|
||||
|
||||
}
|
||||
|
||||
private void returnToMainWorld(Player player) {
|
||||
|
||||
if (!player.isOnline()) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
World mainWorld = null;
|
||||
for (String worldName : SpleggOG.getPlugin().getMainWorlds()) {
|
||||
|
||||
mainWorld = Bukkit.getWorld(worldName);
|
||||
if (mainWorld != null) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (mainWorld == null && !Bukkit.getWorlds().isEmpty()) {
|
||||
|
||||
mainWorld = Bukkit.getWorlds().get(0);
|
||||
|
||||
}
|
||||
|
||||
if (mainWorld == null) {
|
||||
|
||||
Utils.spleggOGMessage(player, "&cNo main world is available.");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (!player.teleport(mainWorld.getSpawnLocation())) {
|
||||
|
||||
Utils.spleggOGMessage(player, "&cUnable to return you to the hub.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package events;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
|
||||
import main.SpleggOG;
|
||||
|
||||
// Players reach Splegg territory by /mw tp, portals and other plugins too. Every
|
||||
// route records a return spot so nobody lands at main spawn afterwards.
|
||||
public class PreJoinLocationListener implements Listener {
|
||||
|
||||
private final SpleggOG plugin;
|
||||
|
||||
public PreJoinLocationListener(SpleggOG plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onTeleport(PlayerTeleportEvent event) {
|
||||
|
||||
Location from = event.getFrom();
|
||||
Location to = event.getTo();
|
||||
if (from == null || to == null || from.getWorld() == null || to.getWorld() == null)
|
||||
return;
|
||||
if (from.getWorld().equals(to.getWorld()))
|
||||
return;
|
||||
|
||||
boolean fromManaged = plugin.isSpleggTerritory(from.getWorld().getName());
|
||||
boolean toManaged = plugin.isSpleggTerritory(to.getWorld().getName());
|
||||
|
||||
if (toManaged && !fromManaged) {
|
||||
|
||||
plugin.savePreJoinLocation(event.getPlayer().getUniqueId(), from);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Left Splegg territory for a real world by any route, so the recorded spot
|
||||
// has served its purpose.
|
||||
if (fromManaged && !toManaged)
|
||||
plugin.removePreJoinLocation(event.getPlayer().getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
// A player still on the death screen when their match world is deleted would
|
||||
// otherwise respawn at main world spawn.
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onRespawn(PlayerRespawnEvent event) {
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if (!plugin.hasPreJoinLocation(player.getUniqueId()))
|
||||
return;
|
||||
|
||||
Location respawn = event.getRespawnLocation();
|
||||
if (respawn != null && respawn.getWorld() != null && plugin.isSpleggTerritory(respawn.getWorld().getName()))
|
||||
return;
|
||||
|
||||
Location saved = plugin.resolvePreJoinLocation(player.getUniqueId());
|
||||
if (saved == null)
|
||||
return;
|
||||
|
||||
event.setRespawnLocation(saved);
|
||||
plugin.removePreJoinLocation(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
// A relog inside Splegg territory, or after the world the player quit in was
|
||||
// deleted, goes back to the recorded spot; the main spawn is the fallback.
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
|
||||
Player player = event.getPlayer();
|
||||
boolean inTerritory = player.getWorld() != null && plugin.isSpleggTerritory(player.getWorld().getName());
|
||||
if (!inTerritory && !plugin.hasPreJoinLocation(player.getUniqueId()))
|
||||
return;
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
|
||||
if (!player.isOnline())
|
||||
return;
|
||||
|
||||
if (!plugin.returnPlayer(player, inTerritory))
|
||||
plugin.getLogger().warning("Could not return " + player.getName() + " after a relog.");
|
||||
|
||||
}, 1L);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -19,10 +20,17 @@ import net.trueog.utilitiesog.UtilitiesOG;
|
||||
|
||||
public class SpleggShopGUI extends GUIBase {
|
||||
|
||||
public SpleggShopGUI(Player player) {
|
||||
public static final List<String> SHOVEL_CONFIG_PATHS = List.of("GUI.Shop.WoodShovel", "GUI.Shop.StoneShovel",
|
||||
"GUI.Shop.IronShovel", "GUI.Shop.GoldShovel", "GUI.Shop.DiamondShovel", "GUI.Shop.NetheriteShovel");
|
||||
|
||||
// Affordability is resolved off the main thread before the GUI is built.
|
||||
private final Map<String, Boolean> affordable;
|
||||
|
||||
public SpleggShopGUI(Player player, Map<String, Boolean> affordable) {
|
||||
|
||||
super(SpleggOG.getPlugin(), player, SpleggOG.getPlugin().getConfig().getString("GUI.Shop.Title"),
|
||||
normalizeSize(SpleggOG.getPlugin().getConfig().getInt("GUI.Shop.Size")), true);
|
||||
this.affordable = affordable;
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +67,7 @@ public class SpleggShopGUI extends GUIBase {
|
||||
{
|
||||
|
||||
final Player player = getPlayer();
|
||||
final boolean affordable = Listeners.isShovelAffordable(player, configPath);
|
||||
final boolean affordable = this.affordable.getOrDefault(configPath, false);
|
||||
|
||||
final GUIItem item = new GUIItem(material, 1, SpleggOG.getPlugin().getConfig().getString(configPath + ".Name"));
|
||||
item.lore(buildLore(player, configPath, affordable));
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@@ -20,7 +21,6 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.bergerkiller.bukkit.mw.MyWorlds;
|
||||
import com.bergerkiller.bukkit.mw.WorldInventory;
|
||||
import com.earth2me.essentials.Essentials;
|
||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||
|
||||
import nl.skbotnl.chatog.api.ChatOGAPI;
|
||||
@@ -34,22 +34,24 @@ import commands.JoinLobbyCommand;
|
||||
import commands.SpleggCommand;
|
||||
import commands.VoteCommand;
|
||||
import commands.VoteCommandListener;
|
||||
import commands.VoteCompleter;
|
||||
import config.MapUtilities;
|
||||
import events.Listeners;
|
||||
import events.MapListener;
|
||||
import events.PlayerListener;
|
||||
import events.PreJoinLocationListener;
|
||||
import events.SignListener;
|
||||
import events.SpleggEvents;
|
||||
import managers.Game;
|
||||
import managers.GameManager;
|
||||
import managers.GameUtilities;
|
||||
import managers.GameWorldManager;
|
||||
import managers.LobbyScoreboard;
|
||||
import managers.Status;
|
||||
import managers.VoidChunkGenerator;
|
||||
import net.trueog.diamondbankog.api.DiamondBankAPIJava;
|
||||
import signs.JoinSignUpdater;
|
||||
import utils.UtilPlayer;
|
||||
import utils.PreJoinLocationStore;
|
||||
import utils.ScoreboardOGBridge;
|
||||
import utils.Utils;
|
||||
|
||||
public class SpleggOG extends JavaPlugin {
|
||||
@@ -81,9 +83,9 @@ public class SpleggOG extends JavaPlugin {
|
||||
public boolean disabling = false;
|
||||
boolean economy = true;
|
||||
private DiamondBankAPIJava diamondBankAPI;
|
||||
private static Essentials essentials;
|
||||
private static MyWorlds myWorlds;
|
||||
private GameWorldManager gameWorldManager;
|
||||
private PreJoinLocationStore preJoinLocations;
|
||||
|
||||
// TODO: If a shovel in the Splegg shop is too expensive, close the inventory
|
||||
// and tell the user about it.
|
||||
@@ -102,15 +104,6 @@ public class SpleggOG extends JavaPlugin {
|
||||
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
|
||||
} else if (this.getServer().getPluginManager().getPlugin("Essentials-OG") == null) {
|
||||
|
||||
final String noEssentialsOGError = "\"ERROR: Essentials-OG not found! Splegg-OG requires Essentials-OG for inventory handling and will now disable.\"";
|
||||
|
||||
this.getLogger().severe(noEssentialsOGError);
|
||||
this.getLogger().info(noEssentialsOGError);
|
||||
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
|
||||
} else if (findMyWorldsPlugin() == null) {
|
||||
|
||||
final String noMyWorldsError = "ERROR: MyWorlds not found! Without MyWorlds, Splegg-OG will not function.";
|
||||
@@ -139,28 +132,23 @@ public class SpleggOG extends JavaPlugin {
|
||||
}
|
||||
|
||||
// Initialize TrueOG APIs.
|
||||
essentials = (Essentials) this.getServer().getPluginManager().getPlugin("Essentials-OG");
|
||||
myWorlds = findMyWorldsPlugin();
|
||||
|
||||
this.maps = new MapUtilities();
|
||||
this.games = new GameUtilities();
|
||||
this.game = new GameManager();
|
||||
this.gameWorldManager = new GameWorldManager(this);
|
||||
this.gameWorldManager.purgeStaleCopies();
|
||||
this.pm = new Utils();
|
||||
this.utils = new Utils();
|
||||
this.config = new Utils();
|
||||
|
||||
Bukkit.getOnlinePlayers().forEach((Player p) -> {
|
||||
|
||||
final UtilPlayer u = new UtilPlayer(p);
|
||||
|
||||
this.pm.PLAYERS.put(p.getName(), u);
|
||||
|
||||
});
|
||||
Bukkit.getOnlinePlayers().forEach(this.pm::track);
|
||||
|
||||
this.maps.c.setup();
|
||||
this.config.setup();
|
||||
// Maps are known now, so the purge only touches copies of real templates.
|
||||
this.gameWorldManager.purgeStaleCopies();
|
||||
this.preJoinLocations = new PreJoinLocationStore(this);
|
||||
|
||||
this.getConfig().options().copyDefaults(true);
|
||||
this.saveConfig();
|
||||
@@ -179,6 +167,7 @@ public class SpleggOG extends JavaPlugin {
|
||||
|
||||
this.getServer().getPluginManager().registerEvents(new MapListener(), this);
|
||||
this.getServer().getPluginManager().registerEvents(new PlayerListener(), this);
|
||||
this.getServer().getPluginManager().registerEvents(new PreJoinLocationListener(this), this);
|
||||
this.getServer().getPluginManager().registerEvents(new SpleggEvents(), this);
|
||||
this.getServer().getPluginManager().registerEvents(new SignListener(), this);
|
||||
getServer().getPluginManager().registerEvents(new Listeners(diamondBankAPI), this);
|
||||
@@ -190,11 +179,9 @@ public class SpleggOG extends JavaPlugin {
|
||||
final JoinLobbyCommand joinLobbyCommand = new JoinLobbyCommand();
|
||||
this.getCommand("spjoin").setExecutor(joinLobbyCommand);
|
||||
this.getCommand("spjoin").setTabCompleter(joinLobbyCommand);
|
||||
final VoteCommand voteCommand = new VoteCommand();
|
||||
this.getCommand("vote").setExecutor(voteCommand);
|
||||
this.getCommand("vote").setTabCompleter(new VoteCompleter());
|
||||
// Claims /v and /vote inside Splegg territory before VotingPlugin sees them.
|
||||
this.getServer().getPluginManager().registerEvents(new VoteCommandListener(voteCommand), this);
|
||||
// /vote and /v are claimed inside Splegg territory only, so VotingPlugin keeps
|
||||
// the labels everywhere else; nothing is registered in plugin.yml for them.
|
||||
this.getServer().getPluginManager().registerEvents(new VoteCommandListener(new VoteCommand()), this);
|
||||
this.registerChatFormatter();
|
||||
|
||||
// Redraw join signs once a second, TheHerobrine-OG style. First run
|
||||
@@ -229,6 +216,8 @@ public class SpleggOG extends JavaPlugin {
|
||||
}
|
||||
|
||||
Listeners.clearAll();
|
||||
LobbyScoreboard.detachAll();
|
||||
ScoreboardOGBridge.releaseAll();
|
||||
|
||||
if (this.stats != null) {
|
||||
|
||||
@@ -237,6 +226,14 @@ public class SpleggOG extends JavaPlugin {
|
||||
|
||||
}
|
||||
|
||||
// Last write wins: anyone the teardown could not move keeps their spot on
|
||||
// disk so the next boot can still return them.
|
||||
if (this.preJoinLocations != null) {
|
||||
|
||||
this.preJoinLocations.shutdown();
|
||||
|
||||
}
|
||||
|
||||
this.getLogger().info("Splegg-OG Shut Down with " + gameCounter + " games running.");
|
||||
|
||||
}
|
||||
@@ -686,10 +683,101 @@ public class SpleggOG extends JavaPlugin {
|
||||
|
||||
}
|
||||
|
||||
// Getter for Essentials-OG API.
|
||||
public static Essentials getEssentials() {
|
||||
// Every world Splegg owns: configured lobby and template worlds plus per-match
|
||||
// copies.
|
||||
public boolean isSpleggTerritory(String worldName) {
|
||||
|
||||
return essentials;
|
||||
if (worldName == null || isProtectedMainWorld(worldName))
|
||||
return false;
|
||||
|
||||
if (isSpleggWorld(worldName))
|
||||
return true;
|
||||
|
||||
return this.gameWorldManager != null && this.gameWorldManager.isGameCopyName(worldName);
|
||||
|
||||
}
|
||||
|
||||
// Records the spot a player came from. Only a world outside Splegg territory
|
||||
// is stored, so hops between Splegg worlds keep it.
|
||||
public void savePreJoinLocation(UUID playerId, Location location) {
|
||||
|
||||
if (this.preJoinLocations == null || location == null || location.getWorld() == null)
|
||||
return;
|
||||
if (isSpleggTerritory(location.getWorld().getName()))
|
||||
return;
|
||||
this.preJoinLocations.put(playerId, location);
|
||||
|
||||
}
|
||||
|
||||
public boolean hasPreJoinLocation(UUID playerId) {
|
||||
|
||||
return this.preJoinLocations != null && this.preJoinLocations.has(playerId)
|
||||
&& !isSpleggTerritory(this.preJoinLocations.getWorldName(playerId));
|
||||
|
||||
}
|
||||
|
||||
// Resolves the stored spot, loading its world through MyWorlds if needed.
|
||||
public Location resolvePreJoinLocation(UUID playerId) {
|
||||
|
||||
if (!hasPreJoinLocation(playerId))
|
||||
return null;
|
||||
return this.preJoinLocations.getOrLoadWorld(playerId);
|
||||
|
||||
}
|
||||
|
||||
public void removePreJoinLocation(UUID playerId) {
|
||||
|
||||
if (this.preJoinLocations != null)
|
||||
this.preJoinLocations.remove(playerId);
|
||||
|
||||
}
|
||||
|
||||
// Returns a player to their pre-join spot, or main spawn when fallbackToSpawn
|
||||
// is set and nothing is recorded. The spot is dropped once a teleport lands.
|
||||
public boolean returnPlayer(Player player, boolean fallbackToSpawn) {
|
||||
|
||||
if (player == null || !player.isOnline())
|
||||
return false;
|
||||
|
||||
if (player.isDead())
|
||||
player.spigot().respawn();
|
||||
|
||||
final Location saved = resolvePreJoinLocation(player.getUniqueId());
|
||||
if (saved != null) {
|
||||
|
||||
if (!player.teleport(saved))
|
||||
return false;
|
||||
|
||||
removePreJoinLocation(player.getUniqueId());
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (!fallbackToSpawn)
|
||||
return false;
|
||||
|
||||
final World mainWorld = findMainWorld();
|
||||
return mainWorld != null && player.teleport(mainWorld.getSpawnLocation());
|
||||
|
||||
}
|
||||
|
||||
// MyWorlds' main world first, then the protected list, then whatever Bukkit
|
||||
// loaded first.
|
||||
public World findMainWorld() {
|
||||
|
||||
World mainWorld = MyWorlds.getMainWorld();
|
||||
if (mainWorld != null)
|
||||
return mainWorld;
|
||||
|
||||
for (String worldName : getMainWorlds()) {
|
||||
|
||||
mainWorld = Bukkit.getWorld(worldName);
|
||||
if (mainWorld != null)
|
||||
return mainWorld;
|
||||
|
||||
}
|
||||
|
||||
return Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -646,11 +646,13 @@ public class Game {
|
||||
// The lowest Y the floor extends to. Computed once from the map config.
|
||||
// No mutable state needed -- the per-game world is reset by being
|
||||
// deleted between games.
|
||||
// Floors live in the per-map file, not in config.yml.
|
||||
final FileConfiguration mapConfig = this.map.getConfig();
|
||||
int small = Integer.MAX_VALUE;
|
||||
for (int i = 1; i <= map.getFloors(); i++) {
|
||||
|
||||
small = Math.min(small, Math.min(config.getInt("Floors." + i + ".p1.y", small),
|
||||
config.getInt("Floors." + i + ".p2.y", small)));
|
||||
small = Math.min(small, Math.min(mapConfig.getInt("Floors." + i + ".p1.y", small),
|
||||
mapConfig.getInt("Floors." + i + ".p2.y", small)));
|
||||
|
||||
}
|
||||
|
||||
@@ -661,16 +663,9 @@ public class Game {
|
||||
public void joinGame(UtilPlayer playerWhoIsJoining) {
|
||||
|
||||
final Player player = playerWhoIsJoining.getPlayer();
|
||||
final World joinWorld = player.getWorld();
|
||||
// Any world outside splegg territory is a valid return spot, not just the
|
||||
// vanilla overworld set -- leaveGame sends the player back here.
|
||||
if (joinWorld != null && !splegg.isSpleggWorld(joinWorld)
|
||||
&& !splegg.getGameWorldManager().isGameCopyName(joinWorld.getName()))
|
||||
{
|
||||
|
||||
playerWhoIsJoining.setPreJoinLocation(player.getLocation());
|
||||
|
||||
}
|
||||
// Any world outside Splegg territory is a valid return spot; the store refuses
|
||||
// the rest.
|
||||
splegg.savePreJoinLocation(player.getUniqueId(), player.getLocation());
|
||||
|
||||
if (playerWhoIsJoining.getGame() != null) {
|
||||
|
||||
@@ -999,24 +994,10 @@ public class Game {
|
||||
LobbyScoreboard.detach(player);
|
||||
LobbyScoreboard.refreshGame(game);
|
||||
|
||||
final Location returnLocation = u.getPreJoinLocation();
|
||||
if (returnLocation != null && returnLocation.getWorld() != null) {
|
||||
if (!this.splegg.returnPlayer(player, true)) {
|
||||
|
||||
player.teleport(returnLocation);
|
||||
|
||||
} else {
|
||||
|
||||
final List<String> mainWorlds = this.splegg.getMainWorlds();
|
||||
if (!mainWorlds.isEmpty()) {
|
||||
|
||||
final org.bukkit.World mainWorld = Bukkit.getWorld(mainWorlds.get(0));
|
||||
if (mainWorld != null) {
|
||||
|
||||
player.teleport(mainWorld.getSpawnLocation());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
SpleggOG.getPlugin().getLogger()
|
||||
.warning("Could not return " + player.getName() + " out of game " + game.getGameId() + ".");
|
||||
|
||||
}
|
||||
|
||||
@@ -1142,14 +1123,16 @@ public class Game {
|
||||
int tx = target.getBlockX();
|
||||
int ty = target.getBlockY();
|
||||
int tz = target.getBlockZ();
|
||||
// Floors live in the per-map file, not in config.yml.
|
||||
final FileConfiguration mapConfig = this.map.getConfig();
|
||||
for (int i = 1; i <= map.getFloors(); i++) {
|
||||
|
||||
int p1x = config.getInt("Floors." + i + ".p1.x");
|
||||
int p1y = config.getInt("Floors." + i + ".p1.y");
|
||||
int p1z = config.getInt("Floors." + i + ".p1.z");
|
||||
int p2x = config.getInt("Floors." + i + ".p2.x");
|
||||
int p2y = config.getInt("Floors." + i + ".p2.y");
|
||||
int p2z = config.getInt("Floors." + i + ".p2.z");
|
||||
int p1x = mapConfig.getInt("Floors." + i + ".p1.x");
|
||||
int p1y = mapConfig.getInt("Floors." + i + ".p1.y");
|
||||
int p1z = mapConfig.getInt("Floors." + i + ".p1.z");
|
||||
int p2x = mapConfig.getInt("Floors." + i + ".p2.x");
|
||||
int p2y = mapConfig.getInt("Floors." + i + ".p2.y");
|
||||
int p2z = mapConfig.getInt("Floors." + i + ".p2.z");
|
||||
int minX = Math.min(p1x, p2x), maxX = Math.max(p1x, p2x);
|
||||
int minY = Math.min(p1y, p2y), maxY = Math.max(p1y, p2y);
|
||||
int minZ = Math.min(p1z, p2z), maxZ = Math.max(p1z, p2z);
|
||||
|
||||
@@ -63,6 +63,34 @@ public class GameWorldManager {
|
||||
|
||||
}
|
||||
|
||||
// Only copies of known templates are purged, so an admin's SP2-something
|
||||
// world that is not in config yet survives a restart.
|
||||
private boolean isKnownTemplateCopy(String name) {
|
||||
|
||||
int dash = name.indexOf('-');
|
||||
if (dash < 0 || dash + 1 >= name.length())
|
||||
return false;
|
||||
|
||||
String suffix = name.substring(dash + 1);
|
||||
for (String template : plugin.getInGameWorlds())
|
||||
if (template != null && template.equalsIgnoreCase(suffix))
|
||||
return true;
|
||||
|
||||
if (plugin.maps == null)
|
||||
return false;
|
||||
|
||||
for (Map map : plugin.maps.getMaps()) {
|
||||
|
||||
String world = map.getWorldName();
|
||||
if (world != null && world.equalsIgnoreCase(suffix))
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private boolean isConfiguredWorld(String name) {
|
||||
|
||||
for (String configured : plugin.getLobbyWorlds())
|
||||
@@ -91,7 +119,7 @@ public class GameWorldManager {
|
||||
for (File child : children) {
|
||||
|
||||
String name = child.getName();
|
||||
if (!isGameCopyName(name))
|
||||
if (!isGameCopyName(name) || !isKnownTemplateCopy(name))
|
||||
continue;
|
||||
|
||||
if (SpleggOG.isProtectedMainWorld(name)) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package managers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -12,13 +15,18 @@ import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.ScoreboardManager;
|
||||
|
||||
import main.SpleggOG;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import utils.ScoreboardOGBridge;
|
||||
import utils.SpleggPlayer;
|
||||
import utils.Utils;
|
||||
|
||||
// The sidebar a Splegg player sees while queued and while playing. With Scoreboard-OG
|
||||
// present it renders through that plugin's sidebar; otherwise a Bukkit board is used.
|
||||
public class LobbyScoreboard {
|
||||
|
||||
private static final HashMap<String, Scoreboard> BOARDS = new HashMap<>();
|
||||
private static final HashMap<UUID, Scoreboard> BOARDS = new HashMap<>();
|
||||
private static final HashMap<UUID, Game> GAMES = new HashMap<>();
|
||||
|
||||
private LobbyScoreboard() {
|
||||
|
||||
@@ -28,6 +36,15 @@ public class LobbyScoreboard {
|
||||
|
||||
public static void attach(Player player, Game game) {
|
||||
|
||||
GAMES.put(player.getUniqueId(), game);
|
||||
|
||||
if (ScoreboardOGBridge.isAvailable()) {
|
||||
|
||||
ScoreboardOGBridge.claim(player, LobbyScoreboard::title, LobbyScoreboard::lines);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final ScoreboardManager manager = Bukkit.getScoreboardManager();
|
||||
if (manager == null) {
|
||||
|
||||
@@ -36,13 +53,10 @@ public class LobbyScoreboard {
|
||||
}
|
||||
|
||||
final Scoreboard board = manager.getNewScoreboard();
|
||||
final String rawTitle = SpleggOG.getPlugin().getConfig().getString("Scoreboard.Title");
|
||||
final TextComponent title = Utils.legacySerializerAnyCase(rawTitle != null ? rawTitle : "&2Splegg&r-&4OG");
|
||||
|
||||
final Objective objective = board.registerNewObjective("splegg_lobby", Criteria.DUMMY, title);
|
||||
final Objective objective = board.registerNewObjective("splegg_lobby", Criteria.DUMMY, title(player));
|
||||
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
|
||||
BOARDS.put(player.getName(), board);
|
||||
BOARDS.put(player.getUniqueId(), board);
|
||||
player.setScoreboard(board);
|
||||
|
||||
render(player, game);
|
||||
@@ -51,6 +65,15 @@ public class LobbyScoreboard {
|
||||
|
||||
public static void detach(Player player) {
|
||||
|
||||
GAMES.remove(player.getUniqueId());
|
||||
|
||||
if (ScoreboardOGBridge.isAvailable()) {
|
||||
|
||||
ScoreboardOGBridge.release(player);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Hand the player back to the server's main scoreboard rather than a
|
||||
// fresh empty one: Scoreboard-OG's world-change hook only reclaims its
|
||||
// sidebar when the player's Bukkit board is the main scoreboard.
|
||||
@@ -61,13 +84,33 @@ public class LobbyScoreboard {
|
||||
|
||||
}
|
||||
|
||||
BOARDS.remove(player.getName());
|
||||
BOARDS.remove(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
public static void detachAll() {
|
||||
|
||||
for (UUID playerId : new ArrayList<>(GAMES.keySet())) {
|
||||
|
||||
final Player player = Bukkit.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
|
||||
detach(player);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GAMES.clear();
|
||||
BOARDS.clear();
|
||||
|
||||
}
|
||||
|
||||
// Scoreboard-OG polls the provider itself; only the Bukkit boards need
|
||||
// redrawing.
|
||||
public static void refreshGame(Game game) {
|
||||
|
||||
if (game == null) {
|
||||
if (game == null || ScoreboardOGBridge.isAvailable()) {
|
||||
|
||||
return;
|
||||
|
||||
@@ -83,9 +126,86 @@ public class LobbyScoreboard {
|
||||
|
||||
}
|
||||
|
||||
private static TextComponent title(Player player) {
|
||||
|
||||
final String rawTitle = SpleggOG.getPlugin().getConfig().getString("Scoreboard.Title");
|
||||
return Utils.legacySerializerAnyCase(rawTitle != null ? rawTitle : "&2Splegg&r-&4OG");
|
||||
|
||||
}
|
||||
|
||||
private static List<Component> lines(Player player) {
|
||||
|
||||
final List<Component> out = new ArrayList<>();
|
||||
final Game game = GAMES.get(player.getUniqueId());
|
||||
if (game == null || game.getMap() == null) {
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
for (String line : rawLines(player, game)) {
|
||||
|
||||
out.add(Utils.legacySerializerAnyCase(line));
|
||||
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
// Top to bottom. The queue card shows the countdown; the match card shows the
|
||||
// survivors, the player's own block count and the time left.
|
||||
private static List<String> rawLines(Player player, Game game) {
|
||||
|
||||
final List<String> lines = new ArrayList<>();
|
||||
lines.add("&7&m ");
|
||||
lines.add(configLine("Scoreboard.Map", "&eMap:"));
|
||||
lines.add("&f" + game.getMap().getName());
|
||||
lines.add("");
|
||||
|
||||
if (game.getStatus() == Status.INGAME) {
|
||||
|
||||
final SpleggPlayer self = game.getPlayers().get(player.getUniqueId());
|
||||
final int broken = self == null ? 0 : self.getBroken();
|
||||
lines.add(configLine("Scoreboard.Alive", "&aPlayers Alive:"));
|
||||
lines.add("&f" + game.getPlayers().size());
|
||||
lines.add(" ");
|
||||
lines.add(configLine("Scoreboard.BrokenBlocks", "&eBlocks Broken:"));
|
||||
lines.add("&f" + broken);
|
||||
lines.add(" ");
|
||||
lines.add(configLine("Scoreboard.TimeLeft", "&6Time Left:"));
|
||||
lines.add("&f" + SpleggOG.getPlugin().game.getDigitTime(Math.max(0, game.getCount())));
|
||||
|
||||
} else {
|
||||
|
||||
final int maxPlayers = game.getMap().getSpawnCount();
|
||||
final int currentPlayers = game.getPlayers().size();
|
||||
lines.add(configLine("Scoreboard.Queue", "&6Players Waiting:"));
|
||||
lines.add("&f" + currentPlayers + "&7/&f" + maxPlayers);
|
||||
lines.add(" ");
|
||||
lines.add(configLine("Scoreboard.Starting", "&6Starting in:"));
|
||||
if (game.isStarting()) {
|
||||
|
||||
lines.add("&f" + game.getLobbyCount() + "s");
|
||||
|
||||
} else {
|
||||
|
||||
final int required = Math.max(2, SpleggOG.getPlugin().getConfig().getInt("Options.AutoStartPlayers"));
|
||||
final int needed = Math.max(0, required - currentPlayers);
|
||||
lines.add(needed == 0 ? "&aReady" : "&f" + needed + " &7more");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lines.add("&7&m ");
|
||||
return lines;
|
||||
|
||||
}
|
||||
|
||||
private static void render(Player player, Game game) {
|
||||
|
||||
final Scoreboard board = BOARDS.get(player.getName());
|
||||
final Scoreboard board = BOARDS.get(player.getUniqueId());
|
||||
if (board == null) {
|
||||
|
||||
return;
|
||||
@@ -105,45 +225,15 @@ public class LobbyScoreboard {
|
||||
|
||||
}
|
||||
|
||||
final String queueLabel = configLine("Scoreboard.Queue", "&6Players Waiting:");
|
||||
final String startingLabel = configLine("Scoreboard.Starting", "&6Starting in:");
|
||||
final String mapLabel = "&eMap:";
|
||||
// Bukkit boards score from the bottom, so the top line gets the highest score.
|
||||
final List<String> lines = rawLines(player, game);
|
||||
int score = lines.size();
|
||||
for (String line : lines) {
|
||||
|
||||
final int maxPlayers = game.getMap().getSpawnCount();
|
||||
final int currentPlayers = game.getPlayers().size();
|
||||
|
||||
final String mapValue = "&f" + game.getMap().getName();
|
||||
final String queueValue = "&f" + currentPlayers + "&7/&f" + maxPlayers;
|
||||
final String startingValue;
|
||||
if (game.isStarting()) {
|
||||
|
||||
startingValue = "&f" + game.getLobbyCount() + "s";
|
||||
|
||||
} else {
|
||||
|
||||
final int required = Math.max(2, SpleggOG.getPlugin().getConfig().getInt("Options.AutoStartPlayers"));
|
||||
final int needed = Math.max(0, required - currentPlayers);
|
||||
startingValue = needed == 0 ? "&aReady" : "&f" + needed + " &7more";
|
||||
objective.getScore(Utils.legacySectionize(line.isEmpty() ? " " : line)).setScore(score--);
|
||||
|
||||
}
|
||||
|
||||
// Higher score = higher on sidebar.
|
||||
setLine(objective, 7, "&7&m ");
|
||||
setLine(objective, 6, mapLabel);
|
||||
setLine(objective, 5, mapValue);
|
||||
setLine(objective, 4, queueLabel);
|
||||
setLine(objective, 3, queueValue);
|
||||
setLine(objective, 2, startingLabel);
|
||||
setLine(objective, 1, startingValue);
|
||||
setLine(objective, 0, "&7&m ");
|
||||
|
||||
}
|
||||
|
||||
private static void setLine(Objective objective, int score, String rawLine) {
|
||||
|
||||
final String text = Utils.legacySectionize(rawLine);
|
||||
objective.getScore(text).setScore(score);
|
||||
|
||||
}
|
||||
|
||||
private static String configLine(String path, String fallback) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package runnables;
|
||||
|
||||
import main.SpleggOG;
|
||||
import managers.Game;
|
||||
import managers.LobbyScoreboard;
|
||||
import managers.Status;
|
||||
|
||||
public class GameTime implements Runnable {
|
||||
@@ -32,6 +33,7 @@ public class GameTime implements Runnable {
|
||||
}
|
||||
|
||||
final int remaining = game.tickTime();
|
||||
LobbyScoreboard.refreshGame(game);
|
||||
if (remaining <= 0) {
|
||||
|
||||
splegg.chat.bc(splegg.getConfig().getString("Messages.Timelimitreached"), game);
|
||||
|
||||
@@ -39,15 +39,22 @@ public class LobbySignUtils {
|
||||
|
||||
}
|
||||
|
||||
// Parsed from the right so a world name containing dots still resolves.
|
||||
public Location stringToLocation(String s) {
|
||||
|
||||
String[] str = s.split("\\.");
|
||||
if (str.length < 4) {
|
||||
|
||||
World w = Bukkit.getServer().getWorld(str[0]);
|
||||
return new Location(null, 0, 0, 0);
|
||||
|
||||
int x = Integer.parseInt(str[1]);
|
||||
int y = Integer.parseInt(str[2]);
|
||||
int z = Integer.parseInt(str[3]);
|
||||
}
|
||||
|
||||
int z = Integer.parseInt(str[str.length - 1]);
|
||||
int y = Integer.parseInt(str[str.length - 2]);
|
||||
int x = Integer.parseInt(str[str.length - 3]);
|
||||
String worldName = String.join(".", java.util.Arrays.copyOf(str, str.length - 3));
|
||||
|
||||
World w = Bukkit.getServer().getWorld(worldName);
|
||||
|
||||
return new Location(w, (double) x, (double) y, (double) z);
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
package utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import com.bergerkiller.bukkit.mw.WorldConfig;
|
||||
|
||||
// Disk-backed record of where each player stood before they entered Splegg territory.
|
||||
// Same layout as TheHerobrine-OG and BuildBattle-OG so a relog or restart still returns them.
|
||||
public class PreJoinLocationStore {
|
||||
|
||||
private static final long DAY_MILLIS = 86400000L;
|
||||
private static final long FLUSH_INTERVAL_TICKS = 100L;
|
||||
|
||||
private record Entry(String world, double x, double y, double z, float yaw, float pitch, long savedAt) {
|
||||
|
||||
static Entry of(Location location) {
|
||||
|
||||
return new Entry(location.getWorld().getName(), location.getX(), location.getY(), location.getZ(),
|
||||
location.getYaw(), location.getPitch(), System.currentTimeMillis());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final File file;
|
||||
private final Map<UUID, Entry> entries = new ConcurrentHashMap<>();
|
||||
private volatile boolean dirty;
|
||||
private BukkitTask flushTask;
|
||||
|
||||
public PreJoinLocationStore(JavaPlugin plugin) {
|
||||
|
||||
this.plugin = plugin;
|
||||
this.file = new File(plugin.getDataFolder(), "prejoin-locations.yaml");
|
||||
load();
|
||||
flushTask = Bukkit.getScheduler().runTaskTimer(plugin, this::save, FLUSH_INTERVAL_TICKS, FLUSH_INTERVAL_TICKS);
|
||||
|
||||
}
|
||||
|
||||
public synchronized void load() {
|
||||
|
||||
entries.clear();
|
||||
if (!file.exists()) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||
ConfigurationSection section = yaml.getConfigurationSection("locations");
|
||||
if (section == null)
|
||||
return;
|
||||
|
||||
long expiryDays = Math.max(0, plugin.getConfig().getInt("Options.PreJoinLocationExpiryDays", 30));
|
||||
long oldestAllowed = expiryDays == 0 ? 0 : System.currentTimeMillis() - (expiryDays * DAY_MILLIS);
|
||||
int expired = 0;
|
||||
|
||||
for (String key : section.getKeys(false)) {
|
||||
|
||||
ConfigurationSection s = section.getConfigurationSection(key);
|
||||
if (s == null)
|
||||
continue;
|
||||
|
||||
String world = s.getString("world");
|
||||
if (world == null) {
|
||||
|
||||
plugin.getLogger().warning("Skipping malformed return location entry '" + key + "'.");
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
UUID id;
|
||||
try {
|
||||
|
||||
id = UUID.fromString(key);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
||||
plugin.getLogger().warning("Skipping return location entry with invalid UUID '" + key + "'.");
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
long savedAt = s.getLong("saved", System.currentTimeMillis());
|
||||
if (savedAt < oldestAllowed) {
|
||||
|
||||
expired++;
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
entries.put(id, new Entry(world, s.getDouble("x"), s.getDouble("y"), s.getDouble("z"),
|
||||
(float) s.getDouble("yaw"), (float) s.getDouble("pitch"), savedAt));
|
||||
|
||||
}
|
||||
|
||||
if (expired > 0)
|
||||
dirty = true;
|
||||
plugin.getLogger().info("Loaded " + entries.size() + " stored return location(s)"
|
||||
+ (expired > 0 ? ", dropped " + expired + " older than " + expiryDays + " day(s)." : "."));
|
||||
|
||||
}
|
||||
|
||||
// Only writes when something actually changed, so the repeating flush is free
|
||||
// on an idle server.
|
||||
public synchronized void save() {
|
||||
|
||||
if (!dirty)
|
||||
return;
|
||||
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
for (Map.Entry<UUID, Entry> e : entries.entrySet()) {
|
||||
|
||||
String path = "locations." + e.getKey();
|
||||
Entry entry = e.getValue();
|
||||
yaml.set(path + ".world", entry.world());
|
||||
yaml.set(path + ".x", entry.x());
|
||||
yaml.set(path + ".y", entry.y());
|
||||
yaml.set(path + ".z", entry.z());
|
||||
yaml.set(path + ".yaw", (double) entry.yaw());
|
||||
yaml.set(path + ".pitch", (double) entry.pitch());
|
||||
yaml.set(path + ".saved", entry.savedAt());
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (!plugin.getDataFolder().exists())
|
||||
plugin.getDataFolder().mkdirs();
|
||||
yaml.save(file);
|
||||
dirty = false;
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
plugin.getLogger().warning("Failed to save return locations: " + e.getMessage());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void put(UUID playerId, Location location) {
|
||||
|
||||
if (playerId == null || location == null || location.getWorld() == null)
|
||||
return;
|
||||
entries.put(playerId, Entry.of(location));
|
||||
dirty = true;
|
||||
|
||||
}
|
||||
|
||||
public void remove(UUID playerId) {
|
||||
|
||||
if (playerId != null && entries.remove(playerId) != null)
|
||||
dirty = true;
|
||||
|
||||
}
|
||||
|
||||
public boolean has(UUID playerId) {
|
||||
|
||||
return playerId != null && entries.containsKey(playerId);
|
||||
|
||||
}
|
||||
|
||||
public String getWorldName(UUID playerId) {
|
||||
|
||||
Entry entry = playerId != null ? entries.get(playerId) : null;
|
||||
return entry != null ? entry.world() : null;
|
||||
|
||||
}
|
||||
|
||||
// Resolves the stored spot against a loaded world. Null when the world is not
|
||||
// loaded right now -- the entry is kept so a later attempt can still use it.
|
||||
public Location get(UUID playerId) {
|
||||
|
||||
Entry entry = playerId != null ? entries.get(playerId) : null;
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
World world = Bukkit.getWorld(entry.world());
|
||||
if (world == null)
|
||||
return null;
|
||||
|
||||
return new Location(world, entry.x(), entry.y(), entry.z(), entry.yaw(), entry.pitch());
|
||||
|
||||
}
|
||||
|
||||
// Same as get(), but pulls the world back in through MyWorlds when it is
|
||||
// unloaded -- a player who came from the End belongs in the End.
|
||||
public Location getOrLoadWorld(UUID playerId) {
|
||||
|
||||
Location resolved = get(playerId);
|
||||
if (resolved != null)
|
||||
return resolved;
|
||||
|
||||
String worldName = getWorldName(playerId);
|
||||
if (worldName == null)
|
||||
return null;
|
||||
|
||||
WorldConfig wc = WorldConfig.getIfExists(worldName);
|
||||
if (wc == null) {
|
||||
|
||||
plugin.getLogger().warning("Stored return world '" + worldName + "' is unknown to MyWorlds.");
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Loading world '" + worldName + "' to return a player to their pre-join location.");
|
||||
if (wc.loadWorld() == null)
|
||||
return null;
|
||||
|
||||
return get(playerId);
|
||||
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
||||
if (flushTask != null) {
|
||||
|
||||
try {
|
||||
|
||||
flushTask.cancel();
|
||||
|
||||
} catch (IllegalStateException ignored) {
|
||||
|
||||
}
|
||||
|
||||
flushTask = null;
|
||||
|
||||
}
|
||||
|
||||
save();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import plugin.api.ScoreboardOGAPI;
|
||||
import plugin.api.SidebarProvider;
|
||||
|
||||
// Soft bridge to Scoreboard-OG. The nested Hook is the only class that touches the
|
||||
// API, and it is loaded only after the plugin is confirmed present and enabled.
|
||||
public final class ScoreboardOGBridge {
|
||||
|
||||
private static final String OWNER = "Splegg-OG";
|
||||
private static Boolean available;
|
||||
|
||||
private ScoreboardOGBridge() {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isAvailable() {
|
||||
|
||||
if (available == null) {
|
||||
|
||||
boolean present = Bukkit.getPluginManager().isPluginEnabled("Scoreboard-OG");
|
||||
available = present && Hook.ready();
|
||||
|
||||
}
|
||||
|
||||
return available;
|
||||
|
||||
}
|
||||
|
||||
public static void claim(Player player, Function<Player, Component> title,
|
||||
Function<Player, List<Component>> lines)
|
||||
{
|
||||
|
||||
if (isAvailable())
|
||||
Hook.claim(player, title, lines);
|
||||
|
||||
}
|
||||
|
||||
public static void release(Player player) {
|
||||
|
||||
if (isAvailable())
|
||||
Hook.release(player);
|
||||
|
||||
}
|
||||
|
||||
public static void releaseAll() {
|
||||
|
||||
if (isAvailable())
|
||||
Hook.releaseAll();
|
||||
|
||||
}
|
||||
|
||||
private static final class Hook {
|
||||
|
||||
static boolean ready() {
|
||||
|
||||
try {
|
||||
|
||||
return ScoreboardOGAPI.isAvailable();
|
||||
|
||||
} catch (NoClassDefFoundError error) {
|
||||
|
||||
// An older Scoreboard-OG without the API; fall back to the local boards.
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void claim(Player player, Function<Player, Component> title, Function<Player, List<Component>> lines) {
|
||||
|
||||
ScoreboardOGAPI.claim(player, OWNER, new SidebarProvider() {
|
||||
|
||||
@Override
|
||||
public Component title(Player viewer) {
|
||||
|
||||
return title.apply(viewer);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Component> lines(Player viewer) {
|
||||
|
||||
return lines.apply(viewer);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
static void release(Player player) {
|
||||
|
||||
ScoreboardOGAPI.release(player, OWNER);
|
||||
|
||||
}
|
||||
|
||||
static void releaseAll() {
|
||||
|
||||
ScoreboardOGAPI.releaseAll(OWNER);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package utils;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import managers.Game;
|
||||
@@ -12,8 +11,6 @@ public class UtilPlayer {
|
||||
boolean alive;
|
||||
Game game;
|
||||
InvStore store;
|
||||
// Where the player stood before joining, in any non-splegg world.
|
||||
Location preJoinLocation;
|
||||
|
||||
public UtilPlayer(Player player) {
|
||||
|
||||
@@ -22,7 +19,6 @@ public class UtilPlayer {
|
||||
this.name = player.getName();
|
||||
this.alive = false;
|
||||
this.store = new InvStore(player);
|
||||
this.preJoinLocation = null;
|
||||
|
||||
}
|
||||
|
||||
@@ -68,16 +64,4 @@ public class UtilPlayer {
|
||||
|
||||
}
|
||||
|
||||
public Location getPreJoinLocation() {
|
||||
|
||||
return this.preJoinLocation != null ? this.preJoinLocation.clone() : null;
|
||||
|
||||
}
|
||||
|
||||
public void setPreJoinLocation(Location location) {
|
||||
|
||||
this.preJoinLocation = location != null ? location.clone() : null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@@ -33,7 +34,8 @@ public class Utils {
|
||||
|
||||
// Enable the conversion of text from config.yml to objects.
|
||||
public FileConfiguration config = SpleggOG.getPlugin().getConfig();
|
||||
public HashMap<String, UtilPlayer> PLAYERS = new HashMap<>();
|
||||
// Keyed by UUID so a name change or relog never resolves to a stale wrapper.
|
||||
public HashMap<UUID, UtilPlayer> PLAYERS = new HashMap<>();
|
||||
private File f;
|
||||
public static String prefix = "&7[&eSplegg&f-&4OG&7] ";
|
||||
|
||||
@@ -168,15 +170,23 @@ public class Utils {
|
||||
|
||||
}
|
||||
|
||||
public UtilPlayer getPlayer(String name) {
|
||||
public UtilPlayer getPlayer(Player player) {
|
||||
|
||||
return (UtilPlayer) this.PLAYERS.get(name);
|
||||
return this.PLAYERS.get(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
public UtilPlayer getPlayer(Player player) {
|
||||
// Returns the tracked wrapper, creating one for a player who joined before the
|
||||
// plugin enabled.
|
||||
public UtilPlayer track(Player player) {
|
||||
|
||||
return (UtilPlayer) this.PLAYERS.get(player.getName());
|
||||
return this.PLAYERS.computeIfAbsent(player.getUniqueId(), id -> new UtilPlayer(player));
|
||||
|
||||
}
|
||||
|
||||
public void untrack(Player player) {
|
||||
|
||||
this.PLAYERS.remove(player.getUniqueId());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,13 @@ Messages:
|
||||
FinishReward: '&BYou received %reward% &BDiamonds for winning!'
|
||||
Scoreboard:
|
||||
Title: '&2Splegg&r-&4OG'
|
||||
Map: '&eMap:'
|
||||
Queue: '&6Players Waiting:'
|
||||
Starting: '&6Starting in:'
|
||||
# Match card lines, shown while a game is live.
|
||||
Alive: '&aPlayers Alive:'
|
||||
BrokenBlocks: '&eBlocks Broken:'
|
||||
TimeLeft: '&6Time Left:'
|
||||
Money:
|
||||
KillPlayer: 0
|
||||
# Diamonds awarded to the match winner.
|
||||
@@ -63,6 +67,8 @@ Options:
|
||||
VotingMaps: 2
|
||||
EndVotingAt: 10
|
||||
VotingReminder: 30
|
||||
# Days a stored pre-game return location survives restarts. 0 keeps them forever.
|
||||
PreJoinLocationExpiryDays: 30
|
||||
# Splegg only manages dedicated lobby and in-game worlds. The vanilla overworld
|
||||
# (world, world_nether, world_the_end) is hard-blacklisted in code and cannot
|
||||
# be added here -- splegg will refuse to put a map, inventory group, or any
|
||||
|
||||
@@ -3,19 +3,17 @@ main: main.SpleggOG
|
||||
version: '${version}'
|
||||
authors: [Hraponssi, MrLuangamer, Theluangamer9416, NotAlexNoyle]
|
||||
api-version: '${apiVersion}'
|
||||
depend: [DiamondBank-OG, Essentials-OG, WorldEdit, MyWorlds, Utilities-OG]
|
||||
softdepend: [My_Worlds, Chat-OG]
|
||||
depend: [DiamondBank-OG, WorldEdit, MyWorlds, Utilities-OG]
|
||||
softdepend: [My_Worlds, Chat-OG, Scoreboard-OG]
|
||||
|
||||
# /vote and /v are not declared here on purpose: VoteCommandListener claims them inside Splegg
|
||||
# territory only, so VotingPlugin keeps the labels everywhere else.
|
||||
commands:
|
||||
splegg:
|
||||
description: The command prefix for all Splegg-OG commands.
|
||||
aliases: [sp]
|
||||
hub:
|
||||
description: Leave the Splegg lobby or game and return to the main world.
|
||||
vote:
|
||||
description: Vote for a Splegg map.
|
||||
usage: /<command> <map number>
|
||||
aliases: [v]
|
||||
spjoin:
|
||||
description: Join a Splegg lobby by its id.
|
||||
usage: /<command> [lobby]
|
||||
|
||||
Reference in new issue
Block a user