Make map setup more straightforward for admins

This commit is contained in:
NotAlexNoyle committed 2026-05-11 02:42:05 -04:00
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) {
+5
View File
@@ -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]