package com.atsuishio.superbwarfare.entity.vehicle; import com.atsuishio.superbwarfare.ModUtils; import com.atsuishio.superbwarfare.entity.vehicle.damage.DamageModifier; import com.atsuishio.superbwarfare.init.ModDamageTypes; import com.atsuishio.superbwarfare.init.ModItems; import com.atsuishio.superbwarfare.init.ModParticleTypes; import com.atsuishio.superbwarfare.init.ModSounds; import com.atsuishio.superbwarfare.item.ContainerBlockItem; import com.atsuishio.superbwarfare.network.message.ClientIndicatorMessage; import com.atsuishio.superbwarfare.tools.EntityFindUtil; import com.atsuishio.superbwarfare.tools.ParticleTool; import com.atsuishio.superbwarfare.tools.VectorTool; import com.google.common.collect.Lists; import com.mojang.math.Axis; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundSoundPacket; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageTypes; import net.minecraft.world.entity.*; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.ThrownPotion; import net.minecraft.world.entity.vehicle.DismountHelper; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.fluids.FluidType; import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; import org.joml.Math; import org.joml.Matrix4f; import org.joml.Vector4f; import java.util.ArrayList; import java.util.List; public class VehicleEntity extends Entity { public static final EntityDataAccessor HEALTH = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.FLOAT); protected static final EntityDataAccessor LAST_ATTACKER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING); protected static final EntityDataAccessor LAST_DRIVER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING); protected int interpolationSteps; protected double x; protected double y; protected double z; protected double serverYRot; protected double serverXRot; public float roll; public float prevRoll; public int lastHurtTick; public int repairCoolDown; public boolean crash; public float getRoll() { return roll; } public float getRoll(float tickDelta) { return Mth.lerp(tickDelta, prevRoll, getRoll()); } public float getYaw(float tickDelta) { return Mth.lerp(tickDelta, yRotO, getYRot()); } public float getPitch(float tickDelta) { return Mth.lerp(tickDelta, xRotO, getXRot()); } public void setZRot(float rot) { roll = rot; } public VehicleEntity(EntityType pEntityType, Level pLevel) { super(pEntityType, pLevel); this.setHealth(this.getMaxHealth()); } @Override protected void defineSynchedData() { this.entityData.define(HEALTH, this.getMaxHealth()); this.entityData.define(LAST_ATTACKER_UUID, "undefined"); this.entityData.define(LAST_DRIVER_UUID, "undefined"); } @Override protected void readAdditionalSaveData(CompoundTag compound) { this.entityData.set(LAST_ATTACKER_UUID, compound.getString("LastAttacker")); this.entityData.set(LAST_DRIVER_UUID, compound.getString("LastDriver")); this.entityData.set(HEALTH, compound.getFloat("Health")); } @Override public void addAdditionalSaveData(CompoundTag compound) { compound.putFloat("Health", this.entityData.get(HEALTH)); compound.putString("LastAttacker", this.entityData.get(LAST_ATTACKER_UUID)); compound.putString("LastDriver", this.entityData.get(LAST_DRIVER_UUID)); } @Override public InteractionResult interact(Player player, InteractionHand hand) { if (player.getVehicle() == this) return InteractionResult.PASS; ItemStack stack = player.getMainHandItem(); if (player.isShiftKeyDown() && stack.is(ModItems.CROWBAR.get()) && this.getFirstPassenger() == null) { ItemStack container = ContainerBlockItem.createInstance(this); if (!player.addItem(container)) { player.drop(container, false); } this.remove(RemovalReason.DISCARDED); this.discard(); return InteractionResult.SUCCESS; } else if (this.getHealth() < this.getMaxHealth() && stack.is(Items.IRON_INGOT)) { this.heal(Math.min(50, this.getMaxHealth())); stack.shrink(1); if (!this.level().isClientSide) { this.level().playSound(null, this, SoundEvents.IRON_GOLEM_REPAIR, this.getSoundSource(), 0.5f, 1); } return InteractionResult.SUCCESS; } else if (!player.isShiftKeyDown()) { if (this.getFirstPassenger() == null) { player.setXRot(this.getXRot()); player.setYRot(this.getYRot()); return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS; } else if (!(this.getFirstPassenger() instanceof Player)) { this.getFirstPassenger().stopRiding(); player.setXRot(this.getXRot()); player.setYRot(this.getYRot()); return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS; } if (this.canAddPassenger(player)) { return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS; } } return InteractionResult.PASS; } @Override public boolean hurt(DamageSource source, float amount) { this.crash = source.is(ModDamageTypes.VEHICLE_STRIKE); if (source.getEntity() != null) { this.entityData.set(LAST_ATTACKER_UUID, source.getEntity().getStringUUID()); } if (amount > 0) { lastHurtTick = 0; repairCoolDown = 200; } return super.hurt(source, amount); } protected final DamageModifier damageModifier = this.getDamageModifier(); /** * 控制载具伤害免疫 * * @return DamageModifier */ public DamageModifier getDamageModifier() { return new DamageModifier() .immuneTo(source -> source.getDirectEntity() instanceof ThrownPotion || source.getDirectEntity() instanceof AreaEffectCloud) .immuneTo(DamageTypes.FALL) .immuneTo(DamageTypes.CACTUS) .immuneTo(DamageTypes.DROWN) .immuneTo(DamageTypes.DRAGON_BREATH) .immuneTo(DamageTypes.WITHER) .immuneTo(DamageTypes.WITHER_SKULL) .reduce(8, ModDamageTypes.VEHICLE_STRIKE); } public void heal(float pHealAmount) { if (this.level() instanceof ServerLevel) { this.setHealth(this.getHealth() + pHealAmount); } } public void hurt(float pHealAmount, Entity attacker, boolean send) { if (this.level() instanceof ServerLevel) { var holder = Holder.direct(ModSounds.INDICATION_VEHICLE.get()); if (attacker instanceof ServerPlayer player && pHealAmount > 0 && this.getHealth() > 0 && send && !(this instanceof DroneEntity)) { player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getEyeY(), player.getZ(), 0.25f + (2.75f * pHealAmount / getMaxHealth()), random.nextFloat() * 0.1f + 0.9f, player.level().random.nextLong())); ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(3, 5)); } if (pHealAmount > 0 && this.getHealth() > 0 && send) { List passengers = this.getPassengers(); for (var entity : passengers) { if (entity instanceof ServerPlayer player1) { player1.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player1.getX(), player1.getEyeY(), player1.getZ(), 0.25f + (4.75f * pHealAmount / getMaxHealth()), random.nextFloat() * 0.1f + 0.6f, player1.level().random.nextLong())); } } } this.setHealth(this.getHealth() - pHealAmount); } } public float getHealth() { return this.entityData.get(HEALTH); } public void setHealth(float pHealth) { this.entityData.set(HEALTH, Mth.clamp(pHealth, 0.0F, this.getMaxHealth())); } public float getMaxHealth() { return 50; } @Override public boolean canBeCollidedWith() { return true; } @Override public boolean isPushable() { return super.isPushable(); } @Override public boolean isPickable() { return !this.isRemoved(); } @Override public boolean skipAttackInteraction(@NotNull Entity attacker) { return hasPassenger(attacker) || super.skipAttackInteraction(attacker); } @Override protected boolean canAddPassenger(Entity pPassenger) { return this.getPassengers().size() < this.getMaxPassengers(); } public int getMaxPassengers() { return 1; } public double getSubmergedHeight(Entity entity) { for (FluidType fluidType : ForgeRegistries.FLUID_TYPES.get().getValues()) { if (entity.level().getFluidState(entity.blockPosition()).getFluidType() == fluidType) return entity.getFluidTypeHeight(fluidType); } return 0; } @Override public void baseTick() { super.baseTick(); this.lastHurtTick++; if (repairCoolDown > 0) { repairCoolDown--; } this.prevRoll = this.getRoll(); float delta = Math.abs(getYRot() - yRotO); while (getYRot() > 180F) { setYRot(getYRot() - 360F); yRotO = getYRot() - delta; } while (getYRot() <= -180F) { setYRot(getYRot() + 360F); yRotO = delta + getYRot(); } float deltaZ = Math.abs(getRoll() - prevRoll); while (getRoll() > 180F) { setZRot(getRoll() - 360F); prevRoll = getRoll() - deltaZ; } while (getRoll() <= -180F) { setZRot(getRoll() + 360F); prevRoll = deltaZ + getRoll(); } handleClientSync(); if (this.level() instanceof ServerLevel && this.getHealth() <= 0) { destroy(); } travel(); Entity attacker = EntityFindUtil.findEntity(this.level(), this.entityData.get(LAST_ATTACKER_UUID)); if (this.getHealth() <= 0.1 * this.getMaxHealth()) { this.hurt(0.1f, attacker, false); } else { if (!(this instanceof DroneEntity) && repairCoolDown == 0) { this.heal(0.05f); } } if (getFirstPassenger() != null) { this.entityData.set(LAST_DRIVER_UUID, getFirstPassenger().getStringUUID()); } this.refreshDimensions(); } public void lowHealthWarning() { if (this.getHealth() <= 0.4 * this.getMaxHealth()) { if (this.level() instanceof ServerLevel serverLevel) { ParticleTool.sendParticle(serverLevel, ParticleTypes.LARGE_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 2, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); } } if (this.getHealth() <= 0.25 * this.getMaxHealth()) { if (this.level() instanceof ServerLevel serverLevel) { ParticleTool.sendParticle(serverLevel, ParticleTypes.LARGE_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 1, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); ParticleTool.sendParticle(serverLevel, ParticleTypes.CAMPFIRE_COSY_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 1, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); } } if (this.getHealth() <= 0.15 * this.getMaxHealth()) { if (this.level() instanceof ServerLevel serverLevel) { ParticleTool.sendParticle(serverLevel, ParticleTypes.LARGE_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 1, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); ParticleTool.sendParticle(serverLevel, ParticleTypes.CAMPFIRE_COSY_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 1, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); } } if (this.getHealth() <= 0.1 * this.getMaxHealth()) { if (this.level() instanceof ServerLevel serverLevel) { ParticleTool.sendParticle(serverLevel, ParticleTypes.LARGE_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 2, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); ParticleTool.sendParticle(serverLevel, ParticleTypes.CAMPFIRE_COSY_SMOKE, this.getX(), this.getY() + 0.7f * getBbHeight(), this.getZ(), 2, 0.35 * this.getBbWidth(), 0.15 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.01, true); ParticleTool.sendParticle(serverLevel, ParticleTypes.FLAME, this.getX(), this.getY() + 0.85f * getBbHeight(), this.getZ(), 4, 0.35 * this.getBbWidth(), 0.12 * this.getBbHeight(), 0.35 * this.getBbWidth(), 0.05, true); ParticleTool.sendParticle(serverLevel, ModParticleTypes.FIRE_STAR.get(), this.getX(), this.getY() + 0.85f * getBbHeight(), this.getZ(), 4, 0.1 * this.getBbWidth(), 0.05 * this.getBbHeight(), 0.1 * this.getBbWidth(), 0.4, true); } if (this.tickCount % 15 == 0) { this.level().playSound(null, this.getOnPos(), SoundEvents.FIRE_AMBIENT, SoundSource.PLAYERS, 1, 1); } } if (this.getHealth() < 0.1f * this.getMaxHealth() && tickCount % 13 == 0) { this.level().playSound(null, this.getOnPos(), ModSounds.NO_HEALTH.get(), SoundSource.PLAYERS, 1, 1); } else if (this.getHealth() >= 0.1f && this.getHealth() < 0.4f * this.getMaxHealth() && tickCount % 10 == 0) { this.level().playSound(null, this.getOnPos(), ModSounds.LOW_HEALTH.get(), SoundSource.PLAYERS, 1, 1); } } public void destroy() { } public void travel() { } // From Immersive_Aircraft public Matrix4f getVehicleTransform() { Matrix4f transform = new Matrix4f(); transform.translate((float) getX(), (float) getY(), (float) getZ()); transform.rotate(Axis.YP.rotationDegrees(-getYRot())); transform.rotate(Axis.XP.rotationDegrees(getXRot())); transform.rotate(Axis.ZP.rotationDegrees(getRoll())); return transform; } public Vector4f transformPosition(Matrix4f transform, float x, float y, float z) { return transform.transform(new Vector4f(x, y, z, 1)); } public void handleClientSync() { if (isControlledByLocalInstance()) { interpolationSteps = 0; syncPacketPositionCodec(getX(), getY(), getZ()); } if (interpolationSteps <= 0) { return; } double interpolatedX = getX() + (x - getX()) / (double) interpolationSteps; double interpolatedY = getY() + (y - getY()) / (double) interpolationSteps; double interpolatedZ = getZ() + (z - getZ()) / (double) interpolationSteps; double interpolatedYaw = Mth.wrapDegrees(serverYRot - (double) getYRot()); setYRot(getYRot() + (float) interpolatedYaw / (float) interpolationSteps); setXRot(getXRot() + (float) (serverXRot - (double) getXRot()) / (float) interpolationSteps); setPos(interpolatedX, interpolatedY, interpolatedZ); setRot(getYRot(), getXRot()); --interpolationSteps; } @Override public void lerpTo(double x, double y, double z, float yaw, float pitch, int interpolationSteps, boolean interpolate) { this.x = x; this.y = y; this.z = z; serverYRot = yaw; serverXRot = pitch; this.interpolationSteps = 10; } public static double calculateAngle(Vec3 move, Vec3 view) { move = move.multiply(1, 0, 1).normalize(); view = view.multiply(1, 0, 1).normalize(); return VectorTool.calculateAngle(move, view); } protected Vec3 getDismountOffset(double vehicleWidth, double passengerWidth) { double offset = (vehicleWidth + passengerWidth + (double) 1.0E-5f) / 2.0; float yaw = getYRot() + 90.0f; float x = -Mth.sin(yaw * ((float) java.lang.Math.PI / 180)); float z = Mth.cos(yaw * ((float) java.lang.Math.PI / 180)); float n = java.lang.Math.max(java.lang.Math.abs(x), java.lang.Math.abs(z)); return new Vec3((double) x * offset / (double) n, 0.0, (double) z * offset / (double) n); } @Override public Vec3 getDismountLocationForPassenger(LivingEntity passenger) { Vec3 vec3d = getDismountOffset(getBbWidth() * Mth.SQRT_OF_TWO, passenger.getBbWidth() * Mth.SQRT_OF_TWO); double ox = getX() - vec3d.x; double oz = getZ() + vec3d.z; BlockPos exitPos = new BlockPos((int) ox, (int) getY(), (int) oz); BlockPos floorPos = exitPos.below(); if (!level().isWaterAt(floorPos)) { ArrayList list = Lists.newArrayList(); double exitHeight = level().getBlockFloorHeight(exitPos); if (DismountHelper.isBlockFloorValid(exitHeight)) { list.add(new Vec3(ox, (double) exitPos.getY() + exitHeight, oz)); } double floorHeight = level().getBlockFloorHeight(floorPos); if (DismountHelper.isBlockFloorValid(floorHeight)) { list.add(new Vec3(ox, (double) floorPos.getY() + floorHeight, oz)); } for (Pose entityPose : passenger.getDismountPoses()) { for (Vec3 vec3d2 : list) { if (!DismountHelper.canDismountTo(level(), vec3d2, passenger, entityPose)) continue; passenger.setPose(entityPose); return vec3d2; } } } return super.getDismountLocationForPassenger(passenger); } public ResourceLocation getVehicleIcon() { return ModUtils.loc("textures/gun_icon/default_icon.png"); } public boolean allowFreeCam() { return false; } }