Make map setup more straightforward for admins
This commit is contained in:
1 parent
c4801db66b
commit
60d31341d0
7 files changed
+382
-4
No files matched your search
@@ -13,6 +13,8 @@ import uk.hotten.herobrine.commands.JoinLobbyCompleter;
|
||||
import uk.hotten.herobrine.commands.PauseTimerCommand;
|
||||
import uk.hotten.herobrine.commands.ReloadConfigsCommand;
|
||||
import uk.hotten.herobrine.commands.SetHerobrineCommand;
|
||||
import uk.hotten.herobrine.commands.SetSpawnCommand;
|
||||
import uk.hotten.herobrine.commands.SetSpawnCompleter;
|
||||
import uk.hotten.herobrine.commands.SpectateCommand;
|
||||
import uk.hotten.herobrine.commands.VoteCommand;
|
||||
import uk.hotten.herobrine.commands.VoteCompleter;
|
||||
@@ -86,6 +88,8 @@ public class HerobrinePluginOG extends JavaPlugin {
|
||||
getCommand("hbdeletelobby").setTabCompleter(new DeleteLobbyCompleter());
|
||||
getCommand("hbspectate").setExecutor(new SpectateCommand());
|
||||
getCommand("hbreloadconfigs").setExecutor(new ReloadConfigsCommand());
|
||||
getCommand("hbsetspawn").setExecutor(new SetSpawnCommand());
|
||||
getCommand("hbsetspawn").setTabCompleter(new SetSpawnCompleter());
|
||||
|
||||
if (getServer().getPluginManager().getPlugin("IllegalStack-OG") != null) {
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
package uk.hotten.herobrine.commands;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
|
||||
|
||||
import uk.hotten.herobrine.utils.Message;
|
||||
import uk.hotten.herobrine.world.data.Datapoint;
|
||||
import uk.hotten.herobrine.world.data.DatapointType;
|
||||
import uk.hotten.herobrine.world.data.MapData;
|
||||
|
||||
public class SetSpawnCommand implements CommandExecutor {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||
|
||||
if (!(sender instanceof Player)) {
|
||||
|
||||
Message.send(sender, Message.format("&cYou must be in-game to set spawn points."));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Player player = (Player) sender;
|
||||
|
||||
if (args == null || args.length == 0) {
|
||||
|
||||
sendUsage(player);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
DatapointType type = parseType(args[0]);
|
||||
if (type == null) {
|
||||
|
||||
sendUsage(player);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Integer shardIndex = null;
|
||||
String mapNameOverride = null;
|
||||
|
||||
if (type == DatapointType.SHARD_SPAWN) {
|
||||
|
||||
// /hbsetspawn shard [index] [mapName]
|
||||
if (args.length >= 2 && !args[1].equalsIgnoreCase("append")) {
|
||||
|
||||
try {
|
||||
|
||||
shardIndex = Integer.parseInt(args[1]);
|
||||
|
||||
} catch (NumberFormatException ex) {
|
||||
|
||||
Message.send(player, Message.format("&cShard index must be an integer (or 'append')."));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (args.length >= 3)
|
||||
mapNameOverride = args[2];
|
||||
|
||||
} else {
|
||||
|
||||
// /hbsetspawn <type> [mapName]
|
||||
if (args.length >= 2)
|
||||
mapNameOverride = args[1];
|
||||
|
||||
}
|
||||
|
||||
JavaPlugin plugin = JavaPlugin.getPlugin(uk.hotten.herobrine.HerobrinePluginOG.class);
|
||||
File baseDir = resolveBaseDir(plugin);
|
||||
|
||||
String mapName = mapNameOverride != null ? mapNameOverride : deriveMapName(player, baseDir);
|
||||
if (mapName == null) {
|
||||
|
||||
Message.send(player,
|
||||
Message.format("&cCould not derive a map name from world '" + player.getWorld().getName()
|
||||
+ "'. Pass it explicitly: /hbsetspawn " + args[0].toLowerCase()
|
||||
+ (type == DatapointType.SHARD_SPAWN ? " <index|append>" : "") + " <mapName>"));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
File mapDir = new File(baseDir, mapName);
|
||||
if (!mapDir.exists() || !mapDir.isDirectory()) {
|
||||
|
||||
Message.send(player, Message.format("&cMap folder does not exist: &e" + mapDir.getPath()
|
||||
+ "&c -- create it (a copy of the world) first."));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
File mapdataFile = new File(mapDir, "mapdata.yaml");
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
|
||||
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES));
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
MapData data;
|
||||
if (mapdataFile.exists()) {
|
||||
|
||||
try {
|
||||
|
||||
data = mapper.readValue(mapdataFile, MapData.class);
|
||||
if (data == null)
|
||||
data = new MapData(mapName, "Unknown", -100, 1000, new ArrayList<>());
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Message.send(player, Message.format("&cFailed to parse existing mapdata.yaml: " + e.getMessage()));
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
data = new MapData(mapName, "Unknown", -100, 1000, new ArrayList<>());
|
||||
|
||||
}
|
||||
|
||||
if (data.getName() == null || data.getName().isBlank())
|
||||
data.setName(mapName);
|
||||
if (data.getBuilder() == null || data.getBuilder().isBlank())
|
||||
data.setBuilder("Unknown");
|
||||
if (data.getDatapoints() == null)
|
||||
data.setDatapoints(new ArrayList<>());
|
||||
|
||||
int x = player.getLocation().getBlockX();
|
||||
int y = player.getLocation().getBlockY();
|
||||
int z = player.getLocation().getBlockZ();
|
||||
|
||||
if (type == DatapointType.SHARD_SPAWN) {
|
||||
|
||||
// Collect current shard points so we can address them by index.
|
||||
ArrayList<Datapoint> shards = new ArrayList<>();
|
||||
for (Datapoint dp : data.getDatapoints())
|
||||
if (dp.getType() == DatapointType.SHARD_SPAWN)
|
||||
shards.add(dp);
|
||||
|
||||
if (shardIndex == null) {
|
||||
|
||||
data.getDatapoints().add(new Datapoint(DatapointType.SHARD_SPAWN, x, y, z));
|
||||
Message.send(player, Message.format("&aAdded shard spawn #" + (shards.size() + 1) + " for &b" + mapName
|
||||
+ "&a at &e" + x + "," + y + "," + z));
|
||||
|
||||
} else {
|
||||
|
||||
if (shardIndex < 1 || shardIndex > shards.size() + 1) {
|
||||
|
||||
Message.send(player, Message.format("&cShard index must be between 1 and " + (shards.size() + 1)
|
||||
+ " (inclusive). Use 'append' to add a new one."));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (shardIndex == shards.size() + 1) {
|
||||
|
||||
data.getDatapoints().add(new Datapoint(DatapointType.SHARD_SPAWN, x, y, z));
|
||||
Message.send(player, Message.format("&aAdded shard spawn #" + shardIndex + " for &b" + mapName
|
||||
+ "&a at &e" + x + "," + y + "," + z));
|
||||
|
||||
} else {
|
||||
|
||||
Datapoint target = shards.get(shardIndex - 1);
|
||||
target.setX(x);
|
||||
target.setY(y);
|
||||
target.setZ(z);
|
||||
Message.send(player, Message.format("&aUpdated shard spawn #" + shardIndex + " for &b" + mapName
|
||||
+ "&a to &e" + x + "," + y + "," + z));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Single-instance datapoints: replace existing or add.
|
||||
Datapoint existing = null;
|
||||
for (Datapoint dp : data.getDatapoints()) {
|
||||
|
||||
if (dp.getType() == type) {
|
||||
|
||||
existing = dp;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
|
||||
existing.setX(x);
|
||||
existing.setY(y);
|
||||
existing.setZ(z);
|
||||
|
||||
} else {
|
||||
|
||||
data.getDatapoints().add(new Datapoint(type, x, y, z));
|
||||
|
||||
}
|
||||
|
||||
Message.send(player, Message
|
||||
.format("&aSet &b" + type.name() + "&a for &b" + mapName + "&a to &e" + x + "," + y + "," + z));
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(mapdataFile, data);
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
Message.send(player,
|
||||
Message.format("&cFailed to write mapdata.yaml: " + e.getMessage() + " (changes lost)"));
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
Message.send(player, Message.format("&7Wrote &e" + mapdataFile.getPath()
|
||||
+ "&7. Restart the lobby (or /hbreloadconfigs after rebuild) for changes to take effect."));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private static DatapointType parseType(String raw) {
|
||||
|
||||
switch (raw.toLowerCase(Locale.ROOT)) {
|
||||
|
||||
case "survivor":
|
||||
case "survivors":
|
||||
case "survivor_spawn":
|
||||
return DatapointType.SURVIVOR_SPAWN;
|
||||
case "herobrine":
|
||||
case "hb":
|
||||
case "herobrine_spawn":
|
||||
return DatapointType.HEROBRINE_SPAWN;
|
||||
case "alter":
|
||||
case "altar":
|
||||
return DatapointType.ALTER;
|
||||
case "shard":
|
||||
case "shards":
|
||||
case "shard_spawn":
|
||||
return DatapointType.SHARD_SPAWN;
|
||||
default:
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void sendUsage(Player player) {
|
||||
|
||||
Message.send(player,
|
||||
Message.format("&6/hbsetspawn <survivor|herobrine|alter|shard> [shardIndex|append] [mapName]"));
|
||||
Message.send(player, Message.format("&7Stand at the location, then run the command."));
|
||||
Message.send(player, Message.format(
|
||||
"&7mapName is auto-derived from your world (e.g. 'HB1-Ancient_Plateau' -> 'Ancient_Plateau')."));
|
||||
|
||||
}
|
||||
|
||||
private static String deriveMapName(Player player, File baseDir) {
|
||||
|
||||
String worldName = player.getWorld().getName();
|
||||
|
||||
// Direct match against a source map folder (operator manually loaded the source
|
||||
// via MyWorlds).
|
||||
File direct = new File(baseDir, worldName);
|
||||
if (direct.exists() && direct.isDirectory())
|
||||
return worldName;
|
||||
|
||||
// Strip <lobbyId>- prefix (e.g. HB1-Ancient_Plateau -> Ancient_Plateau).
|
||||
int dash = worldName.indexOf('-');
|
||||
if (dash > 0 && dash < worldName.length() - 1) {
|
||||
|
||||
String stripped = worldName.substring(dash + 1);
|
||||
File guess = new File(baseDir, stripped);
|
||||
if (guess.exists() && guess.isDirectory())
|
||||
return stripped;
|
||||
|
||||
}
|
||||
|
||||
// No matching source dir.
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private static File resolveBaseDir(JavaPlugin plugin) {
|
||||
|
||||
String fileBase = plugin.getConfig().getString("mapBase");
|
||||
if (fileBase == null || fileBase.isBlank())
|
||||
return plugin.getServer().getWorldContainer();
|
||||
File f = new File(fileBase);
|
||||
if (f.isAbsolute())
|
||||
return f;
|
||||
return new File(plugin.getServer().getWorldContainer(), fileBase);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package uk.hotten.herobrine.commands;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
|
||||
public class SetSpawnCompleter implements TabCompleter {
|
||||
|
||||
private static final List<String> TYPES = Arrays.asList("survivor", "herobrine", "alter", "shard");
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command cmd, String label, String[] args) {
|
||||
|
||||
if (args.length == 1) {
|
||||
|
||||
String prefix = args[0].toLowerCase();
|
||||
return TYPES.stream().filter(s -> s.startsWith(prefix)).collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
if (args.length == 2 && args[0].equalsIgnoreCase("shard"))
|
||||
return Arrays.asList("append", "1", "2", "3", "4", "5");
|
||||
|
||||
return Collections.emptyList();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -299,12 +299,19 @@ public class GameManager {
|
||||
|
||||
public void startWaiting() {
|
||||
|
||||
startWaiting(true);
|
||||
|
||||
}
|
||||
|
||||
public void startWaiting(boolean cleanGameWorld) {
|
||||
|
||||
setGameState(GameState.WAITING);
|
||||
if (waitingRunnable != null)
|
||||
waitingRunnable.cancel();
|
||||
|
||||
// In case the timer decreased from players leaving and a world was loaded
|
||||
Bukkit.getServer().getScheduler().runTask(plugin, () -> gameLobby.getWorldManager().clean(false));
|
||||
if (cleanGameWorld)
|
||||
Bukkit.getServer().getScheduler().runTask(plugin, () -> gameLobby.getWorldManager().clean(false));
|
||||
startTimer = getGameLobby().getLobbyConfig().getStartTime();
|
||||
|
||||
waitingRunnable = new WaitingRunnable(this).runTaskTimerAsynchronously(plugin, 0, 10);
|
||||
@@ -396,9 +403,12 @@ public class GameManager {
|
||||
"ABORTING start(): missing spawn datapoint(s). herobrineSpawn=" + worldManager.herobrineSpawn
|
||||
+ " survivorSpawn=" + worldManager.survivorSpawn
|
||||
+ " -- check the map's mapdata.yaml for SURVIVOR_SPAWN and HEROBRINE_SPAWN entries.");
|
||||
Message.broadcast(gameLobby, Message.format(
|
||||
"&cGame failed to start: arena map is missing spawn points. See console. Returning to waiting..."));
|
||||
startWaiting();
|
||||
Message.broadcast(gameLobby, Message.format("&cGame failed to start: arena map is missing spawn points."));
|
||||
String gw = worldManager.getGameWorld() != null ? worldManager.getGameWorld().getName() : "<none>";
|
||||
Message.broadcast(gameLobby, Message.format("&eOperators: teleport to &b" + gw + "&e via &7/world tp " + gw
|
||||
+ "&e and use &7/hbsetspawn <type>&e to set spawn points."));
|
||||
// Keep the game world loaded so an op can walk to spawn locations and set them.
|
||||
startWaiting(false);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
package uk.hotten.herobrine.world.data;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class Datapoint {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private DatapointType type;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Integer x;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Integer y;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Integer z;
|
||||
|
||||
public Datapoint(DatapointType type, int x, int y, int z) {
|
||||
|
||||
@@ -2,22 +2,28 @@ package uk.hotten.herobrine.world.data;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class MapData {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String name;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String builder;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private double shardMin;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private double shardMax;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<Datapoint> datapoints;
|
||||
|
||||
public MapData(String name, String builder, double shardMin, double shardMax, List<Datapoint> datapoints) {
|
||||
|
||||
@@ -60,3 +60,8 @@ commands:
|
||||
description: Reload the lobby configuration files.
|
||||
permission: theherobrine.command.reloadconfigs
|
||||
usage: /<command>
|
||||
|
||||
hbsetspawn:
|
||||
description: Set a spawn datapoint for the current map at your location.
|
||||
permission: theherobrine.command.setspawn
|
||||
usage: /<command> <survivor|herobrine|alter|shard> [shardIndex|append] [mapName]
|
||||
Reference in new issue
Block a user