Add feature starterkit per dimension
This commit is contained in:
@@ -11,6 +11,8 @@ public final class ConfigPaths {
|
||||
public static final Path PORTALS = DIRECTORY.resolve("portals.json");
|
||||
public static final Path DIMENSION_GAMEMODES = DIRECTORY.resolve("dimension-gamemodes.json");
|
||||
public static final Path DIMENSION_GAMERULES = DIRECTORY.resolve("dimension-gamerules.json");
|
||||
public static final Path STARTER_KITS = DIRECTORY.resolve("starterkits.json");
|
||||
public static final Path STARTER_KIT_RECIPIENTS = DIRECTORY.resolve("starterkit-recipients.json");
|
||||
|
||||
private ConfigPaths() {}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.commands.arguments.IdentifierArgument;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -45,6 +46,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
private static final TeleportCoordinator TELEPORTS = new TeleportCoordinator();
|
||||
private static final DimensionGameModeManager DIMENSION_GAMEMODES = new DimensionGameModeManager();
|
||||
private static final DimensionGameRuleManager DIMENSION_GAMERULES = new DimensionGameRuleManager();
|
||||
private static final StarterKitManager STARTER_KITS = new StarterKitManager();
|
||||
private static int saveTicker;
|
||||
|
||||
@Override
|
||||
@@ -92,6 +94,15 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
"Mode: native per-dimension"
|
||||
);
|
||||
|
||||
boolean starterKitsLoaded = STARTER_KITS.load();
|
||||
LobbyLog.state(
|
||||
starterKitsLoaded ? "STARTER KITS LOADED" : "STARTER KITS LOAD FAILED",
|
||||
"Definitions: " + ConfigPaths.STARTER_KITS,
|
||||
"Recipients: " + ConfigPaths.STARTER_KIT_RECIPIENTS,
|
||||
"Kits: " + STARTER_KITS.size(),
|
||||
"Active dimensions: " + STARTER_KITS.activeCount()
|
||||
);
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||
DimensionGameRuleManager.ApplySummary summary = DIMENSION_GAMERULES.applyAll(server);
|
||||
LobbyLog.state(
|
||||
@@ -140,6 +151,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
DIMENSION_GAMEMODES.applyForPlayer(player, true);
|
||||
DIMENSION_GAMEMODES.beginJoinEnforcement(player.getUUID());
|
||||
SESSIONS.onJoin(player.getUUID(), lobbyKey());
|
||||
STARTER_KITS.onJoin(player);
|
||||
|
||||
LobbyLog.state(
|
||||
"PLAYER JOIN",
|
||||
@@ -157,6 +169,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
TELEPORTS.onDisconnect(player.getUUID());
|
||||
PORTALS.onDisconnect(player.getUUID());
|
||||
DIMENSION_GAMEMODES.onDisconnect(player.getUUID());
|
||||
STARTER_KITS.onDisconnect(player.getUUID());
|
||||
LobbyLog.state(
|
||||
"PLAYER DISCONNECT",
|
||||
"Player: " + player.getName().getString(),
|
||||
@@ -169,6 +182,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
TELEPORTS.tick();
|
||||
|
||||
DIMENSION_GAMEMODES.tick(server);
|
||||
STARTER_KITS.tick(server);
|
||||
|
||||
ResourceKey<Level> lobby = lobbyKey();
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
@@ -271,6 +285,75 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
.executes(ctx -> removeDimensionGameMode(
|
||||
ctx.getSource(),
|
||||
IdentifierArgument.getId(ctx, "dimension").toString())))))
|
||||
.then(Commands.literal("starterkit")
|
||||
.then(Commands.literal("set")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("dimension", IdentifierArgument.id())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.executes(ctx -> starterKitSet(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getServer(),
|
||||
ctx.getSource().getPlayerOrException(),
|
||||
StringArgumentType.getString(ctx, "name"),
|
||||
IdentifierArgument.getId(ctx, "dimension").toString())))))
|
||||
.then(Commands.literal("load")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(STARTER_KITS.names(), builder))
|
||||
.executes(ctx -> starterKitLoad(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getServer(),
|
||||
ctx.getSource().getPlayerOrException(),
|
||||
StringArgumentType.getString(ctx, "name")))))
|
||||
.then(Commands.literal("activate")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(STARTER_KITS.names(), builder))
|
||||
.executes(ctx -> starterKitOperation(
|
||||
ctx.getSource(), STARTER_KITS.activate(StringArgumentType.getString(ctx, "name"))))))
|
||||
.then(Commands.literal("deactivate")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(STARTER_KITS.names(), builder))
|
||||
.executes(ctx -> starterKitOperation(
|
||||
ctx.getSource(), STARTER_KITS.deactivate(StringArgumentType.getString(ctx, "name"))))))
|
||||
.then(Commands.literal("give")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(STARTER_KITS.names(), builder))
|
||||
.then(Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx -> starterKitGive(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getServer(),
|
||||
StringArgumentType.getString(ctx, "name"),
|
||||
EntityArgument.getPlayer(ctx, "player"))))))
|
||||
.then(Commands.literal("list")
|
||||
.executes(ctx -> starterKitList(ctx.getSource())))
|
||||
.then(Commands.literal("info")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(STARTER_KITS.names(), builder))
|
||||
.executes(ctx -> starterKitInfo(ctx.getSource(), StringArgumentType.getString(ctx, "name")))))
|
||||
.then(Commands.literal("status")
|
||||
.then(Commands.argument("dimension", IdentifierArgument.id())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.then(Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx -> starterKitStatus(
|
||||
ctx.getSource(),
|
||||
IdentifierArgument.getId(ctx, "dimension").toString(),
|
||||
EntityArgument.getPlayer(ctx, "player"))))))
|
||||
.then(Commands.literal("reset")
|
||||
.then(Commands.argument("dimension", IdentifierArgument.id())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
ctx.getSource().getServer().levelKeys().stream().map(ResourceKey::identifier), builder))
|
||||
.then(Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx -> starterKitReset(
|
||||
ctx.getSource(),
|
||||
IdentifierArgument.getId(ctx, "dimension").toString(),
|
||||
EntityArgument.getPlayer(ctx, "player"))))))
|
||||
.then(Commands.literal("delete")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> SharedSuggestionProvider.suggest(STARTER_KITS.names(), builder))
|
||||
.executes(ctx -> starterKitOperation(
|
||||
ctx.getSource(), STARTER_KITS.delete(StringArgumentType.getString(ctx, "name"))))))
|
||||
)
|
||||
.then(Commands.literal("portal")
|
||||
.then(Commands.literal("create")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
@@ -313,6 +396,88 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
);
|
||||
}
|
||||
|
||||
private static int starterKitSet(CommandSourceStack source, MinecraftServer server, ServerPlayer player, String name, String dimension) {
|
||||
return starterKitOperation(source, STARTER_KITS.setFromPlayer(server, player, name, dimension));
|
||||
}
|
||||
|
||||
private static int starterKitLoad(CommandSourceStack source, MinecraftServer server, ServerPlayer player, String name) {
|
||||
return starterKitOperation(source, STARTER_KITS.loadToPlayer(server, player, name));
|
||||
}
|
||||
|
||||
private static int starterKitGive(CommandSourceStack source, MinecraftServer server, String name, ServerPlayer target) {
|
||||
return starterKitOperation(source, STARTER_KITS.give(server, target, name));
|
||||
}
|
||||
|
||||
private static int starterKitOperation(CommandSourceStack source, StarterKitManager.OperationResult result) {
|
||||
if (result.success()) {
|
||||
sendCommandMessage(source, Component.literal("§a" + result.message()));
|
||||
return 1;
|
||||
}
|
||||
source.sendFailure(Component.literal("§c" + result.message()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int starterKitList(CommandSourceStack source) {
|
||||
sendCommandMessage(source, Component.literal("§8§m----------------------------------------"));
|
||||
sendCommandMessage(source, Component.literal("§6§lLobby Systems Starter Kits §r§7[§f" + STARTER_KITS.size() + "§7]"));
|
||||
sendCommandMessage(source, Component.literal("§8§m----------------------------------------"));
|
||||
if (STARTER_KITS.size() == 0) {
|
||||
sendCommandMessage(source, Component.literal("§7Belum ada starter kit."));
|
||||
} else {
|
||||
for (Map.Entry<String, StarterKitManager.KitDefinition> entry : STARTER_KITS.entries()) {
|
||||
boolean active = STARTER_KITS.isActive(entry.getKey());
|
||||
sendCommandMessage(source, Component.literal(
|
||||
"§8• §f" + entry.getKey()
|
||||
+ " §8-> §7" + entry.getValue().dimension
|
||||
+ " " + (active ? "§a[ACTIVE]" : "§8[INACTIVE]")
|
||||
+ " §8| §7slots: §f" + entry.getValue().occupiedSlots()));
|
||||
}
|
||||
}
|
||||
sendCommandMessage(source, Component.literal("§8§m----------------------------------------"));
|
||||
return STARTER_KITS.size();
|
||||
}
|
||||
|
||||
private static int starterKitInfo(CommandSourceStack source, String name) {
|
||||
StarterKitManager.KitDefinition kit = STARTER_KITS.get(name);
|
||||
if (kit == null) {
|
||||
source.sendFailure(Component.literal("§cStarter kit '" + name + "' tidak ditemukan."));
|
||||
return 0;
|
||||
}
|
||||
String normalized = name.trim().toLowerCase();
|
||||
sendCommandMessage(source, Component.literal("§6§lStarter Kit: §f" + normalized));
|
||||
sendCommandMessage(source, Component.literal("§7Dimension: §f" + kit.dimension));
|
||||
sendCommandMessage(source, Component.literal("§7Status: " + (STARTER_KITS.isActive(normalized) ? "§aACTIVE" : "§8INACTIVE")));
|
||||
sendCommandMessage(source, Component.literal("§7Occupied slots: §f" + kit.occupiedSlots()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int starterKitStatus(CommandSourceStack source, String dimension, ServerPlayer player) {
|
||||
StarterKitManager.RecipientRecord record = STARTER_KITS.recipient(player.getUUID(), dimension);
|
||||
String active = STARTER_KITS.activeForDimension(dimension);
|
||||
sendCommandMessage(source, Component.literal("§6§lStarter Kit Status"));
|
||||
sendCommandMessage(source, Component.literal("§7Player: §f" + player.getName().getString()));
|
||||
sendCommandMessage(source, Component.literal("§7Dimension: §f" + dimension));
|
||||
if (record == null) {
|
||||
sendCommandMessage(source, Component.literal("§7Status: §eNOT RECEIVED"));
|
||||
} else {
|
||||
sendCommandMessage(source, Component.literal("§7Status: §aRECEIVED"));
|
||||
sendCommandMessage(source, Component.literal("§7Kit received: §f" + record.kit));
|
||||
sendCommandMessage(source, Component.literal("§7Received at: §f" + record.receivedAt));
|
||||
}
|
||||
sendCommandMessage(source, Component.literal("§7Current active kit: §f" + (active == null ? "none" : active)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int starterKitReset(CommandSourceStack source, String dimension, ServerPlayer player) {
|
||||
StarterKitManager.OperationResult result = STARTER_KITS.resetRecipient(player.getUUID(), dimension);
|
||||
if (result.success()) {
|
||||
sendCommandMessage(source, Component.literal("§a" + player.getName().getString() + ": " + result.message()));
|
||||
return 1;
|
||||
}
|
||||
source.sendFailure(Component.literal("§c" + result.message()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int handleLobbyCommand(ServerPlayer player, MinecraftServer server) {
|
||||
ResourceKey<Level> lobby = lobbyKey();
|
||||
if (player.level().dimension().equals(lobby)) {
|
||||
@@ -390,6 +555,13 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
}
|
||||
DimensionGameRuleManager.ApplySummary gameRuleSummary = DIMENSION_GAMERULES.applyAll(server);
|
||||
|
||||
boolean starterKitsOk = STARTER_KITS.load();
|
||||
if (!starterKitsOk) {
|
||||
source.sendFailure(Component.literal("§estarterkits.json atau starterkit-recipients.json bermasalah. Cek console."));
|
||||
LobbyLog.state("RELOAD FAILED", "Reason: starter kit data gagal dibaca");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (source.getEntity() instanceof ServerPlayer player) {
|
||||
player.sendSystemMessage(Component.literal(
|
||||
"§aLobby Systems direload. §7Lobby: §f" + CONFIG.get().lobbyDimension
|
||||
@@ -404,6 +576,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
"Portal visual blocks restored: " + repairedPortalVisuals,
|
||||
"Dimension gamemodes: " + DIMENSION_GAMEMODES.all().size() + " (authoritative)",
|
||||
"Dimension gamerules: " + DIMENSION_GAMERULES.all().size() + " dimensions / " + gameRuleSummary.applied() + " rules applied",
|
||||
"Starter kits: " + STARTER_KITS.size() + " / active dimensions: " + STARTER_KITS.activeCount(),
|
||||
"Player positions lobby exclusion: active"
|
||||
);
|
||||
return 1;
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
package id.dartokloning.lobbysystems;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Starter kit per-dimension.
|
||||
*
|
||||
* Eligibility automatic delivery sengaja dicatat per PLAYER + DIMENSION, bukan per kit.
|
||||
* Dengan begitu mengganti active kit tidak memberikan paket baru kepada player lama.
|
||||
*/
|
||||
public final class StarterKitManager {
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final Path KITS_PATH = ConfigPaths.STARTER_KITS;
|
||||
private static final Path RECIPIENTS_PATH = ConfigPaths.STARTER_KIT_RECIPIENTS;
|
||||
private static final int DELIVERY_DELAY_TICKS = 10;
|
||||
|
||||
private StarterKitConfig config = new StarterKitConfig();
|
||||
private final Map<String, Map<String, RecipientRecord>> recipients = new LinkedHashMap<>();
|
||||
private final Map<UUID, String> lastDimension = new LinkedHashMap<>();
|
||||
private final Map<UUID, PendingDelivery> pending = new LinkedHashMap<>();
|
||||
private long tick;
|
||||
|
||||
public boolean load() {
|
||||
boolean kitsOk = loadKits();
|
||||
boolean recipientsOk = loadRecipients();
|
||||
return kitsOk && recipientsOk;
|
||||
}
|
||||
|
||||
public boolean loadKits() {
|
||||
if (!Files.exists(KITS_PATH)) {
|
||||
config = new StarterKitConfig();
|
||||
return saveKits();
|
||||
}
|
||||
try (Reader reader = Files.newBufferedReader(KITS_PATH)) {
|
||||
StarterKitConfig loaded = GSON.fromJson(reader, StarterKitConfig.class);
|
||||
config = loaded == null ? new StarterKitConfig() : loaded;
|
||||
config.normalize();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to load {}. Keeping previous starter-kit config.", KITS_PATH, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean loadRecipients() {
|
||||
if (!Files.exists(RECIPIENTS_PATH)) {
|
||||
recipients.clear();
|
||||
return saveRecipients();
|
||||
}
|
||||
try (Reader reader = Files.newBufferedReader(RECIPIENTS_PATH)) {
|
||||
JsonElement root = JsonParser.parseReader(reader);
|
||||
Map<String, Map<String, RecipientRecord>> loaded = new LinkedHashMap<>();
|
||||
if (root.isJsonObject()) {
|
||||
for (Map.Entry<String, JsonElement> playerEntry : root.getAsJsonObject().entrySet()) {
|
||||
if (!playerEntry.getValue().isJsonObject()) continue;
|
||||
Map<String, RecipientRecord> byDimension = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, JsonElement> dimensionEntry : playerEntry.getValue().getAsJsonObject().entrySet()) {
|
||||
try {
|
||||
RecipientRecord record = GSON.fromJson(dimensionEntry.getValue(), RecipientRecord.class);
|
||||
if (record != null) byDimension.put(normalizeDimension(dimensionEntry.getKey()), record);
|
||||
} catch (Exception ignored) {
|
||||
LobbySystemsMod.LOGGER.warn("Skipping invalid starter-kit recipient entry for {} / {}",
|
||||
playerEntry.getKey(), dimensionEntry.getKey());
|
||||
}
|
||||
}
|
||||
loaded.put(playerEntry.getKey(), byDimension);
|
||||
}
|
||||
}
|
||||
recipients.clear();
|
||||
recipients.putAll(loaded);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to load {}. Keeping previous starter-kit recipients.", RECIPIENTS_PATH, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean saveKits() {
|
||||
try {
|
||||
Files.createDirectories(KITS_PATH.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(KITS_PATH)) {
|
||||
GSON.toJson(config, writer);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to save {}", KITS_PATH, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean saveRecipients() {
|
||||
try {
|
||||
Files.createDirectories(RECIPIENTS_PATH.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(RECIPIENTS_PATH)) {
|
||||
GSON.toJson(recipients, writer);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to save {}", RECIPIENTS_PATH, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void onJoin(ServerPlayer player) {
|
||||
String dimension = dimensionOf(player);
|
||||
lastDimension.put(player.getUUID(), dimension);
|
||||
schedule(player.getUUID(), dimension);
|
||||
}
|
||||
|
||||
public void onDisconnect(UUID uuid) {
|
||||
lastDimension.remove(uuid);
|
||||
pending.remove(uuid);
|
||||
}
|
||||
|
||||
public void tick(MinecraftServer server) {
|
||||
tick++;
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
UUID uuid = player.getUUID();
|
||||
String dimension = dimensionOf(player);
|
||||
String previous = lastDimension.put(uuid, dimension);
|
||||
if (previous == null || !previous.equals(dimension)) {
|
||||
schedule(uuid, dimension);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<UUID, PendingDelivery>> iterator = pending.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<UUID, PendingDelivery> entry = iterator.next();
|
||||
PendingDelivery job = entry.getValue();
|
||||
if (job.dueTick > tick) continue;
|
||||
iterator.remove();
|
||||
|
||||
ServerPlayer player = server.getPlayerList().getPlayer(entry.getKey());
|
||||
if (player == null) continue;
|
||||
if (!dimensionOf(player).equals(job.dimension)) continue;
|
||||
tryAutomaticDelivery(server, player, job.dimension);
|
||||
}
|
||||
}
|
||||
|
||||
private void schedule(UUID uuid, String dimension) {
|
||||
pending.put(uuid, new PendingDelivery(dimension, tick + DELIVERY_DELAY_TICKS));
|
||||
}
|
||||
|
||||
private void tryAutomaticDelivery(MinecraftServer server, ServerPlayer player, String dimension) {
|
||||
String normalized = normalizeDimension(dimension);
|
||||
String activeName = config.activeByDimension.get(normalized);
|
||||
if (activeName == null || hasReceived(player.getUUID(), normalized)) return;
|
||||
|
||||
KitDefinition kit = config.kits.get(activeName);
|
||||
if (kit == null || !normalizeDimension(kit.dimension).equals(normalized)) {
|
||||
LobbySystemsMod.LOGGER.warn("Starter kit active mapping is invalid: {} -> {}", normalized, activeName);
|
||||
return;
|
||||
}
|
||||
|
||||
DeliveryResult result = deliver(server, player, kit);
|
||||
if (!result.success()) return;
|
||||
|
||||
recipients.computeIfAbsent(player.getUUID().toString(), ignored -> new LinkedHashMap<>())
|
||||
.put(normalized, new RecipientRecord(activeName, Instant.now().toString()));
|
||||
saveRecipients();
|
||||
|
||||
LobbyLog.state(
|
||||
"STARTER KIT DELIVERED",
|
||||
"Player: " + player.getName().getString() + " (" + player.getUUID() + ")",
|
||||
"Dimension: " + normalized,
|
||||
"Kit: " + activeName,
|
||||
"Items: " + result.itemStacks(),
|
||||
"Fallback/drop stacks: " + result.fallbackStacks()
|
||||
);
|
||||
}
|
||||
|
||||
public OperationResult setFromPlayer(MinecraftServer server, ServerPlayer player, String rawName, String rawDimension) {
|
||||
String name = normalizeName(rawName);
|
||||
String dimension = normalizeDimension(rawDimension);
|
||||
if (name.isBlank()) return OperationResult.fail("Nama starter kit tidak valid.");
|
||||
if (dimension.isBlank()) return OperationResult.fail("Dimension tidak valid.");
|
||||
if (server.getLevel(net.minecraft.resources.ResourceKey.create(
|
||||
net.minecraft.core.registries.Registries.DIMENSION,
|
||||
net.minecraft.resources.Identifier.parse(dimension))) == null) {
|
||||
return OperationResult.fail("Dimension '" + dimension + "' tidak ditemukan atau tidak ter-load.");
|
||||
}
|
||||
|
||||
List<SlotSnapshot> slots = new ArrayList<>();
|
||||
try {
|
||||
var ops = server.registryAccess().createSerializationContext(JsonOps.INSTANCE);
|
||||
Inventory inventory = player.getInventory();
|
||||
for (int slot = 0; slot < inventory.getContainerSize(); slot++) {
|
||||
ItemStack stack = inventory.getItem(slot);
|
||||
if (stack.isEmpty()) continue;
|
||||
JsonElement encoded = ItemStack.MAP_CODEC.codec().encodeStart(ops, stack).getOrThrow();
|
||||
slots.add(new SlotSnapshot(slot, encoded));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to serialize starter kit '{}'", name, e);
|
||||
return OperationResult.fail("Gagal menyimpan ItemStack starter kit. Cek console.");
|
||||
}
|
||||
|
||||
KitDefinition previous = config.kits.get(name);
|
||||
String previousDimension = previous == null ? null : normalizeDimension(previous.dimension);
|
||||
boolean wasActive = previousDimension != null && name.equals(config.activeByDimension.get(previousDimension));
|
||||
|
||||
config.kits.put(name, new KitDefinition(dimension, slots));
|
||||
if (wasActive && !previousDimension.equals(dimension)) {
|
||||
config.activeByDimension.remove(previousDimension);
|
||||
}
|
||||
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
|
||||
|
||||
String status = name.equals(config.activeByDimension.get(dimension)) ? "ACTIVE" : "INACTIVE";
|
||||
if (wasActive && !previousDimension.equals(dimension)) {
|
||||
status = "INACTIVE (dipindahkan dari " + previousDimension + ")";
|
||||
}
|
||||
return OperationResult.ok("Starter kit '" + name + "' tersimpan untuk " + dimension
|
||||
+ " dengan " + slots.size() + " slot berisi. Status: " + status + ".");
|
||||
}
|
||||
|
||||
public OperationResult loadToPlayer(MinecraftServer server, ServerPlayer player, String rawName) {
|
||||
String name = normalizeName(rawName);
|
||||
KitDefinition kit = config.kits.get(name);
|
||||
if (kit == null) return OperationResult.fail("Starter kit '" + name + "' tidak ditemukan.");
|
||||
|
||||
List<DecodedSlot> decoded;
|
||||
try {
|
||||
decoded = decode(server, kit);
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to decode starter kit '{}'", name, e);
|
||||
return OperationResult.fail("Gagal membaca ItemStack starter kit. Cek console.");
|
||||
}
|
||||
|
||||
Inventory inventory = player.getInventory();
|
||||
player.closeContainer();
|
||||
inventory.clearContent();
|
||||
int loaded = 0;
|
||||
for (DecodedSlot slot : decoded) {
|
||||
if (slot.slot < 0 || slot.slot >= inventory.getContainerSize()) continue;
|
||||
inventory.setItem(slot.slot, slot.stack.copy());
|
||||
loaded++;
|
||||
}
|
||||
inventory.setChanged();
|
||||
player.inventoryMenu.broadcastChanges();
|
||||
return OperationResult.ok("Starter kit '" + name + "' dimuat ke inventory Anda (" + loaded + " slot berisi). Dimension: " + kit.dimension + ".");
|
||||
}
|
||||
|
||||
public OperationResult activate(String rawName) {
|
||||
String name = normalizeName(rawName);
|
||||
KitDefinition kit = config.kits.get(name);
|
||||
if (kit == null) return OperationResult.fail("Starter kit '" + name + "' tidak ditemukan.");
|
||||
String dimension = normalizeDimension(kit.dimension);
|
||||
String previous = config.activeByDimension.put(dimension, name);
|
||||
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
|
||||
if (name.equals(previous)) return OperationResult.ok("Starter kit '" + name + "' sudah ACTIVE untuk " + dimension + ".");
|
||||
return OperationResult.ok("Starter kit '" + name + "' sekarang ACTIVE untuk " + dimension
|
||||
+ (previous == null ? "." : ". Kit sebelumnya: '" + previous + "' -> INACTIVE."));
|
||||
}
|
||||
|
||||
public OperationResult deactivate(String rawName) {
|
||||
String name = normalizeName(rawName);
|
||||
KitDefinition kit = config.kits.get(name);
|
||||
if (kit == null) return OperationResult.fail("Starter kit '" + name + "' tidak ditemukan.");
|
||||
String dimension = normalizeDimension(kit.dimension);
|
||||
if (!name.equals(config.activeByDimension.get(dimension))) {
|
||||
return OperationResult.ok("Starter kit '" + name + "' sudah INACTIVE.");
|
||||
}
|
||||
config.activeByDimension.remove(dimension);
|
||||
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
|
||||
return OperationResult.ok("Starter kit '" + name + "' sekarang INACTIVE. Dimension " + dimension + " tidak memiliki active starter kit.");
|
||||
}
|
||||
|
||||
public OperationResult delete(String rawName) {
|
||||
String name = normalizeName(rawName);
|
||||
KitDefinition removed = config.kits.remove(name);
|
||||
if (removed == null) return OperationResult.fail("Starter kit '" + name + "' tidak ditemukan.");
|
||||
String dimension = normalizeDimension(removed.dimension);
|
||||
boolean wasActive = name.equals(config.activeByDimension.get(dimension));
|
||||
if (wasActive) config.activeByDimension.remove(dimension);
|
||||
if (!saveKits()) return OperationResult.fail("Gagal menyimpan starterkits.json.");
|
||||
return OperationResult.ok("Starter kit '" + name + "' dihapus" + (wasActive ? " dan active assignment untuk " + dimension + " dilepas." : "."));
|
||||
}
|
||||
|
||||
public OperationResult give(MinecraftServer server, ServerPlayer target, String rawName) {
|
||||
String name = normalizeName(rawName);
|
||||
KitDefinition kit = config.kits.get(name);
|
||||
if (kit == null) return OperationResult.fail("Starter kit '" + name + "' tidak ditemukan.");
|
||||
DeliveryResult result = deliver(server, target, kit);
|
||||
if (!result.success()) return OperationResult.fail("Gagal memberikan starter kit. Cek console.");
|
||||
return OperationResult.ok("Starter kit '" + name + "' diberikan ke " + target.getName().getString()
|
||||
+ ". Recipient history tidak diubah.");
|
||||
}
|
||||
|
||||
public DeliveryResult deliver(MinecraftServer server, ServerPlayer player, KitDefinition kit) {
|
||||
final List<DecodedSlot> decoded;
|
||||
try {
|
||||
decoded = decode(server, kit);
|
||||
} catch (Exception e) {
|
||||
LobbySystemsMod.LOGGER.error("Failed to decode starter kit for delivery", e);
|
||||
return new DeliveryResult(false, 0, 0);
|
||||
}
|
||||
|
||||
Inventory inventory = player.getInventory();
|
||||
int itemStacks = 0;
|
||||
int fallbackStacks = 0;
|
||||
for (DecodedSlot decodedSlot : decoded) {
|
||||
ItemStack stack = decodedSlot.stack.copy();
|
||||
if (stack.isEmpty()) continue;
|
||||
itemStacks++;
|
||||
|
||||
if (decodedSlot.slot >= 0 && decodedSlot.slot < inventory.getContainerSize()
|
||||
&& inventory.getItem(decodedSlot.slot).isEmpty()) {
|
||||
inventory.setItem(decodedSlot.slot, stack);
|
||||
continue;
|
||||
}
|
||||
|
||||
fallbackStacks++;
|
||||
if (!inventory.add(stack) && !stack.isEmpty()) {
|
||||
player.drop(stack, false);
|
||||
} else if (!stack.isEmpty()) {
|
||||
player.drop(stack, false);
|
||||
}
|
||||
}
|
||||
inventory.setChanged();
|
||||
player.inventoryMenu.broadcastChanges();
|
||||
return new DeliveryResult(true, itemStacks, fallbackStacks);
|
||||
}
|
||||
|
||||
private List<DecodedSlot> decode(MinecraftServer server, KitDefinition kit) {
|
||||
var ops = server.registryAccess().createSerializationContext(JsonOps.INSTANCE);
|
||||
List<DecodedSlot> decoded = new ArrayList<>();
|
||||
for (SlotSnapshot snapshot : kit.inventory) {
|
||||
ItemStack stack = ItemStack.MAP_CODEC.codec().parse(ops, snapshot.stack).getOrThrow();
|
||||
if (!stack.isEmpty()) decoded.add(new DecodedSlot(snapshot.slot, stack));
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public boolean hasReceived(UUID uuid, String rawDimension) {
|
||||
Map<String, RecipientRecord> byDimension = recipients.get(uuid.toString());
|
||||
return byDimension != null && byDimension.containsKey(normalizeDimension(rawDimension));
|
||||
}
|
||||
|
||||
public RecipientRecord recipient(UUID uuid, String rawDimension) {
|
||||
Map<String, RecipientRecord> byDimension = recipients.get(uuid.toString());
|
||||
return byDimension == null ? null : byDimension.get(normalizeDimension(rawDimension));
|
||||
}
|
||||
|
||||
public OperationResult resetRecipient(UUID uuid, String rawDimension) {
|
||||
String dimension = normalizeDimension(rawDimension);
|
||||
Map<String, RecipientRecord> byDimension = recipients.get(uuid.toString());
|
||||
if (byDimension == null || byDimension.remove(dimension) == null) {
|
||||
return OperationResult.ok("Player belum memiliki recipient history untuk " + dimension + ".");
|
||||
}
|
||||
if (byDimension.isEmpty()) recipients.remove(uuid.toString());
|
||||
if (!saveRecipients()) return OperationResult.fail("Gagal menyimpan starterkit-recipients.json.");
|
||||
return OperationResult.ok("Recipient history untuk " + dimension + " sudah di-reset.");
|
||||
}
|
||||
|
||||
public KitDefinition get(String rawName) {
|
||||
return config.kits.get(normalizeName(rawName));
|
||||
}
|
||||
|
||||
public String activeForDimension(String rawDimension) {
|
||||
return config.activeByDimension.get(normalizeDimension(rawDimension));
|
||||
}
|
||||
|
||||
public boolean isActive(String rawName) {
|
||||
String name = normalizeName(rawName);
|
||||
KitDefinition kit = config.kits.get(name);
|
||||
return kit != null && name.equals(config.activeByDimension.get(normalizeDimension(kit.dimension)));
|
||||
}
|
||||
|
||||
public List<String> names() {
|
||||
return config.kits.keySet().stream().sorted().toList();
|
||||
}
|
||||
|
||||
public List<Map.Entry<String, KitDefinition>> entries() {
|
||||
return config.kits.entrySet().stream()
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return config.kits.size();
|
||||
}
|
||||
|
||||
public int activeCount() {
|
||||
return config.activeByDimension.size();
|
||||
}
|
||||
|
||||
private static String dimensionOf(ServerPlayer player) {
|
||||
return normalizeDimension(player.level().dimension().identifier().toString());
|
||||
}
|
||||
|
||||
private static String normalizeName(String raw) {
|
||||
return raw == null ? "" : raw.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private static String normalizeDimension(String raw) {
|
||||
return raw == null ? "" : raw.trim().toLowerCase();
|
||||
}
|
||||
|
||||
public static final class StarterKitConfig {
|
||||
public Map<String, String> activeByDimension = new LinkedHashMap<>();
|
||||
public Map<String, KitDefinition> kits = new LinkedHashMap<>();
|
||||
|
||||
void normalize() {
|
||||
if (activeByDimension == null) activeByDimension = new LinkedHashMap<>();
|
||||
if (kits == null) kits = new LinkedHashMap<>();
|
||||
|
||||
Map<String, KitDefinition> normalizedKits = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, KitDefinition> entry : kits.entrySet()) {
|
||||
String name = normalizeName(entry.getKey());
|
||||
KitDefinition kit = entry.getValue();
|
||||
if (name.isBlank() || kit == null) continue;
|
||||
kit.dimension = normalizeDimension(kit.dimension);
|
||||
if (kit.inventory == null) kit.inventory = new ArrayList<>();
|
||||
normalizedKits.put(name, kit);
|
||||
}
|
||||
kits = normalizedKits;
|
||||
|
||||
Map<String, String> normalizedActive = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, String> entry : activeByDimension.entrySet()) {
|
||||
String dimension = normalizeDimension(entry.getKey());
|
||||
String name = normalizeName(entry.getValue());
|
||||
KitDefinition kit = kits.get(name);
|
||||
if (kit != null && normalizeDimension(kit.dimension).equals(dimension)) {
|
||||
normalizedActive.put(dimension, name);
|
||||
}
|
||||
}
|
||||
activeByDimension = normalizedActive;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class KitDefinition {
|
||||
public String dimension;
|
||||
public List<SlotSnapshot> inventory;
|
||||
|
||||
public KitDefinition() {
|
||||
this("", new ArrayList<>());
|
||||
}
|
||||
|
||||
public KitDefinition(String dimension, List<SlotSnapshot> inventory) {
|
||||
this.dimension = dimension;
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
public int occupiedSlots() {
|
||||
return inventory == null ? 0 : inventory.size();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SlotSnapshot {
|
||||
public int slot;
|
||||
public JsonElement stack;
|
||||
|
||||
public SlotSnapshot() {}
|
||||
|
||||
public SlotSnapshot(int slot, JsonElement stack) {
|
||||
this.slot = slot;
|
||||
this.stack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RecipientRecord {
|
||||
public String kit;
|
||||
public String receivedAt;
|
||||
|
||||
public RecipientRecord() {}
|
||||
|
||||
public RecipientRecord(String kit, String receivedAt) {
|
||||
this.kit = kit;
|
||||
this.receivedAt = receivedAt;
|
||||
}
|
||||
}
|
||||
|
||||
private record PendingDelivery(String dimension, long dueTick) {}
|
||||
private record DecodedSlot(int slot, ItemStack stack) {}
|
||||
public record DeliveryResult(boolean success, int itemStacks, int fallbackStacks) {}
|
||||
public record OperationResult(boolean success, String message) {
|
||||
static OperationResult ok(String message) { return new OperationResult(true, message); }
|
||||
static OperationResult fail(String message) { return new OperationResult(false, message); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user