From 1e9c190c063350e2f3a3668f4f5edf3d8049be30 Mon Sep 17 00:00:00 2001 From: Darto KLoning Date: Mon, 28 Sep 2026 19:07:17 +0700 Subject: [PATCH] clock image --- README.md | 6 +- examples/item-despawn-timer-client.json | 13 ++++- gradle.properties | 2 +- .../itemdespawntimer/client/ClientConfig.java | 8 ++- .../mixin/client/ItemEntityRendererMixin.java | 53 +++++++++++++++--- .../assets/item-despawn-timer/font/clock.json | 13 +++++ .../textures/font/clock.png | Bin 0 -> 278 bytes 7 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 src/client/resources/assets/item-despawn-timer/font/clock.json create mode 100644 src/client/resources/assets/item-despawn-timer/textures/font/clock.png diff --git a/README.md b/README.md index fb47a2a..d55b738 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Minecraft 26.2 uses the unobfuscated Mojang/official names and Java 25. This por Run `compiler.bat` on Windows or `./compiler.sh` on Linux/macOS. The script checks whether the existing JAR contains the same `mod_version+mc` 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) +## Commands (v0.3.4) `/idt reload` @@ -17,3 +17,7 @@ Run `compiler.bat` on Windows or `./compiler.sh` on Linux/macOS. The script chec - 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. + +## Client display colors + +`clockColor`, `timerTextColor`, and `nameTextColor` use `#RRGGBB` values. The bundled `clock.png` is rendered as a bitmap glyph on the same baseline as the countdown text. diff --git a/examples/item-despawn-timer-client.json b/examples/item-despawn-timer-client.json index 1f6d32b..0156d95 100644 --- a/examples/item-despawn-timer-client.json +++ b/examples/item-despawn-timer-client.json @@ -1 +1,12 @@ -{"timerVisible":true,"nameVisible":false,"timerLabelHeight":0.75,"nameLabelHeight":1.1,"useTimerVisibilityThreshold":false,"timerVisibilityThresholdSeconds":300,"timerTextColor":"#FFFFFF","nameTextColor":"#FFFFFF","backgroundAlpha":64} +{ + "timerVisible": true, + "nameVisible": false, + "timerLabelHeight": 0.75, + "nameLabelHeight": 1.1, + "useTimerVisibilityThreshold": false, + "timerVisibilityThresholdSeconds": 300, + "clockColor": "#FFFFFF", + "timerTextColor": "#FFFFFF", + "nameTextColor": "#FFFFFF", + "backgroundAlpha": 64 +} diff --git a/gradle.properties b/gradle.properties index ff05e6b..db84951 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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.2 +mod_version=0.3.4 maven_group=whirlfrenzy.itemdespawntimer archives_base_name=item-despawn-timer diff --git a/src/client/java/whirlfrenzy/itemdespawntimer/client/ClientConfig.java b/src/client/java/whirlfrenzy/itemdespawntimer/client/ClientConfig.java index 66821b3..03b52e6 100644 --- a/src/client/java/whirlfrenzy/itemdespawntimer/client/ClientConfig.java +++ b/src/client/java/whirlfrenzy/itemdespawntimer/client/ClientConfig.java @@ -2,7 +2,13 @@ package whirlfrenzy.itemdespawntimer.client; import com.google.gson.*; import net.fabricmc.loader.api.FabricLoader; import java.io.*; import java.nio.file.*; public final class ClientConfig { private static final Gson GSON=new GsonBuilder().setPrettyPrinting().create(); public static Data data=new Data(); - public static final class Data { public boolean timerVisible=true; public boolean nameVisible=false; public float timerLabelHeight=0.75f; public float nameLabelHeight=1.1f; public boolean useTimerVisibilityThreshold=false; public int timerVisibilityThresholdSeconds=300; public String timerTextColor="#FFFFFF"; public String nameTextColor="#FFFFFF"; public int backgroundAlpha=64; } + public static final class Data { + public boolean timerVisible=true; public boolean nameVisible=false; + public float timerLabelHeight=0.75f; public float nameLabelHeight=1.1f; + public boolean useTimerVisibilityThreshold=false; public int timerVisibilityThresholdSeconds=300; + public String clockColor="#FFFFFF"; public String timerTextColor="#FFFFFF"; public String nameTextColor="#FFFFFF"; + public int backgroundAlpha=64; + } public static void load(){ Path p=FabricLoader.getInstance().getConfigDir().resolve("item-despawn-timer-client.json"); try{if(Files.exists(p)){try(Reader r=Files.newBufferedReader(p)){Data d=GSON.fromJson(r,Data.class);if(d!=null)data=d;}}else{try(Writer w=Files.newBufferedWriter(p)){GSON.toJson(data,w);}}}catch(Exception ignored){}} public static int color(String s){try{return 0xFF000000|Integer.parseInt(s.replace("#",""),16);}catch(Exception e){return 0xFFFFFFFF;}} } diff --git a/src/client/java/whirlfrenzy/itemdespawntimer/mixin/client/ItemEntityRendererMixin.java b/src/client/java/whirlfrenzy/itemdespawntimer/mixin/client/ItemEntityRendererMixin.java index f36862e..7451fe1 100644 --- a/src/client/java/whirlfrenzy/itemdespawntimer/mixin/client/ItemEntityRendererMixin.java +++ b/src/client/java/whirlfrenzy/itemdespawntimer/mixin/client/ItemEntityRendererMixin.java @@ -4,28 +4,63 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.entity.ItemEntityRenderer; -import net.minecraft.client.renderer.state.level.CameraRenderState; import net.minecraft.client.renderer.entity.state.ItemEntityRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FontDescription; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.Identifier; import net.minecraft.world.entity.item.ItemEntity; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import whirlfrenzy.itemdespawntimer.client.*; +import whirlfrenzy.itemdespawntimer.client.ClientConfig; +import whirlfrenzy.itemdespawntimer.client.ClientTimerData; +import whirlfrenzy.itemdespawntimer.client.TimerRenderStateAccess; @Mixin(ItemEntityRenderer.class) public abstract class ItemEntityRendererMixin { + private static final String CLOCK_GLYPH = "\uE000"; + private static final FontDescription CLOCK_FONT = + new FontDescription.Resource(Identifier.fromNamespaceAndPath("item-despawn-timer", "clock")); + @Inject(method="extractRenderState", at=@At("TAIL")) - private void itemDespawnTimer$extract(ItemEntity entity, ItemEntityRenderState state, float partialTick, CallbackInfo ci){ ((TimerRenderStateAccess)(Object)state).itemDespawnTimer$set(entity.getId(),entity.getItem().getHoverName().getString()); } + private void itemDespawnTimer$extract(ItemEntity entity, ItemEntityRenderState state, float partialTick, CallbackInfo ci) { + ((TimerRenderStateAccess)(Object)state).itemDespawnTimer$set(entity.getId(),entity.getItem().getHoverName().getString()); + } @Inject(method="submit", at=@At("TAIL")) - private void itemDespawnTimer$submit(ItemEntityRenderState state, PoseStack pose, SubmitNodeCollector queue, CameraRenderState camera, CallbackInfo ci){ - TimerRenderStateAccess ext=(TimerRenderStateAccess)(Object)state; ClientTimerData.Timer timer=ClientTimerData.get(ext.itemDespawnTimer$id()); if(timer==null)return; - long elapsed=Math.max(0,System.currentTimeMillis()-timer.receivedAtMs()); int age=timer.age()+(int)(elapsed/50L); int remainTicks=timer.lifespan()==Integer.MAX_VALUE?Integer.MAX_VALUE:Math.max(0,timer.lifespan()-age); int seconds=remainTicks==Integer.MAX_VALUE?Integer.MAX_VALUE:(remainTicks+19)/20; + private void itemDespawnTimer$submit(ItemEntityRenderState state, PoseStack pose, SubmitNodeCollector queue, CameraRenderState camera, CallbackInfo ci) { + TimerRenderStateAccess ext=(TimerRenderStateAccess)(Object)state; + ClientTimerData.Timer timer=ClientTimerData.get(ext.itemDespawnTimer$id()); if(timer==null)return; + long elapsed=Math.max(0,System.currentTimeMillis()-timer.receivedAtMs()); + int age=timer.age()+(int)(elapsed/50L); + int remainTicks=timer.lifespan()==Integer.MAX_VALUE?Integer.MAX_VALUE:Math.max(0,timer.lifespan()-age); + int seconds=remainTicks==Integer.MAX_VALUE?Integer.MAX_VALUE:(remainTicks+19)/20; var cfg=ClientConfig.data; var font=Minecraft.getInstance().font; + if(cfg.timerVisible && (!cfg.useTimerVisibilityThreshold || seconds<=cfg.timerVisibilityThresholdSeconds)){ - String s=seconds==Integer.MAX_VALUE?"∞":"◷ "+seconds+"s"; float w=font.width(s); pose.pushPose(); pose.translate(0,cfg.timerLabelHeight,0); pose.mulPose(camera.orientation); pose.scale(0.025f,-0.025f,0.025f); queue.submitText(pose,-w/2f,0,Component.literal(s).getVisualOrderText(),false,net.minecraft.client.gui.Font.DisplayMode.SEE_THROUGH,state.lightCoords,ClientConfig.color(cfg.timerTextColor),(cfg.backgroundAlpha&255)<<24,0); pose.popPose(); + String countdown=seconds==Integer.MAX_VALUE?"∞":seconds+"s"; + MutableComponent icon=Component.literal(CLOCK_GLYPH).withStyle(style -> style + .withFont(CLOCK_FONT).withColor(ClientConfig.color(cfg.clockColor)&0xFFFFFF)); + MutableComponent timerText=Component.literal(" "+countdown).withStyle(style -> style + .withColor(ClientConfig.color(cfg.timerTextColor)&0xFFFFFF)); + MutableComponent line=Component.empty().append(icon).append(timerText); + float w=font.width(line); + pose.pushPose(); pose.translate(0,cfg.timerLabelHeight,0); pose.mulPose(camera.orientation); pose.scale(0.025f,-0.025f,0.025f); + queue.submitText(pose,-w/2f,0,line.getVisualOrderText(),false, + net.minecraft.client.gui.Font.DisplayMode.SEE_THROUGH,state.lightCoords, + 0xFFFFFFFF,(cfg.backgroundAlpha&255)<<24,0); + pose.popPose(); + } + if(cfg.nameVisible){ + String s=ext.itemDespawnTimer$name(); float w=font.width(s); + pose.pushPose(); pose.translate(0,cfg.nameLabelHeight,0); pose.mulPose(camera.orientation); pose.scale(0.025f,-0.025f,0.025f); + queue.submitText(pose,-w/2f,0,Component.literal(s).getVisualOrderText(),false, + net.minecraft.client.gui.Font.DisplayMode.SEE_THROUGH,state.lightCoords, + ClientConfig.color(cfg.nameTextColor),(cfg.backgroundAlpha&255)<<24,0); + pose.popPose(); } - if(cfg.nameVisible){String s=ext.itemDespawnTimer$name();float w=font.width(s);pose.pushPose();pose.translate(0,cfg.nameLabelHeight,0);pose.mulPose(camera.orientation);pose.scale(0.025f,-0.025f,0.025f);queue.submitText(pose,-w/2f,0,Component.literal(s).getVisualOrderText(),false,net.minecraft.client.gui.Font.DisplayMode.SEE_THROUGH,state.lightCoords,ClientConfig.color(cfg.nameTextColor),(cfg.backgroundAlpha&255)<<24,0);pose.popPose();} } } diff --git a/src/client/resources/assets/item-despawn-timer/font/clock.json b/src/client/resources/assets/item-despawn-timer/font/clock.json new file mode 100644 index 0000000..43f4cea --- /dev/null +++ b/src/client/resources/assets/item-despawn-timer/font/clock.json @@ -0,0 +1,13 @@ +{ + "providers": [ + { + "type": "bitmap", + "file": "item-despawn-timer:font/clock.png", + "ascent": 8, + "height": 9, + "chars": [ + "" + ] + } + ] +} diff --git a/src/client/resources/assets/item-despawn-timer/textures/font/clock.png b/src/client/resources/assets/item-despawn-timer/textures/font/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..994c7a26ac91c4ba88f9c6d860ff5c7b68a57ef9 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}@0G|+7Af1zwbNu-6PoF-WIB_B`FYnyBbN~PUKY#xGg$oyQb8|0Vya-e? zIY%@XNb!~g`2_=I;ef&Rg;E<(g0sLQvY3H^?+^$xifVW@00rYcT^vI+f_={)WNJ|0 zVG1akwd~*j?`8#=a~@nxOfm`xyE{4Z!BelaogN2j!|k@m=uc|I0}pd%h{_7w=n=z{+)F9@`9{RScf4elF{r G5}E*i4r|o_ literal 0 HcmV?d00001