Add dimension gamemode and read gamerule list
This commit is contained in:
@@ -9,6 +9,7 @@ public final class ConfigPaths {
|
||||
public static final Path CONFIG = DIRECTORY.resolve("config.json");
|
||||
public static final Path PLAYER_POSITIONS = DIRECTORY.resolve("player-positions.json");
|
||||
public static final Path PORTALS = DIRECTORY.resolve("portals.json");
|
||||
public static final Path DIMENSION_GAMEMODES = DIRECTORY.resolve("dimension-gamemodes.json");
|
||||
|
||||
private ConfigPaths() {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package id.dartokloning.lobbysystems;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Mengelola gamemode yang diterapkan ketika player memasuki dimension tertentu.
|
||||
* Minecraft menyimpan gamemode pada player, bukan pada dimension, jadi Lobby Systems
|
||||
* menerapkan mapping dimension -> gamemode setiap kali perpindahan dimension terdeteksi.
|
||||
*/
|
||||
public final class DimensionGameModeManager {
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final Type MAP_TYPE = new TypeToken<LinkedHashMap<String, String>>() {}.getType();
|
||||
private static final Path PATH = ConfigPaths.DIMENSION_GAMEMODES;
|
||||
|
||||
private final Map<String, String> configured = new LinkedHashMap<>();
|
||||
private final Map<UUID, String> lastDimension = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean load() {
|
||||
if (!Files.exists(PATH)) {
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
try (Reader reader = Files.newBufferedReader(PATH)) {
|
||||
Map<String, String> loaded = GSON.fromJson(reader, MAP_TYPE);
|
||||
configured.clear();
|
||||
if (loaded != null) {
|
||||
for (Map.Entry<String, String> entry : loaded.entrySet()) {
|
||||
String dimension = normalizeDimension(entry.getKey());
|
||||
String mode = normalizeMode(entry.getValue());
|
||||
if (dimension.isBlank()) {
|
||||
LobbySystemsMod.LOGGER.warn("Skipping blank dimension in {}", PATH);
|
||||
continue;
|
||||
}
|
||||
if (parseMode(mode) == null) {
|
||||
LobbySystemsMod.LOGGER.warn("Skipping invalid gamemode '{}' for dimension '{}' in {}", entry.getValue(), dimension, PATH);
|
||||
continue;
|
||||
}
|
||||
configured.put(dimension, mode);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to load {}. Keeping previous dimension gamemode config.", PATH, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean save() {
|
||||
try {
|
||||
Files.createDirectories(PATH.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(PATH)) {
|
||||
GSON.toJson(configured, MAP_TYPE, writer);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to save {}", PATH, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void tick(MinecraftServer server) {
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
String dimension = player.level().dimension().identifier().toString();
|
||||
String previous = lastDimension.put(player.getUUID(), dimension);
|
||||
if (!dimension.equals(previous)) {
|
||||
applyForPlayer(player, dimension, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisconnect(UUID uuid) {
|
||||
lastDimension.remove(uuid);
|
||||
}
|
||||
|
||||
public String get(String dimension) {
|
||||
return configured.get(normalizeDimension(dimension));
|
||||
}
|
||||
|
||||
public Map<String, String> all() {
|
||||
return Map.copyOf(configured);
|
||||
}
|
||||
|
||||
public boolean set(String dimension, String mode) {
|
||||
String normalizedDimension = normalizeDimension(dimension);
|
||||
String normalizedMode = normalizeMode(mode);
|
||||
if (normalizedDimension.isBlank() || parseMode(normalizedMode) == null) return false;
|
||||
configured.put(normalizedDimension, normalizedMode);
|
||||
return save();
|
||||
}
|
||||
|
||||
public boolean remove(String dimension) {
|
||||
String key = normalizeDimension(dimension);
|
||||
if (configured.remove(key) == null) return false;
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public int applyToDimension(MinecraftServer server, String dimension) {
|
||||
int changed = 0;
|
||||
String normalized = normalizeDimension(dimension);
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
if (player.level().dimension().identifier().toString().equals(normalized)) {
|
||||
if (applyForPlayer(player, normalized, false)) changed++;
|
||||
lastDimension.put(player.getUUID(), normalized);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean applyForPlayer(ServerPlayer player, String dimension, boolean log) {
|
||||
String configuredMode = configured.get(normalizeDimension(dimension));
|
||||
if (configuredMode == null) return false;
|
||||
|
||||
GameType gameType = parseMode(configuredMode);
|
||||
if (gameType == null) return false;
|
||||
|
||||
boolean changed = player.setGameMode(gameType);
|
||||
if (log && changed) {
|
||||
LobbyLog.state(
|
||||
"DIMENSION GAMEMODE APPLIED",
|
||||
"Player: " + player.getName().getString(),
|
||||
"Dimension: " + dimension,
|
||||
"Gamemode: " + configuredMode
|
||||
);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static GameType parseMode(String raw) {
|
||||
if (raw == null) return null;
|
||||
return switch (raw.trim().toLowerCase()) {
|
||||
case "survival" -> GameType.SURVIVAL;
|
||||
case "creative" -> GameType.CREATIVE;
|
||||
case "adventure" -> GameType.ADVENTURE;
|
||||
case "spectator" -> GameType.SPECTATOR;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static String normalizeMode(String raw) {
|
||||
return raw == null ? "" : raw.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private static String normalizeDimension(String raw) {
|
||||
return raw == null ? "" : raw.trim().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package id.dartokloning.lobbysystems;
|
||||
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
@@ -9,6 +10,7 @@ import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
@@ -17,9 +19,15 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.gamerules.GameRule;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class LobbySystemsMod implements ModInitializer {
|
||||
public static final String MOD_ID = "dartokloning";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("Lobby Systems");
|
||||
@@ -29,6 +37,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
private static final PositionManager POSITIONS = new PositionManager();
|
||||
private static final PortalManager PORTALS = new PortalManager();
|
||||
private static final TeleportCoordinator TELEPORTS = new TeleportCoordinator();
|
||||
private static final DimensionGameModeManager DIMENSION_GAMEMODES = new DimensionGameModeManager();
|
||||
private static int saveTicker;
|
||||
|
||||
@Override
|
||||
@@ -59,6 +68,13 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
"Portals: " + PORTALS.size()
|
||||
);
|
||||
|
||||
boolean gameModesLoaded = DIMENSION_GAMEMODES.load();
|
||||
LobbyLog.state(
|
||||
gameModesLoaded ? "DIMENSION GAMEMODES LOADED" : "DIMENSION GAMEMODES LOAD FAILED",
|
||||
"File: " + ConfigPaths.DIMENSION_GAMEMODES,
|
||||
"Configured dimensions: " + DIMENSION_GAMEMODES.all().size()
|
||||
);
|
||||
|
||||
ServerPlayConnectionEvents.JOIN.register((listener, sender, server) -> {
|
||||
ServerPlayer player = listener.getPlayer();
|
||||
server.execute(() -> {
|
||||
@@ -79,6 +95,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
SESSIONS.onDisconnect(player.getUUID());
|
||||
TELEPORTS.onDisconnect(player.getUUID());
|
||||
PORTALS.onDisconnect(player.getUUID());
|
||||
DIMENSION_GAMEMODES.onDisconnect(player.getUUID());
|
||||
LobbyLog.state(
|
||||
"PLAYER DISCONNECT",
|
||||
"Player: " + player.getName().getString(),
|
||||
@@ -94,6 +111,8 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
}
|
||||
});
|
||||
|
||||
DIMENSION_GAMEMODES.tick(server);
|
||||
|
||||
ResourceKey<Level> lobby = lobbyKey();
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
POSITIONS.update(player);
|
||||
@@ -128,6 +147,50 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
StringArgumentType.getString(ctx, "dimension"),
|
||||
true,
|
||||
true))))
|
||||
.then(Commands.literal("gamerule")
|
||||
.then(Commands.argument("dimension", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.executes(ctx -> listGameRules(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getServer(),
|
||||
StringArgumentType.getString(ctx, "dimension"),
|
||||
1))
|
||||
.then(Commands.literal("--page")
|
||||
.then(Commands.argument("page", IntegerArgumentType.integer(1))
|
||||
.executes(ctx -> listGameRules(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getServer(),
|
||||
StringArgumentType.getString(ctx, "dimension"),
|
||||
IntegerArgumentType.getInteger(ctx, "page")))))))
|
||||
.then(Commands.literal("gamemode")
|
||||
.then(Commands.literal("list")
|
||||
.executes(ctx -> listDimensionGameModes(ctx.getSource())))
|
||||
.then(Commands.literal("set")
|
||||
.then(Commands.argument("dimension", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.then(Commands.argument("mode", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(
|
||||
new String[]{"survival", "creative", "adventure", "spectator"}, builder))
|
||||
.executes(ctx -> setDimensionGameMode(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getServer(),
|
||||
StringArgumentType.getString(ctx, "dimension"),
|
||||
StringArgumentType.getString(ctx, "mode"))))))
|
||||
.then(Commands.literal("remove")
|
||||
.then(Commands.argument("dimension", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.executes(ctx -> removeDimensionGameMode(
|
||||
ctx.getSource(),
|
||||
StringArgumentType.getString(ctx, "dimension")))))
|
||||
.then(Commands.argument("dimension", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.executes(ctx -> showDimensionGameMode(
|
||||
ctx.getSource(),
|
||||
StringArgumentType.getString(ctx, "dimension")))))
|
||||
.then(Commands.literal("portal")
|
||||
.then(Commands.literal("create")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
@@ -226,6 +289,16 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean gameModesOk = DIMENSION_GAMEMODES.load();
|
||||
if (!gameModesOk) {
|
||||
source.sendFailure(Component.literal("§eConfig utama direload, tetapi dimension-gamemodes.json bermasalah. Cek console."));
|
||||
LobbyLog.state("RELOAD FAILED", "Reason: dimension-gamemodes.json gagal dibaca");
|
||||
return 0;
|
||||
}
|
||||
for (String dimension : DIMENSION_GAMEMODES.all().keySet()) {
|
||||
DIMENSION_GAMEMODES.applyToDimension(server, dimension);
|
||||
}
|
||||
|
||||
if (source.getEntity() instanceof ServerPlayer player) {
|
||||
player.sendSystemMessage(Component.literal(
|
||||
"§aLobby Systems direload. §7Lobby: §f" + CONFIG.get().lobbyDimension
|
||||
@@ -237,11 +310,137 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
"Lobby: " + CONFIG.get().lobbyDimension,
|
||||
"Minimum play: " + CONFIG.get().minimumPlaySeconds + "s",
|
||||
"Portals: " + PORTALS.size(),
|
||||
"Dimension gamemodes: " + DIMENSION_GAMEMODES.all().size(),
|
||||
"Player positions lobby exclusion: active"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static void sendCommandMessage(CommandSourceStack source, Component message) {
|
||||
if (source.getEntity() instanceof ServerPlayer player) {
|
||||
player.sendSystemMessage(message);
|
||||
} else {
|
||||
source.sendSuccess(() -> message, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int GAMERULES_PER_PAGE = 10;
|
||||
|
||||
private static int listGameRules(CommandSourceStack source, MinecraftServer server, String dimensionId, int requestedPage) {
|
||||
ServerLevel level = findLevel(server, dimensionId);
|
||||
if (level == null) {
|
||||
source.sendFailure(Component.literal("§cDimension '" + dimensionId + "' tidak ditemukan atau tidak ter-load."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<GameRuleEntry> entries = new ArrayList<>();
|
||||
for (GameRule<?> rule : BuiltInRegistries.GAME_RULE) {
|
||||
Identifier id = BuiltInRegistries.GAME_RULE.getKey(rule);
|
||||
if (id == null) continue;
|
||||
entries.add(new GameRuleEntry(id.toString(), gameRuleValue(level, rule)));
|
||||
}
|
||||
entries.sort(Comparator.comparing(GameRuleEntry::name));
|
||||
|
||||
int totalPages = Math.max(1, (entries.size() + GAMERULES_PER_PAGE - 1) / GAMERULES_PER_PAGE);
|
||||
int page = Math.min(Math.max(1, requestedPage), totalPages);
|
||||
int start = (page - 1) * GAMERULES_PER_PAGE;
|
||||
int end = Math.min(start + GAMERULES_PER_PAGE, entries.size());
|
||||
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§6§lLobby Systems Gamerules §r§7[§f" + page + "§7/§f" + totalPages + "§7]"
|
||||
+ "\n§7Dimension: §f" + level.dimension().identifier()
|
||||
+ " §8| §7Rules: §f" + entries.size()));
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
GameRuleEntry entry = entries.get(i);
|
||||
String valueColor = "true".equals(entry.value()) ? "§a" : "false".equals(entry.value()) ? "§c" : "§e";
|
||||
sendCommandMessage(source, Component.literal("§8• §f" + entry.name() + " §7= " + valueColor + entry.value()));
|
||||
}
|
||||
|
||||
if (page < totalPages) {
|
||||
int next = page + 1;
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§7Halaman selanjutnya: §f/ls gamerule " + level.dimension().identifier() + " --page " + next));
|
||||
}
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static String gameRuleValue(ServerLevel level, GameRule<?> rule) {
|
||||
Object value = level.getGameRules().get((GameRule) rule);
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
private static int showDimensionGameMode(CommandSourceStack source, String dimensionId) {
|
||||
String mode = DIMENSION_GAMEMODES.get(dimensionId);
|
||||
if (mode == null) {
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§6Lobby Systems Gamemode\n§7Dimension: §f" + dimensionId
|
||||
+ "\n§7Configured: §e<none>"
|
||||
+ "\n§8Player akan mempertahankan gamemode sebelumnya sampai mapping dibuat."));
|
||||
return 1;
|
||||
}
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§6Lobby Systems Gamemode\n§7Dimension: §f" + dimensionId + "\n§7Gamemode: §a" + mode));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int listDimensionGameModes(CommandSourceStack source) {
|
||||
Map<String, String> all = DIMENSION_GAMEMODES.all();
|
||||
if (all.isEmpty()) {
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§eBelum ada gamemode dimension yang dikonfigurasi.\n§7Gunakan §f/ls gamemode set <dimension> <mode>"));
|
||||
return 1;
|
||||
}
|
||||
sendCommandMessage(source, Component.literal("§6§lLobby Systems Dimension Gamemodes §r§7(" + all.size() + ")"));
|
||||
all.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry ->
|
||||
sendCommandMessage(source, Component.literal("§8• §f" + entry.getKey() + " §7= §a" + entry.getValue())));
|
||||
return all.size();
|
||||
}
|
||||
|
||||
private static int setDimensionGameMode(CommandSourceStack source, MinecraftServer server, String dimensionId, String rawMode) {
|
||||
ServerLevel level = findLevel(server, dimensionId);
|
||||
if (level == null) {
|
||||
source.sendFailure(Component.literal("§cDimension '" + dimensionId + "' tidak ditemukan atau tidak ter-load."));
|
||||
return 0;
|
||||
}
|
||||
if (DimensionGameModeManager.parseMode(rawMode) == null) {
|
||||
source.sendFailure(Component.literal("§cGamemode tidak valid. Pilih survival, creative, adventure, atau spectator."));
|
||||
return 0;
|
||||
}
|
||||
String normalizedDimension = level.dimension().identifier().toString();
|
||||
String normalizedMode = rawMode.trim().toLowerCase();
|
||||
if (!DIMENSION_GAMEMODES.set(normalizedDimension, normalizedMode)) {
|
||||
source.sendFailure(Component.literal("§cGagal menyimpan dimension-gamemodes.json. Cek console."));
|
||||
return 0;
|
||||
}
|
||||
int applied = DIMENSION_GAMEMODES.applyToDimension(server, normalizedDimension);
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§aGamemode dimension disimpan. §7" + normalizedDimension + " §8→ §f" + normalizedMode
|
||||
+ " §7| Player diterapkan sekarang: §f" + applied));
|
||||
LobbyLog.state(
|
||||
"DIMENSION GAMEMODE UPDATED",
|
||||
"Dimension: " + normalizedDimension,
|
||||
"Gamemode: " + normalizedMode,
|
||||
"Players applied now: " + applied
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int removeDimensionGameMode(CommandSourceStack source, String dimensionId) {
|
||||
if (!DIMENSION_GAMEMODES.remove(dimensionId)) {
|
||||
source.sendFailure(Component.literal("§cTidak ada mapping gamemode untuk dimension '" + dimensionId + "'."));
|
||||
return 0;
|
||||
}
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§aMapping gamemode dimension dihapus: §f" + dimensionId
|
||||
+ "\n§7Player tidak diubah sampai berpindah dimension atau mapping baru dibuat."));
|
||||
LobbyLog.state("DIMENSION GAMEMODE REMOVED", "Dimension: " + dimensionId);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private record GameRuleEntry(String name, String value) {}
|
||||
|
||||
private static int createPortal(ServerPlayer player, MinecraftServer server, String rawName, String rawTarget) {
|
||||
String name = PortalManager.normalizeName(rawName);
|
||||
if (!name.matches("[a-z0-9_-]+")) {
|
||||
|
||||
Reference in New Issue
Block a user