add reload configuration

This commit is contained in:
Darto KLoning
2026-09-28 08:41:03 +07:00
parent 5a2d63c282
commit 2342c07072
7 changed files with 135 additions and 5 deletions
+9
View File
@@ -8,3 +8,12 @@ This branch is Fabric-only and intentionally separates authority:
Minecraft 26.2 uses the unobfuscated Mojang/official names and Java 25. This port therefore does not reuse the old Yarn-named common/client GUI classes from 0.2.2. Client rendering lives only in `src/client`, while server/gameplay code lives in `src/main`.
Run `compiler.bat` on Windows or `./compiler.sh` on Linux/macOS. The script checks whether the existing JAR contains the same `mod_version+mc<minecraft_version>` as `gradle.properties`; matching builds offer Push/Recompile/Quit. After a successful build it offers Git push and retries failed pushes in groups of three.
## Commands (v0.3.2)
`/idt reload`
- Client player (non-operator): reloads only `item-despawn-timer-client.json`.
- Client player (operator/moderator): reloads the client config and requests a server-config reload. The server validates permission before accepting the request.
- Dedicated-server console: reloads only `item-despawn-timer-server.json`.
- The server-side `/idt reload` node requires Minecraft moderator/operator permission, so it is not advertised by the server command tree to ordinary players. The client-side command remains available because ordinary players are allowed to reload their own presentation config.
+1 -1
View File
@@ -3,6 +3,6 @@ org.gradle.configuration-cache=false
minecraft_version=26.2
loader_version=0.19.3
fabric_version=0.160.0+26.2
mod_version=0.3.1
mod_version=0.3.2
maven_group=whirlfrenzy.itemdespawntimer
archives_base_name=item-despawn-timer
@@ -1,3 +1,17 @@
package whirlfrenzy.itemdespawntimer.client;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import whirlfrenzy.itemdespawntimer.network.TimerSyncPayload;
public final class ItemDespawnTimerClient implements ClientModInitializer { @Override public void onInitializeClient(){ ClientConfig.load(); ClientPlayNetworking.registerGlobalReceiver(TimerSyncPayload.TYPE,(p,c)->ClientTimerData.put(p.entityId(),p.age(),p.lifespan())); } }
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import whirlfrenzy.itemdespawntimer.client.command.ItemDespawnTimerClientCommands;
import whirlfrenzy.itemdespawntimer.network.TimerSyncPayload;
public final class ItemDespawnTimerClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientConfig.load();
ItemDespawnTimerClientCommands.register();
ClientPlayNetworking.registerGlobalReceiver(
TimerSyncPayload.TYPE,
(payload, context) -> ClientTimerData.put(payload.entityId(), payload.age(), payload.lifespan()));
}
}
@@ -0,0 +1,33 @@
package whirlfrenzy.itemdespawntimer.client.command;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.ClientCommands;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.network.chat.Component;
import whirlfrenzy.itemdespawntimer.client.ClientConfig;
import whirlfrenzy.itemdespawntimer.network.ReloadConfigPayload;
/**
* Client-side /idt reload is intentionally available to every player.
* It always reloads the local presentation config. A C2S request is also sent;
* the server silently accepts it only when the player has operator/moderator permission.
*/
public final class ItemDespawnTimerClientCommands {
private ItemDespawnTimerClientCommands() {}
public static void register() {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
dispatcher.register(ClientCommands.literal("idt")
.then(ClientCommands.literal("reload")
.executes(context -> {
ClientConfig.load();
context.getSource().sendFeedback(
Component.literal("[Item Despawn Timer] Client config reloaded."));
if (ClientPlayNetworking.canSend(ReloadConfigPayload.TYPE)) {
ClientPlayNetworking.send(new ReloadConfigPayload(true));
}
return 1;
}))));
}
}
@@ -2,18 +2,43 @@ package whirlfrenzy.itemdespawntimer;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.server.permissions.Permissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import whirlfrenzy.itemdespawntimer.command.ItemDespawnTimerCommands;
import whirlfrenzy.itemdespawntimer.config.ServerConfig;
import whirlfrenzy.itemdespawntimer.network.ReloadConfigPayload;
import whirlfrenzy.itemdespawntimer.network.TimerSyncPayload;
public final class ItemDespawnTimer implements ModInitializer {
public static final String MOD_ID = "item-despawn-timer";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
public static Identifier id(String path) { return Identifier.fromNamespaceAndPath(MOD_ID, path); }
@Override public void onInitialize() {
public static Identifier id(String path) {
return Identifier.fromNamespaceAndPath(MOD_ID, path);
}
@Override
public void onInitialize() {
ServerConfig.load();
PayloadTypeRegistry.clientboundPlay().register(TimerSyncPayload.TYPE, TimerSyncPayload.CODEC);
PayloadTypeRegistry.serverboundPlay().register(ReloadConfigPayload.TYPE, ReloadConfigPayload.CODEC);
ServerPlayNetworking.registerGlobalReceiver(ReloadConfigPayload.TYPE, (payload, context) -> {
if (!payload.requested()) return;
// Never trust the client: only an operator/moderator may reload server configuration.
if (!context.player().permissions().hasPermission(Permissions.COMMANDS_MODERATOR)) return;
ServerConfig.load();
context.player().sendSystemMessage(
Component.literal("[Item Despawn Timer] Client and server configs reloaded."));
});
ItemDespawnTimerCommands.register();
}
}
@@ -0,0 +1,29 @@
package whirlfrenzy.itemdespawntimer.command;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.server.permissions.Permissions;
import whirlfrenzy.itemdespawntimer.config.ServerConfig;
/** Server-side /idt commands. The reload branch is only sent to command sources with moderator permission. */
public final class ItemDespawnTimerCommands {
private ItemDespawnTimerCommands() {}
public static void register() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) ->
dispatcher.register(Commands.literal("idt")
.then(Commands.literal("reload")
.requires(source -> source.permissions().hasPermission(Permissions.COMMANDS_MODERATOR))
.executes(ItemDespawnTimerCommands::reloadServerConfig))));
}
private static int reloadServerConfig(CommandContext<CommandSourceStack> context) {
ServerConfig.load();
context.getSource().sendSuccess(
() -> Component.literal("[Item Despawn Timer] Server config reloaded."), false);
return 1;
}
}
@@ -0,0 +1,20 @@
package whirlfrenzy.itemdespawntimer.network;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import whirlfrenzy.itemdespawntimer.ItemDespawnTimer;
/** Client request to reload the server config. The server always validates operator permission. */
public record ReloadConfigPayload(boolean requested) implements CustomPacketPayload {
public static final Type<ReloadConfigPayload> TYPE = new Type<>(ItemDespawnTimer.id("reload_config"));
public static final StreamCodec<RegistryFriendlyByteBuf, ReloadConfigPayload> CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, ReloadConfigPayload::requested,
ReloadConfigPayload::new);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}