superb-warfare/src/main/java/com/atsuishio/superbwarfare/entity/vehicle/VehicleEntity.java

520 lines
21 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
import static com.atsuishio.superbwarfare.tools.ParticleTool.sendParticle;
public class VehicleEntity extends Entity {
public static final EntityDataAccessor<Float> HEALTH = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.FLOAT);
protected static final EntityDataAccessor<String> LAST_ATTACKER_UUID = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.STRING);
protected static final EntityDataAccessor<String> 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 = maxRepairCoolDown();
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(@NotNull DamageSource source, float amount) {
// 计算减伤后的伤害
float computedAmount = damageModifier.compute(source, amount);
this.crash = source.is(ModDamageTypes.VEHICLE_STRIKE);
if (source.getEntity() != null) {
this.entityData.set(LAST_ATTACKER_UUID, source.getEntity().getStringUUID());
}
// 受伤打断呼吸回血
if (computedAmount > 0) {
lastHurtTick = 0;
repairCoolDown = maxRepairCoolDown();
}
this.onHurt(computedAmount, source.getEntity(), true);
// 显示火花粒子效果
if (this.sendFireStarParticleOnHurt() && this.level() instanceof ServerLevel serverLevel) {
sendParticle(serverLevel, ModParticleTypes.FIRE_STAR.get(), this.getX(), this.getY() + 0.5 * getBbHeight(), this.getZ(), 2, 0.4, 0.4, 0.4, 0.2, false);
}
// 播放受击音效
if (this.playHitSoundOnHurt()) {
this.level().playSound(null, this.getOnPos(), ModSounds.HIT.get(), SoundSource.PLAYERS, 1, 1);
}
return super.hurt(source, computedAmount);
}
/**
* 受击时是否显示火花粒子效果
*/
public boolean sendFireStarParticleOnHurt() {
return true;
}
/**
* 受击时是否播放受击音效
*/
public boolean playHitSoundOnHurt() {
return true;
}
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 onHurt(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<Entity> 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;
}
/**
* 呼吸回血冷却时长(单位:tick)设为小于0的值以禁用呼吸回血
*/
public int maxRepairCoolDown() {
return 200;
}
/**
* 呼吸回血回血量
*/
public float repairAmount() {
return 0.05F;
}
@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.onHurt(0.1f, attacker, false);
} else {
// 呼吸回血
if (repairCoolDown == 0) {
this.heal(repairAmount());
}
}
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<Vec3> 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;
}
// 本方法留空
@Override
public void push(double pX, double pY, double pZ) {
}
}