diff --git a/src/main/java/com/atsuishio/superbwarfare/client/overlay/AircraftOverlay.java b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AircraftOverlay.java index edd3bf02f..93046e03e 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/overlay/AircraftOverlay.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/overlay/AircraftOverlay.java @@ -35,6 +35,7 @@ import java.util.List; import static com.atsuishio.superbwarfare.client.RenderHelper.preciseBlit; import static com.atsuishio.superbwarfare.entity.vehicle.base.VehicleEntity.HEAT; +import static com.atsuishio.superbwarfare.event.ClientEventHandler.zoomVehicle; @OnlyIn(Dist.CLIENT) public class AircraftOverlay implements LayeredDraw.Layer { @@ -45,6 +46,8 @@ public class AircraftOverlay implements LayeredDraw.Layer { private static float lerpLock = 1; private static float lerpG = 1; + private static float scopeScale = 1; + private static final ResourceLocation FRAME = Mod.loc("textures/screens/aircraft/frame.png"); private static final ResourceLocation FRAME_TARGET = Mod.loc("textures/screens/aircraft/frame_target.png"); private static final ResourceLocation FRAME_LOCK = Mod.loc("textures/screens/aircraft/frame_lock.png"); @@ -60,7 +63,6 @@ public class AircraftOverlay implements LayeredDraw.Layer { Camera camera = mc.gameRenderer.getMainCamera(); Vec3 cameraPos = camera.getPosition(); PoseStack poseStack = guiGraphics.pose(); - Vec3 lookVec = new Vec3(camera.getLookVector()); var partialTick = deltaTracker.getGameTimeDeltaPartialTick(true); @@ -89,6 +91,42 @@ public class AircraftOverlay implements LayeredDraw.Layer { Vec3 p = VectorUtil.worldToScreen(pos, cameraPos); Vec3 pCross = VectorUtil.worldToScreen(posCross, cameraPos); + // 投弹准星 + if (mobileVehicle instanceof A10Entity a10Entity && weaponVehicle.getWeaponIndex(0) == 2 && (zoomVehicle || mc.options.getCameraType() != CameraType.FIRST_PERSON)) { + Vec3 p0 = a10Entity.bombLandingPosO; + Vec3 p1 = a10Entity.bombLandingPos; + if (p0 != null && p1 != null) { + Vec3 bombCross = p0.lerp(p1, partialTick); + pCross = VectorUtil.worldToScreen(bombCross, cameraPos); + + if (pCross != null && zoomVehicle) { + float f = (float) Math.min(screenWidth, screenHeight); + float f1 = Math.min((float) screenWidth / f, (float) screenHeight / f); + int i = Mth.floor(f * f1); + int j = Mth.floor(f * f1); + + float x = (float) pCross.x; + float y = (float) pCross.y; + poseStack.pushPose(); + poseStack.rotateAround(Axis.ZP.rotationDegrees(aircraftEntity.getRotZ(partialTick)), x, y, 0); + poseStack.pushPose(); + + poseStack.pushPose(); + poseStack.translate(x, y, 0); + guiGraphics.drawString(Minecraft.getInstance().font, Component.literal("MK82 BOMB " + aircraftEntity.getAmmoCount(player)), 25, -11, 1, false); + poseStack.popPose(); + + preciseBlit(guiGraphics, Mod.loc("textures/screens/aircraft/bomb_scope.png"), x - 1.5f * i, y - 1.5f * j, 0, 0, 3 * i, 3 * j, 3 * i, 3 * j); + preciseBlit(guiGraphics, Mod.loc("textures/screens/aircraft/bomb_scope_pitch.png"), x - 1.5f * i, y - 1.5f * j - 4 * a10Entity.getPitch(partialTick), 0, 0, 3 * i, 3 * j, 3 * i, 3 * j); + renderKillIndicator(guiGraphics, x - 7.5f + (float) (2 * (Math.random() - 0.5f)), y - 7.5f + (float) (2 * (Math.random() - 0.5f))); + poseStack.popPose(); + poseStack.popPose(); + return; + } + } + + } + if (p != null) { poseStack.pushPose(); float x = (float) p.x; @@ -226,14 +264,13 @@ public class AircraftOverlay implements LayeredDraw.Layer { } else if (mc.options.getCameraType() == CameraType.THIRD_PERSON_BACK) { poseStack.pushPose(); poseStack.rotateAround(Axis.ZP.rotationDegrees(aircraftEntity.getRotZ(partialTick)), x, y, 0); - preciseBlit(guiGraphics, Mod.loc("textures/screens/drone.png"), x - 8, y - 8, 0, 0, 16, 16, 16, 16); - renderKillIndicator(guiGraphics, x - 7.5f + (float) (2 * (Math.random() - 0.5f)), y - 7.5f + (float) (2 * (Math.random() - 0.5f))); - poseStack.pushPose(); - poseStack.translate(x, y, 0); poseStack.scale(0.75f, 0.75f, 1); + ResourceLocation cross = Mod.loc("textures/screens/drone.png"); + float size = 16; + if (mobileVehicle instanceof A10Entity a10Entity) { if (weaponVehicle.getWeaponIndex(0) == 0) { double heat = a10Entity.getEntityData().get(HEAT) / 100.0F; @@ -241,6 +278,8 @@ public class AircraftOverlay implements LayeredDraw.Layer { } else if (weaponVehicle.getWeaponIndex(0) == 1) { guiGraphics.drawString(Minecraft.getInstance().font, Component.literal("70MM ROCKET " + aircraftEntity.getAmmoCount(player)), 25, -9, -1, false); } else if (weaponVehicle.getWeaponIndex(0) == 2) { + cross = Mod.loc("textures/screens/shotgun_hud.png"); + size = 24; guiGraphics.drawString(Minecraft.getInstance().font, Component.literal("MK82 BOMB " + aircraftEntity.getAmmoCount(player)), 25, -9, -1, false); } else if (weaponVehicle.getWeaponIndex(0) == 3) { guiGraphics.drawString(Minecraft.getInstance().font, Component.literal("AGM-65 " + aircraftEntity.getAmmoCount(player)), 25, -9, -1, false); @@ -249,6 +288,8 @@ public class AircraftOverlay implements LayeredDraw.Layer { guiGraphics.drawString(Minecraft.getInstance().font, Component.literal("IR FLARES " + aircraftEntity.getDecoy()), 25, 1, -1, false); poseStack.popPose(); + preciseBlit(guiGraphics, cross, x - 0.5f * size, y - 0.5f * size, 0, 0, size, size, size, size); + renderKillIndicator(guiGraphics, x - 7.5f + (float) (2 * (Math.random() - 0.5f)), y - 7.5f + (float) (2 * (Math.random() - 0.5f))); poseStack.popPose(); } poseStack.popPose(); diff --git a/src/main/java/com/atsuishio/superbwarfare/client/renderer/entity/A10Renderer.java b/src/main/java/com/atsuishio/superbwarfare/client/renderer/entity/A10Renderer.java index 882066a6d..b59645ff8 100644 --- a/src/main/java/com/atsuishio/superbwarfare/client/renderer/entity/A10Renderer.java +++ b/src/main/java/com/atsuishio/superbwarfare/client/renderer/entity/A10Renderer.java @@ -2,14 +2,17 @@ package com.atsuishio.superbwarfare.client.renderer.entity; import com.atsuishio.superbwarfare.client.model.entity.A10Model; import com.atsuishio.superbwarfare.entity.vehicle.A10Entity; +import com.atsuishio.superbwarfare.event.ClientEventHandler; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.Axis; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.NotNull; import software.bernie.geckolib.cache.object.BakedGeoModel; @@ -54,6 +57,10 @@ public class A10Renderer extends GeoEntityRenderer { @Override public void renderRecursively(PoseStack poseStack, A10Entity animatable, GeoBone bone, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int color) { String name = bone.getName(); + if (name.equals("root")) { + Player player = Minecraft.getInstance().player; + bone.setHidden(ClientEventHandler.zoomVehicle && animatable.getFirstPassenger() == player && animatable.getWeaponIndex(0) == 2); + } if (name.equals("wingLR")) { bone.setRotX(1.5f * Mth.lerp(partialTick, animatable.flap1LRotO, animatable.getFlap1LRot()) * Mth.DEG_TO_RAD); } diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/A10Entity.java b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/A10Entity.java index 7df9854f4..455904f88 100644 --- a/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/A10Entity.java +++ b/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/A10Entity.java @@ -7,7 +7,6 @@ import com.atsuishio.superbwarfare.entity.OBBEntity; import com.atsuishio.superbwarfare.entity.vehicle.base.*; import com.atsuishio.superbwarfare.entity.vehicle.damage.DamageModifier; import com.atsuishio.superbwarfare.entity.vehicle.weapon.*; -import com.atsuishio.superbwarfare.event.ClientEventHandler; import com.atsuishio.superbwarfare.event.ClientMouseHandler; import com.atsuishio.superbwarfare.init.ModDamageTypes; import com.atsuishio.superbwarfare.init.ModItems; @@ -17,6 +16,8 @@ import com.atsuishio.superbwarfare.network.message.receive.ShakeClientMessage; import com.atsuishio.superbwarfare.tools.*; import com.mojang.math.Axis; import it.unimi.dsi.fastutil.Pair; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; @@ -58,6 +59,7 @@ import software.bernie.geckolib.util.GeckoLibUtil; import java.util.List; import java.util.function.Consumer; +import static com.atsuishio.superbwarfare.event.ClientEventHandler.zoomVehicle; import static com.atsuishio.superbwarfare.event.ClientMouseHandler.freeCameraPitch; import static com.atsuishio.superbwarfare.event.ClientMouseHandler.freeCameraYaw; import static com.atsuishio.superbwarfare.tools.ParticleTool.sendParticle; @@ -84,6 +86,10 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity private boolean wasFiring = false; public float delta_x; public float delta_y; + public Vec3 bombLandingPosO; + public Vec3 bombLandingPos; + public Vec3 deltaMovementO; + public OBB obb; public OBB obb2; public OBB obb3; @@ -222,6 +228,8 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity this.wasFiring = this.isFiring(); this.lockingTargetO = getTargetUuid(); + bombLandingPosO = bombLandingPos; + deltaMovementO = getDeltaMovement(); super.baseTick(); this.updateOBB(); @@ -277,6 +285,12 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity lowHealthWarning(); releaseDecoy(); + + // 计算航弹落点 + if (level().isClientSide) { + bombLandingPos = ProjectileCalculator.calculateImpactPosition(level(), shootPos(1), shootVec(1), -0.06).getCenter(); + } + this.refreshDimensions(); } @@ -734,8 +748,10 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity worldPosition = transformPosition(transform, 0.1321625f, -0.56446875f, 7.85210625f); } else if (getWeaponIndex(0) == 1) { worldPosition = transformPosition(transform, 0f, -1.443f, 0.13f); - } else { + } else if (getWeaponIndex(0) == 2) { worldPosition = transformPosition(transform, 0f, -1.203125f, 0.0625f); + } else { + worldPosition = transformPosition(transform, 0, -1.55f, 1.83f); } return new Vec3(worldPosition.x, worldPosition.y, worldPosition.z); } @@ -745,7 +761,9 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity Matrix4f transform = getVehicleTransform(tickDelta); Vector4f worldPosition; Vector4f worldPosition2; - if (getWeaponIndex(0) == 3) { + if (getWeaponIndex(0) == 2) { + return deltaMovementO.lerp(getDeltaMovement(), tickDelta).scale(0.75); + } else if (getWeaponIndex(0) == 3) { worldPosition = transformPosition(transform, 0, 0, 0); worldPosition2 = transformPosition(transform, 0, 0f, 1); } else { @@ -849,7 +867,7 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity } Mk82Entity.setPos(worldPosition.x, worldPosition.y, worldPosition.z); - Mk82Entity.shoot(getDeltaMovement().x, getDeltaMovement().y, getDeltaMovement().z, (float) getDeltaMovement().length(), 10); + Mk82Entity.shoot(getDeltaMovement().x, getDeltaMovement().y, getDeltaMovement().z, (float) getDeltaMovement().scale(0.75).length(), 0.5f); player.level().addFreshEntity(Mk82Entity); BlockPos pos = BlockPos.containing(new Vec3(worldPosition.x, worldPosition.y, worldPosition.z)); @@ -982,7 +1000,7 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity @Override public double getMouseSensitivity() { - return ClientEventHandler.zoomVehicle ? 0.03 : 0.07; + return zoomVehicle ? 0.03 : 0.07; } @Override @@ -1018,7 +1036,21 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity @OnlyIn(Dist.CLIENT) @Override public @Nullable Vec2 getCameraRotation(float partialTicks, Player player, boolean zoom, boolean isFirstPerson) { + Minecraft mc = Minecraft.getInstance(); + Camera camera = mc.gameRenderer.getMainCamera(); + Vec3 cameraPos = camera.getPosition(); + + Vec3 p0 = bombLandingPosO; + Vec3 p1 = bombLandingPos; + Vec3 p2 = getViewVector(partialTicks); + if (p0 != null && p1 != null) { + p2 = cameraPos.vectorTo(p0.lerp(p1, partialTicks)); + } + if (this.getSeatIndex(player) == 0) { + if (getWeaponIndex(0) == 2 && zoomVehicle) { + return new Vec2((float) (-getYRotFromVector(p2) - freeCameraYaw), (float) (-getXRotFromVector(p2) + freeCameraPitch)); + } return new Vec2((float) (getRotY(partialTicks) - freeCameraYaw), (float) (getRotX(partialTicks) + freeCameraPitch)); } @@ -1029,8 +1061,12 @@ public class A10Entity extends ContainerMobileVehicleEntity implements GeoEntity @Override public Vec3 getCameraPosition(float partialTicks, Player player, boolean zoom, boolean isFirstPerson) { if (this.getSeatIndex(player) == 0) { - Matrix4f transform = getClientVehicleTransform(partialTicks); + if (getWeaponIndex(0) == 2 && zoomVehicle) { + return shootPos(partialTicks); + } + + Matrix4f transform = getClientVehicleTransform(partialTicks); Vector4f maxCameraPosition = transformPosition(transform, 0, 4, -14 - (float) ClientMouseHandler.custom3pDistanceLerp); Vec3 finalPos = CameraTool.getMaxZoom(transform, maxCameraPosition); diff --git a/src/main/java/com/atsuishio/superbwarfare/tools/ProjectileCalculator.java b/src/main/java/com/atsuishio/superbwarfare/tools/ProjectileCalculator.java new file mode 100644 index 000000000..35ec8fba1 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/tools/ProjectileCalculator.java @@ -0,0 +1,146 @@ +package com.atsuishio.superbwarfare.tools; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; + +public class ProjectileCalculator { + private static final double TIME_STEP = 0.1; // 时间步长(刻) + private static final int MAX_ITERATIONS = 1000; // 最大迭代次数 + + /** + * 计算炮弹落地位置 + * + * @param world 世界对象 + * @param startPos 发射点位置 + * @param launchVector 发射向量 + * @return 预测的落点方块位置 + */ + public static BlockPos calculateImpactPosition(Level world, Vec3 startPos, Vec3 launchVector, double gravity) { + // 当前炮弹位置和速度 + Vec3 currentPos = startPos; + Vec3 currentVelocity = launchVector; + + // 记录上一刻位置 + Vec3 prevPos = startPos; + + for (int i = 0; i < MAX_ITERATIONS; i++) { + // 更新位置 + Vec3 nextPos = currentPos.add( + currentVelocity.x * TIME_STEP, + currentVelocity.y * TIME_STEP, + currentVelocity.z * TIME_STEP + ); + + // 更新速度(重力影响) + currentVelocity = currentVelocity.add(0, gravity * TIME_STEP, 0); + + // 检查是否碰撞方块 + BlockPos collisionPos = checkCollision(world, prevPos, nextPos); + if (collisionPos != null) { + return collisionPos; + } + + // 更新位置进行下一步 + prevPos = currentPos; + currentPos = nextPos; + + // 安全检查:防止飞出世界边界 + if (currentPos.y < world.getMinBuildHeight() || currentPos.y > world.getMaxBuildHeight()) { + return new BlockPos( + (int)Math.floor(currentPos.x), + (int)Math.floor(currentPos.y), + (int)Math.floor(currentPos.z) + ); + } + } + + // 超过最大迭代次数,返回当前位置 + return new BlockPos( + (int)Math.floor(currentPos.x), + (int)Math.floor(currentPos.y), + (int)Math.floor(currentPos.z) + ); + } + + /** + * 检查两点之间是否有碰撞方块 + */ + private static BlockPos checkCollision(Level world, Vec3 start, Vec3 end) { + // 使用距离和方向向量 + double dx = end.x - start.x; + double dy = end.y - start.y; + double dz = end.z - start.z; + double distance = Math.sqrt(dx*dx + dy*dy + dz*dz); + + if (distance == 0) return null; + + // 方向单位向量 + double dirX = dx / distance; + double dirY = dy / distance; + double dirZ = dz / distance; + + // 步进检查 + double stepSize = 0.1; // 检查步长 + for (double t = 0; t < distance; t += stepSize) { + double x = start.x + dirX * t; + double y = start.y + dirY * t; + double z = start.z + dirZ * t; + + BlockPos pos = new BlockPos((int)Math.floor(x), (int)Math.floor(y), (int)Math.floor(z)); + BlockState state = world.getBlockState(pos); + + // 检查是否碰到固体方块 + if (!state.isAir()) { + return pos; + } + + // 检查是否碰到下方方块(炮弹落地) + BlockPos belowPos = pos.below(); + BlockState belowState = world.getBlockState(belowPos); + + if (y - Math.floor(y) < 0.1 && !belowState.isAir()) { + return belowPos; + } + } + + return null; + } + + + /** + * 快速预测落点(不考虑地形,仅数学计算) + * 用于平坦地形或初始估算 + */ + public static Vec3 estimateLandingPosition(Vec3 startPos, Vec3 launchVector, double gravity) { + double vx = launchVector.x; + double vy = launchVector.y; + double vz = launchVector.z; + + // 计算飞行时间 (解二次方程: y = y0 + vy*t + 0.5*g*t² = 0) + double a = 0.5 * gravity; + double b = vy; + double c = startPos.y; // 假设地面高度为0 + + // 计算判别式 + double discriminant = b*b - 4*a*c; + + if (discriminant < 0) { + // 无实数解,炮弹不会落地 + return null; + } + + // 取正数解 + double t = (-b + Math.sqrt(discriminant)) / (2*a); + if (t < 0) { + t = (-b - Math.sqrt(discriminant)) / (2*a); + } + + // 计算落点 + double x = startPos.x + vx * t; + double z = startPos.z + vz * t; + + return new Vec3(x, 0, z); + } +} diff --git a/src/main/resources/assets/superbwarfare/textures/screens/aircraft/bomb_scope.png b/src/main/resources/assets/superbwarfare/textures/screens/aircraft/bomb_scope.png new file mode 100644 index 000000000..482fe9df6 Binary files /dev/null and b/src/main/resources/assets/superbwarfare/textures/screens/aircraft/bomb_scope.png differ diff --git a/src/main/resources/assets/superbwarfare/textures/screens/aircraft/bomb_scope_pitch.png b/src/main/resources/assets/superbwarfare/textures/screens/aircraft/bomb_scope_pitch.png new file mode 100644 index 000000000..420cce7e0 Binary files /dev/null and b/src/main/resources/assets/superbwarfare/textures/screens/aircraft/bomb_scope_pitch.png differ