Add dimension gamemode and read gamerule list
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Lobby Systems v1.2.2 — Fabric 26.2
|
||||
# Lobby Systems v1.3.0 — Fabric 26.2
|
||||
|
||||
Server-side lobby routing untuk Minecraft 26.2/Fabric. Versi 1.2.2 menambahkan portal antar-dimensi berbasis WorldEdit selection, last-position per player/per dimension, minimum playtime sebelum kembali ke lobby, dan konfigurasi terpusat di `config/lobby/`.
|
||||
Server-side lobby routing untuk Minecraft 26.2/Fabric. Versi 1.3.0 menambahkan viewer gamerule per-dimension dengan pagination dan pengelolaan gamemode player berdasarkan dimension, sambil mempertahankan portal antar-dimensi, last-position, minimum playtime, dan konfigurasi terpusat di `config/lobby/`.
|
||||
|
||||
### Reload feedback v1.2.2
|
||||
### Reload feedback
|
||||
|
||||
`/ls reload` menulis banner `RELOAD START` dan `RELOAD COMPLETE` hanya ke console server. Ringkasan reload satu baris dikirim langsung hanya ke player admin/operator yang menjalankan command; ringkasan tersebut tidak dibroadcast ke player lain dan tidak dikirim melalui command feedback ke console.
|
||||
|
||||
@@ -38,7 +38,7 @@ chmod +x compile.sh release.sh
|
||||
```text
|
||||
============================================
|
||||
Lobby Systems - Fabric 26.2 Build
|
||||
Version: 1.2.2
|
||||
Version: 1.3.0
|
||||
Namespace: id.dartokloning.lobbysystems
|
||||
Mod ID: dartokloning
|
||||
============================================
|
||||
@@ -47,7 +47,7 @@ chmod +x compile.sh release.sh
|
||||
Output utama:
|
||||
|
||||
```text
|
||||
build/libs/lobby-systems-1.2.2.jar
|
||||
build/libs/lobby-systems-1.3.0.jar
|
||||
```
|
||||
|
||||
`*-sources.jar` hanya source archive dan tidak perlu dipasang ke server.
|
||||
@@ -78,21 +78,22 @@ State penting Lobby Systems memakai separator agar mudah terlihat di server cons
|
||||
```text
|
||||
============================================
|
||||
Lobby Systems - READY
|
||||
Version: 1.2.2
|
||||
Version: 1.3.0
|
||||
Minecraft: 26.2
|
||||
Config directory: config/lobby
|
||||
WorldEdit: available
|
||||
============================================
|
||||
```
|
||||
|
||||
## Struktur konfigurasi v1.2.2
|
||||
## Struktur konfigurasi v1.3.0
|
||||
|
||||
```text
|
||||
config/
|
||||
└── lobby/
|
||||
├── config.json
|
||||
├── player-positions.json
|
||||
└── portals.json
|
||||
├── portals.json
|
||||
└── dimension-gamemodes.json
|
||||
```
|
||||
|
||||
### `config/lobby/config.json`
|
||||
@@ -151,9 +152,26 @@ Portal dibuat lewat command dan tersimpan otomatis. Contoh:
|
||||
|
||||
Portal adalah region/cuboid, bukan harus Nether Portal asli. Anda bebas membuat bentuk doorway, arch, lantai, gateway, dan sebagainya di dalam region tersebut.
|
||||
|
||||
|
||||
### `config/lobby/dimension-gamemodes.json`
|
||||
|
||||
Minecraft menyimpan gamemode pada player, bukan pada dimension. Lobby Systems v1.3.0 menambahkan mapping dimension → gamemode dan menerapkannya ketika player masuk ke dimension tersebut. File ini dibuat otomatis dan dapat dikelola lewat command admin.
|
||||
|
||||
Contoh:
|
||||
|
||||
```json
|
||||
{
|
||||
"minecraft:lobby": "adventure",
|
||||
"minecraft:worldcreative": "creative",
|
||||
"minecraft:overworld": "survival"
|
||||
}
|
||||
```
|
||||
|
||||
Gamemode yang didukung: `survival`, `creative`, `adventure`, dan `spectator`.
|
||||
|
||||
## Migrasi dari v1.1.x
|
||||
|
||||
Pada startup pertama v1.2.2:
|
||||
Pada startup pertama v1.3.0:
|
||||
|
||||
```text
|
||||
config/lobby-systems.json
|
||||
@@ -177,7 +195,7 @@ akan dimigrasikan ke:
|
||||
config/lobby/player-positions.json
|
||||
```
|
||||
|
||||
Nama file lama hanya digunakan oleh `LegacyConfigMigrator` untuk proses migrasi. Runtime v1.2.2 membaca dan menulis file baru di `config/lobby/`.
|
||||
Nama file lama hanya digunakan oleh `LegacyConfigMigrator` untuk proses migrasi. Runtime v1.3.0 membaca dan menulis file baru di `config/lobby/`.
|
||||
|
||||
Jika file tujuan baru sudah ada, migrator tidak menimpanya.
|
||||
|
||||
@@ -198,6 +216,14 @@ Jika file tujuan baru sudah ada, migrator tidak menimpanya.
|
||||
/ls reload
|
||||
/ls tp <dimension>
|
||||
|
||||
/ls gamerule <dimension>
|
||||
/ls gamerule <dimension> --page <page>
|
||||
|
||||
/ls gamemode <dimension>
|
||||
/ls gamemode list
|
||||
/ls gamemode set <dimension> <survival|creative|adventure|spectator>
|
||||
/ls gamemode remove <dimension>
|
||||
|
||||
/ls portal create <name> <target_dimension>
|
||||
/ls portal delete <name>
|
||||
/ls portal list
|
||||
@@ -207,12 +233,39 @@ Jika file tujuan baru sudah ada, migrator tidak menimpanya.
|
||||
|
||||
`/ls tp` tetap command maintenance/admin. Player biasa tidak memerlukannya karena flow normal menggunakan portal.
|
||||
|
||||
|
||||
### Gamerule viewer per dimension
|
||||
|
||||
`/ls gamerule <dimension>` menampilkan gamerule aktual dari dimension yang sedang ter-load. Viewer ini bersifat read-only dan cocok dipakai bersama WorldGameRules. Hasil ditampilkan 10 rule per halaman.
|
||||
|
||||
Contoh:
|
||||
|
||||
```text
|
||||
/ls gamerule minecraft:lobby
|
||||
/ls gamerule minecraft:lobby --page 2
|
||||
```
|
||||
|
||||
Header chat menampilkan `halaman/total halaman`, nama dimension, dan jumlah gamerule. Jika masih ada halaman berikutnya, command untuk membuka halaman selanjutnya ikut ditampilkan.
|
||||
|
||||
### Gamemode per dimension
|
||||
|
||||
Gunakan mapping gamemode agar player otomatis mendapat gamemode yang sesuai saat memasuki dimension. Contoh:
|
||||
|
||||
```text
|
||||
/ls gamemode set minecraft:lobby adventure
|
||||
/ls gamemode set minecraft:worldcreative creative
|
||||
/ls gamemode set minecraft:overworld survival
|
||||
```
|
||||
|
||||
`/ls gamemode <dimension>` melihat mapping satu dimension, `/ls gamemode list` melihat semua mapping, dan `/ls gamemode remove <dimension>` menghapus mapping. Mapping disimpan ke `config/lobby/dimension-gamemodes.json` dan ikut direload oleh `/ls reload`.
|
||||
|
||||
### `/ls reload`
|
||||
|
||||
Reload live:
|
||||
|
||||
- `config/lobby/config.json`
|
||||
- `config/lobby/portals.json`
|
||||
- `config/lobby/dimension-gamemodes.json`
|
||||
- lobby exclusion pada player-position store
|
||||
|
||||
Tidak perlu restart untuk perubahan JSON tersebut.
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@ minecraft_version=26.2
|
||||
loader_version=0.19.3
|
||||
loom_version=1.17-SNAPSHOT
|
||||
fabric_api_version=0.156.0+26.2
|
||||
mod_version=1.2.2
|
||||
mod_version=1.3.0
|
||||
maven_group=id.dartokloning
|
||||
archives_base_name=lobby-systems
|
||||
|
||||
@@ -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_-]+")) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"id": "dartokloning",
|
||||
"version": "${version}",
|
||||
"name": "Lobby Systems",
|
||||
"description": "Server-side Fabric lobby routing, persistent world positions, minimum playtime, and WorldEdit-selected dimension portals.",
|
||||
"description": "Server-side Fabric lobby routing, persistent world positions, minimum playtime, portals, gamerule inspection, and per-dimension player gamemodes.",
|
||||
"authors": ["DartoKloning"],
|
||||
"license": "MIT",
|
||||
"environment": "server",
|
||||
|
||||
Reference in New Issue
Block a user