diff --git a/src/main/java/com/atsuishio/superbwarfare/client/ClientRenderHandler.java b/src/main/java/com/atsuishio/superbwarfare/client/ClientRenderHandler.java index 0df4485ea..609ea9781 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/ClientRenderHandler.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/ClientRenderHandler.java @@ -1,5 +1,8 @@ package com.atsuishio.superbwarfare.client; +import com.atsuishio.superbwarfare.client.overlay.AmmoBarOverlay; +import com.atsuishio.superbwarfare.client.overlay.AmmoCountOverlay; +import com.atsuishio.superbwarfare.client.overlay.ArmorPlateOverlay; import com.atsuishio.superbwarfare.client.renderer.block.*; import com.atsuishio.superbwarfare.client.tooltip.*; import com.atsuishio.superbwarfare.client.tooltip.component.*; @@ -9,6 +12,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.EntityRenderersEvent; import net.neoforged.neoforge.client.event.RegisterClientTooltipComponentFactoriesEvent; +import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class ClientRenderHandler { @@ -34,4 +38,11 @@ public class ClientRenderHandler { event.registerBlockEntityRenderer(ModBlockEntities.CREATIVE_CHARGING_STATION.get(), context -> new CreativeChargingStationBlockEntityRenderer()); event.registerBlockEntityRenderer(ModBlockEntities.SMALL_CONTAINER.get(), context -> new SmallContainerBlockEntityRenderer()); } -} + + @SubscribeEvent + public static void registerOverlays(RegisterGuiLayersEvent event) { + event.registerBelowAll(ArmorPlateOverlay.ID, new ArmorPlateOverlay()); + event.registerBelowAll(AmmoBarOverlay.ID, new AmmoBarOverlay()); + event.registerBelowAll(AmmoCountOverlay.ID, new AmmoCountOverlay()); + } +} \ No newline at end of file diff --git a/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoBarOverlay.java b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoBarOverlay.java index 7d036475a..1d0267830 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoBarOverlay.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoBarOverlay.java @@ -3,38 +3,31 @@ package com.atsuishio.superbwarfare.client.overlay; import com.atsuishio.superbwarfare.Mod; import com.atsuishio.superbwarfare.capability.ModCapabilities; import com.atsuishio.superbwarfare.capability.player.PlayerVariable; -import com.atsuishio.superbwarfare.component.ModDataComponents; import com.atsuishio.superbwarfare.config.client.DisplayConfig; import com.atsuishio.superbwarfare.entity.vehicle.base.ArmedVehicleEntity; import com.atsuishio.superbwarfare.init.ModItems; import com.atsuishio.superbwarfare.init.ModKeyMappings; import com.atsuishio.superbwarfare.init.ModTags; -import com.atsuishio.superbwarfare.item.common.ammo.AmmoSupplierItem; import com.atsuishio.superbwarfare.item.gun.GunItem; import com.atsuishio.superbwarfare.item.gun.data.GunData; -import com.atsuishio.superbwarfare.tools.AmmoType; -import com.atsuishio.superbwarfare.tools.GunsTool; import com.atsuishio.superbwarfare.tools.InventoryTool; import com.atsuishio.superbwarfare.tools.NBTTool; -import com.atsuishio.superbwarfare.tools.animation.AnimationCurves; -import com.atsuishio.superbwarfare.tools.animation.AnimationTimer; -import com.atsuishio.superbwarfare.tools.animation.ValueAnimator; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.ChatFormatting; +import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.LayeredDraw; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.FastColor; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.neoforge.client.event.RenderGuiEvent; -@EventBusSubscriber(value = Dist.CLIENT) -public class AmmoBarOverlay { +import javax.annotation.ParametersAreNonnullByDefault; + + +public class AmmoBarOverlay implements LayeredDraw.Layer { + public static final ResourceLocation ID = Mod.loc("ammo_bar"); private static final ResourceLocation LINE = Mod.loc("textures/gun_icon/fire_mode/line.png"); private static final ResourceLocation SEMI = Mod.loc("textures/gun_icon/fire_mode/semi.png"); @@ -50,360 +43,8 @@ public class AmmoBarOverlay { return InventoryTool.hasCreativeAmmoBox(player); } - @SubscribeEvent - public static void renderWeaponInfo(RenderGuiEvent.Pre event) { - if (!DisplayConfig.AMMO_HUD.get()) return; - - int w = event.getGuiGraphics().guiWidth(); - int h = event.getGuiGraphics().guiHeight(); - Player player = Minecraft.getInstance().player; - - if (player == null) return; - if (player.isSpectator()) return; - - ItemStack stack = player.getMainHandItem(); - final var tag = NBTTool.getTag(stack); - if (stack.getItem() instanceof GunItem gunItem && !(player.getVehicle() instanceof ArmedVehicleEntity vehicle && vehicle.banHand(player))) { - PoseStack poseStack = event.getGuiGraphics().pose(); - var data = GunData.from(stack); - - // 渲染图标 - event.getGuiGraphics().blit(gunItem.getGunIcon(), - w - 135, - h - 40, - 0, - 0, - 64, - 16, - 64, - 16); - - // 渲染开火模式切换按键 - if (stack.getItem() != ModItems.MINIGUN.get()) { - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - "[" + ModKeyMappings.FIRE_MODE.getKey().getDisplayName().getString() + "]", - w - 111.5f, - h - 20, - 0xFFFFFF, - false - ); - } - - // 渲染开火模式 - ResourceLocation fireMode = getFireMode(data); - - if (stack.getItem() == ModItems.JAVELIN.get()) { - fireMode = tag.getBoolean("TopMode") ? TOP : DIR; - } - - if (stack.getItem() == ModItems.MINIGUN.get()) { - fireMode = MOUSE; - // 渲染加特林射速 - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - data.rpm() + " RPM", - w - 111f, - h - 20, - 0xFFFFFF, - false - ); - - event.getGuiGraphics().blit(fireMode, - w - 126, - h - 22, - 0, - 0, - 12, - 12, - 12, - 12); - } else { - if (stack.getItem() != ModItems.TRACHELIUM.get()) { - event.getGuiGraphics().blit(fireMode, - w - 95, - h - 21, - 0, - 0, - 8, - 8, - 8, - 8); - } else { - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - tag.getBoolean("DA") ? Component.translatable("des.superbwarfare.revolver.sa").withStyle(ChatFormatting.BOLD) : Component.translatable("des.superbwarfare.revolver.da").withStyle(ChatFormatting.BOLD), - w - 96, - h - 20, - 0xFFFFFF, - false - ); - } - } - - if (stack.getItem() != ModItems.MINIGUN.get() && stack.getItem() != ModItems.TRACHELIUM.get()) { - event.getGuiGraphics().blit(LINE, - w - 95, - h - 16, - 0, - 0, - 8, - 8, - 8, - 8); - } - - // 渲染当前弹药量 - poseStack.pushPose(); - poseStack.scale(1.5f, 1.5f, 1f); - - if ((stack.getItem() == ModItems.MINIGUN.get() || stack.getItem() == ModItems.BOCEK.get()) && hasCreativeAmmo()) { - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - "∞", - w / 1.5f - 64 / 1.5f, - h / 1.5f - 48 / 1.5f, - 0xFFFFFF, - true - ); - } else { - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - getGunAmmoCount(player) + "", - w / 1.5f - 64 / 1.5f, - h / 1.5f - 48 / 1.5f, - 0xFFFFFF, - true - ); - } - - poseStack.popPose(); - - // 渲染备弹量 - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - getPlayerAmmoCount(player), - w - 64, - h - 35, - 0xCCCCCC, - true - ); - - poseStack.pushPose(); - poseStack.scale(0.9f, 0.9f, 1f); - - // 渲染物品名称 - String gunName = gunItem.getGunDisplayName(); - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - gunName, - w / 0.9f - (100 + Minecraft.getInstance().font.width(gunName) / 2f) / 0.9f, - h / 0.9f - 60 / 0.9f, - 0xFFFFFF, - true - ); - - // 渲染弹药类型 - String ammoName = gunItem.getAmmoDisplayName(stack); - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - ammoName, - w / 0.9f - (100 + Minecraft.getInstance().font.width(ammoName) / 2f) / 0.9f, - h / 0.9f - 51 / 0.9f, - 0xC8A679, - true - ); - - poseStack.popPose(); - } - } - - private static final AnimationTimer ammoInfoTimer = new AnimationTimer(500, 2000) - .forwardAnimation(AnimationCurves.EASE_OUT_EXPO) - .backwardAnimation(AnimationCurves.EASE_IN_EXPO); - private static final AnimationTimer ammoBoxTimer = new AnimationTimer(500) - .forwardAnimation(AnimationCurves.EASE_OUT_EXPO) - .backwardAnimation(AnimationCurves.EASE_IN_EXPO); - - private static final ValueAnimator[] ammoCountAnimators = ValueAnimator.create( - AmmoType.values().length, 800, 0 - ); - private static final ValueAnimator[] ammoBoxAnimators = ValueAnimator.create( - AmmoType.values().length, 800, 0 - ); - - - /** - * 在手持弹药或弹药盒时,渲染玩家弹药总量信息 - */ - @SubscribeEvent - public static void renderAmmoInfo(RenderGuiEvent.Pre event) { - boolean startRenderingAmmoInfo = false; - Player player = Minecraft.getInstance().player; - if (player == null || player.isSpectator()) return; - - boolean isAmmoBox = false; - - // 动画计算 - var currentTime = System.currentTimeMillis(); - ItemStack stack = player.getMainHandItem(); - if ((stack.getItem() instanceof AmmoSupplierItem || stack.getItem() == ModItems.AMMO_BOX.get()) - && !(player.getVehicle() instanceof ArmedVehicleEntity vehicle && vehicle.banHand(player)) - ) { - // 刚拿出弹药物品时,视为开始弹药信息渲染 - startRenderingAmmoInfo = ammoInfoTimer.getProgress(currentTime) == 0; - ammoInfoTimer.forward(currentTime); - - if (stack.getItem() == ModItems.AMMO_BOX.get()) { - isAmmoBox = true; - ammoBoxTimer.forward(currentTime); - } else { - ammoBoxTimer.backward(currentTime); - } - } else { - ammoInfoTimer.backward(currentTime); - ammoBoxTimer.backward(currentTime); - } - if (!ammoInfoTimer.isForward() && ammoInfoTimer.finished(currentTime)) return; - - var poseStack = event.getGuiGraphics().pose(); - poseStack.pushPose(); - - int w = event.getGuiGraphics().guiWidth(); - int h = event.getGuiGraphics().guiHeight(); - - var ammoX = ammoInfoTimer.lerp(w + 120, (float) w / 2 + 40, currentTime); - final int fontHeight = 15; - var yOffset = (-h - AmmoType.values().length * fontHeight) / 2f; - - // 渲染总弹药数量 - var cap = player.getCapability(ModCapabilities.PLAYER_VARIABLE); - if (cap == null) cap = new PlayerVariable(); - var font = Minecraft.getInstance().font; - - for (var type : AmmoType.values()) { - var index = type.ordinal(); - var ammoCount = type.get(cap); - var animator = ammoCountAnimators[index]; - - var boxAnimator = ammoBoxAnimators[index]; - var boxAmmoCount = boxAnimator.newValue(); - boolean boxAmmoSelected = false; - - if (isAmmoBox) { - var data = stack.get(ModDataComponents.AMMO_BOX_INFO); - var ammoBoxType = data == null ? "All" : data.type(); - - boxAmmoCount = type.get(stack); - if (ammoBoxType.equals("All") || ammoBoxType.equals(type.name)) { - boxAnimator.forward(currentTime); - boxAmmoSelected = true; - } else { - boxAnimator.reset(boxAmmoCount); - } - } - - // 首次开始渲染弹药信息时,记录弹药数量,便于后续播放动画 - if (startRenderingAmmoInfo) { - animator.reset(ammoCount); - animator.endForward(currentTime); - if (isAmmoBox) { - boxAnimator.reset(type.get(stack)); - boxAnimator.endForward(currentTime); - } - } - - int ammoAdd = Integer.compare(ammoCount, animator.oldValue()); - // 弹药数量变化时,更新并开始播放弹药数量更改动画 - animator.compareAndUpdate(ammoCount, () -> { - // 弹药数量变化时,开始播放弹药数量更改动画 - animator.beginForward(currentTime); - }); - - var progress = animator.getProgress(currentTime); - var ammoCountStr = Integer.toString( - Math.round(animator.lerp(animator.oldValue(), ammoCount, currentTime)) - ); - - // 弹药增加时,颜色由绿变白,否则由红变白 - var fontColor = FastColor.ARGB32.lerp(progress, switch (ammoAdd) { - case 1 -> 0xFF00FF00; - case -1 -> 0xFFFF0000; - default -> 0xFFFFFFFF; - }, 0xFFFFFFFF); - - RenderSystem.setShaderColor(1, 1, 1, ammoInfoTimer.lerp(0, 1, currentTime)); - - // 弹药数量 - event.getGuiGraphics().drawString( - font, - ammoCountStr, - ammoX + (30 - font.width(ammoCountStr)), - h + yOffset, - fontColor, - true - ); - - // 弹药类型 - event.getGuiGraphics().drawString( - font, - Component.translatable(type.translatableKey).getString(), - ammoX + 35, - h + yOffset, - fontColor, - true - ); - - // 弹药盒信息渲染 - RenderSystem.setShaderColor(1, 1, 1, ammoBoxTimer.lerp(0, 1, currentTime)); - var ammoBoxX = ammoBoxTimer.lerp(-30, (float) w / 2, currentTime); - - int ammoBoxAdd = Integer.compare(boxAmmoCount, boxAnimator.oldValue()); - boxAnimator.compareAndUpdate(boxAmmoCount, () -> boxAnimator.beginForward(currentTime)); - - // 选中时显示为黄色,否则为白色 - var targetColor = boxAmmoSelected ? 0xFFFFFF00 : 0xFFFFFFFF; - - var boxFontColor = FastColor.ARGB32.lerp(boxAnimator.getProgress(currentTime), - switch (ammoBoxAdd) { - case 1 -> 0xFF00FF00; - case -1 -> 0xFFFF0000; - default -> targetColor; - }, - targetColor - ); - - // 弹药盒内弹药数量 - event.getGuiGraphics().drawString( - Minecraft.getInstance().font, - Integer.toString( - Math.round(boxAnimator.lerp(boxAnimator.oldValue(), boxAmmoCount, currentTime)) - ), - ammoBoxX - 70, - h + yOffset, - boxFontColor, - true - ); - - yOffset += fontHeight; - } - - RenderSystem.setShaderColor(1, 1, 1, 1); - poseStack.popPose(); - } - - private static ResourceLocation getFireMode(GunData data) { - return switch (data.fireMode()) { - case 1 -> BURST; - case 2 -> AUTO; - default -> SEMI; - }; - } - private static int getGunAmmoCount(Player player) { ItemStack stack = player.getMainHandItem(); - var data = GunData.from(stack); - var tag = data.tag(); if (stack.getItem() == ModItems.MINIGUN.get()) { var cap = player.getCapability(ModCapabilities.PLAYER_VARIABLE); @@ -411,28 +52,26 @@ public class AmmoBarOverlay { } if (stack.getItem() == ModItems.BOCEK.get()) { - return GunsTool.getGunIntTag(tag, "MaxAmmo"); + return GunData.from(stack).data().getInt("MaxAmmo"); } - return data.ammo(); + return GunData.from(stack).ammo(); } private static String getPlayerAmmoCount(Player player) { ItemStack stack = player.getMainHandItem(); - final var tag = NBTTool.getTag(stack); if (stack.getItem() == ModItems.MINIGUN.get() || stack.getItem() == ModItems.BOCEK.get()) { return ""; } + var cap = player.getCapability(ModCapabilities.PLAYER_VARIABLE); + if (cap == null) cap = new PlayerVariable(); if (!hasCreativeAmmo()) { + var data = GunData.from(stack); if (stack.is(ModTags.Items.LAUNCHER) || stack.getItem() == ModItems.TASER.get()) { - return "" + GunsTool.getGunIntTag(tag, "MaxAmmo"); + return "" + data.data().getInt("MaxAmmo"); } - - var cap = player.getCapability(ModCapabilities.PLAYER_VARIABLE); - if (cap == null) return ""; - if (stack.is(ModTags.Items.USE_RIFLE_AMMO)) { return "" + cap.rifleAmmo; } @@ -453,4 +92,181 @@ public class AmmoBarOverlay { return "∞"; } + + @Override + @ParametersAreNonnullByDefault + public void render(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { + if (!DisplayConfig.AMMO_HUD.get()) return; + + int w = guiGraphics.guiWidth(); + int h = guiGraphics.guiHeight(); + Player player = Minecraft.getInstance().player; + + if (player == null) return; + if (player.isSpectator()) return; + + ItemStack stack = player.getMainHandItem(); + final var tag = NBTTool.getTag(stack); + if (stack.getItem() instanceof GunItem gunItem && !(player.getVehicle() instanceof ArmedVehicleEntity vehicle && vehicle.banHand(player))) { + PoseStack poseStack = guiGraphics.pose(); + var data = GunData.from(stack); + + // 渲染图标 + guiGraphics.blit(gunItem.getGunIcon(), + w - 135, + h - 40, + 0, + 0, + 64, + 16, + 64, + 16); + + // 渲染开火模式切换按键 + if (stack.getItem() != ModItems.MINIGUN.get()) { + guiGraphics.drawString( + Minecraft.getInstance().font, + "[" + ModKeyMappings.FIRE_MODE.getKey().getDisplayName().getString() + "]", + w - 111.5f, + h - 20, + 0xFFFFFF, + false + ); + } + + // 渲染开火模式 + ResourceLocation fireMode = getFireMode(data); + + if (stack.getItem() == ModItems.JAVELIN.get()) { + fireMode = tag.getBoolean("TopMode") ? TOP : DIR; + } + + if (stack.getItem() == ModItems.MINIGUN.get()) { + fireMode = MOUSE; + // 渲染加特林射速 + guiGraphics.drawString( + Minecraft.getInstance().font, + data.rpm() + " RPM", + w - 111f, + h - 20, + 0xFFFFFF, + false + ); + + guiGraphics.blit(fireMode, + w - 126, + h - 22, + 0, + 0, + 12, + 12, + 12, + 12); + } else { + if (stack.getItem() != ModItems.TRACHELIUM.get()) { + guiGraphics.blit(fireMode, + w - 95, + h - 21, + 0, + 0, + 8, + 8, + 8, + 8); + } else { + guiGraphics.drawString( + Minecraft.getInstance().font, + tag.getBoolean("DA") ? Component.translatable("des.superbwarfare.revolver.sa").withStyle(ChatFormatting.BOLD) : Component.translatable("des.superbwarfare.revolver.da").withStyle(ChatFormatting.BOLD), + w - 96, + h - 20, + 0xFFFFFF, + false + ); + } + } + + if (stack.getItem() != ModItems.MINIGUN.get() && stack.getItem() != ModItems.TRACHELIUM.get()) { + guiGraphics.blit(LINE, + w - 95, + h - 16, + 0, + 0, + 8, + 8, + 8, + 8); + } + + // 渲染当前弹药量 + poseStack.pushPose(); + poseStack.scale(1.5f, 1.5f, 1f); + + if ((stack.getItem() == ModItems.MINIGUN.get() || stack.getItem() == ModItems.BOCEK.get()) && hasCreativeAmmo()) { + guiGraphics.drawString( + Minecraft.getInstance().font, + "∞", + w / 1.5f - 64 / 1.5f, + h / 1.5f - 48 / 1.5f, + 0xFFFFFF, + true + ); + } else { + guiGraphics.drawString( + Minecraft.getInstance().font, + getGunAmmoCount(player) + "", + w / 1.5f - 64 / 1.5f, + h / 1.5f - 48 / 1.5f, + 0xFFFFFF, + true + ); + } + + poseStack.popPose(); + + // 渲染备弹量 + guiGraphics.drawString( + Minecraft.getInstance().font, + getPlayerAmmoCount(player), + w - 64, + h - 35, + 0xCCCCCC, + true + ); + + poseStack.pushPose(); + poseStack.scale(0.9f, 0.9f, 1f); + + // 渲染物品名称 + String gunName = gunItem.getGunDisplayName(); + guiGraphics.drawString( + Minecraft.getInstance().font, + gunName, + w / 0.9f - (100 + Minecraft.getInstance().font.width(gunName) / 2f) / 0.9f, + h / 0.9f - 60 / 0.9f, + 0xFFFFFF, + true + ); + + // 渲染弹药类型 + String ammoName = gunItem.getAmmoDisplayName(stack); + guiGraphics.drawString( + Minecraft.getInstance().font, + ammoName, + w / 0.9f - (100 + Minecraft.getInstance().font.width(ammoName) / 2f) / 0.9f, + h / 0.9f - 51 / 0.9f, + 0xC8A679, + true + ); + + poseStack.popPose(); + } + } + + private static ResourceLocation getFireMode(GunData data) { + return switch (data.fireMode()) { + case 1 -> BURST; + case 2 -> AUTO; + default -> SEMI; + }; + } } diff --git a/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoCountOverlay.java b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoCountOverlay.java new file mode 100644 index 000000000..6f9f852d3 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoCountOverlay.java @@ -0,0 +1,204 @@ +package com.atsuishio.superbwarfare.client.overlay; + +import com.atsuishio.superbwarfare.Mod; +import com.atsuishio.superbwarfare.capability.ModCapabilities; +import com.atsuishio.superbwarfare.capability.player.PlayerVariable; +import com.atsuishio.superbwarfare.component.ModDataComponents; +import com.atsuishio.superbwarfare.entity.vehicle.base.ArmedVehicleEntity; +import com.atsuishio.superbwarfare.init.ModItems; +import com.atsuishio.superbwarfare.item.common.ammo.AmmoSupplierItem; +import com.atsuishio.superbwarfare.tools.AmmoType; +import com.atsuishio.superbwarfare.tools.animation.AnimationCurves; +import com.atsuishio.superbwarfare.tools.animation.AnimationTimer; +import com.atsuishio.superbwarfare.tools.animation.ValueAnimator; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.LayeredDraw; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.ParametersAreNonnullByDefault; + +public class AmmoCountOverlay implements LayeredDraw.Layer { + + public static final ResourceLocation ID = Mod.loc("ammo_count"); + + private static final AnimationTimer ammoInfoTimer = new AnimationTimer(500, 2000) + .forwardAnimation(AnimationCurves.EASE_OUT_EXPO) + .backwardAnimation(AnimationCurves.EASE_IN_EXPO); + private static final AnimationTimer ammoBoxTimer = new AnimationTimer(500) + .forwardAnimation(AnimationCurves.EASE_OUT_EXPO) + .backwardAnimation(AnimationCurves.EASE_IN_EXPO); + + private static final ValueAnimator[] ammoCountAnimators = ValueAnimator.create( + AmmoType.values().length, 800, 0 + ); + private static final ValueAnimator[] ammoBoxAnimators = ValueAnimator.create( + AmmoType.values().length, 800, 0 + ); + + /** + * 在手持弹药或弹药盒时,渲染玩家弹药总量信息 + */ + @Override + @ParametersAreNonnullByDefault + public void render(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { + boolean startRenderingAmmoInfo = false; + Player player = Minecraft.getInstance().player; + if (player == null || player.isSpectator()) return; + + boolean isAmmoBox = false; + + // 动画计算 + var currentTime = System.currentTimeMillis(); + ItemStack stack = player.getMainHandItem(); + if ((stack.getItem() instanceof AmmoSupplierItem || stack.getItem() == ModItems.AMMO_BOX.get()) + && !(player.getVehicle() instanceof ArmedVehicleEntity vehicle && vehicle.banHand(player)) + ) { + // 刚拿出弹药物品时,视为开始弹药信息渲染 + startRenderingAmmoInfo = ammoInfoTimer.getProgress(currentTime) == 0; + ammoInfoTimer.forward(currentTime); + + if (stack.getItem() == ModItems.AMMO_BOX.get()) { + isAmmoBox = true; + ammoBoxTimer.forward(currentTime); + } else { + ammoBoxTimer.backward(currentTime); + } + } else { + ammoInfoTimer.backward(currentTime); + ammoBoxTimer.backward(currentTime); + } + if (!ammoInfoTimer.isForward() && ammoInfoTimer.finished(currentTime)) return; + + var poseStack = guiGraphics.pose(); + poseStack.pushPose(); + + int w = guiGraphics.guiWidth(); + int h = guiGraphics.guiHeight(); + + var ammoX = ammoInfoTimer.lerp(w + 120, (float) w / 2 + 40, currentTime); + final int fontHeight = 15; + var yOffset = (-h - AmmoType.values().length * fontHeight) / 2f; + + // 渲染总弹药数量 + var cap = player.getCapability(ModCapabilities.PLAYER_VARIABLE); + if (cap == null) cap = new PlayerVariable(); + var font = Minecraft.getInstance().font; + + for (var type : AmmoType.values()) { + var index = type.ordinal(); + var ammoCount = type.get(cap); + var animator = ammoCountAnimators[index]; + + var boxAnimator = ammoBoxAnimators[index]; + var boxAmmoCount = boxAnimator.newValue(); + boolean boxAmmoSelected = false; + + if (isAmmoBox) { + var data = stack.get(ModDataComponents.AMMO_BOX_INFO); + var ammoBoxType = data == null ? "All" : data.type(); + + boxAmmoCount = type.get(stack); + if (ammoBoxType.equals("All") || ammoBoxType.equals(type.name)) { + boxAnimator.forward(currentTime); + boxAmmoSelected = true; + } else { + boxAnimator.reset(boxAmmoCount); + } + } + + // 首次开始渲染弹药信息时,记录弹药数量,便于后续播放动画 + if (startRenderingAmmoInfo) { + animator.reset(ammoCount); + animator.endForward(currentTime); + if (isAmmoBox) { + boxAnimator.reset(type.get(stack)); + boxAnimator.endForward(currentTime); + } + } + + int ammoAdd = Integer.compare(ammoCount, animator.oldValue()); + // 弹药数量变化时,更新并开始播放弹药数量更改动画 + animator.compareAndUpdate(ammoCount, () -> { + // 弹药数量变化时,开始播放弹药数量更改动画 + animator.beginForward(currentTime); + }); + + var progress = animator.getProgress(currentTime); + var ammoCountStr = Integer.toString( + Math.round(animator.lerp(animator.oldValue(), ammoCount, currentTime)) + ); + + // 弹药增加时,颜色由绿变白,否则由红变白 + var fontColor = FastColor.ARGB32.lerp(progress, switch (ammoAdd) { + case 1 -> 0xFF00FF00; + case -1 -> 0xFFFF0000; + default -> 0xFFFFFFFF; + }, 0xFFFFFFFF); + + RenderSystem.setShaderColor(1, 1, 1, ammoInfoTimer.lerp(0, 1, currentTime)); + + // 弹药数量 + guiGraphics.drawString( + font, + ammoCountStr, + ammoX + (30 - font.width(ammoCountStr)), + h + yOffset, + fontColor, + true + ); + + // 弹药类型 + guiGraphics.drawString( + font, + Component.translatable(type.translatableKey).getString(), + ammoX + 35, + h + yOffset, + fontColor, + true + ); + + // 弹药盒信息渲染 + RenderSystem.setShaderColor(1, 1, 1, ammoBoxTimer.lerp(0, 1, currentTime)); + var ammoBoxX = ammoBoxTimer.lerp(-30, (float) w / 2, currentTime); + + int ammoBoxAdd = Integer.compare(boxAmmoCount, boxAnimator.oldValue()); + boxAnimator.compareAndUpdate(boxAmmoCount, () -> boxAnimator.beginForward(currentTime)); + + // 选中时显示为黄色,否则为白色 + var targetColor = boxAmmoSelected ? 0xFFFFFF00 : 0xFFFFFFFF; + + var boxFontColor = FastColor.ARGB32.lerp(boxAnimator.getProgress(currentTime), + switch (ammoBoxAdd) { + case 1 -> 0xFF00FF00; + case -1 -> 0xFFFF0000; + default -> targetColor; + }, + targetColor + ); + + // 弹药盒内弹药数量 + guiGraphics.drawString( + Minecraft.getInstance().font, + Integer.toString( + Math.round(boxAnimator.lerp(boxAnimator.oldValue(), boxAmmoCount, currentTime)) + ), + ammoBoxX - 70, + h + yOffset, + boxFontColor, + true + ); + + yOffset += fontHeight; + } + + RenderSystem.setShaderColor(1, 1, 1, 1); + poseStack.popPose(); + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/client/overlay/ArmorPlateOverlay.java b/src/main/java/com/atsuishio/superbwarfare/client/overlay/ArmorPlateOverlay.java index 2484a5cb0..f45c6ad4d 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/overlay/ArmorPlateOverlay.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/overlay/ArmorPlateOverlay.java @@ -4,18 +4,20 @@ import com.atsuishio.superbwarfare.Mod; import com.atsuishio.superbwarfare.config.client.DisplayConfig; import com.atsuishio.superbwarfare.init.ModTags; import com.atsuishio.superbwarfare.tools.NBTTool; +import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.LayeredDraw; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.neoforge.client.event.RenderGuiEvent; -@EventBusSubscriber(value = Dist.CLIENT) -public class ArmorPlateOverlay { +import javax.annotation.ParametersAreNonnullByDefault; + + +public class ArmorPlateOverlay implements LayeredDraw.Layer { + public static final ResourceLocation ID = Mod.loc("armor_plate"); private static final ResourceLocation ICON = Mod.loc("textures/screens/armor_plate_icon.png"); private static final ResourceLocation LEVEL1 = Mod.loc("textures/screens/armor_plate_level1.png"); @@ -25,12 +27,12 @@ public class ArmorPlateOverlay { private static final ResourceLocation LEVEL2_FRAME = Mod.loc("textures/screens/armor_plate_level2_frame.png"); private static final ResourceLocation LEVEL3_FRAME = Mod.loc("textures/screens/armor_plate_level3_frame.png"); - @SubscribeEvent - public static void onRenderGui(RenderGuiEvent.Pre event) { + @Override + @ParametersAreNonnullByDefault + public void render(GuiGraphics guiGraphics, DeltaTracker deltaTracker) { if (!DisplayConfig.ARMOR_PLATE_HUD.get()) return; - var gui = event.getGuiGraphics(); - int h = gui.guiHeight(); + int h = guiGraphics.guiHeight(); Player player = Minecraft.getInstance().player; if (player == null) return; @@ -63,16 +65,16 @@ public class ArmorPlateOverlay { int length = armorLevel * 30; - gui.pose().pushPose(); + guiGraphics.pose().pushPose(); // 渲染图标 - gui.blit(ICON, 10, h - 13, 0, 0, 8, 8, 8, 8); + guiGraphics.blit(ICON, 10, h - 13, 0, 0, 8, 8, 8, 8); // 渲染框架 - gui.blit(frame, 20, h - 12, 0, 0, length, 6, length, 6); + guiGraphics.blit(frame, 20, h - 12, 0, 0, length, 6, length, 6); // 渲染盔甲值 - gui.blit(texture, 20, h - 12, 0, 0, (int) amount, 6, length, 6); + guiGraphics.blit(texture, 20, h - 12, 0, 0, (int) amount, 6, length, 6); - gui.pose().popPose(); + guiGraphics.pose().popPose(); } } diff --git a/src/main/java/com/atsuishio/superbwarfare/item/ArmorPlate.java b/src/main/java/com/atsuishio/superbwarfare/item/ArmorPlate.java index aa4097bdd..dcd4c6594 100644 --- a/src/main/java/com/atsuishio/superbwarfare/item/ArmorPlate.java +++ b/src/main/java/com/atsuishio/superbwarfare/item/ArmorPlate.java @@ -53,7 +53,6 @@ public class ArmorPlate extends Item { armorLevel = 3; } - // TODO 解决数据双端同步问题 if (NBTTool.getTag(armor).getDouble("ArmorPlate") < armorLevel * 15) { playerIn.startUsingItem(handIn); } @@ -79,7 +78,9 @@ public class ArmorPlate extends Item { armorLevel = 3; } - NBTTool.getTag(armor).putDouble("ArmorPlate", Mth.clamp(NBTTool.getTag(armor).getDouble("ArmorPlate") + 15, 0, armorLevel * 15)); + var tag = NBTTool.getTag(armor); + tag.putDouble("ArmorPlate", Mth.clamp(tag.getDouble("ArmorPlate") + 15, 0, armorLevel * 15)); + NBTTool.saveTag(armor, tag); if (pLivingEntity instanceof ServerPlayer serverPlayer) { serverPlayer.level().playSound((Entity) null, serverPlayer.getOnPos(), SoundEvents.ARMOR_EQUIP_IRON.value(), SoundSource.PLAYERS, 0.5f, 1);