superb-warfare/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoOverlay.java
2025-04-08 08:42:44 +08:00

200 lines
8.1 KiB
Java

package com.atsuishio.superbwarfare.client.overlay;
import com.atsuishio.superbwarfare.Mod;
import com.atsuishio.superbwarfare.init.ModCapabilities;
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.item.common.ammo.box.AmmoBoxInfo;
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.Minecraft;
import net.minecraft.network.chat.Component;
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(modid = Mod.MODID, value = Dist.CLIENT)
public class AmmoOverlay {
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<Integer>[] ammoCountAnimators = ValueAnimator.create(
AmmoType.values().length, 800, 0
);
private static final ValueAnimator<Integer>[] 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, null);
if (cap == null) return;
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 info = stack.get(ModDataComponents.AMMO_BOX_INFO);
if (info == null) info = new AmmoBoxInfo("All", false);
var ammoBoxType = info.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();
}
}