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 3eccf0510..5159fc928 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoBarOverlay.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AmmoBarOverlay.java @@ -14,14 +14,14 @@ import com.atsuishio.superbwarfare.tools.GunsTool; import com.atsuishio.superbwarfare.tools.InventoryTool; import com.atsuishio.superbwarfare.tools.animation.AnimationCurves; import com.atsuishio.superbwarfare.tools.animation.AnimationTimer; -import com.atsuishio.superbwarfare.tools.animation.DualValueHolder; +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.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; +import net.minecraft.util.FastColor; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraftforge.api.distmarker.Dist; @@ -215,10 +215,17 @@ public class AmmoBarOverlay { private static final AnimationTimer ammoInfoTimer = new AnimationTimer(1500) .forwardAnimation(AnimationCurves.EASE_OUT_EXPO) .backwardAnimation(AnimationCurves.EASE_IN_EXPO); + private static final AnimationTimer ammoBoxTimer = new AnimationTimer(1500) + .forwardAnimation(AnimationCurves.EASE_OUT_EXPO) + .backwardAnimation(AnimationCurves.EASE_IN_EXPO); - private static final DualValueHolder[] ammoCountHolders = DualValueHolder.create(AmmoType.values().length, 0); + private static final ValueAnimator[] ammoCountAnimators = ValueAnimator.create( + AmmoType.values().length, 800, 0 + ); + private static final ValueAnimator[] ammoBoxAnimators = ValueAnimator.create( + AmmoType.values().length, 800, 0 + ); - private static final AnimationTimer[] ammoCountTimers = AnimationTimer.createTimers(AmmoType.values().length, 800, AnimationCurves.EASE_OUT_EXPO); /** * 在手持弹药或弹药盒时,渲染玩家弹药总量信息 @@ -229,80 +236,100 @@ public class AmmoBarOverlay { 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))) { + 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(); - var xOffset = -Mth.lerp(ammoInfoTimer.getProgress(currentTime), 0, 120); - final int fontHeight = 15; - var yOffset = -AmmoType.values().length * fontHeight; - - // 渲染总弹药数量 - var cap = player.getCapability(ModVariables.PLAYER_VARIABLES_CAPABILITY, null).orElse(new ModVariables.PlayerVariables()); int w = event.getWindow().getGuiScaledWidth(); int h = event.getWindow().getGuiScaledHeight(); - // 总体透明度设置 - RenderSystem.setShaderColor(1, 1, 1, Mth.lerp(ammoInfoTimer.getProgress(currentTime), 0, 1)); + 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(ModVariables.PLAYER_VARIABLES_CAPABILITY, null).orElse(new ModVariables.PlayerVariables()); var font = Minecraft.getInstance().font; for (var type : AmmoType.values()) { var index = type.ordinal(); var ammoCount = type.get(cap); - var holder = ammoCountHolders[index]; - var timer = ammoCountTimers[index]; + var animator = ammoCountAnimators[index]; + + var boxAnimator = ammoBoxAnimators[index]; + var boxAmmoCount = boxAnimator.newValue(); + boolean boxAmmoSelected = false; + + if (isAmmoBox) { + var ammoBoxType = stack.getOrCreateTag().getString("Type"); + boxAmmoCount = type.get(stack); + if (ammoBoxType.equals("All") || ammoBoxType.equals(type.name)) { + boxAnimator.forward(currentTime); + boxAmmoSelected = true; + } else { + boxAnimator.reset(boxAmmoCount); + } + } // 首次开始渲染弹药信息时,记录弹药数量,便于后续播放动画 if (startRenderingAmmoInfo) { - holder.reset(ammoCount); - timer.endForward(currentTime); + animator.reset(ammoCount); + animator.endForward(currentTime); + if (isAmmoBox) { + boxAnimator.reset(type.get(stack)); + boxAnimator.endForward(currentTime); + } } - int isAdd = ammoCount == holder.oldValue() ? 0 : (ammoCount > holder.oldValue() ? 1 : -1); - // 弹药数量变化时,开始播放弹药数量更改动画 - if (holder.newValue() != ammoCount) { - // 更新初始和当前弹药数量,播放由 初始弹药数量 -> 当前弹药数量 的动画 - holder.update(ammoCount); - // 开始播放弹药数量更改动画 - timer.beginForward(currentTime); - } + int ammoAdd = Integer.compare(ammoCount, animator.oldValue()); + // 弹药数量变化时,更新并开始播放弹药数量更改动画 + animator.compareAndUpdate(ammoCount, () -> { + // 弹药数量变化时,开始播放弹药数量更改动画 + animator.beginForward(currentTime); + }); - var progress = timer.getProgress(currentTime); + var progress = animator.getProgress(currentTime); var ammoCountStr = Integer.toString( - Math.round(Mth.lerp(progress, holder.oldValue(), ammoCount)) + Math.round(animator.lerp(animator.oldValue(), ammoCount, currentTime)) ); // 弹药增加时,颜色由绿变白,否则由红变白 - var fontColor = switch (isAdd) { - case 1 -> Mth.color( - Mth.lerp(progress, 0, 1), - 1, - Mth.lerp(progress, 0, 1) - ); - case -1 -> Mth.color( - 1, - Mth.lerp(progress, 0, 1), - Mth.lerp(progress, 0, 1) - ); - default -> 0xFFFFFF; - }; + 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( - Minecraft.getInstance().font, + font, ammoCountStr, - w + xOffset + (30 - font.width(ammoCountStr)), + ammoX + (30 - font.width(ammoCountStr)), h + yOffset, fontColor, true @@ -310,14 +337,45 @@ public class AmmoBarOverlay { // 弹药类型 event.getGuiGraphics().drawString( - Minecraft.getInstance().font, + font, Component.translatable(type.translatableKey).getString(), - w + xOffset + 35, + 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; } diff --git a/src/main/java/com/atsuishio/superbwarfare/client/overlay/VehicleHudOverlay.java b/src/main/java/com/atsuishio/superbwarfare/client/overlay/VehicleHudOverlay.java index b02dc9175..bc1d7e122 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/overlay/VehicleHudOverlay.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/overlay/VehicleHudOverlay.java @@ -68,9 +68,9 @@ public class VehicleHudOverlay { public static final int ANIMATION_TIME = 300; private static final AnimationTimer[] weaponSlotsTimer = AnimationTimer.createTimers(9, ANIMATION_TIME, AnimationCurves.EASE_OUT_CIRC); - private static boolean lastTimeRenderingWeapons = false; - private static int lastTimeWeaponIndex = 0; - private static int lastTimeRenderWeaponIndex = 0; + private static boolean wasRenderingWeapons = false; + private static int oldWeaponIndex = 0; + private static int oldRenderWeaponIndex = 0; private static final AnimationTimer weaponIndexUpdateTimer = new AnimationTimer(ANIMATION_TIME).animation(AnimationCurves.EASE_OUT_CIRC); @@ -81,7 +81,7 @@ public class VehicleHudOverlay { Player player = Minecraft.getInstance().player; if (!shouldRenderHud(player)) { - lastTimeRenderingWeapons = false; + wasRenderingWeapons = false; return; } @@ -456,8 +456,8 @@ public class VehicleHudOverlay { private static void renderWeaponInfo(GuiGraphics guiGraphics, VehicleEntity vehicle, int w, int h) { if (!(vehicle instanceof WeaponVehicleEntity weaponVehicle)) return; - var temp = lastTimeRenderingWeapons; - lastTimeRenderingWeapons = false; + var temp = wasRenderingWeapons; + wasRenderingWeapons = false; Player player = Minecraft.getInstance().player; assert player != null; @@ -471,37 +471,33 @@ public class VehicleHudOverlay { int weaponIndex = weaponVehicle.getWeaponIndex(index); if (weaponIndex == -1) return; - lastTimeRenderingWeapons = temp; + wasRenderingWeapons = temp; var currentTime = System.currentTimeMillis(); // 若上一帧未在渲染武器信息,则初始化动画相关变量 - if (!lastTimeRenderingWeapons) { - weaponSlotsTimer[weaponIndex].begin(); - weaponSlotsTimer[weaponIndex].forward(currentTime); + if (!wasRenderingWeapons) { + weaponSlotsTimer[weaponIndex].beginForward(currentTime); - if (lastTimeWeaponIndex != weaponIndex) { - weaponSlotsTimer[lastTimeWeaponIndex].backward(currentTime); - weaponSlotsTimer[lastTimeWeaponIndex].end(); + if (oldWeaponIndex != weaponIndex) { + weaponSlotsTimer[oldWeaponIndex].endBackward(currentTime); - lastTimeWeaponIndex = weaponIndex; - lastTimeRenderWeaponIndex = weaponIndex; + oldWeaponIndex = weaponIndex; + oldRenderWeaponIndex = weaponIndex; } - weaponIndexUpdateTimer.begin(); - weaponIndexUpdateTimer.forward(currentTime); + weaponIndexUpdateTimer.beginForward(currentTime); } // 切换武器时,更新上一个武器槽位和当前武器槽位的动画信息 - if (weaponIndex != lastTimeWeaponIndex) { + if (weaponIndex != oldWeaponIndex) { weaponSlotsTimer[weaponIndex].forward(currentTime); - weaponSlotsTimer[lastTimeWeaponIndex].backward(currentTime); + weaponSlotsTimer[oldWeaponIndex].backward(currentTime); - lastTimeRenderWeaponIndex = lastTimeWeaponIndex; - lastTimeWeaponIndex = weaponIndex; + oldRenderWeaponIndex = oldWeaponIndex; + oldWeaponIndex = weaponIndex; - weaponIndexUpdateTimer.begin(); - weaponIndexUpdateTimer.forward(currentTime); + weaponIndexUpdateTimer.beginForward(currentTime); } var pose = guiGraphics.pose(); @@ -537,7 +533,7 @@ public class VehicleHudOverlay { // 当前选中武器 if (weaponIndex == i) { var startY = Mth.lerp(progress, - h - (weapons.size() - 1 - lastTimeRenderWeaponIndex) * 18 - 16, + h - (weapons.size() - 1 - oldRenderWeaponIndex) * 18 - 16, h - (weapons.size() - 1 - weaponIndex) * 18 - 16 ); @@ -562,10 +558,10 @@ public class VehicleHudOverlay { pose.popPose(); // 切换武器光标动画播放结束后,更新上次选择槽位 - if (lastTimeWeaponIndex != lastTimeRenderWeaponIndex && weaponIndexUpdateTimer.finished(currentTime)) { - lastTimeRenderWeaponIndex = lastTimeWeaponIndex; + if (oldWeaponIndex != oldRenderWeaponIndex && weaponIndexUpdateTimer.finished(currentTime)) { + oldRenderWeaponIndex = oldWeaponIndex; } - lastTimeRenderingWeapons = true; + wasRenderingWeapons = true; } private static void renderNumber(GuiGraphics guiGraphics, int number, boolean percent, float x, float y, float scale) { diff --git a/src/main/java/com/atsuishio/superbwarfare/item/common/ammo/AmmoSupplierItem.java b/src/main/java/com/atsuishio/superbwarfare/item/common/ammo/AmmoSupplierItem.java index 3ac4e2c2b..5388c4a4f 100644 --- a/src/main/java/com/atsuishio/superbwarfare/item/common/ammo/AmmoSupplierItem.java +++ b/src/main/java/com/atsuishio/superbwarfare/item/common/ammo/AmmoSupplierItem.java @@ -55,6 +55,6 @@ public class AmmoSupplierItem extends Item { player.displayClientMessage(Component.translatable("item.superbwarfare.ammo_supplier.supply", Component.translatable(this.type.translatableKey), ammoToAdd * count), true); level.playSound(null, player.blockPosition(), ModSounds.BULLET_SUPPLY.get(), SoundSource.PLAYERS, 1, 1); } - return InteractionResultHolder.consume(stack); + return InteractionResultHolder.success(stack); } } diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/animation/AnimationTimer.java b/src/main/java/com/atsuishio/superbwarfare/tools/animation/AnimationTimer.java index fa4fc0609..94f112070 100644 --- a/src/main/java/com/atsuishio/superbwarfare/tools/animation/AnimationTimer.java +++ b/src/main/java/com/atsuishio/superbwarfare/tools/animation/AnimationTimer.java @@ -151,7 +151,7 @@ public class AnimationTimer { if (!initialized) { initialized = true; startTime = currentTime + (isStart ? 0 : duration); - } else { + } else if (reversed) { startTime = currentTime - getElapsedTime(currentTime); } reversed = false; @@ -181,7 +181,7 @@ public class AnimationTimer { if (!initialized) { initialized = true; startTime = currentTime + (isStart ? duration : 0); - } else { + } else if (!reversed) { startTime = currentTime + getElapsedTime(currentTime); } reversed = true; @@ -203,4 +203,8 @@ public class AnimationTimer { backward(currentTime); } + public float lerp(float start, float end, long currentTime) { + return Mth.lerp(getProgress(currentTime), start, end); + } + } diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/animation/DualValueHolder.java b/src/main/java/com/atsuishio/superbwarfare/tools/animation/DualValueHolder.java deleted file mode 100644 index 766377deb..000000000 --- a/src/main/java/com/atsuishio/superbwarfare/tools/animation/DualValueHolder.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.atsuishio.superbwarfare.tools.animation; - -public class DualValueHolder { - private T oldValue; - private T newValue; - - public static DualValueHolder[] create(int size, T defaultValue) { - // 傻逼Java - @SuppressWarnings("unchecked") - DualValueHolder[] holders = new DualValueHolder[size]; - - for (int i = 0; i < size; i++) { - holders[i] = new DualValueHolder<>(); - holders[i].reset(defaultValue); - } - - return holders; - } - - public void update(T value) { - this.oldValue = this.newValue; - this.newValue = value; - } - - public void reset(T value) { - this.oldValue = value; - this.newValue = value; - } - - public T oldValue() { - return this.oldValue; - } - - public T newValue() { - return this.newValue; - } - - public boolean changed() { - return this.oldValue.equals(this.newValue); - } -} diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/animation/ValueAnimator.java b/src/main/java/com/atsuishio/superbwarfare/tools/animation/ValueAnimator.java new file mode 100644 index 000000000..a561ab6e0 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/tools/animation/ValueAnimator.java @@ -0,0 +1,72 @@ +package com.atsuishio.superbwarfare.tools.animation; + +import javax.annotation.Nullable; + +/** + * 可以存储额外的新旧数值的AnimationTimer + */ +public class ValueAnimator extends AnimationTimer { + private T oldValue; + private T newValue; + + public ValueAnimator(long duration, T defaultValue) { + super(duration); + reset(defaultValue); + } + + public static ValueAnimator[] create(int size, long duration, T defaultValue) { + // 傻逼Java + @SuppressWarnings("unchecked") + ValueAnimator[] animators = (ValueAnimator[]) new ValueAnimator[size]; + + for (int i = 0; i < size; i++) { + animators[i] = new ValueAnimator<>(duration, defaultValue); + } + + return animators; + } + + public void update(T value) { + this.oldValue = this.newValue; + this.newValue = value; + } + + /** + * 比较当前值和新值,如果不同则更新 + * + * @param value 当前值 + */ + public void compareAndUpdate(T value) { + compareAndUpdate(value, null); + } + + + /** + * 比较当前值和新值,如果不同则更新 + * + * @param value 当前值 + * @param callback 更新成功后的回调函数 + */ + public void compareAndUpdate(T value, @Nullable Runnable callback) { + if (!this.newValue.equals(value)) { + update(value); + + if (callback != null) { + callback.run(); + } + } + } + + public void reset(T value) { + this.oldValue = value; + this.newValue = value; + } + + public T oldValue() { + return this.oldValue; + } + + public T newValue() { + return this.newValue; + } +}