Initial Fabric 26.2 port
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
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 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;}}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package whirlfrenzy.itemdespawntimer.client;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
public final class ClientTimerData { public record Timer(int age,int lifespan,long receivedAtMs){} private static final ConcurrentHashMap<Integer,Timer> DATA=new ConcurrentHashMap<>(); public static void put(int id,int age,int life){DATA.put(id,new Timer(age,life,System.currentTimeMillis()));} public static Timer get(int id){return DATA.get(id);} }
|
||||
@@ -0,0 +1,3 @@
|
||||
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())); } }
|
||||
@@ -0,0 +1,2 @@
|
||||
package whirlfrenzy.itemdespawntimer.client;
|
||||
public interface TimerRenderStateAccess { void itemDespawnTimer$set(int entityId,String name); int itemDespawnTimer$id(); String itemDespawnTimer$name(); }
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package whirlfrenzy.itemdespawntimer.mixin.client;
|
||||
import net.minecraft.client.renderer.entity.state.ItemEntityRenderState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import whirlfrenzy.itemdespawntimer.client.TimerRenderStateAccess;
|
||||
@Mixin(ItemEntityRenderState.class) public class ItemEntityRenderStateMixin implements TimerRenderStateAccess { @Unique private int itemDespawnTimer$id=-1; @Unique private String itemDespawnTimer$name=""; public void itemDespawnTimer$set(int id,String name){this.itemDespawnTimer$id=id;this.itemDespawnTimer$name=name;} public int itemDespawnTimer$id(){return itemDespawnTimer$id;} public String itemDespawnTimer$name(){return itemDespawnTimer$name;} }
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package whirlfrenzy.itemdespawntimer.mixin.client;
|
||||
|
||||
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.network.chat.Component;
|
||||
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.callback.CallbackInfo;
|
||||
import whirlfrenzy.itemdespawntimer.client.*;
|
||||
|
||||
@Mixin(ItemEntityRenderer.class)
|
||||
public abstract class ItemEntityRendererMixin {
|
||||
@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()); }
|
||||
|
||||
@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;
|
||||
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();
|
||||
}
|
||||
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();}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"required":true,"package":"whirlfrenzy.itemdespawntimer.mixin.client","compatibilityLevel":"JAVA_25","client":["ItemEntityRendererMixin","ItemEntityRenderStateMixin"],"injectors":{"defaultRequire":1}}
|
||||
@@ -0,0 +1,19 @@
|
||||
package whirlfrenzy.itemdespawntimer;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import whirlfrenzy.itemdespawntimer.config.ServerConfig;
|
||||
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() {
|
||||
ServerConfig.load();
|
||||
PayloadTypeRegistry.clientboundPlay().register(TimerSyncPayload.TYPE, TimerSyncPayload.CODEC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package whirlfrenzy.itemdespawntimer.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import whirlfrenzy.itemdespawntimer.ItemDespawnTimer;
|
||||
import java.io.*; import java.nio.file.*; import java.util.*;
|
||||
|
||||
public final class ServerConfig {
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static Data data = new Data();
|
||||
public static final class Data {
|
||||
public int defaultDespawnSeconds = 300;
|
||||
public boolean allowInfiniteLifetime = true;
|
||||
public Map<String,Integer> itemDespawnSeconds = new LinkedHashMap<>();
|
||||
}
|
||||
private ServerConfig() {}
|
||||
public static void load() {
|
||||
Path p = FabricLoader.getInstance().getConfigDir().resolve("item-despawn-timer-server.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 { Files.createDirectories(p.getParent()); try(Writer w=Files.newBufferedWriter(p)){GSON.toJson(data,w);} }
|
||||
} catch(Exception e){ ItemDespawnTimer.LOGGER.error("Cannot load server config",e); }
|
||||
}
|
||||
public static int lifespanTicks(ItemEntity entity) {
|
||||
String id = BuiltInRegistries.ITEM.getKey(entity.getItem().getItem()).toString();
|
||||
int seconds = data.itemDespawnSeconds.getOrDefault(id, data.defaultDespawnSeconds);
|
||||
if (seconds < 0 && data.allowInfiniteLifetime) return Integer.MAX_VALUE;
|
||||
return Math.max(1, seconds) * 20;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package whirlfrenzy.itemdespawntimer.mixin;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import whirlfrenzy.itemdespawntimer.config.ServerConfig;
|
||||
import whirlfrenzy.itemdespawntimer.network.TimerSyncPayload;
|
||||
|
||||
@Mixin(ItemEntity.class)
|
||||
public abstract class ItemEntityMixin {
|
||||
@Shadow private int age;
|
||||
|
||||
@ModifyConstant(method="tick", constant=@Constant(intValue=6000))
|
||||
private int itemDespawnTimer$serverLifetime(int vanillaLifetime) {
|
||||
return ServerConfig.lifespanTicks((ItemEntity)(Object)this);
|
||||
}
|
||||
|
||||
@Inject(method="tick", at=@At("TAIL"))
|
||||
private void itemDespawnTimer$syncTimer(CallbackInfo ci) {
|
||||
ItemEntity self=(ItemEntity)(Object)this;
|
||||
if (!self.level().isClientSide() && (self.tickCount % 20 == 0 || self.tickCount < 3)) {
|
||||
int lifespan=ServerConfig.lifespanTicks(self);
|
||||
TimerSyncPayload payload=new TimerSyncPayload(self.getId(), this.age, lifespan);
|
||||
for (ServerPlayer player : PlayerLookup.tracking(self)) ServerPlayNetworking.send(player,payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
|
||||
public record TimerSyncPayload(int entityId, int age, int lifespan) implements CustomPacketPayload {
|
||||
public static final Type<TimerSyncPayload> TYPE = new Type<>(ItemDespawnTimer.id("timer_sync"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, TimerSyncPayload> CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT, TimerSyncPayload::entityId,
|
||||
ByteBufCodecs.VAR_INT, TimerSyncPayload::age,
|
||||
ByteBufCodecs.VAR_INT, TimerSyncPayload::lifespan,
|
||||
TimerSyncPayload::new);
|
||||
@Override public Type<? extends CustomPacketPayload> type() { return TYPE; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "item-despawn-timer",
|
||||
"version": "${version}",
|
||||
"name": "Item Despawn Timer",
|
||||
"description": "Server-authoritative item despawn times with client-only countdown presentation settings.",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": ["whirlfrenzy.itemdespawntimer.ItemDespawnTimer"],
|
||||
"client": ["whirlfrenzy.itemdespawntimer.client.ItemDespawnTimerClient"]
|
||||
},
|
||||
"mixins": [
|
||||
"item-despawn-timer.mixins.json",
|
||||
{ "config": "item-despawn-timer.client.mixins.json", "environment": "client" }
|
||||
],
|
||||
"depends": { "fabricloader": ">=0.19.3", "minecraft": "~26.2", "java": ">=25", "fabric-api": "*" }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"required":true,"package":"whirlfrenzy.itemdespawntimer.mixin","compatibilityLevel":"JAVA_25","mixins":["ItemEntityMixin"],"injectors":{"defaultRequire":1}}
|
||||
Reference in New Issue
Block a user