Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,18 @@ public class InfoSuggest implements SuggestionProvider<ServerCommandSource> {
"PEACEFUL", "EASY", "NORMAL", "HARD"
};

/**
* Valid Time Actions
*/
public static String[] time_actions = {
"set", "add", "query"
};

/**
* Valid Subcommands
*/
private static String[] subcommands = {
"tp", "list", "version", "create", "spawn", "setspawn", "gamerule", "help", "difficulty", "portal"
"tp", "list", "version", "create", "spawn", "setspawn", "gamerule", "help", "difficulty", "time", "info", "portal"
// TODO: Add: delete, load, unload, info, clone, who, import
};

Expand Down Expand Up @@ -114,6 +121,21 @@ public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerComman
return builder.buildFuture();
}

if (cmds[1].equalsIgnoreCase("time") && (ALL || Perm.has(plr, "multiworld.time"))) {
String last = input.substring(input.lastIndexOf(' ')).trim();
for (String name : time_actions) {
if (name.startsWith(last) || last.contains("time") || name.toLowerCase().contains(last)) {
builder.suggest(name);
}
}
return builder.buildFuture();
}

if (cmds[1].equalsIgnoreCase("info") && (ALL || Perm.has(plr, "multiworld.info"))) {
for (String s : getWorldNames()) builder.suggest(s);
return builder.buildFuture();
}

if (cmds[1].equalsIgnoreCase("portal")) {
for (String s : PortalCommand.SUBCOMMANDS) {
builder.suggest(s);
Expand All @@ -140,6 +162,26 @@ public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerComman
});
for (String s : names) builder.suggest(s);
}

// Time value (argument 3): query types or named times.
if (cmds[1].equalsIgnoreCase("time") && (ALL || Perm.has(plr, "multiworld.time")) ) {
if (cmds[2].equalsIgnoreCase("query")) {
builder.suggest("daytime");
builder.suggest("gametime");
builder.suggest("day");
} else {
builder.suggest("day");
builder.suggest("noon");
builder.suggest("night");
builder.suggest("midnight");
}
}
}

// Time world id (argument 4)
if (cmds[1].equalsIgnoreCase("time") && (ALL || Perm.has(plr, "multiworld.time"))
&& (cmds.length <= 4 || (cmds.length <= 5 && !input.endsWith(" "))) && cmds.length >= 4) {
for (String s : getWorldNames()) builder.suggest(s);
}

// Create Command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
import me.isaiah.multiworld.command.CreateCommand;
import me.isaiah.multiworld.command.DifficultyCommand;
import me.isaiah.multiworld.command.IGameruleCommand;
import me.isaiah.multiworld.command.InfoCommand;
import me.isaiah.multiworld.command.PortalCommand;
import me.isaiah.multiworld.command.SetspawnCommand;
import me.isaiah.multiworld.command.SpawnCommand;
import me.isaiah.multiworld.command.TimeCommand;
import me.isaiah.multiworld.command.TpCommand;
import me.isaiah.multiworld.command.Util;
import me.isaiah.multiworld.perm.Perm;
Expand Down Expand Up @@ -66,7 +68,9 @@ public class MultiworldMod {
"&a/mw list&r - List all worlds",
"&a/mw gamerule <rule> <value>&r - Change a worlds Gamerules",
"&a/mw create <id> <env> [-g=<generator> -s=<seed>]&r - create a new world",
"&a/mw difficulty <value> [world id] - Sets the difficulty of a world"
"&a/mw difficulty <value> [world id] - Sets the difficulty of a world",
"&a/mw time <set|add|query> <time> [world id]&r - Change a world's time of day",
"&a/mw info [world id]&r - Show info about a world (players, time, weather, gamerules, spawn)"
};

// Multiworld Mod Version
Expand Down Expand Up @@ -163,8 +167,11 @@ public static void on_server_started(MinecraftServer mc) {
if (loaded > 0) {
LOGGER.info("Found " + loaded + " saved world portals.");
}

// Verify each loaded portal's destination is synced; logs an ERROR per broken one.
Portal.checkPortalsSync();
}

}

public static void getFileConfiguration() {
Expand Down Expand Up @@ -210,6 +217,8 @@ public static boolean permissionLevel(ServerCommandSource source, int level) {
"multiworld.spawn",
"multiworld.gamerule",
"multiworld.difficulty",
"multiworld.time",
"multiworld.info",
"multiworld.tp",
"multiworld.create",
"multiworld.portal"
Expand Down Expand Up @@ -334,6 +343,16 @@ public static int broadcast(ServerCommandSource source, Formatting formatting, S
return DifficultyCommand.run(mc, plr, args);
}

// Time Command
if (args[0].equalsIgnoreCase("time") && Perm.check(plr, "multiworld.time")) {
return TimeCommand.run(mc, plr, args);
}

// Info Command
if (args[0].equalsIgnoreCase("info") && Perm.check(plr, "multiworld.info")) {
return InfoCommand.run(mc, plr, args);
}

// TP Command
if (args[0].equalsIgnoreCase("tp") ) {
if (!(ALL || Perm.has(plr, "multiworld.tp"))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,22 @@ public static void reinit_world_from_config(MinecraftServer mc, String id) {
wc.createNewFile();
}
config = new FileConfiguration(wc);

// Skip empty/incomplete legacy world configs (e.g. a stale, blank yml): nothing to load.
if (!config.is_set("environment")) {
LOGGER.warn("Skipping incomplete legacy world config: " + wc.getPath());
return;
}

String env = config.getString("environment");

long seed = 0;

try {
seed = config.getLong("seed");
} catch (Exception e) {
seed = config.getInt("seed");
if (config.is_set("seed")) {
try {
seed = config.getLong("seed");
} catch (Exception e) {
try { seed = config.getInt("seed"); } catch (Exception ignore) { /* keep default 0 */ }
}
}

ChunkGenerator gen = get_chunk_gen(mc, env);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package me.isaiah.multiworld.command;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import me.isaiah.multiworld.MultiworldMod;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameRules;
import net.minecraft.world.level.ServerWorldProperties;

public class InfoCommand implements Command {

/**
* "/mw info [world id]"
*
* Read-only diagnostics for a dimension: players, time of day, weather (+ change timers),
* gamerules differing from the overworld, and spawn position. Targets the player's current
* dimension by default, or the named world if an id is supplied. Mirrors {@link DifficultyCommand}.
*/
public static int run(MinecraftServer mc, ServerPlayerEntity plr, String[] args) {
ServerWorld w = Command.getWorldFor(plr);

// Optional world id (args[1]) — same resolution as DifficultyCommand.
if (args.length >= 2) {
String a1 = args[1];

HashMap<String, ServerWorld> worlds = new HashMap<>();
mc.getWorldRegistryKeys().forEach(r -> {
ServerWorld world = mc.getWorld(r);
worlds.put(r.getValue().toString(), world);
});

if (a1.indexOf(':') == -1) a1 = "multiworld:" + a1;

if (worlds.containsKey(a1)) {
w = worlds.get(a1);
} else {
MultiworldMod.message(plr, "&cWorld not found: " + a1);
return 1;
}
}

String id = w.getRegistryKey().getValue().toString();

MultiworldMod.message(plr, "&b=== Multiworld Info: &r" + id + " &b===");

// Players
List<ServerPlayerEntity> players = w.getPlayers();
if (players.isEmpty()) {
MultiworldMod.message(plr, "&aPlayers&r (0): (none)");
} else {
StringBuilder names = new StringBuilder();
for (ServerPlayerEntity p : players) {
if (names.length() > 0) names.append(", ");
names.append(p.getName().getString());
}
MultiworldMod.message(plr, "&aPlayers&r (" + players.size() + "): " + names);
}

// Time
long timeOfDay = w.getTimeOfDay();
long day = timeOfDay / 24000L;
long dayTime = timeOfDay % 24000L;
MultiworldMod.message(plr, "&aTime&r: " + timeOfDay + " (day " + day + ", daytime " + dayTime + ")");

// Weather
boolean raining = w.isRaining();
boolean thundering = w.isThundering();
String state = thundering ? "thunder" : (raining ? "rain" : "clear");
StringBuilder weather = new StringBuilder("&aWeather&r: " + state);
if (w.getLevelProperties() instanceof ServerWorldProperties props) {
int clear = props.getClearWeatherTime();
int rain = props.getRainTime();
int thunder = props.getThunderTime();
weather.append("(clear=").append(clear)
.append(", rain=").append(rain)
.append(", thunder=").append(thunder).append(")");
}
MultiworldMod.message(plr, weather.toString());

// Gamerules differing from the overworld
GameRules worldRules = w.getGameRules();
GameRules overworldRules = mc.getOverworld().getGameRules();
List<String> diffs = new ArrayList<>();
worldRules.accept(new GameRules.Visitor() {
@Override
public <T extends GameRules.Rule<T>> void visit(GameRules.Key<T> key, GameRules.Type<T> type) {
String here = worldRules.get(key).serialize();
String over = overworldRules.get(key).serialize();
if (!here.equals(over)) {
diffs.add(key.getName() + ": " + here + " (overworld: " + over + ")");
}
}
});
if (diffs.isEmpty()) {
MultiworldMod.message(plr, "&aGamerules&r: (same as overworld)");
} else {
MultiworldMod.message(plr, "&aGamerules differing from overworld&r (" + diffs.size() + "):");
for (String d : diffs) {
MultiworldMod.message(plr, " &7- &r" + d);
}
}

// Spawn (same source as /mw spawn: Multiworld config "spawnpos" set by /mw setspawn, with fallback)
BlockPos spawn = SpawnCommand.getSpawn(w);
MultiworldMod.message(plr, "&aSpawn&r: " + spawn.getX() + " / " + spawn.getY() + " / " + spawn.getZ());

return 1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ public static Portal getKnownPortal(String key) {
"&a/mw portal select <name>&r - TODO",
"&a/mw portal wand&r - Gives a Portal Creation Wand",
"&a/mw portal info <name>&r - Displays information about a portal.",
"&a/mw portal remove <name>&r - TODO", // Remove the portal whose name is given.",
"&a/mw portal remove <name>&r - Removes the portal whose name is given.",
};

/**
* Valid Subcommands
*/
public static String[] SUBCOMMANDS = {
"create", "wand", "info"
"create", "wand", "info", "remove"
};

/**
Expand Down Expand Up @@ -98,8 +98,13 @@ public static int run(MinecraftServer mc, ServerPlayerEntity plr, String[] args)
for (Portal p : KNOWN_PORTALS.values()) {
message(plr, " Portal: \"" + p.getName() + "\": ");
String from = p.getOriginWorldId() + " (" + p.getMinPos().toShortString();
String to = p.getDestWorldName() + " (" + p.getDestLocation().toShortString();
message(plr, " - " + from + ") -> " + to + ")");
if (p.canTeleport()) {
String to = p.getDestWorldName() + " (" + p.getDestLocation().toShortString();
message(plr, " - " + from + ") -> " + to + ")");
} else {
message(plr, " - " + from + ") -> " + p.getDestWorldName());
message(plr, " &c! Not synced - will not teleport");
}
}
return 1;
}
Expand All @@ -108,15 +113,58 @@ public static int run(MinecraftServer mc, ServerPlayerEntity plr, String[] args)
Portal p = KNOWN_PORTALS.getOrDefault(name, getPortalIgnoreCase(name));
if (null == p) {
message(plr, "&4Portal with the name " + name + " not found!");
return 0;
}
message(plr, "&6Multiworld Portals:");
message(plr, " Portal: \"" + p.getName() + "\": ");
message(plr, " &6- From:&r " + p.getOriginWorldId() + " @ (" + p.getMinPos().toShortString() + ")");
message(plr, " &6- To:&r " + p.getDestWorldName() + " @ (" + p.getDestLocation().toShortString() + ")");
if (p.canTeleport()) {
message(plr, " &6- To:&r " + p.getDestWorldName() + " @ (" + p.getDestLocation().toShortString() + ")");
} else {
message(plr, " &6- To:&r " + p.getDestWorldName());
}
message(plr, " &6- Destination:&r " + p.getDestination());
message(plr, " &6- Status:&r " + (p.canTeleport()
? "&aOK" : "&cNot synced (destination world not loaded)"));
message(plr, " &6- Portal Frame:&r " + p.getLocationConfigString());
}

// Portal Remove Command
if (args[1].equalsIgnoreCase("remove")) {
if (!Perm.has(plr, "multiworld.portal.remove")) {
message(plr, "Invalid permission! Missing: multiworld.portal.remove");
return 0;
}

if (args.length < 3) {
message(plr, "Usage: /mw portal remove <name>");
return 0;
}

String name = args[2];
Portal p = KNOWN_PORTALS.getOrDefault(name.toLowerCase(Locale.ROOT), getPortalIgnoreCase(name));
if (null == p) {
message(plr, "&4Portal with the name " + name + " not found!");
return 0;
}

// Clear the portal blocks from the world
p.removePortalArea();

// Remove from the in-memory map
KNOWN_PORTALS.remove(p.getName().toLowerCase(Locale.ROOT));

// Remove from portals.yml so it does not reappear on restart
try {
p.delete();
} catch (IOException e) {
e.printStackTrace();
}

message(plr, "&aPortal \"" + p.getName() + "\" removed.");
return 1;
}

// Portal Wand Command
if (args[1].equalsIgnoreCase("wand")) {
if (!Perm.has(plr, "multiworld.portal.wand")) {
Expand Down
Loading